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