Skip to content

Commit a938023

Browse files
WIP
1 parent 4a61bb2 commit a938023

File tree

2 files changed

+47
-78
lines changed

2 files changed

+47
-78
lines changed

client/public/token_detail.html

+2-2
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@
7676
<select class="stored" id="jwt_verification_type" name="jwt_verification_type">
7777
<option value="hmac" selected="selected">HMAC Secret</option>
7878
<option value="x509">X.509 Certificate (PEM)</option>
79-
<option value="jwks">JWKS (Static JSON)</option>
80-
<option value="jwks_url">JWKS URL</option>
79+
<option value="jwks">JWKS (JSON)</option>
80+
<option value="jwks_url">JWKS (URL)</option>
8181
</select>
8282
</td>
8383
</tr>

client/src/token_detail.js

+45-76
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,13 @@ async function verifyJWT() {
5858
if (jwt_verification_type === 'hmac') {
5959
isValid = await verifyHMAC(jwt_, jwt_verification_key, header.alg);
6060
} else if (jwt_verification_type === 'x509') {
61-
isValid = await verifyRSJWT(jwt_, jwt_verification_key, header.alg);
61+
isValid = await verifyX509(jwt_, jwt_verification_key, header.alg);
6262
} else if (jwt_verification_type === 'jwks') {
63-
isValid = await verifyWithJWKS(jwt_, JSON.parse(jwt_verification_key));
63+
isValid = await verifyJWKS(jwt_, JSON.parse(jwt_verification_key));
6464
} else if (jwt_verification_type === 'jwks_url') {
65-
isValid = await verifyWithJWKS(jwt_, await fetchJWKS(jwt_verification_key));
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());
6668
} else {
6769
throw new Error('Unsupported verification method.');
6870
}
@@ -75,24 +77,35 @@ async function verifyJWT() {
7577

7678
function atobUrl(input) {
7779
input = input.replace(/-/g, '+').replace(/_/g, '/');
78-
const pad = input.length % 4 ? '='.repeat(4 - (input.length % 4)) : '';
80+
const pad = '==='.slice(0, (4 - input.length % 4) % 4);
7981
return atob(input + pad);
8082
}
8183

8284
function base64UrlToUint8Array(base64UrlString) {
8385
const binary = atobUrl(base64UrlString);
84-
const len = binary.length;
85-
const bytes = new Uint8Array(len);
86-
for (let i = 0; i < len; i++) {
86+
const bytes = new Uint8Array(binary.length);
87+
88+
for (let i = 0; i < binary.length; i++) {
8789
bytes[i] = binary.charCodeAt(i);
8890
}
91+
8992
return bytes;
9093
}
9194

92-
async function verifyHMAC(jwt, secret, alg = 'HS256') {
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') {
93107
const encoder = new TextEncoder();
94-
const algoMap = { HS256: 'SHA-256', HS384: 'SHA-384', HS512: 'SHA-512' };
95-
const algo = algoMap[alg];
108+
const algo = { HS256: 'SHA-256', HS384: 'SHA-384', HS512: 'SHA-512' }[alg];
96109
if (!algo) throw new Error('Unsupported HMAC algorithm: ' + alg);
97110

98111
const key = await crypto.subtle.importKey(
@@ -102,97 +115,53 @@ async function verifyHMAC(jwt, secret, alg = 'HS256') {
102115
false,
103116
['verify']
104117
);
105-
106-
const data = encoder.encode(jwt.split('.').slice(0, 2).join('.'));
107-
const signature = base64UrlToUint8Array(jwt.split('.')[2]);
118+
const data = encoder.encode(jwt_.split('.').slice(0, 2).join('.'));
119+
const signature = base64UrlToUint8Array(jwt_.split('.')[2]);
108120

109121
return await crypto.subtle.verify('HMAC', key, signature, data);
110122
}
111123

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];
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];
119127
if (!algo) throw new Error('Unsupported RSA algorithm: ' + alg);
120128

121-
const keyData = pemToArrayBuffer(pem);
122129
const key = await crypto.subtle.importKey(
123130
'spki',
124-
keyData,
131+
pemToArrayBuffer(pem),
125132
{ name: 'RSASSA-PKCS1-v1_5', hash: { name: algo } },
126133
false,
127134
['verify']
128135
);
129-
130-
const encoder = new TextEncoder();
131-
const data = encoder.encode(jwt.split('.').slice(0, 2).join('.'));
132-
const signature = base64UrlToUint8Array(jwt.split('.')[2]);
136+
const data = encoder.encode(jwt_.split('.').slice(0, 2).join('.'));
137+
const signature = base64UrlToUint8Array(jwt_.split('.')[2]);
133138

134139
return await crypto.subtle.verify('RSASSA-PKCS1-v1_5', key, signature, data);
135140
}
136141

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.');
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.');
153145

154-
const jwk = jwks.keys.find(k => k.kid === kid);
146+
const jwk = jwks.keys.find(k => k.kid === header.kid);
155147
if (!jwk) throw new Error('Matching "kid" not found in JWKS.');
148+
if (jwk.kty !== 'RSA') throw new Error('Only RSA keys are supported.');
156149

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);
167150
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-
};
151+
const algo = { RS256: 'SHA-256', RS384: 'SHA-384', RS512: 'SHA-512' }[header.alg];
152+
if (!algo) throw new Error('Unsupported algorithm: ' + header.alg);
188153

189-
return await crypto.subtle.importKey(
154+
const key = await crypto.subtle.importKey(
190155
'jwk',
191-
publicKey,
192-
{ name: 'RSASSA-PKCS1-v1_5', hash: { name: hashAlgo } },
156+
{ kty: jwk.kty, n: jwk.n, e: jwk.e },
157+
{ name: 'RSASSA-PKCS1-v1_5', hash: { name: algo } },
193158
false,
194159
['verify']
195160
);
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);
196165
}
197166

198167
$(document).on("change", "#jwt_verification_type", function() {

0 commit comments

Comments
 (0)