From 736182ffdc41a2dbc6ecfb6ac2b1ba4d9ac52494 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Wed, 8 Jul 2026 22:07:45 +0200 Subject: [PATCH 01/16] crypto: split OpenSSL 3, BoringSSL, and legacy backends This separates the native crypto backend paths for OpenSSL >= 3, BoringSSL, and legacy OpenSSL. The OpenSSL >= 3 path now builds with `OPENSSL_API_COMPAT=30000` and `OPENSSL_NO_DEPRECATED`, moving normal crypto/TLS code away from APIs that OpenSSL 3.0.0 marks deprecated. BoringSSL remains on its own branch, and OpenSSL < 3 remains the legacy fallback. The exception is ENGINE support. ENGINE APIs are isolated into a dedicated compatibility target so they can remain available while the JS-facing engine APIs are runtime-deprecated in 27.x. That gives us a clear removal point for 28.x, without letting ENGINE usage leak back into the strict OpenSSL 3 path. The split also makes the eventual OpenSSL 1.1.1 removal easier to reason about. Once support for OpenSSL < 3 is dropped, the legacy branch can be removed in a focused follow-up, possibly targeting 27.x, instead of untangling mixed version guards throughout the crypto implementation. No public crypto or TLS API behavior is intentionally changed. Assisted-by: Codex:gpt-5 Signed-off-by: Filip Skokan PR-URL: https://github.com/nodejs/node/pull/64211 Refs: https://github.com/nodejs/node/issues/56733 Reviewed-By: Matteo Collina Reviewed-By: Richard Lau Reviewed-By: James M Snell --- common.gypi | 1 + configure.py | 96 +- deps/ncrypto/engine.cc | 37 +- deps/ncrypto/ncrypto.cc | 1794 ++++++++++++++++- deps/ncrypto/ncrypto.gyp | 58 +- deps/ncrypto/ncrypto.h | 196 +- deps/ncrypto/unofficial.gni | 6 +- src/crypto/crypto_common.cc | 8 +- src/crypto/crypto_context.cc | 84 +- src/crypto/crypto_context.h | 8 + src/crypto/crypto_dh.cc | 63 +- src/crypto/crypto_ec.cc | 62 +- src/crypto/crypto_hash.cc | 9 +- src/crypto/crypto_keys.cc | 67 +- src/crypto/crypto_rsa.cc | 10 + src/crypto/crypto_tls.cc | 4 + src/crypto/crypto_util.cc | 8 +- src/crypto/crypto_x509.cc | 27 +- src/node.cc | 5 + src/node_constants.cc | 50 + test/parallel/test-crypto-dh-curves.js | 57 + test/parallel/test-crypto-key-objects.js | 35 + .../test-tls-connect-secure-context.js | 3 + 23 files changed, 2477 insertions(+), 211 deletions(-) diff --git a/common.gypi b/common.gypi index 52ea124b175e..2411096f51aa 100644 --- a/common.gypi +++ b/common.gypi @@ -24,6 +24,7 @@ 'node_module_version%': '', 'node_with_ltcg%': '', 'node_shared_openssl%': 'false', + 'openssl_is_boringssl%': 'false', 'node_tag%': '', 'uv_library%': 'static_library', diff --git a/configure.py b/configure.py index fca8f2cdf8ca..7fd64a9620e3 100755 --- a/configure.py +++ b/configure.py @@ -1328,50 +1328,72 @@ def get_gas_version(cc): warn(f'Could not recognize `gas`: {gas_ret}') return '0.0' -def get_openssl_version(): +def get_openssl_macros(o): + """Extract OpenSSL preprocessor macros from the configured headers.""" + + # Use the C compiler to extract preprocessor macros from OpenSSL headers. + # crypto.h is included because BoringSSL declares OPENSSL_IS_BORINGSSL there. + args = ['-E', '-dM', + '-include', 'openssl/opensslv.h', + '-include', 'openssl/crypto.h', + '-'] + if not options.shared_openssl: + args = ['-I', 'deps/openssl/openssl/include'] + args + elif options.shared_openssl_includes: + args = ['-I', options.shared_openssl_includes] + args + else: + for dir in o['include_dirs']: + args = ['-I', dir] + args + + proc = subprocess.Popen( + shlex.split(CC) + args, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + with proc: + proc.stdin.write(b'\n') + out = to_utf8(proc.communicate()[0]) + + if proc.returncode != 0: + warn('Failed to extract OpenSSL macros from headers') + return {} + + macros = {} + for line in out.split('\n'): + if line.startswith('#define OPENSSL_'): + parts = line.split() + if len(parts) >= 2: + macro_name = parts[1] + macro_value = parts[2] if len(parts) >= 3 else '1' + macros[macro_name] = macro_value + + return macros + +def get_openssl_version(o): """Parse OpenSSL version from opensslv.h header file. Returns the version as a number matching OPENSSL_VERSION_NUMBER format: - 0xMNN00PPSL where M=major, NN=minor, PP=patch, S=status(0xf=release,0x0=pre), L=0 + 0xMNN00PPSL where M=major, NN=minor, PP=patch, S=status(0xf=release,0x0=pre), + L denotes as a long type literal """ try: - # Use the C compiler to extract preprocessor macros from opensslv.h - args = ['-E', '-dM', '-include', 'openssl/opensslv.h', '-'] - if not options.shared_openssl: - args = ['-I', 'deps/openssl/openssl/include'] + args - elif options.shared_openssl_includes: - args = ['-I', options.shared_openssl_includes] + args - - proc = subprocess.Popen( - shlex.split(CC) + args, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE - ) - with proc: - proc.stdin.write(b'\n') - out = to_utf8(proc.communicate()[0]) - - if proc.returncode != 0: - warn('Failed to extract OpenSSL version from opensslv.h header') - return 0 - - # Parse the macro definitions - macros = {} - for line in out.split('\n'): - if line.startswith('#define OPENSSL_VERSION_'): - parts = line.split() - if len(parts) >= 3: - macro_name = parts[1] - macro_value = parts[2] - macros[macro_name] = macro_value + macros = get_openssl_macros(o) # Extract version components major = int(macros.get('OPENSSL_VERSION_MAJOR', '0')) minor = int(macros.get('OPENSSL_VERSION_MINOR', '0')) patch = int(macros.get('OPENSSL_VERSION_PATCH', '0')) + # If major, minor and patch are all 0, this is probably OpenSSL < 3. + if (major, minor, patch) == (0, 0, 0): + version_number = macros.get('OPENSSL_VERSION_NUMBER') + # Prior to OpenSSL 3 the value should be in the format 0xMNN00PPSL. + # If it is, we need to strip the `L` suffix prior to parsing. + if version_number[:2] == "0x" and version_number[-1] == "L": + return int(version_number[:-1], 16) + # Check if it's a pre-release (has non-empty PRE_RELEASE string) pre_release = macros.get('OPENSSL_VERSION_PRE_RELEASE', '""').strip('"') status = 0x0 if pre_release else 0xf @@ -1387,6 +1409,13 @@ def get_openssl_version(): warn(f'Failed to determine OpenSSL version from header: {e}') return 0 +def get_openssl_is_boringssl(o): + try: + return b('OPENSSL_IS_BORINGSSL' in get_openssl_macros(o)) + except (OSError, ValueError, subprocess.SubprocessError) as e: + warn(f'Failed to determine whether OpenSSL headers are BoringSSL: {e}') + return 'false' + # Note: Apple clang self-reports as clang 4.2.0 and gcc 4.2.1. It passes # the version check more by accident than anything else but a more rigorous # check involves checking the build number against an allowlist. I'm not @@ -2065,7 +2094,8 @@ def without_ssl_error(option): configure_library('openssl', o) - o['variables']['openssl_version'] = get_openssl_version() + o['variables']['openssl_version'] = get_openssl_version(o) + o['variables']['openssl_is_boringssl'] = get_openssl_is_boringssl(o) def configure_sqlite(o): o['variables']['node_use_sqlite'] = b(not options.without_sqlite) diff --git a/deps/ncrypto/engine.cc b/deps/ncrypto/engine.cc index 1845cfcca47a..a8e64e250491 100644 --- a/deps/ncrypto/engine.cc +++ b/deps/ncrypto/engine.cc @@ -1,12 +1,18 @@ #include "ncrypto.h" +#if !defined(OPENSSL_NO_ENGINE) && \ + ((defined(NCRYPTO_ENGINE_COMPAT) && NCRYPTO_ENGINE_COMPAT) || \ + NCRYPTO_USE_LEGACY_OPENSSL) +#include +#endif + namespace ncrypto { // ============================================================================ // Engine #ifndef OPENSSL_NO_ENGINE -EnginePointer::EnginePointer(ENGINE* engine_, bool finish_on_exit_) +EnginePointer::EnginePointer(void* engine_, bool finish_on_exit_) : engine(engine_), finish_on_exit(finish_on_exit_) {} EnginePointer::EnginePointer(EnginePointer&& other) noexcept @@ -24,21 +30,22 @@ EnginePointer& EnginePointer::operator=(EnginePointer&& other) noexcept { return *new (this) EnginePointer(std::move(other)); } -void EnginePointer::reset(ENGINE* engine_, bool finish_on_exit_) { +void EnginePointer::reset(void* engine_, bool finish_on_exit_) { if (engine != nullptr) { + ENGINE* current = static_cast(engine); if (finish_on_exit) { // This also does the equivalent of ENGINE_free. - ENGINE_finish(engine); + ENGINE_finish(current); } else { - ENGINE_free(engine); + ENGINE_free(current); } } engine = engine_; finish_on_exit = finish_on_exit_; } -ENGINE* EnginePointer::release() { - ENGINE* ret = engine; +void* EnginePointer::release() { + void* ret = engine; engine = nullptr; finish_on_exit = false; return ret; @@ -52,8 +59,9 @@ EnginePointer EnginePointer::getEngineByName(const char* name, // Engine not found, try loading dynamically. engine = EnginePointer(ENGINE_by_id("dynamic")); if (engine) { - if (!ENGINE_ctrl_cmd_string(engine.get(), "SO_PATH", name, 0) || - !ENGINE_ctrl_cmd_string(engine.get(), "LOAD", nullptr, 0)) { + ENGINE* current = static_cast(engine.engine); + if (!ENGINE_ctrl_cmd_string(current, "SO_PATH", name, 0) || + !ENGINE_ctrl_cmd_string(current, "LOAD", nullptr, 0)) { engine.reset(); } } @@ -64,19 +72,24 @@ EnginePointer EnginePointer::getEngineByName(const char* name, bool EnginePointer::setAsDefault(uint32_t flags, CryptoErrorList* errors) { if (engine == nullptr) return false; ClearErrorOnReturn clear_error_on_return(errors); - return ENGINE_set_default(engine, flags) != 0; + return ENGINE_set_default(static_cast(engine), flags) != 0; } bool EnginePointer::init(bool finish_on_exit) { if (engine == nullptr) return false; if (finish_on_exit) setFinishOnExit(); - return ENGINE_init(engine) == 1; + return ENGINE_init(static_cast(engine)) == 1; } EVPKeyPointer EnginePointer::loadPrivateKey(const char* key_name) { if (engine == nullptr) return EVPKeyPointer(); - return EVPKeyPointer( - ENGINE_load_private_key(engine, key_name, nullptr, nullptr)); + return EVPKeyPointer(ENGINE_load_private_key( + static_cast(engine), key_name, nullptr, nullptr)); +} + +bool EnginePointer::setClientCertEngine(SSL_CTX* ctx) { + if (engine == nullptr || ctx == nullptr) return false; + return SSL_CTX_set_client_cert_engine(ctx, static_cast(engine)) == 1; } void EnginePointer::initEnginesOnce() { diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index 650587db51ef..5914f9c729c7 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -14,6 +14,7 @@ #endif #include #include +#include #include #include #if OPENSSL_VERSION_MAJOR >= 3 @@ -75,9 +76,209 @@ using BignumCtxPointer = DeleteFnPtr; using BignumGenCallbackPointer = DeleteFnPtr; using NetscapeSPKIPointer = DeleteFnPtr; +const EVP_CIPHER* GetCipherCtxCipher(const EVP_CIPHER_CTX* ctx) { +#if NCRYPTO_USE_OPENSSL3_PROVIDER + return EVP_CIPHER_CTX_get0_cipher(ctx); +#else + return EVP_CIPHER_CTX_cipher(ctx); +#endif +} + +const EVP_MD* GetDigestCtxMd(const EVP_MD_CTX* ctx) { +#if NCRYPTO_USE_OPENSSL3_PROVIDER || NCRYPTO_USE_BORINGSSL + return EVP_MD_CTX_get0_md(ctx); +#else + return EVP_MD_CTX_md(ctx); +#endif +} + +#if NCRYPTO_USE_OPENSSL3_PROVIDER +using OSSLParamBldPointer = DeleteFnPtr; +using OSSLParamPointer = DeleteFnPtr; +struct OpenSSLBufferDeleter { + void operator()(unsigned char* pointer) const { OPENSSL_free(pointer); } +}; +using OpenSSLBufferPointer = + std::unique_ptr; +#endif + static constexpr int kX509NameFlagsRFC2253WithinUtf8JSON = XN_FLAG_RFC2253 & ~ASN1_STRFLGS_ESC_MSB & ~ASN1_STRFLGS_ESC_CTRL; +#if NCRYPTO_USE_OPENSSL3_PROVIDER +bool GetPKeyBnParam(const EVP_PKEY* pkey, + const char* name, + DeleteFnPtr* out) { + BIGNUM* bn = nullptr; + if (pkey == nullptr) return false; + if (EVP_PKEY_get_bn_param(pkey, name, &bn) == 1) { + out->reset(bn); + return true; + } + + size_t len = 0; + if (EVP_PKEY_get_octet_string_param(pkey, name, nullptr, 0, &len) != 1) { + return false; + } + auto data = DataPointer::Alloc(len); + if (!data || + EVP_PKEY_get_octet_string_param(pkey, + name, + static_cast(data.get()), + data.size(), + &len) != 1) { + return false; + } + bn = BN_bin2bn(static_cast(data.get()), len, nullptr); + if (bn == nullptr) return false; + out->reset(bn); + return true; +} + +bool GetOptionalPKeyBnParam(const EVP_PKEY* pkey, + const char* name, + DeleteFnPtr* out) { + BIGNUM* bn = nullptr; + if (pkey == nullptr) { + out->reset(); + return true; + } + if (EVP_PKEY_get_bn_param(pkey, name, &bn) == 1) { + out->reset(bn); + return true; + } + + size_t len = 0; + if (EVP_PKEY_get_octet_string_param(pkey, name, nullptr, 0, &len) == 1) { + auto data = DataPointer::Alloc(len); + if (!data || + EVP_PKEY_get_octet_string_param(pkey, + name, + static_cast(data.get()), + data.size(), + &len) != 1) { + return false; + } + bn = BN_bin2bn(static_cast(data.get()), len, nullptr); + if (bn == nullptr) return false; + out->reset(bn); + return true; + } + + out->reset(); + return true; +} + +EVPKeyPointer NewPKeyFromData(int id, int selection, OSSL_PARAM* params) { + auto ctx = EVPKeyCtxPointer::NewFromID(id); + if (!ctx || EVP_PKEY_fromdata_init(ctx.get()) != 1) return {}; + + EVP_PKEY* pkey = nullptr; + if (EVP_PKEY_fromdata(ctx.get(), &pkey, selection, params) != 1) { + return {}; + } + return EVPKeyPointer(pkey); +} + +EVPKeyPointer NewDhPKey(const BIGNUM* p, + const BIGNUM* g, + const BIGNUM* pub = nullptr, + const BIGNUM* priv = nullptr) { + if (p == nullptr || g == nullptr) return {}; + + OSSLParamBldPointer bld(OSSL_PARAM_BLD_new()); + if (!bld || + OSSL_PARAM_BLD_push_BN(bld.get(), OSSL_PKEY_PARAM_FFC_P, p) != 1 || + OSSL_PARAM_BLD_push_BN(bld.get(), OSSL_PKEY_PARAM_FFC_G, g) != 1) { + return {}; + } + + int selection = EVP_PKEY_KEY_PARAMETERS; + if (pub != nullptr) { + if (OSSL_PARAM_BLD_push_BN(bld.get(), OSSL_PKEY_PARAM_PUB_KEY, pub) != 1) { + return {}; + } + selection |= EVP_PKEY_PUBLIC_KEY; + } + if (priv != nullptr) { + if (OSSL_PARAM_BLD_push_BN(bld.get(), OSSL_PKEY_PARAM_PRIV_KEY, priv) != + 1) { + return {}; + } + selection |= EVP_PKEY_PRIVATE_KEY; + } + + OSSLParamPointer params(OSSL_PARAM_BLD_to_param(bld.get())); + if (!params) return {}; + return NewPKeyFromData(EVP_PKEY_DH, selection, params.get()); +} + +EVPKeyPointer NewDhPKey(const char* group_name, + const BIGNUM* pub = nullptr, + const BIGNUM* priv = nullptr) { + if (group_name == nullptr) return {}; + + if (pub == nullptr && priv == nullptr) { + EVPKeyCtxPointer ctx(EVP_PKEY_CTX_new_from_name(nullptr, "DH", nullptr)); + OSSL_PARAM params[] = { + OSSL_PARAM_construct_utf8_string( + OSSL_PKEY_PARAM_GROUP_NAME, const_cast(group_name), 0), + OSSL_PARAM_END, + }; + if (!ctx || !ctx.initForParamgen() || + EVP_PKEY_CTX_set_params(ctx.get(), params) != 1) { + return {}; + } + return ctx.paramgen(); + } + + OSSLParamBldPointer bld(OSSL_PARAM_BLD_new()); + if (!bld || OSSL_PARAM_BLD_push_utf8_string( + bld.get(), OSSL_PKEY_PARAM_GROUP_NAME, group_name, 0) != 1) { + return {}; + } + + int selection = EVP_PKEY_KEY_PARAMETERS; + if (pub != nullptr) { + if (OSSL_PARAM_BLD_push_BN(bld.get(), OSSL_PKEY_PARAM_PUB_KEY, pub) != 1) { + return {}; + } + selection |= EVP_PKEY_PUBLIC_KEY; + } + if (priv != nullptr) { + if (OSSL_PARAM_BLD_push_BN(bld.get(), OSSL_PKEY_PARAM_PRIV_KEY, priv) != + 1) { + return {}; + } + selection |= EVP_PKEY_PRIVATE_KEY; + } + + OSSLParamPointer params(OSSL_PARAM_BLD_to_param(bld.get())); + if (!params) return {}; + return NewPKeyFromData(EVP_PKEY_DH, selection, params.get()); +} + +bool GetDhParams(const EVP_PKEY* pkey, + DeleteFnPtr* p, + DeleteFnPtr* g, + DeleteFnPtr* q = nullptr, + DeleteFnPtr* j = nullptr) { + return GetPKeyBnParam(pkey, OSSL_PKEY_PARAM_FFC_P, p) && + GetPKeyBnParam(pkey, OSSL_PKEY_PARAM_FFC_G, g) && + (q == nullptr || + GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_FFC_Q, q)) && + (j == nullptr || + GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_FFC_COFACTOR, j)); +} + +bool GetDhKeys(const EVP_PKEY* pkey, + DeleteFnPtr* pub, + DeleteFnPtr* priv) { + return GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_PUB_KEY, pub) && + GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_PRIV_KEY, priv); +} +#endif + #if NCRYPTO_USE_BORINGSSL_EVP_DO_ALL_FALLBACK struct BoringSSLCipher { const EVP_CIPHER* (*get)(); @@ -503,7 +704,18 @@ int BignumPointer::isPrime(int nchecks, }, &innerCb); } +#if NCRYPTO_USE_OPENSSL3_PROVIDER + return BN_check_prime(get(), ctx.get(), cb.get()); +#elif NCRYPTO_USE_BORINGSSL + int is_probably_prime = 0; + if (BN_primality_test( + &is_probably_prime, get(), nchecks, ctx.get(), 0, cb.get()) != 1) { + return -1; + } + return is_probably_prime; +#else return BN_is_prime_ex(get(), nchecks, ctx.get(), cb.get()); +#endif } BignumPointer BignumPointer::NewPrime(const PrimeConfig& params, @@ -1392,7 +1604,11 @@ bool X509View::ifRsa(KeyCallback callback) const { OSSL3_CONST EVP_PKEY* pkey = X509_get0_pubkey(cert_); auto id = EVP_PKEY_id(pkey); if (id == EVP_PKEY_RSA || id == EVP_PKEY_RSA2 || id == EVP_PKEY_RSA_PSS) { +#if NCRYPTO_USE_OPENSSL3_PROVIDER + Rsa rsa(pkey); +#else Rsa rsa(EVP_PKEY_get0_RSA(pkey)); +#endif if (!rsa) [[unlikely]] return true; return callback(rsa); @@ -1405,7 +1621,11 @@ bool X509View::ifEc(KeyCallback callback) const { OSSL3_CONST EVP_PKEY* pkey = X509_get0_pubkey(cert_); auto id = EVP_PKEY_id(pkey); if (id == EVP_PKEY_EC) { +#if NCRYPTO_USE_OPENSSL3_PROVIDER + Ec ec(pkey); +#else Ec ec(EVP_PKEY_get0_EC_KEY(pkey)); +#endif if (!ec) [[unlikely]] return true; return callback(ec); @@ -1433,7 +1653,11 @@ X509Pointer X509Pointer::IssuerFrom(const SSL_CTX* ctx, const X509View& cert) { } X509Pointer X509Pointer::PeerFrom(const SSLPointer& ssl) { +#if NCRYPTO_USE_OPENSSL3_PROVIDER + return X509Pointer(SSL_get1_peer_certificate(ssl.get())); +#else return X509Pointer(SSL_get_peer_certificate(ssl.get())); +#endif } // When adding or removing errors below, please also update the list in the API @@ -1563,11 +1787,198 @@ bool EqualNoCase(const std::string_view a, const std::string_view b) { return std::tolower(a) == std::tolower(b); }); } + +#if NCRYPTO_USE_OPENSSL3_PROVIDER +const char* GetOpenSSLDhGroupName(const std::string_view name, + DHPointer::FindGroupOption option) { + if (option != DHPointer::FindGroupOption::NO_SMALL_PRIMES && + EqualNoCase(name, "modp5")) { + return "modp_1536"; + } + if (EqualNoCase(name, "modp14")) return "modp_2048"; + if (EqualNoCase(name, "modp15")) return "modp_3072"; + if (EqualNoCase(name, "modp16")) return "modp_4096"; + if (EqualNoCase(name, "modp17")) return "modp_6144"; + if (EqualNoCase(name, "modp18")) return "modp_8192"; + return nullptr; +} + +int GetDhGroupPrivateBits(const char* group_name) { + if (group_name == nullptr) return 0; + if (strcmp(group_name, "modp_1536") == 0) return 200; + if (strcmp(group_name, "modp_2048") == 0) return 225; + if (strcmp(group_name, "modp_3072") == 0) return 275; + if (strcmp(group_name, "modp_4096") == 0) return 325; + if (strcmp(group_name, "modp_6144") == 0) return 375; + if (strcmp(group_name, "modp_8192") == 0) return 400; + return 0; +} + +bool GenerateDhPrivateKey(BignumPointer* out, + const BIGNUM* p, + const char* group_name) { + if (out == nullptr || p == nullptr) return false; + auto priv = BignumPointer::NewSecure(); + if (!priv) return false; + + const int bits = GetDhGroupPrivateBits(group_name); + if (bits > 0) { + if (BN_priv_rand(priv.get(), bits, BN_RAND_TOP_ONE, BN_RAND_BOTTOM_ANY) != + 1) { + return false; + } + } else { + auto range = BignumPointer(BN_dup(p)); + if (!range || BN_sub_word(range.get(), 3) != 1 || + BN_priv_rand_range(priv.get(), range.get()) != 1 || + BN_add_word(priv.get(), 2) != 1) { + return false; + } + } + + *out = std::move(priv); + return true; +} + +// Recompute DH public keys locally when a private key already exists. Provider +// keygen creates a fresh keypair, which is both slower and changes semantics. +bool GenerateDhPublicKey(BignumPointer* out, + const BIGNUM* p, + const BIGNUM* g, + const BIGNUM* priv) { + if (out == nullptr || p == nullptr || g == nullptr || priv == nullptr) { + return false; + } + auto pub = BignumPointer::New(); + BignumCtxPointer ctx(BN_CTX_new()); + if (!pub || !ctx || + BN_mod_exp_mont_consttime(pub.get(), g, priv, p, ctx.get(), nullptr) != + 1) { + return false; + } + + *out = std::move(pub); + return true; +} + +std::optional CheckDhParams(const BIGNUM* p, + const BIGNUM* g, + const BIGNUM* q, + const BIGNUM* j) { + // TODO(panva): In a semver-major, consider tightening OpenSSL 3 validation + // to report generator and q failures as strictly as legacy DH_check(). + if (p == nullptr || g == nullptr) return std::nullopt; + + const int p_bits = BN_num_bits(p); + if (p_bits > OPENSSL_DH_CHECK_MAX_MODULUS_BITS) return std::nullopt; + + int codes = 0; + if (!BN_is_odd(p)) { + codes |= static_cast(DHPointer::CheckResult::P_NOT_PRIME); + } + if (BN_is_negative(g) || BN_is_zero(g) || BN_is_one(g)) { + codes |= static_cast(DHPointer::CheckResult::NOT_SUITABLE_GENERATOR); + } + if (p_bits < 512) { + codes |= static_cast(DHPointer::CheckResult::MODULUS_TOO_SMALL); + } + if (p_bits > OPENSSL_DH_MAX_MODULUS_BITS) { + codes |= static_cast(DHPointer::CheckResult::MODULUS_TOO_LARGE); + } + + BignumCtxPointer ctx(BN_CTX_new()); + if (!ctx) return std::nullopt; + + auto tmp1 = BignumPointer::New(); + auto tmp2 = BignumPointer::New(); + if (!tmp1 || !tmp2) return std::nullopt; + + if (BN_copy(tmp1.get(), p) == nullptr || BN_sub_word(tmp1.get(), 1) != 1) { + return std::nullopt; + } + if (BN_cmp(g, tmp1.get()) >= 0) { + codes |= static_cast(DHPointer::CheckResult::NOT_SUITABLE_GENERATOR); + } + + bool q_good = false; + if (q != nullptr) { + if (BN_ucmp(p, q) > 0) { + q_good = true; + } else { + codes |= static_cast(DHPointer::CheckResult::INVALID_Q); + } + } + + if (q_good) { + if (BN_cmp(g, BN_value_one()) <= 0 || BN_cmp(g, p) >= 0) { + codes |= static_cast(DHPointer::CheckResult::NOT_SUITABLE_GENERATOR); + } else if (BN_mod_exp(tmp1.get(), g, q, p, ctx.get()) != 1) { + return std::nullopt; + } else if (!BN_is_one(tmp1.get())) { + codes |= static_cast(DHPointer::CheckResult::NOT_SUITABLE_GENERATOR); + } + + const int q_is_prime = BN_check_prime(q, ctx.get(), nullptr); + if (q_is_prime < 0) return std::nullopt; + if (q_is_prime == 0) { + codes |= static_cast(DHPointer::CheckResult::Q_NOT_PRIME); + } + + if (BN_div(tmp1.get(), tmp2.get(), p, q, ctx.get()) != 1) { + return std::nullopt; + } + if (!BN_is_one(tmp2.get())) { + codes |= static_cast(DHPointer::CheckResult::INVALID_Q); + } + if (j != nullptr && BN_cmp(j, tmp1.get()) != 0) { + codes |= static_cast(DHPointer::CheckResult::INVALID_J); + } + } + + const int p_is_prime = BN_check_prime(p, ctx.get(), nullptr); + if (p_is_prime < 0) return std::nullopt; + if (p_is_prime == 0) { + codes |= static_cast(DHPointer::CheckResult::P_NOT_PRIME); + } else if (q == nullptr) { + if (BN_rshift1(tmp1.get(), p) != 1) return std::nullopt; + const int q_is_prime = BN_check_prime(tmp1.get(), ctx.get(), nullptr); + if (q_is_prime < 0) return std::nullopt; + if (q_is_prime == 0) { + codes |= static_cast(DHPointer::CheckResult::P_NOT_SAFE_PRIME); + } + } + + return codes; +} +#endif } // namespace +#if NCRYPTO_USE_OPENSSL3_PROVIDER +DHPointer::DHPointer(EVPKeyPointer&& key, const char* group_name) + : dh_(key.release()), group_name_(group_name) {} + +DHPointer::DHPointer(BignumPointer&& p, + BignumPointer&& g, + const char* group_name) + : p_(std::move(p)), g_(std::move(g)), group_name_(group_name) {} +#else DHPointer::DHPointer(DH* dh) : dh_(dh) {} +#endif -DHPointer::DHPointer(DHPointer&& other) noexcept : dh_(other.release()) {} +DHPointer::DHPointer(DHPointer&& other) noexcept +#if NCRYPTO_USE_OPENSSL3_PROVIDER + : dh_(other.dh_.release()), + p_(std::move(other.p_)), + g_(std::move(other.g_)), + pub_key_(std::move(other.pub_key_)), + pvt_key_(std::move(other.pvt_key_)), + group_name_(other.group_name_) { + other.group_name_ = nullptr; +} +#else + : dh_(other.release()) { +} +#endif DHPointer& DHPointer::operator=(DHPointer&& other) noexcept { if (this == &other) return *this; @@ -1579,13 +1990,45 @@ DHPointer::~DHPointer() { reset(); } -void DHPointer::reset(DH* dh) { +void DHPointer::reset( +#if NCRYPTO_USE_OPENSSL3_PROVIDER + EVP_PKEY* dh +#else + DH* dh +#endif +) { dh_.reset(dh); +#if NCRYPTO_USE_OPENSSL3_PROVIDER + p_.reset(); + g_.reset(); + pub_key_.reset(); + pvt_key_.reset(); + group_name_ = nullptr; +#endif } +#if NCRYPTO_USE_OPENSSL3_PROVIDER +EVP_PKEY* DHPointer::release() { + if (!dh_ && p_ && g_) { + auto pkey = + group_name_ != nullptr + ? NewDhPKey(group_name_, pub_key_.get(), pvt_key_.get()) + : NewDhPKey(p_.get(), g_.get(), pub_key_.get(), pvt_key_.get()); + if (!pkey) return nullptr; + dh_.reset(pkey.release()); + } + p_.reset(); + g_.reset(); + pub_key_.reset(); + pvt_key_.reset(); + group_name_ = nullptr; + return dh_.release(); +} +#else DH* DHPointer::release() { return dh_.release(); } +#endif BignumPointer DHPointer::FindGroup(const std::string_view name, FindGroupOption option) { @@ -1611,7 +2054,7 @@ BignumPointer DHPointer::FindGroup(const std::string_view name, BignumPointer DHPointer::GetStandardGenerator() { auto bn = BignumPointer::New(); if (!bn) return {}; - if (!bn.setWord(DH_GENERATOR_2)) return {}; + if (!bn.setWord(2)) return {}; return bn; } @@ -1623,12 +2066,22 @@ DHPointer DHPointer::FromGroup(const std::string_view name, auto generator = GetStandardGenerator(); if (!generator) return {}; // Unable to create the generator. +#if NCRYPTO_USE_OPENSSL3_PROVIDER + const char* group_name = GetOpenSSLDhGroupName(name, option); + return DHPointer(std::move(group), std::move(generator), group_name); +#else return New(std::move(group), std::move(generator)); +#endif } DHPointer DHPointer::New(BignumPointer&& p, BignumPointer&& g) { if (!p || !g) return {}; +#if NCRYPTO_USE_OPENSSL3_PROVIDER + auto pkey = NewDhPKey(p.get(), g.get()); + if (!pkey) return {}; + return DHPointer(std::move(pkey)); +#else DHPointer dh(DH_new()); if (!dh) return {}; @@ -1643,9 +2096,21 @@ DHPointer DHPointer::New(BignumPointer&& p, BignumPointer&& g) { g.release(); return dh; +#endif } DHPointer DHPointer::New(size_t bits, unsigned int generator) { +#if NCRYPTO_USE_OPENSSL3_PROVIDER + auto param_ctx = EVPKeyCtxPointer::NewFromID(EVP_PKEY_DH); + if (!param_ctx.initForParamgen() || + !param_ctx.setDhParameters(bits, generator)) { + return {}; + } + + auto key_params = param_ctx.paramgen(); + if (!key_params) return {}; + return DHPointer(std::move(key_params)); +#else DHPointer dh(DH_new()); if (!dh) return {}; @@ -1654,23 +2119,103 @@ DHPointer DHPointer::New(size_t bits, unsigned int generator) { } return dh; +#endif } DHPointer::CheckResult DHPointer::check() { ClearErrorOnReturn clearErrorOnReturn; - if (!dh_) return DHPointer::CheckResult::NONE; + if (!*this) return DHPointer::CheckResult::NONE; +#if NCRYPTO_USE_OPENSSL3_PROVIDER + // TODO(panva): In a semver-major, consider validating named DH groups + // through the provider instead of preserving the historical verifyError. + if (group_name_ != nullptr) return CheckResult::NONE; + + DeleteFnPtr p; + DeleteFnPtr g; + DeleteFnPtr q; + DeleteFnPtr j; + const BIGNUM* p_bn = p_.get(); + const BIGNUM* g_bn = g_.get(); + const BIGNUM* q_bn = nullptr; + const BIGNUM* j_bn = nullptr; + if ((p_bn == nullptr || g_bn == nullptr) && + !GetDhParams(dh_.get(), &p, &g, &q, &j)) { + return DHPointer::CheckResult::CHECK_FAILED; + } + if (p_bn == nullptr) p_bn = p.get(); + if (g_bn == nullptr) g_bn = g.get(); + q_bn = q.get(); + j_bn = j.get(); + if (p_bn == nullptr || g_bn == nullptr) { + return DHPointer::CheckResult::CHECK_FAILED; + } + + auto codes = CheckDhParams(p_bn, g_bn, q_bn, j_bn); + if (!codes) return DHPointer::CheckResult::CHECK_FAILED; + return static_cast(*codes); +#else int codes = 0; if (DH_check(dh_.get(), &codes) != 1) return DHPointer::CheckResult::CHECK_FAILED; return static_cast(codes); +#endif } DHPointer::CheckPublicKeyResult DHPointer::checkPublicKey( const BignumPointer& pub_key) { ClearErrorOnReturn clearErrorOnReturn; - if (!pub_key || !dh_) { + if (!pub_key || !*this) { return DHPointer::CheckPublicKeyResult::CHECK_FAILED; } +#if NCRYPTO_USE_OPENSSL3_PROVIDER + DeleteFnPtr p; + DeleteFnPtr g; + const BIGNUM* p_bn = p_.get(); + const BIGNUM* g_bn = g_.get(); + if ((p_bn == nullptr || g_bn == nullptr) && !GetDhParams(dh_.get(), &p, &g)) { + return DHPointer::CheckPublicKeyResult::CHECK_FAILED; + } + if (p_bn == nullptr) p_bn = p.get(); + if (g_bn == nullptr) g_bn = g.get(); + if (p_bn == nullptr || g_bn == nullptr) { + return DHPointer::CheckPublicKeyResult::CHECK_FAILED; + } + + if (BN_cmp(pub_key.get(), BN_value_one()) <= 0) { + return DHPointer::CheckPublicKeyResult::TOO_SMALL; + } + + DeleteFnPtr p_minus_one(BN_dup(p_bn)); + if (!p_minus_one || BN_sub_word(p_minus_one.get(), 1) != 1) { + return DHPointer::CheckPublicKeyResult::CHECK_FAILED; + } + + if (BN_cmp(pub_key.get(), p_minus_one.get()) >= 0) { + return DHPointer::CheckPublicKeyResult::TOO_LARGE; + } + + if (p_) { + if (group_name_ == nullptr) return CheckPublicKeyResult::NONE; + + auto peer = NewDhPKey(group_name_, pub_key.get()); + if (!peer) return DHPointer::CheckPublicKeyResult::CHECK_FAILED; + EVPKeyCtxPointer ctx(EVP_PKEY_CTX_new(peer.get(), nullptr)); + if (!ctx) return DHPointer::CheckPublicKeyResult::CHECK_FAILED; + if (EVP_PKEY_public_check(ctx.get()) != 1) { + return DHPointer::CheckPublicKeyResult::INVALID; + } + return CheckPublicKeyResult::NONE; + } + + auto peer = NewDhPKey(p_bn, g_bn, pub_key.get()); + if (!peer) return DHPointer::CheckPublicKeyResult::CHECK_FAILED; + EVPKeyCtxPointer ctx(EVP_PKEY_CTX_new(peer.get(), nullptr)); + if (!ctx) return DHPointer::CheckPublicKeyResult::CHECK_FAILED; + if (EVP_PKEY_public_check(ctx.get()) != 1) { + return DHPointer::CheckPublicKeyResult::INVALID; + } + return CheckPublicKeyResult::NONE; +#else int codes = 0; if (DH_check_pub_key(dh_.get(), pub_key.get(), &codes) != 1) { return DHPointer::CheckPublicKeyResult::CHECK_FAILED; @@ -1687,58 +2232,242 @@ DHPointer::CheckPublicKeyResult DHPointer::checkPublicKey( return DHPointer::CheckPublicKeyResult::INVALID; } return CheckPublicKeyResult::NONE; +#endif } DataPointer DHPointer::getPrime() const { - if (!dh_) return {}; + if (!*this) return {}; +#if NCRYPTO_USE_OPENSSL3_PROVIDER + if (p_) return p_.encode(); + + DeleteFnPtr p; + DeleteFnPtr g; + if (!GetDhParams(dh_.get(), &p, &g)) return {}; + return BignumPointer::Encode(p.get()); +#else const BIGNUM* p; DH_get0_pqg(dh_.get(), &p, nullptr, nullptr); return BignumPointer::Encode(p); +#endif +} + +size_t DHPointer::getPrimeBits() const { + if (!*this) return 0; +#if NCRYPTO_USE_OPENSSL3_PROVIDER + if (p_) return BignumPointer::GetBitCount(p_.get()); + + DeleteFnPtr p; + DeleteFnPtr g; + if (!GetDhParams(dh_.get(), &p, &g)) return 0; + return BignumPointer::GetBitCount(p.get()); +#else + const BIGNUM* p; + DH_get0_pqg(dh_.get(), &p, nullptr, nullptr); + return BignumPointer::GetBitCount(p); +#endif } DataPointer DHPointer::getGenerator() const { - if (!dh_) return {}; + if (!*this) return {}; +#if NCRYPTO_USE_OPENSSL3_PROVIDER + if (g_) return g_.encode(); + + DeleteFnPtr p; + DeleteFnPtr g; + if (!GetDhParams(dh_.get(), &p, &g)) return {}; + return BignumPointer::Encode(g.get()); +#else const BIGNUM* g; DH_get0_pqg(dh_.get(), nullptr, nullptr, &g); return BignumPointer::Encode(g); +#endif } DataPointer DHPointer::getPublicKey() const { + if (!*this) return {}; +#if NCRYPTO_USE_OPENSSL3_PROVIDER + if (pub_key_) return pub_key_.encode(); if (!dh_) return {}; + + DeleteFnPtr pub_key; + DeleteFnPtr pvt_key; + if (!GetDhKeys(dh_.get(), &pub_key, &pvt_key)) return {}; + return BignumPointer::Encode(pub_key.get()); +#else const BIGNUM* pub_key; DH_get0_key(dh_.get(), &pub_key, nullptr); return BignumPointer::Encode(pub_key); +#endif } DataPointer DHPointer::getPrivateKey() const { + if (!*this) return {}; +#if NCRYPTO_USE_OPENSSL3_PROVIDER + if (pvt_key_) return pvt_key_.encode(); if (!dh_) return {}; + + DeleteFnPtr pub_key; + DeleteFnPtr pvt_key; + if (!GetDhKeys(dh_.get(), &pub_key, &pvt_key)) return {}; + return BignumPointer::Encode(pvt_key.get()); +#else const BIGNUM* pvt_key; DH_get0_key(dh_.get(), nullptr, &pvt_key); return BignumPointer::Encode(pvt_key); +#endif } -DataPointer DHPointer::generateKeys() const { +bool DHPointer::hasPrivateKey() const { + if (!*this) return false; +#if NCRYPTO_USE_OPENSSL3_PROVIDER + if (pvt_key_) return true; + if (!dh_) return false; + + DeleteFnPtr pub_key; + DeleteFnPtr pvt_key; + if (!GetDhKeys(dh_.get(), &pub_key, &pvt_key)) return false; + return pvt_key != nullptr; +#else + const BIGNUM* pvt_key = nullptr; + DH_get0_key(dh_.get(), nullptr, &pvt_key); + return pvt_key != nullptr; +#endif +} + +DataPointer DHPointer::generateKeys() { ClearErrorOnReturn clearErrorOnReturn; - if (!dh_) return {}; + if (!*this) return {}; +#if NCRYPTO_USE_OPENSSL3_PROVIDER + if (p_ && g_) { + if (!pvt_key_ && !GenerateDhPrivateKey(&pvt_key_, p_.get(), group_name_)) { + return {}; + } + + BignumPointer generated_pub_key; + if (!GenerateDhPublicKey( + &generated_pub_key, p_.get(), g_.get(), pvt_key_.get())) { + return {}; + } + + if (pub_key_ && BN_cmp(pub_key_.get(), generated_pub_key.get()) == 0) { + return getPublicKey(); + } + + pub_key_ = std::move(generated_pub_key); + return getPublicKey(); + } + + DeleteFnPtr p; + DeleteFnPtr g; + DeleteFnPtr pub_key; + DeleteFnPtr pvt_key; + if (!GetDhParams(dh_.get(), &p, &g) || + !GetDhKeys(dh_.get(), &pub_key, &pvt_key)) { + return {}; + } + + if (pvt_key != nullptr) { + BignumPointer generated_pub_key; + if (!GenerateDhPublicKey( + &generated_pub_key, p.get(), g.get(), pvt_key.get())) { + return {}; + } + + if (pub_key != nullptr && + BN_cmp(pub_key.get(), generated_pub_key.get()) == 0) { + return getPublicKey(); + } + + auto replacement = + group_name_ != nullptr + ? NewDhPKey(group_name_, generated_pub_key.get(), pvt_key.get()) + : NewDhPKey( + p.get(), g.get(), generated_pub_key.get(), pvt_key.get()); + if (!replacement) return {}; + dh_.reset(replacement.release()); + return getPublicKey(); + } + + EVPKeyCtxPointer ctx(EVP_PKEY_CTX_new(dh_.get(), nullptr)); + if (!ctx || !ctx.initForKeygen()) return {}; + EVP_PKEY* generated = nullptr; + if (EVP_PKEY_keygen(ctx.get(), &generated) != 1) return {}; + dh_.reset(generated); + return getPublicKey(); +#else // Key generation failed if (!DH_generate_key(dh_.get())) return {}; return getPublicKey(); +#endif } size_t DHPointer::size() const { - if (!dh_) return 0; + if (!*this) return 0; +#if NCRYPTO_USE_OPENSSL3_PROVIDER + if (p_) return BignumPointer::GetByteCount(p_.get()); + + const int bits = EVP_PKEY_get_bits(dh_.get()); + return bits > 0 ? (static_cast(bits) + 7) / 8 : 0; +#else int ret = DH_size(dh_.get()); // DH_size can return a -1 on error but we just want to return a 0 // in that case so we don't wrap around when returning the size_t. return ret >= 0 ? static_cast(ret) : 0; +#endif } DataPointer DHPointer::computeSecret(const BignumPointer& peer) const { ClearErrorOnReturn clearErrorOnReturn; - if (!dh_ || !peer) return {}; + if (!*this || !peer) return {}; + +#if NCRYPTO_USE_OPENSSL3_PROVIDER + if (p_ && pvt_key_) { + auto secret = BignumPointer::NewSecure(); + BignumCtxPointer ctx(BN_CTX_new()); + if (!secret || !ctx || + BN_mod_exp_mont_consttime(secret.get(), + peer.get(), + pvt_key_.get(), + p_.get(), + ctx.get(), + nullptr) != 1) { + return {}; + } + return secret.encodePadded(size()); + } + + EVPKeyPointer peer_key; + if (group_name_ != nullptr) { + peer_key = NewDhPKey(group_name_, peer.get()); + } else { + DeleteFnPtr p; + DeleteFnPtr g; + if (!GetDhParams(dh_.get(), &p, &g)) return {}; + peer_key = NewDhPKey(p.get(), g.get(), peer.get()); + } + if (!peer_key) return {}; + + EVPKeyCtxPointer ctx(EVP_PKEY_CTX_new(dh_.get(), nullptr)); + size_t out_size = size(); + if (!ctx || EVP_PKEY_derive_init(ctx.get()) != 1 || + EVP_PKEY_CTX_set_dh_pad(ctx.get(), 1) != 1 || + EVP_PKEY_derive_set_peer(ctx.get(), peer_key.get()) != 1 || + EVP_PKEY_derive(ctx.get(), nullptr, &out_size) != 1) { + return {}; + } + + if (out_size == 0) return {}; + auto dp = DataPointer::Alloc(out_size); + if (!dp) return {}; + if (EVP_PKEY_derive( + ctx.get(), static_cast(dp.get()), &out_size) != 1) { + return {}; + } + return dp.resize(out_size); +#else auto dp = DataPointer::Alloc(size()); if (!dp) return {}; @@ -1756,10 +2485,35 @@ DataPointer DHPointer::computeSecret(const BignumPointer& peer) const { } return dp; +#endif } bool DHPointer::setPublicKey(BignumPointer&& key) { - if (!dh_) return false; + if (!*this) return false; +#if NCRYPTO_USE_OPENSSL3_PROVIDER + if (p_ && g_) { + pub_key_ = std::move(key); + return true; + } + + DeleteFnPtr pub_key; + DeleteFnPtr pvt_key; + if (!GetDhKeys(dh_.get(), &pub_key, &pvt_key)) { + return false; + } + EVPKeyPointer pkey; + if (group_name_ != nullptr) { + pkey = NewDhPKey(group_name_, key.get(), pvt_key.get()); + } else { + DeleteFnPtr p; + DeleteFnPtr g; + if (!GetDhParams(dh_.get(), &p, &g)) return false; + pkey = NewDhPKey(p.get(), g.get(), key.get(), pvt_key.get()); + } + if (!pkey) return false; + dh_.reset(pkey.release()); + return true; +#else if (DH_set0_key(dh_.get(), key.get(), nullptr) == 1) { // If DH_set0_key returns successfully, then dh_ takes ownership of the // BIGNUM, so we must release it here. Unfortunately coverity does not @@ -1769,10 +2523,35 @@ bool DHPointer::setPublicKey(BignumPointer&& key) { return true; } return false; +#endif } bool DHPointer::setPrivateKey(BignumPointer&& key) { - if (!dh_) return false; + if (!*this) return false; +#if NCRYPTO_USE_OPENSSL3_PROVIDER + if (p_ && g_) { + pvt_key_ = std::move(key); + return true; + } + + DeleteFnPtr pub_key; + DeleteFnPtr pvt_key; + if (!GetDhKeys(dh_.get(), &pub_key, &pvt_key)) { + return false; + } + EVPKeyPointer pkey; + if (group_name_ != nullptr) { + pkey = NewDhPKey(group_name_, pub_key.get(), key.get()); + } else { + DeleteFnPtr p; + DeleteFnPtr g; + if (!GetDhParams(dh_.get(), &p, &g)) return false; + pkey = NewDhPKey(p.get(), g.get(), pub_key.get(), key.get()); + } + if (!pkey) return false; + dh_.reset(pkey.release()); + return true; +#else if (DH_set0_key(dh_.get(), nullptr, key.get()) == 1) { // If DH_set0_key returns successfully, then dh_ takes ownership of the // BIGNUM, so we must release it here. Unfortunately coverity does not @@ -1782,6 +2561,7 @@ bool DHPointer::setPrivateKey(BignumPointer&& key) { return true; } return false; +#endif } DataPointer DHPointer::stateless(const EVPKeyPointer& ourKey, @@ -1790,8 +2570,12 @@ DataPointer DHPointer::stateless(const EVPKeyPointer& ourKey, if (!ourKey || !theirKey) return {}; auto ctx = EVPKeyCtxPointer::New(ourKey); - if (!ctx || EVP_PKEY_derive_init(ctx.get()) <= 0 || - EVP_PKEY_derive_set_peer(ctx.get(), theirKey.get()) <= 0 || + if (!ctx || EVP_PKEY_derive_init(ctx.get()) <= 0) { + return {}; + } + // TODO(panva): In a semver-major, consider padding OpenSSL 3 DH derivation + // results here to match DiffieHellman::computeSecret(). + if (EVP_PKEY_derive_set_peer(ctx.get(), theirKey.get()) <= 0 || EVP_PKEY_derive(ctx.get(), nullptr, &out_size) <= 0) { return {}; } @@ -2214,14 +2998,60 @@ EVPKeyPointer EVPKeyPointer::NewRawSeed( EVPKeyPointer EVPKeyPointer::NewDH(DHPointer&& dh) { if (!dh) return {}; +#if NCRYPTO_USE_OPENSSL3_PROVIDER + return EVPKeyPointer(dh.release()); +#else auto key = New(); if (!key) return {}; if (EVP_PKEY_assign_DH(key.get(), dh.get())) { dh.release(); } return key; +#endif } +#if NCRYPTO_USE_OPENSSL3_PROVIDER +EVPKeyPointer EVPKeyPointer::NewRSA(const Rsa& rsa) { + const auto public_key = rsa.getPublicKey(); + if (public_key.n == nullptr || public_key.e == nullptr) return {}; + + OSSLParamBldPointer bld(OSSL_PARAM_BLD_new()); + if (!bld || + OSSL_PARAM_BLD_push_BN(bld.get(), OSSL_PKEY_PARAM_RSA_N, public_key.n) != + 1 || + OSSL_PARAM_BLD_push_BN(bld.get(), OSSL_PKEY_PARAM_RSA_E, public_key.e) != + 1) { + return {}; + } + + int selection = EVP_PKEY_PUBLIC_KEY; + if (public_key.d != nullptr) { + const auto private_key = rsa.getPrivateKey(); + if (private_key.p == nullptr || private_key.q == nullptr || + private_key.dp == nullptr || private_key.dq == nullptr || + private_key.qi == nullptr || + OSSL_PARAM_BLD_push_BN( + bld.get(), OSSL_PKEY_PARAM_RSA_D, public_key.d) != 1 || + OSSL_PARAM_BLD_push_BN( + bld.get(), OSSL_PKEY_PARAM_RSA_FACTOR1, private_key.p) != 1 || + OSSL_PARAM_BLD_push_BN( + bld.get(), OSSL_PKEY_PARAM_RSA_FACTOR2, private_key.q) != 1 || + OSSL_PARAM_BLD_push_BN( + bld.get(), OSSL_PKEY_PARAM_RSA_EXPONENT1, private_key.dp) != 1 || + OSSL_PARAM_BLD_push_BN( + bld.get(), OSSL_PKEY_PARAM_RSA_EXPONENT2, private_key.dq) != 1 || + OSSL_PARAM_BLD_push_BN( + bld.get(), OSSL_PKEY_PARAM_RSA_COEFFICIENT1, private_key.qi) != 1) { + return {}; + } + selection = EVP_PKEY_KEYPAIR; + } + + OSSLParamPointer params(OSSL_PARAM_BLD_to_param(bld.get())); + if (!params) return {}; + return NewPKeyFromData(EVP_PKEY_RSA, selection, params.get()); +} +#else EVPKeyPointer EVPKeyPointer::NewRSA(RSAPointer&& rsa) { if (!rsa) return {}; auto key = New(); @@ -2231,6 +3061,7 @@ EVPKeyPointer EVPKeyPointer::NewRSA(RSAPointer&& rsa) { } return key; } +#endif // NCRYPTO_USE_OPENSSL3_PROVIDER EVPKeyPointer::EVPKeyPointer(EVP_PKEY* pkey) : pkey_(pkey) {} @@ -2368,18 +3199,83 @@ BIOPointer EVPKeyPointer::derPublicKey() const { bool EVPKeyPointer::assign(const ECKeyPointer& eckey) { if (!pkey_ || !eckey) return {}; +#if NCRYPTO_USE_OPENSSL3_PROVIDER + return set(eckey); +#else return EVP_PKEY_assign_EC_KEY(pkey_.get(), eckey.get()); +#endif } bool EVPKeyPointer::set(const ECKeyPointer& eckey) { if (!pkey_ || !eckey) return false; +#if NCRYPTO_USE_OPENSSL3_PROVIDER + const int nid = EC_GROUP_get_curve_name(eckey.group_.get()); + const char* group_name = OBJ_nid2sn(nid); + if (group_name == nullptr) return false; + + OSSLParamBldPointer bld(OSSL_PARAM_BLD_new()); + if (!bld || OSSL_PARAM_BLD_push_utf8_string( + bld.get(), OSSL_PKEY_PARAM_GROUP_NAME, group_name, 0) != 1) { + return false; + } + + int selection = EVP_PKEY_KEY_PARAMETERS; + OpenSSLBufferPointer encoded_public_key; + ECPointPointer generated_public_key; + const EC_POINT* public_key = eckey.pub_.get(); + if (public_key == nullptr && eckey.priv_ != nullptr) { + generated_public_key = ECPointPointer::New(eckey.group_.get()); + if (!generated_public_key || + !generated_public_key.mul(eckey.group_.get(), eckey.priv_.get())) { + return false; + } + public_key = generated_public_key.get(); + } + + if (public_key != nullptr) { + unsigned char* encoded_public_key_raw = nullptr; + const size_t encoded_public_key_len = + EC_POINT_point2buf(eckey.group_.get(), + public_key, + POINT_CONVERSION_UNCOMPRESSED, + &encoded_public_key_raw, + nullptr); + if (encoded_public_key_len == 0) return false; + encoded_public_key.reset(encoded_public_key_raw); + if (OSSL_PARAM_BLD_push_octet_string(bld.get(), + OSSL_PKEY_PARAM_PUB_KEY, + encoded_public_key.get(), + encoded_public_key_len) != 1) { + return false; + } + selection |= EVP_PKEY_PUBLIC_KEY; + } + + if (eckey.priv_ != nullptr) { + if (OSSL_PARAM_BLD_push_BN( + bld.get(), OSSL_PKEY_PARAM_PRIV_KEY, eckey.priv_.get()) != 1) { + return false; + } + selection |= EVP_PKEY_PRIVATE_KEY; + } + + OSSLParamPointer params(OSSL_PARAM_BLD_to_param(bld.get())); + if (!params) return false; + auto pkey = NewPKeyFromData(EVP_PKEY_EC, selection, params.get()); + if (!pkey) return false; + reset(pkey.release()); + return true; +#else return EVP_PKEY_set1_EC_KEY(pkey_.get(), eckey); +#endif } +#if NCRYPTO_USE_LEGACY_KEY_TYPES EVPKeyPointer::operator const EC_KEY*() const { if (!pkey_) return nullptr; return EVP_PKEY_get0_EC_KEY(pkey_.get()); } +#endif // NCRYPTO_USE_LEGACY_KEY_TYPES namespace { @@ -2434,6 +3330,34 @@ constexpr bool IsASN1Sequence(const unsigned char* data, return true; } +constexpr bool ReadASN1Element(const unsigned char* data, + size_t size, + unsigned char tag, + size_t* header_size, + size_t* content_size, + size_t* total_size) { + if (size < 2 || data[0] != tag) return false; + + size_t offset; + size_t length; + if (data[1] & 0x80) { + size_t n_bytes = data[1] & ~0x80; + if (n_bytes + 2 > size || n_bytes > sizeof(size_t)) return false; + length = 0; + for (size_t i = 0; i < n_bytes; i++) length = (length << 8) | data[i + 2]; + offset = 2 + n_bytes; + } else { + offset = 2; + length = data[1]; + } + + if (offset > size || length > size - offset) return false; + *header_size = offset; + *content_size = length; + *total_size = offset + length; + return true; +} + constexpr bool IsEncryptedPrivateKeyInfo( const Buffer& buffer) { // Both PrivateKeyInfo and EncryptedPrivateKeyInfo start with a SEQUENCE. @@ -2551,6 +3475,103 @@ Buffer GetPassphrase( } return pass; } + +#if NCRYPTO_USE_OPENSSL3_PROVIDER +using OSSLEncoderCtxPointer = + DeleteFnPtr; + +bool WriteEncodedPKey(BIO* bio, + const EVP_PKEY* pkey, + int selection, + EVPKeyPointer::PKFormatType format, + const char* structure, + const EVP_CIPHER* cipher = nullptr, + Buffer passphrase = {}) { + const char* output_type = + format == EVPKeyPointer::PKFormatType::PEM ? "PEM" : "DER"; + OSSLEncoderCtxPointer ctx(OSSL_ENCODER_CTX_new_for_pkey( + pkey, selection, output_type, structure, nullptr)); + if (!ctx || OSSL_ENCODER_CTX_get_num_encoders(ctx.get()) == 0) { + return false; + } + + if (cipher != nullptr) { + if (OSSL_ENCODER_CTX_set_cipher( + ctx.get(), EVP_CIPHER_get0_name(cipher), nullptr) != 1 || + OSSL_ENCODER_CTX_set_passphrase( + ctx.get(), + reinterpret_cast(passphrase.data), + passphrase.len) != 1) { + return false; + } + } + + return OSSL_ENCODER_to_bio(ctx.get(), bio) == 1; +} + +struct DERView { + const unsigned char* data = nullptr; + size_t len = 0; +}; + +int WriteDERView(const void* x, unsigned char** out) { + const auto* der = static_cast(x); + if (der == nullptr || der->data == nullptr || + der->len > static_cast(INT_MAX)) { + return -1; + } + if (out != nullptr) { + memcpy(*out, der->data, der->len); + *out += der->len; + } + return static_cast(der->len); +} + +bool WriteEncryptedTraditionalPEM(BIO* bio, + const EVP_PKEY* pkey, + const EVP_CIPHER* cipher, + Buffer passphrase) { + if (passphrase.len > static_cast(INT_MAX)) return false; + + unsigned char* der = nullptr; + size_t der_len = 0; + OSSLEncoderCtxPointer ctx(OSSL_ENCODER_CTX_new_for_pkey( + pkey, OSSL_KEYMGMT_SELECT_KEYPAIR, "DER", "pkcs1", nullptr)); + if (!ctx || OSSL_ENCODER_to_data(ctx.get(), &der, &der_len) != 1) { + return false; + } + + OpenSSLBufferPointer der_storage(der); + DERView der_view{der_storage.get(), der_len}; + return PEM_ASN1_write_bio( + WriteDERView, + PEM_STRING_RSA, + bio, + &der_view, + cipher, + reinterpret_cast(passphrase.data), + static_cast(passphrase.len), + nullptr, + nullptr) == 1; +} + +bool ECKeyHasMissingOid(const EVPKeyPointer& key) { + if (key.id() != EVP_PKEY_EC) return false; + + const Ec ec(key.get()); + const EC_GROUP* group = ec.getGroup(); + if (group == nullptr || + EC_GROUP_get_asn1_flag(group) != OPENSSL_EC_NAMED_CURVE) { + return false; + } + + const int nid = EC_GROUP_get_curve_name(group); + if (nid == NID_undef) return true; + + const ASN1_OBJECT* asn1 = OBJ_nid2obj(nid); + return asn1 == nullptr || OBJ_length(asn1) == 0; +} +#endif } // namespace EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePrivateKey( @@ -2638,6 +3659,22 @@ Result EVPKeyPointer::writePrivateKey( // PKCS1 is only permitted for RSA keys. if (id() != EVP_PKEY_RSA) return Result(false); +#if NCRYPTO_USE_OPENSSL3_PROVIDER + const EVP_CIPHER* cipher = + config.format == PKFormatType::PEM ? config.cipher : nullptr; + if (cipher != nullptr && passphrase.len == 0) { + err = + !WriteEncryptedTraditionalPEM(bio.get(), get(), cipher, passphrase); + } else { + err = !WriteEncodedPKey(bio.get(), + get(), + OSSL_KEYMGMT_SELECT_ALL, + config.format, + "pkcs1", + cipher, + passphrase); + } +#else #if OPENSSL_VERSION_MAJOR >= 3 const RSA* rsa = EVP_PKEY_get0_RSA(get()); #else @@ -2665,6 +3702,7 @@ Result EVPKeyPointer::writePrivateKey( return Result(false); } } +#endif break; } case PKEncodingType::PKCS8: { @@ -2701,6 +3739,17 @@ Result EVPKeyPointer::writePrivateKey( // SEC1 is only permitted for EC keys if (id() != EVP_PKEY_EC) return Result(false); +#if NCRYPTO_USE_OPENSSL3_PROVIDER + const EVP_CIPHER* cipher = + config.format == PKFormatType::PEM ? config.cipher : nullptr; + err = !WriteEncodedPKey(bio.get(), + get(), + OSSL_KEYMGMT_SELECT_ALL, + config.format, + "type-specific", + cipher, + passphrase); +#else #if OPENSSL_VERSION_MAJOR >= 3 const EC_KEY* ec = EVP_PKEY_get0_EC_KEY(get()); #else @@ -2728,6 +3777,7 @@ Result EVPKeyPointer::writePrivateKey( return Result(false); } } +#endif break; } default: { @@ -2754,6 +3804,18 @@ Result EVPKeyPointer::writePublicKey( if (config.type == ncrypto::EVPKeyPointer::PKEncodingType::PKCS1) { // PKCS#1 is only valid for RSA keys. +#if NCRYPTO_USE_OPENSSL3_PROVIDER + if (id() != EVP_PKEY_RSA) return Result(false); + if (!WriteEncodedPKey(bio.get(), + get(), + OSSL_KEYMGMT_SELECT_PUBLIC_KEY, + config.format, + "pkcs1")) { + return Result(false, + mark_pop_error_on_return.peekError()); + } + return bio; +#else #if OPENSSL_VERSION_MAJOR >= 3 const RSA* rsa = EVP_PKEY_get0_RSA(get()); #else @@ -2774,7 +3836,16 @@ Result EVPKeyPointer::writePublicKey( mark_pop_error_on_return.peekError()); } return bio; +#endif + } + +#if NCRYPTO_USE_OPENSSL3_PROVIDER + if (ECKeyHasMissingOid(*this)) { + ERR_raise(ERR_LIB_EC, EC_R_MISSING_OID); + return Result(false, + mark_pop_error_on_return.peekError()); } +#endif if (config.format == ncrypto::EVPKeyPointer::PKFormatType::PEM) { // Encode SPKI as PEM. @@ -2846,11 +3917,23 @@ std::optional EVPKeyPointer::getBytesOfRS() const { int bits, id = base_id(); if (id == EVP_PKEY_DSA) { +#if NCRYPTO_USE_OPENSSL3_PROVIDER + DeleteFnPtr q; + if (!GetPKeyBnParam(get(), OSSL_PKEY_PARAM_FFC_Q, &q)) return std::nullopt; + bits = BignumPointer::GetBitCount(q.get()); +#else const DSA* dsa_key = EVP_PKEY_get0_DSA(get()); // Both r and s are computed mod q, so their width is limited by that of q. bits = BignumPointer::GetBitCount(DSA_get0_q(dsa_key)); +#endif } else if (id == EVP_PKEY_EC) { +#if NCRYPTO_USE_OPENSSL3_PROVIDER + Ec ec(get()); + if (!ec) return std::nullopt; + bits = EC_GROUP_order_bits(ec.getGroup()); +#else bits = EC_GROUP_order_bits(ECKeyPointer::GetGroup(*this)); +#endif } else { return std::nullopt; } @@ -2862,6 +3945,9 @@ EVPKeyPointer::operator Rsa() const { int type = id(); if (type != EVP_PKEY_RSA && type != EVP_PKEY_RSA_PSS) return {}; +#if NCRYPTO_USE_OPENSSL3_PROVIDER + return Rsa(get()); +#else // TODO(tniessen): Remove the "else" branch once we drop support for OpenSSL // versions older than 1.1.1e via FIPS / dynamic linking. OSSL3_CONST RSA* rsa; @@ -2872,15 +3958,20 @@ EVPKeyPointer::operator Rsa() const { } if (rsa == nullptr) return {}; return Rsa(rsa); +#endif } EVPKeyPointer::operator Dsa() const { int type = id(); if (type != EVP_PKEY_DSA) return {}; +#if NCRYPTO_USE_OPENSSL3_PROVIDER + return Dsa(get()); +#else OSSL3_CONST DSA* dsa = EVP_PKEY_get0_DSA(get()); if (dsa == nullptr) return {}; return Dsa(dsa); +#endif } bool EVPKeyPointer::validateDsaParameters() const { @@ -2891,12 +3982,25 @@ bool EVPKeyPointer::validateDsaParameters() const { #else if (FIPS_mode() && EVP_PKEY_DSA == id()) { #endif +#if NCRYPTO_USE_OPENSSL3_PROVIDER + DeleteFnPtr p; + DeleteFnPtr q; + if (!GetPKeyBnParam(pkey_.get(), OSSL_PKEY_PARAM_FFC_P, &p) || + !GetPKeyBnParam(pkey_.get(), OSSL_PKEY_PARAM_FFC_Q, &q)) { + return false; + } + const BIGNUM* p_value = p.get(); + const BIGNUM* q_value = q.get(); +#else const DSA* dsa = EVP_PKEY_get0_DSA(pkey_.get()); const BIGNUM* p; const BIGNUM* q; DSA_get0_pqg(dsa, &p, &q, nullptr); - int L = BignumPointer::GetBitCount(p); - int N = BignumPointer::GetBitCount(q); + const BIGNUM* p_value = p; + const BIGNUM* q_value = q; +#endif + int L = BignumPointer::GetBitCount(p_value); + int N = BignumPointer::GetBitCount(q_value); return (L == 1024 && N == 160) || (L == 2048 && N == 224) || (L == 2048 && N == 256) || (L == 3072 && N == 256); @@ -3207,7 +4311,7 @@ const Cipher Cipher::FromNid(int nid) { } const Cipher Cipher::FromCtx(const CipherCtxPointer& ctx) { - return Cipher(EVP_CIPHER_CTX_cipher(ctx.get())); + return Cipher(GetCipherCtxCipher(ctx.get())); } const Cipher Cipher::EMPTY = Cipher(); @@ -3356,7 +4460,7 @@ int Cipher::bytesToKey(const Digest& digest, CipherCtxPointer CipherCtxPointer::New() { auto ret = CipherCtxPointer(EVP_CIPHER_CTX_new()); if (!ret) return {}; - EVP_CIPHER_CTX_init(ret.get()); + EVP_CIPHER_CTX_reset(ret.get()); return ret; } @@ -3620,8 +4724,15 @@ bool ECPointPointer::mul(const EC_GROUP* group, const BIGNUM* priv_key) { // ============================================================================ +#if NCRYPTO_USE_LEGACY_KEY_TYPES ECKeyPointer::ECKeyPointer() : key_(nullptr) {} +ECKeyPointer::ECKeyPointer(const EVPKeyPointer& key) : key_(nullptr) { + if (key.id() != EVP_PKEY_EC) return; + const EC_KEY* ec = key; + if (ec != nullptr) key_.reset(EC_KEY_dup(ec)); +} + ECKeyPointer::ECKeyPointer(EC_KEY* key) : key_(key) {} ECKeyPointer::ECKeyPointer(ECKeyPointer&& other) noexcept @@ -3742,6 +4853,20 @@ bool ECKeyPointer::checkKey() const { return Check(key_.get()); } +DataPointer ECKeyPointer::computeSecret(const ECPointPointer& peer) const { + if (!key_ || !peer) return {}; + const EC_GROUP* group = getGroup(); + const int field_size = EC_GROUP_get_degree(group); + const size_t out_len = (field_size + 7) / 8; + auto out = DataPointer::Alloc(out_len); + if (!out) return {}; + if (ECDH_compute_key( + out.get(), out.size(), peer.get(), key_.get(), nullptr) == 0) { + return {}; + } + return out; +} + ECKeyPointer ECKeyPointer::NewByCurveName(int nid) { return ECKeyPointer(EC_KEY_new_by_curve_name(nid)); } @@ -3752,6 +4877,262 @@ ECKeyPointer ECKeyPointer::New(const EC_GROUP* group) { if (!EC_KEY_set_group(ptr.get(), group)) return {}; return ptr; } +#else +ECKeyPointer::ECKeyPointer() : group_(nullptr), pub_(nullptr), priv_(nullptr) {} + +ECKeyPointer::ECKeyPointer(const EVPKeyPointer& key) : ECKeyPointer() { + if (key.id() != EVP_PKEY_EC) return; + char group_name[80]; + size_t group_name_len = 0; + if (EVP_PKEY_get_utf8_string_param(key.get(), + OSSL_PKEY_PARAM_GROUP_NAME, + group_name, + sizeof(group_name), + &group_name_len) != 1) { + return; + } + + const int nid = Ec::GetCurveIdFromName(group_name); + if (nid == NID_undef) return; + group_.reset(EC_GROUP_new_by_curve_name(nid)); + if (!group_) return; + + GetOptionalPKeyBnParam(key.get(), OSSL_PKEY_PARAM_PRIV_KEY, &priv_); + + size_t public_key_len = 0; + if (EVP_PKEY_get_octet_string_param( + key.get(), OSSL_PKEY_PARAM_PUB_KEY, nullptr, 0, &public_key_len) == + 1) { + auto public_key = DataPointer::Alloc(public_key_len); + if (!public_key || EVP_PKEY_get_octet_string_param( + key.get(), + OSSL_PKEY_PARAM_PUB_KEY, + static_cast(public_key.get()), + public_key.size(), + &public_key_len) != 1) { + reset(); + return; + } + + auto point = ECPointPointer::New(group_.get()); + if (!point || + !point.setFromBuffer( + {static_cast(public_key.get()), public_key_len}, + group_.get())) { + reset(); + return; + } + pub_.reset(point.release()); + } +} + +ECKeyPointer::ECKeyPointer(ECKeyPointer&& other) noexcept + : group_(std::move(other.group_)), + pub_(std::move(other.pub_)), + priv_(std::move(other.priv_)) {} + +ECKeyPointer& ECKeyPointer::operator=(ECKeyPointer&& other) noexcept { + group_ = std::move(other.group_); + pub_ = std::move(other.pub_); + priv_ = std::move(other.priv_); + return *this; +} + +ECKeyPointer::~ECKeyPointer() { + reset(); +} + +void ECKeyPointer::reset() { + group_.reset(); + pub_.reset(); + priv_.reset(); +} + +ECKeyPointer ECKeyPointer::clone() const { + if (!group_) return {}; + ECKeyPointer ret; + ret.group_.reset(EC_GROUP_dup(group_.get())); + if (!ret.group_) return {}; + if (pub_ != nullptr) { + ret.pub_.reset(EC_POINT_dup(pub_.get(), ret.group_.get())); + if (!ret.pub_) return {}; + } + if (priv_ != nullptr) { + ret.priv_.reset(BN_dup(priv_.get())); + if (!ret.priv_) return {}; + } + return ret; +} + +bool ECKeyPointer::generate() { + if (!group_) return false; + const int nid = EC_GROUP_get_curve_name(group_.get()); + auto ctx = EVPKeyCtxPointer::NewFromID(EVP_PKEY_EC); + if (!ctx || !ctx.initForKeygen() || + !ctx.setEcParameters(nid, OPENSSL_EC_NAMED_CURVE)) { + return false; + } + + EVP_PKEY* raw = nullptr; + if (EVP_PKEY_keygen(ctx.get(), &raw) != 1) return false; + EVPKeyPointer pkey(raw); + + DeleteFnPtr priv; + if (!GetPKeyBnParam(pkey.get(), OSSL_PKEY_PARAM_PRIV_KEY, &priv)) { + return false; + } + + size_t public_key_len = 0; + if (EVP_PKEY_get_octet_string_param( + pkey.get(), OSSL_PKEY_PARAM_PUB_KEY, nullptr, 0, &public_key_len) != + 1) { + return false; + } + + auto public_key = DataPointer::Alloc(public_key_len); + if (!public_key || EVP_PKEY_get_octet_string_param( + pkey.get(), + OSSL_PKEY_PARAM_PUB_KEY, + static_cast(public_key.get()), + public_key.size(), + &public_key_len) != 1) { + return false; + } + + auto point = ECPointPointer::New(group_.get()); + if (!point || + !point.setFromBuffer( + {static_cast(public_key.get()), public_key_len}, + group_.get())) { + return false; + } + + priv_ = std::move(priv); + pub_.reset(point.release()); + return true; +} + +bool ECKeyPointer::setPublicKey(const ECPointPointer& pub) { + if (!group_ || !pub) return false; + pub_.reset(EC_POINT_dup(pub.get(), group_.get())); + return pub_ != nullptr; +} + +bool ECKeyPointer::setPublicKeyRaw(const BignumPointer& x, + const BignumPointer& y) { + if (!group_ || !x || !y) return false; + const size_t field_len = (EC_GROUP_get_degree(group_.get()) + 7) / 8; + const size_t uncompressed_len = 1 + 2 * field_len; + auto buf = DataPointer::Alloc(uncompressed_len); + if (!buf) return false; + unsigned char* ptr = static_cast(buf.get()); + ptr[0] = POINT_CONVERSION_UNCOMPRESSED; + x.encodePaddedInto(ptr + 1, field_len); + y.encodePaddedInto(ptr + 1 + field_len, field_len); + + auto point = ECPointPointer::New(group_.get()); + if (!point || !point.setFromBuffer({ptr, uncompressed_len}, group_.get())) { + return false; + } + pub_.reset(point.release()); + return true; +} + +bool ECKeyPointer::setPrivateKey(const BignumPointer& priv) { + if (!group_ || !priv) return false; + priv_.reset(BN_dup(priv.get())); + return priv_ != nullptr; +} + +const BIGNUM* ECKeyPointer::getPrivateKey() const { + return priv_.get(); +} + +const EC_POINT* ECKeyPointer::getPublicKey() const { + return pub_.get(); +} + +const EC_GROUP* ECKeyPointer::getGroup() const { + return group_.get(); +} + +bool ECKeyPointer::checkKey() const { + if (!group_) return false; + + if (priv_ != nullptr) { + auto order = BignumPointer::New(); + if (!order || !EC_GROUP_get_order(group_.get(), order.get(), nullptr)) { + return false; + } + if (BN_is_zero(priv_.get()) || BN_is_negative(priv_.get()) || + BN_cmp(priv_.get(), order.get()) >= 0) { + return false; + } + } + + if (pub_ != nullptr && + EC_POINT_is_on_curve(group_.get(), pub_.get(), nullptr) != 1) { + return false; + } + + if (priv_ != nullptr && pub_ != nullptr) { + auto expected = ECPointPointer::New(group_.get()); + if (!expected || !expected.mul(group_.get(), priv_.get()) || + EC_POINT_cmp(group_.get(), expected.get(), pub_.get(), nullptr) != 0) { + return false; + } + } + + auto pkey = EVPKeyPointer::New(); + if (!pkey || !pkey.set(*this)) return false; + EVPKeyCtxPointer ctx(EVP_PKEY_CTX_new(pkey.get(), nullptr)); + if (!ctx) return false; + if (pub_ != nullptr && EVP_PKEY_public_check(ctx.get()) != 1) return false; + if (priv_ != nullptr && EVP_PKEY_private_check(ctx.get()) != 1) return false; + return true; +} + +DataPointer ECKeyPointer::computeSecret(const ECPointPointer& peer) const { + if (!group_ || !priv_ || !peer) return {}; + auto our_key = EVPKeyPointer::New(); + auto their_key = EVPKeyPointer::New(); + auto their_ec = ECKeyPointer::New(group_.get()); + if (!our_key || !their_key || !our_key.set(*this) || + !their_ec.setPublicKey(peer) || !their_key.set(their_ec)) { + return {}; + } + + EVPKeyCtxPointer ctx(EVP_PKEY_CTX_new(our_key.get(), nullptr)); + size_t out_len = 0; + if (!ctx || EVP_PKEY_derive_init(ctx.get()) != 1 || + EVP_PKEY_derive_set_peer(ctx.get(), their_key.get()) != 1 || + EVP_PKEY_derive(ctx.get(), nullptr, &out_len) != 1) { + return {}; + } + + auto out = DataPointer::Alloc(out_len); + if (!out) return {}; + if (EVP_PKEY_derive( + ctx.get(), static_cast(out.get()), &out_len) != 1) { + return {}; + } + return out.resize(out_len); +} + +ECKeyPointer ECKeyPointer::NewByCurveName(int nid) { + ECKeyPointer ret; + ret.group_.reset(EC_GROUP_new_by_curve_name(nid)); + return ret; +} + +ECKeyPointer ECKeyPointer::New(const EC_GROUP* group) { + ECKeyPointer ret; + if (group != nullptr) { + ret.group_.reset(EC_GROUP_dup(group)); + } + return ret; +} +#endif // NCRYPTO_USE_LEGACY_KEY_TYPES // ============================================================================ @@ -3846,8 +5227,33 @@ bool EVPKeyCtxPointer::setDsaParameters(uint32_t bits, bool EVPKeyCtxPointer::setEcParameters(int curve, int encoding) { if (!ctx_) return false; +#if NCRYPTO_USE_OPENSSL3_PROVIDER + const char* group_name = OBJ_nid2sn(curve); + if (group_name == nullptr) return false; + + const char* encoding_name = nullptr; + switch (encoding) { + case OPENSSL_EC_EXPLICIT_CURVE: + encoding_name = OSSL_PKEY_EC_ENCODING_EXPLICIT; + break; + case OPENSSL_EC_NAMED_CURVE: + encoding_name = OSSL_PKEY_EC_ENCODING_GROUP; + break; + default: + return false; + } + OSSL_PARAM params[] = { + OSSL_PARAM_construct_utf8_string( + OSSL_PKEY_PARAM_GROUP_NAME, const_cast(group_name), 0), + OSSL_PARAM_construct_utf8_string( + OSSL_PKEY_PARAM_EC_ENCODING, const_cast(encoding_name), 0), + OSSL_PARAM_END, + }; + return EVP_PKEY_CTX_set_params(ctx_.get(), params) == 1; +#else return EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx_.get(), curve) == 1 && EVP_PKEY_CTX_set_ec_param_enc(ctx_.get(), encoding) == 1; +#endif } bool EVPKeyCtxPointer::setRsaOaepMd(const Digest& md) { @@ -3886,12 +5292,16 @@ bool EVPKeyCtxPointer::setRsaKeygenBits(int bits) { bool EVPKeyCtxPointer::setRsaKeygenPubExp(BignumPointer&& e) { if (!ctx_) return false; +#if NCRYPTO_USE_OPENSSL3_PROVIDER + return EVP_PKEY_CTX_set1_rsa_keygen_pubexp(ctx_.get(), e.get()) == 1; +#else if (EVP_PKEY_CTX_set_rsa_keygen_pubexp(ctx_.get(), e.get()) == 1) { // The ctx_ takes ownership of e on success. e.release(); return true; } return false; +#endif } bool EVPKeyCtxPointer::setRsaPssKeygenMd(const Digest& md) { @@ -3945,7 +5355,7 @@ bool EVPKeyCtxPointer::setRsaOaepLabel(DataPointer&& data) { bool EVPKeyCtxPointer::setSignatureMd(const EVPMDCtxPointer& md) { if (!ctx_) return false; - return EVP_PKEY_CTX_set_signature_md(ctx_.get(), EVP_MD_CTX_md(md.get())) == + return EVP_PKEY_CTX_set_signature_md(ctx_.get(), GetDigestCtxMd(md.get())) == 1; } @@ -4135,26 +5545,213 @@ DataPointer CipherImpl(const EVPKeyPointer& key, } } // namespace -Rsa::Rsa() : rsa_(nullptr) {} +#if NCRYPTO_USE_OPENSSL3_PROVIDER +namespace { +int DigestAlgorithmIdentifierToNid(const unsigned char* data, size_t size) { + size_t sequence_header; + size_t sequence_len; + size_t sequence_total; + if (!ReadASN1Element( + data, size, 0x30, &sequence_header, &sequence_len, &sequence_total)) { + return NID_undef; + } + + size_t oid_header; + size_t oid_len; + size_t oid_total; + const unsigned char* oid = data + sequence_header; + if (!ReadASN1Element( + oid, sequence_len, 0x06, &oid_header, &oid_len, &oid_total)) { + return NID_undef; + } + + const unsigned char* oid_data = oid; + DeleteFnPtr obj( + d2i_ASN1_OBJECT(nullptr, &oid_data, oid_total)); + if (!obj) return NID_undef; + return OBJ_obj2nid(obj.get()); +} + +bool ReadRsaPssParams(const EVP_PKEY* pkey, Rsa::PssParams* params) { + const int der_len = i2d_PUBKEY(pkey, nullptr); + if (der_len <= 0) return false; + + auto der = DataPointer::Alloc(der_len); + if (!der) return false; + + auto serialized = static_cast(der.get()); + if (i2d_PUBKEY(pkey, &serialized) != der_len) return false; + + size_t outer_header; + size_t outer_len; + size_t outer_total; + const auto* data = static_cast(der.get()); + if (!ReadASN1Element( + data, der.size(), 0x30, &outer_header, &outer_len, &outer_total)) { + return false; + } + + size_t alg_header; + size_t alg_len; + size_t alg_total; + const unsigned char* alg = data + outer_header; + if (!ReadASN1Element( + alg, outer_len, 0x30, &alg_header, &alg_len, &alg_total)) { + return false; + } + + size_t oid_header; + size_t oid_len; + size_t oid_total; + const unsigned char* oid = alg + alg_header; + if (!ReadASN1Element(oid, alg_len, 0x06, &oid_header, &oid_len, &oid_total) || + oid_total == alg_len) { + return false; + } + + size_t pss_header; + size_t pss_len; + size_t pss_total; + const unsigned char* pss = oid + oid_total; + if (!ReadASN1Element( + pss, alg_len - oid_total, 0x30, &pss_header, &pss_len, &pss_total)) { + return false; + } + + const unsigned char* cursor = pss + pss_header; + size_t remaining = pss_len; + while (remaining > 0) { + const unsigned char tag = cursor[0]; + size_t item_header; + size_t item_len; + size_t item_total; + if (!ReadASN1Element( + cursor, remaining, tag, &item_header, &item_len, &item_total)) { + return false; + } + + const unsigned char* item = cursor + item_header; + switch (tag) { + case 0xa0: { + const int nid = DigestAlgorithmIdentifierToNid(item, item_len); + if (nid != NID_undef) params->digest = OBJ_nid2ln(nid); + break; + } + case 0xa1: { + size_t mgf_header; + size_t mgf_len; + size_t mgf_total; + if (!ReadASN1Element( + item, item_len, 0x30, &mgf_header, &mgf_len, &mgf_total)) { + return false; + } + const unsigned char* mgf = item + mgf_header; + size_t mgf_oid_header; + size_t mgf_oid_len; + size_t mgf_oid_total; + if (!ReadASN1Element(mgf, + mgf_len, + 0x06, + &mgf_oid_header, + &mgf_oid_len, + &mgf_oid_total) || + mgf_oid_total == mgf_len) { + return false; + } + const int nid = DigestAlgorithmIdentifierToNid(mgf + mgf_oid_total, + mgf_len - mgf_oid_total); + if (nid != NID_undef) params->mgf1_digest = OBJ_nid2ln(nid); + break; + } + case 0xa2: { + size_t int_header; + size_t int_len; + size_t int_total; + if (!ReadASN1Element( + item, item_len, 0x02, &int_header, &int_len, &int_total)) { + return false; + } + // TODO(panva): In a semver-major, reject malformed RSA-PSS parameters + // at key import instead of omitting asymmetricKeyDetails fields. + if (int_len == 0 || int_len > sizeof(uint64_t) || + (item[int_header] & 0x80) != 0) { + return false; + } + uint64_t salt_length = 0; + for (size_t n = 0; n < int_len; n++) { + salt_length = (salt_length << 8) | item[int_header + n]; + } + params->salt_length = static_cast(salt_length); + break; + } + } + + cursor += item_total; + remaining -= item_total; + } + + return true; +} +} // namespace + +Rsa::Rsa() : rsa_(false) {} + +Rsa::Rsa(const EVP_PKEY* pkey) : Rsa() { + const int type = EVPKeyPointer::id(pkey); + if (type != EVP_PKEY_RSA && type != EVP_PKEY_RSA_PSS) return; + if (!GetPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_N, &n_) || + !GetPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_E, &e_)) { + return; + } + GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_D, &d_); + GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_FACTOR1, &p_); + GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_FACTOR2, &q_); + GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_EXPONENT1, &dp_); + GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_EXPONENT2, &dq_); + GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_COEFFICIENT1, &qi_); + + if (type == EVP_PKEY_RSA_PSS) { + MarkPopErrorOnReturn pop_errors; + PssParams params; + if (ReadRsaPssParams(pkey, ¶ms)) pss_params_ = params; + } + rsa_ = true; +} +#else +Rsa::Rsa() : rsa_(nullptr) {} Rsa::Rsa(OSSL3_CONST RSA* ptr) : rsa_(ptr) {} +#endif const Rsa::PublicKey Rsa::getPublicKey() const { +#if NCRYPTO_USE_OPENSSL3_PROVIDER + if (!rsa_) return {}; + return PublicKey{n_.get(), e_.get(), d_.get()}; +#else if (rsa_ == nullptr) return {}; PublicKey key; RSA_get0_key(rsa_, &key.n, &key.e, &key.d); return key; +#endif } const Rsa::PrivateKey Rsa::getPrivateKey() const { +#if NCRYPTO_USE_OPENSSL3_PROVIDER + if (!rsa_) return {}; + return PrivateKey{p_.get(), q_.get(), dp_.get(), dq_.get(), qi_.get()}; +#else if (rsa_ == nullptr) return {}; PrivateKey key; RSA_get0_factors(rsa_, &key.p, &key.q); RSA_get0_crt_params(rsa_, &key.dp, &key.dq, &key.qi); return key; +#endif } const std::optional Rsa::getPssParams() const { +#if NCRYPTO_USE_OPENSSL3_PROVIDER + return pss_params_; +#else if (rsa_ == nullptr) return std::nullopt; const RSA_PSS_PARAMS* params = RSA_get0_pss_params(rsa_); if (params == nullptr) return std::nullopt; @@ -4187,16 +5784,36 @@ const std::optional Rsa::getPssParams() const { } } return ret; +#endif +} + +BIOPointer Rsa::derPublicKey() const { + auto bio = BIOPointer::NewMem(); + if (!bio) return {}; +#if NCRYPTO_USE_OPENSSL3_PROVIDER + auto pkey = EVPKeyPointer::NewRSA(*this); + if (!pkey || i2d_PUBKEY_bio(bio.get(), pkey.get()) != 1) return {}; +#else + if (rsa_ == nullptr || i2d_RSA_PUBKEY_bio(bio.get(), rsa_) != 1) return {}; +#endif + return bio; } bool Rsa::setPublicKey(BignumPointer&& n, BignumPointer&& e) { if (!n || !e) return false; +#if NCRYPTO_USE_OPENSSL3_PROVIDER + n_.reset(n.release()); + e_.reset(e.release()); + rsa_ = true; + return true; +#else if (RSA_set0_key(const_cast(rsa_), n.get(), e.get(), nullptr) == 1) { n.release(); e.release(); return true; } return false; +#endif } bool Rsa::setPrivateKey(BignumPointer&& d, @@ -4205,6 +5822,17 @@ bool Rsa::setPrivateKey(BignumPointer&& d, BignumPointer&& dp, BignumPointer&& dq, BignumPointer&& qi) { +#if NCRYPTO_USE_OPENSSL3_PROVIDER + if (!d || !q || !p || !dp || !dq || !qi) return false; + d_.reset(d.release()); + q_.reset(q.release()); + p_.reset(p.release()); + dp_.reset(dp.release()); + dq_.reset(dq.release()); + qi_.reset(qi.release()); + rsa_ = n_ != nullptr && e_ != nullptr; + return rsa_; +#else if (!RSA_set0_key(const_cast(rsa_), nullptr, nullptr, d.get())) { return false; } @@ -4224,6 +5852,7 @@ bool Rsa::setPrivateKey(BignumPointer&& d, dq.release(); qi.release(); return true; +#endif } DataPointer Rsa::encrypt(const EVPKeyPointer& key, @@ -4346,12 +5975,99 @@ void Cipher::ForEach(Cipher::CipherNameCallback callback) { // ============================================================================ -Ec::Ec() : ec_(nullptr) {} +#if NCRYPTO_USE_OPENSSL3_PROVIDER +Ec::Ec() : ec_(nullptr), pub_(nullptr) {} + +Ec::Ec(const EVP_PKEY* pkey) : Ec() { + if (EVPKeyPointer::id(pkey) != EVP_PKEY_EC) return; + char group_name[80]; + size_t group_name_len = 0; + if (EVP_PKEY_get_utf8_string_param(pkey, + OSSL_PKEY_PARAM_GROUP_NAME, + group_name, + sizeof(group_name), + &group_name_len) != 1) { + return; + } + + const int nid = GetCurveIdFromName(group_name); + if (nid == NID_undef) return; + ec_.reset(EC_GROUP_new_by_curve_name(nid)); + if (!ec_) return; + + size_t public_key_len = 0; + if (EVP_PKEY_get_octet_string_param( + pkey, OSSL_PKEY_PARAM_PUB_KEY, nullptr, 0, &public_key_len) != 1) { + return; + } + + auto public_key = DataPointer::Alloc(public_key_len); + if (!public_key || + EVP_PKEY_get_octet_string_param( + pkey, + OSSL_PKEY_PARAM_PUB_KEY, + static_cast(public_key.get()), + public_key.size(), + &public_key_len) != 1 || + public_key_len == 0) { + ec_.reset(); + return; + } + + const auto* public_key_data = + static_cast(public_key.get()); + switch (public_key_data[0]) { + case POINT_CONVERSION_COMPRESSED: + case POINT_CONVERSION_COMPRESSED + 1: + form_ = POINT_CONVERSION_COMPRESSED; + break; + case POINT_CONVERSION_UNCOMPRESSED: + form_ = POINT_CONVERSION_UNCOMPRESSED; + break; + case POINT_CONVERSION_HYBRID: + case POINT_CONVERSION_HYBRID + 1: + form_ = POINT_CONVERSION_HYBRID; + break; + default: + ec_.reset(); + return; + } + auto point = ECPointPointer::New(ec_.get()); + if (!point || + !point.setFromBuffer({public_key_data, public_key_len}, ec_.get())) { + ec_.reset(); + return; + } + pub_.reset(point.release()); +} +#else +Ec::Ec() : ec_(nullptr) {} Ec::Ec(OSSL3_CONST EC_KEY* key) : ec_(key) {} +#endif const EC_GROUP* Ec::getGroup() const { +#if NCRYPTO_USE_OPENSSL3_PROVIDER + return ec_.get(); +#else return ECKeyPointer::GetGroup(ec_); +#endif +} + +const EC_POINT* Ec::getPublicKey() const { +#if NCRYPTO_USE_OPENSSL3_PROVIDER + return pub_.get(); +#else + return ECKeyPointer::GetPublicKey(ec_); +#endif +} + +point_conversion_form_t Ec::getPointConversionForm() const { +#if NCRYPTO_USE_OPENSSL3_PROVIDER + return form_; +#else + return EC_KEY_get_conv_form(ec_); +#endif } int Ec::getCurve() const { @@ -4455,7 +6171,7 @@ size_t EVPMDCtxPointer::getDigestSize() const { const EVP_MD* EVPMDCtxPointer::getDigest() const { if (!ctx_) return nullptr; - return EVP_MD_CTX_md(ctx_.get()); + return GetDigestCtxMd(ctx_.get()); } bool EVPMDCtxPointer::hasXofFlag() const { @@ -4997,31 +6713,61 @@ std::pair X509Name::Iterator::operator*() const { // ============================================================================ -Dsa::Dsa() : dsa_(nullptr) {} +#if NCRYPTO_USE_OPENSSL3_PROVIDER +Dsa::Dsa() : dsa_(false) {} +Dsa::Dsa(const EVP_PKEY* pkey) : Dsa() { + if (EVPKeyPointer::id(pkey) != EVP_PKEY_DSA) return; + if (!GetPKeyBnParam(pkey, OSSL_PKEY_PARAM_FFC_P, &p_) || + !GetPKeyBnParam(pkey, OSSL_PKEY_PARAM_FFC_Q, &q_)) { + return; + } + dsa_ = true; +} +#else +Dsa::Dsa() : dsa_(nullptr) {} Dsa::Dsa(OSSL3_CONST DSA* dsa) : dsa_(dsa) {} +#endif const BIGNUM* Dsa::getP() const { +#if NCRYPTO_USE_OPENSSL3_PROVIDER + if (!dsa_) return nullptr; + return p_.get(); +#else if (dsa_ == nullptr) return nullptr; const BIGNUM* p; DSA_get0_pqg(dsa_, &p, nullptr, nullptr); return p; +#endif } const BIGNUM* Dsa::getQ() const { +#if NCRYPTO_USE_OPENSSL3_PROVIDER + if (!dsa_) return nullptr; + return q_.get(); +#else if (dsa_ == nullptr) return nullptr; const BIGNUM* q; DSA_get0_pqg(dsa_, nullptr, &q, nullptr); return q; +#endif } size_t Dsa::getModulusLength() const { +#if NCRYPTO_USE_OPENSSL3_PROVIDER + if (!dsa_) return 0; +#else if (dsa_ == nullptr) return 0; +#endif return BignumPointer::GetBitCount(getP()); } size_t Dsa::getDivisorLength() const { +#if NCRYPTO_USE_OPENSSL3_PROVIDER + if (!dsa_) return 0; +#else if (dsa_ == nullptr) return 0; +#endif return BignumPointer::GetBitCount(getQ()); } diff --git a/deps/ncrypto/ncrypto.gyp b/deps/ncrypto/ncrypto.gyp index 1747f3ea0149..804a664fa0a2 100644 --- a/deps/ncrypto/ncrypto.gyp +++ b/deps/ncrypto/ncrypto.gyp @@ -2,10 +2,25 @@ 'variables': { 'ncrypto_bssl_libdecrepit_missing%': 1, 'ncrypto_sources': [ - 'engine.cc', 'ncrypto.cc', 'ncrypto.h', ], + 'ncrypto_engine_sources': [ + 'engine.cc', + 'ncrypto.h', + ], + 'ncrypto_strict_defines': [ + 'OPENSSL_API_COMPAT=30000', + 'OPENSSL_NO_DEPRECATED', + ], + 'ncrypto_legacy_openssl_defines': [ + 'OPENSSL_API_COMPAT=0x10100000L', + ], + 'ncrypto_engine_defines': [ + 'OPENSSL_API_COMPAT=30000', + 'OPENSSL_SUPPRESS_DEPRECATED', + 'NCRYPTO_ENGINE_COMPAT=1', + ], }, 'targets': [ { @@ -20,9 +35,25 @@ 'defines': [ 'NCRYPTO_BSSL_LIBDECREPIT_MISSING=<(ncrypto_bssl_libdecrepit_missing)', ], + 'conditions': [ + ['openssl_is_boringssl=="false" and openssl_version >= 0x3000000f', { + 'defines!': [ '<@(ncrypto_legacy_openssl_defines)' ], + 'defines': [ '<@(ncrypto_strict_defines)' ], + }], + ], }, 'sources': [ '<@(ncrypto_sources)' ], 'conditions': [ + ['openssl_is_boringssl=="false" and openssl_version >= 0x3000000f', { + 'defines!': [ '<@(ncrypto_legacy_openssl_defines)' ], + 'defines': [ '<@(ncrypto_strict_defines)' ], + 'dependencies': [ + 'ncrypto_engine', + ], + }], + ['openssl_is_boringssl=="false" and openssl_version < 0x3000000f', { + 'sources': [ '<@(ncrypto_engine_sources)' ], + }], ['node_shared_openssl=="false"', { 'dependencies': [ '../openssl/openssl.gyp:openssl' @@ -30,5 +61,28 @@ }], ] }, - ] + ], + 'conditions': [ + ['openssl_is_boringssl=="false" and openssl_version >= 0x3000000f', { + 'targets': [ + { + 'target_name': 'ncrypto_engine', + 'type': 'static_library', + 'include_dirs': ['.'], + 'defines': [ + 'NCRYPTO_BSSL_LIBDECREPIT_MISSING=<(ncrypto_bssl_libdecrepit_missing)', + '<@(ncrypto_engine_defines)', + ], + 'sources': [ '<@(ncrypto_engine_sources)' ], + 'conditions': [ + ['node_shared_openssl=="false"', { + 'dependencies': [ + '../openssl/openssl.gyp:openssl' + ] + }], + ] + }, + ], + }], + ], } diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h index b79962d5a85c..92b36a15dd62 100644 --- a/deps/ncrypto/ncrypto.h +++ b/deps/ncrypto/ncrypto.h @@ -19,9 +19,10 @@ #include #include #include -#ifndef OPENSSL_NO_ENGINE +#if defined(NCRYPTO_ENGINE_COMPAT) && NCRYPTO_ENGINE_COMPAT && \ + !defined(OPENSSL_NO_ENGINE) #include -#endif // !OPENSSL_NO_ENGINE +#endif // NCRYPTO_ENGINE_COMPAT && !OPENSSL_NO_ENGINE #ifndef OPENSSL_VERSION_PREREQ #define OPENSSL_VERSION_PREREQ(maj, min) \ @@ -40,6 +41,40 @@ #define NCRYPTO_USE_BORINGSSL_EVP_DO_ALL_FALLBACK 0 #endif +// Backend split: +// - OpenSSL >= 3 uses provider APIs and hides deprecated low-level objects. +// - BoringSSL has its own API-compatible branch. +// - OpenSSL < 3 remains the legacy fallback branch. +#if !defined(OPENSSL_IS_BORINGSSL) && OPENSSL_VERSION_PREREQ(3, 0) +#define NCRYPTO_USE_OPENSSL3_PROVIDER 1 +#else +#define NCRYPTO_USE_OPENSSL3_PROVIDER 0 +#endif + +#ifdef OPENSSL_IS_BORINGSSL +#define NCRYPTO_USE_BORINGSSL 1 +#else +#define NCRYPTO_USE_BORINGSSL 0 +#endif + +#if !NCRYPTO_USE_OPENSSL3_PROVIDER && !NCRYPTO_USE_BORINGSSL +#define NCRYPTO_USE_LEGACY_OPENSSL 1 +#else +#define NCRYPTO_USE_LEGACY_OPENSSL 0 +#endif + +#if NCRYPTO_USE_BORINGSSL || NCRYPTO_USE_LEGACY_OPENSSL +#define NCRYPTO_USE_LEGACY_KEY_TYPES 1 +#else +#define NCRYPTO_USE_LEGACY_KEY_TYPES 0 +#endif + +#if NCRYPTO_USE_OPENSSL3_PROVIDER +#include +#include +#include +#endif + // The FIPS-related functions are only available // when the OpenSSL itself was compiled with FIPS support. #if defined(OPENSSL_FIPS) && !OPENSSL_VERSION_PREREQ(3, 0) @@ -112,7 +147,6 @@ #define EVP_PKEY_ML_KEM_512 NID_ML_KEM_512 #define EVP_PKEY_ML_KEM_768 NID_ML_KEM_768 #define EVP_PKEY_ML_KEM_1024 NID_ML_KEM_1024 -#include #elif OPENSSL_WITH_BORINGSSL_PQC #define EVP_PKEY_ML_KEM_768 NID_ML_KEM_768 #define EVP_PKEY_ML_KEM_1024 NID_ML_KEM_1024 @@ -303,7 +337,9 @@ template using DeleteFnPtr = typename FunctionDeleter::Pointer; using PKCS8Pointer = DeleteFnPtr; +#if NCRYPTO_USE_LEGACY_KEY_TYPES using RSAPointer = DeleteFnPtr; +#endif using SSLSessionPointer = DeleteFnPtr; class BIOPointer; @@ -502,11 +538,23 @@ class Cipher final { class Dsa final { public: Dsa(); +#if NCRYPTO_USE_OPENSSL3_PROVIDER + explicit Dsa(const EVP_PKEY* pkey); +#else Dsa(OSSL3_CONST DSA* dsa); +#endif NCRYPTO_DISALLOW_COPY_AND_MOVE(Dsa) +#if NCRYPTO_USE_OPENSSL3_PROVIDER + inline operator bool() const { + return dsa_; + } +#else inline operator bool() const { return dsa_ != nullptr; } +#endif +#if NCRYPTO_USE_LEGACY_KEY_TYPES inline operator OSSL3_CONST DSA*() const { return dsa_; } +#endif const BIGNUM* getP() const; const BIGNUM* getQ() const; @@ -514,7 +562,13 @@ class Dsa final { size_t getDivisorLength() const; private: +#if NCRYPTO_USE_OPENSSL3_PROVIDER + bool dsa_ = false; + DeleteFnPtr p_; + DeleteFnPtr q_; +#else OSSL3_CONST DSA* dsa_; +#endif }; // ============================================================================ @@ -523,11 +577,23 @@ class Dsa final { class Rsa final { public: Rsa(); +#if NCRYPTO_USE_OPENSSL3_PROVIDER + explicit Rsa(const EVP_PKEY* pkey); +#else Rsa(OSSL3_CONST RSA* rsa); +#endif NCRYPTO_DISALLOW_COPY_AND_MOVE(Rsa) +#if NCRYPTO_USE_OPENSSL3_PROVIDER + inline operator bool() const { + return rsa_; + } +#else inline operator bool() const { return rsa_ != nullptr; } +#endif +#if NCRYPTO_USE_LEGACY_KEY_TYPES inline operator OSSL3_CONST RSA*() const { return rsa_; } +#endif struct PublicKey { const BIGNUM* n; @@ -561,6 +627,8 @@ class Rsa final { using CipherParams = Cipher::CipherParams; + BIOPointer derPublicKey() const; + static DataPointer encrypt(const EVPKeyPointer& key, const CipherParams& params, const Buffer in); @@ -569,20 +637,41 @@ class Rsa final { const Buffer in); private: +#if NCRYPTO_USE_OPENSSL3_PROVIDER + bool rsa_ = false; + DeleteFnPtr n_; + DeleteFnPtr e_; + DeleteFnPtr d_; + DeleteFnPtr p_; + DeleteFnPtr q_; + DeleteFnPtr dp_; + DeleteFnPtr dq_; + DeleteFnPtr qi_; + std::optional pss_params_; +#else OSSL3_CONST RSA* rsa_; +#endif }; class Ec final { public: Ec(); +#if NCRYPTO_USE_OPENSSL3_PROVIDER + explicit Ec(const EVP_PKEY* pkey); +#else Ec(OSSL3_CONST EC_KEY* key); +#endif NCRYPTO_DISALLOW_COPY_AND_MOVE(Ec) const EC_GROUP* getGroup() const; + const EC_POINT* getPublicKey() const; + point_conversion_form_t getPointConversionForm() const; int getCurve() const; inline operator bool() const { return ec_ != nullptr; } +#if NCRYPTO_USE_LEGACY_KEY_TYPES inline operator OSSL3_CONST EC_KEY*() const { return ec_; } +#endif static int GetCurveIdFromName(const char* name); @@ -590,7 +679,13 @@ class Ec final { static bool GetCurves(GetCurveCallback callback); private: +#if NCRYPTO_USE_OPENSSL3_PROVIDER + DeleteFnPtr ec_; + DeleteFnPtr pub_; + point_conversion_form_t form_ = POINT_CONVERSION_UNCOMPRESSED; +#else OSSL3_CONST EC_KEY* ec_ = nullptr; +#endif }; // A managed pointer to a buffer of data. When destroyed the underlying @@ -926,7 +1021,11 @@ class EVPKeyPointer final { const Buffer& data); #endif static EVPKeyPointer NewDH(DHPointer&& dh); +#if NCRYPTO_USE_OPENSSL3_PROVIDER + static EVPKeyPointer NewRSA(const Rsa& rsa); +#else static EVPKeyPointer NewRSA(RSAPointer&& rsa); +#endif enum class PKEncodingType { // RSAPublicKey / RSAPrivateKey according to PKCS#1. @@ -998,7 +1097,9 @@ class EVPKeyPointer final { bool assign(const ECKeyPointer& eckey); bool set(const ECKeyPointer& eckey); +#if NCRYPTO_USE_LEGACY_KEY_TYPES operator const EC_KEY*() const; +#endif inline bool operator==(std::nullptr_t) const noexcept { return pkey_ == nullptr; @@ -1070,29 +1171,53 @@ class DHPointer final { static DHPointer New(size_t bits, unsigned int generator); DHPointer() = default; +#if NCRYPTO_USE_OPENSSL3_PROVIDER + explicit DHPointer(EVPKeyPointer&& key, const char* group_name = nullptr); + DHPointer(BignumPointer&& p, BignumPointer&& g, const char* group_name); +#else explicit DHPointer(DH* dh); +#endif DHPointer(DHPointer&& other) noexcept; DHPointer& operator=(DHPointer&& other) noexcept; NCRYPTO_DISALLOW_COPY(DHPointer) ~DHPointer(); +#if NCRYPTO_USE_OPENSSL3_PROVIDER + inline bool operator==(std::nullptr_t) noexcept { + return !operator bool(); + } + inline operator bool() const { + return dh_ != nullptr || (p_ && g_); + } +#else inline bool operator==(std::nullptr_t) noexcept { return dh_ == nullptr; } inline operator bool() const { return dh_ != nullptr; } +#endif +#if NCRYPTO_USE_LEGACY_KEY_TYPES inline DH* get() const { return dh_.get(); } void reset(DH* dh = nullptr); DH* release(); +#else + inline EVP_PKEY* get() const { + return dh_.get(); + } + void reset(EVP_PKEY* dh = nullptr); + EVP_PKEY* release(); +#endif enum class CheckResult { NONE, - P_NOT_PRIME = DH_CHECK_P_NOT_PRIME, - P_NOT_SAFE_PRIME = DH_CHECK_P_NOT_SAFE_PRIME, - UNABLE_TO_CHECK_GENERATOR = DH_UNABLE_TO_CHECK_GENERATOR, - NOT_SUITABLE_GENERATOR = DH_NOT_SUITABLE_GENERATOR, - Q_NOT_PRIME = DH_CHECK_Q_NOT_PRIME, + P_NOT_PRIME = 0x01, + P_NOT_SAFE_PRIME = 0x02, + UNABLE_TO_CHECK_GENERATOR = 0x04, + NOT_SUITABLE_GENERATOR = 0x08, + Q_NOT_PRIME = 0x10, #ifndef OPENSSL_IS_BORINGSSL // Boringssl does not define the DH_CHECK_INVALID_[Q or J]_VALUE - INVALID_Q = DH_CHECK_INVALID_Q_VALUE, - INVALID_J = DH_CHECK_INVALID_J_VALUE, + INVALID_Q = 0x20, + INVALID_J = 0x40, + MODULUS_TOO_SMALL = 0x80, + MODULUS_TOO_LARGE = 0x100, #endif CHECK_FAILED = 512, }; @@ -1114,10 +1239,12 @@ class DHPointer final { CheckPublicKeyResult checkPublicKey(const BignumPointer& pub_key); DataPointer getPrime() const; + size_t getPrimeBits() const; DataPointer getGenerator() const; DataPointer getPublicKey() const; DataPointer getPrivateKey() const; - DataPointer generateKeys() const; + bool hasPrivateKey() const; + DataPointer generateKeys(); DataPointer computeSecret(const BignumPointer& peer) const; bool setPublicKey(BignumPointer&& key); @@ -1129,7 +1256,16 @@ class DHPointer final { const EVPKeyPointer& theirKey); private: +#if NCRYPTO_USE_OPENSSL3_PROVIDER + DeleteFnPtr dh_; + BignumPointer p_; + BignumPointer g_; + BignumPointer pub_key_; + BignumPointer pvt_key_; + const char* group_name_ = nullptr; +#else DeleteFnPtr dh_; +#endif }; struct StackOfX509Deleter { @@ -1438,20 +1574,38 @@ class ECPointPointer final { }; class ECKeyPointer final { + friend class EVPKeyPointer; + public: ECKeyPointer(); + explicit ECKeyPointer(const EVPKeyPointer& key); +#if NCRYPTO_USE_LEGACY_KEY_TYPES explicit ECKeyPointer(EC_KEY* key); +#endif ECKeyPointer(ECKeyPointer&& other) noexcept; ECKeyPointer& operator=(ECKeyPointer&& other) noexcept; NCRYPTO_DISALLOW_COPY(ECKeyPointer) ~ECKeyPointer(); +#if NCRYPTO_USE_OPENSSL3_PROVIDER + inline bool operator==(std::nullptr_t) noexcept { + return group_ == nullptr; + } + inline operator bool() const { + return group_ != nullptr; + } +#else inline bool operator==(std::nullptr_t) noexcept { return key_ == nullptr; } inline operator bool() const { return key_ != nullptr; } +#endif +#if NCRYPTO_USE_LEGACY_KEY_TYPES inline EC_KEY* get() const { return key_.get(); } inline operator EC_KEY*() const { return key_.get(); } void reset(EC_KEY* key = nullptr); EC_KEY* release(); +#else + void reset(); +#endif ECKeyPointer clone() const; bool setPrivateKey(const BignumPointer& priv); @@ -1459,6 +1613,7 @@ class ECKeyPointer final { bool setPublicKeyRaw(const BignumPointer& x, const BignumPointer& y); bool generate(); bool checkKey() const; + DataPointer computeSecret(const ECPointPointer& peer) const; const EC_GROUP* getGroup() const; const BIGNUM* getPrivateKey() const; @@ -1467,14 +1622,22 @@ class ECKeyPointer final { static ECKeyPointer New(const EC_GROUP* group); static ECKeyPointer NewByCurveName(int nid); +#if NCRYPTO_USE_LEGACY_KEY_TYPES static const EC_POINT* GetPublicKey(const EC_KEY* key); static const BIGNUM* GetPrivateKey(const EC_KEY* key); static const EC_GROUP* GetGroup(const EC_KEY* key); static int GetGroupName(const EC_KEY* key); static bool Check(const EC_KEY* key); +#endif private: +#if NCRYPTO_USE_OPENSSL3_PROVIDER + DeleteFnPtr group_; + DeleteFnPtr pub_; + DeleteFnPtr priv_; +#else DeleteFnPtr key_; +#endif }; class EVPMDCtxPointer final { @@ -1641,24 +1804,23 @@ class EnginePointer final { public: EnginePointer() = default; - explicit EnginePointer(ENGINE* engine_, bool finish_on_exit = false); + explicit EnginePointer(void* engine_, bool finish_on_exit = false); EnginePointer(EnginePointer&& other) noexcept; EnginePointer& operator=(EnginePointer&& other) noexcept; NCRYPTO_DISALLOW_COPY(EnginePointer) ~EnginePointer(); inline operator bool() const { return engine != nullptr; } - inline ENGINE* get() { return engine; } inline void setFinishOnExit() { finish_on_exit = true; } - void reset(ENGINE* engine_ = nullptr, bool finish_on_exit_ = false); + void reset(void* engine_ = nullptr, bool finish_on_exit_ = false); bool setAsDefault(uint32_t flags, CryptoErrorList* errors = nullptr); bool init(bool finish_on_exit = false); EVPKeyPointer loadPrivateKey(const char* key_name); + bool setClientCertEngine(SSL_CTX* ctx); - // Release ownership of the ENGINE* pointer. - ENGINE* release(); + void* release(); // Retrieve an OpenSSL Engine instance by name. If the name does not // identify a valid named engine, the returned EnginePointer will be @@ -1670,7 +1832,7 @@ class EnginePointer final { static void initEnginesOnce(); private: - ENGINE* engine = nullptr; + void* engine = nullptr; bool finish_on_exit = false; }; #endif // !OPENSSL_NO_ENGINE diff --git a/deps/ncrypto/unofficial.gni b/deps/ncrypto/unofficial.gni index 7cb27d22b9b8..dad4fbbf16f0 100644 --- a/deps/ncrypto/unofficial.gni +++ b/deps/ncrypto/unofficial.gni @@ -26,7 +26,11 @@ template("ncrypto_gn_build") { source_set(target_name) { forward_variables_from(invoker, "*") public_configs = [ ":ncrypto_config" ] - sources = gypi_values.ncrypto_sources + defines = [ + "NCRYPTO_ENGINE_COMPAT=1", + "OPENSSL_SUPPRESS_DEPRECATED", + ] + sources = gypi_values.ncrypto_sources + gypi_values.ncrypto_engine_sources deps = [ "$node_openssl_path" ] } } diff --git a/src/crypto/crypto_common.cc b/src/crypto/crypto_common.cc index 0db5d0eaac8f..fde12953860c 100644 --- a/src/crypto/crypto_common.cc +++ b/src/crypto/crypto_common.cc @@ -231,11 +231,15 @@ MaybeLocal GetEphemeralKey(Environment* env, const SSLPointer& ssl) { case EVP_PKEY_X448: { const char* curve_name; if (kid == EVP_PKEY_EC) { - int nid = ECKeyPointer::GetGroupName(key); + ECKeyPointer ec(key); + if (!ec) break; + int nid = EC_GROUP_get_curve_name(ec.getGroup()); + if (nid == NID_undef) break; curve_name = OBJ_nid2sn(nid); } else { curve_name = OBJ_nid2sn(kid); } + if (curve_name == nullptr) break; values[0] = env->ecdh_string(); values[1] = OneByteString(env->isolate(), curve_name); values[2] = Integer::New(env->isolate(), key.bits()); @@ -291,7 +295,7 @@ MaybeLocal GetPeerCert( // NOTE: This is because of the odd OpenSSL behavior. On client `cert_chain` // contains the `peer_certificate`, but on server it doesn't. - X509Pointer cert(is_server ? SSL_get_peer_certificate(ssl.get()) : nullptr); + X509Pointer cert(is_server ? X509Pointer::PeerFrom(ssl) : X509Pointer()); STACK_OF(X509)* ssl_certs = SSL_get_peer_cert_chain(ssl.get()); if (!cert && (ssl_certs == nullptr || sk_X509_num(ssl_certs) == 0)) return Undefined(env->isolate()); diff --git a/src/crypto/crypto_context.cc b/src/crypto/crypto_context.cc index 02a748ef280c..05fc2a00b362 100644 --- a/src/crypto/crypto_context.cc +++ b/src/crypto/crypto_context.cc @@ -18,9 +18,6 @@ #include #include #include -#ifndef OPENSSL_NO_ENGINE -#include -#endif // !OPENSSL_NO_ENGINE #ifdef __APPLE__ #include #endif @@ -34,7 +31,6 @@ namespace node { -using ncrypto::BignumPointer; using ncrypto::BIOPointer; using ncrypto::Cipher; using ncrypto::ClearErrorOnReturn; @@ -549,7 +545,7 @@ static bool IsCertificateExpired(X509* cert) { // -1 if the time is in the past (expired) // 0 if there was an error // 1 if the time is in the future (not yet expired) - ASN1_TIME* not_after = X509_get_notAfter(cert); + const ASN1_TIME* not_after = X509_get0_notAfter(cert); if (not_after == nullptr) { return false; } @@ -1677,7 +1673,12 @@ void SecureContext::Init(const FunctionCallbackInfo& args) { return THROW_ERR_CRYPTO_OPERATION_FAILED( env, "Error generating ticket keys"); } +#if NCRYPTO_USE_OPENSSL3_PROVIDER + SSL_CTX_set_tlsext_ticket_key_evp_cb(sc->ctx_.get(), + TicketCompatibilityCallback); +#else SSL_CTX_set_tlsext_ticket_key_cb(sc->ctx_.get(), TicketCompatibilityCallback); +#endif } SSLPointer SecureContext::CreateSSL() { @@ -2021,17 +2022,22 @@ void SecureContext::SetDHParam(const FunctionCallbackInfo& args) { if (!bio) return; +#if NCRYPTO_USE_OPENSSL3_PROVIDER + EVPKeyPointer params(PEM_read_bio_Parameters(bio.get(), nullptr)); + if (params && params.id() == EVP_PKEY_DH) dh.reset(params.release()); +#else dh.reset(PEM_read_bio_DHparams(bio.get(), nullptr, nullptr, nullptr)); +#endif } // Invalid dhparam is silently discarded and DHE is no longer used. // TODO(tniessen): don't silently discard invalid dhparam. + // TODO(panva): In a semver-major, reject non-DH parameter PEMs instead of + // silently treating them as absent. if (!dh) return; - const BIGNUM* p; - DH_get0_pqg(dh.get(), &p, nullptr, nullptr); - const int size = BignumPointer::GetBitCount(p); + const int size = dh.getPrimeBits(); if (size < 1024) { return THROW_ERR_INVALID_ARG_VALUE( env, "DH parameter is less than 1024 bits"); @@ -2040,10 +2046,18 @@ void SecureContext::SetDHParam(const FunctionCallbackInfo& args) { env->isolate(), "DH parameter is less than 2048 bits")); } +#if NCRYPTO_USE_OPENSSL3_PROVIDER + EVPKeyPointer dh_pkey(dh.release()); + if (!SSL_CTX_set0_tmp_dh_pkey(sc->ctx_.get(), dh_pkey.get())) { +#else if (!SSL_CTX_set_tmp_dh(sc->ctx_.get(), dh.get())) { +#endif return THROW_ERR_CRYPTO_OPERATION_FAILED( env, "Error setting temp DH parameter"); } +#if NCRYPTO_USE_OPENSSL3_PROVIDER + dh_pkey.release(); +#endif } void SecureContext::SetMinProto(const FunctionCallbackInfo& args) { @@ -2404,7 +2418,7 @@ void SecureContext::SetClientCertEngine( } // Note that this takes another reference to `engine`. - if (!SSL_CTX_set_client_cert_engine(sc->ctx_.get(), engine.get())) + if (!engine.setClientCertEngine(sc->ctx_.get())) return ThrowCryptoError(env, ERR_get_error()); sc->client_cert_engine_provided_ = true; } @@ -2449,14 +2463,43 @@ void SecureContext::EnableTicketKeyCallback( SecureContext* wrap; ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This()); +#if NCRYPTO_USE_OPENSSL3_PROVIDER + SSL_CTX_set_tlsext_ticket_key_evp_cb(wrap->ctx_.get(), TicketKeyCallback); +#else SSL_CTX_set_tlsext_ticket_key_cb(wrap->ctx_.get(), TicketKeyCallback); +#endif +} + +namespace { +#if NCRYPTO_USE_OPENSSL3_PROVIDER +bool InitTicketHmac(EVP_MAC_CTX* hctx, + const unsigned char* key, + size_t key_len) { + const char* md_name = EVP_MD_get0_name(Digest::SHA256); + if (md_name == nullptr) return false; + OSSL_PARAM params[] = { + OSSL_PARAM_construct_utf8_string( + OSSL_MAC_PARAM_DIGEST, const_cast(md_name), 0), + OSSL_PARAM_construct_end(), + }; + return EVP_MAC_init(hctx, key, key_len, params) == 1; +} +#else +bool InitTicketHmac(HMAC_CTX* hctx, const unsigned char* key, size_t key_len) { + return HMAC_Init_ex(hctx, key, key_len, Digest::SHA256, nullptr) == 1; } +#endif +} // namespace int SecureContext::TicketKeyCallback(SSL* ssl, unsigned char* name, unsigned char* iv, EVP_CIPHER_CTX* ectx, +#if NCRYPTO_USE_OPENSSL3_PROVIDER + EVP_MAC_CTX* hctx, +#else HMAC_CTX* hctx, +#endif int enc) { static const int kTicketPartSize = 16; @@ -2529,8 +2572,9 @@ int SecureContext::TicketKeyCallback(SSL* ssl, } ArrayBufferViewContents hmac_buf(hmac); - HMAC_Init_ex( - hctx, hmac_buf.data(), hmac_buf.length(), Digest::SHA256, nullptr); + if (!InitTicketHmac(hctx, hmac_buf.data(), hmac_buf.length())) { + return -1; + } ArrayBufferViewContents aes_key(aes.As()); if (enc) { @@ -2546,7 +2590,11 @@ int SecureContext::TicketCompatibilityCallback(SSL* ssl, unsigned char* name, unsigned char* iv, EVP_CIPHER_CTX* ectx, +#if NCRYPTO_USE_OPENSSL3_PROVIDER + EVP_MAC_CTX* hctx, +#else HMAC_CTX* hctx, +#endif int enc) { SecureContext* sc = static_cast( SSL_CTX_get_app_data(SSL_get_SSL_CTX(ssl))); @@ -2556,11 +2604,8 @@ int SecureContext::TicketCompatibilityCallback(SSL* ssl, if (!ncrypto::CSPRNG(iv, 16) || EVP_EncryptInit_ex( ectx, Cipher::AES_128_CBC, nullptr, sc->ticket_key_aes_, iv) <= 0 || - HMAC_Init_ex(hctx, - sc->ticket_key_hmac_, - sizeof(sc->ticket_key_hmac_), - Digest::SHA256, - nullptr) <= 0) { + !InitTicketHmac( + hctx, sc->ticket_key_hmac_, sizeof(sc->ticket_key_hmac_))) { return -1; } return 1; @@ -2573,11 +2618,8 @@ int SecureContext::TicketCompatibilityCallback(SSL* ssl, if (EVP_DecryptInit_ex( ectx, Cipher::AES_128_CBC, nullptr, sc->ticket_key_aes_, iv) <= 0 || - HMAC_Init_ex(hctx, - sc->ticket_key_hmac_, - sizeof(sc->ticket_key_hmac_), - Digest::SHA256, - nullptr) <= 0) { + !InitTicketHmac( + hctx, sc->ticket_key_hmac_, sizeof(sc->ticket_key_hmac_))) { return -1; } return 1; diff --git a/src/crypto/crypto_context.h b/src/crypto/crypto_context.h index ffb0b88816c0..95ddea4c262d 100644 --- a/src/crypto/crypto_context.h +++ b/src/crypto/crypto_context.h @@ -158,14 +158,22 @@ class SecureContext final : public BaseObject { unsigned char* name, unsigned char* iv, EVP_CIPHER_CTX* ectx, +#if NCRYPTO_USE_OPENSSL3_PROVIDER + EVP_MAC_CTX* hctx, +#else HMAC_CTX* hctx, +#endif int enc); static int TicketCompatibilityCallback(SSL* ssl, unsigned char* name, unsigned char* iv, EVP_CIPHER_CTX* ectx, +#if NCRYPTO_USE_OPENSSL3_PROVIDER + EVP_MAC_CTX* hctx, +#else HMAC_CTX* hctx, +#endif int enc); SecureContext(Environment* env, v8::Local wrap); diff --git a/src/crypto/crypto_dh.cc b/src/crypto/crypto_dh.cc index 81ec6c8f9288..dcaf529b718a 100644 --- a/src/crypto/crypto_dh.cc +++ b/src/crypto/crypto_dh.cc @@ -89,6 +89,26 @@ MaybeLocal DataPointerToBuffer(Environment* env, DataPointer&& data) { return Buffer::New(env, ab, 0, ab->ByteLength()).FromMaybe(Local()); } +void PutDhError(int reason) { +#ifdef OPENSSL_IS_BORINGSSL + OPENSSL_PUT_ERROR(DH, reason); +#elif NCRYPTO_USE_OPENSSL3_PROVIDER + ERR_raise(ERR_LIB_DH, reason); +#else + ERR_put_error(ERR_LIB_DH, 0, reason, __FILE__, __LINE__); +#endif +} + +#if defined(OPENSSL_IS_BORINGSSL) || !NCRYPTO_USE_OPENSSL3_PROVIDER +void PutBnError(int reason) { +#ifdef OPENSSL_IS_BORINGSSL + OPENSSL_PUT_ERROR(BN, reason); +#else + ERR_put_error(ERR_LIB_BN, 0, reason, __FILE__, __LINE__); +#endif +} +#endif + void DiffieHellmanGroup(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK_EQ(args.Length(), 1); @@ -115,12 +135,12 @@ void New(const FunctionCallbackInfo& args) { if (bits < 2) { #ifndef OPENSSL_IS_BORINGSSL #if OPENSSL_VERSION_MAJOR >= 3 - ERR_put_error(ERR_LIB_DH, 0, DH_R_MODULUS_TOO_SMALL, __FILE__, __LINE__); + PutDhError(DH_R_MODULUS_TOO_SMALL); #else - ERR_put_error(ERR_LIB_BN, 0, BN_R_BITS_TOO_SMALL, __FILE__, __LINE__); + PutBnError(BN_R_BITS_TOO_SMALL); #endif // OPENSSL_VERSION_MAJOR >= 3 #else // OPENSSL_IS_BORINGSSL - OPENSSL_PUT_ERROR(BN, BN_R_BITS_TOO_SMALL); + PutBnError(BN_R_BITS_TOO_SMALL); #endif // OPENSSL_IS_BORINGSSL return ThrowCryptoError(env, ERR_get_error(), "Invalid prime length"); } @@ -134,11 +154,7 @@ void New(const FunctionCallbackInfo& args) { } int32_t generator = args[1].As()->Value(); if (generator < 2) { -#ifndef OPENSSL_IS_BORINGSSL - ERR_put_error(ERR_LIB_DH, 0, DH_R_BAD_GENERATOR, __FILE__, __LINE__); -#else - OPENSSL_PUT_ERROR(DH, DH_R_BAD_GENERATOR); -#endif + PutDhError(DH_R_BAD_GENERATOR); return ThrowCryptoError(env, ERR_get_error(), "Invalid generator"); } @@ -167,20 +183,12 @@ void New(const FunctionCallbackInfo& args) { if (args[1]->IsInt32()) { int32_t generator = args[1].As()->Value(); if (generator < 2) { -#ifndef OPENSSL_IS_BORINGSSL - ERR_put_error(ERR_LIB_DH, 0, DH_R_BAD_GENERATOR, __FILE__, __LINE__); -#else - OPENSSL_PUT_ERROR(DH, DH_R_BAD_GENERATOR); -#endif + PutDhError(DH_R_BAD_GENERATOR); return ThrowCryptoError(env, ERR_get_error(), "Invalid generator"); } bn_g = BignumPointer::New(); if (!bn_g.setWord(generator)) { -#ifndef OPENSSL_IS_BORINGSSL - ERR_put_error(ERR_LIB_DH, 0, DH_R_BAD_GENERATOR, __FILE__, __LINE__); -#else - OPENSSL_PUT_ERROR(DH, DH_R_BAD_GENERATOR); -#endif + PutDhError(DH_R_BAD_GENERATOR); return ThrowCryptoError(env, ERR_get_error(), "Invalid generator"); } } else { @@ -189,23 +197,22 @@ void New(const FunctionCallbackInfo& args) { return THROW_ERR_OUT_OF_RANGE(env, "generator is too big"); bn_g = BignumPointer(reinterpret_cast(arg1.data()), arg1.size()); if (!bn_g) { -#ifndef OPENSSL_IS_BORINGSSL - ERR_put_error(ERR_LIB_DH, 0, DH_R_BAD_GENERATOR, __FILE__, __LINE__); -#else - OPENSSL_PUT_ERROR(DH, DH_R_BAD_GENERATOR); -#endif + PutDhError(DH_R_BAD_GENERATOR); return ThrowCryptoError(env, ERR_get_error(), "Invalid generator"); } if (bn_g.getWord().has_value() && bn_g.getWord().value() < 2) { -#ifndef OPENSSL_IS_BORINGSSL - ERR_put_error(ERR_LIB_DH, 0, DH_R_BAD_GENERATOR, __FILE__, __LINE__); -#else - OPENSSL_PUT_ERROR(DH, DH_R_BAD_GENERATOR); -#endif + PutDhError(DH_R_BAD_GENERATOR); return ThrowCryptoError(env, ERR_get_error(), "Invalid generator"); } } +#ifndef OPENSSL_IS_BORINGSSL + if (BN_num_bits(bn_p.get()) >= 512 && BN_cmp(bn_g.get(), bn_p.get()) >= 0) { + PutDhError(DH_R_BAD_GENERATOR); + return ThrowCryptoError(env, ERR_get_error(), "Invalid generator"); + } +#endif + auto dh = DHPointer::New(std::move(bn_p), std::move(bn_g)); if (!dh) { return THROW_ERR_INVALID_ARG_VALUE(env, "Invalid DH parameters"); diff --git a/src/crypto/crypto_ec.cc b/src/crypto/crypto_ec.cc index cf32c9934701..e6684c4be139 100644 --- a/src/crypto/crypto_ec.cc +++ b/src/crypto/crypto_ec.cc @@ -189,15 +189,16 @@ void ECDH::ComputeSecret(const FunctionCallbackInfo& args) { return; } - int field_size = EC_GROUP_get_degree(ecdh->group_); - size_t out_len = (field_size + 7) / 8; - auto bs = ArrayBuffer::NewBackingStore( - env->isolate(), out_len, BackingStoreInitializationMode::kUninitialized); - - if (!ECDH_compute_key( - bs->Data(), bs->ByteLength(), pub, ecdh->key_.get(), nullptr)) + auto secret = ecdh->key_.computeSecret(pub); + if (!secret) return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to compute ECDH key"); + auto bs = ArrayBuffer::NewBackingStore( + env->isolate(), + secret.size(), + BackingStoreInitializationMode::kUninitialized); + memcpy(bs->Data(), secret.get(), secret.size()); + Local ab = ArrayBuffer::New(env->isolate(), std::move(bs)); Local buffer; if (!Buffer::New(env, ab, 0, ab->ByteLength()).ToLocal(&buffer)) return; @@ -472,9 +473,7 @@ WebCryptoKeyExportStatus EC_Raw_Export(const KeyObjectData& key_data, CHECK(m_pkey); Mutex::ScopedLock lock(key_data.mutex()); - const EC_KEY* ec_key = m_pkey; - - if (ec_key == nullptr) { + if (m_pkey.id() != EVP_PKEY_EC) { switch (key_data.GetKeyType()) { case kKeyTypePrivate: { auto data = m_pkey.rawPrivateKey(); @@ -496,8 +495,11 @@ WebCryptoKeyExportStatus EC_Raw_Export(const KeyObjectData& key_data, } else { if (key_data.GetKeyType() != kKeyTypePublic) return WebCryptoKeyExportStatus::INVALID_KEY_TYPE; - const auto group = ECKeyPointer::GetGroup(ec_key); - const auto point = ECKeyPointer::GetPublicKey(ec_key); + ECKeyPointer ec_key(m_pkey); + if (!ec_key) return WebCryptoKeyExportStatus::FAILED; + const auto group = ec_key.getGroup(); + const auto point = ec_key.getPublicKey(); + if (point == nullptr) return WebCryptoKeyExportStatus::FAILED; point_conversion_form_t form = POINT_CONVERSION_UNCOMPRESSED; // Get the allocated data size... @@ -558,8 +560,11 @@ WebCryptoKeyExportStatus ECKeyExportTraits::DoExport( // the header is for all practical purposes a static 26 byte sequence // where only the second byte changes. Mutex::ScopedLock lock(key_data.mutex()); - const auto group = ECKeyPointer::GetGroup(m_pkey); - const auto point = ECKeyPointer::GetPublicKey(m_pkey); + ECKeyPointer ec_key(m_pkey); + if (!ec_key) return WebCryptoKeyExportStatus::FAILED; + const auto group = ec_key.getGroup(); + const auto point = ec_key.getPublicKey(); + if (point == nullptr) return WebCryptoKeyExportStatus::FAILED; const point_conversion_form_t form = POINT_CONVERSION_UNCOMPRESSED; const size_t need = EC_POINT_point2oct(group, point, form, nullptr, 0, nullptr); @@ -602,11 +607,14 @@ bool ExportJWKEcKey(Environment* env, const auto& m_pkey = key.GetAsymmetricKey(); CHECK_EQ(m_pkey.id(), EVP_PKEY_EC); - const EC_KEY* ec = m_pkey; - CHECK_NOT_NULL(ec); + ECKeyPointer ec(m_pkey); + if (!ec) { + THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK EC key"); + return false; + } - const auto pub = ECKeyPointer::GetPublicKey(ec); - const auto group = ECKeyPointer::GetGroup(ec); + const auto pub = ec.getPublicKey(); + const auto group = ec.getGroup(); int degree_bits = EC_GROUP_get_degree(group); int degree_bytes = @@ -671,7 +679,7 @@ bool ExportJWKEcKey(Environment* env, } if (key.GetKeyType() == kKeyTypePrivate) { - auto pvt = ECKeyPointer::GetPrivateKey(ec); + auto pvt = ec.getPrivateKey(); return SetEncodedValue(env, target, env->jwk_d_string(), pvt, degree_bytes) .IsJust(); } @@ -867,11 +875,12 @@ bool GetEcKeyDetail(Environment* env, const auto& m_pkey = key.GetAsymmetricKey(); CHECK_EQ(m_pkey.id(), EVP_PKEY_EC); - const EC_KEY* ec = m_pkey; - CHECK_NOT_NULL(ec); + ECKeyPointer ec(m_pkey); + if (!ec) return true; - const auto group = ECKeyPointer::GetGroup(ec); + const auto group = ec.getGroup(); int nid = EC_GROUP_get_curve_name(group); + if (nid == NID_undef) return true; return target ->Set(env->context(), @@ -886,11 +895,12 @@ bool GetEcKeyDetail(Environment* env, // https://github.com/chromium/chromium/blob/7af6cfd/components/webcrypto/algorithms/ecdsa.cc size_t GroupOrderSize(const EVPKeyPointer& key) { - const EC_KEY* ec = key; - CHECK_NOT_NULL(ec); + ECKeyPointer ec(key); + if (!ec) return 0; auto order = BignumPointer::New(); - CHECK(order); - CHECK(EC_GROUP_get_order(ECKeyPointer::GetGroup(ec), order.get(), nullptr)); + if (!order || !EC_GROUP_get_order(ec.getGroup(), order.get(), nullptr)) { + return 0; + } return order.byteLength(); } } // namespace crypto diff --git a/src/crypto/crypto_hash.cc b/src/crypto/crypto_hash.cc index 3e089d7aca36..fce312446916 100644 --- a/src/crypto/crypto_hash.cc +++ b/src/crypto/crypto_hash.cc @@ -250,6 +250,13 @@ const EVP_MD* GetDigestImplementation(Environment* env, #endif } +void MarkInvalidXofLength() { +#if NCRYPTO_USE_OPENSSL3_PROVIDER + ERR_raise(ERR_LIB_EVP, EVP_R_NOT_XOF_OR_INVALID_LENGTH); +#else + EVPerr(EVP_F_EVP_DIGESTFINALXOF, EVP_R_NOT_XOF_OR_INVALID_LENGTH); +#endif +} // crypto.digest(algorithm, algorithmId, algorithmCache, // input, outputEncoding, outputEncodingId, outputLength) void Hash::OneShotDigest(const FunctionCallbackInfo& args) { @@ -443,7 +450,7 @@ bool Hash::HashInit(const EVP_MD* md, Maybe xof_md_len) { // This is a little hack to cause createHash to fail when an incorrect // hashSize option was passed for a non-XOF hash function. if (!mdctx_.hasXofFlag()) [[unlikely]] { - EVPerr(EVP_F_EVP_DIGESTFINALXOF, EVP_R_NOT_XOF_OR_INVALID_LENGTH); + MarkInvalidXofLength(); mdctx_.reset(); return false; } diff --git a/src/crypto/crypto_keys.cc b/src/crypto/crypto_keys.cc index bdc9aad1cad4..326d47023e4a 100644 --- a/src/crypto/crypto_keys.cc +++ b/src/crypto/crypto_keys.cc @@ -386,11 +386,14 @@ bool KeyObjectData::ToEncodedPublicKey( Mutex::ScopedLock lock(mutex()); const auto& pkey = GetAsymmetricKey(); if (pkey.id() == EVP_PKEY_EC) { - const EC_KEY* ec_key = pkey; - CHECK_NOT_NULL(ec_key); + ECKeyPointer ec_key(pkey); + if (!ec_key) { + THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); + return false; + } auto form = static_cast(config.ec_point_form); - const auto group = ECKeyPointer::GetGroup(ec_key); - const auto point = ECKeyPointer::GetPublicKey(ec_key); + const auto group = ec_key.getGroup(); + const auto point = ec_key.getPublicKey(); return ECPointToBuffer(env, group, point, form).ToLocal(out); } const int id = pkey.id(); @@ -432,14 +435,23 @@ bool KeyObjectData::ToEncodedPrivateKey( Mutex::ScopedLock lock(mutex()); const auto& pkey = GetAsymmetricKey(); if (pkey.id() == EVP_PKEY_EC) { - const EC_KEY* ec_key = pkey; - CHECK_NOT_NULL(ec_key); - const BIGNUM* private_key = ECKeyPointer::GetPrivateKey(ec_key); - CHECK_NOT_NULL(private_key); - const auto group = ECKeyPointer::GetGroup(ec_key); + ECKeyPointer ec_key(pkey); + if (!ec_key) { + THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); + return false; + } + const BIGNUM* private_key = ec_key.getPrivateKey(); + if (private_key == nullptr) { + THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get EC private key"); + return false; + } + const auto group = ec_key.getGroup(); auto order = BignumPointer::New(); - CHECK(order); - CHECK(EC_GROUP_get_order(group, order.get(), nullptr)); + if (!order || !EC_GROUP_get_order(group, order.get(), nullptr)) { + THROW_ERR_CRYPTO_OPERATION_FAILED(env, + "Failed to export EC private key"); + return false; + } auto buf = BignumPointer::EncodePadded(private_key, order.byteLength()); if (!buf) { THROW_ERR_CRYPTO_OPERATION_FAILED(env, @@ -630,7 +642,9 @@ static KeyObjectData ImportRawKey(Environment* env, throw_invalid(); return {}; } +#if NCRYPTO_USE_LEGACY_KEY_TYPES eckey.release(); +#endif return KeyObjectData::CreateAsymmetric(target_type, std::move(pkey)); } @@ -1461,15 +1475,17 @@ void KeyObjectHandle::ExportECPublicRaw( return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); } - const EC_KEY* ec_key = m_pkey; - CHECK_NOT_NULL(ec_key); + ECKeyPointer ec_key(m_pkey); + if (!ec_key) { + return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); + } CHECK(args[0]->IsInt32()); auto form = static_cast(args[0].As()->Value()); - const auto group = ECKeyPointer::GetGroup(ec_key); - const auto point = ECKeyPointer::GetPublicKey(ec_key); + const auto group = ec_key.getGroup(); + const auto point = ec_key.getPublicKey(); Local buf; if (!ECPointToBuffer(env, group, point, form).ToLocal(&buf)) return; @@ -1492,16 +1508,23 @@ void KeyObjectHandle::ExportECPrivateRaw( return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); } - const EC_KEY* ec_key = m_pkey; - CHECK_NOT_NULL(ec_key); + ECKeyPointer ec_key(m_pkey); + if (!ec_key) { + return THROW_ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(env); + } - const BIGNUM* private_key = ECKeyPointer::GetPrivateKey(ec_key); - CHECK_NOT_NULL(private_key); + const BIGNUM* private_key = ec_key.getPrivateKey(); + if (private_key == nullptr) { + return THROW_ERR_CRYPTO_OPERATION_FAILED(env, + "Failed to get EC private key"); + } - const auto group = ECKeyPointer::GetGroup(ec_key); + const auto group = ec_key.getGroup(); auto order = BignumPointer::New(); - CHECK(order); - CHECK(EC_GROUP_get_order(group, order.get(), nullptr)); + if (!order || !EC_GROUP_get_order(group, order.get(), nullptr)) { + return THROW_ERR_CRYPTO_OPERATION_FAILED(env, + "Failed to export EC private key"); + } auto buf = BignumPointer::EncodePadded(private_key, order.byteLength()); if (!buf) { diff --git a/src/crypto/crypto_rsa.cc b/src/crypto/crypto_rsa.cc index eced4175a350..32675f76fbe8 100644 --- a/src/crypto/crypto_rsa.cc +++ b/src/crypto/crypto_rsa.cc @@ -19,7 +19,9 @@ using ncrypto::DataPointer; using ncrypto::Digest; using ncrypto::EVPKeyCtxPointer; using ncrypto::EVPKeyPointer; +#if NCRYPTO_USE_LEGACY_KEY_TYPES using ncrypto::RSAPointer; +#endif using v8::ArrayBuffer; using v8::BackingStoreInitializationMode; using v8::FunctionCallbackInfo; @@ -395,6 +397,9 @@ KeyObjectData ImportJWKRsaKey(Environment* env, Local jwk) { KeyType type = d_value->IsString() ? kKeyTypePrivate : kKeyTypePublic; +#if NCRYPTO_USE_OPENSSL3_PROVIDER + ncrypto::Rsa rsa_view; +#else RSAPointer rsa(RSA_new()); if (!rsa) { THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Unable to create RSA pointer"); @@ -402,6 +407,7 @@ KeyObjectData ImportJWKRsaKey(Environment* env, Local jwk) { } ncrypto::Rsa rsa_view(rsa.get()); +#endif ByteSource n = ByteSource::FromEncodedString(env, n_value.As()); ByteSource e = ByteSource::FromEncodedString(env, e_value.As()); @@ -450,7 +456,11 @@ KeyObjectData ImportJWKRsaKey(Environment* env, Local jwk) { } } +#if NCRYPTO_USE_OPENSSL3_PROVIDER + auto pkey = EVPKeyPointer::NewRSA(rsa_view); +#else auto pkey = EVPKeyPointer::NewRSA(std::move(rsa)); +#endif if (!pkey) { THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Unable to create key pointer"); return {}; diff --git a/src/crypto/crypto_tls.cc b/src/crypto/crypto_tls.cc index 28eb760dbb4c..ce44ad15fa79 100644 --- a/src/crypto/crypto_tls.cc +++ b/src/crypto/crypto_tls.cc @@ -874,7 +874,11 @@ void TLSWrap::ClearOut() { return; const char* ls = ERR_lib_error_string(ssl_err); +#if NCRYPTO_USE_OPENSSL3_PROVIDER + const char* fs = nullptr; +#else const char* fs = ERR_func_error_string(ssl_err); +#endif const char* rs = ERR_reason_error_string(ssl_err); if (!Set(env(), obj, env()->library_string(), ls) || !Set(env(), obj, env()->function_string(), fs) || diff --git a/src/crypto/crypto_util.cc b/src/crypto/crypto_util.cc index 06c4aa87464d..7e256cc75bef 100644 --- a/src/crypto/crypto_util.cc +++ b/src/crypto/crypto_util.cc @@ -12,10 +12,6 @@ #include "util-inl.h" #include "v8.h" -#ifndef OPENSSL_NO_ENGINE -#include -#endif // !OPENSSL_NO_ENGINE - #include "math.h" #if OPENSSL_VERSION_MAJOR >= 3 @@ -505,7 +501,11 @@ Maybe Decorate(Environment* env, if (err == 0) return JustVoid(); // No decoration necessary. const char* ls = ERR_lib_error_string(err); +#if NCRYPTO_USE_OPENSSL3_PROVIDER + const char* fs = nullptr; +#else const char* fs = ERR_func_error_string(err); +#endif const char* rs = ERR_reason_error_string(err); Isolate* isolate = env->isolate(); diff --git a/src/crypto/crypto_x509.cc b/src/crypto/crypto_x509.cc index 711316f92918..be4067df0e22 100644 --- a/src/crypto/crypto_x509.cc +++ b/src/crypto/crypto_x509.cc @@ -20,7 +20,6 @@ using ncrypto::BIOPointer; using ncrypto::ClearErrorOnReturn; using ncrypto::DataPointer; using ncrypto::Digest; -using ncrypto::ECKeyPointer; using ncrypto::SSLPointer; using ncrypto::X509Name; using ncrypto::X509Pointer; @@ -668,17 +667,10 @@ static MaybeLocal GetX509NameObject(Environment* env, } MaybeLocal GetPubKey(Environment* env, const ncrypto::Rsa& rsa) { - int size = i2d_RSA_PUBKEY(rsa, nullptr); - CHECK_GE(size, 0); - - auto bs = ArrayBuffer::NewBackingStore( - env->isolate(), size, BackingStoreInitializationMode::kUninitialized); - - auto serialized = reinterpret_cast(bs->Data()); - CHECK_GE(i2d_RSA_PUBKEY(rsa, &serialized), 0); - - auto ab = ArrayBuffer::New(env->isolate(), std::move(bs)); - return Buffer::New(env, ab, 0, ab->ByteLength()).FromMaybe(Local()); + auto bio = rsa.derPublicKey(); + Local ret; + if (!ToBuffer(env, &bio).ToLocal(&ret)) return {}; + return ret.As(); } MaybeLocal GetModulusString(Environment* env, const BIGNUM* n) { @@ -698,14 +690,13 @@ MaybeLocal GetExponentString(Environment* env, const BIGNUM* e) { return ToV8Value(env->context(), bio); } -MaybeLocal GetECPubKey(Environment* env, - const EC_GROUP* group, - OSSL3_CONST EC_KEY* ec) { - const auto pubkey = ECKeyPointer::GetPublicKey(ec); +MaybeLocal GetECPubKey(Environment* env, const ncrypto::Ec& ec) { + const auto group = ec.getGroup(); + const auto pubkey = ec.getPublicKey(); if (pubkey == nullptr) [[unlikely]] return Undefined(env->isolate()); - return ECPointToBuffer(env, group, pubkey, EC_KEY_get_conv_form(ec)) + return ECPointToBuffer(env, group, pubkey, ec.getPointConversionForm()) .FromMaybe(Local()); } @@ -792,7 +783,7 @@ MaybeLocal X509ToObject(Environment* env, const X509View& cert) { cert.ifEc([&](const ncrypto::Ec& ec) { const auto group = ec.getGroup(); - values[7] = GetECPubKey(env, group, ec); // pubkey + values[7] = GetECPubKey(env, ec); // pubkey values[8] = GetECGroupBits(env, group); // bits const int nid = ec.getCurve(); if (nid != 0) { diff --git a/src/node.cc b/src/node.cc index f6a0d1142b5e..71d709f13f96 100644 --- a/src/node.cc +++ b/src/node.cc @@ -50,6 +50,11 @@ #if HAVE_OPENSSL #include "ncrypto.h" #include "node_crypto.h" +#if OPENSSL_VERSION_MAJOR >= 3 && !defined(CONF_MFLAGS_IGNORE_MISSING_FILE) +// OpenSSL hides this deprecated macro under OPENSSL_NO_DEPRECATED, but the +// non-deprecated OPENSSL_INIT settings API still accepts the flag value. +#define CONF_MFLAGS_IGNORE_MISSING_FILE 0x10 +#endif #endif #if defined(NODE_HAVE_I18N_SUPPORT) diff --git a/src/node_constants.cc b/src/node_constants.cc index fd28e0904d05..6a1100194314 100644 --- a/src/node_constants.cc +++ b/src/node_constants.cc @@ -39,8 +39,58 @@ #if HAVE_OPENSSL #include #include +#if !defined(RSA_PKCS1_PADDING) +#define RSA_PKCS1_PADDING 1 +#endif +#if !defined(RSA_SSLV23_PADDING) +#define RSA_SSLV23_PADDING 2 +#endif +#if !defined(RSA_NO_PADDING) +#define RSA_NO_PADDING 3 +#endif +#if !defined(RSA_PKCS1_OAEP_PADDING) +#define RSA_PKCS1_OAEP_PADDING 4 +#endif +#if !defined(RSA_X931_PADDING) +#define RSA_X931_PADDING 5 +#endif +#if !defined(RSA_PKCS1_PSS_PADDING) +#define RSA_PKCS1_PSS_PADDING 6 +#endif +#if !defined(OPENSSL_IS_BORINGSSL) && OPENSSL_VERSION_MAJOR >= 3 +// OpenSSL hides these deprecated DH check constants under +// OPENSSL_NO_DEPRECATED, but the numeric verifyError values remain public API. +#if !defined(DH_CHECK_P_NOT_PRIME) +#define DH_CHECK_P_NOT_PRIME 0x01 +#endif +#if !defined(DH_CHECK_P_NOT_SAFE_PRIME) +#define DH_CHECK_P_NOT_SAFE_PRIME 0x02 +#endif +#if !defined(DH_UNABLE_TO_CHECK_GENERATOR) +#define DH_UNABLE_TO_CHECK_GENERATOR 0x04 +#endif +#if !defined(DH_NOT_SUITABLE_GENERATOR) +#define DH_NOT_SUITABLE_GENERATOR 0x08 +#endif +#endif #ifndef OPENSSL_NO_ENGINE +#if !defined(OPENSSL_IS_BORINGSSL) && OPENSSL_VERSION_MAJOR >= 3 +// Engine constants remain public API while engine implementation lives in the +// dedicated compatibility target. +#define ENGINE_METHOD_RSA (unsigned int)0x0001 +#define ENGINE_METHOD_DSA (unsigned int)0x0002 +#define ENGINE_METHOD_DH (unsigned int)0x0004 +#define ENGINE_METHOD_RAND (unsigned int)0x0008 +#define ENGINE_METHOD_CIPHERS (unsigned int)0x0040 +#define ENGINE_METHOD_DIGESTS (unsigned int)0x0080 +#define ENGINE_METHOD_PKEY_METHS (unsigned int)0x0200 +#define ENGINE_METHOD_PKEY_ASN1_METHS (unsigned int)0x0400 +#define ENGINE_METHOD_EC (unsigned int)0x0800 +#define ENGINE_METHOD_ALL (unsigned int)0xFFFF +#define ENGINE_METHOD_NONE (unsigned int)0x0000 +#else #include +#endif #endif // !OPENSSL_NO_ENGINE #endif // HAVE_OPENSSL diff --git a/test/parallel/test-crypto-dh-curves.js b/test/parallel/test-crypto-dh-curves.js index ddd5ea9377e6..be4c2079d998 100644 --- a/test/parallel/test-crypto-dh-curves.js +++ b/test/parallel/test-crypto-dh-curves.js @@ -5,6 +5,11 @@ if (!common.hasCrypto) const assert = require('assert'); const crypto = require('crypto'); +const { hasOpenSSL } = require('../common/crypto'); +const { + DH_CHECK_P_NOT_PRIME, + DH_CHECK_P_NOT_SAFE_PRIME, +} = crypto.constants; // Second OAKLEY group, see // https://github.com/nodejs/node-v0.x-archive/issues/2338 and @@ -15,12 +20,64 @@ const p = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' + 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF'; crypto.createDiffieHellman(p, 'hex'); +if (!process.features.openssl_is_boringssl) { + const notPrime = Buffer.from(p, 'hex'); + notPrime[notPrime.length - 1] = 0xfd; + assert.strictEqual( + crypto.createDiffieHellman(notPrime, Buffer.from([2])).verifyError, + DH_CHECK_P_NOT_PRIME); + + const notSafePrime = Buffer.from( + 'd2d6d13e1c1e0bbb63c742199dee010411f089ac74f0f7213348388280700fd6' + + '0ef9c1e7b096a4257dcbce61c544a5d1d23db4c49c63ce302f63be5cf5804327', + 'hex'); + assert.strictEqual( + crypto.createDiffieHellman(notSafePrime, Buffer.from([2])).verifyError, + DH_CHECK_P_NOT_SAFE_PRIME); + + const group = crypto.getDiffieHellman('modp14'); + const alice = crypto.createDiffieHellman( + group.getPrime(), group.getGenerator()); + alice.generateKeys(); + const groupPrime = BigInt(`0x${group.getPrime('hex')}`); + assert.throws( + () => alice.computeSecret(Buffer.from([1])), + { + code: 'ERR_CRYPTO_INVALID_KEYLEN', + message: 'Supplied key is too small' + }); + assert.throws( + () => alice.computeSecret(group.getPrime()), + { + code: 'ERR_CRYPTO_INVALID_KEYLEN', + message: 'Supplied key is too large' + }); + assert.throws( + () => alice.computeSecret( + Buffer.from((groupPrime - 1n).toString(16), 'hex')), + { + code: 'ERR_CRYPTO_INVALID_KEYLEN', + message: 'Supplied key is too large' + }); +} + // Confirm DH_check() results are exposed for optional examination. const bad_dh = process.features.openssl_is_boringssl ? crypto.createDiffieHellman('abcd', 'hex', 0) : crypto.createDiffieHellman('02', 'hex'); assert.notStrictEqual(bad_dh.verifyError, 0); +if (hasOpenSSL(3)) { + const smallSafePrime = crypto.createDiffieHellman( + Buffer.from([23]), Buffer.from([2])); + assert.notStrictEqual(smallSafePrime.verifyError, 0); + + assert.throws( + () => crypto.createDiffieHellman(Buffer.from(p, 'hex'), + Buffer.from(p, 'hex')), + { code: 'ERR_OSSL_DH_BAD_GENERATOR' }); +} + const availableCurves = new Set(crypto.getCurves()); const availableHashes = new Set(crypto.getHashes()); diff --git a/test/parallel/test-crypto-key-objects.js b/test/parallel/test-crypto-key-objects.js index 84250a3ada22..0ece0f17339a 100644 --- a/test/parallel/test-crypto-key-objects.js +++ b/test/parallel/test-crypto-key-objects.js @@ -920,6 +920,41 @@ if (!process.features.openssl_is_boringssl) { } } } + + const der = publicKey.export({ format: 'der', type: 'spki' }); + const saltLengthParam = Buffer.from([0xa2, 0x03, 0x02, 0x01, 0x10]); + const saltLengthOffset = der.indexOf(saltLengthParam); + assert.notStrictEqual(saltLengthOffset, -1); + + const importMalformedPublicKey = common.mustCall((key) => { + const malformedKey = createPublicKey({ + key, + format: 'der', + type: 'spki' + }); + assert.strictEqual(malformedKey.asymmetricKeyType, 'rsa-pss'); + assert.strictEqual(malformedKey.asymmetricKeyDetails.modulusLength, 2048); + assert.strictEqual(malformedKey.asymmetricKeyDetails.publicExponent, + 65537n); + }, 2); + + { + const negativeSaltLength = Buffer.from(der); + negativeSaltLength[saltLengthOffset + saltLengthParam.length - 1] = 0x80; + importMalformedPublicKey(negativeSaltLength); + } + + { + const oversizedSaltLength = Buffer.concat([ + der.subarray(0, saltLengthOffset), + Buffer.from([0xa2, 0x0b, 0x02, 0x09, 1, 0, 0, 0, 0, 0, 0, 0, 0x10]), + der.subarray(saltLengthOffset + saltLengthParam.length), + ]); + oversizedSaltLength.writeUInt16BE(der.readUInt16BE(2) + 8, 2); + oversizedSaltLength[5] = der[5] + 8; + oversizedSaltLength[18] = der[18] + 8; + importMalformedPublicKey(oversizedSaltLength); + } } { diff --git a/test/parallel/test-tls-connect-secure-context.js b/test/parallel/test-tls-connect-secure-context.js index a0d9170c2090..442973118a56 100644 --- a/test/parallel/test-tls-connect-secure-context.js +++ b/test/parallel/test-tls-connect-secure-context.js @@ -23,6 +23,9 @@ connect({ return cleanup(); })); +// Invalid dhparam input is silently discarded for compatibility. +tls.createSecureContext({ dhparam: fixtures.readKey('ec-key.pem') }); + connect({ client: { servername: 'agent1', From 44ee9c3970f9f7f015927fe72d152aca0f3dbd36 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sun, 19 Jul 2026 17:58:25 +0200 Subject: [PATCH 02/16] crypto: limit KangarooTwelveParams customization to 512 bytes To align with a future OpenSSL-imposed limit. Signed-off-by: Filip Skokan PR-URL: https://github.com/nodejs/node/pull/64557 Reviewed-By: Luigi Pinca Reviewed-By: James M Snell --- doc/api/webcrypto.md | 7 ++++++- lib/internal/crypto/webidl.js | 7 ++----- .../webcrypto/supports-modern-algorithms.mjs | 4 ++++ .../test-webcrypto-digest-turboshake-rfc.js | 9 +++++++++ .../test-webcrypto-digest-turboshake.js | 17 ++++++++++++++--- test/wpt/status/WebCryptoAPI.cjs | 3 +++ 6 files changed, 38 insertions(+), 9 deletions(-) diff --git a/doc/api/webcrypto.md b/doc/api/webcrypto.md index 1f10c92cd269..70023d82b165 100644 --- a/doc/api/webcrypto.md +++ b/doc/api/webcrypto.md @@ -2249,11 +2249,16 @@ added: v24.18.0 * Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined} -The optional customization string for KangarooTwelve. +The optional customization string for KangarooTwelve. It must not exceed 512 +bytes. #### `kangarooTwelveParams.name` diff --git a/lib/internal/crypto/webidl.js b/lib/internal/crypto/webidl.js index dbdb7a5ea47c..cd90c36e96ca 100644 --- a/lib/internal/crypto/webidl.js +++ b/lib/internal/crypto/webidl.js @@ -296,10 +296,6 @@ function validateCShakeFunctionName(V) { 'NotSupportedError'); } -function validateCShakeCustomization(V) { - validateMaxBufferLength(V, 'CShakeParams.customization', 512); -} - converters.RsaPssParams = createDictionaryConverter( 'RsaPssParams', [ dictAlgorithm, @@ -452,7 +448,7 @@ converters.CShakeParams = createDictionaryConverter( { key: 'customization', converter: converters.BufferSource, - validator: validateCShakeCustomization, + validator: (V, opts) => validateMaxBufferLength(V, 'CShakeParams.customization', 512), }, ], ]); @@ -774,6 +770,7 @@ converters.KangarooTwelveParams = createDictionaryConverter( { key: 'customization', converter: converters.BufferSource, + validator: (V, opts) => validateMaxBufferLength(V, 'KangarooTwelveParams.customization', 512), }, ], ]); diff --git a/test/fixtures/webcrypto/supports-modern-algorithms.mjs b/test/fixtures/webcrypto/supports-modern-algorithms.mjs index a3e5fc549764..acb0e249dd38 100644 --- a/test/fixtures/webcrypto/supports-modern-algorithms.mjs +++ b/test/fixtures/webcrypto/supports-modern-algorithms.mjs @@ -48,11 +48,15 @@ export const vectors = { [false, 'KT128'], [true, { name: 'KT128', outputLength: 128 }], [true, { name: 'KT128', outputLength: 128, customization: Buffer.alloc(0) }], + [true, { name: 'KT128', outputLength: 128, customization: Buffer.alloc(512) }], + [false, { name: 'KT128', outputLength: 128, customization: Buffer.alloc(513) }], [false, { name: 'KT128', outputLength: 0 }], [false, { name: 'KT128', outputLength: 127 }], [false, 'KT256'], [true, { name: 'KT256', outputLength: 256 }], [true, { name: 'KT256', outputLength: 256, customization: Buffer.alloc(0) }], + [true, { name: 'KT256', outputLength: 256, customization: Buffer.alloc(512) }], + [false, { name: 'KT256', outputLength: 256, customization: Buffer.alloc(513) }], [false, { name: 'KT256', outputLength: 0 }], [false, { name: 'KT256', outputLength: 255 }], ], diff --git a/test/parallel/test-webcrypto-digest-turboshake-rfc.js b/test/parallel/test-webcrypto-digest-turboshake-rfc.js index 43762fecc2c4..271fde76ab23 100644 --- a/test/parallel/test-webcrypto-digest-turboshake-rfc.js +++ b/test/parallel/test-webcrypto-digest-turboshake-rfc.js @@ -326,6 +326,15 @@ async function checkDigest(name, vectors) { else algorithm.domainSeparation = rest[0]; } + + if (isKT && algorithm.customization?.byteLength > 512) { + await assert.rejects(subtle.digest(algorithm, input), { + name: 'OperationError', + message: 'KangarooTwelveParams.customization must be at most 512 bytes', + }); + continue; + } + const result = await subtle.digest(algorithm, input); assert.deepStrictEqual( Buffer.from(result).toString('hex'), diff --git a/test/parallel/test-webcrypto-digest-turboshake.js b/test/parallel/test-webcrypto-digest-turboshake.js index 0b5586b19286..a6f4b2d50f94 100644 --- a/test/parallel/test-webcrypto-digest-turboshake.js +++ b/test/parallel/test-webcrypto-digest-turboshake.js @@ -155,11 +155,22 @@ async function testDigest(size, alg) { // KT128 with customization string (async () => { - const digest = await subtle.digest( - { name: 'KT128', outputLength: 256, customization: Buffer.from('test') }, - Buffer.from('hello')); + const digest = await subtle.digest({ + name: 'KT128', + outputLength: 256, + customization: Buffer.alloc(512), + }, Buffer.from('hello')); assert(digest instanceof ArrayBuffer); assert.strictEqual(digest.byteLength, 32); + + await assert.rejects(subtle.digest({ + name: 'KT128', + outputLength: 256, + customization: Buffer.alloc(513), + }, Buffer.from('hello')), { + name: 'OperationError', + message: 'KangarooTwelveParams.customization must be at most 512 bytes', + }); })().then(common.mustCall()); // TurboSHAKE domain separation out of range diff --git a/test/wpt/status/WebCryptoAPI.cjs b/test/wpt/status/WebCryptoAPI.cjs index 8fdbb458fac7..2a32b330f7d8 100644 --- a/test/wpt/status/WebCryptoAPI.cjs +++ b/test/wpt/status/WebCryptoAPI.cjs @@ -91,6 +91,9 @@ if (process.features.openssl_is_boringssl) { ['supports-modern.tentative.https.any.js', /ml-kem-512/i]); } +skipSubtests( + ['digest/kangarootwelve.tentative.https.any.js', /C=(?:\d{4,}|5(?:1[3-9]|[2-9]\d)|[6-9]\d{2}) bytes/]); + function assertNoOverlap(fileSkips, subtestSkips) { const subtestSkipFiles = new Set(Object.keys(subtestSkips)); const overlap = Object.keys(fileSkips).filter((file) => subtestSkipFiles.has(file)); From 570bc8524c2e5ede088c67027d156a6063d298d8 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Fri, 17 Jul 2026 00:08:28 +0200 Subject: [PATCH 03/16] crypto: retain legacy DH validation Limit the g >= p rejection needed by the provider backend to OpenSSL 3. Preserve the legacy DiffieHellman object and expose the validation result through verifyError as before. Signed-off-by: Filip Skokan PR-URL: https://github.com/nodejs/node/pull/64547 Refs: https://github.com/nodejs/node/pull/64211 Reviewed-By: James M Snell Reviewed-By: Yagiz Nizipli --- src/crypto/crypto_dh.cc | 2 +- test/parallel/test-crypto-dh-curves.js | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/crypto/crypto_dh.cc b/src/crypto/crypto_dh.cc index dcaf529b718a..59781e7488f5 100644 --- a/src/crypto/crypto_dh.cc +++ b/src/crypto/crypto_dh.cc @@ -206,7 +206,7 @@ void New(const FunctionCallbackInfo& args) { } } -#ifndef OPENSSL_IS_BORINGSSL +#if NCRYPTO_USE_OPENSSL3_PROVIDER if (BN_num_bits(bn_p.get()) >= 512 && BN_cmp(bn_g.get(), bn_p.get()) >= 0) { PutDhError(DH_R_BAD_GENERATOR); return ThrowCryptoError(env, ERR_get_error(), "Invalid generator"); diff --git a/test/parallel/test-crypto-dh-curves.js b/test/parallel/test-crypto-dh-curves.js index be4c2079d998..f14c58e7c200 100644 --- a/test/parallel/test-crypto-dh-curves.js +++ b/test/parallel/test-crypto-dh-curves.js @@ -9,6 +9,7 @@ const { hasOpenSSL } = require('../common/crypto'); const { DH_CHECK_P_NOT_PRIME, DH_CHECK_P_NOT_SAFE_PRIME, + DH_NOT_SUITABLE_GENERATOR, } = crypto.constants; // Second OAKLEY group, see @@ -76,6 +77,11 @@ if (hasOpenSSL(3)) { () => crypto.createDiffieHellman(Buffer.from(p, 'hex'), Buffer.from(p, 'hex')), { code: 'ERR_OSSL_DH_BAD_GENERATOR' }); +} else if (!process.features.openssl_is_boringssl) { + assert.strictEqual( + crypto.createDiffieHellman(Buffer.from(p, 'hex'), + Buffer.from(p, 'hex')).verifyError, + DH_NOT_SUITABLE_GENERATOR); } const availableCurves = new Set(crypto.getCurves()); From 6f7db99d7ea1e4cca84cf99e14278f0d99175a30 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Fri, 17 Jul 2026 00:10:53 +0200 Subject: [PATCH 04/16] crypto: handle incomplete RSA private keys Treat missing private RSA parameters as an export failure instead of passing null BIGNUM pointers to the JWK encoder. Also stop constructing a usable RSA view when reading an optional parameter itself fails. Signed-off-by: Filip Skokan PR-URL: https://github.com/nodejs/node/pull/64547 Refs: https://github.com/nodejs/node/pull/64211 Reviewed-By: James M Snell Reviewed-By: Yagiz Nizipli --- deps/ncrypto/ncrypto.cc | 14 ++++++++------ src/crypto/crypto_rsa.cc | 7 +++++++ test/cctest/test_node_crypto_env.cc | 27 +++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index 5914f9c729c7..30a4cd8a12ef 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -5703,12 +5703,14 @@ Rsa::Rsa(const EVP_PKEY* pkey) : Rsa() { !GetPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_E, &e_)) { return; } - GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_D, &d_); - GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_FACTOR1, &p_); - GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_FACTOR2, &q_); - GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_EXPONENT1, &dp_); - GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_EXPONENT2, &dq_); - GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_COEFFICIENT1, &qi_); + if (!GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_D, &d_) || + !GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_FACTOR1, &p_) || + !GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_FACTOR2, &q_) || + !GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_EXPONENT1, &dp_) || + !GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_EXPONENT2, &dq_) || + !GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_COEFFICIENT1, &qi_)) { + return; + } if (type == EVP_PKEY_RSA_PSS) { MarkPopErrorOnReturn pop_errors; diff --git a/src/crypto/crypto_rsa.cc b/src/crypto/crypto_rsa.cc index 32675f76fbe8..4cb9f267d2f0 100644 --- a/src/crypto/crypto_rsa.cc +++ b/src/crypto/crypto_rsa.cc @@ -357,6 +357,13 @@ bool ExportJWKRsaKey(Environment* env, if (key.GetKeyType() == kKeyTypePrivate) { auto pvt_key = rsa.getPrivateKey(); + if (pub_key.d == nullptr || pvt_key.p == nullptr || pvt_key.q == nullptr || + pvt_key.dp == nullptr || pvt_key.dq == nullptr || + pvt_key.qi == nullptr) { + THROW_ERR_CRYPTO_OPERATION_FAILED(env, + "Failed to export RSA private key"); + return false; + } if (SetEncodedValue(env, target, env->jwk_d_string(), pub_key.d) .IsNothing() || SetEncodedValue(env, target, env->jwk_p_string(), pvt_key.p) diff --git a/test/cctest/test_node_crypto_env.cc b/test/cctest/test_node_crypto_env.cc index 77aab8f4182d..fddf584d7d41 100644 --- a/test/cctest/test_node_crypto_env.cc +++ b/test/cctest/test_node_crypto_env.cc @@ -1,11 +1,14 @@ #include #include "crypto/crypto_bio.h" +#include "crypto/crypto_keys.h" +#include "crypto/crypto_rsa.h" #include "gtest/gtest.h" #include "node_options.h" #include "node_test_fixture.h" #include "openssl/err.h" using v8::Local; +using v8::Object; using v8::String; /* @@ -31,3 +34,27 @@ TEST_F(NodeCryptoEnv, LoadBIO) { ASSERT_EQ(ERR_peek_error(), 0UL) << "There should not have left " "any errors on the OpenSSL error stack\n"; } + +#if NCRYPTO_USE_OPENSSL3_PROVIDER +TEST_F(NodeCryptoEnv, ExportIncompleteRsaPrivateKeyAsJwk) { + v8::HandleScope handle_scope(isolate_); + Argv argv; + Env env{handle_scope, argv}; + + ncrypto::Rsa rsa; + auto n = ncrypto::BignumPointer::New(); + auto e = ncrypto::BignumPointer::New(); + ASSERT_TRUE(n.setWord(3233)); + ASSERT_TRUE(e.setWord(17)); + ASSERT_TRUE(rsa.setPublicKey(std::move(n), std::move(e))); + + auto pkey = ncrypto::EVPKeyPointer::NewRSA(rsa); + ASSERT_TRUE(pkey); + auto key = node::crypto::KeyObjectData::CreateAsymmetric( + node::crypto::kKeyTypePrivate, std::move(pkey)); + + v8::TryCatch try_catch(isolate_); + EXPECT_FALSE(node::crypto::ExportJWKRsaKey(*env, key, Object::New(isolate_))); + EXPECT_TRUE(try_catch.HasCaught()); +} +#endif From 48e60bcb6cda9f8cc4ec950f25e767d3a12726df Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Fri, 17 Jul 2026 00:16:25 +0200 Subject: [PATCH 05/16] crypto: cleanse provider private key copies Clear provider-exported RSA, EC, and DH private BIGNUMs before freeing them. Also cleanse OSSL_PARAM builder copies and the plaintext DER intermediate used for encrypted traditional PEM output. Signed-off-by: Filip Skokan PR-URL: https://github.com/nodejs/node/pull/64547 Refs: https://github.com/nodejs/node/pull/64211 Reviewed-By: James M Snell Reviewed-By: Yagiz Nizipli --- deps/ncrypto/ncrypto.cc | 66 ++++++++++++++++++++++++----------------- deps/ncrypto/ncrypto.h | 14 ++++----- 2 files changed, 45 insertions(+), 35 deletions(-) diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index 30a4cd8a12ef..529afe4d718c 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -94,7 +94,19 @@ const EVP_MD* GetDigestCtxMd(const EVP_MD_CTX* ctx) { #if NCRYPTO_USE_OPENSSL3_PROVIDER using OSSLParamBldPointer = DeleteFnPtr; -using OSSLParamPointer = DeleteFnPtr; +struct OSSLParamDeleter { + void operator()(OSSL_PARAM* params) const { + if (params == nullptr) return; + for (OSSL_PARAM* param = params; param->key != nullptr; param++) { + if (param->data != nullptr && param->data_type != OSSL_PARAM_UTF8_PTR && + param->data_type != OSSL_PARAM_OCTET_PTR) { + OPENSSL_cleanse(param->data, param->data_size); + } + } + OSSL_PARAM_free(params); + } +}; +using OSSLParamPointer = std::unique_ptr; struct OpenSSLBufferDeleter { void operator()(unsigned char* pointer) const { OPENSSL_free(pointer); } }; @@ -106,9 +118,8 @@ static constexpr int kX509NameFlagsRFC2253WithinUtf8JSON = XN_FLAG_RFC2253 & ~ASN1_STRFLGS_ESC_MSB & ~ASN1_STRFLGS_ESC_CTRL; #if NCRYPTO_USE_OPENSSL3_PROVIDER -bool GetPKeyBnParam(const EVP_PKEY* pkey, - const char* name, - DeleteFnPtr* out) { +template +bool GetPKeyBnParam(const EVP_PKEY* pkey, const char* name, Pointer* out) { BIGNUM* bn = nullptr; if (pkey == nullptr) return false; if (EVP_PKEY_get_bn_param(pkey, name, &bn) == 1) { @@ -135,9 +146,10 @@ bool GetPKeyBnParam(const EVP_PKEY* pkey, return true; } +template bool GetOptionalPKeyBnParam(const EVP_PKEY* pkey, const char* name, - DeleteFnPtr* out) { + Pointer* out) { BIGNUM* bn = nullptr; if (pkey == nullptr) { out->reset(); @@ -273,9 +285,11 @@ bool GetDhParams(const EVP_PKEY* pkey, bool GetDhKeys(const EVP_PKEY* pkey, DeleteFnPtr* pub, - DeleteFnPtr* priv) { - return GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_PUB_KEY, pub) && - GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_PRIV_KEY, priv); + DeleteFnPtr* priv) { + return (pub == nullptr || + GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_PUB_KEY, pub)) && + (priv == nullptr || + GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_PRIV_KEY, priv)); } #endif @@ -2290,8 +2304,7 @@ DataPointer DHPointer::getPublicKey() const { if (!dh_) return {}; DeleteFnPtr pub_key; - DeleteFnPtr pvt_key; - if (!GetDhKeys(dh_.get(), &pub_key, &pvt_key)) return {}; + if (!GetDhKeys(dh_.get(), &pub_key, nullptr)) return {}; return BignumPointer::Encode(pub_key.get()); #else const BIGNUM* pub_key; @@ -2306,9 +2319,8 @@ DataPointer DHPointer::getPrivateKey() const { if (pvt_key_) return pvt_key_.encode(); if (!dh_) return {}; - DeleteFnPtr pub_key; - DeleteFnPtr pvt_key; - if (!GetDhKeys(dh_.get(), &pub_key, &pvt_key)) return {}; + DeleteFnPtr pvt_key; + if (!GetDhKeys(dh_.get(), nullptr, &pvt_key)) return {}; return BignumPointer::Encode(pvt_key.get()); #else const BIGNUM* pvt_key; @@ -2323,9 +2335,8 @@ bool DHPointer::hasPrivateKey() const { if (pvt_key_) return true; if (!dh_) return false; - DeleteFnPtr pub_key; - DeleteFnPtr pvt_key; - if (!GetDhKeys(dh_.get(), &pub_key, &pvt_key)) return false; + DeleteFnPtr pvt_key; + if (!GetDhKeys(dh_.get(), nullptr, &pvt_key)) return false; return pvt_key != nullptr; #else const BIGNUM* pvt_key = nullptr; @@ -2361,7 +2372,7 @@ DataPointer DHPointer::generateKeys() { DeleteFnPtr p; DeleteFnPtr g; DeleteFnPtr pub_key; - DeleteFnPtr pvt_key; + DeleteFnPtr pvt_key; if (!GetDhParams(dh_.get(), &p, &g) || !GetDhKeys(dh_.get(), &pub_key, &pvt_key)) { return {}; @@ -2496,9 +2507,8 @@ bool DHPointer::setPublicKey(BignumPointer&& key) { return true; } - DeleteFnPtr pub_key; - DeleteFnPtr pvt_key; - if (!GetDhKeys(dh_.get(), &pub_key, &pvt_key)) { + DeleteFnPtr pvt_key; + if (!GetDhKeys(dh_.get(), nullptr, &pvt_key)) { return false; } EVPKeyPointer pkey; @@ -2535,8 +2545,7 @@ bool DHPointer::setPrivateKey(BignumPointer&& key) { } DeleteFnPtr pub_key; - DeleteFnPtr pvt_key; - if (!GetDhKeys(dh_.get(), &pub_key, &pvt_key)) { + if (!GetDhKeys(dh_.get(), &pub_key, nullptr)) { return false; } EVPKeyPointer pkey; @@ -3537,12 +3546,13 @@ bool WriteEncryptedTraditionalPEM(BIO* bio, size_t der_len = 0; OSSLEncoderCtxPointer ctx(OSSL_ENCODER_CTX_new_for_pkey( pkey, OSSL_KEYMGMT_SELECT_KEYPAIR, "DER", "pkcs1", nullptr)); - if (!ctx || OSSL_ENCODER_to_data(ctx.get(), &der, &der_len) != 1) { - return false; - } + if (!ctx) return false; + + const int result = OSSL_ENCODER_to_data(ctx.get(), &der, &der_len); + DataPointer der_storage(der, der_len); + if (result != 1) return false; - OpenSSLBufferPointer der_storage(der); - DERView der_view{der_storage.get(), der_len}; + DERView der_view{der_storage.get(), der_len}; return PEM_ASN1_write_bio( WriteDERView, PEM_STRING_RSA, @@ -4977,7 +4987,7 @@ bool ECKeyPointer::generate() { if (EVP_PKEY_keygen(ctx.get(), &raw) != 1) return false; EVPKeyPointer pkey(raw); - DeleteFnPtr priv; + DeleteFnPtr priv; if (!GetPKeyBnParam(pkey.get(), OSSL_PKEY_PARAM_PRIV_KEY, &priv)) { return false; } diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h index 92b36a15dd62..e4a8f6b02208 100644 --- a/deps/ncrypto/ncrypto.h +++ b/deps/ncrypto/ncrypto.h @@ -641,12 +641,12 @@ class Rsa final { bool rsa_ = false; DeleteFnPtr n_; DeleteFnPtr e_; - DeleteFnPtr d_; - DeleteFnPtr p_; - DeleteFnPtr q_; - DeleteFnPtr dp_; - DeleteFnPtr dq_; - DeleteFnPtr qi_; + DeleteFnPtr d_; + DeleteFnPtr p_; + DeleteFnPtr q_; + DeleteFnPtr dp_; + DeleteFnPtr dq_; + DeleteFnPtr qi_; std::optional pss_params_; #else OSSL3_CONST RSA* rsa_; @@ -1634,7 +1634,7 @@ class ECKeyPointer final { #if NCRYPTO_USE_OPENSSL3_PROVIDER DeleteFnPtr group_; DeleteFnPtr pub_; - DeleteFnPtr priv_; + DeleteFnPtr priv_; #else DeleteFnPtr key_; #endif From f21f5e14feaa6cc3f85103d619f53a7e50a8efca Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Fri, 17 Jul 2026 00:22:02 +0200 Subject: [PATCH 06/16] crypto: preserve RSA-PSS legacy pubkey DER Recreate the historical rsaEncryption AlgorithmIdentifier used by X509Certificate legacy pubkey output for restricted and unrestricted RSA-PSS keys without relying on deprecated RSA APIs. Signed-off-by: Filip Skokan PR-URL: https://github.com/nodejs/node/pull/64547 Refs: https://github.com/nodejs/node/pull/64211 Reviewed-By: James M Snell Reviewed-By: Yagiz Nizipli --- deps/ncrypto/ncrypto.cc | 95 ++++++++++++++++++- deps/ncrypto/ncrypto.h | 1 + test/fixtures/keys/Makefile | 8 ++ test/fixtures/keys/rsa_pss_cert_2048.pem | 20 ++++ .../rsa_pss_cert_2048_sha256_sha256_16.pem | 22 +++++ test/parallel/test-crypto-x509.js | 37 ++++++++ 6 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/keys/rsa_pss_cert_2048.pem create mode 100644 test/fixtures/keys/rsa_pss_cert_2048_sha256_sha256_16.pem diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index 529afe4d718c..af29c0365091 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -93,7 +93,11 @@ const EVP_MD* GetDigestCtxMd(const EVP_MD_CTX* ctx) { } #if NCRYPTO_USE_OPENSSL3_PROVIDER +using ASN1StringPointer = DeleteFnPtr; using OSSLParamBldPointer = DeleteFnPtr; +using RsaPssParamsPointer = DeleteFnPtr; +using X509AlgorPointer = DeleteFnPtr; +using X509PubkeyPointer = DeleteFnPtr; struct OSSLParamDeleter { void operator()(OSSL_PARAM* params) const { if (params == nullptr) return; @@ -5702,6 +5706,66 @@ bool ReadRsaPssParams(const EVP_PKEY* pkey, Rsa::PssParams* params) { return true; } + +bool SetRsaPssHashAlgorithm(X509_ALGOR** out, const Digest& digest) { + if (EVP_MD_is_a(digest.get(), "SHA1")) return true; + + X509AlgorPointer algorithm(X509_ALGOR_new()); + if (!algorithm) return false; + X509_ALGOR_set_md(algorithm.get(), digest.get()); + *out = algorithm.release(); + return true; +} + +bool SetRsaPssMaskGenAlgorithm(X509_ALGOR** out, const Digest& digest) { + if (EVP_MD_is_a(digest.get(), "SHA1")) return true; + + X509AlgorPointer hash_algorithm(X509_ALGOR_new()); + if (!hash_algorithm) return false; + X509_ALGOR_set_md(hash_algorithm.get(), digest.get()); + + ASN1StringPointer hash_algorithm_der(ASN1_item_pack( + hash_algorithm.get(), ASN1_ITEM_rptr(X509_ALGOR), nullptr)); + if (!hash_algorithm_der) return false; + + X509AlgorPointer algorithm(X509_ALGOR_new()); + if (!algorithm || X509_ALGOR_set0(algorithm.get(), + OBJ_nid2obj(NID_mgf1), + V_ASN1_SEQUENCE, + hash_algorithm_der.get()) != 1) { + return false; + } + hash_algorithm_der.release(); + *out = algorithm.release(); + return true; +} + +ASN1StringPointer EncodeRsaPssParams(const Rsa::PssParams& params) { + const Digest digest = Digest::FromName(params.digest.data()); + if (!digest) return {}; + + const Digest mgf1_digest = params.mgf1_digest + ? Digest::FromName(params.mgf1_digest->data()) + : digest; + if (!mgf1_digest) return {}; + + RsaPssParamsPointer pss(RSA_PSS_PARAMS_new()); + if (!pss || !SetRsaPssHashAlgorithm(&pss->hashAlgorithm, digest) || + !SetRsaPssMaskGenAlgorithm(&pss->maskGenAlgorithm, mgf1_digest)) { + return {}; + } + + if (params.salt_length != 20) { + pss->saltLength = ASN1_INTEGER_new(); + if (pss->saltLength == nullptr || + ASN1_INTEGER_set_int64(pss->saltLength, params.salt_length) != 1) { + return {}; + } + } + + return ASN1StringPointer( + ASN1_item_pack(pss.get(), ASN1_ITEM_rptr(RSA_PSS_PARAMS), nullptr)); +} } // namespace Rsa::Rsa() : rsa_(false) {} @@ -5709,6 +5773,7 @@ Rsa::Rsa() : rsa_(false) {} Rsa::Rsa(const EVP_PKEY* pkey) : Rsa() { const int type = EVPKeyPointer::id(pkey); if (type != EVP_PKEY_RSA && type != EVP_PKEY_RSA_PSS) return; + rsa_pss_ = type == EVP_PKEY_RSA_PSS; if (!GetPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_N, &n_) || !GetPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_E, &e_)) { return; @@ -5804,7 +5869,35 @@ BIOPointer Rsa::derPublicKey() const { if (!bio) return {}; #if NCRYPTO_USE_OPENSSL3_PROVIDER auto pkey = EVPKeyPointer::NewRSA(*this); - if (!pkey || i2d_PUBKEY_bio(bio.get(), pkey.get()) != 1) return {}; + if (!pkey) return {}; + if (!rsa_pss_) { + if (i2d_PUBKEY_bio(bio.get(), pkey.get()) != 1) return {}; + return bio; + } + + X509_PUBKEY* raw_pubkey = nullptr; + const int result = X509_PUBKEY_set(&raw_pubkey, pkey.get()); + X509PubkeyPointer pubkey(raw_pubkey); + if (result != 1) return {}; + + int parameter_type = V_ASN1_UNDEF; + ASN1StringPointer parameters; + if (pss_params_) { + parameters = EncodeRsaPssParams(*pss_params_); + if (!parameters) return {}; + parameter_type = V_ASN1_SEQUENCE; + } + + if (X509_PUBKEY_set0_param(pubkey.get(), + OBJ_nid2obj(NID_rsaEncryption), + parameter_type, + parameters.get(), + nullptr, + 0) != 1) { + return {}; + } + parameters.release(); + if (i2d_X509_PUBKEY_bio(bio.get(), pubkey.get()) != 1) return {}; #else if (rsa_ == nullptr || i2d_RSA_PUBKEY_bio(bio.get(), rsa_) != 1) return {}; #endif diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h index e4a8f6b02208..6fb6b384fd4f 100644 --- a/deps/ncrypto/ncrypto.h +++ b/deps/ncrypto/ncrypto.h @@ -639,6 +639,7 @@ class Rsa final { private: #if NCRYPTO_USE_OPENSSL3_PROVIDER bool rsa_ = false; + bool rsa_pss_ = false; DeleteFnPtr n_; DeleteFnPtr e_; DeleteFnPtr d_; diff --git a/test/fixtures/keys/Makefile b/test/fixtures/keys/Makefile index c1e2fde9c387..128e928a5916 100644 --- a/test/fixtures/keys/Makefile +++ b/test/fixtures/keys/Makefile @@ -71,6 +71,8 @@ all: \ rsa_private_4096.pem \ rsa_public_2048.pem \ rsa_public_4096.pem \ + rsa_pss_cert_2048.pem \ + rsa_pss_cert_2048_sha256_sha256_16.pem \ rsa_pss_private_2048.pem \ rsa_pss_private_2048_sha256_sha256_16.pem \ rsa_pss_private_2048_sha512_sha256_20.pem \ @@ -918,6 +920,12 @@ rsa_pss_private_2048_sha512_sha256_20.pem: rsa_pss_private_2048_sha1_sha1_20.pem: openssl genpkey -algorithm RSA-PSS -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keygen_pubexp:65537 -pkeyopt rsa_pss_keygen_md:sha1 -pkeyopt rsa_pss_keygen_mgf1_md:sha1 -pkeyopt rsa_pss_keygen_saltlen:20 -out rsa_pss_private_2048_sha1_sha1_20.pem +rsa_pss_cert_2048.pem: rsa_pss_private_2048.pem + openssl req -new -x509 -key rsa_pss_private_2048.pem -subj "/CN=Node.js" -days 36500 -set_serial 1 -out rsa_pss_cert_2048.pem + +rsa_pss_cert_2048_sha256_sha256_16.pem: rsa_pss_private_2048_sha256_sha256_16.pem + openssl req -new -x509 -key rsa_pss_private_2048_sha256_sha256_16.pem -subj "/CN=Node.js" -days 36500 -set_serial 1 -out rsa_pss_cert_2048_sha256_sha256_16.pem + rsa_pss_public_2048.pem: rsa_pss_private_2048.pem openssl pkey -in rsa_pss_private_2048.pem -pubout -out rsa_pss_public_2048.pem diff --git a/test/fixtures/keys/rsa_pss_cert_2048.pem b/test/fixtures/keys/rsa_pss_cert_2048.pem new file mode 100644 index 000000000000..33834d7f0eb3 --- /dev/null +++ b/test/fixtures/keys/rsa_pss_cert_2048.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDWjCCAg6gAwIBAgIBATBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQCAQUA +oRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAwEjEQMA4GA1UEAwwH +Tm9kZS5qczAgFw0yNjA3MTYyMjE4MzJaGA8yMTI2MDYyMjIyMTgzMlowEjEQMA4G +A1UEAwwHTm9kZS5qczCCASAwCwYJKoZIhvcNAQEKA4IBDwAwggEKAoIBAQDLg4x1 +Lzg+WAjkJEtt1u531G7u+msWvZg01SZ1PsPdiACmK/wKPMpcuJUOdswRP6XCa1qD +8RxmzFUIqi+y7fSlgBIGuIc6IxVi6L35lomhD3IjqU6MbVFu5QElc1KMcEnGoGqZ +yBk5vWZ1Gv95fOkAxYneF+4uiNTb7NRInf+u96l6lwsUbar2cDT48myQns+eHcvT +J2e/dTeF24yfk5V+b8nHUU/JmkPXzi0l9/434xjezeEhx+46cv0+XhE8Z5pMLhPW +jjrQ1obpsbtzZaGDo05pWBoUhqI1utNM0KlWN5PJqWxNdiVBDeGIqOy3RDul8USQ +kOjyqKotp3UyyXn3AgMBAAGjUzBRMB0GA1UdDgQWBBQDCGflRn9LTmklPpSFii4J +fQiQjTAfBgNVHSMEGDAWgBQDCGflRn9LTmklPpSFii4JfQiQjTAPBgNVHRMBAf8E +BTADAQH/MEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIBBQChHDAaBgkqhkiG +9w0BAQgwDQYJYIZIAWUDBAIBBQCiAwIBIAOCAQEAsXJk8iu+XNSmIheNPvzfSoNp +7kkFqFjWRwo/a4vXb4Y7DRDsMeUM4dGNtN75Gj5bppnYtQ3ku/DWGLrVRPGs/sRx +itOJxvJzPNCv4yYUwm8p8ua7l1Sdmrder84XTG/Dm3dOoUOxCmEQzzjghXE0mRih +UC4aPyOGNHNlQg5YyEmfEnltlL12ls8cHE+5R56xeI34T/MukH0NVf7wNNfqrMqy +PrrXG79+D3+Jeu3ddtwvoKQyQ/VuszJGjdplkDodttyTfu3GOgC/bbR9iz2GkcTV +NEkjJHenMzpoAoNZukeuClOHF6NT3hBnKPmcZYlLPgIY1iOBYoGwctgb5pPxNg== +-----END CERTIFICATE----- diff --git a/test/fixtures/keys/rsa_pss_cert_2048_sha256_sha256_16.pem b/test/fixtures/keys/rsa_pss_cert_2048_sha256_sha256_16.pem new file mode 100644 index 000000000000..5740ae9cff06 --- /dev/null +++ b/test/fixtures/keys/rsa_pss_cert_2048_sha256_sha256_16.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDkDCCAkSgAwIBAgIBATBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQCAQUA +oRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCARAwEjEQMA4GA1UEAwwH +Tm9kZS5qczAgFw0yNjA3MTYyMjAyMzVaGA8yMTI2MDYyMjIyMDIzNVowEjEQMA4G +A1UEAwwHTm9kZS5qczCCAVYwQQYJKoZIhvcNAQEKMDSgDzANBglghkgBZQMEAgEF +AKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEFAKIDAgEQA4IBDwAwggEKAoIB +AQDfqNM4C+QtD73iILqOkqfV8ha3O19jpX8UujIk1Z72bbbuwEzh0+sBw0dD0N8C +gkXnePOEEd6q7HNmbyCNqRpDK6NDvaCMDWgEaD/PlHkRntvKh81IXSMC5imjRfOc +ZIE/Gnw7h8tanab0n75+ODvLJrmEWUG2q79Im1mWMx7Spod+Np6XEY+7I7nAUUWi +vr35Yx5DeyxY8rxFGpsLtGsi7JNQO4aHyeBpj8tz0Fhv23uPywE2nGmPHfnkXWbr +TcHGbzYBgEbeSH9KUkRwczqDXNOPhtfaEHEFTm0MoeKCnJe1VOjSywev77dV1KZf +pVh3Kh0ZRQIe9YOVJhj4lMx3AgMBAAGjUzBRMB0GA1UdDgQWBBRrGC6N4gEM/Il9 +PMkKs+dhCbldkjAfBgNVHSMEGDAWgBRrGC6N4gEM/Il9PMkKs+dhCbldkjAPBgNV +HRMBAf8EBTADAQH/MEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIBBQChHDAa +BgkqhkiG9w0BAQgwDQYJYIZIAWUDBAIBBQCiAwIBEAOCAQEA1SP4HNSgQm2OpuRb +VjEDyVANpCyuKjUFY0FAwart4YSKHwERuR5VccugGwlbnH0vKQ2uwTB/a5BlXssg +0KB8qjDoOoxzhgZTA7yuHuQLpgCQhd00ORpxFYI1CyB+HywrpbuV4LHJoZjO7B6O +Z0PPRs77023WkT/O4KRrFH8uaMgPMwUIjIGXzSp+TYObLnYTWmGyJ7rJVwOPH30z ++BY++W0ZhkCuJveajuF3vGpA4o/lA0kcf6Hz2Prl28kHu0hOpZNTx2zMl+GFtlWE +TIybjAvbA4TJW0rAURRuP4lhYsPJAnZDAnxHFvLeLKypfctv7eAc82U8rVjT+DIA +NjayPQ== +-----END CERTIFICATE----- diff --git a/test/parallel/test-crypto-x509.js b/test/parallel/test-crypto-x509.js index a122ee9e300f..353699cf9117 100644 --- a/test/parallel/test-crypto-x509.js +++ b/test/parallel/test-crypto-x509.js @@ -7,6 +7,7 @@ if (!common.hasCrypto) const { X509Certificate, + createHash, createPrivateKey, generateKeyPairSync, createSign, @@ -27,6 +28,42 @@ const ca = readFileSync(fixtures.path('keys', 'ca1-cert.pem')); const privateKey = createPrivateKey(key); +if (!process.features.openssl_is_boringssl) { + const expectedPubkeys = hasOpenSSL3 ? [ + [ + 'rsa_pss_cert_2048.pem', + 292, + 'dff998a209bfa2e6ded1208c6e57f5b6bdedfa44b631265e3e244f38e637f6e4', + ], + [ + 'rsa_pss_cert_2048_sha256_sha256_16.pem', + 342, + 'da0bcd53fbe3969c7cc2730f86abc34e0e1c340264bbdfa3faf01484c2eeece0', + ], + ] : [ + [ + 'rsa_pss_cert_2048.pem', + 294, + '4d4f2f076aced4f0df922b84b466b0a60ba4cb50a23d695ae12ddc5fff7aca14', + ], + [ + 'rsa_pss_cert_2048_sha256_sha256_16.pem', + 294, + 'd37942c3bd02bc25c724fcd31efd647824e536c13d62d9ad0b5db8c0900d3cba', + ], + ]; + + for (const [name, length, digest] of expectedPubkeys) { + const pssCert = new X509Certificate( + readFileSync(fixtures.path('keys', name))); + const pubkey = pssCert.toLegacyObject().pubkey; + assert.strictEqual(pubkey.length, length); + assert.strictEqual( + createHash('sha256').update(pubkey).digest('hex'), + digest); + } +} + [1, {}, false, null].forEach((i) => { assert.throws(() => new X509Certificate(i), { code: 'ERR_INVALID_ARG_TYPE' From c01e28c9a276cff6f81d3a2d9a93caa0065ea72d Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Tue, 28 Jul 2026 15:02:45 +0200 Subject: [PATCH 07/16] crypto: handle DH operation failures Report DH failures instead of aborting or returning an empty secret. Signed-off-by: Filip Skokan PR-URL: https://github.com/nodejs/node/pull/64851 Reviewed-By: James M Snell Reviewed-By: Luigi Pinca --- src/crypto/crypto_dh.cc | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/crypto/crypto_dh.cc b/src/crypto/crypto_dh.cc index 59781e7488f5..40f21fcc3437 100644 --- a/src/crypto/crypto_dh.cc +++ b/src/crypto/crypto_dh.cc @@ -332,6 +332,10 @@ void ComputeSecret(const FunctionCallbackInfo& args) { } auto dp = dh.computeSecret(key); + if (!dp) { + return THROW_ERR_CRYPTO_OPERATION_FAILED(env, + "Failed to compute shared secret"); + } Local buffer; if (DataPointerToBuffer(env, std::move(dp)).ToLocal(&buffer)) { @@ -349,8 +353,8 @@ void SetPublicKey(const FunctionCallbackInfo& args) { if (!buf.CheckSizeInt32()) [[unlikely]] return THROW_ERR_OUT_OF_RANGE(env, "buf is too big"); BignumPointer num(buf.data(), buf.size()); - CHECK(num); - CHECK(dh.setPublicKey(std::move(num))); + if (!num || !dh.setPublicKey(std::move(num))) + return THROW_ERR_INVALID_ARG_VALUE(env, "Invalid public key"); } void SetPrivateKey(const FunctionCallbackInfo& args) { @@ -363,8 +367,8 @@ void SetPrivateKey(const FunctionCallbackInfo& args) { if (!buf.CheckSizeInt32()) [[unlikely]] return THROW_ERR_OUT_OF_RANGE(env, "buf is too big"); BignumPointer num(buf.data(), buf.size()); - CHECK(num); - CHECK(dh.setPrivateKey(std::move(num))); + if (!num || !dh.setPrivateKey(std::move(num))) + return THROW_ERR_INVALID_ARG_VALUE(env, "Invalid private key"); } void Check(const FunctionCallbackInfo& args) { From cdea4cc7a9b91888ef5b4d056d5f8c9cc273d8b5 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Tue, 28 Jul 2026 15:02:46 +0200 Subject: [PATCH 08/16] crypto: initialize KeyObjectData mutex eagerly Create the mutex before sharing key data so every copy uses one lock. Signed-off-by: Filip Skokan PR-URL: https://github.com/nodejs/node/pull/64851 Reviewed-By: James M Snell Reviewed-By: Luigi Pinca --- src/crypto/crypto_keys.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/crypto/crypto_keys.cc b/src/crypto/crypto_keys.cc index 326d47023e4a..3021fe585240 100644 --- a/src/crypto/crypto_keys.cc +++ b/src/crypto/crypto_keys.cc @@ -953,10 +953,13 @@ KeyObjectData::KeyObjectData(std::nullptr_t) KeyObjectData::KeyObjectData(ByteSource symmetric_key) : key_type_(KeyType::kKeyTypeSecret), + mutex_(std::make_shared()), data_(std::make_shared(std::move(symmetric_key))) {} KeyObjectData::KeyObjectData(KeyType type, EVPKeyPointer&& pkey) - : key_type_(type), data_(std::make_shared(std::move(pkey))) {} + : key_type_(type), + mutex_(std::make_shared()), + data_(std::make_shared(std::move(pkey))) {} void KeyObjectData::MemoryInfo(MemoryTracker* tracker) const { if (!*this) return; From 097dbaaeec2fde66813c25120a4b0ce81efb2033 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Tue, 28 Jul 2026 14:36:59 +0200 Subject: [PATCH 09/16] crypto: fix Argon2 validation errors Report the correct parameter name for invalid optional inputs. Signed-off-by: Filip Skokan PR-URL: https://github.com/nodejs/node/pull/64852 Reviewed-By: James M Snell Reviewed-By: Yagiz Nizipli --- lib/internal/crypto/argon2.js | 4 ++-- test/parallel/test-crypto-argon2.js | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/internal/crypto/argon2.js b/lib/internal/crypto/argon2.js index 6d9f9e462d01..bf9eaedd85a6 100644 --- a/lib/internal/crypto/argon2.js +++ b/lib/internal/crypto/argon2.js @@ -169,7 +169,7 @@ function check(algorithm, parameters) { if (parameters.secret === undefined) { secret = new Uint8Array(0); } else { - secret = getArrayBufferOrView(parameters.secret); + secret = getArrayBufferOrView(parameters.secret, 'parameters.secret'); validateInteger(secret.byteLength, 'parameters.secret.byteLength', 0, MAX_POSITIVE_UINT_32); } @@ -177,7 +177,7 @@ function check(algorithm, parameters) { if (parameters.associatedData === undefined) { associatedData = new Uint8Array(0); } else { - associatedData = getArrayBufferOrView(parameters.associatedData); + associatedData = getArrayBufferOrView(parameters.associatedData, 'parameters.associatedData'); validateInteger(associatedData.byteLength, 'parameters.associatedData.byteLength', 0, MAX_POSITIVE_UINT_32); } diff --git a/test/parallel/test-crypto-argon2.js b/test/parallel/test-crypto-argon2.js index 2137bf345d4a..1f238e61a61d 100644 --- a/test/parallel/test-crypto-argon2.js +++ b/test/parallel/test-crypto-argon2.js @@ -140,6 +140,18 @@ for (const key of Object.keys(defaults)) { assert.throws(() => crypto.argon2Sync('argon2id', parameters), expected); } +for (const key of ['secret', 'associatedData']) { + const expected = { + code: 'ERR_INVALID_ARG_TYPE', + message: new RegExp(`"parameters\\.${key}"`), + }; + for (const value of [123, null, true, {}, []]) { + const parameters = { ...defaults, [key]: value }; + assert.throws(() => crypto.argon2('argon2id', parameters, () => {}), expected); + assert.throws(() => crypto.argon2Sync('argon2id', parameters), expected); + } +} + { const expected = { code: 'ERR_INVALID_ARG_TYPE' }; assert.throws(() => crypto.argon2(), expected); From febd7dfb2ebd1f5ad6d910f4b8431b78aa7d8d5c Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Tue, 28 Jul 2026 14:38:19 +0200 Subject: [PATCH 10/16] crypto: validate key generation options Reject null options with ERR_INVALID_ARG_TYPE. Signed-off-by: Filip Skokan PR-URL: https://github.com/nodejs/node/pull/64852 Reviewed-By: James M Snell Reviewed-By: Yagiz Nizipli --- lib/internal/crypto/keygen.js | 4 ++-- test/parallel/test-crypto-keygen.js | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/internal/crypto/keygen.js b/lib/internal/crypto/keygen.js index 553aca2daa48..fe1e87c94299 100644 --- a/lib/internal/crypto/keygen.js +++ b/lib/internal/crypto/keygen.js @@ -209,11 +209,11 @@ const nidOnlyKeyPairs = { function createJob(mode, type, options) { validateString(type, 'type'); - const encoding = new SafeArrayIterator(parseKeyEncoding(type, options)); - if (options !== undefined) validateObject(options, 'options'); + const encoding = new SafeArrayIterator(parseKeyEncoding(type, options)); + switch (type) { case 'rsa': case 'rsa-pss': diff --git a/test/parallel/test-crypto-keygen.js b/test/parallel/test-crypto-keygen.js index 7dddfcab275d..206d6f7a84b5 100644 --- a/test/parallel/test-crypto-keygen.js +++ b/test/parallel/test-crypto-keygen.js @@ -89,6 +89,21 @@ const isBoringSSL = process.features.openssl_is_boringssl; message: 'The "options" argument must be of type object. ' + 'Received type number (0)' }); + + for (const type of ['rsa', 'ed25519']) { + assert.throws(() => generateKeyPairSync(type, null), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "options" argument must be of type object. ' + + 'Received null' + }); + assert.throws(() => generateKeyPair(type, null, common.mustNotCall()), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "options" argument must be of type object. ' + + 'Received null' + }); + } } { From 04eb8824f94258d6a5b0f1aeb0421f93c0c09948 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Tue, 28 Jul 2026 15:00:25 +0200 Subject: [PATCH 11/16] crypto: reuse X509 issuer result Avoid a second native call when reading the issuer certificate. Signed-off-by: Filip Skokan PR-URL: https://github.com/nodejs/node/pull/64852 Reviewed-By: James M Snell Reviewed-By: Yagiz Nizipli --- lib/internal/crypto/x509.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/crypto/x509.js b/lib/internal/crypto/x509.js index cd5b5457e3ca..a75425ffcac8 100644 --- a/lib/internal/crypto/x509.js +++ b/lib/internal/crypto/x509.js @@ -193,7 +193,7 @@ class X509Certificate { if (value === undefined) { const cert = this[kHandle].getIssuerCert(); if (cert) - value = new InternalX509Certificate(this[kHandle].getIssuerCert()); + value = new InternalX509Certificate(cert); this[kInternalState].set('issuerCertificate', value); } return value; From 17ea1162138d5e6ea03f3b4f6b7262d7792fea68 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Tue, 28 Jul 2026 15:00:41 +0200 Subject: [PATCH 12/16] crypto: clarify missing cipher error Explain that a cipher is required when a passphrase is provided. Signed-off-by: Filip Skokan PR-URL: https://github.com/nodejs/node/pull/64852 Reviewed-By: James M Snell Reviewed-By: Yagiz Nizipli --- lib/internal/crypto/keys.js | 4 +++- test/parallel/test-crypto-key-objects.js | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/internal/crypto/keys.js b/lib/internal/crypto/keys.js index 9c6c238c00e1..b63b537620f8 100644 --- a/lib/internal/crypto/keys.js +++ b/lib/internal/crypto/keys.js @@ -479,7 +479,9 @@ function parseKeyEncoding(enc, keyType, isPublic, objName) { encodingNames[type], 'does not support encryption'); } } else if (passphrase !== undefined) { - throw new ERR_INVALID_ARG_VALUE(option('cipher', objName), cipher); + throw new ERR_INVALID_ARG_VALUE( + option('cipher', objName), cipher, + 'is required when a passphrase is specified'); } } diff --git a/test/parallel/test-crypto-key-objects.js b/test/parallel/test-crypto-key-objects.js index 0ece0f17339a..cfcd50f12a3e 100644 --- a/test/parallel/test-crypto-key-objects.js +++ b/test/parallel/test-crypto-key-objects.js @@ -1025,7 +1025,8 @@ if (!process.features.openssl_is_boringssl) { }, { name: 'TypeError', code: 'ERR_INVALID_ARG_VALUE', - message: "The property 'options.cipher' is invalid. Received undefined" + message: "The property 'options.cipher' is required when a " + + 'passphrase is specified. Received undefined', }); } From ba2bdc868a124c3546dc5ade9759df062dd7121c Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Tue, 21 Jul 2026 17:46:39 +0200 Subject: [PATCH 13/16] src: avoid redundant KEM encapsulation copies KEM encapsulation produces separate ciphertext and shared-secret allocations. The existing DeriveBitsJob path packs both values into an intermediate buffer, then copies them again into separate buffers. Instead, this uses a dedicated KEMEncapsulateJob to retain both outputs across the worker boundary and convert each directly through ByteSource. This removes the intermediate allocation and at least one complete round of copies. Signed-off-by: Filip Skokan PR-URL: https://github.com/nodejs/node/pull/64553 Reviewed-By: James M Snell Reviewed-By: Yagiz Nizipli --- src/crypto/crypto_kem.cc | 190 +++++++++++++++++++-------------------- src/crypto/crypto_kem.h | 30 +++++-- 2 files changed, 116 insertions(+), 104 deletions(-) diff --git a/src/crypto/crypto_kem.cc b/src/crypto/crypto_kem.cc index c14866e6af56..b2bcb4b2cc6d 100644 --- a/src/crypto/crypto_kem.cc +++ b/src/crypto/crypto_kem.cc @@ -8,7 +8,6 @@ #include "crypto/crypto_util.h" #include "env-inl.h" #include "memory_tracker-inl.h" -#include "node_buffer.h" #include "threadpoolwork-inl.h" #include "v8.h" @@ -16,13 +15,13 @@ namespace node { using ncrypto::EVPKeyPointer; using v8::Array; -using v8::ArrayBufferView; using v8::FunctionCallbackInfo; using v8::Local; using v8::Maybe; using v8::MaybeLocal; using v8::Nothing; using v8::Object; +using v8::Uint8Array; using v8::Value; namespace crypto { @@ -49,51 +48,6 @@ void KEMConfiguration::MemoryInfo(MemoryTracker* tracker) const { namespace { -bool DoKEMEncapsulate(Environment* env, - const EVPKeyPointer& public_key, - ByteSource* out, - CryptoJobMode mode) { - auto result = ncrypto::KEM::Encapsulate(public_key); - if (!result) { - if (mode == kCryptoJobSync) { - THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to perform encapsulation"); - } - return false; - } - - // Pack the result: [ciphertext_len][shared_key_len][ciphertext][shared_key] - size_t ciphertext_len = result->ciphertext.size(); - size_t shared_key_len = result->shared_key.size(); - size_t total_len = - sizeof(uint32_t) + sizeof(uint32_t) + ciphertext_len + shared_key_len; - - auto data = ncrypto::DataPointer::Alloc(total_len); - if (!data) { - if (mode == kCryptoJobSync) { - THROW_ERR_CRYPTO_OPERATION_FAILED(env, - "Failed to allocate output buffer"); - } - return false; - } - - unsigned char* ptr = static_cast(data.get()); - - // Write size headers - *reinterpret_cast(ptr) = static_cast(ciphertext_len); - *reinterpret_cast(ptr + sizeof(uint32_t)) = - static_cast(shared_key_len); - - // Write ciphertext and shared key data - unsigned char* ciphertext_ptr = ptr + 2 * sizeof(uint32_t); - unsigned char* shared_key_ptr = ciphertext_ptr + ciphertext_len; - - std::memcpy(ciphertext_ptr, result->ciphertext.get(), ciphertext_len); - std::memcpy(shared_key_ptr, result->shared_key.get(), shared_key_len); - - *out = ByteSource::Allocated(data.release()); - return true; -} - bool DoKEMDecapsulate(Environment* env, const EVPKeyPointer& private_key, const ByteSource& ciphertext, @@ -135,71 +89,115 @@ Maybe KEMEncapsulateTraits::AdditionalConfig( return v8::JustVoid(); } -bool KEMEncapsulateTraits::DeriveBits(Environment* env, - const KEMConfiguration& params, - ByteSource* out, - CryptoJobMode mode) { - Mutex::ScopedLock lock(params.key.mutex()); - const auto& public_key = params.key.GetAsymmetricKey(); +void KEMEncapsulateJob::New(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CHECK(args.IsConstructCall()); + + CryptoJobMode mode = GetCryptoJobMode(args[0]); + AdditionalParams params; + if (KEMEncapsulateTraits::AdditionalConfig(mode, args, 1, ¶ms) + .IsNothing()) { + return; + } - return DoKEMEncapsulate(env, public_key, out, mode); + new KEMEncapsulateJob(env, args.This(), mode, std::move(params)); } -MaybeLocal KEMEncapsulateTraits::EncodeOutput( - Environment* env, const KEMConfiguration& params, ByteSource* out) { - // The output contains: - // [ciphertext_len][shared_key_len][ciphertext][shared_key] - const unsigned char* data = out->data(); - - uint32_t ciphertext_len = *reinterpret_cast(data); - uint32_t shared_key_len = - *reinterpret_cast(data + sizeof(uint32_t)); - - const unsigned char* ciphertext_ptr = data + 2 * sizeof(uint32_t); - const unsigned char* shared_key_ptr = ciphertext_ptr + ciphertext_len; - - MaybeLocal ciphertext_buf = - node::Buffer::Copy(env->isolate(), - reinterpret_cast(ciphertext_ptr), - ciphertext_len); - - MaybeLocal shared_key_buf = - node::Buffer::Copy(env->isolate(), - reinterpret_cast(shared_key_ptr), - shared_key_len); - - Local ciphertext_obj; - Local shared_key_obj; - if (!ciphertext_buf.ToLocal(&ciphertext_obj) || - !shared_key_buf.ToLocal(&shared_key_obj)) { - return MaybeLocal(); +void KEMEncapsulateJob::Initialize(Environment* env, Local target) { + CryptoJob::Initialize(New, env, target); +} + +void KEMEncapsulateJob::RegisterExternalReferences( + ExternalReferenceRegistry* registry) { + CryptoJob::RegisterExternalReferences(New, registry); +} + +KEMEncapsulateJob::KEMEncapsulateJob(Environment* env, + Local object, + CryptoJobMode mode, + AdditionalParams&& params) + : CryptoJob(env, + object, + KEMEncapsulateTraits::Provider, + mode, + std::move(params)) {} + +void KEMEncapsulateJob::DoThreadPoolWork() { + ncrypto::ClearErrorOnReturn clear_error_on_return; + AdditionalParams* params = CryptoJob::params(); + Mutex::ScopedLock lock(params->key.mutex()); + out_ = ncrypto::KEM::Encapsulate(params->key.GetAsymmetricKey()); + if (!out_) { + if (mode() == kCryptoJobSync) { + THROW_ERR_CRYPTO_OPERATION_FAILED(AsyncWrap::env(), + "Failed to perform encapsulation"); + } + CryptoErrorStore* errors = CryptoJob::errors(); + errors->Capture(); + if (errors->Empty()) { + errors->Insert(NodeCryptoError::DERIVING_BITS_FAILED); + } } +} - if (params.job_mode == kCryptoJobWebCrypto) { - Local result = Object::New(env->isolate()); - if (!result +Maybe KEMEncapsulateJob::ToResult(Local* err, + Local* result) { + Environment* env = AsyncWrap::env(); + CryptoErrorStore* errors = CryptoJob::errors(); + if (!out_) { + if (errors->Empty()) errors->Capture(); + CHECK(!errors->Empty()); + *result = v8::Undefined(env->isolate()); + if (!errors->ToException(env).ToLocal(err)) return Nothing(); + return v8::JustVoid(); + } + + CHECK(errors->Empty()); + *err = v8::Undefined(env->isolate()); + + ByteSource ciphertext = ByteSource::Allocated(out_->ciphertext.release()); + ByteSource shared_key = ByteSource::Allocated(out_->shared_key.release()); + + if (mode() == kCryptoJobWebCrypto) { + Local output = Object::New(env->isolate()); + if (!output ->DefineOwnProperty(env->context(), OneByteString(env->isolate(), "sharedKey"), - shared_key_obj.As()->Buffer()) + shared_key.ToArrayBuffer(env)) .FromMaybe(false) || - !result + !output ->DefineOwnProperty(env->context(), OneByteString(env->isolate(), "ciphertext"), - ciphertext_obj.As()->Buffer()) + ciphertext.ToArrayBuffer(env)) .FromMaybe(false)) { - return MaybeLocal(); + return Nothing(); } - return result; + *result = output; + return v8::JustVoid(); } - // Return an array [sharedKey, ciphertext]. - Local result = Array::New(env->isolate(), 2); - if (result->Set(env->context(), 0, shared_key_obj).IsNothing() || - result->Set(env->context(), 1, ciphertext_obj).IsNothing()) { - return MaybeLocal(); + Local shared_key_buf; + Local ciphertext_buf; + if (!shared_key.ToBuffer(env).ToLocal(&shared_key_buf) || + !ciphertext.ToBuffer(env).ToLocal(&ciphertext_buf)) { + return Nothing(); } - return result; + Local output = Array::New(env->isolate(), 2); + if (output->Set(env->context(), 0, shared_key_buf).IsNothing() || + output->Set(env->context(), 1, ciphertext_buf).IsNothing()) { + return Nothing(); + } + *result = output; + return v8::JustVoid(); +} + +void KEMEncapsulateJob::MemoryInfo(MemoryTracker* tracker) const { + if (out_) { + tracker->TrackFieldWithSize("ciphertext", out_->ciphertext.size()); + tracker->TrackFieldWithSize("shared_key", out_->shared_key.size()); + } + CryptoJob::MemoryInfo(tracker); } // KEMDecapsulateTraits implementation diff --git a/src/crypto/crypto_kem.h b/src/crypto/crypto_kem.h index d4eb56ea4027..b166bd35b586 100644 --- a/src/crypto/crypto_kem.h +++ b/src/crypto/crypto_kem.h @@ -44,15 +44,30 @@ struct KEMEncapsulateTraits final { const v8::FunctionCallbackInfo& args, unsigned int offset, KEMConfiguration* params); +}; - static bool DeriveBits(Environment* env, - const KEMConfiguration& params, - ByteSource* out, - CryptoJobMode mode); +class KEMEncapsulateJob final : public CryptoJob { + public: + using AdditionalParams = KEMEncapsulateTraits::AdditionalParameters; - static v8::MaybeLocal EncodeOutput(Environment* env, - const KEMConfiguration& params, - ByteSource* out); + static void New(const v8::FunctionCallbackInfo& args); + static void Initialize(Environment* env, v8::Local target); + static void RegisterExternalReferences(ExternalReferenceRegistry* registry); + + KEMEncapsulateJob(Environment* env, + v8::Local object, + CryptoJobMode mode, + AdditionalParams&& params); + + void DoThreadPoolWork() override; + v8::Maybe ToResult(v8::Local* err, + v8::Local* result) override; + + SET_SELF_SIZE(KEMEncapsulateJob) + void MemoryInfo(MemoryTracker* tracker) const override; + + private: + std::optional out_; }; struct KEMDecapsulateTraits final { @@ -78,7 +93,6 @@ struct KEMDecapsulateTraits final { ByteSource* out); }; -using KEMEncapsulateJob = DeriveBitsJob; using KEMDecapsulateJob = DeriveBitsJob; void InitializeKEM(Environment* env, v8::Local target); From dabb485213df34d91088e777974d8e41aa32e8f6 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Wed, 29 Jul 2026 19:48:46 +0200 Subject: [PATCH 14/16] src: implement MemoryRetainer protocol for ByteSource Signed-off-by: Filip Skokan PR-URL: https://github.com/nodejs/node/pull/64660 Reviewed-By: James M Snell --- src/crypto/crypto_aes.cc | 13 +++------- src/crypto/crypto_aes.h | 1 - src/crypto/crypto_argon2.cc | 19 ++++++-------- src/crypto/crypto_argon2.h | 1 - src/crypto/crypto_chacha20_poly1305.cc | 12 +++------ src/crypto/crypto_chacha20_poly1305.h | 1 - src/crypto/crypto_cipher.h | 5 ++-- src/crypto/crypto_hash.cc | 26 ++++++------------- src/crypto/crypto_hash.h | 2 -- src/crypto/crypto_hkdf.cc | 18 ++++++------- src/crypto/crypto_hkdf.h | 1 - src/crypto/crypto_hmac.cc | 12 +++------ src/crypto/crypto_hmac.h | 1 - src/crypto/crypto_kem.cc | 31 +++++++++-------------- src/crypto/crypto_kem.h | 9 ++++--- src/crypto/crypto_keygen.cc | 2 +- src/crypto/crypto_keys.cc | 35 +++++++++----------------- src/crypto/crypto_keys.h | 8 ++++-- src/crypto/crypto_kmac.cc | 14 +++-------- src/crypto/crypto_kmac.h | 1 - src/crypto/crypto_pbkdf2.cc | 16 +++++------- src/crypto/crypto_pbkdf2.h | 1 - src/crypto/crypto_rsa.cc | 7 ++---- src/crypto/crypto_rsa.h | 1 - src/crypto/crypto_scrypt.cc | 25 ++++++++---------- src/crypto/crypto_scrypt.h | 1 - src/crypto/crypto_sig.cc | 13 +++------- src/crypto/crypto_sig.h | 1 - src/crypto/crypto_turboshake.cc | 22 ++++------------ src/crypto/crypto_turboshake.h | 2 -- src/crypto/crypto_util.cc | 18 +++++++++++++ src/crypto/crypto_util.h | 17 ++++++++++++- 32 files changed, 135 insertions(+), 201 deletions(-) diff --git a/src/crypto/crypto_aes.cc b/src/crypto/crypto_aes.cc index b7b3a0517d1d..171688b92926 100644 --- a/src/crypto/crypto_aes.cc +++ b/src/crypto/crypto_aes.cc @@ -491,8 +491,7 @@ void UseDefaultIV(AESCipherConfig* params) { } // namespace AESCipherConfig::AESCipherConfig(AESCipherConfig&& other) noexcept - : mode(other.mode), - variant(other.variant), + : variant(other.variant), cipher(other.cipher), length(other.length), iv(std::move(other.iv)), @@ -505,12 +504,8 @@ AESCipherConfig& AESCipherConfig::operator=(AESCipherConfig&& other) noexcept { } void AESCipherConfig::MemoryInfo(MemoryTracker* tracker) const { - // If mode is sync, then the data in each of these properties - // is not owned by the AESCipherConfig, so we ignore it. - if (IsCryptoJobAsync(mode)) { - tracker->TrackFieldWithSize("iv", iv.size()); - tracker->TrackFieldWithSize("additional_data", additional_data.size()); - } + tracker->TraitTrackInline(iv, "iv"); + tracker->TraitTrackInline(additional_data, "additional_data"); } Maybe AESCipherTraits::AdditionalConfig( @@ -521,8 +516,6 @@ Maybe AESCipherTraits::AdditionalConfig( AESCipherConfig* params) { Environment* env = Environment::GetCurrent(args); - params->mode = mode; - CHECK(args[offset]->IsUint32()); // Key Variant params->variant = static_cast(args[offset].As()->Value()); diff --git a/src/crypto/crypto_aes.h b/src/crypto/crypto_aes.h index 401e7b2c338a..76359f14e7df 100644 --- a/src/crypto/crypto_aes.h +++ b/src/crypto/crypto_aes.h @@ -58,7 +58,6 @@ enum class AESKeyVariant { }; struct AESCipherConfig final : public MemoryRetainer { - CryptoJobMode mode; AESKeyVariant variant; ncrypto::Cipher cipher; size_t length; diff --git a/src/crypto/crypto_argon2.cc b/src/crypto/crypto_argon2.cc index 3dcee3fed88a..27168e2899bd 100644 --- a/src/crypto/crypto_argon2.cc +++ b/src/crypto/crypto_argon2.cc @@ -21,8 +21,7 @@ using v8::Uint32; using v8::Value; Argon2Config::Argon2Config(Argon2Config&& other) noexcept - : mode{other.mode}, - key{std::move(other.key)}, + : key{std::move(other.key)}, pass{std::move(other.pass)}, salt{std::move(other.salt)}, secret{std::move(other.secret)}, @@ -40,13 +39,13 @@ Argon2Config& Argon2Config::operator=(Argon2Config&& other) noexcept { } void Argon2Config::MemoryInfo(MemoryTracker* tracker) const { - if (key) tracker->TrackField("key", key); - if (IsCryptoJobAsync(mode)) { - if (!key) tracker->TrackFieldWithSize("pass", pass.size()); - tracker->TrackFieldWithSize("salt", salt.size()); - tracker->TrackFieldWithSize("secret", secret.size()); - tracker->TrackFieldWithSize("ad", ad.size()); - } + if (key) + tracker->TrackField("key", key); + else + tracker->TraitTrackInline(pass, "pass"); + tracker->TraitTrackInline(salt, "salt"); + tracker->TraitTrackInline(secret, "secret"); + tracker->TraitTrackInline(ad, "ad"); } MaybeLocal Argon2Traits::EncodeOutput(Environment* env, @@ -62,8 +61,6 @@ Maybe Argon2Traits::AdditionalConfig( Argon2Config* config) { Environment* env = Environment::GetCurrent(args); - config->mode = mode; - CHECK(KeyObjectHandle::HasInstance(env, args[offset]) || IsAnyBufferSource(args[offset])); // pass ArrayBufferOrViewContents salt(args[offset + 1]); diff --git a/src/crypto/crypto_argon2.h b/src/crypto/crypto_argon2.h index 71f5def67b65..676fd60cf04f 100644 --- a/src/crypto/crypto_argon2.h +++ b/src/crypto/crypto_argon2.h @@ -22,7 +22,6 @@ namespace node::crypto { // at least 16 bytes in length. struct Argon2Config final : public MemoryRetainer { - CryptoJobMode mode; KeyObjectData key; ByteSource pass; ByteSource salt; diff --git a/src/crypto/crypto_chacha20_poly1305.cc b/src/crypto/crypto_chacha20_poly1305.cc index db45ae356b6b..1cdb933c65d4 100644 --- a/src/crypto/crypto_chacha20_poly1305.cc +++ b/src/crypto/crypto_chacha20_poly1305.cc @@ -81,8 +81,7 @@ bool ValidateAdditionalData(Environment* env, ChaCha20Poly1305CipherConfig::ChaCha20Poly1305CipherConfig( ChaCha20Poly1305CipherConfig&& other) noexcept - : mode(other.mode), - cipher(other.cipher), + : cipher(other.cipher), iv(std::move(other.iv)), additional_data(std::move(other.additional_data)) {} @@ -94,12 +93,8 @@ ChaCha20Poly1305CipherConfig& ChaCha20Poly1305CipherConfig::operator=( } void ChaCha20Poly1305CipherConfig::MemoryInfo(MemoryTracker* tracker) const { - // If mode is sync, then the data in each of these properties - // is not owned by the ChaCha20Poly1305CipherConfig, so we ignore it. - if (IsCryptoJobAsync(mode)) { - tracker->TrackFieldWithSize("iv", iv.size()); - tracker->TrackFieldWithSize("additional_data", additional_data.size()); - } + tracker->TraitTrackInline(iv, "iv"); + tracker->TraitTrackInline(additional_data, "additional_data"); } Maybe ChaCha20Poly1305CipherTraits::AdditionalConfig( @@ -110,7 +105,6 @@ Maybe ChaCha20Poly1305CipherTraits::AdditionalConfig( ChaCha20Poly1305CipherConfig* params) { Environment* env = Environment::GetCurrent(args); - params->mode = mode; params->cipher = ncrypto::Cipher::CHACHA20_POLY1305; #ifndef OPENSSL_IS_BORINGSSL diff --git a/src/crypto/crypto_chacha20_poly1305.h b/src/crypto/crypto_chacha20_poly1305.h index f56b1a2e74a1..f0af11e122c4 100644 --- a/src/crypto/crypto_chacha20_poly1305.h +++ b/src/crypto/crypto_chacha20_poly1305.h @@ -13,7 +13,6 @@ namespace node::crypto { constexpr unsigned kChaCha20Poly1305AuthTagLength = 16; struct ChaCha20Poly1305CipherConfig final : public MemoryRetainer { - CryptoJobMode mode; ncrypto::Cipher cipher; ByteSource iv; ByteSource additional_data; diff --git a/src/crypto/crypto_cipher.h b/src/crypto/crypto_cipher.h index 30e460db0b58..f351c845497d 100644 --- a/src/crypto/crypto_cipher.h +++ b/src/crypto/crypto_cipher.h @@ -268,9 +268,8 @@ class CipherJob final : public CryptoJob { SET_SELF_SIZE(CipherJob) void MemoryInfo(MemoryTracker* tracker) const override { - if (IsCryptoJobAsync(CryptoJob::mode())) - tracker->TrackFieldWithSize("in", in_.size()); - tracker->TrackFieldWithSize("out", out_.size()); + tracker->TraitTrackInline(in_, "in"); + tracker->TraitTrackInline(out_, "out"); CryptoJob::MemoryInfo(tracker); } diff --git a/src/crypto/crypto_hash.cc b/src/crypto/crypto_hash.cc index fce312446916..f1a7bab16967 100644 --- a/src/crypto/crypto_hash.cc +++ b/src/crypto/crypto_hash.cc @@ -58,7 +58,7 @@ Hash::Hash(Environment* env, Local wrap) : BaseObject(env, wrap) { void Hash::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackFieldWithSize("mdctx", mdctx_ ? kSizeOf_EVP_MD_CTX : 0); - tracker->TrackFieldWithSize("md", digest_ ? md_len_ : 0); + tracker->TraitTrackInline(digest_, "md"); } #if NCRYPTO_USE_BORINGSSL_EVP_DO_ALL_FALLBACK @@ -524,10 +524,7 @@ void Hash::HashDigest(const FunctionCallbackInfo& args) { } HashConfig::HashConfig(HashConfig&& other) noexcept - : mode(other.mode), - in(std::move(other.in)), - digest(other.digest), - length(other.length) {} + : in(std::move(other.in)), digest(other.digest), length(other.length) {} HashConfig& HashConfig::operator=(HashConfig&& other) noexcept { if (&other == this) return *this; @@ -536,8 +533,7 @@ HashConfig& HashConfig::operator=(HashConfig&& other) noexcept { } void HashConfig::MemoryInfo(MemoryTracker* tracker) const { - // If the Job is sync, then the HashConfig does not own the data. - if (IsCryptoJobAsync(mode)) tracker->TrackFieldWithSize("in", in.size()); + tracker->TraitTrackInline(in, "in"); } MaybeLocal HashTraits::EncodeOutput(Environment* env, @@ -553,8 +549,6 @@ Maybe HashTraits::AdditionalConfig( HashConfig* params) { Environment* env = Environment::GetCurrent(args); - params->mode = mode; - CHECK(args[offset]->IsString()); // Hash algorithm Utf8Value digest(env->isolate(), args[offset]); params->digest = ncrypto::getDigestByName(*digest); @@ -782,8 +776,7 @@ bool DigestUpdateBytepad(ncrypto::EVPMDCtxPointer* ctx, } // namespace CShakeConfig::CShakeConfig(CShakeConfig&& other) noexcept - : mode(other.mode), - in(std::move(other.in)), + : in(std::move(other.in)), function_name(std::move(other.function_name)), customization(std::move(other.customization)), variant(other.variant), @@ -796,12 +789,9 @@ CShakeConfig& CShakeConfig::operator=(CShakeConfig&& other) noexcept { } void CShakeConfig::MemoryInfo(MemoryTracker* tracker) const { - // If the Job is sync, then the CShakeConfig does not own the data. - if (IsCryptoJobAsync(mode)) { - tracker->TrackFieldWithSize("in", in.size()); - tracker->TrackFieldWithSize("function_name", function_name.size()); - tracker->TrackFieldWithSize("customization", customization.size()); - } + tracker->TraitTrackInline(in, "in"); + tracker->TraitTrackInline(function_name, "function_name"); + tracker->TraitTrackInline(customization, "customization"); } MaybeLocal CShakeTraits::EncodeOutput(Environment* env, @@ -817,8 +807,6 @@ Maybe CShakeTraits::AdditionalConfig( CShakeConfig* params) { Environment* env = Environment::GetCurrent(args); - params->mode = mode; - CHECK(args[offset]->IsString()); // Algorithm name Utf8Value algorithm_name(env->isolate(), args[offset]); std::string_view algorithm_str = algorithm_name.ToStringView(); diff --git a/src/crypto/crypto_hash.h b/src/crypto/crypto_hash.h index d9fe46f37b31..0d90a4045f25 100644 --- a/src/crypto/crypto_hash.h +++ b/src/crypto/crypto_hash.h @@ -42,7 +42,6 @@ class Hash final : public BaseObject { }; struct HashConfig final : public MemoryRetainer { - CryptoJobMode mode; ByteSource in; const EVP_MD* digest; unsigned int length; @@ -107,7 +106,6 @@ struct CShakeParams final { bool DeriveCShakeBits(const CShakeParams& params, ByteSource* out); struct CShakeConfig final : public MemoryRetainer { - CryptoJobMode mode; ByteSource in; ByteSource function_name; ByteSource customization; diff --git a/src/crypto/crypto_hkdf.cc b/src/crypto/crypto_hkdf.cc index 52e81f814083..3704de6b0caa 100644 --- a/src/crypto/crypto_hkdf.cc +++ b/src/crypto/crypto_hkdf.cc @@ -20,8 +20,7 @@ using v8::Value; namespace crypto { HKDFConfig::HKDFConfig(HKDFConfig&& other) noexcept - : mode(other.mode), - length(other.length), + : length(other.length), digest(other.digest), key(std::move(other.key)), key_data(std::move(other.key_data)), @@ -47,8 +46,6 @@ Maybe HKDFTraits::AdditionalConfig( HKDFConfig* params) { Environment* env = Environment::GetCurrent(args); - params->mode = mode; - CHECK(args[offset]->IsString()); // Hash CHECK(KeyObjectHandle::HasInstance(env, args[offset + 1]) || IsAnyBufferSource(args[offset + 1])); // Key @@ -136,13 +133,12 @@ bool HKDFTraits::DeriveBits(Environment* env, } void HKDFConfig::MemoryInfo(MemoryTracker* tracker) const { - if (key) tracker->TrackField("key", key); - // If the job is sync, then the HKDFConfig does not own the data - if (IsCryptoJobAsync(mode)) { - if (!key) tracker->TrackFieldWithSize("key", key_data.size()); - tracker->TrackFieldWithSize("salt", salt.size()); - tracker->TrackFieldWithSize("info", info.size()); - } + if (key) + tracker->TrackField("key", key); + else + tracker->TraitTrackInline(key_data, "key"); + tracker->TraitTrackInline(salt, "salt"); + tracker->TraitTrackInline(info, "info"); } } // namespace crypto diff --git a/src/crypto/crypto_hkdf.h b/src/crypto/crypto_hkdf.h index f87ae4a8f91b..2fe151ec9e57 100644 --- a/src/crypto/crypto_hkdf.h +++ b/src/crypto/crypto_hkdf.h @@ -12,7 +12,6 @@ namespace node { namespace crypto { struct HKDFConfig final : public MemoryRetainer { - CryptoJobMode mode; size_t length; ncrypto::Digest digest; KeyObjectData key; diff --git a/src/crypto/crypto_hmac.cc b/src/crypto/crypto_hmac.cc index 1256a0c63cf4..62a3aa2bacaf 100644 --- a/src/crypto/crypto_hmac.cc +++ b/src/crypto/crypto_hmac.cc @@ -154,8 +154,7 @@ void Hmac::HmacDigest(const FunctionCallbackInfo& args) { } HmacConfig::HmacConfig(HmacConfig&& other) noexcept - : job_mode(other.job_mode), - mode(other.mode), + : mode(other.mode), key(std::move(other.key)), data(std::move(other.data)), signature(std::move(other.signature)), @@ -169,11 +168,8 @@ HmacConfig& HmacConfig::operator=(HmacConfig&& other) noexcept { void HmacConfig::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackField("key", key); - // If the job is sync, then the HmacConfig does not own the data - if (IsCryptoJobAsync(job_mode)) { - tracker->TrackFieldWithSize("data", data.size()); - tracker->TrackFieldWithSize("signature", signature.size()); - } + tracker->TraitTrackInline(data, "data"); + tracker->TraitTrackInline(signature, "signature"); } Maybe HmacTraits::AdditionalConfig( @@ -183,8 +179,6 @@ Maybe HmacTraits::AdditionalConfig( HmacConfig* params) { Environment* env = Environment::GetCurrent(args); - params->job_mode = mode; - CHECK(args[offset]->IsUint32()); // SignConfiguration::Mode params->mode = static_cast(args[offset].As()->Value()); diff --git a/src/crypto/crypto_hmac.h b/src/crypto/crypto_hmac.h index 98fc57f85bac..395ba390f87d 100644 --- a/src/crypto/crypto_hmac.h +++ b/src/crypto/crypto_hmac.h @@ -40,7 +40,6 @@ class Hmac : public BaseObject { }; struct HmacConfig final : public MemoryRetainer { - CryptoJobMode job_mode; SignConfiguration::Mode mode; KeyObjectData key; ByteSource data; diff --git a/src/crypto/crypto_kem.cc b/src/crypto/crypto_kem.cc index b2bcb4b2cc6d..85d0c65247bd 100644 --- a/src/crypto/crypto_kem.cc +++ b/src/crypto/crypto_kem.cc @@ -27,10 +27,7 @@ using v8::Value; namespace crypto { KEMConfiguration::KEMConfiguration(KEMConfiguration&& other) noexcept - : job_mode(other.job_mode), - mode(other.mode), - key(std::move(other.key)), - ciphertext(std::move(other.ciphertext)) {} + : key(std::move(other.key)), ciphertext(std::move(other.ciphertext)) {} KEMConfiguration& KEMConfiguration::operator=( KEMConfiguration&& other) noexcept { @@ -41,9 +38,7 @@ KEMConfiguration& KEMConfiguration::operator=( void KEMConfiguration::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackField("key", key); - if (IsCryptoJobAsync(job_mode)) { - tracker->TrackFieldWithSize("ciphertext", ciphertext.size()); - } + tracker->TraitTrackInline(ciphertext, "ciphertext"); } namespace { @@ -75,9 +70,6 @@ Maybe KEMEncapsulateTraits::AdditionalConfig( const FunctionCallbackInfo& args, unsigned int offset, KEMConfiguration* params) { - params->job_mode = mode; - params->mode = KEMMode::Encapsulate; - unsigned int key_offset = offset; auto public_key_data = KeyObjectData::GetPublicOrPrivateKeyFromJs(args, &key_offset); @@ -126,8 +118,12 @@ void KEMEncapsulateJob::DoThreadPoolWork() { ncrypto::ClearErrorOnReturn clear_error_on_return; AdditionalParams* params = CryptoJob::params(); Mutex::ScopedLock lock(params->key.mutex()); - out_ = ncrypto::KEM::Encapsulate(params->key.GetAsymmetricKey()); - if (!out_) { + auto result = ncrypto::KEM::Encapsulate(params->key.GetAsymmetricKey()); + if (result) { + out_.emplace(); + out_->ciphertext = ByteSource::Allocated(result->ciphertext.release()); + out_->shared_key = ByteSource::Allocated(result->shared_key.release()); + } else { if (mode() == kCryptoJobSync) { THROW_ERR_CRYPTO_OPERATION_FAILED(AsyncWrap::env(), "Failed to perform encapsulation"); @@ -155,8 +151,8 @@ Maybe KEMEncapsulateJob::ToResult(Local* err, CHECK(errors->Empty()); *err = v8::Undefined(env->isolate()); - ByteSource ciphertext = ByteSource::Allocated(out_->ciphertext.release()); - ByteSource shared_key = ByteSource::Allocated(out_->shared_key.release()); + ByteSource ciphertext = std::move(out_->ciphertext); + ByteSource shared_key = std::move(out_->shared_key); if (mode() == kCryptoJobWebCrypto) { Local output = Object::New(env->isolate()); @@ -194,8 +190,8 @@ Maybe KEMEncapsulateJob::ToResult(Local* err, void KEMEncapsulateJob::MemoryInfo(MemoryTracker* tracker) const { if (out_) { - tracker->TrackFieldWithSize("ciphertext", out_->ciphertext.size()); - tracker->TrackFieldWithSize("shared_key", out_->shared_key.size()); + tracker->TraitTrackInline(out_->ciphertext, "ciphertext"); + tracker->TraitTrackInline(out_->shared_key, "shared_key"); } CryptoJob::MemoryInfo(tracker); } @@ -208,9 +204,6 @@ Maybe KEMDecapsulateTraits::AdditionalConfig( KEMConfiguration* params) { Environment* env = Environment::GetCurrent(args); - params->job_mode = mode; - params->mode = KEMMode::Decapsulate; - unsigned int key_offset = offset; auto private_key_data = KeyObjectData::GetPrivateKeyFromJs(args, &key_offset, true); diff --git a/src/crypto/crypto_kem.h b/src/crypto/crypto_kem.h index b166bd35b586..80f62431b497 100644 --- a/src/crypto/crypto_kem.h +++ b/src/crypto/crypto_kem.h @@ -18,8 +18,6 @@ namespace crypto { enum class KEMMode { Encapsulate, Decapsulate }; struct KEMConfiguration final : public MemoryRetainer { - CryptoJobMode job_mode; - KEMMode mode; KeyObjectData key; ByteSource ciphertext; @@ -67,7 +65,12 @@ class KEMEncapsulateJob final : public CryptoJob { void MemoryInfo(MemoryTracker* tracker) const override; private: - std::optional out_; + struct Output { + ByteSource ciphertext; + ByteSource shared_key; + }; + + std::optional out_; }; struct KEMDecapsulateTraits final { diff --git a/src/crypto/crypto_keygen.cc b/src/crypto/crypto_keygen.cc index 24b0f5aca2b4..fd456465e0ee 100644 --- a/src/crypto/crypto_keygen.cc +++ b/src/crypto/crypto_keygen.cc @@ -54,7 +54,7 @@ EVPKeyCtxPointer NidKeyPairGenTraits::Setup(NidKeyPairGenConfig* params) { } void SecretKeyGenConfig::MemoryInfo(MemoryTracker* tracker) const { - if (out) tracker->TrackFieldWithSize("out", length); + tracker->TraitTrackInline(out, "out"); } Maybe SecretKeyGenTraits::AdditionalConfig( diff --git a/src/crypto/crypto_keys.cc b/src/crypto/crypto_keys.cc index 3021fe585240..ed418ce94029 100644 --- a/src/crypto/crypto_keys.cc +++ b/src/crypto/crypto_keys.cc @@ -961,32 +961,21 @@ KeyObjectData::KeyObjectData(KeyType type, EVPKeyPointer&& pkey) mutex_(std::make_shared()), data_(std::make_shared(std::move(pkey))) {} -void KeyObjectData::MemoryInfo(MemoryTracker* tracker) const { - if (!*this) return; - switch (GetKeyType()) { - case kKeyTypeSecret: { - if (data_->symmetric_key) { - tracker->TrackFieldWithSize("symmetric_key", - data_->symmetric_key.size()); - } - break; - } - case kKeyTypePrivate: - // Fall through - case kKeyTypePublic: { - if (data_->asymmetric_key) { - tracker->TrackFieldWithSize( - "key", - kSizeOf_EVP_PKEY + data_->asymmetric_key.rawPublicKeySize() + - data_->asymmetric_key.rawPrivateKeySize()); - } - break; - } - default: - UNREACHABLE(); +void KeyObjectData::Data::MemoryInfo(MemoryTracker* tracker) const { + if (asymmetric_key) { + tracker->TrackFieldWithSize("key", + kSizeOf_EVP_PKEY + + asymmetric_key.rawPublicKeySize() + + asymmetric_key.rawPrivateKeySize()); + } else { + tracker->TraitTrackInline(symmetric_key, "symmetric_key"); } } +void KeyObjectData::MemoryInfo(MemoryTracker* tracker) const { + tracker->TrackField("data", data_); +} + Mutex& KeyObjectData::mutex() const { if (!mutex_) mutex_ = std::make_shared(); return *mutex_.get(); diff --git a/src/crypto/crypto_keys.h b/src/crypto/crypto_keys.h index 49714bdb75e4..145483029646 100644 --- a/src/crypto/crypto_keys.h +++ b/src/crypto/crypto_keys.h @@ -115,13 +115,17 @@ class KeyObjectData final : public MemoryRetainer { KeyType key_type_; mutable std::shared_ptr mutex_; - struct Data { + struct Data final : public MemoryRetainer { const ByteSource symmetric_key; const ncrypto::EVPKeyPointer asymmetric_key; explicit Data(ByteSource symmetric_key) : symmetric_key(std::move(symmetric_key)) {} explicit Data(ncrypto::EVPKeyPointer asymmetric_key) : asymmetric_key(std::move(asymmetric_key)) {} + + void MemoryInfo(MemoryTracker* tracker) const override; + SET_MEMORY_INFO_NAME(KeyObjectData::Data) + SET_SELF_SIZE(Data) }; std::shared_ptr data_; @@ -475,7 +479,7 @@ class KeyExportJob final : public CryptoJob { SET_SELF_SIZE(KeyExportJob) void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackFieldWithSize("out", out_.size()); + tracker->TraitTrackInline(out_, "out"); CryptoJob::MemoryInfo(tracker); } diff --git a/src/crypto/crypto_kmac.cc b/src/crypto/crypto_kmac.cc index 5f151c9a62a6..f728defee7cf 100644 --- a/src/crypto/crypto_kmac.cc +++ b/src/crypto/crypto_kmac.cc @@ -32,8 +32,7 @@ using v8::Uint32; using v8::Value; KmacConfig::KmacConfig(KmacConfig&& other) noexcept - : job_mode(other.job_mode), - mode(other.mode), + : mode(other.mode), key(std::move(other.key)), data(std::move(other.data)), signature(std::move(other.signature)), @@ -50,12 +49,9 @@ KmacConfig& KmacConfig::operator=(KmacConfig&& other) noexcept { void KmacConfig::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackField("key", key); - // If the job is sync, then the KmacConfig does not own the data. - if (IsCryptoJobAsync(job_mode)) { - tracker->TrackFieldWithSize("data", data.size()); - tracker->TrackFieldWithSize("signature", signature.size()); - tracker->TrackFieldWithSize("customization", customization.size()); - } + tracker->TraitTrackInline(data, "data"); + tracker->TraitTrackInline(signature, "signature"); + tracker->TraitTrackInline(customization, "customization"); } Maybe KmacTraits::AdditionalConfig( @@ -65,8 +61,6 @@ Maybe KmacTraits::AdditionalConfig( KmacConfig* params) { Environment* env = Environment::GetCurrent(args); - params->job_mode = mode; - CHECK(args[offset]->IsUint32()); // SignConfiguration::Mode params->mode = static_cast(args[offset].As()->Value()); diff --git a/src/crypto/crypto_kmac.h b/src/crypto/crypto_kmac.h index 40aa83580d0f..65617fd76232 100644 --- a/src/crypto/crypto_kmac.h +++ b/src/crypto/crypto_kmac.h @@ -15,7 +15,6 @@ namespace node::crypto { enum class KmacVariant { KMAC128, KMAC256 }; struct KmacConfig final : public MemoryRetainer { - CryptoJobMode job_mode; SignConfiguration::Mode mode; KeyObjectData key; ByteSource data; diff --git a/src/crypto/crypto_pbkdf2.cc b/src/crypto/crypto_pbkdf2.cc index 3f543392790d..9ccd572950a9 100644 --- a/src/crypto/crypto_pbkdf2.cc +++ b/src/crypto/crypto_pbkdf2.cc @@ -22,8 +22,7 @@ using v8::Value; namespace crypto { PBKDF2Config::PBKDF2Config(PBKDF2Config&& other) noexcept - : mode(other.mode), - key(std::move(other.key)), + : key(std::move(other.key)), pass(std::move(other.pass)), salt(std::move(other.salt)), iterations(other.iterations), @@ -37,12 +36,11 @@ PBKDF2Config& PBKDF2Config::operator=(PBKDF2Config&& other) noexcept { } void PBKDF2Config::MemoryInfo(MemoryTracker* tracker) const { - // If the job is sync, PBKDF2Config does not own the data. - if (key) tracker->TrackField("key", key); - if (IsCryptoJobAsync(mode)) { - if (!key) tracker->TrackFieldWithSize("pass", pass.size()); - tracker->TrackFieldWithSize("salt", salt.size()); - } + if (key) + tracker->TrackField("key", key); + else + tracker->TraitTrackInline(pass, "pass"); + tracker->TraitTrackInline(salt, "salt"); } MaybeLocal PBKDF2Traits::EncodeOutput(Environment* env, @@ -65,8 +63,6 @@ Maybe PBKDF2Traits::AdditionalConfig( PBKDF2Config* params) { Environment* env = Environment::GetCurrent(args); - params->mode = mode; - CHECK(KeyObjectHandle::HasInstance(env, args[offset]) || IsAnyBufferSource(args[offset])); // pass ArrayBufferOrViewContents salt(args[offset + 1]); diff --git a/src/crypto/crypto_pbkdf2.h b/src/crypto/crypto_pbkdf2.h index 05f3dcb96879..a1a98352720e 100644 --- a/src/crypto/crypto_pbkdf2.h +++ b/src/crypto/crypto_pbkdf2.h @@ -26,7 +26,6 @@ namespace crypto { // The iteration count should be as high as possible. struct PBKDF2Config final : public MemoryRetainer { - CryptoJobMode mode; KeyObjectData key; ByteSource pass; ByteSource salt; diff --git a/src/crypto/crypto_rsa.cc b/src/crypto/crypto_rsa.cc index 4cb9f267d2f0..22b277676ff8 100644 --- a/src/crypto/crypto_rsa.cc +++ b/src/crypto/crypto_rsa.cc @@ -261,14 +261,12 @@ WebCryptoKeyExportStatus RSAKeyExportTraits::DoExport( } RSACipherConfig::RSACipherConfig(RSACipherConfig&& other) noexcept - : mode(other.mode), - label(std::move(other.label)), + : label(std::move(other.label)), padding(other.padding), digest(other.digest) {} void RSACipherConfig::MemoryInfo(MemoryTracker* tracker) const { - if (IsCryptoJobAsync(mode)) - tracker->TrackFieldWithSize("label", label.size()); + tracker->TraitTrackInline(label, "label"); } Maybe RSACipherTraits::AdditionalConfig( @@ -279,7 +277,6 @@ Maybe RSACipherTraits::AdditionalConfig( RSACipherConfig* params) { Environment* env = Environment::GetCurrent(args); - params->mode = mode; params->padding = RSA_PKCS1_OAEP_PADDING; CHECK(args[offset]->IsUint32()); diff --git a/src/crypto/crypto_rsa.h b/src/crypto/crypto_rsa.h index 61279c9a18b3..c5ab0edf5928 100644 --- a/src/crypto/crypto_rsa.h +++ b/src/crypto/crypto_rsa.h @@ -77,7 +77,6 @@ struct RSAKeyExportTraits final { using RSAKeyExportJob = KeyExportJob; struct RSACipherConfig final : public MemoryRetainer { - CryptoJobMode mode = kCryptoJobAsync; ByteSource label; int padding = 0; ncrypto::Digest digest; diff --git a/src/crypto/crypto_scrypt.cc b/src/crypto/crypto_scrypt.cc index 601a573e4fc6..8301eb0216ef 100644 --- a/src/crypto/crypto_scrypt.cc +++ b/src/crypto/crypto_scrypt.cc @@ -22,14 +22,13 @@ namespace crypto { #ifndef OPENSSL_NO_SCRYPT ScryptConfig::ScryptConfig(ScryptConfig&& other) noexcept - : mode(other.mode), - pass(std::move(other.pass)), - salt(std::move(other.salt)), - N(other.N), - r(other.r), - p(other.p), - maxmem(other.maxmem), - length(other.length) {} + : pass(std::move(other.pass)), + salt(std::move(other.salt)), + N(other.N), + r(other.r), + p(other.p), + maxmem(other.maxmem), + length(other.length) {} ScryptConfig& ScryptConfig::operator=(ScryptConfig&& other) noexcept { if (&other == this) return *this; @@ -38,10 +37,8 @@ ScryptConfig& ScryptConfig::operator=(ScryptConfig&& other) noexcept { } void ScryptConfig::MemoryInfo(MemoryTracker* tracker) const { - if (IsCryptoJobAsync(mode)) { - tracker->TrackFieldWithSize("pass", pass.size()); - tracker->TrackFieldWithSize("salt", salt.size()); - } + tracker->TraitTrackInline(pass, "pass"); + tracker->TraitTrackInline(salt, "salt"); } MaybeLocal ScryptTraits::EncodeOutput(Environment* env, @@ -57,8 +54,6 @@ Maybe ScryptTraits::AdditionalConfig( ScryptConfig* params) { Environment* env = Environment::GetCurrent(args); - params->mode = mode; - ArrayBufferOrViewContents pass(args[offset]); ArrayBufferOrViewContents salt(args[offset + 1]); @@ -80,7 +75,7 @@ Maybe ScryptTraits::AdditionalConfig( CHECK(args[offset + 3]->IsUint32()); // r CHECK(args[offset + 4]->IsUint32()); // p CHECK(args[offset + 5]->IsNumber()); // maxmem - CHECK(args[offset + 6]->IsInt32()); // length + CHECK(args[offset + 6]->IsInt32()); // length params->N = args[offset + 2].As()->Value(); params->r = args[offset + 3].As()->Value(); diff --git a/src/crypto/crypto_scrypt.h b/src/crypto/crypto_scrypt.h index 5e86de86193c..d3cd0dfef59b 100644 --- a/src/crypto/crypto_scrypt.h +++ b/src/crypto/crypto_scrypt.h @@ -25,7 +25,6 @@ namespace crypto { // at least 16 bytes in length. struct ScryptConfig final : public MemoryRetainer { - CryptoJobMode mode; ByteSource pass; ByteSource salt; uint32_t N; diff --git a/src/crypto/crypto_sig.cc b/src/crypto/crypto_sig.cc index eab8a30b4f15..0c3a29561c1f 100644 --- a/src/crypto/crypto_sig.cc +++ b/src/crypto/crypto_sig.cc @@ -684,8 +684,7 @@ void Verify::VerifyFinal(const FunctionCallbackInfo& args) { } SignConfiguration::SignConfiguration(SignConfiguration&& other) noexcept - : job_mode(other.job_mode), - mode(other.mode), + : mode(other.mode), key(std::move(other.key)), data(std::move(other.data)), signature(std::move(other.signature)), @@ -705,11 +704,9 @@ SignConfiguration& SignConfiguration::operator=( void SignConfiguration::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackField("key", key); - if (IsCryptoJobAsync(job_mode)) { - tracker->TrackFieldWithSize("data", data.size()); - tracker->TrackFieldWithSize("signature", signature.size()); - tracker->TrackFieldWithSize("context_string", context_string.size()); - } + tracker->TraitTrackInline(data, "data"); + tracker->TraitTrackInline(signature, "signature"); + tracker->TraitTrackInline(context_string, "context_string"); } Maybe SignTraits::AdditionalConfig( @@ -720,8 +717,6 @@ Maybe SignTraits::AdditionalConfig( ClearErrorOnReturn clear_error_on_return; Environment* env = Environment::GetCurrent(args); - params->job_mode = mode; - CHECK(args[offset]->IsUint32()); // Sign Mode params->mode = diff --git a/src/crypto/crypto_sig.h b/src/crypto/crypto_sig.h index 35154813a245..f5534a020c6a 100644 --- a/src/crypto/crypto_sig.h +++ b/src/crypto/crypto_sig.h @@ -104,7 +104,6 @@ struct SignConfiguration final : public MemoryRetainer { kHasContextString = 4 }; - CryptoJobMode job_mode; Mode mode; KeyObjectData key; ByteSource data; diff --git a/src/crypto/crypto_turboshake.cc b/src/crypto/crypto_turboshake.cc index 3b4cb944bc63..4d8f9161448d 100644 --- a/src/crypto/crypto_turboshake.cc +++ b/src/crypto/crypto_turboshake.cc @@ -405,8 +405,7 @@ void KT256(const uint8_t* message, // ============================================================================ TurboShakeConfig::TurboShakeConfig(TurboShakeConfig&& other) noexcept - : job_mode(other.job_mode), - variant(other.variant), + : variant(other.variant), output_length(other.output_length), domain_separation(other.domain_separation), data(std::move(other.data)) {} @@ -419,10 +418,7 @@ TurboShakeConfig& TurboShakeConfig::operator=( } void TurboShakeConfig::MemoryInfo(MemoryTracker* tracker) const { - if (IsCryptoJobAsync(job_mode)) { - // TODO(addaleax): Implement MemoryRetainer protocol for ByteSource - tracker->TrackFieldWithSize("data", data.size()); - } + tracker->TraitTrackInline(data, "data"); } Maybe TurboShakeTraits::AdditionalConfig( @@ -432,8 +428,6 @@ Maybe TurboShakeTraits::AdditionalConfig( TurboShakeConfig* params) { Environment* env = Environment::GetCurrent(args); - params->job_mode = mode; - // args[offset + 0] = algorithm name (string) CHECK(args[offset]->IsString()); Utf8Value algorithm_name(env->isolate(), args[offset]); @@ -512,8 +506,7 @@ MaybeLocal TurboShakeTraits::EncodeOutput(Environment* env, KangarooTwelveConfig::KangarooTwelveConfig( KangarooTwelveConfig&& other) noexcept - : job_mode(other.job_mode), - variant(other.variant), + : variant(other.variant), output_length(other.output_length), data(std::move(other.data)), customization(std::move(other.customization)) {} @@ -526,11 +519,8 @@ KangarooTwelveConfig& KangarooTwelveConfig::operator=( } void KangarooTwelveConfig::MemoryInfo(MemoryTracker* tracker) const { - if (IsCryptoJobAsync(job_mode)) { - // TODO(addaleax): Implement MemoryRetainer protocol for ByteSource - tracker->TrackFieldWithSize("data", data.size()); - tracker->TrackFieldWithSize("customization", customization.size()); - } + tracker->TraitTrackInline(data, "data"); + tracker->TraitTrackInline(customization, "customization"); } Maybe KangarooTwelveTraits::AdditionalConfig( @@ -540,8 +530,6 @@ Maybe KangarooTwelveTraits::AdditionalConfig( KangarooTwelveConfig* params) { Environment* env = Environment::GetCurrent(args); - params->job_mode = mode; - // args[offset + 0] = algorithm name (string) CHECK(args[offset]->IsString()); Utf8Value algorithm_name(env->isolate(), args[offset]); diff --git a/src/crypto/crypto_turboshake.h b/src/crypto/crypto_turboshake.h index 53b01eec8bd7..39f908663089 100644 --- a/src/crypto/crypto_turboshake.h +++ b/src/crypto/crypto_turboshake.h @@ -10,7 +10,6 @@ namespace node::crypto { enum class TurboShakeVariant { TurboSHAKE128, TurboSHAKE256 }; struct TurboShakeConfig final : public MemoryRetainer { - CryptoJobMode job_mode; TurboShakeVariant variant; uint32_t output_length; // Output length in bytes uint8_t domain_separation; // Domain separation byte (0x01–0x7F) @@ -54,7 +53,6 @@ using TurboShakeJob = DeriveBitsJob; enum class KangarooTwelveVariant { KT128, KT256 }; struct KangarooTwelveConfig final : public MemoryRetainer { - CryptoJobMode job_mode; KangarooTwelveVariant variant; uint32_t output_length; // Output length in bytes ByteSource data; diff --git a/src/crypto/crypto_util.cc b/src/crypto/crypto_util.cc index 7e256cc75bef..17248bef7d96 100644 --- a/src/crypto/crypto_util.cc +++ b/src/crypto/crypto_util.cc @@ -56,6 +56,24 @@ using v8::Uint32; using v8::Uint8Array; using v8::Value; +void MemoryRetainerTraits::MemoryInfo( + MemoryTracker* tracker, const crypto::ByteSource& value) { + // Foreign ByteSources do not own the memory that they reference. + if (value.allocated_data_ != nullptr) { + tracker->TrackFieldWithSize("data", value.size_); + } +} + +const char* MemoryRetainerTraits::MemoryInfoName( + const crypto::ByteSource& value) { + return "ByteSource"; +} + +size_t MemoryRetainerTraits::SelfSize( + const crypto::ByteSource& value) { + return sizeof(value); +} + namespace crypto { int PasswordCallback(char* buf, int size, int rwflag, void* u) { diff --git a/src/crypto/crypto_util.h b/src/crypto/crypto_util.h index a99ab94fff5d..ca6157e42821 100644 --- a/src/crypto/crypto_util.h +++ b/src/crypto/crypto_util.h @@ -26,6 +26,20 @@ #define NODE_OPENSSL_HAS_CERT_COMP 1 #endif +namespace node { +namespace crypto { +class ByteSource; +} + +template <> +struct MemoryRetainerTraits { + static void MemoryInfo(MemoryTracker* tracker, + const crypto::ByteSource& value); + static const char* MemoryInfoName(const crypto::ByteSource& value); + static size_t SelfSize(const crypto::ByteSource& value); +}; +} // namespace node + namespace node::crypto { // Currently known sizes of commonly used OpenSSL struct sizes. // OpenSSL considers it's various structs to be opaque and the @@ -236,6 +250,7 @@ class ByteSource final { Environment* env, v8::Local value); private: + friend struct node::MemoryRetainerTraits; friend void TruncateToBitLength(size_t length_bits, ByteSource* bytes); const void* data_ = nullptr; @@ -593,7 +608,7 @@ class DeriveBitsJob final : public CryptoJob { SET_SELF_SIZE(DeriveBitsJob) void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackFieldWithSize("out", out_.size()); + tracker->TraitTrackInline(out_, "out"); CryptoJob::MemoryInfo(tracker); } From 5d808f08566c71d9ada092cca17181d09bfb1eec Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Tue, 28 Jul 2026 15:03:01 +0200 Subject: [PATCH 15/16] crypto: handle XOF output allocation failure Return an operation error when XOF output allocation fails. Signed-off-by: Filip Skokan PR-URL: https://github.com/nodejs/node/pull/64851 Reviewed-By: James M Snell Reviewed-By: Luigi Pinca --- src/crypto/crypto_turboshake.cc | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/crypto/crypto_turboshake.cc b/src/crypto/crypto_turboshake.cc index 4d8f9161448d..899ed7c717dd 100644 --- a/src/crypto/crypto_turboshake.cc +++ b/src/crypto/crypto_turboshake.cc @@ -468,7 +468,10 @@ bool TurboShakeTraits::DeriveBits(Environment* env, ByteSource* out, CryptoJobMode mode) { CHECK_GT(params.output_length, 0); - char* buf = MallocOpenSSL(params.output_length); + char* buf = static_cast(OPENSSL_malloc(params.output_length)); + if (buf == nullptr) { + return false; + } const uint8_t* input = reinterpret_cast(params.data.data()); size_t input_len = params.data.size(); @@ -592,7 +595,10 @@ bool KangarooTwelveTraits::DeriveBits(Environment* env, return false; } - char* buf = MallocOpenSSL(params.output_length); + char* buf = static_cast(OPENSSL_malloc(params.output_length)); + if (buf == nullptr) { + return false; + } switch (params.variant) { case KangarooTwelveVariant::KT128: From e176aba1833840c6d37ac9568c5bf863a0665f54 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Mon, 27 Jul 2026 11:54:52 +0200 Subject: [PATCH 16/16] crypto: preserve OpenSSL errors from KDF failures The ncrypto KDF helpers cleared the OpenSSL error queue on return, and the traits insert their own message, which makes DeriveBitsJob skip errors->Capture(). Argon2, HKDF, PBKDF2 and scrypt failures were therefore bare Errors with no code and no opensslErrorStack. Drop the guard, which DeriveBitsJob already provides, and capture before inserting since Capture() clears the store. Signed-off-by: Filip Skokan PR-URL: https://github.com/nodejs/node/pull/64776 Reviewed-By: Yagiz Nizipli --- deps/ncrypto/ncrypto.cc | 8 ------- test/parallel/test-crypto-argon2-job.js | 26 ++++++++++++++++------- test/parallel/test-crypto-no-algorithm.js | 17 +++++++++++++++ 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index af29c0365091..9802153f6d16 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -2642,8 +2642,6 @@ DataPointer hkdf(const Digest& md, const Buffer& info, const Buffer& salt, size_t length) { - ClearErrorOnReturn clearErrorOnReturn; - if (!checkHkdfLength(md, length) || info.len > INT_MAX || salt.len > INT_MAX) { return {}; @@ -2714,8 +2712,6 @@ DataPointer scrypt(const Buffer& pass, uint64_t p, uint64_t maxmem, size_t length) { - ClearErrorOnReturn clearErrorOnReturn; - if (pass.len > INT_MAX || salt.len > INT_MAX) { return {}; } @@ -2742,8 +2738,6 @@ DataPointer pbkdf2(const Digest& md, const Buffer& salt, uint32_t iterations, size_t length) { - ClearErrorOnReturn clearErrorOnReturn; - if (pass.len > INT_MAX || salt.len > INT_MAX || length > INT_MAX) { return {}; } @@ -2775,8 +2769,6 @@ DataPointer argon2(const Buffer& pass, const Buffer& secret, const Buffer& ad, Argon2Type type) { - ClearErrorOnReturn clearErrorOnReturn; - std::string_view algorithm; switch (type) { case Argon2Type::ARGON2I: diff --git a/test/parallel/test-crypto-argon2-job.js b/test/parallel/test-crypto-argon2-job.js index 178798af4adf..7c16f9d850af 100644 --- a/test/parallel/test-crypto-argon2-job.js +++ b/test/parallel/test-crypto-argon2-job.js @@ -29,19 +29,30 @@ const empty = Buffer.alloc(0); // Parameters that OpenSSL's Argon2 KDF rejects. const badParams = [ - { lanes: 0, keylen: 32, memcost: 16, iter: 1 }, // lanes < 1 - { lanes: 1, keylen: 32, memcost: 0, iter: 1 }, // memcost == 0 - { lanes: 1, keylen: 32, memcost: 16, iter: 0 }, // iter == 0 + { lanes: 0, keylen: 32, memcost: 16, iter: 1, + reason: /invalid thread pool size/ }, + { lanes: 1, keylen: 32, memcost: 0, iter: 1, + reason: /invalid memory size/ }, + { lanes: 1, keylen: 32, memcost: 16, iter: 0, + reason: /invalid iteration count/ }, ]; -for (const { lanes, keylen, memcost, iter } of badParams) { +function assertError(err, { reason }) { + assert.ok(err); + const details = [err.message, ...(err.opensslErrorStack ?? [])]; + assert.ok(details.some((msg) => reason.test(msg)), + `did not find ${reason} in ${details}`); +} + +for (const params of badParams) { + const { lanes, keylen, memcost, iter } = params; + { const job = new Argon2Job( kCryptoJobSync, pass, salt, lanes, keylen, memcost, iter, empty, empty, kTypeArgon2id); const { 0: err, 1: result } = job.run(); - assert.ok(err); - assert.match(err.message, /Deriving bits failed/); + assertError(err, params); assert.strictEqual(result, undefined); } @@ -50,8 +61,7 @@ for (const { lanes, keylen, memcost, iter } of badParams) { kCryptoJobAsync, pass, salt, lanes, keylen, memcost, iter, empty, empty, kTypeArgon2id); job.ondone = common.mustCall((err, result) => { - assert.ok(err); - assert.match(err.message, /Deriving bits failed/); + assertError(err, params); assert.strictEqual(result, undefined); }); job.run(); diff --git a/test/parallel/test-crypto-no-algorithm.js b/test/parallel/test-crypto-no-algorithm.js index 96236a976a89..76063a04227e 100644 --- a/test/parallel/test-crypto-no-algorithm.js +++ b/test/parallel/test-crypto-no-algorithm.js @@ -26,6 +26,23 @@ if (isMainThread) { `did not find ${expected} in ${err.opensslErrorStack}`); } })); + + const derivations = [ + ['HKDF', () => crypto.hkdfSync('sha256', Buffer.alloc(32), Buffer.alloc(8), + Buffer.alloc(0), 32)], + ['PBKDF2', () => crypto.pbkdf2Sync('secret', Buffer.alloc(16), 1000, 32, + 'sha256')], + ]; + for (const { 0: name, 1: derive } of derivations) { + try { + derive(); + } catch (err) { + const expected = /digital envelope routines::unsupported/; + const details = [err.message, ...(err.opensslErrorStack ?? [])]; + assert(details.some((msg) => expected.test(msg)), + `${name}: did not find ${expected} in ${details}`); + } + } } {