|
9 | 9 | #include "envoy/runtime/runtime.h" |
10 | 10 |
|
11 | 11 | #include "common/common/assert.h" |
| 12 | +#include "common/common/base64.h" |
12 | 13 | #include "common/common/fmt.h" |
13 | 14 | #include "common/common/hex.h" |
14 | 15 |
|
@@ -131,6 +132,17 @@ ContextImpl::ContextImpl(ContextManagerImpl& parent, Stats::Scope& scope, |
131 | 132 | verify_mode = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT; |
132 | 133 | } |
133 | 134 |
|
| 135 | + if (!config.verifyCertificateSpkiList().empty()) { |
| 136 | + for (auto hash : config.verifyCertificateSpkiList()) { |
| 137 | + const auto decoded = Base64::decode(hash); |
| 138 | + if (decoded.size() != SHA256_DIGEST_LENGTH) { |
| 139 | + throw EnvoyException(fmt::format("Invalid base64-encoded SHA-256 {}", hash)); |
| 140 | + } |
| 141 | + verify_certificate_spki_list_.emplace_back(decoded.begin(), decoded.end()); |
| 142 | + } |
| 143 | + verify_mode = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT; |
| 144 | + } |
| 145 | + |
134 | 146 | if (verify_mode != SSL_VERIFY_NONE) { |
135 | 147 | SSL_CTX_set_verify(ctx_.get(), verify_mode, nullptr); |
136 | 148 | SSL_CTX_set_cert_verify_callback(ctx_.get(), ContextImpl::verifyCallback, this); |
@@ -263,10 +275,18 @@ int ContextImpl::verifyCertificate(X509* cert) { |
263 | 275 | return 0; |
264 | 276 | } |
265 | 277 |
|
266 | | - if (!verify_certificate_hash_list_.empty() && |
267 | | - !verifyCertificateHashList(cert, verify_certificate_hash_list_)) { |
268 | | - stats_.fail_verify_cert_hash_.inc(); |
269 | | - return 0; |
| 278 | + if (!verify_certificate_hash_list_.empty() || !verify_certificate_spki_list_.empty()) { |
| 279 | + const bool valid_certificate_hash = |
| 280 | + !verify_certificate_hash_list_.empty() && |
| 281 | + verifyCertificateHashList(cert, verify_certificate_hash_list_); |
| 282 | + const bool valid_certificate_spki = |
| 283 | + !verify_certificate_spki_list_.empty() && |
| 284 | + verifyCertificateSpkiList(cert, verify_certificate_spki_list_); |
| 285 | + |
| 286 | + if (!valid_certificate_hash && !valid_certificate_spki) { |
| 287 | + stats_.fail_verify_cert_hash_.inc(); |
| 288 | + return 0; |
| 289 | + } |
270 | 290 | } |
271 | 291 |
|
272 | 292 | return 1; |
@@ -334,13 +354,37 @@ bool ContextImpl::dNSNameMatch(const std::string& dNSName, const char* pattern) |
334 | 354 | } |
335 | 355 |
|
336 | 356 | bool ContextImpl::verifyCertificateHashList( |
337 | | - X509* cert, const std::vector<std::vector<uint8_t>>& certificate_hash_list) { |
| 357 | + X509* cert, const std::vector<std::vector<uint8_t>>& expected_hashes) { |
338 | 358 | std::vector<uint8_t> computed_hash(SHA256_DIGEST_LENGTH); |
339 | 359 | unsigned int n; |
340 | 360 | X509_digest(cert, EVP_sha256(), computed_hash.data(), &n); |
341 | 361 | RELEASE_ASSERT(n == computed_hash.size()); |
342 | 362 |
|
343 | | - for (const auto& expected_hash : certificate_hash_list) { |
| 363 | + for (const auto& expected_hash : expected_hashes) { |
| 364 | + if (computed_hash == expected_hash) { |
| 365 | + return true; |
| 366 | + } |
| 367 | + } |
| 368 | + return false; |
| 369 | +} |
| 370 | + |
| 371 | +bool ContextImpl::verifyCertificateSpkiList( |
| 372 | + X509* cert, const std::vector<std::vector<uint8_t>>& expected_hashes) { |
| 373 | + X509_PUBKEY* pubkey = X509_get_X509_PUBKEY(cert); |
| 374 | + if (pubkey == nullptr) { |
| 375 | + return false; |
| 376 | + } |
| 377 | + uint8_t* spki = nullptr; |
| 378 | + const int len = i2d_X509_PUBKEY(pubkey, &spki); |
| 379 | + if (len < 0) { |
| 380 | + return false; |
| 381 | + } |
| 382 | + bssl::UniquePtr<uint8_t> free_spki(spki); |
| 383 | + |
| 384 | + std::vector<uint8_t> computed_hash(SHA256_DIGEST_LENGTH); |
| 385 | + SHA256(spki, len, computed_hash.data()); |
| 386 | + |
| 387 | + for (const auto& expected_hash : expected_hashes) { |
344 | 388 | if (computed_hash == expected_hash) { |
345 | 389 | return true; |
346 | 390 | } |
@@ -579,6 +623,14 @@ ServerContextImpl::ServerContextImpl(ContextManagerImpl& parent, Stats::Scope& s |
579 | 623 | sizeof(std::remove_reference<decltype(hash)>::type::value_type)); |
580 | 624 | RELEASE_ASSERT(rc == 1); |
581 | 625 | } |
| 626 | + |
| 627 | + // verify_certificate_spki_ can only be set with a ca_cert |
| 628 | + for (const auto& hash : verify_certificate_spki_list_) { |
| 629 | + rc = EVP_DigestUpdate(&md, hash.data(), |
| 630 | + hash.size() * |
| 631 | + sizeof(std::remove_reference<decltype(hash)>::type::value_type)); |
| 632 | + RELEASE_ASSERT(rc == 1); |
| 633 | + } |
582 | 634 | } |
583 | 635 |
|
584 | 636 | // Hash configured SNIs for this context, so that sessions cannot be resumed across different |
|
0 commit comments