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