aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--BUILD.gn1
-rw-r--r--cast/common/certificate/BUILD.gn15
-rw-r--r--cast/common/certificate/cast_cert_validator.cc573
-rw-r--r--cast/common/certificate/cast_cert_validator.h30
-rw-r--r--cast/common/certificate/cast_cert_validator_internal.cc571
-rw-r--r--cast/common/certificate/cast_cert_validator_internal.h36
-rw-r--r--cast/common/certificate/cast_cert_validator_unittest.cc36
-rw-r--r--cast/common/certificate/cast_crl.cc275
-rw-r--r--cast/common/certificate/cast_crl.h87
-rw-r--r--cast/common/certificate/cast_crl_root_ca_cert_der-inc.h155
-rw-r--r--cast/common/certificate/cast_crl_unittest.cc230
-rw-r--r--cast/common/certificate/proto/BUILD.gn3
-rw-r--r--cast/common/certificate/proto/revocation.proto2
-rw-r--r--cast/common/certificate/proto/test_suite.proto2
-rw-r--r--cast/common/certificate/test_helpers.cc45
-rw-r--r--cast/common/certificate/test_helpers.h22
-rw-r--r--cast/common/certificate/types.h30
-rw-r--r--test/data/cast/common/certificate/certificates/cast_crl_test_root_ca.pem84
-rw-r--r--test/data/cast/common/certificate/certificates/cast_test_root_ca.pem83
-rw-r--r--test/data/cast/common/certificate/testsuite/testsuite1.pbbin0 -> 83466 bytes
-rw-r--r--third_party/protobuf/BUILD.gn2
21 files changed, 1683 insertions, 599 deletions
diff --git a/BUILD.gn b/BUILD.gn
index 68d5b262..12bc3883 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -10,7 +10,6 @@ import("osp/build/config/services.gni")
group("gn_all") {
deps = [
"cast/common:mdns",
- "cast/common/certificate/proto",
"osp",
"osp/msgs",
"platform",
diff --git a/cast/common/certificate/BUILD.gn b/cast/common/certificate/BUILD.gn
index fab645ce..46449794 100644
--- a/cast/common/certificate/BUILD.gn
+++ b/cast/common/certificate/BUILD.gn
@@ -6,22 +6,37 @@ source_set("certificate") {
sources = [
"cast_cert_validator.cc",
"cast_cert_validator.h",
+ "cast_cert_validator_internal.cc",
"cast_cert_validator_internal.h",
+ "cast_crl.cc",
+ "cast_crl.h",
]
public_deps = [
"../../../third_party/boringssl",
]
+
+ deps = [
+ "../../../platform",
+ "../../../third_party/abseil",
+ "../../../util",
+ "proto",
+ ]
}
source_set("unittests") {
testonly = true
sources = [
"cast_cert_validator_unittest.cc",
+ "cast_crl_unittest.cc",
+ "test_helpers.cc",
+ "test_helpers.h",
]
deps = [
":certificate",
+ "../../../platform",
"../../../third_party/boringssl",
"../../../third_party/googletest:gtest",
+ "proto:unittest_proto",
]
}
diff --git a/cast/common/certificate/cast_cert_validator.cc b/cast/common/certificate/cast_cert_validator.cc
index b5e30d37..fb7217ef 100644
--- a/cast/common/certificate/cast_cert_validator.cc
+++ b/cast/common/certificate/cast_cert_validator.cc
@@ -16,6 +16,7 @@
#include <utility>
#include "cast/common/certificate/cast_cert_validator_internal.h"
+#include "cast/common/certificate/cast_crl.h"
namespace cast {
namespace certificate {
@@ -37,53 +38,6 @@ using CastCertError = openscreen::Error::Code;
#include "cast/common/certificate/cast_root_ca_cert_der-inc.h"
#include "cast/common/certificate/eureka_root_ca_der-inc.h"
-constexpr static int32_t kMinRsaModulusLengthBits = 2048;
-
-// Adds a trust anchor given a DER-encoded certificate from static
-// storage.
-template <size_t N>
-bssl::UniquePtr<X509> MakeTrustAnchor(const uint8_t (&data)[N]) {
- const uint8_t* dptr = data;
- return bssl::UniquePtr<X509>{d2i_X509(nullptr, &dptr, N)};
-}
-
-// Stores intermediate state while attempting to find a valid certificate chain
-// from a set of trusted certificates to a target certificate. Together, a
-// sequence of these forms a certificate chain to be verified as well as a stack
-// that can be unwound for searching more potential paths.
-struct CertPathStep {
- X509* cert;
-
- // The next index that can be checked in |trust_store| if the choice |cert| on
- // the path needs to be reverted.
- uint32_t trust_store_index;
-
- // The next index that can be checked in |intermediate_certs| if the choice
- // |cert| on the path needs to be reverted.
- uint32_t intermediate_cert_index;
-};
-
-// These values are bit positions from RFC 5280 4.2.1.3 and will be passed to
-// ASN1_BIT_STRING_get_bit.
-enum KeyUsageBits {
- kDigitalSignature = 0,
- kKeyCertSign = 5,
-};
-
-bool VerifySignedData(const EVP_MD* digest,
- EVP_PKEY* public_key,
- const ConstDataSpan& data,
- const ConstDataSpan& signature) {
- // This code assumes the signature algorithm was RSASSA PKCS#1 v1.5 with
- // |digest|.
- bssl::ScopedEVP_MD_CTX ctx;
- if (!EVP_DigestVerifyInit(ctx.get(), nullptr, digest, nullptr, public_key)) {
- return false;
- }
- return (EVP_DigestVerify(ctx.get(), signature.data, signature.length,
- data.data, data.length) == 1);
-}
-
// Returns the OID for the Audio-Only Cast policy
// (1.3.6.1.4.1.11129.2.5.2) in DER form.
const ConstDataSpan& AudioOnlyPolicyOid() {
@@ -133,343 +87,7 @@ class CertVerificationContextImpl final : public CertVerificationContext {
const std::string common_name_;
};
-bool CertInPath(X509_NAME* name,
- const std::vector<CertPathStep>& steps,
- uint32_t start,
- uint32_t stop) {
- for (uint32_t i = start; i < stop; ++i) {
- if (X509_NAME_cmp(name, X509_get_subject_name(steps[i].cert)) == 0) {
- return true;
- }
- }
- return false;
-}
-
-uint8_t ParseAsn1TimeDoubleDigit(ASN1_GENERALIZEDTIME* time, int index) {
- return (time->data[index] - '0') * 10 + (time->data[index + 1] - '0');
-}
-
-// Parses DateTime with additional restrictions laid out by RFC 5280
-// 4.1.2.5.2.
-bool ParseAsn1GeneralizedTime(ASN1_GENERALIZEDTIME* time, DateTime* out) {
- static constexpr uint8_t kDaysPerMonth[] = {
- 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
- };
-
- if (time->length != 15) {
- return false;
- }
- if (time->data[14] != 'Z') {
- return false;
- }
- for (int i = 0; i < 14; ++i) {
- if (time->data[i] < '0' || time->data[i] > '9') {
- return false;
- }
- }
- out->year = ParseAsn1TimeDoubleDigit(time, 0) * 100 +
- ParseAsn1TimeDoubleDigit(time, 2);
- out->month = ParseAsn1TimeDoubleDigit(time, 4);
- out->day = ParseAsn1TimeDoubleDigit(time, 6);
- out->hour = ParseAsn1TimeDoubleDigit(time, 8);
- out->minute = ParseAsn1TimeDoubleDigit(time, 10);
- out->second = ParseAsn1TimeDoubleDigit(time, 12);
- if (out->month == 0 || out->month > 12) {
- return false;
- }
- int days_per_month = kDaysPerMonth[out->month - 1];
- if (out->month == 2) {
- if (out->year % 4 == 0 && (out->year % 100 != 0 || out->year % 400 == 0)) {
- days_per_month = 29;
- } else {
- days_per_month = 28;
- }
- }
- if (out->day == 0 || out->day > days_per_month) {
- return false;
- }
- if (out->hour > 23) {
- return false;
- }
- if (out->minute > 59) {
- return false;
- }
- // Leap seconds are allowed.
- if (out->second > 60) {
- return false;
- }
- return true;
-}
-
-bool IsDateTimeBefore(const DateTime& a, const DateTime& b) {
- if (a.year < b.year) {
- return true;
- } else if (a.year > b.year) {
- return false;
- }
- if (a.month < b.month) {
- return true;
- } else if (a.month > b.month) {
- return false;
- }
- if (a.day < b.day) {
- return true;
- } else if (a.day > b.day) {
- return false;
- }
- if (a.hour < b.hour) {
- return true;
- } else if (a.hour > b.hour) {
- return false;
- }
- if (a.minute < b.minute) {
- return true;
- } else if (a.minute > b.minute) {
- return false;
- }
- if (a.second < b.second) {
- return true;
- } else if (a.second > b.second) {
- return false;
- }
- return false;
-}
-
-CastCertError VerifyCertTime(X509* cert, const DateTime& time) {
- ASN1_GENERALIZEDTIME* not_before_asn1 = ASN1_TIME_to_generalizedtime(
- cert->cert_info->validity->notBefore, nullptr);
- ASN1_GENERALIZEDTIME* not_after_asn1 = ASN1_TIME_to_generalizedtime(
- cert->cert_info->validity->notAfter, nullptr);
- if (!not_before_asn1 || !not_after_asn1) {
- return CastCertError::kErrCertsVerifyGeneric;
- }
- DateTime not_before;
- DateTime not_after;
- bool times_valid = ParseAsn1GeneralizedTime(not_before_asn1, &not_before) &&
- ParseAsn1GeneralizedTime(not_after_asn1, &not_after);
- ASN1_GENERALIZEDTIME_free(not_before_asn1);
- ASN1_GENERALIZEDTIME_free(not_after_asn1);
- if (!times_valid) {
- return CastCertError::kErrCertsVerifyGeneric;
- }
- if (IsDateTimeBefore(time, not_before) || IsDateTimeBefore(not_after, time)) {
- return CastCertError::kErrCertsDateInvalid;
- }
- return CastCertError::kNone;
-}
-
-bool VerifyPublicKeyLength(EVP_PKEY* public_key) {
- return EVP_PKEY_bits(public_key) >= kMinRsaModulusLengthBits;
-}
-
-bssl::UniquePtr<ASN1_BIT_STRING> GetKeyUsage(X509* cert) {
- int pos = X509_get_ext_by_NID(cert, NID_key_usage, -1);
- if (pos == -1) {
- return nullptr;
- }
- X509_EXTENSION* key_usage = X509_get_ext(cert, pos);
- const uint8_t* value = key_usage->value->data;
- ASN1_BIT_STRING* key_usage_bit_string = nullptr;
- if (!d2i_ASN1_BIT_STRING(&key_usage_bit_string, &value,
- key_usage->value->length)) {
- return nullptr;
- }
- return bssl::UniquePtr<ASN1_BIT_STRING>{key_usage_bit_string};
-}
-
-CastCertError VerifyCertificateChain(const std::vector<CertPathStep>& path,
- uint32_t step_index,
- const DateTime& time) {
- // Default max path length is the number of intermediate certificates.
- int max_pathlen = path.size() - 2;
-
- std::vector<NAME_CONSTRAINTS*> path_name_constraints;
- CastCertError error = CastCertError::kNone;
- uint32_t i = step_index;
- for (; i < path.size() - 1; ++i) {
- X509* subject = path[i + 1].cert;
- X509* issuer = path[i].cert;
- bool is_root = (i == step_index);
- if (!is_root) {
- if ((error = VerifyCertTime(issuer, time)) != CastCertError::kNone) {
- return error;
- }
- if (X509_NAME_cmp(X509_get_subject_name(issuer),
- X509_get_issuer_name(issuer)) != 0) {
- if (max_pathlen == 0) {
- return CastCertError::kErrCertsPathlen;
- }
- --max_pathlen;
- } else {
- issuer->ex_flags |= EXFLAG_SI;
- }
- } else {
- issuer->ex_flags |= EXFLAG_SI;
- }
-
- bssl::UniquePtr<ASN1_BIT_STRING> key_usage = GetKeyUsage(issuer);
- if (key_usage) {
- const int bit =
- ASN1_BIT_STRING_get_bit(key_usage.get(), KeyUsageBits::kKeyCertSign);
- if (bit == 0) {
- return CastCertError::kErrCertsVerifyGeneric;
- }
- }
-
- // Check that basicConstraints is present, specifies the CA bit, and use
- // pathLenConstraint if present.
- const int basic_constraints_index =
- X509_get_ext_by_NID(issuer, NID_basic_constraints, -1);
- if (basic_constraints_index == -1) {
- return CastCertError::kErrCertsVerifyGeneric;
- }
- X509_EXTENSION* const basic_constraints_extension =
- X509_get_ext(issuer, basic_constraints_index);
- bssl::UniquePtr<BASIC_CONSTRAINTS> basic_constraints{
- reinterpret_cast<BASIC_CONSTRAINTS*>(
- X509V3_EXT_d2i(basic_constraints_extension))};
-
- if (!basic_constraints || !basic_constraints->ca) {
- return CastCertError::kErrCertsVerifyGeneric;
- }
-
- if (basic_constraints->pathlen) {
- if (basic_constraints->pathlen->length != 1) {
- return CastCertError::kErrCertsVerifyGeneric;
- } else {
- const int pathlen = *basic_constraints->pathlen->data;
- if (pathlen < 0) {
- return CastCertError::kErrCertsVerifyGeneric;
- }
- if (pathlen < max_pathlen) {
- max_pathlen = pathlen;
- }
- }
- }
-
- if (X509_ALGOR_cmp(issuer->sig_alg, issuer->cert_info->signature) != 0) {
- return CastCertError::kErrCertsVerifyGeneric;
- }
-
- bssl::UniquePtr<EVP_PKEY> public_key{X509_get_pubkey(issuer)};
- if (!VerifyPublicKeyLength(public_key.get())) {
- return CastCertError::kErrCertsVerifyGeneric;
- }
-
- // NOTE: (!self-issued || target) -> verify name constraints. Target case
- // is after the loop.
- const bool is_self_issued = issuer->ex_flags & EXFLAG_SI;
- if (!is_self_issued) {
- for (NAME_CONSTRAINTS* name_constraints : path_name_constraints) {
- if (NAME_CONSTRAINTS_check(subject, name_constraints) != X509_V_OK) {
- return CastCertError::kErrCertsVerifyGeneric;
- }
- }
- }
-
- if (issuer->nc) {
- path_name_constraints.push_back(issuer->nc);
- } else {
- const int index = X509_get_ext_by_NID(issuer, NID_name_constraints, -1);
- if (index != -1) {
- X509_EXTENSION* ext = X509_get_ext(issuer, index);
- auto* nc = reinterpret_cast<NAME_CONSTRAINTS*>(X509V3_EXT_d2i(ext));
- if (nc) {
- issuer->nc = nc;
- path_name_constraints.push_back(nc);
- } else {
- return CastCertError::kErrCertsVerifyGeneric;
- }
- }
- }
-
- // Check that any policy mappings present are _not_ the anyPolicy OID. Even
- // though we don't otherwise handle policies, this is required by RFC 5280
- // 6.1.4(a).
- const int policy_mappings_index =
- X509_get_ext_by_NID(issuer, NID_policy_mappings, -1);
- if (policy_mappings_index != -1) {
- X509_EXTENSION* policy_mappings_extension =
- X509_get_ext(issuer, policy_mappings_index);
- auto* policy_mappings = reinterpret_cast<POLICY_MAPPINGS*>(
- X509V3_EXT_d2i(policy_mappings_extension));
- const uint32_t policy_mapping_count =
- sk_POLICY_MAPPING_num(policy_mappings);
- const ASN1_OBJECT* any_policy = OBJ_nid2obj(NID_any_policy);
- for (uint32_t i = 0; i < policy_mapping_count; ++i) {
- POLICY_MAPPING* policy_mapping =
- sk_POLICY_MAPPING_value(policy_mappings, i);
- const bool either_matches =
- ((OBJ_cmp(policy_mapping->issuerDomainPolicy, any_policy) == 0) ||
- (OBJ_cmp(policy_mapping->subjectDomainPolicy, any_policy) == 0));
- if (either_matches) {
- error = CastCertError::kErrCertsVerifyGeneric;
- break;
- }
- }
- sk_POLICY_MAPPING_free(policy_mappings);
- if (error != CastCertError::kNone) {
- return error;
- }
- }
-
- // Check that we don't have any unhandled extensions marked as critical.
- int extension_count = X509_get_ext_count(issuer);
- for (int i = 0; i < extension_count; ++i) {
- X509_EXTENSION* extension = X509_get_ext(issuer, i);
- if (extension->critical > 0) {
- const int nid = OBJ_obj2nid(extension->object);
- if (nid != NID_name_constraints && nid != NID_basic_constraints &&
- nid != NID_key_usage) {
- return CastCertError::kErrCertsVerifyGeneric;
- }
- }
- }
-
- int nid = OBJ_obj2nid(subject->sig_alg->algorithm);
- const EVP_MD* digest;
- switch (nid) {
- case NID_sha1WithRSAEncryption:
- digest = EVP_sha1();
- break;
- case NID_sha256WithRSAEncryption:
- digest = EVP_sha256();
- break;
- case NID_sha384WithRSAEncryption:
- digest = EVP_sha384();
- break;
- case NID_sha512WithRSAEncryption:
- digest = EVP_sha512();
- break;
- default:
- return CastCertError::kErrCertsVerifyGeneric;
- }
- if (!VerifySignedData(
- digest, public_key.get(),
- {subject->cert_info->enc.enc,
- static_cast<uint32_t>(subject->cert_info->enc.len)},
- {subject->signature->data,
- static_cast<uint32_t>(subject->signature->length)})) {
- return CastCertError::kErrCertsVerifyGeneric;
- }
- }
- // NOTE: Other half of ((!self-issued || target) -> check name constraints).
- for (NAME_CONSTRAINTS* name_constraints : path_name_constraints) {
- if (NAME_CONSTRAINTS_check(path.back().cert, name_constraints) !=
- X509_V_OK) {
- return CastCertError::kErrCertsVerifyGeneric;
- }
- }
- return error;
-}
-
-X509* ParseX509Der(const std::string& der) {
- const uint8_t* data = reinterpret_cast<const uint8_t*>(der.data());
- return d2i_X509(nullptr, &data, der.size());
-}
-
-CastDeviceCertPolicy GetAudioPolicy(const std::vector<CertPathStep>& path,
- uint32_t path_index) {
+CastDeviceCertPolicy GetAudioPolicy(const std::vector<X509*>& path) {
// 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.
@@ -489,9 +107,13 @@ CastDeviceCertPolicy GetAudioPolicy(const std::vector<CertPathStep>& path,
// certificates in the chain do, it won't matter as the chain is already
// restricted to being audio-only.
CastDeviceCertPolicy policy = CastDeviceCertPolicy::kUnrestricted;
- for (uint32_t i = path_index;
+ const ConstDataSpan& audio_only_policy_oid = AudioOnlyPolicyOid();
+ for (uint32_t i = 0;
i < path.size() && policy != CastDeviceCertPolicy::kAudioOnly; ++i) {
- X509* cert = path[path.size() - 1 - i].cert;
+ X509* cert = path[path.size() - 1 - i];
+
+ // Gets an index into the extension list that points to the
+ // certificatePolicies extension, if it exists, -1 otherwise.
int pos = X509_get_ext_by_NID(cert, NID_certificate_policies, -1);
if (pos != -1) {
X509_EXTENSION* policies_extension = X509_get_ext(cert, pos);
@@ -504,7 +126,6 @@ CastDeviceCertPolicy GetAudioPolicy(const std::vector<CertPathStep>& path,
uint32_t policy_count = sk_POLICYINFO_num(policies);
for (uint32_t i = 0; i < policy_count; ++i) {
POLICYINFO* info = sk_POLICYINFO_value(policies, i);
- const ConstDataSpan& audio_only_policy_oid = AudioOnlyPolicyOid();
if (info->policyid->length ==
static_cast<int>(audio_only_policy_oid.length) &&
memcmp(info->policyid->data, audio_only_policy_oid.data,
@@ -522,18 +143,26 @@ CastDeviceCertPolicy GetAudioPolicy(const std::vector<CertPathStep>& path,
} // namespace
-// static
-CastTrustStore* CastTrustStore::GetInstance() {
- static CastTrustStore* store = new CastTrustStore();
- return store;
-}
+class CastTrustStore {
+ public:
+ // Singleton for the Cast trust store for legacy networkingPrivate use.
+ static CastTrustStore* GetInstance() {
+ static CastTrustStore* store = new CastTrustStore();
+ return store;
+ }
-CastTrustStore::CastTrustStore() : trust_store_(new TrustStore()) {
- trust_store_->certs.emplace_back(MakeTrustAnchor(kCastRootCaDer));
- trust_store_->certs.emplace_back(MakeTrustAnchor(kEurekaRootCaDer));
-}
+ CastTrustStore() {
+ trust_store_.certs.emplace_back(MakeTrustAnchor(kCastRootCaDer));
+ trust_store_.certs.emplace_back(MakeTrustAnchor(kEurekaRootCaDer));
+ }
+ ~CastTrustStore() = default;
+
+ TrustStore* trust_store() { return &trust_store_; }
-CastTrustStore::~CastTrustStore() = default;
+ private:
+ TrustStore trust_store_;
+ OSP_DISALLOW_COPY_AND_ASSIGN(CastTrustStore);
+};
openscreen::Error VerifyDeviceCert(
const std::vector<std::string>& der_certs,
@@ -547,158 +176,28 @@ openscreen::Error VerifyDeviceCert(
trust_store = CastTrustStore::GetInstance()->trust_store();
}
- if (der_certs.empty()) {
- return CastCertError::kErrCertsMissing;
- }
-
// Fail early if CRL is required but not provided.
if (!crl && crl_policy == CRLPolicy::kCrlRequired) {
return CastCertError::kErrCrlInvalid;
}
- bssl::UniquePtr<X509> target_cert;
- std::vector<bssl::UniquePtr<X509>> intermediate_certs;
- target_cert.reset(ParseX509Der(der_certs[0]));
- if (!target_cert) {
- return CastCertError::kErrCertsParse;
- }
- for (size_t i = 1; i < der_certs.size(); ++i) {
- intermediate_certs.emplace_back(ParseX509Der(der_certs[i]));
- if (!intermediate_certs.back()) {
- return CastCertError::kErrCertsParse;
- }
- }
-
- // Basic checks on the target certificate.
- CastCertError error = VerifyCertTime(target_cert.get(), time);
- if (error != CastCertError::kNone) {
+ CertificatePathResult result_path = {};
+ openscreen::Error error =
+ FindCertificatePath(der_certs, time, &result_path, trust_store);
+ if (!error.ok()) {
return error;
}
- bssl::UniquePtr<EVP_PKEY> public_key{X509_get_pubkey(target_cert.get())};
- if (!VerifyPublicKeyLength(public_key.get())) {
- return CastCertError::kErrCertsVerifyGeneric;
- }
- if (X509_ALGOR_cmp(target_cert.get()->sig_alg,
- target_cert.get()->cert_info->signature) != 0) {
- return CastCertError::kErrCertsVerifyGeneric;
- }
- bssl::UniquePtr<ASN1_BIT_STRING> key_usage = GetKeyUsage(target_cert.get());
- if (!key_usage) {
- return CastCertError::kErrCertsRestrictions;
- }
- int bit =
- ASN1_BIT_STRING_get_bit(key_usage.get(), KeyUsageBits::kDigitalSignature);
- if (bit == 0) {
- return CastCertError::kErrCertsRestrictions;
- }
-
- X509* path_head = target_cert.get();
- std::vector<CertPathStep> path;
-
- // This vector isn't used as resizable, so instead we allocate the largest
- // possible single path up front. This would be a single trusted cert, all
- // the intermediate certs used once, and the target cert.
- path.resize(1 + intermediate_certs.size() + 1);
-
- // Additionally, the path is slightly simpler to deal with if the list is
- // sorted from trust->target, so the path is actually built starting from the
- // end.
- uint32_t first_index = path.size() - 1;
- path[first_index].cert = path_head;
-
- // Index into |path| of the current frontier of path construction.
- uint32_t path_index = first_index;
-
- // Whether |path| has reached a certificate in |trust_store| and is ready for
- // verification.
- bool path_cert_in_trust_store = false;
-
- // Attempt to build a valid certificate chain from |target_cert| to a
- // certificate in |trust_store|. This loop tries all possible paths in a
- // depth-first-search fashion. If no valid paths are found, the error
- // returned is whatever the last error was from the last path tried.
- uint32_t trust_store_index = 0;
- uint32_t intermediate_cert_index = 0;
- CastCertError last_error = CastCertError::kNone;
- for (;;) {
- X509_NAME* target_issuer_name = X509_get_issuer_name(path_head);
-
- // The next issuer certificate to add to the current path.
- X509* next_issuer = nullptr;
-
- for (uint32_t i = trust_store_index; i < trust_store->certs.size(); ++i) {
- X509* trust_store_cert = trust_store->certs[i].get();
- X509_NAME* trust_store_cert_name =
- X509_get_subject_name(trust_store_cert);
- if (X509_NAME_cmp(trust_store_cert_name, target_issuer_name) == 0) {
- CertPathStep& next_step = path[--path_index];
- next_step.cert = trust_store_cert;
- next_step.trust_store_index = i + 1;
- next_step.intermediate_cert_index = 0;
- next_issuer = trust_store_cert;
- path_cert_in_trust_store = true;
- break;
- }
- }
- trust_store_index = 0;
- if (!next_issuer) {
- for (uint32_t i = intermediate_cert_index; i < intermediate_certs.size();
- ++i) {
- X509* intermediate_cert = intermediate_certs[i].get();
- X509_NAME* intermediate_cert_name =
- X509_get_subject_name(intermediate_cert);
- if (X509_NAME_cmp(intermediate_cert_name, target_issuer_name) == 0 &&
- !CertInPath(intermediate_cert_name, path, path_index,
- first_index)) {
- CertPathStep& next_step = path[--path_index];
- next_step.cert = intermediate_cert;
- next_step.trust_store_index = trust_store->certs.size();
- next_step.intermediate_cert_index = i + 1;
- next_issuer = intermediate_cert;
- break;
- }
- }
- }
- intermediate_cert_index = 0;
- if (!next_issuer) {
- if (path_index == first_index) {
- // There are no more paths to try. Ensure an error is returned.
- if (last_error == CastCertError::kNone) {
- return CastCertError::kErrCertsVerifyGeneric;
- }
- return last_error;
- } else {
- CertPathStep& last_step = path[path_index++];
- trust_store_index = last_step.trust_store_index;
- intermediate_cert_index = last_step.intermediate_cert_index;
- continue;
- }
- }
-
- // TODO(btolsch): Check against revocation list
- if (path_cert_in_trust_store) {
- last_error = VerifyCertificateChain(path, path_index, time);
- if (last_error != CastCertError::kNone) {
- CertPathStep& last_step = path[path_index++];
- trust_store_index = last_step.trust_store_index;
- intermediate_cert_index = last_step.intermediate_cert_index;
- path_cert_in_trust_store = false;
- } else {
- break;
- }
- }
- path_head = next_issuer;
- }
- if (last_error != CastCertError::kNone) {
- return last_error;
+ if (crl && !crl->CheckRevocation(result_path.path, time)) {
+ return CastCertError::kErrCertsRevoked;
}
- *policy = GetAudioPolicy(path, path_index);
+ *policy = GetAudioPolicy(result_path.path);
// Finally, make sure there is a common name to give to
// CertVerificationContextImpl.
- X509_NAME* target_subject = X509_get_subject_name(target_cert.get());
+ X509_NAME* target_subject =
+ X509_get_subject_name(result_path.target_cert.get());
std::string common_name(target_subject->canon_enclen, 0);
int len = X509_NAME_get_text_by_NID(target_subject, NID_commonName,
&common_name[0], common_name.size());
@@ -708,7 +207,7 @@ openscreen::Error VerifyDeviceCert(
common_name.resize(len);
context->reset(new CertVerificationContextImpl(
- bssl::UniquePtr<EVP_PKEY>{X509_get_pubkey(target_cert.get())},
+ bssl::UniquePtr<EVP_PKEY>{X509_get_pubkey(result_path.target_cert.get())},
std::move(common_name)));
return CastCertError::kNone;
diff --git a/cast/common/certificate/cast_cert_validator.h b/cast/common/certificate/cast_cert_validator.h
index 7f0da1d0..bca17e6f 100644
--- a/cast/common/certificate/cast_cert_validator.h
+++ b/cast/common/certificate/cast_cert_validator.h
@@ -9,6 +9,7 @@
#include <string>
#include <vector>
+#include "cast/common/certificate/types.h"
#include "platform/base/error.h"
#include "platform/base/macros.h"
@@ -41,37 +42,8 @@ enum class DigestAlgorithm {
kSha512,
};
-struct ConstDataSpan {
- const uint8_t* data;
- uint32_t length;
-};
-
-struct DateTime {
- uint16_t year;
- uint8_t month;
- uint8_t day;
- uint8_t hour;
- uint8_t minute;
- uint8_t second;
-};
-
struct TrustStore;
-class CastTrustStore {
- public:
- // Singleton for the Cast trust store for legacy networkingPrivate use.
- static CastTrustStore* GetInstance();
-
- CastTrustStore();
- ~CastTrustStore();
-
- TrustStore* trust_store() const { return trust_store_.get(); }
-
- private:
- std::unique_ptr<TrustStore> trust_store_;
- OSP_DISALLOW_COPY_AND_ASSIGN(CastTrustStore);
-};
-
// An object of this type is returned by the VerifyDeviceCert function, and can
// be used for additional certificate-related operations, using the verified
// certificate.
diff --git a/cast/common/certificate/cast_cert_validator_internal.cc b/cast/common/certificate/cast_cert_validator_internal.cc
new file mode 100644
index 00000000..c704cf4c
--- /dev/null
+++ b/cast/common/certificate/cast_cert_validator_internal.cc
@@ -0,0 +1,571 @@
+// 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_cert_validator_internal.h"
+
+#include <openssl/asn1.h>
+#include <openssl/evp.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+#include <time.h>
+
+#include <vector>
+
+#include "cast/common/certificate/types.h"
+#include "platform/api/logging.h"
+
+namespace cast {
+namespace certificate {
+namespace {
+
+constexpr static int32_t kMinRsaModulusLengthBits = 2048;
+
+using CastCertError = openscreen::Error::Code;
+
+// Stores intermediate state while attempting to find a valid certificate chain
+// from a set of trusted certificates to a target certificate. Together, a
+// sequence of these forms a certificate chain to be verified as well as a stack
+// that can be unwound for searching more potential paths.
+struct CertPathStep {
+ X509* cert;
+
+ // The next index that can be checked in |trust_store| if the choice |cert| on
+ // the path needs to be reverted.
+ uint32_t trust_store_index;
+
+ // The next index that can be checked in |intermediate_certs| if the choice
+ // |cert| on the path needs to be reverted.
+ uint32_t intermediate_cert_index;
+};
+
+// These values are bit positions from RFC 5280 4.2.1.3 and will be passed to
+// ASN1_BIT_STRING_get_bit.
+enum KeyUsageBits {
+ kDigitalSignature = 0,
+ kKeyCertSign = 5,
+};
+
+bool CertInPath(X509_NAME* name,
+ const std::vector<CertPathStep>& steps,
+ uint32_t start,
+ uint32_t stop) {
+ for (uint32_t i = start; i < stop; ++i) {
+ if (X509_NAME_cmp(name, X509_get_subject_name(steps[i].cert)) == 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Parse the data in |time| at |index| as a two-digit ascii number.
+uint8_t ParseAsn1TimeDoubleDigit(ASN1_GENERALIZEDTIME* time, int index) {
+ return (time->data[index] - '0') * 10 + (time->data[index + 1] - '0');
+}
+
+CastCertError VerifyCertTime(X509* cert, const DateTime& time) {
+ ASN1_GENERALIZEDTIME* not_before_asn1 = ASN1_TIME_to_generalizedtime(
+ cert->cert_info->validity->notBefore, nullptr);
+ ASN1_GENERALIZEDTIME* not_after_asn1 = ASN1_TIME_to_generalizedtime(
+ cert->cert_info->validity->notAfter, nullptr);
+ if (!not_before_asn1 || !not_after_asn1) {
+ return CastCertError::kErrCertsVerifyGeneric;
+ }
+ DateTime not_before;
+ DateTime not_after;
+ bool times_valid = ParseAsn1GeneralizedTime(not_before_asn1, &not_before) &&
+ ParseAsn1GeneralizedTime(not_after_asn1, &not_after);
+ ASN1_GENERALIZEDTIME_free(not_before_asn1);
+ ASN1_GENERALIZEDTIME_free(not_after_asn1);
+ if (!times_valid) {
+ return CastCertError::kErrCertsVerifyGeneric;
+ }
+ if ((time < not_before) || (not_after < time)) {
+ return CastCertError::kErrCertsDateInvalid;
+ }
+ return CastCertError::kNone;
+}
+
+bool VerifyPublicKeyLength(EVP_PKEY* public_key) {
+ return EVP_PKEY_bits(public_key) >= kMinRsaModulusLengthBits;
+}
+
+bssl::UniquePtr<ASN1_BIT_STRING> GetKeyUsage(X509* cert) {
+ int pos = X509_get_ext_by_NID(cert, NID_key_usage, -1);
+ if (pos == -1) {
+ return nullptr;
+ }
+ X509_EXTENSION* key_usage = X509_get_ext(cert, pos);
+ const uint8_t* value = key_usage->value->data;
+ ASN1_BIT_STRING* key_usage_bit_string = nullptr;
+ if (!d2i_ASN1_BIT_STRING(&key_usage_bit_string, &value,
+ key_usage->value->length)) {
+ return nullptr;
+ }
+ return bssl::UniquePtr<ASN1_BIT_STRING>{key_usage_bit_string};
+}
+
+CastCertError VerifyCertificateChain(const std::vector<CertPathStep>& path,
+ uint32_t step_index,
+ const DateTime& time) {
+ // Default max path length is the number of intermediate certificates.
+ int max_pathlen = path.size() - 2;
+
+ std::vector<NAME_CONSTRAINTS*> path_name_constraints;
+ CastCertError error = CastCertError::kNone;
+ uint32_t i = step_index;
+ for (; i < path.size() - 1; ++i) {
+ X509* subject = path[i + 1].cert;
+ X509* issuer = path[i].cert;
+ bool is_root = (i == step_index);
+ if (!is_root) {
+ if ((error = VerifyCertTime(issuer, time)) != CastCertError::kNone) {
+ return error;
+ }
+ if (X509_NAME_cmp(X509_get_subject_name(issuer),
+ X509_get_issuer_name(issuer)) != 0) {
+ if (max_pathlen == 0) {
+ return CastCertError::kErrCertsPathlen;
+ }
+ --max_pathlen;
+ } else {
+ issuer->ex_flags |= EXFLAG_SI;
+ }
+ } else {
+ issuer->ex_flags |= EXFLAG_SI;
+ }
+
+ bssl::UniquePtr<ASN1_BIT_STRING> key_usage = GetKeyUsage(issuer);
+ if (key_usage) {
+ const int bit =
+ ASN1_BIT_STRING_get_bit(key_usage.get(), KeyUsageBits::kKeyCertSign);
+ if (bit == 0) {
+ return CastCertError::kErrCertsVerifyGeneric;
+ }
+ }
+
+ // Check that basicConstraints is present, specifies the CA bit, and use
+ // pathLenConstraint if present.
+ const int basic_constraints_index =
+ X509_get_ext_by_NID(issuer, NID_basic_constraints, -1);
+ if (basic_constraints_index == -1) {
+ return CastCertError::kErrCertsVerifyGeneric;
+ }
+ X509_EXTENSION* const basic_constraints_extension =
+ X509_get_ext(issuer, basic_constraints_index);
+ bssl::UniquePtr<BASIC_CONSTRAINTS> basic_constraints{
+ reinterpret_cast<BASIC_CONSTRAINTS*>(
+ X509V3_EXT_d2i(basic_constraints_extension))};
+
+ if (!basic_constraints || !basic_constraints->ca) {
+ return CastCertError::kErrCertsVerifyGeneric;
+ }
+
+ if (basic_constraints->pathlen) {
+ if (basic_constraints->pathlen->length != 1) {
+ return CastCertError::kErrCertsVerifyGeneric;
+ } else {
+ const int pathlen = *basic_constraints->pathlen->data;
+ if (pathlen < 0) {
+ return CastCertError::kErrCertsVerifyGeneric;
+ }
+ if (pathlen < max_pathlen) {
+ max_pathlen = pathlen;
+ }
+ }
+ }
+
+ if (X509_ALGOR_cmp(issuer->sig_alg, issuer->cert_info->signature) != 0) {
+ return CastCertError::kErrCertsVerifyGeneric;
+ }
+
+ bssl::UniquePtr<EVP_PKEY> public_key{X509_get_pubkey(issuer)};
+ if (!VerifyPublicKeyLength(public_key.get())) {
+ return CastCertError::kErrCertsVerifyGeneric;
+ }
+
+ // NOTE: (!self-issued || target) -> verify name constraints. Target case
+ // is after the loop.
+ const bool is_self_issued = issuer->ex_flags & EXFLAG_SI;
+ if (!is_self_issued) {
+ for (NAME_CONSTRAINTS* name_constraints : path_name_constraints) {
+ if (NAME_CONSTRAINTS_check(subject, name_constraints) != X509_V_OK) {
+ return CastCertError::kErrCertsVerifyGeneric;
+ }
+ }
+ }
+
+ if (issuer->nc) {
+ path_name_constraints.push_back(issuer->nc);
+ } else {
+ const int index = X509_get_ext_by_NID(issuer, NID_name_constraints, -1);
+ if (index != -1) {
+ X509_EXTENSION* ext = X509_get_ext(issuer, index);
+ auto* nc = reinterpret_cast<NAME_CONSTRAINTS*>(X509V3_EXT_d2i(ext));
+ if (nc) {
+ issuer->nc = nc;
+ path_name_constraints.push_back(nc);
+ } else {
+ return CastCertError::kErrCertsVerifyGeneric;
+ }
+ }
+ }
+
+ // Check that any policy mappings present are _not_ the anyPolicy OID. Even
+ // though we don't otherwise handle policies, this is required by RFC 5280
+ // 6.1.4(a).
+ const int policy_mappings_index =
+ X509_get_ext_by_NID(issuer, NID_policy_mappings, -1);
+ if (policy_mappings_index != -1) {
+ X509_EXTENSION* policy_mappings_extension =
+ X509_get_ext(issuer, policy_mappings_index);
+ auto* policy_mappings = reinterpret_cast<POLICY_MAPPINGS*>(
+ X509V3_EXT_d2i(policy_mappings_extension));
+ const uint32_t policy_mapping_count =
+ sk_POLICY_MAPPING_num(policy_mappings);
+ const ASN1_OBJECT* any_policy = OBJ_nid2obj(NID_any_policy);
+ for (uint32_t i = 0; i < policy_mapping_count; ++i) {
+ POLICY_MAPPING* policy_mapping =
+ sk_POLICY_MAPPING_value(policy_mappings, i);
+ const bool either_matches =
+ ((OBJ_cmp(policy_mapping->issuerDomainPolicy, any_policy) == 0) ||
+ (OBJ_cmp(policy_mapping->subjectDomainPolicy, any_policy) == 0));
+ if (either_matches) {
+ error = CastCertError::kErrCertsVerifyGeneric;
+ break;
+ }
+ }
+ sk_POLICY_MAPPING_free(policy_mappings);
+ if (error != CastCertError::kNone) {
+ return error;
+ }
+ }
+
+ // Check that we don't have any unhandled extensions marked as critical.
+ int extension_count = X509_get_ext_count(issuer);
+ for (int i = 0; i < extension_count; ++i) {
+ X509_EXTENSION* extension = X509_get_ext(issuer, i);
+ if (extension->critical > 0) {
+ const int nid = OBJ_obj2nid(extension->object);
+ if (nid != NID_name_constraints && nid != NID_basic_constraints &&
+ nid != NID_key_usage) {
+ return CastCertError::kErrCertsVerifyGeneric;
+ }
+ }
+ }
+
+ int nid = OBJ_obj2nid(subject->sig_alg->algorithm);
+ const EVP_MD* digest;
+ switch (nid) {
+ case NID_sha1WithRSAEncryption:
+ digest = EVP_sha1();
+ break;
+ case NID_sha256WithRSAEncryption:
+ digest = EVP_sha256();
+ break;
+ case NID_sha384WithRSAEncryption:
+ digest = EVP_sha384();
+ break;
+ case NID_sha512WithRSAEncryption:
+ digest = EVP_sha512();
+ break;
+ default:
+ return CastCertError::kErrCertsVerifyGeneric;
+ }
+ if (!VerifySignedData(
+ digest, public_key.get(),
+ {subject->cert_info->enc.enc,
+ static_cast<uint32_t>(subject->cert_info->enc.len)},
+ {subject->signature->data,
+ static_cast<uint32_t>(subject->signature->length)})) {
+ return CastCertError::kErrCertsVerifyGeneric;
+ }
+ }
+ // NOTE: Other half of ((!self-issued || target) -> check name constraints).
+ for (NAME_CONSTRAINTS* name_constraints : path_name_constraints) {
+ if (NAME_CONSTRAINTS_check(path.back().cert, name_constraints) !=
+ X509_V_OK) {
+ return CastCertError::kErrCertsVerifyGeneric;
+ }
+ }
+ return error;
+}
+
+X509* ParseX509Der(const std::string& der) {
+ const uint8_t* data = reinterpret_cast<const uint8_t*>(der.data());
+ return d2i_X509(nullptr, &data, der.size());
+}
+
+} // namespace
+
+// Parses DateTime with additional restrictions laid out by RFC 5280
+// 4.1.2.5.2.
+bool ParseAsn1GeneralizedTime(ASN1_GENERALIZEDTIME* time, DateTime* out) {
+ static constexpr uint8_t kDaysPerMonth[] = {
+ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
+ };
+
+ if (time->length != 15) {
+ return false;
+ }
+ if (time->data[14] != 'Z') {
+ return false;
+ }
+ for (int i = 0; i < 14; ++i) {
+ if (time->data[i] < '0' || time->data[i] > '9') {
+ return false;
+ }
+ }
+ out->year = ParseAsn1TimeDoubleDigit(time, 0) * 100 +
+ ParseAsn1TimeDoubleDigit(time, 2);
+ out->month = ParseAsn1TimeDoubleDigit(time, 4);
+ out->day = ParseAsn1TimeDoubleDigit(time, 6);
+ out->hour = ParseAsn1TimeDoubleDigit(time, 8);
+ out->minute = ParseAsn1TimeDoubleDigit(time, 10);
+ out->second = ParseAsn1TimeDoubleDigit(time, 12);
+ if (out->month == 0 || out->month > 12) {
+ return false;
+ }
+ int days_per_month = kDaysPerMonth[out->month - 1];
+ if (out->month == 2) {
+ if (out->year % 4 == 0 && (out->year % 100 != 0 || out->year % 400 == 0)) {
+ days_per_month = 29;
+ } else {
+ days_per_month = 28;
+ }
+ }
+ if (out->day == 0 || out->day > days_per_month) {
+ return false;
+ }
+ if (out->hour > 23) {
+ return false;
+ }
+ if (out->minute > 59) {
+ return false;
+ }
+ // Leap seconds are allowed.
+ if (out->second > 60) {
+ return false;
+ }
+ return true;
+}
+
+bool VerifySignedData(const EVP_MD* digest,
+ EVP_PKEY* public_key,
+ const ConstDataSpan& data,
+ const ConstDataSpan& signature) {
+ // This code assumes the signature algorithm was RSASSA PKCS#1 v1.5 with
+ // |digest|.
+ bssl::ScopedEVP_MD_CTX ctx;
+ if (!EVP_DigestVerifyInit(ctx.get(), nullptr, digest, nullptr, public_key)) {
+ return false;
+ }
+ return (EVP_DigestVerify(ctx.get(), signature.data, signature.length,
+ data.data, data.length) == 1);
+}
+
+bool operator<(const DateTime& a, const DateTime& b) {
+ if (a.year < b.year) {
+ return true;
+ } else if (a.year > b.year) {
+ return false;
+ }
+ if (a.month < b.month) {
+ return true;
+ } else if (a.month > b.month) {
+ return false;
+ }
+ if (a.day < b.day) {
+ return true;
+ } else if (a.day > b.day) {
+ return false;
+ }
+ if (a.hour < b.hour) {
+ return true;
+ } else if (a.hour > b.hour) {
+ return false;
+ }
+ if (a.minute < b.minute) {
+ return true;
+ } else if (a.minute > b.minute) {
+ return false;
+ }
+ if (a.second < b.second) {
+ return true;
+ } else if (a.second > b.second) {
+ return false;
+ }
+ return false;
+}
+
+bool ConvertTimeSeconds(uint64_t seconds, DateTime* time) {
+ struct tm tm = {};
+ time_t sec = static_cast<time_t>(seconds);
+ OSP_DCHECK_GE(sec, 0);
+ OSP_DCHECK_EQ(static_cast<uint64_t>(sec), seconds);
+ if (!gmtime_r(&sec, &tm)) {
+ return false;
+ }
+
+ time->second = tm.tm_sec;
+ time->minute = tm.tm_min;
+ time->hour = tm.tm_hour;
+ time->day = tm.tm_mday;
+ time->month = tm.tm_mon + 1;
+ time->year = tm.tm_year + 1900;
+
+ return true;
+}
+
+openscreen::Error FindCertificatePath(const std::vector<std::string>& der_certs,
+ const DateTime& time,
+ CertificatePathResult* result_path,
+ TrustStore* trust_store) {
+ if (der_certs.empty()) {
+ return CastCertError::kErrCertsMissing;
+ }
+
+ bssl::UniquePtr<X509>& target_cert = result_path->target_cert;
+ std::vector<bssl::UniquePtr<X509>>& intermediate_certs =
+ result_path->intermediate_certs;
+ target_cert.reset(ParseX509Der(der_certs[0]));
+ if (!target_cert) {
+ return CastCertError::kErrCertsParse;
+ }
+ for (size_t i = 1; i < der_certs.size(); ++i) {
+ intermediate_certs.emplace_back(ParseX509Der(der_certs[i]));
+ if (!intermediate_certs.back()) {
+ return CastCertError::kErrCertsParse;
+ }
+ }
+
+ // Basic checks on the target certificate.
+ CastCertError error = VerifyCertTime(target_cert.get(), time);
+ if (error != CastCertError::kNone) {
+ return error;
+ }
+ bssl::UniquePtr<EVP_PKEY> public_key{X509_get_pubkey(target_cert.get())};
+ if (!VerifyPublicKeyLength(public_key.get())) {
+ return CastCertError::kErrCertsVerifyGeneric;
+ }
+ if (X509_ALGOR_cmp(target_cert.get()->sig_alg,
+ target_cert.get()->cert_info->signature) != 0) {
+ return CastCertError::kErrCertsVerifyGeneric;
+ }
+ bssl::UniquePtr<ASN1_BIT_STRING> key_usage = GetKeyUsage(target_cert.get());
+ if (!key_usage) {
+ return CastCertError::kErrCertsRestrictions;
+ }
+ int bit =
+ ASN1_BIT_STRING_get_bit(key_usage.get(), KeyUsageBits::kDigitalSignature);
+ if (bit == 0) {
+ return CastCertError::kErrCertsRestrictions;
+ }
+
+ X509* path_head = target_cert.get();
+ std::vector<CertPathStep> path;
+
+ // This vector isn't used as resizable, so instead we allocate the largest
+ // possible single path up front. This would be a single trusted cert, all
+ // the intermediate certs used once, and the target cert.
+ path.resize(1 + intermediate_certs.size() + 1);
+
+ // Additionally, the path is slightly simpler to deal with if the list is
+ // sorted from trust->target, so the path is actually built starting from the
+ // end.
+ uint32_t first_index = path.size() - 1;
+ path[first_index].cert = path_head;
+
+ // Index into |path| of the current frontier of path construction.
+ uint32_t path_index = first_index;
+
+ // Whether |path| has reached a certificate in |trust_store| and is ready for
+ // verification.
+ bool path_cert_in_trust_store = false;
+
+ // Attempt to build a valid certificate chain from |target_cert| to a
+ // certificate in |trust_store|. This loop tries all possible paths in a
+ // depth-first-search fashion. If no valid paths are found, the error
+ // returned is whatever the last error was from the last path tried.
+ uint32_t trust_store_index = 0;
+ uint32_t intermediate_cert_index = 0;
+ CastCertError last_error = CastCertError::kNone;
+ for (;;) {
+ X509_NAME* target_issuer_name = X509_get_issuer_name(path_head);
+
+ // The next issuer certificate to add to the current path.
+ X509* next_issuer = nullptr;
+
+ for (uint32_t i = trust_store_index; i < trust_store->certs.size(); ++i) {
+ X509* trust_store_cert = trust_store->certs[i].get();
+ X509_NAME* trust_store_cert_name =
+ X509_get_subject_name(trust_store_cert);
+ if (X509_NAME_cmp(trust_store_cert_name, target_issuer_name) == 0) {
+ CertPathStep& next_step = path[--path_index];
+ next_step.cert = trust_store_cert;
+ next_step.trust_store_index = i + 1;
+ next_step.intermediate_cert_index = 0;
+ next_issuer = trust_store_cert;
+ path_cert_in_trust_store = true;
+ break;
+ }
+ }
+ trust_store_index = 0;
+ if (!next_issuer) {
+ for (uint32_t i = intermediate_cert_index; i < intermediate_certs.size();
+ ++i) {
+ X509* intermediate_cert = intermediate_certs[i].get();
+ X509_NAME* intermediate_cert_name =
+ X509_get_subject_name(intermediate_cert);
+ if (X509_NAME_cmp(intermediate_cert_name, target_issuer_name) == 0 &&
+ !CertInPath(intermediate_cert_name, path, path_index,
+ first_index)) {
+ CertPathStep& next_step = path[--path_index];
+ next_step.cert = intermediate_cert;
+ next_step.trust_store_index = trust_store->certs.size();
+ next_step.intermediate_cert_index = i + 1;
+ next_issuer = intermediate_cert;
+ break;
+ }
+ }
+ }
+ intermediate_cert_index = 0;
+ if (!next_issuer) {
+ if (path_index == first_index) {
+ // There are no more paths to try. Ensure an error is returned.
+ if (last_error == CastCertError::kNone) {
+ return CastCertError::kErrCertsVerifyGeneric;
+ }
+ return last_error;
+ } else {
+ CertPathStep& last_step = path[path_index++];
+ trust_store_index = last_step.trust_store_index;
+ intermediate_cert_index = last_step.intermediate_cert_index;
+ continue;
+ }
+ }
+
+ if (path_cert_in_trust_store) {
+ last_error = VerifyCertificateChain(path, path_index, time);
+ if (last_error != CastCertError::kNone) {
+ CertPathStep& last_step = path[path_index++];
+ trust_store_index = last_step.trust_store_index;
+ intermediate_cert_index = last_step.intermediate_cert_index;
+ path_cert_in_trust_store = false;
+ } else {
+ break;
+ }
+ }
+ path_head = next_issuer;
+ }
+
+ result_path->path.reserve(path.size() - path_index);
+ for (uint32_t i = path_index; i < path.size(); ++i) {
+ result_path->path.push_back(path[i].cert);
+ }
+
+ return CastCertError::kNone;
+}
+
+} // namespace certificate
+} // namespace cast
diff --git a/cast/common/certificate/cast_cert_validator_internal.h b/cast/common/certificate/cast_cert_validator_internal.h
index 91779a85..ad8dafbc 100644
--- a/cast/common/certificate/cast_cert_validator_internal.h
+++ b/cast/common/certificate/cast_cert_validator_internal.h
@@ -9,6 +9,8 @@
#include <vector>
+#include "platform/base/error.h"
+
namespace cast {
namespace certificate {
@@ -16,6 +18,40 @@ struct TrustStore {
std::vector<bssl::UniquePtr<X509>> certs;
};
+// Adds a trust anchor given a DER-encoded certificate from static
+// storage.
+template <size_t N>
+bssl::UniquePtr<X509> MakeTrustAnchor(const uint8_t (&data)[N]) {
+ const uint8_t* dptr = data;
+ return bssl::UniquePtr<X509>{d2i_X509(nullptr, &dptr, N)};
+}
+
+struct ConstDataSpan;
+struct DateTime;
+
+bool VerifySignedData(const EVP_MD* digest,
+ EVP_PKEY* public_key,
+ const ConstDataSpan& data,
+ const ConstDataSpan& signature);
+
+bool operator<(const DateTime& a, const DateTime& b);
+bool ConvertTimeSeconds(uint64_t seconds, DateTime* time);
+
+// Parses DateTime with additional restrictions laid out by RFC 5280
+// 4.1.2.5.2.
+bool ParseAsn1GeneralizedTime(ASN1_GENERALIZEDTIME* time, DateTime* out);
+
+struct CertificatePathResult {
+ bssl::UniquePtr<X509> target_cert;
+ std::vector<bssl::UniquePtr<X509>> intermediate_certs;
+ std::vector<X509*> path;
+};
+
+openscreen::Error FindCertificatePath(const std::vector<std::string>& der_certs,
+ const DateTime& time,
+ CertificatePathResult* result_path,
+ TrustStore* trust_store);
+
} // namespace certificate
} // namespace cast
diff --git a/cast/common/certificate/cast_cert_validator_unittest.cc b/cast/common/certificate/cast_cert_validator_unittest.cc
index 8c239239..a4aa1962 100644
--- a/cast/common/certificate/cast_cert_validator_unittest.cc
+++ b/cast/common/certificate/cast_cert_validator_unittest.cc
@@ -5,8 +5,10 @@
#include "cast/common/certificate/cast_cert_validator.h"
#include <stdio.h>
+#include <string.h>
#include "cast/common/certificate/cast_cert_validator_internal.h"
+#include "cast/common/certificate/test_helpers.h"
#include "gtest/gtest.h"
#include "openssl/pem.h"
@@ -29,8 +31,6 @@ enum TrustStoreDependency {
TRUST_STORE_FROM_TEST_FILE,
};
-CastTrustStore g_cast_trust_store;
-
// Reads a test chain from |certs_file_name|, and asserts that verifying it as
// a Cast device certificate yields |expected_result|.
//
@@ -52,34 +52,14 @@ void RunTest(CastCertError expected_result,
const DateTime& time,
TrustStoreDependency trust_store_dependency,
const std::string& optional_signed_data_file_name) {
- FILE* fp = fopen(certs_file_name.c_str(), "r");
- ASSERT_TRUE(fp);
- std::vector<std::string> certs;
-#define STRCMP_LITERAL(s, l) strncmp(s, l, sizeof(l))
- for (;;) {
- char* name;
- char* header;
- unsigned char* data;
- long length;
- if (PEM_read(fp, &name, &header, &data, &length) == 1) {
- if (STRCMP_LITERAL(name, "CERTIFICATE") == 0) {
- certs.emplace_back((char*)data, length);
- }
- OPENSSL_free(name);
- OPENSSL_free(header);
- OPENSSL_free(data);
- } else {
- break;
- }
- }
- fclose(fp);
-
+ std::vector<std::string> certs =
+ testing::ReadCertificatesFromPemFile(certs_file_name);
TrustStore* trust_store;
std::unique_ptr<TrustStore> fake_trust_store;
switch (trust_store_dependency) {
case TRUST_STORE_BUILTIN:
- trust_store = g_cast_trust_store.trust_store();
+ trust_store = nullptr;
break;
case TRUST_STORE_FROM_TEST_FILE: {
@@ -141,15 +121,15 @@ void RunTest(CastCertError expected_result,
unsigned char* data;
long length;
if (PEM_read(fp, &name, &header, &data, &length) == 1) {
- if (STRCMP_LITERAL(name, "MESSAGE") == 0) {
+ if (strcmp(name, "MESSAGE") == 0) {
ASSERT_FALSE(message.data);
message.data = data;
message.length = length;
- } else if (STRCMP_LITERAL(name, "SIGNATURE SHA1") == 0) {
+ } else if (strcmp(name, "SIGNATURE SHA1") == 0) {
ASSERT_FALSE(sha1.data);
sha1.data = data;
sha1.length = length;
- } else if (STRCMP_LITERAL(name, "SIGNATURE SHA256") == 0) {
+ } else if (strcmp(name, "SIGNATURE SHA256") == 0) {
ASSERT_FALSE(sha256.data);
sha256.data = data;
sha256.length = length;
diff --git a/cast/common/certificate/cast_crl.cc b/cast/common/certificate/cast_crl.cc
new file mode 100644
index 00000000..a2f05ea0
--- /dev/null
+++ b/cast/common/certificate/cast_crl.cc
@@ -0,0 +1,275 @@
+// 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 "cast/common/certificate/proto/revocation.pb.h"
+#include "platform/api/logging.h"
+#include "platform/base/macros.h"
+#include "util/crypto/sha2.h"
+
+namespace cast {
+namespace certificate {
+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 = {};
+ openscreen::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 (!ConvertTimeSeconds(tbs_crl.not_before_seconds(), &not_before)) {
+ return false;
+ }
+ DateTime not_after;
+ if (!ConvertTimeSeconds(tbs_crl.not_after_seconds(), &not_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;
+ ASN1_GENERALIZEDTIME* not_after_asn1 = ASN1_TIME_to_generalizedtime(
+ result_path.target_cert->cert_info->validity->notAfter, nullptr);
+ if (!not_after_asn1) {
+ return false;
+ }
+ DateTime cert_not_after;
+ bool time_valid = ParseAsn1GeneralizedTime(not_after_asn1, &cert_not_after);
+ ASN1_GENERALIZEDTIME_free(not_after_asn1);
+ 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;
+}
+
+std::string GetSpkiTlv(X509* cert) {
+ int len = i2d_X509_PUBKEY(cert->cert_info->key, nullptr);
+ if (len <= 0) {
+ return {};
+ }
+ std::string x(len, 0);
+ uint8_t* data = reinterpret_cast<uint8_t*>(&x[0]);
+ if (!i2d_X509_PUBKEY(cert->cert_info->key, &data)) {
+ return {};
+ }
+ size_t actual_size = data - reinterpret_cast<uint8_t*>(&x[0]);
+ OSP_DCHECK_EQ(actual_size, x.size());
+ x.resize(actual_size);
+ return x;
+}
+
+bool ParseDerUint64(ASN1_INTEGER* asn1int, uint64_t* result) {
+ if (asn1int->length > 8 || asn1int->length == 0) {
+ return false;
+ }
+ *result = 0;
+ for (int i = 0; i < asn1int->length; ++i) {
+ *result = (*result << 8) | asn1int->data[i];
+ }
+ return true;
+}
+
+} // namespace
+
+CastCRL::CastCRL(const TbsCrl& tbs_crl, const DateTime& overall_not_after) {
+ // Parse the validity information.
+ // Assume ConvertTimeSeconds will succeed. Successful call to VerifyCRL
+ // means that these calls were successful.
+ ConvertTimeSeconds(tbs_crl.not_before_seconds(), &not_before_);
+ ConvertTimeSeconds(tbs_crl.not_after_seconds(), &not_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;
+ }
+
+ std::string spki_hash = openscreen::SHA256HashString(spki_tlv);
+ if (revoked_hashes_.find(spki_hash) != revoked_hashes_.end()) {
+ return false;
+ }
+
+ // Check if the subordinate certificate was revoked by serial number.
+ if (i < (trusted_chain.size() - 1)) {
+ auto issuer_iter = revoked_serial_numbers_.find(spki_hash);
+ 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.
+ if (!ParseDerUint64(subordinate->cert_info->serialNumber,
+ &serial_number)) {
+ continue;
+ }
+ 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 certificate
+} // namespace cast
diff --git a/cast/common/certificate/cast_crl.h b/cast/common/certificate/cast_crl.h
new file mode 100644
index 00000000..51b8fa51
--- /dev/null
+++ b/cast/common/certificate/cast_crl.h
@@ -0,0 +1,87 @@
+// 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.
+
+#ifndef CAST_COMMON_CERTIFICATE_CAST_CRL_H_
+#define CAST_COMMON_CERTIFICATE_CAST_CRL_H_
+
+#include <openssl/x509.h>
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include "cast/common/certificate/cast_cert_validator.h"
+#include "cast/common/certificate/proto/revocation.pb.h"
+#include "platform/base/macros.h"
+
+namespace cast {
+namespace certificate {
+
+// This class represents the certificate revocation list information parsed from
+// the binary in a protobuf message.
+class CastCRL {
+ public:
+ CastCRL(const TbsCrl& tbs_crl, const DateTime& overall_not_after);
+ ~CastCRL();
+
+ // Verifies the revocation status of a cast device certificate given a chain
+ // of X.509 certificates.
+ //
+ // Inputs:
+ // * |trusted_chain| is the chain of verified certificates, starting with
+ // trust anchor.
+ //
+ // * |time| is the timestamp to use for determining if the certificate is
+ // revoked.
+ //
+ // Output:
+ // Returns true if no certificate in the chain was revoked.
+ bool CheckRevocation(const std::vector<X509*>& trusted_chain,
+ const DateTime& time) const;
+
+ private:
+ struct SerialNumberRange {
+ uint64_t first_serial;
+ uint64_t last_serial;
+ };
+
+ DateTime not_before_;
+ DateTime not_after_;
+
+ // Revoked public key hashes.
+ // The values consist of the SHA256 hash of the SubjectPublicKeyInfo.
+ std::unordered_set<std::string> revoked_hashes_;
+
+ // Revoked serial number ranges indexed by issuer public key hash.
+ // The key is the SHA256 hash of issuer's SubjectPublicKeyInfo.
+ // The value is a list of revoked serial number ranges.
+ std::unordered_map<std::string, std::vector<SerialNumberRange>>
+ revoked_serial_numbers_;
+
+ OSP_DISALLOW_COPY_AND_ASSIGN(CastCRL);
+};
+
+struct TrustStore;
+
+// Parses and verifies the CRL used to verify the revocation status of
+// Cast device certificates, using the built-in Cast CRL trust anchors.
+//
+// Inputs:
+// * |crl_proto| is a serialized cast_certificate.CrlBundle proto.
+// * |time| is the timestamp to use for determining if the CRL is valid.
+// * |trust_store| is the set of trust anchors to use. This should be nullptr
+// in production, but can be overridden in tests.
+//
+// Output:
+// Returns the CRL object if success, nullptr otherwise.
+std::unique_ptr<CastCRL> ParseAndVerifyCRL(const std::string& crl_proto,
+ const DateTime& time,
+ TrustStore* trust_store = nullptr);
+
+} // namespace certificate
+} // namespace cast
+
+#endif // CAST_COMMON_CERTIFICATE_CAST_CRL_H_
diff --git a/cast/common/certificate/cast_crl_root_ca_cert_der-inc.h b/cast/common/certificate/cast_crl_root_ca_cert_der-inc.h
new file mode 100644
index 00000000..9d7b4a22
--- /dev/null
+++ b/cast/common/certificate/cast_crl_root_ca_cert_der-inc.h
@@ -0,0 +1,155 @@
+// 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.
+
+#ifndef CAST_COMMON_CERTIFICATE_CAST_CRL_ROOT_CA_CERT_DER_INC_H_
+#define CAST_COMMON_CERTIFICATE_CAST_CRL_ROOT_CA_CERT_DER_INC_H_
+
+// Certificate:
+// Data:
+// Version: 3 (0x2)
+// Serial Number: 153 (0x99)
+// Signature Algorithm: sha256WithRSAEncryption
+// Issuer: C=US, ST=California, L=Mountain View, O=Google Inc,
+// OU=Cast, CN=Cast CRL Root CA
+// Validity
+// Not Before: Aug 1 21:47:47 2016 GMT
+// Not After : Jul 27 21:47:47 2036 GMT
+// Subject: C=US, ST=California, L=Mountain View, O=Google Inc, OU=Cast,
+// CN=Cast CRL Root CA
+// Subject Public Key Info:
+// Public Key Algorithm: rsaEncryption
+// Public-Key: (2048 bit)
+// Modulus:
+// 00:c2:7f:c0:09:21:d3:60:89:28:b5:96:6e:fe:a6:
+// ad:fe:ae:e0:66:35:bd:99:6e:e8:93:85:29:ba:de:
+// 44:5d:a8:6b:fc:e6:cc:37:dd:1d:0f:cf:1e:3a:32:
+// 2c:7f:e0:1b:c9:bb:4c:34:a9:1c:97:b5:f8:6d:42:
+// 9c:4d:06:6a:a0:2d:95:55:3f:78:1d:5c:ab:e9:3a:
+// a6:08:3b:5a:af:f4:ab:53:77:14:9a:6b:b2:37:2e:
+// cd:6e:ea:bc:22:5d:56:55:73:fd:bd:03:2f:54:5e:
+// 7f:8b:c1:74:36:1a:18:1f:64:de:bf:08:80:4a:12:
+// 0c:49:53:b8:c7:3b:db:5f:dc:59:77:2f:b8:3a:05:
+// 8a:f6:b7:47:2a:9b:74:63:08:31:12:e6:7b:44:d1:
+// c1:7c:c8:87:b8:50:63:6d:9f:d7:ba:36:53:72:47:
+// 5f:dc:43:43:eb:d7:2e:11:d1:8a:7a:a4:03:f2:6a:
+// d3:88:e6:a7:b8:9d:81:b2:b0:88:24:c8:a1:fa:b0:
+// aa:db:08:64:3e:8b:2a:07:5c:5a:82:05:99:c2:d5:
+// ca:52:75:21:a7:fa:c5:a1:da:ac:f7:fe:d0:c7:44:
+// 76:9a:eb:6b:d3:bd:f4:7a:31:a6:ad:2f:5a:c4:31:
+// 3a:6d:f1:dd:7b:44:81:37:cf:13:85:5d:96:ae:7b:
+// 96:2b
+// Exponent: 65537 (0x10001)
+// X509v3 extensions:
+// X509v3 Basic Constraints:
+// CA:TRUE, pathlen:1
+// X509v3 Subject Key Identifier:
+// 1A:65:12:B4:A9:B9:B4:FC:91:0C:9E:67:E0:5B:D9:C9:AD:44:1C:B9
+// X509v3 Authority Key Identifier:
+// keyid:1A:65:12:B4:A9:B9:B4:FC:91:0C:9E:67:E0:5B:D9:C9:AD:44
+// :1C:B9
+//
+// X509v3 Key Usage:
+// Certificate Sign
+// Signature Algorithm: sha256WithRSAEncryption
+// af:5f:8b:c0:f7:c5:26:88:b9:ac:f7:ec:4d:0f:76:ab:e2:74:
+// 9a:44:3c:33:f6:74:3d:04:2a:59:76:a2:05:27:c4:e3:a2:c8:
+// c2:af:7e:fd:be:b9:ca:e9:5b:a8:2a:cd:a7:1e:0e:37:f1:6f:
+// 84:5e:aa:42:1f:ba:f0:44:ba:db:87:61:68:91:bb:1d:5c:3a:
+// f0:8e:02:20:76:aa:47:99:c7:73:0d:90:32:4a:b9:e3:fd:11:
+// 8b:5d:bd:22:4d:05:75:17:61:a2:a6:4f:b0:3d:52:8e:aa:c9:
+// b4:8d:05:5a:1c:36:c1:7b:87:f7:f8:e4:81:36:27:ec:35:ae:
+// b9:ce:15:47:e1:10:c9:16:69:3a:22:8e:63:18:31:cc:3b:56:
+// 69:c6:d4:24:dd:95:25:cf:34:e6:00:ae:e1:87:1e:ee:0c:14:
+// dc:0d:82:81:31:1f:8f:6d:d2:c0:e1:7c:12:f7:9d:ca:02:e3:
+// 76:36:44:53:3a:87:71:7d:ed:32:4c:a4:96:e6:e5:2c:c7:0d:
+// b7:96:c0:f3:7d:e5:58:32:f7:25:25:c0:13:76:d0:76:6c:73:
+// ab:3d:15:cd:c5:e8:85:15:9a:02:52:e9:61:41:e2:66:01:c5:
+// 71:e5:db:c0:a5:b3:4c:1e:ac:93:8a:35:4c:4d:da:57:22:24:
+// 1d:3a:f6:bd
+const unsigned char kCastCRLRootCaDer[] = {
+ 0x30, 0x82, 0x03, 0xce, 0x30, 0x82, 0x02, 0xb6, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x02, 0x00, 0x99, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
+ 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x79, 0x31, 0x0b,
+ 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+ 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x0a, 0x43, 0x61,
+ 0x6c, 0x69, 0x66, 0x6f, 0x72, 0x6e, 0x69, 0x61, 0x31, 0x16, 0x30, 0x14,
+ 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x0d, 0x4d, 0x6f, 0x75, 0x6e, 0x74,
+ 0x61, 0x69, 0x6e, 0x20, 0x56, 0x69, 0x65, 0x77, 0x31, 0x13, 0x30, 0x11,
+ 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0a, 0x47, 0x6f, 0x6f, 0x67, 0x6c,
+ 0x65, 0x20, 0x49, 0x6e, 0x63, 0x31, 0x0d, 0x30, 0x0b, 0x06, 0x03, 0x55,
+ 0x04, 0x0b, 0x0c, 0x04, 0x43, 0x61, 0x73, 0x74, 0x31, 0x19, 0x30, 0x17,
+ 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x10, 0x43, 0x61, 0x73, 0x74, 0x20,
+ 0x43, 0x52, 0x4c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30,
+ 0x1e, 0x17, 0x0d, 0x31, 0x36, 0x30, 0x38, 0x30, 0x31, 0x32, 0x31, 0x34,
+ 0x37, 0x34, 0x37, 0x5a, 0x17, 0x0d, 0x33, 0x36, 0x30, 0x37, 0x32, 0x37,
+ 0x32, 0x31, 0x34, 0x37, 0x34, 0x37, 0x5a, 0x30, 0x79, 0x31, 0x0b, 0x30,
+ 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13,
+ 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x0a, 0x43, 0x61, 0x6c,
+ 0x69, 0x66, 0x6f, 0x72, 0x6e, 0x69, 0x61, 0x31, 0x16, 0x30, 0x14, 0x06,
+ 0x03, 0x55, 0x04, 0x07, 0x0c, 0x0d, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x61,
+ 0x69, 0x6e, 0x20, 0x56, 0x69, 0x65, 0x77, 0x31, 0x13, 0x30, 0x11, 0x06,
+ 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0a, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
+ 0x20, 0x49, 0x6e, 0x63, 0x31, 0x0d, 0x30, 0x0b, 0x06, 0x03, 0x55, 0x04,
+ 0x0b, 0x0c, 0x04, 0x43, 0x61, 0x73, 0x74, 0x31, 0x19, 0x30, 0x17, 0x06,
+ 0x03, 0x55, 0x04, 0x03, 0x0c, 0x10, 0x43, 0x61, 0x73, 0x74, 0x20, 0x43,
+ 0x52, 0x4c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, 0x82,
+ 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+ 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82,
+ 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xc2, 0x7f, 0xc0, 0x09, 0x21,
+ 0xd3, 0x60, 0x89, 0x28, 0xb5, 0x96, 0x6e, 0xfe, 0xa6, 0xad, 0xfe, 0xae,
+ 0xe0, 0x66, 0x35, 0xbd, 0x99, 0x6e, 0xe8, 0x93, 0x85, 0x29, 0xba, 0xde,
+ 0x44, 0x5d, 0xa8, 0x6b, 0xfc, 0xe6, 0xcc, 0x37, 0xdd, 0x1d, 0x0f, 0xcf,
+ 0x1e, 0x3a, 0x32, 0x2c, 0x7f, 0xe0, 0x1b, 0xc9, 0xbb, 0x4c, 0x34, 0xa9,
+ 0x1c, 0x97, 0xb5, 0xf8, 0x6d, 0x42, 0x9c, 0x4d, 0x06, 0x6a, 0xa0, 0x2d,
+ 0x95, 0x55, 0x3f, 0x78, 0x1d, 0x5c, 0xab, 0xe9, 0x3a, 0xa6, 0x08, 0x3b,
+ 0x5a, 0xaf, 0xf4, 0xab, 0x53, 0x77, 0x14, 0x9a, 0x6b, 0xb2, 0x37, 0x2e,
+ 0xcd, 0x6e, 0xea, 0xbc, 0x22, 0x5d, 0x56, 0x55, 0x73, 0xfd, 0xbd, 0x03,
+ 0x2f, 0x54, 0x5e, 0x7f, 0x8b, 0xc1, 0x74, 0x36, 0x1a, 0x18, 0x1f, 0x64,
+ 0xde, 0xbf, 0x08, 0x80, 0x4a, 0x12, 0x0c, 0x49, 0x53, 0xb8, 0xc7, 0x3b,
+ 0xdb, 0x5f, 0xdc, 0x59, 0x77, 0x2f, 0xb8, 0x3a, 0x05, 0x8a, 0xf6, 0xb7,
+ 0x47, 0x2a, 0x9b, 0x74, 0x63, 0x08, 0x31, 0x12, 0xe6, 0x7b, 0x44, 0xd1,
+ 0xc1, 0x7c, 0xc8, 0x87, 0xb8, 0x50, 0x63, 0x6d, 0x9f, 0xd7, 0xba, 0x36,
+ 0x53, 0x72, 0x47, 0x5f, 0xdc, 0x43, 0x43, 0xeb, 0xd7, 0x2e, 0x11, 0xd1,
+ 0x8a, 0x7a, 0xa4, 0x03, 0xf2, 0x6a, 0xd3, 0x88, 0xe6, 0xa7, 0xb8, 0x9d,
+ 0x81, 0xb2, 0xb0, 0x88, 0x24, 0xc8, 0xa1, 0xfa, 0xb0, 0xaa, 0xdb, 0x08,
+ 0x64, 0x3e, 0x8b, 0x2a, 0x07, 0x5c, 0x5a, 0x82, 0x05, 0x99, 0xc2, 0xd5,
+ 0xca, 0x52, 0x75, 0x21, 0xa7, 0xfa, 0xc5, 0xa1, 0xda, 0xac, 0xf7, 0xfe,
+ 0xd0, 0xc7, 0x44, 0x76, 0x9a, 0xeb, 0x6b, 0xd3, 0xbd, 0xf4, 0x7a, 0x31,
+ 0xa6, 0xad, 0x2f, 0x5a, 0xc4, 0x31, 0x3a, 0x6d, 0xf1, 0xdd, 0x7b, 0x44,
+ 0x81, 0x37, 0xcf, 0x13, 0x85, 0x5d, 0x96, 0xae, 0x7b, 0x96, 0x2b, 0x02,
+ 0x03, 0x01, 0x00, 0x01, 0xa3, 0x60, 0x30, 0x5e, 0x30, 0x0f, 0x06, 0x03,
+ 0x55, 0x1d, 0x13, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01,
+ 0x01, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+ 0x1a, 0x65, 0x12, 0xb4, 0xa9, 0xb9, 0xb4, 0xfc, 0x91, 0x0c, 0x9e, 0x67,
+ 0xe0, 0x5b, 0xd9, 0xc9, 0xad, 0x44, 0x1c, 0xb9, 0x30, 0x1f, 0x06, 0x03,
+ 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x1a, 0x65, 0x12,
+ 0xb4, 0xa9, 0xb9, 0xb4, 0xfc, 0x91, 0x0c, 0x9e, 0x67, 0xe0, 0x5b, 0xd9,
+ 0xc9, 0xad, 0x44, 0x1c, 0xb9, 0x30, 0x0b, 0x06, 0x03, 0x55, 0x1d, 0x0f,
+ 0x04, 0x04, 0x03, 0x02, 0x02, 0x04, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01,
+ 0x01, 0x00, 0xaf, 0x5f, 0x8b, 0xc0, 0xf7, 0xc5, 0x26, 0x88, 0xb9, 0xac,
+ 0xf7, 0xec, 0x4d, 0x0f, 0x76, 0xab, 0xe2, 0x74, 0x9a, 0x44, 0x3c, 0x33,
+ 0xf6, 0x74, 0x3d, 0x04, 0x2a, 0x59, 0x76, 0xa2, 0x05, 0x27, 0xc4, 0xe3,
+ 0xa2, 0xc8, 0xc2, 0xaf, 0x7e, 0xfd, 0xbe, 0xb9, 0xca, 0xe9, 0x5b, 0xa8,
+ 0x2a, 0xcd, 0xa7, 0x1e, 0x0e, 0x37, 0xf1, 0x6f, 0x84, 0x5e, 0xaa, 0x42,
+ 0x1f, 0xba, 0xf0, 0x44, 0xba, 0xdb, 0x87, 0x61, 0x68, 0x91, 0xbb, 0x1d,
+ 0x5c, 0x3a, 0xf0, 0x8e, 0x02, 0x20, 0x76, 0xaa, 0x47, 0x99, 0xc7, 0x73,
+ 0x0d, 0x90, 0x32, 0x4a, 0xb9, 0xe3, 0xfd, 0x11, 0x8b, 0x5d, 0xbd, 0x22,
+ 0x4d, 0x05, 0x75, 0x17, 0x61, 0xa2, 0xa6, 0x4f, 0xb0, 0x3d, 0x52, 0x8e,
+ 0xaa, 0xc9, 0xb4, 0x8d, 0x05, 0x5a, 0x1c, 0x36, 0xc1, 0x7b, 0x87, 0xf7,
+ 0xf8, 0xe4, 0x81, 0x36, 0x27, 0xec, 0x35, 0xae, 0xb9, 0xce, 0x15, 0x47,
+ 0xe1, 0x10, 0xc9, 0x16, 0x69, 0x3a, 0x22, 0x8e, 0x63, 0x18, 0x31, 0xcc,
+ 0x3b, 0x56, 0x69, 0xc6, 0xd4, 0x24, 0xdd, 0x95, 0x25, 0xcf, 0x34, 0xe6,
+ 0x00, 0xae, 0xe1, 0x87, 0x1e, 0xee, 0x0c, 0x14, 0xdc, 0x0d, 0x82, 0x81,
+ 0x31, 0x1f, 0x8f, 0x6d, 0xd2, 0xc0, 0xe1, 0x7c, 0x12, 0xf7, 0x9d, 0xca,
+ 0x02, 0xe3, 0x76, 0x36, 0x44, 0x53, 0x3a, 0x87, 0x71, 0x7d, 0xed, 0x32,
+ 0x4c, 0xa4, 0x96, 0xe6, 0xe5, 0x2c, 0xc7, 0x0d, 0xb7, 0x96, 0xc0, 0xf3,
+ 0x7d, 0xe5, 0x58, 0x32, 0xf7, 0x25, 0x25, 0xc0, 0x13, 0x76, 0xd0, 0x76,
+ 0x6c, 0x73, 0xab, 0x3d, 0x15, 0xcd, 0xc5, 0xe8, 0x85, 0x15, 0x9a, 0x02,
+ 0x52, 0xe9, 0x61, 0x41, 0xe2, 0x66, 0x01, 0xc5, 0x71, 0xe5, 0xdb, 0xc0,
+ 0xa5, 0xb3, 0x4c, 0x1e, 0xac, 0x93, 0x8a, 0x35, 0x4c, 0x4d, 0xda, 0x57,
+ 0x22, 0x24, 0x1d, 0x3a, 0xf6, 0xbd,
+};
+
+#endif // CAST_COMMON_CERTIFICATE_CAST_CRL_ROOT_CA_CERT_DER_INC_H_
diff --git a/cast/common/certificate/cast_crl_unittest.cc b/cast/common/certificate/cast_crl_unittest.cc
new file mode 100644
index 00000000..2b17ea73
--- /dev/null
+++ b/cast/common/certificate/cast_crl_unittest.cc
@@ -0,0 +1,230 @@
+// 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 <fcntl.h>
+#include <unistd.h>
+
+#include "cast/common/certificate/cast_cert_validator.h"
+#include "cast/common/certificate/cast_cert_validator_internal.h"
+#include "cast/common/certificate/proto/test_suite.pb.h"
+#include "cast/common/certificate/test_helpers.h"
+#include "gtest/gtest.h"
+#include "platform/api/logging.h"
+
+namespace cast {
+namespace certificate {
+namespace {
+
+using CastCertError = openscreen::Error::Code;
+
+// Indicates the expected result of test step's verification.
+enum TestStepResult {
+ kResultSuccess,
+ kResultFail,
+};
+
+// Verifies that the provided certificate chain is valid at the specified time
+// and chains up to a trust anchor.
+bool TestVerifyCertificate(TestStepResult expected_result,
+ const std::vector<std::string>& der_certs,
+ const DateTime& time,
+ TrustStore* cast_trust_store) {
+ std::unique_ptr<CertVerificationContext> context;
+ CastDeviceCertPolicy policy;
+ openscreen::Error result =
+ VerifyDeviceCert(der_certs, time, &context, &policy, nullptr,
+ CRLPolicy::kCrlOptional, cast_trust_store);
+ bool success = (result.code() == CastCertError::kNone) ==
+ (expected_result == kResultSuccess);
+ EXPECT_TRUE(success);
+ return success;
+}
+
+// Verifies that the provided Cast CRL is signed by a trusted issuer
+// and that the CRL can be parsed successfully.
+// The validity of the CRL is also checked at the specified time.
+bool TestVerifyCRL(TestStepResult expected_result,
+ const std::string& crl_bundle,
+ const DateTime& time,
+ TrustStore* crl_trust_store) {
+ std::unique_ptr<CastCRL> crl =
+ ParseAndVerifyCRL(crl_bundle, time, crl_trust_store);
+
+ bool success = (crl != nullptr) == (expected_result == kResultSuccess);
+ EXPECT_TRUE(success);
+ return success;
+}
+
+// Verifies that the certificate chain provided is not revoked according to
+// the provided Cast CRL at |cert_time|.
+// The provided CRL is verified at |crl_time|.
+// If |crl_required| is set, then a valid Cast CRL must be provided.
+// Otherwise, a missing CRL is be ignored.
+bool TestVerifyRevocation(CastCertError expected_result,
+ const std::vector<std::string>& der_certs,
+ const std::string& crl_bundle,
+ const DateTime& crl_time,
+ const DateTime& cert_time,
+ bool crl_required,
+ TrustStore* cast_trust_store,
+ TrustStore* crl_trust_store) {
+ std::unique_ptr<CastCRL> crl;
+ if (!crl_bundle.empty()) {
+ crl = ParseAndVerifyCRL(crl_bundle, crl_time, crl_trust_store);
+ EXPECT_NE(crl.get(), nullptr);
+ }
+
+ std::unique_ptr<CertVerificationContext> context;
+ CastDeviceCertPolicy policy;
+ CRLPolicy crl_policy =
+ crl_required ? CRLPolicy::kCrlRequired : CRLPolicy::kCrlOptional;
+ openscreen::Error result =
+ VerifyDeviceCert(der_certs, cert_time, &context, &policy, crl.get(),
+ crl_policy, cast_trust_store);
+ EXPECT_EQ(expected_result, result.code());
+ return expected_result == result.code();
+}
+
+TrustStore CreateTrustStoreFromPemFile(const std::string& filename) {
+ TrustStore store;
+
+ std::vector<std::string> certs =
+ testing::ReadCertificatesFromPemFile(filename);
+ for (const auto& der_cert : certs) {
+ const uint8_t* data = (const uint8_t*)der_cert.data();
+ store.certs.emplace_back(d2i_X509(nullptr, &data, der_cert.size()));
+ }
+ return store;
+}
+
+#define TEST_DATA_PREFIX "test/data/cast/common/certificate/"
+
+bool RunTest(const DeviceCertTest& test_case) {
+ TrustStore crl_trust_store_local;
+ TrustStore cast_trust_store_local;
+ TrustStore* crl_trust_store = nullptr;
+ TrustStore* cast_trust_store = nullptr;
+ if (test_case.use_test_trust_anchors()) {
+ crl_trust_store_local = CreateTrustStoreFromPemFile(
+ TEST_DATA_PREFIX "certificates/cast_crl_test_root_ca.pem");
+ cast_trust_store_local = CreateTrustStoreFromPemFile(
+ TEST_DATA_PREFIX "certificates/cast_test_root_ca.pem");
+ crl_trust_store = &crl_trust_store_local;
+ cast_trust_store = &cast_trust_store_local;
+
+ EXPECT_FALSE(crl_trust_store_local.certs.empty());
+ EXPECT_FALSE(cast_trust_store_local.certs.empty());
+ }
+
+ std::vector<std::string> der_cert_path;
+ for (const auto& cert : test_case.der_cert_path()) {
+ der_cert_path.push_back(cert);
+ }
+
+ DateTime cert_verification_time;
+ EXPECT_TRUE(ConvertTimeSeconds(test_case.cert_verification_time_seconds(),
+ &cert_verification_time));
+
+ uint64_t crl_verify_time = test_case.crl_verification_time_seconds();
+ DateTime crl_verification_time;
+ EXPECT_TRUE(ConvertTimeSeconds(crl_verify_time, &crl_verification_time));
+ if (crl_verify_time == 0) {
+ crl_verification_time = cert_verification_time;
+ }
+
+ std::string crl_bundle = test_case.crl_bundle();
+ switch (test_case.expected_result()) {
+ case PATH_VERIFICATION_FAILED:
+ return TestVerifyCertificate(kResultFail, der_cert_path,
+ cert_verification_time, cast_trust_store);
+ case CRL_VERIFICATION_FAILED:
+ return TestVerifyCRL(kResultFail, crl_bundle, crl_verification_time,
+ crl_trust_store);
+ case REVOCATION_CHECK_FAILED_WITHOUT_CRL:
+ return TestVerifyCertificate(kResultSuccess, der_cert_path,
+ cert_verification_time, cast_trust_store) &&
+ TestVerifyCRL(kResultFail, crl_bundle, crl_verification_time,
+ crl_trust_store) &&
+ TestVerifyRevocation(CastCertError::kErrCrlInvalid, der_cert_path,
+ crl_bundle, crl_verification_time,
+ cert_verification_time, true,
+ cast_trust_store, crl_trust_store);
+ case CRL_EXPIRED_AFTER_INITIAL_VERIFICATION: // fallthrough
+ case REVOCATION_CHECK_FAILED:
+ return TestVerifyCertificate(kResultSuccess, der_cert_path,
+ cert_verification_time, cast_trust_store) &&
+ TestVerifyCRL(kResultSuccess, crl_bundle, crl_verification_time,
+ crl_trust_store) &&
+ TestVerifyRevocation(CastCertError::kErrCertsRevoked,
+ der_cert_path, crl_bundle,
+ crl_verification_time, cert_verification_time,
+ false, cast_trust_store, crl_trust_store);
+ case SUCCESS:
+ return (crl_bundle.empty() ||
+ TestVerifyCRL(kResultSuccess, crl_bundle, crl_verification_time,
+ crl_trust_store)) &&
+ TestVerifyCertificate(kResultSuccess, der_cert_path,
+ cert_verification_time, cast_trust_store) &&
+ TestVerifyRevocation(CastCertError::kNone, der_cert_path,
+ crl_bundle, crl_verification_time,
+ cert_verification_time, !crl_bundle.empty(),
+ cast_trust_store, crl_trust_store);
+ case UNSPECIFIED:
+ return false;
+ }
+ return false;
+}
+
+std::string ReadEntireFileToString(const std::string& filename) {
+ int fd = open(filename.c_str(), O_RDONLY);
+ if (fd == -1) {
+ return {};
+ }
+ off_t file_size = lseek(fd, 0, SEEK_END);
+ lseek(fd, 0, SEEK_SET);
+ std::string contents(file_size, 0);
+ off_t bytes_read = 0;
+ while (bytes_read < file_size) {
+ int ret = read(fd, &contents[bytes_read], file_size - bytes_read);
+ if (ret == -1 && errno != EINTR) {
+ return {};
+ } else {
+ bytes_read += ret;
+ }
+ }
+ close(fd);
+
+ return contents;
+}
+
+// Parses the provided test suite provided in wire-format proto.
+// Each test contains the inputs and the expected output.
+// To see the description of the test, execute the test.
+// These tests are generated by a test generator in google3.
+void RunTestSuite(const std::string& test_suite_file_name) {
+ std::string testsuite_raw = ReadEntireFileToString(test_suite_file_name);
+ ASSERT_FALSE(testsuite_raw.empty());
+ DeviceCertTestSuite test_suite;
+ ASSERT_TRUE(test_suite.ParseFromString(testsuite_raw));
+ int successes = 0;
+
+ for (auto const& test_case : test_suite.tests()) {
+ bool result = RunTest(test_case);
+ successes += result;
+ EXPECT_TRUE(result) << test_case.description();
+ }
+ OSP_LOG_IF(ERROR, successes != test_suite.tests().size())
+ << "successes: " << successes
+ << ", failures: " << (test_suite.tests().size() - successes);
+}
+
+TEST(CastCertificateTest, TestSuite1) {
+ RunTestSuite(TEST_DATA_PREFIX "testsuite/testsuite1.pb");
+}
+
+} // namespace
+} // namespace certificate
+} // namespace cast
diff --git a/cast/common/certificate/proto/BUILD.gn b/cast/common/certificate/proto/BUILD.gn
index a4eaee19..987a9f54 100644
--- a/cast/common/certificate/proto/BUILD.gn
+++ b/cast/common/certificate/proto/BUILD.gn
@@ -1,4 +1,4 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
+# 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.
@@ -11,6 +11,7 @@ proto_library("proto") {
}
proto_library("unittest_proto") {
+ testonly = true
sources = [
"test_suite.proto",
]
diff --git a/cast/common/certificate/proto/revocation.proto b/cast/common/certificate/proto/revocation.proto
index c45098e5..cc0b0ada 100644
--- a/cast/common/certificate/proto/revocation.proto
+++ b/cast/common/certificate/proto/revocation.proto
@@ -8,7 +8,7 @@
syntax = "proto2";
-package cast_certificate;
+package cast.certificate;
option optimize_for = LITE_RUNTIME;
diff --git a/cast/common/certificate/proto/test_suite.proto b/cast/common/certificate/proto/test_suite.proto
index 8522ca6d..8a883d94 100644
--- a/cast/common/certificate/proto/test_suite.proto
+++ b/cast/common/certificate/proto/test_suite.proto
@@ -4,7 +4,7 @@
syntax = "proto2";
-package cast_certificate;
+package cast.certificate;
option optimize_for = LITE_RUNTIME;
diff --git a/cast/common/certificate/test_helpers.cc b/cast/common/certificate/test_helpers.cc
new file mode 100644
index 00000000..d64f10c4
--- /dev/null
+++ b/cast/common/certificate/test_helpers.cc
@@ -0,0 +1,45 @@
+// 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/test_helpers.h"
+
+#include <openssl/pem.h>
+#include <stdio.h>
+#include <string.h>
+
+namespace cast {
+namespace certificate {
+namespace testing {
+
+std::vector<std::string> ReadCertificatesFromPemFile(
+ const std::string& filename) {
+ FILE* fp = fopen(filename.c_str(), "r");
+ if (!fp) {
+ return {};
+ }
+ std::vector<std::string> certs;
+#define STRCMP_LITERAL(s, l) strncmp(s, l, sizeof(l))
+ for (;;) {
+ char* name;
+ char* header;
+ unsigned char* data;
+ long length;
+ if (PEM_read(fp, &name, &header, &data, &length) == 1) {
+ if (STRCMP_LITERAL(name, "CERTIFICATE") == 0) {
+ certs.emplace_back((char*)data, length);
+ }
+ OPENSSL_free(name);
+ OPENSSL_free(header);
+ OPENSSL_free(data);
+ } else {
+ break;
+ }
+ }
+ fclose(fp);
+ return certs;
+}
+
+} // namespace testing
+} // namespace certificate
+} // namespace cast
diff --git a/cast/common/certificate/test_helpers.h b/cast/common/certificate/test_helpers.h
new file mode 100644
index 00000000..75a3301f
--- /dev/null
+++ b/cast/common/certificate/test_helpers.h
@@ -0,0 +1,22 @@
+// 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.
+
+#ifndef CAST_COMMON_CERTIFICATE_TEST_HELPERS_H_
+#define CAST_COMMON_CERTIFICATE_TEST_HELPERS_H_
+
+#include <string>
+#include <vector>
+
+namespace cast {
+namespace certificate {
+namespace testing {
+
+std::vector<std::string> ReadCertificatesFromPemFile(
+ const std::string& filename);
+
+} // namespace testing
+} // namespace certificate
+} // namespace cast
+
+#endif // CAST_COMMON_CERTIFICATE_TEST_HELPERS_H_
diff --git a/cast/common/certificate/types.h b/cast/common/certificate/types.h
new file mode 100644
index 00000000..2691c691
--- /dev/null
+++ b/cast/common/certificate/types.h
@@ -0,0 +1,30 @@
+// 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.
+
+#ifndef CAST_COMMON_CERTIFICATE_TYPES_H_
+#define CAST_COMMON_CERTIFICATE_TYPES_H_
+
+#include <stdint.h>
+
+namespace cast {
+namespace certificate {
+
+struct ConstDataSpan {
+ const uint8_t* data;
+ uint32_t length;
+};
+
+struct DateTime {
+ uint16_t year;
+ uint8_t month;
+ uint8_t day;
+ uint8_t hour;
+ uint8_t minute;
+ uint8_t second;
+};
+
+} // namespace certificate
+} // namespace cast
+
+#endif // CAST_COMMON_CERTIFICATE_TYPES_H_
diff --git a/test/data/cast/common/certificate/certificates/cast_crl_test_root_ca.pem b/test/data/cast/common/certificate/certificates/cast_crl_test_root_ca.pem
new file mode 100644
index 00000000..95d8cf7b
--- /dev/null
+++ b/test/data/cast/common/certificate/certificates/cast_crl_test_root_ca.pem
@@ -0,0 +1,84 @@
+$ openssl x509 -text -noout < [CERTIFICATE]
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 145 (0x91)
+ Signature Algorithm: sha256WithRSAEncryption
+ Issuer: C=US, ST=California, L=Mountain View, O=Google Inc, OU=Cast, CN=Cast CRL Test Untrusted Root CA
+ Validity
+ Not Before: May 25 21:00:06 2016 GMT
+ Not After : May 20 21:00:06 2036 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Google Inc, OU=Cast, CN=Cast CRL Test Untrusted Root CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:b5:c1:be:d4:34:2c:d7:df:99:21:a9:4b:56:c3:
+ 2e:78:21:0d:a8:a9:a0:4c:2d:62:cb:4f:c5:a0:a7:
+ 47:1c:9d:2a:eb:61:d1:d4:cc:24:23:e5:11:0f:18:
+ 81:2a:56:41:30:6f:84:04:09:ba:0f:ec:d7:2f:c6:
+ c4:c4:91:2d:ca:6d:76:54:b2:b2:5a:59:c1:3d:17:
+ f2:7f:80:22:b9:39:c5:c0:74:74:9d:70:b9:c7:60:
+ e3:84:95:a0:49:ab:6d:8f:cc:b9:3a:c9:dd:4f:50:
+ 37:9e:b0:c0:6f:22:8c:a9:82:56:26:8f:e1:b3:e6:
+ b4:b9:be:4d:83:e0:f4:d4:2f:10:9b:7f:c9:4f:77:
+ 6f:a0:02:34:1a:ce:be:e3:0e:25:03:ba:db:b1:bc:
+ fa:ec:01:c2:c0:f4:f5:55:b9:7b:ef:c0:8a:52:fe:
+ f5:07:cf:2d:fa:37:fe:4c:54:f2:87:e9:32:ee:04:
+ 6c:d6:fa:fc:51:94:21:e5:43:cb:89:02:07:b3:5b:
+ dc:09:18:cc:55:9c:89:3e:ff:32:6c:93:87:f7:18:
+ 73:7c:6f:ca:57:41:bd:d2:5b:12:60:75:a7:44:c3:
+ 78:35:98:12:b6:1e:3c:25:72:13:2c:9d:fa:74:81:
+ 11:28:cc:55:93:46:42:17:5a:46:ee:57:17:26:83:
+ 4c:1b
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints:
+ CA:TRUE, pathlen:1
+ X509v3 Subject Key Identifier:
+ 56:47:A3:12:A6:DB:B7:F3:F1:E4:68:62:CB:01:45:FD:2B:02:73:68
+ X509v3 Authority Key Identifier:
+ keyid:56:47:A3:12:A6:DB:B7:F3:F1:E4:68:62:CB:01:45:FD:2B:02:73:68
+
+ X509v3 Key Usage:
+ Certificate Sign
+ Signature Algorithm: sha256WithRSAEncryption
+ 22:ce:56:31:cd:e0:ac:b4:af:70:8a:0a:60:4d:a8:16:81:11:
+ cf:b6:cf:89:87:86:ec:8e:72:b3:bc:01:b4:29:b1:88:48:65:
+ cd:7c:8f:a0:6f:05:b8:60:28:60:1b:8d:61:eb:e5:a5:c0:b5:
+ 11:8d:4d:73:73:3a:82:a9:39:fe:5a:54:28:69:1c:ec:9e:b7:
+ 1c:3d:02:d1:33:1c:82:cc:14:0d:c9:c7:ab:7d:c0:89:31:ff:
+ 02:17:8a:d0:37:e5:dc:03:34:d5:07:a7:0f:8c:ec:3e:47:5d:
+ a9:e9:12:6d:0c:8d:89:27:73:9c:4c:88:97:66:9f:4f:76:5b:
+ af:e6:40:de:83:b1:de:35:2e:ae:9e:91:da:a3:37:a5:89:2a:
+ 57:a9:98:40:f2:e5:8e:ad:44:4d:f6:c0:55:c8:71:dc:a5:81:
+ 4a:3b:17:3c:fd:40:77:0c:26:65:da:ec:da:97:19:a0:16:99:
+ 11:d0:2b:0d:2c:58:30:9a:76:18:4f:d0:3c:24:4b:96:51:7e:
+ 82:ce:a1:6c:fa:fb:5a:3c:c2:1d:3c:27:f2:ea:17:2c:5e:ab:
+ 91:45:d3:35:d0:dc:a7:41:e0:e3:3e:23:db:63:a8:fc:fb:76:
+ 37:59:f8:b2:31:e5:6d:cf:fa:17:84:d9:16:2f:97:90:8c:83:
+ f2:6f:82:ed
+-----BEGIN CERTIFICATE-----
+MIID7jCCAtagAwIBAgICAJEwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVT
+MRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRMw
+EQYDVQQKDApHb29nbGUgSW5jMQ0wCwYDVQQLDARDYXN0MSgwJgYDVQQDDB9DYXN0
+IENSTCBUZXN0IFVudHJ1c3RlZCBSb290IENBMB4XDTE2MDUyNTIxMDAwNloXDTM2
+MDUyMDIxMDAwNlowgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh
+MRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKDApHb29nbGUgSW5jMQ0w
+CwYDVQQLDARDYXN0MSgwJgYDVQQDDB9DYXN0IENSTCBUZXN0IFVudHJ1c3RlZCBS
+b290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtcG+1DQs19+Z
+IalLVsMueCENqKmgTC1iy0/FoKdHHJ0q62HR1MwkI+URDxiBKlZBMG+EBAm6D+zX
+L8bExJEtym12VLKyWlnBPRfyf4AiuTnFwHR0nXC5x2DjhJWgSattj8y5OsndT1A3
+nrDAbyKMqYJWJo/hs+a0ub5Ng+D01C8Qm3/JT3dvoAI0Gs6+4w4lA7rbsbz67AHC
+wPT1Vbl778CKUv71B88t+jf+TFTyh+ky7gRs1vr8UZQh5UPLiQIHs1vcCRjMVZyJ
+Pv8ybJOH9xhzfG/KV0G90lsSYHWnRMN4NZgSth48JXITLJ36dIERKMxVk0ZCF1pG
+7lcXJoNMGwIDAQABo2AwXjAPBgNVHRMECDAGAQH/AgEBMB0GA1UdDgQWBBRWR6MS
+ptu38/HkaGLLAUX9KwJzaDAfBgNVHSMEGDAWgBRWR6MSptu38/HkaGLLAUX9KwJz
+aDALBgNVHQ8EBAMCAgQwDQYJKoZIhvcNAQELBQADggEBACLOVjHN4Ky0r3CKCmBN
+qBaBEc+2z4mHhuyOcrO8AbQpsYhIZc18j6BvBbhgKGAbjWHr5aXAtRGNTXNzOoKp
+Of5aVChpHOyetxw9AtEzHILMFA3Jx6t9wIkx/wIXitA35dwDNNUHpw+M7D5HXanp
+Em0MjYknc5xMiJdmn092W6/mQN6Dsd41Lq6ekdqjN6WJKlepmEDy5Y6tRE32wFXI
+cdylgUo7Fzz9QHcMJmXa7NqXGaAWmRHQKw0sWDCadhhP0DwkS5ZRfoLOoWz6+1o8
+wh08J/LqFyxeq5FF0zXQ3KdB4OM+I9tjqPz7djdZ+LIx5W3P+heE2RYvl5CMg/Jv
+gu0=
+-----END CERTIFICATE-----
diff --git a/test/data/cast/common/certificate/certificates/cast_test_root_ca.pem b/test/data/cast/common/certificate/certificates/cast_test_root_ca.pem
new file mode 100644
index 00000000..c3131373
--- /dev/null
+++ b/test/data/cast/common/certificate/certificates/cast_test_root_ca.pem
@@ -0,0 +1,83 @@
+$ openssl x509 -text -noout < [CERTIFICATE]
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 28 (0x1c)
+ Signature Algorithm: sha256WithRSAEncryption
+ Issuer: C=US, ST=California, L=Mountain View, O=Google Inc, OU=Cast, CN=Cast Test Untrusted Root CA
+ Validity
+ Not Before: Jan 21 23:41:26 2015 GMT
+ Not After : Jan 16 23:41:26 2035 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Google Inc, OU=Cast, CN=Cast Test Untrusted Root CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:c1:e4:4c:2e:e0:ac:5c:da:e1:b7:e0:fe:f5:19:
+ 2a:6b:eb:b5:8d:9c:dd:f6:d9:5f:84:64:f8:a9:4b:
+ 07:55:78:73:f7:14:38:38:94:13:70:d9:87:23:d4:
+ 91:c2:93:d1:3f:86:ad:a7:f6:39:e9:78:c0:b8:3e:
+ 32:40:ec:83:4c:a2:47:96:75:af:fc:1d:35:1b:00:
+ da:9a:a5:6c:cd:1b:04:0a:e6:bd:a9:e4:5d:67:71:
+ 7d:60:c8:e1:59:27:c3:f1:87:85:69:b1:e8:e4:39:
+ 92:84:db:df:96:71:b8:5b:a9:ef:b3:de:d4:a6:c6:
+ 4c:cb:4b:02:d9:84:d1:47:1a:45:d8:5d:9f:ae:09:
+ 69:39:1c:4a:d1:f0:9a:88:59:54:44:8e:58:96:58:
+ 24:0c:d5:9a:bc:7b:81:2c:2c:55:52:ac:06:37:7d:
+ 52:89:58:19:cd:fe:f5:55:17:57:e3:c2:47:c3:be:
+ 61:59:9f:86:fc:51:20:11:13:ad:62:d1:3b:b4:55:
+ 83:5e:3d:26:d0:c8:0d:36:2e:6b:86:af:6c:cc:5d:
+ 99:8f:93:18:47:df:f9:29:cd:6b:c6:5d:3f:01:15:
+ b5:06:0d:f4:ce:4a:21:aa:36:4f:45:a0:ca:b1:94:
+ 78:49:b7:3a:c9:23:01:58:87:10:85:4f:6e:ba:90:
+ 40:ef
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints:
+ CA:TRUE, pathlen:2
+ X509v3 Subject Key Identifier:
+ CC:C4:CE:8E:D4:73:22:4D:0F:BC:8C:FB:F5:55:F1:61:A5:38:90:C9
+ X509v3 Authority Key Identifier:
+ keyid:CC:C4:CE:8E:D4:73:22:4D:0F:BC:8C:FB:F5:55:F1:61:A5:38:90:C9
+
+ X509v3 Key Usage:
+ Certificate Sign, CRL Sign
+ Signature Algorithm: sha256WithRSAEncryption
+ a9:bc:38:09:a4:2b:56:2c:ad:ed:32:fb:9a:71:62:8d:12:3d:
+ ff:2d:9a:84:70:9a:09:5a:0b:29:a8:7f:6f:91:93:9d:93:cc:
+ 4c:a3:d8:9d:75:81:27:c4:16:c4:8e:25:97:a5:ce:3e:33:2a:
+ 1a:6f:03:68:b9:35:0e:ee:99:d5:e0:96:d9:59:7b:a7:b9:8d:
+ 86:7d:5d:54:7d:b9:48:ac:fe:57:dd:50:94:c7:6b:f4:29:f4:
+ eb:9a:aa:63:bb:91:64:52:b4:56:7d:e2:73:3b:65:9e:0e:c0:
+ 8c:2c:44:f3:12:b5:1c:4e:41:80:3f:a2:17:c6:f2:53:e6:48:
+ 94:5d:ae:b9:7c:72:4f:7f:77:43:37:f8:dc:05:8d:ac:0b:7d:
+ 61:7d:7d:fc:59:6f:04:52:35:b0:dc:ea:ad:ce:fe:15:36:0a:
+ 2e:79:0e:b7:a1:93:61:34:9a:47:0d:c1:6b:b6:ae:d2:9b:01:
+ f3:6b:ff:5b:d4:f9:03:5e:81:1a:e7:90:28:e8:e5:7d:76:15:
+ c2:73:46:a3:c2:bd:41:51:fe:57:ef:58:6e:b1:94:82:ea:c0:
+ f1:9a:6f:f8:31:00:c3:ce:22:4f:28:5b:a1:e2:17:b5:1c:ba:
+ d8:de:73:ec:a2:3d:cc:12:fe:f5:ae:c9:fa:a7:4e:e3:0b:4e:
+ 24:18:a7:f0
+-----BEGIN CERTIFICATE-----
+MIID5TCCAs2gAwIBAgIBHDANBgkqhkiG9w0BAQsFADCBhDELMAkGA1UEBhMCVVMx
+EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxEzAR
+BgNVBAoMCkdvb2dsZSBJbmMxDTALBgNVBAsMBENhc3QxJDAiBgNVBAMMG0Nhc3Qg
+VGVzdCBVbnRydXN0ZWQgUm9vdCBDQTAeFw0xNTAxMjEyMzQxMjZaFw0zNTAxMTYy
+MzQxMjZaMIGEMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQG
+A1UEBwwNTW91bnRhaW4gVmlldzETMBEGA1UECgwKR29vZ2xlIEluYzENMAsGA1UE
+CwwEQ2FzdDEkMCIGA1UEAwwbQ2FzdCBUZXN0IFVudHJ1c3RlZCBSb290IENBMIIB
+IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAweRMLuCsXNrht+D+9Rkqa+u1
+jZzd9tlfhGT4qUsHVXhz9xQ4OJQTcNmHI9SRwpPRP4atp/Y56XjAuD4yQOyDTKJH
+lnWv/B01GwDamqVszRsECua9qeRdZ3F9YMjhWSfD8YeFabHo5DmShNvflnG4W6nv
+s97UpsZMy0sC2YTRRxpF2F2frglpORxK0fCaiFlURI5YllgkDNWavHuBLCxVUqwG
+N31SiVgZzf71VRdX48JHw75hWZ+G/FEgEROtYtE7tFWDXj0m0MgNNi5rhq9szF2Z
+j5MYR9/5Kc1rxl0/ARW1Bg30zkohqjZPRaDKsZR4Sbc6ySMBWIcQhU9uupBA7wID
+AQABo2AwXjAPBgNVHRMECDAGAQH/AgECMB0GA1UdDgQWBBTMxM6O1HMiTQ+8jPv1
+VfFhpTiQyTAfBgNVHSMEGDAWgBTMxM6O1HMiTQ+8jPv1VfFhpTiQyTALBgNVHQ8E
+BAMCAQYwDQYJKoZIhvcNAQELBQADggEBAKm8OAmkK1Ysre0y+5pxYo0SPf8tmoRw
+mglaCymof2+Rk52TzEyj2J11gSfEFsSOJZelzj4zKhpvA2i5NQ7umdXgltlZe6e5
+jYZ9XVR9uUis/lfdUJTHa/Qp9OuaqmO7kWRStFZ94nM7ZZ4OwIwsRPMStRxOQYA/
+ohfG8lPmSJRdrrl8ck9/d0M3+NwFjawLfWF9ffxZbwRSNbDc6q3O/hU2Ci55Dreh
+k2E0mkcNwWu2rtKbAfNr/1vU+QNegRrnkCjo5X12FcJzRqPCvUFR/lfvWG6xlILq
+wPGab/gxAMPOIk8oW6HiF7Ucutjec+yiPcwS/vWuyfqnTuMLTiQYp/A=
+-----END CERTIFICATE-----
diff --git a/test/data/cast/common/certificate/testsuite/testsuite1.pb b/test/data/cast/common/certificate/testsuite/testsuite1.pb
new file mode 100644
index 00000000..a7fc7442
--- /dev/null
+++ b/test/data/cast/common/certificate/testsuite/testsuite1.pb
Binary files differ
diff --git a/third_party/protobuf/BUILD.gn b/third_party/protobuf/BUILD.gn
index 7a3c96d2..f9228840 100644
--- a/third_party/protobuf/BUILD.gn
+++ b/third_party/protobuf/BUILD.gn
@@ -41,7 +41,7 @@ config("protobuf_warnings") {
config("using_proto") {
include_dirs = [
"src/src",
- "$root_gen_dir/protoc_out",
+ "$root_gen_dir",
]
}