blob: ec1fae2ee850d8a9b230ef0bb2cfc0b4d7debc6b [file] [log] [blame]
// Copyright 2014 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_cert_validator.h"
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <memory>
#include <utility>
#include "base/memory/ptr_util.h"
#include "base/memory/singleton.h"
#include "net/cert/internal/cert_issuer_source_static.h"
#include "net/cert/internal/certificate_policies.h"
#include "net/cert/internal/extended_key_usage.h"
#include "net/cert/internal/parse_certificate.h"
#include "net/cert/internal/parse_name.h"
#include "net/cert/internal/parsed_certificate.h"
#include "net/cert/internal/path_builder.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_signed_data.h"
#include "net/der/input.h"
namespace cast_certificate {
namespace {
// -------------------------------------------------------------------------
// Cast trust anchors.
// -------------------------------------------------------------------------
// There are two trusted roots for Cast certificate chains:
// (1) CN=Cast Root CA (kCastRootCaDer)
// (2) CN=Eureka Root CA (kEurekaRootCaDer)
// These constants are defined by the files included next:
#include "components/cast_certificate/cast_root_ca_cert_der-inc.h"
#include "components/cast_certificate/eureka_root_ca_der-inc.h"
// Singleton for the Cast trust store.
class CastTrustStore {
static CastTrustStore* GetInstance() {
return base::Singleton<CastTrustStore,
static net::TrustStore& Get() { return GetInstance()->store_; }
friend struct base::DefaultSingletonTraits<CastTrustStore>;
CastTrustStore() {
// Adds a trust anchor given a DER-encoded certificate from static
// storage.
template <size_t N>
void AddAnchor(const uint8_t (&data)[N]) {
scoped_refptr<net::ParsedCertificate> root =
data, N, net::ParsedCertificate::DataSource::EXTERNAL_REFERENCE,
net::TrustStore store_;
using ExtensionsMap = std::map<net::der::Input, net::ParsedExtension>;
// Helper that looks up an extension by OID given a map of extensions.
bool GetExtensionValue(const ExtensionsMap& extensions,
const net::der::Input& oid,
net::der::Input* value) {
auto it = extensions.find(oid);
if (it == extensions.end())
return false;
*value = it->second.value;
return true;
// Returns the OID for the Audio-Only Cast policy
// ( in DER form.
net::der::Input AudioOnlyPolicyOid() {
static const uint8_t kAudioOnlyPolicy[] = {0x2B, 0x06, 0x01, 0x04, 0x01,
0xD6, 0x79, 0x02, 0x05, 0x02};
return net::der::Input(kAudioOnlyPolicy);
// Cast certificates rely on RSASSA-PKCS#1 v1.5 with SHA-1 for signatures.
// The following signature policy specifies which signature algorithms (and key
// sizes) are acceptable. It is used when verifying a chain of certificates, as
// well as when verifying digital signature using the target certificate's
// SPKI.
// This particular policy allows for:
// * Supported EC curves: P-256, P-384, P-521.
// * Hashes: All SHA hashes including SHA-1 (despite being known weak).
// * RSA keys must have a modulus at least 2048-bits long.
std::unique_ptr<net::SignaturePolicy> CreateCastSignaturePolicy() {
return base::WrapUnique(new net::SimpleSignaturePolicy(2048));
class CertVerificationContextImpl : public CertVerificationContext {
// Save a copy of the passed in public key (DER) and common name (text).
CertVerificationContextImpl(const net::der::Input& spki,
const base::StringPiece& common_name)
: spki_(spki.AsString()), common_name_(common_name.as_string()) {}
bool VerifySignatureOverData(const base::StringPiece& signature,
const base::StringPiece& data) const override {
// This code assumes the signature algorithm was RSASSA PKCS#1 v1.5 with
// SHA-1.
// TODO(eroman): Is it possible to use other hash algorithms?
auto signature_algorithm =
// Use the same policy as was used for verifying signatures in
// certificates. This will ensure for instance that the key used is at
// least 2048-bits long.
auto signature_policy = CreateCastSignaturePolicy();
return net::VerifySignedData(
*signature_algorithm, net::der::Input(data),
net::der::BitString(net::der::Input(signature), 0),
net::der::Input(&spki_), signature_policy.get());
std::string GetCommonName() const override { return common_name_; }
std::string spki_;
std::string common_name_;
// Helper that extracts the Common Name from a certificate's subject field. On
// success |common_name| contains the text for the attribute (unescaped, so
// will depend on the encoding used, but for Cast device certs it should
// be ASCII).
bool GetCommonNameFromSubject(const net::der::Input& subject_tlv,
std::string* common_name) {
net::RDNSequence rdn_sequence;
if (!net::ParseName(subject_tlv, &rdn_sequence))
return false;
for (const net::RelativeDistinguishedName& rdn : rdn_sequence) {
for (const auto& atv : rdn) {
if (atv.type == net::TypeCommonNameOid()) {
return atv.ValueAsStringUnsafe(common_name);
return false;
// Returns true if the extended key usage list |ekus| contains client auth.
bool HasClientAuth(const std::vector<net::der::Input>& ekus) {
for (const auto& oid : ekus) {
if (oid == net::ClientAuth())
return true;
return false;
// Checks properties on the target certificate.
// * The Key Usage must include Digital Signature
// * The Extended Key Usage must include TLS Client Auth
// * May have the policy to indicate it
// is an audio-only device.
WARN_UNUSED_RESULT bool CheckTargetCertificate(
const net::ParsedCertificate* cert,
std::unique_ptr<CertVerificationContext>* context,
CastDeviceCertPolicy* policy) {
// Get the Key Usage extension.
if (!cert->has_key_usage())
return false;
// Ensure Key Usage contains digitalSignature.
if (!cert->key_usage().AssertsBit(net::KEY_USAGE_BIT_DIGITAL_SIGNATURE))
return false;
// Get the Extended Key Usage extension.
net::der::Input extension_value;
if (!GetExtensionValue(cert->unparsed_extensions(), net::ExtKeyUsageOid(),
&extension_value)) {
return false;
std::vector<net::der::Input> ekus;
if (!net::ParseEKUExtension(extension_value, &ekus))
return false;
// Ensure Extended Key Usage contains client auth.
if (!HasClientAuth(ekus))
return false;
// Check for an optional audio-only policy extension.
*policy = CastDeviceCertPolicy::NONE;
if (GetExtensionValue(cert->unparsed_extensions(),
net::CertificatePoliciesOid(), &extension_value)) {
std::vector<net::der::Input> policies;
if (!net::ParseCertificatePoliciesExtension(extension_value, &policies))
return false;
// Look for an audio-only policy. Disregard any other policy found.
if (std::find(policies.begin(), policies.end(), AudioOnlyPolicyOid()) !=
policies.end()) {
*policy = CastDeviceCertPolicy::AUDIO_ONLY;
// Get the Common Name for the certificate.
std::string common_name;
if (!GetCommonNameFromSubject(cert->tbs().subject_tlv, &common_name))
return false;
new CertVerificationContextImpl(cert->tbs().spki_tlv, common_name));
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; = exploded.day_of_month;
result.hours = exploded.hour;
result.minutes = exploded.minute;
result.seconds = exploded.second;
return result;
// Returns the parsing options used for Cast certificates.
net::ParseCertificateOptions GetCertParsingOptions() {
net::ParseCertificateOptions options;
// Some cast intermediate certificates contain serial numbers that are
// 21 octets long, and might also not use valid DER encoding for an
// INTEGER (non-minimal encoding).
// Allow these sorts of serial numbers.
// TODO(eroman): At some point in the future this workaround will no longer be
// necessary. Should revisit this for removal in 2017 if not earlier.
options.allow_invalid_serial_numbers = true;
return options;
} // namespace
bool VerifyDeviceCert(const std::vector<std::string>& certs,
const base::Time::Exploded& time,
std::unique_ptr<CertVerificationContext>* context,
CastDeviceCertPolicy* policy) {
if (certs.empty())
return false;
// No reference to these ParsedCertificates is kept past the end of this
// function, so using EXTERNAL_REFERENCE here is safe.
scoped_refptr<net::ParsedCertificate> target_cert;
net::CertIssuerSourceStatic intermediate_cert_issuer_source;
for (size_t i = 0; i < certs.size(); ++i) {
scoped_refptr<net::ParsedCertificate> cert(
reinterpret_cast<const uint8_t*>(certs[i].data()), certs[i].size(),
if (!cert)
return false;
if (i == 0)
target_cert = std::move(cert);
// Use a signature policy compatible with Cast's PKI.
auto signature_policy = CreateCastSignaturePolicy();
// Do path building and RFC 5280 compatible certificate verification using the
// two Cast trust anchors and Cast signature policy.
net::CertPathBuilder::Result result;
net::CertPathBuilder path_builder(target_cert.get(), &CastTrustStore::Get(),
ConvertExplodedTime(time), &result);
net::CompletionStatus rv = path_builder.Run(base::Closure());
DCHECK_EQ(rv, net::CompletionStatus::SYNC);
if (!result.is_success())
return false;
// Check properties of the leaf certificate (key usage, policy), and construct
// a CertVerificationContext that uses its public key.
return CheckTargetCertificate(target_cert.get(), context, policy);
std::unique_ptr<CertVerificationContext> CertVerificationContextImplForTest(
const base::StringPiece& spki) {
// Use a bogus CommonName, since this is just exposed for testing signature
// verification by unittests.
return base::WrapUnique(
new CertVerificationContextImpl(net::der::Input(spki), "CommonName"));
bool AddTrustAnchorForTest(const uint8_t* data, size_t length) {
scoped_refptr<net::ParsedCertificate> anchor(
data, length, net::ParsedCertificate::DataSource::EXTERNAL_REFERENCE,
if (!anchor)
return false;
return true;
} // namespace cast_certificate