/
githubmirror
/
node
Обзор
Документация
Войти
/
githubmirror
/
node
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
deps/ncrypto/ncrypto.cc
7 191 строка
213 KB
Adam Mcgrath
crypto: add mgf1Hash for RSA-OAEP
10 авг 2026, 02:06
10 авг 2026, 02:06
11423b1
Код
Авторство
О чём код?
#include "ncrypto.h" #include <openssl/asn1.h> #include <openssl/bn.h> #include <openssl/dh.h> #include <openssl/evp.h> #include <openssl/hmac.h> #include <openssl/pem.h> #include <openssl/pkcs12.h> #include <openssl/rand.h> #include <openssl/x509v3.h> #if NCRYPTO_USE_BORINGSSL_EVP_DO_ALL_FALLBACK #include <openssl/bytestring.h> #include <openssl/cipher.h> #endif #include <algorithm> #include <array> #include <climits> #include <cstring> #include <string_view> #if OPENSSL_VERSION_MAJOR >= 3 #include <openssl/core_names.h> #include <openssl/params.h> #include <openssl/provider.h> #include <openssl/store.h> #include <openssl/ui.h> #if OPENSSL_WITH_ARGON2 #include <openssl/thread.h> #endif #endif #if OPENSSL_WITH_PQC struct PQCMapping { const char* name; int nid; }; constexpr static PQCMapping pqc_mappings[] = { {"ML-DSA-44", EVP_PKEY_ML_DSA_44}, {"ML-DSA-65", EVP_PKEY_ML_DSA_65}, {"ML-DSA-87", EVP_PKEY_ML_DSA_87}, {"ML-KEM-768", EVP_PKEY_ML_KEM_768}, {"ML-KEM-1024", EVP_PKEY_ML_KEM_1024}, #if OPENSSL_WITH_PQC_ML_KEM_512 {"ML-KEM-512", EVP_PKEY_ML_KEM_512}, #endif #if OPENSSL_WITH_PQC_SLH_DSA {"SLH-DSA-SHA2-128f", EVP_PKEY_SLH_DSA_SHA2_128F}, {"SLH-DSA-SHA2-128s", EVP_PKEY_SLH_DSA_SHA2_128S}, {"SLH-DSA-SHA2-192f", EVP_PKEY_SLH_DSA_SHA2_192F}, {"SLH-DSA-SHA2-192s", EVP_PKEY_SLH_DSA_SHA2_192S}, {"SLH-DSA-SHA2-256f", EVP_PKEY_SLH_DSA_SHA2_256F}, {"SLH-DSA-SHA2-256s", EVP_PKEY_SLH_DSA_SHA2_256S}, {"SLH-DSA-SHAKE-128f", EVP_PKEY_SLH_DSA_SHAKE_128F}, {"SLH-DSA-SHAKE-128s", EVP_PKEY_SLH_DSA_SHAKE_128S}, {"SLH-DSA-SHAKE-192f", EVP_PKEY_SLH_DSA_SHAKE_192F}, {"SLH-DSA-SHAKE-192s", EVP_PKEY_SLH_DSA_SHAKE_192S}, {"SLH-DSA-SHAKE-256f", EVP_PKEY_SLH_DSA_SHAKE_256F}, {"SLH-DSA-SHAKE-256s", EVP_PKEY_SLH_DSA_SHAKE_256S}, #endif }; #endif // EVP_PKEY_CTX_set_dsa_paramgen_q_bits was added in OpenSSL 1.1.1e. #if OPENSSL_VERSION_NUMBER < 0x1010105fL #define EVP_PKEY_CTX_set_dsa_paramgen_q_bits(ctx, qbits) \ EVP_PKEY_CTX_ctrl((ctx), \ EVP_PKEY_DSA, \ EVP_PKEY_OP_PARAMGEN, \ EVP_PKEY_CTRL_DSA_PARAMGEN_Q_BITS, \ (qbits), \ nullptr) #endif namespace ncrypto { namespace { using BignumCtxPointer = DeleteFnPtr<BN_CTX, BN_CTX_free>; using BignumGenCallbackPointer = DeleteFnPtr<BN_GENCB, BN_GENCB_free>; using NetscapeSPKIPointer = DeleteFnPtr<NETSCAPE_SPKI, NETSCAPE_SPKI_free>; #if NCRYPTO_USE_OPENSSL3_PROVIDER using X509PubKeyPointer = DeleteFnPtr<X509_PUBKEY, X509_PUBKEY_free>; // OSSL_STORE_close() returns int, so it needs a void-returning adapter to be // usable as a DeleteFnPtr deleter. void CloseStoreCtx(OSSL_STORE_CTX* ctx) { OSSL_STORE_close(ctx); } using StoreCtxPointer = DeleteFnPtr<OSSL_STORE_CTX, CloseStoreCtx>; using UIMethodPointer = DeleteFnPtr<UI_METHOD, UI_destroy_method>; #endif 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<ASN1_STRING, ASN1_STRING_free>; using OSSLParamBldPointer = DeleteFnPtr<OSSL_PARAM_BLD, OSSL_PARAM_BLD_free>; using RsaPssParamsPointer = DeleteFnPtr<RSA_PSS_PARAMS, RSA_PSS_PARAMS_free>; using X509AlgorPointer = DeleteFnPtr<X509_ALGOR, X509_ALGOR_free>; using X509PubkeyPointer = DeleteFnPtr<X509_PUBKEY, X509_PUBKEY_free>; 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<OSSL_PARAM, OSSLParamDeleter>; struct OpenSSLBufferDeleter { void operator()(unsigned char* pointer) const { OPENSSL_free(pointer); } }; using OpenSSLBufferPointer = std::unique_ptr<unsigned char, OpenSSLBufferDeleter>; #endif static constexpr int kX509NameFlagsRFC2253WithinUtf8JSON = XN_FLAG_RFC2253 & ~ASN1_STRFLGS_ESC_MSB & ~ASN1_STRFLGS_ESC_CTRL; #if NCRYPTO_USE_OPENSSL3_PROVIDER template <typename Pointer> 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<unsigned char*>(data.get()), data.size(), &len) != 1) { return false; } bn = BN_bin2bn(static_cast<const unsigned char*>(data.get()), len, nullptr); if (bn == nullptr) return false; out->reset(bn); return true; } template <typename Pointer> 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<unsigned char*>(data.get()), data.size(), &len) != 1) { return false; } bn = BN_bin2bn(static_cast<const unsigned char*>(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<char*>(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<BIGNUM, BN_free>* p, DeleteFnPtr<BIGNUM, BN_free>* g, DeleteFnPtr<BIGNUM, BN_free>* q = nullptr, DeleteFnPtr<BIGNUM, BN_free>* 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<BIGNUM, BN_free>* pub, DeleteFnPtr<BIGNUM, BN_clear_free>* 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)(); const char* name; }; constexpr BoringSSLCipher kBoringSSLCiphers[] = { {EVP_aes_128_cbc, "aes-128-cbc"}, {EVP_aes_128_ctr, "aes-128-ctr"}, {EVP_aes_128_ecb, "aes-128-ecb"}, {EVP_aes_128_gcm, "aes-128-gcm"}, {EVP_aes_128_ofb, "aes-128-ofb"}, {EVP_aes_192_cbc, "aes-192-cbc"}, {EVP_aes_192_ctr, "aes-192-ctr"}, {EVP_aes_192_ecb, "aes-192-ecb"}, {EVP_aes_192_gcm, "aes-192-gcm"}, {EVP_aes_192_ofb, "aes-192-ofb"}, {EVP_aes_256_cbc, "aes-256-cbc"}, {EVP_aes_256_ctr, "aes-256-ctr"}, {EVP_aes_256_ecb, "aes-256-ecb"}, {EVP_aes_256_gcm, "aes-256-gcm"}, {EVP_aes_256_ofb, "aes-256-ofb"}, {EVP_des_cbc, "des-cbc"}, {EVP_des_ecb, "des-ecb"}, {EVP_des_ede, "des-ede"}, {EVP_des_ede3_cbc, "des-ede3-cbc"}, {EVP_des_ede_cbc, "des-ede-cbc"}, {EVP_rc2_cbc, "rc2-cbc"}, {EVP_rc4, "rc4"}, }; #endif } // namespace // ============================================================================ ClearErrorOnReturn::ClearErrorOnReturn(CryptoErrorList* errors) : errors_(errors) { ERR_clear_error(); } ClearErrorOnReturn::~ClearErrorOnReturn() { if (errors_ != nullptr) errors_->capture(); ERR_clear_error(); } unsigned long ClearErrorOnReturn::peekError() { // NOLINT(runtime/int) return ERR_peek_error(); } MarkPopErrorOnReturn::MarkPopErrorOnReturn(CryptoErrorList* errors) : errors_(errors) { ERR_set_mark(); } MarkPopErrorOnReturn::~MarkPopErrorOnReturn() { if (errors_ != nullptr) errors_->capture(); ERR_pop_to_mark(); } unsigned long MarkPopErrorOnReturn::peekError() { // NOLINT(runtime/int) return ERR_peek_error(); } CryptoErrorList::CryptoErrorList(CryptoErrorList::Option option) { if (option == Option::CAPTURE_ON_CONSTRUCT) capture(); } void CryptoErrorList::capture() { errors_.clear(); while (const auto err = ERR_get_error()) { char buf[256]; ERR_error_string_n(err, buf, sizeof(buf)); errors_.emplace_front(buf); } } void CryptoErrorList::add(std::string error) { errors_.push_back(error); } std::optional<std::string> CryptoErrorList::pop_back() { if (errors_.empty()) return std::nullopt; std::string error = errors_.back(); errors_.pop_back(); return error; } std::optional<std::string> CryptoErrorList::pop_front() { if (errors_.empty()) return std::nullopt; std::string error = errors_.front(); errors_.pop_front(); return error; } // ============================================================================ DataPointer DataPointer::Alloc(size_t len) { #ifdef OPENSSL_IS_BORINGSSL // Boringssl does not implement OPENSSL_zalloc auto ptr = OPENSSL_malloc(len); if (ptr == nullptr) return {}; memset(ptr, 0, len); return DataPointer(ptr, len); #else return DataPointer(OPENSSL_zalloc(len), len); #endif } DataPointer DataPointer::SecureAlloc(size_t len) { #ifndef OPENSSL_IS_BORINGSSL auto ptr = OPENSSL_secure_zalloc(len); if (ptr == nullptr) return {}; // OPENSSL_secure_zalloc transparently falls back to a regular allocation // when the secure heap is not initialized or is exhausted. Reflect the // actual provenance of the pointer so that reset() routes to the correct // free function (OPENSSL_secure_clear_free vs. OPENSSL_clear_free) and // callers of isSecure() get a truthful answer. return DataPointer(ptr, len, CRYPTO_secure_allocated(ptr) == 1); #else // BoringSSL does not implement the OPENSSL_secure_zalloc API. auto ptr = OPENSSL_malloc(len); if (ptr == nullptr) return {}; memset(ptr, 0, len); return DataPointer(ptr, len); #endif } size_t DataPointer::GetSecureHeapUsed() { #ifndef OPENSSL_IS_BORINGSSL return CRYPTO_secure_malloc_initialized() ? CRYPTO_secure_used() : 0; #else // BoringSSL does not have the secure heap and therefore // will always return 0. return 0; #endif } DataPointer::InitSecureHeapResult DataPointer::TryInitSecureHeap(size_t amount, size_t min) { #ifndef OPENSSL_IS_BORINGSSL switch (CRYPTO_secure_malloc_init(amount, min)) { case 0: return InitSecureHeapResult::FAILED; case 2: return InitSecureHeapResult::UNABLE_TO_MEMORY_MAP; case 1: return InitSecureHeapResult::OK; default: return InitSecureHeapResult::FAILED; } #else // BoringSSL does not actually support the secure heap return InitSecureHeapResult::FAILED; #endif } DataPointer DataPointer::Copy(const Buffer<const void>& buffer) { return DataPointer(OPENSSL_memdup(buffer.data, buffer.len), buffer.len); } DataPointer::DataPointer(void* data, size_t length, bool secure) : data_(data), len_(length), secure_(secure) {} DataPointer::DataPointer(const Buffer<void>& buffer, bool secure) : data_(buffer.data), len_(buffer.len), secure_(secure) {} DataPointer::DataPointer(DataPointer&& other) noexcept : data_(other.data_), len_(other.len_), secure_(other.secure_) { other.data_ = nullptr; other.len_ = 0; other.secure_ = false; } DataPointer& DataPointer::operator=(DataPointer&& other) noexcept { if (this == &other) return *this; this->~DataPointer(); return *new (this) DataPointer(std::move(other)); } DataPointer::~DataPointer() { reset(); } void DataPointer::zero() { if (!data_) return; OPENSSL_cleanse(data_, len_); } void DataPointer::reset(void* data, size_t length) { if (data_ != nullptr) { if (secure_) { OPENSSL_secure_clear_free(data_, len_); } else { OPENSSL_clear_free(data_, len_); } } data_ = data; len_ = length; } void DataPointer::reset(const Buffer<void>& buffer) { reset(buffer.data, buffer.len); } Buffer<void> DataPointer::release() { Buffer<void> buf{ .data = data_, .len = len_, }; data_ = nullptr; len_ = 0; return buf; } DataPointer DataPointer::resize(size_t len) { const size_t actual_len = std::min(len_, len); if (actual_len == len_) return std::move(*this); auto buf = release(); 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); } // ============================================================================ bool isFipsEnabled() { ClearErrorOnReturn clear_error_on_return; #if OPENSSL_VERSION_MAJOR >= 3 return EVP_default_properties_is_fips_enabled(nullptr) == 1; #else return FIPS_mode() == 1; #endif } bool setFipsEnabled(bool enable, CryptoErrorList* errors) { if (isFipsEnabled() == enable) return true; ClearErrorOnReturn clearErrorOnReturn(errors); #if OPENSSL_VERSION_MAJOR >= 3 return EVP_default_properties_enable_fips(nullptr, enable ? 1 : 0) == 1; #else return FIPS_mode_set(enable ? 1 : 0) == 1; #endif } bool testFipsEnabled() { ClearErrorOnReturn clear_error_on_return; #if OPENSSL_VERSION_MAJOR >= 3 OSSL_PROVIDER* fips_provider = nullptr; if (OSSL_PROVIDER_available(nullptr, "fips")) { fips_provider = OSSL_PROVIDER_load(nullptr, "fips"); } if (fips_provider == nullptr) return false; int result = OSSL_PROVIDER_self_test(fips_provider); OSSL_PROVIDER_unload(fips_provider); return result; #else #ifdef OPENSSL_FIPS return FIPS_selftest(); #else // OPENSSL_FIPS return false; #endif // OPENSSL_FIPS #endif } // ============================================================================ // Bignum BignumPointer::BignumPointer(BIGNUM* bignum) : bn_(bignum) {} BignumPointer::BignumPointer(const unsigned char* data, size_t len) : BignumPointer(BN_bin2bn(data, len, nullptr)) {} BignumPointer::BignumPointer(BignumPointer&& other) noexcept : bn_(other.release()) {} BignumPointer BignumPointer::New() { return BignumPointer(BN_new()); } BignumPointer BignumPointer::NewSecure() { #ifdef OPENSSL_IS_BORINGSSL // Boringssl does not implement BN_secure_new. return New(); #else return BignumPointer(BN_secure_new()); #endif } BignumPointer& BignumPointer::operator=(BignumPointer&& other) noexcept { if (this == &other) return *this; this->~BignumPointer(); return *new (this) BignumPointer(std::move(other)); } BignumPointer::~BignumPointer() { reset(); } void BignumPointer::reset(BIGNUM* bn) { bn_.reset(bn); } void BignumPointer::reset(const unsigned char* data, size_t len) { reset(BN_bin2bn(data, len, nullptr)); } BIGNUM* BignumPointer::release() { return bn_.release(); } size_t BignumPointer::byteLength() const { if (bn_ == nullptr) return 0; return BN_num_bytes(bn_.get()); } DataPointer BignumPointer::encode() const { return EncodePadded(bn_.get(), byteLength()); } DataPointer BignumPointer::encodePadded(size_t size) const { return EncodePadded(bn_.get(), size); } size_t BignumPointer::encodeInto(unsigned char* out) const { if (!bn_) return 0; return BN_bn2bin(bn_.get(), out); } size_t BignumPointer::encodePaddedInto(unsigned char* out, size_t size) const { if (!bn_) return 0; return BN_bn2binpad(bn_.get(), out, size); } DataPointer BignumPointer::Encode(const BIGNUM* bn) { return EncodePadded(bn, bn != nullptr ? BN_num_bytes(bn) : 0); } bool BignumPointer::setWord(unsigned long w) { // NOLINT(runtime/int) if (!bn_) return false; return BN_set_word(bn_.get(), w) == 1; } std::optional<unsigned long> BignumPointer::GetWord( // NOLINT(runtime/int) const BIGNUM* bn) { BN_ULONG ret = BN_get_word(bn); if (ret == static_cast<BN_ULONG>(-1)) return std::nullopt; return ret; } std::optional<unsigned long> BignumPointer::getWord() // NOLINT(runtime/int) const { if (!bn_) return std::nullopt; return GetWord(bn_.get()); } DataPointer BignumPointer::EncodePadded(const BIGNUM* bn, size_t s) { if (bn == nullptr) return DataPointer(); size_t size = std::max(s, static_cast<size_t>(GetByteCount(bn))); auto buf = DataPointer::Alloc(size); BN_bn2binpad(bn, reinterpret_cast<unsigned char*>(buf.get()), size); return buf; } size_t BignumPointer::EncodePaddedInto(const BIGNUM* bn, unsigned char* out, size_t size) { if (bn == nullptr) return 0; return BN_bn2binpad(bn, out, size); } int BignumPointer::operator<=>(const BignumPointer& other) const noexcept { if (bn_ == nullptr && other.bn_ != nullptr) return -1; if (bn_ != nullptr && other.bn_ == nullptr) return 1; if (bn_ == nullptr && other.bn_ == nullptr) return 0; return BN_cmp(bn_.get(), other.bn_.get()); } int BignumPointer::operator<=>(const BIGNUM* other) const noexcept { if (bn_ == nullptr && other != nullptr) return -1; if (bn_ != nullptr && other == nullptr) return 1; if (bn_ == nullptr && other == nullptr) return 0; return BN_cmp(bn_.get(), other); } DataPointer BignumPointer::toHex() const { if (!bn_) return {}; char* hex = BN_bn2hex(bn_.get()); if (!hex) return {}; return DataPointer(hex, strlen(hex)); } int BignumPointer::GetBitCount(const BIGNUM* bn) { return BN_num_bits(bn); } int BignumPointer::GetByteCount(const BIGNUM* bn) { return BN_num_bytes(bn); } bool BignumPointer::isZero() const { return bn_ && BN_is_zero(bn_.get()); } bool BignumPointer::isOne() const { return bn_ && BN_is_one(bn_.get()); } const BIGNUM* BignumPointer::One() { return BN_value_one(); } BignumPointer BignumPointer::clone() { if (!bn_) return {}; return BignumPointer(BN_dup(bn_.get())); } int BignumPointer::isPrime(int nchecks, BignumPointer::PrimeCheckCallback innerCb) const { BignumCtxPointer ctx(BN_CTX_new()); BignumGenCallbackPointer cb(nullptr); if (innerCb != nullptr) { cb = BignumGenCallbackPointer(BN_GENCB_new()); if (!cb) [[unlikely]] return -1; BN_GENCB_set( cb.get(), // TODO(@jasnell): This could be refactored to allow inlining. // Not too important right now tho. [](int a, int b, BN_GENCB* ctx) mutable -> int { PrimeCheckCallback& ptr = *static_cast<PrimeCheckCallback*>(BN_GENCB_get_arg(ctx)); return ptr(a, b) ? 1 : 0; }, &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, PrimeCheckCallback cb) { BignumPointer prime(BN_new()); if (!prime || !prime.generate(params, std::move(cb))) { return {}; } return prime; } bool BignumPointer::generate(const PrimeConfig& params, PrimeCheckCallback innerCb) const { // BN_generate_prime_ex() calls RAND_bytes_ex() internally. // Make sure the CSPRNG is properly seeded. std::ignore = CSPRNG(nullptr, 0); BignumGenCallbackPointer cb(nullptr); if (innerCb != nullptr) { cb = BignumGenCallbackPointer(BN_GENCB_new()); if (!cb) [[unlikely]] return -1; BN_GENCB_set( cb.get(), [](int a, int b, BN_GENCB* ctx) mutable -> int { PrimeCheckCallback& ptr = *static_cast<PrimeCheckCallback*>(BN_GENCB_get_arg(ctx)); return ptr(a, b) ? 1 : 0; }, &innerCb); } if (BN_generate_prime_ex(get(), params.bits, params.safe ? 1 : 0, params.add.get(), params.rem.get(), cb.get()) == 0) { return false; } return true; } BignumPointer BignumPointer::NewSub(const BignumPointer& a, const BignumPointer& b) { BignumPointer res = New(); if (!res) return {}; if (!BN_sub(res.get(), a.get(), b.get())) { return {}; } return res; } BignumPointer BignumPointer::NewLShift(size_t length) { BignumPointer res = New(); if (!res) return {}; if (!BN_lshift(res.get(), One(), length)) { return {}; } return res; } // ============================================================================ // Utility methods bool CSPRNG(void* buffer, size_t length) { auto buf = reinterpret_cast<unsigned char*>(buffer); do { if (1 == RAND_status()) { #if OPENSSL_VERSION_MAJOR >= 3 if (1 == RAND_bytes_ex(nullptr, buf, length, 0)) { return true; } #else while (length > INT_MAX && 1 == RAND_bytes(buf, INT_MAX)) { buf += INT_MAX; length -= INT_MAX; } if (length <= INT_MAX && 1 == RAND_bytes(buf, static_cast<int>(length))) return true; #endif } #if OPENSSL_VERSION_MAJOR >= 3 const auto code = ERR_peek_last_error(); // A misconfigured OpenSSL 3 installation may report 1 from RAND_poll() // and RAND_status() but fail in RAND_bytes() if it cannot look up // a matching algorithm for the CSPRNG. if (ERR_GET_LIB(code) == ERR_LIB_RAND) { const auto reason = ERR_GET_REASON(code); if (reason == RAND_R_ERROR_INSTANTIATING_DRBG || reason == RAND_R_UNABLE_TO_FETCH_DRBG || reason == RAND_R_UNABLE_TO_CREATE_DRBG) { return false; } } #endif } while (1 == RAND_poll()); return false; } int NoPasswordCallback(char* buf, int size, int rwflag, void* u) { return 0; } int PasswordCallback(char* buf, int size, int rwflag, void* u) { auto passphrase = static_cast<const Buffer<char>*>(u); if (size <= 0) return -1; if (passphrase != nullptr) { size_t buflen = static_cast<size_t>(size); size_t len = passphrase->len; if (buflen < len) return -1; memcpy(buf, reinterpret_cast<const char*>(passphrase->data), len); return len; } return -1; } #if NCRYPTO_USE_OPENSSL3_PROVIDER namespace { struct StorePassphraseData { Buffer<char> passphrase{.data = nullptr, .len = 0}; bool has_passphrase = false; bool missing_passphrase = false; }; int StorePasswordCallback(char* buf, int size, int rwflag, void* u) { auto data = static_cast<StorePassphraseData*>(u); if (data == nullptr || !data->has_passphrase) { if (data != nullptr) data->missing_passphrase = true; return -1; } if (size <= 0) return -1; size_t buflen = static_cast<size_t>(size); size_t len = data->passphrase.len; if (buflen < len) return -1; memcpy(buf, reinterpret_cast<const char*>(data->passphrase.data), len); return len; } } // namespace #endif // Algorithm: http://howardhinnant.github.io/date_algorithms.html constexpr int days_from_epoch(int y, unsigned m, unsigned d) { y -= m <= 2; const int era = (y >= 0 ? y : y - 399) / 400; const unsigned yoe = static_cast<unsigned>(y - era * 400); // [0, 399] const unsigned doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; // [0, 365] const unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; // [0, 146096] return era * 146097 + static_cast<int>(doe) - 719468; } #ifndef OPENSSL_IS_BORINGSSL // tm must be in UTC // using time_t causes problems on 32-bit systems and windows x64. int64_t PortableTimeGM(struct tm* t) { int year = t->tm_year + 1900; int month = t->tm_mon; if (month > 11) { year += month / 12; month %= 12; } else if (month < 0) { int years_diff = (11 - month) / 12; year -= years_diff; month += 12 * years_diff; } int days_since_epoch = days_from_epoch(year, month + 1, t->tm_mday); return 60 * (60 * (24LL * static_cast<int64_t>(days_since_epoch) + t->tm_hour) + t->tm_min) + t->tm_sec; } #endif // ============================================================================ // SPKAC bool VerifySpkac(const char* input, size_t length) { #ifdef OPENSSL_IS_BORINGSSL // OpenSSL uses EVP_DecodeBlock, which explicitly removes trailing characters, // while BoringSSL uses EVP_DecodedLength and EVP_DecodeBase64, which do not. // As such, we trim those characters here for compatibility. // // find_last_not_of can return npos, which is the maximum value of size_t. // The + 1 will force a roll-ver to 0, which is the correct value. in that // case. length = std::string_view(input, length).find_last_not_of(" \n\r\t") + 1; #endif NetscapeSPKIPointer spki(NETSCAPE_SPKI_b64_decode(input, length)); if (!spki) return false; EVPKeyPointer pkey(X509_PUBKEY_get(spki->spkac->pubkey)); return pkey ? NETSCAPE_SPKI_verify(spki.get(), pkey.get()) > 0 : false; } BIOPointer ExportPublicKey(const char* input, size_t length) { BIOPointer bio(BIO_new(BIO_s_mem())); if (!bio) return {}; #ifdef OPENSSL_IS_BORINGSSL // OpenSSL uses EVP_DecodeBlock, which explicitly removes trailing characters, // while BoringSSL uses EVP_DecodedLength and EVP_DecodeBase64, which do not. // As such, we trim those characters here for compatibility. length = std::string_view(input, length).find_last_not_of(" \n\r\t") + 1; #endif NetscapeSPKIPointer spki(NETSCAPE_SPKI_b64_decode(input, length)); if (!spki) return {}; EVPKeyPointer pkey(NETSCAPE_SPKI_get_pubkey(spki.get())); if (!pkey) return {}; if (PEM_write_bio_PUBKEY(bio.get(), pkey.get()) <= 0) return {}; return bio; } Buffer<char> ExportChallenge(const char* input, size_t length) { #ifdef OPENSSL_IS_BORINGSSL // OpenSSL uses EVP_DecodeBlock, which explicitly removes trailing characters, // while BoringSSL uses EVP_DecodedLength and EVP_DecodeBase64, which do not. // As such, we trim those characters here for compatibility. length = std::string_view(input, length).find_last_not_of(" \n\r\t") + 1; #endif NetscapeSPKIPointer sp(NETSCAPE_SPKI_b64_decode(input, length)); if (!sp) return {}; unsigned char* buf = nullptr; int buf_size = ASN1_STRING_to_UTF8(&buf, sp->spkac->challenge); if (buf_size >= 0) { return { .data = reinterpret_cast<char*>(buf), .len = static_cast<size_t>(buf_size), }; } return {}; } // ============================================================================ namespace { enum class AltNameOption { NONE, UTF8, }; bool IsSafeAltName(const char* name, size_t length, AltNameOption option) { for (size_t i = 0; i < length; i++) { char c = name[i]; switch (c) { case '"': case '\\': // These mess with encoding rules. // Fall through. case ',': // Commas make it impossible to split the list of subject alternative // names unambiguously, which is why we have to escape. // Fall through. case '\'': // Single quotes are unlikely to appear in any legitimate values, but // they could be used to make a value look like it was escaped (i.e., // enclosed in single/double quotes). return false; default: if (option == AltNameOption::UTF8) { // In UTF8 strings, we require escaping for any ASCII control // character, but NOT for non-ASCII characters. Note that all bytes of // any code point that consists of more than a single byte have their // MSB set. if (static_cast<unsigned char>(c) < ' ' || c == '\x7f') { return false; } } else { // Check if the char is a control character or non-ASCII character. // Note that char may or may not be a signed type. Regardless, // non-ASCII values will always be outside of this range. if (c < ' ' || c > '~') { return false; } } } } return true; } void PrintAltName(const BIOPointer& out, const char* name, size_t length, AltNameOption option = AltNameOption::NONE, const char* safe_prefix = nullptr) { if (IsSafeAltName(name, length, option)) { // For backward-compatibility, append "safe" names without any // modifications. if (safe_prefix != nullptr) { BIO_printf(out.get(), "%s:", safe_prefix); } BIO_write(out.get(), name, length); } else { // If a name is not "safe", we cannot embed it without special // encoding. This does not usually happen, but we don't want to hide // it from the user either. We use JSON compatible escaping here. BIO_write(out.get(), "\"", 1); if (safe_prefix != nullptr) { BIO_printf(out.get(), "%s:", safe_prefix); } for (size_t j = 0; j < length; j++) { char c = static_cast<char>(name[j]); if (c == '\\') { BIO_write(out.get(), "\\\\", 2); } else if (c == '"') { BIO_write(out.get(), "\\\"", 2); } else if ((c >= ' ' && c != ',' && c <= '~') || (option == AltNameOption::UTF8 && (c & 0x80))) { // Note that the above condition explicitly excludes commas, which means // that those are encoded as Unicode escape sequences in the "else" // block. That is not strictly necessary, and Node.js itself would parse // it correctly either way. We only do this to account for third-party // code that might be splitting the string at commas (as Node.js itself // used to do). BIO_write(out.get(), &c, 1); } else { // Control character or non-ASCII character. We treat everything as // Latin-1, which corresponds to the first 255 Unicode code points. const char hex[] = "0123456789abcdef"; char u[] = {'\\', 'u', '0', '0', hex[(c & 0xf0) >> 4], hex[c & 0x0f]}; BIO_write(out.get(), u, sizeof(u)); } } BIO_write(out.get(), "\"", 1); } } // This function emulates the behavior of i2v_GENERAL_NAME in a safer and less // ambiguous way. "othername:" entries use the GENERAL_NAME_print format. bool PrintGeneralName(const BIOPointer& out, const GENERAL_NAME* gen) { if (gen->type == GEN_DNS) { ASN1_IA5STRING* name = gen->d.dNSName; BIO_write(out.get(), "DNS:", 4); // Note that the preferred name syntax (see RFCs 5280 and 1034) with // wildcards is a subset of what we consider "safe", so spec-compliant DNS // names will never need to be escaped. PrintAltName(out, reinterpret_cast<const char*>(ASN1_STRING_get0_data(name)), ASN1_STRING_length(name)); } else if (gen->type == GEN_EMAIL) { ASN1_IA5STRING* name = gen->d.rfc822Name; BIO_write(out.get(), "email:", 6); PrintAltName(out, reinterpret_cast<const char*>(ASN1_STRING_get0_data(name)), ASN1_STRING_length(name)); } else if (gen->type == GEN_URI) { ASN1_IA5STRING* name = gen->d.uniformResourceIdentifier; BIO_write(out.get(), "URI:", 4); // The set of "safe" names was designed to include just about any URI, // with a few exceptions, most notably URIs that contains commas (see // RFC 2396). In other words, most legitimate URIs will not require // escaping. PrintAltName(out, reinterpret_cast<const char*>(ASN1_STRING_get0_data(name)), ASN1_STRING_length(name)); } else if (gen->type == GEN_DIRNAME) { // Earlier versions of Node.js used X509_NAME_oneline to print the X509_NAME // object. The format was non standard and should be avoided. The use of // X509_NAME_oneline is discouraged by OpenSSL but was required for backward // compatibility. Conveniently, X509_NAME_oneline produced ASCII and the // output was unlikely to contains commas or other characters that would // require escaping. However, it SHOULD NOT produce ASCII output since an // RFC5280 AttributeValue may be a UTF8String. // Newer versions of Node.js have since switched to X509_NAME_print_ex to // produce a better format at the cost of backward compatibility. The new // format may contain Unicode characters and it is likely to contain commas, // which require escaping. Fortunately, the recently safeguarded function // PrintAltName handles all of that safely. BIO_printf(out.get(), "DirName:"); BIOPointer tmp(BIO_new(BIO_s_mem())); NCRYPTO_ASSERT_TRUE(tmp); if (X509_NAME_print_ex( tmp.get(), gen->d.dirn, 0, kX509NameFlagsRFC2253WithinUtf8JSON) < 0) { return false; } char* oline = nullptr; long n_bytes = BIO_get_mem_data(tmp.get(), &oline); // NOLINT(runtime/int) NCRYPTO_ASSERT_TRUE(n_bytes >= 0); PrintAltName(out, oline, static_cast<size_t>(n_bytes), ncrypto::AltNameOption::UTF8, nullptr); } else if (gen->type == GEN_IPADD) { BIO_printf(out.get(), "IP Address:"); const ASN1_OCTET_STRING* ip = gen->d.ip; const unsigned char* b = ASN1_STRING_get0_data(ip); int ip_len = ASN1_STRING_length(ip); if (ip_len == 4) { BIO_printf(out.get(), "%d.%d.%d.%d", b[0], b[1], b[2], b[3]); } else if (ip_len == 16) { for (unsigned int j = 0; j < 8; j++) { uint16_t pair = (b[2 * j] << 8) | b[2 * j + 1]; BIO_printf(out.get(), (j == 0) ? "%X" : ":%X", pair); } } else { #if OPENSSL_VERSION_MAJOR >= 3 BIO_printf(out.get(), "<invalid length=%d>", ip_len); #else BIO_printf(out.get(), "<invalid>"); #endif } } else if (gen->type == GEN_RID) { // Unlike OpenSSL's default implementation, never print the OID as text and // instead always print its numeric representation. char oline[256]; OBJ_obj2txt(oline, sizeof(oline), gen->d.rid, true); BIO_printf(out.get(), "Registered ID:%s", oline); } else if (gen->type == GEN_OTHERNAME) { // The format that is used here is based on OpenSSL's implementation of // GENERAL_NAME_print (as of OpenSSL 3.0.1). Earlier versions of Node.js // instead produced the same format as i2v_GENERAL_NAME, which was somewhat // awkward, especially when passed to translatePeerCertificate. bool unicode = true; const char* prefix = nullptr; // OpenSSL 1.1.1 does not support othername in GENERAL_NAME_print and may // not define these NIDs. #if OPENSSL_VERSION_MAJOR >= 3 int nid = OBJ_obj2nid(gen->d.otherName->type_id); switch (nid) { case NID_id_on_SmtpUTF8Mailbox: prefix = "SmtpUTF8Mailbox"; break; case NID_XmppAddr: prefix = "XmppAddr"; break; case NID_SRVName: prefix = "SRVName"; unicode = false; break; case NID_ms_upn: prefix = "UPN"; break; case NID_NAIRealm: prefix = "NAIRealm"; break; } #endif // OPENSSL_VERSION_MAJOR >= 3 int val_type = gen->d.otherName->value->type; if (prefix == nullptr || (unicode && val_type != V_ASN1_UTF8STRING) || (!unicode && val_type != V_ASN1_IA5STRING)) { BIO_printf(out.get(), "othername:<unsupported>"); } else { BIO_printf(out.get(), "othername:"); if (unicode) { auto name = gen->d.otherName->value->value.utf8string; PrintAltName(out, reinterpret_cast<const char*>(ASN1_STRING_get0_data(name)), ASN1_STRING_length(name), AltNameOption::UTF8, prefix); } else { auto name = gen->d.otherName->value->value.ia5string; PrintAltName(out, reinterpret_cast<const char*>(ASN1_STRING_get0_data(name)), ASN1_STRING_length(name), AltNameOption::NONE, prefix); } } } else if (gen->type == GEN_X400) { // TODO(tniessen): this is what OpenSSL does, implement properly instead BIO_printf(out.get(), "X400Name:<unsupported>"); } else if (gen->type == GEN_EDIPARTY) { // TODO(tniessen): this is what OpenSSL does, implement properly instead BIO_printf(out.get(), "EdiPartyName:<unsupported>"); } else { // This is safe because X509V3_EXT_d2i would have returned nullptr in this // case already. unreachable(); } return true; } } // namespace bool SafeX509SubjectAltNamePrint(const BIOPointer& out, const X509_EXTENSION* ext) { // const_cast needed for OpenSSL < 4.0 which lacks const-correctness auto* mext = const_cast<X509_EXTENSION*>(ext); auto ret = OBJ_obj2nid(X509_EXTENSION_get_object(mext)); if (ret != NID_subject_alt_name) return false; GENERAL_NAMES* names = static_cast<GENERAL_NAMES*>(X509V3_EXT_d2i(mext)); if (names == nullptr) return false; bool ok = true; for (OPENSSL_SIZE_T i = 0; i < sk_GENERAL_NAME_num(names); i++) { GENERAL_NAME* gen = sk_GENERAL_NAME_value(names, i); if (i != 0) BIO_write(out.get(), ", ", 2); if (!(ok = ncrypto::PrintGeneralName(out, gen))) { break; } } sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free); return ok; } bool SafeX509InfoAccessPrint(const BIOPointer& out, const X509_EXTENSION* ext) { // const_cast needed for OpenSSL < 4.0 which lacks const-correctness auto* mext = const_cast<X509_EXTENSION*>(ext); auto ret = OBJ_obj2nid(X509_EXTENSION_get_object(mext)); if (ret != NID_info_access) return false; AUTHORITY_INFO_ACCESS* descs = static_cast<AUTHORITY_INFO_ACCESS*>(X509V3_EXT_d2i(mext)); if (descs == nullptr) return false; bool ok = true; for (OPENSSL_SIZE_T i = 0; i < sk_ACCESS_DESCRIPTION_num(descs); i++) { ACCESS_DESCRIPTION* desc = sk_ACCESS_DESCRIPTION_value(descs, i); if (i != 0) BIO_write(out.get(), "\n", 1); char objtmp[80]; i2t_ASN1_OBJECT(objtmp, sizeof(objtmp), desc->method); BIO_printf(out.get(), "%s - ", objtmp); if (!(ok = ncrypto::PrintGeneralName(out, desc->location))) { break; } } sk_ACCESS_DESCRIPTION_pop_free(descs, ACCESS_DESCRIPTION_free); #if OPENSSL_VERSION_MAJOR < 3 BIO_write(out.get(), "\n", 1); #endif return ok; } // ============================================================================ // X509Pointer X509Pointer::X509Pointer(X509* x509) : cert_(x509) {} X509Pointer::X509Pointer(X509Pointer&& other) noexcept : cert_(other.release()) {} X509Pointer& X509Pointer::operator=(X509Pointer&& other) noexcept { if (this == &other) return *this; this->~X509Pointer(); return *new (this) X509Pointer(std::move(other)); } X509Pointer::~X509Pointer() { reset(); } void X509Pointer::reset(X509* x509) { cert_.reset(x509); } X509* X509Pointer::release() { return cert_.release(); } X509View X509Pointer::view() const { return X509View(cert_.get()); } BIOPointer X509View::toPEM() const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr) return {}; BIOPointer bio(BIO_new(BIO_s_mem())); if (!bio) return {}; if (PEM_write_bio_X509(bio.get(), const_cast<X509*>(cert_)) <= 0) return {}; return bio; } BIOPointer X509View::toDER() const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr) return {}; BIOPointer bio(BIO_new(BIO_s_mem())); if (!bio) return {}; if (i2d_X509_bio(bio.get(), const_cast<X509*>(cert_)) <= 0) return {}; return bio; } const X509Name X509View::getSubjectName() const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr) return {}; return X509Name(X509_get_subject_name(cert_)); } const X509Name X509View::getIssuerName() const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr) return {}; return X509Name(X509_get_issuer_name(cert_)); } BIOPointer X509View::getSubject() const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr) return {}; BIOPointer bio(BIO_new(BIO_s_mem())); if (!bio) return {}; if (X509_NAME_print_ex(bio.get(), X509_get_subject_name(cert_), 0, kX509NameFlagsMultiline) <= 0) { return {}; } return bio; } BIOPointer X509View::getSubjectAltName() const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr) return {}; BIOPointer bio(BIO_new(BIO_s_mem())); if (!bio) return {}; int index = X509_get_ext_by_NID(cert_, NID_subject_alt_name, -1); if (index < 0 || !SafeX509SubjectAltNamePrint(bio, X509_get_ext(cert_, index))) { return {}; } return bio; } BIOPointer X509View::getIssuer() const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr) return {}; BIOPointer bio(BIO_new(BIO_s_mem())); if (!bio) return {}; if (X509_NAME_print_ex( bio.get(), X509_get_issuer_name(cert_), 0, kX509NameFlagsMultiline) <= 0) { return {}; } return bio; } BIOPointer X509View::getInfoAccess() const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr) return {}; BIOPointer bio(BIO_new(BIO_s_mem())); if (!bio) return {}; int index = X509_get_ext_by_NID(cert_, NID_info_access, -1); if (index < 0) return {}; if (!SafeX509InfoAccessPrint(bio, X509_get_ext(cert_, index))) { return {}; } return bio; } BIOPointer X509View::getValidFrom() const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr) return {}; BIOPointer bio(BIO_new(BIO_s_mem())); if (!bio) return {}; ASN1_TIME_print(bio.get(), X509_get0_notBefore(cert_)); return bio; } BIOPointer X509View::getValidTo() const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr) return {}; BIOPointer bio(BIO_new(BIO_s_mem())); if (!bio) return {}; ASN1_TIME_print(bio.get(), X509_get0_notAfter(cert_)); return bio; } std::optional<std::string_view> X509View::getSignatureAlgorithm() const { if (cert_ == nullptr) return std::nullopt; int nid = X509_get_signature_nid(cert_); if (nid == NID_undef) return std::nullopt; const char* ln = OBJ_nid2ln(nid); if (ln == nullptr) return std::nullopt; return std::string_view(ln); } std::optional<std::string> X509View::getSignatureAlgorithmOID() const { if (cert_ == nullptr) return std::nullopt; const X509_ALGOR* alg = nullptr; X509_get0_signature(nullptr, &alg, cert_); if (alg == nullptr) return std::nullopt; const ASN1_OBJECT* obj = nullptr; X509_ALGOR_get0(&obj, nullptr, nullptr, alg); if (obj == nullptr) return std::nullopt; std::array<char, 128> buf{}; int len = OBJ_obj2txt(buf.data(), buf.size(), obj, 1); if (len < 0 || static_cast<size_t>(len) >= buf.size()) return std::nullopt; return std::string(buf.data(), static_cast<size_t>(len)); } int64_t X509View::getValidToTime() const { #ifdef OPENSSL_IS_BORINGSSL // Boringssl does not implement ASN1_TIME_to_tm in a public way, // and only recently added ASN1_TIME_to_posix. Some boringssl // users on older version may still need to patch around this // or use a different implementation. int64_t tp; ASN1_TIME_to_posix(X509_get0_notAfter(cert_), &tp); return tp; #else struct tm tp; ASN1_TIME_to_tm(X509_get0_notAfter(cert_), &tp); return PortableTimeGM(&tp); #endif } int64_t X509View::getValidFromTime() const { #ifdef OPENSSL_IS_BORINGSSL int64_t tp; ASN1_TIME_to_posix(X509_get0_notBefore(cert_), &tp); return tp; #else struct tm tp; ASN1_TIME_to_tm(X509_get0_notBefore(cert_), &tp); return PortableTimeGM(&tp); #endif } DataPointer X509View::getSerialNumber() const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr) return {}; if (ASN1_INTEGER* serial_number = X509_get_serialNumber(const_cast<X509*>(cert_))) { if (auto bn = BignumPointer(ASN1_INTEGER_to_BN(serial_number, nullptr))) { return bn.toHex(); } } return {}; } Result<EVPKeyPointer, int> X509View::getPublicKey() const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr) return Result<EVPKeyPointer, int>(EVPKeyPointer{}); auto pkey = EVPKeyPointer(X509_get_pubkey(const_cast<X509*>(cert_))); if (!pkey) return Result<EVPKeyPointer, int>(ERR_get_error()); return pkey; } StackOfASN1 X509View::getKeyUsage() const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr) return {}; return StackOfASN1(static_cast<STACK_OF(ASN1_OBJECT)*>( X509_get_ext_d2i(cert_, NID_ext_key_usage, nullptr, nullptr))); } bool X509View::isCA() const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr) return false; return X509_check_ca(const_cast<X509*>(cert_)) == 1; } bool X509View::isIssuedBy(const X509View& issuer) const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr || issuer.cert_ == nullptr) return false; return X509_check_issued(const_cast<X509*>(issuer.cert_), const_cast<X509*>(cert_)) == X509_V_OK; } bool X509View::checkPrivateKey(const EVPKeyPointer& pkey) const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr || pkey == nullptr) return false; return X509_check_private_key(const_cast<X509*>(cert_), pkey.get()) == 1; } bool X509View::checkPublicKey(const EVPKeyPointer& pkey) const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr || pkey == nullptr) return false; return X509_verify(const_cast<X509*>(cert_), pkey.get()) == 1; } X509View::CheckMatch X509View::checkHost(const std::string_view host, int flags, DataPointer* peerName) const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr) return CheckMatch::NO_MATCH; char* peername; switch (X509_check_host( const_cast<X509*>(cert_), host.data(), host.size(), flags, &peername)) { case 0: return CheckMatch::NO_MATCH; case 1: { if (peername != nullptr) { DataPointer name(peername, strlen(peername)); if (peerName != nullptr) *peerName = std::move(name); } return CheckMatch::MATCH; } case -2: return CheckMatch::INVALID_NAME; default: return CheckMatch::OPERATION_FAILED; } } X509View::CheckMatch X509View::checkEmail(const std::string_view email, int flags) const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr) return CheckMatch::NO_MATCH; switch (X509_check_email( const_cast<X509*>(cert_), email.data(), email.size(), flags)) { case 0: return CheckMatch::NO_MATCH; case 1: return CheckMatch::MATCH; case -2: return CheckMatch::INVALID_NAME; default: return CheckMatch::OPERATION_FAILED; } } X509View::CheckMatch X509View::checkIp(const std::string_view ip, int flags) const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr) return CheckMatch::NO_MATCH; switch (X509_check_ip_asc(const_cast<X509*>(cert_), ip.data(), flags)) { case 0: return CheckMatch::NO_MATCH; case 1: return CheckMatch::MATCH; case -2: return CheckMatch::INVALID_NAME; default: return CheckMatch::OPERATION_FAILED; } } X509View X509View::From(const SSLPointer& ssl) { ClearErrorOnReturn clear_error_on_return; if (!ssl) return {}; return X509View(SSL_get_certificate(ssl.get())); } X509View X509View::From(const SSLCtxPointer& ctx) { ClearErrorOnReturn clear_error_on_return; if (!ctx) return {}; return X509View(SSL_CTX_get0_certificate(ctx.get())); } std::optional<std::string> X509View::getFingerprint( const Digest& method) const { unsigned int md_size; unsigned char md[EVP_MAX_MD_SIZE]; static constexpr char hex[] = "0123456789ABCDEF"; if (X509_digest(get(), method, md, &md_size)) { if (md_size == 0) return std::nullopt; std::string fingerprint((md_size * 3) - 1, 0); for (unsigned int i = 0; i < md_size; i++) { auto idx = 3 * i; fingerprint[idx] = hex[(md[i] & 0xf0) >> 4]; fingerprint[idx + 1] = hex[(md[i] & 0x0f)]; if (i == md_size - 1) break; fingerprint[idx + 2] = ':'; } return fingerprint; } return std::nullopt; } X509Pointer X509View::clone() const { ClearErrorOnReturn clear_error_on_return; if (!cert_) return {}; return X509Pointer(X509_dup(const_cast<X509*>(cert_))); } Result<X509Pointer, int> X509Pointer::Parse( Buffer<const unsigned char> buffer) { ClearErrorOnReturn clearErrorOnReturn; BIOPointer bio(BIO_new_mem_buf(buffer.data, buffer.len)); if (!bio) return Result<X509Pointer, int>(ERR_get_error()); X509Pointer pem( PEM_read_bio_X509_AUX(bio.get(), nullptr, NoPasswordCallback, nullptr)); if (pem) return Result<X509Pointer, int>(std::move(pem)); BIO_reset(bio.get()); X509Pointer der(d2i_X509_bio(bio.get(), nullptr)); if (der) return Result<X509Pointer, int>(std::move(der)); return Result<X509Pointer, int>(ERR_get_error()); } bool X509View::enumUsages(UsageCallback callback) const { if (cert_ == nullptr) return false; StackOfASN1 eku(static_cast<STACK_OF(ASN1_OBJECT)*>( X509_get_ext_d2i(cert_, NID_ext_key_usage, nullptr, nullptr))); if (!eku) return false; const int count = sk_ASN1_OBJECT_num(eku.get()); char buf[256]{}; for (int i = 0; i < count; i++) { if (OBJ_obj2txt(buf, sizeof(buf), sk_ASN1_OBJECT_value(eku.get(), i), 1) >= 0) { callback(buf); } } return true; } bool X509View::ifRsa(KeyCallback<Rsa> callback) const { if (cert_ == nullptr) return true; 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); } return true; } bool X509View::ifEc(KeyCallback<Ec> callback) const { if (cert_ == nullptr) return true; 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); } return true; } X509Pointer X509Pointer::IssuerFrom(const SSLPointer& ssl, const X509View& view) { return IssuerFrom(SSL_get_SSL_CTX(ssl.get()), view); } X509Pointer X509Pointer::IssuerFrom(const SSL_CTX* ctx, const X509View& cert) { X509_STORE* store = SSL_CTX_get_cert_store(ctx); DeleteFnPtr<X509_STORE_CTX, X509_STORE_CTX_free> store_ctx( X509_STORE_CTX_new()); X509Pointer result; X509* issuer; if (store_ctx.get() != nullptr && X509_STORE_CTX_init(store_ctx.get(), store, nullptr, nullptr) == 1 && X509_STORE_CTX_get1_issuer(&issuer, store_ctx.get(), cert.get()) == 1) { result.reset(issuer); } return result; } 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 // documentation. See the "OpenSSL Error Codes" section of doc/api/errors.md // Also *please* update the respective section in doc/api/tls.md as well const char* X509Pointer::ErrorCode(int32_t err) { // NOLINT(runtime/int) #define CASE(CODE) \ case X509_V_ERR_##CODE: \ return #CODE; switch (err) { CASE(UNABLE_TO_GET_ISSUER_CERT) CASE(UNABLE_TO_GET_CRL) CASE(UNABLE_TO_DECRYPT_CERT_SIGNATURE) CASE(UNABLE_TO_DECRYPT_CRL_SIGNATURE) CASE(UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY) CASE(CERT_SIGNATURE_FAILURE) CASE(CRL_SIGNATURE_FAILURE) CASE(CERT_NOT_YET_VALID) CASE(CERT_HAS_EXPIRED) CASE(CRL_NOT_YET_VALID) CASE(CRL_HAS_EXPIRED) CASE(ERROR_IN_CERT_NOT_BEFORE_FIELD) CASE(ERROR_IN_CERT_NOT_AFTER_FIELD) CASE(ERROR_IN_CRL_LAST_UPDATE_FIELD) CASE(ERROR_IN_CRL_NEXT_UPDATE_FIELD) CASE(OUT_OF_MEM) CASE(DEPTH_ZERO_SELF_SIGNED_CERT) CASE(SELF_SIGNED_CERT_IN_CHAIN) CASE(UNABLE_TO_GET_ISSUER_CERT_LOCALLY) CASE(UNABLE_TO_VERIFY_LEAF_SIGNATURE) CASE(CERT_CHAIN_TOO_LONG) CASE(CERT_REVOKED) CASE(INVALID_CA) CASE(PATH_LENGTH_EXCEEDED) CASE(INVALID_PURPOSE) CASE(CERT_UNTRUSTED) CASE(CERT_REJECTED) CASE(HOSTNAME_MISMATCH) } #undef CASE return "UNSPECIFIED"; } std::optional<const char*> X509Pointer::ErrorReason(int32_t err) { if (err == X509_V_OK) return std::nullopt; return X509_verify_cert_error_string(err); } // ============================================================================ // BIOPointer BIOPointer::BIOPointer(BIO* bio) : bio_(bio) {} BIOPointer::BIOPointer(BIOPointer&& other) noexcept : bio_(other.release()) {} BIOPointer& BIOPointer::operator=(BIOPointer&& other) noexcept { if (this == &other) return *this; this->~BIOPointer(); return *new (this) BIOPointer(std::move(other)); } BIOPointer::~BIOPointer() { reset(); } void BIOPointer::reset(BIO* bio) { bio_.reset(bio); } BIO* BIOPointer::release() { return bio_.release(); } bool BIOPointer::resetBio() const { if (!bio_) return 0; return BIO_reset(bio_.get()) == 1; } BIOPointer BIOPointer::NewMem() { return BIOPointer(BIO_new(BIO_s_mem())); } BIOPointer BIOPointer::NewSecMem() { #ifdef OPENSSL_IS_BORINGSSL // Boringssl does not implement the BIO_s_secmem API. return BIOPointer(BIO_new(BIO_s_mem())); #else return BIOPointer(BIO_new(BIO_s_secmem())); #endif } BIOPointer BIOPointer::New(const BIO_METHOD* method) { if (method == nullptr) return {}; return BIOPointer(BIO_new(method)); } BIOPointer BIOPointer::New(const void* data, size_t len) { return BIOPointer(BIO_new_mem_buf(data, len)); } BIOPointer BIOPointer::NewFile(const char* filename, const char* mode) { return BIOPointer(BIO_new_file(filename, mode)); } BIOPointer BIOPointer::NewFp(FILE* fd, int close_flag) { return BIOPointer(BIO_new_fp(fd, close_flag)); } BIOPointer BIOPointer::New(const BIGNUM* bn) { auto res = NewMem(); if (!res || !BN_print(res.get(), bn)) return {}; return res; } int BIOPointer::Write(BIOPointer* bio, std::string_view message) { if (bio == nullptr || !*bio) return 0; return BIO_write(bio->get(), message.data(), message.size()); } // ============================================================================ // DHPointer namespace { bool EqualNoCase(const std::string_view a, const std::string_view b) { if (a.size() != b.size()) return false; return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](char a, char 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<int> 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<int>(DHPointer::CheckResult::P_NOT_PRIME); } if (BN_is_negative(g) || BN_is_zero(g) || BN_is_one(g)) { codes |= static_cast<int>(DHPointer::CheckResult::NOT_SUITABLE_GENERATOR); } if (p_bits < 512) { codes |= static_cast<int>(DHPointer::CheckResult::MODULUS_TOO_SMALL); } if (p_bits > OPENSSL_DH_MAX_MODULUS_BITS) { codes |= static_cast<int>(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<int>(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<int>(DHPointer::CheckResult::INVALID_Q); } } if (q_good) { if (BN_cmp(g, BN_value_one()) <= 0 || BN_cmp(g, p) >= 0) { codes |= static_cast<int>(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<int>(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<int>(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<int>(DHPointer::CheckResult::INVALID_Q); } if (j != nullptr && BN_cmp(j, tmp1.get()) != 0) { codes |= static_cast<int>(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<int>(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<int>(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 #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; this->~DHPointer(); return *new (this) DHPointer(std::move(other)); } DHPointer::~DHPointer() { reset(); } 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) { #define V(n, p) \ if (EqualNoCase(name, n)) return BignumPointer(p(nullptr)); if (option != FindGroupOption::NO_SMALL_PRIMES) { #ifndef OPENSSL_IS_BORINGSSL // Boringssl does not support the 768 and 1024 small primes V("modp1", BN_get_rfc2409_prime_768); V("modp2", BN_get_rfc2409_prime_1024); #endif V("modp5", BN_get_rfc3526_prime_1536); } V("modp14", BN_get_rfc3526_prime_2048); V("modp15", BN_get_rfc3526_prime_3072); V("modp16", BN_get_rfc3526_prime_4096); V("modp17", BN_get_rfc3526_prime_6144); V("modp18", BN_get_rfc3526_prime_8192); #undef V return {}; } BignumPointer DHPointer::GetStandardGenerator() { auto bn = BignumPointer::New(); if (!bn) return {}; if (!bn.setWord(2)) return {}; return bn; } DHPointer DHPointer::FromGroup(const std::string_view name, FindGroupOption option) { auto group = FindGroup(name, option); if (!group) return {}; // Unable to find the named group. 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 {}; if (DH_set0_pqg(dh.get(), p.get(), nullptr, g.get()) != 1) return {}; // If the call above is successful, the DH object takes ownership of the // BIGNUMs, so we must release them here. Unfortunately coverity does not // know that so we need to tell it not to complain. // coverity[resource_leak] p.release(); // coverity[resource_leak] 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 {}; if (DH_generate_parameters_ex(dh.get(), bits, generator, nullptr) != 1) { return {}; } return dh; #endif } DHPointer::CheckResult DHPointer::check() { ClearErrorOnReturn clearErrorOnReturn; 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<BIGNUM, BN_free> p; DeleteFnPtr<BIGNUM, BN_free> g; DeleteFnPtr<BIGNUM, BN_free> q; DeleteFnPtr<BIGNUM, BN_free> 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<CheckResult>(*codes); #else int codes = 0; if (DH_check(dh_.get(), &codes) != 1) return DHPointer::CheckResult::CHECK_FAILED; return static_cast<CheckResult>(codes); #endif } DHPointer::CheckPublicKeyResult DHPointer::checkPublicKey( const BignumPointer& pub_key) { ClearErrorOnReturn clearErrorOnReturn; if (!pub_key || !*this) { return DHPointer::CheckPublicKeyResult::CHECK_FAILED; } #if NCRYPTO_USE_OPENSSL3_PROVIDER DeleteFnPtr<BIGNUM, BN_free> p; DeleteFnPtr<BIGNUM, BN_free> 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<BIGNUM, BN_free> 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; } #ifndef OPENSSL_IS_BORINGSSL // Boringssl does not define DH_CHECK_PUBKEY_TOO_SMALL or TOO_LARGE if (codes & DH_CHECK_PUBKEY_TOO_SMALL) { return DHPointer::CheckPublicKeyResult::TOO_SMALL; } else if (codes & DH_CHECK_PUBKEY_TOO_LARGE) { return DHPointer::CheckPublicKeyResult::TOO_LARGE; } #endif if (codes != 0) { return DHPointer::CheckPublicKeyResult::INVALID; } return CheckPublicKeyResult::NONE; #endif } DataPointer DHPointer::getPrime() const { if (!*this) return {}; #if NCRYPTO_USE_OPENSSL3_PROVIDER if (p_) return p_.encode(); DeleteFnPtr<BIGNUM, BN_free> p; DeleteFnPtr<BIGNUM, BN_free> 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<BIGNUM, BN_free> p; DeleteFnPtr<BIGNUM, BN_free> 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 (!*this) return {}; #if NCRYPTO_USE_OPENSSL3_PROVIDER if (g_) return g_.encode(); DeleteFnPtr<BIGNUM, BN_free> p; DeleteFnPtr<BIGNUM, BN_free> 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<BIGNUM, BN_free> 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<BIGNUM, BN_clear_free> 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 } bool DHPointer::hasPrivateKey() const { if (!*this) return false; #if NCRYPTO_USE_OPENSSL3_PROVIDER if (pvt_key_) return true; if (!dh_) return false; DeleteFnPtr<BIGNUM, BN_clear_free> 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::generateKeys() { ClearErrorOnReturn clearErrorOnReturn; if (!*this) return {}; #if NCRYPTO_USE_OPENSSL3_PROVIDER if (p_ && g_) { if (!pvt_key_ && !GenerateDhPrivateKey(&pvt_key_, p_.get(), group_name_)) { return {}; } BignumPointer generated_pub_key; if (!GenerateDhPublicKey( &generated_pub_key, p_.get(), g_.get(), pvt_key_.get())) { return {}; } if (pub_key_ && BN_cmp(pub_key_.get(), generated_pub_key.get()) == 0) { return getPublicKey(); } pub_key_ = std::move(generated_pub_key); return getPublicKey(); } DeleteFnPtr<BIGNUM, BN_free> p; DeleteFnPtr<BIGNUM, BN_free> g; DeleteFnPtr<BIGNUM, BN_free> pub_key; DeleteFnPtr<BIGNUM, BN_clear_free> 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<size_t>(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<size_t>(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<BIGNUM, BN_free> p; DeleteFnPtr<BIGNUM, BN_free> 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<unsigned char*>(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<uint8_t*>(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. if (static_cast<size_t>(size) < dp.size()) { const size_t padding = dp.size() - size; uint8_t* data = static_cast<uint8_t*>(dp.get()); memmove(data + padding, data, size); memset(data, 0, padding); } return dp; #endif } bool DHPointer::setPublicKey(BignumPointer&& key) { if (!*this) return false; #if NCRYPTO_USE_OPENSSL3_PROVIDER if (p_ && g_) { pub_key_ = std::move(key); return true; } DeleteFnPtr<BIGNUM, BN_clear_free> 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<BIGNUM, BN_free> p; DeleteFnPtr<BIGNUM, BN_free> 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 // know that so we need to tell it not to complain. // coverity[resource_leak] key.release(); return true; } return false; #endif } bool DHPointer::setPrivateKey(BignumPointer&& key) { if (!*this) return false; #if NCRYPTO_USE_OPENSSL3_PROVIDER if (p_ && g_) { pvt_key_ = std::move(key); return true; } DeleteFnPtr<BIGNUM, BN_free> 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<BIGNUM, BN_free> p; DeleteFnPtr<BIGNUM, BN_free> 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 // know that so we need to tell it not to complain. // coverity[resource_leak] key.release(); return true; } return false; #endif } DataPointer DHPointer::stateless(const EVPKeyPointer& ourKey, const EVPKeyPointer& theirKey) { size_t out_size; if (!ourKey || !theirKey) return {}; auto ctx = EVPKeyCtxPointer::New(ourKey); 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 {}; } if (out_size == 0) return {}; auto out = DataPointer::Alloc(out_size); if (EVP_PKEY_derive( ctx.get(), reinterpret_cast<uint8_t*>(out.get()), &out_size) <= 0) { return {}; } if (out_size < out.size()) { const size_t padding = out.size() - out_size; uint8_t* data = static_cast<uint8_t*>(out.get()); memmove(data + padding, data, out_size); memset(data, 0, padding); } return out; } // ============================================================================ // KDF const EVP_MD* getDigestByName(const char* name) { // Historically, "dss1" and "DSS1" were DSA aliases for SHA-1 // exposed through the public API. if (strcmp(name, "dss1") == 0 || strcmp(name, "DSS1") == 0) [[unlikely]] { return EVP_sha1(); } return EVP_get_digestbyname(name); } const EVP_CIPHER* getCipherByName(const char* name) { return EVP_get_cipherbyname(name); } bool checkHkdfLength(const Digest& md, size_t length) { // HKDF-Expand computes up to 255 HMAC blocks, each having as many bits as // the output of the hash function. 255 is a hard limit because HKDF appends // an 8-bit counter to each HMAC'd message, starting at 1. static constexpr size_t kMaxDigestMultiplier = 255; size_t max_length = md.size() * kMaxDigestMultiplier; if (length > max_length) return false; return true; } DataPointer hkdf(const Digest& md, const Buffer<const unsigned char>& key, const Buffer<const unsigned char>& info, const Buffer<const unsigned char>& salt, size_t length) { if (!checkHkdfLength(md, length) || info.len > INT_MAX || salt.len > INT_MAX) { return {}; } auto ctx = EVPKeyCtxPointer::NewFromID(EVP_PKEY_HKDF); // OpenSSL < 3.0.0 accepted only a void* as the argument of // EVP_PKEY_CTX_set_hkdf_md. const EVP_MD* md_ptr = md; if (!ctx || !EVP_PKEY_derive_init(ctx.get()) || !EVP_PKEY_CTX_set_hkdf_md(ctx.get(), md_ptr) || !EVP_PKEY_CTX_add1_hkdf_info(ctx.get(), info.data, info.len)) { return {}; } std::string_view actual_salt; static const char default_salt[EVP_MAX_MD_SIZE] = {0}; if (salt.len > 0) { actual_salt = {reinterpret_cast<const char*>(salt.data), salt.len}; } else { actual_salt = {default_salt, static_cast<unsigned>(md.size())}; } // We do not use EVP_PKEY_HKDF_MODE_EXTRACT_AND_EXPAND because and instead // implement the extraction step ourselves because EVP_PKEY_derive does not // handle zero-length keys, which are required for Web Crypto. // TODO(jasnell): Once OpenSSL 1.1.1 support is dropped completely, and once // BoringSSL is confirmed to support it, wen can hopefully drop this and use // EVP_KDF directly which does support zero length keys. unsigned char pseudorandom_key[EVP_MAX_MD_SIZE]; unsigned pseudorandom_key_len = sizeof(pseudorandom_key); if (HMAC(md, actual_salt.data(), actual_salt.size(), key.data, key.len, pseudorandom_key, &pseudorandom_key_len) == nullptr) { return {}; } if (!EVP_PKEY_CTX_hkdf_mode(ctx.get(), EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) || !EVP_PKEY_CTX_set1_hkdf_key( ctx.get(), pseudorandom_key, pseudorandom_key_len)) { return {}; } auto buf = DataPointer::Alloc(length); if (!buf) return {}; if (EVP_PKEY_derive( ctx.get(), static_cast<unsigned char*>(buf.get()), &length) <= 0) { return {}; } return buf; } bool checkScryptParams(uint64_t N, uint64_t r, uint64_t p, uint64_t maxmem) { return EVP_PBE_scrypt(nullptr, 0, nullptr, 0, N, r, p, maxmem, nullptr, 0) == 1; } DataPointer scrypt(const Buffer<const char>& pass, const Buffer<const unsigned char>& salt, uint64_t N, uint64_t r, uint64_t p, uint64_t maxmem, size_t length) { if (pass.len > INT_MAX || salt.len > INT_MAX) { return {}; } auto dp = DataPointer::Alloc(length); if (dp && EVP_PBE_scrypt(pass.data, pass.len, salt.data, salt.len, N, r, p, maxmem, reinterpret_cast<unsigned char*>(dp.get()), length)) { return dp; } return {}; } DataPointer pbkdf2(const Digest& md, const Buffer<const char>& pass, const Buffer<const unsigned char>& salt, uint32_t iterations, size_t length) { if (pass.len > INT_MAX || salt.len > INT_MAX || length > INT_MAX) { return {}; } auto dp = DataPointer::Alloc(length); const EVP_MD* md_ptr = md; if (dp && PKCS5_PBKDF2_HMAC(pass.data, pass.len, salt.data, salt.len, iterations, md_ptr, length, reinterpret_cast<unsigned char*>(dp.get()))) { return dp; } return {}; } #if OPENSSL_WITH_ARGON2 DataPointer argon2(const Buffer<const char>& pass, const Buffer<const unsigned char>& salt, uint32_t lanes, size_t length, uint32_t memcost, uint32_t iter, uint32_t version, const Buffer<const unsigned char>& secret, const Buffer<const unsigned char>& ad, Argon2Type type) { std::string_view algorithm; switch (type) { case Argon2Type::ARGON2I: algorithm = "ARGON2I"; break; case Argon2Type::ARGON2D: algorithm = "ARGON2D"; break; case Argon2Type::ARGON2ID: algorithm = "ARGON2ID"; break; default: // Invalid Argon2 type return {}; } // A new library context is only needed for OSSL_set_max_threads(), which is // per-context. It inherits no configuration, so availability is checked // against the default context, otherwise Argon2 works in FIPS mode. DeleteFnPtr<OSSL_LIB_CTX, OSSL_LIB_CTX_free> ctx; if (lanes > 1) { if (!DeleteFnPtr<EVP_KDF, EVP_KDF_free>{ EVP_KDF_fetch(nullptr, algorithm.data(), nullptr)}) { return {}; } ctx.reset(OSSL_LIB_CTX_new()); if (!ctx) { return {}; } if (OSSL_set_max_threads(ctx.get(), lanes) != 1) { return {}; } } auto kdf = DeleteFnPtr<EVP_KDF, EVP_KDF_free>{ EVP_KDF_fetch(ctx.get(), algorithm.data(), nullptr)}; if (!kdf) { return {}; } auto kctx = DeleteFnPtr<EVP_KDF_CTX, EVP_KDF_CTX_free>{EVP_KDF_CTX_new(kdf.get())}; if (!kctx) { return {}; } std::vector<OSSL_PARAM> params; params.reserve(9); params.push_back(OSSL_PARAM_construct_octet_string( OSSL_KDF_PARAM_PASSWORD, const_cast<char*>(pass.len > 0 ? pass.data : ""), pass.len)); params.push_back(OSSL_PARAM_construct_octet_string( OSSL_KDF_PARAM_SALT, const_cast<unsigned char*>(salt.data), salt.len)); params.push_back(OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_THREADS, &lanes)); params.push_back( OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_LANES, &lanes)); params.push_back( OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_MEMCOST, &memcost)); params.push_back(OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ITER, &iter)); if (ad.len != 0) { params.push_back(OSSL_PARAM_construct_octet_string( OSSL_KDF_PARAM_ARGON2_AD, const_cast<unsigned char*>(ad.data), ad.len)); } if (secret.len != 0) { params.push_back(OSSL_PARAM_construct_octet_string( OSSL_KDF_PARAM_SECRET, const_cast<unsigned char*>(secret.data), secret.len)); } params.push_back(OSSL_PARAM_construct_end()); auto dp = DataPointer::Alloc(length); if (dp && EVP_KDF_derive(kctx.get(), reinterpret_cast<unsigned char*>(dp.get()), length, params.data()) == 1) { return dp; } return {}; } #endif // ============================================================================ EVPKeyPointer::PrivateKeyEncodingConfig::PrivateKeyEncodingConfig( const PrivateKeyEncodingConfig& other) : PrivateKeyEncodingConfig( other.output_key_object, other.format, other.type) { cipher = other.cipher; if (other.passphrase.has_value()) { auto& otherPassphrase = other.passphrase.value(); auto newPassphrase = DataPointer::Alloc(otherPassphrase.size()); memcpy(newPassphrase.get(), otherPassphrase.get(), otherPassphrase.size()); passphrase = std::move(newPassphrase); } } EVPKeyPointer::AsymmetricKeyEncodingConfig::AsymmetricKeyEncodingConfig( bool output_key_object, PKFormatType format, PKEncodingType type) : output_key_object(output_key_object), format(format), type(type) {} EVPKeyPointer::PrivateKeyEncodingConfig& EVPKeyPointer::PrivateKeyEncodingConfig::operator=( const PrivateKeyEncodingConfig& other) { if (this == &other) return *this; this->~PrivateKeyEncodingConfig(); return *new (this) PrivateKeyEncodingConfig(other); } EVPKeyPointer EVPKeyPointer::New() { return EVPKeyPointer(EVP_PKEY_new()); } EVPKeyPointer EVPKeyPointer::NewRawPublic( int id, const Buffer<const unsigned char>& data) { if (id == 0) return {}; return EVPKeyPointer( EVP_PKEY_new_raw_public_key(id, nullptr, data.data, data.len)); } EVPKeyPointer EVPKeyPointer::NewRawPrivate( int id, const Buffer<const unsigned char>& data) { if (id == 0) return {}; return EVPKeyPointer( EVP_PKEY_new_raw_private_key(id, nullptr, data.data, data.len)); } #if OPENSSL_WITH_PQC namespace { constexpr size_t kPqcMlDsaSeedSize = 32; constexpr size_t kPqcMlKemSeedSize = 64; size_t GetPqcSeedSize(int id) { switch (id) { case EVP_PKEY_ML_DSA_44: case EVP_PKEY_ML_DSA_65: case EVP_PKEY_ML_DSA_87: return kPqcMlDsaSeedSize; #if OPENSSL_WITH_PQC_ML_KEM_512 case EVP_PKEY_ML_KEM_512: #endif case EVP_PKEY_ML_KEM_768: case EVP_PKEY_ML_KEM_1024: return kPqcMlKemSeedSize; default: unreachable(); } } #if OPENSSL_WITH_BORINGSSL_PQC const EVP_PKEY_ALG* GetPqcSeedAlg(int id) { switch (id) { case EVP_PKEY_ML_DSA_44: return EVP_pkey_ml_dsa_44(); case EVP_PKEY_ML_DSA_65: return EVP_pkey_ml_dsa_65(); case EVP_PKEY_ML_DSA_87: return EVP_pkey_ml_dsa_87(); case EVP_PKEY_ML_KEM_768: return EVP_pkey_ml_kem_768(); case EVP_PKEY_ML_KEM_1024: return EVP_pkey_ml_kem_1024(); default: unreachable(); } } #else const char* GetPqcSeedParamName(int id) { switch (id) { case EVP_PKEY_ML_DSA_44: case EVP_PKEY_ML_DSA_65: case EVP_PKEY_ML_DSA_87: return OSSL_PKEY_PARAM_ML_DSA_SEED; case EVP_PKEY_ML_KEM_512: case EVP_PKEY_ML_KEM_768: case EVP_PKEY_ML_KEM_1024: return OSSL_PKEY_PARAM_ML_KEM_SEED; default: unreachable(); } } #endif EVPKeyPointer NewPqcKeyFromSeed(int id, const Buffer<const unsigned char>& data) { #if OPENSSL_WITH_BORINGSSL_PQC return EVPKeyPointer( EVP_PKEY_from_private_seed(GetPqcSeedAlg(id), data.data, data.len)); #else OSSL_PARAM params[] = { OSSL_PARAM_construct_octet_string(GetPqcSeedParamName(id), const_cast<unsigned char*>(data.data), data.len), OSSL_PARAM_END}; auto ctx = EVPKeyCtxPointer::NewFromID(id); if (!ctx) return {}; EVP_PKEY* pkey = nullptr; if (EVP_PKEY_fromdata_init(ctx.get()) <= 0 || EVP_PKEY_fromdata(ctx.get(), &pkey, EVP_PKEY_KEYPAIR, params) <= 0) { return {}; } return EVPKeyPointer(pkey); #endif } bool GetPqcSeed(EVP_PKEY* pkey, int id, const Buffer<unsigned char>& out) { size_t len = out.len; #if OPENSSL_WITH_BORINGSSL_PQC return EVP_PKEY_get_private_seed(pkey, out.data, &len) == 1; #else return EVP_PKEY_get_octet_string_param( pkey, GetPqcSeedParamName(id), out.data, out.len, &len) == 1; #endif } } // namespace EVPKeyPointer EVPKeyPointer::NewRawSeed( int id, const Buffer<const unsigned char>& data) { return NewPqcKeyFromSeed(id, data); } #endif 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(); if (!key) return {}; if (EVP_PKEY_assign_RSA(key.get(), rsa.get())) { rsa.release(); } return key; } #endif // NCRYPTO_USE_OPENSSL3_PROVIDER EVPKeyPointer::EVPKeyPointer(EVP_PKEY* pkey) : pkey_(pkey) {} EVPKeyPointer::EVPKeyPointer(EVPKeyPointer&& other) noexcept : pkey_(other.release()) {} EVPKeyPointer& EVPKeyPointer::operator=(EVPKeyPointer&& other) noexcept { if (this == &other) return *this; this->~EVPKeyPointer(); return *new (this) EVPKeyPointer(std::move(other)); } EVPKeyPointer::~EVPKeyPointer() { reset(); } void EVPKeyPointer::reset(EVP_PKEY* pkey) { pkey_.reset(pkey); } EVP_PKEY* EVPKeyPointer::release() { return pkey_.release(); } int EVPKeyPointer::id(const EVP_PKEY* key) { if (key == nullptr) return 0; int type = EVP_PKEY_id(key); #if OPENSSL_WITH_OPENSSL_PQC // EVP_PKEY_id returns -1 when EVP_PKEY_* is only implemented in a provider // which is the case for all post-quantum NIST algorithms // one suggested way would be to use a chain of `EVP_PKEY_is_a` // https://github.com/openssl/openssl/issues/27738#issuecomment-3013215870 // or, this way there are less calls to the OpenSSL provider, just // getting the name once if (type == -1) { const char* type_name = EVP_PKEY_get0_type_name(key); if (type_name == nullptr) return -1; for (const auto& mapping : pqc_mappings) { if (strcmp(type_name, mapping.name) == 0) { return mapping.nid; } } } #endif return type; } int EVPKeyPointer::base_id(const EVP_PKEY* key) { if (key == nullptr) return 0; return EVP_PKEY_base_id(key); } int EVPKeyPointer::id() const { return id(get()); } int EVPKeyPointer::base_id() const { return base_id(get()); } int EVPKeyPointer::bits() const { if (get() == nullptr) return 0; return EVP_PKEY_bits(get()); } size_t EVPKeyPointer::size() const { if (get() == nullptr) return 0; return EVP_PKEY_size(get()); } EVPKeyCtxPointer EVPKeyPointer::newCtx() const { if (!pkey_) return {}; return EVPKeyCtxPointer::New(*this); } size_t EVPKeyPointer::rawPublicKeySize() const { if (!pkey_) return 0; size_t len = 0; if (EVP_PKEY_get_raw_public_key(get(), nullptr, &len) == 1) return len; return 0; } size_t EVPKeyPointer::rawPrivateKeySize() const { if (!pkey_) return 0; size_t len = 0; if (EVP_PKEY_get_raw_private_key(get(), nullptr, &len) == 1) return len; return 0; } DataPointer EVPKeyPointer::rawPublicKey() const { if (!pkey_) return {}; if (auto data = DataPointer::Alloc(rawPublicKeySize())) { const Buffer<unsigned char> buf = data; size_t len = data.size(); if (EVP_PKEY_get_raw_public_key(get(), buf.data, &len) != 1) return {}; return data; } return {}; } #if OPENSSL_WITH_PQC DataPointer EVPKeyPointer::rawSeed() const { if (!pkey_) return {}; const size_t seed_len = GetPqcSeedSize(id()); if (auto data = DataPointer::Alloc(seed_len)) { const Buffer<unsigned char> buf = data; if (!GetPqcSeed(get(), id(), buf)) return {}; return data; } return {}; } #endif DataPointer EVPKeyPointer::rawPrivateKey() const { if (!pkey_) return {}; if (auto data = DataPointer::Alloc(rawPrivateKeySize())) { const Buffer<unsigned char> buf = data; size_t len = data.size(); if (EVP_PKEY_get_raw_private_key(get(), buf.data, &len) != 1) return {}; return data; } return {}; } BIOPointer EVPKeyPointer::derPublicKey() const { if (!pkey_) return {}; auto bio = BIOPointer::NewMem(); if (!bio) return {}; if (!i2d_PUBKEY_bio(bio.get(), get())) return {}; return bio; } 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 { EVPKeyPointer::ParseKeyResult TryParsePublicKeyInner(const BIOPointer& bp, const char* name, auto&& parse) { if (!bp.resetBio()) { return EVPKeyPointer::ParseKeyResult(EVPKeyPointer::PKParseError::FAILED); } unsigned char* der_data; long der_len; // NOLINT(runtime/int) // This skips surrounding data and decodes PEM to DER. { MarkPopErrorOnReturn mark_pop_error_on_return; if (PEM_bytes_read_bio( &der_data, &der_len, nullptr, name, bp.get(), nullptr, nullptr) != 1) return EVPKeyPointer::ParseKeyResult( EVPKeyPointer::PKParseError::NOT_RECOGNIZED); } DataPointer data(der_data, der_len); // OpenSSL might modify the pointer, so we need to make a copy before parsing. const unsigned char* p = der_data; EVPKeyPointer pkey(parse(&p, der_len)); if (!pkey) return EVPKeyPointer::ParseKeyResult(EVPKeyPointer::PKParseError::FAILED); return EVPKeyPointer::ParseKeyResult(std::move(pkey)); } constexpr bool IsASN1Sequence(const unsigned char* data, size_t size, size_t* data_offset, size_t* data_size) { if (size < 2 || data[0] != 0x30) return false; if (data[1] & 0x80) { // Long form. size_t n_bytes = data[1] & ~0x80; if (n_bytes + 2 > size || n_bytes > sizeof(size_t)) return false; size_t length = 0; for (size_t i = 0; i < n_bytes; i++) length = (length << 8) | data[i + 2]; *data_offset = 2 + n_bytes; *data_size = std::min(size - 2 - n_bytes, length); } else { // Short form. *data_offset = 2; *data_size = std::min<size_t>(size - 2, data[1]); } 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<const unsigned char>& buffer) { // Both PrivateKeyInfo and EncryptedPrivateKeyInfo start with a SEQUENCE. if (buffer.len == 0 || buffer.data == nullptr) return false; size_t offset, len; if (!IsASN1Sequence(buffer.data, buffer.len, &offset, &len)) return false; // A PrivateKeyInfo sequence always starts with an integer whereas an // EncryptedPrivateKeyInfo starts with an AlgorithmIdentifier. return len >= 1 && buffer.data[offset] != 2; } } // namespace bool EVPKeyPointer::IsRSAPrivateKey(const Buffer<const unsigned char>& buffer) { // Both RSAPrivateKey and RSAPublicKey structures start with a SEQUENCE. size_t offset, len; if (!IsASN1Sequence(buffer.data, buffer.len, &offset, &len)) return false; // An RSAPrivateKey sequence always starts with a single-byte integer whose // value is either 0 or 1, whereas an RSAPublicKey starts with the modulus // (which is the product of two primes and therefore at least 4), so we can // decide the type of the structure based on the first three bytes of the // sequence. return len >= 3 && buffer.data[offset] == 2 && buffer.data[offset + 1] == 1 && !(buffer.data[offset + 2] & 0xfe); } EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePublicKeyPEM( const Buffer<const unsigned char>& buffer) { auto bp = BIOPointer::New(buffer.data, buffer.len); if (!bp) return ParseKeyResult(PKParseError::FAILED); // Try parsing as SubjectPublicKeyInfo (SPKI) first. if (auto ret = TryParsePublicKeyInner( bp, "PUBLIC KEY", [](const unsigned char** p, long l) { // NOLINT(runtime/int) return d2i_PUBKEY(nullptr, p, l); })) { return ret; } // Maybe it is PKCS#1. if (auto ret = TryParsePublicKeyInner( bp, "RSA PUBLIC KEY", [](const unsigned char** p, long l) { // NOLINT(runtime/int) return d2i_PublicKey(EVP_PKEY_RSA, nullptr, p, l); })) { return ret; } // X.509 fallback. if (auto ret = TryParsePublicKeyInner( bp, "CERTIFICATE", [](const unsigned char** p, long l) { // NOLINT(runtime/int) X509Pointer x509(d2i_X509(nullptr, p, l)); return x509 ? X509_get_pubkey(x509.get()) : nullptr; })) { return ret; }; return ParseKeyResult(PKParseError::NOT_RECOGNIZED); } EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePublicKey( const PublicKeyEncodingConfig& config, const Buffer<const unsigned char>& buffer) { if (config.format == PKFormatType::PEM) { return TryParsePublicKeyPEM(buffer); } if (config.format != PKFormatType::DER) { return ParseKeyResult(PKParseError::FAILED); } const unsigned char* start = buffer.data; EVP_PKEY* key = nullptr; if (config.type == PKEncodingType::PKCS1 && (key = d2i_PublicKey(EVP_PKEY_RSA, nullptr, &start, buffer.len))) { return EVPKeyPointer::ParseKeyResult(EVPKeyPointer(key)); } if (config.type == PKEncodingType::SPKI && (key = d2i_PUBKEY(nullptr, &start, buffer.len))) { return EVPKeyPointer::ParseKeyResult(EVPKeyPointer(key)); } return ParseKeyResult(PKParseError::FAILED); } namespace { Buffer<char> GetPassphrase( const EVPKeyPointer::PrivateKeyEncodingConfig& config) { Buffer<char> pass{ // OpenSSL will not actually dereference this pointer, so it can be any // non-null pointer. We cannot assert that directly, which is why we // intentionally use a pointer that will likely cause a segmentation fault // when dereferenced. .data = reinterpret_cast<char*>(-1), .len = 0, }; if (config.passphrase.has_value()) { auto& passphrase = config.passphrase.value(); // The pass.data can't be a nullptr, even if the len is zero or else // openssl will prompt for a password and we really don't want that. if (passphrase.get() != nullptr) { pass.data = static_cast<char*>(passphrase.get()); } pass.len = passphrase.size(); } return pass; } #if NCRYPTO_USE_OPENSSL3_PROVIDER using OSSLEncoderCtxPointer = DeleteFnPtr<OSSL_ENCODER_CTX, OSSL_ENCODER_CTX_free>; bool WriteEncodedPKey(BIO* bio, const EVP_PKEY* pkey, int selection, EVPKeyPointer::PKFormatType format, const char* structure, const EVP_CIPHER* cipher = nullptr, Buffer<char> 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<const unsigned char*>(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<const DERView*>(x); if (der == nullptr || der->data == nullptr || der->len > static_cast<size_t>(INT_MAX)) { return -1; } if (out != nullptr) { memcpy(*out, der->data, der->len); *out += der->len; } return static_cast<int>(der->len); } bool WriteEncryptedTraditionalPEM(BIO* bio, const EVP_PKEY* pkey, const EVP_CIPHER* cipher, Buffer<char> passphrase) { if (passphrase.len > static_cast<size_t>(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<const unsigned char>(), der_len}; return PEM_ASN1_write_bio( WriteDERView, PEM_STRING_RSA, bio, &der_view, cipher, reinterpret_cast<const unsigned char*>(passphrase.data), static_cast<int>(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( const PrivateKeyEncodingConfig& config, const Buffer<const unsigned char>& buffer) { static constexpr auto keyOrError = [](EVPKeyPointer pkey, bool had_passphrase = false) { if (unsigned long err = ERR_peek_error()) { // NOLINT(runtime/int) if (ERR_GET_LIB(err) == ERR_LIB_PEM && ERR_GET_REASON(err) == PEM_R_BAD_PASSWORD_READ && !had_passphrase) { return ParseKeyResult(PKParseError::NEED_PASSPHRASE); } return ParseKeyResult(PKParseError::FAILED, err); } if (!pkey) return ParseKeyResult(PKParseError::FAILED); return ParseKeyResult(std::move(pkey)); }; auto bio = BIOPointer::New(buffer); if (!bio) return ParseKeyResult(PKParseError::FAILED); auto passphrase = GetPassphrase(config); if (config.format == PKFormatType::PEM) { auto key = PEM_read_bio_PrivateKey( bio.get(), nullptr, PasswordCallback, config.passphrase.has_value() ? &passphrase : nullptr); return keyOrError(EVPKeyPointer(key), config.passphrase.has_value()); } if (config.format != PKFormatType::DER) { return ParseKeyResult(PKParseError::FAILED); } switch (config.type) { case PKEncodingType::PKCS1: { auto key = d2i_PrivateKey_bio(bio.get(), nullptr); return keyOrError(EVPKeyPointer(key)); } case PKEncodingType::PKCS8: { if (IsEncryptedPrivateKeyInfo(buffer)) { auto key = d2i_PKCS8PrivateKey_bio( bio.get(), nullptr, PasswordCallback, config.passphrase.has_value() ? &passphrase : nullptr); return keyOrError(EVPKeyPointer(key), config.passphrase.has_value()); } PKCS8Pointer p8inf(d2i_PKCS8_PRIV_KEY_INFO_bio(bio.get(), nullptr)); if (!p8inf) { return ParseKeyResult(PKParseError::FAILED, ERR_peek_error()); } return keyOrError(EVPKeyPointer(EVP_PKCS82PKEY(p8inf.get()))); } case PKEncodingType::SEC1: { auto key = d2i_PrivateKey_bio(bio.get(), nullptr); return keyOrError(EVPKeyPointer(key)); } default: { return ParseKeyResult(PKParseError::FAILED, ERR_peek_error()); } }; } EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryLoadPrivateKeyFromStore( const StorePrivateKeyConfig& config) { #if !NCRYPTO_USE_OPENSSL3_PROVIDER return ParseKeyResult(PKParseError::FAILED); #else // The error queue is left populated on failure so the caller can surface a // `code` and an `opensslErrorStack`, matching TryParsePrivateKey(), and is // cleared on success because decoders leave entries behind either way. std::string uri_str(config.uri); std::string properties_str; const char* properties = nullptr; if (config.properties.has_value() && !config.properties->empty()) { properties_str.assign(config.properties->data(), config.properties->size()); properties = properties_str.c_str(); } // config.passphrase outlives this call, so no copy is needed. Buffer<char> passbuf{.data = nullptr, .len = 0}; if (config.passphrase.has_value()) { passbuf.data = const_cast<char*>(config.passphrase->data); passbuf.len = config.passphrase->len; } StorePassphraseData passphrase_data{ .passphrase = passbuf, .has_passphrase = config.passphrase.has_value(), }; // Declared before ctx so that reverse destruction closes the store first; // it holds both for its lifetime. UIMethodPointer ui_method( UI_UTIL_wrap_read_pem_callback(StorePasswordCallback, 0)); if (!ui_method) return ParseKeyResult(PKParseError::FAILED); // Errors from loaders that declined the URI are retained oldest-first, so the // newest entry is the loader that actually handled it. Must run before ctx is // destroyed, since OSSL_STORE_close() can push errors of its own. const auto failed = [&](bool missing_passphrase) { if (missing_passphrase) return ParseKeyResult(PKParseError::NEED_PASSPHRASE); return ParseKeyResult(PKParseError::FAILED, ERR_peek_last_error()); }; const OSSL_PARAM store_params[] = {OSSL_PARAM_END}; StoreCtxPointer ctx(OSSL_STORE_open_ex(uri_str.c_str(), nullptr, properties, ui_method.get(), &passphrase_data, store_params, nullptr, nullptr)); if (!ctx) return failed(passphrase_data.missing_passphrase); if (!OSSL_STORE_expect(ctx.get(), OSSL_STORE_INFO_PKEY)) { return failed(passphrase_data.missing_passphrase); } EVPKeyPointer pkey; bool store_error = false; while (!OSSL_STORE_eof(ctx.get())) { OSSL_STORE_INFO* info = OSSL_STORE_load(ctx.get()); if (info == nullptr) { if (OSSL_STORE_error(ctx.get())) { store_error = true; break; } continue; } if (OSSL_STORE_INFO_get_type(info) == OSSL_STORE_INFO_PKEY) { EVP_PKEY* raw_pkey = OSSL_STORE_INFO_get1_PKEY(info); if (raw_pkey != nullptr) { pkey = EVPKeyPointer(raw_pkey); } else { store_error = true; } } OSSL_STORE_INFO_free(info); if (pkey || store_error) break; } // missing_passphrase is sticky, so a key that loaded anyway wins over it. if (pkey) { ctx.reset(); ERR_clear_error(); return ParseKeyResult(std::move(pkey)); } if (passphrase_data.missing_passphrase || store_error) { return failed(passphrase_data.missing_passphrase); } return ParseKeyResult(PKParseError::NOT_RECOGNIZED); #endif } Result<BIOPointer, bool> EVPKeyPointer::writePrivateKey( const PrivateKeyEncodingConfig& config) const { if (config.format == PKFormatType::JWK) { return Result<BIOPointer, bool>(false); } auto bio = BIOPointer::NewMem(); if (!bio) { return Result<BIOPointer, bool>(false); } auto passphrase = GetPassphrase(config); MarkPopErrorOnReturn mark_pop_error_on_return; bool err; switch (config.type) { case PKEncodingType::PKCS1: { // PKCS1 is only permitted for RSA keys. if (id() != EVP_PKEY_RSA) return Result<BIOPointer, bool>(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 RSA* rsa = EVP_PKEY_get0_RSA(get()); #endif if (rsa == nullptr) return Result<BIOPointer, bool>(false); switch (config.format) { case PKFormatType::PEM: { err = PEM_write_bio_RSAPrivateKey( bio.get(), rsa, config.cipher, reinterpret_cast<unsigned char*>(passphrase.data), passphrase.len, nullptr, nullptr) != 1; break; } case PKFormatType::DER: { // Encoding PKCS1 as DER. This variation does not permit encryption. err = i2d_RSAPrivateKey_bio(bio.get(), rsa) != 1; break; } default: { // Should never get here. return Result<BIOPointer, bool>(false); } } #endif break; } case PKEncodingType::PKCS8: { switch (config.format) { case PKFormatType::PEM: { // Encode PKCS#8 as PEM. err = PEM_write_bio_PKCS8PrivateKey(bio.get(), get(), config.cipher, passphrase.data, passphrase.len, nullptr, nullptr) != 1; break; } case PKFormatType::DER: { err = i2d_PKCS8PrivateKey_bio(bio.get(), get(), config.cipher, passphrase.data, passphrase.len, nullptr, nullptr) != 1; break; } default: { // Should never get here. return Result<BIOPointer, bool>(false); } } break; } case PKEncodingType::SEC1: { // SEC1 is only permitted for EC keys if (id() != EVP_PKEY_EC) return Result<BIOPointer, bool>(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 EC_KEY* ec = EVP_PKEY_get0_EC_KEY(get()); #endif if (ec == nullptr) return Result<BIOPointer, bool>(false); switch (config.format) { case PKFormatType::PEM: { err = PEM_write_bio_ECPrivateKey( bio.get(), ec, config.cipher, reinterpret_cast<unsigned char*>(passphrase.data), passphrase.len, nullptr, nullptr) != 1; break; } case PKFormatType::DER: { // Encoding SEC1 as DER. This variation does not permit encryption. err = i2d_ECPrivateKey_bio(bio.get(), ec) != 1; break; } default: { // Should never get here. return Result<BIOPointer, bool>(false); } } #endif break; } default: { // Not a valid private key encoding return Result<BIOPointer, bool>(false); } } if (err) { // Failed to encode the private key. return Result<BIOPointer, bool>(false, mark_pop_error_on_return.peekError()); } return bio; } Result<BIOPointer, bool> EVPKeyPointer::writePublicKey( const ncrypto::EVPKeyPointer::PublicKeyEncodingConfig& config) const { auto bio = BIOPointer::NewMem(); if (!bio) return Result<BIOPointer, bool>(false); MarkPopErrorOnReturn mark_pop_error_on_return; 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<BIOPointer, bool>(false); if (!WriteEncodedPKey(bio.get(), get(), OSSL_KEYMGMT_SELECT_PUBLIC_KEY, config.format, "pkcs1")) { return Result<BIOPointer, bool>(false, mark_pop_error_on_return.peekError()); } return bio; #else #if OPENSSL_VERSION_MAJOR >= 3 const RSA* rsa = EVP_PKEY_get0_RSA(get()); #else RSA* rsa = EVP_PKEY_get0_RSA(get()); #endif if (rsa == nullptr) return Result<BIOPointer, bool>(false); if (config.format == ncrypto::EVPKeyPointer::PKFormatType::PEM) { // Encode PKCS#1 as PEM. if (PEM_write_bio_RSAPublicKey(bio.get(), rsa) != 1) { return Result<BIOPointer, bool>(false, mark_pop_error_on_return.peekError()); } return bio; } // Encode PKCS#1 as DER. if (i2d_RSAPublicKey_bio(bio.get(), rsa) != 1) { return Result<BIOPointer, bool>(false, 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<BIOPointer, bool>(false, mark_pop_error_on_return.peekError()); } #endif if (config.format == ncrypto::EVPKeyPointer::PKFormatType::PEM) { // Encode SPKI as PEM. #if NCRYPTO_USE_OPENSSL3_PROVIDER // Build the SubjectPublicKeyInfo wrapper explicitly before PEM encoding. // Provider-backed keys can fail the direct PEM_write_bio_PUBKEY() path even // when OpenSSL can materialize the public wrapper with X509_PUBKEY_set(). X509_PUBKEY* pubkey = nullptr; if (X509_PUBKEY_set(&pubkey, get()) != 1) { X509_PUBKEY_free(pubkey); return Result<BIOPointer, bool>(false, mark_pop_error_on_return.peekError()); } X509PubKeyPointer pubkey_ptr(pubkey); if (PEM_write_bio_X509_PUBKEY(bio.get(), pubkey_ptr.get()) != 1) { return Result<BIOPointer, bool>(false, mark_pop_error_on_return.peekError()); } #else // Non-OpenSSL >= 3 builds do not all declare PEM_write_bio_X509_PUBKEY(). if (PEM_write_bio_PUBKEY(bio.get(), get()) != 1) { return Result<BIOPointer, bool>(false, mark_pop_error_on_return.peekError()); } #endif return bio; } // Encode SPKI as DER. if (i2d_PUBKEY_bio(bio.get(), get()) != 1) { return Result<BIOPointer, bool>(false, mark_pop_error_on_return.peekError()); } return bio; } bool EVPKeyPointer::isRsaVariant() const { if (!pkey_) return false; int type = id(); return type == EVP_PKEY_RSA || type == EVP_PKEY_RSA2 || type == EVP_PKEY_RSA_PSS; } bool EVPKeyPointer::isOneShotVariant() const { if (!pkey_) return false; int type = id(); switch (type) { case EVP_PKEY_ED25519: case EVP_PKEY_ED448: #if OPENSSL_WITH_PQC case EVP_PKEY_ML_DSA_44: case EVP_PKEY_ML_DSA_65: case EVP_PKEY_ML_DSA_87: #if OPENSSL_WITH_PQC_SLH_DSA case EVP_PKEY_SLH_DSA_SHA2_128F: case EVP_PKEY_SLH_DSA_SHA2_128S: case EVP_PKEY_SLH_DSA_SHA2_192F: case EVP_PKEY_SLH_DSA_SHA2_192S: case EVP_PKEY_SLH_DSA_SHA2_256F: case EVP_PKEY_SLH_DSA_SHA2_256S: case EVP_PKEY_SLH_DSA_SHAKE_128F: case EVP_PKEY_SLH_DSA_SHAKE_128S: case EVP_PKEY_SLH_DSA_SHAKE_192F: case EVP_PKEY_SLH_DSA_SHAKE_192S: case EVP_PKEY_SLH_DSA_SHAKE_256F: case EVP_PKEY_SLH_DSA_SHAKE_256S: #endif #endif return true; default: return false; } } bool EVPKeyPointer::isSigVariant() const { if (!pkey_) return false; int type = id(); return type == EVP_PKEY_EC || type == EVP_PKEY_DSA; } int EVPKeyPointer::getDefaultSignPadding() const { return id() == EVP_PKEY_RSA_PSS ? RSA_PKCS1_PSS_PADDING : RSA_PKCS1_PADDING; } std::optional<uint32_t> EVPKeyPointer::getBytesOfRS() const { if (!pkey_) return std::nullopt; int bits, id = base_id(); if (id == EVP_PKEY_DSA) { #if NCRYPTO_USE_OPENSSL3_PROVIDER DeleteFnPtr<BIGNUM, BN_free> 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()); bool has_bits = false; // Both r and s are computed mod q, so their width is limited by that of q. if (dsa_key != nullptr) { const BIGNUM* q = DSA_get0_q(dsa_key); if (q != nullptr) { bits = BignumPointer::GetBitCount(q); has_bits = true; } } if (!has_bits) return std::nullopt; #endif } else if (id == EVP_PKEY_EC) { #if NCRYPTO_USE_OPENSSL3_PROVIDER Ec ec(get()); if (!ec) return std::nullopt; const EC_GROUP* group = ec.getGroup(); if (group == nullptr) return std::nullopt; bits = EC_GROUP_order_bits(group); #else const EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(get()); if (ec_key == nullptr) return std::nullopt; const EC_GROUP* group = ECKeyPointer::GetGroup(ec_key); if (group == nullptr) return std::nullopt; bits = EC_GROUP_order_bits(group); #endif } else { return std::nullopt; } if (bits <= 0) return std::nullopt; return (bits + 7) / 8; } 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; if (OPENSSL_VERSION_NUMBER >= 0x1010105fL) { rsa = EVP_PKEY_get0_RSA(get()); } else { rsa = static_cast<OSSL3_CONST RSA*>(EVP_PKEY_get0(get())); } 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 { if (!pkey_) return false; #if OPENSSL_VERSION_MAJOR >= 3 if (EVP_default_properties_is_fips_enabled(nullptr) && EVP_PKEY_DSA == id()) { #else if (FIPS_mode() && EVP_PKEY_DSA == id()) { #endif // Validate DSA2 parameters from FIPS 186-4. #if NCRYPTO_USE_OPENSSL3_PROVIDER DeleteFnPtr<BIGNUM, BN_free> p; DeleteFnPtr<BIGNUM, BN_free> 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()); if (dsa == nullptr) return false; const BIGNUM* p; const BIGNUM* q; DSA_get0_pqg(dsa, &p, &q, nullptr); if (p == nullptr || q == nullptr) return false; 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); } return true; } // ============================================================================ SSLPointer::SSLPointer(SSL* ssl) : ssl_(ssl) {} SSLPointer::SSLPointer(SSLPointer&& other) noexcept : ssl_(other.release()) {} SSLPointer& SSLPointer::operator=(SSLPointer&& other) noexcept { if (this == &other) return *this; this->~SSLPointer(); return *new (this) SSLPointer(std::move(other)); } SSLPointer::~SSLPointer() { reset(); } void SSLPointer::reset(SSL* ssl) { ssl_.reset(ssl); } SSL* SSLPointer::release() { return ssl_.release(); } SSLPointer SSLPointer::New(const SSLCtxPointer& ctx) { if (!ctx) return {}; return SSLPointer(SSL_new(ctx.get())); } void SSLPointer::getCiphers(std::function<void(const char*)> cb) const { if (!ssl_) return; STACK_OF(SSL_CIPHER)* ciphers = SSL_get_ciphers(get()); // TLSv1.3 ciphers aren't listed by EVP. There are only 5, we could just // document them, but since there are only 5, easier to just add them manually // and not have to explain their absence in the API docs. They are lower-cased // because the docs say they will be. static constexpr const char* TLS13_CIPHERS[] = { "tls_aes_256_gcm_sha384", "tls_chacha20_poly1305_sha256", "tls_aes_128_gcm_sha256", "tls_aes_128_ccm_8_sha256", "tls_aes_128_ccm_sha256"}; const int n = sk_SSL_CIPHER_num(ciphers); for (int i = 0; i < n; ++i) { const SSL_CIPHER* cipher = sk_SSL_CIPHER_value(ciphers, i); cb(SSL_CIPHER_get_name(cipher)); } for (unsigned i = 0; i < 5; ++i) { cb(TLS13_CIPHERS[i]); } } bool SSLPointer::setSession(const SSLSessionPointer& session) { if (!session || !ssl_) return false; return SSL_set_session(get(), session.get()) == 1; } bool SSLPointer::setSniContext(const SSLCtxPointer& ctx) const { if (!ctx) return false; auto x509 = ncrypto::X509View::From(ctx); if (!x509) return false; EVP_PKEY* pkey = SSL_CTX_get0_privatekey(ctx.get()); STACK_OF(X509) * chain; int err = SSL_CTX_get0_chain_certs(ctx.get(), &chain); if (err == 1) { // SSL_use_certificate replaces only the certificate matching the key // type. Clear all existing certificates so credentials from the default // context cannot be selected for a different key type. SSL_certs_clear(get()); err = SSL_use_certificate(get(), x509); } if (err == 1) err = SSL_use_PrivateKey(get(), pkey); if (err == 1 && chain != nullptr) err = SSL_set1_chain(get(), chain); return err == 1; } std::optional<uint32_t> SSLPointer::verifyPeerCertificate() const { if (!ssl_) return std::nullopt; if (X509Pointer::PeerFrom(*this)) { return SSL_get_verify_result(get()); } const SSL_CIPHER* curr_cipher = SSL_get_current_cipher(get()); const SSL_SESSION* sess = SSL_get_session(get()); // Allow no-cert for PSK authentication in TLS1.2 and lower. // In TLS1.3 check that session was reused because TLS1.3 PSK // looks like session resumption. if (SSL_CIPHER_get_auth_nid(curr_cipher) == NID_auth_psk || (SSL_SESSION_get_protocol_version(sess) == TLS1_3_VERSION && SSL_session_reused(get()))) { return X509_V_OK; } return std::nullopt; } const char* SSLPointer::getClientHelloAlpn() const { if (ssl_ == nullptr) return {}; #ifndef OPENSSL_IS_BORINGSSL const unsigned char* buf; size_t len; size_t rem; if (!SSL_client_hello_get0_ext( get(), TLSEXT_TYPE_application_layer_protocol_negotiation, &buf, &rem) || rem < 2) { return {}; } len = (buf[0] << 8) | buf[1]; if (len + 2 != rem) return {}; return reinterpret_cast<const char*>(buf + 3); #else // Boringssl doesn't have a public API for this. return {}; #endif } const char* SSLPointer::getClientHelloServerName() const { if (ssl_ == nullptr) return {}; #ifndef OPENSSL_IS_BORINGSSL const unsigned char* buf; size_t len; size_t rem; if (!SSL_client_hello_get0_ext(get(), TLSEXT_TYPE_server_name, &buf, &rem) || rem <= 2) { return {}; } len = (*buf << 8) | *(buf + 1); if (len + 2 != rem) return {}; rem = len; if (rem == 0 || *(buf + 2) != TLSEXT_NAMETYPE_host_name) return {}; rem--; if (rem <= 2) return {}; len = (*(buf + 3) << 8) | *(buf + 4); if (len + 2 > rem) return {}; return reinterpret_cast<const char*>(buf + 5); #else // Boringssl doesn't have a public API for this. return {}; #endif } std::optional<const std::string_view> SSLPointer::GetServerName( const SSL* ssl) { if (ssl == nullptr) return std::nullopt; auto res = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); if (res == nullptr) return std::nullopt; return res; } std::optional<const std::string_view> SSLPointer::getServerName() const { if (!ssl_) return std::nullopt; return GetServerName(get()); } X509View SSLPointer::getCertificate() const { if (!ssl_) return {}; ClearErrorOnReturn clear_error_on_return; return ncrypto::X509View(SSL_get_certificate(get())); } const SSL_CIPHER* SSLPointer::getCipher() const { if (!ssl_) return nullptr; return SSL_get_current_cipher(get()); } bool SSLPointer::isServer() const { return SSL_is_server(get()) != 0; } EVPKeyPointer SSLPointer::getPeerTempKey() const { if (!ssl_) return {}; EVP_PKEY* raw_key = nullptr; #ifndef OPENSSL_IS_BORINGSSL if (!SSL_get_peer_tmp_key(get(), &raw_key)) return {}; #else if (!SSL_get_server_tmp_key(get(), &raw_key)) return {}; #endif return EVPKeyPointer(raw_key); } std::optional<std::string_view> SSLPointer::getNegotiatedGroup() const { #if OPENSSL_VERSION_PREREQ(3, 5) if (!ssl_) return std::nullopt; const char* group = SSL_get0_group_name(get()); if (group == nullptr) return std::nullopt; return group; #else return std::nullopt; #endif } std::optional<std::string_view> SSLPointer::getCipherName() const { auto cipher = getCipher(); if (cipher == nullptr) return std::nullopt; return SSL_CIPHER_get_name(cipher); } std::optional<std::string_view> SSLPointer::getCipherStandardName() const { auto cipher = getCipher(); if (cipher == nullptr) return std::nullopt; return SSL_CIPHER_standard_name(cipher); } std::optional<std::string_view> SSLPointer::getCipherVersion() const { auto cipher = getCipher(); if (cipher == nullptr) return std::nullopt; return SSL_CIPHER_get_version(cipher); } std::optional<int> SSLPointer::getSecurityLevel() { #ifndef OPENSSL_IS_BORINGSSL auto ctx = SSLCtxPointer::New(); if (!ctx) return std::nullopt; auto ssl = SSLPointer::New(ctx); if (!ssl) return std::nullopt; return SSL_get_security_level(ssl); #else // OPENSSL_TLS_SECURITY_LEVEL is not defined in BoringSSL // so assume it is the default OPENSSL_TLS_SECURITY_LEVEL value. return 1; #endif // OPENSSL_IS_BORINGSSL } SSLCtxPointer::SSLCtxPointer(SSL_CTX* ctx) : ctx_(ctx) {} SSLCtxPointer::SSLCtxPointer(SSLCtxPointer&& other) noexcept : ctx_(other.release()) {} SSLCtxPointer& SSLCtxPointer::operator=(SSLCtxPointer&& other) noexcept { if (this == &other) return *this; this->~SSLCtxPointer(); return *new (this) SSLCtxPointer(std::move(other)); } SSLCtxPointer::~SSLCtxPointer() { reset(); } void SSLCtxPointer::reset(SSL_CTX* ctx) { ctx_.reset(ctx); } void SSLCtxPointer::reset(const SSL_METHOD* method) { ctx_.reset(SSL_CTX_new(method)); } SSL_CTX* SSLCtxPointer::release() { return ctx_.release(); } SSLCtxPointer SSLCtxPointer::NewServer() { return SSLCtxPointer(SSL_CTX_new(TLS_server_method())); } SSLCtxPointer SSLCtxPointer::NewClient() { return SSLCtxPointer(SSL_CTX_new(TLS_client_method())); } SSLCtxPointer SSLCtxPointer::New(const SSL_METHOD* method) { return SSLCtxPointer(SSL_CTX_new(method)); } bool SSLCtxPointer::setGroups(const char* groups) { return SSL_CTX_set1_groups_list(get(), groups) == 1; } bool SSLCtxPointer::setCipherSuites(const char* ciphers) { #ifndef OPENSSL_IS_BORINGSSL if (!ctx_) return false; return SSL_CTX_set_ciphersuites(ctx_.get(), ciphers); #else // BoringSSL does not allow API config of TLS 1.3 cipher suites. // We treat this as a non-op. return true; #endif } // ============================================================================ const Cipher Cipher::FromName(const char* name) { return Cipher(EVP_get_cipherbyname(name)); } const Cipher Cipher::FromNid(int nid) { return Cipher(EVP_get_cipherbynid(nid)); } const Cipher Cipher::FromCtx(const CipherCtxPointer& ctx) { return Cipher(GetCipherCtxCipher(ctx.get())); } const Cipher Cipher::EMPTY = Cipher(); const Cipher Cipher::AES_128_CBC = Cipher::FromNid(NID_aes_128_cbc); const Cipher Cipher::AES_192_CBC = Cipher::FromNid(NID_aes_192_cbc); const Cipher Cipher::AES_256_CBC = Cipher::FromNid(NID_aes_256_cbc); const Cipher Cipher::AES_128_CTR = Cipher::FromNid(NID_aes_128_ctr); const Cipher Cipher::AES_192_CTR = Cipher::FromNid(NID_aes_192_ctr); const Cipher Cipher::AES_256_CTR = Cipher::FromNid(NID_aes_256_ctr); const Cipher Cipher::AES_128_GCM = Cipher::FromNid(NID_aes_128_gcm); const Cipher Cipher::AES_192_GCM = Cipher::FromNid(NID_aes_192_gcm); const Cipher Cipher::AES_256_GCM = Cipher::FromNid(NID_aes_256_gcm); const Cipher Cipher::AES_128_KW = Cipher::FromNid(NID_id_aes128_wrap); const Cipher Cipher::AES_192_KW = Cipher::FromNid(NID_id_aes192_wrap); const Cipher Cipher::AES_256_KW = Cipher::FromNid(NID_id_aes256_wrap); #ifndef OPENSSL_IS_BORINGSSL const Cipher Cipher::AES_128_OCB = Cipher::FromNid(NID_aes_128_ocb); const Cipher Cipher::AES_192_OCB = Cipher::FromNid(NID_aes_192_ocb); const Cipher Cipher::AES_256_OCB = Cipher::FromNid(NID_aes_256_ocb); #endif const Cipher Cipher::CHACHA20_POLY1305 = Cipher::FromNid(NID_chacha20_poly1305); bool Cipher::isGcmMode() const { if (!cipher_) return false; return getMode() == EVP_CIPH_GCM_MODE; } bool Cipher::isWrapMode() const { if (!cipher_) return false; return getMode() == EVP_CIPH_WRAP_MODE; } bool Cipher::isCtrMode() const { if (!cipher_) return false; return getMode() == EVP_CIPH_CTR_MODE; } bool Cipher::isCcmMode() const { if (!cipher_) return false; return getMode() == EVP_CIPH_CCM_MODE; } bool Cipher::isOcbMode() const { if (!cipher_) return false; return getMode() == EVP_CIPH_OCB_MODE; } bool Cipher::isStreamMode() const { if (!cipher_) return false; return getMode() == EVP_CIPH_STREAM_CIPHER; } bool Cipher::isChaCha20Poly1305() const { if (!cipher_) return false; return getNid() == NID_chacha20_poly1305; } int Cipher::getMode() const { if (!cipher_) return 0; return EVP_CIPHER_mode(cipher_); } int Cipher::getIvLength() const { if (!cipher_) return 0; return EVP_CIPHER_iv_length(cipher_); } int Cipher::getKeyLength() const { if (!cipher_) return 0; return EVP_CIPHER_key_length(cipher_); } int Cipher::getBlockSize() const { if (!cipher_) return 0; return EVP_CIPHER_block_size(cipher_); } int Cipher::getNid() const { if (!cipher_) return 0; return EVP_CIPHER_nid(cipher_); } std::string_view Cipher::getModeLabel() const { if (!cipher_) return {}; switch (getMode()) { case EVP_CIPH_CCM_MODE: return "ccm"; case EVP_CIPH_CFB_MODE: return "cfb"; case EVP_CIPH_CBC_MODE: return "cbc"; case EVP_CIPH_CTR_MODE: return "ctr"; case EVP_CIPH_ECB_MODE: return "ecb"; case EVP_CIPH_GCM_MODE: return "gcm"; case EVP_CIPH_OCB_MODE: return "ocb"; case EVP_CIPH_OFB_MODE: return "ofb"; case EVP_CIPH_WRAP_MODE: return "wrap"; case EVP_CIPH_XTS_MODE: return "xts"; case EVP_CIPH_STREAM_CIPHER: return "stream"; } return "{unknown}"; } const char* Cipher::getName() const { if (!cipher_) return {}; // OBJ_nid2sn(EVP_CIPHER_nid(cipher)) is used here instead of // EVP_CIPHER_name(cipher) for compatibility with BoringSSL. return OBJ_nid2sn(getNid()); } bool Cipher::isSupportedAuthenticatedMode() const { switch (getMode()) { case EVP_CIPH_CCM_MODE: case EVP_CIPH_GCM_MODE: #ifndef OPENSSL_NO_OCB case EVP_CIPH_OCB_MODE: #endif return true; case EVP_CIPH_STREAM_CIPHER: return getNid() == NID_chacha20_poly1305; default: return false; } } int Cipher::bytesToKey(const Digest& digest, const Buffer<const unsigned char>& input, unsigned char* key, unsigned char* iv) const { return EVP_BytesToKey( *this, Digest::MD5, nullptr, input.data, input.len, 1, key, iv); } // ============================================================================ CipherCtxPointer CipherCtxPointer::New() { auto ret = CipherCtxPointer(EVP_CIPHER_CTX_new()); if (!ret) return {}; EVP_CIPHER_CTX_reset(ret.get()); return ret; } CipherCtxPointer::CipherCtxPointer(EVP_CIPHER_CTX* ctx) : ctx_(ctx) {} CipherCtxPointer::CipherCtxPointer(CipherCtxPointer&& other) noexcept : ctx_(other.release()) {} CipherCtxPointer& CipherCtxPointer::operator=( CipherCtxPointer&& other) noexcept { if (this == &other) return *this; this->~CipherCtxPointer(); return *new (this) CipherCtxPointer(std::move(other)); } CipherCtxPointer::~CipherCtxPointer() { reset(); } void CipherCtxPointer::reset(EVP_CIPHER_CTX* ctx) { ctx_.reset(ctx); } EVP_CIPHER_CTX* CipherCtxPointer::release() { return ctx_.release(); } void CipherCtxPointer::setAllowWrap() { if (!ctx_) return; EVP_CIPHER_CTX_set_flags(ctx_.get(), EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); } bool CipherCtxPointer::setKeyLength(size_t length) { if (!ctx_) return false; return EVP_CIPHER_CTX_set_key_length(ctx_.get(), length); } bool CipherCtxPointer::setIvLength(size_t length) { if (!ctx_) return false; return EVP_CIPHER_CTX_ctrl( ctx_.get(), EVP_CTRL_AEAD_SET_IVLEN, length, nullptr); } bool CipherCtxPointer::setAeadTag(const Buffer<const char>& tag) { if (!ctx_) return false; return EVP_CIPHER_CTX_ctrl( ctx_.get(), EVP_CTRL_AEAD_SET_TAG, tag.len, const_cast<char*>(tag.data)); } bool CipherCtxPointer::setAeadTagLength(size_t length) { if (!ctx_) return false; return EVP_CIPHER_CTX_ctrl( ctx_.get(), EVP_CTRL_AEAD_SET_TAG, length, nullptr); } bool CipherCtxPointer::setPadding(bool padding) { if (!ctx_) return false; return EVP_CIPHER_CTX_set_padding(ctx_.get(), padding); } int CipherCtxPointer::getBlockSize() const { if (!ctx_) return 0; return EVP_CIPHER_CTX_block_size(ctx_.get()); } int CipherCtxPointer::getMode() const { if (!ctx_) return 0; return EVP_CIPHER_CTX_mode(ctx_.get()); } bool CipherCtxPointer::isGcmMode() const { if (!ctx_) return false; return getMode() == EVP_CIPH_GCM_MODE; } bool CipherCtxPointer::isOcbMode() const { if (!ctx_) return false; return getMode() == EVP_CIPH_OCB_MODE; } bool CipherCtxPointer::isCcmMode() const { if (!ctx_) return false; return getMode() == EVP_CIPH_CCM_MODE; } bool CipherCtxPointer::isWrapMode() const { if (!ctx_) return false; return getMode() == EVP_CIPH_WRAP_MODE; } bool CipherCtxPointer::isChaCha20Poly1305() const { if (!ctx_) return false; return getNid() == NID_chacha20_poly1305; } int CipherCtxPointer::getNid() const { if (!ctx_) return 0; return EVP_CIPHER_CTX_nid(ctx_.get()); } bool CipherCtxPointer::init(const Cipher& cipher, bool encrypt, const unsigned char* key, const unsigned char* iv) { if (!ctx_) return false; return EVP_CipherInit_ex( ctx_.get(), cipher, nullptr, key, iv, encrypt ? 1 : 0) == 1; } bool CipherCtxPointer::update(const Buffer<const unsigned char>& in, unsigned char* out, int* out_len, bool finalize) { if (!ctx_) return false; if (!finalize) { return EVP_CipherUpdate(ctx_.get(), out, out_len, in.data, in.len) == 1; } return EVP_CipherFinal_ex(ctx_.get(), out, out_len) == 1; } bool CipherCtxPointer::getAeadTag(size_t len, unsigned char* out) { if (!ctx_) return false; return EVP_CIPHER_CTX_ctrl(ctx_.get(), EVP_CTRL_AEAD_GET_TAG, len, out); } // ============================================================================ ECDSASigPointer::ECDSASigPointer() : sig_(nullptr) {} ECDSASigPointer::ECDSASigPointer(ECDSA_SIG* sig) : sig_(sig) { if (sig_) { ECDSA_SIG_get0(sig_.get(), &pr_, &ps_); } } ECDSASigPointer::ECDSASigPointer(ECDSASigPointer&& other) noexcept : sig_(other.release()) { if (sig_) { ECDSA_SIG_get0(sig_.get(), &pr_, &ps_); } } ECDSASigPointer& ECDSASigPointer::operator=(ECDSASigPointer&& other) noexcept { sig_.reset(other.release()); if (sig_) { ECDSA_SIG_get0(sig_.get(), &pr_, &ps_); } return *this; } ECDSASigPointer::~ECDSASigPointer() { reset(); } void ECDSASigPointer::reset(ECDSA_SIG* sig) { sig_.reset(); pr_ = nullptr; ps_ = nullptr; } ECDSA_SIG* ECDSASigPointer::release() { pr_ = nullptr; ps_ = nullptr; return sig_.release(); } ECDSASigPointer ECDSASigPointer::New() { return ECDSASigPointer(ECDSA_SIG_new()); } ECDSASigPointer ECDSASigPointer::Parse(const Buffer<const unsigned char>& sig) { const unsigned char* ptr = sig.data; return ECDSASigPointer(d2i_ECDSA_SIG(nullptr, &ptr, sig.len)); } bool ECDSASigPointer::setParams(BignumPointer&& r, BignumPointer&& s) { if (!sig_) return false; return ECDSA_SIG_set0(sig_.get(), r.release(), s.release()); } Buffer<unsigned char> ECDSASigPointer::encode() const { if (!sig_) return { .data = nullptr, .len = 0, }; Buffer<unsigned char> buf; buf.len = i2d_ECDSA_SIG(sig_.get(), &buf.data); return buf; } // ============================================================================ ECGroupPointer::ECGroupPointer() : group_(nullptr) {} ECGroupPointer::ECGroupPointer(EC_GROUP* group) : group_(group) {} ECGroupPointer::ECGroupPointer(ECGroupPointer&& other) noexcept : group_(other.release()) {} ECGroupPointer& ECGroupPointer::operator=(ECGroupPointer&& other) noexcept { group_.reset(other.release()); return *this; } ECGroupPointer::~ECGroupPointer() { reset(); } void ECGroupPointer::reset(EC_GROUP* group) { group_.reset(); } EC_GROUP* ECGroupPointer::release() { return group_.release(); } ECGroupPointer ECGroupPointer::NewByCurveName(int nid) { return ECGroupPointer(EC_GROUP_new_by_curve_name(nid)); } // ============================================================================ ECPointPointer::ECPointPointer() : point_(nullptr) {} ECPointPointer::ECPointPointer(EC_POINT* point) : point_(point) {} ECPointPointer::ECPointPointer(ECPointPointer&& other) noexcept : point_(other.release()) {} ECPointPointer& ECPointPointer::operator=(ECPointPointer&& other) noexcept { point_.reset(other.release()); return *this; } ECPointPointer::~ECPointPointer() { reset(); } void ECPointPointer::reset(EC_POINT* point) { point_.reset(point); } EC_POINT* ECPointPointer::release() { return point_.release(); } ECPointPointer ECPointPointer::New(const EC_GROUP* group) { return ECPointPointer(EC_POINT_new(group)); } bool ECPointPointer::setFromBuffer(const Buffer<const unsigned char>& buffer, const EC_GROUP* group) { if (!point_) return false; return EC_POINT_oct2point( group, point_.get(), buffer.data, buffer.len, nullptr); } bool ECPointPointer::mul(const EC_GROUP* group, const BIGNUM* priv_key) { if (!point_) return false; return EC_POINT_mul(group, point_.get(), priv_key, nullptr, nullptr, nullptr); } // ============================================================================ #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 : key_(other.release()) {} ECKeyPointer& ECKeyPointer::operator=(ECKeyPointer&& other) noexcept { key_.reset(other.release()); return *this; } ECKeyPointer::~ECKeyPointer() { reset(); } void ECKeyPointer::reset(EC_KEY* key) { key_.reset(key); } EC_KEY* ECKeyPointer::release() { return key_.release(); } ECKeyPointer ECKeyPointer::clone() const { if (!key_) return {}; return ECKeyPointer(EC_KEY_dup(key_.get())); } bool ECKeyPointer::generate() { if (!key_) return false; return EC_KEY_generate_key(key_.get()); } bool ECKeyPointer::setPublicKey(const ECPointPointer& pub) { if (!key_) return false; return EC_KEY_set_public_key(key_.get(), pub.get()) == 1; } bool ECKeyPointer::setPublicKeyRaw(const BignumPointer& x, const BignumPointer& y) { if (!key_) return false; const EC_GROUP* group = EC_KEY_get0_group(key_.get()); if (group == nullptr) return false; // For curves with cofactor h=1, use EC_POINT_oct2point + // EC_KEY_set_public_key instead of EC_KEY_set_public_key_affine_coordinates. // The latter internally calls EC_KEY_check_key() which performs a scalar // multiplication (n*Q) for order validation — redundant when h=1 since every // on-curve point already has order n. EC_POINT_oct2point validates the point // is on the curve, which is sufficient. For curves with h!=1, fall back to // the full check. auto cofactor = BignumPointer::New(); if (!cofactor || !EC_GROUP_get_cofactor(group, cofactor.get(), nullptr) || !cofactor.isOne()) { return EC_KEY_set_public_key_affine_coordinates( key_.get(), x.get(), y.get()) == 1; } // Field element byte length: ceil(degree_bits / 8). size_t field_len = (EC_GROUP_get_degree(group) + 7) / 8; // Build an uncompressed point: 0x04 || x || y, each padded to field_len. size_t uncompressed_len = 1 + 2 * field_len; auto buf = DataPointer::Alloc(uncompressed_len); if (!buf) return false; unsigned char* ptr = static_cast<unsigned char*>(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); if (!point) return false; if (!point.setFromBuffer({ptr, uncompressed_len}, group)) return false; return EC_KEY_set_public_key(key_.get(), point.get()) == 1; } bool ECKeyPointer::setPrivateKey(const BignumPointer& priv) { if (!key_) return false; return EC_KEY_set_private_key(key_.get(), priv.get()) == 1; } const BIGNUM* ECKeyPointer::getPrivateKey() const { if (!key_) return nullptr; return GetPrivateKey(key_.get()); } const BIGNUM* ECKeyPointer::GetPrivateKey(const EC_KEY* key) { return EC_KEY_get0_private_key(key); } const EC_POINT* ECKeyPointer::getPublicKey() const { if (!key_) return nullptr; return GetPublicKey(key_.get()); } const EC_POINT* ECKeyPointer::GetPublicKey(const EC_KEY* key) { return EC_KEY_get0_public_key(key); } const EC_GROUP* ECKeyPointer::getGroup() const { if (!key_) return nullptr; return GetGroup(key_.get()); } const EC_GROUP* ECKeyPointer::GetGroup(const EC_KEY* key) { return EC_KEY_get0_group(key); } int ECKeyPointer::GetGroupName(const EC_KEY* key) { const EC_GROUP* group = GetGroup(key); return group ? EC_GROUP_get_curve_name(group) : 0; } bool ECKeyPointer::Check(const EC_KEY* key) { return EC_KEY_check_key(key) == 1; } bool ECKeyPointer::checkKey() const { if (!key_) return false; 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)); } ECKeyPointer ECKeyPointer::New(const EC_GROUP* group) { auto ptr = ECKeyPointer(EC_KEY_new()); if (!ptr) return {}; 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<unsigned char*>(public_key.get()), public_key.size(), &public_key_len) != 1) { reset(); return; } auto point = ECPointPointer::New(group_.get()); if (!point || !point.setFromBuffer( {static_cast<unsigned char*>(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<BIGNUM, BN_clear_free> 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<unsigned char*>(public_key.get()), public_key.size(), &public_key_len) != 1) { return false; } auto point = ECPointPointer::New(group_.get()); if (!point || !point.setFromBuffer( {static_cast<unsigned char*>(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<unsigned char*>(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<unsigned char*>(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 // ============================================================================ EVPKeyCtxPointer::EVPKeyCtxPointer() : ctx_(nullptr) {} EVPKeyCtxPointer::EVPKeyCtxPointer(EVP_PKEY_CTX* ctx) : ctx_(ctx) {} EVPKeyCtxPointer::EVPKeyCtxPointer(EVPKeyCtxPointer&& other) noexcept : ctx_(other.release()) {} EVPKeyCtxPointer& EVPKeyCtxPointer::operator=( EVPKeyCtxPointer&& other) noexcept { ctx_.reset(other.release()); return *this; } EVPKeyCtxPointer::~EVPKeyCtxPointer() { reset(); } void EVPKeyCtxPointer::reset(EVP_PKEY_CTX* ctx) { ctx_.reset(ctx); } EVP_PKEY_CTX* EVPKeyCtxPointer::release() { return ctx_.release(); } EVPKeyCtxPointer EVPKeyCtxPointer::New(const EVPKeyPointer& key) { if (!key) return {}; return EVPKeyCtxPointer(EVP_PKEY_CTX_new(key.get(), nullptr)); } EVPKeyCtxPointer EVPKeyCtxPointer::NewFromID(int id) { #ifdef OPENSSL_IS_BORINGSSL // DSA keys are not supported with BoringSSL if (id == EVP_PKEY_DSA) return {}; #endif return EVPKeyCtxPointer(EVP_PKEY_CTX_new_id(id, nullptr)); } bool EVPKeyCtxPointer::initForDerive(const EVPKeyPointer& peer) { if (!ctx_) return false; if (EVP_PKEY_derive_init(ctx_.get()) != 1) return false; return EVP_PKEY_derive_set_peer(ctx_.get(), peer.get()) == 1; } bool EVPKeyCtxPointer::initForKeygen() { if (!ctx_) return false; return EVP_PKEY_keygen_init(ctx_.get()) == 1; } bool EVPKeyCtxPointer::initForParamgen() { if (!ctx_) return false; return EVP_PKEY_paramgen_init(ctx_.get()) == 1; } int EVPKeyCtxPointer::initForVerify() { if (!ctx_) return 0; return EVP_PKEY_verify_init(ctx_.get()); } int EVPKeyCtxPointer::initForSign() { if (!ctx_) return 0; return EVP_PKEY_sign_init(ctx_.get()); } bool EVPKeyCtxPointer::setDhParameters(int prime_size, uint32_t generator) { #ifndef OPENSSL_IS_BORINGSSL if (!ctx_) return false; return EVP_PKEY_CTX_set_dh_paramgen_prime_len(ctx_.get(), prime_size) == 1 && EVP_PKEY_CTX_set_dh_paramgen_generator(ctx_.get(), generator) == 1; #else // TODO(jasnell): Boringssl appears not to support this operation. // Is there an alternative approach that Boringssl does support? return false; #endif } bool EVPKeyCtxPointer::setDsaParameters(uint32_t bits, std::optional<int> q_bits) { if (!ctx_) return false; if (EVP_PKEY_CTX_set_dsa_paramgen_bits(ctx_.get(), bits) != 1) { return false; } if (q_bits.has_value() && EVP_PKEY_CTX_set_dsa_paramgen_q_bits(ctx_.get(), q_bits.value()) != 1) { return false; } return true; } 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<char*>(group_name), 0), OSSL_PARAM_construct_utf8_string( OSSL_PKEY_PARAM_EC_ENCODING, const_cast<char*>(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) { if (!md || !ctx_) return false; const EVP_MD* md_ptr = md; return EVP_PKEY_CTX_set_rsa_oaep_md(ctx_.get(), md_ptr) > 0; } bool EVPKeyCtxPointer::setRsaMgf1Md(const Digest& md) { if (!md || !ctx_) return false; const EVP_MD* md_ptr = md; return EVP_PKEY_CTX_set_rsa_mgf1_md(ctx_.get(), md_ptr) > 0; } bool EVPKeyCtxPointer::setRsaPadding(int padding) { return setRsaPadding(ctx_.get(), padding, std::nullopt); } bool EVPKeyCtxPointer::setRsaPadding(EVP_PKEY_CTX* ctx, int padding, std::optional<int> salt_len) { if (ctx == nullptr) return false; if (EVP_PKEY_CTX_set_rsa_padding(ctx, padding) <= 0) { return false; } if (padding == RSA_PKCS1_PSS_PADDING && salt_len.has_value()) { return EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, salt_len.value()) > 0; } return true; } bool EVPKeyCtxPointer::setRsaKeygenBits(int bits) { if (!ctx_) return false; return EVP_PKEY_CTX_set_rsa_keygen_bits(ctx_.get(), bits) == 1; } 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) { if (!md || !ctx_) return false; // OpenSSL < 3 accepts a void* for the md parameter. const EVP_MD* md_ptr = md; return EVP_PKEY_CTX_set_rsa_pss_keygen_md(ctx_.get(), md_ptr) > 0; } bool EVPKeyCtxPointer::setRsaPssKeygenMgf1Md(const Digest& md) { if (!md || !ctx_) return false; const EVP_MD* md_ptr = md; return EVP_PKEY_CTX_set_rsa_pss_keygen_mgf1_md(ctx_.get(), md_ptr) > 0; } bool EVPKeyCtxPointer::setRsaPssSaltlen(int salt_len) { if (!ctx_) return false; return EVP_PKEY_CTX_set_rsa_pss_keygen_saltlen(ctx_.get(), salt_len) > 0; } bool EVPKeyCtxPointer::setRsaImplicitRejection() { #ifndef OPENSSL_IS_BORINGSSL if (!ctx_) return false; return EVP_PKEY_CTX_ctrl_str( ctx_.get(), "rsa_pkcs1_implicit_rejection", "1") > 0; // From the doc -2 means that the option is not supported. // The default for the option is enabled and if it has been // specifically disabled we want to respect that so we will // not throw an error if the option is supported regardless // of how it is set. The call to set the value // will not affect what is used since a different context is // used in the call if the option is supported #else // TODO(jasnell): Boringssl appears not to support this operation. // Is there an alternative approach that Boringssl does support? return true; #endif } bool EVPKeyCtxPointer::setRsaOaepLabel(DataPointer&& data) { if (!ctx_) return false; if (EVP_PKEY_CTX_set0_rsa_oaep_label(ctx_.get(), static_cast<unsigned char*>(data.get()), data.size()) > 0) { // The ctx_ takes ownership of data on success. data.release(); return true; } return false; } bool EVPKeyCtxPointer::setSignatureMd(const EVPMDCtxPointer& md) { if (!ctx_) return false; return EVP_PKEY_CTX_set_signature_md(ctx_.get(), GetDigestCtxMd(md.get())) == 1; } bool EVPKeyCtxPointer::initForEncrypt() { if (!ctx_) return false; return EVP_PKEY_encrypt_init(ctx_.get()) == 1; } bool EVPKeyCtxPointer::initForDecrypt() { if (!ctx_) return false; return EVP_PKEY_decrypt_init(ctx_.get()) == 1; } DataPointer EVPKeyCtxPointer::derive() const { if (!ctx_) return {}; size_t len = 0; if (EVP_PKEY_derive(ctx_.get(), nullptr, &len) != 1) return {}; auto data = DataPointer::Alloc(len); if (!data) return {}; if (EVP_PKEY_derive( ctx_.get(), static_cast<unsigned char*>(data.get()), &len) != 1) { return {}; } return data; } EVPKeyPointer EVPKeyCtxPointer::paramgen() const { if (!ctx_) return {}; EVP_PKEY* key = nullptr; if (EVP_PKEY_paramgen(ctx_.get(), &key) != 1) return {}; return EVPKeyPointer(key); } bool EVPKeyCtxPointer::publicCheck() const { if (!ctx_) return false; #ifndef OPENSSL_IS_BORINGSSL #if OPENSSL_VERSION_MAJOR >= 3 return EVP_PKEY_public_check_quick(ctx_.get()) == 1; #else return EVP_PKEY_public_check(ctx_.get()) == 1; #endif #else // OPENSSL_IS_BORINGSSL // Boringssl appears not to support this operation. // TODO(jasnell): Is there an alternative approach that Boringssl does // support? return true; #endif } bool EVPKeyCtxPointer::privateCheck() const { if (!ctx_) return false; #ifndef OPENSSL_IS_BORINGSSL return EVP_PKEY_check(ctx_.get()) == 1; #else // Boringssl appears not to support this operation. // TODO(jasnell): Is there an alternative approach that Boringssl does // support? return true; #endif } bool EVPKeyCtxPointer::verify(const Buffer<const unsigned char>& sig, const Buffer<const unsigned char>& data) { if (!ctx_) return false; return EVP_PKEY_verify(ctx_.get(), sig.data, sig.len, data.data, data.len) == 1; } DataPointer EVPKeyCtxPointer::sign(const Buffer<const unsigned char>& data) { if (!ctx_) return {}; size_t len = 0; if (EVP_PKEY_sign(ctx_.get(), nullptr, &len, data.data, data.len) != 1) { return {}; } auto buf = DataPointer::Alloc(len); if (!buf) return {}; if (EVP_PKEY_sign(ctx_.get(), static_cast<unsigned char*>(buf.get()), &len, data.data, data.len) != 1) { return {}; } return buf.resize(len); } bool EVPKeyCtxPointer::signInto(const Buffer<const unsigned char>& data, Buffer<unsigned char>* sig) { if (!ctx_) return false; size_t len = sig->len; if (EVP_PKEY_sign(ctx_.get(), sig->data, &len, data.data, data.len) != 1) { return false; } sig->len = len; return true; } // ============================================================================ namespace { using EVP_PKEY_cipher_init_t = int(EVP_PKEY_CTX* ctx); using EVP_PKEY_cipher_t = int(EVP_PKEY_CTX* ctx, unsigned char* out, size_t* outlen, const unsigned char* in, size_t inlen); template <EVP_PKEY_cipher_init_t init, EVP_PKEY_cipher_t cipher> DataPointer RSA_Cipher(const EVPKeyPointer& key, const Rsa::CipherParams& params, const Buffer<const void> in) { if (!key) return {}; EVPKeyCtxPointer ctx = key.newCtx(); const Digest& mgf1_digest = params.mgf1_digest != nullptr ? params.mgf1_digest : params.digest; if (!ctx || init(ctx.get()) <= 0 || !ctx.setRsaPadding(params.padding) || (params.digest != nullptr && (!ctx.setRsaOaepMd(params.digest) || !ctx.setRsaMgf1Md(mgf1_digest)))) { return {}; } if (params.label.len != 0 && params.label.data != nullptr && !ctx.setRsaOaepLabel(DataPointer::Copy(params.label))) { return {}; } size_t out_len = 0; if (cipher(ctx.get(), nullptr, &out_len, reinterpret_cast<const unsigned char*>(in.data), in.len) <= 0) { return {}; } auto buf = DataPointer::Alloc(out_len); if (!buf) return {}; if (cipher(ctx.get(), static_cast<unsigned char*>(buf.get()), &out_len, static_cast<const unsigned char*>(in.data), in.len) <= 0) { return {}; } return buf.resize(out_len); } template <EVP_PKEY_cipher_init_t init, EVP_PKEY_cipher_t cipher> DataPointer CipherImpl(const EVPKeyPointer& key, const Rsa::CipherParams& params, const Buffer<const void> in) { if (!key) return {}; EVPKeyCtxPointer ctx = key.newCtx(); if (!ctx || init(ctx.get()) <= 0 || !ctx.setRsaPadding(params.padding) || (params.digest != nullptr && !ctx.setRsaOaepMd(params.digest)) || (params.mgf1_digest != nullptr && !ctx.setRsaMgf1Md(params.mgf1_digest))) { return {}; } if (params.label.len != 0 && params.label.data != nullptr && !ctx.setRsaOaepLabel(DataPointer::Copy(params.label))) { return {}; } size_t out_len = 0; if (cipher(ctx.get(), nullptr, &out_len, static_cast<const unsigned char*>(in.data), in.len) <= 0) { return {}; } auto buf = DataPointer::Alloc(out_len); if (!buf) return {}; if (cipher(ctx.get(), static_cast<unsigned char*>(buf.get()), &out_len, static_cast<const unsigned char*>(in.data), in.len) <= 0) { return {}; } return buf.resize(out_len); } } // namespace #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<ASN1_OBJECT, ASN1_OBJECT_free> 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<unsigned char*>(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<const unsigned char*>(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<int64_t>(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::PssParams> 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; Rsa::PssParams ret{ .digest = OBJ_nid2ln(NID_sha1), .mgf1_digest = OBJ_nid2ln(NID_sha1), .salt_length = 20, }; if (params->hashAlgorithm != nullptr) { const ASN1_OBJECT* hash_obj; X509_ALGOR_get0(&hash_obj, nullptr, nullptr, params->hashAlgorithm); ret.digest = OBJ_nid2ln(OBJ_obj2nid(hash_obj)); } if (params->maskGenAlgorithm != nullptr) { const ASN1_OBJECT* mgf_obj; X509_ALGOR_get0(&mgf_obj, nullptr, nullptr, params->maskGenAlgorithm); int mgf_nid = OBJ_obj2nid(mgf_obj); if (mgf_nid == NID_mgf1) { const ASN1_OBJECT* mgf1_hash_obj; X509_ALGOR_get0(&mgf1_hash_obj, nullptr, nullptr, params->maskHash); ret.mgf1_digest = OBJ_nid2ln(OBJ_obj2nid(mgf1_hash_obj)); } } if (params->saltLength != nullptr) { if (ASN1_INTEGER_get_int64(&ret.salt_length, params->saltLength) != 1) { return std::nullopt; } } 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*>(rsa_), n.get(), e.get(), nullptr) == 1) { n.release(); e.release(); return true; } return false; #endif } bool Rsa::setPrivateKey(BignumPointer&& d, BignumPointer&& q, BignumPointer&& p, 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*>(rsa_), nullptr, nullptr, d.get())) { return false; } d.release(); if (!RSA_set0_factors(const_cast<RSA*>(rsa_), p.get(), q.get())) { return false; } p.release(); q.release(); if (!RSA_set0_crt_params( const_cast<RSA*>(rsa_), dp.get(), dq.get(), qi.get())) { return false; } dp.release(); dq.release(); qi.release(); return true; #endif } DataPointer Rsa::encrypt(const EVPKeyPointer& key, const Rsa::CipherParams& params, const Buffer<const void> in) { if (!key) return {}; return RSA_Cipher<EVP_PKEY_encrypt_init, EVP_PKEY_encrypt>(key, params, in); } DataPointer Rsa::decrypt(const EVPKeyPointer& key, const Rsa::CipherParams& params, const Buffer<const void> in) { if (!key) return {}; return RSA_Cipher<EVP_PKEY_decrypt_init, EVP_PKEY_decrypt>(key, params, in); } DataPointer Cipher::encrypt(const EVPKeyPointer& key, const CipherParams& params, const Buffer<const void> in) { // public operation return CipherImpl<EVP_PKEY_encrypt_init, EVP_PKEY_encrypt>(key, params, in); } DataPointer Cipher::decrypt(const EVPKeyPointer& key, const CipherParams& params, const Buffer<const void> in) { // private operation return CipherImpl<EVP_PKEY_decrypt_init, EVP_PKEY_decrypt>(key, params, in); } DataPointer Cipher::sign(const EVPKeyPointer& key, const CipherParams& params, const Buffer<const void> in) { // private operation return CipherImpl<EVP_PKEY_sign_init, EVP_PKEY_sign>(key, params, in); } DataPointer Cipher::recover(const EVPKeyPointer& key, const CipherParams& params, const Buffer<const void> in) { // public operation return CipherImpl<EVP_PKEY_verify_recover_init, EVP_PKEY_verify_recover>( key, params, in); } namespace { struct CipherCallbackContext { Cipher::CipherNameCallback cb; void operator()(const char* name) { cb(name); } }; #if OPENSSL_VERSION_MAJOR >= 3 template <class TypeName, TypeName* fetch_type(OSSL_LIB_CTX*, const char*, const char*), void free_type(TypeName*), const TypeName* getbyname(const char*), const char* getname(const TypeName*)> void array_push_back(const TypeName* evp_ref, const char* from, const char* to, void* arg) { if (from == nullptr) return; const TypeName* real_instance = getbyname(from); if (!real_instance) return; const char* real_name = getname(real_instance); if (!real_name) return; // EVP_*_fetch() does not support alias names, so we need to pass it the // real/original algorithm name. // We use EVP_*_fetch() as a filter here because it will only return an // instance if the algorithm is supported by the public OpenSSL APIs (some // algorithms are used internally by OpenSSL and are also passed to this // callback). TypeName* fetched = fetch_type(nullptr, real_name, nullptr); if (fetched == nullptr) return; free_type(fetched); auto& cb = *(static_cast<CipherCallbackContext*>(arg)); cb(from); } #else template <class TypeName> void array_push_back(const TypeName* evp_ref, const char* from, const char* to, void* arg) { if (!from) return; auto& cb = *(static_cast<CipherCallbackContext*>(arg)); cb(from); } #endif } // namespace void Cipher::ForEach(Cipher::CipherNameCallback callback) { ClearErrorOnReturn clearErrorOnReturn; CipherCallbackContext context; context.cb = std::move(callback); #if NCRYPTO_USE_BORINGSSL_EVP_DO_ALL_FALLBACK for (const auto& cipher : kBoringSSLCiphers) { static_cast<void>(cipher.get); context.cb(cipher.name); } #else EVP_CIPHER_do_all_sorted( #if OPENSSL_VERSION_MAJOR >= 3 array_push_back<EVP_CIPHER, EVP_CIPHER_fetch, EVP_CIPHER_free, EVP_get_cipherbyname, EVP_CIPHER_get0_name>, #else array_push_back<EVP_CIPHER>, #endif &context); #endif } // ============================================================================ #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<unsigned char*>(public_key.get()), public_key.size(), &public_key_len) != 1 || public_key_len == 0) { ec_.reset(); return; } const auto* public_key_data = static_cast<const unsigned char*>(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 { return EC_GROUP_get_curve_name(getGroup()); } int Ec::GetCurveIdFromName(const char* name) { int nid = EC_curve_nist2nid(name); if (nid == NID_undef) { nid = OBJ_sn2nid(name); } return nid; } bool Ec::GetCurves(Ec::GetCurveCallback callback) { const size_t count = EC_get_builtin_curves(nullptr, 0); std::vector<EC_builtin_curve> curves(count); if (EC_get_builtin_curves(curves.data(), count) != count) { return false; } for (auto curve : curves) { if (!callback(OBJ_nid2sn(curve.nid))) return false; } return true; } // ============================================================================ EVPMDCtxPointer::EVPMDCtxPointer() : ctx_(nullptr) {} EVPMDCtxPointer::EVPMDCtxPointer(EVP_MD_CTX* ctx) : ctx_(ctx) {} EVPMDCtxPointer::EVPMDCtxPointer(EVPMDCtxPointer&& other) noexcept : ctx_(other.release()) {} EVPMDCtxPointer& EVPMDCtxPointer::operator=(EVPMDCtxPointer&& other) noexcept { ctx_.reset(other.release()); return *this; } EVPMDCtxPointer::~EVPMDCtxPointer() { reset(); } void EVPMDCtxPointer::reset(EVP_MD_CTX* ctx) { ctx_.reset(ctx); } EVP_MD_CTX* EVPMDCtxPointer::release() { return ctx_.release(); } bool EVPMDCtxPointer::digestInit(const Digest& digest) { if (!ctx_) return false; return EVP_DigestInit_ex(ctx_.get(), digest, nullptr) > 0; } bool EVPMDCtxPointer::digestUpdate(const Buffer<const void>& in) { if (!ctx_) return false; return EVP_DigestUpdate(ctx_.get(), in.data, in.len) > 0; } DataPointer EVPMDCtxPointer::digestFinal(size_t length) { if (!ctx_) return {}; auto buf = DataPointer::Alloc(length); if (!buf) return {}; Buffer<void> buffer = buf; if (!digestFinalInto(&buffer)) [[unlikely]] { return {}; } return buf; } bool EVPMDCtxPointer::digestFinalInto(Buffer<void>* buf) { if (!ctx_) return false; auto ptr = static_cast<unsigned char*>(buf->data); int ret = (buf->len == getExpectedSize()) ? EVP_DigestFinal_ex(ctx_.get(), ptr, nullptr) : EVP_DigestFinalXOF(ctx_.get(), ptr, buf->len); if (ret != 1) [[unlikely]] return false; return true; } size_t EVPMDCtxPointer::getExpectedSize() { if (!ctx_) return 0; return EVP_MD_CTX_size(ctx_.get()); } size_t EVPMDCtxPointer::getDigestSize() const { return EVP_MD_size(getDigest()); } const EVP_MD* EVPMDCtxPointer::getDigest() const { if (!ctx_) return nullptr; return GetDigestCtxMd(ctx_.get()); } bool EVPMDCtxPointer::hasXofFlag() const { if (!ctx_) return false; return (EVP_MD_flags(getDigest()) & EVP_MD_FLAG_XOF) == EVP_MD_FLAG_XOF; } bool EVPMDCtxPointer::copyTo(const EVPMDCtxPointer& other) const { if (!ctx_ || !other) return {}; if (EVP_MD_CTX_copy(other.get(), ctx_.get()) != 1) return false; return true; } std::optional<EVP_PKEY_CTX*> EVPMDCtxPointer::signInit(const EVPKeyPointer& key, const Digest& digest) { EVP_PKEY_CTX* ctx = nullptr; if (!EVP_DigestSignInit(ctx_.get(), &ctx, digest, nullptr, key.get())) { return std::nullopt; } return ctx; } std::optional<EVP_PKEY_CTX*> EVPMDCtxPointer::verifyInit( const EVPKeyPointer& key, const Digest& digest) { EVP_PKEY_CTX* ctx = nullptr; if (!EVP_DigestVerifyInit(ctx_.get(), &ctx, digest, nullptr, key.get())) { return std::nullopt; } return ctx; } std::optional<EVP_PKEY_CTX*> EVPMDCtxPointer::signInitWithContext( const EVPKeyPointer& key, const Digest& digest, const Buffer<const unsigned char>& context_string) { #ifdef OPENSSL_IS_BORINGSSL EVP_PKEY_CTX* ctx = nullptr; if (!EVP_DigestSignInit(ctx_.get(), &ctx, digest, nullptr, key.get())) { return std::nullopt; } if (EVP_PKEY_CTX_set1_signature_context_string( ctx, context_string.data, context_string.len) <= 0) { return std::nullopt; } return ctx; #elif defined(OSSL_SIGNATURE_PARAM_CONTEXT_STRING) EVP_PKEY_CTX* ctx = nullptr; #ifdef OSSL_SIGNATURE_PARAM_INSTANCE // Ed25519 requires the INSTANCE param to switch into Ed25519ctx mode. // Without it, OpenSSL silently ignores the context string. if (key.id() == EVP_PKEY_ED25519) { const OSSL_PARAM params[] = { OSSL_PARAM_construct_utf8_string( OSSL_SIGNATURE_PARAM_INSTANCE, const_cast<char*>("Ed25519ctx"), 0), OSSL_PARAM_construct_octet_string( OSSL_SIGNATURE_PARAM_CONTEXT_STRING, const_cast<unsigned char*>(context_string.data), context_string.len), OSSL_PARAM_END}; if (!EVP_DigestSignInit_ex( ctx_.get(), &ctx, nullptr, nullptr, nullptr, key.get(), params)) { return std::nullopt; } return ctx; } #endif // OSSL_SIGNATURE_PARAM_INSTANCE const OSSL_PARAM params[] = { OSSL_PARAM_construct_octet_string( OSSL_SIGNATURE_PARAM_CONTEXT_STRING, const_cast<unsigned char*>(context_string.data), context_string.len), OSSL_PARAM_END}; if (!EVP_DigestSignInit_ex( ctx_.get(), &ctx, nullptr, nullptr, nullptr, key.get(), params)) { return std::nullopt; } return ctx; #else return std::nullopt; #endif } std::optional<EVP_PKEY_CTX*> EVPMDCtxPointer::verifyInitWithContext( const EVPKeyPointer& key, const Digest& digest, const Buffer<const unsigned char>& context_string) { #ifdef OPENSSL_IS_BORINGSSL EVP_PKEY_CTX* ctx = nullptr; if (!EVP_DigestVerifyInit(ctx_.get(), &ctx, digest, nullptr, key.get())) { return std::nullopt; } if (EVP_PKEY_CTX_set1_signature_context_string( ctx, context_string.data, context_string.len) <= 0) { return std::nullopt; } return ctx; #elif defined(OSSL_SIGNATURE_PARAM_CONTEXT_STRING) EVP_PKEY_CTX* ctx = nullptr; #ifdef OSSL_SIGNATURE_PARAM_INSTANCE // Ed25519 requires the INSTANCE param to switch into Ed25519ctx mode. // Without it, OpenSSL silently ignores the context string. if (key.id() == EVP_PKEY_ED25519) { const OSSL_PARAM params[] = { OSSL_PARAM_construct_utf8_string( OSSL_SIGNATURE_PARAM_INSTANCE, const_cast<char*>("Ed25519ctx"), 0), OSSL_PARAM_construct_octet_string( OSSL_SIGNATURE_PARAM_CONTEXT_STRING, const_cast<unsigned char*>(context_string.data), context_string.len), OSSL_PARAM_END}; if (!EVP_DigestVerifyInit_ex( ctx_.get(), &ctx, nullptr, nullptr, nullptr, key.get(), params)) { return std::nullopt; } return ctx; } #endif // OSSL_SIGNATURE_PARAM_INSTANCE const OSSL_PARAM params[] = { OSSL_PARAM_construct_octet_string( OSSL_SIGNATURE_PARAM_CONTEXT_STRING, const_cast<unsigned char*>(context_string.data), context_string.len), OSSL_PARAM_END}; if (!EVP_DigestVerifyInit_ex( ctx_.get(), &ctx, nullptr, nullptr, nullptr, key.get(), params)) { return std::nullopt; } return ctx; #else return std::nullopt; #endif } DataPointer EVPMDCtxPointer::signOneShot( const Buffer<const unsigned char>& buf) const { if (!ctx_) return {}; size_t len; if (!EVP_DigestSign(ctx_.get(), nullptr, &len, buf.data, buf.len)) { return {}; } auto data = DataPointer::Alloc(len); if (!data) [[unlikely]] return {}; if (!EVP_DigestSign(ctx_.get(), static_cast<unsigned char*>(data.get()), &len, buf.data, buf.len)) { return {}; } return data; } DataPointer EVPMDCtxPointer::sign( const Buffer<const unsigned char>& buf) const { if (!ctx_) [[unlikely]] return {}; size_t len; if (!EVP_DigestSignUpdate(ctx_.get(), buf.data, buf.len) || !EVP_DigestSignFinal(ctx_.get(), nullptr, &len)) { return {}; } auto data = DataPointer::Alloc(len); if (!data) [[unlikely]] return {}; if (!EVP_DigestSignFinal( ctx_.get(), static_cast<unsigned char*>(data.get()), &len)) { return {}; } return data.resize(len); } bool EVPMDCtxPointer::verify(const Buffer<const unsigned char>& buf, const Buffer<const unsigned char>& sig) const { return verifyOneShot(buf, sig) == 1; } int EVPMDCtxPointer::verifyOneShot( const Buffer<const unsigned char>& buf, const Buffer<const unsigned char>& sig) const { if (!ctx_) return -1; return EVP_DigestVerify(ctx_.get(), sig.data, sig.len, buf.data, buf.len); } EVPMDCtxPointer EVPMDCtxPointer::New() { return EVPMDCtxPointer(EVP_MD_CTX_new()); } // ============================================================================ bool extractP1363(const Buffer<const unsigned char>& buf, unsigned char* dest, size_t n) { auto asn1_sig = ECDSASigPointer::Parse(buf); if (!asn1_sig) return false; return BignumPointer::EncodePaddedInto(asn1_sig.r(), dest, n) > 0 && BignumPointer::EncodePaddedInto(asn1_sig.s(), dest + n, n) > 0; } // ============================================================================ #if !OPENSSL_WITH_EVP_MAC HMACCtxPointer::HMACCtxPointer() : ctx_(nullptr) {} HMACCtxPointer::HMACCtxPointer(HMAC_CTX* ctx) : ctx_(ctx) {} HMACCtxPointer::HMACCtxPointer(HMACCtxPointer&& other) noexcept : ctx_(other.release()) {} HMACCtxPointer& HMACCtxPointer::operator=(HMACCtxPointer&& other) noexcept { ctx_.reset(other.release()); return *this; } HMACCtxPointer::~HMACCtxPointer() { reset(); } void HMACCtxPointer::reset(HMAC_CTX* ctx) { ctx_.reset(ctx); } HMAC_CTX* HMACCtxPointer::release() { return ctx_.release(); } bool HMACCtxPointer::init(const Buffer<const void>& buf, const Digest& md) { if (!ctx_) return false; const EVP_MD* md_ptr = md; return HMAC_Init_ex(ctx_.get(), buf.data, buf.len, md_ptr, nullptr) == 1; } bool HMACCtxPointer::update(const Buffer<const void>& buf) { if (!ctx_) return false; return HMAC_Update(ctx_.get(), static_cast<const unsigned char*>(buf.data), buf.len) == 1; } DataPointer HMACCtxPointer::digest() { auto data = DataPointer::Alloc(EVP_MAX_MD_SIZE); if (!data) return {}; Buffer<void> buf = data; if (!digestInto(&buf)) return {}; return data.resize(buf.len); } bool HMACCtxPointer::digestInto(Buffer<void>* buf) { if (!ctx_) return false; unsigned int len = buf->len; if (!HMAC_Final(ctx_.get(), static_cast<unsigned char*>(buf->data), &len)) { return false; } buf->len = len; return true; } HMACCtxPointer HMACCtxPointer::New() { return HMACCtxPointer(HMAC_CTX_new()); } #endif // !OPENSSL_WITH_EVP_MAC #if OPENSSL_WITH_EVP_MAC EVPMacPointer::EVPMacPointer(EVP_MAC* mac) : mac_(mac) {} EVPMacPointer::EVPMacPointer(EVPMacPointer&& other) noexcept : mac_(std::move(other.mac_)) {} EVPMacPointer& EVPMacPointer::operator=(EVPMacPointer&& other) noexcept { if (this == &other) return *this; mac_ = std::move(other.mac_); return *this; } EVPMacPointer::~EVPMacPointer() { mac_.reset(); } void EVPMacPointer::reset(EVP_MAC* mac) { mac_.reset(mac); } EVP_MAC* EVPMacPointer::release() { return mac_.release(); } EVPMacPointer EVPMacPointer::Fetch(const char* algorithm) { return EVPMacPointer(EVP_MAC_fetch(nullptr, algorithm, nullptr)); } EVPMacCtxPointer::EVPMacCtxPointer(EVP_MAC_CTX* ctx) : ctx_(ctx) {} EVPMacCtxPointer::EVPMacCtxPointer(EVPMacCtxPointer&& other) noexcept : ctx_(std::move(other.ctx_)) {} EVPMacCtxPointer& EVPMacCtxPointer::operator=( EVPMacCtxPointer&& other) noexcept { if (this == &other) return *this; ctx_ = std::move(other.ctx_); return *this; } EVPMacCtxPointer::~EVPMacCtxPointer() { ctx_.reset(); } void EVPMacCtxPointer::reset(EVP_MAC_CTX* ctx) { ctx_.reset(ctx); } EVP_MAC_CTX* EVPMacCtxPointer::release() { return ctx_.release(); } bool EVPMacCtxPointer::init(const Buffer<const void>& key, const OSSL_PARAM* params) { if (!ctx_) return false; return EVP_MAC_init(ctx_.get(), static_cast<const unsigned char*>(key.data), key.len, params) == 1; } bool EVPMacCtxPointer::update(const Buffer<const void>& data) { if (!ctx_) return false; return EVP_MAC_update(ctx_.get(), static_cast<const unsigned char*>(data.data), data.len) == 1; } DataPointer EVPMacCtxPointer::final(size_t length) { if (!ctx_) return {}; auto buf = DataPointer::Alloc(length); if (!buf) return {}; size_t result_len = length; if (EVP_MAC_final(ctx_.get(), static_cast<unsigned char*>(buf.get()), &result_len, length) != 1) { return {}; } return buf; } EVPMacCtxPointer EVPMacCtxPointer::New(EVP_MAC* mac) { if (!mac) return EVPMacCtxPointer(); return EVPMacCtxPointer(EVP_MAC_CTX_new(mac)); } HMACCtxPointer::HMACCtxPointer() = default; HMACCtxPointer::HMACCtxPointer(EVPMacPointer&& mac, EVPMacCtxPointer&& ctx) : mac_(std::move(mac)), ctx_(std::move(ctx)) {} HMACCtxPointer::HMACCtxPointer(HMACCtxPointer&& other) noexcept : mac_(std::move(other.mac_)), ctx_(std::move(other.ctx_)), md_size_(other.md_size_) { other.md_size_ = 0; } HMACCtxPointer& HMACCtxPointer::operator=(HMACCtxPointer&& other) noexcept { if (this == &other) return *this; mac_ = std::move(other.mac_); ctx_ = std::move(other.ctx_); md_size_ = other.md_size_; other.md_size_ = 0; return *this; } HMACCtxPointer::~HMACCtxPointer() { reset(); } void HMACCtxPointer::reset() { ctx_.reset(); mac_.reset(); md_size_ = 0; } bool HMACCtxPointer::init(const Buffer<const void>& buf, const Digest& md) { if (!ctx_ || !md) return false; const char* md_name = EVP_MD_get0_name(md); if (md_name == nullptr) return false; OSSL_PARAM params[] = { OSSL_PARAM_construct_utf8_string( OSSL_MAC_PARAM_DIGEST, const_cast<char*>(md_name), 0), OSSL_PARAM_construct_end(), }; if (!ctx_.init(buf, params)) return false; md_size_ = md.size(); return true; } bool HMACCtxPointer::update(const Buffer<const void>& buf) { if (!ctx_) return false; return ctx_.update(buf); } DataPointer HMACCtxPointer::digest() { if (md_size_ == 0) return {}; auto data = DataPointer::Alloc(md_size_); if (!data) return {}; Buffer<void> buf = data; if (!digestInto(&buf)) return {}; return data.resize(buf.len); } bool HMACCtxPointer::digestInto(Buffer<void>* buf) { if (!ctx_) return false; size_t len = buf->len; if (EVP_MAC_final( ctx_.get(), static_cast<unsigned char*>(buf->data), &len, buf->len) != 1) return false; buf->len = len; return true; } HMACCtxPointer HMACCtxPointer::New() { auto mac = EVPMacPointer::Fetch(OSSL_MAC_NAME_HMAC); if (!mac) return {}; auto ctx = EVPMacCtxPointer::New(mac.get()); if (!ctx) return {}; return HMACCtxPointer(std::move(mac), std::move(ctx)); } #endif // OPENSSL_WITH_EVP_MAC DataPointer hashDigest(const Buffer<const unsigned char>& buf, const EVP_MD* md) { if (md == nullptr) return {}; size_t md_len = EVP_MD_size(md); unsigned int result_size; auto data = DataPointer::Alloc(md_len); if (!data) return {}; if (!EVP_Digest(buf.data, buf.len, reinterpret_cast<unsigned char*>(data.get()), &result_size, md, nullptr)) { return {}; } return data.resize(result_size); } DataPointer xofHashDigest(const Buffer<const unsigned char>& buf, const EVP_MD* md, size_t output_length) { if (md == nullptr) return {}; EVPMDCtxPointer ctx = EVPMDCtxPointer::New(); if (!ctx) return {}; if (ctx.digestInit(md) != 1) { return {}; } if (ctx.digestUpdate(reinterpret_cast<const Buffer<const void>&>(buf)) != 1) { return {}; } return ctx.digestFinal(output_length); } // ============================================================================ X509Name::X509Name() : name_(nullptr), total_(0) {} X509Name::X509Name(const X509_NAME* name) : name_(name), total_(X509_NAME_entry_count(name)) {} X509Name::Iterator::Iterator(const X509Name& name, int pos) : name_(name), loc_(pos) {} X509Name::Iterator& X509Name::Iterator::operator++() { ++loc_; return *this; } X509Name::Iterator::operator bool() const { return loc_ < name_.total_; } bool X509Name::Iterator::operator==(const Iterator& other) const { return loc_ == other.loc_; } bool X509Name::Iterator::operator!=(const Iterator& other) const { return loc_ != other.loc_; } std::pair<std::string, std::string> X509Name::Iterator::operator*() const { if (loc_ == name_.total_) return {{}, {}}; const X509_NAME_ENTRY* entry = X509_NAME_get_entry(name_, loc_); if (entry == nullptr) [[unlikely]] return {{}, {}}; const ASN1_OBJECT* name = X509_NAME_ENTRY_get_object(entry); const ASN1_STRING* value = X509_NAME_ENTRY_get_data(entry); if (name == nullptr || value == nullptr) [[unlikely]] { return {{}, {}}; } int nid = OBJ_obj2nid(name); std::string name_str; if (nid != NID_undef) { name_str = std::string(OBJ_nid2sn(nid)); } else { char buf[80]; OBJ_obj2txt(buf, sizeof(buf), name, 0); name_str = std::string(buf); } unsigned char* value_str; int value_str_size = ASN1_STRING_to_UTF8(&value_str, value); std::string out(reinterpret_cast<const char*>(value_str), value_str_size); OPENSSL_free(value_str); // free after copy return {std::move(name_str), std::move(out)}; } // ============================================================================ #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()); } // ============================================================================ size_t Digest::size() const { if (md_ == nullptr) return 0; return EVP_MD_size(md_); } const Digest Digest::MD5 = Digest(EVP_md5()); const Digest Digest::SHA1 = Digest(EVP_sha1()); const Digest Digest::SHA256 = Digest(EVP_sha256()); const Digest Digest::SHA384 = Digest(EVP_sha384()); const Digest Digest::SHA512 = Digest(EVP_sha512()); const Digest Digest::FromName(const char* name) { return ncrypto::getDigestByName(name); } // ============================================================================ // KEM Implementation #if OPENSSL_WITH_KEM #if OPENSSL_WITH_KEM_OPERATION_PARAM bool KEM::SetOperationParameter(EVP_PKEY_CTX* ctx, const EVPKeyPointer& key) { const char* operation = nullptr; switch (EVP_PKEY_id(key.get())) { case EVP_PKEY_RSA: operation = OSSL_KEM_PARAM_OPERATION_RSASVE; break; #if OPENSSL_WITH_OPENSSL_DHKEM case EVP_PKEY_EC: case EVP_PKEY_X25519: case EVP_PKEY_X448: operation = OSSL_KEM_PARAM_OPERATION_DHKEM; break; #endif default: unreachable(); } if (operation != nullptr) { OSSL_PARAM params[] = { OSSL_PARAM_utf8_string( OSSL_KEM_PARAM_OPERATION, const_cast<char*>(operation), 0), OSSL_PARAM_END}; if (EVP_PKEY_CTX_set_params(ctx, params) <= 0) { return false; } } return true; } #endif std::optional<KEM::EncapsulateResult> KEM::Encapsulate( const EVPKeyPointer& public_key) { ClearErrorOnReturn clear_error_on_return; auto ctx = public_key.newCtx(); if (!ctx) return std::nullopt; if (EVP_PKEY_encapsulate_init(ctx.get(), nullptr) <= 0) { return std::nullopt; } #if OPENSSL_WITH_KEM_OPERATION_PARAM if (!SetOperationParameter(ctx.get(), public_key)) { return std::nullopt; } #endif // Determine output buffer sizes size_t ciphertext_len = 0; size_t shared_key_len = 0; if (EVP_PKEY_encapsulate( ctx.get(), nullptr, &ciphertext_len, nullptr, &shared_key_len) <= 0) { return std::nullopt; } auto ciphertext = DataPointer::Alloc(ciphertext_len); auto shared_key = DataPointer::Alloc(shared_key_len); if (!ciphertext || !shared_key) return std::nullopt; if (EVP_PKEY_encapsulate(ctx.get(), static_cast<unsigned char*>(ciphertext.get()), &ciphertext_len, static_cast<unsigned char*>(shared_key.get()), &shared_key_len) <= 0) { return std::nullopt; } return EncapsulateResult(std::move(ciphertext), std::move(shared_key)); } DataPointer KEM::Decapsulate(const EVPKeyPointer& private_key, const Buffer<const void>& ciphertext) { ClearErrorOnReturn clear_error_on_return; auto ctx = private_key.newCtx(); if (!ctx) return {}; if (EVP_PKEY_decapsulate_init(ctx.get(), nullptr) <= 0) { return {}; } #if OPENSSL_WITH_KEM_OPERATION_PARAM if (!SetOperationParameter(ctx.get(), private_key)) { return {}; } #endif // First pass: determine shared secret size size_t shared_key_len = 0; if (EVP_PKEY_decapsulate(ctx.get(), nullptr, &shared_key_len, static_cast<const unsigned char*>(ciphertext.data), ciphertext.len) <= 0) { return {}; } auto shared_key = DataPointer::Alloc(shared_key_len); if (!shared_key) return {}; if (EVP_PKEY_decapsulate(ctx.get(), static_cast<unsigned char*>(shared_key.get()), &shared_key_len, static_cast<const unsigned char*>(ciphertext.data), ciphertext.len) <= 0) { return {}; } return shared_key; } #endif // OPENSSL_WITH_KEM } // namespace ncrypto