Chromium Code Reviews| 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 6ed1cdc78fdab1e546371d2d6bc1d19724588caa..1df1b5106c9d97791129a601052175c6c19150fb 100644 |
| --- a/extensions/browser/api/cast_channel/cast_auth_util.cc |
| +++ b/extensions/browser/api/cast_channel/cast_auth_util.cc |
| @@ -7,8 +7,11 @@ |
| #include <vector> |
| #include "base/feature_list.h" |
| +#include "base/guid.h" |
| #include "base/logging.h" |
| #include "base/macros.h" |
| +#include "base/memory/ptr_util.h" |
| +#include "base/memory/singleton.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringprintf.h" |
| @@ -29,6 +32,9 @@ const char kParseErrorPrefix[] = "Failed to parse auth message: "; |
| // The maximum number of days a cert can live for. |
| const int kMaxSelfSignedCertLifetimeInDays = 4; |
| +// The number of hours after which a nonce is regenerated. |
| +long kNonceExpirationTimeInHours = 24; |
| + |
| // Enforce certificate revocation when enabled. |
| // If disabled, any revocation failures are ignored. |
| // |
| @@ -38,6 +44,15 @@ const int kMaxSelfSignedCertLifetimeInDays = 4; |
| const base::Feature kEnforceRevocationChecking{ |
| "CastCertificateRevocation", base::FEATURE_DISABLED_BY_DEFAULT}; |
| +// Enforce nonce checking when enabled. |
| +// If disabled, the nonce value returned from the device is not checked against |
| +// the one sent to the device. As a result, the nonce can be empty and omitted |
| +// from the signature. This allows backwards compatibility with legacy Cast |
| +// receivers. |
| + |
| +const base::Feature kEnforceNonceChecking{"CastNonceEnforced", |
| + base::FEATURE_DISABLED_BY_DEFAULT}; |
| + |
| namespace cast_crypto = ::cast_certificate; |
| // Extracts an embedded DeviceAuthMessage payload from an auth challenge reply |
| @@ -75,6 +90,41 @@ AuthResult ParseAuthMessage(const CastMessage& challenge_reply, |
| return AuthResult(); |
| } |
| +class CastNonce { |
| + public: |
| + static CastNonce* GetInstance() { |
| + return base::Singleton<CastNonce, |
| + base::LeakySingletonTraits<CastNonce>>::get(); |
| + } |
| + |
| + static const std::string& Get() { |
| + GetInstance()->EnsureNonceTimely(); |
| + return GetInstance()->nonce_; |
| + } |
| + |
| + private: |
| + friend struct base::DefaultSingletonTraits<CastNonce>; |
| + |
| + CastNonce() { GenerateNonce(); } |
| + void GenerateNonce() { |
| + nonce_ = base::GenerateGUID(); |
| + nonce_generation_time_ = base::Time::Now(); |
| + } |
| + |
| + void EnsureNonceTimely() { |
| + if (base::Time::Now() > |
| + (nonce_generation_time_ + |
| + base::TimeDelta::FromHours(kNonceExpirationTimeInHours))) { |
| + GenerateNonce(); |
| + } |
| + } |
| + |
| + // The nonce challenge to send to the Cast receiver. |
| + // The nonce is updated daily. |
| + std::string nonce_; |
| + base::Time nonce_generation_time_; |
| +}; |
| + |
| // Must match with histogram enum CastCertificateStatus. |
| // This should never be reordered. |
| enum CertVerificationStatus { |
| @@ -85,6 +135,13 @@ enum CertVerificationStatus { |
| CERT_STATUS_COUNT, |
| }; |
| +enum NonceVerificationStatus { |
| + NONCE_MATCH, |
| + NONCE_MISMATCH, |
| + NONCE_MISSING, |
| + NONCE_COUNT, |
| +}; |
| + |
| } // namespace |
| AuthResult::AuthResult() |
| @@ -102,19 +159,19 @@ AuthResult AuthResult::CreateWithParseError(const std::string& error_message, |
| return AuthResult(kParseErrorPrefix + error_message, error_type); |
| } |
| -AuthResult AuthenticateChallengeReply(const CastMessage& challenge_reply, |
| - const net::X509Certificate& peer_cert) { |
| - DeviceAuthMessage auth_message; |
| - AuthResult result = ParseAuthMessage(challenge_reply, &auth_message); |
| - if (!result.success()) { |
| - return result; |
| - } |
| +AuthContext::AuthContext(const std::string& nonce) : nonce_(nonce) {} |
|
mark a. foltz
2017/03/10 00:47:23
Can you explain why you need to wrap a single nonc
ryanchung
2017/03/10 02:10:27
I just thought it'd be cleaner to encapsulate the
|
| +AuthContext::~AuthContext() {} |
| + |
| +// Verifies the peer certificate and populates |peer_cert_der| with the DER |
| +// encoded certificate. |
| +AuthResult VerifyTLSCertificate(const net::X509Certificate& peer_cert, |
| + std::string* peer_cert_der, |
| + const base::Time& verification_time) { |
| // Get the DER-encoded form of the certificate. |
| - std::string peer_cert_der; |
| if (!net::X509Certificate::GetDEREncoded(peer_cert.os_cert_handle(), |
| - &peer_cert_der) || |
| - peer_cert_der.empty()) { |
| + peer_cert_der) || |
| + peer_cert_der->empty()) { |
| return AuthResult::CreateWithParseError( |
| "Could not create DER-encoded peer cert.", |
| AuthResult::ERROR_CERT_PARSING_FAILED); |
| @@ -126,15 +183,15 @@ AuthResult AuthenticateChallengeReply(const CastMessage& challenge_reply, |
| // is repurposed as this signature's expiration. |
| base::Time expiry = peer_cert.valid_expiry(); |
| base::Time lifetime_limit = |
| - base::Time::Now() + |
| + verification_time + |
| base::TimeDelta::FromDays(kMaxSelfSignedCertLifetimeInDays); |
| if (peer_cert.valid_start().is_null() || |
| - peer_cert.valid_start() > base::Time::Now()) { |
| + peer_cert.valid_start() > verification_time) { |
| return AuthResult::CreateWithParseError( |
| "Certificate's valid start date is in the future.", |
| AuthResult::ERROR_TLS_CERT_VALID_START_DATE_IN_FUTURE); |
| } |
| - if (expiry.is_null() || peer_cert.HasExpired()) { |
| + if (expiry.is_null() || peer_cert.valid_expiry() < verification_time) { |
| return AuthResult::CreateWithParseError("Certificate has expired.", |
| AuthResult::ERROR_TLS_CERT_EXPIRED); |
| } |
| @@ -143,9 +200,55 @@ AuthResult AuthenticateChallengeReply(const CastMessage& challenge_reply, |
| "Peer cert lifetime is too long.", |
| AuthResult::ERROR_TLS_CERT_VALIDITY_PERIOD_TOO_LONG); |
| } |
| + return AuthResult(); |
| +} |
| + |
| +// Verifies the nonce received in the response is equivalent to the one sent. |
| +AuthResult VerifySenderNonce(const std::string& nonce, |
| + const std::string& nonce_response) { |
| + if (nonce != nonce_response) { |
| + if (nonce_response.empty()) { |
| + UMA_HISTOGRAM_ENUMERATION("Cast.Channel.Nonce", NONCE_MISSING, |
| + NONCE_COUNT); |
| + } else { |
| + UMA_HISTOGRAM_ENUMERATION("Cast.Channel.Nonce", NONCE_MISMATCH, |
| + NONCE_COUNT); |
| + } |
| + if (base::FeatureList::IsEnabled(kEnforceNonceChecking)) { |
| + return AuthResult("Sender nonce mismatched.", |
| + AuthResult::ERROR_SENDER_NONCE_MISMATCH); |
| + } |
| + } else { |
| + UMA_HISTOGRAM_ENUMERATION("Cast.Channel.Nonce", NONCE_MATCH, NONCE_COUNT); |
| + } |
| + return AuthResult(); |
| +} |
| + |
| +AuthResult AuthenticateChallengeReply(const CastMessage& challenge_reply, |
| + const net::X509Certificate& peer_cert, |
| + const AuthContext* auth_context) { |
|
mark a. foltz
2017/03/10 00:47:23
const AuthContext&
ryanchung
2017/03/10 02:10:27
Done.
|
| + DCHECK(auth_context); |
| + DeviceAuthMessage auth_message; |
| + AuthResult result = ParseAuthMessage(challenge_reply, &auth_message); |
| + if (!result.success()) { |
| + return result; |
| + } |
| + |
| + std::string peer_cert_der; |
| + result = VerifyTLSCertificate(peer_cert, &peer_cert_der, base::Time::Now()); |
| + if (!result.success()) { |
| + return result; |
| + } |
| const AuthResponse& response = auth_message.response(); |
| - return VerifyCredentials(response, peer_cert_der); |
| + const std::string& nonce_response = response.sender_nonce(); |
| + |
| + result = VerifySenderNonce(auth_context->nonce(), nonce_response); |
| + if (!result.success()) { |
| + return result; |
| + } |
| + |
| + return VerifyCredentials(response, nonce_response + peer_cert_der); |
| } |
| // This function does the following |
| @@ -276,6 +379,12 @@ AuthResult VerifyCredentialsForTest(const AuthResponse& response, |
| verification_time); |
| } |
| +std::unique_ptr<AuthContext> GetChallengeContext() { |
| + std::unique_ptr<AuthContext> context = |
| + base::MakeUnique<AuthContext>(CastNonce::Get()); |
| + return context; |
| +} |
| + |
| } // namespace cast_channel |
| } // namespace api |
| } // namespace extensions |