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

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

Powered by Google App Engine
This is Rietveld 408576698