Chromium Code Reviews| Index: components/cast_certificate/cast_crl.cc |
| diff --git a/components/cast_certificate/cast_crl.cc b/components/cast_certificate/cast_crl.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..1f1ae6531f59abcafd9e6b29b2d7959e32cbb69a |
| --- /dev/null |
| +++ b/components/cast_certificate/cast_crl.cc |
| @@ -0,0 +1,367 @@ |
| +// Copyright 2016 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 "components/cast_certificate/cast_crl.h" |
| + |
| +#include "base/base64.h" |
| +#include "base/memory/ptr_util.h" |
| +#include "base/memory/singleton.h" |
| +#include "components/cast_certificate/cast_cert_validator.h" |
| +#include "components/cast_certificate/proto/revocation.pb.h" |
| +#include "crypto/sha2.h" |
| +#include "net/cert/internal/parse_certificate.h" |
| +#include "net/cert/internal/parsed_certificate.h" |
| +#include "net/cert/internal/signature_algorithm.h" |
| +#include "net/cert/internal/signature_policy.h" |
| +#include "net/cert/internal/trust_store.h" |
| +#include "net/cert/internal/verify_certificate_chain.h" |
| +#include "net/cert/internal/verify_signed_data.h" |
| +#include "net/cert/x509_certificate.h" |
| +#include "net/der/input.h" |
| +#include "net/der/parser.h" |
| +#include "net/der/parse_values.h" |
| + |
| +namespace cast_certificate { |
| +namespace { |
| +// ------------------------------------------------------------------------- |
| +// 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 "components/cast_certificate/cast_crl_root_ca_cert_der-inc.h" |
| + |
| +// Singleton for the Cast CRL trust store. |
| +class CastCRLTrustStore { |
| + public: |
| + static CastCRLTrustStore* GetInstance() { |
| + return base::Singleton<CastCRLTrustStore, base::LeakySingletonTraits< |
| + CastCRLTrustStore>>::get(); |
| + } |
| + |
| + static net::TrustStore& Get() { return GetInstance()->store_; } |
| + |
| + private: |
| + friend struct base::DefaultSingletonTraits<CastCRLTrustStore>; |
| + |
| + CastCRLTrustStore() { |
| + // Initialize the trust store with the root certificate. |
| + // TODO (ryanchung): Add official Cast CRL Root here |
| + // scoped_refptr<net::ParsedCertificate> root = net::ParsedCertificate:: |
| + // net::ParsedCertificate::CreateFromCertificateData( |
| + // kCastCRLRootCaDer, sizeof(kCastCRLRootCaDer), |
| + // net::ParsedCertificate::DataSource::EXTERNAL_REFERENCE); |
| + // CHECK(root); |
| + // store_.AddTrustedCertificate(std::move(root)); |
| + } |
| + |
| + net::TrustStore store_; |
| + DISALLOW_COPY_AND_ASSIGN(CastCRLTrustStore); |
| +}; |
| + |
| +// The parsed certificate information. |
| +struct CastCertificateInfo { |
| + uint64_t serial_number; |
| + std::string issuer_name; |
| + net::der::Input spki_tlv; |
| + std::string key_hash; |
| +}; |
| + |
| +// Parses a der encoded certificate for the information for CastCertificateInfo. |
| +bool ParseCertificateInfo(scoped_refptr<net::ParsedCertificate>& cert, |
| + CastCertificateInfo* parsed_cert) { |
| + // Parse the serial number. |
| + if (!net::der::ParseUint64(cert->tbs().serial_number, |
| + &parsed_cert->serial_number)) { |
| + return false; |
| + } |
| + |
| + // Parse the public key and calculate its hash. |
|
sheretov
2016/06/13 19:18:35
This is wrong. We should hash the SPKI directly -
ryanchung
2016/06/21 21:45:06
OK, Done.
Originally, the hash of the subject publ
|
| + net::der::Parser outer_parser(cert->tbs().spki_tlv); |
| + net::der::Parser spki_parser; |
| + net::der::BitString key_bits; |
| + if (!outer_parser.ReadSequence(&spki_parser)) |
| + return false; |
| + if (outer_parser.HasMore()) |
| + return false; |
| + if (!spki_parser.SkipTag(net::der::kSequence)) |
| + return false; |
| + if (!spki_parser.ReadBitString(&key_bits)) |
| + return false; |
| + std::string public_key = key_bits.bytes().AsString(); |
| + parsed_cert->key_hash = crypto::SHA256HashString(public_key); |
| + parsed_cert->spki_tlv = cert->tbs().spki_tlv; |
| + |
| + // The issuer's name. |
| + parsed_cert->issuer_name = cert->normalized_issuer().AsString(); |
| + return true; |
| +} |
| + |
| +// Converts a base::Time::Exploded to a net::der::GeneralizedTime. |
| +net::der::GeneralizedTime ConvertExplodedTime( |
| + const base::Time::Exploded& exploded) { |
| + net::der::GeneralizedTime result; |
| + result.year = exploded.year; |
| + result.month = exploded.month; |
| + result.day = exploded.day_of_month; |
| + result.hours = exploded.hour; |
| + result.minutes = exploded.minute; |
| + result.seconds = exploded.second; |
| + return result; |
| +} |
| + |
| +// Specifies the signature verification policy. |
| +std::unique_ptr<net::SignaturePolicy> CreateCastSignaturePolicy() { |
| + return base::WrapUnique(new net::SimpleSignaturePolicy(2048)); |
| +} |
| + |
| +class ScopedCheckUnreferencedCert { |
| + public: |
| + explicit ScopedCheckUnreferencedCert(net::ParsedCertificate* cert) |
| + : cert_(cert) {} |
| + ~ScopedCheckUnreferencedCert() { DCHECK(cert_->HasOneRef()); } |
| + |
| + private: |
| + net::ParsedCertificate* cert_; |
| +}; |
| + |
| +// Verifies the CRL is signed by a trusted CRL authority at the time the CRL was |
|
sheretov
2016/06/13 19:18:35
The CRL, including all the certs in the path shoul
ryanchung
2016/06/21 21:45:05
Added the current time here.
As discussed offline,
|
| +// issued. |
| +bool VerifyCRL(const Crl& crl, const TbsCrl& tbs_crl) { |
| + // Verify the trust of the CRL authority. |
| + base::Time signing_time = |
| + base::Time::UnixEpoch() + |
| + base::TimeDelta::FromMilliseconds(tbs_crl.issuance_time_millis()); |
| + base::Time::Exploded signing_time_exploded; |
| + signing_time.UTCExplode(&signing_time_exploded); |
| + net::der::GeneralizedTime signing_time_generalized = |
| + ConvertExplodedTime(signing_time_exploded); |
| + std::vector<scoped_refptr<net::ParsedCertificate>> input_chain; |
| + scoped_refptr<net::ParsedCertificate> parsed_cert = |
| + net::ParsedCertificate::CreateFromCertificateData( |
| + reinterpret_cast<const uint8_t*>(crl.signer_cert().data()), |
| + crl.signer_cert().size(), |
| + net::ParsedCertificate::DataSource::EXTERNAL_REFERENCE); |
| + if (parsed_cert == nullptr) |
| + return false; |
| + ScopedCheckUnreferencedCert ref_checker(parsed_cert.get()); |
| + input_chain.push_back(std::move(parsed_cert)); |
| + |
| + auto signature_policy = CreateCastSignaturePolicy(); |
| + if (!net::VerifyCertificateChain(input_chain, CastCRLTrustStore::Get(), |
| + signature_policy.get(), |
| + signing_time_generalized)) { |
| + return false; |
| + } |
| + |
| + // Parse the signer certificate |
| + if (input_chain.size() == 0) |
| + return false; |
| + CastCertificateInfo cert_info; |
| + if (!ParseCertificateInfo(input_chain[0], &cert_info)) |
| + return false; |
| + |
| + // Parse the signature. |
| + std::string signature; |
| + signature = crl.signature(); |
| + net::der::BitString signature_value_bit_string; |
| + signature_value_bit_string = |
| + net::der::BitString(net::der::Input(&signature), 0); |
| + |
| + // Verify the signature |
| + std::unique_ptr<net::SignaturePolicy> policy = |
| + base::WrapUnique(new net::SimpleSignaturePolicy(2048)); |
| + std::unique_ptr<net::SignatureAlgorithm> signature_algorithm_type = |
| + net::SignatureAlgorithm::CreateRsaPkcs1(net::DigestAlgorithm::Sha256); |
| + if (!VerifySignedData( |
| + *signature_algorithm_type, net::der::Input(&crl.tbs_crl()), |
| + signature_value_bit_string, cert_info.spki_tlv, policy.get())) { |
| + VLOG(2) << "CRL - Signature verification failed."; |
| + return false; |
| + } |
| + return true; |
| +} |
| + |
| +class CastCRLImpl : public CastCRL { |
| + public: |
| + CastCRLImpl(const TbsCrl& tbs_crl); |
| + ~CastCRLImpl() override; |
| + |
| + bool VerifyDeviceCertRevocation(const std::vector<std::string>& certs, |
| + const base::Time::Exploded& time) override; |
| + |
| + private: |
| + uint64_t issuance_time_millis_; |
| + uint64_t validity_period_millis_; |
| + |
| + // Hash of all revoked public key. |
| + std::unordered_set<std::string> revoked_hashes_; |
| + |
| + // Revoked serial number ranges indexed by issuer public key hash. |
| + std::unordered_map<std::string, std::pair<uint64_t, uint64_t>> |
| + revoked_serial_numbers_; |
| + DISALLOW_COPY_AND_ASSIGN(CastCRLImpl); |
| +}; |
| + |
| +CastCRLImpl::CastCRLImpl(const TbsCrl& tbs_crl) { |
| + // Parse the validity information. |
| + issuance_time_millis_ = tbs_crl.issuance_time_millis(); |
| + validity_period_millis_ = tbs_crl.validity_period_millis(); |
| + |
| + // 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 hash = range.issuer_public_key_hash(); |
| + uint64_t first_serial_number = range.first_serial_number(); |
| + uint64_t last_serial_number = range.last_serial_number(); |
| + std::pair<uint64_t, uint64_t> revocation_range(first_serial_number, |
| + last_serial_number); |
| + std::pair<std::string, std::pair<uint64_t, uint64_t>> revocation_entry( |
| + hash, revocation_range); |
| + revoked_serial_numbers_.insert(revocation_entry); |
| + } |
| +} |
| + |
| +CastCRLImpl::~CastCRLImpl() {} |
| + |
| +// Verifies the revocation status of the certificate chain, at the specified |
| +// time. |
| +bool CastCRLImpl::VerifyDeviceCertRevocation( |
|
sheretov
2016/06/13 19:18:35
I would call this checkRevocation
ryanchung
2016/06/21 21:45:05
Done.
|
| + const std::vector<std::string>& certs, |
| + const base::Time::Exploded& time) { |
| + if (certs.empty()) |
| + return false; |
| + |
| + // Check the validity of the CRl at the specified time. |
| + base::Time utc_time = base::Time::FromUTCExploded(time); |
| + base::Time validity_start = |
| + base::Time::UnixEpoch() + |
| + base::TimeDelta::FromMilliseconds(issuance_time_millis_); |
| + base::Time validity_end = |
| + base::Time::UnixEpoch() + |
| + base::TimeDelta::FromMilliseconds(issuance_time_millis_ + |
| + validity_period_millis_); |
| + if (utc_time < validity_start || utc_time > validity_end) { |
|
sheretov
2016/06/13 19:18:35
nit: utc_time >= validity_end
ryanchung
2016/06/21 21:45:06
Done.
|
| + VLOG(2) << "CRL expired. Perform hard fail."; |
| + return false; |
| + } |
| + |
| + // Assume the certs form a valid path. |
| + std::string issuer_key_hash; |
| + for (int i = certs.size() - 1; i >= 0; --i) { |
| + // Check public key revocation. |
| + scoped_refptr<net::ParsedCertificate> parsed_cert = |
| + net::ParsedCertificate::CreateFromCertificateData( |
| + reinterpret_cast<const uint8_t*>(certs[i].data()), certs[i].size(), |
| + net::ParsedCertificate::DataSource::EXTERNAL_REFERENCE); |
| + if (parsed_cert == nullptr) |
| + return false; |
| + ScopedCheckUnreferencedCert ref_checker(parsed_cert.get()); |
| + CastCertificateInfo cert_info; |
| + if (!ParseCertificateInfo(parsed_cert, &cert_info)) |
| + return false; |
| + if (revoked_hashes_.find(cert_info.key_hash) != revoked_hashes_.end()) { |
| + VLOG(2) << "Public key is revoked."; |
| + return false; |
| + } |
| + |
| + // Check serial range revocation. |
| + if (issuer_key_hash.empty()) { |
| + std::string issuer_cert = |
| + FindCastTrustAnchorByName(cert_info.issuer_name); |
|
sheretov
2016/06/13 19:18:35
This seems awkward here. Can we pass in the root's
ryanchung
2016/06/21 21:45:05
I've updated this function to take in the verified
|
| + if (issuer_cert.empty()) |
| + return false; |
| + scoped_refptr<net::ParsedCertificate> parsed_issuer_cert = |
| + net::ParsedCertificate::CreateFromCertificateData( |
| + reinterpret_cast<const uint8_t*>(issuer_cert.data()), |
| + issuer_cert.size(), |
| + net::ParsedCertificate::DataSource::EXTERNAL_REFERENCE); |
| + if (parsed_cert == nullptr) |
| + return false; |
| + ScopedCheckUnreferencedCert ref_checker(parsed_issuer_cert.get()); |
| + CastCertificateInfo issuer_cert_info; |
| + if (!ParseCertificateInfo(parsed_issuer_cert, &issuer_cert_info)) |
| + return false; |
| + if (revoked_hashes_.find(issuer_cert_info.key_hash) != |
|
sheretov
2016/06/13 19:18:35
Why are we checking revocation on the trust anchor
ryanchung
2016/06/21 21:45:06
Removed.
|
| + revoked_hashes_.end()) { |
| + VLOG(2) << "Issuer public key is revoked."; |
| + return false; |
| + } |
| + issuer_key_hash = issuer_cert_info.key_hash; |
| + } |
| + DCHECK(!issuer_key_hash.empty()); |
| + auto issuer_revoked_serials = revoked_serial_numbers_.find(issuer_key_hash); |
| + if (issuer_revoked_serials != revoked_serial_numbers_.end()) { |
| + if (issuer_revoked_serials->second.first <= cert_info.serial_number && |
| + issuer_revoked_serials->second.second >= cert_info.serial_number) { |
| + VLOG(2) << "Serial number is revoked"; |
| + return false; |
| + } |
| + } |
| + issuer_key_hash = cert_info.key_hash; |
| + } |
| + return true; |
| +} |
| + |
| +} // namespace |
| + |
| +std::unique_ptr<CastCRL> ParseCRL(const std::string& crl_proto) { |
| + CrlBundle cast_crl; |
| + if (!cast_crl.ParseFromString(crl_proto)) { |
| + LOG(ERROR) << "CRL - Binary could not be parsed."; |
| + return nullptr; |
| + } |
| + TbsCrl tbs_crl; |
| + Crl supported_crl; |
| + bool supported_supported_crl_found = false; |
|
sheretov
2016/06/13 19:18:35
supported_supported?
ryanchung
2016/06/21 21:45:05
Removed this due to comment below.
|
| + for (auto const& crl : cast_crl.crls()) { |
| + if (!tbs_crl.ParseFromString(crl.tbs_crl())) { |
| + LOG(WARNING) << "Binary TBS CRL could not be parsed."; |
| + continue; |
| + } |
| + // The supported version of the CRL is version 0 |
| + // version 0: Spki Hash Algorithm = SHA-256 |
| + // Signature Algorithm = RSA-PKCS1 V1.5 with SHA-256 |
| + if (tbs_crl.version() == 0) { |
|
sheretov
2016/06/13 19:18:35
Please create a constant for the CRL version, so w
ryanchung
2016/06/21 21:45:06
Done.
|
| + supported_crl = crl; |
|
sheretov
2016/06/13 19:18:35
Is this assignment making a copy? Why have the tb
ryanchung
2016/06/21 21:45:05
Changed this to return in the loop.
|
| + supported_supported_crl_found = true; |
| + break; |
| + } |
| + } |
| + if (!supported_supported_crl_found) { |
| + LOG(ERROR) << "No supported version of revocation data."; |
| + return nullptr; |
| + } |
| + if (!VerifyCRL(supported_crl, tbs_crl)) { |
| + LOG(ERROR) << "CRL - Verification failed."; |
| + return nullptr; |
| + } |
| + |
| + return base::WrapUnique(new CastCRLImpl(tbs_crl)); |
| +} |
| + |
| +bool AddCRLTrustAnchorForTest(const uint8_t* data, size_t length) { |
| + scoped_refptr<net::ParsedCertificate> anchor( |
| + net::ParsedCertificate::CreateFromCertificateData( |
| + data, length, |
| + net::ParsedCertificate::DataSource::EXTERNAL_REFERENCE)); |
| + if (!anchor) |
| + return false; |
| + CastCRLTrustStore::Get().AddTrustedCertificate(std::move(anchor)); |
| + return true; |
| +} |
| + |
| +void ClearCRLTrustAnchorForTest() { |
| + CastCRLTrustStore::Get().Clear(); |
| +} |
| + |
| +} // namespace cast_certificate |