blob: 715b50457ddfe53a71897f71ae7588ff4eddeee0 [file] [log] [blame]
// Copyright 2022 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/net_trust_store.h"
#include "base/check.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "components/cast_certificate/net_parsed_certificate.h"
#include "net/cert/pem.h"
#include "net/cert/pki/cert_issuer_source_static.h"
#include "net/cert/pki/common_cert_errors.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/x509_util.h"
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"
// -------------------------------------------------------------------------
// 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"
} // namespace
namespace openscreen::cast {
// static
std::unique_ptr<openscreen::cast::TrustStore> TrustStore::CreateInstanceForTest(
const std::vector<uint8_t>& trust_anchor_der) {
// TODO(issuetracker.google.com/222145200): We need to allow linking this
// implementation into `openscreen_unittests` when in the chromium waterfall.
auto result = std::make_unique<cast_certificate::NetTrustStore>();
result->AddAnchor(trust_anchor_der);
return result;
}
// static
std::unique_ptr<openscreen::cast::TrustStore>
TrustStore::CreateInstanceFromPemFile(absl::string_view file_path) {
std::string pem_data;
CHECK(base::ReadFileToString(base::FilePath::FromASCII(base::StringPiece(
file_path.data(), file_path.size())),
&pem_data));
net::PEMTokenizer tokenizer(pem_data, {std::string("CERTIFICATE")});
auto result = std::make_unique<cast_certificate::NetTrustStore>();
while (tokenizer.GetNext()) {
const std::string& data = tokenizer.data();
auto* data_ptr = reinterpret_cast<const uint8_t*>(data.data());
result->AddAnchor(
base::span<const uint8_t>(data_ptr, data_ptr + data.size()));
}
return result;
}
// static
std::unique_ptr<openscreen::cast::TrustStore> CastTrustStore::Create() {
auto cast_trust_store = std::make_unique<::cast_certificate::NetTrustStore>();
cast_trust_store->AddAnchor(kCastRootCaDer);
cast_trust_store->AddAnchor(kEurekaRootCaDer);
return cast_trust_store;
}
// static
std::unique_ptr<openscreen::cast::TrustStore> CastCRLTrustStore::Create() {
auto crl_trust_store = std::make_unique<::cast_certificate::NetTrustStore>();
crl_trust_store->AddAnchor(kCastCRLRootCaDer);
return crl_trust_store;
}
} // namespace openscreen::cast
namespace cast_certificate {
namespace {
// 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) {}
};
// Returns the CastCertError for the failed path building.
// This function must only be called if path building failed.
openscreen::Error::Code MapToCastError(
const net::CertPathBuilder::Result& result) {
DCHECK(!result.HasValidPath());
if (result.paths.empty()) {
return openscreen::Error::Code::kErrCertsVerifyGeneric;
}
if (!result.GetBestPathPossiblyInvalid()->GetTrustedCert()) {
return openscreen::Error::Code::kErrCertsVerifyUntrustedCert;
}
// TODO(issuetracker.google.com/222145200): Here and elsewhere, we would like
// to provide better error messages for logs and feedback reports. For
// example, collecting the certificate validity dates for a date error.
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 openscreen::Error::Code::kErrCertsDateInvalid;
}
if (path_errors.ContainsError(net::cert_errors::kMaxPathLengthViolated)) {
return openscreen::Error::Code::kErrCertsPathlen;
}
return openscreen::Error::Code::kErrCertsVerifyGeneric;
}
} // namespace
NetTrustStore::NetTrustStore() = default;
NetTrustStore::~NetTrustStore() = default;
void NetTrustStore::AddAnchor(base::span<const uint8_t> data) {
net::CertErrors errors;
scoped_refptr<net::ParsedCertificate> cert = net::ParsedCertificate::Create(
net::x509_util::CreateCryptoBuffer(data), {}, &errors);
CHECK(cert) << errors.ToDebugString();
// Enforce pathlen constraints and policies defined on the root certificate.
store_.AddTrustAnchorWithConstraints(std::move(cert));
}
openscreen::ErrorOr<NetTrustStore::CertificatePathResult>
NetTrustStore::FindCertificatePath(const std::vector<std::string>& der_certs,
const openscreen::cast::DateTime& time) {
scoped_refptr<net::ParsedCertificate> leaf_cert;
net::CertIssuerSourceStatic intermediate_cert_issuer_source;
for (const std::string& der_cert : der_certs) {
scoped_refptr<net::ParsedCertificate> cert(net::ParsedCertificate::Create(
net::x509_util::CreateCryptoBuffer(der_cert), GetCertParsingOptions(),
nullptr));
if (!cert) {
return openscreen::Error::Code::kErrCertsParse;
}
if (!leaf_cert) {
leaf_cert = std::move(cert);
} else {
intermediate_cert_issuer_source.AddCert(std::move(cert));
}
}
net::der::GeneralizedTime verification_time;
verification_time.year = time.year;
verification_time.month = time.month;
verification_time.day = time.day;
verification_time.hours = time.hour;
verification_time.minutes = time.minute;
verification_time.seconds = time.second;
// Do path building and RFC 5280 compatible certificate verification using the
// two Cast trust anchors and Cast signature policy.
CastPathBuilderDelegate path_builder_delegate;
net::CertPathBuilder path_builder(
leaf_cert, &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);
}
const net::CertPathBuilderResultPath* path = result.GetBestValidPath();
// Check that the leaf is valid as a _device_ certificate, which is not
// checked by path building.
if (!leaf_cert->has_key_usage() ||
!leaf_cert->key_usage().AssertsBit(
net::KEY_USAGE_BIT_DIGITAL_SIGNATURE)) {
return openscreen::Error::Code::kErrCertsRestrictions;
}
CertificatePathResult out_path;
out_path.reserve(path->certs.size());
for (const auto& cert : path->certs) {
out_path.push_back(std::make_unique<NetParsedCertificate>(cert));
}
return out_path;
}
} // namespace cast_certificate