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