| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "quic/quic_transport/web_transport_fingerprint_proof_verifier.h" |
| |
| #include <cstdint> |
| #include <memory> |
| |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| #include "third_party/boringssl/src/include/openssl/sha.h" |
| #include "quic/core/crypto/certificate_view.h" |
| #include "quic/core/quic_time.h" |
| #include "quic/core/quic_types.h" |
| #include "quic/platform/api/quic_bug_tracker.h" |
| #include "common/quiche_text_utils.h" |
| |
| namespace quic { |
| namespace { |
| |
| constexpr size_t kFingerprintLength = SHA256_DIGEST_LENGTH * 3 - 1; |
| |
| constexpr std::array<char, 16> kHexDigits = {'0', '1', '2', '3', '4', '5', |
| '6', '7', '8', '9', 'a', 'b', |
| 'c', 'd', 'e', 'f'}; |
| |
| // Assumes that the character is normalized to lowercase beforehand. |
| bool IsNormalizedHexDigit(char c) { |
| return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'); |
| } |
| |
| void NormalizeFingerprint(CertificateFingerprint& fingerprint) { |
| fingerprint.fingerprint = |
| quiche::QuicheTextUtils::ToLower(fingerprint.fingerprint); |
| } |
| |
| } // namespace |
| |
| constexpr char CertificateFingerprint::kSha256[]; |
| |
| std::string ComputeSha256Fingerprint(absl::string_view input) { |
| std::vector<uint8_t> raw_hash; |
| raw_hash.resize(SHA256_DIGEST_LENGTH); |
| SHA256(reinterpret_cast<const uint8_t*>(input.data()), input.size(), |
| raw_hash.data()); |
| |
| std::string output; |
| output.resize(kFingerprintLength); |
| for (size_t i = 0; i < output.size(); i++) { |
| uint8_t hash_byte = raw_hash[i / 3]; |
| switch (i % 3) { |
| case 0: |
| output[i] = kHexDigits[hash_byte >> 4]; |
| break; |
| case 1: |
| output[i] = kHexDigits[hash_byte & 0xf]; |
| break; |
| case 2: |
| output[i] = ':'; |
| break; |
| } |
| } |
| return output; |
| } |
| |
| ProofVerifyDetails* WebTransportFingerprintProofVerifier::Details::Clone() |
| const { |
| return new Details(*this); |
| } |
| |
| WebTransportFingerprintProofVerifier::WebTransportFingerprintProofVerifier( |
| const QuicClock* clock, |
| int max_validity_days) |
| : clock_(clock), |
| max_validity_days_(max_validity_days), |
| // Add an extra second to max validity to accomodate various edge cases. |
| max_validity_( |
| QuicTime::Delta::FromSeconds(max_validity_days * 86400 + 1)) {} |
| |
| bool WebTransportFingerprintProofVerifier::AddFingerprint( |
| CertificateFingerprint fingerprint) { |
| NormalizeFingerprint(fingerprint); |
| if (fingerprint.algorithm != CertificateFingerprint::kSha256) { |
| QUIC_DLOG(WARNING) << "Algorithms other than SHA-256 are not supported"; |
| return false; |
| } |
| if (fingerprint.fingerprint.size() != kFingerprintLength) { |
| QUIC_DLOG(WARNING) << "Invalid fingerprint length"; |
| return false; |
| } |
| for (size_t i = 0; i < fingerprint.fingerprint.size(); i++) { |
| char current = fingerprint.fingerprint[i]; |
| if (i % 3 == 2) { |
| if (current != ':') { |
| QUIC_DLOG(WARNING) |
| << "Missing colon separator between the bytes of the hash"; |
| return false; |
| } |
| } else { |
| if (!IsNormalizedHexDigit(current)) { |
| QUIC_DLOG(WARNING) << "Fingerprint must be in hexadecimal"; |
| return false; |
| } |
| } |
| } |
| |
| fingerprints_.push_back(fingerprint); |
| return true; |
| } |
| |
| QuicAsyncStatus WebTransportFingerprintProofVerifier::VerifyProof( |
| const std::string& /*hostname*/, |
| const uint16_t /*port*/, |
| const std::string& /*server_config*/, |
| QuicTransportVersion /*transport_version*/, |
| absl::string_view /*chlo_hash*/, |
| const std::vector<std::string>& /*certs*/, |
| const std::string& /*cert_sct*/, |
| const std::string& /*signature*/, |
| const ProofVerifyContext* /*context*/, |
| std::string* error_details, |
| std::unique_ptr<ProofVerifyDetails>* details, |
| std::unique_ptr<ProofVerifierCallback> /*callback*/) { |
| *error_details = |
| "QUIC crypto certificate verification is not supported in " |
| "WebTransportFingerprintProofVerifier"; |
| QUIC_BUG(quic_bug_10879_1) << *error_details; |
| *details = std::make_unique<Details>(Status::kInternalError); |
| return QUIC_FAILURE; |
| } |
| |
| QuicAsyncStatus WebTransportFingerprintProofVerifier::VerifyCertChain( |
| const std::string& /*hostname*/, |
| const uint16_t /*port*/, |
| const std::vector<std::string>& certs, |
| const std::string& /*ocsp_response*/, |
| const std::string& /*cert_sct*/, |
| const ProofVerifyContext* /*context*/, |
| std::string* error_details, |
| std::unique_ptr<ProofVerifyDetails>* details, |
| uint8_t* /*out_alert*/, |
| std::unique_ptr<ProofVerifierCallback> /*callback*/) { |
| if (certs.empty()) { |
| *details = std::make_unique<Details>(Status::kInternalError); |
| *error_details = "No certificates provided"; |
| return QUIC_FAILURE; |
| } |
| |
| if (!HasKnownFingerprint(certs[0])) { |
| *details = std::make_unique<Details>(Status::kUnknownFingerprint); |
| *error_details = "Certificate does not match any fingerprint"; |
| return QUIC_FAILURE; |
| } |
| |
| std::unique_ptr<CertificateView> view = |
| CertificateView::ParseSingleCertificate(certs[0]); |
| if (view == nullptr) { |
| *details = std::make_unique<Details>(Status::kCertificateParseFailure); |
| *error_details = "Failed to parse the certificate"; |
| return QUIC_FAILURE; |
| } |
| |
| if (!HasValidExpiry(*view)) { |
| *details = std::make_unique<Details>(Status::kExpiryTooLong); |
| *error_details = |
| absl::StrCat("Certificate expiry exceeds the configured limit of ", |
| max_validity_days_, " days"); |
| return QUIC_FAILURE; |
| } |
| |
| if (!IsWithinValidityPeriod(*view)) { |
| *details = std::make_unique<Details>(Status::kExpired); |
| *error_details = |
| "Certificate has expired or has validity listed in the future"; |
| return QUIC_FAILURE; |
| } |
| |
| *details = std::make_unique<Details>(Status::kValidCertificate); |
| return QUIC_SUCCESS; |
| } |
| |
| std::unique_ptr<ProofVerifyContext> |
| WebTransportFingerprintProofVerifier::CreateDefaultContext() { |
| return nullptr; |
| } |
| |
| bool WebTransportFingerprintProofVerifier::HasKnownFingerprint( |
| absl::string_view der_certificate) { |
| // https://wicg.github.io/web-transport/#verify-a-certificate-fingerprint |
| const std::string fingerprint = ComputeSha256Fingerprint(der_certificate); |
| for (const CertificateFingerprint& reference : fingerprints_) { |
| if (reference.algorithm != CertificateFingerprint::kSha256) { |
| QUIC_BUG(quic_bug_10879_2) << "Unexpected non-SHA-256 hash"; |
| continue; |
| } |
| if (fingerprint == reference.fingerprint) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool WebTransportFingerprintProofVerifier::HasValidExpiry( |
| const CertificateView& certificate) { |
| if (!certificate.validity_start().IsBefore(certificate.validity_end())) { |
| return false; |
| } |
| |
| const QuicTime::Delta duration_seconds = |
| certificate.validity_end() - certificate.validity_start(); |
| return duration_seconds <= max_validity_; |
| } |
| |
| bool WebTransportFingerprintProofVerifier::IsWithinValidityPeriod( |
| const CertificateView& certificate) { |
| QuicWallTime now = clock_->WallNow(); |
| return now.IsAfter(certificate.validity_start()) && |
| now.IsBefore(certificate.validity_end()); |
| } |
| |
| } // namespace quic |