#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_OPENSSL_PROVIDER
#include <openssl/decoder.h>
#endif
#if NCRYPTO_USE_BORINGSSL_EVP_DO_ALL_FALLBACK
#include <openssl/bytestring.h>
#include <openssl/cipher.h>
#endif
#include <algorithm>
#include <array>
#include <atomic>
#include <climits>
#include <cstring>
#include <string_view>
#include <vector>
#ifndef OPENSSL_IS_BORINGSSL
#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
// Ubuntu 22.04's default libssl-dev still lacks EVP_PKEY_PRIVATE_KEY when
// fully updated. The macro was added upstream in OpenSSL 3.0.12.
#if NCRYPTO_USE_OPENSSL_PROVIDER && !defined(EVP_PKEY_PRIVATE_KEY)
#define EVP_PKEY_PRIVATE_KEY \
(EVP_PKEY_KEY_PARAMETERS | OSSL_KEYMGMT_SELECT_PRIVATE_KEY)
#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>;
struct ASN1SequenceDeleter final {
void operator()(ASN1_SEQUENCE_ANY* sequence) const {
sk_ASN1_TYPE_pop_free(sequence, ASN1_TYPE_free);
}
};
using ASN1SequencePointer =
std::unique_ptr<ASN1_SEQUENCE_ANY, ASN1SequenceDeleter>;
#if NCRYPTO_USE_OPENSSL_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_OPENSSL_PROVIDER
return EVP_CIPHER_CTX_get0_cipher(ctx);
#elif NCRYPTO_USE_BORINGSSL
return EVP_CIPHER_CTX_cipher(ctx);
#else
#error "Unsupported crypto backend"
#endif
}
const EVP_MD* GetDigestCtxMd(const EVP_MD_CTX* ctx) {
return EVP_MD_CTX_get0_md(ctx);
}
#if NCRYPTO_USE_OPENSSL_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>;
struct RsaOtherPrimeParamNames {
const char* factor;
const char* exponent;
const char* coefficient;
};
#define RSA_OTHER_PRIME_PARAM_NAMES(prime, coefficient) \
{ \
OSSL_PKEY_PARAM_RSA_FACTOR #prime, OSSL_PKEY_PARAM_RSA_EXPONENT #prime, \
OSSL_PKEY_PARAM_RSA_COEFFICIENT #coefficient \
}
constexpr std::array<RsaOtherPrimeParamNames, 8> kRsaOtherPrimeParamNames = {{
RSA_OTHER_PRIME_PARAM_NAMES(3, 2),
RSA_OTHER_PRIME_PARAM_NAMES(4, 3),
RSA_OTHER_PRIME_PARAM_NAMES(5, 4),
RSA_OTHER_PRIME_PARAM_NAMES(6, 5),
RSA_OTHER_PRIME_PARAM_NAMES(7, 6),
RSA_OTHER_PRIME_PARAM_NAMES(8, 7),
RSA_OTHER_PRIME_PARAM_NAMES(9, 8),
RSA_OTHER_PRIME_PARAM_NAMES(10, 9),
}};
#undef RSA_OTHER_PRIME_PARAM_NAMES
#endif
static constexpr int kX509NameFlagsRFC2253WithinUtf8JSON =
XN_FLAG_RFC2253 & ~ASN1_STRFLGS_ESC_MSB & ~ASN1_STRFLGS_ESC_CTRL;
#if NCRYPTO_USE_OPENSSL_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(const KeyAlgorithm& algorithm,
int selection,
OSSL_PARAM* params) {
auto ctx = EVPKeyCtxPointer::NewFromAlgorithm(algorithm);
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(KeyAlgorithm::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) {
auto ctx = EVPKeyCtxPointer::NewFromAlgorithm(KeyAlgorithm::DH);
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(KeyAlgorithm::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
// ============================================================================
namespace {
// ERR_clear_error() resets every slot of the error queue even when it is
// empty, which costs several times more than checking for an error first.
void ClearErrorQueue() {
if (ERR_peek_error() != 0) ERR_clear_error();
}
} // namespace
ClearErrorOnReturn::ClearErrorOnReturn(CryptoErrorList* errors)
: errors_(errors) {
ClearErrorQueue();
}
ClearErrorOnReturn::~ClearErrorOnReturn() {
if (errors_ != nullptr) errors_->capture();
ClearErrorQueue();
}
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) {
return DataPointer(OPENSSL_zalloc(len), len);
}
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);
#elif defined(OPENSSL_IS_BORINGSSL)
// 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() {
return CRYPTO_secure_malloc_initialized() ? CRYPTO_secure_used() : 0;
}
DataPointer::InitSecureHeapResult DataPointer::TryInitSecureHeap(size_t amount,
size_t min) {
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;
}
}
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);
}
// ============================================================================
namespace {
// This generation only coordinates cache invalidation. It does not make
// OpenSSL default property changes safe to race with crypto operations.
std::atomic<uint64_t> fips_state_generation{0};
bool isFipsEnabledRaw() {
#ifndef OPENSSL_IS_BORINGSSL
return EVP_default_properties_is_fips_enabled(nullptr) == 1;
#elif defined(OPENSSL_IS_BORINGSSL)
return FIPS_mode() == 1;
#endif
}
} // namespace
bool isFipsEnabled() {
ClearErrorOnReturn clear_error_on_return;
return isFipsEnabledRaw();
}
bool setFipsEnabled(bool enable, CryptoErrorList* errors) {
const bool was_enabled = isFipsEnabled();
if (was_enabled == enable) return true;
ClearErrorOnReturn clearErrorOnReturn(errors);
#ifndef OPENSSL_IS_BORINGSSL
const bool success =
EVP_default_properties_enable_fips(nullptr, enable ? 1 : 0) == 1;
#elif defined(OPENSSL_IS_BORINGSSL)
const bool success = FIPS_mode_set(enable ? 1 : 0) == 1;
#endif
if (success && isFipsEnabledRaw() != was_enabled) {
fips_state_generation.fetch_add(1, std::memory_order_release);
}
return success;
}
uint64_t getFipsStateGeneration() {
return fips_state_generation.load(std::memory_order_acquire);
}
bool testFipsEnabled() {
ClearErrorOnReturn clear_error_on_return;
#ifndef OPENSSL_IS_BORINGSSL
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;
#elif defined(OPENSSL_IS_BORINGSSL)
#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() {
return BignumPointer(BN_secure_new());
}
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_OPENSSL_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
#error "Unsupported crypto backend"
#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()) {
#ifndef OPENSSL_IS_BORINGSSL
if (1 == RAND_bytes_ex(nullptr, buf, length, 0)) {
return true;
}
#elif defined(OPENSSL_IS_BORINGSSL)
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
}
#ifndef OPENSSL_IS_BORINGSSL
const auto code = ERR_peek_last_error();
// A misconfigured OpenSSL 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_OPENSSL_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) {
ClearErrorOnReturn clear_error_on_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.
//
// 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) {
ClearErrorOnReturn clear_error_on_return;
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) {
ClearErrorOnReturn clear_error_on_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 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,
};
std::optional<std::string> ObjectToText(const ASN1_OBJECT* object,
bool no_name) {
if (object == nullptr) return std::nullopt;
int length = OBJ_obj2txt(nullptr, 0, object, no_name);
if (length <= 0 || length == INT_MAX) return std::nullopt;
std::string text(static_cast<size_t>(length) + 1, '\0');
int actual = OBJ_obj2txt(text.data(), length + 1, object, no_name);
if (actual != length) return std::nullopt;
text.resize(static_cast<size_t>(length));
return text;
}
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;
}
[[nodiscard]] bool PrintAltName(const BIOPointer& out,
const char* name,
size_t length,
AltNameOption option = AltNameOption::NONE,
const char* safe_prefix = nullptr) {
if (!out || (name == nullptr && length != 0)) return false;
if (IsSafeAltName(name, length, option)) {
// For backward-compatibility, append "safe" names without any
// modifications.
if (safe_prefix != nullptr) {
if (!BIOPointer::PrintfAll(out, "%s:", safe_prefix)) return false;
}
if (length != 0 &&
!BIOPointer::WriteAll(out, std::string_view(name, length))) {
return false;
}
} 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.
if (!BIOPointer::WriteAll(out, "\"")) return false;
if (safe_prefix != nullptr) {
if (!BIOPointer::PrintfAll(out, "%s:", safe_prefix)) return false;
}
for (size_t j = 0; j < length; j++) {
char c = static_cast<char>(name[j]);
if (c == '\\') {
if (!BIOPointer::WriteAll(out, "\\\\")) return false;
} else if (c == '"') {
if (!BIOPointer::WriteAll(out, "\\\"")) return false;
} 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).
if (!BIOPointer::WriteAll(out, std::string_view(&c, 1))) return false;
} 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]};
if (!BIOPointer::WriteAll(out, std::string_view(u, sizeof(u)))) {
return false;
}
}
}
if (!BIOPointer::WriteAll(out, "\"")) return false;
}
return true;
}
// 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 (!out || gen == nullptr) return false;
if (gen->type == GEN_DNS) {
ASN1_IA5STRING* name = gen->d.dNSName;
if (name == nullptr) return false;
if (!BIOPointer::WriteAll(out, "DNS:")) return false;
// 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.
int length = ASN1_STRING_length(name);
return length >= 0 && PrintAltName(out,
reinterpret_cast<const char*>(
ASN1_STRING_get0_data(name)),
static_cast<size_t>(length));
} else if (gen->type == GEN_EMAIL) {
ASN1_IA5STRING* name = gen->d.rfc822Name;
if (name == nullptr) return false;
if (!BIOPointer::WriteAll(out, "email:")) return false;
int length = ASN1_STRING_length(name);
return length >= 0 && PrintAltName(out,
reinterpret_cast<const char*>(
ASN1_STRING_get0_data(name)),
static_cast<size_t>(length));
} else if (gen->type == GEN_URI) {
ASN1_IA5STRING* name = gen->d.uniformResourceIdentifier;
if (name == nullptr) return false;
if (!BIOPointer::WriteAll(out, "URI:")) return false;
// 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.
int length = ASN1_STRING_length(name);
return length >= 0 && PrintAltName(out,
reinterpret_cast<const char*>(
ASN1_STRING_get0_data(name)),
static_cast<size_t>(length));
} else if (gen->type == GEN_DIRNAME) {
if (gen->d.dirn == nullptr) return false;
// 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.
if (!BIOPointer::WriteAll(out, "DirName:")) return false;
BIOPointer tmp = BIOPointer::NewMem();
if (!tmp) return false;
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)
if (n_bytes < 0) return false;
return PrintAltName(out,
oline,
static_cast<size_t>(n_bytes),
ncrypto::AltNameOption::UTF8,
nullptr);
} else if (gen->type == GEN_IPADD) {
if (!BIOPointer::WriteAll(out, "IP Address:")) return false;
const ASN1_OCTET_STRING* ip = gen->d.ip;
if (ip == nullptr) return false;
const unsigned char* b = ASN1_STRING_get0_data(ip);
int ip_len = ASN1_STRING_length(ip);
if (ip_len < 0 || (b == nullptr && ip_len != 0)) return false;
if (ip_len == 4) {
return BIOPointer::PrintfAll(out, "%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];
if (!BIOPointer::PrintfAll(out, (j == 0) ? "%X" : ":%X", pair)) {
return false;
}
}
} else {
#if NCRYPTO_USE_OPENSSL_PROVIDER
return BIOPointer::PrintfAll(out, "<invalid length=%d>", ip_len);
#elif NCRYPTO_USE_BORINGSSL
return BIOPointer::WriteAll(out, "<invalid>");
#else
#error "Unsupported crypto backend"
#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.
auto oid = ObjectToText(gen->d.rid, true);
return oid.has_value() && BIOPointer::WriteAll(out, "Registered ID:") &&
BIOPointer::WriteAll(out, oid.value());
} else if (gen->type == GEN_OTHERNAME) {
if (gen->d.otherName == nullptr || gen->d.otherName->type_id == nullptr ||
gen->d.otherName->value == nullptr) {
return false;
}
// The format that is used here is based on OpenSSL's implementation of
// GENERAL_NAME_print. 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;
// BoringSSL does not support othername in GENERAL_NAME_print and may
// not define these NIDs.
#if NCRYPTO_USE_OPENSSL_PROVIDER
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 // NCRYPTO_USE_OPENSSL_PROVIDER
int val_type = gen->d.otherName->value->type;
if (prefix == nullptr || (unicode && val_type != V_ASN1_UTF8STRING) ||
(!unicode && val_type != V_ASN1_IA5STRING)) {
return BIOPointer::WriteAll(out, "othername:<unsupported>");
} else {
if (!BIOPointer::WriteAll(out, "othername:")) return false;
if (unicode) {
auto name = gen->d.otherName->value->value.utf8string;
if (name == nullptr) return false;
int length = ASN1_STRING_length(name);
return length >= 0 && PrintAltName(out,
reinterpret_cast<const char*>(
ASN1_STRING_get0_data(name)),
static_cast<size_t>(length),
AltNameOption::UTF8,
prefix);
} else {
auto name = gen->d.otherName->value->value.ia5string;
if (name == nullptr) return false;
int length = ASN1_STRING_length(name);
return length >= 0 && PrintAltName(out,
reinterpret_cast<const char*>(
ASN1_STRING_get0_data(name)),
static_cast<size_t>(length),
AltNameOption::NONE,
prefix);
}
}
} else if (gen->type == GEN_X400) {
// TODO(tniessen): this is what OpenSSL does, implement properly instead
return BIOPointer::WriteAll(out, "X400Name:<unsupported>");
} else if (gen->type == GEN_EDIPARTY) {
// TODO(tniessen): this is what OpenSSL does, implement properly instead
return BIOPointer::WriteAll(out, "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) {
if (!out || ext == nullptr) return false;
// const_cast needed for OpenSSL < 4.0 which lacks const-correctness
auto* mext = const_cast<X509_EXTENSION*>(ext);
const ASN1_OBJECT* object = X509_EXTENSION_get_object(mext);
if (object == nullptr) return false;
auto ret = OBJ_obj2nid(object);
if (ret != NID_subject_alt_name) return false;
GENERAL_NAMES* names = static_cast<GENERAL_NAMES*>(X509V3_EXT_d2i(mext));
if (names == nullptr) return false;
const OPENSSL_SIZE_T count = sk_GENERAL_NAME_num(names);
bool ok = count > 0;
for (OPENSSL_SIZE_T i = 0; ok && i < count; i++) {
GENERAL_NAME* gen = sk_GENERAL_NAME_value(names, i);
if (gen == nullptr) {
ok = false;
break;
}
if (i != 0 && !(ok = BIOPointer::WriteAll(out, ", "))) {
break;
}
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) {
if (!out || ext == nullptr) return false;
// const_cast needed for OpenSSL < 4.0 which lacks const-correctness
auto* mext = const_cast<X509_EXTENSION*>(ext);
const ASN1_OBJECT* object = X509_EXTENSION_get_object(mext);
if (object == nullptr) return false;
auto ret = OBJ_obj2nid(object);
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;
const OPENSSL_SIZE_T count = sk_ACCESS_DESCRIPTION_num(descs);
bool ok = count > 0;
for (OPENSSL_SIZE_T i = 0; ok && i < count; i++) {
ACCESS_DESCRIPTION* desc = sk_ACCESS_DESCRIPTION_value(descs, i);
if (desc == nullptr || desc->method == nullptr ||
desc->location == nullptr) {
ok = false;
break;
}
if (i != 0 && !(ok = BIOPointer::WriteAll(out, "\n"))) {
break;
}
auto method = ObjectToText(desc->method, false);
if (!(ok = method.has_value() &&
BIOPointer::WriteAll(out, method.value()) &&
BIOPointer::WriteAll(out, " - "))) {
break;
}
if (!(ok = ncrypto::PrintGeneralName(out, desc->location))) {
break;
}
}
sk_ACCESS_DESCRIPTION_pop_free(descs, ACCESS_DESCRIPTION_free);
#if NCRYPTO_USE_BORINGSSL
if (ok && !BIOPointer::WriteAll(out, "\n")) return false;
#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 {
MarkPopErrorOnReturn mark_pop_error_on_return;
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 {
MarkPopErrorOnReturn mark_pop_error_on_return;
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 {
MarkPopErrorOnReturn mark_pop_error_on_return;
if (cert_ == nullptr) return {};
return X509Name(X509_get_subject_name(cert_));
}
const X509Name X509View::getIssuerName() const {
MarkPopErrorOnReturn mark_pop_error_on_return;
if (cert_ == nullptr) return {};
return X509Name(X509_get_issuer_name(cert_));
}
BIOPointer X509View::getSubject() const {
MarkPopErrorOnReturn mark_pop_error_on_return;
if (cert_ == nullptr) return {};
const X509_NAME* name = X509_get_subject_name(cert_);
if (name == nullptr) return {};
BIOPointer bio = BIOPointer::NewMem();
if (!bio) return {};
if (X509_NAME_print_ex(bio.get(), name, 0, kX509NameFlagsMultiline) < 0) {
return {};
}
return bio;
}
BIOPointer X509View::getSubjectAltName(bool* has_extension) const {
MarkPopErrorOnReturn mark_pop_error_on_return;
if (has_extension != nullptr) *has_extension = false;
if (cert_ == nullptr) return {};
int index = X509_get_ext_by_NID(cert_, NID_subject_alt_name, -1);
if (index < 0) return {};
if (has_extension != nullptr) *has_extension = true;
const X509_EXTENSION* extension = X509_get_ext(cert_, index);
BIOPointer bio = BIOPointer::NewMem();
if (!bio || extension == nullptr ||
!SafeX509SubjectAltNamePrint(bio, extension)) {
return {};
}
return bio;
}
BIOPointer X509View::getIssuer() const {
MarkPopErrorOnReturn mark_pop_error_on_return;
if (cert_ == nullptr) return {};
const X509_NAME* name = X509_get_issuer_name(cert_);
if (name == nullptr) return {};
BIOPointer bio = BIOPointer::NewMem();
if (!bio) return {};
if (X509_NAME_print_ex(bio.get(), name, 0, kX509NameFlagsMultiline) < 0) {
return {};
}
return bio;
}
BIOPointer X509View::getInfoAccess() const {
MarkPopErrorOnReturn mark_pop_error_on_return;
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 {};
const X509_EXTENSION* extension = X509_get_ext(cert_, index);
if (extension == nullptr || !SafeX509InfoAccessPrint(bio, extension)) {
return {};
}
return bio;
}
namespace {
bool IsX509TimePrintable(const ASN1_TIME* time) {
#if NCRYPTO_USE_BORINGSSL
// BoringSSL's ASN1_TIME_check() accepts legacy timezone offsets that its
// ASN1_TIME_print() deliberately rejects. Use the same strict conversion
// path as its printer so the fallback is selected before writing output.
int64_t ignored{};
return ASN1_TIME_to_posix(time, &ignored) == 1;
#elif NCRYPTO_USE_OPENSSL_PROVIDER
return ASN1_TIME_check(time) == 1;
#else
#error "Unsupported crypto backend"
#endif
}
} // namespace
BIOPointer X509View::getValidFrom() const {
MarkPopErrorOnReturn mark_pop_error_on_return;
if (cert_ == nullptr) return {};
const ASN1_TIME* time = X509_get0_notBefore(cert_);
if (time == nullptr) return {};
BIOPointer bio = BIOPointer::NewMem();
if (!bio) return {};
if (IsX509TimePrintable(time)) {
if (ASN1_TIME_print(bio.get(), time) <= 0) return {};
} else if (!BIOPointer::WriteAll(bio, "Bad time value")) {
return {};
}
return bio;
}
BIOPointer X509View::getValidTo() const {
MarkPopErrorOnReturn mark_pop_error_on_return;
if (cert_ == nullptr) return {};
const ASN1_TIME* time = X509_get0_notAfter(cert_);
if (time == nullptr) return {};
BIOPointer bio = BIOPointer::NewMem();
if (!bio) return {};
if (IsX509TimePrintable(time)) {
if (ASN1_TIME_print(bio.get(), time) <= 0) return {};
} else if (!BIOPointer::WriteAll(bio, "Bad time value")) {
return {};
}
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 {
MarkPopErrorOnReturn mark_pop_error_on_return;
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;
return ObjectToText(obj, true);
}
std::optional<int64_t> X509View::getValidToTime() const {
MarkPopErrorOnReturn mark_pop_error_on_return;
if (cert_ == nullptr) return std::nullopt;
const ASN1_TIME* time = X509_get0_notAfter(cert_);
if (time == nullptr) return std::nullopt;
#if NCRYPTO_USE_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{};
if (ASN1_TIME_to_posix(time, &tp) != 1) return std::nullopt;
return tp;
#elif NCRYPTO_USE_OPENSSL_PROVIDER
struct tm tp {};
if (ASN1_TIME_to_tm(time, &tp) != 1) return std::nullopt;
return PortableTimeGM(&tp);
#else
#error "Unsupported crypto backend"
#endif
}
std::optional<int64_t> X509View::getValidFromTime() const {
MarkPopErrorOnReturn mark_pop_error_on_return;
if (cert_ == nullptr) return std::nullopt;
const ASN1_TIME* time = X509_get0_notBefore(cert_);
if (time == nullptr) return std::nullopt;
#if NCRYPTO_USE_BORINGSSL
int64_t tp{};
if (ASN1_TIME_to_posix(time, &tp) != 1) return std::nullopt;
return tp;
#elif NCRYPTO_USE_OPENSSL_PROVIDER
struct tm tp {};
if (ASN1_TIME_to_tm(time, &tp) != 1) return std::nullopt;
return PortableTimeGM(&tp);
#else
#error "Unsupported crypto backend"
#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;
}
namespace {
bool CopyStringBytes(const ASN1_STRING* value,
std::vector<unsigned char>* output) {
if (value == nullptr) return false;
const int length = ASN1_STRING_length(value);
const unsigned char* data = ASN1_STRING_get0_data(value);
if (length < 0 || (length > 0 && data == nullptr)) return false;
if (length == 0) {
output->clear();
} else {
output->assign(data, data + static_cast<size_t>(length));
}
return true;
}
bool CanConvertNameValueToUtf8(const ASN1_STRING* value) {
if (value == nullptr) return false;
switch (ASN1_STRING_type(value)) {
case V_ASN1_UTF8STRING:
case V_ASN1_NUMERICSTRING:
case V_ASN1_PRINTABLESTRING:
case V_ASN1_T61STRING:
case V_ASN1_IA5STRING:
case V_ASN1_UTCTIME:
case V_ASN1_GENERALIZEDTIME:
case V_ASN1_ISO64STRING:
case V_ASN1_UNIVERSALSTRING:
case V_ASN1_BMPSTRING:
return true;
default:
return false;
}
}
std::optional<size_t> GetEncodedRdnCount(const X509_NAME* name) {
const unsigned char* der = nullptr;
size_t der_length = 0;
if (X509_NAME_get0_der(name, &der, &der_length) != 1 || der == nullptr ||
der_length > static_cast<size_t>(LONG_MAX)) {
return std::nullopt;
}
const unsigned char* cursor = der;
ASN1SequencePointer rdns(d2i_ASN1_SEQUENCE_ANY(
nullptr, &cursor, static_cast<long>(der_length))); // NOLINT(runtime/int)
if (!rdns || cursor != der + der_length) return std::nullopt;
const OPENSSL_SIZE_T count = sk_ASN1_TYPE_num(rdns.get());
for (OPENSSL_SIZE_T i = 0; i < count; i++) {
const ASN1_TYPE* rdn = sk_ASN1_TYPE_value(rdns.get(), i);
if (rdn == nullptr || ASN1_TYPE_get(rdn) != V_ASN1_SET) {
return std::nullopt;
}
}
return static_cast<size_t>(count);
}
std::optional<X509NameInspection> InspectName(const X509_NAME* name) {
if (name == nullptr) return std::nullopt;
const int count = X509_NAME_entry_count(name);
if (count < 0) return std::nullopt;
const auto encoded_rdn_count = GetEncodedRdnCount(name);
if (!encoded_rdn_count.has_value()) return std::nullopt;
X509NameInspection output;
output.reserve(static_cast<size_t>(count));
int previous_set = -1;
for (int i = 0; i < count; i++) {
const X509_NAME_ENTRY* entry = X509_NAME_get_entry(name, i);
if (entry == nullptr) return std::nullopt;
const ASN1_OBJECT* object = X509_NAME_ENTRY_get_object(entry);
const ASN1_STRING* value = X509_NAME_ENTRY_get_data(entry);
auto oid = ObjectToText(object, true);
const int set = X509_NAME_ENTRY_set(entry);
if (!oid.has_value() || value == nullptr || set < 0 ||
(set != previous_set &&
(previous_set == INT_MAX || set != previous_set + 1))) {
return std::nullopt;
}
previous_set = set;
X509NameAttribute attribute{
.set = set,
.oid = std::move(oid.value()),
};
if (CanConvertNameValueToUtf8(value)) {
unsigned char* data = nullptr;
const int length = ASN1_STRING_to_UTF8(&data, value);
DataPointer owned(data, length > 0 ? static_cast<size_t>(length) : 0);
if (length < 0 || (length > 0 && !owned)) return std::nullopt;
attribute.value =
length == 0 ? std::string()
: std::string(reinterpret_cast<const char*>(owned.get()),
static_cast<size_t>(length));
}
output.push_back(std::move(attribute));
}
const size_t inspected_rdn_count =
previous_set < 0 ? 0 : static_cast<size_t>(previous_set) + 1;
if (inspected_rdn_count != encoded_rdn_count.value()) return std::nullopt;
return output;
}
std::optional<std::vector<X509RawExtension>> InspectExtensions(
const X509* certificate) {
if (certificate == nullptr) return std::nullopt;
const int count = X509_get_ext_count(certificate);
if (count < 0) return std::nullopt;
std::vector<X509RawExtension> output;
output.reserve(static_cast<size_t>(count));
for (int i = 0; i < count; i++) {
const X509_EXTENSION* extension = X509_get_ext(certificate, i);
if (extension == nullptr) return std::nullopt;
auto* mutable_extension = const_cast<X509_EXTENSION*>(extension);
auto oid = ObjectToText(X509_EXTENSION_get_object(mutable_extension), true);
const ASN1_OCTET_STRING* value = X509_EXTENSION_get_data(mutable_extension);
if (!oid.has_value() || value == nullptr) return std::nullopt;
X509RawExtension inspected{
.oid = std::move(oid.value()),
.critical = X509_EXTENSION_get_critical(mutable_extension) != 0,
};
if (!CopyStringBytes(value, &inspected.value)) return std::nullopt;
output.push_back(std::move(inspected));
}
return output;
}
} // namespace
std::optional<int64_t> X509View::getVersion() const {
MarkPopErrorOnReturn mark_pop_error_on_return;
if (cert_ == nullptr) return std::nullopt;
const long version = X509_get_version(cert_); // NOLINT(runtime/int)
if (version < 0 || version > 2) return std::nullopt;
return static_cast<int64_t>(version) + 1;
}
std::optional<X509NameInspection> X509View::inspectSubjectName() const {
MarkPopErrorOnReturn mark_pop_error_on_return;
if (cert_ == nullptr) return std::nullopt;
return InspectName(X509_get_subject_name(cert_));
}
std::optional<X509NameInspection> X509View::inspectIssuerName() const {
MarkPopErrorOnReturn mark_pop_error_on_return;
if (cert_ == nullptr) return std::nullopt;
return InspectName(X509_get_issuer_name(cert_));
}
std::optional<std::vector<X509RawExtension>> X509View::inspectExtensions()
const {
MarkPopErrorOnReturn mark_pop_error_on_return;
return InspectExtensions(cert_);
}
bool X509View::isIssuedBy(const X509View& issuer) const {
MarkPopErrorOnReturn mark_pop_error_on_return;
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 {
MarkPopErrorOnReturn mark_pop_error_on_return;
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 {
MarkPopErrorOnReturn mark_pop_error_on_return;
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 Buffer<const unsigned char>& ip,
int flags) const {
MarkPopErrorOnReturn mark_pop_error_on_return;
if (cert_ == nullptr) return CheckMatch::NO_MATCH;
if (ip.len != 4 && ip.len != 16) return CheckMatch::INVALID_NAME;
switch (X509_check_ip(const_cast<X509*>(cert_), ip.data, ip.len, 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;
{
MarkPopErrorOnReturn mark_pop_error_on_return;
eku.reset(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());
for (int i = 0; i < count; i++) {
std::optional<std::string> usage;
{
MarkPopErrorOnReturn mark_pop_error_on_return;
usage = ObjectToText(sk_ASN1_OBJECT_value(eku.get(), i), true);
}
if (!usage.has_value()) return false;
callback(usage->c_str());
}
return true;
}
bool X509View::ifRsa(KeyCallback<Rsa> callback) const {
if (cert_ == nullptr) return true;
OSSL3_CONST EVP_PKEY* pkey;
{
MarkPopErrorOnReturn mark_pop_error_on_return;
pkey = X509_get0_pubkey(cert_);
}
if (pkey == nullptr) return true;
if (EVPKeyPointer::isRsaVariant(pkey)) {
std::optional<Rsa> rsa;
{
MarkPopErrorOnReturn mark_pop_error_on_return;
#if NCRYPTO_USE_OPENSSL_PROVIDER
rsa.emplace(pkey);
#elif NCRYPTO_USE_BORINGSSL
rsa.emplace(EVP_PKEY_get0_RSA(pkey));
#else
#error "Unsupported crypto backend"
#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;
{
MarkPopErrorOnReturn mark_pop_error_on_return;
pkey = X509_get0_pubkey(cert_);
}
if (pkey == nullptr) return true;
if (EVPKeyPointer::isA(pkey, KeyAlgorithm::EC)) {
std::optional<Ec> ec;
{
MarkPopErrorOnReturn mark_pop_error_on_return;
#if NCRYPTO_USE_OPENSSL_PROVIDER
ec.emplace(pkey);
#elif NCRYPTO_USE_BORINGSSL
ec.emplace(EVP_PKEY_get0_EC_KEY(pkey));
#else
#error "Unsupported crypto backend"
#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_OPENSSL_PROVIDER
return X509Pointer(SSL_get1_peer_certificate(ssl.get()));
#elif NCRYPTO_USE_BORINGSSL
return X509Pointer(SSL_get_peer_certificate(ssl.get()));
#else
#error "Unsupported crypto backend"
#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());
}
bool BIOPointer::WriteAll(const BIOPointer& bio, std::string_view message) {
if (!bio) return false;
while (!message.empty()) {
const size_t chunk_size =
std::min(message.size(), static_cast<size_t>(INT_MAX));
const int length = static_cast<int>(chunk_size);
if (BIO_write(bio.get(), message.data(), length) != length) return false;
message.remove_prefix(chunk_size);
}
return true;
}
// ============================================================================
// DHPointer
namespace {
#if NCRYPTO_USE_OPENSSL_PROVIDER
const char* GetOpenSSLDhGroupName(const std::string_view name,
DHPointer::FindGroupOption option) {
if (option != DHPointer::FindGroupOption::NO_SMALL_PRIMES &&
CaseInsensitiveNameEqual()(name, "modp5")) {
return "modp_1536";
}
if (CaseInsensitiveNameEqual()(name, "modp14")) return "modp_2048";
if (CaseInsensitiveNameEqual()(name, "modp15")) return "modp_3072";
if (CaseInsensitiveNameEqual()(name, "modp16")) return "modp_4096";
if (CaseInsensitiveNameEqual()(name, "modp17")) return "modp_6144";
if (CaseInsensitiveNameEqual()(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 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_OPENSSL_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) {}
#elif NCRYPTO_USE_BORINGSSL
DHPointer::DHPointer(DH* dh) : dh_(dh) {}
#else
#error "Unsupported crypto backend"
#endif
DHPointer::DHPointer(DHPointer&& other) noexcept
#if NCRYPTO_USE_OPENSSL_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;
}
#elif NCRYPTO_USE_BORINGSSL
: dh_(other.release()) {
}
#else
#error "Unsupported crypto backend"
#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_OPENSSL_PROVIDER
EVP_PKEY* dh
#elif NCRYPTO_USE_BORINGSSL
DH* dh
#else
#error "Unsupported crypto backend"
#endif
) {
dh_.reset(dh);
#if NCRYPTO_USE_OPENSSL_PROVIDER
p_.reset();
g_.reset();
pub_key_.reset();
pvt_key_.reset();
group_name_ = nullptr;
#endif
}
#if NCRYPTO_USE_OPENSSL_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();
}
#elif NCRYPTO_USE_BORINGSSL
DH* DHPointer::release() {
return dh_.release();
}
#else
#error "Unsupported crypto backend"
#endif
BignumPointer DHPointer::FindGroup(const std::string_view name,
FindGroupOption option) {
#define V(n, p) \
if (CaseInsensitiveNameEqual()(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_OPENSSL_PROVIDER
const char* group_name = GetOpenSSLDhGroupName(name, option);
return DHPointer(std::move(group), std::move(generator), group_name);
#elif NCRYPTO_USE_BORINGSSL
return New(std::move(group), std::move(generator));
#else
#error "Unsupported crypto backend"
#endif
}
DHPointer DHPointer::New(BignumPointer&& p, BignumPointer&& g) {
if (!p || !g) return {};
#if NCRYPTO_USE_OPENSSL_PROVIDER
auto pkey = NewDhPKey(p.get(), g.get());
if (!pkey) return {};
return DHPointer(std::move(pkey));
#elif NCRYPTO_USE_BORINGSSL
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;
#else
#error "Unsupported crypto backend"
#endif
}
DHPointer DHPointer::New(size_t bits, unsigned int generator) {
#if NCRYPTO_USE_OPENSSL_PROVIDER
auto param_ctx = EVPKeyCtxPointer::NewFromAlgorithm(KeyAlgorithm::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));
#elif NCRYPTO_USE_BORINGSSL
DHPointer dh(DH_new());
if (!dh) return {};
if (DH_generate_parameters_ex(dh.get(), bits, generator, nullptr) != 1) {
return {};
}
return dh;
#else
#error "Unsupported crypto backend"
#endif
}
DHPointer::CheckResult DHPointer::check() {
ClearErrorOnReturn clearErrorOnReturn;
if (!*this) return DHPointer::CheckResult::NONE;
#if NCRYPTO_USE_OPENSSL_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;
// Like DH_check(), skip primality checks for groups OpenSSL recognizes from
// explicitly supplied parameters.
size_t group_name_len = 0;
if (dh_ &&
EVP_PKEY_get_group_name(dh_.get(), nullptr, 0, &group_name_len) == 1 &&
group_name_len > 0) {
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);
#elif NCRYPTO_USE_BORINGSSL
int codes = 0;
if (DH_check(dh_.get(), &codes) != 1)
return DHPointer::CheckResult::CHECK_FAILED;
return static_cast<CheckResult>(codes);
#else
#error "Unsupported crypto backend"
#endif
}
DHPointer::CheckPublicKeyResult DHPointer::checkPublicKey(
const BignumPointer& pub_key) {
ClearErrorOnReturn clearErrorOnReturn;
if (!pub_key || !*this) {
return DHPointer::CheckPublicKeyResult::CHECK_FAILED;
}
#if NCRYPTO_USE_OPENSSL_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;
#elif NCRYPTO_USE_BORINGSSL
int codes = 0;
if (DH_check_pub_key(dh_.get(), pub_key.get(), &codes) != 1) {
return DHPointer::CheckPublicKeyResult::CHECK_FAILED;
}
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;
}
if (codes != 0) {
return DHPointer::CheckPublicKeyResult::INVALID;
}
return CheckPublicKeyResult::NONE;
#else
#error "Unsupported crypto backend"
#endif
}
DataPointer DHPointer::getPrime() const {
if (!*this) return {};
#if NCRYPTO_USE_OPENSSL_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());
#elif NCRYPTO_USE_BORINGSSL
const BIGNUM* p;
DH_get0_pqg(dh_.get(), &p, nullptr, nullptr);
return BignumPointer::Encode(p);
#else
#error "Unsupported crypto backend"
#endif
}
size_t DHPointer::getPrimeBits() const {
if (!*this) return 0;
#if NCRYPTO_USE_OPENSSL_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());
#elif NCRYPTO_USE_BORINGSSL
const BIGNUM* p;
DH_get0_pqg(dh_.get(), &p, nullptr, nullptr);
return BignumPointer::GetBitCount(p);
#else
#error "Unsupported crypto backend"
#endif
}
DataPointer DHPointer::getGenerator() const {
if (!*this) return {};
#if NCRYPTO_USE_OPENSSL_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());
#elif NCRYPTO_USE_BORINGSSL
const BIGNUM* g;
DH_get0_pqg(dh_.get(), nullptr, nullptr, &g);
return BignumPointer::Encode(g);
#else
#error "Unsupported crypto backend"
#endif
}
DataPointer DHPointer::getPublicKey() const {
if (!*this) return {};
#if NCRYPTO_USE_OPENSSL_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());
#elif NCRYPTO_USE_BORINGSSL
const BIGNUM* pub_key;
DH_get0_key(dh_.get(), &pub_key, nullptr);
return BignumPointer::Encode(pub_key);
#else
#error "Unsupported crypto backend"
#endif
}
DataPointer DHPointer::getPrivateKey() const {
if (!*this) return {};
#if NCRYPTO_USE_OPENSSL_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());
#elif NCRYPTO_USE_BORINGSSL
const BIGNUM* pvt_key;
DH_get0_key(dh_.get(), nullptr, &pvt_key);
return BignumPointer::Encode(pvt_key);
#else
#error "Unsupported crypto backend"
#endif
}
bool DHPointer::hasPrivateKey() const {
if (!*this) return false;
#if NCRYPTO_USE_OPENSSL_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;
#elif NCRYPTO_USE_BORINGSSL
const BIGNUM* pvt_key = nullptr;
DH_get0_key(dh_.get(), nullptr, &pvt_key);
return pvt_key != nullptr;
#else
#error "Unsupported crypto backend"
#endif
}
DataPointer DHPointer::generateKeys() {
ClearErrorOnReturn clearErrorOnReturn;
if (!*this) return {};
#if NCRYPTO_USE_OPENSSL_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();
#elif NCRYPTO_USE_BORINGSSL
// Key generation failed
if (!DH_generate_key(dh_.get())) return {};
return getPublicKey();
#else
#error "Unsupported crypto backend"
#endif
}
size_t DHPointer::size() const {
if (!*this) return 0;
#if NCRYPTO_USE_OPENSSL_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;
#elif NCRYPTO_USE_BORINGSSL
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;
#else
#error "Unsupported crypto backend"
#endif
}
DataPointer DHPointer::computeSecret(const BignumPointer& peer) const {
ClearErrorOnReturn clearErrorOnReturn;
if (!*this || !peer) return {};
#if NCRYPTO_USE_OPENSSL_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);
#elif NCRYPTO_USE_BORINGSSL
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;
#else
#error "Unsupported crypto backend"
#endif
}
bool DHPointer::setPublicKey(BignumPointer&& key) {
if (!*this) return false;
#if NCRYPTO_USE_OPENSSL_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;
#elif NCRYPTO_USE_BORINGSSL
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;
#else
#error "Unsupported crypto backend"
#endif
}
bool DHPointer::setPrivateKey(BignumPointer&& key) {
if (!*this) return false;
#if NCRYPTO_USE_OPENSSL_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;
#elif NCRYPTO_USE_BORINGSSL
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;
#else
#error "Unsupported crypto backend"
#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 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
#if NCRYPTO_USE_OPENSSL_PROVIDER
KDF::KDF(EVP_KDF* kdf) : kdf_(kdf) {}
KDF KDF::Fetch(const char* algorithm, OSSL_LIB_CTX* libctx) {
return KDF(EVP_KDF_fetch(libctx, algorithm, nullptr));
}
bool KDF::derive(const Buffer<unsigned char>& out,
const OSSL_PARAM* params) const {
if (!kdf_) return false;
DeleteFnPtr<EVP_KDF_CTX, EVP_KDF_CTX_free> ctx(EVP_KDF_CTX_new(kdf_.get()));
return ctx && EVP_KDF_derive(ctx.get(), out.data, out.len, params) == 1;
}
#endif
const EVP_MD* getDigestByName(const char* name) {
#if NCRYPTO_USE_BORINGSSL
// BoringSSL exposes SHA-3 through EVP_MD descriptors without registering
// their names or assigning them NIDs.
if (CaseInsensitiveNameEqual()(name, "sha3-256")) return EVP_sha3_256();
if (CaseInsensitiveNameEqual()(name, "sha3-384")) return EVP_sha3_384();
if (CaseInsensitiveNameEqual()(name, "sha3-512")) return EVP_sha3_512();
#endif
return EVP_get_digestbyname(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 {};
}
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())};
}
// Keep extraction as a one-shot HMAC because BoringSSL's EVP_PKEY_derive
// rejects the zero-length keys Web Crypto allows. Both backends expand a
// pseudorandom key of exactly one digest block.
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 {};
}
auto buf = DataPointer::Alloc(length);
if (!buf) return {};
#if NCRYPTO_USE_OPENSSL_PROVIDER
// Expand through EVP_KDF directly. The EVP_PKEY_HKDF interface reaches the
// same provider implementation, but only after allocating a second context
// and translating every parameter across the legacy bridge.
auto kdf = KDF::Fetch(OSSL_KDF_NAME_HKDF);
if (!kdf) return {};
const char* md_name = EVP_MD_get0_name(md);
if (md_name == nullptr) return {};
int mode = EVP_KDF_HKDF_MODE_EXPAND_ONLY;
std::array<OSSL_PARAM, 5> params;
size_t n = 0;
params[n++] = OSSL_PARAM_construct_int(OSSL_KDF_PARAM_MODE, &mode);
params[n++] = OSSL_PARAM_construct_utf8_string(
OSSL_KDF_PARAM_DIGEST, const_cast<char*>(md_name), 0);
params[n++] = OSSL_PARAM_construct_octet_string(
OSSL_KDF_PARAM_KEY, pseudorandom_key, pseudorandom_key_len);
if (info.len > 0) {
params[n++] = OSSL_PARAM_construct_octet_string(
OSSL_KDF_PARAM_INFO, const_cast<unsigned char*>(info.data), info.len);
}
params[n++] = OSSL_PARAM_construct_end();
if (!kdf.derive({buf.get<unsigned char>(), length}, params.data())) {
return {};
}
#elif NCRYPTO_USE_BORINGSSL
auto ctx = EVPKeyCtxPointer::NewFromName("HKDF");
if (!ctx || !EVP_PKEY_derive_init(ctx.get()) ||
!EVP_PKEY_CTX_set_hkdf_md(ctx.get(), md) ||
!EVP_PKEY_CTX_add1_hkdf_info(ctx.get(), info.data, info.len) ||
!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 {};
}
if (EVP_PKEY_derive(
ctx.get(), static_cast<unsigned char*>(buf.get()), &length) <= 0) {
return {};
}
#else
#error "Unsupported crypto backend"
#endif
return buf;
}
#if NCRYPTO_USE_OPENSSL_PROVIDER
namespace {
bool ScryptDerive(const Buffer<const char>& pass,
const Buffer<const unsigned char>& salt,
uint64_t N,
uint64_t r,
uint64_t p,
uint64_t maxmem,
unsigned char* out,
size_t length) {
// EVP_PBE_scrypt limits these parameters to the provider's declared width.
if (r > UINT32_MAX || p > UINT32_MAX) {
ERR_raise(ERR_LIB_EVP, EVP_R_PARAMETER_TOO_LARGE);
return false;
}
// Keep EVP_PBE_scrypt's 32 MiB default instead of the provider's default.
if (maxmem == 0) maxmem = 32 * 1024 * 1024;
auto kdf = KDF::Fetch(OSSL_KDF_NAME_SCRYPT);
if (!kdf) return false;
unsigned char empty_salt = 0;
OSSL_PARAM params[] = {
OSSL_PARAM_construct_octet_string(
OSSL_KDF_PARAM_PASSWORD,
const_cast<char*>(pass.data == nullptr ? "" : pass.data),
pass.data == nullptr ? 0 : pass.len),
OSSL_PARAM_construct_octet_string(
OSSL_KDF_PARAM_SALT,
salt.data == nullptr ? &empty_salt
: const_cast<unsigned char*>(salt.data),
salt.data == nullptr ? 0 : salt.len),
OSSL_PARAM_construct_uint64(OSSL_KDF_PARAM_SCRYPT_N, &N),
OSSL_PARAM_construct_uint64(OSSL_KDF_PARAM_SCRYPT_R, &r),
OSSL_PARAM_construct_uint64(OSSL_KDF_PARAM_SCRYPT_P, &p),
OSSL_PARAM_construct_uint64(OSSL_KDF_PARAM_SCRYPT_MAXMEM, &maxmem),
OSSL_PARAM_END,
};
return kdf.derive({out, length}, params);
}
} // namespace
#endif
bool checkScryptParams(uint64_t N, uint64_t r, uint64_t p, uint64_t maxmem) {
#if NCRYPTO_USE_OPENSSL_PROVIDER
// A null output validates the parameters without deriving a key.
return ScryptDerive({nullptr, 0}, {nullptr, 0}, N, r, p, maxmem, nullptr, 0);
#elif NCRYPTO_USE_BORINGSSL
return EVP_PBE_scrypt(nullptr, 0, nullptr, 0, N, r, p, maxmem, nullptr, 0) ==
1;
#else
#error "Unsupported crypto backend"
#endif
}
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 NCRYPTO_USE_OPENSSL_PROVIDER
if (dp && ScryptDerive(
pass, salt, N, r, p, maxmem, dp.get<unsigned char>(), length)) {
#elif NCRYPTO_USE_BORINGSSL
if (dp && EVP_PBE_scrypt(pass.data,
pass.len,
salt.data,
salt.len,
N,
r,
p,
maxmem,
reinterpret_cast<unsigned char*>(dp.get()),
length)) {
#else
#error "Unsupported crypto backend"
#endif
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);
#if NCRYPTO_USE_OPENSSL_PROVIDER
if (!dp) return {};
auto kdf = KDF::Fetch(OSSL_KDF_NAME_PBKDF2);
if (!kdf) return {};
const char* md_name = EVP_MD_get0_name(md);
if (md_name == nullptr) return {};
#if OPENSSL_VERSION_MAJOR < 4
// Match PKCS5_PBKDF2_HMAC: OpenSSL 3 disables the provider's lower bounds,
// while OpenSSL 4 leaves them at their provider defaults.
int pkcs5 = 1;
#endif
int iteration_count = static_cast<int>(iterations);
unsigned char empty_salt = 0;
OSSL_PARAM params[] = {
OSSL_PARAM_construct_octet_string(
OSSL_KDF_PARAM_PASSWORD,
const_cast<char*>(pass.data == nullptr ? "" : pass.data),
pass.data == nullptr ? 0 : pass.len),
#if OPENSSL_VERSION_MAJOR < 4
OSSL_PARAM_construct_int(OSSL_KDF_PARAM_PKCS5, &pkcs5),
#endif
OSSL_PARAM_construct_octet_string(
OSSL_KDF_PARAM_SALT,
salt.data == nullptr && salt.len == 0
? &empty_salt
: const_cast<unsigned char*>(salt.data),
salt.len),
OSSL_PARAM_construct_int(OSSL_KDF_PARAM_ITER, &iteration_count),
OSSL_PARAM_construct_utf8_string(
OSSL_KDF_PARAM_DIGEST, const_cast<char*>(md_name), 0),
OSSL_PARAM_END,
};
if (kdf.derive({dp.get<unsigned char>(), length}, params)) {
return dp;
}
#elif NCRYPTO_USE_BORINGSSL
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;
}
#else
#error "Unsupported crypto backend"
#endif
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;
uint32_t threads = lanes == 0 ? 0 : 1;
if (lanes > 1 && (OSSL_get_thread_support_flags() &
OSSL_THREAD_SUPPORT_FLAG_DEFAULT_SPAWN) != 0) {
if (!KDF::Fetch(algorithm.data())) {
return {};
}
ctx.reset(OSSL_LIB_CTX_new());
if (!ctx) {
return {};
}
MarkPopErrorOnReturn mark_pop_error_on_return;
if (OSSL_set_max_threads(ctx.get(), lanes) == 1) {
threads = lanes;
} else {
// Lane count is an Argon2 input; worker threads are only an
// optimization. Compute the same lanes serially if unavailable.
ctx.reset();
}
}
auto kdf = KDF::Fetch(algorithm.data(), ctx.get());
if (!kdf) {
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, &threads));
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 && kdf.derive({dp.get<unsigned char>(), length}, params.data())) {
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);
}
// clang-format off
// NOLINTBEGIN(whitespace/line_length)
const KeyAlgorithm KeyAlgorithm::RSA("RSA", Family::Other);
const KeyAlgorithm KeyAlgorithm::RSA_PSS("RSA-PSS", Family::Other);
const KeyAlgorithm KeyAlgorithm::DSA("DSA", Family::Other);
const KeyAlgorithm KeyAlgorithm::DH("DH", Family::Other);
const KeyAlgorithm KeyAlgorithm::EC("EC", Family::Other);
const KeyAlgorithm KeyAlgorithm::ED25519("Ed25519", Family::EdDSA);
const KeyAlgorithm KeyAlgorithm::ED448("Ed448", Family::EdDSA);
const KeyAlgorithm KeyAlgorithm::X25519("X25519", Family::XDH);
const KeyAlgorithm KeyAlgorithm::X448("X448", Family::XDH);
const KeyAlgorithm KeyAlgorithm::SM2("SM2", Family::Other, /* has_key_type */ false);
const KeyAlgorithm KeyAlgorithm::ML_DSA_44("ML-DSA-44", Family::MLDSA);
const KeyAlgorithm KeyAlgorithm::ML_DSA_65("ML-DSA-65", Family::MLDSA);
const KeyAlgorithm KeyAlgorithm::ML_DSA_87("ML-DSA-87", Family::MLDSA);
const KeyAlgorithm KeyAlgorithm::ML_KEM_512("ML-KEM-512", Family::MLKEM);
const KeyAlgorithm KeyAlgorithm::ML_KEM_768("ML-KEM-768", Family::MLKEM);
const KeyAlgorithm KeyAlgorithm::ML_KEM_1024("ML-KEM-1024", Family::MLKEM);
const KeyAlgorithm KeyAlgorithm::SLH_DSA_SHA2_128F("SLH-DSA-SHA2-128f", Family::SLHDSA);
const KeyAlgorithm KeyAlgorithm::SLH_DSA_SHA2_128S("SLH-DSA-SHA2-128s", Family::SLHDSA);
const KeyAlgorithm KeyAlgorithm::SLH_DSA_SHA2_192F("SLH-DSA-SHA2-192f", Family::SLHDSA);
const KeyAlgorithm KeyAlgorithm::SLH_DSA_SHA2_192S("SLH-DSA-SHA2-192s", Family::SLHDSA);
const KeyAlgorithm KeyAlgorithm::SLH_DSA_SHA2_256F("SLH-DSA-SHA2-256f", Family::SLHDSA);
const KeyAlgorithm KeyAlgorithm::SLH_DSA_SHA2_256S("SLH-DSA-SHA2-256s", Family::SLHDSA);
const KeyAlgorithm KeyAlgorithm::SLH_DSA_SHAKE_128F("SLH-DSA-SHAKE-128f", Family::SLHDSA);
const KeyAlgorithm KeyAlgorithm::SLH_DSA_SHAKE_128S("SLH-DSA-SHAKE-128s", Family::SLHDSA);
const KeyAlgorithm KeyAlgorithm::SLH_DSA_SHAKE_192F("SLH-DSA-SHAKE-192f", Family::SLHDSA);
const KeyAlgorithm KeyAlgorithm::SLH_DSA_SHAKE_192S("SLH-DSA-SHAKE-192s", Family::SLHDSA);
const KeyAlgorithm KeyAlgorithm::SLH_DSA_SHAKE_256F("SLH-DSA-SHAKE-256f", Family::SLHDSA);
const KeyAlgorithm KeyAlgorithm::SLH_DSA_SHAKE_256S("SLH-DSA-SHAKE-256s", Family::SLHDSA);
// NOLINTEND(whitespace/line_length)
// clang-format on
namespace {
#if NCRYPTO_USE_OPENSSL_PROVIDER
constexpr char kSignatureContextString[] = "context-string";
constexpr char kSignatureInstance[] = "instance";
#endif
const KeyAlgorithm* const kKeyAlgorithms[] = {
&KeyAlgorithm::RSA,
&KeyAlgorithm::RSA_PSS,
&KeyAlgorithm::DSA,
&KeyAlgorithm::DH,
&KeyAlgorithm::EC,
&KeyAlgorithm::ED25519,
&KeyAlgorithm::ED448,
&KeyAlgorithm::X25519,
&KeyAlgorithm::X448,
&KeyAlgorithm::SM2,
&KeyAlgorithm::ML_DSA_44,
&KeyAlgorithm::ML_DSA_65,
&KeyAlgorithm::ML_DSA_87,
&KeyAlgorithm::ML_KEM_512,
&KeyAlgorithm::ML_KEM_768,
&KeyAlgorithm::ML_KEM_1024,
&KeyAlgorithm::SLH_DSA_SHA2_128F,
&KeyAlgorithm::SLH_DSA_SHA2_128S,
&KeyAlgorithm::SLH_DSA_SHA2_192F,
&KeyAlgorithm::SLH_DSA_SHA2_192S,
&KeyAlgorithm::SLH_DSA_SHA2_256F,
&KeyAlgorithm::SLH_DSA_SHA2_256S,
&KeyAlgorithm::SLH_DSA_SHAKE_128F,
&KeyAlgorithm::SLH_DSA_SHAKE_128S,
&KeyAlgorithm::SLH_DSA_SHAKE_192F,
&KeyAlgorithm::SLH_DSA_SHAKE_192S,
&KeyAlgorithm::SLH_DSA_SHAKE_256F,
&KeyAlgorithm::SLH_DSA_SHAKE_256S,
};
} // namespace
const KeyAlgorithm* KeyAlgorithm::FromName(const char* name) {
if (name == nullptr) return nullptr;
for (const auto* algorithm : kKeyAlgorithms) {
if (CaseInsensitiveNameEqual()(name, algorithm->name())) return algorithm;
}
return nullptr;
}
bool KeyAlgorithm::isRsa() const {
return this == &RSA || this == &RSA_PSS;
}
bool KeyAlgorithm::isPqc() const {
return family_ == Family::MLDSA || family_ == Family::MLKEM ||
family_ == Family::SLHDSA;
}
bool KeyAlgorithm::isOkp() const {
return family_ == Family::EdDSA || family_ == Family::XDH;
}
bool KeyAlgorithm::isOneShot() const {
return family_ == Family::EdDSA || family_ == Family::MLDSA ||
family_ == Family::SLHDSA;
}
bool KeyAlgorithm::supportsRawPublic() const {
return isOkp() || isPqc();
}
bool KeyAlgorithm::supportsRawPrivate() const {
return isOkp() || family_ == Family::SLHDSA;
}
size_t KeyAlgorithm::seedSize() const {
if (family_ == Family::MLDSA) return 32;
if (family_ == Family::MLKEM) return 64;
return 0;
}
namespace {
#if NCRYPTO_USE_BORINGSSL
struct BoringSSLKeyAlgorithm {
const char* name;
int id;
const EVP_PKEY_ALG* (*raw_key_algorithm)() = nullptr;
};
// BoringSSL requires native key IDs and EVP_PKEY_ALG descriptors for raw keys;
// keep both adapters in the same table.
// clang-format off
// NOLINTBEGIN(whitespace/line_length)
const BoringSSLKeyAlgorithm kBoringSSLKeyAlgorithms[] = {
{KeyAlgorithm::RSA.name(), EVP_PKEY_RSA},
{KeyAlgorithm::RSA_PSS.name(), EVP_PKEY_RSA_PSS},
{KeyAlgorithm::DSA.name(), EVP_PKEY_DSA},
{KeyAlgorithm::DH.name(), EVP_PKEY_DH},
{KeyAlgorithm::EC.name(), EVP_PKEY_EC},
{KeyAlgorithm::ED25519.name(), EVP_PKEY_ED25519, EVP_pkey_ed25519},
{KeyAlgorithm::X25519.name(), EVP_PKEY_X25519, EVP_pkey_x25519},
{"HKDF", EVP_PKEY_HKDF},
{KeyAlgorithm::ED448.name(), EVP_PKEY_ED448},
{KeyAlgorithm::X448.name(), EVP_PKEY_X448},
{KeyAlgorithm::ML_DSA_44.name(), EVP_PKEY_ML_DSA_44, EVP_pkey_ml_dsa_44},
{KeyAlgorithm::ML_DSA_65.name(), EVP_PKEY_ML_DSA_65, EVP_pkey_ml_dsa_65},
{KeyAlgorithm::ML_DSA_87.name(), EVP_PKEY_ML_DSA_87, EVP_pkey_ml_dsa_87},
{KeyAlgorithm::ML_KEM_768.name(), EVP_PKEY_ML_KEM_768, EVP_pkey_ml_kem_768},
{KeyAlgorithm::ML_KEM_1024.name(), EVP_PKEY_ML_KEM_1024, EVP_pkey_ml_kem_1024},
};
// NOLINTEND(whitespace/line_length)
// clang-format on
const BoringSSLKeyAlgorithm* FindBoringSSLKeyAlgorithm(const char* name) {
if (name == nullptr) return nullptr;
for (const auto& algorithm : kBoringSSLKeyAlgorithms) {
if (CaseInsensitiveNameEqual()(name, algorithm.name)) return &algorithm;
}
return nullptr;
}
int GetBoringSSLKeyId(const char* name) {
const auto* algorithm = FindBoringSSLKeyAlgorithm(name);
return algorithm == nullptr ? NID_undef : algorithm->id;
}
const EVP_PKEY_ALG* GetBoringSSLKeyAlgorithm(const KeyAlgorithm& algorithm) {
const auto* entry = FindBoringSSLKeyAlgorithm(algorithm.name());
return entry != nullptr && entry->raw_key_algorithm != nullptr
? entry->raw_key_algorithm()
: nullptr;
}
#endif
} // namespace
void ConfigurePqcEncoding() {
#if NCRYPTO_USE_OPENSSL_PROVIDER && OPENSSL_VERSION_PREREQ(3, 5)
// Configure all loaded providers to prefer seed-only format for ML-KEM and
// ML-DSA private keys in PKCS#8 export, falling back to priv-only when a
// seed is not available. The provider encoder reads these parameters at
// encoding time via ossl_prov_ctx_get_param().
OSSL_PROVIDER_do_all(
nullptr,
[](OSSL_PROVIDER* provider, void*) -> int {
OSSL_PROVIDER_add_conf_parameter(
provider, "ml-kem.output_formats", "seed-only,priv-only");
OSSL_PROVIDER_add_conf_parameter(
provider, "ml-dsa.output_formats", "seed-only,priv-only");
return 1;
},
nullptr);
#endif
}
bool KeyAlgorithm::isAvailable() const {
MarkPopErrorOnReturn mark_pop_error_on_return;
return static_cast<bool>(EVPKeyCtxPointer::NewFromName(name_));
}
EVPKeyPointer EVPKeyPointer::New() {
return EVPKeyPointer(EVP_PKEY_new());
}
EVPKeyPointer EVPKeyPointer::NewRawPublic(
const KeyAlgorithm& algorithm, const Buffer<const unsigned char>& data) {
#if NCRYPTO_USE_OPENSSL_PROVIDER
return EVPKeyPointer(EVP_PKEY_new_raw_public_key_ex(
nullptr, algorithm.name(), nullptr, data.data, data.len));
#elif NCRYPTO_USE_BORINGSSL
const auto* alg = GetBoringSSLKeyAlgorithm(algorithm);
if (alg == nullptr) return {};
return EVPKeyPointer(EVP_PKEY_from_raw_public_key(alg, data.data, data.len));
#else
#error "Unsupported crypto backend"
#endif
}
EVPKeyPointer EVPKeyPointer::NewRawPrivate(
const KeyAlgorithm& algorithm, const Buffer<const unsigned char>& data) {
#if NCRYPTO_USE_OPENSSL_PROVIDER
return EVPKeyPointer(EVP_PKEY_new_raw_private_key_ex(
nullptr, algorithm.name(), nullptr, data.data, data.len));
#elif NCRYPTO_USE_BORINGSSL
const auto* alg = GetBoringSSLKeyAlgorithm(algorithm);
if (alg == nullptr) return {};
return EVPKeyPointer(EVP_PKEY_from_raw_private_key(alg, data.data, data.len));
#else
#error "Unsupported crypto backend"
#endif
}
EVPKeyPointer EVPKeyPointer::NewRawSeed(
const KeyAlgorithm& algorithm, const Buffer<const unsigned char>& data) {
if (algorithm.seedSize() == 0) return {};
#if NCRYPTO_USE_BORINGSSL
const auto* seed_alg = GetBoringSSLKeyAlgorithm(algorithm);
if (seed_alg == nullptr) return {};
return EVPKeyPointer(
EVP_PKEY_from_private_seed(seed_alg, data.data, data.len));
#elif NCRYPTO_USE_OPENSSL_PROVIDER
// ML-DSA and ML-KEM both use the provider parameter "seed".
OSSL_PARAM params[] = {
OSSL_PARAM_construct_octet_string(
"seed", const_cast<unsigned char*>(data.data), data.len),
OSSL_PARAM_END};
auto ctx = EVPKeyCtxPointer::NewFromAlgorithm(algorithm);
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);
#else
#error "Unsupported crypto backend"
#endif
}
EVPKeyPointer EVPKeyPointer::NewDH(DHPointer&& dh) {
if (!dh) return {};
#if NCRYPTO_USE_OPENSSL_PROVIDER
return EVPKeyPointer(dh.release());
#elif NCRYPTO_USE_BORINGSSL
auto key = New();
if (!key) return {};
if (EVP_PKEY_assign_DH(key.get(), dh.get())) {
dh.release();
}
return key;
#else
#error "Unsupported crypto backend"
#endif
}
#if NCRYPTO_USE_OPENSSL_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 {};
}
const auto other_prime_infos = rsa.getOtherPrimeInfos();
if (other_prime_infos.size() > kRsaOtherPrimeParamNames.size()) return {};
for (size_t i = 0; i < other_prime_infos.size(); i++) {
const auto& info = other_prime_infos[i];
const auto& names = kRsaOtherPrimeParamNames[i];
if (info.r == nullptr || info.d == nullptr || info.t == nullptr ||
OSSL_PARAM_BLD_push_BN(bld.get(), names.factor, info.r) != 1 ||
OSSL_PARAM_BLD_push_BN(bld.get(), names.exponent, info.d) != 1 ||
OSSL_PARAM_BLD_push_BN(bld.get(), names.coefficient, info.t) != 1) {
return {};
}
}
selection = EVP_PKEY_KEYPAIR;
}
OSSLParamPointer params(OSSL_PARAM_BLD_to_param(bld.get()));
if (!params) return {};
return NewPKeyFromData(KeyAlgorithm::RSA, selection, params.get());
}
#elif NCRYPTO_USE_BORINGSSL
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;
}
#else
#error "Unsupported crypto backend"
#endif // NCRYPTO_USE_OPENSSL_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();
}
bool EVPKeyPointer::isA(const EVP_PKEY* key, const char* name) {
if (key == nullptr || name == nullptr) return false;
#if NCRYPTO_USE_OPENSSL_PROVIDER
// EVP_PKEY_is_a() can match an untyped key to an unknown legacy name.
return EVP_PKEY_get0_type_name(key) != nullptr &&
EVP_PKEY_is_a(key, name) == 1;
#elif NCRYPTO_USE_BORINGSSL
const int id = GetBoringSSLKeyId(name);
return id != NID_undef && EVP_PKEY_id(key) == id;
#else
#error "Unsupported crypto backend"
#endif
}
// Returns true unless the key is known not to be SM2, so that a key whose curve
// cannot be determined opts out of the prehashed fallback rather than into it.
bool EVPKeyPointer::mayBeSM2() const {
#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_NO_SM2)
return false;
#else
if (isA(KeyAlgorithm::SM2)) return true;
if (!isA(KeyAlgorithm::EC)) return false;
// An ECKeyPointer would also need the public point, which a provider-backed
// key need not expose.
char group_name[64];
size_t group_name_len = 0;
if (EVP_PKEY_get_utf8_string_param(get(),
OSSL_PKEY_PARAM_GROUP_NAME,
group_name,
sizeof(group_name),
&group_name_len) != 1) {
return true;
}
return OBJ_sn2nid(group_name) == NID_sm2 ||
EC_curve_nist2nid(group_name) == NID_sm2;
#endif
}
bool EVPKeyPointer::isA(const char* name) const {
return isA(get(), name);
}
bool EVPKeyPointer::isA(const EVP_PKEY* key, const KeyAlgorithm& algorithm) {
return isA(key, algorithm.name());
}
bool EVPKeyPointer::isA(const KeyAlgorithm& algorithm) const {
return isA(get(), algorithm);
}
const KeyAlgorithm* EVPKeyPointer::getAlgorithm() const {
if (!pkey_) return nullptr;
#if NCRYPTO_USE_OPENSSL_PROVIDER
// Provider primary names identify algorithms. Legacy ASN.1 methods can
// share names (for example, SM2 uses EC), so resolve those through isA().
// The fallback also handles providers with a noncanonical primary alias.
if (EVP_PKEY_get0_provider(get()) != nullptr) {
if (const auto* algorithm =
KeyAlgorithm::FromName(EVP_PKEY_get0_type_name(get()))) {
return algorithm;
}
}
for (const auto* algorithm : kKeyAlgorithms) {
if (isA(*algorithm)) return algorithm;
}
#elif NCRYPTO_USE_BORINGSSL
const int id = EVP_PKEY_id(get());
for (const auto& algorithm : kBoringSSLKeyAlgorithms) {
if (id == algorithm.id) return KeyAlgorithm::FromName(algorithm.name);
}
#else
#error "Unsupported crypto backend"
#endif
return nullptr;
}
const char* EVPKeyPointer::getKeyTypeName() const {
const auto* algorithm = getAlgorithm();
return algorithm == nullptr ? nullptr : algorithm->keyTypeName();
}
bool EVPKeyPointer::supportsRawPublic() const {
const auto* algorithm = getAlgorithm();
return algorithm != nullptr && algorithm->supportsRawPublic();
}
bool EVPKeyPointer::supportsRawPrivate() const {
const auto* algorithm = getAlgorithm();
return algorithm != nullptr && algorithm->supportsRawPrivate();
}
bool EVPKeyPointer::supportsContextString() const {
const auto* algorithm = getAlgorithm();
if (algorithm == nullptr || !algorithm->isOneShot()) return false;
#if NCRYPTO_USE_OPENSSL_PROVIDER
MarkPopErrorOnReturn mark_pop_error_on_return;
DeleteFnPtr<EVP_SIGNATURE, EVP_SIGNATURE_free> signature(
EVP_SIGNATURE_fetch(nullptr, EVP_PKEY_get0_type_name(get()), nullptr));
if (!signature) return false;
const OSSL_PARAM* params = EVP_SIGNATURE_settable_ctx_params(signature.get());
return params != nullptr &&
OSSL_PARAM_locate_const(params, kSignatureContextString) != nullptr &&
(algorithm != &KeyAlgorithm::ED25519 ||
OSSL_PARAM_locate_const(params, kSignatureInstance) != nullptr);
#elif NCRYPTO_USE_BORINGSSL
return algorithm->isPqc();
#else
#error "Unsupported crypto backend"
#endif
}
namespace {
constexpr size_t kEd25519PointSize = 32;
constexpr size_t kEd448PointSize = 57;
// Ed25519 has cofactor 8, so the first eight entries are the full
// canonical small-order subgroup: identity, one point of order 2,
// two points of order 4, and four points of order 8.
constexpr unsigned char kEd25519SmallOrderPoints[][kEd25519PointSize] = {
// Identity.
{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
// Order 2.
{0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f},
// Order 4.
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80},
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
// Order 8.
{0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b,
0x76, 0x0d, 0x10, 0x67, 0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39,
0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0x7a},
{0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b,
0x76, 0x0d, 0x10, 0x67, 0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39,
0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0xfa},
{0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4,
0x89, 0xf2, 0xef, 0x98, 0xf0, 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6,
0x33, 0x39, 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x05},
{0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4,
0x89, 0xf2, 0xef, 0x98, 0xf0, 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6,
0x33, 0x39, 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x85},
// Non-canonical encodings of the same small-order points.
{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80},
{0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
{0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f},
{0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
{0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
{0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f},
};
// Ed448 has cofactor 4, so these four entries are the full canonical
// small-order subgroup: identity, one point of order 2, and two points
// of order 4.
constexpr unsigned char kEd448SmallOrderPoints[][kEd448PointSize] = {
// Identity.
{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
// Order 2.
{0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00},
// Order 4.
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80},
};
template <size_t PointSize, size_t Count>
bool ContainsPoint(const unsigned char* candidate,
const unsigned char (&points)[Count][PointSize]) {
for (const auto& point : points) {
if (memcmp(candidate, point, PointSize) == 0) return true;
}
return false;
}
bool IsSmallOrderEdDsaPoint(const EVPKeyPointer& key,
const unsigned char* candidate,
size_t size) {
if (key.isA(KeyAlgorithm::ED25519)) {
return size == kEd25519PointSize &&
ContainsPoint(candidate, kEd25519SmallOrderPoints);
}
if (key.isA(KeyAlgorithm::ED448)) {
return size == kEd448PointSize &&
ContainsPoint(candidate, kEd448SmallOrderPoints);
}
return false;
}
} // namespace
bool EVPKeyPointer::hasSmallOrderEdDsaPoint(
const Buffer<const unsigned char>& signature) const {
const size_t point_size = isA(KeyAlgorithm::ED25519) ? kEd25519PointSize
: isA(KeyAlgorithm::ED448) ? kEd448PointSize
: 0;
if (point_size == 0) return false;
if (signature.len != point_size * 2) return false;
if (IsSmallOrderEdDsaPoint(*this, signature.data, point_size)) {
return true;
}
unsigned char raw_public_key[kEd448PointSize];
size_t raw_public_key_size = point_size;
if (EVP_PKEY_get_raw_public_key(
get(), raw_public_key, &raw_public_key_size) != 1) {
return false;
}
return IsSmallOrderEdDsaPoint(*this, raw_public_key, raw_public_key_size);
}
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 {};
}
namespace {
DataPointer GetRawSeed(EVP_PKEY* key, size_t seed_len) {
auto data = DataPointer::Alloc(seed_len);
if (!data) return {};
const Buffer<unsigned char> buf = data;
size_t len = data.size();
#if NCRYPTO_USE_BORINGSSL
if (EVP_PKEY_get_private_seed(key, buf.data, &len) != 1) return {};
#elif NCRYPTO_USE_OPENSSL_PROVIDER
if (EVP_PKEY_get_octet_string_param(key, "seed", buf.data, buf.len, &len) !=
1)
return {};
#else
#error "Unsupported crypto backend"
#endif
return data;
}
} // namespace
Result<DataPointer, EVPKeyPointer::RawExportError> EVPKeyPointer::rawSeed()
const {
const auto* algorithm = getAlgorithm();
if (algorithm == nullptr || algorithm->seedSize() == 0) {
return RawExportError::UNSUPPORTED_KEY_TYPE;
}
auto data = GetRawSeed(get(), algorithm->seedSize());
if (!data) return RawExportError::MISSING_SEED;
return data;
}
Result<EVPKeyPointer::RawJwkData, EVPKeyPointer::RawExportError>
EVPKeyPointer::exportRawJwk(bool include_private) const {
const auto* algorithm = getAlgorithm();
if (algorithm == nullptr || (!algorithm->isOkp() && !algorithm->isPqc())) {
return RawExportError::UNSUPPORTED_KEY_TYPE;
}
RawJwkData data{algorithm, {}, {}};
if (include_private) {
const size_t seed_len = algorithm->seedSize();
data.private_key =
seed_len != 0 ? GetRawSeed(get(), seed_len) : rawPrivateKey();
if (!data.private_key) {
return seed_len != 0 ? RawExportError::MISSING_SEED
: RawExportError::FAILED;
}
}
data.public_key = rawPublicKey();
if (!data.public_key) return RawExportError::FAILED;
return data;
}
EVPKeyPointer EVPKeyPointer::NewRawJwk(
const KeyAlgorithm& algorithm,
const Buffer<const unsigned char>& public_key,
const std::optional<Buffer<const unsigned char>>& private_key) {
if (!algorithm.isOkp() && !algorithm.isPqc()) return {};
if (!private_key) return NewRawPublic(algorithm, public_key);
auto key = algorithm.seedSize() != 0 ? NewRawSeed(algorithm, *private_key)
: NewRawPrivate(algorithm, *private_key);
if (!key) return {};
const auto derived_public = key.rawPublicKey();
if (!derived_public || derived_public.size() != public_key.len ||
CRYPTO_memcmp(derived_public.get(), public_key.data, public_key.len) !=
0) {
return {};
}
return key;
}
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_OPENSSL_PROVIDER
return set(eckey);
#elif NCRYPTO_USE_BORINGSSL
return EVP_PKEY_assign_EC_KEY(pkey_.get(), eckey.get());
#else
#error "Unsupported crypto backend"
#endif
}
bool EVPKeyPointer::set(const ECKeyPointer& eckey) {
if (!pkey_ || !eckey) return false;
#if NCRYPTO_USE_OPENSSL_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(KeyAlgorithm::EC, selection, params.get());
if (!pkey) return false;
reset(pkey.release());
return true;
#elif NCRYPTO_USE_BORINGSSL
return EVP_PKEY_set1_EC_KEY(pkey_.get(), eckey);
#else
#error "Unsupported crypto backend"
#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 {
EVP_PKEY* DecodeRsaPublicKey(const unsigned char** data, size_t length) {
#if NCRYPTO_USE_OPENSSL_PROVIDER
// Borrow the EVP_PKEY constructor and its data from a context that stays
// alive until after the restricted decoder context is destroyed.
EVP_PKEY* raw = nullptr;
DeleteFnPtr<OSSL_DECODER_CTX, OSSL_DECODER_CTX_free> construct_ctx(
OSSL_DECODER_CTX_new_for_pkey(&raw,
"DER",
"type-specific",
KeyAlgorithm::RSA.name(),
EVP_PKEY_PUBLIC_KEY,
nullptr,
nullptr));
if (!construct_ctx) return nullptr;
auto* construct = OSSL_DECODER_CTX_get_construct(construct_ctx.get());
void* construct_data =
OSSL_DECODER_CTX_get_construct_data(construct_ctx.get());
if (construct == nullptr || construct_data == nullptr) return nullptr;
// Add only the type-specific RSA decoder: new_for_pkey() can also build
// chains that accept SPKI. The owning context retains the cleanup callback.
DeleteFnPtr<OSSL_DECODER, OSSL_DECODER_free> decoder(OSSL_DECODER_fetch(
nullptr, KeyAlgorithm::RSA.name(), "input=der,structure=type-specific"));
DeleteFnPtr<OSSL_DECODER_CTX, OSSL_DECODER_CTX_free> ctx(
OSSL_DECODER_CTX_new());
if (!decoder || !ctx ||
OSSL_DECODER_CTX_add_decoder(ctx.get(), decoder.get()) != 1 ||
OSSL_DECODER_CTX_set_input_type(ctx.get(), "DER") != 1 ||
OSSL_DECODER_CTX_set_selection(ctx.get(), EVP_PKEY_PUBLIC_KEY) != 1 ||
OSSL_DECODER_CTX_set_construct(ctx.get(), construct) != 1 ||
OSSL_DECODER_CTX_set_construct_data(ctx.get(), construct_data) != 1) {
return nullptr;
}
const int result = OSSL_DECODER_from_data(ctx.get(), data, &length);
EVPKeyPointer key(raw);
return result == 1 ? key.release() : nullptr;
#elif NCRYPTO_USE_BORINGSSL
return d2i_PublicKey(NID_rsaEncryption, nullptr, data, length);
#else
#error "Unsupported crypto backend"
#endif
}
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 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 DecodeRsaPublicKey(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 = DecodeRsaPublicKey(&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_OPENSSL_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.isA(KeyAlgorithm::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) {
ERR_clear_error();
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_BORINGSSL
return ParseKeyResult(PKParseError::FAILED);
#elif NCRYPTO_USE_OPENSSL_PROVIDER
// 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);
#else
#error "Unsupported crypto backend"
#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 (!isA(KeyAlgorithm::RSA)) return Result<BIOPointer, bool>(false);
#if NCRYPTO_USE_OPENSSL_PROVIDER
const EVP_CIPHER* cipher =
config.format == PKFormatType::PEM ? config.cipher.get() : 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);
}
#elif NCRYPTO_USE_BORINGSSL
RSA* rsa = EVP_PKEY_get0_RSA(get());
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);
}
}
#else
#error "Unsupported crypto backend"
#endif
break;
}
case PKEncodingType::PKCS8: {
EVP_PKEY* export_key = get();
#if NCRYPTO_USE_OPENSSL_PROVIDER
// OpenSSL's provider EC PKCS8 encoders temporarily change encoding flags.
// Use an independent key so concurrent exports do not change the source.
EVPKeyPointer key_copy;
if ((isA(KeyAlgorithm::EC) || isA(KeyAlgorithm::SM2)) &&
EVP_PKEY_get0_provider(get()) != nullptr) {
key_copy.reset(EVP_PKEY_dup(get()));
if (!key_copy) {
return Result<BIOPointer, bool>(false,
mark_pop_error_on_return.peekError());
}
export_key = key_copy.get();
}
#endif
switch (config.format) {
case PKFormatType::PEM: {
// Encode PKCS#8 as PEM.
err = PEM_write_bio_PKCS8PrivateKey(bio.get(),
export_key,
config.cipher,
passphrase.data,
passphrase.len,
nullptr,
nullptr) != 1;
break;
}
case PKFormatType::DER: {
err = i2d_PKCS8PrivateKey_bio(bio.get(),
export_key,
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 (!isA(KeyAlgorithm::EC)) return Result<BIOPointer, bool>(false);
#if NCRYPTO_USE_OPENSSL_PROVIDER
const EVP_CIPHER* cipher =
config.format == PKFormatType::PEM ? config.cipher.get() : nullptr;
err = !WriteEncodedPKey(bio.get(),
get(),
OSSL_KEYMGMT_SELECT_ALL,
config.format,
"type-specific",
cipher,
passphrase);
#elif NCRYPTO_USE_BORINGSSL
EC_KEY* ec = EVP_PKEY_get0_EC_KEY(get());
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);
}
}
#else
#error "Unsupported crypto backend"
#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_OPENSSL_PROVIDER
if (!isA(KeyAlgorithm::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;
#elif NCRYPTO_USE_BORINGSSL
RSA* rsa = EVP_PKEY_get0_RSA(get());
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;
#else
#error "Unsupported crypto backend"
#endif
}
#if NCRYPTO_USE_OPENSSL_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_OPENSSL_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());
}
#elif NCRYPTO_USE_BORINGSSL
// BoringSSL does not 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());
}
#else
#error "Unsupported crypto backend"
#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 EVP_PKEY* key) {
return isA(key, KeyAlgorithm::RSA) || isA(key, KeyAlgorithm::RSA_PSS);
}
bool EVPKeyPointer::isRsaVariant() const {
return isRsaVariant(get());
}
bool EVPKeyPointer::isSigVariant() const {
return isA(KeyAlgorithm::EC) || isA(KeyAlgorithm::DSA);
}
int EVPKeyPointer::getDefaultSignPadding() const {
return isA(KeyAlgorithm::RSA_PSS) ? RSA_PKCS1_PSS_PADDING : RSA_PKCS1_PADDING;
}
std::optional<uint32_t> EVPKeyPointer::getBytesOfRS() const {
if (!pkey_) return std::nullopt;
int bits;
if (isA(KeyAlgorithm::DSA)) {
#if NCRYPTO_USE_OPENSSL_PROVIDER
DeleteFnPtr<BIGNUM, BN_free> q;
if (!GetPKeyBnParam(get(), OSSL_PKEY_PARAM_FFC_Q, &q)) return std::nullopt;
bits = BignumPointer::GetBitCount(q.get());
#elif NCRYPTO_USE_BORINGSSL
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;
#else
#error "Unsupported crypto backend"
#endif
} else if (isA(KeyAlgorithm::EC)) {
#if NCRYPTO_USE_OPENSSL_PROVIDER
bits = EVP_PKEY_bits(get());
#elif NCRYPTO_USE_BORINGSSL
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);
#else
#error "Unsupported crypto backend"
#endif
} else {
return std::nullopt;
}
if (bits <= 0) return std::nullopt;
return (bits + 7) / 8;
}
EVPKeyPointer::operator Rsa() const {
if (!isA(KeyAlgorithm::RSA) && !isA(KeyAlgorithm::RSA_PSS)) return {};
#if NCRYPTO_USE_OPENSSL_PROVIDER
return Rsa(get());
#elif NCRYPTO_USE_BORINGSSL
OSSL3_CONST RSA* rsa = EVP_PKEY_get0_RSA(get());
if (rsa == nullptr) return {};
return Rsa(rsa);
#else
#error "Unsupported crypto backend"
#endif
}
EVPKeyPointer::operator Dsa() const {
if (!isA(KeyAlgorithm::DSA)) return {};
#if NCRYPTO_USE_OPENSSL_PROVIDER
return Dsa(get());
#elif NCRYPTO_USE_BORINGSSL
OSSL3_CONST DSA* dsa = EVP_PKEY_get0_DSA(get());
if (dsa == nullptr) return {};
return Dsa(dsa);
#else
#error "Unsupported crypto backend"
#endif
}
bool EVPKeyPointer::validateDsaParameters() const {
if (!pkey_) return false;
#ifndef OPENSSL_IS_BORINGSSL
if (EVP_default_properties_is_fips_enabled(nullptr) &&
isA(KeyAlgorithm::DSA)) {
#elif defined(OPENSSL_IS_BORINGSSL)
if (FIPS_mode() && isA(KeyAlgorithm::DSA)) {
#endif
// Validate DSA2 parameters from FIPS 186-4.
#if NCRYPTO_USE_OPENSSL_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();
#elif NCRYPTO_USE_BORINGSSL
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;
#else
#error "Unsupported crypto backend"
#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;
}
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;
if (!SSL_get_peer_tmp_key(get(), &raw_key)) return {};
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;
#elif defined(OPENSSL_IS_BORINGSSL)
if (!ssl_) return std::nullopt;
const int nid = SSL_get_negotiated_group(get());
if (nid == NID_undef) return std::nullopt;
const char* group = OBJ_nid2sn(nid);
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() {
auto ctx = SSLCtxPointer::New();
if (!ctx) return std::nullopt;
#ifdef OPENSSL_IS_BORINGSSL
return SSL_CTX_get_security_level(ctx.get());
#else
auto ssl = SSLPointer::New(ctx);
if (!ssl) return std::nullopt;
return SSL_get_security_level(ssl);
#endif
}
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) {
if (!ctx_) return false;
#ifndef OPENSSL_IS_BORINGSSL
return SSL_CTX_set_ciphersuites(ctx_.get(), ciphers);
#elif defined(OPENSSL_IS_BORINGSSL)
constexpr std::array<uint16_t, 3> supported = {
SSL_CIPHER_AES_128_GCM_SHA256,
SSL_CIPHER_AES_256_GCM_SHA384,
SSL_CIPHER_CHACHA20_POLY1305_SHA256,
};
std::vector<uint16_t> cipher_ids;
std::string_view remaining(ciphers);
while (!remaining.empty()) {
const size_t end = remaining.find(':');
std::string_view name = remaining.substr(0, end);
const size_t first = name.find_first_not_of(" \t\n\r\f\v");
if (first != std::string_view::npos) {
name = name.substr(first);
name = name.substr(0, name.find_last_not_of(" \t\n\r\f\v") + 1);
for (const auto id : supported) {
const SSL_CIPHER* cipher = SSL_get_cipher_by_value(id);
if (name == SSL_CIPHER_standard_name(cipher)) {
// OpenSSL ignores unknown names and accepts duplicates, while
// BoringSSL requires unique protocol IDs.
if (std::find(cipher_ids.begin(), cipher_ids.end(), id) ==
cipher_ids.end()) {
cipher_ids.push_back(id);
}
break;
}
}
}
if (end == std::string_view::npos) break;
remaining.remove_prefix(end + 1);
}
if (*ciphers != '\0' && cipher_ids.empty()) {
OPENSSL_PUT_ERROR(SSL, SSL_R_NO_CIPHER_MATCH);
return false;
}
std::vector<uint32_t> flags(cipher_ids.size());
if (!flags.empty() &&
(SSL_CTX_get_options(get()) & SSL_OP_CIPHER_SERVER_PREFERENCE) == 0) {
// TLS 1.3 otherwise always uses server preference in BoringSSL. An
// equal-preference group lets the client choose from the configured list.
std::fill(flags.begin(),
flags.end() - 1,
SSL_CIPHER_FLAG_EQUAL_PREFERENCE_WITH_NEXT);
}
// An empty input resets BoringSSL's built-in preference order. The public
// TLS API expands empty cipher strings to Node's defaults before this call.
return SSL_CTX_set1_tls13_ciphers(
get(), cipher_ids.data(), flags.data(), cipher_ids.size()) == 1;
#endif
}
// ============================================================================
namespace {
constexpr char AsciiToLower(char c) {
return c >= 'A' && c <= 'Z' ? c + ('a' - 'A') : c;
}
#if NCRYPTO_USE_OPENSSL_PROVIDER
constexpr auto kUnsupportedCipherFlags =
EVP_CIPH_FLAG_CIPHER_WITH_MAC | EVP_CIPH_FLAG_TLS1_1_MULTIBLOCK;
bool HasUnsupportedCipherFlags(const EVP_CIPHER* cipher) {
return (EVP_CIPHER_get_flags(cipher) & kUnsupportedCipherFlags) != 0;
}
bool IsSupportedLegacyCipher(const EVP_CIPHER* cipher) {
return cipher != nullptr && cipher != EVP_enc_null() &&
!HasUnsupportedCipherFlags(cipher);
}
bool IsSupportedFetchedCipher(const EVP_CIPHER* cipher) {
if (cipher == nullptr || EVP_CIPHER_is_a(cipher, "NULL") ||
HasUnsupportedCipherFlags(cipher)) {
return false;
}
#ifdef OSSL_CIPHER_PARAM_ENCRYPT_THEN_MAC
int encrypt_then_mac = 0;
OSSL_PARAM params[] = {
OSSL_PARAM_construct_int(OSSL_CIPHER_PARAM_ENCRYPT_THEN_MAC,
&encrypt_then_mac),
OSSL_PARAM_construct_end(),
};
if (EVP_CIPHER_get_params(const_cast<EVP_CIPHER*>(cipher), params) == 1 &&
encrypt_then_mac != 0) {
return false;
}
#endif
return true;
}
void PushAlgorithmAlias(const char* name, void* arg) {
if (name == nullptr) return;
static_cast<std::vector<std::string>*>(arg)->emplace_back(name);
}
#endif
} // namespace
#if NCRYPTO_USE_OPENSSL_PROVIDER
Cipher::Cipher(DeleteFnPtr<EVP_CIPHER, EVP_CIPHER_free> cipher)
: cipher_(cipher.get()), fetched_cipher_(std::move(cipher)) {}
#endif
size_t CaseInsensitiveNameHash::operator()(
std::string_view name) const noexcept {
size_t hash = 5381;
for (char c : name) hash = ((hash << 5) + hash) ^ AsciiToLower(c);
return hash;
}
bool CaseInsensitiveNameEqual::operator()(std::string_view lhs,
std::string_view rhs) const noexcept {
if (lhs.size() != rhs.size()) return false;
for (size_t n = 0; n < lhs.size(); n++) {
if (AsciiToLower(lhs[n]) != AsciiToLower(rhs[n])) return false;
}
return true;
}
DigestCache::Result DigestCache::lookup(const char* name,
uint64_t generation) const {
#if NCRYPTO_USE_OPENSSL_PROVIDER
if (generation_ != generation) return {};
const auto it = aliases_.find(name);
if (it == aliases_.end()) return {};
return lookup(it->second, generation);
#elif NCRYPTO_USE_BORINGSSL
static_cast<void>(name);
static_cast<void>(generation);
return {};
#else
#error "Unsupported crypto backend"
#endif
}
DigestCache::Result DigestCache::insert(const char* name,
const EVP_MD* digest,
uint64_t generation) {
#if NCRYPTO_USE_OPENSSL_PROVIDER
if (generation_ != generation || name == nullptr || digest == nullptr) {
return {};
}
const char* canonical_name = EVP_MD_get0_name(digest);
const OSSL_PROVIDER* provider = EVP_MD_get0_provider(digest);
if (canonical_name == nullptr || provider == nullptr) return {};
for (size_t index = 0; index < digests_.size(); index++) {
const EVP_MD* cached = digests_[index].get();
if (cached == nullptr) continue;
const char* cached_name = EVP_MD_get0_name(cached);
if (EVP_MD_get0_provider(cached) == provider && cached_name != nullptr &&
CaseInsensitiveNameEqual()(cached_name, canonical_name)) {
const int32_t id = static_cast<int32_t>(first_id_ + index);
aliases_.insert_or_assign(name, id);
return {cached, id};
}
}
if (next_id_ == UINT32_MAX ||
EVP_MD_up_ref(const_cast<EVP_MD*>(digest)) != 1) {
return {};
}
digests_.emplace_back(const_cast<EVP_MD*>(digest));
const int32_t id = static_cast<int32_t>(next_id_++);
const size_t index = digests_.size() - 1;
std::vector<std::string> aliases;
EVP_MD_names_do_all(digests_[index].get(), PushAlgorithmAlias, &aliases);
for (const std::string& alias : aliases) aliases_.emplace(alias, id);
aliases_.insert_or_assign(name, id);
return {digests_[index].get(), id};
#elif NCRYPTO_USE_BORINGSSL
static_cast<void>(name);
static_cast<void>(digest);
static_cast<void>(generation);
return {};
#else
#error "Unsupported crypto backend"
#endif
}
DigestCache::~DigestCache() = default;
void DigestCache::reset(uint64_t generation) {
#if NCRYPTO_USE_OPENSSL_PROVIDER
if (generation_ == generation) return;
aliases_.clear();
hmac_templates_.clear();
digests_.clear();
first_id_ = next_id_;
#endif
generation_ = generation;
}
#if NCRYPTO_USE_OPENSSL_PROVIDER
const HMACCtxPointer* DigestCache::getHmacTemplate(const EVP_MD* digest,
uint64_t generation) {
if (generation_ != generation || digest == nullptr) return nullptr;
for (size_t index = 0; index < digests_.size(); index++) {
if (digests_[index].get() != digest) continue;
if (hmac_templates_.size() <= index) {
hmac_templates_.resize(digests_.size());
}
if (!hmac_templates_[index]) {
auto ctx = HMACCtxPointer::New(Digest::Ref(digest));
if (!ctx) return nullptr;
hmac_templates_[index] = std::make_unique<HMACCtxPointer>(std::move(ctx));
}
return hmac_templates_[index].get();
}
return nullptr;
}
#endif
const DigestCache::AliasMap& DigestCache::aliases() const {
#if NCRYPTO_USE_OPENSSL_PROVIDER
return aliases_;
#elif NCRYPTO_USE_BORINGSSL
static const AliasMap empty;
return empty;
#else
#error "Unsupported crypto backend"
#endif
}
const EVP_CIPHER* CipherCache::lookup(const char* name, uint64_t generation) {
#if NCRYPTO_USE_OPENSSL_PROVIDER
if (generation_ != generation) {
aliases_.clear();
ciphers_.clear();
generation_ = generation;
}
const auto it = aliases_.find(name);
if (it == aliases_.end()) return nullptr;
if (it->second >= ciphers_.size()) return nullptr;
return ciphers_[it->second].get();
#elif NCRYPTO_USE_BORINGSSL
static_cast<void>(name);
static_cast<void>(generation);
return nullptr;
#else
#error "Unsupported crypto backend"
#endif
}
#if NCRYPTO_USE_OPENSSL_PROVIDER
const EVP_CIPHER* CipherCache::insert(
const char* name,
DeleteFnPtr<EVP_CIPHER, EVP_CIPHER_free>&& cipher,
uint64_t generation) {
if (generation_ != generation || cipher == nullptr) return nullptr;
const char* canonical_name = EVP_CIPHER_get0_name(cipher.get());
const OSSL_PROVIDER* provider = EVP_CIPHER_get0_provider(cipher.get());
if (canonical_name != nullptr && provider != nullptr) {
for (size_t id = 0; id < ciphers_.size(); id++) {
const EVP_CIPHER* cached = ciphers_[id].get();
const char* cached_name = EVP_CIPHER_get0_name(cached);
if (EVP_CIPHER_get0_provider(cached) == provider &&
cached_name != nullptr &&
CaseInsensitiveNameEqual()(cached_name, canonical_name)) {
aliases_.insert_or_assign(name, id);
return cached;
}
}
}
ciphers_.emplace_back(std::move(cipher));
const size_t id = ciphers_.size() - 1;
std::vector<std::string> aliases;
EVP_CIPHER_names_do_all(ciphers_[id].get(), PushAlgorithmAlias, &aliases);
for (const std::string& alias : aliases) {
aliases_.emplace(alias, id);
}
aliases_.insert_or_assign(name, id);
return ciphers_[id].get();
}
#endif
Cipher::Cipher(const Cipher& other) : cipher_(other.cipher_) {
#if NCRYPTO_USE_OPENSSL_PROVIDER
if (other.fetched_cipher_ != nullptr) {
if (EVP_CIPHER_up_ref(other.fetched_cipher_.get()) == 1) {
fetched_cipher_.reset(other.fetched_cipher_.get());
} else {
cipher_ = nullptr;
}
}
#endif
}
Cipher& Cipher::operator=(const Cipher& other) {
if (this == &other) return *this;
#if NCRYPTO_USE_OPENSSL_PROVIDER
if (other.fetched_cipher_ != nullptr) {
if (EVP_CIPHER_up_ref(other.fetched_cipher_.get()) == 1) {
fetched_cipher_.reset(other.fetched_cipher_.get());
} else {
fetched_cipher_.reset();
cipher_ = nullptr;
return *this;
}
} else {
fetched_cipher_.reset();
}
#endif
cipher_ = other.cipher_;
return *this;
}
const Cipher Cipher::FromName(const char* name, CipherCache* cache) {
const EVP_CIPHER* cipher = EVP_get_cipherbyname(name);
if (cipher != nullptr) {
#if NCRYPTO_USE_OPENSSL_PROVIDER
if (!IsSupportedLegacyCipher(cipher)) return Cipher();
#endif
return Cipher(cipher);
}
#if NCRYPTO_USE_OPENSSL_PROVIDER
// A resolution that overlaps a FIPS transition may use either property
// state. The cache retains the generation observed here, so the first
// resolution begun after the transition clears any stale entries.
const uint64_t generation = getFipsStateGeneration();
if (cache != nullptr) {
if (const EVP_CIPHER* cached = cache->lookup(name, generation)) {
return Cipher(cached);
}
}
MarkPopErrorOnReturn mark_pop_error_on_return;
DeleteFnPtr<EVP_CIPHER, EVP_CIPHER_free> fetched(
EVP_CIPHER_fetch(nullptr, name, nullptr));
if (!IsSupportedFetchedCipher(fetched.get())) return Cipher();
if (cache != nullptr && generation == getFipsStateGeneration()) {
if (const EVP_CIPHER* cached =
cache->insert(name, std::move(fetched), generation)) {
return Cipher(cached);
}
}
return Cipher(std::move(fetched));
#elif NCRYPTO_USE_BORINGSSL
static_cast<void>(cache);
return Cipher();
#else
#error "Unsupported crypto backend"
#endif
}
const Cipher Cipher::FromNameForKeyEncoding(const char* name) {
// Key serializers have their own cipher restrictions. Preserve their policy
// instead of applying the filters used by the general cipher operations.
#if NCRYPTO_USE_OPENSSL_PROVIDER
MarkPopErrorOnReturn mark_pop_error_on_return;
DeleteFnPtr<EVP_CIPHER, EVP_CIPHER_free> fetched(
EVP_CIPHER_fetch(nullptr, name, nullptr));
if (fetched) return Cipher(std::move(fetched));
#endif
// Preserve serializer errors for known ciphers that cannot be fetched.
return Cipher(EVP_get_cipherbyname(name));
}
const Cipher Cipher::FromNid(int nid, CipherCache* cache) {
const EVP_CIPHER* cipher = EVP_get_cipherbynid(nid);
if (cipher != nullptr) {
#if NCRYPTO_USE_OPENSSL_PROVIDER
if (!IsSupportedLegacyCipher(cipher)) return Cipher();
#endif
return Cipher(cipher);
}
#if NCRYPTO_USE_OPENSSL_PROVIDER
const char* name = OBJ_nid2sn(nid);
if (name != nullptr) return FromName(name, cache);
#elif NCRYPTO_USE_BORINGSSL
static_cast<void>(cache);
#else
#error "Unsupported crypto backend"
#endif
return Cipher();
}
const Cipher Cipher::FromCtx(const CipherCtxPointer& ctx) {
return Cipher(GetCipherCtxCipher(ctx.get()));
}
namespace {
template <int nid>
const Cipher& GetPredefinedCipher() {
static const Cipher cipher = Cipher::FromNid(nid);
return cipher;
}
} // namespace
const Cipher& Cipher::AES_128_CBC() {
return GetPredefinedCipher<NID_aes_128_cbc>();
}
const Cipher& Cipher::AES_192_CBC() {
return GetPredefinedCipher<NID_aes_192_cbc>();
}
const Cipher& Cipher::AES_256_CBC() {
return GetPredefinedCipher<NID_aes_256_cbc>();
}
const Cipher& Cipher::AES_128_CTR() {
return GetPredefinedCipher<NID_aes_128_ctr>();
}
const Cipher& Cipher::AES_192_CTR() {
return GetPredefinedCipher<NID_aes_192_ctr>();
}
const Cipher& Cipher::AES_256_CTR() {
return GetPredefinedCipher<NID_aes_256_ctr>();
}
const Cipher& Cipher::AES_128_GCM() {
return GetPredefinedCipher<NID_aes_128_gcm>();
}
const Cipher& Cipher::AES_192_GCM() {
return GetPredefinedCipher<NID_aes_192_gcm>();
}
const Cipher& Cipher::AES_256_GCM() {
return GetPredefinedCipher<NID_aes_256_gcm>();
}
const Cipher& Cipher::AES_128_KW() {
return GetPredefinedCipher<NID_id_aes128_wrap>();
}
const Cipher& Cipher::AES_192_KW() {
return GetPredefinedCipher<NID_id_aes192_wrap>();
}
const Cipher& Cipher::AES_256_KW() {
return GetPredefinedCipher<NID_id_aes256_wrap>();
}
#ifndef OPENSSL_IS_BORINGSSL
const Cipher& Cipher::AES_128_OCB() {
return GetPredefinedCipher<NID_aes_128_ocb>();
}
const Cipher& Cipher::AES_192_OCB() {
return GetPredefinedCipher<NID_aes_192_ocb>();
}
const Cipher& Cipher::AES_256_OCB() {
return GetPredefinedCipher<NID_aes_256_ocb>();
}
#endif
const Cipher& Cipher::CHACHA20_POLY1305() {
return GetPredefinedCipher<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::isCtsMode() const {
if (!cipher_) return false;
#if NCRYPTO_USE_OPENSSL_PROVIDER
return (EVP_CIPHER_get_flags(cipher_) & EVP_CIPH_FLAG_CTS) != 0;
#elif NCRYPTO_USE_BORINGSSL
return false;
#else
#error "Unsupported crypto backend"
#endif
}
bool Cipher::isOcbMode() const {
if (!cipher_) return false;
return getMode() == EVP_CIPH_OCB_MODE;
}
bool Cipher::isSivMode() const {
if (!cipher_) return false;
#if OPENSSL_WITH_AES_SIV
return getMode() == EVP_CIPH_SIV_MODE;
#elif NCRYPTO_USE_BORINGSSL
return false;
#else
#error "Unsupported crypto backend"
#endif
}
bool Cipher::isGcmSivMode() const {
if (!cipher_) return false;
#if OPENSSL_WITH_AES_GCM_SIV
return getMode() == EVP_CIPH_GCM_SIV_MODE;
#else
return false;
#endif
}
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";
#if OPENSSL_WITH_AES_SIV
case EVP_CIPH_SIV_MODE:
return "siv";
#endif
#if OPENSSL_WITH_AES_GCM_SIV
case EVP_CIPH_GCM_SIV_MODE:
return "gcm-siv";
#endif
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.
const int nid = getNid();
if (nid != NID_undef) {
const char* name = OBJ_nid2sn(nid);
if (name != nullptr) return name;
}
#if NCRYPTO_USE_OPENSSL_PROVIDER
return EVP_CIPHER_get0_name(cipher_);
#elif NCRYPTO_USE_BORINGSSL
return {};
#else
#error "Unsupported crypto backend"
#endif
}
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
#if OPENSSL_WITH_AES_SIV
case EVP_CIPH_SIV_MODE:
#endif
#if OPENSSL_WITH_AES_GCM_SIV
case EVP_CIPH_GCM_SIV_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);
}
#if NCRYPTO_USE_OPENSSL_PROVIDER
namespace {
// OSSL_CIPHER_PARAM_XTS_STANDARD is not defined by OpenSSL 3.0. Use its
// parameter name directly so custom providers can advertise it too.
constexpr char kCipherParamXtsStandard[] = "xts_standard";
bool SetCipherCtxStringParam(EVP_CIPHER_CTX* ctx,
const char* key,
const char* value) {
if (ctx == nullptr || value == nullptr) return false;
const OSSL_PARAM* settable = EVP_CIPHER_CTX_settable_params(ctx);
const OSSL_PARAM* descriptor =
settable == nullptr ? nullptr : OSSL_PARAM_locate_const(settable, key);
if (descriptor == nullptr ||
descriptor->data_type != OSSL_PARAM_UTF8_STRING) {
return false;
}
OSSL_PARAM params[] = {
OSSL_PARAM_construct_utf8_string(key, const_cast<char*>(value), 0),
OSSL_PARAM_END,
};
return EVP_CIPHER_CTX_set_params(ctx, params) == 1;
}
} // namespace
#endif
bool CipherCtxPointer::setCtsMode(const char* mode) {
#if NCRYPTO_USE_OPENSSL_PROVIDER
return SetCipherCtxStringParam(ctx_.get(), OSSL_CIPHER_PARAM_CTS_MODE, mode);
#elif NCRYPTO_USE_BORINGSSL
static_cast<void>(mode);
return false;
#else
#error "Unsupported crypto backend"
#endif
}
bool CipherCtxPointer::setPadding(bool padding) {
if (!ctx_) return false;
return EVP_CIPHER_CTX_set_padding(ctx_.get(), padding);
}
bool CipherCtxPointer::setXtsStandard(const char* standard) {
#if NCRYPTO_USE_OPENSSL_PROVIDER
return SetCipherCtxStringParam(ctx_.get(), kCipherParamXtsStandard, standard);
#elif NCRYPTO_USE_BORINGSSL
static_cast<void>(standard);
return false;
#else
#error "Unsupported crypto backend"
#endif
}
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::isCtsMode() const {
if (!ctx_) return false;
return Cipher::FromCtx(*this).isCtsMode();
}
bool CipherCtxPointer::isXtsMode() const {
if (!ctx_) return false;
return getMode() == EVP_CIPH_XTS_MODE;
}
bool CipherCtxPointer::isWrapMode() const {
if (!ctx_) return false;
return getMode() == EVP_CIPH_WRAP_MODE;
}
bool CipherCtxPointer::isSivMode() const {
if (!ctx_) return false;
#if OPENSSL_WITH_AES_SIV
return getMode() == EVP_CIPH_SIV_MODE;
#elif NCRYPTO_USE_BORINGSSL
return false;
#else
#error "Unsupported crypto backend"
#endif
}
bool CipherCtxPointer::isGcmSivMode() const {
if (!ctx_) return false;
#if OPENSSL_WITH_AES_GCM_SIV
return getMode() == EVP_CIPH_GCM_SIV_MODE;
#else
return false;
#endif
}
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);
}
// ============================================================================
bool ECKeyPointer::checkPrivateKey() const {
const auto group = getGroup();
const auto priv = getPrivateKey();
const auto pub = getPublicKey();
if (group == nullptr || priv == nullptr || pub == nullptr) return false;
auto order = BignumPointer::New();
if (!order || !EC_GROUP_get_order(group, order.get(), nullptr) ||
BN_is_zero(priv) || BN_is_negative(priv) ||
BN_cmp(priv, order.get()) >= 0) {
return false;
}
auto expected = ECPointPointer::New(group);
return expected && expected.mul(group, priv) &&
EC_POINT_cmp(group, expected.get(), pub, nullptr) == 0;
}
#if NCRYPTO_USE_BORINGSSL
ECKeyPointer::ECKeyPointer() : key_(nullptr) {}
ECKeyPointer::ECKeyPointer(const EVPKeyPointer& key) : key_(nullptr) {
if (!key.isA(KeyAlgorithm::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;
if (x.encodePaddedInto(ptr + 1, field_len) != field_len ||
y.encodePaddedInto(ptr + 1 + field_len, field_len) != field_len) {
return false;
}
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;
}
#elif NCRYPTO_USE_OPENSSL_PROVIDER
ECKeyPointer::ECKeyPointer() : group_(nullptr), pub_(nullptr), priv_(nullptr) {}
ECKeyPointer::ECKeyPointer(const EVPKeyPointer& key) : ECKeyPointer() {
if (!key.isA(KeyAlgorithm::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::NewFromAlgorithm(KeyAlgorithm::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;
if (x.encodePaddedInto(ptr + 1, field_len) != field_len ||
y.encodePaddedInto(ptr + 1 + field_len, field_len) != field_len) {
return false;
}
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;
}
#else
#error "Unsupported crypto backend"
#endif // NCRYPTO_USE_BORINGSSL
// ============================================================================
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::NewFromName(const char* name) {
if (name == nullptr) return {};
#if NCRYPTO_USE_OPENSSL_PROVIDER
return EVPKeyCtxPointer(EVP_PKEY_CTX_new_from_name(nullptr, name, nullptr));
#elif NCRYPTO_USE_BORINGSSL
const int id = GetBoringSSLKeyId(name);
if (id == NID_undef) return {};
// DSA keys are not supported with BoringSSL.
if (id == EVP_PKEY_DSA) return {};
return EVPKeyCtxPointer(EVP_PKEY_CTX_new_id(id, nullptr));
#else
#error "Unsupported crypto backend"
#endif
}
EVPKeyCtxPointer EVPKeyCtxPointer::NewFromAlgorithm(
const KeyAlgorithm& algorithm) {
return NewFromName(algorithm.name());
}
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;
#elif defined(OPENSSL_IS_BORINGSSL)
// 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) {
return setEcParameters(OBJ_nid2sn(curve), encoding);
}
bool EVPKeyCtxPointer::setEcParameters(const char* group_name, int encoding) {
if (!ctx_ || group_name == nullptr) return false;
const int curve = Ec::GetCurveIdFromName(group_name);
#if NCRYPTO_USE_OPENSSL_PROVIDER
// Keep the historical aliases while allowing names known only to providers.
if (curve != NID_undef) group_name = OBJ_nid2sn(curve);
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;
#elif NCRYPTO_USE_BORINGSSL
return curve != NID_undef &&
EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx_.get(), curve) == 1 &&
EVP_PKEY_CTX_set_ec_param_enc(ctx_.get(), encoding) == 1;
#else
#error "Unsupported crypto backend"
#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_OPENSSL_PROVIDER
return EVP_PKEY_CTX_set1_rsa_keygen_pubexp(ctx_.get(), e.get()) == 1;
#elif NCRYPTO_USE_BORINGSSL
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;
#else
#error "Unsupported crypto backend"
#endif
}
bool EVPKeyCtxPointer::setRsaPssKeygenMd(const Digest& md) {
if (!md || !ctx_) return false;
return EVP_PKEY_CTX_set_rsa_pss_keygen_md(ctx_.get(), md) > 0;
}
bool EVPKeyCtxPointer::setRsaPssKeygenMgf1Md(const Digest& md) {
if (!md || !ctx_) return false;
return EVP_PKEY_CTX_set_rsa_pss_keygen_mgf1_md(ctx_.get(), md) > 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
#elif defined(OPENSSL_IS_BORINGSSL)
// 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
return EVP_PKEY_public_check_quick(ctx_.get()) == 1;
#elif defined(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;
#elif defined(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::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
Rsa::OtherPrimeInfoPointer::OtherPrimeInfoPointer(BignumPointer&& r,
BignumPointer&& d,
BignumPointer&& t)
: r(r.release()), d(d.release()), t(t.release()) {}
#if NCRYPTO_USE_OPENSSL_PROVIDER
namespace {
// Normalizes a provider digest name such as "SHA2-256" to the long name the
// rest of the key details use ("sha256"). The returned storage has static
// lifetime, which the string_view fields of PssParams require.
const char* RsaPssDigestLongName(const char* name) {
const EVP_MD* md = EVP_get_digestbyname(name);
if (md == nullptr) return nullptr;
const int nid = EVP_MD_get_type(md);
return nid != NID_undef ? OBJ_nid2ln(nid) : nullptr;
}
bool ReadRsaPssParams(const EVP_PKEY* pkey, Rsa::PssParams* params) {
// The RSASSA-PSS-params sequence is exposed as a unit. The salt length is
// readable whenever the sequence is present, including when it is empty
// because every field carried its default, and unreadable when the algorithm
// identifier has no parameters at all. That is the distinction between a
// restricted key and an unrestricted one.
int salt_length = 0;
// TODO(panva): In a semver-major, reject malformed RSA-PSS parameters
// at key import instead of omitting asymmetricKeyDetails fields.
if (EVP_PKEY_get_int_param(
pkey, OSSL_PKEY_PARAM_RSA_PSS_SALTLEN, &salt_length) != 1 ||
salt_length < 0) {
return false;
}
params->salt_length = salt_length;
// The provider may omit default SHA-1 digest parameters. Keep the initialized
// defaults when a digest name is absent or cannot be resolved.
char name[80];
if (EVP_PKEY_get_utf8_string_param(
pkey, OSSL_PKEY_PARAM_RSA_DIGEST, name, sizeof(name), nullptr) == 1) {
if (const char* long_name = RsaPssDigestLongName(name)) {
params->digest = long_name;
}
}
if (EVP_PKEY_get_utf8_string_param(
pkey, OSSL_PKEY_PARAM_RSA_MGF1_DIGEST, name, sizeof(name), nullptr) ==
1) {
if (const char* long_name = RsaPssDigestLongName(name)) {
params->mgf1_digest = long_name;
}
}
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, Selection selection) : Rsa() {
rsa_pss_ = EVPKeyPointer::isA(pkey, KeyAlgorithm::RSA_PSS);
if (!EVPKeyPointer::isA(pkey, KeyAlgorithm::RSA) && !rsa_pss_) return;
if (!GetPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_N, &n_) ||
!GetPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_E, &e_)) {
return;
}
if (rsa_pss_) {
MarkPopErrorOnReturn pop_errors;
PssParams params;
if (ReadRsaPssParams(pkey, ¶ms)) pss_params_ = params;
}
if (selection == Selection::Public) {
rsa_ = true;
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;
}
for (const auto& names : kRsaOtherPrimeParamNames) {
OtherPrimeInfoPointer info;
if (!GetOptionalPKeyBnParam(pkey, names.factor, &info.r) ||
!GetOptionalPKeyBnParam(pkey, names.exponent, &info.d) ||
!GetOptionalPKeyBnParam(pkey, names.coefficient, &info.t)) {
return;
}
if (!info.r && !info.d && !info.t) break;
if (!info.r || !info.d || !info.t) return;
other_prime_infos_.push_back(std::move(info));
}
rsa_ = true;
}
#elif NCRYPTO_USE_BORINGSSL
Rsa::Rsa() : rsa_(nullptr) {}
Rsa::Rsa(OSSL3_CONST RSA* ptr) : rsa_(ptr) {}
#else
#error "Unsupported crypto backend"
#endif
Rsa Rsa::PublicOnly(const EVPKeyPointer& key) {
#if NCRYPTO_USE_OPENSSL_PROVIDER
return Rsa(key.get(), Selection::Public);
#elif NCRYPTO_USE_BORINGSSL
return key;
#else
#error "Unsupported crypto backend"
#endif
}
const Rsa::PublicKey Rsa::getPublicKey() const {
#if NCRYPTO_USE_OPENSSL_PROVIDER
if (!rsa_) return {};
return PublicKey{n_.get(), e_.get(), d_.get()};
#elif NCRYPTO_USE_BORINGSSL
if (rsa_ == nullptr) return {};
PublicKey key;
RSA_get0_key(rsa_, &key.n, &key.e, &key.d);
return key;
#else
#error "Unsupported crypto backend"
#endif
}
const Rsa::PrivateKey Rsa::getPrivateKey() const {
#if NCRYPTO_USE_OPENSSL_PROVIDER
if (!rsa_) return {};
return PrivateKey{p_.get(), q_.get(), dp_.get(), dq_.get(), qi_.get()};
#elif NCRYPTO_USE_BORINGSSL
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;
#else
#error "Unsupported crypto backend"
#endif
}
const Rsa::OtherPrimeInfos Rsa::getOtherPrimeInfos() const {
OtherPrimeInfos infos;
#if NCRYPTO_USE_OPENSSL_PROVIDER
infos.reserve(other_prime_infos_.size());
for (const auto& info : other_prime_infos_) {
infos.push_back({info.r.get(), info.d.get(), info.t.get()});
}
#endif
return infos;
}
bool Rsa::checkPrimeProduct() const {
const auto pub = getPublicKey();
const auto priv = getPrivateKey();
if (pub.n == nullptr || priv.p == nullptr || priv.q == nullptr) return false;
auto product = BignumPointer::New();
BignumCtxPointer ctx(BN_CTX_new());
if (!product || !ctx ||
BN_mul(product.get(), priv.p, priv.q, ctx.get()) != 1) {
return false;
}
for (const auto& info : getOtherPrimeInfos()) {
auto next = BignumPointer::New();
if (!next || BN_mul(next.get(), product.get(), info.r, ctx.get()) != 1) {
return false;
}
product = std::move(next);
}
return BN_cmp(product.get(), pub.n) == 0;
}
const std::optional<Rsa::PssParams> Rsa::getPssParams() const {
#if NCRYPTO_USE_OPENSSL_PROVIDER
return pss_params_;
#elif NCRYPTO_USE_BORINGSSL
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;
#else
#error "Unsupported crypto backend"
#endif
}
BIOPointer Rsa::derPublicKey() const {
auto bio = BIOPointer::NewMem();
if (!bio) return {};
#if NCRYPTO_USE_OPENSSL_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 {};
#elif NCRYPTO_USE_BORINGSSL
if (rsa_ == nullptr || i2d_RSA_PUBKEY_bio(bio.get(), rsa_) != 1) return {};
#else
#error "Unsupported crypto backend"
#endif
return bio;
}
bool Rsa::setPublicKey(BignumPointer&& n, BignumPointer&& e) {
if (!n || !e) return false;
#if NCRYPTO_USE_OPENSSL_PROVIDER
n_.reset(n.release());
e_.reset(e.release());
rsa_ = true;
return true;
#elif NCRYPTO_USE_BORINGSSL
if (RSA_set0_key(const_cast<RSA*>(rsa_), n.get(), e.get(), nullptr) == 1) {
n.release();
e.release();
return true;
}
return false;
#else
#error "Unsupported crypto backend"
#endif
}
bool Rsa::setPrivateKey(BignumPointer&& d,
BignumPointer&& q,
BignumPointer&& p,
BignumPointer&& dp,
BignumPointer&& dq,
BignumPointer&& qi,
OtherPrimeInfoPointers&& other_prime_infos) {
#if NCRYPTO_USE_OPENSSL_PROVIDER
if (!d || !q || !p || !dp || !dq || !qi) return false;
for (const auto& info : other_prime_infos) {
if (!info.r || !info.d || !info.t) 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());
other_prime_infos_ = std::move(other_prime_infos);
rsa_ = n_ != nullptr && e_ != nullptr;
return rsa_;
#elif NCRYPTO_USE_BORINGSSL
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();
if (!other_prime_infos.empty()) return false;
return true;
#else
#error "Unsupported crypto backend"
#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 NCRYPTO_USE_OPENSSL_PROVIDER
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).
DeleteFnPtr<TypeName, free_type> fetched(
fetch_type(nullptr, real_name, nullptr));
if (!IsSupportedFetchedCipher(fetched.get())) return;
auto& cb = *(static_cast<CipherCallbackContext*>(arg));
cb(from);
}
void array_push_back_provider_name(const char* name, void* arg) {
if (name == nullptr) return;
const std::string_view name_view(name);
const bool is_dotted_decimal =
name_view.find('.') != std::string_view::npos &&
std::all_of(name_view.begin(), name_view.end(), [](unsigned char c) {
return (c >= '0' && c <= '9') || c == '.';
});
if (is_dotted_decimal) return;
std::string normalized_name(name_view);
std::transform(normalized_name.begin(),
normalized_name.end(),
normalized_name.begin(),
[](unsigned char c) {
if (c >= 'A' && c <= 'Z') {
return static_cast<char>(c + ('a' - 'A'));
}
return static_cast<char>(c);
});
auto& cb = *(static_cast<CipherCallbackContext*>(arg));
cb(normalized_name.c_str());
}
void array_push_back_provider(EVP_CIPHER* cipher, void* arg) {
const char* name = EVP_CIPHER_get0_name(cipher);
if (name == nullptr) return;
DeleteFnPtr<EVP_CIPHER, EVP_CIPHER_free> fetched(
EVP_CIPHER_fetch(nullptr, name, nullptr));
if (!IsSupportedFetchedCipher(fetched.get())) return;
EVP_CIPHER_names_do_all(fetched.get(), array_push_back_provider_name, arg);
}
#elif NCRYPTO_USE_BORINGSSL
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);
}
#else
#error "Unsupported crypto backend"
#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 NCRYPTO_USE_OPENSSL_PROVIDER
array_push_back<EVP_CIPHER,
EVP_CIPHER_fetch,
EVP_CIPHER_free,
EVP_get_cipherbyname,
EVP_CIPHER_get0_name>,
#elif NCRYPTO_USE_BORINGSSL
array_push_back<EVP_CIPHER>,
#else
#error "Unsupported crypto backend"
#endif
&context);
#if NCRYPTO_USE_OPENSSL_PROVIDER
EVP_CIPHER_do_all_provided(nullptr, array_push_back_provider, &context);
#endif
#endif
}
// ============================================================================
#if NCRYPTO_USE_OPENSSL_PROVIDER
Ec::Ec() : ec_(nullptr), pub_(nullptr) {}
Ec::Ec(const EVP_PKEY* pkey) : Ec() {
if (!EVPKeyPointer::isA(pkey, KeyAlgorithm::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());
}
#elif NCRYPTO_USE_BORINGSSL
Ec::Ec() : ec_(nullptr) {}
Ec::Ec(OSSL3_CONST EC_KEY* key) : ec_(key) {}
#else
#error "Unsupported crypto backend"
#endif
const EC_GROUP* Ec::getGroup() const {
#if NCRYPTO_USE_OPENSSL_PROVIDER
return ec_.get();
#elif NCRYPTO_USE_BORINGSSL
return ECKeyPointer::GetGroup(ec_);
#else
#error "Unsupported crypto backend"
#endif
}
const EC_POINT* Ec::getPublicKey() const {
#if NCRYPTO_USE_OPENSSL_PROVIDER
return pub_.get();
#elif NCRYPTO_USE_BORINGSSL
return ECKeyPointer::GetPublicKey(ec_);
#else
#error "Unsupported crypto backend"
#endif
}
point_conversion_form_t Ec::getPointConversionForm() const {
#if NCRYPTO_USE_OPENSSL_PROVIDER
return form_;
#elif NCRYPTO_USE_BORINGSSL
return EC_KEY_get_conv_form(ec_);
#else
#error "Unsupported crypto backend"
#endif
}
int Ec::getCurve() const {
return EC_GROUP_get_curve_name(getGroup());
}
BIOPointer Ec::ExportPrivatePkcs8(const EVPKeyPointer& key) {
MarkPopErrorOnReturn mark_pop_error_on_return;
if (!key || !key.isA(KeyAlgorithm::EC)) return {};
auto ec = ECKeyPointer(key).clone();
if (!ec) return {};
#if NCRYPTO_USE_LEGACY_KEY_TYPES
// Decoding an ECPrivateKey without publicKey reconstructs the public point
// but retains a flag that omits it from subsequent encodings.
EC_KEY_set_enc_flags(ec.get(),
EC_KEY_get_enc_flags(ec.get()) & ~EC_PKEY_NO_PUBKEY);
#endif
auto export_key = EVPKeyPointer::New();
if (!export_key || !export_key.set(ec)) return {};
auto encoded = export_key.writePrivateKey({});
if (!encoded) return {};
return std::move(encoded.value);
}
DataPointer Ec::TryExportPublic(const EVPKeyPointer& key,
point_conversion_form_t form) {
if (!key || form != POINT_CONVERSION_UNCOMPRESSED) return {};
#if NCRYPTO_USE_OPENSSL_PROVIDER
{
MarkPopErrorOnReturn pop_errors;
size_t length = 0;
if (EVP_PKEY_get_octet_string_param(
key.get(), OSSL_PKEY_PARAM_PUB_KEY, nullptr, 0, &length) == 1) {
auto bytes = DataPointer::Alloc(length);
if (bytes && length != 0 &&
EVP_PKEY_get_octet_string_param(key.get(),
OSSL_PKEY_PARAM_PUB_KEY,
bytes.get<unsigned char>(),
length,
&length) == 1 &&
(bytes.get<unsigned char>()[0] & ~1) == form) {
return bytes.resize(length);
}
}
}
#endif
return {};
}
DataPointer Ec::ExportPrivate(const EVPKeyPointer& key) {
if (!key) return {};
#if NCRYPTO_USE_OPENSSL_PROVIDER
{
MarkPopErrorOnReturn pop_errors;
BignumPointer priv;
BignumPointer order;
if (GetPKeyBnParam(key.get(), OSSL_PKEY_PARAM_PRIV_KEY, &priv) &&
GetPKeyBnParam(key.get(), OSSL_PKEY_PARAM_EC_ORDER, &order)) {
return priv.encodePadded(order.byteLength());
}
}
#endif
ECKeyPointer ec(key);
if (!ec || ec.getPrivateKey() == nullptr) return {};
auto order = BignumPointer::New();
if (!order || !EC_GROUP_get_order(ec.getGroup(), order.get(), nullptr))
return {};
return BignumPointer::EncodePadded(ec.getPrivateKey(), order.byteLength());
}
bool Ec::GetKeyComponents(const EVPKeyPointer& key,
BignumPointer* x,
BignumPointer* y,
BignumPointer* priv,
int* degree) {
if (!key) return false;
#if NCRYPTO_USE_OPENSSL_PROVIDER
const int nid = GetCurveId(key);
switch (nid) {
case NID_X9_62_prime256v1:
case NID_secp256k1:
*degree = 256;
break;
case NID_secp384r1:
*degree = 384;
break;
case NID_secp521r1:
*degree = 521;
break;
default:
*degree = 0;
}
if (*degree != 0) {
MarkPopErrorOnReturn pop_errors;
unsigned char x_bytes[66]{};
unsigned char y_bytes[66]{};
const size_t width = (*degree + 7) / 8;
OSSL_PARAM params[] = {
OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_EC_PUB_X, x_bytes, width),
OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_EC_PUB_Y, y_bytes, width),
OSSL_PARAM_construct_end(),
};
if (EVP_PKEY_get_params(key.get(), params) == 1 &&
OSSL_PARAM_modified(¶ms[0]) && OSSL_PARAM_modified(¶ms[1])) {
x->reset(BN_native2bn(x_bytes, width, nullptr));
y->reset(BN_native2bn(y_bytes, width, nullptr));
return *x && *y &&
(priv == nullptr ||
GetPKeyBnParam(key.get(), OSSL_PKEY_PARAM_PRIV_KEY, priv));
}
}
#endif
ECKeyPointer ec(key);
if (!ec || ec.getPublicKey() == nullptr) return false;
*degree = EC_GROUP_get_degree(ec.getGroup());
x->reset(BN_new());
y->reset(BN_new());
if (!*x || !*y ||
EC_POINT_get_affine_coordinates(
ec.getGroup(), ec.getPublicKey(), x->get(), y->get(), nullptr) != 1) {
return false;
}
if (priv != nullptr) {
if (ec.getPrivateKey() == nullptr) return false;
priv->reset(BN_dup(ec.getPrivateKey()));
if (!*priv) return false;
}
return true;
}
int Ec::GetCurveId(const EVPKeyPointer& key) {
if (!key) return NID_undef;
#if NCRYPTO_USE_OPENSSL_PROVIDER
char name[80];
size_t length = 0;
if (EVP_PKEY_get_utf8_string_param(
key.get(), OSSL_PKEY_PARAM_GROUP_NAME, name, sizeof(name), &length) !=
1) {
return NID_undef;
}
return GetCurveIdFromName(name);
#elif NCRYPTO_USE_BORINGSSL
const EC_KEY* ec = key;
if (ec == nullptr) return NID_undef;
const EC_GROUP* group = EC_KEY_get0_group(ec);
return group == nullptr ? NID_undef : EC_GROUP_get_curve_name(group);
#else
#error "Unsupported crypto backend"
#endif
}
std::optional<std::string> Ec::GetCurveName(const EVPKeyPointer& key) {
if (!key) return std::nullopt;
#if NCRYPTO_USE_OPENSSL_PROVIDER
size_t length = 0;
if (EVP_PKEY_get_utf8_string_param(
key.get(), OSSL_PKEY_PARAM_GROUP_NAME, nullptr, 0, &length) != 1) {
return std::nullopt;
}
std::string name(length, '\0');
if (EVP_PKEY_get_utf8_string_param(key.get(),
OSSL_PKEY_PARAM_GROUP_NAME,
name.data(),
name.size() + 1,
&length) != 1) {
return std::nullopt;
}
name.resize(length);
// Preserve the public short names for the curves OpenSSL already knows.
const int nid = GetCurveIdFromName(name.c_str());
return nid == NID_undef ? name : std::string(OBJ_nid2sn(nid));
#elif NCRYPTO_USE_BORINGSSL
const int nid = GetCurveId(key);
if (nid == NID_undef) return std::nullopt;
return std::string(OBJ_nid2sn(nid));
#else
#error "Unsupported crypto backend"
#endif
}
#if NCRYPTO_USE_OPENSSL_PROVIDER
namespace {
bool IsAvailableEcGroup(const char* name) {
MarkPopErrorOnReturn mark;
auto ctx = EVPKeyCtxPointer::NewFromAlgorithm(KeyAlgorithm::EC);
return ctx.initForParamgen() &&
ctx.setEcParameters(name, OPENSSL_EC_NAMED_CURVE) && ctx.paramgen();
}
} // namespace
#endif
bool Ec::CheckCurveName(const char* name) {
if (name == nullptr) return false;
if (GetCurveIdFromName(name) != NID_undef) return true;
#if NCRYPTO_USE_OPENSSL_PROVIDER
// Keep invalid names a synchronous argument error. Generation contexts can
// defer rejecting a group until parameter generation. Use the same parameter
// generation path as key generation without requiring parameter import.
return IsAvailableEcGroup(name);
#elif NCRYPTO_USE_BORINGSSL
return false;
#else
#error "Unsupported crypto backend"
#endif
}
int Ec::GetCurveIdFromName(const char* name) {
int nid = EC_curve_nist2nid(name);
if (nid == NID_undef) {
nid = OBJ_sn2nid(name);
}
return nid;
}
const KeyAlgorithm* Ec::GetNamedKeyAlgorithm(const char* name) {
// Preserve the aliases accepted by the historical namedCurve option.
const int nid = GetCurveIdFromName(name);
if (nid == NID_undef) return nullptr;
const auto* algorithm = KeyAlgorithm::FromName(OBJ_nid2sn(nid));
return algorithm != nullptr && algorithm->isOkp() ? algorithm : nullptr;
}
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 (const auto& curve : curves) {
const char* name = OBJ_nid2sn(curve.nid);
#if NCRYPTO_USE_OPENSSL_PROVIDER
if (!IsAvailableEcGroup(name)) continue;
#endif
if (!callback(name)) 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 EVP_MD* digest) {
if (!ctx_) return false;
return EVP_DigestInit_ex(ctx_.get(), digest, nullptr) > 0;
}
#if !defined(OPENSSL_IS_BORINGSSL) && OPENSSL_VERSION_PREREQ(4, 0)
bool EVPMDCtxPointer::digestInit(const EVP_MD* digest,
const OSSL_PARAM* params) {
if (!ctx_) return false;
return EVP_DigestInit_ex2(ctx_.get(), digest, params) > 0;
}
#endif
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;
#else
EVP_PKEY_CTX* ctx = nullptr;
// Ed25519 requires the INSTANCE param to switch into Ed25519ctx mode.
// Without it, OpenSSL silently ignores the context string.
if (key.isA(KeyAlgorithm::ED25519)) {
const OSSL_PARAM params[] = {
OSSL_PARAM_construct_utf8_string(
kSignatureInstance, const_cast<char*>("Ed25519ctx"), 0),
OSSL_PARAM_construct_octet_string(
kSignatureContextString,
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;
}
const OSSL_PARAM params[] = {
OSSL_PARAM_construct_octet_string(
kSignatureContextString,
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
}
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;
#else
EVP_PKEY_CTX* ctx = nullptr;
// Ed25519 requires the INSTANCE param to switch into Ed25519ctx mode.
// Without it, OpenSSL silently ignores the context string.
if (key.isA(KeyAlgorithm::ED25519)) {
const OSSL_PARAM params[] = {
OSSL_PARAM_construct_utf8_string(
kSignatureInstance, const_cast<char*>("Ed25519ctx"), 0),
OSSL_PARAM_construct_octet_string(
kSignatureContextString,
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;
}
const OSSL_PARAM params[] = {
OSSL_PARAM_construct_octet_string(
kSignatureContextString,
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
}
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 NCRYPTO_USE_BORINGSSL
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 // NCRYPTO_USE_BORINGSSL
#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));
}
MacKind MacCache::GetKind(EVP_MAC* mac) {
if (EVP_MAC_is_a(mac, OSSL_MAC_NAME_HMAC)) return MacKind::kHmac;
if (EVP_MAC_is_a(mac, OSSL_MAC_NAME_CMAC)) return MacKind::kCmac;
if (EVP_MAC_is_a(mac, OSSL_MAC_NAME_GMAC)) return MacKind::kGmac;
return MacKind::kOther;
}
MacCache::Result MacCache::lookup(const char* name, uint64_t generation) const {
if (generation_ != generation || name == nullptr) return {};
const auto it = aliases_.find(name);
if (it == aliases_.end()) return {};
return lookup(it->second, generation);
}
MacCache::Result MacCache::insert(const char* name,
EVPMacPointer&& mac,
uint64_t generation) {
if (generation_ != generation || generation != getFipsStateGeneration() ||
name == nullptr || mac == nullptr) {
return {};
}
const char* canonical_name = EVP_MAC_get0_name(mac.get());
const OSSL_PROVIDER* provider = EVP_MAC_get0_provider(mac.get());
if (canonical_name == nullptr || provider == nullptr) return {};
for (size_t index = 0; index < macs_.size(); index++) {
EVP_MAC* cached = macs_[index].mac.get();
if (cached == nullptr) continue;
const char* cached_name = EVP_MAC_get0_name(cached);
if (EVP_MAC_get0_provider(cached) == provider && cached_name != nullptr &&
CaseInsensitiveNameEqual()(cached_name, canonical_name)) {
if (generation != getFipsStateGeneration()) return {};
const int32_t id = static_cast<int32_t>(first_id_ + index);
aliases_.insert_or_assign(name, id);
return {cached, id, macs_[index].kind};
}
}
if (next_id_ == UINT32_MAX) return {};
std::vector<std::string> aliases;
{
MarkPopErrorOnReturn mark_pop_error_on_return;
if (EVP_MAC_names_do_all(mac.get(), PushAlgorithmAlias, &aliases) != 1) {
return {};
}
}
if (generation != getFipsStateGeneration()) return {};
const MacKind kind = GetKind(mac.get());
macs_.push_back({std::move(mac), kind});
const int32_t id = static_cast<int32_t>(next_id_++);
const size_t index = macs_.size() - 1;
for (const std::string& alias : aliases) aliases_.emplace(alias, id);
aliases_.insert_or_assign(name, id);
return {macs_[index].mac.get(), id, kind};
}
void MacCache::reset(uint64_t generation) {
if (generation_ == generation) return;
aliases_.clear();
macs_.clear();
first_id_ = next_id_;
generation_ = generation;
}
const MacCache::AliasMap& MacCache::aliases() const {
return aliases_;
}
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;
static constexpr unsigned char kEmptyKey = 0;
const unsigned char* key_data = static_cast<const unsigned char*>(key.data);
if (key_data == nullptr) {
if (key.len != 0) return false;
key_data = &kEmptyKey;
}
return EVP_MAC_init(ctx_.get(), key_data, key.len, params) == 1;
}
bool EVPMacCtxPointer::update(const Buffer<const void>& data) {
if (!ctx_) return false;
if (data.len == 0) return true;
if (data.data == nullptr) return false;
return EVP_MAC_update(ctx_.get(),
static_cast<const unsigned char*>(data.data),
data.len) == 1;
}
size_t EVPMacCtxPointer::getSize() const {
return ctx_ ? EVP_MAC_CTX_get_mac_size(ctx_.get()) : 0;
}
const OSSL_PARAM* EVPMacCtxPointer::getSettableParams() const {
return ctx_ ? EVP_MAC_CTX_settable_params(ctx_.get()) : nullptr;
}
DataPointer EVPMacCtxPointer::final(size_t length) {
if (!ctx_) return {};
// DataPointer uses a null allocation to represent failure. Retain a
// one-byte allocation for a successful zero-length result while passing the
// requested zero capacity to OpenSSL. A non-null output pointer is required
// to actually finalize; nullptr only queries the output length.
auto buf = DataPointer::Alloc(length == 0 ? 1 : 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 {};
}
if (result_len > length) return {};
return buf.resize(result_len);
}
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_(other.md_),
md_size_(other.md_size_) {
other.md_ = nullptr;
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_ = other.md_;
md_size_ = other.md_size_;
other.md_ = nullptr;
other.md_size_ = 0;
return *this;
}
HMACCtxPointer::~HMACCtxPointer() {
reset();
}
void HMACCtxPointer::reset() {
ctx_.reset();
mac_.reset();
md_ = nullptr;
md_size_ = 0;
}
bool HMACCtxPointer::init(const Buffer<const void>& buf, const Digest& md) {
if (!ctx_ || !md) return false;
if (md.get() == md_) return ctx_.init(buf);
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_ = md.get();
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::clone() const {
if (!ctx_) return {};
EVPMacCtxPointer ctx(EVP_MAC_CTX_dup(ctx_.get()));
if (!ctx) return {};
HMACCtxPointer result(EVPMacPointer(), std::move(ctx));
result.md_ = md_;
result.md_size_ = md_size_;
return result;
}
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));
}
HMACCtxPointer HMACCtxPointer::New(const Digest& md) {
if (!md) return {};
const char* md_name = EVP_MD_get0_name(md);
if (md_name == nullptr) return {};
auto result = New();
if (!result) return {};
OSSL_PARAM params[] = {
OSSL_PARAM_construct_utf8_string(
OSSL_MAC_PARAM_DIGEST, const_cast<char*>(md_name), 0),
OSSL_PARAM_construct_end(),
};
if (EVP_MAC_CTX_set_params(result.ctx_.get(), params) != 1) return {};
result.md_ = md.get();
result.md_size_ = md.size();
return result;
}
#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(Buffer<const void>{
.data = buf.data,
.len = buf.len,
}) != 1) {
return {};
}
return ctx.digestFinal(output_length);
}
// ============================================================================
X509Name::X509Name() : name_(nullptr), total_(0) {}
X509Name::X509Name(const X509_NAME* name)
: name_(name), total_(name == nullptr ? 0 : 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::optional<X509Name::Entry> X509Name::Iterator::operator*() const {
MarkPopErrorOnReturn mark_pop_error_on_return;
if (loc_ == name_.total_) return std::nullopt;
const X509_NAME_ENTRY* entry = X509_NAME_get_entry(name_, loc_);
if (entry == nullptr) [[unlikely]]
return std::nullopt;
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 std::nullopt;
}
int nid = OBJ_obj2nid(name);
std::string name_str;
if (nid != NID_undef) {
const char* short_name = OBJ_nid2sn(nid);
if (short_name == nullptr) return std::nullopt;
name_str = short_name;
} else {
auto name_text = ObjectToText(name, false);
if (!name_text.has_value()) [[unlikely]] {
return std::nullopt;
}
name_str = std::move(name_text.value());
}
switch (ASN1_STRING_type(value)) {
case V_ASN1_UTF8STRING:
case V_ASN1_NUMERICSTRING:
case V_ASN1_PRINTABLESTRING:
case V_ASN1_T61STRING:
case V_ASN1_IA5STRING:
case V_ASN1_UTCTIME:
case V_ASN1_GENERALIZEDTIME:
case V_ASN1_ISO64STRING:
case V_ASN1_UNIVERSALSTRING:
case V_ASN1_BMPSTRING:
break;
default:
return Entry{std::move(name_str), std::nullopt};
}
unsigned char* value_str = nullptr;
int value_str_size = ASN1_STRING_to_UTF8(&value_str, value);
DataPointer value_data(
value_str, value_str_size > 0 ? static_cast<size_t>(value_str_size) : 0);
if (value_str_size < 0) [[unlikely]] {
return std::nullopt;
}
std::string out;
if (value_str_size > 0) {
if (!value_data) return std::nullopt;
out.assign(reinterpret_cast<const char*>(value_data.get()),
static_cast<size_t>(value_str_size));
}
return Entry{std::move(name_str), std::move(out)};
}
// ============================================================================
#if NCRYPTO_USE_OPENSSL_PROVIDER
Dsa::Dsa() : dsa_(false) {}
Dsa::Dsa(const EVP_PKEY* pkey) : Dsa() {
if (!EVPKeyPointer::isA(pkey, KeyAlgorithm::DSA)) return;
if (!GetPKeyBnParam(pkey, OSSL_PKEY_PARAM_FFC_P, &p_) ||
!GetPKeyBnParam(pkey, OSSL_PKEY_PARAM_FFC_Q, &q_)) {
return;
}
dsa_ = true;
}
#elif NCRYPTO_USE_BORINGSSL
Dsa::Dsa() : dsa_(nullptr) {}
Dsa::Dsa(OSSL3_CONST DSA* dsa) : dsa_(dsa) {}
#else
#error "Unsupported crypto backend"
#endif
const BIGNUM* Dsa::getP() const {
#if NCRYPTO_USE_OPENSSL_PROVIDER
if (!dsa_) return nullptr;
return p_.get();
#elif NCRYPTO_USE_BORINGSSL
if (dsa_ == nullptr) return nullptr;
const BIGNUM* p;
DSA_get0_pqg(dsa_, &p, nullptr, nullptr);
return p;
#else
#error "Unsupported crypto backend"
#endif
}
const BIGNUM* Dsa::getQ() const {
#if NCRYPTO_USE_OPENSSL_PROVIDER
if (!dsa_) return nullptr;
return q_.get();
#elif NCRYPTO_USE_BORINGSSL
if (dsa_ == nullptr) return nullptr;
const BIGNUM* q;
DSA_get0_pqg(dsa_, nullptr, &q, nullptr);
return q;
#else
#error "Unsupported crypto backend"
#endif
}
size_t Dsa::getModulusLength() const {
#if NCRYPTO_USE_OPENSSL_PROVIDER
if (!dsa_) return 0;
#elif NCRYPTO_USE_BORINGSSL
if (dsa_ == nullptr) return 0;
#else
#error "Unsupported crypto backend"
#endif
return BignumPointer::GetBitCount(getP());
}
size_t Dsa::getDivisorLength() const {
#if NCRYPTO_USE_OPENSSL_PROVIDER
if (!dsa_) return 0;
#elif NCRYPTO_USE_BORINGSSL
if (dsa_ == nullptr) return 0;
#else
#error "Unsupported crypto backend"
#endif
return BignumPointer::GetBitCount(getQ());
}
// ============================================================================
size_t Digest::size() const {
if (md_ == nullptr) return 0;
return EVP_MD_size(md_);
}
#if NCRYPTO_USE_OPENSSL_PROVIDER
Digest::Digest(DeleteFnPtr<EVP_MD, EVP_MD_free> md)
: md_(md.get()), fetched_md_(std::move(md)) {}
#endif
Digest::Digest(const Digest& other) : md_(other.md_) {
#if NCRYPTO_USE_OPENSSL_PROVIDER
if (other.fetched_md_ != nullptr) {
if (EVP_MD_up_ref(other.fetched_md_.get()) == 1) {
fetched_md_.reset(other.fetched_md_.get());
} else {
md_ = nullptr;
}
}
#endif
}
Digest& Digest::operator=(const Digest& other) {
if (this == &other) return *this;
#if NCRYPTO_USE_OPENSSL_PROVIDER
if (other.fetched_md_ != nullptr) {
if (EVP_MD_up_ref(other.fetched_md_.get()) == 1) {
fetched_md_.reset(other.fetched_md_.get());
} else {
fetched_md_.reset();
md_ = nullptr;
return *this;
}
} else {
fetched_md_.reset();
}
#endif
md_ = other.md_;
return *this;
}
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());
#if NCRYPTO_USE_OPENSSL_PROVIDER
namespace {
bool IsSupportedDigest(const EVP_MD* md) {
if (md == nullptr || EVP_MD_is_a(md, "NULL")) return false;
// OpenSSL currently crashes when ML-DSA-MU finalizes an empty input. Keep it
// unavailable until the provider implementation is fixed.
// https://github.com/openssl/openssl/issues/32445
if (EVP_MD_is_a(md, "ML-DSA-MU")) return false;
return true;
}
} // namespace
#endif
const Digest Digest::FromName(const char* name) {
const EVP_MD* md = ncrypto::getDigestByName(name);
if (md != nullptr) {
#if NCRYPTO_USE_OPENSSL_PROVIDER
if (md == EVP_md_null()) return Digest();
#endif
return Digest(md);
}
return Fetch(name);
}
const Digest Digest::Fetch(const char* name) {
#if NCRYPTO_USE_OPENSSL_PROVIDER
MarkPopErrorOnReturn mark_pop_error_on_return;
DeleteFnPtr<EVP_MD, EVP_MD_free> fetched(
EVP_MD_fetch(nullptr, name, nullptr));
if (IsSupportedDigest(fetched.get())) {
return Digest(std::move(fetched));
}
#endif
return Digest();
}
const Digest Digest::Ref(const EVP_MD* md) {
#if NCRYPTO_USE_OPENSSL_PROVIDER
if (md != nullptr && EVP_MD_get0_provider(md) != nullptr) {
if (EVP_MD_up_ref(const_cast<EVP_MD*>(md)) != 1) return Digest();
return Digest(DeleteFnPtr<EVP_MD, EVP_MD_free>(const_cast<EVP_MD*>(md)));
}
#endif
return Digest(md);
}
// ============================================================================
// KEM Implementation
#if OPENSSL_WITH_KEM
#if NCRYPTO_USE_OPENSSL_PROVIDER
bool KEM::SetOperationParameter(EVP_PKEY_CTX* ctx, const EVPKeyPointer& key) {
const OSSL_PARAM* settable = EVP_PKEY_CTX_settable_params(ctx);
if (settable == nullptr ||
OSSL_PARAM_locate_const(settable, "operation") == nullptr)
return true;
const char* operation = nullptr;
if (key.isA(KeyAlgorithm::RSA)) {
operation = "RSASVE";
} else if (key.isA(KeyAlgorithm::EC) || key.isA(KeyAlgorithm::X25519) ||
key.isA(KeyAlgorithm::X448)) {
operation = "DHKEM";
}
if (operation == nullptr) return true;
OSSL_PARAM params[] = {
OSSL_PARAM_utf8_string("operation", const_cast<char*>(operation), 0),
OSSL_PARAM_END};
return EVP_PKEY_CTX_set_params(ctx, params) > 0;
}
#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 NCRYPTO_USE_OPENSSL_PROVIDER
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 NCRYPTO_USE_OPENSSL_PROVIDER
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