1
0

🐛 Fix verification relying on a signing key

This commit is contained in:
Nick DeGroot
2024-01-25 14:43:54 -08:00
committed by Toby
parent fdc80e565c
commit 578f9fd889
3 changed files with 17 additions and 16 deletions

View File

@@ -156,7 +156,7 @@ export async function sign<Payload = {}, Header = {}>(payload: JwtPayload<Payloa
const partialToken = `${textToBase64Url(JSON.stringify({ ...options.header, alg: options.algorithm }))}.${textToBase64Url(JSON.stringify(payload))}` const partialToken = `${textToBase64Url(JSON.stringify({ ...options.header, alg: options.algorithm }))}.${textToBase64Url(JSON.stringify(payload))}`
const key = secret instanceof CryptoKey ? secret : await importKey(secret, algorithm) const key = secret instanceof CryptoKey ? secret : await importKey(secret, algorithm, ['sign'])
const signature = await crypto.subtle.sign(algorithm, key, textToArrayBuffer(partialToken)) const signature = await crypto.subtle.sign(algorithm, key, textToArrayBuffer(partialToken))
return `${partialToken}.${arrayBufferToBase64Url(signature)}` return `${partialToken}.${arrayBufferToBase64Url(signature)}`
@@ -208,7 +208,7 @@ export async function verify(token: string, secret: string | JsonWebKey | Crypto
if (payload.exp && payload.exp <= Math.floor(Date.now() / 1000)) if (payload.exp && payload.exp <= Math.floor(Date.now() / 1000))
throw new Error('EXPIRED') throw new Error('EXPIRED')
const key = secret instanceof CryptoKey ? secret : await importKey(secret, algorithm) const key = secret instanceof CryptoKey ? secret : await importKey(secret, algorithm, ['verify'])
return await crypto.subtle.verify(algorithm, key, base64UrlToArrayBuffer(tokenParts[2]), textToArrayBuffer(`${tokenParts[0]}.${tokenParts[1]}`)) return await crypto.subtle.verify(algorithm, key, base64UrlToArrayBuffer(tokenParts[2]), textToArrayBuffer(`${tokenParts[0]}.${tokenParts[1]}`))
} catch(err) { } catch(err) {

View File

@@ -49,36 +49,37 @@ export function pemToBinary(pem: string): ArrayBuffer {
return base64StringToArrayBuffer(pem.replace(/-+(BEGIN|END).*/g, '').replace(/\s/g, '')) return base64StringToArrayBuffer(pem.replace(/-+(BEGIN|END).*/g, '').replace(/\s/g, ''))
} }
export async function importTextSecret(key: string, algorithm: SubtleCryptoImportKeyAlgorithm): Promise<CryptoKey> { type KeyUsages = 'sign' | 'verify';
return await crypto.subtle.importKey("raw", textToArrayBuffer(key), algorithm, true, ["verify", "sign"]) export async function importTextSecret(key: string, algorithm: SubtleCryptoImportKeyAlgorithm, keyUsages: KeyUsages[]): Promise<CryptoKey> {
return await crypto.subtle.importKey("raw", textToArrayBuffer(key), algorithm, true, keyUsages)
} }
export async function importJwk(key: JsonWebKey, algorithm: SubtleCryptoImportKeyAlgorithm): Promise<CryptoKey> { export async function importJwk(key: JsonWebKey, algorithm: SubtleCryptoImportKeyAlgorithm, keyUsages: KeyUsages[]): Promise<CryptoKey> {
return await crypto.subtle.importKey("jwk", key, algorithm, true, ["verify", "sign"]) return await crypto.subtle.importKey("jwk", key, algorithm, true, keyUsages)
} }
export async function importPublicKey(key: string, algorithm: SubtleCryptoImportKeyAlgorithm): Promise<CryptoKey> { export async function importPublicKey(key: string, algorithm: SubtleCryptoImportKeyAlgorithm, keyUsages: KeyUsages[]): Promise<CryptoKey> {
return await crypto.subtle.importKey("spki", pemToBinary(key), algorithm, true, ["verify"]) return await crypto.subtle.importKey("spki", pemToBinary(key), algorithm, true, keyUsages)
} }
export async function importPrivateKey(key: string, algorithm: SubtleCryptoImportKeyAlgorithm): Promise<CryptoKey> { export async function importPrivateKey(key: string, algorithm: SubtleCryptoImportKeyAlgorithm, keyUsages: KeyUsages[]): Promise<CryptoKey> {
return await crypto.subtle.importKey("pkcs8", pemToBinary(key), algorithm, true, ["sign"]) return await crypto.subtle.importKey("pkcs8", pemToBinary(key), algorithm, true, keyUsages)
} }
export async function importKey(key: string | JsonWebKey, algorithm: SubtleCryptoImportKeyAlgorithm): Promise<CryptoKey> { export async function importKey(key: string | JsonWebKey, algorithm: SubtleCryptoImportKeyAlgorithm, keyUsages: KeyUsages[]): Promise<CryptoKey> {
if (typeof key === 'object') if (typeof key === 'object')
return importJwk(key, algorithm) return importJwk(key, algorithm, keyUsages)
if (typeof key !== 'string') if (typeof key !== 'string')
throw new Error('Unsupported key type!') throw new Error('Unsupported key type!')
if (key.includes('PUBLIC')) if (key.includes('PUBLIC'))
return importPublicKey(key, algorithm) return importPublicKey(key, algorithm, keyUsages)
if (key.includes('PRIVATE')) if (key.includes('PRIVATE'))
return importPrivateKey(key, algorithm) return importPrivateKey(key, algorithm, keyUsages)
return importTextSecret(key, algorithm) return importTextSecret(key, algorithm, keyUsages)
} }
export function decodePayload<T = any>(raw: string): T | undefined { export function decodePayload<T = any>(raw: string): T | undefined {

View File

@@ -67,7 +67,7 @@ describe('Imports', () => {
const testAlgorithm = { name: 'HMAC', hash: { name: 'SHA-256' } } const testAlgorithm = { name: 'HMAC', hash: { name: 'SHA-256' } }
const testCryptoKey = { type: 'secret', extractable: true, algorithm: { ...testAlgorithm, length: 168 }, usages: ['verify', 'sign'] } const testCryptoKey = { type: 'secret', extractable: true, algorithm: { ...testAlgorithm, length: 168 }, usages: ['verify', 'sign'] }
expect(await importTextSecret(testKey, testAlgorithm)).toMatchObject(testCryptoKey) expect(await importTextSecret(testKey, testAlgorithm, ['verify', 'sign'])).toMatchObject(testCryptoKey)
}) })
//test('importJwk', async () => {}) //test('importJwk', async () => {})