From 3406b9f73b1884b5db9c60675a68fe85794d48e0 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Tue, 7 Feb 2023 11:05:53 +0100 Subject: [PATCH] fix(CF Workers): improve miniflare compat with different Node.js versions, get ready for future non-proprietary support closes #446 closes #495 closes #497 --- src/lib/crypto_key.ts | 8 +++---- src/runtime/browser/asn1.ts | 38 ++++++++++++++++++++----------- src/runtime/browser/generate.ts | 31 +++++++++++++------------ src/runtime/browser/jwk_to_key.ts | 27 +++++++++++----------- src/runtime/browser/subtle_dsa.ts | 7 +++--- 5 files changed, 63 insertions(+), 48 deletions(-) diff --git a/src/lib/crypto_key.ts b/src/lib/crypto_key.ts index 3ecdf7cf69..fb964c42d4 100644 --- a/src/lib/crypto_key.ts +++ b/src/lib/crypto_key.ts @@ -71,12 +71,12 @@ export function checkSigCryptoKey(key: CryptoKey, alg: string, ...usages: KeyUsa if (actual !== expected) throw unusable(`SHA-${expected}`, 'algorithm.hash') break } - case isCloudflareWorkers() && 'EdDSA': { - if (!isAlgorithm(key.algorithm, 'NODE-ED25519')) throw unusable('NODE-ED25519') - break - } case 'EdDSA': { if (key.algorithm.name !== 'Ed25519' && key.algorithm.name !== 'Ed448') { + if (isCloudflareWorkers()) { + if (isAlgorithm(key.algorithm, 'NODE-ED25519')) break + throw unusable('Ed25519, Ed448, or NODE-ED25519') + } throw unusable('Ed25519 or Ed448') } break diff --git a/src/runtime/browser/asn1.ts b/src/runtime/browser/asn1.ts index a9344253d9..7e0610f046 100644 --- a/src/runtime/browser/asn1.ts +++ b/src/runtime/browser/asn1.ts @@ -135,12 +135,6 @@ const genericImport = async ( keyUsages = isPublic ? [] : ['deriveBits'] break } - case isCloudflareWorkers() && 'EdDSA': { - const namedCurve = getNamedCurve(keyData).toUpperCase() - algorithm = { name: `NODE-${namedCurve}`, namedCurve: `NODE-${namedCurve}` } - keyUsages = isPublic ? ['verify'] : ['sign'] - break - } case 'EdDSA': algorithm = { name: getNamedCurve(keyData) } keyUsages = isPublic ? ['verify'] : ['sign'] @@ -149,13 +143,31 @@ const genericImport = async ( throw new JOSENotSupported('Invalid or unsupported "alg" (Algorithm) value') } - return crypto.subtle.importKey( - keyFormat, - keyData, - algorithm, - options?.extractable ?? false, - keyUsages, - ) + try { + return await crypto.subtle.importKey( + keyFormat, + keyData, + algorithm, + options?.extractable ?? false, + keyUsages, + ) + } catch (err) { + if ( + algorithm.name === 'Ed25519' && + (err)?.name === 'NotSupportedError' && + isCloudflareWorkers() + ) { + algorithm = { name: 'NODE-ED25519', namedCurve: 'NODE-ED25519' } + return await crypto.subtle.importKey( + keyFormat, + keyData, + algorithm, + options?.extractable ?? false, + keyUsages, + ) + } + throw err + } } export const fromPKCS8: PEMImportFunction = (pem, alg, options?) => { diff --git a/src/runtime/browser/generate.ts b/src/runtime/browser/generate.ts index 1b80fb9c3d..4c21762b30 100644 --- a/src/runtime/browser/generate.ts +++ b/src/runtime/browser/generate.ts @@ -109,17 +109,6 @@ export async function generateKeyPair(alg: string, options?: GenerateKeyPairOpti algorithm = { name: 'ECDSA', namedCurve: 'P-521' } keyUsages = ['sign', 'verify'] break - case isCloudflareWorkers() && 'EdDSA': - switch (options?.crv) { - case undefined: - case 'Ed25519': - algorithm = { name: 'NODE-ED25519', namedCurve: 'NODE-ED25519' } - keyUsages = ['sign', 'verify'] - break - default: - throw new JOSENotSupported('Invalid or unsupported crv option provided') - } - break case 'EdDSA': keyUsages = ['sign', 'verify'] const crv = options?.crv ?? 'Ed25519' @@ -160,7 +149,21 @@ export async function generateKeyPair(alg: string, options?: GenerateKeyPairOpti throw new JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value') } - return >( - crypto.subtle.generateKey(algorithm, options?.extractable ?? false, keyUsages) - ) + try { + return <{ publicKey: CryptoKey; privateKey: CryptoKey }>( + await crypto.subtle.generateKey(algorithm, options?.extractable ?? false, keyUsages) + ) + } catch (err) { + if ( + algorithm.name === 'Ed25519' && + (err)?.name === 'NotSupportedError' && + isCloudflareWorkers() + ) { + algorithm = { name: 'NODE-ED25519', namedCurve: 'NODE-ED25519' } + return <{ publicKey: CryptoKey; privateKey: CryptoKey }>( + await crypto.subtle.generateKey(algorithm, options?.extractable ?? false, keyUsages) + ) + } + throw err + } } diff --git a/src/runtime/browser/jwk_to_key.ts b/src/runtime/browser/jwk_to_key.ts index a0e0d1224b..dd4766e05a 100644 --- a/src/runtime/browser/jwk_to_key.ts +++ b/src/runtime/browser/jwk_to_key.ts @@ -106,19 +106,6 @@ function subtleMapping(jwk: JWK): { } break } - case isCloudflareWorkers() && 'OKP': - if (jwk.alg !== 'EdDSA') { - throw new JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value') - } - switch (jwk.crv) { - case 'Ed25519': - algorithm = { name: 'NODE-ED25519', namedCurve: 'NODE-ED25519' } - keyUsages = jwk.d ? ['sign'] : ['verify'] - break - default: - throw new JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value') - } - break case 'OKP': { switch (jwk.alg) { case 'EdDSA': @@ -159,6 +146,18 @@ const parse: JWKImportFunction = async (jwk: JWK): Promise => { const keyData: JWK = { ...jwk } delete keyData.alg delete keyData.use - return crypto.subtle.importKey('jwk', keyData, ...rest) + try { + return await crypto.subtle.importKey('jwk', keyData, ...rest) + } catch (err) { + if ( + algorithm.name === 'Ed25519' && + (err)?.name === 'NotSupportedError' && + isCloudflareWorkers() + ) { + rest[0] = { name: 'NODE-ED25519', namedCurve: 'NODE-ED25519' } + return await crypto.subtle.importKey('jwk', keyData, ...rest) + } + throw err + } } export default parse diff --git a/src/runtime/browser/subtle_dsa.ts b/src/runtime/browser/subtle_dsa.ts index 9bf9b061ec..741f115e82 100644 --- a/src/runtime/browser/subtle_dsa.ts +++ b/src/runtime/browser/subtle_dsa.ts @@ -21,10 +21,11 @@ export default function subtleDsa(alg: string, algorithm: KeyAlgorithm | EcKeyAl case 'ES384': case 'ES512': return { hash, name: 'ECDSA', namedCurve: (algorithm).namedCurve } - case isCloudflareWorkers() && 'EdDSA': - const { namedCurve } = algorithm - return { name: namedCurve, namedCurve } case 'EdDSA': + if (isCloudflareWorkers() && algorithm.name === 'NODE-ED25519') { + return { name: 'NODE-ED25519', namedCurve: 'NODE-ED25519' } + } + return { name: algorithm.name } default: throw new JOSENotSupported(