Use raw hashes in WebTransportFingerprintProofVerifier. The previous revision of the specification used WebRFC fingerprint format; this has been changed since. PiperOrigin-RevId: 410393221
diff --git a/quic/core/quic_utils.cc b/quic/core/quic_utils.cc index 98fbf55..021fe24 100644 --- a/quic/core/quic_utils.cc +++ b/quic/core/quic_utils.cc
@@ -14,6 +14,7 @@ #include "absl/base/optimization.h" #include "absl/numeric/int128.h" #include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/sha.h" #include "quic/core/quic_connection_id.h" #include "quic/core/quic_constants.h" #include "quic/core/quic_types.h" @@ -736,5 +737,13 @@ return total; } +std::string RawSha256(absl::string_view input) { + std::string raw_hash; + raw_hash.resize(SHA256_DIGEST_LENGTH); + SHA256(reinterpret_cast<const uint8_t*>(input.data()), input.size(), + reinterpret_cast<uint8_t*>(&raw_hash[0])); + return raw_hash; +} + #undef RETURN_STRING_LITERAL // undef for jumbo builds } // namespace quic
diff --git a/quic/core/quic_utils.h b/quic/core/quic_utils.h index c335bc0..a7bfc70 100644 --- a/quic/core/quic_utils.h +++ b/quic/core/quic_utils.h
@@ -256,6 +256,9 @@ QuicByteCount MemSliceSpanTotalSize(absl::Span<QuicMemSlice> span); +// Computes a SHA-256 hash and returns the raw bytes of the hash. +std::string RawSha256(absl::string_view input); + template <typename Mask> class QUIC_EXPORT_PRIVATE BitMask { public:
diff --git a/quic/quic_transport/web_transport_fingerprint_proof_verifier.cc b/quic/quic_transport/web_transport_fingerprint_proof_verifier.cc index 7e43341..ec58386 100644 --- a/quic/quic_transport/web_transport_fingerprint_proof_verifier.cc +++ b/quic/quic_transport/web_transport_fingerprint_proof_verifier.cc
@@ -7,12 +7,15 @@ #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 "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/core/quic_utils.h" #include "quic/platform/api/quic_bug_tracker.h" #include "common/quiche_text_utils.h" @@ -21,10 +24,6 @@ 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'); @@ -38,31 +37,7 @@ } // 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; -} +constexpr char WebTransportHash::kSha256[]; ProofVerifyDetails* WebTransportFingerprintProofVerifier::Details::Clone() const { @@ -70,8 +45,7 @@ } WebTransportFingerprintProofVerifier::WebTransportFingerprintProofVerifier( - const QuicClock* clock, - int max_validity_days) + 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. @@ -105,22 +79,34 @@ } } - fingerprints_.push_back(fingerprint); + 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& /*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, + 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 " @@ -131,14 +117,10 @@ } 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, + 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()) { @@ -187,14 +169,14 @@ 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) { + // 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 (fingerprint == reference.fingerprint) { + if (hash == reference.value) { return true; } }
diff --git a/quic/quic_transport/web_transport_fingerprint_proof_verifier.h b/quic/quic_transport/web_transport_fingerprint_proof_verifier.h index f597565..76323d5 100644 --- a/quic/quic_transport/web_transport_fingerprint_proof_verifier.h +++ b/quic/quic_transport/web_transport_fingerprint_proof_verifier.h
@@ -11,11 +11,14 @@ #include "quic/core/crypto/certificate_view.h" #include "quic/core/crypto/proof_verifier.h" #include "quic/core/quic_clock.h" +#include "quic/platform/api/quic_export.h" namespace quic { // Represents a fingerprint of an X.509 certificate in a format based on // https://w3c.github.io/webrtc-pc/#dom-rtcdtlsfingerprint. +// TODO(vasilvv): remove this once all consumers of this API use +// WebTransportHash. struct QUIC_EXPORT_PRIVATE CertificateFingerprint { static constexpr char kSha256[] = "sha-256"; @@ -27,10 +30,17 @@ std::string fingerprint; }; -// Computes a SHA-256 fingerprint of the specified input formatted in the same -// format as CertificateFingerprint::fingerprint would contain. -QUIC_EXPORT_PRIVATE std::string ComputeSha256Fingerprint( - absl::string_view input); +// Represents a fingerprint of an X.509 certificate in a format based on +// https://w3c.github.io/webtransport/#dictdef-webtransporthash. +struct QUIC_EXPORT_PRIVATE WebTransportHash { + static constexpr char kSha256[] = "sha-256"; + + // An algorithm described by one of the names in + // https://www.iana.org/assignments/hash-function-text-names/hash-function-text-names.xhtml + std::string algorithm; + // Raw bytes of the hash. + std::string value; +}; // WebTransportFingerprintProofVerifier verifies the server leaf certificate // against a supplied list of certificate fingerprints following the procedure @@ -76,6 +86,7 @@ // case-insensitive and are validated internally; the function returns true if // the validation passes. bool AddFingerprint(CertificateFingerprint fingerprint); + bool AddFingerprint(WebTransportHash hash); // ProofVerifier implementation. QuicAsyncStatus VerifyProof( @@ -112,7 +123,7 @@ const QuicClock* clock_; // Unowned. const int max_validity_days_; const QuicTime::Delta max_validity_; - std::vector<CertificateFingerprint> fingerprints_; + std::vector<WebTransportHash> hashes_; }; } // namespace quic
diff --git a/quic/quic_transport/web_transport_fingerprint_proof_verifier_test.cc b/quic/quic_transport/web_transport_fingerprint_proof_verifier_test.cc index 252af59..0b73312 100644 --- a/quic/quic_transport/web_transport_fingerprint_proof_verifier_test.cc +++ b/quic/quic_transport/web_transport_fingerprint_proof_verifier_test.cc
@@ -6,8 +6,10 @@ #include <memory> +#include "absl/strings/escaping.h" #include "absl/strings/string_view.h" #include "quic/core/quic_types.h" +#include "quic/core/quic_utils.h" #include "quic/platform/api/quic_test.h" #include "quic/test_tools/mock_clock.h" #include "quic/test_tools/test_certificates.h" @@ -56,9 +58,8 @@ } void AddTestCertificate() { - EXPECT_TRUE(verifier_->AddFingerprint( - CertificateFingerprint{CertificateFingerprint::kSha256, - ComputeSha256Fingerprint(kTestCertificate)})); + EXPECT_TRUE(verifier_->AddFingerprint(WebTransportHash{ + WebTransportHash::kSha256, RawSha256(kTestCertificate)})); } MockClock clock_; @@ -67,9 +68,9 @@ TEST_F(WebTransportFingerprintProofVerifierTest, Sha256Fingerprint) { // Computed using `openssl x509 -fingerprint -sha256`. - EXPECT_EQ(ComputeSha256Fingerprint(kTestCertificate), - "f2:e5:46:5e:2b:f7:ec:d6:f6:30:66:a5:a3:75:11:73:4a:a0:eb:7c:47:01:" - "0e:86:d6:75:8e:d4:f4:fa:1b:0f"); + EXPECT_EQ(absl::BytesToHexString(RawSha256(kTestCertificate)), + "f2e5465e2bf7ecd6f63066a5a37511734aa0eb7c4701" + "0e86d6758ed4f4fa1b0f"); } TEST_F(WebTransportFingerprintProofVerifierTest, SimpleFingerprint) { @@ -138,9 +139,8 @@ TEST_F(WebTransportFingerprintProofVerifierTest, InvalidCertificate) { constexpr absl::string_view kInvalidCertificate = "Hello, world!"; - ASSERT_TRUE(verifier_->AddFingerprint( - {CertificateFingerprint::kSha256, - ComputeSha256Fingerprint(kInvalidCertificate)})); + ASSERT_TRUE(verifier_->AddFingerprint(WebTransportHash{ + WebTransportHash::kSha256, RawSha256(kInvalidCertificate)})); VerifyResult result = Verify(kInvalidCertificate); EXPECT_EQ(result.status, QUIC_FAILURE); @@ -153,30 +153,29 @@ // Accept all-uppercase fingerprints. verifier_ = std::make_unique<WebTransportFingerprintProofVerifier>( &clock_, /*max_validity_days=*/365); - EXPECT_TRUE(verifier_->AddFingerprint( - {CertificateFingerprint::kSha256, - "F2:E5:46:5E:2B:F7:EC:D6:F6:30:66:A5:A3:75:11:73:4A:A0:EB:" - "7C:47:01:0E:86:D6:75:8E:D4:F4:FA:1B:0F"})); + EXPECT_TRUE(verifier_->AddFingerprint(CertificateFingerprint{ + CertificateFingerprint::kSha256, + "F2:E5:46:5E:2B:F7:EC:D6:F6:30:66:A5:A3:75:11:73:4A:A0:EB:" + "7C:47:01:0E:86:D6:75:8E:D4:F4:FA:1B:0F"})); EXPECT_EQ(Verify(kTestCertificate).detailed_status, WebTransportFingerprintProofVerifier::Status::kValidCertificate); // Reject unknown hash algorithms. - EXPECT_FALSE(verifier_->AddFingerprint( - {"sha-1", - "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00"})); + EXPECT_FALSE(verifier_->AddFingerprint(CertificateFingerprint{ + "sha-1", "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00"})); // Reject invalid length. EXPECT_FALSE(verifier_->AddFingerprint( - {CertificateFingerprint::kSha256, "00:00:00:00"})); + CertificateFingerprint{CertificateFingerprint::kSha256, "00:00:00:00"})); // Reject missing colons. - EXPECT_FALSE(verifier_->AddFingerprint( - {CertificateFingerprint::kSha256, - "00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00." - "00.00.00.00.00.00.00.00.00.00.00.00.00"})); + EXPECT_FALSE(verifier_->AddFingerprint(CertificateFingerprint{ + CertificateFingerprint::kSha256, + "00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00." + "00.00.00.00.00.00.00.00.00.00.00.00.00"})); // Reject non-hex symbols. - EXPECT_FALSE(verifier_->AddFingerprint( - {CertificateFingerprint::kSha256, - "zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:" - "zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz"})); + EXPECT_FALSE(verifier_->AddFingerprint(CertificateFingerprint{ + CertificateFingerprint::kSha256, + "zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:" + "zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz:zz"})); } } // namespace