| // 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 "quiche/quic/core/crypto/web_transport_fingerprint_proof_verifier.h" |
| |
| #include <cstdint> |
| #include <memory> |
| |
| #include "absl/strings/escaping.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/str_replace.h" |
| #include "absl/strings/string_view.h" |
| #include "openssl/sha.h" |
| #include "quiche/quic/core/crypto/certificate_view.h" |
| #include "quiche/quic/core/quic_time.h" |
| #include "quiche/quic/core/quic_types.h" |
| #include "quiche/quic/core/quic_utils.h" |
| #include "quiche/quic/platform/api/quic_bug_tracker.h" |
| #include "quiche/common/quiche_text_utils.h" |
| |
| namespace quic { |
| namespace { |
| |
| constexpr size_t kFingerprintLength = SHA256_DIGEST_LENGTH * 3 - 1; |
| |
| // 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[]; |
| constexpr char WebTransportHash::kSha256[]; |
| |
| 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; |
| } |
| } |
| } |
| |
| std::string normalized = |
| absl::StrReplaceAll(fingerprint.fingerprint, {{":", ""}}); |
| hashes_.push_back(WebTransportHash{fingerprint.algorithm, |
| absl::HexStringToBytes(normalized)}); |
| return true; |
| } |
| |
| bool WebTransportFingerprintProofVerifier::AddFingerprint( |
| WebTransportHash hash) { |
| if (hash.algorithm != CertificateFingerprint::kSha256) { |
| QUIC_DLOG(WARNING) << "Algorithms other than SHA-256 are not supported"; |
| return false; |
| } |
| if (hash.value.size() != SHA256_DIGEST_LENGTH) { |
| QUIC_DLOG(WARNING) << "Invalid fingerprint length"; |
| return false; |
| } |
| hashes_.push_back(std::move(hash)); |
| 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; |
| } |
| |
| if (!IsKeyTypeAllowedByPolicy(*view)) { |
| *details = std::make_unique<Details>(Status::kDisallowedKeyAlgorithm); |
| *error_details = |
| absl::StrCat("Certificate uses a disallowed public key type (", |
| PublicKeyTypeToString(view->public_key_type()), ")"); |
| 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://w3c.github.io/webtransport/#verify-a-certificate-hash |
| const std::string hash = RawSha256(der_certificate); |
| for (const WebTransportHash& reference : hashes_) { |
| if (reference.algorithm != WebTransportHash::kSha256) { |
| QUIC_BUG(quic_bug_10879_2) << "Unexpected non-SHA-256 hash"; |
| continue; |
| } |
| if (hash == reference.value) { |
| 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()); |
| } |
| |
| bool WebTransportFingerprintProofVerifier::IsKeyTypeAllowedByPolicy( |
| const CertificateView& certificate) { |
| switch (certificate.public_key_type()) { |
| // https://github.com/w3c/webtransport/pull/375 defines P-256 as an MTI |
| // algorithm, and prohibits RSA. We also allow P-384 and Ed25519. |
| case PublicKeyType::kP256: |
| case PublicKeyType::kP384: |
| case PublicKeyType::kEd25519: |
| return true; |
| case PublicKeyType::kRsa: |
| // TODO(b/213614428): this should be false by default. |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| } // namespace quic |