Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 42 additions & 1 deletion deps/ncrypto/ncrypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
#include <cstring>
#if OPENSSL_VERSION_MAJOR >= 3
#include <openssl/provider.h>
#if OPENSSL_VERSION_MINOR >= 5
#include <crypto/ml_kem.h>
#endif
#endif

// EVP_PKEY_CTX_set_dsa_paramgen_q_bits was added in OpenSSL 1.1.1e.
Expand Down Expand Up @@ -1942,7 +1945,19 @@ 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;
if (EVP_PKEY_is_a(key, "ML-KEM-512")) return EVP_PKEY_ML_KEM_512;
if (EVP_PKEY_is_a(key, "ML-KEM-768")) return EVP_PKEY_ML_KEM_768;
if (EVP_PKEY_is_a(key, "ML-KEM-1024")) return EVP_PKEY_ML_KEM_1024;
}
#endif
return type;
}

int EVPKeyPointer::base_id(const EVP_PKEY* key) {
Expand Down Expand Up @@ -1998,6 +2013,32 @@ 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();
// TODO(@panva): use OSSL_PKEY_PARAM_ML_DSA_SEED instead of "seed"
if (EVP_PKEY_get_octet_string_param(
get(), "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())) {
Expand Down
7 changes: 7 additions & 0 deletions deps/ncrypto/ncrypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
#endif // OPENSSL_FIPS

#if OPENSSL_VERSION_MAJOR >= 3
#if OPENSSL_VERSION_MINOR >= 5
#include <crypto/ml_kem.h>
#endif
#define OSSL3_CONST const
#else
#define OSSL3_CONST
Expand Down Expand Up @@ -910,6 +913,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(
Expand Down
55 changes: 53 additions & 2 deletions doc/api/crypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -1916,6 +1916,12 @@ 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/00000
description: Add support for ML-KEM keys.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/00000
description: Add support for ML-DSA keys.
- version:
- v14.5.0
- v12.19.0
Expand Down Expand Up @@ -2021,6 +2027,12 @@ 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/00000
description: Add support for ML-KEM keys.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/00000
description: Add support for ML-DSA keys.
- version:
- v13.9.0
- v12.17.0
Expand Down Expand Up @@ -2055,6 +2067,12 @@ 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)
* `'ml-kem-512'`[^openssl35] (OID 2.16.840.1.101.3.4.4.1)
* `'ml-kem-768'`[^openssl35] (OID 2.16.840.1.101.3.4.4.2)
* `'ml-kem-1024'`[^openssl35] (OID 2.16.840.1.101.3.4.4.3)

This property is `undefined` for unrecognized `KeyObject` types and symmetric
keys.
Expand Down Expand Up @@ -2125,6 +2143,8 @@ encryption mechanism, PEM-level encryption is not supported when encrypting
a PKCS#8 key. See [RFC 5208][] for PKCS#8 encryption and [RFC 1421][] for
PKCS#1 and SEC1 encryption.

Note: ML-KEM keys JWK export is not yet supported.

### `keyObject.symmetricKeySize`

<!-- YAML
Expand Down Expand Up @@ -3403,6 +3423,12 @@ input.on('readable', () => {
<!-- YAML
added: v11.6.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/00000
description: Add support for ML-KEM keys.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/00000
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.
Expand Down Expand Up @@ -3434,11 +3460,19 @@ must be an object with the properties described above.
If the private key is encrypted, a `passphrase` must be specified. The length
of the passphrase is limited to 1024 bytes.

Note: ML-DSA and ML-KEM keys JWK import is not yet supported.

### `crypto.createPublicKey(key)`

<!-- YAML
added: v11.6.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/00000
description: Add support for ML-KEM keys.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/00000
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.
Expand Down Expand Up @@ -3484,6 +3518,8 @@ extracted from the returned `KeyObject`. Similarly, if a `KeyObject` with type
`'private'` is given, a new `KeyObject` with type `'public'` will be returned
and it will be impossible to extract the private key from the returned object.

Note: ML-DSA and ML-KEM keys JWK import is not yet supported.

### `crypto.createSecretKey(key[, encoding])`

<!-- YAML
Expand Down Expand Up @@ -3648,6 +3684,12 @@ 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/00000
description: Add support for ML-KEM key pairs.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/00000
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
Expand Down Expand Up @@ -3767,6 +3809,12 @@ 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/00000
description: Add support for ML-KEM key pairs.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/00000
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
Expand All @@ -3792,7 +3840,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`.
Expand All @@ -3816,7 +3865,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,
Expand Down Expand Up @@ -6150,6 +6199,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
Expand Down
43 changes: 28 additions & 15 deletions lib/internal/crypto/keygen.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ 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_ML_KEM_512,
EVP_PKEY_ML_KEM_768,
EVP_PKEY_ML_KEM_1024,
EVP_PKEY_X25519,
EVP_PKEY_X448,
OPENSSL_EC_NAMED_CURVE,
Expand Down Expand Up @@ -162,6 +168,19 @@ 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,
'ml-kem-512': EVP_PKEY_ML_KEM_512,
'ml-kem-768': EVP_PKEY_ML_KEM_768,
'ml-kem-1024': EVP_PKEY_ML_KEM_1024,
};

function createJob(mode, type, options) {
validateString(type, 'type');

Expand Down Expand Up @@ -278,23 +297,17 @@ function createJob(mode, type, options) {
case 'ed448':
case 'x25519':
case 'x448':
case 'ml-dsa-44':
case 'ml-dsa-65':
case 'ml-dsa-87':
case 'ml-kem-512':
case 'ml-kem-768':
case 'ml-kem-1024':
{
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':
{
Expand Down
2 changes: 2 additions & 0 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,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',
Expand Down Expand Up @@ -367,6 +368,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',
Expand Down
37 changes: 34 additions & 3 deletions src/crypto/crypto_keys.cc
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
#include "crypto/crypto_keys.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_dh.h"
#include "crypto/crypto_ml_dsa.h"
#include "crypto/crypto_rsa.h"
#include "crypto/crypto_util.h"
#include "async_wrap-inl.h"
#include "base_object-inl.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "node.h"
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -903,6 +912,20 @@ 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();
case EVP_PKEY_ML_KEM_512:
return env()->crypto_ml_kem_512_string();
case EVP_PKEY_ML_KEM_768:
return env()->crypto_ml_kem_768_string();
case EVP_PKEY_ML_KEM_1024:
return env()->crypto_ml_kem_1024_string();
#endif
default:
return Undefined(env()->isolate());
}
Expand Down Expand Up @@ -1178,6 +1201,14 @@ 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);
NODE_DEFINE_CONSTANT(target, EVP_PKEY_ML_KEM_512);
NODE_DEFINE_CONSTANT(target, EVP_PKEY_ML_KEM_768);
NODE_DEFINE_CONSTANT(target, EVP_PKEY_ML_KEM_1024);
#endif
NODE_DEFINE_CONSTANT(target, EVP_PKEY_X25519);
NODE_DEFINE_CONSTANT(target, EVP_PKEY_X448);
NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS1);
Expand Down
Loading