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