@@ -13,10 +13,188 @@ var log = bunyan.createLogger({ name: 'token_detail',
13
13
log . info ( "Log initialized. logLevel=" + log . level ( ) ) ;
14
14
const jwt = require ( 'jsonwebtoken' ) ;
15
15
16
+ function getParameterByName ( name , url )
17
+ {
18
+ log . debug ( "Entering getParameterByName()." ) ;
19
+ if ( ! url )
20
+ {
21
+ url = window . location . search ;
22
+ }
23
+ var urlParams = new URLSearchParams ( url ) ;
24
+ return urlParams . get ( name ) ;
25
+ }
26
+
16
27
function decodeJWT ( jwt_ ) {
17
28
return jwt . decode ( jwt_ , { complete : true } ) ;
18
29
}
19
30
31
+ async function verifyJWT ( ) {
32
+ var type = getParameterByName ( 'type' ) ;
33
+ var jwt_verification_type = document . getElementById ( "jwt_verification_type" ) . value ;
34
+ var jwt_verification_key = document . getElementById ( "jwt_verification_key" ) . value ;
35
+ var jwt_ = "" ;
36
+ if ( type == 'access' ) {
37
+ jwt_ = localStorage . getItem ( "token_access_token" ) ;
38
+ } else if ( type == 'refresh' ) {
39
+ jwt_ = localStorage . getItem ( "token_refresh_token" ) ;
40
+ } else if ( type == 'id' ) {
41
+ jwt_ = localStorage . getItem ( "token_id_token" ) ;
42
+ } else if ( type == 'refresh_access' ) {
43
+ jwt_ = localStorage . getItem ( "refresh_access_token" ) ;
44
+ } else if ( type == 'refresh_refresh' ) {
45
+ jwt_ = localStorage ( "refresh_refresh_token" ) ;
46
+ } else if ( type == 'refresh_id' ) {
47
+ jwt_ = localStorage . getItem ( 'refresh_id_token' ) ;
48
+ } else {
49
+ log . error ( 'Unknown token type encountered.' ) ;
50
+ }
51
+
52
+ try {
53
+ const [ headerB64 , payloadB64 , signatureB64 ] = jwt_ . split ( '.' ) ;
54
+ if ( ! headerB64 || ! payloadB64 || ! signatureB64 ) throw new Error ( 'Invalid JWT format.' ) ;
55
+
56
+ const header = JSON . parse ( atobUrl ( headerB64 ) ) ;
57
+ var isValid = false ;
58
+ if ( jwt_verification_type === 'hmac' ) {
59
+ isValid = await verifyHMAC ( jwt_ , jwt_verification_key , header . alg ) ;
60
+ } else if ( jwt_verification_type === 'x509' ) {
61
+ isValid = await verifyRSJWT ( jwt_ , jwt_verification_key , header . alg ) ;
62
+ } else if ( jwt_verification_type === 'jwks' ) {
63
+ isValid = await verifyWithJWKS ( jwt_ , JSON . parse ( jwt_verification_key ) ) ;
64
+ } else if ( jwt_verification_type === 'jwks_url' ) {
65
+ isValid = await verifyWithJWKS ( jwt_ , await fetchJWKS ( jwt_verification_key ) ) ;
66
+ } else {
67
+ throw new Error ( 'Unsupported verification method.' ) ;
68
+ }
69
+ } catch ( err ) {
70
+ log . error ( "Error while verifying JWT: " + err . message ) ;
71
+ }
72
+
73
+ document . getElementById ( 'jwt_verification_output' ) . value = "Signature Verified: " + isValid ;
74
+ }
75
+
76
+ function atobUrl ( input ) {
77
+ input = input . replace ( / - / g, '+' ) . replace ( / _ / g, '/' ) ;
78
+ const pad = input . length % 4 ? '=' . repeat ( 4 - ( input . length % 4 ) ) : '' ;
79
+ return atob ( input + pad ) ;
80
+ }
81
+
82
+ function base64UrlToUint8Array ( base64UrlString ) {
83
+ const binary = atobUrl ( base64UrlString ) ;
84
+ const len = binary . length ;
85
+ const bytes = new Uint8Array ( len ) ;
86
+ for ( let i = 0 ; i < len ; i ++ ) {
87
+ bytes [ i ] = binary . charCodeAt ( i ) ;
88
+ }
89
+ return bytes ;
90
+ }
91
+
92
+ async function verifyHMAC ( jwt , secret , alg = 'HS256' ) {
93
+ const encoder = new TextEncoder ( ) ;
94
+ const algoMap = { HS256 : 'SHA-256' , HS384 : 'SHA-384' , HS512 : 'SHA-512' } ;
95
+ const algo = algoMap [ alg ] ;
96
+ if ( ! algo ) throw new Error ( 'Unsupported HMAC algorithm: ' + alg ) ;
97
+
98
+ const key = await crypto . subtle . importKey (
99
+ 'raw' ,
100
+ encoder . encode ( secret ) ,
101
+ { name : 'HMAC' , hash : { name : algo } } ,
102
+ false ,
103
+ [ 'verify' ]
104
+ ) ;
105
+
106
+ const data = encoder . encode ( jwt . split ( '.' ) . slice ( 0 , 2 ) . join ( '.' ) ) ;
107
+ const signature = base64UrlToUint8Array ( jwt . split ( '.' ) [ 2 ] ) ;
108
+
109
+ return await crypto . subtle . verify ( 'HMAC' , key , signature , data ) ;
110
+ }
111
+
112
+ async function verifyRSJWT ( jwt , pem , alg = 'RS256' ) {
113
+ const algoMap = {
114
+ RS256 : 'SHA-256' ,
115
+ RS384 : 'SHA-384' ,
116
+ RS512 : 'SHA-512'
117
+ } ;
118
+ const algo = algoMap [ alg ] ;
119
+ if ( ! algo ) throw new Error ( 'Unsupported RSA algorithm: ' + alg ) ;
120
+
121
+ const keyData = pemToArrayBuffer ( pem ) ;
122
+ const key = await crypto . subtle . importKey (
123
+ 'spki' ,
124
+ keyData ,
125
+ { name : 'RSASSA-PKCS1-v1_5' , hash : { name : algo } } ,
126
+ false ,
127
+ [ 'verify' ]
128
+ ) ;
129
+
130
+ const encoder = new TextEncoder ( ) ;
131
+ const data = encoder . encode ( jwt . split ( '.' ) . slice ( 0 , 2 ) . join ( '.' ) ) ;
132
+ const signature = base64UrlToUint8Array ( jwt . split ( '.' ) [ 2 ] ) ;
133
+
134
+ return await crypto . subtle . verify ( 'RSASSA-PKCS1-v1_5' , key , signature , data ) ;
135
+ }
136
+
137
+ function pemToArrayBuffer ( pem ) {
138
+ const b64Lines = pem . replace ( / - - - - - [ ^ - ] + - - - - - / g, '' ) . replace ( / \s + / g, '' ) ;
139
+ const binary = atob ( b64Lines ) ;
140
+ const len = binary . length ;
141
+ const buffer = new ArrayBuffer ( len ) ;
142
+ const view = new Uint8Array ( buffer ) ;
143
+ for ( let i = 0 ; i < len ; i ++ ) {
144
+ view [ i ] = binary . charCodeAt ( i ) ;
145
+ }
146
+ return buffer ;
147
+ }
148
+
149
+ async function verifyWithJWKS ( jwt , jwks ) {
150
+ const header = JSON . parse ( atobUrl ( jwt . split ( '.' ) [ 0 ] ) ) ;
151
+ const kid = header . kid ;
152
+ if ( ! kid ) throw new Error ( 'No "kid" found in JWT header.' ) ;
153
+
154
+ const jwk = jwks . keys . find ( k => k . kid === kid ) ;
155
+ if ( ! jwk ) throw new Error ( 'Matching "kid" not found in JWKS.' ) ;
156
+
157
+ const algoMap = {
158
+ RS256 : 'SHA-256' ,
159
+ RS384 : 'SHA-384' ,
160
+ RS512 : 'SHA-512'
161
+ } ;
162
+
163
+ const algo = algoMap [ header . alg ] ;
164
+ if ( ! algo ) throw new Error ( 'Unsupported algorithm: ' + header . alg ) ;
165
+
166
+ const key = await importJWK ( jwk , algo ) ;
167
+ const encoder = new TextEncoder ( ) ;
168
+ const data = encoder . encode ( jwt . split ( '.' ) . slice ( 0 , 2 ) . join ( '.' ) ) ;
169
+ const signature = base64UrlToUint8Array ( jwt . split ( '.' ) [ 2 ] ) ;
170
+
171
+ return await crypto . subtle . verify ( 'RSASSA-PKCS1-v1_5' , key , signature , data ) ;
172
+ }
173
+
174
+ async function fetchJWKS ( url ) {
175
+ const response = await fetch ( url ) ;
176
+ if ( ! response . ok ) throw new Error ( 'Failed to fetch JWKS.' ) ;
177
+ return await response . json ( ) ;
178
+ }
179
+
180
+ async function importJWK ( jwk , hashAlgo ) {
181
+ if ( jwk . kty !== 'RSA' ) throw new Error ( 'Only RSA keys are supported in this example.' ) ;
182
+
183
+ const publicKey = {
184
+ kty : jwk . kty ,
185
+ n : jwk . n ,
186
+ e : jwk . e
187
+ } ;
188
+
189
+ return await crypto . subtle . importKey (
190
+ 'jwk' ,
191
+ publicKey ,
192
+ { name : 'RSASSA-PKCS1-v1_5' , hash : { name : hashAlgo } } ,
193
+ false ,
194
+ [ 'verify' ]
195
+ ) ;
196
+ }
197
+
20
198
window . onload = function ( ) {
21
199
log . debug ( "Entering onload function." ) ;
22
200
const type = getParameterByName ( 'type' ) ;
@@ -43,19 +221,7 @@ window.onload = function() {
43
221
document . getElementById ( 'jwt_payload' ) . value = JSON . stringify ( decodedJWT . payload , null , 2 ) ;
44
222
}
45
223
46
- function getParameterByName ( name , url )
47
- {
48
- log . debug ( "Entering getParameterByName()." ) ;
49
- if ( ! url )
50
- {
51
- url = window . location . search ;
52
- }
53
- var urlParams = new URLSearchParams ( url ) ;
54
- return urlParams . get ( name ) ;
55
- }
56
-
57
224
module . exports = {
58
- decodeJWT
59
- } ;
60
-
61
-
225
+ decodeJWT,
226
+ verifyJWT
227
+ } ;
0 commit comments