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

Side by Side 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 unified diff | Download patch
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "extensions/browser/api/cast_channel/cast_auth_util.h" 5 #include "extensions/browser/api/cast_channel/cast_auth_util.h"
6 6
7 #include <vector> 7 #include <vector>
8 8
9 #include "base/feature_list.h" 9 #include "base/feature_list.h"
10 #include "base/guid.h"
10 #include "base/logging.h" 11 #include "base/logging.h"
11 #include "base/macros.h" 12 #include "base/macros.h"
13 #include "base/memory/ptr_util.h"
14 #include "base/memory/singleton.h"
12 #include "base/metrics/histogram_macros.h" 15 #include "base/metrics/histogram_macros.h"
13 #include "base/strings/string_number_conversions.h" 16 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/stringprintf.h" 17 #include "base/strings/stringprintf.h"
15 #include "components/cast_certificate/cast_cert_validator.h" 18 #include "components/cast_certificate/cast_cert_validator.h"
16 #include "components/cast_certificate/cast_crl.h" 19 #include "components/cast_certificate/cast_crl.h"
17 #include "extensions/browser/api/cast_channel/cast_message_util.h" 20 #include "extensions/browser/api/cast_channel/cast_message_util.h"
18 #include "extensions/common/api/cast_channel/cast_channel.pb.h" 21 #include "extensions/common/api/cast_channel/cast_channel.pb.h"
19 #include "net/cert/x509_certificate.h" 22 #include "net/cert/x509_certificate.h"
20 #include "net/der/parse_values.h" 23 #include "net/der/parse_values.h"
21 24
22 namespace extensions { 25 namespace extensions {
23 namespace api { 26 namespace api {
24 namespace cast_channel { 27 namespace cast_channel {
25 namespace { 28 namespace {
26 29
27 const char kParseErrorPrefix[] = "Failed to parse auth message: "; 30 const char kParseErrorPrefix[] = "Failed to parse auth message: ";
28 31
29 // The maximum number of days a cert can live for. 32 // The maximum number of days a cert can live for.
30 const int kMaxSelfSignedCertLifetimeInDays = 4; 33 const int kMaxSelfSignedCertLifetimeInDays = 4;
31 34
35 // The number of hours after which a nonce is regenerated.
36 long kNonceExpirationTimeInHours = 24;
37
32 // Enforce certificate revocation when enabled. 38 // Enforce certificate revocation when enabled.
33 // If disabled, any revocation failures are ignored. 39 // If disabled, any revocation failures are ignored.
34 // 40 //
35 // This flags only controls the enforcement. Revocation is checked regardless. 41 // This flags only controls the enforcement. Revocation is checked regardless.
36 // 42 //
37 // This flag tracks the changes necessary to fully enforce revocation. 43 // This flag tracks the changes necessary to fully enforce revocation.
38 const base::Feature kEnforceRevocationChecking{ 44 const base::Feature kEnforceRevocationChecking{
39 "CastCertificateRevocation", base::FEATURE_DISABLED_BY_DEFAULT}; 45 "CastCertificateRevocation", base::FEATURE_DISABLED_BY_DEFAULT};
40 46
47 // Enforce nonce checking when enabled.
48 // If disabled, the nonce value returned from the device is not checked against
49 // the one sent to the device. As a result, the nonce can be empty and omitted
50 // from the signature. This allows backwards compatibility with legacy Cast
51 // receivers.
52
53 const base::Feature kEnforceNonceChecking{"CastNonceEnforced",
54 base::FEATURE_DISABLED_BY_DEFAULT};
55
41 namespace cast_crypto = ::cast_certificate; 56 namespace cast_crypto = ::cast_certificate;
42 57
43 // Extracts an embedded DeviceAuthMessage payload from an auth challenge reply 58 // Extracts an embedded DeviceAuthMessage payload from an auth challenge reply
44 // message. 59 // message.
45 AuthResult ParseAuthMessage(const CastMessage& challenge_reply, 60 AuthResult ParseAuthMessage(const CastMessage& challenge_reply,
46 DeviceAuthMessage* auth_message) { 61 DeviceAuthMessage* auth_message) {
47 if (challenge_reply.payload_type() != CastMessage_PayloadType_BINARY) { 62 if (challenge_reply.payload_type() != CastMessage_PayloadType_BINARY) {
48 return AuthResult::CreateWithParseError( 63 return AuthResult::CreateWithParseError(
49 "Wrong payload type in challenge reply", 64 "Wrong payload type in challenge reply",
50 AuthResult::ERROR_WRONG_PAYLOAD_TYPE); 65 AuthResult::ERROR_WRONG_PAYLOAD_TYPE);
(...skipping 17 matching lines...) Expand all
68 base::IntToString(auth_message->error().error_type()), 83 base::IntToString(auth_message->error().error_type()),
69 AuthResult::ERROR_MESSAGE_ERROR); 84 AuthResult::ERROR_MESSAGE_ERROR);
70 } 85 }
71 if (!auth_message->has_response()) { 86 if (!auth_message->has_response()) {
72 return AuthResult::CreateWithParseError( 87 return AuthResult::CreateWithParseError(
73 "Auth message has no response field", AuthResult::ERROR_NO_RESPONSE); 88 "Auth message has no response field", AuthResult::ERROR_NO_RESPONSE);
74 } 89 }
75 return AuthResult(); 90 return AuthResult();
76 } 91 }
77 92
93 class CastNonce {
94 public:
95 static CastNonce* GetInstance() {
96 return base::Singleton<CastNonce,
97 base::LeakySingletonTraits<CastNonce>>::get();
98 }
99
100 static const std::string& Get() {
101 GetInstance()->EnsureNonceTimely();
102 return GetInstance()->nonce_;
103 }
104
105 private:
106 friend struct base::DefaultSingletonTraits<CastNonce>;
107
108 CastNonce() { GenerateNonce(); }
109 void GenerateNonce() {
110 nonce_ = base::GenerateGUID();
111 nonce_generation_time_ = base::Time::Now();
112 }
113
114 void EnsureNonceTimely() {
115 if (base::Time::Now() >
116 (nonce_generation_time_ +
117 base::TimeDelta::FromHours(kNonceExpirationTimeInHours))) {
118 GenerateNonce();
119 }
120 }
121
122 // The nonce challenge to send to the Cast receiver.
123 // The nonce is updated daily.
124 std::string nonce_;
125 base::Time nonce_generation_time_;
126 };
127
78 // Must match with histogram enum CastCertificateStatus. 128 // Must match with histogram enum CastCertificateStatus.
79 // This should never be reordered. 129 // This should never be reordered.
80 enum CertVerificationStatus { 130 enum CertVerificationStatus {
81 CERT_STATUS_OK, 131 CERT_STATUS_OK,
82 CERT_STATUS_INVALID_CRL, 132 CERT_STATUS_INVALID_CRL,
83 CERT_STATUS_VERIFICATION_FAILED, 133 CERT_STATUS_VERIFICATION_FAILED,
84 CERT_STATUS_REVOKED, 134 CERT_STATUS_REVOKED,
85 CERT_STATUS_COUNT, 135 CERT_STATUS_COUNT,
86 }; 136 };
87 137
138 enum NonceVerificationStatus {
139 NONCE_MATCH,
140 NONCE_MISMATCH,
141 NONCE_MISSING,
142 NONCE_COUNT,
143 };
144
88 } // namespace 145 } // namespace
89 146
90 AuthResult::AuthResult() 147 AuthResult::AuthResult()
91 : error_type(ERROR_NONE), channel_policies(POLICY_NONE) {} 148 : error_type(ERROR_NONE), channel_policies(POLICY_NONE) {}
92 149
93 AuthResult::AuthResult(const std::string& error_message, ErrorType error_type) 150 AuthResult::AuthResult(const std::string& error_message, ErrorType error_type)
94 : error_message(error_message), error_type(error_type) {} 151 : error_message(error_message), error_type(error_type) {}
95 152
96 AuthResult::~AuthResult() { 153 AuthResult::~AuthResult() {
97 } 154 }
98 155
99 // static 156 // static
100 AuthResult AuthResult::CreateWithParseError(const std::string& error_message, 157 AuthResult AuthResult::CreateWithParseError(const std::string& error_message,
101 ErrorType error_type) { 158 ErrorType error_type) {
102 return AuthResult(kParseErrorPrefix + error_message, error_type); 159 return AuthResult(kParseErrorPrefix + error_message, error_type);
103 } 160 }
104 161
105 AuthResult AuthenticateChallengeReply(const CastMessage& challenge_reply, 162 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
106 const net::X509Certificate& peer_cert) {
107 DeviceAuthMessage auth_message;
108 AuthResult result = ParseAuthMessage(challenge_reply, &auth_message);
109 if (!result.success()) {
110 return result;
111 }
112 163
164 AuthContext::~AuthContext() {}
165
166 // Verifies the peer certificate and populates |peer_cert_der| with the DER
167 // encoded certificate.
168 AuthResult VerifyTLSCertificate(const net::X509Certificate& peer_cert,
169 std::string* peer_cert_der,
170 const base::Time& verification_time) {
113 // Get the DER-encoded form of the certificate. 171 // Get the DER-encoded form of the certificate.
114 std::string peer_cert_der;
115 if (!net::X509Certificate::GetDEREncoded(peer_cert.os_cert_handle(), 172 if (!net::X509Certificate::GetDEREncoded(peer_cert.os_cert_handle(),
116 &peer_cert_der) || 173 peer_cert_der) ||
117 peer_cert_der.empty()) { 174 peer_cert_der->empty()) {
118 return AuthResult::CreateWithParseError( 175 return AuthResult::CreateWithParseError(
119 "Could not create DER-encoded peer cert.", 176 "Could not create DER-encoded peer cert.",
120 AuthResult::ERROR_CERT_PARSING_FAILED); 177 AuthResult::ERROR_CERT_PARSING_FAILED);
121 } 178 }
122 179
123 // Ensure the peer cert is valid and doesn't have an excessive remaining 180 // Ensure the peer cert is valid and doesn't have an excessive remaining
124 // lifetime. Although it is not verified as an X.509 certificate, the entire 181 // lifetime. Although it is not verified as an X.509 certificate, the entire
125 // structure is signed by the AuthResponse, so the validity field from X.509 182 // structure is signed by the AuthResponse, so the validity field from X.509
126 // is repurposed as this signature's expiration. 183 // is repurposed as this signature's expiration.
127 base::Time expiry = peer_cert.valid_expiry(); 184 base::Time expiry = peer_cert.valid_expiry();
128 base::Time lifetime_limit = 185 base::Time lifetime_limit =
129 base::Time::Now() + 186 verification_time +
130 base::TimeDelta::FromDays(kMaxSelfSignedCertLifetimeInDays); 187 base::TimeDelta::FromDays(kMaxSelfSignedCertLifetimeInDays);
131 if (peer_cert.valid_start().is_null() || 188 if (peer_cert.valid_start().is_null() ||
132 peer_cert.valid_start() > base::Time::Now()) { 189 peer_cert.valid_start() > verification_time) {
133 return AuthResult::CreateWithParseError( 190 return AuthResult::CreateWithParseError(
134 "Certificate's valid start date is in the future.", 191 "Certificate's valid start date is in the future.",
135 AuthResult::ERROR_TLS_CERT_VALID_START_DATE_IN_FUTURE); 192 AuthResult::ERROR_TLS_CERT_VALID_START_DATE_IN_FUTURE);
136 } 193 }
137 if (expiry.is_null() || peer_cert.HasExpired()) { 194 if (expiry.is_null() || peer_cert.valid_expiry() < verification_time) {
138 return AuthResult::CreateWithParseError("Certificate has expired.", 195 return AuthResult::CreateWithParseError("Certificate has expired.",
139 AuthResult::ERROR_TLS_CERT_EXPIRED); 196 AuthResult::ERROR_TLS_CERT_EXPIRED);
140 } 197 }
141 if (expiry > lifetime_limit) { 198 if (expiry > lifetime_limit) {
142 return AuthResult::CreateWithParseError( 199 return AuthResult::CreateWithParseError(
143 "Peer cert lifetime is too long.", 200 "Peer cert lifetime is too long.",
144 AuthResult::ERROR_TLS_CERT_VALIDITY_PERIOD_TOO_LONG); 201 AuthResult::ERROR_TLS_CERT_VALIDITY_PERIOD_TOO_LONG);
145 } 202 }
203 return AuthResult();
204 }
205
206 // Verifies the nonce received in the response is equivalent to the one sent.
207 AuthResult VerifySenderNonce(const std::string& nonce,
208 const std::string& nonce_response) {
209 if (nonce != nonce_response) {
210 if (nonce_response.empty()) {
211 UMA_HISTOGRAM_ENUMERATION("Cast.Channel.Nonce", NONCE_MISSING,
212 NONCE_COUNT);
213 } else {
214 UMA_HISTOGRAM_ENUMERATION("Cast.Channel.Nonce", NONCE_MISMATCH,
215 NONCE_COUNT);
216 }
217 if (base::FeatureList::IsEnabled(kEnforceNonceChecking)) {
218 return AuthResult("Sender nonce mismatched.",
219 AuthResult::ERROR_SENDER_NONCE_MISMATCH);
220 }
221 } else {
222 UMA_HISTOGRAM_ENUMERATION("Cast.Channel.Nonce", NONCE_MATCH, NONCE_COUNT);
223 }
224 return AuthResult();
225 }
226
227 AuthResult AuthenticateChallengeReply(const CastMessage& challenge_reply,
228 const net::X509Certificate& peer_cert,
229 const AuthContext* auth_context) {
mark a. foltz 2017/03/10 00:47:23 const AuthContext&
ryanchung 2017/03/10 02:10:27 Done.
230 DCHECK(auth_context);
231 DeviceAuthMessage auth_message;
232 AuthResult result = ParseAuthMessage(challenge_reply, &auth_message);
233 if (!result.success()) {
234 return result;
235 }
236
237 std::string peer_cert_der;
238 result = VerifyTLSCertificate(peer_cert, &peer_cert_der, base::Time::Now());
239 if (!result.success()) {
240 return result;
241 }
146 242
147 const AuthResponse& response = auth_message.response(); 243 const AuthResponse& response = auth_message.response();
148 return VerifyCredentials(response, peer_cert_der); 244 const std::string& nonce_response = response.sender_nonce();
245
246 result = VerifySenderNonce(auth_context->nonce(), nonce_response);
247 if (!result.success()) {
248 return result;
249 }
250
251 return VerifyCredentials(response, nonce_response + peer_cert_der);
149 } 252 }
150 253
151 // This function does the following 254 // This function does the following
152 // 255 //
153 // * Verifies that the certificate chain |response.client_auth_certificate| + 256 // * Verifies that the certificate chain |response.client_auth_certificate| +
154 // |response.intermediate_certificate| is valid and chains to a trusted 257 // |response.intermediate_certificate| is valid and chains to a trusted
155 // Cast root. The list of trusted Cast roots can be overrided by providing a 258 // Cast root. The list of trusted Cast roots can be overrided by providing a
156 // non-nullptr |cast_trust_store|. The certificate is verified at 259 // non-nullptr |cast_trust_store|. The certificate is verified at
157 // |verification_time|. 260 // |verification_time|.
158 // 261 //
(...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after
269 const std::string& signature_input, 372 const std::string& signature_input,
270 const cast_crypto::CRLPolicy& crl_policy, 373 const cast_crypto::CRLPolicy& crl_policy,
271 net::TrustStore* cast_trust_store, 374 net::TrustStore* cast_trust_store,
272 net::TrustStore* crl_trust_store, 375 net::TrustStore* crl_trust_store,
273 const base::Time& verification_time) { 376 const base::Time& verification_time) {
274 return VerifyCredentialsImpl(response, signature_input, crl_policy, 377 return VerifyCredentialsImpl(response, signature_input, crl_policy,
275 cast_trust_store, crl_trust_store, 378 cast_trust_store, crl_trust_store,
276 verification_time); 379 verification_time);
277 } 380 }
278 381
382 std::unique_ptr<AuthContext> GetChallengeContext() {
383 std::unique_ptr<AuthContext> context =
384 base::MakeUnique<AuthContext>(CastNonce::Get());
385 return context;
386 }
387
279 } // namespace cast_channel 388 } // namespace cast_channel
280 } // namespace api 389 } // namespace api
281 } // namespace extensions 390 } // namespace extensions
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698