Files
node/test/cctest/test_node_crypto.cc
Filip Skokan 85bfe9a938 deps: remove ncrypto legacy OpenSSL backend
Remove the OpenSSL 1.x backend and its legacy dss1 digest aliases.
Rename the provider-backed path switch to NCRYPTO_USE_OPENSSL_PROVIDER
and rewrite the remaining version guards as OPENSSL_IS_BORINGSSL checks.

Remove version-adapter casts now that supported OpenSSL signatures are
uniform. Drop legacy digest-context, raw-key, seed, signature-context,
SM2, primality, and RSA2 key-detail fallbacks. Keep name-based key
algorithms and capability discovery, with numeric key adapters and
low-level key paths only for BoringSSL.

Use the same backend split for KDFs, PKCS#1 decoding, key serialization,
and provider EC/RSA key details. Gate BoringSSL-only alternatives
explicitly instead of leaving them in #else branches.

Signed-off-by: Filip Skokan <panva.ip@gmail.com>
Assisted-by: Codex
PR-URL: https://github.com/nodejs/node/pull/64777
Fixes: https://github.com/nodejs/node/issues/56733
Refs: https://github.com/nodejs/node/pull/64211
Refs: https://github.com/nodejs/node/pull/63966
Refs: https://github.com/nodejs/node/pull/53329
Refs: https://github.com/nodejs/node/pull/48950
Refs: https://github.com/nodejs/node/issues/45465
Refs: https://github.com/nodejs/node/issues/42827
Refs: https://github.com/nodejs/node/issues/47076
Refs: https://github.com/nodejs/node/pull/62862
Refs: https://github.com/nodejs/node/pull/38512
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Richard Lau <richard.lau@ibm.com>
Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
2026-10-25 22:36:30 +01:00

1239 lines
45 KiB
C++

