crypto: support ML-DSA KeyObject, sign, and verify

PR-URL: https://github.com/nodejs/node/pull/59259
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Ethan Arrowood <ethan@arrowood.dev>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Filip Skokan 2025-08-06 22:49:30 +01:00 committed by GitHub
parent 2b4a09ef8b
commit 24e28c41b5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 1482 additions and 47 deletions

View file

@ -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],
});

View file

@ -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');

View file

@ -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');

View file

@ -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<const unsigned char>& data) {
if (id == 0) return {};
OSSL_PARAM params[] = {
OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_ML_DSA_SEED,
const_cast<unsigned char*>(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<unsigned char> 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 {

View file

@ -30,6 +30,9 @@
#if OPENSSL_VERSION_MAJOR >= 3
#define OSSL3_CONST const
#if OPENSSL_VERSION_MINOR >= 5
#include <openssl/core_names.h>
#endif
#else
#define OSSL3_CONST
#endif
@ -817,6 +820,10 @@ class EVPKeyPointer final {
const Buffer<const unsigned char>& data);
static EVPKeyPointer NewRawPrivate(int id,
const Buffer<const unsigned char>& data);
#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5
static EVPKeyPointer NewRawSeed(int id,
const Buffer<const unsigned char>& 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<BIOPointer, bool> writePrivateKey(
const PrivateKeyEncodingConfig& config) const;
Result<BIOPointer, bool> writePublicKey(

View file

@ -1916,6 +1916,9 @@ This can be called many times with new data as it is streamed.
<!-- YAML
added: v11.6.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/59259
description: Add support for ML-DSA keys.
- version:
- v14.5.0
- v12.19.0
@ -2021,6 +2024,9 @@ Other key details might be exposed via this API using additional attributes.
<!-- YAML
added: v11.6.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/59259
description: Add support for ML-DSA keys.
- version:
- v13.9.0
- v12.17.0
@ -2055,6 +2061,9 @@ types are:
* `'ed25519'` (OID 1.3.101.112)
* `'ed448'` (OID 1.3.101.113)
* `'dh'` (OID 1.2.840.113549.1.3.1)
* `'ml-dsa-44'`[^openssl35] (OID 2.16.840.1.101.3.4.3.17)
* `'ml-dsa-65'`[^openssl35] (OID 2.16.840.1.101.3.4.3.18)
* `'ml-dsa-87'`[^openssl35] (OID 2.16.840.1.101.3.4.3.19)
This property is `undefined` for unrecognized `KeyObject` types and symmetric
keys.
@ -3403,6 +3412,9 @@ input.on('readable', () => {
<!-- YAML
added: v11.6.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/59259
description: Add support for ML-DSA keys.
- version: v15.12.0
pr-url: https://github.com/nodejs/node/pull/37254
description: The key can also be a JWK object.
@ -3439,6 +3451,9 @@ of the passphrase is limited to 1024 bytes.
<!-- YAML
added: v11.6.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/59259
description: Add support for ML-DSA keys.
- version: v15.12.0
pr-url: https://github.com/nodejs/node/pull/37254
description: The key can also be a JWK object.
@ -3648,6 +3663,9 @@ underlying hash function. See [`crypto.createHmac()`][] for more information.
<!-- YAML
added: v10.12.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/59259
description: Add support for ML-DSA key pairs.
- version: v18.0.0
pr-url: https://github.com/nodejs/node/pull/41678
description: Passing an invalid callback to the `callback` argument
@ -3678,7 +3696,8 @@ changes:
-->
* `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.
<!-- YAML
added: v10.12.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/59259
description: Add support for ML-DSA key pairs.
- version: v16.10.0
pr-url: https://github.com/nodejs/node/pull/39927
description: Add ability to define `RSASSA-PSS-params` sequence parameters
@ -3792,7 +3814,8 @@ changes:
-->
* `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.
<!-- YAML
added: v12.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/59259
description: Add support for ML-DSA signing.
- version: v18.0.0
pr-url: https://github.com/nodejs/node/pull/41678
description: Passing an invalid callback to the `callback` argument
@ -5445,7 +5471,10 @@ changes:
Calculates and returns the signature for `data` using the given private key and
algorithm. If `algorithm` is `null` or `undefined`, then the algorithm is
dependent upon the key type (especially Ed25519 and Ed448).
dependent upon the key type.
`algorithm` is required to be `null` or `undefined` for Ed25519, Ed448, and
ML-DSA.
If `key` is not a [`KeyObject`][], this function behaves as if `key` had been
passed to [`crypto.createPrivateKey()`][]. If it is an object, the following
@ -5526,6 +5555,9 @@ not introduce timing vulnerabilities.
<!-- YAML
added: v12.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/59259
description: Add support for ML-DSA signature verification.
- version: v18.0.0
pr-url: https://github.com/nodejs/node/pull/41678
description: Passing an invalid callback to the `callback` argument
@ -5561,7 +5593,10 @@ changes:
Verifies the given signature for `data` using the given key and algorithm. If
`algorithm` is `null` or `undefined`, then the algorithm is dependent upon the
key type (especially Ed25519 and Ed448).
key type.
`algorithm` is required to be `null` or `undefined` for Ed25519, Ed448, and
ML-DSA.
If `key` is not a [`KeyObject`][], this function behaves as if `key` had been
passed to [`crypto.createPublicKey()`][]. If it is an object, the following
@ -6150,6 +6185,8 @@ See the [list of SSL OP Flags][] for details.
</tr>
</table>
[^openssl35]: Requires OpenSSL >= 3.5
[AEAD algorithms]: https://en.wikipedia.org/wiki/Authenticated_encryption
[CCM mode]: #ccm-mode
[CVE-2021-44532]: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-44532

View file

@ -19,6 +19,9 @@ const {
kKeyVariantRSA_SSA_PKCS1_v1_5,
EVP_PKEY_ED25519,
EVP_PKEY_ED448,
EVP_PKEY_ML_DSA_44,
EVP_PKEY_ML_DSA_65,
EVP_PKEY_ML_DSA_87,
EVP_PKEY_X25519,
EVP_PKEY_X448,
OPENSSL_EC_NAMED_CURVE,
@ -162,6 +165,16 @@ function parseKeyEncoding(keyType, options = kEmptyObject) {
];
}
const ids = {
'ed25519': EVP_PKEY_ED25519,
'ed448': EVP_PKEY_ED448,
'x25519': EVP_PKEY_X25519,
'x448': EVP_PKEY_X448,
'ml-dsa-44': EVP_PKEY_ML_DSA_44,
'ml-dsa-65': EVP_PKEY_ML_DSA_65,
'ml-dsa-87': EVP_PKEY_ML_DSA_87,
};
function createJob(mode, type, options) {
validateString(type, 'type');
@ -278,23 +291,14 @@ function createJob(mode, type, options) {
case 'ed448':
case 'x25519':
case 'x448':
case 'ml-dsa-44':
case 'ml-dsa-65':
case 'ml-dsa-87':
{
let id;
switch (type) {
case 'ed25519':
id = EVP_PKEY_ED25519;
break;
case 'ed448':
id = EVP_PKEY_ED448;
break;
case 'x25519':
id = EVP_PKEY_X25519;
break;
case 'x448':
id = EVP_PKEY_X448;
break;
if (ids[type] === undefined) {
throw new ERR_INVALID_ARG_VALUE('type', type, 'must be a supported key type');
}
return new NidKeyPairGenJob(mode, id, ...encoding);
return new NidKeyPairGenJob(mode, ids[type], ...encoding);
}
case 'dh':
{

View file

@ -24,6 +24,9 @@ const {
kKeyEncodingPKCS8,
kKeyEncodingSPKI,
kKeyEncodingSEC1,
EVP_PKEY_ML_DSA_44,
EVP_PKEY_ML_DSA_65,
EVP_PKEY_ML_DSA_87,
} = internalBinding('crypto');
const {
@ -509,12 +512,54 @@ 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']);
if (EVP_PKEY_ML_DSA_44 || EVP_PKEY_ML_DSA_65 || EVP_PKEY_ML_DSA_87) {
validateOneOf(
key.kty, 'key.kty', ['RSA', 'EC', 'OKP', 'AKP']);
} else {
validateOneOf(
key.kty, 'key.kty', ['RSA', 'EC', 'OKP']);
}
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.initMlDsaRaw(key.alg, keyData, keyType)) {
throw new ERR_CRYPTO_INVALID_JWK();
}
return handle;
}
if (key.kty === 'OKP') {
validateString(key.crv, 'key.crv');
validateOneOf(

View file

@ -342,6 +342,7 @@
'src/crypto/crypto_cipher.cc',
'src/crypto/crypto_context.cc',
'src/crypto/crypto_ec.cc',
'src/crypto/crypto_ml_dsa.cc',
'src/crypto/crypto_hmac.cc',
'src/crypto/crypto_random.cc',
'src/crypto/crypto_rsa.cc',
@ -373,6 +374,7 @@
'src/crypto/crypto_clienthello.h',
'src/crypto/crypto_context.h',
'src/crypto/crypto_ec.h',
'src/crypto/crypto_ml_dsa.h',
'src/crypto/crypto_hkdf.h',
'src/crypto/crypto_pbkdf2.h',
'src/crypto/crypto_sig.h',

View file

@ -1,12 +1,13 @@
#include "crypto/crypto_keys.h"
#include "crypto/crypto_common.h"
#include "crypto/crypto_dsa.h"
#include "crypto/crypto_ec.h"
#include "crypto/crypto_dh.h"
#include "crypto/crypto_rsa.h"
#include "crypto/crypto_util.h"
#include "async_wrap-inl.h"
#include "base_object-inl.h"
#include "crypto/crypto_common.h"
#include "crypto/crypto_dh.h"
#include "crypto/crypto_dsa.h"
#include "crypto/crypto_ec.h"
#include "crypto/crypto_ml_dsa.h"
#include "crypto/crypto_rsa.h"
#include "crypto/crypto_util.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "node.h"
@ -176,6 +177,14 @@ bool ExportJWKAsymmetricKey(Environment* env,
// Fall through
case EVP_PKEY_X448:
return ExportJWKEdKey(env, key, target);
#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5
case EVP_PKEY_ML_DSA_44:
// Fall through
case EVP_PKEY_ML_DSA_65:
// Fall through
case EVP_PKEY_ML_DSA_87:
return ExportJwkMlDsaKey(env, key, target);
#endif
}
THROW_ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE(env);
return false;
@ -261,7 +270,7 @@ bool ExportJWKInner(Environment* env,
env, key, result.As<Object>(), handleRsaPss);
}
int GetOKPCurveFromName(const char* name) {
int GetNidFromName(const char* name) {
int nid;
if (strcmp(name, "Ed25519") == 0) {
nid = EVP_PKEY_ED25519;
@ -271,6 +280,14 @@ int GetOKPCurveFromName(const char* name) {
nid = EVP_PKEY_X25519;
} else if (strcmp(name, "X448") == 0) {
nid = EVP_PKEY_X448;
#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5
} else if (strcmp(name, "ML-DSA-44") == 0) {
nid = EVP_PKEY_ML_DSA_44;
} else if (strcmp(name, "ML-DSA-65") == 0) {
nid = EVP_PKEY_ML_DSA_65;
} else if (strcmp(name, "ML-DSA-87") == 0) {
nid = EVP_PKEY_ML_DSA_87;
#endif
} else {
nid = NID_undef;
}
@ -603,6 +620,9 @@ Local<Function> KeyObjectHandle::Initialize(Environment* env) {
SetProtoMethod(isolate, templ, "exportJwk", ExportJWK);
SetProtoMethod(isolate, templ, "initECRaw", InitECRaw);
SetProtoMethod(isolate, templ, "initEDRaw", InitEDRaw);
#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5
SetProtoMethod(isolate, templ, "initMlDsaRaw", InitMlDsaRaw);
#endif
SetProtoMethod(isolate, templ, "initJwk", InitJWK);
SetProtoMethod(isolate, templ, "keyDetail", GetKeyDetail);
SetProtoMethod(isolate, templ, "equals", Equals);
@ -623,6 +643,9 @@ void KeyObjectHandle::RegisterExternalReferences(
registry->Register(ExportJWK);
registry->Register(InitECRaw);
registry->Register(InitEDRaw);
#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5
registry->Register(InitMlDsaRaw);
#endif
registry->Register(InitJWK);
registry->Register(GetKeyDetail);
registry->Register(Equals);
@ -789,7 +812,7 @@ void KeyObjectHandle::InitEDRaw(const FunctionCallbackInfo<Value>& args) {
new_key_fn fn = type == kKeyTypePrivate ? EVPKeyPointer::NewRawPrivate
: EVPKeyPointer::NewRawPublic;
int id = GetOKPCurveFromName(*name);
int id = GetNidFromName(*name);
switch (id) {
case EVP_PKEY_X25519:
@ -815,6 +838,51 @@ void KeyObjectHandle::InitEDRaw(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(true);
}
#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5
void KeyObjectHandle::InitMlDsaRaw(const FunctionCallbackInfo<Value>& 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<unsigned char> key_data(args[1]);
KeyType type = FromV8Value<KeyType>(args[2]);
MarkPopErrorOnReturn mark_pop_error_on_return;
typedef EVPKeyPointer (*new_key_fn)(
int, const ncrypto::Buffer<const unsigned char>&);
new_key_fn fn = type == kKeyTypePrivate ? EVPKeyPointer::NewRawSeed
: EVPKeyPointer::NewRawPublic;
int id = GetNidFromName(*name);
switch (id) {
case EVP_PKEY_ML_DSA_44:
case EVP_PKEY_ML_DSA_65:
case EVP_PKEY_ML_DSA_87: {
auto pkey = fn(id,
ncrypto::Buffer<const unsigned char>{
.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:
UNREACHABLE();
}
args.GetReturnValue().Set(true);
}
#endif
void KeyObjectHandle::Equals(const FunctionCallbackInfo<Value>& args) {
KeyObjectHandle* self_handle;
KeyObjectHandle* arg_handle;
@ -903,6 +971,14 @@ Local<Value> KeyObjectHandle::GetAsymmetricKeyType() const {
return env()->crypto_x25519_string();
case EVP_PKEY_X448:
return env()->crypto_x448_string();
#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5
case EVP_PKEY_ML_DSA_44:
return env()->crypto_ml_dsa_44_string();
case EVP_PKEY_ML_DSA_65:
return env()->crypto_ml_dsa_65_string();
case EVP_PKEY_ML_DSA_87:
return env()->crypto_ml_dsa_87_string();
#endif
default:
return Undefined(env()->isolate());
}
@ -1178,6 +1254,11 @@ void Initialize(Environment* env, Local<Object> target) {
NODE_DEFINE_CONSTANT(target, kWebCryptoKeyFormatJWK);
NODE_DEFINE_CONSTANT(target, EVP_PKEY_ED25519);
NODE_DEFINE_CONSTANT(target, EVP_PKEY_ED448);
#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5
NODE_DEFINE_CONSTANT(target, EVP_PKEY_ML_DSA_44);
NODE_DEFINE_CONSTANT(target, EVP_PKEY_ML_DSA_65);
NODE_DEFINE_CONSTANT(target, EVP_PKEY_ML_DSA_87);
#endif
NODE_DEFINE_CONSTANT(target, EVP_PKEY_X25519);
NODE_DEFINE_CONSTANT(target, EVP_PKEY_X448);
NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS1);

View file

@ -152,6 +152,9 @@ class KeyObjectHandle : public BaseObject {
static void Init(const v8::FunctionCallbackInfo<v8::Value>& args);
static void InitECRaw(const v8::FunctionCallbackInfo<v8::Value>& args);
static void InitEDRaw(const v8::FunctionCallbackInfo<v8::Value>& args);
#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5
static void InitMlDsaRaw(const v8::FunctionCallbackInfo<v8::Value>& args);
#endif
static void InitJWK(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetKeyDetail(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Equals(const v8::FunctionCallbackInfo<v8::Value>& args);

View file

@ -0,0 +1,85 @@
#include "crypto/crypto_ml_dsa.h"
#include "crypto/crypto_util.h"
#include "env-inl.h"
#include "string_bytes.h"
#include "v8.h"
namespace node {
using ncrypto::DataPointer;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;
namespace crypto {
#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5
constexpr const char* GetMlDsaAlgorithmName(int id) {
switch (id) {
case EVP_PKEY_ML_DSA_44:
return "ML-DSA-44";
case EVP_PKEY_ML_DSA_65:
return "ML-DSA-65";
case EVP_PKEY_ML_DSA_87:
return "ML-DSA-87";
default:
return nullptr;
}
}
/**
* Exports an ML-DSA key to JWK format.
*
* The resulting JWK object contains:
* - "kty": "AKP" (Asymmetric Key Pair - required)
* - "alg": "ML-DSA-XX" (Algorithm identifier - required for "AKP")
* - "pub": "<Base64URL-encoded raw public key>" (required)
* - "priv": <"Base64URL-encoded raw seed>" (required for private keys only)
*/
bool ExportJwkMlDsaKey(Environment* env,
const KeyObjectData& key,
Local<Object> target) {
Mutex::ScopedLock lock(key.mutex());
const auto& pkey = key.GetAsymmetricKey();
const char* alg = GetMlDsaAlgorithmName(pkey.id());
CHECK(alg);
static constexpr auto trySetKey = [](Environment* env,
DataPointer data,
Local<Object> target,
Local<String> key) {
Local<Value> encoded;
if (!data) return false;
const ncrypto::Buffer<const char> out = data;
return StringBytes::Encode(env->isolate(), out.data, out.len, BASE64URL)
.ToLocal(&encoded) &&
target->Set(env->context(), key, encoded).IsJust();
};
if (key.GetKeyType() == kKeyTypePrivate) {
auto seed = pkey.rawSeed();
if (!seed) {
THROW_ERR_CRYPTO_OPERATION_FAILED(env,
"key does not have an available seed");
return false;
}
if (!trySetKey(env, pkey.rawSeed(), target, env->jwk_priv_string())) {
return false;
}
}
return !(
target->Set(env->context(), env->jwk_kty_string(), env->jwk_akp_string())
.IsNothing() ||
target
->Set(env->context(),
env->jwk_alg_string(),
OneByteString(env->isolate(), alg))
.IsNothing() ||
!trySetKey(env, pkey.rawPublicKey(), target, env->jwk_pub_string()));
}
#endif
} // namespace crypto
} // namespace node

View file

@ -0,0 +1,21 @@
#ifndef SRC_CRYPTO_CRYPTO_ML_DSA_H_
#define SRC_CRYPTO_CRYPTO_ML_DSA_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "crypto/crypto_keys.h"
#include "env.h"
#include "v8.h"
namespace node {
namespace crypto {
#if OPENSSL_VERSION_MAJOR >= 3 && OPENSSL_VERSION_MINOR >= 5
bool ExportJwkMlDsaKey(Environment* env,
const KeyObjectData& key,
v8::Local<v8::Object> target);
#endif
} // namespace crypto
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_ML_DSA_H_

View file

@ -115,6 +115,9 @@
V(crypto_ec_string, "ec") \
V(crypto_ed25519_string, "ed25519") \
V(crypto_ed448_string, "ed448") \
V(crypto_ml_dsa_44_string, "ml-dsa-44") \
V(crypto_ml_dsa_65_string, "ml-dsa-65") \
V(crypto_ml_dsa_87_string, "ml-dsa-87") \
V(crypto_x25519_string, "x25519") \
V(crypto_x448_string, "x448") \
V(crypto_rsa_string, "rsa") \
@ -220,6 +223,8 @@
V(issuer_string, "issuer") \
V(issuercert_string, "issuerCertificate") \
V(iterator_string, "Iterator") \
V(jwk_akp_string, "AKP") \
V(jwk_alg_string, "alg") \
V(jwk_crv_string, "crv") \
V(jwk_d_string, "d") \
V(jwk_dp_string, "dp") \
@ -229,13 +234,15 @@
V(jwk_ec_string, "EC") \
V(jwk_g_string, "g") \
V(jwk_k_string, "k") \
V(jwk_p_string, "p") \
V(jwk_q_string, "q") \
V(jwk_qi_string, "qi") \
V(jwk_kty_string, "kty") \
V(jwk_n_string, "n") \
V(jwk_oct_string, "oct") \
V(jwk_okp_string, "OKP") \
V(jwk_p_string, "p") \
V(jwk_priv_string, "priv") \
V(jwk_pub_string, "pub") \
V(jwk_q_string, "q") \
V(jwk_qi_string, "qi") \
V(jwk_rsa_string, "RSA") \
V(jwk_x_string, "x") \
V(jwk_y_string, "y") \

View file

@ -40,6 +40,7 @@
#include "crypto/crypto_hmac.h"
#include "crypto/crypto_keygen.h"
#include "crypto/crypto_keys.h"
#include "crypto/crypto_ml_dsa.h"
#include "crypto/crypto_pbkdf2.h"
#include "crypto/crypto_random.h"
#include "crypto/crypto_rsa.h"

View file

@ -98,6 +98,18 @@ all: \
incorrect_san_correct_subject-key.pem \
irrelevant_san_correct_subject-cert.pem \
irrelevant_san_correct_subject-key.pem \
ml_dsa_44_private.pem \
ml_dsa_44_private_seed_only.pem \
ml_dsa_44_private_priv_only.pem \
ml_dsa_44_public.pem \
ml_dsa_65_private.pem \
ml_dsa_65_private_seed_only.pem \
ml_dsa_65_private_priv_only.pem \
ml_dsa_65_public.pem \
ml_dsa_87_private.pem \
ml_dsa_87_private_seed_only.pem \
ml_dsa_87_private_priv_only.pem \
ml_dsa_87_public.pem \
#
# Create Certificate Authority: ca1
@ -855,6 +867,42 @@ ed448_private.pem:
ed448_public.pem: ed448_private.pem
openssl pkey -in ed448_private.pem -pubout -out ed448_public.pem
ml_dsa_44_private.pem:
openssl genpkey -algorithm ml-dsa-44 -out ml_dsa_44_private.pem
ml_dsa_44_private_seed_only.pem: ml_dsa_44_private.pem
openssl pkey -in ml_dsa_44_private.pem -provparam ml-dsa.output_formats=seed-only -out ml_dsa_44_private_seed_only.pem
ml_dsa_44_private_priv_only.pem: ml_dsa_44_private.pem
openssl pkey -in ml_dsa_44_private.pem -provparam ml-dsa.output_formats=priv-only -out ml_dsa_44_private_priv_only.pem
ml_dsa_44_public.pem: ml_dsa_44_private.pem
openssl pkey -in ml_dsa_44_private.pem -pubout -out ml_dsa_44_public.pem
ml_dsa_65_private.pem:
openssl genpkey -algorithm ml-dsa-65 -out ml_dsa_65_private.pem
ml_dsa_65_private_seed_only.pem: ml_dsa_65_private.pem
openssl pkey -in ml_dsa_65_private.pem -provparam ml-dsa.output_formats=seed-only -out ml_dsa_65_private_seed_only.pem
ml_dsa_65_private_priv_only.pem: ml_dsa_65_private.pem
openssl pkey -in ml_dsa_65_private.pem -provparam ml-dsa.output_formats=priv-only -out ml_dsa_65_private_priv_only.pem
ml_dsa_65_public.pem: ml_dsa_65_private.pem
openssl pkey -in ml_dsa_65_private.pem -pubout -out ml_dsa_65_public.pem
ml_dsa_87_private.pem:
openssl genpkey -algorithm ml-dsa-87 -out ml_dsa_87_private.pem
ml_dsa_87_private_seed_only.pem: ml_dsa_87_private.pem
openssl pkey -in ml_dsa_87_private.pem -provparam ml-dsa.output_formats=seed-only -out ml_dsa_87_private_seed_only.pem
ml_dsa_87_private_priv_only.pem: ml_dsa_87_private.pem
openssl pkey -in ml_dsa_87_private.pem -provparam ml-dsa.output_formats=priv-only -out ml_dsa_87_private_priv_only.pem
ml_dsa_87_public.pem: ml_dsa_87_private.pem
openssl pkey -in ml_dsa_87_private.pem -pubout -out ml_dsa_87_public.pem
x448_private.pem:
openssl genpkey -algorithm x448 -out x448_private.pem

View file

@ -0,0 +1,57 @@
-----BEGIN PRIVATE KEY-----
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
-----END PRIVATE KEY-----

View file

@ -0,0 +1,56 @@
-----BEGIN PRIVATE KEY-----
MIIKGAIBADALBglghkgBZQMEAxEEggoEBIIKAH2Jg9Ucf45KFvShuwbP+c8mBIlh
GbNebWAcTXSqvTYq8+cjY6myMXFUTUASHuc3op65BiH6kVI85ZeO/81a7NWx77Ou
zEvU6pJrq3utOolAG40Ekkn4jTLO/tCiw3XuHT3P5y8p2btiwir4jqb8SCAMPZH2
q7wUd3ujcvv+AhvCiBBJ5DRl40QwAzcAEkBEYZaFQzgQS7QpQUJqCJJoIRMFAQCJ
gxRB0kJmZCQKkhZoAiZiScQEUQgtSsQkRLQREbSBChZqCARKADJoBEaAEzRxgbSM
CBlohIBoASNxiZSAg0ZCAcWMkxhAYoIJoLgsjCgkWDZqWRRsoTRkEANkCIVMI8WB
icSJ1ChtwjCMUrZtEYIsGgAQGKQw2EgK2baEyJgEFAlA4YaQG0BKyoIEkZJxCDRO
QkiCpCSIkRKEGiNgJMUFo7ZJkbAFCEIxGKkBACONIRFS4LIx2iSBoUYCArWEGxlh
lJRBpAIIXLQRiiSQUUBsGLGJi4QA0TQx0rCE07ApQzQxA5BtC0UOASON0JBtAIVp
gkYRIheMoJZwhKINABFJFJkwSJRJghZxA0YhQjJmISQBALMME0mMlEIsCocQ2cQF
47iAQMAQgwIFERJIEwIGkwBRFBUxEZMQiChQzMQgCTiEGEZk0TYFhKiQWZhp5BIS
CBaEw0gSA4YNJCQkoAgGwSCN0MBNDCZw0cgImAgwAoQIQoQQoiABkAIgAkdRSJJJ
A6BxCRiQ4LKR4qhtihBlEzNAnAZkg8KISTRJikIiAhVNAUZmG6Nk0gANA8ZhDIkI
U6ggGBhtoCYO2pBh0kgM4EYgYxQQohhGjKgkAyYigBYCGySBAjMlWAKRVJBxohYx
S4ZoQIhwGEFGSKJlxAJk5KhA47BEARCBIRVFGgJiWEAiVAaFYhhkmiIyAaWR3MhQ
DBMADCIJGcMMYziOAbCBCzlGRCKKHDEBxCKIg7AB4wKGYTYCyoYECQYI0KJR04SF
AkRI2kQQYziAEDISEUFyJEElmSYEjLgEEQkIXERpkcQAXIQFGDJmEchhWDSCm5aN
iagh3KaIYLhoSZQMwZhpTJZNyCKMYyZOGbNlAbkl4iKNGcBk0ohpTLhxWBSJGkiR
STRJ0ZSBG6Zs06aIW4RRggJSYyAmJIYoCCdSEkJEiqSQzJKASDJKFIZsAiVy2wRM
AaZsi4IlwRJkkDYO20chuMIw1aTPh+C9Wb5kgwUk+WAmxqnhoq+2CtvTbefFqYPp
EiP8ODBv7zx6QIjiS2re1pKVXOmTsMwBfcywDiooNnIRkkuvqN/BiOhgb7NRM0sv
EMeFEGCX4/nP5EKW9VDs2sosaEfg3kk1HepAfyIZqnFvCWjeIgVYoN8gcwe5Y+nG
L7h8RVyREkxTDQC5Wb4ERsr0oxhfMCd9beLLwgQP7sESjsDiMW2awoQdb4+3bSK/
jEBW0Me9oCIVPRRmdwzPf9yMtW8c4hIy4OCrFKeGuHjV1kOcteuM44/b/XBmVEjs
NtJhHNMMKngeQT7HLj2n0dxjNL0BMjHzaAJLpH85ywhdHjL7wmfdDMoK2m7V2bht
kpEXJsSsrP8wBYo7qCM5ViuGQ3ULEjiuHavlqNJyT/nIYgfX4/CTBEJn744pfX1P
16O56ifrIVXQ1L1+DmBX57xNymoeHsw/gUBX63t+INhdg3v1F9HX+5pe6jWigmFM
qvJNc4BW/8HaxdB1S2KanPf9tHus7+uDt6yF8GHBWm6AUODTxV6qYy4HYU0adap9
vuyz8LnAMgWKW5YpLiNFvKNiX+e3Uy9gWFI6I61yCpwqgQ3fMp6f5Fq+yRvjelV7
QFULVuS6cTLAArEis7sOWjvsD81vV+gVt9KWl82vUtNxzyFQvy5fmeSnvDWUDhy/
Ka+raHjRiKy4X9hzBe8ivb51HtplLgmCefkzpkCtC3bFLP6pj6VOT+4RxMuUfwca
SZGaW9QfdgTcl69RTqDrNjgRQ8Fn2c+xS/6Y2ZS6pZyPsA7nubH3Y3bPQIXKEXjf
YgaVKQDcIYGX1iG6zEDOiiZ1twMFIIm2l/4UOxe4hm0VDyyaehoL4Swr13e+UiI6
N/oivBGB17GTtb2UqF1fQfG58bFTo2RdDdHoSct2Yh+PzuIKi8MRI0BbwMrmbegI
o1GzzPa2wscSTbOxAtFHKAaKnR9a9HwjsjuqJYzEdhOlJ8qHjRbtPkI3HqoSFHWg
g2SmK5+Rt2kwLzfyLsJjG2pzeGFF3XHUyBj1IAeoR+9yrJBJGdWEvdTMh0uYzAS4
n5VsjHZNAPunX0qZZHQTqXBgfQnPpUkHvkgNLe+oEWQDtETbAscB55rEpt0G2zg1
ZwtH21Gc+py+mnAzcOkyzgJiGW3Pfg6+HMCXojaafCSo2F0fjbAEDMC6DZn1hO1W
VJeKktTe5FfoGEfBZelOpIiCQhBK0RXRiWhhbIy+vYjQF5OzQXF6sMVX7bJlgt7o
qOAgu4hQaQiLFJWJJ/PAXAbL8hK9HUjIw19Ydri1G1ZUj6CIuD71ubYzKPGTqVgD
RrVNvMMq8QaBD6WuzV7xgsIEidHRNxaCDrcxhbh3zKI0WZk6BwOgRiz5jYr7TsLT
QJWaYUY6k4NNZazZp6Eu9rP9PUv4S5HDm5INcnWX00qoRB2PcL9XAkZyjH1KCQ8T
K4TFi8iHZumXxfzpu84NUqow3yDRe+gF6MI97Frq2VGy+jcgimEP+ZIkHsEWYUbi
Dtit+juR1L8EqVTY8BnNkcRze5JgERtg6WCAMiv6xfCmJ3jHbabQYkxTWfWcCu9c
hl5zQVMTMjVgCgiJQQzEvZnX1CC+dseBTb0ernjwoEXNi6RWC3Oz9cfa7g0BckTa
QiepjKOlZBCxeKF1IfH5lDNOFk9lhyOPivAgtlfTNKPMFpb8tSZR9kJ2JVLYmB5A
sdVca+Z+4pYGeef5A7JH5Qd3xWZlRNb/i3cKXgOu625Fou8Qw4HUwZrqU5daVb19
qP3vMJacbO7bqj8tDDamtFDXoKkUPl9Jv3XKVKpQSVFbcknJhp5Z/8VhBmkF4+vB
izG4amdLZvo6/J2m+MVD3XUq+n6IGi4hqeu6UIgcHtn0+TbWNDR81uXpV7HRmCmY
hwTDLCukXzBFCJpaOzK84cwzO9xUPoF1lbgFyEbayACp9A9mRfYQmzHsZ3EmAzXJ
zZ5mC2TwAw3bM7pVPpx4Ea8nEQGzTUi4dXahVnVulcBX7jtS02oQDA+EYBDBbBOQ
gDzboCtm1AfdZOD4eWcUXQbGACt33SAa3OcQVF0FNoj40yQ3O3/zJyz15Cy7H8pL
LEynAC55abYSjuJYPSpRdF8vX84mEQZ5UwxhgCz3kpYuyrOCAg9HvrAmzZ5s9GOj
Mgj9Tr0kyp4wYlAdY76upbi+1JhLVeCZmsA/77YCmyvNkLWUkSJ7KXE8nLI=
-----END PRIVATE KEY-----

View file

@ -0,0 +1,4 @@
-----BEGIN PRIVATE KEY-----
MDQCAQAwCwYJYIZIAWUDBAMRBCKAINu9wITD4mVi5UkAmONcotXzI+sWrxh9G7Qd
t8zISNbm
-----END PRIVATE KEY-----

30
test/fixtures/keys/ml_dsa_44_public.pem vendored Normal file
View file

@ -0,0 +1,30 @@
-----BEGIN PUBLIC KEY-----
MIIFMjALBglghkgBZQMEAxEDggUhAH2Jg9Ucf45KFvShuwbP+c8mBIlhGbNebWAc
TXSqvTYq3GZ72QcMMGhm49FLMSTXOA8n6diBQGWTzt6xQklHaVxz6xkR8CFTM8Is
H7tle1dNsaIXluJq5D04QNyUSUpjXbMemBrt+zGniZCJOgJVHsaOOiXxq5EV2afy
MmcYi/y9nT638Mft9o+1hSObAWnae3A1nzZCY3UMFuKbk3uUcO4dehh2/gzDgCqX
3jB7o4eyGB7iwlOpVYvdI9ZvEbtHcXZnkNXmNbw5zu3zUCn8zi4aVDZ4+wQWduBk
6hXPkjeRwwkgrEhiknE2GAcIklU3UhhtRjaiIjdhYBOM9SY+iRVQfziFJ5eLCm29
5vJAdAcLVD25AtBVaJBkVBBrem2tT3PyDKQeRrN4PTxURCPwvaNclVtzUtj8x/1Z
OE6X9DdStZMyCv9yKnpC38ql5lQzbDqL7Eu0sPU3ZnVlDRkZoVxSkZHseVY6TCMe
zsARrGCQnOBYWrlna1bW5LxxoIEqbJWu92XAOFc4e2quOyzL9CktmC6fgzJ+0hKI
TxxoO+NdfHiy5/PMqbccLcLdXQRY/ASlHz68rbjdRCEUMt+IcG5ASzfcA6oYJ92d
375LEb5oDf+GUPGI3g/h9zhYeVif0XeusB0pfmBWrfvyReFD3HZZDVG8T7w4uVS/
dPT3t1ZcBGYU+aLXbV0F+FN3VH6lihIdpDdENpaPMG39zjJgThU38l9HmGv+dPPG
Zyb75Ynn5/kwg8nOlpXGazasAThfDfPiDd2//ET6vkblfPpivjPNx4uBJUAe2eOH
mTJn1MBa6MxBuFqtiujMEP3nRuHVnlFTApaE6RgxTrZS0JQ6ADeJv7Eti28Jv962
tvZ6FHCvP49dMsvoRDRNmJbodaSC/hqFmY97mDvqxClW7M2moHRNAyf7kHB1HTZw
Ryp+uPnEOsYoV88Wnh8fTsZFljCm8y7SqbmAyv1+xFb5vGE45f6E7K9klk9rrIzS
ZKN9g4UVfjlyyih6ORSaQstmxhS1xhtugoJVL4GUNncl4lnhlTSn3zr9XEOUf0ZV
NjKS4mdW4oMvYYeJbv1wBy340H4X+9Y3jLheJxAVlfmDgIw02gwdoXBqr/13Bl3J
i1h6jloHOTG+pZG9s2LP6MjrJJshcegtD+1K1fJAojeM27k6mTXTY5Vi/0CVJlN7
5OgqTWtFzdTN7C42aThLGqoEVTy3CAx/o7lYclosprsXvvMjQjhUisYbHHsNPBEP
QxHJD/4iAPKlNoHK3h7bwEuVQORJs9StsqWHuqEt6ID/gl+JzgBH747G9RxyaHJY
IsIy9QbQixYDhPtL59TPp1ThOlmxxh6DSjCTBz9mtx0Auw5g30jsOT+go+V+0Fao
QmOrQBmVhD7t4VLLKeyuVCXjxHQZ/y6xK7eLnMl1ustTwANhZ/BLh2kcn9oIImwx
p86vBNeZv0eCzL2QLhkdGzfYwU5Zv8eGUDKhcCpCHZZZDdUExK2szsjbIFOn4QP9
tIeRCGE+vi6l6/zrY9yqfDJCQfXN1zRqKObGx00ArdFTTh6MQhT/7MIgCS7Zu+N3
jrCNTOp2liyKj2oEgoqKLALyWKMMqSUBrzPjd4QbvClK9H/ddSkTe1qkwF0zh2f7
706nFinrdHRXYdRB7j9061PIUjHImqYRr7ivd094GlriepXApEJ3VrxKPUuhUAe3
i1PwCyjkN0Tsm1FJF4H8k5Fgz8Wx3XiQoDEb+iINT0oT8sWG+Io=
-----END PUBLIC KEY-----

View file

@ -0,0 +1,88 @@
-----BEGIN PRIVATE KEY-----
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
5/C+6BuzkxxL/NVOWWf8cL7k
-----END PRIVATE KEY-----

View file

@ -0,0 +1,87 @@
-----BEGIN PRIVATE KEY-----
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
I7FwWPXsXy2gtufwvugbs5McS/zVTlln/HC+5A==
-----END PRIVATE KEY-----

View file

@ -0,0 +1,4 @@
-----BEGIN PRIVATE KEY-----
MDQCAQAwCwYJYIZIAWUDBAMSBCKAINV/VRK/4lzEcAb50srRJh67QPg6Q+hVgFKj
KwzZSVQY
-----END PRIVATE KEY-----

44
test/fixtures/keys/ml_dsa_65_public.pem vendored Normal file
View file

@ -0,0 +1,44 @@
-----BEGIN PUBLIC KEY-----
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
7lR+4duV
-----END PUBLIC KEY-----

106
test/fixtures/keys/ml_dsa_87_private.pem vendored Normal file
View file

@ -0,0 +1,106 @@
-----BEGIN PRIVATE KEY-----
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
3kDDdVtohnHxepbHfY4aVmkl
-----END PRIVATE KEY-----

View file

@ -0,0 +1,105 @@
-----BEGIN PRIVATE KEY-----
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
+589VN2umDAHnN5Aw3VbaIZx8XqWx32OGlZpJQ==
-----END PRIVATE KEY-----

View file

@ -0,0 +1,4 @@
-----BEGIN PRIVATE KEY-----
MDQCAQAwCwYJYIZIAWUDBAMTBCKAIC2UjpRD21PUuf5krDFC3308sWbuqihDkNZ3
xItjQrHI
-----END PRIVATE KEY-----

57
test/fixtures/keys/ml_dsa_87_public.pem vendored Normal file
View file

@ -0,0 +1,57 @@
-----BEGIN PUBLIC KEY-----
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
S9+/QEkRno/BZDVmjsqhj+gJ6uT0iQ==
-----END PUBLIC KEY-----

View file

@ -0,0 +1,179 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const { hasOpenSSL } = require('../common/crypto');
const assert = require('assert');
const {
createPublicKey,
createPrivateKey,
} = require('crypto');
const fixtures = require('../common/fixtures');
function getKeyFileName(type, suffix) {
return `${type.replaceAll('-', '_')}_${suffix}.pem`;
}
for (const [asymmetricKeyType, pubLen] of [
['ml-dsa-44', 1312], ['ml-dsa-65', 1952], ['ml-dsa-87', 2592],
]) {
const keys = {
public: fixtures.readKey(getKeyFileName(asymmetricKeyType, 'public'), 'ascii'),
private: fixtures.readKey(getKeyFileName(asymmetricKeyType, 'private'), 'ascii'),
private_seed_only: fixtures.readKey(getKeyFileName(asymmetricKeyType, 'private_seed_only'), 'ascii'),
private_priv_only: fixtures.readKey(getKeyFileName(asymmetricKeyType, 'private_priv_only'), 'ascii'),
};
function assertJwk(jwk) {
assert.strictEqual(jwk.kty, 'AKP');
assert.strictEqual(jwk.alg, asymmetricKeyType.toUpperCase());
assert.ok(jwk.pub);
assert.strictEqual(Buffer.from(jwk.pub, 'base64url').byteLength, pubLen);
}
function assertPublicJwk(jwk) {
assertJwk(jwk);
assert.ok(!jwk.priv);
}
function assertPrivateJwk(jwk) {
assertJwk(jwk);
assert.ok(jwk.priv);
assert.strictEqual(Buffer.from(jwk.priv, 'base64url').byteLength, 32);
}
function assertKey(key) {
assert.deepStrictEqual(key.asymmetricKeyDetails, {});
assert.strictEqual(key.asymmetricKeyType, asymmetricKeyType);
assert.strictEqual(key.equals(key), true);
assert.deepStrictEqual(key, key);
}
function assertPublicKey(key) {
assertKey(key);
assert.strictEqual(key.type, 'public');
assert.strictEqual(key.export({ format: 'pem', type: 'spki' }), keys.public);
key.export({ format: 'der', type: 'spki' });
const jwk = key.export({ format: 'jwk' });
assertPublicJwk(jwk);
assert.strictEqual(key.equals(createPublicKey({ format: 'jwk', key: jwk })), true);
}
function assertPrivateKey(key, hasSeed) {
assertKey(key);
assert.strictEqual(key.type, 'private');
assertPublicKey(createPublicKey(key));
key.export({ format: 'der', type: 'pkcs8' });
if (hasSeed) {
assert.strictEqual(key.export({ format: 'pem', type: 'pkcs8' }), keys.private);
} else {
assert.strictEqual(key.export({ format: 'pem', type: 'pkcs8' }), keys.private_priv_only);
}
if (hasSeed) {
const jwk = key.export({ format: 'jwk' });
assertPrivateJwk(jwk);
assert.strictEqual(key.equals(createPrivateKey({ format: 'jwk', key: jwk })), true);
assert.ok(createPublicKey({ format: 'jwk', key: jwk }));
} else {
assert.throws(() => key.export({ format: 'jwk' }),
{ code: 'ERR_CRYPTO_OPERATION_FAILED', message: 'key does not have an available seed' });
}
}
if (!hasOpenSSL(3, 5)) {
assert.throws(() => createPublicKey(keys.public), {
code: hasOpenSSL(3) ? 'ERR_OSSL_EVP_DECODE_ERROR' : 'ERR_OSSL_EVP_UNSUPPORTED_ALGORITHM',
});
for (const pem of [keys.private, keys.private_seed_only, keys.private_priv_only]) {
assert.throws(() => createPrivateKey(pem), {
code: hasOpenSSL(3) ? 'ERR_OSSL_UNSUPPORTED' : 'ERR_OSSL_EVP_UNSUPPORTED_ALGORITHM',
});
}
} else {
const publicKey = createPublicKey(keys.public);
assertPublicKey(publicKey);
{
for (const [pem, hasSeed] of [
[keys.private, true],
[keys.private_seed_only, true],
[keys.private_priv_only, false],
]) {
const pubFromPriv = createPublicKey(pem);
assertPublicKey(pubFromPriv);
assertPrivateKey(createPrivateKey(pem), hasSeed);
assert.strictEqual(pubFromPriv.equals(publicKey), true);
}
}
}
}
{
const format = 'jwk';
const jwk = {
kty: 'AKP',
alg: 'ML-DSA-44',
priv: '9_uqvxH0WKJFgfLyse1a1des2bwPgsHctl_jCt5AfEo',
// eslint-disable-next-line @stylistic/js/max-len
pub: 'SXghXj9P-DJ5eznNa_zLJxRxpa0mt86WlIid0EVEv1qraLBkC1UKevSZrjtgo1QUEN0oa3tP-HyYj8Onnc1zEnxsSoeC5A-PgywKgYuZP581wGPS-cbA2-5acsg-YUi_9fDkLR5YOTQQ3Iu952K1m8w0QDIBxZjecm32HgkD56CCC6ZyBOwfx9qcNUeO0aImya1igzL2_LRsqomogl9OuduWhtussAavGlAK7ZR4_4lmyjWcdeIc-z--iy42biV5d_tnopfNTJFlycBKinZu3h0lr4-ldl6apGDIyvSdZulhgj_j6jgEX-AgQZgS93ctx680GvROkBL7YI_3iXW3REWzVgS9HLasagEi2h6-RYQ9RzgUTODbei5fNRj3bNSqr8IKTZ08DCsRasN61TGwE3F7meoauw2NYkV51mhTxIafwhLWJrRA4C09Y-afrOtqk6a7Fiy21ObP95TGujXwThuwQSjKcUzTdCbD94ERhleZLqnPYEpb6_Jcc1OBY3kUJvCjoUhXwbW6PhWr533JDEFHoNCkPfhHS7vVCUFx4mQASkPLBud5arFSZU1uDStuiftJXfnQTWaMoJeA1N6rywB3xwLH__lHZQwEh4KnuYVuCeOMU1t8inuHI4EpZ4iTi2LrL0Cl6HadpHv-GENYwuPDVq9qg2Mo75o1X6wpPSN1J5KUDAATyR_0hurg4A1DlVpVWtykP5YWEmx_g5w4MZfEVwH-JJjEhJRxLKajxrjfG4XlnwwxPTznr1k1Mb7getKbLSbiMA3fvAgl1IjBIB8eFaISauFPpLPSpKHCVZrQYPIKSxMVdlXHgwm3CRrkR29GevCM5iSwRHQK6_HWfIQIlJ8H7uVQqXkNMvNmFldnfi3dj-oY6wMhs1ffP4RAsb5UTljvhJc6GoygBL_b0rv4aKcywDF8wa2P6B8gGFl6cVvWBQmWLJ-HL5RR68J_OmvJUm3PD-wwX3YigStd6thdTNlHOZhl4ysn8ulkFY3Rz2jEBV_nO6EXBLdOmxn_yX77qQ9yPcE64uC8iDTFWpQU1gmOF38od96oYD-T-whVl1NLD2bOvFVdd4UmWpb3Ui8AYzKzFBHNczAogQfplFmr8VABsgtWk8hW8csam70NADWK54SZOPQHeiOt1Mb488OZiDpX0FifEnCac78C_5uEiOPa8FAHpgUJ_XeXg83doDsAvrE1ZkrgFDwzT5pUTLyqh9eI1PAQKCoKmAbofcZM65o_qGvmnpN8fGVudOoHb0_Dqu_E2RBbaLcxsIe5jWmGmth4sb_4ANLFCmtt8T8sDmOdcYtQmhpUzg6rjDqeU76yq6fC15bGjT6Qc-EAgrUftFVwLw2UIGAbHmeAyFbSuJiMaUDJeYoZ3zxoID_DRCP9kN3ty-EQMHM9BZgXlH9dJ8ZUCAeH59h4PinM5LQuiS_kvP1iyT1Be6sYglV2dB7W_AziOcrrBiLfjazbUUpwqLm4_Yt_QwYJrAWfYyFOxkKxkT5qi2c1RNGBtiYjv8X_TRJDg0uxX_Hbq0Pc7yYezFQdFYIlRAvJcnc8PiOfhWtQZpCIYKTskg_Y2UfVvjydXYcGuIA2700PzR9ga-1VR7mv9UOLHe2x4ALiOO7Iz6KgOfVYMJ9dIC7f4HY9nrnLdhfKw5dcIf7RDhDqrkPyz8LLTuAuO-hGSwoP35XFkf0FQ8f1Cg5J_k-S3S2dCj8DXIRLcEJ9Qb5zvDofIPSmNKlvwJkqplDLBWlAow'
};
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'/ });
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'/ });
assert.throws(() => createPrivateKey({ format, key: { ...jwk, pub: undefined } }),
{ code: 'ERR_INVALID_ARG_TYPE', message: /The "key\.pub" property must be of type string/ });
assert.throws(() => createPrivateKey({ format, key: { ...jwk, priv: undefined } }),
{ code: 'ERR_INVALID_ARG_TYPE', message: /The "key\.priv" property must be of type string/ });
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') } }),
{ code: 'ERR_CRYPTO_INVALID_JWK' });
assert.ok(createPrivateKey({ format, key: jwk }));
assert.ok(createPublicKey({ format, key: jwk }));
// Test vectors from ietf-cose-dilithium
{
for (const jwk of [
{
kty: 'AKP',
alg: 'ML-DSA-44',
// eslint-disable-next-line @stylistic/js/max-len
pub: 'unH59k4RuutY-pxvu24U5h8YZD2rSVtHU5qRZsoBmBMcRPgmu9VuNOVdteXi1zNIXjnqJg_GAAxepLqA00Vc3lO0bzRIKu39VFD8Lhuk8l0V-cFEJC-zm7UihxiQMMUEmOFxe3x1ixkKZ0jqmqP3rKryx8tSbtcXyfea64QhT6XNje2SoMP6FViBDxLHBQo2dwjRls0k5a-XSQSu2OTOiHLoaWsLe8pQ5FLNfTDqmkrawDEdZyxr3oSWJAsHQxRjcIiVzZuvwxYy1zl2STiP2vy_fTBaPemkleynQzqPg7oPCyXEE8bjnJbrfWkbNNN8438e6tHPIX4l7zTuzz98YPhLjt_d6EBdT4MldsYe-Y4KLyjaGHcAlTkk9oa5RhRwW89T0z_t1DSO3dvfKLUGXh8gd1BD6Fz5MfgpF5NjoafnQEqDjsAAhrCXY4b-Y3yYJEdX4_dp3dRGdHG_rWcPmgX4JG7lCnser4f8QGnDriqiAzJYEXeS8LzUngg_0bx0lqv_KcyU5IaLISFO0xZSU5mmEPvdSoDnyAcV8pV44qhLtAvd29n0ehG259oRihtljTWeiu9V60a1N2tbZVl5mEqSK-6_xZvNYA1TCdzNctvweH24unV7U3wer9XA9Q6kvJWDVJ4oKaQsKMrCSMlteBJMRxWbGK7ddUq6F7GdQw-3j2M-qdJvVKm9UPjY9rc1lPgol25-oJxTu7nxGlbJUH-4m5pevAN6NyZ6lfhbjWTKlxkrEKZvQXs_Yf6cpXEwpI_ZJeriq1UC1XHIpRkDwdOY9MH3an4RdDl2r9vGl_IwlKPNdh_5aF3jLgn7PCit1FNJAwC8fIncAXgAlgcXIpRXdfJk4bBiO89GGccSyDh2EgXYdpG3XvNgGWy7npuSoNTE7WIyblAk13UQuO4sdCbMIuriCdyfE73mvwj15xgb07RZRQtFGlFTmnFcIdZ90zDrWXDbANntv7KCKwNvoTuv64bY3HiGbj-NQ-U9eMylWVpvr4hrXcES8c9K3PqHWADZC0iIOvlzFv4VBoc_wVflcOrL_SIoaNFCNBAZZq-2v5lAgpJTqVOtqJ_HVraoSfcKy5g45p-qULunXj6Jwq21fobQiKubBKKOZwcJFyJD7F4ACKXOrz-HIvSHMCWW_9dVrRuCpJw0s0aVFbRqopDNhu446nqb4_EDYQM1tTHMozPd_jKxRRD0sH75X8ZoToxFSpLBDbtdWcenxj-zBf6IGWfZnmaetjKEBYJWC7QDQx1A91pJVJCEgieCkoIfTqkeQuePpIyu48g2FG3P1zjRF-kumhUTfSjo5qS0YiZQy0E1BMs6M11EvuxXRsHClLHoy5nLYI2Sj4zjVjYyxSHyPRPGGo9hwB34yWxzYNtPPGiqXS_dNCpi_zRZwRY4lCGrQ-hYTEWIK1Dm5OlttvC4_eiQ1dv63NiGkLRJ5kJA3bICN0fzCDY-MBqnd1cWn8YVBijVkgtaoascjL9EywDgJdeHnXK0eeOvUxHHhXJVkNqcibn8O4RQdpVU60TSA-uiu675ytIjcBHC6kTv8A8pmkj_4oypPd-F92YIJC741swkYQoeIHj8rE-ThcMUkF7KqC5VORbZTRp8HsZSqgiJcIPaouuxd1-8Rxrid3fXkE6p8bkrysPYoxWEJgh7ZFsRCPDWX-yTeJwFN0PKFP1j0F6YtlLfK5wv-c4F8ZQHA_-yc_gODicy7KmWDZgbTP07e7gEWzw4MFRrndjbDQ',
priv: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
},
{
kty: 'AKP',
alg: 'ML-DSA-65',
// eslint-disable-next-line @stylistic/js/max-len
pub: 'QksvJn5Y1bO0TXGs_Gpla7JpUNV8YdsciAvPof6rRD8JQquL2619cIq7w1YHj22ZolInH-YsdAkeuUr7m5JkxQqIjg3-2AzV-yy9NmfmDVOevkSTAhnNT67RXbs0VaJkgCufSbzkLudVD-_91GQqVa3mk4aKRgy-wD9PyZpOMLzP-opHXlOVOWZ067galJN1h4gPbb0nvxxPWp7kPN2LDlOzt_tJxzrfvC1PjFQwNSDCm_l-Ju5X2zQtlXyJOTZSLQlCtB2C7jdyoAVwrftUXBFDkisElvgmoKlwBks23fU0tfjhwc0LVWXqhGtFQx8GGBQ-zol3e7P2EXmtIClf4KbgYq5u7Lwu848qwaItyTt7EmM2IjxVth64wHlVQruy3GXnIurcaGb_qWg764qZmteoPl5uAWwuTDX292Sa071S7GfsHFxue5lydxIYvpVUu6dyfwuExEubCovYMfz_LJd5zNTKMMatdbBJg-Qd6JPuXznqc1UYC3CccEXCLTOgg_auB6EUdG0b_cy-5bkEOHm7Wi4SDipGNig_ShzUkkot5qSqPZnd2I9IqqToi_0ep2nYLBB3ny3teW21Qpccoom3aGPt5Zl7fpzhg7Q8zsJ4sQ2SuHRCzgQ1uxYlFx21VUtHAjnFDSoMOkGyo4gH2wcLR7-z59EPPNl51pljyNefgCnMSkjrBPyz1wiET-uqi23f8Bq2TVk1jmUFxOwdfLsU7SIS30WOzvwD_gMDexUFpMlEQyL1-Y36kaTLjEWGCi2tx1FTULttQx5JpryPW6lW5oKw5RMyGpfRliYCiRyQePYqipZGoxOHpvCWhCZIN4meDY7H0RxWWQEpiyCzRQgWkOtMViwao6Jb7wZWbLNMebwLJeQJXWunk-gTEeQaMykVJobwDUiX-E_E7fSybVRTZXherY1jrvZKh8C5Gi5VADg5Vs319uN8-dVILRyOOlvjjxclmsRcn6HEvTvxd9MS7lKm2gI8BXIqhzgnTdqNGwTpmDHPV8hygqJWxWXCltBSSgY6OkGkioMAmXjZjYq_Ya9o6AE7WU_hUdm-wZmQLExwtJWEIBdDxrUxA9L9JL3weNyQtaGItPjXcheZiNBBbJTUxXwIYLnXtT1M0mHzMqGFFWXVKsN_AIdHyv4yDzY9m-tuQRfbQ_2K7r5eDOL1Tj8DZ-s8yXG74MMBqOUvlglJNgNcbuPKLRPbSDoN0E3BYkfeDgiUrXy34a5-vU-PkAWCsgAh539wJUUBxqw90V1Du7eTHFKDJEMSFYwusbPhEX4ZTwoeTHg--8Ysn4HCFWLQ00pfBCteqvMvMflcWwVfTnogcPsJb1bEFVSc3nTzhk6Ln8J-MplyS0Y5mGBEtVko_WlyeFsoDCWj4hqrgU7L-ww8vsCRSQfskH8lodiLzj0xmugiKjWUXbYq98x1zSnB9dmPy5P3UNwwMQdpebtR38N9I-jup4Bzok0-JsaOe7EORZ8ld7kAgDWa4K7BAxjc2eD540Apwxs-VLGFVkXbQgYYeDNG2tW1Xt20-XezJqZVUl6-IZXsqc7DijwNInO3fT5o8ZAcLKUUlzSlEXe8sIlHaxjLoJ-oubRtlKKUbzWOHeyxmYZSxYqQhSQj4sheedGXJEYWJ-Y5DRqB-xpy-cftxL10fdXIUhe1hWFBAoQU3b5xRY8KCytYnfLhsFF4O49xhnax3vuumLpJbCqTXpLureoKg5PvWfnpFPB0P-ZWQN35mBzqbb3ZV6U0rU55DvyXTuiZOK2Z1TxbaAd1OZMmg0cpuzewgueV-Nh_UubIqNto5RXCd7vqgqdXDUKAiWyYegYIkD4wbGMqIjxV8Oo2ggOcSj9UQPS1rD5u0rLckAzsxyty9Q5JsmKa0w8Eh7Jwe4Yob4xPVWWbJfm916avRgzDxXo5gmY7txdGFYHhlolJKdhBU9h6f0gtKEtbiUzhp4IWsqAR8riHQs7lLVEz6P537a4kL1r5FjfDf_yjJDBQmy_kdWMDqaNln-MlKK8eENjUO-qZGy0Ql4bMZtNbHXjfJUuSzapA-RqYfkqSLKgQUOW8NTDKhUk73yqCU3TQqDEKaGAoTsPscyMm7u_8QrvUK8kbc-XnxrWZ0BZJBjdinzh2w-QvjbWQ5mqFp4OMgY94__tIU8vvCUNJiYA1RdyodlfPfH5-avpxOCvBD6C7ZIDyQ-6huGEQEAb6DP8ydWIZQ8xY603DoEKKXkJWcP6CJo3nHFEdj_vcEbDQ-WESDpcQFa1fRIiGuALj-sEWcjGdSHyE8QATOcuWl4TLVzRPKAf4tCXx1zyvhJbXQu0jf0yfzVpOhPun4n-xqK4SxPBCeuJOkQ2VG9jDXWH4pnjbAcrqjveJqVti7huMXTLGuqU2uoihBw6mGqu_WSlOP2-XTEyRyvxbv2t-z9V6GPt1V9ceBukA0oGwtJqgD-q7NXFK8zhw7desI5PZMXf3nuVgbJ3xdvAlzkmm5f9RoqQS6_hqwPQEcclq1MEZ3yML5hc99TDtZWy9gGkhR0Hs3QJxxgP7bEqGFP-HjTPnJsrGaT6TjKP7qCxJlcFKLUr5AU_kxMULeUysWWtSGJ9mpxBvsyW1Juo',
priv: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
},
{
kty: 'AKP',
alg: 'ML-DSA-87',
// eslint-disable-next-line @stylistic/js/max-len
pub: '5F_8jMc9uIXcZi5ioYzY44AylxF_pWWIFKmFtf8dt7Roz8gruSnx2Gt37RT1rhamU2h3LOUZEkEBBeBFaXWukf22Q7US8STV5gvWi4x-Mf4Bx7DcZa5HBQHMVlpuHfz8_RJWVDPEr-3VEYIeLpYQxFJ14oNt7jXO1p1--mcv0eQxi-9etuiX6LRRqiAt7QQrKq73envj9pkUbaIpqL2z_6SWRFln51IXv7yQSPmVZEPYcx-DPrMN4Q2slv_-fPZeoERcPjHoYB4TO-ahAHZP4xluJncmRB8xdR-_mm9YgGRPTnJ15X3isPEF5NsFXVDdHJyTT931NbjeKLDHTARJ8iLNLtC7j7x3XM7oyUBmW0D3EvT34AdQ6eHkzZz_JdGUXD6bylPM1PEu7nWBhW69aPJoRZVuPnvrdh8P51vdMb_i-gGBEzl7OHvVnWKmi4r3-iRauTLmn3eOLO79ITBPu4CZ6hPY6lfBgTGXovda4lEHW1Ha04-FNmnp1fmKNlUJiUGZOhWUhg-6cf5TDuXCn1jyl4r2iMy3Wlg4o1nBEumOJahYOsjawfhh_Vjir7pd5aUuAgkE9bQrwIdONb788-YRloR2jzbgCPBHEhd86-YnYHOB5W6q7hYcFym43lHb3kdNSMxoJJ6icWK4eZPmDITtbMZCPLNnbZ61CyyrWjoEnvExOB1iP6b7y8nbHnzAJeoEGLna0sxszU6V-izsJP7spwMYp1Fxa3IT9j7b9lpjM4NX-Dj5TsBxgiwkhRJIiFEHs9HE6SRnjHYU6hrwOBBGGfKuNylAvs-mninLtf9sPiCke-Sk90usNMEzwApqcGrMxv_T2OT71pqZcE4Sg8hQ2MWNHldTzZWHuDxMNGy5pYE3IT7BCDTGat_iu1xQGo7y7K3Rtnej3xpt64br8HIsT1Aw4g-QGN1bb8U-6iT9kre1tAJf6umW0-SP1MZQ2C261-r5NmOWmFEvJiU9LvaEfIUY6FZcyaVJXG__V83nMjiCxUp9tHCrLa-P_Sv3lPp8aS2ef71TLuzB14gOLKCzIWEovii0qfHRUfrJeAiwvZi3tDphKprIZYEr_qxvR0YCd4QLUqOwh_kWynztwPdo6ivRnqIRVfhLSgTEAArSrgWHFU1WC8Ckd6T5MpqJhN0x6x8qBePZGHAdYwz8qa9h7wiNLFWBrLRj5DmQLl1CVxnpVrjW33MFso4P8n060N4ghdKSSZsZozkNQ5b7O6yajYy-rSp6QpD8msb8oEX5imFKRaOcviQ2D4TRT45HJxKs63Tb9FtT1JoORzfkdv_E1bL3zSR6oYbTt2Stnpz-7kVqc8KR2N45EkFKxDkRw3IXOte0cq81xoU87S_ntf4KiVZaszuqb2XN2SgxnXBl4EDnpehPmqkD92SAlLrQcTaxaSe47G28K-8MwoVt4eeVkj4UEsSfJN7rbCH2yKl2XJx5huDaS0xn2ODQyNRmgk-5I9hXMUiZDNLvEzx4zuyrcu2d0oXFo3ZoUtVFNCB__TQCf2x27ej9GjLXLDAEi7qnl9Xfb94n0IfeVyGte3-j6NP3DWv8OrLiUjNTaLv6Fay1yzfUaU6LI86-Jd6ckloiGhg7kE0_hd-ZKakZxU1vh0Vzc6DW7MFAPky75iCZlDXoBpZjTNGo5HR-mCW_ozblu60U9zZA8bn-voANuu_hYwxh-uY1sHTFZOqp2xicnnMChz_GTm1Je8XCkICYegeiHUryEHA6T6B_L9gW8S_R4ptMD0Sv6b1KHqqKeubwKltCWPUsr2En9iYypnz06DEL5Wp8KMhrLid2AMPpLI0j1CWGJExXHpBWjfIC8vbYH4YKVl-euRo8eDcuKosb5hxUGM9Jvy1siVXUpIKpkZt2YLP5pEBP_EVOoHPh5LJomrLMpORr1wBKbEkfom7npX1g817bK4IeYmZELI8zXUUtUkx3LgNTckwjx90Vt6oVXpFEICIUDF_LAVMUftzz6JUvbwOZo8iAZqcnVslAmRXeY_ZPp5eEHFfHlsb8VQ73Rd_p8XlFf5R1WuWiUGp2TzJ-VQvj3BTdQfOwSxR9RUk4xjqNabLqTFcQ7As246bHJXH6XVnd4DbEIDPfNa8FaWb_DNEgQAiXGqa6n7l7aFq5_6Kp0XeBBM0sOzJt4fy8JC6U0DEcMnWxKFDtMM7q06LubQYFCEEdQ5b1Qh2LbQZ898tegmeF--EZ4F4hvYebZPV8sM0ZcsKBXyCr585qs00PRxr0S6rReekGRBIvXzMojmid3dxc6DPpdV3x5zxlxaIBxO3i_6axknSSdxnS04_bemWqQ3CLf6mpSqfTIQJT1407GB4QINAAC9Ch3AXUR_n1jr64TGWzbIr8uDcnoVCJlOgmlXpmOwubigAzJattbWRi7k4QYBnA3_4QMjt73n2Co4-F_Qh4boYLpmwWG2SwcIw2PeXGr2LY2zwkPR4bcSyx1Z6UK5trQpWlpQCxgsvV_RvGzpN22RtHoihPH74K0cBIzCz7tK-jqeuWl1A7af7KmQ66fpRBr5ykTLOsa17WblkcIB_jDvqKfEcdxhPWJUwmOo4TIQS-xH8arLOy_NQFG2m14_yxwUemXC-QxLUYi6_FIcqwPBKjCdpQtadRdyftQSKO0SP-GxUvamMZzWI780rXuOBkq5kyYLy9QF9bf_-bL6QLpe1WMCQlOeXZaCPoncgYoT0WZ17jB52Xb2lPWsyXYK54npszkbKJ4OIqfvF8xqRXcVe22VwJuqT9Uy4-4KKQgQ7TXla7Gdm2H7mKl8YXQlsGCT2Ypc8O4t0Sfw7qYAuaDGf752Hbm3fl1bupcB2huIPlIaDP6IRR9XvTYIW2flbwYfhKLmoVKnG85uUi2qtqCjPOIuU3-peT0othfmwKQXaoOqO-V4r6wPL1VHxVFtIYmEdVt0RccUOvpOVR_OAHG9uHOzTmueK5557Qxp0ojtZCHyN-hgoMZJLrvdKkTCxPNo2-mZQbHoVh2FnThZ9JbO49dB8lKXP4_MU5xAnjXMgKXtbfI8w6ZWATE_XWgf2VQMUpGp4wpy44yWQTxHxh_4T9540BGwG0FU0bkgrwA_erseGZnepqdmz5_ScCs84O5Xr5MbYhJLCGGxY6O5GqS-ooB2w0Mt87KbbE4bpYje9CAHH8FX3pDrJyLsyasA3zxmk4OmGpG7Z70ofONJtHRe56R5287vFmuazEEutXn81kNzB-3aJT1ga3vnWZw4CSvFKoWYSA7auLgrHSHFZdITfOrgtmQmGbFhM9kSBdY1UCnpzf65oos3PZWRa2twfUxxLAnPNtrxpRGyvtsapw7ljUagZmuyh3hLCjhAxYmnoE1dbyIWvpCqSlEtVjL1yb_nuLEzgvmZuV02fHxGuWgHTOMVGXpf81Rce3eoBK3lapW1wkzezlk3tcA2bZOtA9qbxdsbVR37kemzQ9K1e3Y0OWhtSj',
priv: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
},
]) {
assert.partialDeepStrictEqual(jwk, createPublicKey({ format, key: jwk }).export({ format: 'jwk' }));
assert.deepStrictEqual(createPrivateKey({ format, key: jwk }).export({ format: 'jwk' }), jwk);
}
}
} else {
assert.throws(() => createPrivateKey({ format, key: jwk }),
{ code: 'ERR_INVALID_ARG_VALUE', message: /must be one of: 'RSA', 'EC', 'OKP'\. Received 'AKP'/ });
assert.throws(() => createPublicKey({ format, key: jwk }),
{ code: 'ERR_INVALID_ARG_VALUE', message: /must be one of: 'RSA', 'EC', 'OKP'\. Received 'AKP'/ });
}
}

View file

@ -0,0 +1,69 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const { hasOpenSSL } = require('../common/crypto');
const assert = require('assert');
const {
generateKeyPair,
} = require('crypto');
if (!hasOpenSSL(3, 5)) {
for (const asymmetricKeyType of ['ml-dsa-44', 'ml-dsa-65', 'ml-dsa-87']) {
assert.throws(() => generateKeyPair(asymmetricKeyType, common.mustNotCall()), {
code: 'ERR_INVALID_ARG_VALUE',
message: /The argument 'type' must be a supported key type/
});
}
} else {
for (const [asymmetricKeyType, pubLen] of [
['ml-dsa-44', 1312], ['ml-dsa-65', 1952], ['ml-dsa-87', 2592],
]) {
function assertJwk(jwk) {
assert.strictEqual(jwk.kty, 'AKP');
assert.strictEqual(jwk.alg, asymmetricKeyType.toUpperCase());
assert.ok(jwk.pub);
assert.strictEqual(Buffer.from(jwk.pub, 'base64url').byteLength, pubLen);
}
function assertPublicJwk(jwk) {
assertJwk(jwk);
assert.ok(!jwk.priv);
}
function assertPrivateJwk(jwk) {
assertJwk(jwk);
assert.ok(jwk.priv);
assert.strictEqual(Buffer.from(jwk.priv, 'base64url').byteLength, 32);
}
for (const [publicKeyEncoding, validate] of [
[undefined, (publicKey) => {
assert.strictEqual(publicKey.type, 'public');
assert.strictEqual(publicKey.asymmetricKeyType, asymmetricKeyType);
assert.deepStrictEqual(publicKey.asymmetricKeyDetails, {});
}],
[{ format: 'jwk' }, (publicKey) => assertPublicJwk(publicKey)],
[{ format: 'pem', type: 'spki' }, (publicKey) => assert.strictEqual(typeof publicKey, 'string')],
[{ format: 'der', type: 'spki' }, (publicKey) => assert.strictEqual(Buffer.isBuffer(publicKey), true)],
]) {
generateKeyPair(asymmetricKeyType, { publicKeyEncoding }, common.mustSucceed(validate));
}
for (const [privateKeyEncoding, validate] of [
[undefined, (_, privateKey) => {
assert.strictEqual(privateKey.type, 'private');
assert.strictEqual(privateKey.asymmetricKeyType, asymmetricKeyType);
assert.deepStrictEqual(privateKey.asymmetricKeyDetails, {});
}],
[{ format: 'jwk' }, (_, privateKey) => assertPrivateJwk(privateKey)],
[{ format: 'pem', type: 'pkcs8' }, (_, privateKey) => assert.strictEqual(typeof privateKey, 'string')],
[{ format: 'der', type: 'pkcs8' }, (_, privateKey) => assert.strictEqual(Buffer.isBuffer(privateKey), true)],
]) {
generateKeyPair(asymmetricKeyType, { privateKeyEncoding }, common.mustSucceed(validate));
}
}
}

File diff suppressed because one or more lines are too long