Index: extensions/browser/api/cast_channel/cast_auth_util.cc |
diff --git a/extensions/browser/api/cast_channel/cast_auth_util.cc b/extensions/browser/api/cast_channel/cast_auth_util.cc |
index 0e5c38844cbe1e84e1e8210a4c4bbd56a9ac34ea..15bee3c59d7267caec893e889c14c8dc73a04c33 100644 |
--- a/extensions/browser/api/cast_channel/cast_auth_util.cc |
+++ b/extensions/browser/api/cast_channel/cast_auth_util.cc |
@@ -6,11 +6,14 @@ |
#include <vector> |
+#include "base/feature_list.h" |
#include "base/logging.h" |
#include "base/macros.h" |
+#include "base/metrics/histogram.h" |
#include "base/strings/string_number_conversions.h" |
#include "base/strings/stringprintf.h" |
#include "components/cast_certificate/cast_cert_validator.h" |
+#include "components/cast_certificate/cast_crl.h" |
#include "extensions/browser/api/cast_channel/cast_message_util.h" |
#include "extensions/common/api/cast_channel/cast_channel.pb.h" |
#include "net/cert/x509_certificate.h" |
@@ -26,6 +29,15 @@ const char* const kParseErrorPrefix = "Failed to parse auth message: "; |
// The maximum number of days a cert can live for. |
const int kMaxSelfSignedCertLifetimeInDays = 4; |
+// Enforce certificate revocation when enabled. |
+// If disabled, any revocation failures are ignored. |
+// |
+// This flags only controls the enforcement. Revocation is checked regardless. |
+// |
+// This flag tracks the changes necessary to fully enforce revocation. |
+const base::Feature kEnforceRevocationChecking{ |
+ "CastCertificateRevocation", base::FEATURE_DISABLED_BY_DEFAULT}; |
+ |
namespace cast_crypto = ::cast_certificate; |
// Extracts an embedded DeviceAuthMessage payload from an auth challenge reply |
@@ -63,6 +75,16 @@ AuthResult ParseAuthMessage(const CastMessage& challenge_reply, |
return AuthResult(); |
} |
+// Must match with histogram enum CastCertificateStatus. |
+// This should never be reordered. |
+enum CertVerificationStatus { |
+ CERT_STATUS_OK, |
+ CERT_STATUS_INVALID_CRL, |
+ CERT_STATUS_VERIFICATION_FAILED, |
+ CERT_STATUS_REVOKED, |
+ CERT_STATUS_COUNT, |
+}; |
+ |
} // namespace |
AuthResult::AuthResult() |
@@ -110,16 +132,16 @@ AuthResult AuthenticateChallengeReply(const CastMessage& challenge_reply, |
peer_cert.valid_start() > base::Time::Now()) { |
return AuthResult::CreateWithParseError( |
"Certificate's valid start date is in the future.", |
- AuthResult::ERROR_VALID_START_DATE_IN_FUTURE); |
+ AuthResult::ERROR_TLS_CERT_VALID_START_DATE_IN_FUTURE); |
} |
if (expiry.is_null() || peer_cert.HasExpired()) { |
return AuthResult::CreateWithParseError("Certificate has expired.", |
- AuthResult::ERROR_CERT_EXPIRED); |
+ AuthResult::ERROR_TLS_CERT_EXPIRED); |
} |
if (expiry > lifetime_limit) { |
return AuthResult::CreateWithParseError( |
"Peer cert lifetime is too long.", |
- AuthResult::ERROR_VALIDITY_PERIOD_TOO_LONG); |
+ AuthResult::ERROR_TLS_CERT_VALIDITY_PERIOD_TOO_LONG); |
} |
const AuthResponse& response = auth_message.response(); |
@@ -130,13 +152,27 @@ AuthResult AuthenticateChallengeReply(const CastMessage& challenge_reply, |
// |
// * Verifies that the certificate chain |response.client_auth_certificate| + |
// |response.intermediate_certificate| is valid and chains to a trusted |
-// Cast root. |
+// Cast root. The list of trusted Cast roots can be overrided by providing a |
+// non-nullptr |cast_trust_store|. The certificate is verified at |
+// |verification_time|. |
+// |
+// * Verifies that none of the certificates in the chain are revoked based on |
+// the CRL provided in the response |response.crl|. The CRL is verified to be |
+// valid and its issuer certificate chains to a trusted Cast CRL root. The |
+// list of trusted Cast CRL roots can be overrided by providing a non-nullptr |
+// |crl_trust_store|. If |crl_policy| is CRL_OPTIONAL then the result of |
+// revocation checking is ignored. The CRL is verified at |
+// |verification_time|. |
// |
// * Verifies that |response.signature| matches the signature |
// of |signature_input| by |response.client_auth_certificate|'s public |
// key. |
-AuthResult VerifyCredentials(const AuthResponse& response, |
- const std::string& signature_input) { |
+AuthResult VerifyCredentialsImpl(const AuthResponse& response, |
+ const std::string& signature_input, |
+ const cast_crypto::CRLPolicy& crl_policy, |
+ net::TrustStore* cast_trust_store, |
+ net::TrustStore* crl_trust_store, |
+ const base::Time& verification_time) { |
// Verify the certificate |
std::unique_ptr<cast_crypto::CertVerificationContext> verification_context; |
@@ -147,19 +183,56 @@ AuthResult VerifyCredentials(const AuthResponse& response, |
response.intermediate_certificate().begin(), |
response.intermediate_certificate().end()); |
- // Use the current time when checking certificate validity. |
- base::Time now = base::Time::Now(); |
+ // Parse the CRL. |
+ std::unique_ptr<cast_crypto::CastCRL> crl = |
+ cast_crypto::ParseAndVerifyCRLUsingCustomTrustStore( |
+ response.crl(), verification_time, crl_trust_store); |
+ if (!crl) { |
+ // CRL is invalid. |
+ UMA_HISTOGRAM_ENUMERATION("Cast.Channel.Certificate", |
+ CERT_STATUS_INVALID_CRL, CERT_STATUS_COUNT); |
+ if (crl_policy == cast_crypto::CRLPolicy::CRL_REQUIRED) { |
+ return AuthResult("Failed verifying Cast CRL.", |
+ AuthResult::ERROR_CRL_INVALID); |
+ } |
+ } |
- // 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, 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); |
+ bool verification_success = |
+ cast_crypto::VerifyDeviceCertUsingCustomTrustStore( |
+ cert_chain, verification_time, &verification_context, &device_policy, |
+ crl.get(), crl_policy, cast_trust_store); |
+ if (!verification_success) { |
+ // TODO(ryanchung): Once this feature is completely rolled-out, remove the |
+ // reverification step and use error reporting to get verification errors |
+ // for metrics. |
+ bool verification_no_crl_success = |
+ cast_crypto::VerifyDeviceCertUsingCustomTrustStore( |
+ cert_chain, verification_time, &verification_context, |
+ &device_policy, nullptr, cast_crypto::CRLPolicy::CRL_OPTIONAL, |
+ cast_trust_store); |
+ if (!verification_no_crl_success) { |
+ // TODO(eroman): The error information was lost; this error is ambiguous. |
+ UMA_HISTOGRAM_ENUMERATION("Cast.Channel.Certificate", |
+ CERT_STATUS_VERIFICATION_FAILED, |
+ CERT_STATUS_COUNT); |
+ return AuthResult("Failed verifying cast device certificate", |
+ AuthResult::ERROR_CERT_NOT_SIGNED_BY_TRUSTED_CA); |
+ } |
+ if (crl) { |
+ // If CRL was not present, it should've been recorded as such. |
+ UMA_HISTOGRAM_ENUMERATION("Cast.Channel.Certificate", CERT_STATUS_REVOKED, |
+ CERT_STATUS_COUNT); |
+ } |
+ if (crl_policy == cast_crypto::CRLPolicy::CRL_REQUIRED) { |
+ // Device is revoked. |
+ return AuthResult("Failed certificate revocation check.", |
+ AuthResult::ERROR_CERT_REVOKED); |
+ } |
} |
- |
+ // The certificate is verified at this point. |
+ UMA_HISTOGRAM_ENUMERATION("Cast.Channel.Certificate", CERT_STATUS_OK, |
+ CERT_STATUS_COUNT); |
if (!verification_context->VerifySignatureOverData(response.signature(), |
signature_input)) { |
return AuthResult("Failed verifying signature over data", |
@@ -181,6 +254,28 @@ AuthResult VerifyCredentials(const AuthResponse& response, |
return success; |
} |
+AuthResult VerifyCredentials(const AuthResponse& response, |
+ const std::string& signature_input) { |
+ base::Time now = base::Time::Now(); |
+ cast_crypto::CRLPolicy policy = cast_crypto::CRLPolicy::CRL_REQUIRED; |
+ if (!base::FeatureList::IsEnabled(kEnforceRevocationChecking)) { |
+ policy = cast_crypto::CRLPolicy::CRL_OPTIONAL; |
+ } |
+ return VerifyCredentialsImpl(response, signature_input, policy, nullptr, |
+ nullptr, now); |
+} |
+ |
+AuthResult VerifyCredentialsForTest(const AuthResponse& response, |
+ const std::string& signature_input, |
+ const cast_crypto::CRLPolicy& crl_policy, |
+ net::TrustStore* cast_trust_store, |
+ net::TrustStore* crl_trust_store, |
+ const base::Time& verification_time) { |
+ return VerifyCredentialsImpl(response, signature_input, crl_policy, |
+ cast_trust_store, crl_trust_store, |
+ verification_time); |
+} |
+ |
} // namespace cast_channel |
} // namespace api |
} // namespace extensions |