|
14 | 14 | #endif |
15 | 15 | #include <algorithm> |
16 | 16 | #include <array> |
| 17 | +#include <atomic> |
17 | 18 | #include <climits> |
18 | 19 | #include <cstring> |
19 | 20 | #include <string_view> |
@@ -507,23 +508,43 @@ DataPointer DataPointer::resize(size_t len) { |
507 | 508 | } |
508 | 509 |
|
509 | 510 | // ============================================================================ |
510 | | -bool isFipsEnabled() { |
511 | | - ClearErrorOnReturn clear_error_on_return; |
| 511 | +namespace { |
| 512 | +// This generation only coordinates cache invalidation. It does not make |
| 513 | +// OpenSSL default property changes safe to race with crypto operations. |
| 514 | +std::atomic<uint64_t> fips_state_generation{0}; |
| 515 | + |
| 516 | +bool isFipsEnabledRaw() { |
512 | 517 | #if OPENSSL_VERSION_MAJOR >= 3 |
513 | 518 | return EVP_default_properties_is_fips_enabled(nullptr) == 1; |
514 | 519 | #else |
515 | 520 | return FIPS_mode() == 1; |
516 | 521 | #endif |
517 | 522 | } |
| 523 | +} // namespace |
| 524 | + |
| 525 | +bool isFipsEnabled() { |
| 526 | + ClearErrorOnReturn clear_error_on_return; |
| 527 | + return isFipsEnabledRaw(); |
| 528 | +} |
518 | 529 |
|
519 | 530 | bool setFipsEnabled(bool enable, CryptoErrorList* errors) { |
520 | | - if (isFipsEnabled() == enable) return true; |
| 531 | + const bool was_enabled = isFipsEnabled(); |
| 532 | + if (was_enabled == enable) return true; |
521 | 533 | ClearErrorOnReturn clearErrorOnReturn(errors); |
522 | 534 | #if OPENSSL_VERSION_MAJOR >= 3 |
523 | | - return EVP_default_properties_enable_fips(nullptr, enable ? 1 : 0) == 1; |
| 535 | + const bool success = |
| 536 | + EVP_default_properties_enable_fips(nullptr, enable ? 1 : 0) == 1; |
524 | 537 | #else |
525 | | - return FIPS_mode_set(enable ? 1 : 0) == 1; |
| 538 | + const bool success = FIPS_mode_set(enable ? 1 : 0) == 1; |
526 | 539 | #endif |
| 540 | + if (isFipsEnabledRaw() != was_enabled) { |
| 541 | + fips_state_generation.fetch_add(1, std::memory_order_release); |
| 542 | + } |
| 543 | + return success; |
| 544 | +} |
| 545 | + |
| 546 | +uint64_t getFipsStateGeneration() { |
| 547 | + return fips_state_generation.load(std::memory_order_acquire); |
527 | 548 | } |
528 | 549 |
|
529 | 550 | bool testFipsEnabled() { |
@@ -4406,11 +4427,120 @@ bool SSLCtxPointer::setCipherSuites(const char* ciphers) { |
4406 | 4427 |
|
4407 | 4428 | // ============================================================================ |
4408 | 4429 |
|
| 4430 | +namespace { |
| 4431 | +constexpr char AsciiToLower(char c) { |
| 4432 | + return c >= 'A' && c <= 'Z' ? c + ('a' - 'A') : c; |
| 4433 | +} |
| 4434 | + |
| 4435 | +#if NCRYPTO_USE_OPENSSL3_PROVIDER |
| 4436 | +void PushAlgorithmAlias(const char* name, void* arg) { |
| 4437 | + if (name == nullptr) return; |
| 4438 | + static_cast<std::vector<std::string>*>(arg)->emplace_back(name); |
| 4439 | +} |
| 4440 | +#endif |
| 4441 | +} // namespace |
| 4442 | + |
4409 | 4443 | #if OPENSSL_WITH_AES_SIV || OPENSSL_WITH_AES_GCM_SIV |
4410 | 4444 | Cipher::Cipher(DeleteFnPtr<EVP_CIPHER, EVP_CIPHER_free> cipher) |
4411 | 4445 | : cipher_(cipher.get()), fetched_cipher_(std::move(cipher)) {} |
4412 | 4446 | #endif |
4413 | 4447 |
|
| 4448 | +size_t CaseInsensitiveNameHash::operator()( |
| 4449 | + std::string_view name) const noexcept { |
| 4450 | + size_t hash = 5381; |
| 4451 | + for (char c : name) hash = ((hash << 5) + hash) ^ AsciiToLower(c); |
| 4452 | + return hash; |
| 4453 | +} |
| 4454 | + |
| 4455 | +bool CaseInsensitiveNameEqual::operator()(std::string_view lhs, |
| 4456 | + std::string_view rhs) const noexcept { |
| 4457 | + if (lhs.size() != rhs.size()) return false; |
| 4458 | + for (size_t n = 0; n < lhs.size(); n++) { |
| 4459 | + if (AsciiToLower(lhs[n]) != AsciiToLower(rhs[n])) return false; |
| 4460 | + } |
| 4461 | + return true; |
| 4462 | +} |
| 4463 | + |
| 4464 | +DigestCache::Result DigestCache::lookup(const char* name, |
| 4465 | + uint64_t generation) const { |
| 4466 | +#if NCRYPTO_USE_OPENSSL3_PROVIDER |
| 4467 | + if (generation_ != generation) return {}; |
| 4468 | + const auto it = aliases_.find(name); |
| 4469 | + if (it == aliases_.end()) return {}; |
| 4470 | + return lookup(it->second, generation); |
| 4471 | +#else |
| 4472 | + static_cast<void>(name); |
| 4473 | + static_cast<void>(generation); |
| 4474 | + return {}; |
| 4475 | +#endif |
| 4476 | +} |
| 4477 | + |
| 4478 | +DigestCache::Result DigestCache::insert(const char* name, |
| 4479 | + const EVP_MD* digest, |
| 4480 | + uint64_t generation) { |
| 4481 | +#if NCRYPTO_USE_OPENSSL3_PROVIDER |
| 4482 | + if (generation_ != generation || name == nullptr || digest == nullptr) { |
| 4483 | + return {}; |
| 4484 | + } |
| 4485 | + |
| 4486 | + const char* canonical_name = EVP_MD_get0_name(digest); |
| 4487 | + const OSSL_PROVIDER* provider = EVP_MD_get0_provider(digest); |
| 4488 | + if (canonical_name == nullptr || provider == nullptr) return {}; |
| 4489 | + |
| 4490 | + for (size_t index = 0; index < digests_.size(); index++) { |
| 4491 | + const EVP_MD* cached = digests_[index].get(); |
| 4492 | + if (cached == nullptr) continue; |
| 4493 | + const char* cached_name = EVP_MD_get0_name(cached); |
| 4494 | + if (EVP_MD_get0_provider(cached) == provider && cached_name != nullptr && |
| 4495 | + CaseInsensitiveNameEqual()(cached_name, canonical_name)) { |
| 4496 | + const int32_t id = static_cast<int32_t>(first_id_ + index); |
| 4497 | + aliases_.insert_or_assign(name, id); |
| 4498 | + return {cached, id}; |
| 4499 | + } |
| 4500 | + } |
| 4501 | + |
| 4502 | + if (next_id_ == UINT32_MAX || |
| 4503 | + EVP_MD_up_ref(const_cast<EVP_MD*>(digest)) != 1) { |
| 4504 | + return {}; |
| 4505 | + } |
| 4506 | + |
| 4507 | + digests_.emplace_back(const_cast<EVP_MD*>(digest)); |
| 4508 | + const int32_t id = static_cast<int32_t>(next_id_++); |
| 4509 | + const size_t index = digests_.size() - 1; |
| 4510 | + |
| 4511 | + std::vector<std::string> aliases; |
| 4512 | + EVP_MD_names_do_all(digests_[index].get(), PushAlgorithmAlias, &aliases); |
| 4513 | + for (const std::string& alias : aliases) aliases_.emplace(alias, id); |
| 4514 | + aliases_.insert_or_assign(name, id); |
| 4515 | + |
| 4516 | + return {digests_[index].get(), id}; |
| 4517 | +#else |
| 4518 | + static_cast<void>(name); |
| 4519 | + static_cast<void>(digest); |
| 4520 | + static_cast<void>(generation); |
| 4521 | + return {}; |
| 4522 | +#endif |
| 4523 | +} |
| 4524 | + |
| 4525 | +void DigestCache::reset(uint64_t generation) { |
| 4526 | +#if NCRYPTO_USE_OPENSSL3_PROVIDER |
| 4527 | + if (generation_ == generation) return; |
| 4528 | + aliases_.clear(); |
| 4529 | + digests_.clear(); |
| 4530 | + first_id_ = next_id_; |
| 4531 | +#endif |
| 4532 | + generation_ = generation; |
| 4533 | +} |
| 4534 | + |
| 4535 | +const DigestCache::AliasMap& DigestCache::aliases() const { |
| 4536 | +#if NCRYPTO_USE_OPENSSL3_PROVIDER |
| 4537 | + return aliases_; |
| 4538 | +#else |
| 4539 | + static const AliasMap empty; |
| 4540 | + return empty; |
| 4541 | +#endif |
| 4542 | +} |
| 4543 | + |
4414 | 4544 | Cipher::Cipher(const Cipher& other) : cipher_(other.cipher_) { |
4415 | 4545 | #if OPENSSL_WITH_AES_SIV || OPENSSL_WITH_AES_GCM_SIV |
4416 | 4546 | if (other.fetched_cipher_ != nullptr) { |
@@ -6479,11 +6609,19 @@ EVP_MD_CTX* EVPMDCtxPointer::release() { |
6479 | 6609 | return ctx_.release(); |
6480 | 6610 | } |
6481 | 6611 |
|
6482 | | -bool EVPMDCtxPointer::digestInit(const Digest& digest) { |
| 6612 | +bool EVPMDCtxPointer::digestInit(const EVP_MD* digest) { |
6483 | 6613 | if (!ctx_) return false; |
6484 | 6614 | return EVP_DigestInit_ex(ctx_.get(), digest, nullptr) > 0; |
6485 | 6615 | } |
6486 | 6616 |
|
| 6617 | +#if !defined(OPENSSL_IS_BORINGSSL) && OPENSSL_VERSION_PREREQ(4, 0) |
| 6618 | +bool EVPMDCtxPointer::digestInit(const EVP_MD* digest, |
| 6619 | + const OSSL_PARAM* params) { |
| 6620 | + if (!ctx_) return false; |
| 6621 | + return EVP_DigestInit_ex2(ctx_.get(), digest, params) > 0; |
| 6622 | +} |
| 6623 | +#endif |
| 6624 | + |
6487 | 6625 | bool EVPMDCtxPointer::digestUpdate(const Buffer<const void>& in) { |
6488 | 6626 | if (!ctx_) return false; |
6489 | 6627 | return EVP_DigestUpdate(ctx_.get(), in.data, in.len) > 0; |
@@ -7009,7 +7147,10 @@ DataPointer xofHashDigest(const Buffer<const unsigned char>& buf, |
7009 | 7147 | if (ctx.digestInit(md) != 1) { |
7010 | 7148 | return {}; |
7011 | 7149 | } |
7012 | | - if (ctx.digestUpdate(reinterpret_cast<const Buffer<const void>&>(buf)) != 1) { |
| 7150 | + if (ctx.digestUpdate(Buffer<const void>{ |
| 7151 | + .data = buf.data, |
| 7152 | + .len = buf.len, |
| 7153 | + }) != 1) { |
7013 | 7154 | return {}; |
7014 | 7155 | } |
7015 | 7156 | return ctx.digestFinal(output_length); |
@@ -7145,14 +7286,86 @@ size_t Digest::size() const { |
7145 | 7286 | return EVP_MD_size(md_); |
7146 | 7287 | } |
7147 | 7288 |
|
| 7289 | +#if NCRYPTO_USE_OPENSSL3_PROVIDER |
| 7290 | +Digest::Digest(DeleteFnPtr<EVP_MD, EVP_MD_free> md) |
| 7291 | + : md_(md.get()), fetched_md_(std::move(md)) {} |
| 7292 | +#endif |
| 7293 | + |
| 7294 | +Digest::Digest(const Digest& other) : md_(other.md_) { |
| 7295 | +#if NCRYPTO_USE_OPENSSL3_PROVIDER |
| 7296 | + if (other.fetched_md_ != nullptr) { |
| 7297 | + if (EVP_MD_up_ref(other.fetched_md_.get()) == 1) { |
| 7298 | + fetched_md_.reset(other.fetched_md_.get()); |
| 7299 | + } else { |
| 7300 | + md_ = nullptr; |
| 7301 | + } |
| 7302 | + } |
| 7303 | +#endif |
| 7304 | +} |
| 7305 | + |
| 7306 | +Digest& Digest::operator=(const Digest& other) { |
| 7307 | + if (this == &other) return *this; |
| 7308 | +#if NCRYPTO_USE_OPENSSL3_PROVIDER |
| 7309 | + if (other.fetched_md_ != nullptr) { |
| 7310 | + if (EVP_MD_up_ref(other.fetched_md_.get()) == 1) { |
| 7311 | + fetched_md_.reset(other.fetched_md_.get()); |
| 7312 | + } else { |
| 7313 | + fetched_md_.reset(); |
| 7314 | + md_ = nullptr; |
| 7315 | + return *this; |
| 7316 | + } |
| 7317 | + } else { |
| 7318 | + fetched_md_.reset(); |
| 7319 | + } |
| 7320 | +#endif |
| 7321 | + md_ = other.md_; |
| 7322 | + return *this; |
| 7323 | +} |
| 7324 | + |
7148 | 7325 | const Digest Digest::MD5 = Digest(EVP_md5()); |
7149 | 7326 | const Digest Digest::SHA1 = Digest(EVP_sha1()); |
7150 | 7327 | const Digest Digest::SHA256 = Digest(EVP_sha256()); |
7151 | 7328 | const Digest Digest::SHA384 = Digest(EVP_sha384()); |
7152 | 7329 | const Digest Digest::SHA512 = Digest(EVP_sha512()); |
7153 | 7330 |
|
| 7331 | +#if NCRYPTO_USE_OPENSSL3_PROVIDER |
| 7332 | +namespace { |
| 7333 | +bool IsSupportedDigest(const EVP_MD* md) { |
| 7334 | + if (md == nullptr || EVP_MD_is_a(md, "NULL")) return false; |
| 7335 | + |
| 7336 | + // OpenSSL currently crashes when ML-DSA-MU finalizes an empty input. Keep it |
| 7337 | + // unavailable until the provider implementation is fixed. |
| 7338 | + // https://github.com/openssl/openssl/issues/32445 |
| 7339 | + if (EVP_MD_is_a(md, "ML-DSA-MU")) return false; |
| 7340 | + |
| 7341 | + return true; |
| 7342 | +} |
| 7343 | +} // namespace |
| 7344 | +#endif |
| 7345 | + |
7154 | 7346 | const Digest Digest::FromName(const char* name) { |
7155 | | - return ncrypto::getDigestByName(name); |
| 7347 | + const EVP_MD* md = ncrypto::getDigestByName(name); |
| 7348 | + if (md != nullptr) { |
| 7349 | +#if NCRYPTO_USE_OPENSSL3_PROVIDER |
| 7350 | + if (md == EVP_md_null()) return Digest(); |
| 7351 | +#endif |
| 7352 | + return Digest(md); |
| 7353 | + } |
| 7354 | + |
| 7355 | + return Fetch(name); |
| 7356 | +} |
| 7357 | + |
| 7358 | +const Digest Digest::Fetch(const char* name) { |
| 7359 | +#if NCRYPTO_USE_OPENSSL3_PROVIDER |
| 7360 | + MarkPopErrorOnReturn mark_pop_error_on_return; |
| 7361 | + DeleteFnPtr<EVP_MD, EVP_MD_free> fetched( |
| 7362 | + EVP_MD_fetch(nullptr, name, nullptr)); |
| 7363 | + if (IsSupportedDigest(fetched.get())) { |
| 7364 | + return Digest(std::move(fetched)); |
| 7365 | + } |
| 7366 | +#endif |
| 7367 | + |
| 7368 | + return Digest(); |
7156 | 7369 | } |
7157 | 7370 |
|
7158 | 7371 | // ============================================================================ |
|
0 commit comments