#include "ncrypto.h" #include #include #include #include #include #include #include #include #include #include #if OPENSSL_VERSION_MAJOR >= 3 #include #endif // EVP_PKEY_CTX_set_dsa_paramgen_q_bits was added in OpenSSL 1.1.1e. #if OPENSSL_VERSION_NUMBER < 0x1010105fL #define EVP_PKEY_CTX_set_dsa_paramgen_q_bits(ctx, qbits) \ EVP_PKEY_CTX_ctrl((ctx), \ EVP_PKEY_DSA, \ EVP_PKEY_OP_PARAMGEN, \ EVP_PKEY_CTRL_DSA_PARAMGEN_Q_BITS, \ (qbits), \ nullptr) #endif namespace ncrypto { namespace { using BignumCtxPointer = DeleteFnPtr; using BignumGenCallbackPointer = DeleteFnPtr; using NetscapeSPKIPointer = DeleteFnPtr; static constexpr int kX509NameFlagsRFC2253WithinUtf8JSON = XN_FLAG_RFC2253 & ~ASN1_STRFLGS_ESC_MSB & ~ASN1_STRFLGS_ESC_CTRL; } // namespace // ============================================================================ ClearErrorOnReturn::ClearErrorOnReturn(CryptoErrorList* errors) : errors_(errors) { ERR_clear_error(); } ClearErrorOnReturn::~ClearErrorOnReturn() { if (errors_ != nullptr) errors_->capture(); ERR_clear_error(); } int ClearErrorOnReturn::peekError() { return ERR_peek_error(); } MarkPopErrorOnReturn::MarkPopErrorOnReturn(CryptoErrorList* errors) : errors_(errors) { ERR_set_mark(); } MarkPopErrorOnReturn::~MarkPopErrorOnReturn() { if (errors_ != nullptr) errors_->capture(); ERR_pop_to_mark(); } int MarkPopErrorOnReturn::peekError() { 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 CryptoErrorList::pop_back() { if (errors_.empty()) return std::nullopt; std::string error = errors_.back(); errors_.pop_back(); return error; } std::optional CryptoErrorList::pop_front() { if (errors_.empty()) return std::nullopt; std::string error = errors_.front(); errors_.pop_front(); return error; } // ============================================================================ DataPointer DataPointer::Alloc(size_t len) { #ifdef OPENSSL_IS_BORINGSSL // Boringssl does not implement OPENSSL_zalloc auto ptr = OPENSSL_malloc(len); if (ptr == nullptr) return {}; memset(ptr, 0, len); return DataPointer(ptr, len); #else return DataPointer(OPENSSL_zalloc(len), len); #endif } DataPointer DataPointer::SecureAlloc(size_t len) { #ifndef OPENSSL_IS_BORINGSSL auto ptr = OPENSSL_secure_zalloc(len); if (ptr == nullptr) return {}; return DataPointer(ptr, len, true); #else // BoringSSL does not implement the OPENSSL_secure_zalloc API. auto ptr = OPENSSL_malloc(len); if (ptr == nullptr) return {}; memset(ptr, 0, len); return DataPointer(ptr, len); #endif } size_t DataPointer::GetSecureHeapUsed() { #ifndef OPENSSL_IS_BORINGSSL return CRYPTO_secure_malloc_initialized() ? CRYPTO_secure_used() : 0; #else // BoringSSL does not have the secure heap and therefore // will always return 0. return 0; #endif } DataPointer::InitSecureHeapResult DataPointer::TryInitSecureHeap(size_t amount, size_t min) { #ifndef OPENSSL_IS_BORINGSSL switch (CRYPTO_secure_malloc_init(amount, min)) { case 0: return InitSecureHeapResult::FAILED; case 2: return InitSecureHeapResult::UNABLE_TO_MEMORY_MAP; case 1: return InitSecureHeapResult::OK; default: return InitSecureHeapResult::FAILED; } #else // BoringSSL does not actually support the secure heap return InitSecureHeapResult::FAILED; #endif } DataPointer DataPointer::Copy(const Buffer& 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& 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& buffer) { reset(buffer.data, buffer.len); } Buffer DataPointer::release() { Buffer buf{ .data = data_, .len = len_, }; data_ = nullptr; len_ = 0; return buf; } DataPointer DataPointer::resize(size_t len) { size_t actual_len = std::min(len_, len); auto buf = release(); if (actual_len == len_) return DataPointer(buf.data, actual_len); buf.data = OPENSSL_realloc(buf.data, actual_len); buf.len = actual_len; return DataPointer(buf); } // ============================================================================ bool isFipsEnabled() { ClearErrorOnReturn clear_error_on_return; #if OPENSSL_VERSION_MAJOR >= 3 return EVP_default_properties_is_fips_enabled(nullptr) == 1; #else return FIPS_mode() == 1; #endif } bool setFipsEnabled(bool enable, CryptoErrorList* errors) { if (isFipsEnabled() == enable) return true; ClearErrorOnReturn clearErrorOnReturn(errors); #if OPENSSL_VERSION_MAJOR >= 3 return EVP_default_properties_enable_fips(nullptr, enable ? 1 : 0) == 1 && EVP_default_properties_is_fips_enabled(nullptr); #else return FIPS_mode_set(enable ? 1 : 0) == 1; #endif } bool testFipsEnabled() { ClearErrorOnReturn clear_error_on_return; #if OPENSSL_VERSION_MAJOR >= 3 OSSL_PROVIDER* fips_provider = nullptr; if (OSSL_PROVIDER_available(nullptr, "fips")) { fips_provider = OSSL_PROVIDER_load(nullptr, "fips"); } if (fips_provider == nullptr) return false; int result = OSSL_PROVIDER_self_test(fips_provider); OSSL_PROVIDER_unload(fips_provider); return result; #else #ifdef OPENSSL_FIPS return FIPS_selftest(); #else // OPENSSL_FIPS return false; #endif // OPENSSL_FIPS #endif } // ============================================================================ // Bignum BignumPointer::BignumPointer(BIGNUM* bignum) : bn_(bignum) {} BignumPointer::BignumPointer(const unsigned char* data, size_t len) : BignumPointer(BN_bin2bn(data, len, nullptr)) {} BignumPointer::BignumPointer(BignumPointer&& other) noexcept : bn_(other.release()) {} BignumPointer BignumPointer::New() { return BignumPointer(BN_new()); } BignumPointer BignumPointer::NewSecure() { #ifdef OPENSSL_IS_BORINGSSL // Boringssl does not implement BN_secure_new. return New(); #else return BignumPointer(BN_secure_new()); #endif } BignumPointer& BignumPointer::operator=(BignumPointer&& other) noexcept { if (this == &other) return *this; this->~BignumPointer(); return *new (this) BignumPointer(std::move(other)); } BignumPointer::~BignumPointer() { reset(); } void BignumPointer::reset(BIGNUM* bn) { bn_.reset(bn); } void BignumPointer::reset(const unsigned char* data, size_t len) { reset(BN_bin2bn(data, len, nullptr)); } BIGNUM* BignumPointer::release() { return bn_.release(); } size_t BignumPointer::byteLength() const { if (bn_ == nullptr) return 0; return BN_num_bytes(bn_.get()); } DataPointer BignumPointer::encode() const { return EncodePadded(bn_.get(), byteLength()); } DataPointer BignumPointer::encodePadded(size_t size) const { return EncodePadded(bn_.get(), size); } size_t BignumPointer::encodeInto(unsigned char* out) const { if (!bn_) return 0; return BN_bn2bin(bn_.get(), out); } size_t BignumPointer::encodePaddedInto(unsigned char* out, size_t size) const { if (!bn_) return 0; return BN_bn2binpad(bn_.get(), out, size); } DataPointer BignumPointer::Encode(const BIGNUM* bn) { return EncodePadded(bn, bn != nullptr ? BN_num_bytes(bn) : 0); } bool BignumPointer::setWord(unsigned long w) { // NOLINT(runtime/int) if (!bn_) return false; return BN_set_word(bn_.get(), w) == 1; } unsigned long BignumPointer::GetWord(const BIGNUM* bn) { // NOLINT(runtime/int) return BN_get_word(bn); } unsigned long BignumPointer::getWord() const { // NOLINT(runtime/int) if (!bn_) return 0; 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(GetByteCount(bn))); auto buf = DataPointer::Alloc(size); BN_bn2binpad(bn, reinterpret_cast(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(BN_GENCB_get_arg(ctx)); return ptr(a, b) ? 1 : 0; }, &innerCb); } return BN_is_prime_ex(get(), nchecks, ctx.get(), cb.get()); } 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(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(buffer); do { if (1 == RAND_status()) { #if OPENSSL_VERSION_MAJOR >= 3 if (1 == RAND_bytes_ex(nullptr, buf, length, 0)) { return true; } #else while (length > INT_MAX && 1 == RAND_bytes(buf, INT_MAX)) { buf += INT_MAX; length -= INT_MAX; } if (length <= INT_MAX && 1 == RAND_bytes(buf, static_cast(length))) return true; #endif } #if OPENSSL_VERSION_MAJOR >= 3 const auto code = ERR_peek_last_error(); // A misconfigured OpenSSL 3 installation may report 1 from RAND_poll() // and RAND_status() but fail in RAND_bytes() if it cannot look up // a matching algorithm for the CSPRNG. if (ERR_GET_LIB(code) == ERR_LIB_RAND) { const auto reason = ERR_GET_REASON(code); if (reason == RAND_R_ERROR_INSTANTIATING_DRBG || reason == RAND_R_UNABLE_TO_FETCH_DRBG || reason == RAND_R_UNABLE_TO_CREATE_DRBG) { return false; } } #endif } while (1 == RAND_poll()); return false; } int NoPasswordCallback(char* buf, int size, int rwflag, void* u) { return 0; } int PasswordCallback(char* buf, int size, int rwflag, void* u) { auto passphrase = static_cast*>(u); if (passphrase != nullptr) { size_t buflen = static_cast(size); size_t len = passphrase->len; if (buflen < len) return -1; memcpy(buf, reinterpret_cast(passphrase->data), len); return len; } return -1; } // 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(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(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(days_since_epoch) + t->tm_hour) + t->tm_min) + t->tm_sec; } #endif // ============================================================================ // SPKAC bool VerifySpkac(const char* input, size_t length) { #ifdef OPENSSL_IS_BORINGSSL // OpenSSL uses EVP_DecodeBlock, which explicitly removes trailing characters, // while BoringSSL uses EVP_DecodedLength and EVP_DecodeBase64, which do not. // As such, we trim those characters here for compatibility. // // find_last_not_of can return npos, which is the maximum value of size_t. // The + 1 will force a roll-ver to 0, which is the correct value. in that // case. length = std::string_view(input, length).find_last_not_of(" \n\r\t") + 1; #endif NetscapeSPKIPointer spki(NETSCAPE_SPKI_b64_decode(input, length)); if (!spki) return false; EVPKeyPointer pkey(X509_PUBKEY_get(spki->spkac->pubkey)); return pkey ? NETSCAPE_SPKI_verify(spki.get(), pkey.get()) > 0 : false; } BIOPointer ExportPublicKey(const char* input, size_t length) { BIOPointer bio(BIO_new(BIO_s_mem())); if (!bio) return {}; #ifdef OPENSSL_IS_BORINGSSL // OpenSSL uses EVP_DecodeBlock, which explicitly removes trailing characters, // while BoringSSL uses EVP_DecodedLength and EVP_DecodeBase64, which do not. // As such, we trim those characters here for compatibility. length = std::string_view(input, length).find_last_not_of(" \n\r\t") + 1; #endif NetscapeSPKIPointer spki(NETSCAPE_SPKI_b64_decode(input, length)); if (!spki) return {}; EVPKeyPointer pkey(NETSCAPE_SPKI_get_pubkey(spki.get())); if (!pkey) return {}; if (PEM_write_bio_PUBKEY(bio.get(), pkey.get()) <= 0) return {}; return bio; } Buffer ExportChallenge(const char* input, size_t length) { #ifdef OPENSSL_IS_BORINGSSL // OpenSSL uses EVP_DecodeBlock, which explicitly removes trailing characters, // while BoringSSL uses EVP_DecodedLength and EVP_DecodeBase64, which do not. // As such, we trim those characters here for compatibility. length = std::string_view(input, length).find_last_not_of(" \n\r\t") + 1; #endif NetscapeSPKIPointer sp(NETSCAPE_SPKI_b64_decode(input, length)); if (!sp) return {}; unsigned char* buf = nullptr; int buf_size = ASN1_STRING_to_UTF8(&buf, sp->spkac->challenge); if (buf_size >= 0) { return { .data = reinterpret_cast(buf), .len = static_cast(buf_size), }; } return {}; } // ============================================================================ namespace { enum class AltNameOption { NONE, UTF8, }; bool IsSafeAltName(const char* name, size_t length, AltNameOption option) { for (size_t i = 0; i < length; i++) { char c = name[i]; switch (c) { case '"': case '\\': // These mess with encoding rules. // Fall through. case ',': // Commas make it impossible to split the list of subject alternative // names unambiguously, which is why we have to escape. // Fall through. case '\'': // Single quotes are unlikely to appear in any legitimate values, but // they could be used to make a value look like it was escaped (i.e., // enclosed in single/double quotes). return false; default: if (option == AltNameOption::UTF8) { // In UTF8 strings, we require escaping for any ASCII control // character, but NOT for non-ASCII characters. Note that all bytes of // any code point that consists of more than a single byte have their // MSB set. if (static_cast(c) < ' ' || c == '\x7f') { return false; } } else { // Check if the char is a control character or non-ASCII character. // Note that char may or may not be a signed type. Regardless, // non-ASCII values will always be outside of this range. if (c < ' ' || c > '~') { return false; } } } } return true; } void PrintAltName(const BIOPointer& out, const char* name, size_t length, AltNameOption option = AltNameOption::NONE, const char* safe_prefix = nullptr) { if (IsSafeAltName(name, length, option)) { // For backward-compatibility, append "safe" names without any // modifications. if (safe_prefix != nullptr) { BIO_printf(out.get(), "%s:", safe_prefix); } BIO_write(out.get(), name, length); } else { // If a name is not "safe", we cannot embed it without special // encoding. This does not usually happen, but we don't want to hide // it from the user either. We use JSON compatible escaping here. BIO_write(out.get(), "\"", 1); if (safe_prefix != nullptr) { BIO_printf(out.get(), "%s:", safe_prefix); } for (size_t j = 0; j < length; j++) { char c = static_cast(name[j]); if (c == '\\') { BIO_write(out.get(), "\\\\", 2); } else if (c == '"') { BIO_write(out.get(), "\\\"", 2); } else if ((c >= ' ' && c != ',' && c <= '~') || (option == AltNameOption::UTF8 && (c & 0x80))) { // Note that the above condition explicitly excludes commas, which means // that those are encoded as Unicode escape sequences in the "else" // block. That is not strictly necessary, and Node.js itself would parse // it correctly either way. We only do this to account for third-party // code that might be splitting the string at commas (as Node.js itself // used to do). BIO_write(out.get(), &c, 1); } else { // Control character or non-ASCII character. We treat everything as // Latin-1, which corresponds to the first 255 Unicode code points. const char hex[] = "0123456789abcdef"; char u[] = {'\\', 'u', '0', '0', hex[(c & 0xf0) >> 4], hex[c & 0x0f]}; BIO_write(out.get(), u, sizeof(u)); } } BIO_write(out.get(), "\"", 1); } } // This function emulates the behavior of i2v_GENERAL_NAME in a safer and less // ambiguous way. "othername:" entries use the GENERAL_NAME_print format. bool PrintGeneralName(const BIOPointer& out, const GENERAL_NAME* gen) { if (gen->type == GEN_DNS) { ASN1_IA5STRING* name = gen->d.dNSName; BIO_write(out.get(), "DNS:", 4); // Note that the preferred name syntax (see RFCs 5280 and 1034) with // wildcards is a subset of what we consider "safe", so spec-compliant DNS // names will never need to be escaped. PrintAltName(out, reinterpret_cast(name->data), name->length); } else if (gen->type == GEN_EMAIL) { ASN1_IA5STRING* name = gen->d.rfc822Name; BIO_write(out.get(), "email:", 6); PrintAltName(out, reinterpret_cast(name->data), name->length); } else if (gen->type == GEN_URI) { ASN1_IA5STRING* name = gen->d.uniformResourceIdentifier; BIO_write(out.get(), "URI:", 4); // The set of "safe" names was designed to include just about any URI, // with a few exceptions, most notably URIs that contains commas (see // RFC 2396). In other words, most legitimate URIs will not require // escaping. PrintAltName(out, reinterpret_cast(name->data), name->length); } else if (gen->type == GEN_DIRNAME) { // Earlier versions of Node.js used X509_NAME_oneline to print the X509_NAME // object. The format was non standard and should be avoided. The use of // X509_NAME_oneline is discouraged by OpenSSL but was required for backward // compatibility. Conveniently, X509_NAME_oneline produced ASCII and the // output was unlikely to contains commas or other characters that would // require escaping. However, it SHOULD NOT produce ASCII output since an // RFC5280 AttributeValue may be a UTF8String. // Newer versions of Node.js have since switched to X509_NAME_print_ex to // produce a better format at the cost of backward compatibility. The new // format may contain Unicode characters and it is likely to contain commas, // which require escaping. Fortunately, the recently safeguarded function // PrintAltName handles all of that safely. BIO_printf(out.get(), "DirName:"); BIOPointer tmp(BIO_new(BIO_s_mem())); NCRYPTO_ASSERT_TRUE(tmp); if (X509_NAME_print_ex( tmp.get(), gen->d.dirn, 0, kX509NameFlagsRFC2253WithinUtf8JSON) < 0) { return false; } char* oline = nullptr; long n_bytes = BIO_get_mem_data(tmp.get(), &oline); // NOLINT(runtime/int) NCRYPTO_ASSERT_TRUE(n_bytes >= 0); PrintAltName(out, oline, static_cast(n_bytes), ncrypto::AltNameOption::UTF8, nullptr); } else if (gen->type == GEN_IPADD) { BIO_printf(out.get(), "IP Address:"); const ASN1_OCTET_STRING* ip = gen->d.ip; const unsigned char* b = ip->data; if (ip->length == 4) { BIO_printf(out.get(), "%d.%d.%d.%d", b[0], b[1], b[2], b[3]); } else if (ip->length == 16) { for (unsigned int j = 0; j < 8; j++) { uint16_t pair = (b[2 * j] << 8) | b[2 * j + 1]; BIO_printf(out.get(), (j == 0) ? "%X" : ":%X", pair); } } else { #if OPENSSL_VERSION_MAJOR >= 3 BIO_printf(out.get(), "", ip->length); #else BIO_printf(out.get(), ""); #endif } } else if (gen->type == GEN_RID) { // Unlike OpenSSL's default implementation, never print the OID as text and // instead always print its numeric representation. char oline[256]; OBJ_obj2txt(oline, sizeof(oline), gen->d.rid, true); BIO_printf(out.get(), "Registered ID:%s", oline); } else if (gen->type == GEN_OTHERNAME) { // The format that is used here is based on OpenSSL's implementation of // GENERAL_NAME_print (as of OpenSSL 3.0.1). Earlier versions of Node.js // instead produced the same format as i2v_GENERAL_NAME, which was somewhat // awkward, especially when passed to translatePeerCertificate. bool unicode = true; const char* prefix = nullptr; // OpenSSL 1.1.1 does not support othername in GENERAL_NAME_print and may // not define these NIDs. #if OPENSSL_VERSION_MAJOR >= 3 int nid = OBJ_obj2nid(gen->d.otherName->type_id); switch (nid) { case NID_id_on_SmtpUTF8Mailbox: prefix = "SmtpUTF8Mailbox"; break; case NID_XmppAddr: prefix = "XmppAddr"; break; case NID_SRVName: prefix = "SRVName"; unicode = false; break; case NID_ms_upn: prefix = "UPN"; break; case NID_NAIRealm: prefix = "NAIRealm"; break; } #endif // OPENSSL_VERSION_MAJOR >= 3 int val_type = gen->d.otherName->value->type; if (prefix == nullptr || (unicode && val_type != V_ASN1_UTF8STRING) || (!unicode && val_type != V_ASN1_IA5STRING)) { BIO_printf(out.get(), "othername:"); } else { BIO_printf(out.get(), "othername:"); if (unicode) { auto name = gen->d.otherName->value->value.utf8string; PrintAltName(out, reinterpret_cast(name->data), name->length, AltNameOption::UTF8, prefix); } else { auto name = gen->d.otherName->value->value.ia5string; PrintAltName(out, reinterpret_cast(name->data), name->length, AltNameOption::NONE, prefix); } } } else if (gen->type == GEN_X400) { // TODO(tniessen): this is what OpenSSL does, implement properly instead BIO_printf(out.get(), "X400Name:"); } else if (gen->type == GEN_EDIPARTY) { // TODO(tniessen): this is what OpenSSL does, implement properly instead BIO_printf(out.get(), "EdiPartyName:"); } 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, X509_EXTENSION* ext) { auto ret = OBJ_obj2nid(X509_EXTENSION_get_object(ext)); if (ret != NID_subject_alt_name) return false; GENERAL_NAMES* names = static_cast(X509V3_EXT_d2i(ext)); if (names == nullptr) return false; bool ok = true; for (OPENSSL_SIZE_T i = 0; i < sk_GENERAL_NAME_num(names); i++) { GENERAL_NAME* gen = sk_GENERAL_NAME_value(names, i); if (i != 0) BIO_write(out.get(), ", ", 2); if (!(ok = ncrypto::PrintGeneralName(out, gen))) { break; } } sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free); return ok; } bool SafeX509InfoAccessPrint(const BIOPointer& out, X509_EXTENSION* ext) { auto ret = OBJ_obj2nid(X509_EXTENSION_get_object(ext)); if (ret != NID_info_access) return false; AUTHORITY_INFO_ACCESS* descs = static_cast(X509V3_EXT_d2i(ext)); if (descs == nullptr) return false; bool ok = true; for (OPENSSL_SIZE_T i = 0; i < sk_ACCESS_DESCRIPTION_num(descs); i++) { ACCESS_DESCRIPTION* desc = sk_ACCESS_DESCRIPTION_value(descs, i); if (i != 0) BIO_write(out.get(), "\n", 1); char objtmp[80]; i2t_ASN1_OBJECT(objtmp, sizeof(objtmp), desc->method); BIO_printf(out.get(), "%s - ", objtmp); if (!(ok = ncrypto::PrintGeneralName(out, desc->location))) { break; } } sk_ACCESS_DESCRIPTION_pop_free(descs, ACCESS_DESCRIPTION_free); #if OPENSSL_VERSION_MAJOR < 3 BIO_write(out.get(), "\n", 1); #endif return ok; } // ============================================================================ // X509Pointer X509Pointer::X509Pointer(X509* x509) : cert_(x509) {} X509Pointer::X509Pointer(X509Pointer&& other) noexcept : cert_(other.release()) {} X509Pointer& X509Pointer::operator=(X509Pointer&& other) noexcept { if (this == &other) return *this; this->~X509Pointer(); return *new (this) X509Pointer(std::move(other)); } X509Pointer::~X509Pointer() { reset(); } void X509Pointer::reset(X509* x509) { cert_.reset(x509); } X509* X509Pointer::release() { return cert_.release(); } X509View X509Pointer::view() const { return X509View(cert_.get()); } BIOPointer X509View::toPEM() const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr) return {}; BIOPointer bio(BIO_new(BIO_s_mem())); if (!bio) return {}; if (PEM_write_bio_X509(bio.get(), const_cast(cert_)) <= 0) return {}; return bio; } BIOPointer X509View::toDER() const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr) return {}; BIOPointer bio(BIO_new(BIO_s_mem())); if (!bio) return {}; if (i2d_X509_bio(bio.get(), const_cast(cert_)) <= 0) return {}; return bio; } const X509Name X509View::getSubjectName() const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr) return {}; return X509Name(X509_get_subject_name(cert_)); } const X509Name X509View::getIssuerName() const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr) return {}; return X509Name(X509_get_issuer_name(cert_)); } BIOPointer X509View::getSubject() const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr) return {}; BIOPointer bio(BIO_new(BIO_s_mem())); if (!bio) return {}; if (X509_NAME_print_ex(bio.get(), X509_get_subject_name(cert_), 0, kX509NameFlagsMultiline) <= 0) { return {}; } return bio; } BIOPointer X509View::getSubjectAltName() const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr) return {}; BIOPointer bio(BIO_new(BIO_s_mem())); if (!bio) return {}; int index = X509_get_ext_by_NID(cert_, NID_subject_alt_name, -1); if (index < 0 || !SafeX509SubjectAltNamePrint(bio, X509_get_ext(cert_, index))) { return {}; } return bio; } BIOPointer X509View::getIssuer() const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr) return {}; BIOPointer bio(BIO_new(BIO_s_mem())); if (!bio) return {}; if (X509_NAME_print_ex( bio.get(), X509_get_issuer_name(cert_), 0, kX509NameFlagsMultiline) <= 0) { return {}; } return bio; } BIOPointer X509View::getInfoAccess() const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr) return {}; BIOPointer bio(BIO_new(BIO_s_mem())); if (!bio) return {}; int index = X509_get_ext_by_NID(cert_, NID_info_access, -1); if (index < 0) return {}; if (!SafeX509InfoAccessPrint(bio, X509_get_ext(cert_, index))) { return {}; } return bio; } BIOPointer X509View::getValidFrom() const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr) return {}; BIOPointer bio(BIO_new(BIO_s_mem())); if (!bio) return {}; ASN1_TIME_print(bio.get(), X509_get_notBefore(cert_)); return bio; } BIOPointer X509View::getValidTo() const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr) return {}; BIOPointer bio(BIO_new(BIO_s_mem())); if (!bio) return {}; ASN1_TIME_print(bio.get(), X509_get_notAfter(cert_)); return bio; } int64_t X509View::getValidToTime() const { #ifdef OPENSSL_IS_BORINGSSL // Boringssl does not implement ASN1_TIME_to_tm in a public way, // and only recently added ASN1_TIME_to_posix. Some boringssl // users on older version may still need to patch around this // or use a different implementation. int64_t tp; ASN1_TIME_to_posix(X509_get0_notAfter(cert_), &tp); return tp; #else struct tm tp; ASN1_TIME_to_tm(X509_get0_notAfter(cert_), &tp); return PortableTimeGM(&tp); #endif } int64_t X509View::getValidFromTime() const { #ifdef OPENSSL_IS_BORINGSSL int64_t tp; ASN1_TIME_to_posix(X509_get0_notBefore(cert_), &tp); return tp; #else struct tm tp; ASN1_TIME_to_tm(X509_get0_notBefore(cert_), &tp); return PortableTimeGM(&tp); #endif } DataPointer X509View::getSerialNumber() const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr) return {}; if (ASN1_INTEGER* serial_number = X509_get_serialNumber(const_cast(cert_))) { if (auto bn = BignumPointer(ASN1_INTEGER_to_BN(serial_number, nullptr))) { return bn.toHex(); } } return {}; } Result X509View::getPublicKey() const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr) return Result(EVPKeyPointer{}); auto pkey = EVPKeyPointer(X509_get_pubkey(const_cast(cert_))); if (!pkey) return Result(ERR_get_error()); return pkey; } StackOfASN1 X509View::getKeyUsage() const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr) return {}; return StackOfASN1(static_cast( 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(cert_)) == 1; } bool X509View::isIssuedBy(const X509View& issuer) const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr || issuer.cert_ == nullptr) return false; return X509_check_issued(const_cast(issuer.cert_), const_cast(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(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(cert_), pkey.get()) == 1; } X509View::CheckMatch X509View::checkHost(const std::string_view host, int flags, DataPointer* peerName) const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr) return CheckMatch::NO_MATCH; char* peername; switch (X509_check_host( const_cast(cert_), host.data(), host.size(), flags, &peername)) { case 0: return CheckMatch::NO_MATCH; case 1: { if (peername != nullptr) { DataPointer name(peername, strlen(peername)); if (peerName != nullptr) *peerName = std::move(name); } return CheckMatch::MATCH; } case -2: return CheckMatch::INVALID_NAME; default: return CheckMatch::OPERATION_FAILED; } } X509View::CheckMatch X509View::checkEmail(const std::string_view email, int flags) const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr) return CheckMatch::NO_MATCH; switch (X509_check_email( const_cast(cert_), email.data(), email.size(), flags)) { case 0: return CheckMatch::NO_MATCH; case 1: return CheckMatch::MATCH; case -2: return CheckMatch::INVALID_NAME; default: return CheckMatch::OPERATION_FAILED; } } X509View::CheckMatch X509View::checkIp(const std::string_view ip, int flags) const { ClearErrorOnReturn clearErrorOnReturn; if (cert_ == nullptr) return CheckMatch::NO_MATCH; switch (X509_check_ip_asc(const_cast(cert_), ip.data(), flags)) { case 0: return CheckMatch::NO_MATCH; case 1: return CheckMatch::MATCH; case -2: return CheckMatch::INVALID_NAME; default: return CheckMatch::OPERATION_FAILED; } } X509View X509View::From(const SSLPointer& ssl) { ClearErrorOnReturn clear_error_on_return; if (!ssl) return {}; return X509View(SSL_get_certificate(ssl.get())); } X509View X509View::From(const SSLCtxPointer& ctx) { ClearErrorOnReturn clear_error_on_return; if (!ctx) return {}; return X509View(SSL_CTX_get0_certificate(ctx.get())); } std::optional 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(cert_))); } Result X509Pointer::Parse( Buffer buffer) { ClearErrorOnReturn clearErrorOnReturn; BIOPointer bio(BIO_new_mem_buf(buffer.data, buffer.len)); if (!bio) return Result(ERR_get_error()); X509Pointer pem( PEM_read_bio_X509_AUX(bio.get(), nullptr, NoPasswordCallback, nullptr)); if (pem) return Result(std::move(pem)); BIO_reset(bio.get()); X509Pointer der(d2i_X509_bio(bio.get(), nullptr)); if (der) return Result(std::move(der)); return Result(ERR_get_error()); } bool X509View::enumUsages(UsageCallback callback) const { if (cert_ == nullptr) return false; StackOfASN1 eku(static_cast( X509_get_ext_d2i(cert_, NID_ext_key_usage, nullptr, nullptr))); if (!eku) return false; const int count = sk_ASN1_OBJECT_num(eku.get()); char buf[256]{}; for (int i = 0; i < count; i++) { if (OBJ_obj2txt(buf, sizeof(buf), sk_ASN1_OBJECT_value(eku.get(), i), 1) >= 0) { callback(buf); } } return true; } bool X509View::ifRsa(KeyCallback callback) const { if (cert_ == nullptr) return true; OSSL3_CONST EVP_PKEY* pkey = X509_get0_pubkey(cert_); auto id = EVP_PKEY_id(pkey); if (id == EVP_PKEY_RSA || id == EVP_PKEY_RSA2 || id == EVP_PKEY_RSA_PSS) { Rsa rsa(EVP_PKEY_get0_RSA(pkey)); if (!rsa) [[unlikely]] return true; return callback(rsa); } return true; } bool X509View::ifEc(KeyCallback callback) const { if (cert_ == nullptr) return true; OSSL3_CONST EVP_PKEY* pkey = X509_get0_pubkey(cert_); auto id = EVP_PKEY_id(pkey); if (id == EVP_PKEY_EC) { Ec ec(EVP_PKEY_get0_EC_KEY(pkey)); 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 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) { return X509Pointer(SSL_get_peer_certificate(ssl.get())); } // 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 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) { return BIOPointer(BIO_new(method)); } BIOPointer BIOPointer::New(const void* data, size_t len) { return BIOPointer(BIO_new_mem_buf(data, len)); } BIOPointer BIOPointer::NewFile(const char* filename, const char* mode) { return BIOPointer(BIO_new_file(filename, mode)); } BIOPointer BIOPointer::NewFp(FILE* fd, int close_flag) { return BIOPointer(BIO_new_fp(fd, close_flag)); } BIOPointer BIOPointer::New(const BIGNUM* bn) { auto res = NewMem(); if (!res || !BN_print(res.get(), bn)) return {}; return res; } int BIOPointer::Write(BIOPointer* bio, std::string_view message) { if (bio == nullptr || !*bio) return 0; return BIO_write(bio->get(), message.data(), message.size()); } // ============================================================================ // DHPointer namespace { bool EqualNoCase(const std::string_view a, const std::string_view b) { if (a.size() != b.size()) return false; return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](char a, char b) { return std::tolower(a) == std::tolower(b); }); } } // namespace DHPointer::DHPointer(DH* dh) : dh_(dh) {} DHPointer::DHPointer(DHPointer&& other) noexcept : dh_(other.release()) {} 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(DH* dh) { dh_.reset(dh); } DH* DHPointer::release() { return dh_.release(); } BignumPointer DHPointer::FindGroup(const std::string_view name, FindGroupOption option) { #define V(n, p) \ if (EqualNoCase(name, n)) return BignumPointer(p(nullptr)); if (option != FindGroupOption::NO_SMALL_PRIMES) { #ifndef OPENSSL_IS_BORINGSSL // Boringssl does not support the 768 and 1024 small primes V("modp1", BN_get_rfc2409_prime_768); V("modp2", BN_get_rfc2409_prime_1024); #endif V("modp5", BN_get_rfc3526_prime_1536); } V("modp14", BN_get_rfc3526_prime_2048); V("modp15", BN_get_rfc3526_prime_3072); V("modp16", BN_get_rfc3526_prime_4096); V("modp17", BN_get_rfc3526_prime_6144); V("modp18", BN_get_rfc3526_prime_8192); #undef V return {}; } BignumPointer DHPointer::GetStandardGenerator() { auto bn = BignumPointer::New(); if (!bn) return {}; if (!bn.setWord(DH_GENERATOR_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. return New(std::move(group), std::move(generator)); } DHPointer DHPointer::New(BignumPointer&& p, BignumPointer&& g) { if (!p || !g) return {}; 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; } DHPointer DHPointer::New(size_t bits, unsigned int generator) { DHPointer dh(DH_new()); if (!dh) return {}; if (DH_generate_parameters_ex(dh.get(), bits, generator, nullptr) != 1) { return {}; } return dh; } DHPointer::CheckResult DHPointer::check() { ClearErrorOnReturn clearErrorOnReturn; if (!dh_) return DHPointer::CheckResult::NONE; int codes = 0; if (DH_check(dh_.get(), &codes) != 1) return DHPointer::CheckResult::CHECK_FAILED; return static_cast(codes); } DHPointer::CheckPublicKeyResult DHPointer::checkPublicKey( const BignumPointer& pub_key) { ClearErrorOnReturn clearErrorOnReturn; if (!pub_key || !dh_) { return DHPointer::CheckPublicKeyResult::CHECK_FAILED; } int codes = 0; if (DH_check_pub_key(dh_.get(), pub_key.get(), &codes) != 1) { return DHPointer::CheckPublicKeyResult::CHECK_FAILED; } #ifndef OPENSSL_IS_BORINGSSL // Boringssl does not define DH_CHECK_PUBKEY_TOO_SMALL or TOO_LARGE if (codes & DH_CHECK_PUBKEY_TOO_SMALL) { return DHPointer::CheckPublicKeyResult::TOO_SMALL; } else if (codes & DH_CHECK_PUBKEY_TOO_LARGE) { return DHPointer::CheckPublicKeyResult::TOO_LARGE; } #endif if (codes != 0) { return DHPointer::CheckPublicKeyResult::INVALID; } return CheckPublicKeyResult::NONE; } DataPointer DHPointer::getPrime() const { if (!dh_) return {}; const BIGNUM* p; DH_get0_pqg(dh_.get(), &p, nullptr, nullptr); return BignumPointer::Encode(p); } DataPointer DHPointer::getGenerator() const { if (!dh_) return {}; const BIGNUM* g; DH_get0_pqg(dh_.get(), nullptr, nullptr, &g); return BignumPointer::Encode(g); } DataPointer DHPointer::getPublicKey() const { if (!dh_) return {}; const BIGNUM* pub_key; DH_get0_key(dh_.get(), &pub_key, nullptr); return BignumPointer::Encode(pub_key); } DataPointer DHPointer::getPrivateKey() const { if (!dh_) return {}; const BIGNUM* pvt_key; DH_get0_key(dh_.get(), nullptr, &pvt_key); return BignumPointer::Encode(pvt_key); } DataPointer DHPointer::generateKeys() const { ClearErrorOnReturn clearErrorOnReturn; if (!dh_) return {}; // Key generation failed if (!DH_generate_key(dh_.get())) return {}; return getPublicKey(); } size_t DHPointer::size() const { if (!dh_) return 0; int ret = DH_size(dh_.get()); // DH_size can return a -1 on error but we just want to return a 0 // in that case so we don't wrap around when returning the size_t. return ret >= 0 ? static_cast(ret) : 0; } DataPointer DHPointer::computeSecret(const BignumPointer& peer) const { ClearErrorOnReturn clearErrorOnReturn; if (!dh_ || !peer) return {}; auto dp = DataPointer::Alloc(size()); if (!dp) return {}; int size = DH_compute_key(static_cast(dp.get()), peer.get(), dh_.get()); if (size < 0) return {}; // The size of the computed key can be smaller than the size of the DH key. // We want to make sure that the key is correctly padded. if (static_cast(size) < dp.size()) { const size_t padding = dp.size() - size; uint8_t* data = static_cast(dp.get()); memmove(data + padding, data, size); memset(data, 0, padding); } return dp; } bool DHPointer::setPublicKey(BignumPointer&& key) { if (!dh_) return false; 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; } bool DHPointer::setPrivateKey(BignumPointer&& key) { if (!dh_) return false; 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; } 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 || 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(out.get()), &out_size) <= 0) { return {}; } if (out_size < out.size()) { const size_t padding = out.size() - out_size; uint8_t* data = static_cast(out.get()); memmove(data + padding, data, out_size); memset(data, 0, padding); } return out; } // ============================================================================ // KDF const EVP_MD* getDigestByName(const char* name) { // Historically, "dss1" and "DSS1" were DSA aliases for SHA-1 // exposed through the public API. if (strcmp(name, "dss1") == 0 || strcmp(name, "DSS1") == 0) [[unlikely]] { return EVP_sha1(); } return EVP_get_digestbyname(name); } const EVP_CIPHER* getCipherByName(const char* name) { return EVP_get_cipherbyname(name); } bool checkHkdfLength(const Digest& md, size_t length) { // HKDF-Expand computes up to 255 HMAC blocks, each having as many bits as // the output of the hash function. 255 is a hard limit because HKDF appends // an 8-bit counter to each HMAC'd message, starting at 1. static constexpr size_t kMaxDigestMultiplier = 255; size_t max_length = md.size() * kMaxDigestMultiplier; if (length > max_length) return false; return true; } DataPointer hkdf(const Digest& md, const Buffer& key, const Buffer& info, const Buffer& salt, size_t length) { ClearErrorOnReturn clearErrorOnReturn; if (!checkHkdfLength(md, length) || info.len > INT_MAX || salt.len > INT_MAX) { return {}; } auto ctx = EVPKeyCtxPointer::NewFromID(EVP_PKEY_HKDF); // OpenSSL < 3.0.0 accepted only a void* as the argument of // EVP_PKEY_CTX_set_hkdf_md. const EVP_MD* md_ptr = md; if (!ctx || !EVP_PKEY_derive_init(ctx.get()) || !EVP_PKEY_CTX_set_hkdf_md(ctx.get(), md_ptr) || !EVP_PKEY_CTX_add1_hkdf_info(ctx.get(), info.data, info.len)) { return {}; } std::string_view actual_salt; static const char default_salt[EVP_MAX_MD_SIZE] = {0}; if (salt.len > 0) { actual_salt = {reinterpret_cast(salt.data), salt.len}; } else { actual_salt = {default_salt, static_cast(md.size())}; } // We do not use EVP_PKEY_HKDF_MODE_EXTRACT_AND_EXPAND because and instead // implement the extraction step ourselves because EVP_PKEY_derive does not // handle zero-length keys, which are required for Web Crypto. // TODO(jasnell): Once OpenSSL 1.1.1 support is dropped completely, and once // BoringSSL is confirmed to support it, wen can hopefully drop this and use // EVP_KDF directly which does support zero length keys. unsigned char pseudorandom_key[EVP_MAX_MD_SIZE]; unsigned pseudorandom_key_len = sizeof(pseudorandom_key); if (HMAC(md, actual_salt.data(), actual_salt.size(), key.data, key.len, pseudorandom_key, &pseudorandom_key_len) == nullptr) { return {}; } if (!EVP_PKEY_CTX_hkdf_mode(ctx.get(), EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) || !EVP_PKEY_CTX_set1_hkdf_key( ctx.get(), pseudorandom_key, pseudorandom_key_len)) { return {}; } auto buf = DataPointer::Alloc(length); if (!buf) return {}; if (EVP_PKEY_derive( ctx.get(), static_cast(buf.get()), &length) <= 0) { return {}; } return buf; } bool checkScryptParams(uint64_t N, uint64_t r, uint64_t p, uint64_t maxmem) { return EVP_PBE_scrypt(nullptr, 0, nullptr, 0, N, r, p, maxmem, nullptr, 0) == 1; } DataPointer scrypt(const Buffer& pass, const Buffer& salt, uint64_t N, uint64_t r, uint64_t p, uint64_t maxmem, size_t length) { ClearErrorOnReturn clearErrorOnReturn; if (pass.len > INT_MAX || salt.len > INT_MAX) { return {}; } auto dp = DataPointer::Alloc(length); if (dp && EVP_PBE_scrypt(pass.data, pass.len, salt.data, salt.len, N, r, p, maxmem, reinterpret_cast(dp.get()), length)) { return dp; } return {}; } DataPointer pbkdf2(const Digest& md, const Buffer& pass, const Buffer& salt, uint32_t iterations, size_t length) { ClearErrorOnReturn clearErrorOnReturn; if (pass.len > INT_MAX || salt.len > INT_MAX || length > INT_MAX) { return {}; } auto dp = DataPointer::Alloc(length); const EVP_MD* md_ptr = md; if (dp && PKCS5_PBKDF2_HMAC(pass.data, pass.len, salt.data, salt.len, iterations, md_ptr, length, reinterpret_cast(dp.get()))) { return dp; } return {}; } // ============================================================================ EVPKeyPointer::PrivateKeyEncodingConfig::PrivateKeyEncodingConfig( const PrivateKeyEncodingConfig& other) : PrivateKeyEncodingConfig( other.output_key_object, other.format, other.type) { cipher = other.cipher; if (other.passphrase.has_value()) { auto& otherPassphrase = other.passphrase.value(); auto newPassphrase = DataPointer::Alloc(otherPassphrase.size()); memcpy(newPassphrase.get(), otherPassphrase.get(), otherPassphrase.size()); passphrase = std::move(newPassphrase); } } EVPKeyPointer::AsymmetricKeyEncodingConfig::AsymmetricKeyEncodingConfig( bool output_key_object, PKFormatType format, PKEncodingType type) : output_key_object(output_key_object), format(format), type(type) {} EVPKeyPointer::PrivateKeyEncodingConfig& EVPKeyPointer::PrivateKeyEncodingConfig::operator=( const PrivateKeyEncodingConfig& other) { if (this == &other) return *this; this->~PrivateKeyEncodingConfig(); return *new (this) PrivateKeyEncodingConfig(other); } EVPKeyPointer EVPKeyPointer::New() { return EVPKeyPointer(EVP_PKEY_new()); } EVPKeyPointer EVPKeyPointer::NewRawPublic( int id, const Buffer& data) { if (id == 0) return {}; return EVPKeyPointer( EVP_PKEY_new_raw_public_key(id, nullptr, data.data, data.len)); } EVPKeyPointer EVPKeyPointer::NewRawPrivate( int id, const Buffer& data) { if (id == 0) return {}; return EVPKeyPointer( EVP_PKEY_new_raw_private_key(id, nullptr, data.data, data.len)); } EVPKeyPointer EVPKeyPointer::NewDH(DHPointer&& dh) { if (!dh) return {}; auto key = New(); if (!key) return {}; if (EVP_PKEY_assign_DH(key.get(), dh.get())) { dh.release(); } return key; } 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; } EVPKeyPointer::EVPKeyPointer(EVP_PKEY* pkey) : pkey_(pkey) {} EVPKeyPointer::EVPKeyPointer(EVPKeyPointer&& other) noexcept : pkey_(other.release()) {} EVPKeyPointer& EVPKeyPointer::operator=(EVPKeyPointer&& other) noexcept { if (this == &other) return *this; this->~EVPKeyPointer(); return *new (this) EVPKeyPointer(std::move(other)); } EVPKeyPointer::~EVPKeyPointer() { reset(); } void EVPKeyPointer::reset(EVP_PKEY* pkey) { pkey_.reset(pkey); } EVP_PKEY* EVPKeyPointer::release() { return pkey_.release(); } int EVPKeyPointer::id(const EVP_PKEY* key) { if (key == nullptr) return 0; return EVP_PKEY_id(key); } int EVPKeyPointer::base_id(const EVP_PKEY* key) { if (key == nullptr) return 0; return EVP_PKEY_base_id(key); } int EVPKeyPointer::id() const { return id(get()); } int EVPKeyPointer::base_id() const { return base_id(get()); } int EVPKeyPointer::bits() const { if (get() == nullptr) return 0; return EVP_PKEY_bits(get()); } size_t EVPKeyPointer::size() const { if (get() == nullptr) return 0; return EVP_PKEY_size(get()); } EVPKeyCtxPointer EVPKeyPointer::newCtx() const { if (!pkey_) return {}; return EVPKeyCtxPointer::New(*this); } size_t EVPKeyPointer::rawPublicKeySize() const { if (!pkey_) return 0; size_t len = 0; if (EVP_PKEY_get_raw_public_key(get(), nullptr, &len) == 1) return len; return 0; } size_t EVPKeyPointer::rawPrivateKeySize() const { if (!pkey_) return 0; size_t len = 0; if (EVP_PKEY_get_raw_private_key(get(), nullptr, &len) == 1) return len; return 0; } DataPointer EVPKeyPointer::rawPublicKey() const { if (!pkey_) return {}; if (auto data = DataPointer::Alloc(rawPublicKeySize())) { const Buffer buf = data; size_t len = data.size(); if (EVP_PKEY_get_raw_public_key(get(), buf.data, &len) != 1) return {}; return data; } return {}; } DataPointer EVPKeyPointer::rawPrivateKey() const { if (!pkey_) return {}; if (auto data = DataPointer::Alloc(rawPrivateKeySize())) { const Buffer 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 {}; return EVP_PKEY_assign_EC_KEY(pkey_.get(), eckey.get()); } bool EVPKeyPointer::set(const ECKeyPointer& eckey) { if (!pkey_ || !eckey) return false; return EVP_PKEY_set1_EC_KEY(pkey_.get(), eckey); } EVPKeyPointer::operator const EC_KEY*() const { if (!pkey_) return nullptr; return EVP_PKEY_get0_EC_KEY(pkey_.get()); } namespace { EVPKeyPointer::ParseKeyResult TryParsePublicKeyInner(const BIOPointer& bp, const char* name, auto&& parse) { if (!bp.resetBio()) { return EVPKeyPointer::ParseKeyResult(EVPKeyPointer::PKParseError::FAILED); } unsigned char* der_data; long der_len; // NOLINT(runtime/int) // This skips surrounding data and decodes PEM to DER. { MarkPopErrorOnReturn mark_pop_error_on_return; if (PEM_bytes_read_bio( &der_data, &der_len, nullptr, name, bp.get(), nullptr, nullptr) != 1) return EVPKeyPointer::ParseKeyResult( EVPKeyPointer::PKParseError::NOT_RECOGNIZED); } DataPointer data(der_data, der_len); // OpenSSL might modify the pointer, so we need to make a copy before parsing. const unsigned char* p = der_data; EVPKeyPointer pkey(parse(&p, der_len)); if (!pkey) return EVPKeyPointer::ParseKeyResult(EVPKeyPointer::PKParseError::FAILED); return EVPKeyPointer::ParseKeyResult(std::move(pkey)); } constexpr bool IsASN1Sequence(const unsigned char* data, size_t size, size_t* data_offset, size_t* data_size) { if (size < 2 || data[0] != 0x30) return false; if (data[1] & 0x80) { // Long form. size_t n_bytes = data[1] & ~0x80; if (n_bytes + 2 > size || n_bytes > sizeof(size_t)) return false; size_t length = 0; for (size_t i = 0; i < n_bytes; i++) length = (length << 8) | data[i + 2]; *data_offset = 2 + n_bytes; *data_size = std::min(size - 2 - n_bytes, length); } else { // Short form. *data_offset = 2; *data_size = std::min(size - 2, data[1]); } return true; } constexpr bool IsEncryptedPrivateKeyInfo( const Buffer& 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& 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& buffer) { auto bp = BIOPointer::New(buffer.data, buffer.len); if (!bp) return ParseKeyResult(PKParseError::FAILED); // Try parsing as SubjectPublicKeyInfo (SPKI) first. if (auto ret = TryParsePublicKeyInner( bp, "PUBLIC KEY", [](const unsigned char** p, long l) { // NOLINT(runtime/int) return d2i_PUBKEY(nullptr, p, l); })) { return ret; } // Maybe it is PKCS#1. if (auto ret = TryParsePublicKeyInner( bp, "RSA PUBLIC KEY", [](const unsigned char** p, long l) { // NOLINT(runtime/int) return d2i_PublicKey(EVP_PKEY_RSA, nullptr, p, l); })) { return ret; } // X.509 fallback. if (auto ret = TryParsePublicKeyInner( bp, "CERTIFICATE", [](const unsigned char** p, long l) { // NOLINT(runtime/int) X509Pointer x509(d2i_X509(nullptr, p, l)); return x509 ? X509_get_pubkey(x509.get()) : nullptr; })) { return ret; }; return ParseKeyResult(PKParseError::NOT_RECOGNIZED); } EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePublicKey( const PublicKeyEncodingConfig& config, const Buffer& buffer) { if (config.format == PKFormatType::PEM) { return TryParsePublicKeyPEM(buffer); } if (config.format != PKFormatType::DER) { return ParseKeyResult(PKParseError::FAILED); } const unsigned char* start = buffer.data; EVP_PKEY* key = nullptr; if (config.type == PKEncodingType::PKCS1 && (key = d2i_PublicKey(EVP_PKEY_RSA, nullptr, &start, buffer.len))) { return EVPKeyPointer::ParseKeyResult(EVPKeyPointer(key)); } if (config.type == PKEncodingType::SPKI && (key = d2i_PUBKEY(nullptr, &start, buffer.len))) { return EVPKeyPointer::ParseKeyResult(EVPKeyPointer(key)); } return ParseKeyResult(PKParseError::FAILED); } namespace { Buffer GetPassphrase( const EVPKeyPointer::PrivateKeyEncodingConfig& config) { Buffer 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(-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(passphrase.get()); } pass.len = passphrase.size(); } return pass; } } // namespace EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePrivateKey( const PrivateKeyEncodingConfig& config, const Buffer& buffer) { static constexpr auto keyOrError = [](EVPKeyPointer pkey, bool had_passphrase = false) { if (int err = ERR_peek_error()) { 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()); } }; } Result EVPKeyPointer::writePrivateKey( const PrivateKeyEncodingConfig& config) const { if (config.format == PKFormatType::JWK) { return Result(false); } auto bio = BIOPointer::NewMem(); if (!bio) { return Result(false); } auto passphrase = GetPassphrase(config); MarkPopErrorOnReturn mark_pop_error_on_return; bool err; switch (config.type) { case PKEncodingType::PKCS1: { // PKCS1 is only permitted for RSA keys. if (id() != EVP_PKEY_RSA) return Result(false); #if OPENSSL_VERSION_MAJOR >= 3 const RSA* rsa = EVP_PKEY_get0_RSA(get()); #else RSA* rsa = EVP_PKEY_get0_RSA(get()); #endif switch (config.format) { case PKFormatType::PEM: { err = PEM_write_bio_RSAPrivateKey( bio.get(), rsa, config.cipher, reinterpret_cast(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(false); } } break; } case PKEncodingType::PKCS8: { switch (config.format) { case PKFormatType::PEM: { // Encode PKCS#8 as PEM. err = PEM_write_bio_PKCS8PrivateKey(bio.get(), get(), config.cipher, passphrase.data, passphrase.len, nullptr, nullptr) != 1; break; } case PKFormatType::DER: { err = i2d_PKCS8PrivateKey_bio(bio.get(), get(), config.cipher, passphrase.data, passphrase.len, nullptr, nullptr) != 1; break; } default: { // Should never get here. return Result(false); } } break; } case PKEncodingType::SEC1: { // SEC1 is only permitted for EC keys if (id() != EVP_PKEY_EC) return Result(false); #if OPENSSL_VERSION_MAJOR >= 3 const EC_KEY* ec = EVP_PKEY_get0_EC_KEY(get()); #else EC_KEY* ec = EVP_PKEY_get0_EC_KEY(get()); #endif switch (config.format) { case PKFormatType::PEM: { err = PEM_write_bio_ECPrivateKey( bio.get(), ec, config.cipher, reinterpret_cast(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(false); } } break; } default: { // Not a valid private key encoding return Result(false); } } if (err) { // Failed to encode the private key. return Result(false, mark_pop_error_on_return.peekError()); } return bio; } Result EVPKeyPointer::writePublicKey( const ncrypto::EVPKeyPointer::PublicKeyEncodingConfig& config) const { auto bio = BIOPointer::NewMem(); if (!bio) return Result(false); MarkPopErrorOnReturn mark_pop_error_on_return; if (config.type == ncrypto::EVPKeyPointer::PKEncodingType::PKCS1) { // PKCS#1 is only valid for RSA keys. #if OPENSSL_VERSION_MAJOR >= 3 const RSA* rsa = EVP_PKEY_get0_RSA(get()); #else RSA* rsa = EVP_PKEY_get0_RSA(get()); #endif if (config.format == ncrypto::EVPKeyPointer::PKFormatType::PEM) { // Encode PKCS#1 as PEM. if (PEM_write_bio_RSAPublicKey(bio.get(), rsa) != 1) { return Result(false, mark_pop_error_on_return.peekError()); } return bio; } // Encode PKCS#1 as DER. if (i2d_RSAPublicKey_bio(bio.get(), rsa) != 1) { return Result(false, mark_pop_error_on_return.peekError()); } return bio; } if (config.format == ncrypto::EVPKeyPointer::PKFormatType::PEM) { // Encode SPKI as PEM. if (PEM_write_bio_PUBKEY(bio.get(), get()) != 1) { return Result(false, mark_pop_error_on_return.peekError()); } return bio; } // Encode SPKI as DER. if (i2d_PUBKEY_bio(bio.get(), get()) != 1) { return Result(false, mark_pop_error_on_return.peekError()); } return bio; } bool EVPKeyPointer::isRsaVariant() const { if (!pkey_) return false; int type = id(); return type == EVP_PKEY_RSA || type == EVP_PKEY_RSA2 || type == EVP_PKEY_RSA_PSS; } bool EVPKeyPointer::isOneShotVariant() const { if (!pkey_) return false; int type = id(); return type == EVP_PKEY_ED25519 || type == EVP_PKEY_ED448; } bool EVPKeyPointer::isSigVariant() const { if (!pkey_) return false; int type = id(); return type == EVP_PKEY_EC || type == EVP_PKEY_DSA; } int EVPKeyPointer::getDefaultSignPadding() const { return id() == EVP_PKEY_RSA_PSS ? RSA_PKCS1_PSS_PADDING : RSA_PKCS1_PADDING; } std::optional EVPKeyPointer::getBytesOfRS() const { if (!pkey_) return std::nullopt; int bits, id = base_id(); if (id == EVP_PKEY_DSA) { const DSA* dsa_key = EVP_PKEY_get0_DSA(get()); // Both r and s are computed mod q, so their width is limited by that of q. bits = BignumPointer::GetBitCount(DSA_get0_q(dsa_key)); } else if (id == EVP_PKEY_EC) { bits = EC_GROUP_order_bits(ECKeyPointer::GetGroup(*this)); } else { return std::nullopt; } return (bits + 7) / 8; } EVPKeyPointer::operator Rsa() const { int type = id(); if (type != EVP_PKEY_RSA && type != EVP_PKEY_RSA_PSS) return {}; // TODO(tniessen): Remove the "else" branch once we drop support for OpenSSL // versions older than 1.1.1e via FIPS / dynamic linking. OSSL3_CONST RSA* rsa; if (OPENSSL_VERSION_NUMBER >= 0x1010105fL) { rsa = EVP_PKEY_get0_RSA(get()); } else { rsa = static_cast(EVP_PKEY_get0(get())); } if (rsa == nullptr) return {}; return Rsa(rsa); } EVPKeyPointer::operator Dsa() const { int type = id(); if (type != EVP_PKEY_DSA) return {}; OSSL3_CONST DSA* dsa = EVP_PKEY_get0_DSA(get()); if (dsa == nullptr) return {}; return Dsa(dsa); } bool EVPKeyPointer::validateDsaParameters() const { if (!pkey_) return false; /* Validate DSA2 parameters from FIPS 186-4 */ #if OPENSSL_VERSION_MAJOR >= 3 if (EVP_default_properties_is_fips_enabled(nullptr) && EVP_PKEY_DSA == id()) { #else if (FIPS_mode() && EVP_PKEY_DSA == id()) { #endif const DSA* dsa = EVP_PKEY_get0_DSA(pkey_.get()); const BIGNUM* p; const BIGNUM* q; DSA_get0_pqg(dsa, &p, &q, nullptr); int L = BignumPointer::GetBitCount(p); int N = BignumPointer::GetBitCount(q); 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 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) 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 SSLPointer::verifyPeerCertificate() const { if (!ssl_) return std::nullopt; if (X509Pointer::PeerFrom(*this)) { return SSL_get_verify_result(get()); } const SSL_CIPHER* curr_cipher = SSL_get_current_cipher(get()); const SSL_SESSION* sess = SSL_get_session(get()); // Allow no-cert for PSK authentication in TLS1.2 and lower. // In TLS1.3 check that session was reused because TLS1.3 PSK // looks like session resumption. if (SSL_CIPHER_get_auth_nid(curr_cipher) == NID_auth_psk || (SSL_SESSION_get_protocol_version(sess) == TLS1_3_VERSION && SSL_session_reused(get()))) { return X509_V_OK; } return std::nullopt; } const char* SSLPointer::getClientHelloAlpn() const { if (ssl_ == nullptr) return {}; #ifndef OPENSSL_IS_BORINGSSL const unsigned char* buf; size_t len; size_t rem; if (!SSL_client_hello_get0_ext( get(), TLSEXT_TYPE_application_layer_protocol_negotiation, &buf, &rem) || rem < 2) { return {}; } len = (buf[0] << 8) | buf[1]; if (len + 2 != rem) return {}; return reinterpret_cast(buf + 3); #else // Boringssl doesn't have a public API for this. return {}; #endif } const char* SSLPointer::getClientHelloServerName() const { if (ssl_ == nullptr) return {}; #ifndef OPENSSL_IS_BORINGSSL const unsigned char* buf; size_t len; size_t rem; if (!SSL_client_hello_get0_ext(get(), TLSEXT_TYPE_server_name, &buf, &rem) || rem <= 2) { return {}; } len = (*buf << 8) | *(buf + 1); if (len + 2 != rem) return {}; rem = len; if (rem == 0 || *(buf + 2) != TLSEXT_NAMETYPE_host_name) return {}; rem--; if (rem <= 2) return {}; len = (*(buf + 3) << 8) | *(buf + 4); if (len + 2 > rem) return {}; return reinterpret_cast(buf + 5); #else // Boringssl doesn't have a public API for this. return {}; #endif } std::optional 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 SSLPointer::getServerName() const { if (!ssl_) return std::nullopt; return GetServerName(get()); } X509View SSLPointer::getCertificate() const { if (!ssl_) return {}; ClearErrorOnReturn clear_error_on_return; return ncrypto::X509View(SSL_get_certificate(get())); } const SSL_CIPHER* SSLPointer::getCipher() const { if (!ssl_) return nullptr; return SSL_get_current_cipher(get()); } bool SSLPointer::isServer() const { return SSL_is_server(get()) != 0; } EVPKeyPointer SSLPointer::getPeerTempKey() const { if (!ssl_) return {}; EVP_PKEY* raw_key = nullptr; #ifndef OPENSSL_IS_BORINGSSL if (!SSL_get_peer_tmp_key(get(), &raw_key)) return {}; #else if (!SSL_get_server_tmp_key(get(), &raw_key)) return {}; #endif return EVPKeyPointer(raw_key); } std::optional SSLPointer::getCipherName() const { auto cipher = getCipher(); if (cipher == nullptr) return std::nullopt; return SSL_CIPHER_get_name(cipher); } std::optional SSLPointer::getCipherStandardName() const { auto cipher = getCipher(); if (cipher == nullptr) return std::nullopt; return SSL_CIPHER_standard_name(cipher); } std::optional SSLPointer::getCipherVersion() const { auto cipher = getCipher(); if (cipher == nullptr) return std::nullopt; return SSL_CIPHER_get_version(cipher); } std::optional SSLPointer::getSecurityLevel() { #ifndef OPENSSL_IS_BORINGSSL auto ctx = SSLCtxPointer::New(); if (!ctx) return std::nullopt; auto ssl = SSLPointer::New(ctx); if (!ssl) return std::nullopt; return SSL_get_security_level(ssl); #else // for BoringSSL assume the same as the default return OPENSSL_TLS_SECURITY_LEVEL; #endif // OPENSSL_IS_BORINGSSL } SSLCtxPointer::SSLCtxPointer(SSL_CTX* ctx) : ctx_(ctx) {} SSLCtxPointer::SSLCtxPointer(SSLCtxPointer&& other) noexcept : ctx_(other.release()) {} SSLCtxPointer& SSLCtxPointer::operator=(SSLCtxPointer&& other) noexcept { if (this == &other) return *this; this->~SSLCtxPointer(); return *new (this) SSLCtxPointer(std::move(other)); } SSLCtxPointer::~SSLCtxPointer() { reset(); } void SSLCtxPointer::reset(SSL_CTX* ctx) { ctx_.reset(ctx); } void SSLCtxPointer::reset(const SSL_METHOD* method) { ctx_.reset(SSL_CTX_new(method)); } SSL_CTX* SSLCtxPointer::release() { return ctx_.release(); } SSLCtxPointer SSLCtxPointer::NewServer() { return SSLCtxPointer(SSL_CTX_new(TLS_server_method())); } SSLCtxPointer SSLCtxPointer::NewClient() { return SSLCtxPointer(SSL_CTX_new(TLS_client_method())); } SSLCtxPointer SSLCtxPointer::New(const SSL_METHOD* method) { return SSLCtxPointer(SSL_CTX_new(method)); } bool SSLCtxPointer::setGroups(const char* groups) { return SSL_CTX_set1_groups_list(get(), groups) == 1; } bool SSLCtxPointer::setCipherSuites(const char* ciphers) { #ifndef OPENSSL_IS_BORINGSSL if (!ctx_) return false; return SSL_CTX_set_ciphersuites(ctx_.get(), ciphers); #else // BoringSSL does not allow API config of TLS 1.3 cipher suites. // We treat this as a non-op. return true; #endif } // ============================================================================ const Cipher Cipher::FromName(const char* name) { return Cipher(EVP_get_cipherbyname(name)); } const Cipher Cipher::FromNid(int nid) { return Cipher(EVP_get_cipherbynid(nid)); } const Cipher Cipher::FromCtx(const CipherCtxPointer& ctx) { return Cipher(EVP_CIPHER_CTX_cipher(ctx.get())); } const Cipher Cipher::EMPTY = Cipher(); const Cipher Cipher::AES_128_CBC = Cipher::FromNid(NID_aes_128_cbc); const Cipher Cipher::AES_192_CBC = Cipher::FromNid(NID_aes_192_cbc); const Cipher Cipher::AES_256_CBC = Cipher::FromNid(NID_aes_256_cbc); const Cipher Cipher::AES_128_CTR = Cipher::FromNid(NID_aes_128_ctr); const Cipher Cipher::AES_192_CTR = Cipher::FromNid(NID_aes_192_ctr); const Cipher Cipher::AES_256_CTR = Cipher::FromNid(NID_aes_256_ctr); const Cipher Cipher::AES_128_GCM = Cipher::FromNid(NID_aes_128_gcm); const Cipher Cipher::AES_192_GCM = Cipher::FromNid(NID_aes_192_gcm); const Cipher Cipher::AES_256_GCM = Cipher::FromNid(NID_aes_256_gcm); const Cipher Cipher::AES_128_KW = Cipher::FromNid(NID_id_aes128_wrap); const Cipher Cipher::AES_192_KW = Cipher::FromNid(NID_id_aes192_wrap); const Cipher Cipher::AES_256_KW = Cipher::FromNid(NID_id_aes256_wrap); bool Cipher::isGcmMode() const { if (!cipher_) return false; return getMode() == EVP_CIPH_GCM_MODE; } bool Cipher::isWrapMode() const { if (!cipher_) return false; return getMode() == EVP_CIPH_WRAP_MODE; } bool Cipher::isCtrMode() const { if (!cipher_) return false; return getMode() == EVP_CIPH_CTR_MODE; } bool Cipher::isCcmMode() const { if (!cipher_) return false; return getMode() == EVP_CIPH_CCM_MODE; } bool Cipher::isOcbMode() const { if (!cipher_) return false; return getMode() == EVP_CIPH_OCB_MODE; } bool Cipher::isStreamMode() const { if (!cipher_) return false; return getMode() == EVP_CIPH_STREAM_CIPHER; } bool Cipher::isChaCha20Poly1305() const { if (!cipher_) return false; return getNid() == NID_chacha20_poly1305; } int Cipher::getMode() const { if (!cipher_) return 0; return EVP_CIPHER_mode(cipher_); } int Cipher::getIvLength() const { if (!cipher_) return 0; return EVP_CIPHER_iv_length(cipher_); } int Cipher::getKeyLength() const { if (!cipher_) return 0; return EVP_CIPHER_key_length(cipher_); } int Cipher::getBlockSize() const { if (!cipher_) return 0; return EVP_CIPHER_block_size(cipher_); } int Cipher::getNid() const { if (!cipher_) return 0; return EVP_CIPHER_nid(cipher_); } std::string_view Cipher::getModeLabel() const { if (!cipher_) return {}; switch (getMode()) { case EVP_CIPH_CCM_MODE: return "ccm"; case EVP_CIPH_CFB_MODE: return "cfb"; case EVP_CIPH_CBC_MODE: return "cbc"; case EVP_CIPH_CTR_MODE: return "ctr"; case EVP_CIPH_ECB_MODE: return "ecb"; case EVP_CIPH_GCM_MODE: return "gcm"; case EVP_CIPH_OCB_MODE: return "ocb"; case EVP_CIPH_OFB_MODE: return "ofb"; case EVP_CIPH_WRAP_MODE: return "wrap"; case EVP_CIPH_XTS_MODE: return "xts"; case EVP_CIPH_STREAM_CIPHER: return "stream"; } return "{unknown}"; } const char* Cipher::getName() const { if (!cipher_) return {}; // OBJ_nid2sn(EVP_CIPHER_nid(cipher)) is used here instead of // EVP_CIPHER_name(cipher) for compatibility with BoringSSL. return OBJ_nid2sn(getNid()); } bool Cipher::isSupportedAuthenticatedMode() const { switch (getMode()) { case EVP_CIPH_CCM_MODE: case EVP_CIPH_GCM_MODE: #ifndef OPENSSL_NO_OCB case EVP_CIPH_OCB_MODE: #endif return true; case EVP_CIPH_STREAM_CIPHER: return getNid() == NID_chacha20_poly1305; default: return false; } } int Cipher::bytesToKey(const Digest& digest, const Buffer& 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_init(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& tag) { if (!ctx_) return false; return EVP_CIPHER_CTX_ctrl( ctx_.get(), EVP_CTRL_AEAD_SET_TAG, tag.len, const_cast(tag.data)); } bool CipherCtxPointer::setAeadTagLength(size_t length) { if (!ctx_) return false; return EVP_CIPHER_CTX_ctrl( ctx_.get(), EVP_CTRL_AEAD_SET_TAG, length, nullptr); } bool CipherCtxPointer::setPadding(bool padding) { if (!ctx_) return false; return EVP_CIPHER_CTX_set_padding(ctx_.get(), padding); } int CipherCtxPointer::getBlockSize() const { if (!ctx_) return 0; return EVP_CIPHER_CTX_block_size(ctx_.get()); } int CipherCtxPointer::getMode() const { if (!ctx_) return 0; return EVP_CIPHER_CTX_mode(ctx_.get()); } bool CipherCtxPointer::isGcmMode() const { if (!ctx_) return false; return getMode() == EVP_CIPH_GCM_MODE; } bool CipherCtxPointer::isCcmMode() const { if (!ctx_) return false; return getMode() == EVP_CIPH_CCM_MODE; } bool CipherCtxPointer::isWrapMode() const { if (!ctx_) return false; return getMode() == EVP_CIPH_WRAP_MODE; } bool CipherCtxPointer::isChaCha20Poly1305() const { if (!ctx_) return false; return getNid() == NID_chacha20_poly1305; } int CipherCtxPointer::getNid() const { if (!ctx_) return 0; return EVP_CIPHER_CTX_nid(ctx_.get()); } bool CipherCtxPointer::init(const Cipher& cipher, bool encrypt, const unsigned char* key, const unsigned char* iv) { if (!ctx_) return false; return EVP_CipherInit_ex( ctx_.get(), cipher, nullptr, key, iv, encrypt ? 1 : 0) == 1; } bool CipherCtxPointer::update(const Buffer& 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& 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 ECDSASigPointer::encode() const { if (!sig_) return { .data = nullptr, .len = 0, }; Buffer 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& 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); } // ============================================================================ ECKeyPointer::ECKeyPointer() : key_(nullptr) {} 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; return EC_KEY_set_public_key_affine_coordinates( key_.get(), x.get(), y.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()); } 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; } // ============================================================================ EVPKeyCtxPointer::EVPKeyCtxPointer() : ctx_(nullptr) {} EVPKeyCtxPointer::EVPKeyCtxPointer(EVP_PKEY_CTX* ctx) : ctx_(ctx) {} EVPKeyCtxPointer::EVPKeyCtxPointer(EVPKeyCtxPointer&& other) noexcept : ctx_(other.release()) {} EVPKeyCtxPointer& EVPKeyCtxPointer::operator=( EVPKeyCtxPointer&& other) noexcept { ctx_.reset(other.release()); return *this; } EVPKeyCtxPointer::~EVPKeyCtxPointer() { reset(); } void EVPKeyCtxPointer::reset(EVP_PKEY_CTX* ctx) { ctx_.reset(ctx); } EVP_PKEY_CTX* EVPKeyCtxPointer::release() { return ctx_.release(); } EVPKeyCtxPointer EVPKeyCtxPointer::New(const EVPKeyPointer& key) { if (!key) return {}; return EVPKeyCtxPointer(EVP_PKEY_CTX_new(key.get(), nullptr)); } EVPKeyCtxPointer EVPKeyCtxPointer::NewFromID(int id) { #ifdef OPENSSL_IS_BORINGSSL // DSA keys are not supported with BoringSSL if (id == EVP_PKEY_DSA) return {}; #endif return EVPKeyCtxPointer(EVP_PKEY_CTX_new_id(id, nullptr)); } bool EVPKeyCtxPointer::initForDerive(const EVPKeyPointer& peer) { if (!ctx_) return false; if (EVP_PKEY_derive_init(ctx_.get()) != 1) return false; return EVP_PKEY_derive_set_peer(ctx_.get(), peer.get()) == 1; } bool EVPKeyCtxPointer::initForKeygen() { if (!ctx_) return false; return EVP_PKEY_keygen_init(ctx_.get()) == 1; } bool EVPKeyCtxPointer::initForParamgen() { if (!ctx_) return false; return EVP_PKEY_paramgen_init(ctx_.get()) == 1; } int EVPKeyCtxPointer::initForVerify() { if (!ctx_) return 0; return EVP_PKEY_verify_init(ctx_.get()); } int EVPKeyCtxPointer::initForSign() { if (!ctx_) return 0; return EVP_PKEY_sign_init(ctx_.get()); } bool EVPKeyCtxPointer::setDhParameters(int prime_size, uint32_t generator) { #ifndef OPENSSL_IS_BORINGSSL if (!ctx_) return false; return EVP_PKEY_CTX_set_dh_paramgen_prime_len(ctx_.get(), prime_size) == 1 && EVP_PKEY_CTX_set_dh_paramgen_generator(ctx_.get(), generator) == 1; #else // TODO(jasnell): Boringssl appears not to support this operation. // Is there an alternative approach that Boringssl does support? return false; #endif } bool EVPKeyCtxPointer::setDsaParameters(uint32_t bits, std::optional q_bits) { if (!ctx_) return false; if (EVP_PKEY_CTX_set_dsa_paramgen_bits(ctx_.get(), bits) != 1) { return false; } if (q_bits.has_value() && EVP_PKEY_CTX_set_dsa_paramgen_q_bits(ctx_.get(), q_bits.value()) != 1) { return false; } return true; } bool EVPKeyCtxPointer::setEcParameters(int curve, int encoding) { if (!ctx_) return false; return EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx_.get(), curve) == 1 && EVP_PKEY_CTX_set_ec_param_enc(ctx_.get(), encoding) == 1; } 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 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 (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; } bool EVPKeyCtxPointer::setRsaPssKeygenMd(const Digest& md) { if (!md || !ctx_) return false; // OpenSSL < 3 accepts a void* for the md parameter. const EVP_MD* md_ptr = md; return EVP_PKEY_CTX_set_rsa_pss_keygen_md(ctx_.get(), md_ptr) > 0; } bool EVPKeyCtxPointer::setRsaPssKeygenMgf1Md(const Digest& md) { if (!md || !ctx_) return false; const EVP_MD* md_ptr = md; return EVP_PKEY_CTX_set_rsa_pss_keygen_mgf1_md(ctx_.get(), md_ptr) > 0; } bool EVPKeyCtxPointer::setRsaPssSaltlen(int salt_len) { if (!ctx_) return false; return EVP_PKEY_CTX_set_rsa_pss_keygen_saltlen(ctx_.get(), salt_len) > 0; } bool EVPKeyCtxPointer::setRsaImplicitRejection() { #ifndef OPENSSL_IS_BORINGSSL if (!ctx_) return false; return EVP_PKEY_CTX_ctrl_str( ctx_.get(), "rsa_pkcs1_implicit_rejection", "1") > 0; // From the doc -2 means that the option is not supported. // The default for the option is enabled and if it has been // specifically disabled we want to respect that so we will // not throw an error if the option is supported regardless // of how it is set. The call to set the value // will not affect what is used since a different context is // used in the call if the option is supported #else // TODO(jasnell): Boringssl appears not to support this operation. // Is there an alternative approach that Boringssl does support? return true; #endif } bool EVPKeyCtxPointer::setRsaOaepLabel(DataPointer&& data) { if (!ctx_) return false; if (EVP_PKEY_CTX_set0_rsa_oaep_label(ctx_.get(), static_cast(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(), EVP_MD_CTX_md(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(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(ctx_.get()) == 1; #if OPENSSL_VERSION_MAJOR >= 3 return EVP_PKEY_public_check_quick(ctx_.get()) == 1; #else return EVP_PKEY_public_check(ctx_.get()) == 1; #endif #else // OPENSSL_IS_BORINGSSL // Boringssl appears not to support this operation. // TODO(jasnell): Is there an alternative approach that Boringssl does // support? return true; #endif } bool EVPKeyCtxPointer::privateCheck() const { if (!ctx_) return false; #ifndef OPENSSL_IS_BORINGSSL return EVP_PKEY_check(ctx_.get()) == 1; #else // Boringssl appears not to support this operation. // TODO(jasnell): Is there an alternative approach that Boringssl does // support? return true; #endif } bool EVPKeyCtxPointer::verify(const Buffer& sig, const Buffer& 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& 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(buf.get()), &len, data.data, data.len) != 1) { return {}; } return buf.resize(len); } bool EVPKeyCtxPointer::signInto(const Buffer& data, Buffer* 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 DataPointer RSA_Cipher(const EVPKeyPointer& key, const Rsa::CipherParams& params, const Buffer 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) || !ctx.setRsaMgf1Md(params.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(in.data), in.len) <= 0) { return {}; } auto buf = DataPointer::Alloc(out_len); if (!buf) return {}; if (cipher(ctx.get(), static_cast(buf.get()), &out_len, static_cast(in.data), in.len) <= 0) { return {}; } return buf.resize(out_len); } template DataPointer CipherImpl(const EVPKeyPointer& key, const Rsa::CipherParams& params, const Buffer 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))) { 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(in.data), in.len) <= 0) { return {}; } auto buf = DataPointer::Alloc(out_len); if (!buf) return {}; if (cipher(ctx.get(), static_cast(buf.get()), &out_len, static_cast(in.data), in.len) <= 0) { return {}; } return buf.resize(out_len); } } // namespace Rsa::Rsa() : rsa_(nullptr) {} Rsa::Rsa(OSSL3_CONST RSA* ptr) : rsa_(ptr) {} const Rsa::PublicKey Rsa::getPublicKey() const { if (rsa_ == nullptr) return {}; PublicKey key; RSA_get0_key(rsa_, &key.n, &key.e, &key.d); return key; } const Rsa::PrivateKey Rsa::getPrivateKey() const { 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; } const std::optional Rsa::getPssParams() const { 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; } bool Rsa::setPublicKey(BignumPointer&& n, BignumPointer&& e) { if (!n || !e) return false; if (RSA_set0_key(const_cast(rsa_), n.get(), e.get(), nullptr) == 1) { n.release(); e.release(); return true; } return false; } bool Rsa::setPrivateKey(BignumPointer&& d, BignumPointer&& q, BignumPointer&& p, BignumPointer&& dp, BignumPointer&& dq, BignumPointer&& qi) { if (!RSA_set0_key(const_cast(rsa_), nullptr, nullptr, d.get())) { return false; } d.release(); if (!RSA_set0_factors(const_cast(rsa_), p.get(), q.get())) { return false; } p.release(); q.release(); if (!RSA_set0_crt_params( const_cast(rsa_), dp.get(), dq.get(), qi.get())) { return false; } dp.release(); dq.release(); qi.release(); return true; } DataPointer Rsa::encrypt(const EVPKeyPointer& key, const Rsa::CipherParams& params, const Buffer in) { if (!key) return {}; return RSA_Cipher(key, params, in); } DataPointer Rsa::decrypt(const EVPKeyPointer& key, const Rsa::CipherParams& params, const Buffer in) { if (!key) return {}; return RSA_Cipher(key, params, in); } DataPointer Cipher::encrypt(const EVPKeyPointer& key, const CipherParams& params, const Buffer in) { // public operation return CipherImpl(key, params, in); } DataPointer Cipher::decrypt(const EVPKeyPointer& key, const CipherParams& params, const Buffer in) { // private operation return CipherImpl(key, params, in); } DataPointer Cipher::sign(const EVPKeyPointer& key, const CipherParams& params, const Buffer in) { // private operation return CipherImpl(key, params, in); } DataPointer Cipher::recover(const EVPKeyPointer& key, const CipherParams& params, const Buffer in) { // public operation return CipherImpl( key, params, in); } namespace { struct CipherCallbackContext { Cipher::CipherNameCallback cb; void operator()(const char* name) { cb(name); } }; #if OPENSSL_VERSION_MAJOR >= 3 template void array_push_back(const TypeName* evp_ref, const char* from, const char* to, void* arg) { if (from == nullptr) return; const TypeName* real_instance = getbyname(from); if (!real_instance) return; const char* real_name = getname(real_instance); if (!real_name) return; // EVP_*_fetch() does not support alias names, so we need to pass it the // real/original algorithm name. // We use EVP_*_fetch() as a filter here because it will only return an // instance if the algorithm is supported by the public OpenSSL APIs (some // algorithms are used internally by OpenSSL and are also passed to this // callback). TypeName* fetched = fetch_type(nullptr, real_name, nullptr); if (fetched == nullptr) return; free_type(fetched); auto& cb = *(static_cast(arg)); cb(from); } #else template void array_push_back(const TypeName* evp_ref, const char* from, const char* to, void* arg) { if (!from) return; auto& cb = *(static_cast(arg)); cb(from); } #endif } // namespace void Cipher::ForEach(Cipher::CipherNameCallback callback) { ClearErrorOnReturn clearErrorOnReturn; CipherCallbackContext context; context.cb = std::move(callback); EVP_CIPHER_do_all_sorted( #if OPENSSL_VERSION_MAJOR >= 3 array_push_back, #else array_push_back, #endif &context); } // ============================================================================ Ec::Ec() : ec_(nullptr) {} Ec::Ec(OSSL3_CONST EC_KEY* key) : ec_(key) {} const EC_GROUP* Ec::getGroup() const { return ECKeyPointer::GetGroup(ec_); } int Ec::getCurve() const { return EC_GROUP_get_curve_name(getGroup()); } int Ec::GetCurveIdFromName(const char* name) { int nid = EC_curve_nist2nid(name); if (nid == NID_undef) { nid = OBJ_sn2nid(name); } return nid; } bool Ec::GetCurves(Ec::GetCurveCallback callback) { const size_t count = EC_get_builtin_curves(nullptr, 0); std::vector curves(count); if (EC_get_builtin_curves(curves.data(), count) != count) { return false; } for (auto curve : curves) { if (!callback(OBJ_nid2sn(curve.nid))) return false; } return true; } // ============================================================================ EVPMDCtxPointer::EVPMDCtxPointer() : ctx_(nullptr) {} EVPMDCtxPointer::EVPMDCtxPointer(EVP_MD_CTX* ctx) : ctx_(ctx) {} EVPMDCtxPointer::EVPMDCtxPointer(EVPMDCtxPointer&& other) noexcept : ctx_(other.release()) {} EVPMDCtxPointer& EVPMDCtxPointer::operator=(EVPMDCtxPointer&& other) noexcept { ctx_.reset(other.release()); return *this; } EVPMDCtxPointer::~EVPMDCtxPointer() { reset(); } void EVPMDCtxPointer::reset(EVP_MD_CTX* ctx) { ctx_.reset(ctx); } EVP_MD_CTX* EVPMDCtxPointer::release() { return ctx_.release(); } bool EVPMDCtxPointer::digestInit(const Digest& digest) { if (!ctx_) return false; return EVP_DigestInit_ex(ctx_.get(), digest, nullptr) > 0; } bool EVPMDCtxPointer::digestUpdate(const Buffer& 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 buffer = buf; if (!digestFinalInto(&buffer)) [[unlikely]] { return {}; } return buf; } bool EVPMDCtxPointer::digestFinalInto(Buffer* buf) { if (!ctx_) return false; auto ptr = static_cast(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 EVP_MD_CTX_md(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 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 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; } DataPointer EVPMDCtxPointer::signOneShot( const Buffer& 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(data.get()), &len, buf.data, buf.len)) { return {}; } return data; } DataPointer EVPMDCtxPointer::sign( const Buffer& 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(data.get()), &len)) { return {}; } return data.resize(len); } bool EVPMDCtxPointer::verify(const Buffer& buf, const Buffer& sig) const { if (!ctx_) return false; int ret = EVP_DigestVerify(ctx_.get(), sig.data, sig.len, buf.data, buf.len); return ret == 1; } EVPMDCtxPointer EVPMDCtxPointer::New() { return EVPMDCtxPointer(EVP_MD_CTX_new()); } // ============================================================================ bool extractP1363(const Buffer& 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; } // ============================================================================ 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& 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& buf) { if (!ctx_) return false; return HMAC_Update(ctx_.get(), static_cast(buf.data), buf.len) == 1; } DataPointer HMACCtxPointer::digest() { auto data = DataPointer::Alloc(EVP_MAX_MD_SIZE); if (!data) return {}; Buffer buf = data; if (!digestInto(&buf)) return {}; return data.resize(buf.len); } bool HMACCtxPointer::digestInto(Buffer* buf) { if (!ctx_) return false; unsigned int len = buf->len; if (!HMAC_Final(ctx_.get(), static_cast(buf->data), &len)) { return false; } buf->len = len; return true; } HMACCtxPointer HMACCtxPointer::New() { return HMACCtxPointer(HMAC_CTX_new()); } DataPointer hashDigest(const Buffer& 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(data.get()), &result_size, md, nullptr)) { return {}; } return data.resize(result_size); } // ============================================================================ X509Name::X509Name() : name_(nullptr), total_(0) {} X509Name::X509Name(const X509_NAME* name) : name_(name), total_(X509_NAME_entry_count(name)) {} X509Name::Iterator::Iterator(const X509Name& name, int pos) : name_(name), loc_(pos) {} X509Name::Iterator& X509Name::Iterator::operator++() { ++loc_; return *this; } X509Name::Iterator::operator bool() const { return loc_ < name_.total_; } bool X509Name::Iterator::operator==(const Iterator& other) const { return loc_ == other.loc_; } bool X509Name::Iterator::operator!=(const Iterator& other) const { return loc_ != other.loc_; } std::pair X509Name::Iterator::operator*() const { if (loc_ == name_.total_) return {{}, {}}; X509_NAME_ENTRY* entry = X509_NAME_get_entry(name_, loc_); if (entry == nullptr) [[unlikely]] return {{}, {}}; ASN1_OBJECT* name = X509_NAME_ENTRY_get_object(entry); ASN1_STRING* value = X509_NAME_ENTRY_get_data(entry); if (name == nullptr || value == nullptr) [[unlikely]] { return {{}, {}}; } int nid = OBJ_obj2nid(name); std::string name_str; if (nid != NID_undef) { name_str = std::string(OBJ_nid2sn(nid)); } else { char buf[80]; OBJ_obj2txt(buf, sizeof(buf), name, 0); name_str = std::string(buf); } unsigned char* value_str; int value_str_size = ASN1_STRING_to_UTF8(&value_str, value); return { std::move(name_str), std::string(reinterpret_cast(value_str), value_str_size)}; } // ============================================================================ Dsa::Dsa() : dsa_(nullptr) {} Dsa::Dsa(OSSL3_CONST DSA* dsa) : dsa_(dsa) {} const BIGNUM* Dsa::getP() const { if (dsa_ == nullptr) return nullptr; const BIGNUM* p; DSA_get0_pqg(dsa_, &p, nullptr, nullptr); return p; } const BIGNUM* Dsa::getQ() const { if (dsa_ == nullptr) return nullptr; const BIGNUM* q; DSA_get0_pqg(dsa_, nullptr, &q, nullptr); return q; } size_t Dsa::getModulusLength() const { if (dsa_ == nullptr) return 0; return BignumPointer::GetBitCount(getP()); } size_t Dsa::getDivisorLength() const { if (dsa_ == nullptr) return 0; return BignumPointer::GetBitCount(getQ()); } // ============================================================================ size_t Digest::size() const { if (md_ == nullptr) return 0; return EVP_MD_size(md_); } const Digest Digest::MD5 = Digest(EVP_md5()); const Digest Digest::SHA1 = Digest(EVP_sha1()); const Digest Digest::SHA256 = Digest(EVP_sha256()); const Digest Digest::SHA384 = Digest(EVP_sha384()); const Digest Digest::SHA512 = Digest(EVP_sha512()); const Digest Digest::FromName(const char* name) { return ncrypto::getDigestByName(name); } } // namespace ncrypto