diff --git a/benchmark/crypto/create-keyobject.js b/benchmark/crypto/create-keyobject.js index 1e98ac347d7..75988031abf 100644 --- a/benchmark/crypto/create-keyobject.js +++ b/benchmark/crypto/create-keyobject.js @@ -18,13 +18,14 @@ function readKeyPair(publicKeyName, privateKeyName) { } const keyFixtures = { - ec: readKeyPair('ec_p256_public', 'ec_p256_private'), - rsa: readKeyPair('rsa_public_2048', 'rsa_private_2048'), - ed25519: readKeyPair('ed25519_public', 'ed25519_private'), + 'ec': readKeyPair('ec_p256_public', 'ec_p256_private'), + 'rsa': readKeyPair('rsa_public_2048', 'rsa_private_2048'), + 'ed25519': readKeyPair('ed25519_public', 'ed25519_private'), + 'ml-dsa-44': readKeyPair('ml_dsa_44_public', 'ml_dsa_44_private'), }; const bench = common.createBenchmark(main, { - keyType: ['rsa', 'ec', 'ed25519'], + keyType: ['rsa', 'ec', 'ed25519', 'ml-dsa-44'], keyFormat: ['pkcs8', 'spki', 'der-pkcs8', 'der-spki', 'jwk-public', 'jwk-private'], n: [1e3], }); diff --git a/benchmark/crypto/oneshot-sign.js b/benchmark/crypto/oneshot-sign.js index 88e67b9dfb1..97372606a10 100644 --- a/benchmark/crypto/oneshot-sign.js +++ b/benchmark/crypto/oneshot-sign.js @@ -6,10 +6,15 @@ const fs = require('fs'); const path = require('path'); const fixtures_keydir = path.resolve(__dirname, '../../test/fixtures/keys/'); +function readKey(name) { + return fs.readFileSync(`${fixtures_keydir}/${name}.pem`, 'utf8'); +} + const keyFixtures = { - ec: fs.readFileSync(`${fixtures_keydir}/ec_p256_private.pem`, 'utf-8'), - rsa: fs.readFileSync(`${fixtures_keydir}/rsa_private_2048.pem`, 'utf-8'), - ed25519: fs.readFileSync(`${fixtures_keydir}/ed25519_private.pem`, 'utf-8'), + 'ec': readKey('ec_p256_private'), + 'rsa': readKey('rsa_private_2048'), + 'ed25519': readKey('ed25519_private'), + 'ml-dsa-44': readKey('ml_dsa_44_private'), }; const data = crypto.randomBytes(256); @@ -18,7 +23,7 @@ let pems; let keyObjects; const bench = common.createBenchmark(main, { - keyType: ['rsa', 'ec', 'ed25519'], + keyType: ['rsa', 'ec', 'ed25519', 'ml-dsa-44'], mode: ['sync', 'async', 'async-parallel'], keyFormat: ['pem', 'der', 'jwk', 'keyObject', 'keyObject.unique'], n: [1e3], @@ -90,6 +95,7 @@ function main({ n, mode, keyFormat, keyType }) { digest = 'sha256'; break; case 'ed25519': + case 'ml-dsa-44': break; default: throw new Error('not implemented'); diff --git a/benchmark/crypto/oneshot-verify.js b/benchmark/crypto/oneshot-verify.js index 121be28d4d9..9569d5168f6 100644 --- a/benchmark/crypto/oneshot-verify.js +++ b/benchmark/crypto/oneshot-verify.js @@ -18,9 +18,10 @@ function readKeyPair(publicKeyName, privateKeyName) { } const keyFixtures = { - ec: readKeyPair('ec_p256_public', 'ec_p256_private'), - rsa: readKeyPair('rsa_public_2048', 'rsa_private_2048'), - ed25519: readKeyPair('ed25519_public', 'ed25519_private'), + 'ec': readKeyPair('ec_p256_public', 'ec_p256_private'), + 'rsa': readKeyPair('rsa_public_2048', 'rsa_private_2048'), + 'ed25519': readKeyPair('ed25519_public', 'ed25519_private'), + 'ml-dsa-44': readKeyPair('ml_dsa_44_public', 'ml_dsa_44_private'), }; const data = crypto.randomBytes(256); @@ -29,7 +30,7 @@ let pems; let keyObjects; const bench = common.createBenchmark(main, { - keyType: ['rsa', 'ec', 'ed25519'], + keyType: ['rsa', 'ec', 'ed25519', 'ml-dsa-44'], mode: ['sync', 'async', 'async-parallel'], keyFormat: ['pem', 'der', 'jwk', 'keyObject', 'keyObject.unique'], n: [1e3], @@ -104,6 +105,7 @@ function main({ n, mode, keyFormat, keyType }) { digest = 'sha256'; break; case 'ed25519': + case 'ml-dsa-44': break; default: throw new Error('not implemented'); diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index cb6b6ab4a61..a2bfe874650 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -1897,6 +1897,31 @@ EVPKeyPointer EVPKeyPointer::NewRawPrivate( EVP_PKEY_new_raw_private_key(id, nullptr, data.data, data.len)); } +#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5 +EVPKeyPointer EVPKeyPointer::NewRawSeed( + int id, const Buffer& data) { + if (id == 0) return {}; + + OSSL_PARAM params[] = { + OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_ML_DSA_SEED, + const_cast(data.data), + data.len), + OSSL_PARAM_END}; + + EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(id, nullptr); + if (ctx == nullptr) return {}; + + EVP_PKEY* pkey = nullptr; + if (ctx == nullptr || EVP_PKEY_fromdata_init(ctx) <= 0 || + EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_KEYPAIR, params) <= 0) { + EVP_PKEY_CTX_free(ctx); + return {}; + } + + return EVPKeyPointer(pkey); +} +#endif + EVPKeyPointer EVPKeyPointer::NewDH(DHPointer&& dh) { if (!dh) return {}; auto key = New(); @@ -1942,7 +1967,16 @@ EVP_PKEY* EVPKeyPointer::release() { int EVPKeyPointer::id(const EVP_PKEY* key) { if (key == nullptr) return 0; - return EVP_PKEY_id(key); + int type = EVP_PKEY_id(key); +#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5 + // https://github.com/openssl/openssl/issues/27738#issuecomment-3013215870 + if (type == -1) { + if (EVP_PKEY_is_a(key, "ML-DSA-44")) return EVP_PKEY_ML_DSA_44; + if (EVP_PKEY_is_a(key, "ML-DSA-65")) return EVP_PKEY_ML_DSA_65; + if (EVP_PKEY_is_a(key, "ML-DSA-87")) return EVP_PKEY_ML_DSA_87; + } +#endif + return type; } int EVPKeyPointer::base_id(const EVP_PKEY* key) { @@ -1998,6 +2032,31 @@ DataPointer EVPKeyPointer::rawPublicKey() const { return {}; } +#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5 +DataPointer EVPKeyPointer::rawSeed() const { + if (!pkey_) return {}; + switch (id()) { + case EVP_PKEY_ML_DSA_44: + case EVP_PKEY_ML_DSA_65: + case EVP_PKEY_ML_DSA_87: + break; + default: + unreachable(); + } + + size_t seed_len = 32; + if (auto data = DataPointer::Alloc(seed_len)) { + const Buffer buf = data; + size_t len = data.size(); + if (EVP_PKEY_get_octet_string_param( + get(), OSSL_PKEY_PARAM_ML_DSA_SEED, buf.data, len, &seed_len) != 1) + return {}; + return data; + } + return {}; +} +#endif + DataPointer EVPKeyPointer::rawPrivateKey() const { if (!pkey_) return {}; if (auto data = DataPointer::Alloc(rawPrivateKeySize())) { @@ -2453,7 +2512,18 @@ bool EVPKeyPointer::isRsaVariant() const { bool EVPKeyPointer::isOneShotVariant() const { if (!pkey_) return false; int type = id(); - return type == EVP_PKEY_ED25519 || type == EVP_PKEY_ED448; + switch (type) { + case EVP_PKEY_ED25519: + case EVP_PKEY_ED448: +#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5 + case EVP_PKEY_ML_DSA_44: + case EVP_PKEY_ML_DSA_65: + case EVP_PKEY_ML_DSA_87: +#endif + return true; + default: + return false; + } } bool EVPKeyPointer::isSigVariant() const { diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h index 28e836f0bdb..82af70798f3 100644 --- a/deps/ncrypto/ncrypto.h +++ b/deps/ncrypto/ncrypto.h @@ -30,6 +30,9 @@ #if OPENSSL_VERSION_MAJOR >= 3 #define OSSL3_CONST const +#if OPENSSL_VERSION_MINOR >= 5 +#include +#endif #else #define OSSL3_CONST #endif @@ -817,6 +820,10 @@ class EVPKeyPointer final { const Buffer& data); static EVPKeyPointer NewRawPrivate(int id, const Buffer& data); +#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5 + static EVPKeyPointer NewRawSeed(int id, + const Buffer& data); +#endif static EVPKeyPointer NewDH(DHPointer&& dh); static EVPKeyPointer NewRSA(RSAPointer&& rsa); @@ -910,6 +917,10 @@ class EVPKeyPointer final { DataPointer rawPrivateKey() const; BIOPointer derPublicKey() const; +#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5 + DataPointer rawSeed() const; +#endif + Result writePrivateKey( const PrivateKeyEncodingConfig& config) const; Result writePublicKey( diff --git a/doc/api/crypto.md b/doc/api/crypto.md index 62c7b4976c6..cde3893ab4a 100644 --- a/doc/api/crypto.md +++ b/doc/api/crypto.md @@ -1916,6 +1916,9 @@ This can be called many times with new data as it is streamed. * `type` {string} Must be `'rsa'`, `'rsa-pss'`, `'dsa'`, `'ec'`, `'ed25519'`, - `'ed448'`, `'x25519'`, `'x448'`, or `'dh'`. + `'ed448'`, `'x25519'`, `'x448'`, `'dh'`, `'ml-dsa-44'`[^openssl35], + `'ml-dsa-65'`[^openssl35], or `'ml-dsa-87'`[^openssl35]. * `options` {Object} * `modulusLength` {number} Key size in bits (RSA, DSA). * `publicExponent` {number} Public exponent (RSA). **Default:** `0x10001`. @@ -3767,6 +3786,9 @@ a `Promise` for an `Object` with `publicKey` and `privateKey` properties. * `type` {string} Must be `'rsa'`, `'rsa-pss'`, `'dsa'`, `'ec'`, `'ed25519'`, - `'ed448'`, `'x25519'`, `'x448'`, or `'dh'`. + `'ed448'`, `'x25519'`, `'x448'`, `'dh'`, `'ml-dsa-44'`[^openssl35], + `'ml-dsa-65'`[^openssl35], or `'ml-dsa-87'`[^openssl35]. * `options` {Object} * `modulusLength` {number} Key size in bits (RSA, DSA). * `publicExponent` {number} Public exponent (RSA). **Default:** `0x10001`. @@ -3816,7 +3839,7 @@ changes: * `privateKey` {string | Buffer | KeyObject} Generates a new asymmetric key pair of the given `type`. RSA, RSA-PSS, DSA, EC, -Ed25519, Ed448, X25519, X448, and DH are currently supported. +Ed25519, Ed448, X25519, X448, DH, and ML-DSA[^openssl35] are currently supported. If a `publicKeyEncoding` or `privateKeyEncoding` was specified, this function behaves as if [`keyObject.export()`][] had been called on its result. Otherwise, @@ -5416,6 +5439,9 @@ Throws an error if FIPS mode is not available.