From dac91a9df65c82c79a683b3f4d12bdc8f9aa27d4 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Wed, 30 Jul 2025 12:02:01 +0200 Subject: [PATCH] crypto: prepare webcrypto key import/export for modern algorithms --- lib/internal/crypto/aes.js | 4 +- lib/internal/crypto/cfrg.js | 2 + lib/internal/crypto/ec.js | 2 + lib/internal/crypto/keys.js | 10 +- lib/internal/crypto/mac.js | 2 +- lib/internal/crypto/rsa.js | 4 +- lib/internal/crypto/util.js | 16 +++ lib/internal/crypto/webcrypto.js | 182 +++++++++++++++++-------------- 8 files changed, 125 insertions(+), 97 deletions(-) diff --git a/lib/internal/crypto/aes.js b/lib/internal/crypto/aes.js index d2f1111a7155bd..b7d1abf4a85daf 100644 --- a/lib/internal/crypto/aes.js +++ b/lib/internal/crypto/aes.js @@ -285,9 +285,7 @@ function aesImportKey( break; } default: - throw lazyDOMException( - `Unable to import AES key with format ${format}`, - 'NotSupportedError'); + return undefined; } if (length === undefined) { diff --git a/lib/internal/crypto/cfrg.js b/lib/internal/crypto/cfrg.js index 34d8e294163fc7..e8af5750a865fd 100644 --- a/lib/internal/crypto/cfrg.js +++ b/lib/internal/crypto/cfrg.js @@ -324,6 +324,8 @@ function cfrgImportKey( keyObject = createCFRGRawKey(name, keyData, true); break; } + default: + return undefined; } if (keyObject.asymmetricKeyType !== name.toLowerCase()) { diff --git a/lib/internal/crypto/ec.js b/lib/internal/crypto/ec.js index 6c73344ef17e1c..f4ea317b86ee73 100644 --- a/lib/internal/crypto/ec.js +++ b/lib/internal/crypto/ec.js @@ -250,6 +250,8 @@ function ecImportKey( keyObject = createECPublicKeyRaw(namedCurve, keyData); break; } + default: + return undefined; } switch (algorithm.name) { diff --git a/lib/internal/crypto/keys.js b/lib/internal/crypto/keys.js index 7529254685fb17..7f21dd671cbc68 100644 --- a/lib/internal/crypto/keys.js +++ b/lib/internal/crypto/keys.js @@ -922,15 +922,11 @@ function importGenericSecretKey( keyObject = createSecretKey(keyData); break; } + default: + return undefined; } - if (keyObject) { - return new InternalCryptoKey(keyObject, { name }, keyUsages, false); - } - - throw lazyDOMException( - `Unable to import ${name} key with format ${format}`, - 'NotSupportedError'); + return new InternalCryptoKey(keyObject, { name }, keyUsages, false); } module.exports = { diff --git a/lib/internal/crypto/mac.js b/lib/internal/crypto/mac.js index 3e232fe1ca6491..0f9b1f9618d260 100644 --- a/lib/internal/crypto/mac.js +++ b/lib/internal/crypto/mac.js @@ -143,7 +143,7 @@ function hmacImportKey( break; } default: - throw lazyDOMException(`Unable to import HMAC key with format ${format}`); + return undefined; } const { length } = keyObject[kHandle].keyDetail({}); diff --git a/lib/internal/crypto/rsa.js b/lib/internal/crypto/rsa.js index a3ef89ed6f2e7e..bf6c341c5a723d 100644 --- a/lib/internal/crypto/rsa.js +++ b/lib/internal/crypto/rsa.js @@ -305,9 +305,7 @@ function rsaImportKey( break; } default: - throw lazyDOMException( - `Unable to import RSA key with format ${format}`, - 'NotSupportedError'); + return undefined; } if (keyObject.asymmetricKeyType !== 'rsa') { diff --git a/lib/internal/crypto/util.js b/lib/internal/crypto/util.js index 2eba29333bcba4..c7e0c31a4cc609 100644 --- a/lib/internal/crypto/util.js +++ b/lib/internal/crypto/util.js @@ -189,6 +189,20 @@ const kSupportedAlgorithms = { 'Ed25519': null, 'X25519': null, }, + 'exportKey': { + 'RSASSA-PKCS1-v1_5': null, + 'RSA-PSS': null, + 'RSA-OAEP': null, + 'ECDSA': null, + 'ECDH': null, + 'HMAC': null, + 'AES-CTR': null, + 'AES-CBC': null, + 'AES-GCM': null, + 'AES-KW': null, + 'Ed25519': null, + 'X25519': null, + }, 'sign': { 'RSASSA-PKCS1-v1_5': null, 'RSA-PSS': 'RsaPssParams', @@ -259,12 +273,14 @@ const experimentalAlgorithms = ObjectEntries({ generateKey: null, importKey: null, deriveBits: 'EcdhKeyDeriveParams', + exportKey: null, }, 'Ed448': { generateKey: null, sign: 'Ed448Params', verify: 'Ed448Params', importKey: null, + exportKey: null, }, }); diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js index e39035c46209f0..82bdc29f50a4d4 100644 --- a/lib/internal/crypto/webcrypto.js +++ b/lib/internal/crypto/webcrypto.js @@ -322,19 +322,13 @@ async function exportKeySpki(key) { case 'RSA-PSS': // Fall through case 'RSA-OAEP': - if (key.type === 'public') { - return require('internal/crypto/rsa') - .rsaExportKey(key, kWebCryptoKeyFormatSPKI); - } - break; + return require('internal/crypto/rsa') + .rsaExportKey(key, kWebCryptoKeyFormatSPKI); case 'ECDSA': // Fall through case 'ECDH': - if (key.type === 'public') { - return require('internal/crypto/ec') - .ecExportKey(key, kWebCryptoKeyFormatSPKI); - } - break; + return require('internal/crypto/ec') + .ecExportKey(key, kWebCryptoKeyFormatSPKI); case 'Ed25519': // Fall through case 'Ed448': @@ -342,16 +336,11 @@ async function exportKeySpki(key) { case 'X25519': // Fall through case 'X448': - if (key.type === 'public') { - return require('internal/crypto/cfrg') - .cfrgExportKey(key, kWebCryptoKeyFormatSPKI); - } - break; + return require('internal/crypto/cfrg') + .cfrgExportKey(key, kWebCryptoKeyFormatSPKI); + default: + return undefined; } - - throw lazyDOMException( - `Unable to export a raw ${key.algorithm.name} ${key.type} key`, - 'InvalidAccessError'); } async function exportKeyPkcs8(key) { @@ -361,19 +350,13 @@ async function exportKeyPkcs8(key) { case 'RSA-PSS': // Fall through case 'RSA-OAEP': - if (key.type === 'private') { - return require('internal/crypto/rsa') - .rsaExportKey(key, kWebCryptoKeyFormatPKCS8); - } - break; + return require('internal/crypto/rsa') + .rsaExportKey(key, kWebCryptoKeyFormatPKCS8); case 'ECDSA': // Fall through case 'ECDH': - if (key.type === 'private') { - return require('internal/crypto/ec') - .ecExportKey(key, kWebCryptoKeyFormatPKCS8); - } - break; + return require('internal/crypto/ec') + .ecExportKey(key, kWebCryptoKeyFormatPKCS8); case 'Ed25519': // Fall through case 'Ed448': @@ -381,28 +364,20 @@ async function exportKeyPkcs8(key) { case 'X25519': // Fall through case 'X448': - if (key.type === 'private') { - return require('internal/crypto/cfrg') - .cfrgExportKey(key, kWebCryptoKeyFormatPKCS8); - } - break; + return require('internal/crypto/cfrg') + .cfrgExportKey(key, kWebCryptoKeyFormatPKCS8); + default: + return undefined; } - - throw lazyDOMException( - `Unable to export a pkcs8 ${key.algorithm.name} ${key.type} key`, - 'InvalidAccessError'); } -async function exportKeyRaw(key) { +async function exportKeyRawPublic(key) { switch (key.algorithm.name) { case 'ECDSA': // Fall through case 'ECDH': - if (key.type === 'public') { - return require('internal/crypto/ec') - .ecExportKey(key, kWebCryptoKeyFormatRaw); - } - break; + return require('internal/crypto/ec') + .ecExportKey(key, kWebCryptoKeyFormatRaw); case 'Ed25519': // Fall through case 'Ed448': @@ -410,11 +385,15 @@ async function exportKeyRaw(key) { case 'X25519': // Fall through case 'X448': - if (key.type === 'public') { - return require('internal/crypto/cfrg') - .cfrgExportKey(key, kWebCryptoKeyFormatRaw); - } - break; + return require('internal/crypto/cfrg') + .cfrgExportKey(key, kWebCryptoKeyFormatRaw); + default: + return undefined; + } +} + +async function exportKeyRawSecret(key) { + switch (key.algorithm.name) { case 'AES-CTR': // Fall through case 'AES-CBC': @@ -424,51 +403,46 @@ async function exportKeyRaw(key) { case 'AES-KW': // Fall through case 'HMAC': - return key[kKeyObject].export().buffer; + return key[kKeyObject][kHandle].export().buffer; + default: + return undefined; } - - throw lazyDOMException( - `Unable to export a raw ${key.algorithm.name} ${key.type} key`, - 'InvalidAccessError'); } async function exportKeyJWK(key) { - const jwk = key[kKeyObject][kHandle].exportJwk({ + const parameters = { key_ops: key.usages, ext: key.extractable, - }, true); + }; switch (key.algorithm.name) { case 'RSASSA-PKCS1-v1_5': - jwk.alg = normalizeHashName( + parameters.alg = normalizeHashName( key.algorithm.hash.name, normalizeHashName.kContextJwkRsa); - return jwk; + break; case 'RSA-PSS': - jwk.alg = normalizeHashName( + parameters.alg = normalizeHashName( key.algorithm.hash.name, normalizeHashName.kContextJwkRsaPss); - return jwk; + break; case 'RSA-OAEP': - jwk.alg = normalizeHashName( + parameters.alg = normalizeHashName( key.algorithm.hash.name, normalizeHashName.kContextJwkRsaOaep); - return jwk; + break; case 'ECDSA': // Fall through case 'ECDH': - jwk.crv ||= key.algorithm.namedCurve; - return jwk; + // Fall through case 'X25519': // Fall through case 'X448': - jwk.crv ||= key.algorithm.name; - return jwk; + break; case 'Ed25519': // Fall through case 'Ed448': - jwk.crv ||= key.algorithm.name; - jwk.alg = key.algorithm.name; - return jwk; + parameters.alg = key.algorithm.name; + break; case 'AES-CTR': // Fall through case 'AES-CBC': @@ -476,19 +450,19 @@ async function exportKeyJWK(key) { case 'AES-GCM': // Fall through case 'AES-KW': - jwk.alg = require('internal/crypto/aes') + parameters.alg = require('internal/crypto/aes') .getAlgorithmName(key.algorithm.name, key.algorithm.length); - return jwk; + break; case 'HMAC': - jwk.alg = normalizeHashName( + parameters.alg = normalizeHashName( key.algorithm.hash.name, normalizeHashName.kContextJwkHmac); - return jwk; + break; default: - // Fall through + return undefined; } - throw lazyDOMException('Not yet supported', 'NotSupportedError'); + return key[kKeyObject][kHandle].exportJwk(parameters, true); } async function exportKey(format, key) { @@ -506,17 +480,55 @@ async function exportKey(format, key) { context: '2nd argument', }); + try { + normalizeAlgorithm(key.algorithm, 'exportKey'); + } catch { + throw lazyDOMException( + `${key.algorithm.name} key export is not supported`, 'NotSupportedError'); + } + if (!key.extractable) throw lazyDOMException('key is not extractable', 'InvalidAccessException'); + let result; switch (format) { - case 'spki': return exportKeySpki(key); - case 'pkcs8': return exportKeyPkcs8(key); - case 'jwk': return exportKeyJWK(key); - case 'raw': return exportKeyRaw(key); + case 'spki': { + if (key.type === 'public') { + result = await exportKeySpki(key); + } + break; + } + case 'pkcs8': { + if (key.type === 'private') { + result = await exportKeyPkcs8(key); + } + break; + } + case 'jwk': { + result = await exportKeyJWK(key); + break; + } + case 'raw': { + if (key.type === 'secret') { + result = await exportKeyRawSecret(key); + break; + } + + if (key.type === 'public') { + result = await exportKeyRawPublic(key); + break; + } + break; + } } - throw lazyDOMException( - 'Export format is unsupported', 'NotSupportedError'); + + if (!result) { + throw lazyDOMException( + `Unable to export ${key.algorithm.name} ${key.type} key using ${format} format`, + 'NotSupportedError'); + } + + return result; } async function importKey( @@ -603,8 +615,12 @@ async function importKey( extractable, keyUsages); break; - default: - throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); + } + + if (!result) { + throw lazyDOMException( + `Unable to import ${algorithm.name} using ${format} format`, + 'NotSupportedError'); } if ((result.type === 'secret' || result.type === 'private') && result.usages.length === 0) {