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