// This simulates specifying the configuration option --openssl-system-ca-path
// and setting it to a file that does not exist.
#define NODE_OPENSSL_SYSTEM_CERT_PATH "/missing/ca.pem"
#include "crypto/crypto_cipher.h"
#include "crypto/crypto_context.h"
#include "gtest/gtest.h"
#include "ncrypto.h"
#include "node_options.h"
#include "openssl/err.h"
#include "openssl/x509v3.h"
#include "util.h"
#include <climits>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#if NCRYPTO_USE_OPENSSL_PROVIDER
#include <openssl/core_dispatch.h>
#include <openssl/core_names.h>
#include <openssl/params.h>
#include <openssl/provider.h>
#endif
using ncrypto::Ec;
using ncrypto::EVPKeyCtxPointer;
using ncrypto::EVPKeyPointer;
using ncrypto::KeyAlgorithm;
namespace {
struct GeneralNamesDeleter {
void operator()(GENERAL_NAMES* names) const {
sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
}
};
struct AccessDescriptionsDeleter {
void operator()(AUTHORITY_INFO_ACCESS* descriptions) const {
sk_ACCESS_DESCRIPTION_pop_free(descriptions, ACCESS_DESCRIPTION_free);
}
};
using GeneralNamePointer =
ncrypto::DeleteFnPtr<GENERAL_NAME, GENERAL_NAME_free>;
using GeneralNamesPointer = std::unique_ptr<GENERAL_NAMES, GeneralNamesDeleter>;
using AccessDescriptionPointer =
ncrypto::DeleteFnPtr<ACCESS_DESCRIPTION, ACCESS_DESCRIPTION_free>;
using AccessDescriptionsPointer =
std::unique_ptr<AUTHORITY_INFO_ACCESS, AccessDescriptionsDeleter>;
using ASN1ObjectPointer = ncrypto::DeleteFnPtr<ASN1_OBJECT, ASN1_OBJECT_free>;
using ASN1StringPointer =
ncrypto::DeleteFnPtr<ASN1_IA5STRING, ASN1_IA5STRING_free>;
using ASN1TimePointer = ncrypto::DeleteFnPtr<ASN1_TIME, ASN1_TIME_free>;
using X509ExtensionPointer =
ncrypto::DeleteFnPtr<X509_EXTENSION, X509_EXTENSION_free>;
using X509NamePointer = ncrypto::DeleteFnPtr<X509_NAME, X509_NAME_free>;
std::string LongObjectIdentifier() {
std::string oid = "1.2";
for (int i = 0; i < 30; i++) oid += ".123456789";
return oid;
}
std::string BioContents(const ncrypto::BIOPointer& bio) {
BUF_MEM* mem = bio;
if (mem == nullptr) return {};
return std::string(mem->data, mem->length);
}
} // namespace
/*
* This test verifies that a call to NewRootCertDir with the build time
* configuration option --openssl-system-ca-path set to an missing file, will
* not leave any OpenSSL errors on the OpenSSL error stack.
* See https://github.com/nodejs/node/issues/35456 for details.
*/
TEST(NodeCrypto, NewRootCertStore) {
node::per_process::cli_options->ssl_openssl_cert_store = true;
X509_STORE* store = node::crypto::NewRootCertStore(nullptr);
ASSERT_TRUE(store);
ASSERT_EQ(ERR_peek_error(), 0UL) << "NewRootCertStore should not have left "
"any errors on the OpenSSL error stack\n";
X509_STORE_free(store);
}
TEST(NodeCrypto, TryGetIntCipherOutputLength) {
int output_len = 0;
EXPECT_TRUE(
node::crypto::TryGetIntCipherOutputLength(INT_MAX - 16, 16, &output_len));
EXPECT_EQ(output_len, INT_MAX);
EXPECT_FALSE(
node::crypto::TryGetIntCipherOutputLength(INT_MAX - 15, 16, &output_len));
EXPECT_FALSE(node::crypto::TryGetIntCipherOutputLength(
0, static_cast<size_t>(INT_MAX) + 1, &output_len));
}
TEST(NodeCrypto, KeyAlgorithmNames) {
EXPECT_EQ(KeyAlgorithm::FromName("rsa-pss"), &KeyAlgorithm::RSA_PSS);
EXPECT_EQ(KeyAlgorithm::FromName("ML-DSA-44"), &KeyAlgorithm::ML_DSA_44);
EXPECT_EQ(KeyAlgorithm::FromName("unknown-key-algorithm"), nullptr);
EXPECT_EQ(KeyAlgorithm::FromName(nullptr), nullptr);
EXPECT_FALSE(EVPKeyPointer().isA(KeyAlgorithm::RSA));
EXPECT_FALSE(EVPKeyPointer::isA(nullptr, KeyAlgorithm::RSA));
auto empty = EVPKeyPointer::New();
ASSERT_TRUE(empty);
EXPECT_FALSE(empty.isA(KeyAlgorithm::RSA));
EXPECT_FALSE(empty.supportsContextString());
EXPECT_FALSE(EVPKeyPointer().supportsContextString());
EXPECT_FALSE(empty.rawSeed());
EXPECT_FALSE(EVPKeyPointer().rawSeed());
EXPECT_FALSE(empty.isA("unknown-key-algorithm"));
EXPECT_FALSE(empty.isA(static_cast<const char*>(nullptr)));
}
#if NCRYPTO_USE_OPENSSL_PROVIDER
TEST(NodeCrypto, ProviderPkcs1PublicKeyImport) {
ncrypto::ClearErrorOnReturn clear_errors;
auto ctx = EVPKeyCtxPointer::NewFromAlgorithm(KeyAlgorithm::RSA);
ASSERT_TRUE(ctx);
ASSERT_TRUE(ctx.initForKeygen());
ASSERT_TRUE(ctx.setRsaKeygenBits(2048));
EVP_PKEY* raw = nullptr;
ASSERT_EQ(EVP_PKEY_keygen(ctx.get(), &raw), 1);
EVPKeyPointer key(raw);
for (const auto format :
{EVPKeyPointer::PKFormatType::PEM, EVPKeyPointer::PKFormatType::DER}) {
const EVPKeyPointer::PublicKeyEncodingConfig config(
false, format, EVPKeyPointer::PKEncodingType::PKCS1);
auto encoded = key.writePublicKey(config);
ASSERT_TRUE(encoded);
const BUF_MEM* mem = encoded.value;
ASSERT_NE(mem, nullptr);
const ncrypto::Buffer<const unsigned char> input{
reinterpret_cast<const unsigned char*>(mem->data), mem->length};
auto imported = EVPKeyPointer::TryParsePublicKey(config, input);
ASSERT_TRUE(imported);
EXPECT_NE(EVP_PKEY_get0_provider(imported.value.get()), nullptr);
EXPECT_TRUE(imported.value.isA(KeyAlgorithm::RSA));
EXPECT_EQ(EVP_PKEY_eq(key.get(), imported.value.get()), 1);
}
}
namespace {
struct RsaLoadTestContext {
OSSL_FUNC_BIO_read_ex_fn* read = nullptr;
int selection = 0;
int loads = 0;
int frees = 0;
};
void* RsaLoadTestDecoderNew(void* context) {
return context;
}
void RsaLoadTestDecoderFree(void*) {}
int RsaLoadTestDecode(void* context,
OSSL_CORE_BIO* input,
int selection,
OSSL_CALLBACK* callback,
void* arg,
OSSL_PASSPHRASE_CALLBACK*,
void*) {
auto* state = static_cast<RsaLoadTestContext*>(context);
unsigned char sentinel = 0;
size_t size = 0;
// Only exercise construction and reference ownership, not ASN.1 parsing.
if (!state->read(input, &sentinel, 1, &size) || size != 1 ||
sentinel != 0x42) {
return 0;
}
state->selection = selection;
char type[] = "RSA";
const OSSL_PARAM params[] = {
OSSL_PARAM_utf8_string(
OSSL_OBJECT_PARAM_DATA_TYPE, type, sizeof(type) - 1),
OSSL_PARAM_octet_string(
OSSL_OBJECT_PARAM_REFERENCE, &state, sizeof(state)),
OSSL_PARAM_END,
};
return callback(params, arg);
}
void* RsaLoadTestLoad(const void* reference, size_t size) {
if (size != sizeof(RsaLoadTestContext*)) return nullptr;
auto* state = *static_cast<RsaLoadTestContext* const*>(reference);
state->loads++;
return state;
}
void RsaLoadTestFree(void* context) {
static_cast<RsaLoadTestContext*>(context)->frees++;
}
int RsaLoadTestHas(const void*, int selection) {
return (selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) == 0;
}
const OSSL_ALGORITHM* RsaLoadTestQuery(void*, int operation, int* no_cache) {
*no_cache = 0;
static const OSSL_DISPATCH decoder[] = {
{OSSL_FUNC_DECODER_NEWCTX,
reinterpret_cast<void (*)(void)>(RsaLoadTestDecoderNew)},
{OSSL_FUNC_DECODER_FREECTX,
reinterpret_cast<void (*)(void)>(RsaLoadTestDecoderFree)},
{OSSL_FUNC_DECODER_DECODE,
reinterpret_cast<void (*)(void)>(RsaLoadTestDecode)},
{0, nullptr},
};
static const OSSL_DISPATCH keymgmt[] = {
{OSSL_FUNC_KEYMGMT_LOAD,
reinterpret_cast<void (*)(void)>(RsaLoadTestLoad)},
{OSSL_FUNC_KEYMGMT_FREE,
reinterpret_cast<void (*)(void)>(RsaLoadTestFree)},
{OSSL_FUNC_KEYMGMT_HAS, reinterpret_cast<void (*)(void)>(RsaLoadTestHas)},
{0, nullptr},
};
static const OSSL_ALGORITHM decoders[] = {
{"RSA",
"provider=node-test-rsa-load,input=der,structure=type-specific",
decoder,
"Test RSA decoder without export"},
{nullptr, nullptr, nullptr, nullptr},
};
static const OSSL_ALGORITHM keymgmts[] = {
{"RSA",
"provider=node-test-rsa-load",
keymgmt,
"Test RSA reference load"},
{nullptr, nullptr, nullptr, nullptr},
};
if (operation == OSSL_OP_DECODER) return decoders;
return operation == OSSL_OP_KEYMGMT ? keymgmts : nullptr;
}
void RsaLoadTestTeardown(void* context) {
delete static_cast<RsaLoadTestContext*>(context);
}
int RsaLoadTestProviderInit(const OSSL_CORE_HANDLE*,
const OSSL_DISPATCH* in,
const OSSL_DISPATCH** out,
void** context) {
auto state = std::make_unique<RsaLoadTestContext>();
for (; in->function_id != 0; in++) {
if (in->function_id == OSSL_FUNC_BIO_READ_EX) {
state->read = OSSL_FUNC_BIO_read_ex(in);
}
}
if (state->read == nullptr) return 0;
static const OSSL_DISPATCH dispatch[] = {
{OSSL_FUNC_PROVIDER_QUERY_OPERATION,
reinterpret_cast<void (*)(void)>(RsaLoadTestQuery)},
{OSSL_FUNC_PROVIDER_TEARDOWN,
reinterpret_cast<void (*)(void)>(RsaLoadTestTeardown)},
{0, nullptr},
};
*context = state.release();
*out = dispatch;
return 1;
}
} // namespace
TEST(NodeCrypto, ProviderPkcs1PublicKeyLoadWithoutExport) {
ncrypto::ClearErrorOnReturn clear_errors;
ncrypto::DeleteFnPtr<OSSL_LIB_CTX, OSSL_LIB_CTX_free> libctx(
OSSL_LIB_CTX_new());
ASSERT_TRUE(libctx);
ASSERT_EQ(OSSL_PROVIDER_add_builtin(
libctx.get(), "node-test-rsa-load", RsaLoadTestProviderInit),
1);
auto* provider = OSSL_PROVIDER_load(libctx.get(), "node-test-rsa-load");
auto unload_provider =
node::OnScopeLeave([provider] { OSSL_PROVIDER_unload(provider); });
ASSERT_NE(provider, nullptr);
auto* state = static_cast<RsaLoadTestContext*>(
OSSL_PROVIDER_get0_provider_ctx(provider));
OSSL_LIB_CTX* previous_libctx = OSSL_LIB_CTX_set0_default(libctx.get());
auto restore_libctx = node::OnScopeLeave(
[previous_libctx] { OSSL_LIB_CTX_set0_default(previous_libctx); });
// Neither decoder export nor keymgmt import is available in this provider.
const unsigned char sentinel[] = {0x42};
const EVPKeyPointer::PublicKeyEncodingConfig config(
false,
EVPKeyPointer::PKFormatType::DER,
EVPKeyPointer::PKEncodingType::PKCS1);
{
auto imported = EVPKeyPointer::TryParsePublicKey(config, {sentinel, 1});
ASSERT_TRUE(imported);
EXPECT_EQ(EVP_PKEY_get0_provider(imported.value.get()), provider);
EXPECT_TRUE(imported.value.isA(KeyAlgorithm::RSA));
EXPECT_EQ(state->selection, EVP_PKEY_PUBLIC_KEY);
EXPECT_EQ(state->loads, 1);
EXPECT_EQ(state->frees, 0);
}
EXPECT_EQ(state->frees, 1);
}
#endif
TEST(NodeCrypto, UnsupportedRawExports) {
using Error = EVPKeyPointer::RawExportError;
EVPKeyPointer key;
for (int i = 0; i < 2; i++) {
const auto seed = key.rawSeed();
EXPECT_FALSE(seed);
EXPECT_EQ(seed.error, Error::UNSUPPORTED_KEY_TYPE);
const auto jwk = key.exportRawJwk(/* include_private */ false);
EXPECT_FALSE(jwk);
EXPECT_EQ(jwk.error, Error::UNSUPPORTED_KEY_TYPE);
key = EVPKeyPointer::New();
ASSERT_TRUE(key);
}
auto ctx = EVPKeyCtxPointer::NewFromAlgorithm(KeyAlgorithm::RSA);
ASSERT_TRUE(ctx);
ASSERT_EQ(EVP_PKEY_keygen_init(ctx.get()), 1);
EVP_PKEY* raw = nullptr;
ASSERT_EQ(EVP_PKEY_keygen(ctx.get(), &raw), 1);
key.reset(raw);
EXPECT_EQ(key.rawSeed().error, Error::UNSUPPORTED_KEY_TYPE);
EXPECT_EQ(key.exportRawJwk(/* include_private */ false).error,
Error::UNSUPPORTED_KEY_TYPE);
}
namespace {
void CheckRawJwkImport(const EVPKeyPointer& key) {
auto jwk = key.exportRawJwk(/* include_private */ true);
ASSERT_TRUE(jwk);
const auto& data = jwk.value;
const ncrypto::Buffer<const unsigned char> pub = data.public_key;
const ncrypto::Buffer<const unsigned char> priv = data.private_key;
auto private_key = EVPKeyPointer::NewRawJwk(*data.algorithm, pub, priv);
ASSERT_TRUE(private_key);
auto exported = private_key.exportRawJwk(/* include_private */ true);
ASSERT_TRUE(exported);
EXPECT_EQ(exported.value.algorithm, data.algorithm);
ASSERT_EQ(exported.value.private_key.size(), priv.len);
EXPECT_EQ(memcmp(exported.value.private_key.get(), priv.data, priv.len), 0);
auto public_key = EVPKeyPointer::NewRawJwk(*data.algorithm, pub);
ASSERT_TRUE(public_key);
auto exported_public = public_key.exportRawJwk(/* include_private */ false);
ASSERT_TRUE(exported_public);
EXPECT_FALSE(exported_public.value.private_key);
ASSERT_EQ(exported_public.value.public_key.size(), pub.len);
EXPECT_EQ(memcmp(exported_public.value.public_key.get(), pub.data, pub.len),
0);
auto different_pub = ncrypto::DataPointer::Copy({pub.data, pub.len});
ASSERT_TRUE(different_pub);
ASSERT_GT(different_pub.size(), 0u);
different_pub.get<unsigned char>()[0] ^= 1;
const ncrypto::Buffer<const unsigned char> mismatch = different_pub;
EXPECT_FALSE(EVPKeyPointer::NewRawJwk(*data.algorithm, mismatch, priv));
EXPECT_FALSE(EVPKeyPointer::NewRawJwk(
*data.algorithm, pub, ncrypto::Buffer<const unsigned char>{nullptr, 0}));
EXPECT_FALSE(EVPKeyPointer::NewRawJwk(KeyAlgorithm::RSA, pub, priv));
}
} // namespace
TEST(NodeCrypto, RsaPrimeProduct) {
ncrypto::ClearErrorOnReturn clear_errors;
EXPECT_FALSE(ncrypto::Rsa().checkPrimeProduct());
auto ctx = EVPKeyCtxPointer::NewFromAlgorithm(KeyAlgorithm::RSA);
ASSERT_TRUE(ctx);
ASSERT_TRUE(ctx.initForKeygen());
EVP_PKEY* raw = nullptr;
ASSERT_EQ(EVP_PKEY_keygen(ctx.get(), &raw), 1);
EVPKeyPointer key(raw);
ncrypto::Rsa rsa = key;
ASSERT_TRUE(rsa);
EXPECT_TRUE(rsa.checkPrimeProduct());
auto pub = rsa.getPublicKey();
ncrypto::BignumPointer n(BN_dup(pub.n));
ncrypto::BignumPointer e(BN_dup(pub.e));
ASSERT_TRUE(n);
ASSERT_TRUE(e);
ASSERT_EQ(BN_add_word(n.get(), 1), 1);
ASSERT_TRUE(rsa.setPublicKey(std::move(n), std::move(e)));
EXPECT_FALSE(rsa.checkPrimeProduct());
}
TEST(NodeCrypto, EcKeyComponents) {
ncrypto::ClearErrorOnReturn clear_errors;
ncrypto::BignumPointer x;
ncrypto::BignumPointer y;
ncrypto::BignumPointer priv;
int degree = 0;
EXPECT_FALSE(
ncrypto::Ec::GetKeyComponents(EVPKeyPointer(), &x, &y, &priv, &degree));
EXPECT_EQ(ncrypto::Ec::GetCurveId(EVPKeyPointer()), NID_undef);
EXPECT_FALSE(ncrypto::Ec::TryExportPublic(EVPKeyPointer(),
POINT_CONVERSION_UNCOMPRESSED));
EXPECT_FALSE(ncrypto::Ec::ExportPrivate(EVPKeyPointer()));
auto empty = EVPKeyPointer::New();
ASSERT_TRUE(empty);
EXPECT_FALSE(ncrypto::Ec::GetKeyComponents(empty, &x, &y, &priv, &degree));
for (int nid : {NID_X9_62_prime256v1, NID_secp384r1, NID_secp521r1}) {
auto ec = ncrypto::ECKeyPointer::NewByCurveName(nid);
ASSERT_TRUE(ec);
EXPECT_FALSE(ec.checkPrivateKey());
auto parameters = EVPKeyPointer::New();
ASSERT_TRUE(parameters);
ASSERT_TRUE(parameters.set(ec));
EXPECT_EQ(ncrypto::Ec::GetCurveId(parameters), nid);
EXPECT_FALSE(
ncrypto::Ec::GetKeyComponents(parameters, &x, &y, nullptr, &degree));
ASSERT_TRUE(ec.generate());
EXPECT_TRUE(ec.checkPrivateKey());
auto key = EVPKeyPointer::New();
ASSERT_TRUE(key);
ASSERT_TRUE(key.set(ec));
EXPECT_TRUE(key.isA(KeyAlgorithm::EC));
EXPECT_EQ(ncrypto::Ec::GetCurveId(key), nid);
ASSERT_TRUE(ncrypto::Ec::GetKeyComponents(key, &x, &y, &priv, &degree));
EXPECT_EQ(degree, EC_GROUP_get_degree(ec.getGroup()));
EXPECT_EQ(BN_cmp(priv.get(), ec.getPrivateKey()), 0);
const size_t width = (degree + 7) / 8;
auto point = ncrypto::DataPointer::Alloc(1 + 2 * width);
ASSERT_TRUE(point);
ASSERT_EQ(EC_POINT_point2oct(ec.getGroup(),
ec.getPublicKey(),
POINT_CONVERSION_UNCOMPRESSED,
point.get<unsigned char>(),
point.size(),
nullptr),
point.size());
auto x_bytes = x.encodePadded(width);
auto y_bytes = y.encodePadded(width);
ASSERT_TRUE(x_bytes);
ASSERT_TRUE(y_bytes);
EXPECT_EQ(memcmp(x_bytes.get(), point.get<unsigned char>() + 1, width), 0);
EXPECT_EQ(
memcmp(y_bytes.get(), point.get<unsigned char>() + 1 + width, width),
0);
auto raw_private = ncrypto::Ec::ExportPrivate(key);
auto expected_private = priv.encodePadded(width);
ASSERT_TRUE(raw_private);
ASSERT_EQ(raw_private.size(), expected_private.size());
EXPECT_EQ(
memcmp(raw_private.get(), expected_private.get(), raw_private.size()),
0);
auto raw_public =
ncrypto::Ec::TryExportPublic(key, POINT_CONVERSION_UNCOMPRESSED);
#if NCRYPTO_USE_OPENSSL_PROVIDER
ASSERT_TRUE(raw_public);
ASSERT_EQ(raw_public.size(), point.size());
EXPECT_EQ(memcmp(raw_public.get(), point.get(), point.size()), 0);
#elif NCRYPTO_USE_BORINGSSL
EXPECT_FALSE(raw_public);
#endif
EXPECT_FALSE(
ncrypto::Ec::TryExportPublic(key, POINT_CONVERSION_COMPRESSED));
auto public_ec = ncrypto::ECKeyPointer::NewByCurveName(nid);
ASSERT_TRUE(public_ec);
ASSERT_TRUE(public_ec.setPublicKeyRaw(x, y));
auto public_key = EVPKeyPointer::New();
ASSERT_TRUE(public_key);
ASSERT_TRUE(public_key.set(public_ec));
EXPECT_TRUE(
ncrypto::Ec::GetKeyComponents(public_key, &x, &y, nullptr, &degree));
EXPECT_FALSE(
ncrypto::Ec::GetKeyComponents(public_key, &x, &y, &priv, &degree));
EXPECT_FALSE(ncrypto::Ec::ExportPrivate(public_key));
}
}
TEST(NodeCrypto, ResolveKeyAlgorithm) {
ncrypto::ClearErrorOnReturn clear_errors;
EXPECT_EQ(EVPKeyPointer().getAlgorithm(), nullptr);
auto key = EVPKeyPointer::New();
ASSERT_TRUE(key);
EXPECT_EQ(key.getAlgorithm(), nullptr);
const unsigned char seed[64] = {};
for (const auto* algorithm : {&KeyAlgorithm::ED25519,
&KeyAlgorithm::X25519,
&KeyAlgorithm::ML_DSA_44,
&KeyAlgorithm::ML_KEM_768}) {
if (!algorithm->isAvailable()) continue;
const ncrypto::Buffer<const unsigned char> input{
seed, algorithm->seedSize() == 0 ? 32 : algorithm->seedSize()};
key = algorithm->seedSize() == 0
? EVPKeyPointer::NewRawPrivate(*algorithm, input)
: EVPKeyPointer::NewRawSeed(*algorithm, input);
ASSERT_TRUE(key);
EXPECT_EQ(key.getAlgorithm(), algorithm);
CheckRawJwkImport(key);
if (algorithm->seedSize() != 0) {
auto exported = key.rawSeed();
ASSERT_TRUE(exported);
ASSERT_EQ(exported.value.size(), input.len);
EXPECT_EQ(memcmp(exported.value.get(), input.data, input.len), 0);
}
EVPKeyPointer moved(std::move(key));
EXPECT_EQ(key.getAlgorithm(), nullptr);
EXPECT_EQ(moved.getAlgorithm(), algorithm);
key.reset(moved.release());
EXPECT_EQ(moved.getAlgorithm(), nullptr);
EXPECT_EQ(key.getAlgorithm(), algorithm);
key.reset();
EXPECT_EQ(key.getAlgorithm(), nullptr);
}
#if NCRYPTO_USE_OPENSSL_PROVIDER
// Legacy keys must resolve even without provider-backed key material.
key = EVPKeyPointer::New();
ASSERT_EQ(EVP_PKEY_set_type(key.get(), NID_rsaEncryption), 1);
EXPECT_EQ(key.getAlgorithm(), &KeyAlgorithm::RSA);
#endif
}
TEST(NodeCrypto, PublicKeyTypeNames) {
EXPECT_EQ(EVPKeyPointer().getKeyTypeName(), nullptr);
EXPECT_EQ(EVPKeyPointer::New().getKeyTypeName(), nullptr);
EXPECT_STREQ(KeyAlgorithm::RSA.keyTypeName(), "rsa");
EXPECT_STREQ(KeyAlgorithm::RSA_PSS.keyTypeName(), "rsa-pss");
EXPECT_STREQ(KeyAlgorithm::ML_DSA_44.keyTypeName(), "ml-dsa-44");
EXPECT_STREQ(KeyAlgorithm::SLH_DSA_SHAKE_256S.keyTypeName(),
"slh-dsa-shake-256s");
EXPECT_EQ(KeyAlgorithm::SM2.keyTypeName(), nullptr);
const unsigned char seed[32] = {};
auto key =
EVPKeyPointer::NewRawPrivate(KeyAlgorithm::ED25519, {seed, sizeof(seed)});
ASSERT_TRUE(key);
EXPECT_STREQ(key.getKeyTypeName(), "ed25519");
}
#if NCRYPTO_USE_OPENSSL_PROVIDER
TEST(NodeCrypto, LegacyKeyAlgorithmResolution) {
ncrypto::ClearErrorOnReturn clear_errors;
auto key = EVPKeyPointer::New();
ASSERT_TRUE(key);
ASSERT_EQ(EVP_PKEY_set_type(key.get(), NID_rsassaPss), 1);
EXPECT_EQ(key.getAlgorithm(), &KeyAlgorithm::RSA_PSS);
EXPECT_STREQ(key.getKeyTypeName(), "rsa-pss");
#ifndef OPENSSL_NO_SM2
ASSERT_EQ(EVP_PKEY_set_type(key.get(), NID_sm2), 1);
EXPECT_TRUE(key.isA(KeyAlgorithm::SM2));
EXPECT_FALSE(key.isA(KeyAlgorithm::EC));
EXPECT_EQ(key.getAlgorithm(), &KeyAlgorithm::SM2);
EXPECT_EQ(key.getKeyTypeName(), nullptr);
#endif
}
#endif
TEST(NodeCrypto, NamedKeysAndEcCurves) {
EXPECT_EQ(Ec::GetNamedKeyAlgorithm("ED25519"), &KeyAlgorithm::ED25519);
EXPECT_EQ(Ec::GetNamedKeyAlgorithm("X25519"), &KeyAlgorithm::X25519);
EXPECT_EQ(Ec::GetNamedKeyAlgorithm("P-256"), nullptr);
EXPECT_EQ(Ec::GetNamedKeyAlgorithm("prime256v1"), nullptr);
EXPECT_EQ(Ec::GetNamedKeyAlgorithm("unknown-curve"), nullptr);
EXPECT_EQ(Ec::GetCurveIdFromName("P-256"),
Ec::GetCurveIdFromName("prime256v1"));
}
TEST(NodeCrypto, EcGroupNames) {
ncrypto::ClearErrorOnReturn clear_errors;
EXPECT_FALSE(Ec::GetCurveName(EVPKeyPointer()));
EXPECT_FALSE(Ec::CheckCurveName(nullptr));
EXPECT_FALSE(Ec::CheckCurveName("node-test-unknown-curve"));
for (const char* name : {"P-256", "prime256v1"}) {
EXPECT_TRUE(Ec::CheckCurveName(name));
auto ctx = EVPKeyCtxPointer::NewFromAlgorithm(KeyAlgorithm::EC);
ASSERT_TRUE(ctx.initForParamgen());
ASSERT_TRUE(ctx.setEcParameters(name, OPENSSL_EC_NAMED_CURVE));
auto key = ctx.paramgen();
ASSERT_TRUE(key);
const auto group = Ec::GetCurveName(key);
ASSERT_TRUE(group);
EXPECT_EQ(group.value(), "prime256v1");
}
}
#if NCRYPTO_USE_OPENSSL_PROVIDER
namespace {
// Deliberately longer than the fixed-size group buffer formerly used by
// ncrypto.
constexpr char kProviderEcGroup[] =
"node-test-provider-ec-group-without-an-object-identifier-"
"and-with-a-name-longer-than-eighty-bytes";
int EcGroupTestSetParams(void* data, const OSSL_PARAM params[]) {
const OSSL_PARAM* group =
OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_GROUP_NAME);
if (group == nullptr) return 1;
const char* name = nullptr;
if (OSSL_PARAM_get_utf8_string_ptr(group, &name) != 1 || name == nullptr) {
return 0;
}
*static_cast<std::string*>(data) = name;
return 1;
}
void* EcGroupTestGenInit(void*, int, const OSSL_PARAM params[]) {
auto data = std::make_unique<std::string>();
if (params != nullptr && !EcGroupTestSetParams(data.get(), params)) {
return nullptr;
}
return data.release();
}
void* EcGroupTestGen(void* data, OSSL_CALLBACK*, void*) {
const auto& group = *static_cast<std::string*>(data);
// Defer rejecting unknown names until generation, as a provider may do.
if (group != kProviderEcGroup && group != "prime256v1") {
ERR_raise(ERR_LIB_USER, ERR_R_PASSED_INVALID_ARGUMENT);
return nullptr;
}
return new std::string(group);
}
void EcGroupTestFree(void* data) {
delete static_cast<std::string*>(data);
}
int EcGroupTestHas(const void* data, int selection) {
return (selection & OSSL_KEYMGMT_SELECT_KEYPAIR) == 0 &&
!static_cast<const std::string*>(data)->empty();
}
int EcGroupTestGetParams(void* data, OSSL_PARAM params[]) {
OSSL_PARAM* group = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_GROUP_NAME);
return group == nullptr ||
OSSL_PARAM_set_utf8_string(group,
static_cast<std::string*>(data)->c_str());
}
const OSSL_PARAM* EcGroupTestParams(void*, void*) {
static const OSSL_PARAM params[] = {
OSSL_PARAM_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME, nullptr, 0),
OSSL_PARAM_utf8_string(OSSL_PKEY_PARAM_EC_ENCODING, nullptr, 0),
OSSL_PARAM_END,
};
return params;
}
const OSSL_PARAM* EcGroupTestGettableParams(void*) {
return EcGroupTestParams(nullptr, nullptr);
}
const OSSL_ALGORITHM* EcGroupTestQuery(void*, int operation, int* no_cache) {
*no_cache = 0;
static const OSSL_DISPATCH keymgmt[] = {
{OSSL_FUNC_KEYMGMT_GEN_INIT,
reinterpret_cast<void (*)(void)>(EcGroupTestGenInit)},
{OSSL_FUNC_KEYMGMT_GEN_SET_PARAMS,
reinterpret_cast<void (*)(void)>(EcGroupTestSetParams)},
{OSSL_FUNC_KEYMGMT_GEN_SETTABLE_PARAMS,
reinterpret_cast<void (*)(void)>(EcGroupTestParams)},
{OSSL_FUNC_KEYMGMT_GEN, reinterpret_cast<void (*)(void)>(EcGroupTestGen)},
{OSSL_FUNC_KEYMGMT_GEN_CLEANUP,
reinterpret_cast<void (*)(void)>(EcGroupTestFree)},
{OSSL_FUNC_KEYMGMT_FREE,
reinterpret_cast<void (*)(void)>(EcGroupTestFree)},
{OSSL_FUNC_KEYMGMT_HAS, reinterpret_cast<void (*)(void)>(EcGroupTestHas)},
{OSSL_FUNC_KEYMGMT_GET_PARAMS,
reinterpret_cast<void (*)(void)>(EcGroupTestGetParams)},
{OSSL_FUNC_KEYMGMT_GETTABLE_PARAMS,
reinterpret_cast<void (*)(void)>(EcGroupTestGettableParams)},
{0, nullptr},
};
static const OSSL_ALGORITHM algorithms[] = {
{"EC", "provider=node-test-ec", keymgmt, "Test provider EC group names"},
{nullptr, nullptr, nullptr, nullptr},
};
return operation == OSSL_OP_KEYMGMT ? algorithms : nullptr;
}
int EcGroupTestProviderInit(const OSSL_CORE_HANDLE*,
const OSSL_DISPATCH*,
const OSSL_DISPATCH** out,
void**) {
static const OSSL_DISPATCH dispatch[] = {
{OSSL_FUNC_PROVIDER_QUERY_OPERATION,
reinterpret_cast<void (*)(void)>(EcGroupTestQuery)},
{0, nullptr},
};
*out = dispatch;
return 1;
}
void EcGroupTestUnloadProvider(OSSL_PROVIDER* provider) {
OSSL_PROVIDER_unload(provider);
}
} // namespace
TEST(NodeCrypto, ProviderEcGroupName) {
ncrypto::ClearErrorOnReturn clear_errors;
ncrypto::DeleteFnPtr<OSSL_LIB_CTX, OSSL_LIB_CTX_free> libctx(
OSSL_LIB_CTX_new());
ASSERT_TRUE(libctx);
ASSERT_EQ(OSSL_PROVIDER_add_builtin(
libctx.get(), "node-test-ec", EcGroupTestProviderInit),
1);
ncrypto::DeleteFnPtr<OSSL_PROVIDER, EcGroupTestUnloadProvider> provider(
OSSL_PROVIDER_load(libctx.get(), "node-test-ec"));
ASSERT_TRUE(provider);
{
OSSL_LIB_CTX* previous_libctx = OSSL_LIB_CTX_set0_default(libctx.get());
auto restore_libctx = node::OnScopeLeave(
[previous_libctx] { OSSL_LIB_CTX_set0_default(previous_libctx); });
// The provider supports generation, but intentionally has no import API.
EXPECT_TRUE(Ec::CheckCurveName(kProviderEcGroup));
EXPECT_FALSE(Ec::CheckCurveName("node-test-unknown-curve"));
EXPECT_TRUE(Ec::CheckCurveName("P-256"));
auto ctx = EVPKeyCtxPointer::NewFromAlgorithm(KeyAlgorithm::EC);
ASSERT_TRUE(ctx);
ASSERT_TRUE(ctx.initForParamgen());
ASSERT_TRUE(ctx.setEcParameters(kProviderEcGroup, OPENSSL_EC_NAMED_CURVE));
auto key = ctx.paramgen();
ASSERT_TRUE(key);
EXPECT_EQ(Ec::GetCurveId(key), NID_undef);
const auto name = Ec::GetCurveName(key);
ASSERT_TRUE(name);
EXPECT_EQ(name.value(), kProviderEcGroup);
// Only usable built-in curves are enumerated. The provider-only group
// remains usable by name without appearing in this list.
ERR_clear_error();
ERR_raise(ERR_LIB_USER, ERR_R_INTERNAL_ERROR);
const auto saved_error = ERR_peek_error();
std::vector<std::string> curves;
EXPECT_TRUE(Ec::GetCurves([&curves](const char* curve) {
curves.emplace_back(curve);
return true;
}));
EXPECT_EQ(curves, std::vector<std::string>{"prime256v1"});
EXPECT_EQ(ERR_peek_error(), saved_error);
EXPECT_EQ(ERR_peek_last_error(), saved_error);
unsigned int calls = 0;
EXPECT_FALSE(Ec::GetCurves([&calls](const char*) {
calls++;
ERR_raise(ERR_LIB_USER, ERR_R_PASSED_INVALID_ARGUMENT);
return false;
}));
EXPECT_EQ(calls, 1U);
EXPECT_EQ(ERR_get_error(), saved_error);
EXPECT_EQ(ERR_GET_REASON(ERR_get_error()), ERR_R_PASSED_INVALID_ARGUMENT);
EXPECT_EQ(ERR_get_error(), 0UL);
ASSERT_EQ(EVP_set_default_properties(nullptr, "provider=default"), 1);
EXPECT_TRUE(Ec::GetCurves([](const char*) {
ADD_FAILURE() << "No EC groups should match the default properties";
return true;
}));
}
}
#endif
TEST(NodeCrypto, NamedRawKey) {
const unsigned char seed[32] = {};
const ncrypto::Buffer<const unsigned char> input{seed, sizeof(seed)};
auto key = EVPKeyPointer::NewRawPrivate(KeyAlgorithm::ED25519, input);
ASSERT_TRUE(key);
EXPECT_TRUE(key.isA(KeyAlgorithm::ED25519));
EXPECT_FALSE(key.isA(KeyAlgorithm::X25519));
ASSERT_EQ(key.getAlgorithm(), &KeyAlgorithm::ED25519);
EXPECT_TRUE(key.getAlgorithm()->isOneShot());
EXPECT_TRUE(key.supportsRawPublic());
EXPECT_TRUE(key.supportsRawPrivate());
EXPECT_EQ(key.getAlgorithm()->seedSize(), 0);
#if NCRYPTO_USE_OPENSSL_PROVIDER
// Provider aliases are recognized without comparing the primary type name.
EXPECT_TRUE(key.isA("1.3.101.112"));
auto ctx = ncrypto::EVPKeyCtxPointer::NewFromName("1.3.101.112");
ASSERT_TRUE(ctx);
ASSERT_TRUE(ctx.initForKeygen());
EVP_PKEY* generated = nullptr;
ASSERT_EQ(EVP_PKEY_keygen(ctx.get(), &generated), 1);
EXPECT_TRUE(EVPKeyPointer(generated).isA(KeyAlgorithm::ED25519));
#endif
auto pub = key.rawPublicKey();
ASSERT_TRUE(pub);
auto imported = EVPKeyPointer::NewRawPublic(KeyAlgorithm::ED25519, pub);
ASSERT_TRUE(imported);
EXPECT_TRUE(imported.isA(KeyAlgorithm::ED25519));
auto exported = imported.rawPublicKey();
ASSERT_EQ(pub.size(), exported.size());
EXPECT_EQ(memcmp(pub.get(), exported.get(), pub.size()), 0);
}
TEST(NodeCrypto, NamedRsaPssKey) {
ncrypto::ClearErrorOnReturn clear_errors;
EXPECT_FALSE(EVPKeyCtxPointer::NewFromName("unknown-key-algorithm"));
EXPECT_FALSE(EVPKeyCtxPointer::NewFromName(nullptr));
#ifndef OPENSSL_IS_BORINGSSL
auto ctx = EVPKeyCtxPointer::NewFromAlgorithm(KeyAlgorithm::RSA_PSS);
ASSERT_TRUE(ctx);
ASSERT_TRUE(ctx.initForKeygen());
ASSERT_TRUE(ctx.setRsaKeygenBits(2048));
EVP_PKEY* raw = nullptr;
ASSERT_EQ(EVP_PKEY_keygen(ctx.get(), &raw), 1);
EVPKeyPointer key(raw);
EXPECT_TRUE(key.isA(KeyAlgorithm::RSA_PSS));
EXPECT_FALSE(key.isA(KeyAlgorithm::RSA));
EXPECT_FALSE(key.supportsContextString());
EXPECT_TRUE(key.isRsaVariant());
EXPECT_EQ(key.getDefaultSignPadding(), RSA_PKCS1_PSS_PADDING);
EXPECT_FALSE(key.supportsRawPublic());
ASSERT_EQ(key.getAlgorithm(), &KeyAlgorithm::RSA_PSS);
EXPECT_FALSE(key.getAlgorithm()->isOneShot());
#endif
}
TEST(NodeCrypto, ProviderPqcKeyWithoutLegacyId) {
ncrypto::ClearErrorOnReturn clear_errors;
if (!KeyAlgorithm::ML_DSA_44.isAvailable() ||
!KeyAlgorithm::ML_KEM_768.isAvailable())
GTEST_SKIP();
const unsigned char seed[64] = {};
for (const auto* algorithm :
{&KeyAlgorithm::ML_DSA_44, &KeyAlgorithm::ML_KEM_768}) {
const ncrypto::Buffer<const unsigned char> input{seed,
algorithm->seedSize()};
auto key = EVPKeyPointer::NewRawSeed(*algorithm, input);
ASSERT_TRUE(key);
#if NCRYPTO_USE_OPENSSL_PROVIDER
EXPECT_EQ(EVP_PKEY_id(key.get()), -1);
#endif
EXPECT_TRUE(key.isA(*algorithm));
ASSERT_EQ(key.getAlgorithm(), algorithm);
EXPECT_EQ(key.getAlgorithm()->seedSize(), input.len);
EXPECT_TRUE(key.supportsRawPublic());
EXPECT_FALSE(key.supportsRawPrivate());
auto exported_seed = key.rawSeed();
ASSERT_TRUE(exported_seed);
ASSERT_EQ(exported_seed.value.size(), input.len);
EXPECT_EQ(memcmp(exported_seed.value.get(), seed, input.len), 0);
auto pub = key.rawPublicKey();
ASSERT_TRUE(pub);
auto imported = EVPKeyPointer::NewRawPublic(*algorithm, pub);
ASSERT_TRUE(imported);
EXPECT_EQ(imported.getAlgorithm(), algorithm);
auto private_jwk = key.exportRawJwk(/* include_private */ true);
ASSERT_TRUE(private_jwk);
EXPECT_EQ(private_jwk.value.algorithm, algorithm);
ASSERT_EQ(private_jwk.value.private_key.size(), input.len);
EXPECT_EQ(memcmp(private_jwk.value.private_key.get(), seed, input.len), 0);
ASSERT_EQ(private_jwk.value.public_key.size(), pub.size());
EXPECT_EQ(memcmp(private_jwk.value.public_key.get(), pub.get(), pub.size()),
0);
auto public_jwk = imported.exportRawJwk(/* include_private */ false);
ASSERT_TRUE(public_jwk);
EXPECT_EQ(public_jwk.value.algorithm, algorithm);
EXPECT_FALSE(public_jwk.value.private_key);
ASSERT_EQ(public_jwk.value.public_key.size(), pub.size());
EXPECT_EQ(memcmp(public_jwk.value.public_key.get(), pub.get(), pub.size()),
0);
EXPECT_EQ(imported.rawSeed().error,
EVPKeyPointer::RawExportError::MISSING_SEED);
EXPECT_EQ(imported.exportRawJwk(/* include_private */ true).error,
EVPKeyPointer::RawExportError::MISSING_SEED);
}
}
TEST(NodeCrypto, PqcJwkRawPrivateExport) {
ncrypto::ClearErrorOnReturn clear_errors;
const auto& algorithm = KeyAlgorithm::SLH_DSA_SHA2_128S;
if (!algorithm.isAvailable()) GTEST_SKIP();
auto ctx = EVPKeyCtxPointer::NewFromAlgorithm(algorithm);
ASSERT_TRUE(ctx);
ASSERT_EQ(EVP_PKEY_keygen_init(ctx.get()), 1);
EVP_PKEY* raw = nullptr;
ASSERT_EQ(EVP_PKEY_keygen(ctx.get(), &raw), 1);
EVPKeyPointer key(raw);
CheckRawJwkImport(key);
auto priv = key.rawPrivateKey();
auto pub = key.rawPublicKey();
ASSERT_TRUE(priv);
ASSERT_TRUE(pub);
auto jwk = key.exportRawJwk(/* include_private */ true);
ASSERT_TRUE(jwk);
EXPECT_EQ(jwk.value.algorithm, &algorithm);
ASSERT_EQ(jwk.value.private_key.size(), priv.size());
EXPECT_EQ(memcmp(jwk.value.private_key.get(), priv.get(), priv.size()), 0);
ASSERT_EQ(jwk.value.public_key.size(), pub.size());
EXPECT_EQ(memcmp(jwk.value.public_key.get(), pub.get(), pub.size()), 0);
EXPECT_EQ(key.rawSeed().error,
EVPKeyPointer::RawExportError::UNSUPPORTED_KEY_TYPE);
}
#ifdef OPENSSL_IS_BORINGSSL
TEST(NodeCrypto, UnavailableBoringSSLKeyAlgorithms) {
ncrypto::ClearErrorOnReturn clear_errors;
for (const auto* algorithm :
{&KeyAlgorithm::SM2, &KeyAlgorithm::X448, &KeyAlgorithm::ED448}) {
EXPECT_FALSE(algorithm->isAvailable());
EXPECT_FALSE(EVPKeyCtxPointer::NewFromAlgorithm(*algorithm));
}
}
#endif
#if NCRYPTO_USE_OPENSSL_PROVIDER
TEST(NodeCrypto, PrivateKeyEncodingOwnsFetchedCipher) {
ncrypto::ClearErrorOnReturn clear_errors;
EVPKeyPointer::PrivateKeyEncodingConfig assigned;
{
EVPKeyPointer::PrivateKeyEncodingConfig original;
original.cipher =
ncrypto::Cipher::FromNameForKeyEncoding("2.16.840.1.101.3.4.1.42");
ASSERT_TRUE(original.cipher);
ASSERT_NE(EVP_CIPHER_get0_provider(original.cipher.get()), nullptr);
const auto copied = original;
assigned = copied;
}
ASSERT_TRUE(assigned.cipher);
EXPECT_NE(EVP_CIPHER_get0_provider(assigned.cipher.get()), nullptr);
EXPECT_EQ(EVP_CIPHER_is_a(assigned.cipher.get(), "AES-256-CBC"), 1);
auto ctx = ncrypto::CipherCtxPointer::New();
const unsigned char key[32] = {};
const unsigned char iv[16] = {};
EXPECT_TRUE(ctx.init(assigned.cipher, true, key, iv));
}
TEST(NodeCrypto, PrivateKeyEncodingProviderOnlyCipher) {
ncrypto::ClearErrorOnReturn clear_errors;
const auto available = ncrypto::Cipher::FromName("AES-128-CBC-CTS");
if (!available) GTEST_SKIP();
const auto cipher =
ncrypto::Cipher::FromNameForKeyEncoding("AES-128-CBC-CTS");
ASSERT_TRUE(cipher);
EXPECT_NE(EVP_CIPHER_get0_provider(cipher.get()), nullptr);
EXPECT_EQ(EVP_CIPHER_is_a(cipher.get(), "AES-128-CBC-CTS"), 1);
}
#endif
TEST(NodeCrypto, BIOPointerRequiresCompleteWrites) {
BIO* raw_output = nullptr;
BIO* raw_input = nullptr;
ASSERT_EQ(BIO_new_bio_pair(&raw_output, 1, &raw_input, 1), 1);
ncrypto::BIOPointer output(raw_output);
ncrypto::BIOPointer input(raw_input);
EXPECT_FALSE(ncrypto::BIOPointer::WriteAll(output, "ab"));
raw_output = nullptr;
raw_input = nullptr;
ASSERT_EQ(BIO_new_bio_pair(&raw_output, 1, &raw_input, 1), 1);
output.reset(raw_output);
input.reset(raw_input);
EXPECT_FALSE(ncrypto::BIOPointer::PrintfAll(output, "%s", "ab"));
}
TEST(NodeCrypto, PreservesInvalidX509TimeDiagnostic) {
ncrypto::X509Pointer cert(X509_new());
ASN1TimePointer invalid_time(ASN1_STRING_type_new(V_ASN1_UTCTIME));
ASSERT_TRUE(cert);
ASSERT_TRUE(invalid_time);
ASSERT_EQ(ASN1_STRING_set(invalid_time.get(), "221303214037Z", -1), 1);
ASSERT_EQ(X509_set1_notBefore(cert.get(), invalid_time.get()), 1);
const ncrypto::BIOPointer valid_from = cert.view().getValidFrom();
ASSERT_TRUE(valid_from);
EXPECT_EQ(BioContents(valid_from), "Bad time value");
EXPECT_FALSE(cert.view().getValidFromTime().has_value());
}
#if NCRYPTO_USE_BORINGSSL
TEST(NodeCrypto, PreservesBoringSSLTimezoneOffsetDiagnostic) {
ncrypto::X509Pointer cert(X509_new());
ASN1TimePointer offset_time(ASN1_STRING_type_new(V_ASN1_UTCTIME));
ASSERT_TRUE(cert);
ASSERT_TRUE(offset_time);
ASSERT_EQ(ASN1_STRING_set(offset_time.get(), "700101000000+0400", -1), 1);
ASSERT_EQ(ASN1_TIME_check(offset_time.get()), 1);
ASSERT_EQ(X509_set1_notBefore(cert.get(), offset_time.get()), 1);
ASSERT_EQ(X509_set1_notAfter(cert.get(), offset_time.get()), 1);
const ncrypto::BIOPointer valid_from = cert.view().getValidFrom();
const ncrypto::BIOPointer valid_to = cert.view().getValidTo();
ASSERT_TRUE(valid_from);
ASSERT_TRUE(valid_to);
EXPECT_EQ(BioContents(valid_from), "Bad time value");
EXPECT_EQ(BioContents(valid_to), "Bad time value");
EXPECT_FALSE(cert.view().getValidFromTime().has_value());
EXPECT_FALSE(cert.view().getValidToTime().has_value());
}
#endif
TEST(NodeCrypto, PrintsEmptyX509Names) {
ncrypto::X509Pointer cert(X509_new());
X509NamePointer empty_name(X509_NAME_new());
ASSERT_TRUE(cert);
ASSERT_TRUE(empty_name);
ASSERT_EQ(X509_set_subject_name(cert.get(), empty_name.get()), 1);
ASSERT_EQ(X509_set_issuer_name(cert.get(), empty_name.get()), 1);
const ncrypto::BIOPointer subject = cert.view().getSubject();
const ncrypto::BIOPointer issuer = cert.view().getIssuer();
ASSERT_TRUE(subject);
ASSERT_TRUE(issuer);
EXPECT_EQ(BioContents(subject), "");
EXPECT_EQ(BioContents(issuer), "");
}
TEST(NodeCrypto, X509FormattingPreservesUnrelatedErrors) {
ncrypto::X509Pointer cert(X509_new());
X509NamePointer empty_name(X509_NAME_new());
ASSERT_TRUE(cert);
ASSERT_TRUE(empty_name);
ASSERT_EQ(X509_set_subject_name(cert.get(), empty_name.get()), 1);
ERR_clear_error();
#if NCRYPTO_USE_OPENSSL_PROVIDER
ERR_raise(ERR_LIB_USER, 1);
#elif NCRYPTO_USE_BORINGSSL
ERR_put_error(ERR_LIB_USER, 0, 1, __FILE__, __LINE__);
#endif
const unsigned long expected = ERR_peek_error(); // NOLINT(runtime/int)
ASSERT_NE(expected, 0UL);
const ncrypto::BIOPointer subject = cert.view().getSubject();
ASSERT_TRUE(subject);
EXPECT_EQ(ERR_peek_error(), expected);
ERR_clear_error();
}
TEST(NodeCrypto, X509KeyVisitorsHandleMissingPublicKey) {
ncrypto::X509Pointer cert(X509_new());
ASSERT_TRUE(cert);
const auto visit = [&cert] {
EXPECT_TRUE(cert.view().ifRsa([](const ncrypto::Rsa&) {
ADD_FAILURE() << "RSA callback called without a public key";
return false;
}));
EXPECT_TRUE(cert.view().ifEc([](const ncrypto::Ec&) {
ADD_FAILURE() << "EC callback called without a public key";
return false;
}));
};
ERR_clear_error();
visit();
EXPECT_EQ(ERR_peek_error(), 0UL);
#if NCRYPTO_USE_OPENSSL_PROVIDER
ERR_raise(ERR_LIB_USER, 1);
#elif NCRYPTO_USE_BORINGSSL
ERR_put_error(ERR_LIB_USER, 0, 1, __FILE__, __LINE__);
#endif
const unsigned long expected = ERR_peek_error(); // NOLINT(runtime/int)
ASSERT_NE(expected, 0UL);
visit();
EXPECT_EQ(ERR_peek_error(), expected);
ERR_clear_error();
}
TEST(NodeCrypto, X509UsageVisitorPreservesCallbackErrors) {
ncrypto::X509Pointer cert(X509_new());
ncrypto::StackOfASN1 usages(sk_ASN1_OBJECT_new_null());
ASN1ObjectPointer usage(OBJ_txt2obj("1.3.6.1.5.5.7.3.1", 1));
ASSERT_TRUE(cert);
ASSERT_TRUE(usages);
ASSERT_TRUE(usage);
ASSERT_GT(sk_ASN1_OBJECT_push(usages.get(), usage.get()), 0);
usage.release();
X509ExtensionPointer eku(X509V3_EXT_i2d(NID_ext_key_usage, 0, usages.get()));
ASSERT_TRUE(eku);
ASSERT_EQ(X509_add_ext(cert.get(), eku.get(), -1), 1);
ERR_clear_error();
int calls = 0;
EXPECT_TRUE(cert.view().enumUsages([&calls](const char* oid) {
calls++;
EXPECT_STREQ(oid, "1.3.6.1.5.5.7.3.1");
#if NCRYPTO_USE_OPENSSL_PROVIDER
ERR_raise(ERR_LIB_USER, 1);
#elif NCRYPTO_USE_BORINGSSL
ERR_put_error(ERR_LIB_USER, 0, 1, __FILE__, __LINE__);
#endif
}));
EXPECT_EQ(calls, 1);
EXPECT_NE(ERR_peek_error(), 0UL);
ERR_clear_error();
}
TEST(NodeCrypto, PreservesLongX509NameObjectIdentifiers) {
const std::string oid_text = LongObjectIdentifier();
X509NamePointer name(X509_NAME_new());
ASN1ObjectPointer oid(OBJ_txt2obj(oid_text.c_str(), 1));
ASSERT_TRUE(name);
ASSERT_TRUE(oid);
ASSERT_EQ(X509_NAME_add_entry_by_OBJ(
name.get(),
oid.get(),
V_ASN1_UTF8STRING,
reinterpret_cast<const unsigned char*>("value"),
-1,
-1,
0),
1);
ncrypto::X509Name x509_name(name.get());
const auto entry = *x509_name.begin();
ASSERT_TRUE(entry.has_value());
EXPECT_EQ(entry->key, oid_text);
ASSERT_TRUE(entry->value.has_value());
EXPECT_EQ(*entry->value, "value");
}
TEST(NodeCrypto, IdentifiesUnconvertibleX509NameValues) {
X509NamePointer name(X509_NAME_new());
ASN1ObjectPointer oid(OBJ_txt2obj("2.5.4.3", 1));
const unsigned char value[] = "value";
ASSERT_TRUE(name);
ASSERT_TRUE(oid);
ASSERT_EQ(X509_NAME_add_entry_by_OBJ(name.get(),
oid.get(),
V_ASN1_BIT_STRING,
value,
sizeof(value) - 1,
-1,
0),
1);
ncrypto::X509Name x509_name(name.get());
const auto entry = *x509_name.begin();
ASSERT_TRUE(entry.has_value());
EXPECT_EQ(entry->key, "CN");
EXPECT_FALSE(entry->value.has_value());
}
TEST(NodeCrypto, RejectsInvalidConvertibleX509NameValues) {
X509NamePointer name(X509_NAME_new());
ASN1ObjectPointer oid(OBJ_txt2obj("2.5.4.3", 1));
const unsigned char valid_value[] = "value";
const unsigned char invalid_value[] = {0xff};
ASSERT_TRUE(name);
ASSERT_TRUE(oid);
ASSERT_EQ(X509_NAME_add_entry_by_OBJ(name.get(),
oid.get(),
V_ASN1_UTF8STRING,
valid_value,
sizeof(valid_value) - 1,
-1,
0),
1);
// Intentionally corrupt the otherwise valid entry after construction.
auto* entry =
const_cast<X509_NAME_ENTRY*>(X509_NAME_get_entry(name.get(), 0));
ASSERT_NE(entry, nullptr);
auto* value = const_cast<ASN1_STRING*>(X509_NAME_ENTRY_get_data(entry));
ASSERT_NE(value, nullptr);
ASSERT_EQ(ASN1_STRING_set(value, invalid_value, sizeof(invalid_value)), 1);
ncrypto::X509Name x509_name(name.get());
EXPECT_FALSE((*x509_name.begin()).has_value());
}
TEST(NodeCrypto, RejectsEmptyX509SubjectAltNames) {
GeneralNamesPointer names(sk_GENERAL_NAME_new_null());
ASSERT_TRUE(names);
X509ExtensionPointer san(
X509V3_EXT_i2d(NID_subject_alt_name, 0, names.get()));
ASSERT_TRUE(san);
ncrypto::BIOPointer output = ncrypto::BIOPointer::NewMem();
ASSERT_TRUE(output);
EXPECT_FALSE(ncrypto::SafeX509SubjectAltNamePrint(output, san.get()));
EXPECT_TRUE(BioContents(output).empty());
}
TEST(NodeCrypto, RejectsEmptyX509AuthorityInfoAccess) {
AccessDescriptionsPointer descriptions(sk_ACCESS_DESCRIPTION_new_null());
ASSERT_TRUE(descriptions);
X509ExtensionPointer info_access(
X509V3_EXT_i2d(NID_info_access, 0, descriptions.get()));
ASSERT_TRUE(info_access);
ncrypto::BIOPointer output = ncrypto::BIOPointer::NewMem();
ASSERT_TRUE(output);
EXPECT_FALSE(ncrypto::SafeX509InfoAccessPrint(output, info_access.get()));
EXPECT_TRUE(BioContents(output).empty());
}
TEST(NodeCrypto, PrintsLongX509ExtensionObjectIdentifiers) {
const std::string oid_text = LongObjectIdentifier();
ASSERT_GT(oid_text.size(), 256U);
GeneralNamesPointer names(sk_GENERAL_NAME_new_null());
GeneralNamePointer name(GENERAL_NAME_new());
ASN1ObjectPointer oid(OBJ_txt2obj(oid_text.c_str(), 1));
ASSERT_TRUE(names);
ASSERT_TRUE(name);
ASSERT_TRUE(oid);
GENERAL_NAME_set0_value(name.get(), GEN_RID, oid.release());
ASSERT_GT(sk_GENERAL_NAME_push(names.get(), name.get()), 0);
name.release();
X509ExtensionPointer san(
X509V3_EXT_i2d(NID_subject_alt_name, 0, names.get()));
ASSERT_TRUE(san);
ncrypto::BIOPointer output = ncrypto::BIOPointer::NewMem();
ASSERT_TRUE(output);
ASSERT_TRUE(ncrypto::SafeX509SubjectAltNamePrint(output, san.get()));
EXPECT_EQ(BioContents(output), "Registered ID:" + oid_text);
AccessDescriptionsPointer descriptions(sk_ACCESS_DESCRIPTION_new_null());
AccessDescriptionPointer description(ACCESS_DESCRIPTION_new());
ASN1ObjectPointer method(OBJ_txt2obj(oid_text.c_str(), 1));
ASN1StringPointer uri(ASN1_IA5STRING_new());
ASSERT_TRUE(descriptions);
ASSERT_TRUE(description);
ASSERT_TRUE(method);
ASSERT_TRUE(uri);
ASSERT_EQ(ASN1_STRING_set(uri.get(), "https://example.com/", -1), 1);
ASN1_OBJECT_free(description->method);
description->method = method.release();
GENERAL_NAME_set0_value(description->location, GEN_URI, uri.release());
ASSERT_GT(sk_ACCESS_DESCRIPTION_push(descriptions.get(), description.get()),
0);
description.release();
X509ExtensionPointer info_access(
X509V3_EXT_i2d(NID_info_access, 0, descriptions.get()));
ASSERT_TRUE(info_access);
output = ncrypto::BIOPointer::NewMem();
ASSERT_TRUE(output);
ASSERT_TRUE(ncrypto::SafeX509InfoAccessPrint(output, info_access.get()));
std::string expected = oid_text + " - URI:https://example.com/";
#if NCRYPTO_USE_BORINGSSL
expected += "\n";
#endif
EXPECT_EQ(BioContents(output), expected);
}