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