blob: 2459387b045ed48cf05ca8cd2149e0cd0938b773 [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/containers/contains.h"
#include "base/containers/span.h"
#include "base/logging.h"
#include "base/no_destructor.h"
#include "base/strings/string_piece.h"
#include "base/synchronization/lock.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "components/cast_certificate/cast_crl.h"
#include "net/cert/pki/cert_issuer_source_static.h"
#include "net/cert/pki/certificate_policies.h"
#include "net/cert/pki/common_cert_errors.h"
#include "net/cert/pki/parse_certificate.h"
#include "net/cert/pki/parse_name.h"
#include "net/cert/pki/parsed_certificate.h"
#include "net/cert/pki/path_builder.h"
#include "net/cert/pki/simple_path_builder_delegate.h"
#include "net/cert/pki/trust_store_in_memory.h"
#include "net/cert/x509_util.h"
#include "net/der/encode_values.h"
#include "net/der/input.h"
#include "third_party/boringssl/src/include/openssl/bytestring.h"
#include "third_party/boringssl/src/include/openssl/digest.h"
#include "third_party/boringssl/src/include/openssl/evp.h"
// Used specifically when CAST_ALLOW_DEVELOPER_CERTIFICATE is true:
#include "base/command_line.h"
#include "base/memory/weak_ptr.h"
#include "base/path_service.h"
#include "components/cast_certificate/cast_cert_reader.h"
#include "components/cast_certificate/switches.h"
namespace cast_certificate {
namespace {
#define RETURN_STRING_LITERAL(x) \
case x: \
return #x;
// -------------------------------------------------------------------------
// 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"
class CastTrustStore {
public:
using AccessCallback = base::OnceCallback<void(net::TrustStore*)>;
CastTrustStore(const CastTrustStore&) = delete;
CastTrustStore& operator=(const CastTrustStore&) = delete;
static void AccessInstance(AccessCallback callback) {
CastTrustStore* instance = GetInstance();
const base::AutoLock guard(instance->lock_);
std::move(callback).Run(&instance->store_);
}
private:
friend class base::NoDestructor<CastTrustStore>;
static CastTrustStore* GetInstance() {
static base::NoDestructor<CastTrustStore> instance;
return instance.get();
}
CastTrustStore() {
AddAnchor(kCastRootCaDer);
AddAnchor(kEurekaRootCaDer);
// Adding developer certificates must be done off of the IO thread due
// to blocking file access.
#if defined(CAST_ALLOW_DEVELOPER_CERTIFICATE)
base::ThreadPool::PostTask(
FROM_HERE, {base::MayBlock()},
// NOTE: the singleton instance is never destroyed, so we can use
// Unretained here instead of a weak pointer.
base::BindOnce(&CastTrustStore::AddDeveloperCertificates,
base::Unretained(this)));
}
// Check for custom root developer certificate and create a trust store
// from it if present and enabled.
void AddDeveloperCertificates() {
base::AutoLock guard(lock_);
auto* command_line = base::CommandLine::ForCurrentProcess();
std::string cert_path_arg = command_line->GetSwitchValueASCII(
switches::kCastDeveloperCertificatePath);
if (!cert_path_arg.empty()) {
base::FilePath cert_path(cert_path_arg);
if (!cert_path.IsAbsolute()) {
base::FilePath path;
base::PathService::Get(base::DIR_CURRENT, &path);
cert_path = path.Append(cert_path);
}
VLOG(1) << "Using cast developer certificate path " << cert_path;
if (!PopulateStoreWithCertsFromPath(&store_, cert_path)) {
LOG(WARNING) << "No developer certs added to store, only official"
"Google root CA certificates will work.";
}
}
#endif
}
// Adds a trust anchor given a DER-encoded certificate from static
// storage.
template <size_t N>
void AddAnchor(const uint8_t (&data)[N]) {
net::CertErrors errors;
scoped_refptr<net::ParsedCertificate> cert = net::ParsedCertificate::Create(
net::x509_util::CreateCryptoBufferFromStaticDataUnsafe(data), {},
&errors);
CHECK(cert) << errors.ToDebugString();
// Enforce pathlen constraints and policies defined on the root certificate.
base::AutoLock guard(lock_);
store_.AddTrustAnchorWithConstraints(std::move(cert));
}
base::Lock lock_;
net::TrustStoreInMemory store_ GUARDED_BY(lock_);
};
// Returns the OID for the Audio-Only Cast policy
// (1.3.6.1.4.1.11129.2.5.2) 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 delegate will allow signature algorithms of:
//
// * ECDSA, RSA-SSA, and RSA-PSS
// * Supported EC curves: P-256, P-384, P-521.
// * Hashes: All SHA hashes including SHA-1 (despite being known weak).
//
// It will also require RSA keys have a modulus at least 2048-bits long.
class CastPathBuilderDelegate : public net::SimplePathBuilderDelegate {
public:
CastPathBuilderDelegate()
: SimplePathBuilderDelegate(
2048,
SimplePathBuilderDelegate::DigestPolicy::kWeakAllowSha1) {}
};
class CertVerificationContextImpl : public CertVerificationContext {
public:
// Save a copy of the passed in public key and common name (text).
CertVerificationContextImpl(bssl::UniquePtr<EVP_PKEY> key,
base::StringPiece common_name)
: key_(std::move(key)), common_name_(common_name) {}
bool VerifySignatureOverData(
const base::StringPiece& signature,
const base::StringPiece& data,
CastDigestAlgorithm digest_algorithm) const override {
const EVP_MD* digest = nullptr;
switch (digest_algorithm) {
case CastDigestAlgorithm::SHA1:
digest = EVP_sha1();
break;
case CastDigestAlgorithm::SHA256:
digest = EVP_sha256();
break;
};
// Verify with RSASSA-PKCS1-v1_5 and |digest|.
auto signature_bytes = base::as_bytes(base::make_span(signature));
auto data_bytes = base::as_bytes(base::make_span(data));
bssl::ScopedEVP_MD_CTX ctx;
return EVP_PKEY_id(key_.get()) == EVP_PKEY_RSA &&
EVP_DigestVerifyInit(ctx.get(), nullptr, digest, nullptr,
key_.get()) &&
EVP_DigestVerify(ctx.get(), signature_bytes.data(),
signature_bytes.size(), data_bytes.data(),
data_bytes.size());
}
std::string GetCommonName() const override { return common_name_; }
private:
bssl::UniquePtr<EVP_PKEY> key_;
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 (UTF-8, 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::der::Input(net::kTypeCommonNameOid)) {
return atv.ValueAsString(common_name);
}
}
}
return false;
}
// Cast device certificates use the policy 1.3.6.1.4.1.11129.2.5.2 to indicate
// it is *restricted* to an audio-only device whereas the absence of a policy
// means it is unrestricted.
//
// This is somewhat different than RFC 5280's notion of policies, so policies
// are checked separately outside of path building.
//
// See the unit-tests VerifyCastDeviceCertTest.Policies* for some
// concrete examples of how this works.
void DetermineDeviceCertificatePolicy(
const net::CertPathBuilderResultPath* result_path,
CastDeviceCertPolicy* policy) {
// Iterate over all the certificates, including the root certificate. If any
// certificate contains the audio-only policy, the whole chain is considered
// constrained to audio-only device certificates.
//
// Policy mappings are not accounted for. The expectation is that top-level
// intermediates issued with audio-only will have no mappings. If subsequent
// certificates in the chain do, it won't matter as the chain is already
// restricted to being audio-only.
bool audio_only = false;
for (const auto& cert : result_path->certs) {
if (cert->has_policy_oids()) {
const std::vector<net::der::Input>& policies = cert->policy_oids();
if (base::Contains(policies, AudioOnlyPolicyOid())) {
audio_only = true;
break;
}
}
}
*policy = audio_only ? CastDeviceCertPolicy::AUDIO_ONLY
: CastDeviceCertPolicy::NONE;
}
// Checks properties on the target certificate.
//
// * The Key Usage must include Digital Signature
[[nodiscard]] bool CheckTargetCertificate(
const net::ParsedCertificate* cert,
std::unique_ptr<CertVerificationContext>* context) {
// 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 Common Name for the certificate.
std::string common_name;
if (!GetCommonNameFromSubject(cert->tbs().subject_tlv, &common_name))
return false;
// Get the public key for the certificate.
CBS spki;
CBS_init(&spki, cert->tbs().spki_tlv.UnsafeData(),
cert->tbs().spki_tlv.Length());
bssl::UniquePtr<EVP_PKEY> key(EVP_parse_public_key(&spki));
if (!key || CBS_len(&spki) != 0)
return false;
*context = std::make_unique<CertVerificationContextImpl>(std::move(key),
common_name);
return true;
}
// 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;
}
// Returns the CastCertError for the failed path building.
// This function must only be called if path building failed.
CastCertError MapToCastError(const net::CertPathBuilder::Result& result) {
DCHECK(!result.HasValidPath());
if (result.paths.empty())
return CastCertError::ERR_CERTS_VERIFY_GENERIC;
const net::CertPathErrors& path_errors =
result.paths.at(result.best_result_index)->errors;
if (path_errors.ContainsError(net::cert_errors::kValidityFailedNotAfter) ||
path_errors.ContainsError(net::cert_errors::kValidityFailedNotBefore)) {
return CastCertError::ERR_CERTS_DATE_INVALID;
}
return CastCertError::ERR_CERTS_VERIFY_GENERIC;
}
} // namespace
CastCertError VerifyDeviceCert(
const std::vector<std::string>& certs,
const base::Time& time,
std::unique_ptr<CertVerificationContext>* context,
CastDeviceCertPolicy* policy,
const CastCRL* crl,
CRLPolicy crl_policy) {
CastCertError verification_result;
CastTrustStore::AccessInstance(base::BindOnce(
[](const std::vector<std::string>& certs, const base::Time& time,
std::unique_ptr<CertVerificationContext>* context,
CastDeviceCertPolicy* policy, const CastCRL* crl, CRLPolicy crl_policy,
CastCertError* result, net::TrustStore* store) {
*result = VerifyDeviceCertUsingCustomTrustStore(
certs, time, context, policy, crl, crl_policy, store);
},
certs, time, context, policy, crl, crl_policy, &verification_result));
return verification_result;
}
CastCertError VerifyDeviceCertUsingCustomTrustStore(
const std::vector<std::string>& certs,
const base::Time& time,
std::unique_ptr<CertVerificationContext>* context,
CastDeviceCertPolicy* policy,
const CastCRL* crl,
CRLPolicy crl_policy,
net::TrustStore* trust_store) {
if (!trust_store)
return VerifyDeviceCert(certs, time, context, policy, crl, crl_policy);
if (certs.empty())
return CastCertError::ERR_CERTS_MISSING;
// Fail early if CRL is required but not provided.
if (!crl && crl_policy == CRLPolicy::CRL_REQUIRED)
return CastCertError::ERR_CRL_INVALID;
net::CertErrors errors;
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(net::ParsedCertificate::Create(
net::x509_util::CreateCryptoBuffer(certs[i]), GetCertParsingOptions(),
&errors));
if (!cert)
return CastCertError::ERR_CERTS_PARSE;
if (i == 0)
target_cert = std::move(cert);
else
intermediate_cert_issuer_source.AddCert(std::move(cert));
}
CastPathBuilderDelegate path_builder_delegate;
// Do path building and RFC 5280 compatible certificate verification using the
// two Cast trust anchors and Cast signature policy.
net::der::GeneralizedTime verification_time;
if (!net::der::EncodeTimeAsGeneralizedTime(time, &verification_time))
return CastCertError::ERR_UNEXPECTED;
net::CertPathBuilder path_builder(
target_cert.get(), trust_store, &path_builder_delegate, verification_time,
net::KeyPurpose::CLIENT_AUTH, net::InitialExplicitPolicy::kFalse,
{net::der::Input(net::kAnyPolicyOid)},
net::InitialPolicyMappingInhibit::kFalse,
net::InitialAnyPolicyInhibit::kFalse);
path_builder.AddCertIssuerSource(&intermediate_cert_issuer_source);
net::CertPathBuilder::Result result = path_builder.Run();
if (!result.HasValidPath())
return MapToCastError(result);
// Determine whether this device certificate is restricted to audio-only.
DetermineDeviceCertificatePolicy(result.GetBestValidPath(), policy);
// Check properties of the leaf certificate not already verified by path
// building (key usage), and construct a CertVerificationContext that uses
// its public key.
if (!CheckTargetCertificate(target_cert.get(), context))
return CastCertError::ERR_CERTS_RESTRICTIONS;
// Check for revocation.
if (crl && !crl->CheckRevocation(result.GetBestValidPath()->certs, time))
return CastCertError::ERR_CERTS_REVOKED;
return CastCertError::OK;
}
std::string CastCertErrorToString(CastCertError error) {
switch (error) {
RETURN_STRING_LITERAL(CastCertError::ERR_CERTS_MISSING);
RETURN_STRING_LITERAL(CastCertError::ERR_CERTS_PARSE);
RETURN_STRING_LITERAL(CastCertError::ERR_CERTS_DATE_INVALID);
RETURN_STRING_LITERAL(CastCertError::ERR_CERTS_VERIFY_GENERIC);
RETURN_STRING_LITERAL(CastCertError::ERR_CERTS_RESTRICTIONS);
RETURN_STRING_LITERAL(CastCertError::ERR_CRL_INVALID);
RETURN_STRING_LITERAL(CastCertError::ERR_CERTS_REVOKED);
RETURN_STRING_LITERAL(CastCertError::ERR_UNEXPECTED);
RETURN_STRING_LITERAL(CastCertError::OK);
}
return "CastCertError::UNKNOWN";
}
} // namespace cast_certificate