OLD | NEW |
| (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 | |
OLD | NEW |