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