xref: /aosp_15_r20/external/cronet/third_party/boringssl/src/pki/test_helpers.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2015 The Chromium Authors
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 "test_helpers.h"
6 
7 #include <fstream>
8 #include <iostream>
9 #include <sstream>
10 #include <streambuf>
11 #include <string>
12 #include <string_view>
13 
14 #include <gtest/gtest.h>
15 
16 #include <openssl/bytestring.h>
17 #include <openssl/mem.h>
18 #include <openssl/pool.h>
19 
20 #include "../crypto/test/test_data.h"
21 #include "cert_error_params.h"
22 #include "cert_errors.h"
23 #include "parser.h"
24 #include "pem.h"
25 #include "simple_path_builder_delegate.h"
26 #include "string_util.h"
27 #include "trust_store.h"
28 
29 namespace bssl {
30 
31 namespace {
32 
GetValue(std::string_view prefix,std::string_view line,std::string * value,bool * has_value)33 bool GetValue(std::string_view prefix, std::string_view line,
34               std::string *value, bool *has_value) {
35   if (!bssl::string_util::StartsWith(line, prefix)) {
36     return false;
37   }
38 
39   if (*has_value) {
40     ADD_FAILURE() << "Duplicated " << prefix;
41     return false;
42   }
43 
44   *has_value = true;
45   *value = std::string(line.substr(prefix.size()));
46   return true;
47 }
48 
49 // Returns a string containing the dotted numeric form of |oid|, or a
50 // hex-encoded string on error.
OidToString(der::Input oid)51 std::string OidToString(der::Input oid) {
52   CBS cbs;
53   CBS_init(&cbs, oid.data(), oid.size());
54   bssl::UniquePtr<char> text(CBS_asn1_oid_to_text(&cbs));
55   if (!text) {
56     return "invalid:" + bssl::string_util::HexEncode(oid);
57   }
58   return text.get();
59 }
60 
StrSetToString(const std::set<std::string> & str_set)61 std::string StrSetToString(const std::set<std::string> &str_set) {
62   std::string out;
63   for (const auto &s : str_set) {
64     EXPECT_FALSE(s.empty());
65     if (!out.empty()) {
66       out += ", ";
67     }
68     out += s;
69   }
70   return out;
71 }
72 
StripString(std::string_view str)73 std::string StripString(std::string_view str) {
74   size_t start = str.find_first_not_of(' ');
75   if (start == str.npos) {
76     return std::string();
77   }
78   str = str.substr(start);
79   size_t end = str.find_last_not_of(' ');
80   if (end != str.npos) {
81     ++end;
82   }
83   return std::string(str.substr(0, end));
84 }
85 
SplitString(std::string_view str)86 std::vector<std::string> SplitString(std::string_view str) {
87   std::vector<std::string_view> split = string_util::SplitString(str, ',');
88 
89   std::vector<std::string> out;
90   for (const auto &s : split) {
91     out.push_back(StripString(s));
92   }
93   return out;
94 }
95 
96 }  // namespace
97 
98 namespace der {
99 
PrintTo(Input data,::std::ostream * os)100 void PrintTo(Input data, ::std::ostream *os) {
101   size_t len;
102   if (!EVP_EncodedLength(&len, data.size())) {
103     *os << "[]";
104     return;
105   }
106   std::vector<uint8_t> encoded(len);
107   len = EVP_EncodeBlock(encoded.data(), data.data(), data.size());
108   // Skip the trailing \0.
109   std::string b64_encoded(encoded.begin(), encoded.begin() + len);
110   *os << "[" << b64_encoded << "]";
111 }
112 
113 }  // namespace der
114 
SequenceValueFromString(std::string_view s)115 der::Input SequenceValueFromString(std::string_view s) {
116   der::Parser parser((der::Input(s)));
117   der::Input data;
118   if (!parser.ReadTag(CBS_ASN1_SEQUENCE, &data)) {
119     ADD_FAILURE();
120     return der::Input();
121   }
122   if (parser.HasMore()) {
123     ADD_FAILURE();
124     return der::Input();
125   }
126   return data;
127 }
128 
ReadTestDataFromPemFile(const std::string & file_path_ascii,const PemBlockMapping * mappings,size_t mappings_length)129 ::testing::AssertionResult ReadTestDataFromPemFile(
130     const std::string &file_path_ascii, const PemBlockMapping *mappings,
131     size_t mappings_length) {
132   std::string file_data = ReadTestFileToString(file_path_ascii);
133 
134   // mappings_copy is used to keep track of which mappings have already been
135   // satisfied (by nulling the |value| field). This is used to track when
136   // blocks are mulitply defined.
137   std::vector<PemBlockMapping> mappings_copy(mappings,
138                                              mappings + mappings_length);
139 
140   // Build the |pem_headers| vector needed for PEMTokenzier.
141   std::vector<std::string> pem_headers;
142   for (const auto &mapping : mappings_copy) {
143     pem_headers.push_back(mapping.block_name);
144   }
145 
146   PEMTokenizer pem_tokenizer(file_data, pem_headers);
147   while (pem_tokenizer.GetNext()) {
148     for (auto &mapping : mappings_copy) {
149       // Find the mapping for this block type.
150       if (pem_tokenizer.block_type() == mapping.block_name) {
151         if (!mapping.value) {
152           return ::testing::AssertionFailure()
153                  << "PEM block defined multiple times: " << mapping.block_name;
154         }
155 
156         // Copy the data to the result.
157         mapping.value->assign(pem_tokenizer.data());
158 
159         // Mark the mapping as having been satisfied.
160         mapping.value = nullptr;
161       }
162     }
163   }
164 
165   // Ensure that all specified blocks were found.
166   for (const auto &mapping : mappings_copy) {
167     if (mapping.value && !mapping.optional) {
168       return ::testing::AssertionFailure()
169              << "PEM block missing: " << mapping.block_name;
170     }
171   }
172 
173   return ::testing::AssertionSuccess();
174 }
175 
VerifyCertChainTest()176 VerifyCertChainTest::VerifyCertChainTest()
177     : user_initial_policy_set{der::Input(kAnyPolicyOid)} {}
178 VerifyCertChainTest::~VerifyCertChainTest() = default;
179 
HasHighSeverityErrors() const180 bool VerifyCertChainTest::HasHighSeverityErrors() const {
181   // This function assumes that high severity warnings are prefixed with
182   // "ERROR: " and warnings are prefixed with "WARNING: ". This is an
183   // implementation detail of CertError::ToDebugString).
184   //
185   // Do a quick sanity-check to confirm this.
186   CertError error(CertError::SEVERITY_HIGH, "unused", nullptr);
187   EXPECT_EQ("ERROR: unused\n", error.ToDebugString());
188   CertError warning(CertError::SEVERITY_WARNING, "unused", nullptr);
189   EXPECT_EQ("WARNING: unused\n", warning.ToDebugString());
190 
191   // Do a simple substring test (not perfect, but good enough for our test
192   // corpus).
193   return expected_errors.find("ERROR: ") != std::string::npos;
194 }
195 
ReadCertChainFromFile(const std::string & file_path_ascii,ParsedCertificateList * chain)196 bool ReadCertChainFromFile(const std::string &file_path_ascii,
197                            ParsedCertificateList *chain) {
198   // Reset all the out parameters to their defaults.
199   *chain = ParsedCertificateList();
200 
201   std::string file_data = ReadTestFileToString(file_path_ascii);
202   if (file_data.empty()) {
203     return false;
204   }
205 
206   std::vector<std::string> pem_headers = {"CERTIFICATE"};
207 
208   PEMTokenizer pem_tokenizer(file_data, pem_headers);
209   while (pem_tokenizer.GetNext()) {
210     const std::string &block_data = pem_tokenizer.data();
211 
212     CertErrors errors;
213     if (!ParsedCertificate::CreateAndAddToVector(
214             bssl::UniquePtr<CRYPTO_BUFFER>(CRYPTO_BUFFER_new(
215                 reinterpret_cast<const uint8_t *>(block_data.data()),
216                 block_data.size(), nullptr)),
217             {}, chain, &errors)) {
218       ADD_FAILURE() << errors.ToDebugString();
219       return false;
220     }
221   }
222 
223   return true;
224 }
225 
ReadCertFromFile(const std::string & file_path_ascii)226 std::shared_ptr<const ParsedCertificate> ReadCertFromFile(
227     const std::string &file_path_ascii) {
228   ParsedCertificateList chain;
229   if (!ReadCertChainFromFile(file_path_ascii, &chain)) {
230     return nullptr;
231   }
232   if (chain.size() != 1) {
233     return nullptr;
234   }
235   return chain[0];
236 }
237 
ReadVerifyCertChainTestFromFile(const std::string & file_path_ascii,VerifyCertChainTest * test)238 bool ReadVerifyCertChainTestFromFile(const std::string &file_path_ascii,
239                                      VerifyCertChainTest *test) {
240   // Reset all the out parameters to their defaults.
241   *test = {};
242 
243   std::string file_data = ReadTestFileToString(file_path_ascii);
244   if (file_data.empty()) {
245     return false;
246   }
247 
248   bool has_chain = false;
249   bool has_trust = false;
250   bool has_time = false;
251   bool has_errors = false;
252   bool has_key_purpose = false;
253   bool has_digest_policy = false;
254   bool has_user_constrained_policy_set = false;
255 
256   std::string kExpectedErrors = "expected_errors:";
257 
258   std::istringstream stream(file_data);
259   for (std::string line; std::getline(stream, line, '\n');) {
260     size_t start = line.find_first_not_of(" \n\t\r\f\v");
261     if (start == std::string::npos) {
262       continue;
263     }
264     size_t end = line.find_last_not_of(" \n\t\r\f\v");
265     if (end == std::string::npos) {
266       continue;
267     }
268     line = line.substr(start, end + 1);
269     if (line.empty()) {
270       continue;
271     }
272     std::string_view line_piece(line);
273 
274     std::string value;
275 
276     // For details on the file format refer to:
277     // net/data/verify_certificate_chain_unittest/README.
278     if (GetValue("chain: ", line_piece, &value, &has_chain)) {
279       // Interpret the |chain| path as being relative to the .test file.
280       size_t slash = file_path_ascii.rfind('/');
281       if (slash == std::string::npos) {
282         ADD_FAILURE() << "Bad path - expecting slashes";
283         return false;
284       }
285       std::string chain_path = file_path_ascii.substr(0, slash) + "/" + value;
286 
287       ReadCertChainFromFile(chain_path, &test->chain);
288     } else if (GetValue("utc_time: ", line_piece, &value, &has_time)) {
289       if (value == "DEFAULT") {
290         value = "211005120000Z";
291       }
292       if (!der::ParseUTCTime(der::Input(value), &test->time)) {
293         ADD_FAILURE() << "Failed parsing UTC time";
294         return false;
295       }
296     } else if (GetValue("key_purpose: ", line_piece, &value,
297                         &has_key_purpose)) {
298       if (value == "ANY_EKU") {
299         test->key_purpose = KeyPurpose::ANY_EKU;
300       } else if (value == "SERVER_AUTH") {
301         test->key_purpose = KeyPurpose::SERVER_AUTH;
302       } else if (value == "CLIENT_AUTH") {
303         test->key_purpose = KeyPurpose::CLIENT_AUTH;
304       } else if (value == "SERVER_AUTH_STRICT") {
305         test->key_purpose = KeyPurpose::SERVER_AUTH_STRICT;
306       } else if (value == "CLIENT_AUTH_STRICT") {
307         test->key_purpose = KeyPurpose::CLIENT_AUTH_STRICT;
308       } else {
309         ADD_FAILURE() << "Unrecognized key_purpose: " << value;
310         return false;
311       }
312     } else if (GetValue("last_cert_trust: ", line_piece, &value, &has_trust)) {
313       // TODO(mattm): convert test files to use
314       // CertificateTrust::FromDebugString strings.
315       if (value == "TRUSTED_ANCHOR") {
316         test->last_cert_trust = CertificateTrust::ForTrustAnchor();
317       } else if (value == "TRUSTED_ANCHOR_WITH_EXPIRATION") {
318         test->last_cert_trust =
319             CertificateTrust::ForTrustAnchor().WithEnforceAnchorExpiry();
320       } else if (value == "TRUSTED_ANCHOR_WITH_CONSTRAINTS") {
321         test->last_cert_trust =
322             CertificateTrust::ForTrustAnchor().WithEnforceAnchorConstraints();
323       } else if (value == "TRUSTED_ANCHOR_WITH_REQUIRE_BASIC_CONSTRAINTS") {
324         test->last_cert_trust = CertificateTrust::ForTrustAnchor()
325                                     .WithRequireAnchorBasicConstraints();
326       } else if (value ==
327                  "TRUSTED_ANCHOR_WITH_CONSTRAINTS_REQUIRE_BASIC_CONSTRAINTS") {
328         test->last_cert_trust = CertificateTrust::ForTrustAnchor()
329                                     .WithEnforceAnchorConstraints()
330                                     .WithRequireAnchorBasicConstraints();
331       } else if (value == "TRUSTED_ANCHOR_WITH_EXPIRATION_AND_CONSTRAINTS") {
332         test->last_cert_trust = CertificateTrust::ForTrustAnchor()
333                                     .WithEnforceAnchorExpiry()
334                                     .WithEnforceAnchorConstraints();
335       } else if (value == "TRUSTED_ANCHOR_OR_LEAF") {
336         test->last_cert_trust = CertificateTrust::ForTrustAnchorOrLeaf();
337       } else if (value == "TRUSTED_LEAF") {
338         test->last_cert_trust = CertificateTrust::ForTrustedLeaf();
339       } else if (value == "TRUSTED_LEAF_REQUIRE_SELF_SIGNED") {
340         test->last_cert_trust =
341             CertificateTrust::ForTrustedLeaf().WithRequireLeafSelfSigned();
342       } else if (value == "DISTRUSTED") {
343         test->last_cert_trust = CertificateTrust::ForDistrusted();
344       } else if (value == "UNSPECIFIED") {
345         test->last_cert_trust = CertificateTrust::ForUnspecified();
346       } else {
347         ADD_FAILURE() << "Unrecognized last_cert_trust: " << value;
348         return false;
349       }
350     } else if (GetValue("digest_policy: ", line_piece, &value,
351                         &has_digest_policy)) {
352       if (value == "STRONG") {
353         test->digest_policy = SimplePathBuilderDelegate::DigestPolicy::kStrong;
354       } else if (value == "ALLOW_SHA_1") {
355         test->digest_policy =
356             SimplePathBuilderDelegate::DigestPolicy::kWeakAllowSha1;
357       } else {
358         ADD_FAILURE() << "Unrecognized digest_policy: " << value;
359         return false;
360       }
361     } else if (GetValue("expected_user_constrained_policy_set: ", line_piece,
362                         &value, &has_user_constrained_policy_set)) {
363       std::vector<std::string> split_value(SplitString(value));
364       test->expected_user_constrained_policy_set =
365           std::set<std::string>(split_value.begin(), split_value.end());
366     } else if (bssl::string_util::StartsWith(line_piece, "#")) {
367       // Skip comments.
368       continue;
369     } else if (line_piece == kExpectedErrors) {
370       has_errors = true;
371       // The errors start on the next line, and extend until the end of the
372       // file.
373       std::string prefix =
374           std::string("\n") + kExpectedErrors + std::string("\n");
375       size_t errors_start = file_data.find(prefix);
376       if (errors_start == std::string::npos) {
377         ADD_FAILURE() << "expected_errors not found";
378         return false;
379       }
380       test->expected_errors = file_data.substr(errors_start + prefix.size());
381       break;
382     } else {
383       ADD_FAILURE() << "Unknown line: " << line_piece;
384       return false;
385     }
386   }
387 
388   if (!has_chain) {
389     ADD_FAILURE() << "Missing chain: ";
390     return false;
391   }
392 
393   if (!has_trust) {
394     ADD_FAILURE() << "Missing last_cert_trust: ";
395     return false;
396   }
397 
398   if (!has_time) {
399     ADD_FAILURE() << "Missing time: ";
400     return false;
401   }
402 
403   if (!has_key_purpose) {
404     ADD_FAILURE() << "Missing key_purpose: ";
405     return false;
406   }
407 
408   if (!has_errors) {
409     ADD_FAILURE() << "Missing errors:";
410     return false;
411   }
412 
413   // `has_user_constrained_policy_set` is intentionally not checked here. Not
414   // specifying expected_user_constrained_policy_set means the expected policy
415   // set is empty.
416 
417   return true;
418 }
419 
ReadTestFileToString(const std::string & file_path_ascii)420 std::string ReadTestFileToString(const std::string &file_path_ascii) {
421   return GetTestData(("pki/" + file_path_ascii).c_str());
422 }
423 
VerifyCertPathErrors(const std::string & expected_errors_str,const CertPathErrors & actual_errors,const ParsedCertificateList & chain,const std::string & errors_file_path)424 void VerifyCertPathErrors(const std::string &expected_errors_str,
425                           const CertPathErrors &actual_errors,
426                           const ParsedCertificateList &chain,
427                           const std::string &errors_file_path) {
428   std::string actual_errors_str = actual_errors.ToDebugString(chain);
429 
430   if (expected_errors_str != actual_errors_str) {
431     ADD_FAILURE() << "Cert path errors don't match expectations ("
432                   << errors_file_path << ")\n\n"
433                   << "EXPECTED:\n\n"
434                   << expected_errors_str << "\n"
435                   << "ACTUAL:\n\n"
436                   << actual_errors_str << "\n"
437                   << "===> Use "
438                      "pki/testdata/verify_certificate_chain_unittest/"
439                      "rebase-errors.py to rebaseline.\n";
440   }
441 }
442 
VerifyCertErrors(const std::string & expected_errors_str,const CertErrors & actual_errors,const std::string & errors_file_path)443 void VerifyCertErrors(const std::string &expected_errors_str,
444                       const CertErrors &actual_errors,
445                       const std::string &errors_file_path) {
446   std::string actual_errors_str = actual_errors.ToDebugString();
447 
448   if (expected_errors_str != actual_errors_str) {
449     ADD_FAILURE() << "Cert errors don't match expectations ("
450                   << errors_file_path << ")\n\n"
451                   << "EXPECTED:\n\n"
452                   << expected_errors_str << "\n"
453                   << "ACTUAL:\n\n"
454                   << actual_errors_str << "\n"
455                   << "===> Use "
456                      "pki/testdata/parse_certificate_unittest/"
457                      "rebase-errors.py to rebaseline.\n";
458   }
459 }
460 
VerifyUserConstrainedPolicySet(const std::set<std::string> & expected_user_constrained_policy_str_set,const std::set<der::Input> & actual_user_constrained_policy_set,const std::string & errors_file_path)461 void VerifyUserConstrainedPolicySet(
462     const std::set<std::string> &expected_user_constrained_policy_str_set,
463     const std::set<der::Input> &actual_user_constrained_policy_set,
464     const std::string &errors_file_path) {
465   std::set<std::string> actual_user_constrained_policy_str_set;
466   for (der::Input der_oid : actual_user_constrained_policy_set) {
467     actual_user_constrained_policy_str_set.insert(OidToString(der_oid));
468   }
469   if (expected_user_constrained_policy_str_set !=
470       actual_user_constrained_policy_str_set) {
471     ADD_FAILURE() << "user_constrained_policy_set doesn't match expectations ("
472                   << errors_file_path << ")\n\n"
473                   << "EXPECTED: "
474                   << StrSetToString(expected_user_constrained_policy_str_set)
475                   << "\n"
476                   << "ACTUAL: "
477                   << StrSetToString(actual_user_constrained_policy_str_set)
478                   << "\n";
479   }
480 }
481 
482 }  // namespace bssl
483