|  | // 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 |