xref: /aosp_15_r20/external/openscreen/cast/sender/channel/cast_auth_util.cc (revision 3f982cf4871df8771c9d4abe6e9a6f8d829b2736)
1 // Copyright 2019 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 "cast/sender/channel/cast_auth_util.h"
6 
7 #include <openssl/rand.h>
8 
9 #include <algorithm>
10 #include <memory>
11 
12 #include "absl/strings/str_cat.h"
13 #include "cast/common/certificate/cast_cert_validator.h"
14 #include "cast/common/certificate/cast_cert_validator_internal.h"
15 #include "cast/common/certificate/cast_crl.h"
16 #include "cast/common/channel/proto/cast_channel.pb.h"
17 #include "platform/api/time.h"
18 #include "platform/base/error.h"
19 #include "util/osp_logging.h"
20 
21 namespace openscreen {
22 namespace cast {
23 
24 using ::cast::channel::AuthResponse;
25 using ::cast::channel::CastMessage;
26 using ::cast::channel::DeviceAuthMessage;
27 using ::cast::channel::HashAlgorithm;
28 
29 namespace {
30 
31 #define PARSE_ERROR_PREFIX "Failed to parse auth message: "
32 
33 // The maximum number of days a cert can live for.
34 constexpr int kMaxSelfSignedCertLifetimeInDays = 4;
35 
36 // The size of the nonce challenge in bytes.
37 constexpr int kNonceSizeInBytes = 16;
38 
39 // The number of hours after which a nonce is regenerated.
40 constexpr int kNonceExpirationTimeInHours = 24;
41 
42 // Extracts an embedded DeviceAuthMessage payload from an auth challenge reply
43 // message.
ParseAuthMessage(const CastMessage & challenge_reply,DeviceAuthMessage * auth_message)44 Error ParseAuthMessage(const CastMessage& challenge_reply,
45                        DeviceAuthMessage* auth_message) {
46   if (challenge_reply.payload_type() !=
47       ::cast::channel::CastMessage_PayloadType_BINARY) {
48     return Error(Error::Code::kCastV2WrongPayloadType,
49                  PARSE_ERROR_PREFIX "Wrong payload type in challenge reply");
50   }
51   if (!challenge_reply.has_payload_binary()) {
52     return Error(Error::Code::kCastV2NoPayload, PARSE_ERROR_PREFIX
53                  "Payload type is binary but payload_binary field not set");
54   }
55   if (!auth_message->ParseFromString(challenge_reply.payload_binary())) {
56     return Error(Error::Code::kCastV2PayloadParsingFailed, PARSE_ERROR_PREFIX
57                  "Cannot parse binary payload into DeviceAuthMessage");
58   }
59 
60   if (auth_message->has_error()) {
61     std::stringstream ss;
62     ss << PARSE_ERROR_PREFIX "Auth message error: "
63        << auth_message->error().error_type();
64     return Error(Error::Code::kCastV2MessageError, ss.str());
65   }
66   if (!auth_message->has_response()) {
67     return Error(Error::Code::kCastV2NoResponse,
68                  PARSE_ERROR_PREFIX "Auth message has no response field");
69   }
70   return Error::None();
71 }
72 
73 class CastNonce {
74  public:
GetInstance()75   static CastNonce* GetInstance() {
76     static CastNonce* cast_nonce = new CastNonce();
77     return cast_nonce;
78   }
79 
Get()80   static const std::string& Get() {
81     GetInstance()->EnsureNonceTimely();
82     return GetInstance()->nonce_;
83   }
84 
85  private:
CastNonce()86   CastNonce() : nonce_(kNonceSizeInBytes, 0) { GenerateNonce(); }
GenerateNonce()87   void GenerateNonce() {
88     OSP_CHECK_EQ(
89         RAND_bytes(reinterpret_cast<uint8_t*>(&nonce_[0]), kNonceSizeInBytes),
90         1);
91     nonce_generation_time_ = GetWallTimeSinceUnixEpoch();
92   }
93 
EnsureNonceTimely()94   void EnsureNonceTimely() {
95     if (GetWallTimeSinceUnixEpoch() >
96         (nonce_generation_time_ +
97          std::chrono::hours(kNonceExpirationTimeInHours))) {
98       GenerateNonce();
99     }
100   }
101 
102   // The nonce challenge to send to the Cast receiver.
103   // The nonce is updated daily.
104   std::string nonce_;
105   std::chrono::seconds nonce_generation_time_;
106 };
107 
108 // Maps an error from certificate verification to an error reported to the
109 // library client.  If crl_required is set to false, all revocation related
110 // errors are ignored.
111 //
112 // TODO(https://issuetracker.google.com/issues/193164666): It would be simpler
113 // to just pass the underlying verification error directly to the client.
MapToOpenscreenError(Error verify_error,bool crl_required)114 Error MapToOpenscreenError(Error verify_error, bool crl_required) {
115   switch (verify_error.code()) {
116     case Error::Code::kErrCertsMissing:
117       return Error(Error::Code::kCastV2PeerCertEmpty,
118                    absl::StrCat("Failed to locate certificates: ",
119                                 verify_error.message()));
120     case Error::Code::kErrCertsParse:
121       return Error(Error::Code::kErrCertsParse,
122                    absl::StrCat("Failed to parse certificates: ",
123                                 verify_error.message()));
124     case Error::Code::kErrCertsDateInvalid:
125       return Error(
126           Error::Code::kCastV2CertNotSignedByTrustedCa,
127           absl::StrCat("Failed date validity check: ", verify_error.message()));
128     case Error::Code::kErrCertsVerifyGeneric:
129       return Error(
130           Error::Code::kCastV2CertNotSignedByTrustedCa,
131           absl::StrCat("Failed with a generic certificate verification error: ",
132                        verify_error.message()));
133     case Error::Code::kErrCertsRestrictions:
134       return Error(Error::Code::kCastV2CertNotSignedByTrustedCa,
135                    absl::StrCat("Failed certificate restrictions: ",
136                                 verify_error.message()));
137     case Error::Code::kErrCertsVerifyUntrustedCert:
138       return Error(Error::Code::kCastV2CertNotSignedByTrustedCa,
139                    absl::StrCat("Failed with untrusted certificate: ",
140                                 verify_error.message()));
141     case Error::Code::kErrCrlInvalid:
142       // This error is only encountered if |crl_required| is true.
143       OSP_DCHECK(crl_required);
144       return Error(Error::Code::kErrCrlInvalid,
145                    absl::StrCat("Failed to provide a valid CRL: ",
146                                 verify_error.message()));
147     case Error::Code::kErrCertsRevoked:
148       return Error(Error::Code::kErrCertsRevoked,
149                    absl::StrCat("Failed certificate revocation check: ",
150                                 verify_error.message()));
151     case Error::Code::kNone:
152       return Error::None();
153     default:
154       return Error(Error::Code::kCastV2CertNotSignedByTrustedCa,
155                    absl::StrCat("Failed verifying cast device certificate: ",
156                                 verify_error.message()));
157   }
158 }
159 
VerifyAndMapDigestAlgorithm(HashAlgorithm response_digest_algorithm,DigestAlgorithm * digest_algorithm,bool enforce_sha256_checking)160 Error VerifyAndMapDigestAlgorithm(HashAlgorithm response_digest_algorithm,
161                                   DigestAlgorithm* digest_algorithm,
162                                   bool enforce_sha256_checking) {
163   switch (response_digest_algorithm) {
164     case ::cast::channel::SHA1:
165       if (enforce_sha256_checking) {
166         return Error(Error::Code::kCastV2DigestUnsupported,
167                      "Unsupported digest algorithm.");
168       }
169       *digest_algorithm = DigestAlgorithm::kSha1;
170       break;
171     case ::cast::channel::SHA256:
172       *digest_algorithm = DigestAlgorithm::kSha256;
173       break;
174     default:
175       return Error::Code::kCastV2DigestUnsupported;
176   }
177   return Error::None();
178 }
179 
180 }  // namespace
181 
182 // static
Create()183 AuthContext AuthContext::Create() {
184   return AuthContext(CastNonce::Get());
185 }
186 
187 // static
CreateForTest(const std::string & nonce_data)188 AuthContext AuthContext::CreateForTest(const std::string& nonce_data) {
189   std::string nonce;
190   if (nonce_data.empty()) {
191     nonce = std::string(kNonceSizeInBytes, '0');
192   } else {
193     while (nonce.size() < kNonceSizeInBytes) {
194       nonce += nonce_data;
195     }
196     nonce.erase(kNonceSizeInBytes);
197   }
198   OSP_DCHECK_EQ(nonce.size(), kNonceSizeInBytes);
199   return AuthContext(nonce);
200 }
201 
AuthContext(const std::string & nonce)202 AuthContext::AuthContext(const std::string& nonce) : nonce_(nonce) {}
203 
~AuthContext()204 AuthContext::~AuthContext() {}
205 
VerifySenderNonce(const std::string & nonce_response,bool enforce_nonce_checking) const206 Error AuthContext::VerifySenderNonce(const std::string& nonce_response,
207                                      bool enforce_nonce_checking) const {
208   if (nonce_ != nonce_response) {
209     if (enforce_nonce_checking) {
210       return Error(Error::Code::kCastV2SenderNonceMismatch,
211                    "Sender nonce mismatched.");
212     }
213   }
214   return Error::None();
215 }
216 
VerifyTLSCertificateValidity(X509 * peer_cert,std::chrono::seconds verification_time)217 Error VerifyTLSCertificateValidity(X509* peer_cert,
218                                    std::chrono::seconds verification_time) {
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   DateTime not_before;
224   DateTime not_after;
225   if (!GetCertValidTimeRange(peer_cert, &not_before, &not_after)) {
226     return Error(Error::Code::kErrCertsParse,
227                  PARSE_ERROR_PREFIX "Parsing validity fields failed.");
228   }
229 
230   std::chrono::seconds lifetime_limit =
231       verification_time +
232       std::chrono::hours(24 * kMaxSelfSignedCertLifetimeInDays);
233   DateTime verification_time_exploded = {};
234   DateTime lifetime_limit_exploded = {};
235   OSP_CHECK(DateTimeFromSeconds(verification_time.count(),
236                                 &verification_time_exploded));
237   OSP_CHECK(
238       DateTimeFromSeconds(lifetime_limit.count(), &lifetime_limit_exploded));
239   if (verification_time_exploded < not_before) {
240     return Error(Error::Code::kCastV2TlsCertValidStartDateInFuture,
241                  PARSE_ERROR_PREFIX
242                  "Certificate's valid start date is in the future.");
243   }
244   if (not_after < verification_time_exploded) {
245     return Error(Error::Code::kCastV2TlsCertExpired,
246                  PARSE_ERROR_PREFIX "Certificate has expired.");
247   }
248   if (lifetime_limit_exploded < not_after) {
249     return Error(Error::Code::kCastV2TlsCertValidityPeriodTooLong,
250                  PARSE_ERROR_PREFIX "Peer cert lifetime is too long.");
251   }
252   return Error::None();
253 }
254 
255 ErrorOr<CastDeviceCertPolicy> VerifyCredentialsImpl(
256     const AuthResponse& response,
257     const std::vector<uint8_t>& signature_input,
258     const CRLPolicy& crl_policy,
259     TrustStore* cast_trust_store,
260     TrustStore* crl_trust_store,
261     const DateTime& verification_time,
262     bool enforce_sha256_checking);
263 
AuthenticateChallengeReplyImpl(const CastMessage & challenge_reply,X509 * peer_cert,const AuthContext & auth_context,const CRLPolicy & crl_policy,TrustStore * cast_trust_store,TrustStore * crl_trust_store,const DateTime & verification_time)264 ErrorOr<CastDeviceCertPolicy> AuthenticateChallengeReplyImpl(
265     const CastMessage& challenge_reply,
266     X509* peer_cert,
267     const AuthContext& auth_context,
268     const CRLPolicy& crl_policy,
269     TrustStore* cast_trust_store,
270     TrustStore* crl_trust_store,
271     const DateTime& verification_time) {
272   DeviceAuthMessage auth_message;
273   Error result = ParseAuthMessage(challenge_reply, &auth_message);
274   if (!result.ok()) {
275     return result;
276   }
277 
278   result = VerifyTLSCertificateValidity(peer_cert,
279                                         DateTimeToSeconds(verification_time));
280   if (!result.ok()) {
281     return result;
282   }
283 
284   const AuthResponse& response = auth_message.response();
285   const std::string& nonce_response = response.sender_nonce();
286 
287   result = auth_context.VerifySenderNonce(nonce_response);
288   if (!result.ok()) {
289     return result;
290   }
291 
292   int cert_len = i2d_X509(peer_cert, nullptr);
293   if (cert_len <= 0) {
294     return Error(Error::Code::kErrCertsParse, "Serializing cert failed.");
295   }
296   size_t nonce_response_size = nonce_response.size();
297   std::vector<uint8_t> nonce_plus_peer_cert_der(nonce_response_size + cert_len,
298                                                 0);
299   std::copy(nonce_response.begin(), nonce_response.end(),
300             &nonce_plus_peer_cert_der[0]);
301   uint8_t* data = &nonce_plus_peer_cert_der[nonce_response_size];
302   if (!i2d_X509(peer_cert, &data)) {
303     return Error(Error::Code::kErrCertsParse, "Serializing cert failed.");
304   }
305 
306   return VerifyCredentialsImpl(response, nonce_plus_peer_cert_der, crl_policy,
307                                cast_trust_store, crl_trust_store,
308                                verification_time, false);
309 }
310 
AuthenticateChallengeReply(const CastMessage & challenge_reply,X509 * peer_cert,const AuthContext & auth_context)311 ErrorOr<CastDeviceCertPolicy> AuthenticateChallengeReply(
312     const CastMessage& challenge_reply,
313     X509* peer_cert,
314     const AuthContext& auth_context) {
315   DateTime now = {};
316   OSP_CHECK(DateTimeFromSeconds(GetWallTimeSinceUnixEpoch().count(), &now));
317   CRLPolicy policy = CRLPolicy::kCrlOptional;
318   return AuthenticateChallengeReplyImpl(
319       challenge_reply, peer_cert, auth_context, policy,
320       /* cast_trust_store */ nullptr, /* crl_trust_store */ nullptr, now);
321 }
322 
AuthenticateChallengeReplyForTest(const CastMessage & challenge_reply,X509 * peer_cert,const AuthContext & auth_context,CRLPolicy crl_policy,TrustStore * cast_trust_store,TrustStore * crl_trust_store,const DateTime & verification_time)323 ErrorOr<CastDeviceCertPolicy> AuthenticateChallengeReplyForTest(
324     const CastMessage& challenge_reply,
325     X509* peer_cert,
326     const AuthContext& auth_context,
327     CRLPolicy crl_policy,
328     TrustStore* cast_trust_store,
329     TrustStore* crl_trust_store,
330     const DateTime& verification_time) {
331   return AuthenticateChallengeReplyImpl(
332       challenge_reply, peer_cert, auth_context, crl_policy, cast_trust_store,
333       crl_trust_store, verification_time);
334 }
335 
336 // This function does the following
337 //
338 // * Verifies that the certificate chain |response.client_auth_certificate| +
339 //   |response.intermediate_certificate| is valid and chains to a trusted Cast
340 //   root. The list of trusted Cast roots can be overrided by providing a
341 //   non-nullptr |cast_trust_store|. The certificate is verified at
342 //   |verification_time|.
343 //
344 // * Verifies that none of the certificates in the chain are revoked based on
345 //   the CRL provided in the response |response.crl|. The CRL is verified to be
346 //   valid and its issuer certificate chains to a trusted Cast CRL root. The
347 //   list of trusted Cast CRL roots can be overrided by providing a non-nullptr
348 //   |crl_trust_store|. If |crl_policy| is kCrlOptional then the result of
349 //   revocation checking is ignored. The CRL is verified at |verification_time|.
350 //
351 // * Verifies that |response.signature| matches the signature of
352 //   |signature_input| by |response.client_auth_certificate|'s public key.
VerifyCredentialsImpl(const AuthResponse & response,const std::vector<uint8_t> & signature_input,const CRLPolicy & crl_policy,TrustStore * cast_trust_store,TrustStore * crl_trust_store,const DateTime & verification_time,bool enforce_sha256_checking)353 ErrorOr<CastDeviceCertPolicy> VerifyCredentialsImpl(
354     const AuthResponse& response,
355     const std::vector<uint8_t>& signature_input,
356     const CRLPolicy& crl_policy,
357     TrustStore* cast_trust_store,
358     TrustStore* crl_trust_store,
359     const DateTime& verification_time,
360     bool enforce_sha256_checking) {
361   if (response.signature().empty() && !signature_input.empty()) {
362     return Error(Error::Code::kCastV2SignatureEmpty, "Signature is empty.");
363   }
364 
365   // Verify the certificate
366   std::unique_ptr<CertVerificationContext> verification_context;
367 
368   // Build a single vector containing the certificate chain.
369   std::vector<std::string> cert_chain;
370   cert_chain.push_back(response.client_auth_certificate());
371   cert_chain.insert(cert_chain.end(),
372                     response.intermediate_certificate().begin(),
373                     response.intermediate_certificate().end());
374 
375   // Parse the CRL.
376   std::unique_ptr<CastCRL> crl;
377   if (!response.crl().empty()) {
378     crl = ParseAndVerifyCRL(response.crl(), verification_time, crl_trust_store);
379   }
380 
381   // Perform certificate verification.
382   CastDeviceCertPolicy device_policy;
383   Error verify_result =
384       VerifyDeviceCert(cert_chain, verification_time, &verification_context,
385                        &device_policy, crl.get(), crl_policy, cast_trust_store);
386 
387   // Handle and report errors.
388   Error result = MapToOpenscreenError(verify_result,
389                                       crl_policy == CRLPolicy::kCrlRequired);
390   if (!result.ok()) {
391     return result;
392   }
393 
394   // The certificate is verified at this point.
395   DigestAlgorithm digest_algorithm;
396   Error digest_result = VerifyAndMapDigestAlgorithm(
397       response.hash_algorithm(), &digest_algorithm, enforce_sha256_checking);
398   if (!digest_result.ok()) {
399     return digest_result;
400   }
401 
402   ConstDataSpan signature = {
403       reinterpret_cast<const uint8_t*>(response.signature().data()),
404       static_cast<uint32_t>(response.signature().size())};
405   ConstDataSpan siginput = {signature_input.data(),
406                             static_cast<uint32_t>(signature_input.size())};
407   if (!verification_context->VerifySignatureOverData(signature, siginput,
408                                                      digest_algorithm)) {
409     return Error(Error::Code::kCastV2SignedBlobsMismatch,
410                  "Failed verifying signature over data.");
411   }
412 
413   return device_policy;
414 }
415 
VerifyCredentials(const AuthResponse & response,const std::vector<uint8_t> & signature_input,bool enforce_revocation_checking,bool enforce_sha256_checking)416 ErrorOr<CastDeviceCertPolicy> VerifyCredentials(
417     const AuthResponse& response,
418     const std::vector<uint8_t>& signature_input,
419     bool enforce_revocation_checking,
420     bool enforce_sha256_checking) {
421   DateTime now = {};
422   OSP_CHECK(DateTimeFromSeconds(GetWallTimeSinceUnixEpoch().count(), &now));
423   CRLPolicy policy = (enforce_revocation_checking) ? CRLPolicy::kCrlRequired
424                                                    : CRLPolicy::kCrlOptional;
425   return VerifyCredentialsImpl(response, signature_input, policy, nullptr,
426                                nullptr, now, enforce_sha256_checking);
427 }
428 
VerifyCredentialsForTest(const AuthResponse & response,const std::vector<uint8_t> & signature_input,CRLPolicy crl_policy,TrustStore * cast_trust_store,TrustStore * crl_trust_store,const DateTime & verification_time,bool enforce_sha256_checking)429 ErrorOr<CastDeviceCertPolicy> VerifyCredentialsForTest(
430     const AuthResponse& response,
431     const std::vector<uint8_t>& signature_input,
432     CRLPolicy crl_policy,
433     TrustStore* cast_trust_store,
434     TrustStore* crl_trust_store,
435     const DateTime& verification_time,
436     bool enforce_sha256_checking) {
437   return VerifyCredentialsImpl(response, signature_input, crl_policy,
438                                cast_trust_store, crl_trust_store,
439                                verification_time, enforce_sha256_checking);
440 }
441 
442 }  // namespace cast
443 }  // namespace openscreen
444