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