Skip to content

Commit

Permalink
Cast device revocation checking.
Browse files Browse the repository at this point in the history
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}
  • Loading branch information
ryanchung authored and Commit bot committed Jul 26, 2016
1 parent 5a24ca6 commit ed1a0da
Show file tree
Hide file tree
Showing 25 changed files with 1,303 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
31 changes: 31 additions & 0 deletions components/cast_certificate.gypi
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
5 changes: 5 additions & 0 deletions components/cast_certificate/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
]
}
Expand All @@ -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",
]
Expand Down
1 change: 1 addition & 0 deletions components/cast_certificate/DEPS
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
include_rules = [
"+crypto",
"+net",
]
56 changes: 34 additions & 22 deletions components/cast_certificate/cast_cert_validator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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 {
Expand Down Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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);
Expand All @@ -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(
Expand All @@ -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;
}
Expand Down
35 changes: 25 additions & 10 deletions components/cast_certificate/cast_cert_validator.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

namespace cast_certificate {

class CastCRL;

// Describes the policy for a Device certificate.
enum class CastDeviceCertPolicy {
// The device certificate is unrestricted.
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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().
Expand All @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit ed1a0da

Please sign in to comment.