@@ -13,10 +13,163 @@ 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 verifyX509 ( jwt_ , jwt_verification_key , header . alg ) ;
62
+ } else if ( jwt_verification_type === 'jwks' ) {
63
+ isValid = await verifyJWKS ( jwt_ , JSON . parse ( jwt_verification_key ) ) ;
64
+ } else if ( jwt_verification_type === 'jwks_url' ) {
65
+ const response = await fetch ( jwt_verification_key ) ;
66
+ if ( ! response . ok ) throw new Error ( 'Failed to fetch JWKS.' ) ;
67
+ isValid = await verifyJWKS ( jwt_ , await response . json ( ) ) ;
68
+ } else {
69
+ throw new Error ( 'Unsupported verification method.' ) ;
70
+ }
71
+ } catch ( err ) {
72
+ log . error ( "Error while verifying JWT: " + err . message ) ;
73
+ }
74
+
75
+ document . getElementById ( 'jwt_verification_output' ) . value = "Signature Verified: " + isValid ;
76
+ }
77
+
78
+ function atobUrl ( input ) {
79
+ input = input . replace ( / - / g, '+' ) . replace ( / _ / g, '/' ) ;
80
+ const pad = '===' . slice ( 0 , ( 4 - input . length % 4 ) % 4 ) ;
81
+ return atob ( input + pad ) ;
82
+ }
83
+
84
+ function base64UrlToUint8Array ( base64UrlString ) {
85
+ const binary = atobUrl ( base64UrlString ) ;
86
+ const bytes = new Uint8Array ( binary . length ) ;
87
+
88
+ for ( let i = 0 ; i < binary . length ; i ++ ) {
89
+ bytes [ i ] = binary . charCodeAt ( i ) ;
90
+ }
91
+
92
+ return bytes ;
93
+ }
94
+
95
+ function pemToArrayBuffer ( pem ) {
96
+ const binary = atob ( pem . replace ( / - - - - - [ ^ - ] + - - - - - / g, '' ) . replace ( / \s + / g, '' ) ) ;
97
+ const buffer = new Uint8Array ( binary . length ) ;
98
+
99
+ for ( let i = 0 ; i < binary . length ; i ++ ) {
100
+ buffer [ i ] = binary . charCodeAt ( i ) ;
101
+ }
102
+
103
+ return buffer . buffer ;
104
+ }
105
+
106
+ async function verifyHMAC ( jwt_ , secret , alg = 'HS256' ) {
107
+ const encoder = new TextEncoder ( ) ;
108
+ const algo = { HS256 : 'SHA-256' , HS384 : 'SHA-384' , HS512 : 'SHA-512' } [ alg ] ;
109
+ if ( ! algo ) throw new Error ( 'Unsupported HMAC algorithm: ' + alg ) ;
110
+
111
+ const key = await crypto . subtle . importKey (
112
+ 'raw' ,
113
+ encoder . encode ( secret ) ,
114
+ { name : 'HMAC' , hash : { name : algo } } ,
115
+ false ,
116
+ [ 'verify' ]
117
+ ) ;
118
+ const data = encoder . encode ( jwt_ . split ( '.' ) . slice ( 0 , 2 ) . join ( '.' ) ) ;
119
+ const signature = base64UrlToUint8Array ( jwt_ . split ( '.' ) [ 2 ] ) ;
120
+
121
+ return await crypto . subtle . verify ( 'HMAC' , key , signature , data ) ;
122
+ }
123
+
124
+ async function verifyX509 ( jwt_ , pem , alg = 'RS256' ) {
125
+ const encoder = new TextEncoder ( ) ;
126
+ const algo = { RS256 : 'SHA-256' , RS384 : 'SHA-384' , RS512 : 'SHA-512' } [ alg ] ;
127
+ if ( ! algo ) throw new Error ( 'Unsupported RSA algorithm: ' + alg ) ;
128
+
129
+ const key = await crypto . subtle . importKey (
130
+ 'spki' ,
131
+ pemToArrayBuffer ( pem ) ,
132
+ { name : 'RSASSA-PKCS1-v1_5' , hash : { name : algo } } ,
133
+ false ,
134
+ [ 'verify' ]
135
+ ) ;
136
+ const data = encoder . encode ( jwt_ . split ( '.' ) . slice ( 0 , 2 ) . join ( '.' ) ) ;
137
+ const signature = base64UrlToUint8Array ( jwt_ . split ( '.' ) [ 2 ] ) ;
138
+
139
+ return await crypto . subtle . verify ( 'RSASSA-PKCS1-v1_5' , key , signature , data ) ;
140
+ }
141
+
142
+ async function verifyJWKS ( jwt_ , jwks ) {
143
+ const header = JSON . parse ( atobUrl ( jwt_ . split ( '.' ) [ 0 ] ) ) ;
144
+ if ( ! header . kid ) throw new Error ( 'No "kid" found in JWT header.' ) ;
145
+
146
+ const jwk = jwks . keys . find ( k => k . kid === header . kid ) ;
147
+ if ( ! jwk ) throw new Error ( 'Matching "kid" not found in JWKS.' ) ;
148
+ if ( jwk . kty !== 'RSA' ) throw new Error ( 'Only RSA keys are supported.' ) ;
149
+
150
+ const encoder = new TextEncoder ( ) ;
151
+ const algo = { RS256 : 'SHA-256' , RS384 : 'SHA-384' , RS512 : 'SHA-512' } [ header . alg ] ;
152
+ if ( ! algo ) throw new Error ( 'Unsupported algorithm: ' + header . alg ) ;
153
+
154
+ const key = await crypto . subtle . importKey (
155
+ 'jwk' ,
156
+ { kty : jwk . kty , n : jwk . n , e : jwk . e } ,
157
+ { name : 'RSASSA-PKCS1-v1_5' , hash : { name : algo } } ,
158
+ false ,
159
+ [ 'verify' ]
160
+ ) ;
161
+ const data = encoder . encode ( jwt_ . split ( '.' ) . slice ( 0 , 2 ) . join ( '.' ) ) ;
162
+ const signature = base64UrlToUint8Array ( jwt_ . split ( '.' ) [ 2 ] ) ;
163
+
164
+ return await crypto . subtle . verify ( 'RSASSA-PKCS1-v1_5' , key , signature , data ) ;
165
+ }
166
+
167
+ $ ( document ) . on ( "change" , "#jwt_verification_type" , function ( ) {
168
+ if ( this . value == "jwks_url" ) {
169
+ document . getElementById ( 'jwt_verification_key' ) . value = localStorage . getItem ( "jwks_endpoint" ) ;
170
+ }
171
+ } ) ;
172
+
20
173
window . onload = function ( ) {
21
174
log . debug ( "Entering onload function." ) ;
22
175
const type = getParameterByName ( 'type' ) ;
@@ -43,19 +196,7 @@ window.onload = function() {
43
196
document . getElementById ( 'jwt_payload' ) . value = JSON . stringify ( decodedJWT . payload , null , 2 ) ;
44
197
}
45
198
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
199
module . exports = {
58
- decodeJWT
59
- } ;
60
-
61
-
200
+ decodeJWT,
201
+ verifyJWT
202
+ } ;
0 commit comments