|
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> |
@@ -508,23 +509,43 @@ DataPointer DataPointer::resize(size_t len) { |
508 | 509 | } |
509 | 510 |
|
510 | 511 | // ============================================================================ |
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() { |
513 | 518 | #if OPENSSL_VERSION_MAJOR >= 3 |
514 | 519 | return EVP_default_properties_is_fips_enabled(nullptr) == 1; |
515 | 520 | #else |
516 | 521 | return FIPS_mode() == 1; |
517 | 522 | #endif |
518 | 523 | } |
| 524 | +} // namespace |
| 525 | + |
| 526 | +bool isFipsEnabled() { |
| 527 | + ClearErrorOnReturn clear_error_on_return; |
| 528 | + return isFipsEnabledRaw(); |
| 529 | +} |
519 | 530 |
|
520 | 531 | 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; |
522 | 534 | ClearErrorOnReturn clearErrorOnReturn(errors); |
523 | 535 | #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; |
525 | 538 | #else |
526 | | - return FIPS_mode_set(enable ? 1 : 0) == 1; |
| 539 | + const bool success = FIPS_mode_set(enable ? 1 : 0) == 1; |
527 | 540 | #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); |
528 | 549 | } |
529 | 550 |
|
530 | 551 | bool testFipsEnabled() { |
@@ -4407,11 +4428,120 @@ bool SSLCtxPointer::setCipherSuites(const char* ciphers) { |
4407 | 4428 |
|
4408 | 4429 | // ============================================================================ |
4409 | 4430 |
|
| 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 | + |
4410 | 4444 | #if OPENSSL_WITH_AES_SIV || OPENSSL_WITH_AES_GCM_SIV |
4411 | 4445 | Cipher::Cipher(DeleteFnPtr<EVP_CIPHER, EVP_CIPHER_free> cipher) |
4412 | 4446 | : cipher_(cipher.get()), fetched_cipher_(std::move(cipher)) {} |
4413 | 4447 | #endif |
4414 | 4448 |
|
| 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 | + |
4415 | 4545 | Cipher::Cipher(const Cipher& other) : cipher_(other.cipher_) { |
4416 | 4546 | #if OPENSSL_WITH_AES_SIV || OPENSSL_WITH_AES_GCM_SIV |
4417 | 4547 | if (other.fetched_cipher_ != nullptr) { |
@@ -6476,11 +6606,19 @@ EVP_MD_CTX* EVPMDCtxPointer::release() { |
6476 | 6606 | return ctx_.release(); |
6477 | 6607 | } |
6478 | 6608 |
|
6479 | | -bool EVPMDCtxPointer::digestInit(const Digest& digest) { |
| 6609 | +bool EVPMDCtxPointer::digestInit(const EVP_MD* digest) { |
6480 | 6610 | if (!ctx_) return false; |
6481 | 6611 | return EVP_DigestInit_ex(ctx_.get(), digest, nullptr) > 0; |
6482 | 6612 | } |
6483 | 6613 |
|
| 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 | + |
6484 | 6622 | bool EVPMDCtxPointer::digestUpdate(const Buffer<const void>& in) { |
6485 | 6623 | if (!ctx_) return false; |
6486 | 6624 | return EVP_DigestUpdate(ctx_.get(), in.data, in.len) > 0; |
@@ -7006,7 +7144,10 @@ DataPointer xofHashDigest(const Buffer<const unsigned char>& buf, |
7006 | 7144 | if (ctx.digestInit(md) != 1) { |
7007 | 7145 | return {}; |
7008 | 7146 | } |
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) { |
7010 | 7151 | return {}; |
7011 | 7152 | } |
7012 | 7153 | return ctx.digestFinal(output_length); |
@@ -7142,14 +7283,86 @@ size_t Digest::size() const { |
7142 | 7283 | return EVP_MD_size(md_); |
7143 | 7284 | } |
7144 | 7285 |
|
| 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 | + |
7145 | 7322 | const Digest Digest::MD5 = Digest(EVP_md5()); |
7146 | 7323 | const Digest Digest::SHA1 = Digest(EVP_sha1()); |
7147 | 7324 | const Digest Digest::SHA256 = Digest(EVP_sha256()); |
7148 | 7325 | const Digest Digest::SHA384 = Digest(EVP_sha384()); |
7149 | 7326 | const Digest Digest::SHA512 = Digest(EVP_sha512()); |
7150 | 7327 |
|
| 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 | + |
7151 | 7343 | 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(); |
7153 | 7366 | } |
7154 | 7367 |
|
7155 | 7368 | // ============================================================================ |
|
0 commit comments