1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "quiche/quic/core/crypto/web_transport_fingerprint_proof_verifier.h"
6
7 #include <cstdint>
8 #include <memory>
9 #include <string>
10 #include <utility>
11
12 #include "absl/strings/escaping.h"
13 #include "absl/strings/match.h"
14 #include "absl/strings/str_cat.h"
15 #include "absl/strings/str_replace.h"
16 #include "absl/strings/string_view.h"
17 #include "openssl/sha.h"
18 #include "quiche/quic/core/crypto/certificate_view.h"
19 #include "quiche/quic/core/quic_time.h"
20 #include "quiche/quic/core/quic_types.h"
21 #include "quiche/quic/core/quic_utils.h"
22 #include "quiche/quic/platform/api/quic_bug_tracker.h"
23 #include "quiche/quic/platform/api/quic_logging.h"
24 #include "quiche/common/quiche_text_utils.h"
25
26 namespace quic {
27 namespace {
28
29 constexpr size_t kFingerprintLength = SHA256_DIGEST_LENGTH * 3 - 1;
30
31 // Assumes that the character is normalized to lowercase beforehand.
IsNormalizedHexDigit(char c)32 bool IsNormalizedHexDigit(char c) {
33 return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f');
34 }
35
NormalizeFingerprint(CertificateFingerprint & fingerprint)36 void NormalizeFingerprint(CertificateFingerprint& fingerprint) {
37 fingerprint.fingerprint =
38 quiche::QuicheTextUtils::ToLower(fingerprint.fingerprint);
39 }
40
41 } // namespace
42
43 constexpr char CertificateFingerprint::kSha256[];
44 constexpr char WebTransportHash::kSha256[];
45
Clone() const46 ProofVerifyDetails* WebTransportFingerprintProofVerifier::Details::Clone()
47 const {
48 return new Details(*this);
49 }
50
WebTransportFingerprintProofVerifier(const QuicClock * clock,int max_validity_days)51 WebTransportFingerprintProofVerifier::WebTransportFingerprintProofVerifier(
52 const QuicClock* clock, int max_validity_days)
53 : clock_(clock),
54 max_validity_days_(max_validity_days),
55 // Add an extra second to max validity to accomodate various edge cases.
56 max_validity_(
57 QuicTime::Delta::FromSeconds(max_validity_days * 86400 + 1)) {}
58
AddFingerprint(CertificateFingerprint fingerprint)59 bool WebTransportFingerprintProofVerifier::AddFingerprint(
60 CertificateFingerprint fingerprint) {
61 NormalizeFingerprint(fingerprint);
62 if (!absl::EqualsIgnoreCase(fingerprint.algorithm,
63 CertificateFingerprint::kSha256)) {
64 QUIC_DLOG(WARNING) << "Algorithms other than SHA-256 are not supported";
65 return false;
66 }
67 if (fingerprint.fingerprint.size() != kFingerprintLength) {
68 QUIC_DLOG(WARNING) << "Invalid fingerprint length";
69 return false;
70 }
71 for (size_t i = 0; i < fingerprint.fingerprint.size(); i++) {
72 char current = fingerprint.fingerprint[i];
73 if (i % 3 == 2) {
74 if (current != ':') {
75 QUIC_DLOG(WARNING)
76 << "Missing colon separator between the bytes of the hash";
77 return false;
78 }
79 } else {
80 if (!IsNormalizedHexDigit(current)) {
81 QUIC_DLOG(WARNING) << "Fingerprint must be in hexadecimal";
82 return false;
83 }
84 }
85 }
86
87 std::string normalized =
88 absl::StrReplaceAll(fingerprint.fingerprint, {{":", ""}});
89 std::string normalized_bytes;
90 if (!absl::HexStringToBytes(normalized, &normalized_bytes)) {
91 QUIC_DLOG(WARNING) << "Fingerprint hexadecimal is invalid";
92 return false;
93 }
94 hashes_.push_back(
95 WebTransportHash{fingerprint.algorithm, std::move(normalized_bytes)});
96 return true;
97 }
98
AddFingerprint(WebTransportHash hash)99 bool WebTransportFingerprintProofVerifier::AddFingerprint(
100 WebTransportHash hash) {
101 if (hash.algorithm != CertificateFingerprint::kSha256) {
102 QUIC_DLOG(WARNING) << "Algorithms other than SHA-256 are not supported";
103 return false;
104 }
105 if (hash.value.size() != SHA256_DIGEST_LENGTH) {
106 QUIC_DLOG(WARNING) << "Invalid fingerprint length";
107 return false;
108 }
109 hashes_.push_back(std::move(hash));
110 return true;
111 }
112
VerifyProof(const std::string &,const uint16_t,const std::string &,QuicTransportVersion,absl::string_view,const std::vector<std::string> &,const std::string &,const std::string &,const ProofVerifyContext *,std::string * error_details,std::unique_ptr<ProofVerifyDetails> * details,std::unique_ptr<ProofVerifierCallback>)113 QuicAsyncStatus WebTransportFingerprintProofVerifier::VerifyProof(
114 const std::string& /*hostname*/, const uint16_t /*port*/,
115 const std::string& /*server_config*/,
116 QuicTransportVersion /*transport_version*/, absl::string_view /*chlo_hash*/,
117 const std::vector<std::string>& /*certs*/, const std::string& /*cert_sct*/,
118 const std::string& /*signature*/, const ProofVerifyContext* /*context*/,
119 std::string* error_details, std::unique_ptr<ProofVerifyDetails>* details,
120 std::unique_ptr<ProofVerifierCallback> /*callback*/) {
121 *error_details =
122 "QUIC crypto certificate verification is not supported in "
123 "WebTransportFingerprintProofVerifier";
124 QUIC_BUG(quic_bug_10879_1) << *error_details;
125 *details = std::make_unique<Details>(Status::kInternalError);
126 return QUIC_FAILURE;
127 }
128
VerifyCertChain(const std::string &,const uint16_t,const std::vector<std::string> & certs,const std::string &,const std::string &,const ProofVerifyContext *,std::string * error_details,std::unique_ptr<ProofVerifyDetails> * details,uint8_t *,std::unique_ptr<ProofVerifierCallback>)129 QuicAsyncStatus WebTransportFingerprintProofVerifier::VerifyCertChain(
130 const std::string& /*hostname*/, const uint16_t /*port*/,
131 const std::vector<std::string>& certs, const std::string& /*ocsp_response*/,
132 const std::string& /*cert_sct*/, const ProofVerifyContext* /*context*/,
133 std::string* error_details, std::unique_ptr<ProofVerifyDetails>* details,
134 uint8_t* /*out_alert*/,
135 std::unique_ptr<ProofVerifierCallback> /*callback*/) {
136 if (certs.empty()) {
137 *details = std::make_unique<Details>(Status::kInternalError);
138 *error_details = "No certificates provided";
139 return QUIC_FAILURE;
140 }
141
142 if (!HasKnownFingerprint(certs[0])) {
143 *details = std::make_unique<Details>(Status::kUnknownFingerprint);
144 *error_details = "Certificate does not match any fingerprint";
145 return QUIC_FAILURE;
146 }
147
148 std::unique_ptr<CertificateView> view =
149 CertificateView::ParseSingleCertificate(certs[0]);
150 if (view == nullptr) {
151 *details = std::make_unique<Details>(Status::kCertificateParseFailure);
152 *error_details = "Failed to parse the certificate";
153 return QUIC_FAILURE;
154 }
155
156 if (!HasValidExpiry(*view)) {
157 *details = std::make_unique<Details>(Status::kExpiryTooLong);
158 *error_details =
159 absl::StrCat("Certificate expiry exceeds the configured limit of ",
160 max_validity_days_, " days");
161 return QUIC_FAILURE;
162 }
163
164 if (!IsWithinValidityPeriod(*view)) {
165 *details = std::make_unique<Details>(Status::kExpired);
166 *error_details =
167 "Certificate has expired or has validity listed in the future";
168 return QUIC_FAILURE;
169 }
170
171 if (!IsKeyTypeAllowedByPolicy(*view)) {
172 *details = std::make_unique<Details>(Status::kDisallowedKeyAlgorithm);
173 *error_details =
174 absl::StrCat("Certificate uses a disallowed public key type (",
175 PublicKeyTypeToString(view->public_key_type()), ")");
176 return QUIC_FAILURE;
177 }
178
179 *details = std::make_unique<Details>(Status::kValidCertificate);
180 return QUIC_SUCCESS;
181 }
182
183 std::unique_ptr<ProofVerifyContext>
CreateDefaultContext()184 WebTransportFingerprintProofVerifier::CreateDefaultContext() {
185 return nullptr;
186 }
187
HasKnownFingerprint(absl::string_view der_certificate)188 bool WebTransportFingerprintProofVerifier::HasKnownFingerprint(
189 absl::string_view der_certificate) {
190 // https://w3c.github.io/webtransport/#verify-a-certificate-hash
191 const std::string hash = RawSha256(der_certificate);
192 for (const WebTransportHash& reference : hashes_) {
193 if (reference.algorithm != WebTransportHash::kSha256) {
194 QUIC_BUG(quic_bug_10879_2) << "Unexpected non-SHA-256 hash";
195 continue;
196 }
197 if (hash == reference.value) {
198 return true;
199 }
200 }
201 return false;
202 }
203
HasValidExpiry(const CertificateView & certificate)204 bool WebTransportFingerprintProofVerifier::HasValidExpiry(
205 const CertificateView& certificate) {
206 if (!certificate.validity_start().IsBefore(certificate.validity_end())) {
207 return false;
208 }
209
210 const QuicTime::Delta duration_seconds =
211 certificate.validity_end() - certificate.validity_start();
212 return duration_seconds <= max_validity_;
213 }
214
IsWithinValidityPeriod(const CertificateView & certificate)215 bool WebTransportFingerprintProofVerifier::IsWithinValidityPeriod(
216 const CertificateView& certificate) {
217 QuicWallTime now = clock_->WallNow();
218 return now.IsAfter(certificate.validity_start()) &&
219 now.IsBefore(certificate.validity_end());
220 }
221
IsKeyTypeAllowedByPolicy(const CertificateView & certificate)222 bool WebTransportFingerprintProofVerifier::IsKeyTypeAllowedByPolicy(
223 const CertificateView& certificate) {
224 switch (certificate.public_key_type()) {
225 // https://github.com/w3c/webtransport/pull/375 defines P-256 as an MTI
226 // algorithm, and prohibits RSA. We also allow P-384 and Ed25519.
227 case PublicKeyType::kP256:
228 case PublicKeyType::kP384:
229 case PublicKeyType::kEd25519:
230 return true;
231 case PublicKeyType::kRsa:
232 // TODO(b/213614428): this should be false by default.
233 return true;
234 default:
235 return false;
236 }
237 }
238
239 } // namespace quic
240