diff options
21 files changed, 1683 insertions, 599 deletions
@@ -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, ¬_before) && - ParseAsn1GeneralizedTime(not_after_asn1, ¬_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, ¬_before) && + ParseAsn1GeneralizedTime(not_after_asn1, ¬_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(), ¬_before)) { + return false; + } + DateTime not_after; + if (!ConvertTimeSeconds(tbs_crl.not_after_seconds(), ¬_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(), ¬_before_); + ConvertTimeSeconds(tbs_crl.not_after_seconds(), ¬_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 Binary files differnew file mode 100644 index 00000000..a7fc7442 --- /dev/null +++ b/test/data/cast/common/certificate/testsuite/testsuite1.pb 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", ] } |