blob: 98487d75b7424225188276a872226b37a1e90c59 [file] [log] [blame]
Bob Beckbc97b7a2023-04-18 08:35:15 -06001// 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
25namespace bssl {
26
27namespace {
28
29bool 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.
48std::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
59std::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
71std::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
84std::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
96namespace der {
97
98void 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
113der::Input SequenceValueFromString(const std::string* 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
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
175VerifyCertChainTest::VerifyCertChainTest()
176 : user_initial_policy_set{der::Input(kAnyPolicyOid)} {}
177VerifyCertChainTest::~VerifyCertChainTest() = default;
178
179bool 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
195bool 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
224std::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
234bool 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
415std::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::DIR_SOURCE_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
431void 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
450void 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
468void 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