Skip to content
Draft
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
6 changes: 2 additions & 4 deletions doc/api/crypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -4009,8 +4009,7 @@ changes:
* `publicKey` {string | Buffer | KeyObject}
* `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.
Generates a new asymmetric key pair of the given `type`.

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 @@ -4131,8 +4130,7 @@ changes:
* `publicKey` {string | Buffer | KeyObject}
* `privateKey` {string | Buffer | KeyObject}

Generates a new asymmetric key pair of the given `type`. RSA, RSA-PSS, DSA, EC,
Ed25519, Ed448, X25519, X448, DH, and ML-DSA[^openssl35] are currently supported.
Generates a new asymmetric key pair of the given `type`.

If a `publicKeyEncoding` or `privateKeyEncoding` was specified, this function
behaves as if [`keyObject.export()`][] had been called on its result. Otherwise,
Expand Down
52 changes: 44 additions & 8 deletions lib/internal/crypto/keys.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ const {
kKeyEncodingPKCS8,
kKeyEncodingSPKI,
kKeyEncodingSEC1,
EVP_PKEY_ML_DSA_44,
EVP_PKEY_ML_DSA_65,
EVP_PKEY_ML_DSA_87,
} = internalBinding('crypto');

const {
Expand Down Expand Up @@ -542,40 +539,79 @@ function getKeyTypes(allowKeyObject, bufferOnly = false) {
return types;
}

function mlDsaPubLen(alg) {
function akpPubLen(alg) {
switch (alg) {
case 'ML-DSA-44': return 1312;
case 'ML-DSA-65': return 1952;
case 'ML-DSA-87': return 2592;
case 'SLH-DSA-SHA2-128s':
case 'SLH-DSA-SHAKE-128s':
case 'SLH-DSA-SHA2-128f':
case 'SLH-DSA-SHAKE-128f': return 32;
case 'SLH-DSA-SHA2-192s':
case 'SLH-DSA-SHAKE-192s':
case 'SLH-DSA-SHA2-192f':
case 'SLH-DSA-SHAKE-192f': return 48;
case 'SLH-DSA-SHA2-256s':
case 'SLH-DSA-SHAKE-256s':
case 'SLH-DSA-SHA2-256f':
case 'SLH-DSA-SHAKE-256f': return 64;
}
}

function akpPrivLen(alg) {
switch (alg) {
case 'ML-DSA-44':
case 'ML-DSA-65':
case 'ML-DSA-87': return 32;
case 'SLH-DSA-SHA2-128s':
case 'SLH-DSA-SHAKE-128s':
case 'SLH-DSA-SHA2-128f':
case 'SLH-DSA-SHAKE-128f': return 64;
case 'SLH-DSA-SHA2-192s':
case 'SLH-DSA-SHAKE-192s':
case 'SLH-DSA-SHA2-192f':
case 'SLH-DSA-SHAKE-192f': return 96;
case 'SLH-DSA-SHA2-256s':
case 'SLH-DSA-SHAKE-256s':
case 'SLH-DSA-SHA2-256f':
case 'SLH-DSA-SHAKE-256f': return 128;
}
}

function getKeyObjectHandleFromJwk(key, ctx) {
validateObject(key, 'key');
if (EVP_PKEY_ML_DSA_44 || EVP_PKEY_ML_DSA_65 || EVP_PKEY_ML_DSA_87) {
if (KeyObjectHandle.prototype.initPqcRaw) {
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']);
key.alg, 'key.alg', [
'ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87',
'SLH-DSA-SHA2-128s', 'SLH-DSA-SHAKE-128s', 'SLH-DSA-SHA2-128f',
'SLH-DSA-SHAKE-128f', 'SLH-DSA-SHA2-192s', 'SLH-DSA-SHAKE-192s',
'SLH-DSA-SHA2-192f', 'SLH-DSA-SHAKE-192f', 'SLH-DSA-SHA2-256s',
'SLH-DSA-SHAKE-256s', 'SLH-DSA-SHA2-256f', 'SLH-DSA-SHAKE-256f',
]);
validateString(key.pub, 'key.pub');

let keyData;
if (isPublic) {
keyData = Buffer.from(key.pub, 'base64url');
if (keyData.byteLength !== mlDsaPubLen(key.alg)) {
if (keyData.byteLength !== akpPubLen(key.alg)) {
throw new ERR_CRYPTO_INVALID_JWK();
}
} else {
validateString(key.priv, 'key.priv');
keyData = Buffer.from(key.priv, 'base64url');
if (keyData.byteLength !== 32) {
if (keyData.byteLength !== akpPrivLen(key.alg)) {
throw new ERR_CRYPTO_INVALID_JWK();
}
}
Expand Down
2 changes: 2 additions & 0 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,7 @@
'src/crypto/crypto_context.cc',
'src/crypto/crypto_ec.cc',
'src/crypto/crypto_ml_dsa.cc',
'src/crypto/crypto_slh_dsa.cc',
'src/crypto/crypto_kem.cc',
'src/crypto/crypto_hmac.cc',
'src/crypto/crypto_kmac.cc',
Expand Down Expand Up @@ -406,6 +407,7 @@
'src/crypto/crypto_context.h',
'src/crypto/crypto_ec.h',
'src/crypto/crypto_ml_dsa.h',
'src/crypto/crypto_slh_dsa.h',
'src/crypto/crypto_hkdf.h',
'src/crypto/crypto_pbkdf2.h',
'src/crypto/crypto_sig.h',
Expand Down
96 changes: 82 additions & 14 deletions src/crypto/crypto_keys.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "crypto/crypto_ec.h"
#include "crypto/crypto_ml_dsa.h"
#include "crypto/crypto_rsa.h"
#include "crypto/crypto_slh_dsa.h"
#include "crypto/crypto_util.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
Expand Down Expand Up @@ -184,6 +185,30 @@ bool ExportJWKAsymmetricKey(Environment* env,
// Fall through
case EVP_PKEY_ML_DSA_87:
return ExportJwkMlDsaKey(env, key, target);
case EVP_PKEY_SLH_DSA_SHA2_128F:
// Fall through
case EVP_PKEY_SLH_DSA_SHA2_128S:
// Fall through
case EVP_PKEY_SLH_DSA_SHA2_192F:
// Fall through
case EVP_PKEY_SLH_DSA_SHA2_192S:
// Fall through
case EVP_PKEY_SLH_DSA_SHA2_256F:
// Fall through
case EVP_PKEY_SLH_DSA_SHA2_256S:
// Fall through
case EVP_PKEY_SLH_DSA_SHAKE_128F:
// Fall through
case EVP_PKEY_SLH_DSA_SHAKE_128S:
// Fall through
case EVP_PKEY_SLH_DSA_SHAKE_192F:
// Fall through
case EVP_PKEY_SLH_DSA_SHAKE_192S:
// Fall through
case EVP_PKEY_SLH_DSA_SHAKE_256F:
// Fall through
case EVP_PKEY_SLH_DSA_SHAKE_256S:
return ExportJwkSlhDsaKey(env, key, target);
#endif
}
THROW_ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE(env);
Expand Down Expand Up @@ -293,6 +318,30 @@ int GetNidFromName(const char* name) {
nid = EVP_PKEY_ML_KEM_768;
} else if (strcmp(name, "ML-KEM-1024") == 0) {
nid = EVP_PKEY_ML_KEM_1024;
} else if (strcmp(name, "SLH-DSA-SHA2-128f") == 0) {
nid = EVP_PKEY_SLH_DSA_SHA2_128F;
} else if (strcmp(name, "SLH-DSA-SHA2-128s") == 0) {
nid = EVP_PKEY_SLH_DSA_SHA2_128S;
} else if (strcmp(name, "SLH-DSA-SHA2-192f") == 0) {
nid = EVP_PKEY_SLH_DSA_SHA2_192F;
} else if (strcmp(name, "SLH-DSA-SHA2-192s") == 0) {
nid = EVP_PKEY_SLH_DSA_SHA2_192S;
} else if (strcmp(name, "SLH-DSA-SHA2-256f") == 0) {
nid = EVP_PKEY_SLH_DSA_SHA2_256F;
} else if (strcmp(name, "SLH-DSA-SHA2-256s") == 0) {
nid = EVP_PKEY_SLH_DSA_SHA2_256S;
} else if (strcmp(name, "SLH-DSA-SHAKE-128f") == 0) {
nid = EVP_PKEY_SLH_DSA_SHAKE_128F;
} else if (strcmp(name, "SLH-DSA-SHAKE-128s") == 0) {
nid = EVP_PKEY_SLH_DSA_SHAKE_128S;
} else if (strcmp(name, "SLH-DSA-SHAKE-192f") == 0) {
nid = EVP_PKEY_SLH_DSA_SHAKE_192F;
} else if (strcmp(name, "SLH-DSA-SHAKE-192s") == 0) {
nid = EVP_PKEY_SLH_DSA_SHAKE_192S;
} else if (strcmp(name, "SLH-DSA-SHAKE-256f") == 0) {
nid = EVP_PKEY_SLH_DSA_SHAKE_256F;
} else if (strcmp(name, "SLH-DSA-SHAKE-256s") == 0) {
nid = EVP_PKEY_SLH_DSA_SHAKE_256S;
#endif
} else {
nid = NID_undef;
Expand Down Expand Up @@ -862,34 +911,53 @@ void KeyObjectHandle::InitPqcRaw(const FunctionCallbackInfo<Value>& args) {

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

typedef EVPKeyPointer (*new_key_fn)(
int, const ncrypto::Buffer<const unsigned char>&);
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: {
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_);
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:
UNREACHABLE();
}

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_);

args.GetReturnValue().Set(true);
}
#endif
Expand Down
2 changes: 1 addition & 1 deletion src/crypto/crypto_ml_dsa.cc
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ constexpr const char* GetMlDsaAlgorithmName(int id) {
* - "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)
* - "priv": "<Base64URL-encoded raw seed>" (required for private keys)
*/
bool ExportJwkMlDsaKey(Environment* env,
const KeyObjectData& key,
Expand Down
97 changes: 97 additions & 0 deletions src/crypto/crypto_slh_dsa.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#include "crypto/crypto_slh_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_WITH_PQC
constexpr const char* GetSlhDsaAlgorithmName(int id) {
switch (id) {
case EVP_PKEY_SLH_DSA_SHA2_128F:
return "SLH-DSA-SHA2-128f";
case EVP_PKEY_SLH_DSA_SHA2_128S:
return "SLH-DSA-SHA2-128s";
case EVP_PKEY_SLH_DSA_SHA2_192F:
return "SLH-DSA-SHA2-192f";
case EVP_PKEY_SLH_DSA_SHA2_192S:
return "SLH-DSA-SHA2-192s";
case EVP_PKEY_SLH_DSA_SHA2_256F:
return "SLH-DSA-SHA2-256f";
case EVP_PKEY_SLH_DSA_SHA2_256S:
return "SLH-DSA-SHA2-256s";
case EVP_PKEY_SLH_DSA_SHAKE_128F:
return "SLH-DSA-SHAKE-128f";
case EVP_PKEY_SLH_DSA_SHAKE_128S:
return "SLH-DSA-SHAKE-128s";
case EVP_PKEY_SLH_DSA_SHAKE_192F:
return "SLH-DSA-SHAKE-192f";
case EVP_PKEY_SLH_DSA_SHAKE_192S:
return "SLH-DSA-SHAKE-192s";
case EVP_PKEY_SLH_DSA_SHAKE_256F:
return "SLH-DSA-SHAKE-256f";
case EVP_PKEY_SLH_DSA_SHAKE_256S:
return "SLH-DSA-SHAKE-256s";
default:
return nullptr;
}
}

/**
* Exports an SLH-DSA key to JWK format.
*
* The resulting JWK object contains:
* - "kty": "AKP" (Asymmetric Key Pair - required)
* - "alg": "SLH-DSA-XX-XX" (Algorithm identifier - required for "AKP")
* - "pub": "<Base64URL-encoded raw public key>" (required)
* - "priv": "<Base64URL-encoded raw private key>" (required for private keys)
*/
bool ExportJwkSlhDsaKey(Environment* env,
const KeyObjectData& key,
Local<Object> target) {
Mutex::ScopedLock lock(key.mutex());
const auto& pkey = key.GetAsymmetricKey();

const char* alg = GetSlhDsaAlgorithmName(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) {
if (!trySetKey(env, pkey.rawPrivateKey(), 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
21 changes: 21 additions & 0 deletions src/crypto/crypto_slh_dsa.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#ifndef SRC_CRYPTO_CRYPTO_SLH_DSA_H_
#define SRC_CRYPTO_CRYPTO_SLH_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_WITH_PQC
bool ExportJwkSlhDsaKey(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_SLH_DSA_H_
Loading