Commit ed1a0da4 authored by ryanchung's avatar ryanchung Committed by Commit bot

Cast device revocation checking.

Cast device certificates may be revoked through two ways:
1. The hash of the public key.
2. A serial number range for an issuer identified by the hash
   of its public key.

A customized proto is used as the medium for this information.

This change contains the implementation for verifying the custom CRL
and verifying a certificate's revocation status based on that CRL.

BUG=618463

Committed: https://crrev.com/698608f28ed2df276f920c9691cbfcb8f9069337
Review-Url: https://codereview.chromium.org/2050983002
Cr-Original-Commit-Position: refs/heads/master@{#407492}
Cr-Commit-Position: refs/heads/master@{#407704}
parent 5a24ca67
......@@ -51,8 +51,7 @@ bool VerifyCredentials(
const std::string& signature,
const std::string& data,
const std::string& connected_mac) {
base::Time::Exploded now;
base::Time::Now().UTCExplode(&now);
base::Time now = base::Time::Now();
return VerifyCredentialsAtTime(certificate, intermediate_certificates,
signature, data, connected_mac, now);
}
......@@ -63,7 +62,7 @@ bool VerifyCredentialsAtTime(
const std::string& signature,
const std::string& data,
const std::string& connected_mac,
const base::Time::Exploded& time) {
const base::Time& time) {
static const char kErrorPrefix[] = "Device verification failed. ";
std::vector<std::string> headers;
......@@ -97,7 +96,8 @@ bool VerifyCredentialsAtTime(
std::unique_ptr<cast_crypto::CertVerificationContext> verification_context;
if (!cast_crypto::VerifyDeviceCert(certs, time, &verification_context,
&unused_policy)) {
&unused_policy, nullptr,
cast_crypto::CRLPolicy::CRL_OPTIONAL)) {
LOG(ERROR) << kErrorPrefix << "Failed verifying cast device cert";
return false;
}
......
......@@ -35,7 +35,7 @@ bool VerifyCredentialsAtTime(
const std::string& signature,
const std::string& data,
const std::string& connected_mac,
const base::Time::Exploded& time);
const base::Time& time);
// Encrypt |data| with |public_key|. |public_key| is a DER-encoded
// RSAPublicKey. |data| is some string of bytes that is smaller than the
......
......@@ -118,16 +118,21 @@ TEST_F(NetworkingPrivateCryptoTest, VerifyCredentials) {
static const char kBadHotspotBssid[] = "bad bssid";
// April 1, 2016
base::Time::Exploded time = {0};
time.year = 2016;
time.month = 4;
time.day_of_month = 1;
base::Time::Exploded time_exploded = {0};
time_exploded.year = 2016;
time_exploded.month = 4;
time_exploded.day_of_month = 1;
base::Time time;
ASSERT_TRUE(base::Time::FromUTCExploded(time_exploded, &time));
// September 1, 2035
base::Time::Exploded expired_time = {0};
expired_time.year = 2035;
expired_time.month = 9;
expired_time.day_of_month = 1;
base::Time::Exploded expired_time_exploded = {0};
expired_time_exploded.year = 2035;
expired_time_exploded.month = 9;
expired_time_exploded.day_of_month = 1;
base::Time expired_time;
ASSERT_TRUE(
base::Time::FromUTCExploded(expired_time_exploded, &expired_time));
std::string unsigned_data = std::string(std::begin(kData), std::end(kData));
std::string signed_data =
......
......@@ -13,14 +13,45 @@
'dependencies': [
'../base/base.gyp:base',
'../net/net.gyp:net',
'cast_certificate_proto',
],
'sources': [
'cast_certificate/cast_cert_validator.cc',
'cast_certificate/cast_cert_validator.h',
'cast_certificate/cast_crl.cc',
'cast_certificate/cast_crl.h',
'cast_certificate/cast_root_ca_cert_der-inc.h',
'cast_certificate/eureka_root_ca_der-inc.h',
],
},
{
'target_name': 'cast_certificate_proto',
'type': 'static_library',
'sources': [
'cast_certificate/proto/revocation.proto',
],
'includes': [
'../build/protoc.gypi'
],
'variables': {
'proto_in_dir': 'cast_certificate/proto',
'proto_out_dir': 'components/cast_certificate/proto',
},
},
{
'target_name': 'cast_certificate_test_proto',
'type': 'static_library',
'sources': [
'cast_certificate/proto/test_suite.proto',
],
'includes': [
'../build/protoc.gypi'
],
'variables': {
'proto_in_dir': 'cast_certificate/proto',
'proto_out_dir': 'components/cast_certificate/proto',
},
},
{
'target_name': 'cast_certificate_test_support',
'type': 'static_library',
......
......@@ -6,11 +6,14 @@ static_library("cast_certificate") {
sources = [
"cast_cert_validator.cc",
"cast_cert_validator.h",
"cast_crl.cc",
"cast_crl.h",
"cast_root_ca_cert_der-inc.h",
"eureka_root_ca_der-inc.h",
]
deps = [
"//base",
"//components/cast_certificate/proto",
"//net",
]
}
......@@ -33,11 +36,13 @@ source_set("unit_tests") {
testonly = true
sources = [
"cast_cert_validator_unittest.cc",
"cast_crl_unittest.cc",
]
deps = [
":cast_certificate",
":test_support",
"//base",
"//components/cast_certificate/proto:unittest_proto",
"//net",
"//testing/gtest",
]
......
include_rules = [
"+crypto",
"+net",
]
......@@ -14,6 +14,7 @@
#include "base/memory/ptr_util.h"
#include "base/memory/singleton.h"
#include "net/cert/internal/cert_issuer_source_static.h"
#include "components/cast_certificate/cast_crl.h"
#include "net/cert/internal/certificate_policies.h"
#include "net/cert/internal/extended_key_usage.h"
#include "net/cert/internal/parse_certificate.h"
......@@ -24,6 +25,7 @@
#include "net/cert/internal/signature_policy.h"
#include "net/cert/internal/trust_store.h"
#include "net/cert/internal/verify_signed_data.h"
#include "net/der/encode_values.h"
#include "net/der/input.h"
namespace cast_certificate {
......@@ -233,19 +235,6 @@ WARN_UNUSED_RESULT bool CheckTargetCertificate(
return true;
}
// Converts a base::Time::Exploded to a net::der::GeneralizedTime.
net::der::GeneralizedTime ConvertExplodedTime(
const base::Time::Exploded& exploded) {
net::der::GeneralizedTime result;
result.year = exploded.year;
result.month = exploded.month;
result.day = exploded.day_of_month;
result.hours = exploded.hour;
result.minutes = exploded.minute;
result.seconds = exploded.second;
return result;
}
// Returns the parsing options used for Cast certificates.
net::ParseCertificateOptions GetCertParsingOptions() {
net::ParseCertificateOptions options;
......@@ -265,9 +254,11 @@ net::ParseCertificateOptions GetCertParsingOptions() {
} // namespace
bool VerifyDeviceCert(const std::vector<std::string>& certs,
const base::Time::Exploded& time,
const base::Time& time,
std::unique_ptr<CertVerificationContext>* context,
CastDeviceCertPolicy* policy) {
CastDeviceCertPolicy* policy,
const CastCRL* crl,
CRLPolicy crl_policy) {
if (certs.empty())
return false;
......@@ -295,10 +286,13 @@ bool VerifyDeviceCert(const std::vector<std::string>& certs,
// Do path building and RFC 5280 compatible certificate verification using the
// two Cast trust anchors and Cast signature policy.
net::der::GeneralizedTime verification_time;
if (!net::der::EncodeTimeAsGeneralizedTime(time, &verification_time))
return false;
net::CertPathBuilder::Result result;
net::CertPathBuilder path_builder(target_cert.get(), &CastTrustStore::Get(),
signature_policy.get(),
ConvertExplodedTime(time), &result);
signature_policy.get(), verification_time,
&result);
path_builder.AddCertIssuerSource(&intermediate_cert_issuer_source);
net::CompletionStatus rv = path_builder.Run(base::Closure());
DCHECK_EQ(rv, net::CompletionStatus::SYNC);
......@@ -307,7 +301,25 @@ bool VerifyDeviceCert(const std::vector<std::string>& certs,
// Check properties of the leaf certificate (key usage, policy), and construct
// a CertVerificationContext that uses its public key.
return CheckTargetCertificate(target_cert.get(), context, policy);
if (!CheckTargetCertificate(target_cert.get(), context, policy))
return false;
// Check if a CRL is available.
if (!crl) {
if (crl_policy == CRLPolicy::CRL_REQUIRED) {
return false;
}
} else {
if (result.paths.empty() ||
!result.paths[result.best_result_index]->is_success())
return false;
if (!crl->CheckRevocation(result.paths[result.best_result_index]->path,
time)) {
return false;
}
}
return true;
}
std::unique_ptr<CertVerificationContext> CertVerificationContextImplForTest(
......@@ -318,13 +330,13 @@ std::unique_ptr<CertVerificationContext> CertVerificationContextImplForTest(
new CertVerificationContextImpl(net::der::Input(spki), "CommonName"));
}
bool AddTrustAnchorForTest(const uint8_t* data, size_t length) {
bool SetTrustAnchorForTest(const std::string& cert) {
scoped_refptr<net::ParsedCertificate> anchor(
net::ParsedCertificate::CreateFromCertificateData(
data, length, net::ParsedCertificate::DataSource::EXTERNAL_REFERENCE,
GetCertParsingOptions()));
net::ParsedCertificate::CreateFromCertificateCopy(
cert, GetCertParsingOptions()));
if (!anchor)
return false;
CastTrustStore::Get().Clear();
CastTrustStore::Get().AddTrustedCertificate(std::move(anchor));
return true;
}
......
......@@ -16,6 +16,8 @@
namespace cast_certificate {
class CastCRL;
// Describes the policy for a Device certificate.
enum class CastDeviceCertPolicy {
// The device certificate is unrestricted.
......@@ -25,6 +27,14 @@ enum class CastDeviceCertPolicy {
AUDIO_ONLY,
};
enum class CRLPolicy {
// Revocation is only checked if a CRL is provided.
CRL_OPTIONAL,
// Revocation is always checked. A missing CRL results in failure.
CRL_REQUIRED,
};
// An object of this type is returned by the VerifyDeviceCert function, and can
// be used for additional certificate-related operations, using the verified
// certificate.
......@@ -58,9 +68,16 @@ class CertVerificationContext {
// * |certs[1..n-1]| are intermediates certificates to use in path building.
// Their ordering does not matter.
//
// * |time| is the UTC time to use for determining if the certificate
// * |time| is the unix timestamp to use for determining if the certificate
// is expired.
//
// * |crl| is the CRL to check for certificate revocation status.
// If this is a nullptr, then revocation checking is currently disabled.
//
// * |crl_options| is for choosing how to handle the absence of a CRL.
// If crl_required is set to true, then an empty |crl| input would result
// in a failed verification. Otherwise, |crl| is ignored if it is absent.
//
// Outputs:
//
// Returns true on success, false on failure. On success the output
......@@ -72,9 +89,11 @@ class CertVerificationContext {
// * |policy| is filled with an indication of the device certificate's policy
// (i.e. is it for audio-only devices or is it unrestricted?)
bool VerifyDeviceCert(const std::vector<std::string>& certs,
const base::Time::Exploded& time,
const base::Time& time,
std::unique_ptr<CertVerificationContext>* context,
CastDeviceCertPolicy* policy) WARN_UNUSED_RESULT;
CastDeviceCertPolicy* policy,
const CastCRL* crl,
CRLPolicy crl_policy) WARN_UNUSED_RESULT;
// Exposed only for unit-tests, not for use in production code.
// Production code would get a context from VerifyDeviceCert().
......@@ -86,13 +105,9 @@ std::unique_ptr<CertVerificationContext> CertVerificationContextImplForTest(
// Exposed only for testing, not for use in production code.
//
// Injects trusted root certificates into the CastTrustStore.
// |data| must remain valid and not be mutated throughout the lifetime of
// the program.
// Warning: Using this function concurrently with VerifyDeviceCert()
// is not thread safe.
bool AddTrustAnchorForTest(const uint8_t* data,
size_t length) WARN_UNUSED_RESULT;
// Replaces trusted root certificates in the CastTrustStore.
// Returns true if successful, false if nothing is changed.
bool SetTrustAnchorForTest(const std::string& cert) WARN_UNUSED_RESULT;
} // namespace cast_certificate
......
......@@ -13,10 +13,6 @@ namespace cast_certificate {
namespace testing {
namespace {
// Reads a file from the test data directory
// (//src/components/test/data/cast_certificate)
std::string ReadTestFileToString(const base::StringPiece& file_name) {
base::FilePath filepath;
PathService::Get(base::DIR_SOURCE_ROOT, &filepath);
......@@ -36,8 +32,6 @@ std::string ReadTestFileToString(const base::StringPiece& file_name) {
return file_data;
}
} // namespace
std::vector<std::string> ReadCertificateChainFromFile(
const base::StringPiece& file_name) {
std::string file_data = ReadTestFileToString(file_name);
......
......@@ -35,6 +35,10 @@ struct SignatureTestData {
// |file_name| should be relative to //components/test/data/cast_certificate
SignatureTestData ReadSignatureTestData(const base::StringPiece& file_name);
// Reads a file from the test data directory
// (//src/components/test/data/cast_certificate)
std::string ReadTestFileToString(const base::StringPiece& file_name);
} // namespace testing
} // namespace cast_certificate
......
......@@ -39,14 +39,15 @@ void RunTest(TestResult expected_result,
const std::string& expected_common_name,
CastDeviceCertPolicy expected_policy,
const std::string& certs_file_name,
const base::Time::Exploded& time,
const base::Time& time,
const std::string& optional_signed_data_file_name) {
auto certs =
cast_certificate::testing::ReadCertificateChainFromFile(certs_file_name);
std::unique_ptr<CertVerificationContext> context;
CastDeviceCertPolicy policy;
bool result = VerifyDeviceCert(certs, time, &context, &policy);
bool result = VerifyDeviceCert(certs, time, &context, &policy, nullptr,
CRLPolicy::CRL_OPTIONAL);
if (expected_result == RESULT_FAIL) {
ASSERT_FALSE(result);
......@@ -89,33 +90,39 @@ void RunTest(TestResult expected_result,
}
// Creates a time in UTC at midnight.
base::Time::Exploded CreateDate(int year, int month, int day) {
//
// The maximum date usable here is limited to year 2038 on 32 bit systems due to
// base::Time::FromExploded clamping the range to what is supported by mktime
// and timegm.
base::Time CreateDate(int year, int month, int day) {
base::Time::Exploded time = {0};
time.year = year;
time.month = month;
time.day_of_month = day;
return time;
base::Time result;
EXPECT_TRUE(base::Time::FromUTCExploded(time, &result));
return result;
}
// Returns 2016-04-01 00:00:00 UTC.
//
// This is a time when most of the test certificate paths are
// valid.
base::Time::Exploded AprilFirst2016() {
base::Time AprilFirst2016() {
return CreateDate(2016, 4, 1);
}
// Returns 2015-01-01 00:00:00 UTC.
base::Time::Exploded JanuaryFirst2015() {
base::Time JanuaryFirst2015() {
return CreateDate(2015, 1, 1);
}
// Returns 2040-03-01 00:00:00 UTC.
// Returns 2037-03-01 00:00:00 UTC.
//
// This is so far in the future that the test chains in this unit-test
// should all be invalid.
base::Time::Exploded MarchFirst2040() {
return CreateDate(2040, 3, 1);
base::Time MarchFirst2037() {
return CreateDate(2037, 3, 1);
}
// Tests verifying a valid certificate chain of length 2:
......@@ -258,7 +265,7 @@ TEST(VerifyCastDeviceCertTest, ChromecastGen2InvalidTime) {
// Use a time after notAfter.
RunTest(RESULT_FAIL, "", CastDeviceCertPolicy::NONE, kCertsFile,
MarchFirst2040(), "");
MarchFirst2037(), "");
}
// Tests verifying a valid certificate chain of length 3:
......
This diff is collapsed.
// Copyright 2016 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 COMPONENTS_CAST_CERTIFICATE_CAST_CRL_H_
#define COMPONENTS_CAST_CERTIFICATE_CAST_CRL_H_
#include <memory>
#include <string>
#include <vector>
#include "base/compiler_specific.h"
#include "base/memory/ref_counted.h"
#include "base/time/time.h"
#include "net/cert/internal/parsed_certificate.h"
namespace cast_certificate {
// This class represents the CRL information parsed from the binary proto.
class CastCRL {
public:
virtual ~CastCRL(){};
// Verifies the revocation status of a cast device certificate given a chain
// of X.509 certificates.
//
// Inputs:
// * |certs| is the verified chain of X.509 certificates:
// * |certs[0]| is the target certificate (i.e. the device certificate).
// * |certs[i]| is the certificate that issued certs[i-1].
// * |certs.back()| is assumed to be a trusted root.
//
// * |time| is the unix timestamp to use for determining if the certificate
// is revoked.
//
// Output:
// Returns true if no certificate in the chain was revoked.
virtual bool CheckRevocation(const net::ParsedCertificateList& certs,
const base::Time& time) const = 0;
};
// Parses and verifies the CRL used to verify the revocation status of
// Cast device certificates.
//
// Inputs:
// * |crl_proto| is a serialized cast_certificate.CrlBundle proto.
// * |time| is the unix timestamp to use for determining if the CRL is valid.
//
// Output:
// Returns the CRL object if success, nullptr otherwise.
std::unique_ptr<CastCRL> ParseAndVerifyCRL(const std::string& crl_proto,
const base::Time& time);
// Exposed only for testing, not for use in production code.
//
// Replaces trusted root certificates into the CastCRLTrustStore.
//
// Output:
// Returns true if successful, false if nothing is changed.
bool SetCRLTrustAnchorForTest(const std::string& cert) WARN_UNUSED_RESULT;
} // namespace cast_certificate
#endif // COMPONENTS_CAST_CERTIFICATE_CAST_CRL_H_
// Copyright 2016 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.
// TODO(ryanchung): Add Cast CRL Root here.
const unsigned char kCastCRLRootCaDer[] = {
0x30,
};
// Copyright 2016 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 "base/time/time.h"
#include "components/cast_certificate/cast_cert_validator.h"
#include "components/cast_certificate/cast_cert_validator_test_helpers.h"
#include "components/cast_certificate/cast_crl.h"
#include "components/cast_certificate/proto/test_suite.pb.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cast_certificate {
namespace {
// Converts uint64_t unix timestamp in seconds to base::Time.
base::Time ConvertUnixTimestampSeconds(uint64_t time) {
return base::Time::UnixEpoch() +
base::TimeDelta::FromMilliseconds(time * 1000);
}
// Indicates the expected result of test step's verification.
enum TestStepResult {
RESULT_SUCCESS,
RESULT_FAIL,
};
// 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>& certificate_chain,
const base::Time& time) {
std::unique_ptr<CertVerificationContext> context;
CastDeviceCertPolicy policy;
bool result = VerifyDeviceCert(certificate_chain, time, &context, &policy,
nullptr, CRLPolicy::CRL_OPTIONAL);
if (expected_result != RESULT_SUCCESS) {
EXPECT_FALSE(result);
return !result;
}
EXPECT_TRUE(result);
return result;
}
// 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 base::Time& time) {
std::unique_ptr<CastCRL> crl = ParseAndVerifyCRL(crl_bundle, time);
if (expected_result != RESULT_SUCCESS) {
EXPECT_EQ(crl, nullptr);
return crl == nullptr;
}
EXPECT_NE(crl, nullptr);
return crl != nullptr;
}
// 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(TestStepResult expected_result,
const std::vector<std::string>& certificate_chain,
const std::string& crl_bundle,
const base::Time& crl_time,
const base::Time& cert_time,
bool crl_required) {
std::unique_ptr<CastCRL> crl;
if (!crl_bundle.empty()) {
crl = ParseAndVerifyCRL(crl_bundle, crl_time);
EXPECT_NE(crl.get(), nullptr);
}
std::unique_ptr<CertVerificationContext> context;
CastDeviceCertPolicy policy;
CRLPolicy crl_policy = CRLPolicy::CRL_REQUIRED;
if (!crl_required)
crl_policy = CRLPolicy::CRL_OPTIONAL;
int result = VerifyDeviceCert(certificate_chain, cert_time, &context, &policy,
crl.get(), crl_policy);
if (expected_result != RESULT_SUCCESS) {
EXPECT_FALSE(result);
return !result;
}
EXPECT_TRUE(result);
return result;
}
// Runs a single test case.
bool RunTest(const DeviceCertTest& test_case) {
bool use_test_trust_anchors = test_case.use_test_trust_anchors();
if (use_test_trust_anchors) {
const auto crl_test_root =
cast_certificate::testing::ReadCertificateChainFromFile(
"certificates/cast_crl_test_root_ca.pem");
EXPECT_EQ(crl_test_root.size(), 1u);
EXPECT_TRUE(SetCRLTrustAnchorForTest(crl_test_root[0]));
const auto cast_test_root =
cast_certificate::testing::ReadCertificateChainFromFile(
"certificates/cast_test_root_ca.pem");
EXPECT_EQ(cast_test_root.size(), 1u);
EXPECT_TRUE(SetTrustAnchorForTest(cast_test_root[0]));
}
VerificationResult expected_result = test_case.expected_result();
std::vector<std::string> certificate_chain;
for (auto const& cert : test_case.der_cert_path()) {
certificate_chain.push_back(cert);
}
base::Time cert_verification_time =
ConvertUnixTimestampSeconds(test_case.cert_verification_time_seconds());
uint64_t crl_verify_time = test_case.crl_verification_time_seconds();
base::Time crl_verification_time =
ConvertUnixTimestampSeconds(crl_verify_time);
if (crl_verify_time == 0)
crl_verification_time = cert_verification_time;
std::string crl_bundle = test_case.crl_bundle();
switch (expected_result) {
case PATH_VERIFICATION_FAILED:
return TestVerifyCertificate(RESULT_FAIL, certificate_chain,
cert_verification_time);
break;
case CRL_VERIFICATION_FAILED:
return TestVerifyCRL(RESULT_FAIL, crl_bundle, crl_verification_time);
break;
case REVOCATION_CHECK_FAILED_WITHOUT_CRL:
return TestVerifyCertificate(RESULT_SUCCESS, certificate_chain,
cert_verification_time) &&
TestVerifyCRL(RESULT_FAIL, crl_bundle, crl_verification_time) &&
TestVerifyRevocation(RESULT_FAIL, certificate_chain, crl_bundle,
crl_verification_time, cert_verification_time,
true);
break;
case REVOCATION_CHECK_FAILED:
return TestVerifyCertificate(RESULT_SUCCESS, certificate_chain,
cert_verification_time) &&
TestVerifyCRL(RESULT_SUCCESS, crl_bundle, crl_verification_time) &&
TestVerifyRevocation(RESULT_FAIL, certificate_chain, crl_bundle,
crl_verification_time, cert_verification_time,
false);
break;
case SUCCESS:
return (crl_bundle.empty() || TestVerifyCRL(RESULT_SUCCESS, crl_bundle,
crl_verification_time)) &&
TestVerifyCertificate(RESULT_SUCCESS, certificate_chain,
cert_verification_time) &&
TestVerifyRevocation(RESULT_SUCCESS, certificate_chain, crl_bundle,
crl_verification_time, cert_verification_time,
!crl_bundle.empty());
break;
case UNSPECIFIED:
return false;
break;
}
return false;
}
// 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 =
cast_certificate::testing::ReadTestFileToString(test_suite_file_name);
DeviceCertTestSuite test_suite;
EXPECT_TRUE(test_suite.ParseFromString(testsuite_raw));
uint16_t success = 0;
uint16_t failed = 0;
std::vector<std::string> failed_tests;
for (auto const& test_case : test_suite.tests()) {
LOG(INFO) << "[ RUN ] " << test_case.description();
bool result = RunTest(test_case);
EXPECT_TRUE(result);
if (!result) {
LOG(INFO) << "[ FAILED ] " << test_case.description();
++failed;
failed_tests.push_back(test_case.description());
} else {
LOG(INFO) << "[ PASSED ] " << test_case.description();
++success;
}
}
LOG(INFO) << "[ PASSED ] " << success << " test(s).";
if (failed) {
LOG(INFO) << "[ FAILED ] " << failed << " test(s), listed below:";
for (const auto& failed_test : failed_tests) {
LOG(INFO) << "[ FAILED ] " << failed_test;
}
}
}
TEST(CastCertificateTest, TestSuite1) {
RunTestSuite("testsuite/testsuite1.pb");
}
} // namespace
} // namespace cast_certificate
# Copyright 2016 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.
import("//third_party/protobuf/proto_library.gni")
if (is_android) {
import("//build/config/android/rules.gni")
}
proto_library("proto") {
sources = [
"revocation.proto",
]
}
proto_library("unittest_proto") {
sources = [
"test_suite.proto",
]
}
if (is_android) {
proto_java_library("proto_java") {
proto_path = "//components/cast_certificate/proto"
sources = [
"revocation.proto",
]
}
proto_java_library("unittest_proto_java") {
proto_path = "//components/cast_certificate/proto"
sources = [
"test_suite.proto",
]
}
}
// Copyright (c) 2016 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.
//
// Data structures related to Cast device certificate revocation infrastructure.
// This proto must be kept in sync with google3.
syntax = "proto2";
package cast_certificate;
option optimize_for = LITE_RUNTIME;
message CrlBundle {
// List of supported versions of the same revocation list.
repeated Crl crls = 1;
}
message Crl {
// Octet string of serialized TbsCrl protobuf.
optional bytes tbs_crl = 1;
// Binary ASN.1 DER encoding of the signer's certificate.
optional bytes signer_cert = 2;
// Signature calculated over the contents of the tbs_crl field. Signature
// algorithm is implied by TbsCrl.version.
optional bytes signature = 3;
}
message TbsCrl {
// Version 0 algorithms:
// revoked_public_key_hashes: SHA-256
// SerialNumberRange.issuer_public_key_hash: SHA-256
// Crl.signature: RSA-PKCS1 V1.5 with SHA-256
optional uint64 version = 1 [default = 0];
// Inclusive validity range of the CRL in Unix time.
optional uint64 not_before_seconds = 2;
optional uint64 not_after_seconds = 3;
// SPKI hashes of revoked credentials. Hashing algorithm is implied by
// TbsCrl.version.
repeated bytes revoked_public_key_hashes = 4;
repeated SerialNumberRange revoked_serial_number_ranges = 5;
}
message SerialNumberRange {
// SPKI hash of the certificate issuer. Hashing algorithm is implied by the
// enclosing TbsCrl.version.
optional bytes issuer_public_key_hash = 1;
// Inclusive range of revoked certificate serial numbers. Only certificates
// with positive serial numbers that fit within 64 bits can be revoked through
// this mechanism.
optional uint64 first_serial_number = 2;
optional uint64 last_serial_number = 3;
}
// Copyright (c) 2016 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.
syntax = "proto2";
package cast_certificate;
option optimize_for = LITE_RUNTIME;
// A suite of test data to exercise Cast device certificate verification and
// revocation logic.
message DeviceCertTestSuite {
repeated DeviceCertTest tests = 1;
}
enum VerificationResult {
// This should never be encountered in a valid test.
UNSPECIFIED = 0;
// The device certificate is valid.
SUCCESS = 1;
// Problem with device certificate or its path.
PATH_VERIFICATION_FAILED = 2;
// Problem with the CRL.
CRL_VERIFICATION_FAILED = 3;
// Device certificate or one of the certificates in its path did not pass the
// revocation check.
REVOCATION_CHECK_FAILED = 4;
// No CRL was provided, but revocation check is required, and therefore fails.
REVOCATION_CHECK_FAILED_WITHOUT_CRL = 5;
}
message DeviceCertTest {
// Human-readable description of the test.
optional string description = 1;
// Expected result of the certificate verification.
optional VerificationResult expected_result = 4;
// Device certiticate path up to a trusted root. Root is not included.
repeated bytes der_cert_path = 2;
// Serialized cast.CrlBundle proto if revocation check is required.
optional bytes crl_bundle = 3;
// Time at which to verify the device certificate.
optional uint64 cert_verification_time_seconds = 5;
// Time at which to verify the CRL. It this field is omitted, the CRL is
// verified at cert_verification_time_seconds.
optional uint64 crl_verification_time_seconds = 6;
// Chooses between test and production trust anchors for device certificates
// and CRLs. Defaults to using the test trust anchors.
optional bool use_test_trust_anchors = 7 [default = true];
}
......@@ -107,9 +107,8 @@
'captive_portal/captive_portal_detector_unittest.cc',
],
'cast_certificate_unittest_sources': [
'cast_certificate/cast_cert_validator_test_helpers.cc',
'cast_certificate/cast_cert_validator_test_helpers.h',
'cast_certificate/cast_cert_validator_unittest.cc',
'cast_certificate/cast_crl_unittest.cc',
],
'certificate_reporting_unittest_sources': [
'certificate_reporting/error_report_unittest.cc',
......@@ -1159,6 +1158,8 @@
'components.gyp:bubble',
'components.gyp:captive_portal_test_support',
'components.gyp:cast_certificate',
'components.gyp:cast_certificate_test_proto',
'components.gyp:cast_certificate_test_support',
'components.gyp:certificate_reporting',
'components.gyp:cloud_devices_common',
'components.gyp:component_updater',
......
$ 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-----
$ 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-----
The contents of the test suite can be examined using gqui.
Example command:
gqui from testsuite1.pb proto ../../../../cast_certificate/proto/test_suite.proto:cast_certificate.DeviceCertTestSuite
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -148,12 +148,13 @@ AuthResult VerifyCredentials(const AuthResponse& response,
response.intermediate_certificate().end());
// Use the current time when checking certificate validity.
base::Time::Exploded now;
base::Time::Now().UTCExplode(&now);
base::Time now = base::Time::Now();
// CRL should not be enforced until it is served.
cast_crypto::CastDeviceCertPolicy device_policy;
if (!cast_crypto::VerifyDeviceCert(cert_chain, now, &verification_context,
&device_policy)) {
if (!cast_crypto::VerifyDeviceCert(
cert_chain, now, &verification_context, &device_policy, nullptr,
cast_certificate::CRLPolicy::CRL_OPTIONAL)) {
// TODO(eroman): The error information was lost; this error is ambiguous.
return AuthResult("Failed verifying cast device certificate",
AuthResult::ERROR_CERT_NOT_SIGNED_BY_TRUSTED_CA);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment