diff --git a/lib/internal/crypto/aes.js b/lib/internal/crypto/aes.js index c0765f75642189..cc443b575da46a 100644 --- a/lib/internal/crypto/aes.js +++ b/lib/internal/crypto/aes.js @@ -10,6 +10,8 @@ const { AESCipherJob, KeyObjectHandle, kCryptoJobAsync, + kKeyFormatJWK, + kKeyTypeSecret, kKeyVariantAES_CTR_128, kKeyVariantAES_CBC_128, kKeyVariantAES_GCM_128, @@ -30,7 +32,6 @@ const { const { hasAnyNotIn, jobPromise, - validateKeyOps, kHandle, kKeyObject, } = require('internal/crypto/util'); @@ -47,6 +48,10 @@ const { kAlgorithm, } = require('internal/crypto/keys'); +const { + validateJwk, +} = require('internal/crypto/webcrypto_util'); + const { generateKey: _generateKey, } = require('internal/crypto/keygen'); @@ -245,31 +250,11 @@ function aesImportKey( break; } case 'jwk': { - if (!keyData.kty) - throw lazyDOMException('Invalid keyData', 'DataError'); - - if (keyData.kty !== 'oct') - throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError'); - - if (usagesSet.size > 0 && - keyData.use !== undefined && - keyData.use !== 'enc') { - throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError'); - } - - validateKeyOps(keyData.key_ops, usagesSet); - - if (keyData.ext !== undefined && - keyData.ext === false && - extractable === true) { - throw lazyDOMException( - 'JWK "ext" Parameter and extractable mismatch', - 'DataError'); - } + validateJwk(keyData, 'oct', extractable, usagesSet, 'enc'); const handle = new KeyObjectHandle(); try { - handle.initJwk(keyData); + handle.init(kKeyTypeSecret, keyData, kKeyFormatJWK, null, null); } catch (err) { throw lazyDOMException( 'Invalid keyData', { name: 'DataError', cause: err }); diff --git a/lib/internal/crypto/cfrg.js b/lib/internal/crypto/cfrg.js index b943ba020750f9..58e8fb02943b78 100644 --- a/lib/internal/crypto/cfrg.js +++ b/lib/internal/crypto/cfrg.js @@ -6,15 +6,11 @@ const { TypedArrayPrototypeGetBuffer, } = primordials; -const { Buffer } = require('buffer'); - const { - KeyObjectHandle, SignJob, kCryptoJobAsync, kKeyFormatDER, - kKeyTypePrivate, - kKeyTypePublic, + kKeyFormatRawPublic, kSignJobModeSign, kSignJobModeVerify, kWebCryptoKeyFormatPKCS8, @@ -22,17 +18,10 @@ const { kWebCryptoKeyFormatSPKI, } = internalBinding('crypto'); -const { - codes: { - ERR_CRYPTO_INVALID_JWK, - }, -} = require('internal/errors'); - const { getUsagesUnion, hasAnyNotIn, jobPromise, - validateKeyOps, kHandle, kKeyObject, } = require('internal/crypto/util'); @@ -48,13 +37,16 @@ const { const { InternalCryptoKey, - PrivateKeyObject, - PublicKeyObject, - createPrivateKey, - createPublicKey, kKeyType, } = require('internal/crypto/keys'); +const { + importDerKey, + importJwkKey, + importRawKey, + validateJwk, +} = require('internal/crypto/webcrypto_util'); + const generateKeyPair = promisify(_generateKeyPair); function verifyAcceptableCfrgKeyUse(name, isPublic, usages) { @@ -81,39 +73,6 @@ function verifyAcceptableCfrgKeyUse(name, isPublic, usages) { } } -function createCFRGRawKey(name, keyData, isPublic) { - const handle = new KeyObjectHandle(); - - switch (name) { - case 'Ed25519': - case 'X25519': - if (keyData.byteLength !== 32) { - throw lazyDOMException( - `${name} raw keys must be exactly 32-bytes`, 'DataError'); - } - break; - case 'Ed448': - if (keyData.byteLength !== 57) { - throw lazyDOMException( - `${name} raw keys must be exactly 57-bytes`, 'DataError'); - } - break; - case 'X448': - if (keyData.byteLength !== 56) { - throw lazyDOMException( - `${name} raw keys must be exactly 56-bytes`, 'DataError'); - } - break; - } - - const keyType = isPublic ? kKeyTypePublic : kKeyTypePrivate; - if (!handle.initEDRaw(name, keyData, keyType)) { - throw lazyDOMException('Invalid keyData', 'DataError'); - } - - return isPublic ? new PublicKeyObject(handle) : new PrivateKeyObject(handle); -} - async function cfrgGenerateKey(algorithm, extractable, keyUsages) { const { name } = algorithm; @@ -243,113 +202,36 @@ function cfrgImportKey( } case 'spki': { verifyAcceptableCfrgKeyUse(name, true, usagesSet); - try { - keyObject = createPublicKey({ - key: keyData, - format: 'der', - type: 'spki', - }); - } catch (err) { - throw lazyDOMException( - 'Invalid keyData', { name: 'DataError', cause: err }); - } + keyObject = importDerKey(keyData, true); break; } case 'pkcs8': { verifyAcceptableCfrgKeyUse(name, false, usagesSet); - try { - keyObject = createPrivateKey({ - key: keyData, - format: 'der', - type: 'pkcs8', - }); - } catch (err) { - throw lazyDOMException( - 'Invalid keyData', { name: 'DataError', cause: err }); - } + keyObject = importDerKey(keyData, false); break; } case 'jwk': { - if (!keyData.kty) - throw lazyDOMException('Invalid keyData', 'DataError'); - if (keyData.kty !== 'OKP') - throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError'); + const expectedUse = (name === 'X25519' || name === 'X448') ? 'enc' : 'sig'; + validateJwk(keyData, 'OKP', extractable, usagesSet, expectedUse); + if (keyData.crv !== name) throw lazyDOMException( 'JWK "crv" Parameter and algorithm name mismatch', 'DataError'); - const isPublic = keyData.d === undefined; - - if (usagesSet.size > 0 && keyData.use !== undefined) { - let checkUse; - switch (name) { - case 'Ed25519': - // Fall through - case 'Ed448': - checkUse = 'sig'; - break; - case 'X25519': - // Fall through - case 'X448': - checkUse = 'enc'; - break; - } - if (keyData.use !== checkUse) - throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError'); - } - - validateKeyOps(keyData.key_ops, usagesSet); - - if (keyData.ext !== undefined && - keyData.ext === false && - extractable === true) { - throw lazyDOMException( - 'JWK "ext" Parameter and extractable mismatch', - 'DataError'); - } if (keyData.alg !== undefined && (name === 'Ed25519' || name === 'Ed448')) { - if (keyData.alg !== name && keyData.alg !== 'EdDSA') { + if (keyData.alg !== name && keyData.alg !== 'EdDSA') throw lazyDOMException( - 'JWK "alg" does not match the requested algorithm', - 'DataError'); - } + 'JWK "alg" does not match the requested algorithm', 'DataError'); } - if (!isPublic && typeof keyData.x !== 'string') { - throw lazyDOMException('Invalid JWK', 'DataError'); - } - - verifyAcceptableCfrgKeyUse( - name, - isPublic, - usagesSet); - - try { - const publicKeyObject = createCFRGRawKey( - name, - Buffer.from(keyData.x, 'base64'), - true); - - if (isPublic) { - keyObject = publicKeyObject; - } else { - keyObject = createCFRGRawKey( - name, - Buffer.from(keyData.d, 'base64'), - false); - - if (!createPublicKey(keyObject).equals(publicKeyObject)) { - throw new ERR_CRYPTO_INVALID_JWK(); - } - } - } catch (err) { - throw lazyDOMException('Invalid keyData', { name: 'DataError', cause: err }); - } + const isPublic = keyData.d === undefined; + verifyAcceptableCfrgKeyUse(name, isPublic, usagesSet); + keyObject = importJwkKey(isPublic, keyData); break; } case 'raw': { verifyAcceptableCfrgKeyUse(name, true, usagesSet); - keyObject = createCFRGRawKey(name, keyData, true); + keyObject = importRawKey(true, keyData, kKeyFormatRawPublic, name); break; } default: @@ -381,6 +263,7 @@ async function eddsaSignVerify(key, data, algorithm, signature) { undefined, undefined, undefined, + undefined, data, undefined, undefined, diff --git a/lib/internal/crypto/chacha20_poly1305.js b/lib/internal/crypto/chacha20_poly1305.js index a2b7c1fb04fb89..2230097c4c5c9f 100644 --- a/lib/internal/crypto/chacha20_poly1305.js +++ b/lib/internal/crypto/chacha20_poly1305.js @@ -9,12 +9,13 @@ const { ChaCha20Poly1305CipherJob, KeyObjectHandle, kCryptoJobAsync, + kKeyFormatJWK, + kKeyTypeSecret, } = internalBinding('crypto'); const { hasAnyNotIn, jobPromise, - validateKeyOps, kHandle, kKeyObject, } = require('internal/crypto/util'); @@ -30,6 +31,10 @@ const { createSecretKey, } = require('internal/crypto/keys'); +const { + validateJwk, +} = require('internal/crypto/webcrypto_util'); + const { randomBytes: _randomBytes, } = require('internal/crypto/random'); @@ -107,31 +112,11 @@ function c20pImportKey( break; } case 'jwk': { - if (!keyData.kty) - throw lazyDOMException('Invalid keyData', 'DataError'); - - if (keyData.kty !== 'oct') - throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError'); - - if (usagesSet.size > 0 && - keyData.use !== undefined && - keyData.use !== 'enc') { - throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError'); - } - - validateKeyOps(keyData.key_ops, usagesSet); - - if (keyData.ext !== undefined && - keyData.ext === false && - extractable === true) { - throw lazyDOMException( - 'JWK "ext" Parameter and extractable mismatch', - 'DataError'); - } + validateJwk(keyData, 'oct', extractable, usagesSet, 'enc'); const handle = new KeyObjectHandle(); try { - handle.initJwk(keyData); + handle.init(kKeyTypeSecret, keyData, kKeyFormatJWK, null, null); } catch (err) { throw lazyDOMException( 'Invalid keyData', { name: 'DataError', cause: err }); diff --git a/lib/internal/crypto/cipher.js b/lib/internal/crypto/cipher.js index 113713bc601014..c463ea41f763b3 100644 --- a/lib/internal/crypto/cipher.js +++ b/lib/internal/crypto/cipher.js @@ -64,7 +64,7 @@ const { StringDecoder } = require('string_decoder'); function rsaFunctionFor(method, defaultPadding, keyType) { return (options, buffer) => { - const { format, type, data, passphrase } = + const { format, type, data, passphrase, namedCurve } = keyType === 'private' ? preparePrivateKey(options) : preparePublicOrPrivateKey(options); @@ -76,8 +76,8 @@ function rsaFunctionFor(method, defaultPadding, keyType) { if (oaepLabel !== undefined) oaepLabel = getArrayBufferOrView(oaepLabel, 'key.oaepLabel', encoding); buffer = getArrayBufferOrView(buffer, 'buffer', encoding); - return method(data, format, type, passphrase, buffer, padding, oaepHash, - oaepLabel); + return method(data, format, type, passphrase, namedCurve, buffer, + padding, oaepHash, oaepLabel); }; } diff --git a/lib/internal/crypto/ec.js b/lib/internal/crypto/ec.js index a73b1c6c0bc4bf..3b0b35e8f0f822 100644 --- a/lib/internal/crypto/ec.js +++ b/lib/internal/crypto/ec.js @@ -11,7 +11,8 @@ const { SignJob, kCryptoJobAsync, kKeyFormatDER, - kKeyTypePrivate, + kKeyFormatRawPublic, + kKeyTypePublic, kSignJobModeSign, kSignJobModeVerify, kSigEncP1363, @@ -31,7 +32,6 @@ const { hasAnyNotIn, jobPromise, normalizeHashName, - validateKeyOps, kHandle, kKeyObject, kNamedCurveAliases, @@ -48,14 +48,17 @@ const { const { InternalCryptoKey, - PrivateKeyObject, - PublicKeyObject, - createPrivateKey, - createPublicKey, kAlgorithm, kKeyType, } = require('internal/crypto/keys'); +const { + importDerKey, + importJwkKey, + importRawKey, + validateJwk, +} = require('internal/crypto/webcrypto_util'); + const generateKeyPair = promisify(_generateKeyPair); function verifyAcceptableEcKeyUse(name, isPublic, usages) { @@ -78,16 +81,6 @@ function verifyAcceptableEcKeyUse(name, isPublic, usages) { } } -function createECPublicKeyRaw(namedCurve, keyData) { - const handle = new KeyObjectHandle(); - - if (!handle.initECRaw(kNamedCurveAliases[namedCurve], keyData)) { - throw lazyDOMException('Invalid keyData', 'DataError'); - } - - return new PublicKeyObject(handle); -} - async function ecGenerateKey(algorithm, extractable, keyUsages) { const { name, namedCurve } = algorithm; @@ -173,7 +166,8 @@ function ecExportKey(key, format) { }[key[kAlgorithm].namedCurve]) { const raw = handle.exportECPublicRaw(POINT_CONVERSION_UNCOMPRESSED); const tmp = new KeyObjectHandle(); - tmp.initECRaw(kNamedCurveAliases[key[kAlgorithm].namedCurve], raw); + tmp.init(kKeyTypePublic, raw, kKeyFormatRawPublic, + 'ec', null, key[kAlgorithm].namedCurve); spki = tmp.export(kKeyFormatDER, kWebCryptoKeyFormatSPKI); } return TypedArrayPrototypeGetBuffer(spki); @@ -211,63 +205,23 @@ function ecImportKey( } case 'spki': { verifyAcceptableEcKeyUse(name, true, usagesSet); - try { - keyObject = createPublicKey({ - key: keyData, - format: 'der', - type: 'spki', - }); - } catch (err) { - throw lazyDOMException( - 'Invalid keyData', { name: 'DataError', cause: err }); - } + keyObject = importDerKey(keyData, true); break; } case 'pkcs8': { verifyAcceptableEcKeyUse(name, false, usagesSet); - try { - keyObject = createPrivateKey({ - key: keyData, - format: 'der', - type: 'pkcs8', - }); - } catch (err) { - throw lazyDOMException( - 'Invalid keyData', { name: 'DataError', cause: err }); - } + keyObject = importDerKey(keyData, false); break; } case 'jwk': { - if (!keyData.kty) - throw lazyDOMException('Invalid keyData', 'DataError'); - if (keyData.kty !== 'EC') - throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError'); + const expectedUse = name === 'ECDH' ? 'enc' : 'sig'; + validateJwk(keyData, 'EC', extractable, usagesSet, expectedUse); + if (keyData.crv !== namedCurve) throw lazyDOMException( 'JWK "crv" does not match the requested algorithm', 'DataError'); - verifyAcceptableEcKeyUse( - name, - keyData.d === undefined, - usagesSet); - - if (usagesSet.size > 0 && keyData.use !== undefined) { - const checkUse = name === 'ECDH' ? 'enc' : 'sig'; - if (keyData.use !== checkUse) - throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError'); - } - - validateKeyOps(keyData.key_ops, usagesSet); - - if (keyData.ext !== undefined && - keyData.ext === false && - extractable === true) { - throw lazyDOMException( - 'JWK "ext" Parameter and extractable mismatch', - 'DataError'); - } - if (algorithm.name === 'ECDSA' && keyData.alg !== undefined) { let algNamedCurve; switch (keyData.alg) { @@ -281,24 +235,14 @@ function ecImportKey( 'DataError'); } - const handle = new KeyObjectHandle(); - let type; - try { - type = handle.initJwk(keyData, namedCurve); - } catch (err) { - throw lazyDOMException( - 'Invalid keyData', { name: 'DataError', cause: err }); - } - if (type === undefined) - throw lazyDOMException('Invalid keyData', 'DataError'); - keyObject = type === kKeyTypePrivate ? - new PrivateKeyObject(handle) : - new PublicKeyObject(handle); + const isPublic = keyData.d === undefined; + verifyAcceptableEcKeyUse(name, isPublic, usagesSet); + keyObject = importJwkKey(isPublic, keyData); break; } case 'raw': { verifyAcceptableEcKeyUse(name, true, usagesSet); - keyObject = createECPublicKeyRaw(namedCurve, keyData); + keyObject = importRawKey(true, keyData, kKeyFormatRawPublic, 'ec', namedCurve); break; } default: @@ -347,6 +291,7 @@ async function ecdsaSignVerify(key, data, { name, hash }, signature) { undefined, undefined, undefined, + undefined, data, hashname, undefined, // Salt length, not used with ECDSA diff --git a/lib/internal/crypto/kem.js b/lib/internal/crypto/kem.js index 43c7bde52ea99f..d38f8b1c59a050 100644 --- a/lib/internal/crypto/kem.js +++ b/lib/internal/crypto/kem.js @@ -42,6 +42,7 @@ function encapsulate(key, callback) { format: keyFormat, type: keyType, passphrase: keyPassphrase, + namedCurve: keyNamedCurve, } = preparePublicOrPrivateKey(key); const job = new KEMEncapsulateJob( @@ -49,7 +50,8 @@ function encapsulate(key, callback) { keyData, keyFormat, keyType, - keyPassphrase); + keyPassphrase, + keyNamedCurve); if (!callback) { const { 0: err, 1: result } = job.run(); @@ -79,6 +81,7 @@ function decapsulate(key, ciphertext, callback) { format: keyFormat, type: keyType, passphrase: keyPassphrase, + namedCurve: keyNamedCurve, } = preparePrivateKey(key); ciphertext = getArrayBufferOrView(ciphertext, 'ciphertext'); @@ -89,6 +92,7 @@ function decapsulate(key, ciphertext, callback) { keyFormat, keyType, keyPassphrase, + keyNamedCurve, ciphertext); if (!callback) { diff --git a/lib/internal/crypto/keys.js b/lib/internal/crypto/keys.js index 90f55a88a346b5..d66f03a4ebcea7 100644 --- a/lib/internal/crypto/keys.js +++ b/lib/internal/crypto/keys.js @@ -46,7 +46,6 @@ const { const { codes: { ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS, - ERR_CRYPTO_INVALID_JWK, ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE, ERR_ILLEGAL_CONSTRUCTOR, ERR_INVALID_ARG_TYPE, @@ -84,8 +83,6 @@ const { const { inspect } = require('internal/util/inspect'); -const { Buffer } = require('buffer'); - const kAlgorithm = Symbol('kAlgorithm'); const kExtractable = Symbol('kExtractable'); const kKeyType = Symbol('kKeyType'); @@ -630,225 +627,6 @@ function getKeyTypes(allowKeyObject, bufferOnly = false) { return types; } -function mlDsaPubLen(alg) { - switch (alg) { - case 'ML-DSA-44': return 1312; - case 'ML-DSA-65': return 1952; - case 'ML-DSA-87': return 2592; - } -} - -function getKeyObjectHandleFromJwk(key, ctx) { - validateObject(key, 'key'); - validateOneOf( - key.kty, 'key.kty', ['RSA', 'EC', 'OKP', 'AKP']); - const isPublic = ctx === kConsumePublic || ctx === kCreatePublic; - - if (key.kty === 'AKP') { - validateOneOf( - key.alg, 'key.alg', ['ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87']); - validateString(key.pub, 'key.pub'); - - let keyData; - if (isPublic) { - keyData = Buffer.from(key.pub, 'base64url'); - if (keyData.byteLength !== mlDsaPubLen(key.alg)) { - throw new ERR_CRYPTO_INVALID_JWK(); - } - } else { - validateString(key.priv, 'key.priv'); - keyData = Buffer.from(key.priv, 'base64url'); - if (keyData.byteLength !== 32) { - throw new ERR_CRYPTO_INVALID_JWK(); - } - } - - const handle = new KeyObjectHandle(); - - const keyType = isPublic ? kKeyTypePublic : kKeyTypePrivate; - if (!handle.initPqcRaw(key.alg, keyData, keyType)) { - throw new ERR_CRYPTO_INVALID_JWK(); - } - - return handle; - } - - if (key.kty === 'OKP') { - validateString(key.crv, 'key.crv'); - validateOneOf( - key.crv, 'key.crv', ['Ed25519', 'Ed448', 'X25519', 'X448']); - validateString(key.x, 'key.x'); - - if (!isPublic) - validateString(key.d, 'key.d'); - - let keyData; - if (isPublic) - keyData = Buffer.from(key.x, 'base64'); - else - keyData = Buffer.from(key.d, 'base64'); - - switch (key.crv) { - case 'Ed25519': - case 'X25519': - if (keyData.byteLength !== 32) { - throw new ERR_CRYPTO_INVALID_JWK(); - } - break; - case 'Ed448': - if (keyData.byteLength !== 57) { - throw new ERR_CRYPTO_INVALID_JWK(); - } - break; - case 'X448': - if (keyData.byteLength !== 56) { - throw new ERR_CRYPTO_INVALID_JWK(); - } - break; - } - - const handle = new KeyObjectHandle(); - - const keyType = isPublic ? kKeyTypePublic : kKeyTypePrivate; - if (!handle.initEDRaw(key.crv, keyData, keyType)) { - throw new ERR_CRYPTO_INVALID_JWK(); - } - - return handle; - } - - if (key.kty === 'EC') { - validateString(key.crv, 'key.crv'); - validateOneOf( - key.crv, 'key.crv', ['P-256', 'secp256k1', 'P-384', 'P-521']); - validateString(key.x, 'key.x'); - validateString(key.y, 'key.y'); - - const jwk = { - kty: key.kty, - crv: key.crv, - x: key.x, - y: key.y, - }; - - if (!isPublic) { - validateString(key.d, 'key.d'); - jwk.d = key.d; - } - - const handle = new KeyObjectHandle(); - const type = handle.initJwk(jwk, jwk.crv); - if (type === undefined) - throw new ERR_CRYPTO_INVALID_JWK(); - - return handle; - } - - // RSA - validateString(key.n, 'key.n'); - validateString(key.e, 'key.e'); - - const jwk = { - kty: key.kty, - n: key.n, - e: key.e, - }; - - if (!isPublic) { - validateString(key.d, 'key.d'); - validateString(key.p, 'key.p'); - validateString(key.q, 'key.q'); - validateString(key.dp, 'key.dp'); - validateString(key.dq, 'key.dq'); - validateString(key.qi, 'key.qi'); - jwk.d = key.d; - jwk.p = key.p; - jwk.q = key.q; - jwk.dp = key.dp; - jwk.dq = key.dq; - jwk.qi = key.qi; - } - - const handle = new KeyObjectHandle(); - const type = handle.initJwk(jwk); - if (type === undefined) - throw new ERR_CRYPTO_INVALID_JWK(); - - return handle; -} - - -function getKeyObjectHandleFromRaw(options, data, format) { - if (!isStringOrBuffer(data)) { - throw new ERR_INVALID_ARG_TYPE( - 'key.key', - ['ArrayBuffer', 'Buffer', 'TypedArray', 'DataView'], - data); - } - - const keyData = getArrayBufferOrView(data, 'key.key'); - - validateString(options.asymmetricKeyType, 'key.asymmetricKeyType'); - const asymmetricKeyType = options.asymmetricKeyType; - - const handle = new KeyObjectHandle(); - - switch (asymmetricKeyType) { - case 'ec': { - validateString(options.namedCurve, 'key.namedCurve'); - if (format === 'raw-public') { - if (!handle.initECRaw(options.namedCurve, keyData)) { - throw new ERR_INVALID_ARG_VALUE('key.key', keyData); - } - } else if (!handle.initECPrivateRaw(options.namedCurve, keyData)) { - throw new ERR_INVALID_ARG_VALUE('key.key', keyData); - } - return handle; - } - case 'ed25519': - case 'ed448': - case 'x25519': - case 'x448': { - const keyType = format === 'raw-public' ? kKeyTypePublic : kKeyTypePrivate; - if (!handle.initEDRaw(asymmetricKeyType, keyData, keyType)) { - throw new ERR_INVALID_ARG_VALUE('key.key', keyData); - } - return handle; - } - case 'rsa': - case 'rsa-pss': - case 'dsa': - case 'dh': - throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS( - format, `is not supported for ${asymmetricKeyType} keys`); - case 'ml-dsa-44': - case 'ml-dsa-65': - case 'ml-dsa-87': - case 'ml-kem-512': - case 'ml-kem-768': - case 'ml-kem-1024': - case 'slh-dsa-sha2-128f': - case 'slh-dsa-sha2-128s': - case 'slh-dsa-sha2-192f': - case 'slh-dsa-sha2-192s': - case 'slh-dsa-sha2-256f': - case 'slh-dsa-sha2-256s': - case 'slh-dsa-shake-128f': - case 'slh-dsa-shake-128s': - case 'slh-dsa-shake-192f': - case 'slh-dsa-shake-192s': - case 'slh-dsa-shake-256f': - case 'slh-dsa-shake-256s': { - const keyType = format === 'raw-public' ? kKeyTypePublic : kKeyTypePrivate; - if (!handle.initPqcRaw(asymmetricKeyType, keyData, keyType)) { - throw new ERR_INVALID_ARG_VALUE('key.key', keyData); - } - return handle; - } - default: - throw new ERR_INVALID_ARG_VALUE('asymmetricKeyType', asymmetricKeyType); - } -} function prepareAsymmetricKey(key, ctx) { if (isKeyObject(key)) { @@ -877,12 +655,25 @@ function prepareAsymmetricKey(key, ctx) { } if (format === 'jwk') { validateObject(data, 'key.key'); - return { data: getKeyObjectHandleFromJwk(data, ctx), format: 'jwk' }; + return { data, format: kKeyFormatJWK }; } else if (format === 'raw-public' || format === 'raw-private' || format === 'raw-seed') { + if (!isStringOrBuffer(data)) { + throw new ERR_INVALID_ARG_TYPE( + 'key.key', + ['ArrayBuffer', 'Buffer', 'TypedArray', 'DataView'], + data); + } + validateString(key.asymmetricKeyType, 'key.asymmetricKeyType'); + if (key.asymmetricKeyType === 'ec') { + validateString(key.namedCurve, 'key.namedCurve'); + } + const rawFormat = parseKeyFormat(format, undefined, 'options.format'); return { - data: getKeyObjectHandleFromRaw(key, data, format), - format, + data: getArrayBufferOrView(data, 'key.key'), + format: rawFormat, + type: key.asymmetricKeyType, + namedCurve: key.namedCurve ?? null, }; } @@ -949,28 +740,20 @@ function createSecretKey(key, encoding) { } function createPublicKey(key) { - const { format, type, data, passphrase } = + const { format, type, data, passphrase, namedCurve } = prepareAsymmetricKey(key, kCreatePublic); - let handle; - if (format === 'jwk' || format === 'raw-public') { - handle = data; - } else { - handle = new KeyObjectHandle(); - handle.init(kKeyTypePublic, data, format, type, passphrase); - } + const handle = new KeyObjectHandle(); + handle.init(kKeyTypePublic, data, format ?? null, + type ?? null, passphrase ?? null, namedCurve ?? null); return new PublicKeyObject(handle); } function createPrivateKey(key) { - const { format, type, data, passphrase } = + const { format, type, data, passphrase, namedCurve } = prepareAsymmetricKey(key, kCreatePrivate); - let handle; - if (format === 'jwk' || format === 'raw-private' || format === 'raw-seed') { - handle = data; - } else { - handle = new KeyObjectHandle(); - handle.init(kKeyTypePrivate, data, format, type, passphrase); - } + const handle = new KeyObjectHandle(); + handle.init(kKeyTypePrivate, data, format ?? null, + type ?? null, passphrase ?? null, namedCurve ?? null); return new PrivateKeyObject(handle); } diff --git a/lib/internal/crypto/mac.js b/lib/internal/crypto/mac.js index 1ad4e27c6a8d39..1689d19d4a0660 100644 --- a/lib/internal/crypto/mac.js +++ b/lib/internal/crypto/mac.js @@ -11,6 +11,8 @@ const { KeyObjectHandle, KmacJob, kCryptoJobAsync, + kKeyFormatJWK, + kKeyTypeSecret, kSignJobModeSign, kSignJobModeVerify, } = internalBinding('crypto'); @@ -20,7 +22,6 @@ const { hasAnyNotIn, jobPromise, normalizeHashName, - validateKeyOps, kHandle, kKeyObject, } = require('internal/crypto/util'); @@ -48,6 +49,10 @@ const { kAlgorithm, } = require('internal/crypto/keys'); +const { + validateJwk, +} = require('internal/crypto/webcrypto_util'); + const generateKey = promisify(_generateKey); async function hmacGenerateKey(algorithm, extractable, keyUsages) { @@ -143,27 +148,7 @@ function macImportKey( break; } case 'jwk': { - if (!keyData.kty) - throw lazyDOMException('Invalid keyData', 'DataError'); - - if (keyData.kty !== 'oct') - throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError'); - - if (usagesSet.size > 0 && - keyData.use !== undefined && - keyData.use !== 'sig') { - throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError'); - } - - validateKeyOps(keyData.key_ops, usagesSet); - - if (keyData.ext !== undefined && - keyData.ext === false && - extractable === true) { - throw lazyDOMException( - 'JWK "ext" Parameter and extractable mismatch', - 'DataError'); - } + validateJwk(keyData, 'oct', extractable, usagesSet, 'sig'); if (keyData.alg !== undefined) { const expected = isHmac ? @@ -177,7 +162,7 @@ function macImportKey( const handle = new KeyObjectHandle(); try { - handle.initJwk(keyData); + handle.init(kKeyTypeSecret, keyData, kKeyFormatJWK, null, null); } catch (err) { throw lazyDOMException( 'Invalid keyData', { name: 'DataError', cause: err }); diff --git a/lib/internal/crypto/ml_dsa.js b/lib/internal/crypto/ml_dsa.js index 4d90a80f0200f7..e6c70db034275f 100644 --- a/lib/internal/crypto/ml_dsa.js +++ b/lib/internal/crypto/ml_dsa.js @@ -7,33 +7,23 @@ const { TypedArrayPrototypeGetByteLength, } = primordials; -const { Buffer } = require('buffer'); - const { - KeyObjectHandle, SignJob, kCryptoJobAsync, - kKeyTypePrivate, - kKeyTypePublic, + kKeyFormatDER, + kKeyFormatRawPublic, + kKeyFormatRawSeed, kSignJobModeSign, kSignJobModeVerify, - kKeyFormatDER, kWebCryptoKeyFormatRaw, kWebCryptoKeyFormatPKCS8, kWebCryptoKeyFormatSPKI, } = internalBinding('crypto'); -const { - codes: { - ERR_CRYPTO_INVALID_JWK, - }, -} = require('internal/errors'); - const { getUsagesUnion, hasAnyNotIn, jobPromise, - validateKeyOps, kHandle, kKeyObject, } = require('internal/crypto/util'); @@ -49,13 +39,16 @@ const { const { InternalCryptoKey, - PrivateKeyObject, - PublicKeyObject, - createPrivateKey, - createPublicKey, kKeyType, } = require('internal/crypto/keys'); +const { + importDerKey, + importJwkKey, + importRawKey, + validateJwk, +} = require('internal/crypto/webcrypto_util'); + const generateKeyPair = promisify(_generateKeyPair); function verifyAcceptableMlDsaKeyUse(name, isPublic, usages) { @@ -67,16 +60,6 @@ function verifyAcceptableMlDsaKeyUse(name, isPublic, usages) { } } -function createMlDsaRawKey(name, keyData, isPublic) { - const handle = new KeyObjectHandle(); - const keyType = isPublic ? kKeyTypePublic : kKeyTypePrivate; - if (!handle.initPqcRaw(name, keyData, keyType)) { - throw lazyDOMException('Invalid keyData', 'DataError'); - } - - return isPublic ? new PublicKeyObject(handle) : new PrivateKeyObject(handle); -} - async function mlDsaGenerateKey(algorithm, extractable, keyUsages) { const { name } = algorithm; @@ -169,16 +152,7 @@ function mlDsaImportKey( } case 'spki': { verifyAcceptableMlDsaKeyUse(name, true, usagesSet); - try { - keyObject = createPublicKey({ - key: keyData, - format: 'der', - type: 'spki', - }); - } catch (err) { - throw lazyDOMException( - 'Invalid keyData', { name: 'DataError', cause: err }); - } + keyObject = importDerKey(keyData, true); break; } case 'pkcs8': { @@ -196,85 +170,26 @@ function mlDsaImportKey( 'NotSupportedError'); } - try { - keyObject = createPrivateKey({ - key: keyData, - format: 'der', - type: 'pkcs8', - }); - } catch (err) { - throw lazyDOMException( - 'Invalid keyData', { name: 'DataError', cause: err }); - } + keyObject = importDerKey(keyData, false); break; } case 'jwk': { - if (!keyData.kty) - throw lazyDOMException('Invalid keyData', 'DataError'); - if (keyData.kty !== 'AKP') - throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError'); + validateJwk(keyData, 'AKP', extractable, usagesSet, 'sig'); + if (keyData.alg !== name) throw lazyDOMException( 'JWK "alg" Parameter and algorithm name mismatch', 'DataError'); - const isPublic = keyData.priv === undefined; - if (usagesSet.size > 0 && keyData.use !== undefined) { - if (keyData.use !== 'sig') - throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError'); - } - - validateKeyOps(keyData.key_ops, usagesSet); - - if (keyData.ext !== undefined && - keyData.ext === false && - extractable === true) { - throw lazyDOMException( - 'JWK "ext" Parameter and extractable mismatch', - 'DataError'); - } - - if (!isPublic && typeof keyData.pub !== 'string') { - throw lazyDOMException('Invalid JWK', 'DataError'); - } - - verifyAcceptableMlDsaKeyUse( - name, - isPublic, - usagesSet); - - try { - const publicKeyObject = createMlDsaRawKey( - name, - Buffer.from(keyData.pub, 'base64url'), - true); - - if (isPublic) { - keyObject = publicKeyObject; - } else { - keyObject = createMlDsaRawKey( - name, - Buffer.from(keyData.priv, 'base64url'), - false); - - if (!createPublicKey(keyObject).equals(publicKeyObject)) { - throw new ERR_CRYPTO_INVALID_JWK(); - } - } - } catch (err) { - throw lazyDOMException('Invalid keyData', { name: 'DataError', cause: err }); - } + const isPublic = keyData.priv === undefined; + verifyAcceptableMlDsaKeyUse(name, isPublic, usagesSet); + keyObject = importJwkKey(isPublic, keyData); break; } case 'raw-public': case 'raw-seed': { const isPublic = format === 'raw-public'; verifyAcceptableMlDsaKeyUse(name, isPublic, usagesSet); - - try { - keyObject = createMlDsaRawKey(name, keyData, isPublic); - } catch (err) { - throw lazyDOMException('Invalid keyData', { name: 'DataError', cause: err }); - } + keyObject = importRawKey(isPublic, keyData, isPublic ? kKeyFormatRawPublic : kKeyFormatRawSeed, name); break; } default: @@ -306,6 +221,7 @@ async function mlDsaSignVerify(key, data, algorithm, signature) { undefined, undefined, undefined, + undefined, data, undefined, undefined, diff --git a/lib/internal/crypto/ml_kem.js b/lib/internal/crypto/ml_kem.js index 0a82ca8453b8fe..f62d596ccdfc1b 100644 --- a/lib/internal/crypto/ml_kem.js +++ b/lib/internal/crypto/ml_kem.js @@ -12,10 +12,9 @@ const { kCryptoJobAsync, KEMDecapsulateJob, KEMEncapsulateJob, - KeyObjectHandle, kKeyFormatDER, - kKeyTypePrivate, - kKeyTypePublic, + kKeyFormatRawPrivate, + kKeyFormatRawPublic, kWebCryptoKeyFormatPKCS8, kWebCryptoKeyFormatRaw, kWebCryptoKeyFormatSPKI, @@ -39,13 +38,14 @@ const { const { InternalCryptoKey, - PrivateKeyObject, - PublicKeyObject, - createPrivateKey, - createPublicKey, kKeyType, } = require('internal/crypto/keys'); +const { + importDerKey, + importRawKey, +} = require('internal/crypto/webcrypto_util'); + const generateKeyPair = promisify(_generateKeyPair); async function mlKemGenerateKey(algorithm, extractable, keyUsages) { @@ -131,16 +131,6 @@ function verifyAcceptableMlKemKeyUse(name, isPublic, usages) { } } -function createMlKemRawKey(name, keyData, isPublic) { - const handle = new KeyObjectHandle(); - const keyType = isPublic ? kKeyTypePublic : kKeyTypePrivate; - if (!handle.initPqcRaw(name, keyData, keyType)) { - throw lazyDOMException('Invalid keyData', 'DataError'); - } - - return isPublic ? new PublicKeyObject(handle) : new PrivateKeyObject(handle); -} - function mlKemImportKey( format, keyData, @@ -159,16 +149,7 @@ function mlKemImportKey( } case 'spki': { verifyAcceptableMlKemKeyUse(name, true, usagesSet); - try { - keyObject = createPublicKey({ - key: keyData, - format: 'der', - type: 'spki', - }); - } catch (err) { - throw lazyDOMException( - 'Invalid keyData', { name: 'DataError', cause: err }); - } + keyObject = importDerKey(keyData, true); break; } case 'pkcs8': { @@ -186,28 +167,14 @@ function mlKemImportKey( 'NotSupportedError'); } - try { - keyObject = createPrivateKey({ - key: keyData, - format: 'der', - type: 'pkcs8', - }); - } catch (err) { - throw lazyDOMException( - 'Invalid keyData', { name: 'DataError', cause: err }); - } + keyObject = importDerKey(keyData, false); break; } case 'raw-public': case 'raw-seed': { const isPublic = format === 'raw-public'; verifyAcceptableMlKemKeyUse(name, isPublic, usagesSet); - - try { - keyObject = createMlKemRawKey(name, keyData, isPublic); - } catch (err) { - throw lazyDOMException('Invalid keyData', { name: 'DataError', cause: err }); - } + keyObject = importRawKey(isPublic, keyData, isPublic ? kKeyFormatRawPublic : kKeyFormatRawPrivate, name); break; } default: @@ -237,6 +204,7 @@ function mlKemEncapsulate(encapsulationKey) { encapsulationKey[kKeyObject][kHandle], undefined, undefined, + undefined, undefined); job.ondone = (error, result) => { @@ -271,6 +239,7 @@ function mlKemDecapsulate(decapsulationKey, ciphertext) { undefined, undefined, undefined, + undefined, ciphertext); job.ondone = (error, result) => { diff --git a/lib/internal/crypto/rsa.js b/lib/internal/crypto/rsa.js index 52c1257f48b0c6..b87ef49dbd5aa4 100644 --- a/lib/internal/crypto/rsa.js +++ b/lib/internal/crypto/rsa.js @@ -8,7 +8,6 @@ const { } = primordials; const { - KeyObjectHandle, RSACipherJob, SignJob, kCryptoJobAsync, @@ -16,7 +15,6 @@ const { kSignJobModeSign, kSignJobModeVerify, kKeyVariantRSA_OAEP, - kKeyTypePrivate, kWebCryptoCipherEncrypt, kWebCryptoKeyFormatPKCS8, kWebCryptoKeyFormatSPKI, @@ -34,7 +32,6 @@ const { hasAnyNotIn, jobPromise, normalizeHashName, - validateKeyOps, validateMaxBufferLength, kHandle, kKeyObject, @@ -47,14 +44,16 @@ const { const { InternalCryptoKey, - PrivateKeyObject, - PublicKeyObject, - createPublicKey, - createPrivateKey, kAlgorithm, kKeyType, } = require('internal/crypto/keys'); +const { + importDerKey, + importJwkKey, + validateJwk, +} = require('internal/crypto/webcrypto_util'); + const { generateKeyPair: _generateKeyPair, } = require('internal/crypto/keygen'); @@ -234,59 +233,17 @@ function rsaImportKey( } case 'spki': { verifyAcceptableRsaKeyUse(algorithm.name, true, usagesSet); - try { - keyObject = createPublicKey({ - key: keyData, - format: 'der', - type: 'spki', - }); - } catch (err) { - throw lazyDOMException( - 'Invalid keyData', { name: 'DataError', cause: err }); - } + keyObject = importDerKey(keyData, true); break; } case 'pkcs8': { verifyAcceptableRsaKeyUse(algorithm.name, false, usagesSet); - try { - keyObject = createPrivateKey({ - key: keyData, - format: 'der', - type: 'pkcs8', - }); - } catch (err) { - throw lazyDOMException( - 'Invalid keyData', { name: 'DataError', cause: err }); - } + keyObject = importDerKey(keyData, false); break; } case 'jwk': { - if (!keyData.kty) - throw lazyDOMException('Invalid keyData', 'DataError'); - - if (keyData.kty !== 'RSA') - throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError'); - - verifyAcceptableRsaKeyUse( - algorithm.name, - keyData.d === undefined, - usagesSet); - - if (usagesSet.size > 0 && keyData.use !== undefined) { - const checkUse = algorithm.name === 'RSA-OAEP' ? 'enc' : 'sig'; - if (keyData.use !== checkUse) - throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError'); - } - - validateKeyOps(keyData.key_ops, usagesSet); - - if (keyData.ext !== undefined && - keyData.ext === false && - extractable === true) { - throw lazyDOMException( - 'JWK "ext" Parameter and extractable mismatch', - 'DataError'); - } + const expectedUse = algorithm.name === 'RSA-OAEP' ? 'enc' : 'sig'; + validateJwk(keyData, 'RSA', extractable, usagesSet, expectedUse); if (keyData.alg !== undefined) { const expected = @@ -301,21 +258,9 @@ function rsaImportKey( 'DataError'); } - const handle = new KeyObjectHandle(); - let type; - try { - type = handle.initJwk(keyData); - } catch (err) { - throw lazyDOMException( - 'Invalid keyData', { name: 'DataError', cause: err }); - } - if (type === undefined) - throw lazyDOMException('Invalid keyData', 'DataError'); - - keyObject = type === kKeyTypePrivate ? - new PrivateKeyObject(handle) : - new PublicKeyObject(handle); - + const isPublic = keyData.d === undefined; + verifyAcceptableRsaKeyUse(algorithm.name, isPublic, usagesSet); + keyObject = importJwkKey(isPublic, keyData); break; } default: @@ -362,6 +307,7 @@ async function rsaSignVerify(key, data, { saltLength }, signature) { undefined, undefined, undefined, + undefined, data, normalizeHashName(key[kAlgorithm].hash.name), saltLength, diff --git a/lib/internal/crypto/sig.js b/lib/internal/crypto/sig.js index 7d38c0bdf60687..324b804a817a3d 100644 --- a/lib/internal/crypto/sig.js +++ b/lib/internal/crypto/sig.js @@ -134,7 +134,8 @@ Sign.prototype.sign = function sign(options, encoding) { if (!options) throw new ERR_CRYPTO_SIGN_KEY_REQUIRED(); - const { data, format, type, passphrase } = preparePrivateKey(options, true); + const { data, format, type, passphrase, namedCurve } = + preparePrivateKey(options, true); // Options specific to RSA const rsaPadding = getPadding(options); @@ -143,7 +144,9 @@ Sign.prototype.sign = function sign(options, encoding) { // Options specific to (EC)DSA const dsaSigEnc = getDSASignatureEncoding(options); - const ret = this[kHandle].sign(data, format, type, passphrase, rsaPadding, + const ret = this[kHandle].sign(data, format, type, + passphrase, namedCurve, + rsaPadding, pssSaltLength, dsaSigEnc); if (encoding && encoding !== 'buffer') @@ -179,6 +182,7 @@ function signOneShot(algorithm, data, key, callback) { format: keyFormat, type: keyType, passphrase: keyPassphrase, + namedCurve: keyNamedCurve, } = preparePrivateKey(key); const job = new SignJob( @@ -188,6 +192,7 @@ function signOneShot(algorithm, data, key, callback) { keyFormat, keyType, keyPassphrase, + keyNamedCurve, data, algorithm, pssSaltLength, @@ -233,6 +238,7 @@ Verify.prototype.verify = function verify(options, signature, sigEncoding) { format, type, passphrase, + namedCurve, } = preparePublicOrPrivateKey(options, true); // Options specific to RSA @@ -244,7 +250,9 @@ Verify.prototype.verify = function verify(options, signature, sigEncoding) { signature = getArrayBufferOrView(signature, 'signature', sigEncoding); - return this[kHandle].verify(data, format, type, passphrase, signature, + return this[kHandle].verify(data, format, type, + passphrase, namedCurve, + signature, rsaPadding, pssSaltLength, dsaSigEnc); }; @@ -288,6 +296,7 @@ function verifyOneShot(algorithm, data, key, signature, callback) { format: keyFormat, type: keyType, passphrase: keyPassphrase, + namedCurve: keyNamedCurve, } = preparePublicOrPrivateKey(key); const job = new SignJob( @@ -297,6 +306,7 @@ function verifyOneShot(algorithm, data, key, signature, callback) { keyFormat, keyType, keyPassphrase, + keyNamedCurve, data, algorithm, pssSaltLength, diff --git a/lib/internal/crypto/webcrypto_util.js b/lib/internal/crypto/webcrypto_util.js new file mode 100644 index 00000000000000..db340fa620c35a --- /dev/null +++ b/lib/internal/crypto/webcrypto_util.js @@ -0,0 +1,93 @@ +'use strict'; + +const { + KeyObjectHandle, + kKeyFormatDER, + kKeyFormatJWK, + kKeyEncodingPKCS8, + kKeyEncodingSPKI, + kKeyTypePublic, + kKeyTypePrivate, +} = internalBinding('crypto'); + +const { + validateKeyOps, +} = require('internal/crypto/util'); + +const { + lazyDOMException, +} = require('internal/util'); + +const { + PrivateKeyObject, + PublicKeyObject, +} = require('internal/crypto/keys'); + +function importDerKey(keyData, isPublic) { + const handle = new KeyObjectHandle(); + const keyType = isPublic ? kKeyTypePublic : kKeyTypePrivate; + const encoding = isPublic ? kKeyEncodingSPKI : kKeyEncodingPKCS8; + try { + handle.init(keyType, keyData, kKeyFormatDER, encoding, null, null); + } catch (err) { + throw lazyDOMException( + 'Invalid keyData', { name: 'DataError', cause: err }); + } + return isPublic ? + new PublicKeyObject(handle) : + new PrivateKeyObject(handle); +} + +function validateJwk(keyData, kty, extractable, usagesSet, expectedUse) { + if (!keyData.kty) + throw lazyDOMException('Invalid keyData', 'DataError'); + if (keyData.kty !== kty) + throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError'); + if (usagesSet.size > 0 && keyData.use !== undefined) { + if (keyData.use !== expectedUse) + throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError'); + } + validateKeyOps(keyData.key_ops, usagesSet); + if (keyData.ext !== undefined && + keyData.ext === false && + extractable === true) { + throw lazyDOMException( + 'JWK "ext" Parameter and extractable mismatch', + 'DataError'); + } +} + +function importJwkKey(isPublic, keyData) { + const handle = new KeyObjectHandle(); + const keyType = isPublic ? kKeyTypePublic : kKeyTypePrivate; + try { + handle.init(keyType, keyData, kKeyFormatJWK, null, null, null); + } catch (err) { + throw lazyDOMException( + 'Invalid keyData', { name: 'DataError', cause: err }); + } + return isPublic ? + new PublicKeyObject(handle) : + new PrivateKeyObject(handle); +} + +function importRawKey(isPublic, keyData, format, name, namedCurve) { + const handle = new KeyObjectHandle(); + const keyType = isPublic ? kKeyTypePublic : kKeyTypePrivate; + try { + handle.init(keyType, keyData, format, name ?? null, null, namedCurve ?? null); + } catch (err) { + throw lazyDOMException( + 'Invalid keyData', { name: 'DataError', cause: err }); + } + return isPublic ? + new PublicKeyObject(handle) : + new PrivateKeyObject(handle); +} + +module.exports = { + importDerKey, + importJwkKey, + importRawKey, + validateJwk, +}; diff --git a/src/crypto/crypto_ec.cc b/src/crypto/crypto_ec.cc index ee0de59f7be592..6738edd590c300 100644 --- a/src/crypto/crypto_ec.cc +++ b/src/crypto/crypto_ec.cc @@ -690,14 +690,95 @@ bool ExportJWKEdKey(Environment* env, target->Set(env->context(), env->jwk_kty_string(), env->jwk_okp_string()) .IsNothing()); } +KeyObjectData ImportJWKEdKey(Environment* env, Local jwk) { + Local crv_value; + Local x_value; + Local d_value; + + if (!jwk->Get(env->context(), env->jwk_crv_string()).ToLocal(&crv_value) || + !jwk->Get(env->context(), env->jwk_x_string()).ToLocal(&x_value) || + !jwk->Get(env->context(), env->jwk_d_string()).ToLocal(&d_value)) { + return {}; + } + + if (!crv_value->IsString() || !x_value->IsString() || + (!d_value->IsUndefined() && !d_value->IsString())) { + THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK OKP key"); + return {}; + } + + Utf8Value crv(env->isolate(), crv_value.As()); + + static constexpr struct { + const char* name; + int nid; + } kCurveToNid[] = { + {"Ed25519", EVP_PKEY_ED25519}, + {"Ed448", EVP_PKEY_ED448}, + {"X25519", EVP_PKEY_X25519}, + {"X448", EVP_PKEY_X448}, + }; + + int id = NID_undef; + for (const auto& entry : kCurveToNid) { + if (strcmp(*crv, entry.name) == 0) { + id = entry.nid; + break; + } + } + + if (id == NID_undef) { + THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK OKP key"); + return {}; + } + + KeyType type = d_value->IsString() ? kKeyTypePrivate : kKeyTypePublic; + + ByteSource raw; + if (type == kKeyTypePrivate) { + raw = ByteSource::FromEncodedString(env, d_value.As()); + } else { + raw = ByteSource::FromEncodedString(env, x_value.As()); + } + + typedef EVPKeyPointer (*new_key_fn)( + int, const ncrypto::Buffer&); + new_key_fn fn = type == kKeyTypePrivate ? EVPKeyPointer::NewRawPrivate + : EVPKeyPointer::NewRawPublic; + + auto pkey = fn(id, + ncrypto::Buffer{ + .data = raw.data(), + .len = raw.size(), + }); + if (!pkey) { + THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK OKP key"); + return {}; + } -KeyObjectData ImportJWKEcKey(Environment* env, - Local jwk, - const FunctionCallbackInfo& args, - unsigned int offset) { - CHECK(args[offset]->IsString()); // curve name - Utf8Value curve(env->isolate(), args[offset].As()); + // When importing a private key, verify that the JWK's x field matches + // the public key derived from the private key. + if (type == kKeyTypePrivate && x_value->IsString()) { + ByteSource x = ByteSource::FromEncodedString(env, x_value.As()); + auto derived_pub = pkey.rawPublicKey(); + if (!derived_pub || derived_pub.size() != x.size() || + CRYPTO_memcmp(derived_pub.get(), x.data(), x.size()) != 0) { + THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK OKP key"); + return {}; + } + } + return KeyObjectData::CreateAsymmetric(type, std::move(pkey)); +} +KeyObjectData ImportJWKEcKey(Environment* env, Local jwk) { + Local crv_value; + if (!jwk->Get(env->context(), env->jwk_crv_string()).ToLocal(&crv_value) || + !crv_value->IsString()) { + THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK EC key"); + return {}; + } + + Utf8Value curve(env->isolate(), crv_value.As()); int nid = Ec::GetCurveIdFromName(*curve); if (nid == NID_undef) { // Unknown curve THROW_ERR_CRYPTO_INVALID_CURVE(env); @@ -732,6 +813,8 @@ KeyObjectData ImportJWKEcKey(Environment* env, ByteSource x = ByteSource::FromEncodedString(env, x_value.As()); ByteSource y = ByteSource::FromEncodedString(env, y_value.As()); + // setPublicKeyRaw validates the point is on the curve. For h=1 curves + // (P-256/P-384/P-521), this skips EC_KEY_check_key for efficiency. if (!ec.setPublicKeyRaw(x.ToBN(), y.ToBN())) { THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK EC key"); return {}; @@ -743,6 +826,11 @@ KeyObjectData ImportJWKEcKey(Environment* env, THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK EC key"); return {}; } + // Verify that the public point matches the private scalar (d*G == (x,y)). + if (!ec.checkKey()) { + THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK EC key"); + return {}; + } } auto pkey = EVPKeyPointer::New(); diff --git a/src/crypto/crypto_ec.h b/src/crypto/crypto_ec.h index 0bfc7c3e35e16b..5522ac743e3089 100644 --- a/src/crypto/crypto_ec.h +++ b/src/crypto/crypto_ec.h @@ -123,10 +123,9 @@ bool ExportJWKEdKey(Environment* env, const KeyObjectData& key, v8::Local target); -KeyObjectData ImportJWKEcKey(Environment* env, - v8::Local jwk, - const v8::FunctionCallbackInfo& args, - unsigned int offset); +KeyObjectData ImportJWKEdKey(Environment* env, v8::Local jwk); + +KeyObjectData ImportJWKEcKey(Environment* env, v8::Local jwk); bool GetEcKeyDetail(Environment* env, const KeyObjectData& key, diff --git a/src/crypto/crypto_keys.cc b/src/crypto/crypto_keys.cc index bcea72facc7ca9..d1a46a15d898ab 100644 --- a/src/crypto/crypto_keys.cc +++ b/src/crypto/crypto_keys.cc @@ -207,22 +207,6 @@ bool ExportJWKAsymmetricKey(Environment* env, return false; } -KeyObjectData ImportJWKAsymmetricKey(Environment* env, - Local jwk, - std::string_view kty, - const FunctionCallbackInfo& args, - unsigned int offset) { - if (kty == "RSA") { - return ImportJWKRsaKey(env, jwk, args, offset); - } else if (kty == "EC") { - return ImportJWKEcKey(env, jwk, args, offset); - } - - THROW_ERR_CRYPTO_INVALID_JWK( - env, "%s is not a supported JWK key type", kty.data()); - return {}; -} - bool GetSecretKeyDetail(Environment* env, const KeyObjectData& key, Local target) { @@ -555,12 +539,273 @@ KeyObjectData::GetPublicKeyEncodingFromJs( return GetKeyFormatAndTypeFromJs(args, offset, context); } +// Shared helper for importing raw asymmetric keys. Called from +// ImportRawKeyFromArgs. +static KeyObjectData ImportRawKey(Environment* env, + const unsigned char* key_data, + size_t key_data_len, + EVPKeyPointer::PKFormatType format, + Local key_type, + const char* key_type_name, + const char* named_curve, + KeyType target_type) { + auto throw_invalid = [&]() { + if (!env->isolate()->HasPendingException()) { + THROW_ERR_INVALID_ARG_VALUE(env, "Invalid key data"); + } + }; + + // EC keys + if (key_type->StringEquals(env->crypto_ec_string())) { + int curve_nid = ncrypto::Ec::GetCurveIdFromName(named_curve); + if (curve_nid == NID_undef) { + THROW_ERR_CRYPTO_INVALID_CURVE(env); + return {}; + } + auto eckey = ECKeyPointer::NewByCurveName(curve_nid); + if (!eckey) { + throw_invalid(); + return {}; + } + if (format == EVPKeyPointer::PKFormatType::RAW_PUBLIC) { + const auto group = eckey.getGroup(); + auto pub = ECPointPointer::New(group); + if (!pub) { + throw_invalid(); + return {}; + } + ncrypto::Buffer buffer{ + .data = key_data, + .len = key_data_len, + }; + if (!pub.setFromBuffer(buffer, group) || !eckey.setPublicKey(pub)) { + throw_invalid(); + return {}; + } + } else { + const auto group = eckey.getGroup(); + auto order = BignumPointer::New(); + CHECK(order); + CHECK(EC_GROUP_get_order(group, order.get(), nullptr)); + if (key_data_len != order.byteLength()) { + throw_invalid(); + return {}; + } + BignumPointer priv_bn(key_data, key_data_len); + if (!priv_bn || !eckey.setPrivateKey(priv_bn)) { + throw_invalid(); + return {}; + } + auto pub_point = ECPointPointer::New(group); + if (!pub_point || !pub_point.mul(group, priv_bn.get()) || + !eckey.setPublicKey(pub_point)) { + throw_invalid(); + return {}; + } + } + auto pkey = EVPKeyPointer::New(); + if (!pkey.assign(eckey)) { + throw_invalid(); + return {}; + } + eckey.release(); + return KeyObjectData::CreateAsymmetric(target_type, std::move(pkey)); + } + + int id = GetNidFromName(key_type_name); + + typedef EVPKeyPointer (*new_key_fn)( + int, const ncrypto::Buffer&); + new_key_fn fn = nullptr; + switch (id) { + case EVP_PKEY_X25519: + case EVP_PKEY_X448: + case EVP_PKEY_ED25519: + case EVP_PKEY_ED448: + fn = target_type == kKeyTypePrivate ? EVPKeyPointer::NewRawPrivate + : EVPKeyPointer::NewRawPublic; + break; +#if OPENSSL_WITH_PQC + case EVP_PKEY_ML_DSA_44: + case EVP_PKEY_ML_DSA_65: + case EVP_PKEY_ML_DSA_87: + case EVP_PKEY_ML_KEM_512: + case EVP_PKEY_ML_KEM_768: + case EVP_PKEY_ML_KEM_1024: + fn = target_type == kKeyTypePrivate ? EVPKeyPointer::NewRawSeed + : EVPKeyPointer::NewRawPublic; + break; + case EVP_PKEY_SLH_DSA_SHA2_128F: + case EVP_PKEY_SLH_DSA_SHA2_128S: + case EVP_PKEY_SLH_DSA_SHA2_192F: + case EVP_PKEY_SLH_DSA_SHA2_192S: + case EVP_PKEY_SLH_DSA_SHA2_256F: + case EVP_PKEY_SLH_DSA_SHA2_256S: + case EVP_PKEY_SLH_DSA_SHAKE_128F: + case EVP_PKEY_SLH_DSA_SHAKE_128S: + case EVP_PKEY_SLH_DSA_SHAKE_192F: + case EVP_PKEY_SLH_DSA_SHAKE_192S: + case EVP_PKEY_SLH_DSA_SHAKE_256F: + case EVP_PKEY_SLH_DSA_SHAKE_256S: + fn = target_type == kKeyTypePrivate ? EVPKeyPointer::NewRawPrivate + : EVPKeyPointer::NewRawPublic; + break; +#endif + default: + break; + } + + if (fn != nullptr) { + auto pkey = fn(id, + ncrypto::Buffer{ + .data = key_data, + .len = key_data_len, + }); + if (!pkey) { + throw_invalid(); + return {}; + } + return KeyObjectData::CreateAsymmetric(target_type, std::move(pkey)); + } + + if (key_type->StringEquals(env->crypto_rsa_string()) || + key_type->StringEquals(env->crypto_rsa_pss_string()) || + key_type->StringEquals(env->crypto_dsa_string()) || + key_type->StringEquals(env->crypto_dh_string())) { + THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); + return {}; + } + +#if !OPENSSL_WITH_PQC + if (key_type->StringEquals(env->crypto_ml_dsa_44_string()) || + key_type->StringEquals(env->crypto_ml_dsa_65_string()) || + key_type->StringEquals(env->crypto_ml_dsa_87_string()) || + key_type->StringEquals(env->crypto_ml_kem_512_string()) || + key_type->StringEquals(env->crypto_ml_kem_768_string()) || + key_type->StringEquals(env->crypto_ml_kem_1024_string()) || + key_type->StringEquals(env->crypto_slh_dsa_sha2_128f_string()) || + key_type->StringEquals(env->crypto_slh_dsa_sha2_128s_string()) || + key_type->StringEquals(env->crypto_slh_dsa_sha2_192f_string()) || + key_type->StringEquals(env->crypto_slh_dsa_sha2_192s_string()) || + key_type->StringEquals(env->crypto_slh_dsa_sha2_256f_string()) || + key_type->StringEquals(env->crypto_slh_dsa_sha2_256s_string()) || + key_type->StringEquals(env->crypto_slh_dsa_shake_128f_string()) || + key_type->StringEquals(env->crypto_slh_dsa_shake_128s_string()) || + key_type->StringEquals(env->crypto_slh_dsa_shake_192f_string()) || + key_type->StringEquals(env->crypto_slh_dsa_shake_192s_string()) || + key_type->StringEquals(env->crypto_slh_dsa_shake_256f_string()) || + key_type->StringEquals(env->crypto_slh_dsa_shake_256s_string())) { + THROW_ERR_INVALID_ARG_VALUE(env, "Unsupported key type"); + return {}; + } +#endif + + THROW_ERR_INVALID_ARG_VALUE( + env, "Invalid asymmetricKeyType: %s", key_type_name); + return {}; +} + +// Shared helper for importing a JWK asymmetric key. Extracts kty from the +// JWK object and dispatches to the appropriate importer. +static KeyObjectData ImportJWKFromArgs(Environment* env, Local jwk) { + Local kty; + if (!jwk->Get(env->context(), env->jwk_kty_string()).ToLocal(&kty) || + !kty->IsString()) { + THROW_ERR_CRYPTO_INVALID_JWK(env); + return {}; + } + Utf8Value kty_string(env->isolate(), kty); + if (*kty_string == std::string_view("RSA")) { + return ImportJWKRsaKey(env, jwk); + } else if (*kty_string == std::string_view("EC")) { + return ImportJWKEcKey(env, jwk); + } else if (*kty_string == std::string_view("OKP")) { + return ImportJWKEdKey(env, jwk); + } else if (*kty_string == std::string_view("AKP")) { +#if OPENSSL_WITH_PQC + return ImportJWKAkpKey(env, jwk); +#else + THROW_ERR_INVALID_ARG_VALUE(env, "Unsupported key type"); + return {}; +#endif + } + + THROW_ERR_CRYPTO_INVALID_JWK( + env, "%s is not a supported JWK key type", *kty_string); + return {}; +} + +// Shared helper for importing raw asymmetric keys from positional args. +// args layout: [... offset+0: buffer, offset+1: formatInt, +// offset+2: asymmetricKeyType, offset+3: passphrase, +// offset+4: namedCurve] +static KeyObjectData ImportRawKeyFromArgs( + const FunctionCallbackInfo& args, unsigned int offset) { + Environment* env = Environment::GetCurrent(args); + + auto format = static_cast( + args[offset + 1].As()->Value()); + KeyType type = (format == EVPKeyPointer::PKFormatType::RAW_PUBLIC) + ? kKeyTypePublic + : kKeyTypePrivate; + + ArrayBufferOrViewContents key_data(args[offset]); + if (!key_data.CheckSizeInt32()) [[unlikely]] { + THROW_ERR_OUT_OF_RANGE(env, "keyData is too big"); + return {}; + } + + CHECK(args[offset + 2]->IsString()); + Local key_type = args[offset + 2].As(); + Utf8Value key_type_name(env->isolate(), key_type); + + DCHECK_IMPLIES(key_type->StringEquals(env->crypto_ec_string()), + args[offset + 4]->IsString()); + Utf8Value curve(env->isolate(), + args[offset + 4]->IsString() ? args[offset + 4].As() + : String::Empty(env->isolate())); + + return ImportRawKey(env, + key_data.data(), + key_data.size(), + format, + key_type, + *key_type_name, + *curve, + type); +} + KeyObjectData KeyObjectData::GetPrivateKeyFromJs( const v8::FunctionCallbackInfo& args, unsigned int* offset, bool allow_key_object) { + Environment* env = Environment::GetCurrent(args); + + // JWK format: data is a JS Object (not buffer), format int is JWK. + if (args[*offset]->IsObject() && !IsAnyBufferSource(args[*offset]) && + args[*offset + 1]->IsInt32()) { + auto format = static_cast( + args[*offset + 1].As()->Value()); + if (format == EVPKeyPointer::PKFormatType::JWK) { + auto data = ImportJWKFromArgs(env, args[*offset].As()); + *offset += 5; + return data; + } + } + if (args[*offset]->IsString() || IsAnyBufferSource(args[*offset])) { - Environment* env = Environment::GetCurrent(args); + // Raw format: buffer + raw format int. + if (args[*offset + 1]->IsInt32()) { + auto format = static_cast( + args[*offset + 1].As()->Value()); + if (format == EVPKeyPointer::PKFormatType::RAW_PRIVATE || + format == EVPKeyPointer::PKFormatType::RAW_SEED) { + auto data = ImportRawKeyFromArgs(args, *offset); + *offset += 5; + return data; + } + } + auto key = ByteSource::FromStringOrBuffer(env, args[(*offset)++]); EVPKeyPointer::PrivateKeyEncodingConfig config; @@ -569,6 +814,9 @@ KeyObjectData KeyObjectData::GetPrivateKeyFromJs( return {}; } + // Skip the namedCurve argument (only used by raw format imports). + (*offset)++; + return TryParsePrivateKey( env, config, @@ -582,14 +830,40 @@ KeyObjectData KeyObjectData::GetPrivateKeyFromJs( KeyObjectHandle* key; ASSIGN_OR_RETURN_UNWRAP(&key, args[*offset].As(), KeyObjectData()); CHECK_EQ(key->Data().GetKeyType(), kKeyTypePrivate); - (*offset) += 4; + (*offset) += 5; return key->Data().addRef(); } KeyObjectData KeyObjectData::GetPublicOrPrivateKeyFromJs( const FunctionCallbackInfo& args, unsigned int* offset) { - if (IsAnyBufferSource(args[*offset])) { - Environment* env = Environment::GetCurrent(args); + Environment* env = Environment::GetCurrent(args); + + // JWK format: data is a JS Object (not buffer), format int is JWK. + if (args[*offset]->IsObject() && !IsAnyBufferSource(args[*offset]) && + args[*offset + 1]->IsInt32()) { + auto format = static_cast( + args[*offset + 1].As()->Value()); + if (format == EVPKeyPointer::PKFormatType::JWK) { + auto data = ImportJWKFromArgs(env, args[*offset].As()); + *offset += 5; + return data; + } + } + + if (args[*offset]->IsString() || IsAnyBufferSource(args[*offset])) { + // Raw format: buffer + raw format int. + if (args[*offset + 1]->IsInt32()) { + auto format = static_cast( + args[*offset + 1].As()->Value()); + if (format == EVPKeyPointer::PKFormatType::RAW_PUBLIC || + format == EVPKeyPointer::PKFormatType::RAW_PRIVATE || + format == EVPKeyPointer::PKFormatType::RAW_SEED) { + auto data = ImportRawKeyFromArgs(args, *offset); + *offset += 5; + return data; + } + } + ArrayBufferOrViewContents data(args[(*offset)++]); if (!data.CheckSizeInt32()) [[unlikely]] { THROW_ERR_OUT_OF_RANGE(env, "keyData is too big"); @@ -603,6 +877,9 @@ KeyObjectData KeyObjectData::GetPublicOrPrivateKeyFromJs( return {}; } + // Skip the namedCurve argument (only used by raw format imports). + (*offset)++; + ncrypto::Buffer buffer = { .data = reinterpret_cast(data.data()), .len = data.size(), @@ -661,7 +938,7 @@ KeyObjectData KeyObjectData::GetPublicOrPrivateKeyFromJs( BaseObject::Unwrap(args[*offset].As()); CHECK_NOT_NULL(key); CHECK_NE(key->Data().GetKeyType(), kKeyTypeSecret); - (*offset) += 4; + (*offset) += 5; return key->Data().addRef(); } @@ -783,18 +1060,13 @@ Local KeyObjectHandle::Initialize(Environment* env) { isolate, templ, "checkEcKeyData", CheckEcKeyData); SetProtoMethod(isolate, templ, "export", Export); SetProtoMethod(isolate, templ, "exportJwk", ExportJWK); - SetProtoMethod(isolate, templ, "initECRaw", InitECRaw); - SetProtoMethod(isolate, templ, "initEDRaw", InitEDRaw); SetProtoMethodNoSideEffect(isolate, templ, "rawPublicKey", RawPublicKey); SetProtoMethodNoSideEffect(isolate, templ, "rawPrivateKey", RawPrivateKey); - SetProtoMethod(isolate, templ, "initPqcRaw", InitPqcRaw); SetProtoMethodNoSideEffect(isolate, templ, "rawSeed", RawSeed); - SetProtoMethod(isolate, templ, "initECPrivateRaw", InitECPrivateRaw); SetProtoMethodNoSideEffect( isolate, templ, "exportECPublicRaw", ExportECPublicRaw); SetProtoMethodNoSideEffect( isolate, templ, "exportECPrivateRaw", ExportECPrivateRaw); - SetProtoMethod(isolate, templ, "initJwk", InitJWK); SetProtoMethod(isolate, templ, "keyDetail", GetKeyDetail); SetProtoMethod(isolate, templ, "equals", Equals); @@ -812,16 +1084,11 @@ void KeyObjectHandle::RegisterExternalReferences( registry->Register(CheckEcKeyData); registry->Register(Export); registry->Register(ExportJWK); - registry->Register(InitECRaw); - registry->Register(InitEDRaw); registry->Register(RawPublicKey); registry->Register(RawPrivateKey); - registry->Register(InitPqcRaw); registry->Register(RawSeed); - registry->Register(InitECPrivateRaw); registry->Register(ExportECPublicRaw); registry->Register(ExportECPrivateRaw); - registry->Register(InitJWK); registry->Register(GetKeyDetail); registry->Register(Equals); } @@ -862,6 +1129,7 @@ void KeyObjectHandle::Init(const FunctionCallbackInfo& args) { ASSIGN_OR_RETURN_UNWRAP(&key, args.This()); MarkPopErrorOnReturn mark_pop_error_on_return; + Environment* env = Environment::GetCurrent(args); CHECK(args[0]->IsInt32()); KeyType type = static_cast(args[0].As()->Value()); @@ -869,25 +1137,70 @@ void KeyObjectHandle::Init(const FunctionCallbackInfo& args) { switch (type) { case kKeyTypeSecret: { + if (args.Length() == 5 && args[2]->IsInt32()) { + auto format = static_cast( + args[2].As()->Value()); + if (format == EVPKeyPointer::PKFormatType::JWK) { + CHECK(args[1]->IsObject()); + key->data_ = ImportJWKSecretKey(env, args[1].As()); + break; + } + } CHECK_EQ(args.Length(), 2); ArrayBufferOrViewContents buf(args[1]); key->data_ = KeyObjectData::CreateSecret(buf.ToCopy()); break; } - case kKeyTypePublic: { - CHECK_EQ(args.Length(), 5); - - offset = 1; - auto data = KeyObjectData::GetPublicOrPrivateKeyFromJs(args, &offset); - if (!data) return; - key->data_ = data.addRefWithType(kKeyTypePublic); - break; - } + case kKeyTypePublic: case kKeyTypePrivate: { - CHECK_EQ(args.Length(), 5); + CHECK_EQ(args.Length(), 6); + + // Check if this is a raw or JWK format import: + // args: [keyType, buffer/object, formatInt, typeString/null, + // passphrase/null, namedCurve/null] + if (args[2]->IsInt32()) { + auto format = static_cast( + args[2].As()->Value()); + if (format == EVPKeyPointer::PKFormatType::RAW_PUBLIC || + format == EVPKeyPointer::PKFormatType::RAW_PRIVATE || + format == EVPKeyPointer::PKFormatType::RAW_SEED) { + auto data = ImportRawKeyFromArgs(args, 1); + if (!data) return; + if (type == kKeyTypePublic && data.GetKeyType() == kKeyTypePrivate) { + key->data_ = data.addRefWithType(kKeyTypePublic); + } else { + key->data_ = std::move(data); + } + break; + } + if (format == EVPKeyPointer::PKFormatType::JWK) { + CHECK(args[1]->IsObject()); + key->data_ = ImportJWKFromArgs(env, args[1].As()); + if (!key->data_) return; + if (type == kKeyTypePublic && + key->data_.GetKeyType() == kKeyTypePrivate) { + key->data_ = key->data_.addRefWithType(kKeyTypePublic); + } else if (type == kKeyTypePrivate && + key->data_.GetKeyType() == kKeyTypePublic) { + THROW_ERR_CRYPTO_INVALID_JWK( + env, "JWK does not contain private key material"); + return; + } + args.GetReturnValue().Set(key->data_.GetKeyType()); + break; + } + } + offset = 1; - if (auto data = KeyObjectData::GetPrivateKeyFromJs(args, &offset, false)) { - key->data_ = std::move(data); + if (type == kKeyTypePublic) { + auto data = KeyObjectData::GetPublicOrPrivateKeyFromJs(args, &offset); + if (!data) return; + key->data_ = data.addRefWithType(kKeyTypePublic); + } else { + if (auto data = + KeyObjectData::GetPrivateKeyFromJs(args, &offset, false)) { + key->data_ = std::move(data); + } } break; } @@ -896,190 +1209,6 @@ void KeyObjectHandle::Init(const FunctionCallbackInfo& args) { } } -void KeyObjectHandle::InitJWK(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - KeyObjectHandle* key; - ASSIGN_OR_RETURN_UNWRAP(&key, args.This()); - MarkPopErrorOnReturn mark_pop_error_on_return; - - // The argument must be a JavaScript object that we will inspect - // to get the JWK properties from. - CHECK(args[0]->IsObject()); - - // Step one, Secret key or not? - Local input = args[0].As(); - - Local kty; - if (!input->Get(env->context(), env->jwk_kty_string()).ToLocal(&kty) || - !kty->IsString()) { - return THROW_ERR_CRYPTO_INVALID_JWK(env); - } - - Utf8Value kty_string(env->isolate(), kty); - - if (kty_string == "oct") { - // Secret key - key->data_ = ImportJWKSecretKey(env, input); - if (!key->data_) { - // ImportJWKSecretKey is responsible for throwing an appropriate error - return; - } - } else { - key->data_ = ImportJWKAsymmetricKey(env, input, *kty_string, args, 1); - if (!key->data_) { - // ImportJWKAsymmetricKey is responsible for throwing an appropriate error - return; - } - } - - args.GetReturnValue().Set(key->data_.GetKeyType()); -} - -void KeyObjectHandle::InitECRaw(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - KeyObjectHandle* key; - ASSIGN_OR_RETURN_UNWRAP(&key, args.This()); - - CHECK(args[0]->IsString()); - Utf8Value name(env->isolate(), args[0]); - - MarkPopErrorOnReturn mark_pop_error_on_return; - - int id = ncrypto::Ec::GetCurveIdFromName(*name); - if (id == NID_undef) return THROW_ERR_CRYPTO_INVALID_CURVE(env); - - auto eckey = ECKeyPointer::NewByCurveName(id); - if (!eckey) - return args.GetReturnValue().Set(false); - - const auto group = eckey.getGroup(); - auto pub = ECDH::BufferToPoint(env, group, args[1]); - - if (!pub || !eckey || !eckey.setPublicKey(pub)) { - return args.GetReturnValue().Set(false); - } - - auto pkey = EVPKeyPointer::New(); - if (!pkey.assign(eckey)) { - args.GetReturnValue().Set(false); - } - - eckey.release(); // Release ownership of the key - - key->data_ = KeyObjectData::CreateAsymmetric(kKeyTypePublic, std::move(pkey)); - - args.GetReturnValue().Set(true); -} - -void KeyObjectHandle::InitEDRaw(const FunctionCallbackInfo& args) { - KeyObjectHandle* key; - ASSIGN_OR_RETURN_UNWRAP(&key, args.This()); - - CHECK(args[0]->IsString()); - Utf8Value name(args.GetIsolate(), args[0]); - - ArrayBufferOrViewContents key_data(args[1]); - KeyType type = FromV8Value(args[2]); - - MarkPopErrorOnReturn mark_pop_error_on_return; - - typedef EVPKeyPointer (*new_key_fn)( - int, const ncrypto::Buffer&); - new_key_fn fn = type == kKeyTypePrivate ? EVPKeyPointer::NewRawPrivate - : EVPKeyPointer::NewRawPublic; - - int id = GetNidFromName(*name); - - switch (id) { - case EVP_PKEY_X25519: - case EVP_PKEY_X448: - case EVP_PKEY_ED25519: - case EVP_PKEY_ED448: { - auto pkey = fn(id, - ncrypto::Buffer{ - .data = key_data.data(), - .len = key_data.size(), - }); - if (!pkey) { - return args.GetReturnValue().Set(false); - } - key->data_ = KeyObjectData::CreateAsymmetric(type, std::move(pkey)); - CHECK(key->data_); - break; - } - default: - return args.GetReturnValue().Set(false); - } - - args.GetReturnValue().Set(true); -} - -void KeyObjectHandle::InitPqcRaw(const FunctionCallbackInfo& args) { -#if OPENSSL_WITH_PQC - KeyObjectHandle* key; - ASSIGN_OR_RETURN_UNWRAP(&key, args.This()); - - CHECK(args[0]->IsString()); - Utf8Value name(args.GetIsolate(), args[0]); - - ArrayBufferOrViewContents key_data(args[1]); - KeyType type = FromV8Value(args[2]); - - MarkPopErrorOnReturn mark_pop_error_on_return; - - int id = GetNidFromName(*name); - - typedef EVPKeyPointer (*new_key_fn)( - int, const ncrypto::Buffer&); - new_key_fn fn; - - switch (id) { - case EVP_PKEY_ML_DSA_44: - case EVP_PKEY_ML_DSA_65: - case EVP_PKEY_ML_DSA_87: - case EVP_PKEY_ML_KEM_512: - case EVP_PKEY_ML_KEM_768: - case EVP_PKEY_ML_KEM_1024: - fn = type == kKeyTypePrivate ? EVPKeyPointer::NewRawSeed - : EVPKeyPointer::NewRawPublic; - break; - case EVP_PKEY_SLH_DSA_SHA2_128F: - case EVP_PKEY_SLH_DSA_SHA2_128S: - case EVP_PKEY_SLH_DSA_SHA2_192F: - case EVP_PKEY_SLH_DSA_SHA2_192S: - case EVP_PKEY_SLH_DSA_SHA2_256F: - case EVP_PKEY_SLH_DSA_SHA2_256S: - case EVP_PKEY_SLH_DSA_SHAKE_128F: - case EVP_PKEY_SLH_DSA_SHAKE_128S: - case EVP_PKEY_SLH_DSA_SHAKE_192F: - case EVP_PKEY_SLH_DSA_SHAKE_192S: - case EVP_PKEY_SLH_DSA_SHAKE_256F: - case EVP_PKEY_SLH_DSA_SHAKE_256S: - fn = type == kKeyTypePrivate ? EVPKeyPointer::NewRawPrivate - : EVPKeyPointer::NewRawPublic; - break; - default: - return args.GetReturnValue().Set(false); - } - - auto pkey = fn(id, - ncrypto::Buffer{ - .data = key_data.data(), - .len = key_data.size(), - }); - if (!pkey) { - return args.GetReturnValue().Set(false); - } - key->data_ = KeyObjectData::CreateAsymmetric(type, std::move(pkey)); - CHECK(key->data_); - - args.GetReturnValue().Set(true); -#else - Environment* env = Environment::GetCurrent(args); - THROW_ERR_INVALID_ARG_VALUE(env, "Unsupported key type"); -#endif -} - void KeyObjectHandle::Equals(const FunctionCallbackInfo& args) { KeyObjectHandle* self_handle; KeyObjectHandle* arg_handle; @@ -1471,59 +1600,6 @@ void KeyObjectHandle::ExportECPrivateRaw( .FromMaybe(Local())); } -void KeyObjectHandle::InitECPrivateRaw( - const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - KeyObjectHandle* key; - ASSIGN_OR_RETURN_UNWRAP(&key, args.This()); - - CHECK(args[0]->IsString()); - Utf8Value name(env->isolate(), args[0]); - - ArrayBufferOrViewContents key_data(args[1]); - - MarkPopErrorOnReturn mark_pop_error_on_return; - - int nid = ncrypto::Ec::GetCurveIdFromName(*name); - if (nid == NID_undef) return THROW_ERR_CRYPTO_INVALID_CURVE(env); - - auto eckey = ECKeyPointer::NewByCurveName(nid); - if (!eckey) return args.GetReturnValue().Set(false); - - // Validate key data size matches the curve's expected private key length - const auto group = eckey.getGroup(); - auto order = BignumPointer::New(); - CHECK(order); - CHECK(EC_GROUP_get_order(group, order.get(), nullptr)); - if (key_data.size() != order.byteLength()) - return args.GetReturnValue().Set(false); - - BignumPointer priv_bn(key_data.data(), key_data.size()); - if (!priv_bn) return args.GetReturnValue().Set(false); - - if (!eckey.setPrivateKey(priv_bn)) return args.GetReturnValue().Set(false); - - // Compute public key from private key - auto pub_point = ECPointPointer::New(group); - if (!pub_point || !pub_point.mul(group, priv_bn.get())) { - return args.GetReturnValue().Set(false); - } - - if (!eckey.setPublicKey(pub_point)) return args.GetReturnValue().Set(false); - - auto pkey = EVPKeyPointer::New(); - if (!pkey.assign(eckey)) { - return args.GetReturnValue().Set(false); - } - - eckey.release(); - - key->data_ = - KeyObjectData::CreateAsymmetric(kKeyTypePrivate, std::move(pkey)); - - args.GetReturnValue().Set(true); -} - void KeyObjectHandle::RawSeed(const v8::FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); KeyObjectHandle* key; diff --git a/src/crypto/crypto_keys.h b/src/crypto/crypto_keys.h index 4a8438b38c9f1e..237a45ec955beb 100644 --- a/src/crypto/crypto_keys.h +++ b/src/crypto/crypto_keys.h @@ -150,9 +150,6 @@ class KeyObjectHandle : public BaseObject { static void New(const v8::FunctionCallbackInfo& args); static void Init(const v8::FunctionCallbackInfo& args); - static void InitECRaw(const v8::FunctionCallbackInfo& args); - static void InitEDRaw(const v8::FunctionCallbackInfo& args); - static void InitJWK(const v8::FunctionCallbackInfo& args); static void GetKeyDetail(const v8::FunctionCallbackInfo& args); static void Equals(const v8::FunctionCallbackInfo& args); @@ -176,8 +173,6 @@ class KeyObjectHandle : public BaseObject { const v8::FunctionCallbackInfo& args); static void ExportECPrivateRaw( const v8::FunctionCallbackInfo& args); - static void InitECPrivateRaw(const v8::FunctionCallbackInfo& args); - static void InitPqcRaw(const v8::FunctionCallbackInfo& args); static void RawSeed(const v8::FunctionCallbackInfo& args); v8::MaybeLocal ExportSecretKey() const; diff --git a/src/crypto/crypto_ml_dsa.cc b/src/crypto/crypto_ml_dsa.cc index 65f7053cc1fa1d..75cb17ab038091 100644 --- a/src/crypto/crypto_ml_dsa.cc +++ b/src/crypto/crypto_ml_dsa.cc @@ -7,6 +7,7 @@ namespace node { using ncrypto::DataPointer; +using ncrypto::EVPKeyPointer; using v8::Local; using v8::Object; using v8::String; @@ -80,6 +81,85 @@ bool ExportJwkMlDsaKey(Environment* env, .IsNothing() || !trySetKey(env, pkey.rawPublicKey(), target, env->jwk_pub_string())); } + +KeyObjectData ImportJWKAkpKey(Environment* env, Local jwk) { + Local alg_value; + Local pub_value; + Local priv_value; + + if (!jwk->Get(env->context(), env->jwk_alg_string()).ToLocal(&alg_value) || + !jwk->Get(env->context(), env->jwk_pub_string()).ToLocal(&pub_value) || + !jwk->Get(env->context(), env->jwk_priv_string()).ToLocal(&priv_value)) { + return {}; + } + + static constexpr int kMlDsaIds[] = { + EVP_PKEY_ML_DSA_44, EVP_PKEY_ML_DSA_65, EVP_PKEY_ML_DSA_87}; + + Utf8Value alg(env->isolate(), + alg_value->IsString() ? alg_value.As() + : String::Empty(env->isolate())); + + int id = NID_undef; + for (int candidate : kMlDsaIds) { + if (strcmp(*alg, GetMlDsaAlgorithmName(candidate)) == 0) { + id = candidate; + break; + } + } + + if (id == NID_undef) { + THROW_ERR_CRYPTO_INVALID_JWK(env, "Unsupported JWK AKP \"alg\""); + return {}; + } + + if (!pub_value->IsString() || + (!priv_value->IsUndefined() && !priv_value->IsString())) { + THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK AKP key"); + return {}; + } + + KeyType type = priv_value->IsString() ? kKeyTypePrivate : kKeyTypePublic; + + EVPKeyPointer pkey; + if (type == kKeyTypePrivate) { + ByteSource seed = + ByteSource::FromEncodedString(env, priv_value.As()); + pkey = + EVPKeyPointer::NewRawSeed(id, + ncrypto::Buffer{ + .data = seed.data(), + .len = seed.size(), + }); + } else { + ByteSource pub = ByteSource::FromEncodedString(env, pub_value.As()); + pkey = + EVPKeyPointer::NewRawPublic(id, + ncrypto::Buffer{ + .data = pub.data(), + .len = pub.size(), + }); + } + + if (!pkey) { + THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK AKP key"); + return {}; + } + + // When importing a private key, verify that the JWK's pub field matches + // the public key derived from the seed. + if (type == kKeyTypePrivate && pub_value->IsString()) { + ByteSource pub = ByteSource::FromEncodedString(env, pub_value.As()); + auto derived_pub = pkey.rawPublicKey(); + if (!derived_pub || derived_pub.size() != pub.size() || + CRYPTO_memcmp(derived_pub.get(), pub.data(), pub.size()) != 0) { + THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK AKP key"); + return {}; + } + } + + return KeyObjectData::CreateAsymmetric(type, std::move(pkey)); +} #endif } // namespace crypto } // namespace node diff --git a/src/crypto/crypto_ml_dsa.h b/src/crypto/crypto_ml_dsa.h index e4739fcdd7fda7..8f8a395c5e94f5 100644 --- a/src/crypto/crypto_ml_dsa.h +++ b/src/crypto/crypto_ml_dsa.h @@ -13,6 +13,8 @@ namespace crypto { bool ExportJwkMlDsaKey(Environment* env, const KeyObjectData& key, v8::Local target); + +KeyObjectData ImportJWKAkpKey(Environment* env, v8::Local jwk); #endif } // namespace crypto } // namespace node diff --git a/src/crypto/crypto_rsa.cc b/src/crypto/crypto_rsa.cc index 3619c1d21dd238..e39a7fe72de651 100644 --- a/src/crypto/crypto_rsa.cc +++ b/src/crypto/crypto_rsa.cc @@ -318,10 +318,7 @@ bool ExportJWKRsaKey(Environment* env, return true; } -KeyObjectData ImportJWKRsaKey(Environment* env, - Local jwk, - const FunctionCallbackInfo& args, - unsigned int offset) { +KeyObjectData ImportJWKRsaKey(Environment* env, Local jwk) { Local n_value; Local e_value; Local d_value; @@ -395,6 +392,19 @@ KeyObjectData ImportJWKRsaKey(Environment* env, THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK RSA key"); return {}; } + + // Verify that n == p * q. + const auto& pub = rsa_view.getPublicKey(); + const auto& priv = rsa_view.getPrivateKey(); + auto pq = BignumPointer::New(); + BN_CTX* ctx = BN_CTX_new(); + bool n_valid = ctx && pq && BN_mul(pq.get(), priv.p, priv.q, ctx) == 1 && + BN_cmp(pq.get(), pub.n) == 0; + BN_CTX_free(ctx); + if (!n_valid) { + THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK RSA key"); + return {}; + } } auto pkey = EVPKeyPointer::NewRSA(std::move(rsa)); diff --git a/src/crypto/crypto_rsa.h b/src/crypto/crypto_rsa.h index 5b7f90f502df03..8ca657ea101fff 100644 --- a/src/crypto/crypto_rsa.h +++ b/src/crypto/crypto_rsa.h @@ -92,10 +92,7 @@ bool ExportJWKRsaKey(Environment* env, const KeyObjectData& key, v8::Local target); -KeyObjectData ImportJWKRsaKey(Environment* env, - v8::Local jwk, - const v8::FunctionCallbackInfo& args, - unsigned int offset); +KeyObjectData ImportJWKRsaKey(Environment* env, v8::Local jwk); bool GetRsaKeyDetail(Environment* env, const KeyObjectData& key, diff --git a/src/crypto/crypto_sig.cc b/src/crypto/crypto_sig.cc index 7ecee3525f4fca..692239eb538ef5 100644 --- a/src/crypto/crypto_sig.cc +++ b/src/crypto/crypto_sig.cc @@ -614,7 +614,7 @@ Maybe SignTraits::AdditionalConfig( params->key = std::move(data); } - ArrayBufferOrViewContents data(args[offset + 5]); + ArrayBufferOrViewContents data(args[offset + 6]); if (!data.CheckSizeInt32()) [[unlikely]] { THROW_ERR_OUT_OF_RANGE(env, "data is too big"); return Nothing(); @@ -623,8 +623,8 @@ Maybe SignTraits::AdditionalConfig( ? data.ToCopy() : data.ToByteSource(); - if (args[offset + 6]->IsString()) { - Utf8Value digest(env->isolate(), args[offset + 6]); + if (args[offset + 7]->IsString()) { + Utf8Value digest(env->isolate(), args[offset + 7]); params->digest = Digest::FromName(*digest); if (!params->digest) [[unlikely]] { THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Invalid digest: %s", digest); @@ -632,27 +632,27 @@ Maybe SignTraits::AdditionalConfig( } } - if (args[offset + 7]->IsInt32()) { // Salt length + if (args[offset + 8]->IsInt32()) { // Salt length params->flags |= SignConfiguration::kHasSaltLength; params->salt_length = - GetSaltLenFromJS(args[offset + 7]).value_or(params->salt_length); + GetSaltLenFromJS(args[offset + 8]).value_or(params->salt_length); } - if (args[offset + 8]->IsUint32()) { // Padding + if (args[offset + 9]->IsUint32()) { // Padding params->flags |= SignConfiguration::kHasPadding; params->padding = - GetPaddingFromJS(params->key.GetAsymmetricKey(), args[offset + 8]); + GetPaddingFromJS(params->key.GetAsymmetricKey(), args[offset + 9]); } - if (args[offset + 9]->IsUint32()) { // DSA Encoding - params->dsa_encoding = GetDSASigEncFromJS(args[offset + 9]); + if (args[offset + 10]->IsUint32()) { // DSA Encoding + params->dsa_encoding = GetDSASigEncFromJS(args[offset + 10]); if (params->dsa_encoding == DSASigEnc::Invalid) [[unlikely]] { THROW_ERR_OUT_OF_RANGE(env, "invalid signature encoding"); return Nothing(); } } - if (!args[offset + 10]->IsUndefined()) { // Context string - ArrayBufferOrViewContents context_string(args[offset + 10]); + if (!args[offset + 11]->IsUndefined()) { // Context string + ArrayBufferOrViewContents context_string(args[offset + 11]); if (context_string.size() > 255) [[unlikely]] { THROW_ERR_OUT_OF_RANGE(env, "context string must be at most 255 bytes"); return Nothing(); @@ -664,7 +664,7 @@ Maybe SignTraits::AdditionalConfig( } if (params->mode == SignConfiguration::Mode::Verify) { - ArrayBufferOrViewContents signature(args[offset + 11]); + ArrayBufferOrViewContents signature(args[offset + 12]); if (!signature.CheckSizeInt32()) [[unlikely]] { THROW_ERR_OUT_OF_RANGE(env, "signature is too big"); return Nothing(); diff --git a/test/parallel/test-crypto-key-objects.js b/test/parallel/test-crypto-key-objects.js index 6c1c3fd3afa448..52fcad0882e1c7 100644 --- a/test/parallel/test-crypto-key-objects.js +++ b/test/parallel/test-crypto-key-objects.js @@ -246,6 +246,18 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' }); + // Importing an RSA private JWK where n does not equal p * q should fail. + assert.throws( + () => createPrivateKey({ key: { ...jwk, n: `A${publicJwk.n.slice(1)}` }, format: 'jwk' }), + { code: 'ERR_CRYPTO_INVALID_JWK' } + ); + + // Importing a public-only RSA JWK as a private key should fail. + assert.throws( + () => createPrivateKey({ key: publicJwk, format: 'jwk' }), + { code: 'ERR_CRYPTO_INVALID_JWK' } + ); + const publicDER = publicKey.export({ format: 'der', type: 'pkcs1' @@ -461,6 +473,51 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', } }); +// Importing an OKP private JWK where x does not match d should fail. +{ + const okpJwk = { + crv: 'Ed25519', + x: 'K1wIouqnuiA04b3WrMa-xKIKIpfHetNZRv3h9fBf768', + d: 'wVK6M3SMhQh3NK-7GRrSV-BVWQx1FO5pW8hhQeu_NdA', + kty: 'OKP' + }; + + assert.throws( + () => createPrivateKey({ + key: { ...okpJwk, x: `A${okpJwk.x.slice(1)}` }, + format: 'jwk', + }), + { code: 'ERR_CRYPTO_INVALID_JWK' } + ); + + // Importing a public-only OKP JWK as a private key should fail. + assert.throws( + () => createPrivateKey({ + key: { kty: okpJwk.kty, crv: okpJwk.crv, x: okpJwk.x }, + format: 'jwk', + }), + { code: 'ERR_CRYPTO_INVALID_JWK' } + ); + + // Importing an OKP JWK with missing crv should fail. + assert.throws( + () => createPublicKey({ + key: { kty: okpJwk.kty, x: okpJwk.x }, + format: 'jwk', + }), + { code: 'ERR_CRYPTO_INVALID_JWK' } + ); + + // Importing an OKP JWK with invalid crv should fail. + assert.throws( + () => createPublicKey({ + key: { ...okpJwk, crv: 'invalid' }, + format: 'jwk', + }), + { code: 'ERR_CRYPTO_INVALID_JWK' } + ); +} + [ { private: fixtures.readKey('ec_p256_private.pem', 'ascii'), public: fixtures.readKey('ec_p256_public.pem', 'ascii'), @@ -593,6 +650,52 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', } }); +// Importing an EC private JWK where x does not match d should fail. +{ + const ecJwk = { + crv: 'P-256', + d: 'DxBsPQPIgMuMyQbxzbb9toew6Ev6e9O6ZhpxLNgmAEo', + kty: 'EC', + x: 'X0mMYR_uleZSIPjNztIkAS3_ud5LhNpbiIFp6fNf2Gs', + y: 'UbJuPy2Xi0lW7UYTBxPK3yGgDu9EAKYIecjkHX5s2lI' + }; + + assert.throws( + () => createPrivateKey({ + key: { ...ecJwk, x: `A${ecJwk.x.slice(1)}` }, + format: 'jwk', + }), + { code: 'ERR_CRYPTO_INVALID_JWK' } + ); + + // Importing a public-only EC JWK as a private key should fail. + assert.throws( + () => createPrivateKey({ + key: { kty: ecJwk.kty, crv: ecJwk.crv, x: ecJwk.x, y: ecJwk.y }, + format: 'jwk', + }), + { code: 'ERR_CRYPTO_INVALID_JWK' } + ); + + // Importing an EC JWK with missing crv should fail. + assert.throws( + () => createPublicKey({ + key: { kty: ecJwk.kty, x: ecJwk.x, y: ecJwk.y }, + format: 'jwk', + }), + { code: 'ERR_CRYPTO_INVALID_JWK' } + ); + + // Importing an EC JWK with invalid crv should fail. + assert.throws( + () => createPublicKey({ + key: { ...ecJwk, crv: 'invalid' }, + format: 'jwk', + }), + { code: 'ERR_CRYPTO_INVALID_CURVE' } + ); +} + { // Reading an encrypted key without a passphrase should fail. assert.throws(() => createPrivateKey(privateDsa), hasOpenSSL3 ? { diff --git a/test/parallel/test-crypto-pqc-key-objects-ml-dsa.js b/test/parallel/test-crypto-pqc-key-objects-ml-dsa.js index 1a832609d3f813..ddce614a56ea9b 100644 --- a/test/parallel/test-crypto-pqc-key-objects-ml-dsa.js +++ b/test/parallel/test-crypto-pqc-key-objects-ml-dsa.js @@ -143,18 +143,28 @@ for (const [asymmetricKeyType, pubLen] of [ if (hasOpenSSL(3, 5)) { assert.throws(() => createPrivateKey({ format, key: { ...jwk, alg: 'ml-dsa-44' } }), - { code: 'ERR_INVALID_ARG_VALUE', message: /must be one of: 'ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87'/ }); + { code: 'ERR_CRYPTO_INVALID_JWK' }); assert.throws(() => createPrivateKey({ format, key: { ...jwk, alg: undefined } }), - { code: 'ERR_INVALID_ARG_VALUE', message: /must be one of: 'ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87'/ }); + { code: 'ERR_CRYPTO_INVALID_JWK' }); assert.throws(() => createPrivateKey({ format, key: { ...jwk, pub: undefined } }), - { code: 'ERR_INVALID_ARG_TYPE', message: /The "key\.pub" property must be of type string/ }); + { code: 'ERR_CRYPTO_INVALID_JWK' }); assert.throws(() => createPrivateKey({ format, key: { ...jwk, priv: undefined } }), - { code: 'ERR_INVALID_ARG_TYPE', message: /The "key\.priv" property must be of type string/ }); + { code: 'ERR_CRYPTO_INVALID_JWK', message: /JWK does not contain private key material/ }); assert.throws(() => createPrivateKey({ format, key: { ...jwk, priv: Buffer.alloc(33).toString('base64url') } }), { code: 'ERR_CRYPTO_INVALID_JWK' }); - assert.throws(() => createPublicKey({ format, key: { ...jwk, pub: Buffer.alloc(1313).toString('base64url') } }), + // eslint-disable-next-line @stylistic/js/max-len + assert.throws(() => createPublicKey({ format, key: { kty: jwk.kty, alg: jwk.alg, pub: Buffer.alloc(1313).toString('base64url') } }), { code: 'ERR_CRYPTO_INVALID_JWK' }); + // Importing an ML-DSA private JWK where pub does not match priv should fail. + assert.throws( + () => createPrivateKey({ + format, + key: { ...jwk, pub: `${jwk.pub[0] === 'A' ? 'B' : 'A'}${jwk.pub.slice(1)}` }, + }), + { code: 'ERR_CRYPTO_INVALID_JWK' } + ); + assert.ok(createPrivateKey({ format, key: jwk })); assert.ok(createPublicKey({ format, key: jwk })); diff --git a/test/parallel/test-webcrypto-export-import-ml-dsa.js b/test/parallel/test-webcrypto-export-import-ml-dsa.js index ebd61c67f55d32..f522b2cd5066e7 100644 --- a/test/parallel/test-webcrypto-export-import-ml-dsa.js +++ b/test/parallel/test-webcrypto-export-import-ml-dsa.js @@ -320,7 +320,7 @@ async function testImportJwk({ name, publicUsages, privateUsages }, extractable) { name }, extractable, privateUsages), - { message: 'Invalid JWK' }); + { message: 'Invalid keyData' }); await assert.rejects( subtle.importKey(