| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/cert/internal/test_helpers.h" |
| |
| #include "base/base64.h" |
| #include "base/base_paths.h" |
| #include "base/files/file_util.h" |
| #include "base/path_service.h" |
| #include "base/strings/string_split.h" |
| #include "net/cert/internal/cert_error_params.h" |
| #include "net/cert/internal/cert_errors.h" |
| #include "net/cert/pem_tokenizer.h" |
| #include "net/der/parser.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/boringssl/src/include/openssl/pool.h" |
| |
| namespace net { |
| |
| namespace { |
| |
| bool GetValue(base::StringPiece prefix, |
| base::StringPiece line, |
| std::string* value, |
| bool* has_value) { |
| if (!line.starts_with(prefix)) |
| return false; |
| |
| if (*has_value) { |
| ADD_FAILURE() << "Duplicated " << prefix; |
| return false; |
| } |
| |
| *has_value = true; |
| *value = line.substr(prefix.size()).as_string(); |
| return true; |
| } |
| |
| } // namespace |
| |
| namespace der { |
| |
| void PrintTo(const Input& data, ::std::ostream* os) { |
| std::string b64; |
| base::Base64Encode( |
| base::StringPiece(reinterpret_cast<const char*>(data.UnsafeData()), |
| data.Length()), |
| &b64); |
| |
| *os << "[" << b64 << "]"; |
| } |
| |
| } // namespace der |
| |
| der::Input SequenceValueFromString(const std::string* s) { |
| der::Parser parser((der::Input(s))); |
| der::Input data; |
| if (!parser.ReadTag(der::kSequence, &data)) { |
| ADD_FAILURE(); |
| return der::Input(); |
| } |
| if (parser.HasMore()) { |
| ADD_FAILURE(); |
| return der::Input(); |
| } |
| return data; |
| } |
| |
| ::testing::AssertionResult ReadTestDataFromPemFile( |
| const std::string& file_path_ascii, |
| const PemBlockMapping* mappings, |
| size_t mappings_length) { |
| // Compute the full path, relative to the src/ directory. |
| base::FilePath src_root; |
| base::PathService::Get(base::DIR_TEST_DATA, &src_root); |
| base::FilePath filepath = src_root.AppendASCII(file_path_ascii); |
| |
| // Read the full contents of the PEM file. |
| std::string file_data; |
| if (!base::ReadFileToString(filepath, &file_data)) { |
| return ::testing::AssertionFailure() |
| << "Couldn't read file: " << filepath.value(); |
| } |
| |
| // mappings_copy is used to keep track of which mappings have already been |
| // satisfied (by nulling the |value| field). This is used to track when |
| // blocks are mulitply defined. |
| std::vector<PemBlockMapping> mappings_copy(mappings, |
| mappings + mappings_length); |
| |
| // Build the |pem_headers| vector needed for PEMTokenzier. |
| std::vector<std::string> pem_headers; |
| for (const auto& mapping : mappings_copy) { |
| pem_headers.push_back(mapping.block_name); |
| } |
| |
| PEMTokenizer pem_tokenizer(file_data, pem_headers); |
| while (pem_tokenizer.GetNext()) { |
| for (auto& mapping : mappings_copy) { |
| // Find the mapping for this block type. |
| if (pem_tokenizer.block_type() == mapping.block_name) { |
| if (!mapping.value) { |
| return ::testing::AssertionFailure() |
| << "PEM block defined multiple times: " << mapping.block_name; |
| } |
| |
| // Copy the data to the result. |
| mapping.value->assign(pem_tokenizer.data()); |
| |
| // Mark the mapping as having been satisfied. |
| mapping.value = nullptr; |
| } |
| } |
| } |
| |
| // Ensure that all specified blocks were found. |
| for (const auto& mapping : mappings_copy) { |
| if (mapping.value && !mapping.optional) { |
| return ::testing::AssertionFailure() |
| << "PEM block missing: " << mapping.block_name; |
| } |
| } |
| |
| return ::testing::AssertionSuccess(); |
| } |
| |
| VerifyCertChainTest::VerifyCertChainTest() |
| : user_initial_policy_set{AnyPolicy()} {} |
| VerifyCertChainTest::~VerifyCertChainTest() = default; |
| |
| bool VerifyCertChainTest::HasHighSeverityErrors() const { |
| // This function assumes that high severity warnings are prefixed with |
| // "ERROR: " and warnings are prefixed with "WARNING: ". This is an |
| // implementation detail of CertError::ToDebugString). |
| // |
| // Do a quick sanity-check to confirm this. |
| CertError error(CertError::SEVERITY_HIGH, "unused", nullptr); |
| EXPECT_EQ("ERROR: unused\n", error.ToDebugString()); |
| CertError warning(CertError::SEVERITY_WARNING, "unused", nullptr); |
| EXPECT_EQ("WARNING: unused\n", warning.ToDebugString()); |
| |
| // Do a simple substring test (not perfect, but good enough for our test |
| // corpus). |
| return expected_errors.find("ERROR: ") != std::string::npos; |
| } |
| |
| bool ReadCertChainFromFile(const std::string& file_path_ascii, |
| ParsedCertificateList* chain) { |
| // Reset all the out parameters to their defaults. |
| *chain = ParsedCertificateList(); |
| |
| std::string file_data = ReadTestFileToString(file_path_ascii); |
| if (file_data.empty()) |
| return false; |
| |
| std::vector<std::string> pem_headers = {"CERTIFICATE"}; |
| |
| PEMTokenizer pem_tokenizer(file_data, pem_headers); |
| while (pem_tokenizer.GetNext()) { |
| const std::string& block_data = pem_tokenizer.data(); |
| |
| CertErrors errors; |
| if (!ParsedCertificate::CreateAndAddToVector( |
| bssl::UniquePtr<CRYPTO_BUFFER>(CRYPTO_BUFFER_new( |
| reinterpret_cast<const uint8_t*>(block_data.data()), |
| block_data.size(), nullptr)), |
| {}, chain, &errors)) { |
| ADD_FAILURE() << errors.ToDebugString(); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool ReadVerifyCertChainTestFromFile(const std::string& file_path_ascii, |
| VerifyCertChainTest* test) { |
| // Reset all the out parameters to their defaults. |
| *test = {}; |
| |
| std::string file_data = ReadTestFileToString(file_path_ascii); |
| if (file_data.empty()) |
| return false; |
| |
| std::vector<std::string> lines = base::SplitString( |
| file_data, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| |
| bool has_chain = false; |
| bool has_trust = false; |
| bool has_time = false; |
| bool has_errors = false; |
| bool has_key_purpose = false; |
| |
| base::StringPiece kExpectedErrors = "expected_errors:"; |
| |
| for (const std::string& line : lines) { |
| base::StringPiece line_piece(line); |
| |
| std::string value; |
| |
| // For details on the file format refer to: |
| // net/data/verify_certificate_chain_unittest/README. |
| if (GetValue("chain: ", line_piece, &value, &has_chain)) { |
| // Interpret the |chain| path as being relative to the .test file. |
| size_t slash = file_path_ascii.rfind('/'); |
| if (slash == std::string::npos) { |
| ADD_FAILURE() << "Bad path - expecting slashes"; |
| return false; |
| } |
| std::string chain_path = file_path_ascii.substr(0, slash) + "/" + value; |
| |
| ReadCertChainFromFile(chain_path, &test->chain); |
| } else if (GetValue("utc_time: ", line_piece, &value, &has_time)) { |
| if (value == "DEFAULT") { |
| value = "180510120000Z"; |
| } |
| if (!der::ParseUTCTime(der::Input(&value), &test->time)) { |
| ADD_FAILURE() << "Failed parsing UTC time"; |
| return false; |
| } |
| } else if (GetValue("key_purpose: ", line_piece, &value, |
| &has_key_purpose)) { |
| if (value == "ANY_EKU") { |
| test->key_purpose = KeyPurpose::ANY_EKU; |
| } else if (value == "SERVER_AUTH") { |
| test->key_purpose = KeyPurpose::SERVER_AUTH; |
| } else if (value == "CLIENT_AUTH") { |
| test->key_purpose = KeyPurpose::CLIENT_AUTH; |
| } else { |
| ADD_FAILURE() << "Unrecognized key_purpose: " << value; |
| return false; |
| } |
| } else if (GetValue("last_cert_trust: ", line_piece, &value, &has_trust)) { |
| if (value == "TRUSTED_ANCHOR") { |
| test->last_cert_trust = CertificateTrust::ForTrustAnchor(); |
| } else if (value == "TRUSTED_ANCHOR_WITH_CONSTRAINTS") { |
| test->last_cert_trust = |
| CertificateTrust::ForTrustAnchorEnforcingConstraints(); |
| } else if (value == "DISTRUSTED") { |
| test->last_cert_trust = CertificateTrust::ForDistrusted(); |
| } else if (value == "UNSPECIFIED") { |
| test->last_cert_trust = CertificateTrust::ForUnspecified(); |
| } else { |
| ADD_FAILURE() << "Unrecognized last_cert_trust: " << value; |
| return false; |
| } |
| } else if (line_piece.starts_with("#")) { |
| // Skip comments. |
| continue; |
| } else if (line_piece == kExpectedErrors) { |
| has_errors = true; |
| // The errors start on the next line, and extend until the end of the |
| // file. |
| std::string prefix = |
| std::string("\n") + kExpectedErrors.as_string() + std::string("\n"); |
| size_t errors_start = file_data.find(prefix); |
| if (errors_start == std::string::npos) { |
| ADD_FAILURE() << "expected_errors not found"; |
| return false; |
| } |
| test->expected_errors = file_data.substr(errors_start + prefix.size()); |
| break; |
| } else { |
| ADD_FAILURE() << "Unknown line: " << line_piece; |
| return false; |
| } |
| } |
| |
| if (!has_chain) { |
| ADD_FAILURE() << "Missing chain: "; |
| return false; |
| } |
| |
| if (!has_trust) { |
| ADD_FAILURE() << "Missing last_cert_trust: "; |
| return false; |
| } |
| |
| if (!has_time) { |
| ADD_FAILURE() << "Missing time: "; |
| return false; |
| } |
| |
| if (!has_key_purpose) { |
| ADD_FAILURE() << "Missing key_purpose: "; |
| return false; |
| } |
| |
| if (!has_errors) { |
| ADD_FAILURE() << "Missing errors:"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| std::string ReadTestFileToString(const std::string& file_path_ascii) { |
| // Compute the full path, relative to the src/ directory. |
| base::FilePath src_root; |
| base::PathService::Get(base::DIR_TEST_DATA, &src_root); |
| base::FilePath filepath = src_root.AppendASCII(file_path_ascii); |
| |
| // Read the full contents of the file. |
| std::string file_data; |
| if (!base::ReadFileToString(filepath, &file_data)) { |
| ADD_FAILURE() << "Couldn't read file: " << filepath.value(); |
| return std::string(); |
| } |
| |
| return file_data; |
| } |
| |
| void VerifyCertPathErrors(const std::string& expected_errors_str, |
| const CertPathErrors& actual_errors, |
| const ParsedCertificateList& chain, |
| const std::string& errors_file_path) { |
| std::string actual_errors_str = actual_errors.ToDebugString(chain); |
| |
| if (expected_errors_str != actual_errors_str) { |
| ADD_FAILURE() << "Cert path errors don't match expectations (" |
| << errors_file_path << ")\n\n" |
| << "EXPECTED:\n\n" |
| << expected_errors_str << "\n" |
| << "ACTUAL:\n\n" |
| << actual_errors_str << "\n" |
| << "===> Use " |
| "net/data/verify_certificate_chain_unittest/" |
| "rebase-errors.py to rebaseline.\n"; |
| } |
| } |
| |
| void VerifyCertErrors(const std::string& expected_errors_str, |
| const CertErrors& actual_errors, |
| const std::string& errors_file_path) { |
| std::string actual_errors_str = actual_errors.ToDebugString(); |
| |
| if (expected_errors_str != actual_errors_str) { |
| ADD_FAILURE() << "Cert errors don't match expectations (" |
| << errors_file_path << ")\n\n" |
| << "EXPECTED:\n\n" |
| << expected_errors_str << "\n" |
| << "ACTUAL:\n\n" |
| << actual_errors_str << "\n" |
| << "===> Use " |
| "net/data/parse_certificate_unittest/" |
| "rebase-errors.py to rebaseline.\n"; |
| } |
| } |
| |
| } // namespace net |