diff --git a/common.gypi b/common.gypi index 2afa1713d567ce..8cf118455ec827 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 73a755f67e7068..f7fa65db775fee 100755 --- a/configure.py +++ b/configure.py @@ -1434,6 +1434,48 @@ def get_gas_version(cc): warn(f'Could not recognize `gas`: {gas_ret}') return '0.0' +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. @@ -1443,39 +1485,7 @@ def get_openssl_version(o): """ 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 - 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 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')) @@ -1505,6 +1515,13 @@ def get_openssl_version(o): 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' + def get_cargo_version(cargo): try: proc = subprocess.Popen(shlex.split(cargo) + ['--version'], @@ -2309,6 +2326,7 @@ def without_ssl_error(option): configure_library('openssl', o) o['variables']['openssl_version'] = get_openssl_version(o) + o['variables']['openssl_is_boringssl'] = get_openssl_is_boringssl(o) def configure_lief(o): if options.without_lief: diff --git a/deps/ncrypto/engine.cc b/deps/ncrypto/engine.cc index 1845cfcca47aa4..a8e64e25049183 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 e2b9e46c1776b3..bed4d48d118d51 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,227 @@ 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 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; + 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); } +}; +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 +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) { + 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; +} + +template +bool GetOptionalPKeyBnParam(const EVP_PKEY* pkey, + const char* name, + Pointer* 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 (pub == nullptr || + GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_PUB_KEY, pub)) && + (priv == nullptr || + GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_PRIV_KEY, priv)); +} +#endif + #if NCRYPTO_USE_BORINGSSL_EVP_DO_ALL_FALLBACK struct BoringSSLCipher { const EVP_CIPHER* (*get)(); @@ -282,9 +501,12 @@ Buffer DataPointer::release() { } DataPointer DataPointer::resize(size_t len) { - size_t actual_len = std::min(len_, len); + const size_t actual_len = std::min(len_, len); + if (actual_len == len_) return std::move(*this); + auto buf = release(); - if (actual_len == len_) return DataPointer(buf.data, actual_len); + if (actual_len == 0) return DataPointer(buf.data, actual_len); + buf.data = OPENSSL_realloc(buf.data, actual_len); buf.len = actual_len; return DataPointer(buf); @@ -500,7 +722,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, @@ -1389,7 +1622,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); @@ -1402,7 +1639,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); @@ -1430,7 +1671,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 @@ -1560,11 +1805,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; @@ -1576,13 +2008,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) { @@ -1608,7 +2072,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; } @@ -1620,12 +2084,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 {}; @@ -1640,9 +2114,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 {}; @@ -1651,23 +2137,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; @@ -1684,64 +2250,245 @@ 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; + if (!GetDhKeys(dh_.get(), &pub_key, nullptr)) 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 pvt_key; + if (!GetDhKeys(dh_.get(), nullptr, &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 { - ClearErrorOnReturn clearErrorOnReturn; - if (!dh_) return {}; - - // Key generation failed - if (!DH_generate_key(dh_.get())) return {}; - - return getPublicKey(); -} +bool DHPointer::hasPrivateKey() const { + if (!*this) return false; +#if NCRYPTO_USE_OPENSSL3_PROVIDER + if (pvt_key_) return true; + if (!dh_) return false; -size_t DHPointer::size() const { - if (!dh_) return 0; - 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; + DeleteFnPtr pvt_key; + if (!GetDhKeys(dh_.get(), nullptr, &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::computeSecret(const BignumPointer& peer) const { +DataPointer DHPointer::generateKeys() { ClearErrorOnReturn clearErrorOnReturn; - if (!dh_ || !peer) return {}; + if (!*this) return {}; - auto dp = DataPointer::Alloc(size()); - if (!dp) return {}; +#if NCRYPTO_USE_OPENSSL3_PROVIDER + if (p_ && g_) { + if (!pvt_key_ && !GenerateDhPrivateKey(&pvt_key_, p_.get(), group_name_)) { + return {}; + } - int size = - DH_compute_key(static_cast(dp.get()), peer.get(), dh_.get()); - if (size < 0) 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 (!*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 (!*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 {}; + + int size = + DH_compute_key(static_cast(dp.get()), peer.get(), dh_.get()); + if (size < 0) return {}; // The size of the computed key can be smaller than the size of the DH key. // We want to make sure that the key is correctly padded. @@ -1753,10 +2500,34 @@ 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 pvt_key; + if (!GetDhKeys(dh_.get(), nullptr, &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 @@ -1766,10 +2537,34 @@ 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; + if (!GetDhKeys(dh_.get(), &pub_key, nullptr)) { + 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 @@ -1779,6 +2574,7 @@ bool DHPointer::setPrivateKey(BignumPointer&& key) { return true; } return false; +#endif } DataPointer DHPointer::stateless(const EVPKeyPointer& ourKey, @@ -1787,8 +2583,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 {}; } @@ -2202,14 +3002,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(); @@ -2219,6 +3065,7 @@ EVPKeyPointer EVPKeyPointer::NewRSA(RSAPointer&& rsa) { } return key; } +#endif // NCRYPTO_USE_OPENSSL3_PROVIDER EVPKeyPointer::EVPKeyPointer(EVP_PKEY* pkey) : pkey_(pkey) {} @@ -2356,18 +3203,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 { @@ -2422,6 +3334,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. @@ -2539,6 +3479,104 @@ 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) 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; + + 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( @@ -2626,6 +3664,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 @@ -2653,6 +3707,7 @@ Result EVPKeyPointer::writePrivateKey( return Result(false); } } +#endif break; } case PKEncodingType::PKCS8: { @@ -2689,6 +3744,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 @@ -2716,6 +3782,7 @@ Result EVPKeyPointer::writePrivateKey( return Result(false); } } +#endif break; } default: { @@ -2742,6 +3809,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 @@ -2762,8 +3841,17 @@ 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. if (PEM_write_bio_PUBKEY(bio.get(), get()) != 1) { @@ -2834,11 +3922,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; } @@ -2850,6 +3950,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; @@ -2860,15 +3963,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 { @@ -2879,12 +3987,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); @@ -3189,7 +4310,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(); @@ -3338,7 +4459,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; } @@ -3602,8 +4723,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 @@ -3724,6 +4852,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)); } @@ -3734,6 +4876,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 // ============================================================================ @@ -3828,8 +5226,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) { @@ -3868,12 +5291,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) { @@ -3927,7 +5354,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; } @@ -4117,26 +5544,276 @@ 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; +} + +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) {} + +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; + } + 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; + 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; @@ -4169,16 +5846,64 @@ 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) 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 + 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, @@ -4187,6 +5912,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; } @@ -4206,6 +5942,7 @@ bool Rsa::setPrivateKey(BignumPointer&& d, dq.release(); qi.release(); return true; +#endif } DataPointer Rsa::encrypt(const EVPKeyPointer& key, @@ -4328,12 +6065,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 { @@ -4437,7 +6261,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 { @@ -4979,31 +6803,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 1747f3ea0149b9..804a664fa0a2a1 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 b79962d5a85cbc..6fb6b384fd4fa4 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,42 @@ class Rsa final { const Buffer in); private: +#if NCRYPTO_USE_OPENSSL3_PROVIDER + bool rsa_ = false; + bool rsa_pss_ = 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 +680,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 +1022,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 +1098,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 +1172,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 +1240,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 +1257,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 +1575,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 +1614,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 +1623,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 +1805,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 +1833,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 7cb27d22b9b8e0..dad4fbbf16f060 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/doc/api/webcrypto.md b/doc/api/webcrypto.md index 09f64409957dce..66e7cca3d3f3a3 100644 --- a/doc/api/webcrypto.md +++ b/doc/api/webcrypto.md @@ -2250,11 +2250,16 @@ added: v25.9.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 dbdb7a5ea47cb6..cd90c36e96ca9e 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/src/crypto/crypto_common.cc b/src/crypto/crypto_common.cc index 0db5d0eaac8fc3..fde12953860cd0 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 01d8a17d8e2f53..5447de596f98ec 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 ffb0b88816c092..95ddea4c262dd5 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 cb49cc703426d3..a06d126be301d8 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"); } } +#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"); + } +#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 a89e3391dbf896..38f6831e7a9dee 100644 --- a/src/crypto/crypto_ec.cc +++ b/src/crypto/crypto_ec.cc @@ -187,15 +187,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; @@ -469,11 +470,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 = @@ -538,7 +542,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(); } @@ -751,11 +755,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(), @@ -770,11 +775,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 20cadc610bace1..2c413b10b29c86 100644 --- a/src/crypto/crypto_hash.cc +++ b/src/crypto/crypto_hash.cc @@ -246,6 +246,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) { @@ -441,7 +448,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_kem.cc b/src/crypto/crypto_kem.cc index 09fbf0844f48f2..2cbeed3660d131 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,49 +48,6 @@ void KEMConfiguration::MemoryInfo(MemoryTracker* tracker) const { namespace { -bool DoKEMEncapsulate(Environment* env, - const EVPKeyPointer& public_key, - ByteSource* out, - CryptoJobMode mode, - CryptoErrorStore* errors) { - auto result = ncrypto::KEM::Encapsulate(public_key); - if (!result) { - errors->Insert(NodeCryptoError::ENCAPSULATION_FAILED); - errors->SetNodeErrorCode("ERR_CRYPTO_OPERATION_FAILED"); - 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) { - errors->Insert(NodeCryptoError::ALLOCATION_FAILED); - errors->SetNodeErrorCode("ERR_CRYPTO_OPERATION_FAILED"); - 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, @@ -133,72 +89,109 @@ Maybe KEMEncapsulateTraits::AdditionalConfig( return v8::JustVoid(); } -bool KEMEncapsulateTraits::DeriveBits(Environment* env, - const KEMConfiguration& params, - ByteSource* out, - CryptoJobMode mode, - CryptoErrorStore* errors) { - 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, errors); + 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_) { + CryptoErrorStore* errors = CryptoJob::errors(); + errors->Insert(NodeCryptoError::ENCAPSULATION_FAILED); + errors->SetNodeErrorCode("ERR_CRYPTO_OPERATION_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 e00aa04baa897e..70eb96d57c6387 100644 --- a/src/crypto/crypto_kem.h +++ b/src/crypto/crypto_kem.h @@ -44,16 +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, - CryptoErrorStore* errors); +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 { @@ -80,7 +94,6 @@ struct KEMDecapsulateTraits final { ByteSource* out); }; -using KEMEncapsulateJob = DeriveBitsJob; using KEMDecapsulateJob = DeriveBitsJob; void InitializeKEM(Environment* env, v8::Local target); diff --git a/src/crypto/crypto_keys.cc b/src/crypto/crypto_keys.cc index 40b5ae9563ee7b..1404390f4bc8bc 100644 --- a/src/crypto/crypto_keys.cc +++ b/src/crypto/crypto_keys.cc @@ -385,11 +385,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(); @@ -431,14 +434,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, @@ -629,7 +641,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)); } @@ -1460,15 +1474,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; @@ -1491,16 +1507,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 acec4b993613cd..56fbde663cd1bb 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; @@ -313,6 +315,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) @@ -353,6 +362,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"); @@ -360,6 +372,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()); @@ -421,7 +434,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 28eb760dbb4cd6..ce44ad15fa79d3 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 8d22350adffaac..711984e5e4f23f 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 @@ -544,7 +540,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 7c680dc2c5eae9..6ed7d7d25db02e 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 4ba019ddca05f4..b368c58734345f 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 50ece35429e932..8abf3fd8afe483 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/cctest/test_node_crypto_env.cc b/test/cctest/test_node_crypto_env.cc index 77aab8f4182d4f..fddf584d7d41f7 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 diff --git a/test/fixtures/keys/Makefile b/test/fixtures/keys/Makefile index c1e2fde9c3874b..128e928a59168e 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 00000000000000..33834d7f0eb385 --- /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 00000000000000..5740ae9cff0659 --- /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/fixtures/webcrypto/supports-modern-algorithms.mjs b/test/fixtures/webcrypto/supports-modern-algorithms.mjs index a3e5fc54976483..acb0e249dd3886 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-crypto-dh-curves.js b/test/parallel/test-crypto-dh-curves.js index ddd5ea9377e63f..f14c58e7c200c1 100644 --- a/test/parallel/test-crypto-dh-curves.js +++ b/test/parallel/test-crypto-dh-curves.js @@ -5,6 +5,12 @@ 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, + DH_NOT_SUITABLE_GENERATOR, +} = crypto.constants; // Second OAKLEY group, see // https://github.com/nodejs/node-v0.x-archive/issues/2338 and @@ -15,12 +21,69 @@ 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' }); +} 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()); 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 7c82e4dd8b97ea..775b752ac3b105 100644 --- a/test/parallel/test-crypto-key-objects.js +++ b/test/parallel/test-crypto-key-objects.js @@ -909,6 +909,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-crypto-x509.js b/test/parallel/test-crypto-x509.js index a122ee9e300f30..353699cf911725 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' diff --git a/test/parallel/test-tls-connect-secure-context.js b/test/parallel/test-tls-connect-secure-context.js index a0d9170c20904f..442973118a563f 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', diff --git a/test/parallel/test-webcrypto-digest-turboshake-rfc.js b/test/parallel/test-webcrypto-digest-turboshake-rfc.js index 43762fecc2c41e..271fde76ab23ef 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 0b5586b19286be..a6f4b2d50f948f 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 4572f7e1dbcb94..8ec27109eeca41 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));