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