Skip to content

Commit c2f1e82

Browse files
panvaaduh95
authored andcommitted
crypto: discover hashes from OpenSSL providers
Enumerate usable digests and aliases from activated OpenSSL 3 providers rather than relying only on the legacy digest registry. Normalize provider aliases, omit numeric OIDs and NULL, and validate them against the active default property query. Preserve legacy names and the OpenSSL 1.1.1 and BoringSSL paths. Expose KECCAK-KMAC-128, KECCAK-256, SHA256-192, and other provider digests. Add `functionName` and `customization` options for cSHAKE digests in `createHash()` and `crypto.hash()` with OpenSSL 4.0 or later. Resolve provider-only digest names across hashing, HMAC, KDF, signing, verification, and RSA digest options. Keep ordinary hash construction and one-shot hashing on the original binding arities and direct initialization paths. Use parameterized setup only when cSHAKE options are supplied. Lazily cache successful provider fetches per Environment. Index entries by case-insensitive query, canonical, and alias names. Deduplicate owners by provider and canonical identity. Return borrowed pointers on warm hits. Introduce a process-wide FIPS-state generation that advances only after successful, state-changing `setFips()` calls. Use it to invalidate per-Environment digest caches and refresh `getHashes()` snapshots in the main thread and workers. Keep cache IDs monotonic across invalidation because JavaScript Realms can retain them. Existing hash contexts can finish across a transition. Release provider owners before unloading worker addon DSOs. Document provider-dependent availability and operation-specific restrictions. Add known-answer vectors, option validation, provider resolution, property-query, FIPS transition, worker, snapshot, and cross-API coverage. Refs: #62982 PR-URL: #65484 Backport-PR-URL: #66128 Fixes: #43040 Fixes: #64866 Assisted-by: Codex Signed-off-by: Filip Skokan <panva.ip@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
1 parent 51aad5a commit c2f1e82

19 files changed

Lines changed: 1952 additions & 179 deletions

File tree

‎deps/ncrypto/ncrypto.cc‎

Lines changed: 221 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#endif
1515
#include <algorithm>
1616
#include <array>
17+
#include <atomic>
1718
#include <climits>
1819
#include <cstring>
1920
#include <string_view>
@@ -508,23 +509,43 @@ DataPointer DataPointer::resize(size_t len) {
508509
}
509510

