blob: 5f2d587b777f9bd7cafbfa9057112730739dadbd [file] [log] [blame]
// 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