Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(16)

Unified Diff: extensions/browser/api/cast_channel/cast_auth_util.cc

Issue 2709523008: [Cast Channel] Add support for nonce challenge to Cast channel authentication. (Closed)
Patch Set: Addresses comments Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698