510511
// ============================================================================
511-
bool isFipsEnabled() {
512-
ClearErrorOnReturn clear_error_on_return;
512+
namespace {
513+
// This generation only coordinates cache invalidation. It does not make
514+
// OpenSSL default property changes safe to race with crypto operations.
515+
std::atomic<uint64_t> fips_state_generation{0};
516+
517+
bool isFipsEnabledRaw() {
513518
#if OPENSSL_VERSION_MAJOR >= 3
514519
return EVP_default_properties_is_fips_enabled(nullptr) == 1;
515520
#else
516521
return FIPS_mode() == 1;
517522
#endif
518523
}
524+
} // namespace
525+
526+
bool isFipsEnabled() {
527+
ClearErrorOnReturn clear_error_on_return;
528+
return isFipsEnabledRaw();
529+
}
519530

520531
bool setFipsEnabled(bool enable, CryptoErrorList* errors) {
521-
if (isFipsEnabled() == enable) return true;
532+
const bool was_enabled = isFipsEnabled();
533+
if (was_enabled == enable) return true;
522534
ClearErrorOnReturn clearErrorOnReturn(errors);
523535
#if OPENSSL_VERSION_MAJOR >= 3
524-
return EVP_default_properties_enable_fips(nullptr, enable ? 1 : 0) == 1;
536+
const bool success =
537+
EVP_default_properties_enable_fips(nullptr, enable ? 1 : 0) == 1;
525538
#else
526-
return FIPS_mode_set(enable ? 1 : 0) == 1;
539+
const bool success = FIPS_mode_set(enable ? 1 : 0) == 1;
527540
#endif
541+
if (isFipsEnabledRaw() != was_enabled) {
542+
fips_state_generation.fetch_add(1, std::memory_order_release);
543+
}
544+
return success;
545+
}
546+
547+
uint64_t getFipsStateGeneration() {
548+
return fips_state_generation.load(std::memory_order_acquire);
528549
}
529550

530551
bool testFipsEnabled() {
@@ -4407,11 +4428,120 @@ bool SSLCtxPointer::setCipherSuites(const char* ciphers) {
44074428

44084429
// ============================================================================
44094430

4431+
namespace {
4432+
constexpr char AsciiToLower(char c) {
4433+
return c >= 'A' && c <= 'Z' ? c + ('a' - 'A') : c;
4434+
}
4435+
4436+
#if NCRYPTO_USE_OPENSSL3_PROVIDER
4437+
void PushAlgorithmAlias(const char* name, void* arg) {
4438+
if (name == nullptr) return;
4439+
static_cast<std::vector<std::string>*>(arg)->emplace_back(name);
4440+
}
4441+
#endif
4442+
} // namespace
4443+
44104444
#if OPENSSL_WITH_AES_SIV || OPENSSL_WITH_AES_GCM_SIV
44114445
Cipher::Cipher(DeleteFnPtr<EVP_CIPHER, EVP_CIPHER_free> cipher)
44124446
: cipher_(cipher.get()), fetched_cipher_(std::move(cipher)) {}
44134447
#endif
44144448

4449+
size_t CaseInsensitiveNameHash::operator()(
4450+
std::string_view name) const noexcept {
4451+
size_t hash = 5381;
4452+
for (char c : name) hash = ((hash << 5) + hash) ^ AsciiToLower(c);
4453+
return hash;
4454+
}
4455+
4456+
bool CaseInsensitiveNameEqual::operator()(std::string_view lhs,
4457+
std::string_view rhs) const noexcept {
4458+
if (lhs.size() != rhs.size()) return false;
4459+
for (size_t n = 0; n < lhs.size(); n++) {
4460+
if (AsciiToLower(lhs[n]) != AsciiToLower(rhs[n])) return false;
4461+
}
4462+
return true;
4463+
}
4464+
4465+
DigestCache::Result DigestCache::lookup(const char* name,
4466+
uint64_t generation) const {
4467+
#if NCRYPTO_USE_OPENSSL3_PROVIDER
4468+
if (generation_ != generation) return {};
4469+
const auto it = aliases_.find(name);
4470+
if (it == aliases_.end()) return {};
4471+
return lookup(it->second, generation);
4472+
#else
4473+
static_cast<void>(name);
4474+
static_cast<void>(generation);
4475+
return {};
4476+
#endif
4477+
}
4478+
4479+
DigestCache::Result DigestCache::insert(const char* name,
4480+
const EVP_MD* digest,
4481+
uint64_t generation) {
4482+
#if NCRYPTO_USE_OPENSSL3_PROVIDER
4483+
if (generation_ != generation || name == nullptr || digest == nullptr) {
4484+
return {};
4485+
}
4486+
4487+
const char* canonical_name = EVP_MD_get0_name(digest);
4488+
const OSSL_PROVIDER* provider = EVP_MD_get0_provider(digest);
4489+
if (canonical_name == nullptr || provider == nullptr) return {};
4490+
4491+
for (size_t index = 0; index < digests_.size(); index++) {
4492+
const EVP_MD* cached = digests_[index].get();
4493+
if (cached == nullptr) continue;
4494+
const char* cached_name = EVP_MD_get0_name(cached);
4495+
if (EVP_MD_get0_provider(cached) == provider && cached_name != nullptr &&
4496+
CaseInsensitiveNameEqual()(cached_name, canonical_name)) {
4497+
const int32_t id = static_cast<int32_t>(first_id_ + index);
4498+
aliases_.insert_or_assign(name, id);
4499+
return {cached, id};
4500+
}
4501+
}
4502+
4503+
if (next_id_ == UINT32_MAX ||
4504+
EVP_MD_up_ref(const_cast<EVP_MD*>(digest)) != 1) {
4505+
return {};
4506+
}
4507+
4508+
digests_.emplace_back(const_cast<EVP_MD*>(digest));
4509+
const int32_t id = static_cast<int32_t>(next_id_++);
4510+
const size_t index = digests_.size() - 1;
4511+
4512+
std::vector<std::string> aliases;
4513+
EVP_MD_names_do_all(digests_[index].get(), PushAlgorithmAlias, &aliases);
4514+
for (const std::string& alias : aliases) aliases_.emplace(alias, id);
4515+
aliases_.insert_or_assign(name, id);
4516+
4517+
return {digests_[index].get(), id};
4518+
#else
4519+
static_cast<void>(name);
4520+
static_cast<void>(digest);
4521+
static_cast<void>(generation);
4522+
return {};
4523+
#endif
4524+
}
4525+
4526+
void DigestCache::reset(uint64_t generation) {
4527+
#if NCRYPTO_USE_OPENSSL3_PROVIDER
4528+
if (generation_ == generation) return;
4529+
aliases_.clear();
4530+
digests_.clear();
4531+
first_id_ = next_id_;
4532+
#endif
4533+
generation_ = generation;
4534+
}
4535+
4536+
const DigestCache::AliasMap& DigestCache::aliases() const {
4537+
#if NCRYPTO_USE_OPENSSL3_PROVIDER
4538+
return aliases_;
4539+
#else
4540+
static const AliasMap empty;
4541+
return empty;
4542+
#endif
4543+
}
4544+
44154545
Cipher::Cipher(const Cipher& other) : cipher_(other.cipher_) {
44164546
#if OPENSSL_WITH_AES_SIV || OPENSSL_WITH_AES_GCM_SIV
44174547
if (other.fetched_cipher_ != nullptr) {
@@ -6476,11 +6606,19 @@ EVP_MD_CTX* EVPMDCtxPointer::release() {
64766606
return ctx_.release();
64776607
}
64786608

6479-
bool EVPMDCtxPointer::digestInit(const Digest& digest) {
6609+
bool EVPMDCtxPointer::digestInit(const EVP_MD* digest) {
64806610
if (!ctx_) return false;
64816611
return EVP_DigestInit_ex(ctx_.get(), digest, nullptr) > 0;
64826612
}
64836613

6614+
#if !defined(OPENSSL_IS_BORINGSSL) && OPENSSL_VERSION_PREREQ(4, 0)
6615+
bool EVPMDCtxPointer::digestInit(const EVP_MD* digest,
6616+
const OSSL_PARAM* params) {
6617+
if (!ctx_) return false;
6618+
return EVP_DigestInit_ex2(ctx_.get(), digest, params) > 0;
6619+
}
6620+
#endif
6621+
64846622
bool EVPMDCtxPointer::digestUpdate(const Buffer<const void>& in) {
64856623
if (!ctx_) return false;
64866624
return EVP_DigestUpdate(ctx_.get(), in.data, in.len) > 0;
@@ -7006,7 +7144,10 @@ DataPointer xofHashDigest(const Buffer<const unsigned char>& buf,
70067144
if (ctx.digestInit(md) != 1) {
70077145
return {};
70087146
}
7009-
if (ctx.digestUpdate(reinterpret_cast<const Buffer<const void>&>(buf)) != 1) {
7147+
if (ctx.digestUpdate(Buffer<const void>{
7148+
.data = buf.data,
7149+
.len = buf.len,
7150+
}) != 1) {
70107151
return {};
70117152
}
70127153
return ctx.digestFinal(output_length);
@@ -7142,14 +7283,86 @@ size_t Digest::size() const {
71427283
return EVP_MD_size(md_);
71437284
}
71447285

7286+
#if NCRYPTO_USE_OPENSSL3_PROVIDER
7287+
Digest::Digest(DeleteFnPtr<EVP_MD, EVP_MD_free> md)
7288+
: md_(md.get()), fetched_md_(std::move(md)) {}
7289+
#endif
7290+
7291+
Digest::Digest(const Digest& other) : md_(other.md_) {
7292+
#if NCRYPTO_USE_OPENSSL3_PROVIDER
7293+
if (other.fetched_md_ != nullptr) {
7294+
if (EVP_MD_up_ref(other.fetched_md_.get()) == 1) {
7295+
fetched_md_.reset(other.fetched_md_.get());
7296+
} else {
7297+
md_ = nullptr;
7298+
}
7299+
}
7300+
#endif
7301+
}
7302+
7303+
Digest& Digest::operator=(const Digest& other) {
7304+
if (this == &other) return *this;
7305+
#if NCRYPTO_USE_OPENSSL3_PROVIDER
7306+
if (other.fetched_md_ != nullptr) {
7307+
if (EVP_MD_up_ref(other.fetched_md_.get()) == 1) {
7308+
fetched_md_.reset(other.fetched_md_.get());
7309+
} else {
7310+
fetched_md_.reset();
7311+
md_ = nullptr;
7312+
return *this;
7313+
}
7314+
} else {
7315+
fetched_md_.reset();
7316+
}
7317+
#endif
7318+
md_ = other.md_;
7319+
return *this;
7320+
}
7321+
71457322
const Digest Digest::MD5 = Digest(EVP_md5());
71467323
const Digest Digest::SHA1 = Digest(EVP_sha1());
71477324
const Digest Digest::SHA256 = Digest(EVP_sha256());
71487325
const Digest Digest::SHA384 = Digest(EVP_sha384());
71497326
const Digest Digest::SHA512 = Digest(EVP_sha512());
71507327

7328+
#if NCRYPTO_USE_OPENSSL3_PROVIDER
7329+
namespace {
7330+
bool IsSupportedDigest(const EVP_MD* md) {
7331+
if (md == nullptr || EVP_MD_is_a(md, "NULL")) return false;
7332+
7333+
// OpenSSL currently crashes when ML-DSA-MU finalizes an empty input. Keep it
7334+
// unavailable until the provider implementation is fixed.
7335+
// https://github.com/openssl/openssl/issues/32445
7336+
if (EVP_MD_is_a(md, "ML-DSA-MU")) return false;
7337+
7338+
return true;
7339+
}
7340+
} // namespace
7341+
#endif
7342+
71517343
const Digest Digest::FromName(const char* name) {
7152-
return ncrypto::getDigestByName(name);
7344+
const EVP_MD* md = ncrypto::getDigestByName(name);
7345+
if (md != nullptr) {
7346+
#if NCRYPTO_USE_OPENSSL3_PROVIDER
7347+
if (md == EVP_md_null()) return Digest();
7348+
#endif
7349+
return Digest(md);
7350+
}
7351+
7352+
return Fetch(name);
7353+
}
7354+
7355+
const Digest Digest::Fetch(const char* name) {
7356+
#if NCRYPTO_USE_OPENSSL3_PROVIDER
7357+
MarkPopErrorOnReturn mark_pop_error_on_return;
7358+
DeleteFnPtr<EVP_MD, EVP_MD_free> fetched(
7359+
EVP_MD_fetch(nullptr, name, nullptr));
7360+
if (IsSupportedDigest(fetched.get())) {
7361+
return Digest(std::move(fetched));
7362+
}
7363+
#endif
7364+
7365+
return Digest();
71537366
}
71547367

71557368
// ============================================================================

0 commit comments

Comments
 (0)