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

Side by Side Diff: extensions/browser/api/cast_channel/cast_auth_util.cc

Issue 2926313002: Revert of [cast_channel] Move cast_channel related files from //extensions to //components (Closed)
Patch Set: Created 3 years, 6 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
(Empty)
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
3 // found in the LICENSE file.
4
5 #include "extensions/browser/api/cast_channel/cast_auth_util.h"
6
7 #include <vector>
8
9 #include "base/feature_list.h"
10 #include "base/logging.h"
11 #include "base/macros.h"
12 #include "base/memory/ptr_util.h"
13 #include "base/memory/singleton.h"
14 #include "base/metrics/histogram_macros.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/stringprintf.h"
18 #include "components/cast_certificate/cast_cert_validator.h"
19 #include "components/cast_certificate/cast_crl.h"
20 #include "crypto/random.h"
21 #include "extensions/browser/api/cast_channel/cast_message_util.h"
22 #include "extensions/common/api/cast_channel/cast_channel.pb.h"
23 #include "net/cert/x509_certificate.h"
24 #include "net/der/parse_values.h"
25
26 namespace extensions {
27 namespace api {
28 namespace cast_channel {
29 namespace {
30
31 const char kParseErrorPrefix[] = "Failed to parse auth message: ";
32
33 // The maximum number of days a cert can live for.
34 const int kMaxSelfSignedCertLifetimeInDays = 4;
35
36 // The size of the nonce challenge in bytes.
37 const int kNonceSizeInBytes = 16;
38
39 // The number of hours after which a nonce is regenerated.
40 long kNonceExpirationTimeInHours = 24;
41
42 // Enforce certificate revocation when enabled.
43 // If disabled, any revocation failures are ignored.
44 //
45 // This flags only controls the enforcement. Revocation is checked regardless.
46 //
47 // This flag tracks the changes necessary to fully enforce revocation.
48 const base::Feature kEnforceRevocationChecking{
49 "CastCertificateRevocation", base::FEATURE_DISABLED_BY_DEFAULT};
50
51 // Enforce nonce checking when enabled.
52 // If disabled, the nonce value returned from the device is not checked against
53 // the one sent to the device. As a result, the nonce can be empty and omitted
54 // from the signature. This allows backwards compatibility with legacy Cast
55 // receivers.
56
57 const base::Feature kEnforceNonceChecking{"CastNonceEnforced",
58 base::FEATURE_DISABLED_BY_DEFAULT};
59
60 namespace cast_crypto = ::cast_certificate;
61
62 // Extracts an embedded DeviceAuthMessage payload from an auth challenge reply
63 // message.
64 AuthResult ParseAuthMessage(const CastMessage& challenge_reply,
65 DeviceAuthMessage* auth_message) {
66 if (challenge_reply.payload_type() != CastMessage_PayloadType_BINARY) {
67 return AuthResult::CreateWithParseError(
68 "Wrong payload type in challenge reply",
69 AuthResult::ERROR_WRONG_PAYLOAD_TYPE);
70 }
71 if (!challenge_reply.has_payload_binary()) {
72 return AuthResult::CreateWithParseError(
73 "Payload type is binary but payload_binary field not set",
74 AuthResult::ERROR_NO_PAYLOAD);
75 }
76 if (!auth_message->ParseFromString(challenge_reply.payload_binary())) {
77 return AuthResult::CreateWithParseError(
78 "Cannot parse binary payload into DeviceAuthMessage",
79 AuthResult::ERROR_PAYLOAD_PARSING_FAILED);
80 }
81
82 VLOG(1) << "Auth message: " << AuthMessageToString(*auth_message);
83
84 if (auth_message->has_error()) {
85 return AuthResult::CreateWithParseError(
86 "Auth message error: " +
87 base::IntToString(auth_message->error().error_type()),
88 AuthResult::ERROR_MESSAGE_ERROR);
89 }
90 if (!auth_message->has_response()) {
91 return AuthResult::CreateWithParseError(
92 "Auth message has no response field", AuthResult::ERROR_NO_RESPONSE);
93 }
94 return AuthResult();
95 }
96
97 class CastNonce {
98 public:
99 static CastNonce* GetInstance() {
100 return base::Singleton<CastNonce,
101 base::LeakySingletonTraits<CastNonce>>::get();
102 }
103
104 static const std::string& Get() {
105 GetInstance()->EnsureNonceTimely();
106 return GetInstance()->nonce_;
107 }
108
109 private:
110 friend struct base::DefaultSingletonTraits<CastNonce>;
111
112 CastNonce() { GenerateNonce(); }
113 void GenerateNonce() {
114 // Create a cryptographically secure nonce.
115 crypto::RandBytes(base::WriteInto(&nonce_, kNonceSizeInBytes + 1),
116 kNonceSizeInBytes);
117 nonce_generation_time_ = base::Time::Now();
118 }
119
120 void EnsureNonceTimely() {
121 if (base::Time::Now() >
122 (nonce_generation_time_ +
123 base::TimeDelta::FromHours(kNonceExpirationTimeInHours))) {
124 GenerateNonce();
125 }
126 }
127
128 // The nonce challenge to send to the Cast receiver.
129 // The nonce is updated daily.
130 std::string nonce_;
131 base::Time nonce_generation_time_;
132 };
133
134 // Must match with histogram enum CastCertificateStatus.
135 // This should never be reordered.
136 enum CertVerificationStatus {
137 CERT_STATUS_OK,
138 CERT_STATUS_INVALID_CRL,
139 CERT_STATUS_VERIFICATION_FAILED,
140 CERT_STATUS_REVOKED,
141 CERT_STATUS_COUNT,
142 };
143
144 // Must match with histogram enum CastNonce.
145 // This should never be reordered.
146 enum NonceVerificationStatus {
147 NONCE_MATCH,
148 NONCE_MISMATCH,
149 NONCE_MISSING,
150 NONCE_COUNT,
151 };
152
153 // Record certificate verification histogram events.
154 void RecordCertificateEvent(CertVerificationStatus event) {
155 UMA_HISTOGRAM_ENUMERATION("Cast.Channel.Certificate", event,
156 CERT_STATUS_COUNT);
157 }
158
159 // Record nonce verification histogram events.
160 void RecordNonceEvent(NonceVerificationStatus event) {
161 UMA_HISTOGRAM_ENUMERATION("Cast.Channel.Nonce", event, NONCE_COUNT);
162 }
163
164 } // namespace
165
166 AuthResult::AuthResult()
167 : error_type(ERROR_NONE), channel_policies(POLICY_NONE) {}
168
169 AuthResult::AuthResult(const std::string& error_message, ErrorType error_type)
170 : error_message(error_message), error_type(error_type) {}
171
172 AuthResult::~AuthResult() {
173 }
174
175 // static
176 AuthResult AuthResult::CreateWithParseError(const std::string& error_message,
177 ErrorType error_type) {
178 return AuthResult(kParseErrorPrefix + error_message, error_type);
179 }
180
181 // static
182 AuthContext AuthContext::Create() {
183 return AuthContext(CastNonce::Get());
184 }
185
186 AuthContext::AuthContext(const std::string& nonce) : nonce_(nonce) {}
187
188 AuthContext::~AuthContext() {}
189
190 AuthResult AuthContext::VerifySenderNonce(
191 const std::string& nonce_response) const {
192 if (nonce_ != nonce_response) {
193 if (nonce_response.empty()) {
194 RecordNonceEvent(NONCE_MISSING);
195 } else {
196 RecordNonceEvent(NONCE_MISMATCH);
197 }
198 if (base::FeatureList::IsEnabled(kEnforceNonceChecking)) {
199 return AuthResult("Sender nonce mismatched.",
200 AuthResult::ERROR_SENDER_NONCE_MISMATCH);
201 }
202 } else {
203 RecordNonceEvent(NONCE_MATCH);
204 }
205 return AuthResult();
206 }
207
208 // Verifies the peer certificate and populates |peer_cert_der| with the DER
209 // encoded certificate.
210 AuthResult VerifyTLSCertificate(const net::X509Certificate& peer_cert,
211 std::string* peer_cert_der,
212 const base::Time& verification_time) {
213 // Get the DER-encoded form of the certificate.
214 if (!net::X509Certificate::GetDEREncoded(peer_cert.os_cert_handle(),
215 peer_cert_der) ||
216 peer_cert_der->empty()) {
217 return AuthResult::CreateWithParseError(
218 "Could not create DER-encoded peer cert.",
219 AuthResult::ERROR_CERT_PARSING_FAILED);
220 }
221
222 // Ensure the peer cert is valid and doesn't have an excessive remaining
223 // lifetime. Although it is not verified as an X.509 certificate, the entire
224 // structure is signed by the AuthResponse, so the validity field from X.509
225 // is repurposed as this signature's expiration.
226 base::Time expiry = peer_cert.valid_expiry();
227 base::Time lifetime_limit =
228 verification_time +
229 base::TimeDelta::FromDays(kMaxSelfSignedCertLifetimeInDays);
230 if (peer_cert.valid_start().is_null() ||
231 peer_cert.valid_start() > verification_time) {
232 return AuthResult::CreateWithParseError(
233 "Certificate's valid start date is in the future.",
234 AuthResult::ERROR_TLS_CERT_VALID_START_DATE_IN_FUTURE);
235 }
236 if (expiry.is_null() || peer_cert.valid_expiry() < verification_time) {
237 return AuthResult::CreateWithParseError("Certificate has expired.",
238 AuthResult::ERROR_TLS_CERT_EXPIRED);
239 }
240 if (expiry > lifetime_limit) {
241 return AuthResult::CreateWithParseError(
242 "Peer cert lifetime is too long.",
243 AuthResult::ERROR_TLS_CERT_VALIDITY_PERIOD_TOO_LONG);
244 }
245 return AuthResult();
246 }
247
248 AuthResult AuthenticateChallengeReply(const CastMessage& challenge_reply,
249 const net::X509Certificate& peer_cert,
250 const AuthContext& auth_context) {
251 DeviceAuthMessage auth_message;
252 AuthResult result = ParseAuthMessage(challenge_reply, &auth_message);
253 if (!result.success()) {
254 return result;
255 }
256
257 std::string peer_cert_der;
258 result = VerifyTLSCertificate(peer_cert, &peer_cert_der, base::Time::Now());
259 if (!result.success()) {
260 return result;
261 }
262
263 const AuthResponse& response = auth_message.response();
264 const std::string& nonce_response = response.sender_nonce();
265
266 result = auth_context.VerifySenderNonce(nonce_response);
267 if (!result.success()) {
268 return result;
269 }
270
271 return VerifyCredentials(response, nonce_response + peer_cert_der);
272 }
273
274 // This function does the following
275 //
276 // * Verifies that the certificate chain |response.client_auth_certificate| +
277 // |response.intermediate_certificate| is valid and chains to a trusted
278 // Cast root. The list of trusted Cast roots can be overrided by providing a
279 // non-nullptr |cast_trust_store|. The certificate is verified at
280 // |verification_time|.
281 //
282 // * Verifies that none of the certificates in the chain are revoked based on
283 // the CRL provided in the response |response.crl|. The CRL is verified to be
284 // valid and its issuer certificate chains to a trusted Cast CRL root. The
285 // list of trusted Cast CRL roots can be overrided by providing a non-nullptr
286 // |crl_trust_store|. If |crl_policy| is CRL_OPTIONAL then the result of
287 // revocation checking is ignored. The CRL is verified at
288 // |verification_time|.
289 //
290 // * Verifies that |response.signature| matches the signature
291 // of |signature_input| by |response.client_auth_certificate|'s public
292 // key.
293 AuthResult VerifyCredentialsImpl(const AuthResponse& response,
294 const std::string& signature_input,
295 const cast_crypto::CRLPolicy& crl_policy,
296 net::TrustStore* cast_trust_store,
297 net::TrustStore* crl_trust_store,
298 const base::Time& verification_time) {
299 // Verify the certificate
300 std::unique_ptr<cast_crypto::CertVerificationContext> verification_context;
301
302 // Build a single vector containing the certificate chain.
303 std::vector<std::string> cert_chain;
304 cert_chain.push_back(response.client_auth_certificate());
305 cert_chain.insert(cert_chain.end(),
306 response.intermediate_certificate().begin(),
307 response.intermediate_certificate().end());
308
309 // Parse the CRL.
310 std::unique_ptr<cast_crypto::CastCRL> crl =
311 cast_crypto::ParseAndVerifyCRLUsingCustomTrustStore(
312 response.crl(), verification_time, crl_trust_store);
313 if (!crl) {
314 // CRL is invalid.
315 RecordCertificateEvent(CERT_STATUS_INVALID_CRL);
316 if (crl_policy == cast_crypto::CRLPolicy::CRL_REQUIRED) {
317 return AuthResult("Failed verifying Cast CRL.",
318 AuthResult::ERROR_CRL_INVALID);
319 }
320 }
321
322 cast_crypto::CastDeviceCertPolicy device_policy;
323 bool verification_success =
324 cast_crypto::VerifyDeviceCertUsingCustomTrustStore(
325 cert_chain, verification_time, &verification_context, &device_policy,
326 crl.get(), crl_policy, cast_trust_store);
327 if (!verification_success) {
328 // TODO(ryanchung): Once this feature is completely rolled-out, remove the
329 // reverification step and use error reporting to get verification errors
330 // for metrics.
331 bool verification_no_crl_success =
332 cast_crypto::VerifyDeviceCertUsingCustomTrustStore(
333 cert_chain, verification_time, &verification_context,
334 &device_policy, nullptr, cast_crypto::CRLPolicy::CRL_OPTIONAL,
335 cast_trust_store);
336 if (!verification_no_crl_success) {
337 // TODO(eroman): The error information was lost; this error is ambiguous.
338 RecordCertificateEvent(CERT_STATUS_VERIFICATION_FAILED);
339 return AuthResult("Failed verifying cast device certificate",
340 AuthResult::ERROR_CERT_NOT_SIGNED_BY_TRUSTED_CA);
341 }
342 if (crl) {
343 // If CRL was not present, it should've been recorded as such.
344 RecordCertificateEvent(CERT_STATUS_REVOKED);
345 }
346 if (crl_policy == cast_crypto::CRLPolicy::CRL_REQUIRED) {
347 // Device is revoked.
348 return AuthResult("Failed certificate revocation check.",
349 AuthResult::ERROR_CERT_REVOKED);
350 }
351 }
352 // The certificate is verified at this point.
353 RecordCertificateEvent(CERT_STATUS_OK);
354 if (!verification_context->VerifySignatureOverData(response.signature(),
355 signature_input)) {
356 return AuthResult("Failed verifying signature over data",
357 AuthResult::ERROR_SIGNED_BLOBS_MISMATCH);
358 }
359
360 AuthResult success;
361
362 // Set the policy into the result.
363 switch (device_policy) {
364 case cast_crypto::CastDeviceCertPolicy::AUDIO_ONLY:
365 success.channel_policies = AuthResult::POLICY_AUDIO_ONLY;
366 break;
367 case cast_crypto::CastDeviceCertPolicy::NONE:
368 success.channel_policies = AuthResult::POLICY_NONE;
369 break;
370 }
371
372 return success;
373 }
374
375 AuthResult VerifyCredentials(const AuthResponse& response,
376 const std::string& signature_input) {
377 base::Time now = base::Time::Now();
378 cast_crypto::CRLPolicy policy = cast_crypto::CRLPolicy::CRL_REQUIRED;
379 if (!base::FeatureList::IsEnabled(kEnforceRevocationChecking)) {
380 policy = cast_crypto::CRLPolicy::CRL_OPTIONAL;
381 }
382 return VerifyCredentialsImpl(response, signature_input, policy, nullptr,
383 nullptr, now);
384 }
385
386 AuthResult VerifyCredentialsForTest(const AuthResponse& response,
387 const std::string& signature_input,
388 const cast_crypto::CRLPolicy& crl_policy,
389 net::TrustStore* cast_trust_store,
390 net::TrustStore* crl_trust_store,
391 const base::Time& verification_time) {
392 return VerifyCredentialsImpl(response, signature_input, crl_policy,
393 cast_trust_store, crl_trust_store,
394 verification_time);
395 }
396
397 } // namespace cast_channel
398 } // namespace api
399 } // namespace extensions
OLDNEW
« no previous file with comments | « extensions/browser/api/cast_channel/cast_auth_util.h ('k') | extensions/browser/api/cast_channel/cast_auth_util_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698