You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
253 lines
8.1 KiB
253 lines
8.1 KiB
// Copyright 2019 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 "cast/common/certificate/cast_crl.h"
|
|
|
|
#include <openssl/digest.h>
|
|
#include <time.h>
|
|
|
|
#include <memory>
|
|
|
|
#include "absl/strings/string_view.h"
|
|
#include "cast/common/certificate/cast_cert_validator_internal.h"
|
|
#include "platform/base/macros.h"
|
|
#include "util/crypto/certificate_utils.h"
|
|
#include "util/crypto/sha2.h"
|
|
#include "util/osp_logging.h"
|
|
|
|
namespace openscreen {
|
|
namespace cast {
|
|
namespace {
|
|
|
|
enum CrlVersion {
|
|
// version 0: Spki Hash Algorithm = SHA-256
|
|
// Signature Algorithm = RSA-PKCS1 V1.5 with SHA-256
|
|
kCrlVersion0 = 0,
|
|
};
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Cast CRL trust anchors.
|
|
// -------------------------------------------------------------------------
|
|
|
|
// There is one trusted root for Cast CRL certificate chains:
|
|
//
|
|
// (1) CN=Cast CRL Root CA (kCastCRLRootCaDer)
|
|
//
|
|
// These constants are defined by the file included next:
|
|
|
|
#include "cast/common/certificate/cast_crl_root_ca_cert_der-inc.h"
|
|
|
|
// Singleton for the trust store using the default Cast CRL root.
|
|
class CastCRLTrustStore {
|
|
public:
|
|
static CastCRLTrustStore* GetInstance() {
|
|
static CastCRLTrustStore* store = new CastCRLTrustStore();
|
|
return store;
|
|
}
|
|
|
|
TrustStore* trust_store() { return &trust_store_; }
|
|
|
|
~CastCRLTrustStore() = default;
|
|
|
|
private:
|
|
CastCRLTrustStore() {
|
|
trust_store_.certs.emplace_back(MakeTrustAnchor(kCastCRLRootCaDer));
|
|
}
|
|
|
|
TrustStore trust_store_;
|
|
OSP_DISALLOW_COPY_AND_ASSIGN(CastCRLTrustStore);
|
|
};
|
|
|
|
ConstDataSpan ConstDataSpanFromString(const std::string& s) {
|
|
return ConstDataSpan{reinterpret_cast<const uint8_t*>(s.data()),
|
|
static_cast<uint32_t>(s.size())};
|
|
}
|
|
|
|
// Verifies the CRL is signed by a trusted CRL authority at the time the CRL
|
|
// was issued. Verifies the signature of |tbs_crl| is valid based on the
|
|
// certificate and signature in |crl|. The validity of |tbs_crl| is verified
|
|
// at |time|. The validity period of the CRL is adjusted to be the earliest
|
|
// of the issuer certificate chain's expiration and the CRL's expiration and
|
|
// the result is stored in |overall_not_after|.
|
|
bool VerifyCRL(const Crl& crl,
|
|
const TbsCrl& tbs_crl,
|
|
const DateTime& time,
|
|
TrustStore* trust_store,
|
|
DateTime* overall_not_after) {
|
|
CertificatePathResult result_path = {};
|
|
Error error =
|
|
FindCertificatePath({crl.signer_cert()}, time, &result_path, trust_store);
|
|
if (!error.ok()) {
|
|
return false;
|
|
}
|
|
|
|
bssl::UniquePtr<EVP_PKEY> public_key{
|
|
X509_get_pubkey(result_path.target_cert.get())};
|
|
if (!VerifySignedData(EVP_sha256(), public_key.get(),
|
|
ConstDataSpanFromString(crl.tbs_crl()),
|
|
ConstDataSpanFromString(crl.signature()))) {
|
|
return false;
|
|
}
|
|
|
|
// Verify the CRL is still valid.
|
|
DateTime not_before;
|
|
if (!DateTimeFromSeconds(tbs_crl.not_before_seconds(), ¬_before)) {
|
|
return false;
|
|
}
|
|
DateTime not_after;
|
|
if (!DateTimeFromSeconds(tbs_crl.not_after_seconds(), ¬_after)) {
|
|
return false;
|
|
}
|
|
if ((time < not_before) || (not_after < time)) {
|
|
return false;
|
|
}
|
|
|
|
// Set CRL expiry to the earliest of the cert chain expiry and CRL expiry
|
|
// (excluding trust anchor). No intermediates are provided above, so this
|
|
// just amounts to |signer_cert| vs. |not_after_seconds|.
|
|
*overall_not_after = not_after;
|
|
bssl::UniquePtr<ASN1_GENERALIZEDTIME> not_after_asn1{
|
|
ASN1_TIME_to_generalizedtime(
|
|
X509_get0_notAfter(result_path.target_cert.get()), nullptr)};
|
|
if (!not_after_asn1) {
|
|
return false;
|
|
}
|
|
DateTime cert_not_after;
|
|
bool time_valid =
|
|
ParseAsn1GeneralizedTime(not_after_asn1.get(), &cert_not_after);
|
|
if (!time_valid) {
|
|
return false;
|
|
}
|
|
if (cert_not_after < *overall_not_after) {
|
|
*overall_not_after = cert_not_after;
|
|
}
|
|
|
|
// Perform sanity check on serial numbers.
|
|
for (const auto& range : tbs_crl.revoked_serial_number_ranges()) {
|
|
uint64_t first_serial_number = range.first_serial_number();
|
|
uint64_t last_serial_number = range.last_serial_number();
|
|
if (last_serial_number < first_serial_number) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
CastCRL::CastCRL(const TbsCrl& tbs_crl, const DateTime& overall_not_after) {
|
|
// Parse the validity information.
|
|
// Assume DateTimeFromSeconds will succeed. Successful call to VerifyCRL means
|
|
// that these calls were successful.
|
|
DateTimeFromSeconds(tbs_crl.not_before_seconds(), ¬_before_);
|
|
DateTimeFromSeconds(tbs_crl.not_after_seconds(), ¬_after_);
|
|
if (overall_not_after < not_after_) {
|
|
not_after_ = overall_not_after;
|
|
}
|
|
|
|
// Parse the revoked hashes.
|
|
for (const auto& hash : tbs_crl.revoked_public_key_hashes()) {
|
|
revoked_hashes_.insert(hash);
|
|
}
|
|
|
|
// Parse the revoked serial ranges.
|
|
for (const auto& range : tbs_crl.revoked_serial_number_ranges()) {
|
|
std::string issuer_hash = range.issuer_public_key_hash();
|
|
|
|
uint64_t first_serial_number = range.first_serial_number();
|
|
uint64_t last_serial_number = range.last_serial_number();
|
|
auto& serial_number_range = revoked_serial_numbers_[issuer_hash];
|
|
serial_number_range.push_back({first_serial_number, last_serial_number});
|
|
}
|
|
}
|
|
|
|
CastCRL::~CastCRL() {}
|
|
|
|
// Verifies the revocation status of the certificate chain, at the specified
|
|
// time.
|
|
bool CastCRL::CheckRevocation(const std::vector<X509*>& trusted_chain,
|
|
const DateTime& time) const {
|
|
if (trusted_chain.empty())
|
|
return false;
|
|
|
|
if ((time < not_before_) || (not_after_ < time)) {
|
|
return false;
|
|
}
|
|
|
|
// Check revocation. This loop iterates over both certificates AND then the
|
|
// trust anchor after exhausting the certs.
|
|
for (size_t i = 0; i < trusted_chain.size(); ++i) {
|
|
std::string spki_tlv = GetSpkiTlv(trusted_chain[i]);
|
|
if (spki_tlv.empty()) {
|
|
return false;
|
|
}
|
|
|
|
ErrorOr<std::string> spki_hash = SHA256HashString(spki_tlv);
|
|
if (spki_hash.is_error() ||
|
|
(revoked_hashes_.find(spki_hash.value()) != revoked_hashes_.end())) {
|
|
return false;
|
|
}
|
|
|
|
// Check if the subordinate certificate was revoked by serial number.
|
|
if (i < (trusted_chain.size() - 1)) {
|
|
const auto issuer_iter = revoked_serial_numbers_.find(spki_hash.value());
|
|
if (issuer_iter != revoked_serial_numbers_.end()) {
|
|
const auto& subordinate = trusted_chain[i + 1];
|
|
uint64_t serial_number;
|
|
|
|
// Only Google generated device certificates will be revoked by range.
|
|
// These will always be less than 64 bits in length.
|
|
ErrorOr<uint64_t> maybe_serial =
|
|
ParseDerUint64(X509_get0_serialNumber(subordinate));
|
|
if (!maybe_serial) {
|
|
continue;
|
|
}
|
|
serial_number = maybe_serial.value();
|
|
for (const auto& revoked_serial : issuer_iter->second) {
|
|
if (revoked_serial.first_serial <= serial_number &&
|
|
revoked_serial.last_serial >= serial_number) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::unique_ptr<CastCRL> ParseAndVerifyCRL(const std::string& crl_proto,
|
|
const DateTime& time,
|
|
TrustStore* trust_store) {
|
|
if (!trust_store)
|
|
trust_store = CastCRLTrustStore::GetInstance()->trust_store();
|
|
|
|
CrlBundle crl_bundle;
|
|
if (!crl_bundle.ParseFromString(crl_proto)) {
|
|
return nullptr;
|
|
}
|
|
for (const auto& crl : crl_bundle.crls()) {
|
|
TbsCrl tbs_crl;
|
|
if (!tbs_crl.ParseFromString(crl.tbs_crl())) {
|
|
OSP_LOG_WARN << "Binary TBS CRL could not be parsed.";
|
|
continue;
|
|
}
|
|
if (tbs_crl.version() != kCrlVersion0) {
|
|
OSP_LOG_WARN << "Binary TBS CRL has unknown version: "
|
|
<< tbs_crl.version();
|
|
continue;
|
|
}
|
|
DateTime overall_not_after;
|
|
if (!VerifyCRL(crl, tbs_crl, time, trust_store, &overall_not_after)) {
|
|
return nullptr;
|
|
}
|
|
// TODO(btolsch): Why is this 'return first successful CRL'?
|
|
return std::make_unique<CastCRL>(tbs_crl, overall_not_after);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
} // namespace cast
|
|
} // namespace openscreen
|