• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/common/certificate/cast_cert_validator_internal.h"
6 
7 #include <openssl/asn1.h>
8 #include <openssl/evp.h>
9 #include <openssl/mem.h>
10 #include <openssl/ossl_typ.h>
11 #include <openssl/x509.h>
12 #include <openssl/x509v3.h>
13 #include <time.h>
14 
15 #include <chrono>
16 #include <memory>
17 #include <string>
18 #include <utility>
19 #include <vector>
20 
21 #include "cast/common/certificate/types.h"
22 #include "util/crypto/pem_helpers.h"
23 #include "util/osp_logging.h"
24 
25 namespace openscreen {
26 namespace cast {
27 namespace {
28 
29 constexpr static int32_t kMinRsaModulusLengthBits = 2048;
30 
31 // TODO(davidben): Switch this to bssl::UniquePtr after
32 // https://boringssl-review.googlesource.com/c/boringssl/+/46105 lands.
33 struct FreeNameConstraints {
operator ()openscreen::cast::__anon1ed52aaa0111::FreeNameConstraints34   void operator()(NAME_CONSTRAINTS* nc) { NAME_CONSTRAINTS_free(nc); }
35 };
36 using NameConstraintsPtr =
37     std::unique_ptr<NAME_CONSTRAINTS, FreeNameConstraints>;
38 
39 // Stores intermediate state while attempting to find a valid certificate chain
40 // from a set of trusted certificates to a target certificate.  Together, a
41 // sequence of these forms a certificate chain to be verified as well as a stack
42 // that can be unwound for searching more potential paths.
43 struct CertPathStep {
44   X509* cert;
45 
46   // The next index that can be checked in |trust_store| if the choice |cert| on
47   // the path needs to be reverted.
48   uint32_t trust_store_index;
49 
50   // The next index that can be checked in |intermediate_certs| if the choice
51   // |cert| on the path needs to be reverted.
52   uint32_t intermediate_cert_index;
53 };
54 
55 // These values are bit positions from RFC 5280 4.2.1.3 and will be passed to
56 // ASN1_BIT_STRING_get_bit.
57 enum KeyUsageBits {
58   kDigitalSignature = 0,
59   kKeyCertSign = 5,
60 };
61 
CertInPath(X509_NAME * name,const std::vector<CertPathStep> & steps,uint32_t start,uint32_t stop)62 bool CertInPath(X509_NAME* name,
63                 const std::vector<CertPathStep>& steps,
64                 uint32_t start,
65                 uint32_t stop) {
66   for (uint32_t i = start; i < stop; ++i) {
67     if (X509_NAME_cmp(name, X509_get_subject_name(steps[i].cert)) == 0) {
68       return true;
69     }
70   }
71   return false;
72 }
73 
74 // Parse the data in |time| at |index| as a two-digit ascii number. Note this
75 // function assumes the caller already did a bounds check and checked the inputs
76 // are digits.
ParseAsn1TimeDoubleDigit(absl::string_view time,size_t index)77 uint8_t ParseAsn1TimeDoubleDigit(absl::string_view time, size_t index) {
78   OSP_DCHECK_LT(index + 1, time.size());
79   OSP_DCHECK('0' <= time[index] && time[index] <= '9');
80   OSP_DCHECK('0' <= time[index + 1] && time[index + 1] <= '9');
81   return (time[index] - '0') * 10 + (time[index + 1] - '0');
82 }
83 
GetConstraints(X509 * issuer)84 bssl::UniquePtr<BASIC_CONSTRAINTS> GetConstraints(X509* issuer) {
85   // TODO(davidben): This and other |X509_get_ext_d2i| are missing
86   // error-handling for syntax errors in certificates. See BoringSSL
87   // documentation for the calling convention.
88   return bssl::UniquePtr<BASIC_CONSTRAINTS>{
89       reinterpret_cast<BASIC_CONSTRAINTS*>(
90           X509_get_ext_d2i(issuer, NID_basic_constraints, nullptr, nullptr))};
91 }
92 
VerifyCertTime(X509 * cert,const DateTime & time)93 Error::Code VerifyCertTime(X509* cert, const DateTime& time) {
94   DateTime not_before;
95   DateTime not_after;
96   if (!GetCertValidTimeRange(cert, &not_before, &not_after)) {
97     return Error::Code::kErrCertsVerifyGeneric;
98   }
99 
100   if ((time < not_before) || (not_after < time)) {
101     return Error::Code::kErrCertsDateInvalid;
102   }
103   return Error::Code::kNone;
104 }
105 
VerifyPublicKeyLength(EVP_PKEY * public_key)106 bool VerifyPublicKeyLength(EVP_PKEY* public_key) {
107   return EVP_PKEY_bits(public_key) >= kMinRsaModulusLengthBits;
108 }
109 
GetKeyUsage(X509 * cert)110 bssl::UniquePtr<ASN1_BIT_STRING> GetKeyUsage(X509* cert) {
111   return bssl::UniquePtr<ASN1_BIT_STRING>{reinterpret_cast<ASN1_BIT_STRING*>(
112       X509_get_ext_d2i(cert, NID_key_usage, nullptr, nullptr))};
113 }
114 
VerifyCertificateChain(const std::vector<CertPathStep> & path,uint32_t step_index,const DateTime & time)115 Error::Code VerifyCertificateChain(const std::vector<CertPathStep>& path,
116                                    uint32_t step_index,
117                                    const DateTime& time) {
118   // Default max path length is the number of intermediate certificates.
119   int max_pathlen = path.size() - 2;
120 
121   std::vector<NameConstraintsPtr> path_name_constraints;
122   Error::Code error = Error::Code::kNone;
123   uint32_t i = step_index;
124   for (; i < path.size() - 1; ++i) {
125     X509* subject = path[i + 1].cert;
126     X509* issuer = path[i].cert;
127     bool is_root = (i == step_index);
128     bool issuer_is_self_issued = false;
129     if (!is_root) {
130       if ((error = VerifyCertTime(issuer, time)) != Error::Code::kNone) {
131         return error;
132       }
133       if (X509_NAME_cmp(X509_get_subject_name(issuer),
134                         X509_get_issuer_name(issuer)) != 0) {
135         if (max_pathlen == 0) {
136           return Error::Code::kErrCertsPathlen;
137         }
138         --max_pathlen;
139       } else {
140         issuer_is_self_issued = true;
141       }
142     } else {
143       issuer_is_self_issued = true;
144     }
145 
146     bssl::UniquePtr<ASN1_BIT_STRING> key_usage = GetKeyUsage(issuer);
147     if (key_usage) {
148       const int bit =
149           ASN1_BIT_STRING_get_bit(key_usage.get(), KeyUsageBits::kKeyCertSign);
150       if (bit == 0) {
151         return Error::Code::kErrCertsVerifyGeneric;
152       }
153     }
154 
155     // Certificates issued by a valid CA authority shall have the
156     // basicConstraints property present with the CA bit set. Self-signed
157     // certificates do not have this property present.
158     bssl::UniquePtr<BASIC_CONSTRAINTS> basic_constraints =
159         GetConstraints(issuer);
160     if (!basic_constraints || !basic_constraints->ca) {
161       return Error::Code::kErrCertsVerifyGeneric;
162     }
163 
164     if (basic_constraints->pathlen) {
165       if (basic_constraints->pathlen->length != 1) {
166         return Error::Code::kErrCertsVerifyGeneric;
167       } else {
168         const int pathlen = *basic_constraints->pathlen->data;
169         if (pathlen < 0) {
170           return Error::Code::kErrCertsVerifyGeneric;
171         }
172         if (pathlen < max_pathlen) {
173           max_pathlen = pathlen;
174         }
175       }
176     }
177 
178     const X509_ALGOR* issuer_sig_alg;
179     X509_get0_signature(nullptr, &issuer_sig_alg, issuer);
180     if (X509_ALGOR_cmp(issuer_sig_alg, X509_get0_tbs_sigalg(issuer)) != 0) {
181       return Error::Code::kErrCertsVerifyGeneric;
182     }
183 
184     bssl::UniquePtr<EVP_PKEY> public_key{X509_get_pubkey(issuer)};
185     if (!VerifyPublicKeyLength(public_key.get())) {
186       return Error::Code::kErrCertsVerifyGeneric;
187     }
188 
189     // NOTE: (!self-issued || target) -> verify name constraints.  Target case
190     // is after the loop.
191     if (!issuer_is_self_issued) {
192       for (const auto& name_constraints : path_name_constraints) {
193         if (NAME_CONSTRAINTS_check(subject, name_constraints.get()) !=
194             X509_V_OK) {
195           return Error::Code::kErrCertsVerifyGeneric;
196         }
197       }
198     }
199 
200     int critical;
201     NameConstraintsPtr nc{reinterpret_cast<NAME_CONSTRAINTS*>(
202         X509_get_ext_d2i(issuer, NID_name_constraints, &critical, nullptr))};
203     if (!nc && critical != -1) {
204       // X509_get_ext_d2i's error handling is a little confusing. See
205       // https://boringssl.googlesource.com/boringssl/+/215f4a0287/include/openssl/x509.h#1384
206       // https://boringssl.googlesource.com/boringssl/+/215f4a0287/include/openssl/x509v3.h#651
207       return Error::Code::kErrCertsVerifyGeneric;
208     }
209     if (nc) {
210       path_name_constraints.push_back(std::move(nc));
211     }
212 
213     // Check that any policy mappings present are _not_ the anyPolicy OID.  Even
214     // though we don't otherwise handle policies, this is required by RFC 5280
215     // 6.1.4(a).
216     //
217     // TODO(davidben): Switch to bssl::UniquePtr once
218     // https://boringssl-review.googlesource.com/c/boringssl/+/46104 has landed.
219     auto* policy_mappings = reinterpret_cast<POLICY_MAPPINGS*>(
220         X509_get_ext_d2i(issuer, NID_policy_mappings, nullptr, nullptr));
221     if (policy_mappings) {
222       const ASN1_OBJECT* any_policy = OBJ_nid2obj(NID_any_policy);
223       for (const POLICY_MAPPING* policy_mapping : policy_mappings) {
224         const bool either_matches =
225             ((OBJ_cmp(policy_mapping->issuerDomainPolicy, any_policy) == 0) ||
226              (OBJ_cmp(policy_mapping->subjectDomainPolicy, any_policy) == 0));
227         if (either_matches) {
228           error = Error::Code::kErrCertsVerifyGeneric;
229           break;
230         }
231       }
232       sk_POLICY_MAPPING_free(policy_mappings);
233       if (error != Error::Code::kNone) {
234         return error;
235       }
236     }
237 
238     // Check that we don't have any unhandled extensions marked as critical.
239     int extension_count = X509_get_ext_count(issuer);
240     for (int i = 0; i < extension_count; ++i) {
241       X509_EXTENSION* extension = X509_get_ext(issuer, i);
242       if (X509_EXTENSION_get_critical(extension)) {
243         const int nid = OBJ_obj2nid(X509_EXTENSION_get_object(extension));
244         if (nid != NID_name_constraints && nid != NID_basic_constraints &&
245             nid != NID_key_usage) {
246           return Error::Code::kErrCertsVerifyGeneric;
247         }
248       }
249     }
250 
251     int nid = X509_get_signature_nid(subject);
252     const EVP_MD* digest;
253     switch (nid) {
254       case NID_sha1WithRSAEncryption:
255         digest = EVP_sha1();
256         break;
257       case NID_sha256WithRSAEncryption:
258         digest = EVP_sha256();
259         break;
260       case NID_sha384WithRSAEncryption:
261         digest = EVP_sha384();
262         break;
263       case NID_sha512WithRSAEncryption:
264         digest = EVP_sha512();
265         break;
266       default:
267         return Error::Code::kErrCertsVerifyGeneric;
268     }
269     uint8_t* tbs = nullptr;
270     int tbs_len = i2d_X509_tbs(subject, &tbs);
271     if (tbs_len < 0) {
272       return Error::Code::kErrCertsVerifyGeneric;
273     }
274     bssl::UniquePtr<uint8_t> free_tbs{tbs};
275     const ASN1_BIT_STRING* signature;
276     X509_get0_signature(&signature, nullptr, subject);
277     if (!VerifySignedData(
278             digest, public_key.get(), {tbs, static_cast<uint32_t>(tbs_len)},
279             {ASN1_STRING_get0_data(signature),
280              static_cast<uint32_t>(ASN1_STRING_length(signature))})) {
281       return Error::Code::kErrCertsVerifyGeneric;
282     }
283   }
284   // NOTE: Other half of ((!self-issued || target) -> check name constraints).
285   for (const auto& name_constraints : path_name_constraints) {
286     if (NAME_CONSTRAINTS_check(path.back().cert, name_constraints.get()) !=
287         X509_V_OK) {
288       return Error::Code::kErrCertsVerifyGeneric;
289     }
290   }
291   return error;
292 }
293 
ParseX509Der(const std::string & der)294 X509* ParseX509Der(const std::string& der) {
295   const uint8_t* data = reinterpret_cast<const uint8_t*>(der.data());
296   return d2i_X509(nullptr, &data, der.size());
297 }
298 
299 }  // namespace
300 
301 // Parses DateTime with additional restrictions laid out by RFC 5280
302 // 4.1.2.5.2.
ParseAsn1GeneralizedTime(ASN1_GENERALIZEDTIME * time,DateTime * out)303 bool ParseAsn1GeneralizedTime(ASN1_GENERALIZEDTIME* time, DateTime* out) {
304   static constexpr uint8_t kDaysPerMonth[] = {
305       31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
306   };
307 
308   absl::string_view time_str{
309       reinterpret_cast<const char*>(ASN1_STRING_get0_data(time)),
310       static_cast<size_t>(ASN1_STRING_length(time))};
311   if (time_str.size() != 15) {
312     return false;
313   }
314   if (time_str[14] != 'Z') {
315     return false;
316   }
317   for (size_t i = 0; i < 14; ++i) {
318     if (time_str[i] < '0' || time_str[i] > '9') {
319       return false;
320     }
321   }
322   out->year = ParseAsn1TimeDoubleDigit(time_str, 0) * 100 +
323               ParseAsn1TimeDoubleDigit(time_str, 2);
324   out->month = ParseAsn1TimeDoubleDigit(time_str, 4);
325   out->day = ParseAsn1TimeDoubleDigit(time_str, 6);
326   out->hour = ParseAsn1TimeDoubleDigit(time_str, 8);
327   out->minute = ParseAsn1TimeDoubleDigit(time_str, 10);
328   out->second = ParseAsn1TimeDoubleDigit(time_str, 12);
329   if (out->month == 0 || out->month > 12) {
330     return false;
331   }
332   int days_per_month = kDaysPerMonth[out->month - 1];
333   if (out->month == 2) {
334     if (out->year % 4 == 0 && (out->year % 100 != 0 || out->year % 400 == 0)) {
335       days_per_month = 29;
336     } else {
337       days_per_month = 28;
338     }
339   }
340   if (out->day == 0 || out->day > days_per_month) {
341     return false;
342   }
343   if (out->hour > 23) {
344     return false;
345   }
346   if (out->minute > 59) {
347     return false;
348   }
349   // Leap seconds are allowed.
350   if (out->second > 60) {
351     return false;
352   }
353   return true;
354 }
355 
GetCertValidTimeRange(X509 * cert,DateTime * not_before,DateTime * not_after)356 bool GetCertValidTimeRange(X509* cert,
357                            DateTime* not_before,
358                            DateTime* not_after) {
359   bssl::UniquePtr<ASN1_GENERALIZEDTIME> not_before_asn1{
360       ASN1_TIME_to_generalizedtime(X509_get0_notBefore(cert), nullptr)};
361   bssl::UniquePtr<ASN1_GENERALIZEDTIME> not_after_asn1{
362       ASN1_TIME_to_generalizedtime(X509_get0_notAfter(cert), nullptr)};
363   if (!not_before_asn1 || !not_after_asn1) {
364     return false;
365   }
366   return ParseAsn1GeneralizedTime(not_before_asn1.get(), not_before) &&
367          ParseAsn1GeneralizedTime(not_after_asn1.get(), not_after);
368 }
369 
370 // static
CreateInstanceFromPemFile(absl::string_view file_path)371 TrustStore TrustStore::CreateInstanceFromPemFile(absl::string_view file_path) {
372   TrustStore store;
373 
374   std::vector<std::string> certs = ReadCertificatesFromPemFile(file_path);
375   for (const auto& der_cert : certs) {
376     const uint8_t* data = (const uint8_t*)der_cert.data();
377     store.certs.emplace_back(d2i_X509(nullptr, &data, der_cert.size()));
378   }
379 
380   return store;
381 }
382 
VerifySignedData(const EVP_MD * digest,EVP_PKEY * public_key,const ConstDataSpan & data,const ConstDataSpan & signature)383 bool VerifySignedData(const EVP_MD* digest,
384                       EVP_PKEY* public_key,
385                       const ConstDataSpan& data,
386                       const ConstDataSpan& signature) {
387   // This code assumes the signature algorithm was RSASSA PKCS#1 v1.5 with
388   // |digest|.
389   bssl::ScopedEVP_MD_CTX ctx;
390   if (!EVP_DigestVerifyInit(ctx.get(), nullptr, digest, nullptr, public_key)) {
391     return false;
392   }
393   return (EVP_DigestVerify(ctx.get(), signature.data, signature.length,
394                            data.data, data.length) == 1);
395 }
396 
FindCertificatePath(const std::vector<std::string> & der_certs,const DateTime & time,CertificatePathResult * result_path,TrustStore * trust_store)397 Error FindCertificatePath(const std::vector<std::string>& der_certs,
398                           const DateTime& time,
399                           CertificatePathResult* result_path,
400                           TrustStore* trust_store) {
401   if (der_certs.empty()) {
402     return Error(Error::Code::kErrCertsMissing, "Missing DER certificates");
403   }
404 
405   bssl::UniquePtr<X509>& target_cert = result_path->target_cert;
406   std::vector<bssl::UniquePtr<X509>>& intermediate_certs =
407       result_path->intermediate_certs;
408   target_cert.reset(ParseX509Der(der_certs[0]));
409   if (!target_cert) {
410     OSP_DVLOG << "FindCertificatePath: Invalid target certificate";
411     return Error::Code::kErrCertsParse;
412   }
413   for (size_t i = 1; i < der_certs.size(); ++i) {
414     intermediate_certs.emplace_back(ParseX509Der(der_certs[i]));
415     if (!intermediate_certs.back()) {
416       OSP_DVLOG
417           << "FindCertificatePath: Failed to parse intermediate certificate "
418           << i << " of " << der_certs.size();
419       return Error::Code::kErrCertsParse;
420     }
421   }
422 
423   // Basic checks on the target certificate.
424   Error::Code error = VerifyCertTime(target_cert.get(), time);
425   if (error != Error::Code::kNone) {
426     OSP_DVLOG << "FindCertificatePath: Failed to verify certificate time";
427     return error;
428   }
429   bssl::UniquePtr<EVP_PKEY> public_key{X509_get_pubkey(target_cert.get())};
430   if (!VerifyPublicKeyLength(public_key.get())) {
431     OSP_DVLOG << "FindCertificatePath: Failed with invalid public key length";
432     return Error::Code::kErrCertsVerifyGeneric;
433   }
434   const X509_ALGOR* sig_alg;
435   X509_get0_signature(nullptr, &sig_alg, target_cert.get());
436   if (X509_ALGOR_cmp(sig_alg, X509_get0_tbs_sigalg(target_cert.get())) != 0) {
437     return Error::Code::kErrCertsVerifyGeneric;
438   }
439   bssl::UniquePtr<ASN1_BIT_STRING> key_usage = GetKeyUsage(target_cert.get());
440   if (!key_usage) {
441     OSP_DVLOG << "FindCertificatePath: Failed with no key usage";
442     return Error::Code::kErrCertsRestrictions;
443   }
444   int bit =
445       ASN1_BIT_STRING_get_bit(key_usage.get(), KeyUsageBits::kDigitalSignature);
446   if (bit == 0) {
447     OSP_DVLOG << "FindCertificatePath: Failed to get digital signature";
448     return Error::Code::kErrCertsRestrictions;
449   }
450 
451   X509* path_head = target_cert.get();
452   std::vector<CertPathStep> path;
453 
454   // This vector isn't used as resizable, so instead we allocate the largest
455   // possible single path up front.  This would be a single trusted cert, all
456   // the intermediate certs used once, and the target cert.
457   path.resize(1 + intermediate_certs.size() + 1);
458 
459   // Additionally, the path is slightly simpler to deal with if the list is
460   // sorted from trust->target, so the path is actually built starting from the
461   // end.
462   uint32_t first_index = path.size() - 1;
463   path[first_index].cert = path_head;
464 
465   // Index into |path| of the current frontier of path construction.
466   uint32_t path_index = first_index;
467 
468   // Whether |path| has reached a certificate in |trust_store| and is ready for
469   // verification.
470   bool path_cert_in_trust_store = false;
471 
472   // Attempt to build a valid certificate chain from |target_cert| to a
473   // certificate in |trust_store|.  This loop tries all possible paths in a
474   // depth-first-search fashion.  If no valid paths are found, the error
475   // returned is whatever the last error was from the last path tried.
476   uint32_t trust_store_index = 0;
477   uint32_t intermediate_cert_index = 0;
478   Error::Code last_error = Error::Code::kNone;
479   for (;;) {
480     X509_NAME* target_issuer_name = X509_get_issuer_name(path_head);
481     OSP_DVLOG << "FindCertificatePath: Target certificate issuer name: "
482               << X509_NAME_oneline(target_issuer_name, 0, 0);
483 
484     // The next issuer certificate to add to the current path.
485     X509* next_issuer = nullptr;
486 
487     for (uint32_t i = trust_store_index; i < trust_store->certs.size(); ++i) {
488       X509* trust_store_cert = trust_store->certs[i].get();
489       X509_NAME* trust_store_cert_name =
490           X509_get_subject_name(trust_store_cert);
491       OSP_DVLOG << "FindCertificatePath: Trust store certificate issuer name: "
492                 << X509_NAME_oneline(trust_store_cert_name, 0, 0);
493       if (X509_NAME_cmp(trust_store_cert_name, target_issuer_name) == 0) {
494         CertPathStep& next_step = path[--path_index];
495         next_step.cert = trust_store_cert;
496         next_step.trust_store_index = i + 1;
497         next_step.intermediate_cert_index = 0;
498         next_issuer = trust_store_cert;
499         path_cert_in_trust_store = true;
500         break;
501       }
502     }
503     trust_store_index = 0;
504     if (!next_issuer) {
505       for (uint32_t i = intermediate_cert_index; i < intermediate_certs.size();
506            ++i) {
507         X509* intermediate_cert = intermediate_certs[i].get();
508         X509_NAME* intermediate_cert_name =
509             X509_get_subject_name(intermediate_cert);
510         if (X509_NAME_cmp(intermediate_cert_name, target_issuer_name) == 0 &&
511             !CertInPath(intermediate_cert_name, path, path_index,
512                         first_index)) {
513           CertPathStep& next_step = path[--path_index];
514           next_step.cert = intermediate_cert;
515           next_step.trust_store_index = trust_store->certs.size();
516           next_step.intermediate_cert_index = i + 1;
517           next_issuer = intermediate_cert;
518           break;
519         }
520       }
521     }
522     intermediate_cert_index = 0;
523     if (!next_issuer) {
524       if (path_index == first_index) {
525         // There are no more paths to try.  Ensure an error is returned.
526         if (last_error == Error::Code::kNone) {
527           OSP_DVLOG << "FindCertificatePath: Failed after trying all "
528                        "certificate paths, no matches";
529           return Error::Code::kErrCertsVerifyUntrustedCert;
530         }
531         return last_error;
532       } else {
533         CertPathStep& last_step = path[path_index++];
534         trust_store_index = last_step.trust_store_index;
535         intermediate_cert_index = last_step.intermediate_cert_index;
536         continue;
537       }
538     }
539 
540     if (path_cert_in_trust_store) {
541       last_error = VerifyCertificateChain(path, path_index, time);
542       if (last_error != Error::Code::kNone) {
543         CertPathStep& last_step = path[path_index++];
544         trust_store_index = last_step.trust_store_index;
545         intermediate_cert_index = last_step.intermediate_cert_index;
546         path_cert_in_trust_store = false;
547       } else {
548         break;
549       }
550     }
551     path_head = next_issuer;
552   }
553 
554   result_path->path.reserve(path.size() - path_index);
555   for (uint32_t i = path_index; i < path.size(); ++i) {
556     result_path->path.push_back(path[i].cert);
557   }
558 
559   OSP_DVLOG
560       << "FindCertificatePath: Succeeded at validating receiver certificates";
561   return Error::Code::kNone;
562 }
563 
564 }  // namespace cast
565 }  // namespace openscreen
566