diff --git a/src/lib/cek.ts b/src/lib/cek.ts index 53a520a745..91f8664c92 100644 --- a/src/lib/cek.ts +++ b/src/lib/cek.ts @@ -1,23 +1,22 @@ import { JOSENotSupported } from '../util/errors.js' import random from '../runtime/random.js' -const bitLengths = new Map([ - ['A128CBC-HS256', 256], - ['A128GCM', 128], - ['A192CBC-HS384', 384], - ['A192GCM', 192], - ['A256CBC-HS512', 512], - ['A256GCM', 256], -]) - -const generateCek = (alg: string): Uint8Array => { - const bitLength = bitLengths.get(alg) - if (!bitLength) { - throw new JOSENotSupported(`Unsupported JWE Algorithm: ${alg}`) +export function bitLength(alg: string) { + switch (alg) { + case 'A128CBC-HS256': + return 256 + case 'A192CBC-HS384': + return 384 + case 'A256CBC-HS512': + return 512 + case 'A128GCM': + return 128 + case 'A192GCM': + return 192 + case 'A256GCM': + return 256 + default: + throw new JOSENotSupported(`Unsupported JWE Algorithm: ${alg}`) } - - return random(new Uint8Array(bitLength >> 3)) } - -export default generateCek -export { bitLengths } +export default (alg: string): Uint8Array => random(new Uint8Array(bitLength(alg) >> 3)) diff --git a/src/lib/check_iv_length.ts b/src/lib/check_iv_length.ts index 9124373d5b..a0b0601f89 100644 --- a/src/lib/check_iv_length.ts +++ b/src/lib/check_iv_length.ts @@ -1,8 +1,8 @@ import { JWEInvalid } from '../util/errors.js' -import { bitLengths } from './iv.js' +import { bitLength } from './iv.js' const checkIvLength = (enc: string, iv: Uint8Array) => { - if (iv.length << 3 !== bitLengths.get(enc)) { + if (iv.length << 3 !== bitLength(enc)) { throw new JWEInvalid('Invalid Initialization Vector length') } } diff --git a/src/lib/crypto_key.ts b/src/lib/crypto_key.ts new file mode 100644 index 0000000000..455222e2ae --- /dev/null +++ b/src/lib/crypto_key.ts @@ -0,0 +1,209 @@ +import { isCloudflareWorkers, isNodeJs } from '../runtime/global.js' + +function getHashLength(hash: KeyAlgorithm) { + return parseInt(hash?.name.substr(4), 10) +} + +function getNamedCurve(alg: string) { + switch (alg) { + case 'ES256': + return 'P-256' + case 'ES384': + return 'P-384' + case 'ES512': + return 'P-521' + } +} + +function checkUsage(key: CryptoKey, usages: KeyUsage[]) { + if (usages.length && !usages.some((expected) => key.usages.includes(expected))) { + let msg = 'CryptoKey does not support this operation, its usages must include ' + if (usages.length > 2) { + const last = usages.pop() + msg += `one of ${usages.join(', ')}, or ${last}.` + } else if (usages.length === 2) { + msg += `one of ${usages[0]} or ${usages[1]}.` + } else { + msg += `${usages[0]}.` + } + + throw new TypeError(msg) + } +} + +export function checkSigCryptoKey(key: CryptoKey, alg: string, ...usages: KeyUsage[]) { + switch (alg) { + case 'HS256': + case 'HS384': + case 'HS512': { + if (key.algorithm.name !== 'HMAC') { + throw new TypeError( + `CryptoKey does not support this operation, its algorithm.name must be HMAC.`, + ) + } + + const expected = parseInt(alg.substr(2), 10) + const actual = getHashLength((key.algorithm).hash) + if (actual !== expected) { + throw new TypeError( + `CryptoKey does not support this operation, its algorithm.hash must be SHA-${expected}.`, + ) + } + break + } + case 'RS256': + case 'RS384': + case 'RS512': { + if (key.algorithm.name !== 'RSASSA-PKCS1-v1_5') { + throw new TypeError( + `CryptoKey does not support this operation, its algorithm.name must be RSASSA-PKCS1-v1_5.`, + ) + } + + const expected = parseInt(alg.substr(2), 10) + const actual = getHashLength((key.algorithm).hash) + if (actual !== expected) { + throw new TypeError( + `CryptoKey does not support this operation, its algorithm.hash must be SHA-${expected}.`, + ) + } + break + } + case 'PS256': + case 'PS384': + case 'PS512': { + if (key.algorithm.name !== 'RSA-PSS') { + throw new TypeError( + `CryptoKey does not support this operation, its algorithm.name must be RSA-PSS.`, + ) + } + + const expected = parseInt(alg.substr(2), 10) + const actual = getHashLength((key.algorithm).hash) + if (actual !== expected) { + throw new TypeError( + `CryptoKey does not support this operation, its algorithm.hash must be SHA-${expected}.`, + ) + } + break + } + case isNodeJs() && 'EdDSA': { + if (key.algorithm.name !== 'NODE-ED25519' && key.algorithm.name !== 'NODE-ED448') { + throw new TypeError( + `CryptoKey does not support this operation, its algorithm.name must be NODE-ED25519 or NODE-ED448.`, + ) + } + break + } + case isCloudflareWorkers() && 'EdDSA': { + if (key.algorithm.name !== 'NODE-ED25519') { + throw new TypeError( + `CryptoKey does not support this operation, its algorithm.name must be NODE-ED25519.`, + ) + } + break + } + case 'ES256': + case 'ES384': + case 'ES512': { + if (key.algorithm.name !== 'ECDSA') { + throw new TypeError( + `CryptoKey does not support this operation, its algorithm.name must be ECDSA.`, + ) + } + + const expected = getNamedCurve(alg) + const actual = (key.algorithm).namedCurve + if (actual !== expected) { + throw new TypeError( + `CryptoKey does not support this operation, its algorithm.namedCurve must be ${expected}.`, + ) + } + break + } + default: + throw new TypeError('CryptoKey does not support this operation') + } + + checkUsage(key, usages) +} + +export function checkEncCryptoKey(key: CryptoKey, alg: string, ...usages: KeyUsage[]) { + switch (alg) { + case 'A128GCM': + case 'A192GCM': + case 'A256GCM': { + if (key.algorithm.name !== 'AES-GCM') { + throw new TypeError( + `CryptoKey does not support this operation, its algorithm.name must be AES-GCM.`, + ) + } + + const expected = parseInt(alg.substr(1, 3), 10) + const actual = (key.algorithm).length + if (actual !== expected) { + throw new TypeError( + `CryptoKey does not support this operation, its algorithm.length must be ${expected}.`, + ) + } + break + } + case 'A128KW': + case 'A192KW': + case 'A256KW': { + if (key.algorithm.name !== 'AES-KW') { + throw new TypeError( + `CryptoKey does not support this operation, its algorithm.name must be AES-KW.`, + ) + } + + const expected = parseInt(alg.substr(1, 3), 10) + const actual = (key.algorithm).length + if (actual !== expected) { + throw new TypeError( + `CryptoKey does not support this operation, its algorithm.length must be ${expected}.`, + ) + } + break + } + case 'ECDH-ES': + if (key.algorithm.name !== 'ECDH') { + throw new TypeError( + `CryptoKey does not support this operation, its algorithm.name must be ECDH.`, + ) + } + break + case 'PBES2-HS256+A128KW': + case 'PBES2-HS384+A192KW': + case 'PBES2-HS512+A256KW': + if (key.algorithm.name !== 'PBKDF2') { + throw new TypeError( + `CryptoKey does not support this operation, its algorithm.name must be PBKDF2.`, + ) + } + break + case 'RSA-OAEP': + case 'RSA-OAEP-256': + case 'RSA-OAEP-384': + case 'RSA-OAEP-512': { + if (key.algorithm.name !== 'RSA-OAEP') { + throw new TypeError( + `CryptoKey does not support this operation, its algorithm.name must be RSA-OAEP.`, + ) + } + + const expected = parseInt(alg.substr(9), 10) || 1 + const actual = getHashLength((key.algorithm).hash) + if (actual !== expected) { + throw new TypeError( + `CryptoKey does not support this operation, its algorithm.hash must be SHA-${expected}.`, + ) + } + break + } + default: + throw new TypeError('CryptoKey does not support this operation') + } + + checkUsage(key, usages) +} diff --git a/src/lib/decrypt_key_management.ts b/src/lib/decrypt_key_management.ts index db0f0addb0..b8faff336d 100644 --- a/src/lib/decrypt_key_management.ts +++ b/src/lib/decrypt_key_management.ts @@ -7,7 +7,7 @@ import { decode as base64url } from '../runtime/base64url.js' import type { JWEHeaderParameters, KeyLike, JWK } from '../types.d' import { JOSENotSupported, JWEInvalid } from '../util/errors.js' -import { bitLengths as cekLengths } from '../lib/cek.js' +import { bitLength as cekLength } from '../lib/cek.js' import { importJWK } from '../key/import.js' import checkKeyType from './check_key_type.js' import isObject from './is_object.js' @@ -65,7 +65,7 @@ async function decryptKeyManagement( epk, key, alg === 'ECDH-ES' ? joseHeader.enc! : alg, - parseInt(alg.substr(-5, 3), 10) || cekLengths.get(joseHeader.enc!), + parseInt(alg.substr(-5, 3), 10) || cekLength(joseHeader.enc!), partyUInfo, partyVInfo, ) diff --git a/src/lib/encrypt_key_management.ts b/src/lib/encrypt_key_management.ts index 183519858b..77653b988f 100644 --- a/src/lib/encrypt_key_management.ts +++ b/src/lib/encrypt_key_management.ts @@ -6,7 +6,7 @@ import { wrap as aesGcmKw } from '../runtime/aesgcmkw.js' import { encode as base64url } from '../runtime/base64url.js' import type { KeyLike, JWEKeyManagementHeaderParameters, JWEHeaderParameters } from '../types.d' -import generateCek, { bitLengths as cekLengths } from '../lib/cek.js' +import generateCek, { bitLength as cekLength } from '../lib/cek.js' import { JOSENotSupported } from '../util/errors.js' import { exportJWK } from '../key/export.js' import checkKeyType from './check_key_type.js' @@ -52,7 +52,7 @@ async function encryptKeyManagement( key, ephemeralKey, alg === 'ECDH-ES' ? enc : alg, - parseInt(alg.substr(-5, 3), 10) || cekLengths.get(enc), + parseInt(alg.substr(-5, 3), 10) || cekLength(enc), apu, apv, ) diff --git a/src/lib/iv.ts b/src/lib/iv.ts index c824dcef74..ab5678ee72 100644 --- a/src/lib/iv.ts +++ b/src/lib/iv.ts @@ -1,26 +1,28 @@ import { JOSENotSupported } from '../util/errors.js' import random from '../runtime/random.js' -const bitLengths = new Map([ - ['A128CBC-HS256', 128], - ['A128GCM', 96], - ['A128GCMKW', 96], - ['A192CBC-HS384', 128], - ['A192GCM', 96], - ['A192GCMKW', 96], - ['A256CBC-HS512', 128], - ['A256GCM', 96], - ['A256GCMKW', 96], -]) - -const generateIv = (alg: string): Uint8Array => { - const bitLength = bitLengths.get(alg) - if (!bitLength) { - throw new JOSENotSupported(`Unsupported JWE Algorithm: ${alg}`) +export function bitLength(alg: string) { + switch (alg) { + case 'A128CBC-HS256': + return 128 + case 'A128GCM': + return 96 + case 'A128GCMKW': + return 96 + case 'A192CBC-HS384': + return 128 + case 'A192GCM': + return 96 + case 'A192GCMKW': + return 96 + case 'A256CBC-HS512': + return 128 + case 'A256GCM': + return 96 + case 'A256GCMKW': + return 96 + default: + throw new JOSENotSupported(`Unsupported JWE Algorithm: ${alg}`) } - - return random(new Uint8Array(bitLength >> 3)) } - -export default generateIv -export { bitLengths } +export default (alg: string): Uint8Array => random(new Uint8Array(bitLength(alg) >> 3)) diff --git a/src/runtime/browser/aeskw.ts b/src/runtime/browser/aeskw.ts index d3e86b80a6..ef8f6dcffa 100644 --- a/src/runtime/browser/aeskw.ts +++ b/src/runtime/browser/aeskw.ts @@ -1,6 +1,7 @@ import type { AesKwUnwrapFunction, AesKwWrapFunction } from '../interfaces.d' import bogusWebCrypto from './bogus.js' -import crypto, { checkCryptoKey, isCryptoKey } from './webcrypto.js' +import crypto, { isCryptoKey } from './webcrypto.js' +import { checkEncCryptoKey } from '../../lib/crypto_key.js' import invalidKeyInput from './invalid_key_input.js' function checkKeySize(key: CryptoKey, alg: string) { @@ -11,7 +12,7 @@ function checkKeySize(key: CryptoKey, alg: string) { function getCryptoKey(key: unknown, alg: string, usage: KeyUsage) { if (isCryptoKey(key)) { - checkCryptoKey(key, alg, usage) + checkEncCryptoKey(key, alg, usage) return key } diff --git a/src/runtime/browser/check_cek_length.ts b/src/runtime/browser/check_cek_length.ts new file mode 100644 index 0000000000..71b4a5c6a6 --- /dev/null +++ b/src/runtime/browser/check_cek_length.ts @@ -0,0 +1,9 @@ +import { JWEInvalid } from '../../util/errors.js' + +const checkCekLength = (cek: Uint8Array, expected: number) => { + if (cek.length << 3 !== expected) { + throw new JWEInvalid('Invalid Content Encryption Key length') + } +} + +export default checkCekLength diff --git a/src/runtime/browser/decrypt.ts b/src/runtime/browser/decrypt.ts index 6c259b411d..96712f9ff0 100644 --- a/src/runtime/browser/decrypt.ts +++ b/src/runtime/browser/decrypt.ts @@ -2,9 +2,11 @@ import { concat, uint64be } from '../../lib/buffer_utils.js' import type { DecryptFunction } from '../interfaces.d' import checkIvLength from '../../lib/check_iv_length.js' +import checkCekLength from './check_cek_length.js' import timingSafeEqual from './timing_safe_equal.js' import { JOSENotSupported, JWEDecryptionFailed } from '../../util/errors.js' -import crypto, { checkCryptoKey, isCryptoKey } from './webcrypto.js' +import crypto, { isCryptoKey } from './webcrypto.js' +import { checkEncCryptoKey } from '../../lib/crypto_key.js' import invalidKeyInput from './invalid_key_input.js' async function cbcDecrypt( @@ -79,7 +81,7 @@ async function gcmDecrypt( if (cek instanceof Uint8Array) { encKey = await crypto.subtle.importKey('raw', cek, 'AES-GCM', false, ['decrypt']) } else { - checkCryptoKey(cek, enc, 'decrypt') + checkEncCryptoKey(cek, enc, 'decrypt') encKey = cek } @@ -120,10 +122,12 @@ const decrypt: DecryptFunction = async ( case 'A128CBC-HS256': case 'A192CBC-HS384': case 'A256CBC-HS512': + if (cek instanceof Uint8Array) checkCekLength(cek, parseInt(enc.substr(-3), 10)) return cbcDecrypt(enc, cek, ciphertext, iv, tag, aad) case 'A128GCM': case 'A192GCM': case 'A256GCM': + if (cek instanceof Uint8Array) checkCekLength(cek, parseInt(enc.substr(1, 3), 10)) return gcmDecrypt(enc, cek, ciphertext, iv, tag, aad) default: throw new JOSENotSupported('Unsupported JWE Content Encryption Algorithm') diff --git a/src/runtime/browser/ecdhes.ts b/src/runtime/browser/ecdhes.ts index aa1c07a973..5b30ba669d 100644 --- a/src/runtime/browser/ecdhes.ts +++ b/src/runtime/browser/ecdhes.ts @@ -4,7 +4,8 @@ import type { GenerateEpkFunction, } from '../interfaces.d' import { encoder, concat, uint32be, lengthAndInput, concatKdf } from '../../lib/buffer_utils.js' -import crypto, { checkCryptoKey, isCryptoKey } from './webcrypto.js' +import crypto, { isCryptoKey } from './webcrypto.js' +import { checkEncCryptoKey } from '../../lib/crypto_key.js' import digest from './digest.js' import invalidKeyInput from './invalid_key_input.js' @@ -19,11 +20,11 @@ export const deriveKey: EcdhESDeriveKeyFunction = async ( if (!isCryptoKey(publicKey)) { throw new TypeError(invalidKeyInput(publicKey, 'CryptoKey')) } - checkCryptoKey(publicKey, 'ECDH-ES') + checkEncCryptoKey(publicKey, 'ECDH-ES') if (!isCryptoKey(privateKey)) { throw new TypeError(invalidKeyInput(privateKey, 'CryptoKey')) } - checkCryptoKey(privateKey, 'ECDH-ES', 'deriveBits', 'deriveKey') + checkEncCryptoKey(privateKey, 'ECDH-ES', 'deriveBits', 'deriveKey') const value = concat( lengthAndInput(encoder.encode(algorithm)), diff --git a/src/runtime/browser/encrypt.ts b/src/runtime/browser/encrypt.ts index 2f741e5779..5aa618e6a3 100644 --- a/src/runtime/browser/encrypt.ts +++ b/src/runtime/browser/encrypt.ts @@ -1,7 +1,9 @@ import { concat, uint64be } from '../../lib/buffer_utils.js' import type { EncryptFunction } from '../interfaces.d' import checkIvLength from '../../lib/check_iv_length.js' -import crypto, { checkCryptoKey, isCryptoKey } from './webcrypto.js' +import checkCekLength from './check_cek_length.js' +import crypto, { isCryptoKey } from './webcrypto.js' +import { checkEncCryptoKey } from '../../lib/crypto_key.js' import invalidKeyInput from './invalid_key_input.js' import { JOSENotSupported } from '../../util/errors.js' @@ -64,7 +66,7 @@ async function gcmEncrypt( if (cek instanceof Uint8Array) { encKey = await crypto.subtle.importKey('raw', cek, 'AES-GCM', false, ['encrypt']) } else { - checkCryptoKey(cek, enc, 'encrypt') + checkEncCryptoKey(cek, enc, 'encrypt') encKey = cek } @@ -105,10 +107,12 @@ const encrypt: EncryptFunction = async ( case 'A128CBC-HS256': case 'A192CBC-HS384': case 'A256CBC-HS512': - return cbcEncrypt(enc, plaintext, cek, iv, aad) + if (cek instanceof Uint8Array) checkCekLength(cek, parseInt(enc.substr(-3), 10)) + return cbcEncrypt(enc, plaintext, cek, iv, aad) case 'A128GCM': case 'A192GCM': case 'A256GCM': + if (cek instanceof Uint8Array) checkCekLength(cek, parseInt(enc.substr(1, 3), 10)) return gcmEncrypt(enc, plaintext, cek, iv, aad) default: throw new JOSENotSupported('Unsupported JWE Content Encryption Algorithm') diff --git a/src/runtime/browser/get_sign_verify_key.ts b/src/runtime/browser/get_sign_verify_key.ts index 5b65536b89..9fd87c4901 100644 --- a/src/runtime/browser/get_sign_verify_key.ts +++ b/src/runtime/browser/get_sign_verify_key.ts @@ -1,9 +1,10 @@ -import crypto, { checkCryptoKey, isCryptoKey } from './webcrypto.js' +import crypto, { isCryptoKey } from './webcrypto.js' +import { checkSigCryptoKey } from '../../lib/crypto_key.js' import invalidKeyInput from './invalid_key_input.js' export default function getCryptoKey(alg: string, key: unknown, usage: KeyUsage) { if (isCryptoKey(key)) { - checkCryptoKey(key, alg, usage) + checkSigCryptoKey(key, alg, usage) return key } diff --git a/src/runtime/browser/pbes2kw.ts b/src/runtime/browser/pbes2kw.ts index 819ded7113..2f8cdae60c 100644 --- a/src/runtime/browser/pbes2kw.ts +++ b/src/runtime/browser/pbes2kw.ts @@ -4,7 +4,8 @@ import { p2s as concatSalt } from '../../lib/buffer_utils.js' import { encode as base64url } from './base64url.js' import { wrap, unwrap } from './aeskw.js' import checkP2s from '../../lib/check_p2s.js' -import crypto, { checkCryptoKey, isCryptoKey } from './webcrypto.js' +import crypto, { isCryptoKey } from './webcrypto.js' +import { checkEncCryptoKey } from '../../lib/crypto_key.js' import invalidKeyInput from './invalid_key_input.js' function getCryptoKey(key: unknown, alg: string) { @@ -13,7 +14,7 @@ function getCryptoKey(key: unknown, alg: string) { } if (isCryptoKey(key)) { - checkCryptoKey(key, alg, 'deriveBits', 'deriveKey') + checkEncCryptoKey(key, alg, 'deriveBits', 'deriveKey') return key } diff --git a/src/runtime/browser/rsaes.ts b/src/runtime/browser/rsaes.ts index fc3479af23..8304873355 100644 --- a/src/runtime/browser/rsaes.ts +++ b/src/runtime/browser/rsaes.ts @@ -1,7 +1,8 @@ import type { RsaEsDecryptFunction, RsaEsEncryptFunction } from '../interfaces.d' import subtleAlgorithm from './subtle_rsaes.js' import bogusWebCrypto from './bogus.js' -import crypto, { checkCryptoKey, isCryptoKey } from './webcrypto.js' +import crypto, { isCryptoKey } from './webcrypto.js' +import { checkEncCryptoKey } from '../../lib/crypto_key.js' import checkKeyLength from './check_key_length.js' import invalidKeyInput from './invalid_key_input.js' @@ -9,7 +10,7 @@ export const encrypt: RsaEsEncryptFunction = async (alg: string, key: unknown, c if (!isCryptoKey(key)) { throw new TypeError(invalidKeyInput(key, 'CryptoKey')) } - checkCryptoKey(key, alg, 'encrypt', 'wrapKey') + checkEncCryptoKey(key, alg, 'encrypt', 'wrapKey') checkKeyLength(alg, key) if (key.usages.includes('encrypt')) { @@ -37,7 +38,7 @@ export const decrypt: RsaEsDecryptFunction = async ( if (!isCryptoKey(key)) { throw new TypeError(invalidKeyInput(key, 'CryptoKey')) } - checkCryptoKey(key, alg, 'decrypt', 'unwrapKey') + checkEncCryptoKey(key, alg, 'decrypt', 'unwrapKey') checkKeyLength(alg, key) if (key.usages.includes('decrypt')) { diff --git a/src/runtime/browser/webcrypto.ts b/src/runtime/browser/webcrypto.ts index 6e2ff35418..1ec3cb9162 100644 --- a/src/runtime/browser/webcrypto.ts +++ b/src/runtime/browser/webcrypto.ts @@ -8,192 +8,3 @@ export function isCryptoKey(key: unknown): key is CryptoKey { } return key != null && key instanceof globalThis.CryptoKey } - -function getHashLength(hash: KeyAlgorithm) { - return parseInt(hash?.name.substr(4), 10) -} - -function getNamedCurve(alg: string) { - switch (alg) { - case 'ES256': - return 'P-256' - case 'ES384': - return 'P-384' - case 'ES512': - return 'P-521' - } -} - -export function checkCryptoKey(key: CryptoKey, alg?: string, ...usages: KeyUsage[]) { - switch (alg) { - case undefined: - break - case 'HS256': - case 'HS384': - case 'HS512': { - if (key.algorithm.name !== 'HMAC') { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.name must be HMAC.`, - ) - } - - const expected = parseInt(alg.substr(2), 10) - const actual = getHashLength((key.algorithm).hash) - if (actual !== expected) { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.hash must be SHA-${expected}.`, - ) - } - break - } - case 'RS256': - case 'RS384': - case 'RS512': { - if (key.algorithm.name !== 'RSASSA-PKCS1-v1_5') { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.name must be RSASSA-PKCS1-v1_5.`, - ) - } - - const expected = parseInt(alg.substr(2), 10) - const actual = getHashLength((key.algorithm).hash) - if (actual !== expected) { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.hash must be SHA-${expected}.`, - ) - } - break - } - case 'PS256': - case 'PS384': - case 'PS512': { - if (key.algorithm.name !== 'RSA-PSS') { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.name must be RSA-PSS.`, - ) - } - - const expected = parseInt(alg.substr(2), 10) - const actual = getHashLength((key.algorithm).hash) - if (actual !== expected) { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.hash must be SHA-${expected}.`, - ) - } - break - } - case 'EdDSA': { - if (key.algorithm.name !== 'NODE-ED25519' && key.algorithm.name !== 'NODE-ED448') { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.name must be NODE-ED25519 or NODE-ED448.`, - ) - } - break - } - case 'ES256': - case 'ES384': - case 'ES512': { - if (key.algorithm.name !== 'ECDSA') { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.name must be ECDSA.`, - ) - } - - const expected = getNamedCurve(alg) - const actual = (key.algorithm).namedCurve - if (actual !== expected) { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.namedCurve must be ${expected}.`, - ) - } - break - } - case 'A128GCM': - case 'A192GCM': - case 'A256GCM': { - if (key.algorithm.name !== 'AES-GCM') { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.name must be AES-GCM.`, - ) - } - - const expected = parseInt(alg.substr(1, 3), 10) - const actual = (key.algorithm).length - if (actual !== expected) { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.length must be ${expected}.`, - ) - } - break - } - case 'A128KW': - case 'A192KW': - case 'A256KW': { - if (key.algorithm.name !== 'AES-KW') { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.name must be AES-KW.`, - ) - } - - const expected = parseInt(alg.substr(1, 3), 10) - const actual = (key.algorithm).length - if (actual !== expected) { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.length must be ${expected}.`, - ) - } - break - } - case 'ECDH-ES': - if (key.algorithm.name !== 'ECDH') { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.name must be ECDH.`, - ) - } - break - case 'PBES2-HS256+A128KW': - case 'PBES2-HS384+A192KW': - case 'PBES2-HS512+A256KW': - if (key.algorithm.name !== 'PBKDF2') { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.name must be PBKDF2.`, - ) - } - break - case 'RSA-OAEP': - case 'RSA-OAEP-256': - case 'RSA-OAEP-384': - case 'RSA-OAEP-512': { - if (key.algorithm.name !== 'RSA-OAEP') { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.name must be RSA-OAEP.`, - ) - } - - const expected = parseInt(alg.substr(9), 10) || 1 - const actual = getHashLength((key.algorithm).hash) - if (actual !== expected) { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.hash must be SHA-${expected}.`, - ) - } - break - } - default: - throw new TypeError('CryptoKey does not support this operation') - } - - if (usages.length && !usages.some((expected) => key.usages.includes(expected))) { - let msg = 'CryptoKey does not support this operation, its usages must include ' - if (usages.length > 2) { - const last = usages.pop() - msg += `one of ${usages.join(', ')}, or ${last}.` - } else if (usages.length === 2) { - msg += `one of ${usages[0]} or ${usages[1]}.` - } else { - msg += `${usages[0]}.` - } - - throw new TypeError(msg) - } -} diff --git a/src/runtime/node/aeskw.ts b/src/runtime/node/aeskw.ts index 74265e65e3..c6083a4edd 100644 --- a/src/runtime/node/aeskw.ts +++ b/src/runtime/node/aeskw.ts @@ -4,7 +4,8 @@ import { JOSENotSupported } from '../../util/errors.js' import type { AesKwUnwrapFunction, AesKwWrapFunction } from '../interfaces.d' import { concat } from '../../lib/buffer_utils.js' import getSecretKey from './secret_key.js' -import { isCryptoKey, getKeyObject } from './webcrypto.js' +import { isCryptoKey } from './webcrypto.js' +import { checkEncCryptoKey } from '../../lib/crypto_key.js' import isKeyObject from './is_key_object.js' import invalidKeyInput from './invalid_key_input.js' import supported from './ciphers.js' @@ -23,7 +24,8 @@ function ensureKeyObject(key: unknown, alg: string, usage: KeyUsage) { return getSecretKey(key) } if (isCryptoKey(key)) { - return getKeyObject(key, alg, usage) + checkEncCryptoKey(key, alg, usage) + return KeyObject.from(key) } throw new TypeError(invalidKeyInput(key, 'KeyObject', 'CryptoKey', 'Uint8Array')) diff --git a/src/runtime/node/asn1.ts b/src/runtime/node/asn1.ts index 8864e7351a..8e1de5f0e2 100644 --- a/src/runtime/node/asn1.ts +++ b/src/runtime/node/asn1.ts @@ -1,6 +1,6 @@ import { createPrivateKey, createPublicKey, KeyObject } from 'crypto' import { Buffer } from 'buffer' -import { isCryptoKey, getKeyObject } from './webcrypto.js' +import { isCryptoKey } from './webcrypto.js' import type { PEMExportFunction, PEMImportFunction } from '../interfaces.d' import isKeyObject from './is_key_object.js' import invalidKeyInput from './invalid_key_input.js' @@ -15,7 +15,7 @@ const genericExport = ( if (!key.extractable) { throw new TypeError('CryptoKey is not extractable') } - keyObject = getKeyObject(key) + keyObject = KeyObject.from(key) } else if (isKeyObject(key)) { keyObject = key } else { diff --git a/src/runtime/node/decrypt.ts b/src/runtime/node/decrypt.ts index 19b823acdd..0f354a2af4 100644 --- a/src/runtime/node/decrypt.ts +++ b/src/runtime/node/decrypt.ts @@ -1,5 +1,5 @@ -import { createDecipheriv } from 'crypto' -import type { KeyObject, CipherGCMTypes } from 'crypto' +import { createDecipheriv, KeyObject } from 'crypto' +import type { CipherGCMTypes } from 'crypto' import type { DecryptFunction } from '../interfaces.d' import checkIvLength from '../../lib/check_iv_length.js' @@ -8,7 +8,8 @@ import { concat } from '../../lib/buffer_utils.js' import { JOSENotSupported, JWEDecryptionFailed } from '../../util/errors.js' import timingSafeEqual from './timing_safe_equal.js' import cbcTag from './cbc_tag.js' -import { isCryptoKey, getKeyObject } from './webcrypto.js' +import { isCryptoKey } from './webcrypto.js' +import { checkEncCryptoKey } from '../../lib/crypto_key.js' import isKeyObject from './is_key_object.js' import invalidKeyInput from './invalid_key_input.js' import supported from './ciphers.js' @@ -98,7 +99,8 @@ const decrypt: DecryptFunction = async ( ) => { let key: KeyObject | Uint8Array if (isCryptoKey(cek)) { - key = getKeyObject(cek, enc, 'decrypt') + checkEncCryptoKey(cek, enc, 'decrypt') + key = KeyObject.from(cek) } else if (cek instanceof Uint8Array || isKeyObject(cek)) { key = cek } else { diff --git a/src/runtime/node/ecdhes.ts b/src/runtime/node/ecdhes.ts index 56fd9415dc..9f8f961c3e 100644 --- a/src/runtime/node/ecdhes.ts +++ b/src/runtime/node/ecdhes.ts @@ -1,5 +1,4 @@ -import type { KeyObject } from 'crypto' -import { diffieHellman, generateKeyPair as generateKeyPairCb } from 'crypto' +import { diffieHellman, generateKeyPair as generateKeyPairCb, KeyObject } from 'crypto' import { promisify } from 'util' import type { @@ -11,7 +10,8 @@ import getNamedCurve from './get_named_curve.js' import { encoder, concat, uint32be, lengthAndInput, concatKdf } from '../../lib/buffer_utils.js' import digest from './digest.js' import { JOSENotSupported } from '../../util/errors.js' -import { isCryptoKey, getKeyObject } from './webcrypto.js' +import { isCryptoKey } from './webcrypto.js' +import { checkEncCryptoKey } from '../../lib/crypto_key.js' import isKeyObject from './is_key_object.js' import invalidKeyInput from './invalid_key_input.js' @@ -27,7 +27,8 @@ export const deriveKey: EcdhESDeriveKeyFunction = async ( ) => { let publicKey: KeyObject if (isCryptoKey(publicKee)) { - publicKey = getKeyObject(publicKee, 'ECDH-ES') + checkEncCryptoKey(publicKee, 'ECDH-ES') + publicKey = KeyObject.from(publicKee) } else if (isKeyObject(publicKee)) { publicKey = publicKee } else { @@ -36,7 +37,8 @@ export const deriveKey: EcdhESDeriveKeyFunction = async ( let privateKey: KeyObject if (isCryptoKey(privateKee)) { - privateKey = getKeyObject(privateKee, 'ECDH-ES', 'deriveBits', 'deriveKey') + checkEncCryptoKey(privateKee, 'ECDH-ES', 'deriveBits', 'deriveKey') + privateKey = KeyObject.from(privateKee) } else if (isKeyObject(privateKee)) { privateKey = privateKee } else { @@ -57,7 +59,7 @@ export const deriveKey: EcdhESDeriveKeyFunction = async ( export const generateEpk: GenerateEpkFunction = async (kee: unknown) => { let key: KeyObject if (isCryptoKey(kee)) { - key = getKeyObject(kee) + key = KeyObject.from(kee) } else if (isKeyObject(kee)) { key = kee } else { diff --git a/src/runtime/node/encrypt.ts b/src/runtime/node/encrypt.ts index bde6933a72..ffc5478f06 100644 --- a/src/runtime/node/encrypt.ts +++ b/src/runtime/node/encrypt.ts @@ -1,12 +1,13 @@ -import { createCipheriv } from 'crypto' -import type { KeyObject, CipherGCMTypes } from 'crypto' +import { createCipheriv, KeyObject } from 'crypto' +import type { CipherGCMTypes } from 'crypto' import type { EncryptFunction } from '../interfaces.d' import checkIvLength from '../../lib/check_iv_length.js' import checkCekLength from './check_cek_length.js' import { concat } from '../../lib/buffer_utils.js' import cbcTag from './cbc_tag.js' -import { isCryptoKey, getKeyObject } from './webcrypto.js' +import { isCryptoKey } from './webcrypto.js' +import { checkEncCryptoKey } from '../../lib/crypto_key.js' import isKeyObject from './is_key_object.js' import invalidKeyInput from './invalid_key_input.js' import { JOSENotSupported } from '../../util/errors.js' @@ -75,7 +76,8 @@ const encrypt: EncryptFunction = async ( ) => { let key: KeyObject | Uint8Array if (isCryptoKey(cek)) { - key = getKeyObject(cek, enc, 'encrypt') + checkEncCryptoKey(cek, enc, 'encrypt') + key = KeyObject.from(cek) } else if (cek instanceof Uint8Array || isKeyObject(cek)) { key = cek } else { diff --git a/src/runtime/node/get_named_curve.ts b/src/runtime/node/get_named_curve.ts index 9a34149cff..4206542d1b 100644 --- a/src/runtime/node/get_named_curve.ts +++ b/src/runtime/node/get_named_curve.ts @@ -1,8 +1,7 @@ import { Buffer } from 'buffer' -import type { KeyObject } from 'crypto' -import { createPublicKey } from 'crypto' +import { createPublicKey, KeyObject } from 'crypto' import { JOSENotSupported } from '../../util/errors.js' -import { isCryptoKey, getKeyObject } from './webcrypto.js' +import { isCryptoKey } from './webcrypto.js' import isKeyObject from './is_key_object.js' import invalidKeyInput from './invalid_key_input.js' @@ -31,7 +30,7 @@ const namedCurveToJOSE = (namedCurve: string) => { const getNamedCurve = (kee: unknown, raw?: boolean): string => { let key: KeyObject if (isCryptoKey(kee)) { - key = getKeyObject(kee) + key = KeyObject.from(kee) } else if (isKeyObject(kee)) { key = kee } else { diff --git a/src/runtime/node/get_sign_verify_key.ts b/src/runtime/node/get_sign_verify_key.ts index 6e229f5e76..d20fb8b278 100644 --- a/src/runtime/node/get_sign_verify_key.ts +++ b/src/runtime/node/get_sign_verify_key.ts @@ -1,5 +1,6 @@ -import * as crypto from 'crypto' -import { isCryptoKey, getKeyObject } from './webcrypto.js' +import { KeyObject } from 'crypto' +import { isCryptoKey } from './webcrypto.js' +import { checkSigCryptoKey } from '../../lib/crypto_key.js' import getSecretKey from './secret_key.js' import invalidKeyInput from './invalid_key_input.js' @@ -10,11 +11,12 @@ export default function getSignVerifyKey(alg: string, key: unknown, usage: KeyUs } return getSecretKey(key) } - if (key instanceof crypto.KeyObject) { + if (key instanceof KeyObject) { return key } if (isCryptoKey(key)) { - return getKeyObject(key, alg, usage) + checkSigCryptoKey(key, alg, usage) + return KeyObject.from(key) } throw new TypeError(invalidKeyInput(key, 'KeyObject', 'CryptoKey', 'Uint8Array')) } diff --git a/src/runtime/node/global.ts b/src/runtime/node/global.ts new file mode 100644 index 0000000000..d3d7a4f299 --- /dev/null +++ b/src/runtime/node/global.ts @@ -0,0 +1,6 @@ +export function isCloudflareWorkers() { + return false +} +export function isNodeJs() { + return true +} diff --git a/src/runtime/node/key_to_jwk.ts b/src/runtime/node/key_to_jwk.ts index ce5af6a9fc..401d4809b9 100644 --- a/src/runtime/node/key_to_jwk.ts +++ b/src/runtime/node/key_to_jwk.ts @@ -5,7 +5,7 @@ import { encode as base64url } from './base64url.js' import Asn1SequenceDecoder from './asn1_sequence_decoder.js' import { JOSENotSupported } from '../../util/errors.js' import getNamedCurve from './get_named_curve.js' -import { isCryptoKey, getKeyObject } from './webcrypto.js' +import { isCryptoKey } from './webcrypto.js' import isKeyObject from './is_key_object.js' import invalidKeyInput from './invalid_key_input.js' @@ -22,7 +22,7 @@ const keyToJWK: JWKExportFunction = (key: unknown): JWK => { if (!key.extractable) { throw new TypeError('CryptoKey is not extractable') } - keyObject = getKeyObject(key) + keyObject = KeyObject.from(key) } else if (isKeyObject(key)) { keyObject = key } else if (key instanceof Uint8Array) { diff --git a/src/runtime/node/pbes2kw.ts b/src/runtime/node/pbes2kw.ts index b4826ba01b..91569c56c6 100644 --- a/src/runtime/node/pbes2kw.ts +++ b/src/runtime/node/pbes2kw.ts @@ -1,12 +1,13 @@ import { promisify } from 'util' -import { pbkdf2 as pbkdf2cb } from 'crypto' +import { KeyObject, pbkdf2 as pbkdf2cb } from 'crypto' import type { Pbes2KWDecryptFunction, Pbes2KWEncryptFunction } from '../interfaces.d' import random from './random.js' import { p2s as concatSalt } from '../../lib/buffer_utils.js' import { encode as base64url } from './base64url.js' import { wrap, unwrap } from './aeskw.js' import checkP2s from '../../lib/check_p2s.js' -import { isCryptoKey, getKeyObject } from './webcrypto.js' +import { isCryptoKey } from './webcrypto.js' +import { checkEncCryptoKey } from '../../lib/crypto_key.js' import isKeyObject from './is_key_object.js' import invalidKeyInput from './invalid_key_input.js' @@ -20,7 +21,8 @@ function getPassword(key: unknown, alg: string) { return key } if (isCryptoKey(key)) { - return getKeyObject(key, alg, 'deriveBits', 'deriveKey').export() + checkEncCryptoKey(key, alg, 'deriveBits', 'deriveKey') + return KeyObject.from(key).export() } throw new TypeError(invalidKeyInput(key, 'KeyObject', 'CryptoKey', 'Uint8Array')) } diff --git a/src/runtime/node/rsaes.ts b/src/runtime/node/rsaes.ts index 075fe38bf8..052c1a4024 100644 --- a/src/runtime/node/rsaes.ts +++ b/src/runtime/node/rsaes.ts @@ -1,7 +1,8 @@ import { KeyObject, publicEncrypt, constants, privateDecrypt } from 'crypto' import type { RsaEsDecryptFunction, RsaEsEncryptFunction } from '../interfaces.d' import checkModulusLength from './check_modulus_length.js' -import { isCryptoKey, getKeyObject } from './webcrypto.js' +import { isCryptoKey } from './webcrypto.js' +import { checkEncCryptoKey } from '../../lib/crypto_key.js' import isKeyObject from './is_key_object.js' import invalidKeyInput from './invalid_key_input.js' @@ -46,7 +47,8 @@ function ensureKeyObject(key: unknown, alg: string, ...usages: KeyUsage[]) { return key } if (isCryptoKey(key)) { - return getKeyObject(key, alg, ...usages) + checkEncCryptoKey(key, alg, ...usages) + return KeyObject.from(key) } throw new TypeError(invalidKeyInput(key, 'KeyObject', 'CryptoKey')) } diff --git a/src/runtime/node/webcrypto.ts b/src/runtime/node/webcrypto.ts index c8916b7f45..f981db6511 100644 --- a/src/runtime/node/webcrypto.ts +++ b/src/runtime/node/webcrypto.ts @@ -10,197 +10,3 @@ export const isCryptoKey = util.types.isCryptoKey ? (obj: unknown): obj is CryptoKey => util.types.isCryptoKey(obj) : // @ts-expect-error (obj: unknown): obj is CryptoKey => false - -function getHashLength(hash: KeyAlgorithm) { - return parseInt(hash?.name.substr(4), 10) -} - -function getNamedCurve(alg: string) { - switch (alg) { - case 'ES256': - return 'P-256' - case 'ES384': - return 'P-384' - case 'ES512': - return 'P-521' - } -} - -export function checkCryptoKey(key: CryptoKey, alg?: string, ...usages: KeyUsage[]) { - switch (alg) { - case undefined: - break - case 'HS256': - case 'HS384': - case 'HS512': { - if (key.algorithm.name !== 'HMAC') { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.name must be HMAC.`, - ) - } - - const expected = parseInt(alg.substr(2), 10) - const actual = getHashLength((key.algorithm).hash) - if (actual !== expected) { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.hash must be SHA-${expected}.`, - ) - } - break - } - case 'RS256': - case 'RS384': - case 'RS512': { - if (key.algorithm.name !== 'RSASSA-PKCS1-v1_5') { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.name must be RSASSA-PKCS1-v1_5.`, - ) - } - - const expected = parseInt(alg.substr(2), 10) - const actual = getHashLength((key.algorithm).hash) - if (actual !== expected) { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.hash must be SHA-${expected}.`, - ) - } - break - } - case 'PS256': - case 'PS384': - case 'PS512': { - if (key.algorithm.name !== 'RSA-PSS') { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.name must be RSA-PSS.`, - ) - } - - const expected = parseInt(alg.substr(2), 10) - const actual = getHashLength((key.algorithm).hash) - if (actual !== expected) { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.hash must be SHA-${expected}.`, - ) - } - break - } - case 'EdDSA': { - if (key.algorithm.name !== 'NODE-ED25519' && key.algorithm.name !== 'NODE-ED448') { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.name must be NODE-ED25519 or NODE-ED448.`, - ) - } - break - } - case 'ES256': - case 'ES384': - case 'ES512': { - if (key.algorithm.name !== 'ECDSA') { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.name must be ECDSA.`, - ) - } - - const expected = getNamedCurve(alg) - const actual = (key.algorithm).namedCurve - if (actual !== expected) { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.namedCurve must be ${expected}.`, - ) - } - break - } - case 'A128GCM': - case 'A192GCM': - case 'A256GCM': { - if (key.algorithm.name !== 'AES-GCM') { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.name must be AES-GCM.`, - ) - } - - const expected = parseInt(alg.substr(1, 3), 10) - const actual = (key.algorithm).length - if (actual !== expected) { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.length must be ${expected}.`, - ) - } - break - } - case 'A128KW': - case 'A192KW': - case 'A256KW': { - if (key.algorithm.name !== 'AES-KW') { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.name must be AES-KW.`, - ) - } - - const expected = parseInt(alg.substr(1, 3), 10) - const actual = (key.algorithm).length - if (actual !== expected) { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.length must be ${expected}.`, - ) - } - break - } - case 'ECDH-ES': - if (key.algorithm.name !== 'ECDH') { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.name must be ECDH.`, - ) - } - break - case 'PBES2-HS256+A128KW': - case 'PBES2-HS384+A192KW': - case 'PBES2-HS512+A256KW': - if (key.algorithm.name !== 'PBKDF2') { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.name must be PBKDF2.`, - ) - } - break - case 'RSA-OAEP': - case 'RSA-OAEP-256': - case 'RSA-OAEP-384': - case 'RSA-OAEP-512': { - if (key.algorithm.name !== 'RSA-OAEP') { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.name must be RSA-OAEP.`, - ) - } - - const expected = parseInt(alg.substr(9), 10) || 1 - const actual = getHashLength((key.algorithm).hash) - if (actual !== expected) { - throw new TypeError( - `CryptoKey does not support this operation, its algorithm.hash must be SHA-${expected}.`, - ) - } - break - } - default: - throw new TypeError('CryptoKey does not support this operation') - } - - if (usages.length && !usages.some((expected) => key.usages.includes(expected))) { - let msg = 'CryptoKey does not support this operation, its usages must include ' - if (usages.length > 2) { - const last = usages.pop() - msg += `one of ${usages.join(', ')}, or ${last}.` - } else if (usages.length === 2) { - msg += `one of ${usages[0]} or ${usages[1]}.` - } else { - msg += `${usages[0]}.` - } - - throw new TypeError(msg) - } -} - -export function getKeyObject(key: CryptoKey, alg?: string, ...usages: KeyUsage[]) { - checkCryptoKey(key, alg, ...usages) - return crypto.KeyObject.from(key) -}