Index: net/cert/internal/parse_certificate_unittest.cc |
diff --git a/net/cert/internal/parse_certificate_unittest.cc b/net/cert/internal/parse_certificate_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..0dc87fb6bbfae825868ec26760509cfe2493439b |
--- /dev/null |
+++ b/net/cert/internal/parse_certificate_unittest.cc |
@@ -0,0 +1,261 @@ |
+// 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/parse_certificate.h" |
+ |
+#include "base/base64.h" |
+#include "base/base_paths.h" |
+#include "base/files/file_util.h" |
+#include "base/path_service.h" |
+#include "net/base/test_data_directory.h" |
+#include "net/cert/pem_tokenizer.h" |
+#include "net/der/input.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+namespace net { |
+ |
+namespace der { |
+ |
+// These functions are used by GTEST to support EXPECT_EQ() for |
davidben
2015/08/11 20:31:57
Nit: I think GTest is the more common capitalizati
eroman
2015/08/12 00:37:10
Done.
|
+// der::Input. |
+ |
+void PrintTo(const Input& data, ::std::ostream* os) { |
davidben
2015/08/11 20:31:57
#include <ostream>
eroman
2015/08/12 00:37:10
Done.
|
+ std::string b64; |
+ base::Base64Encode( |
+ base::StringPiece(reinterpret_cast<const char*>(data.UnsafeData()), |
+ data.Length()), |
+ &b64); |
+ |
+ *os << "[" << b64 << "]"; |
+} |
+ |
+bool operator==(const Input& a, const Input& b) { |
+ return a.Equals(b); |
+} |
+ |
+} // namespace der |
+ |
+namespace { |
+ |
+// Returns a path to the file |file_name| within the unittest data directory. |
+base::FilePath GetTestFilePath(const std::string& file_name) { |
+ base::FilePath src_root; |
+ PathService::Get(base::DIR_SOURCE_ROOT, &src_root); |
+ return src_root.Append( |
+ FILE_PATH_LITERAL("net/data/parse_certificate_unittest")) |
+ .AppendASCII(file_name); |
+} |
+ |
+// Contains the expected values in a ParsedCertificate. |
+struct ParsedCertificateExpectations { |
+ std::string signature; |
+ std::string signature_algorithm; |
+}; |
+ |
+// Contains the expected values in a ParsedTbsCertificate. |
+struct ParsedTbsCertificateExpectations { |
+ std::string serial_number; |
+ std::string signature_algorithm; |
+ std::string issuer; |
+ std::string validity; |
+ std::string subject; |
+ std::string spki; |
+ std::string extensions; |
+}; |
+ |
+// Reads a test file |file_name| which contains both a certificate |
+// (CERTIFICATE), and all the expected values for the parsed files (like SPKI). |
+// On success fills |cert| with the cert DER data, and fills |
+// |cert_expectations| and |tbs_expectations| with the expected values for its |
+// consitituent fields. |
+::testing::AssertionResult LoadTestCertificatePemData( |
+ const std::string& file_name, |
+ std::string* cert, |
+ ParsedCertificateExpectations* cert_expectations, |
+ ParsedTbsCertificateExpectations* tbs_expectations) { |
+ base::FilePath path = GetTestFilePath(file_name); |
+ std::string file_data; |
+ if (!base::ReadFileToString(path, &file_data)) { |
+ return ::testing::AssertionFailure() << "Couldn't read file: " |
+ << path.value(); |
+ } |
+ |
+ const char kCertificateBlock[] = "CERTIFICATE"; |
+ const char kSignatureBlock[] = "SIGNATURE"; |
+ const char kSignatureAlgorithmBlock[] = "SIGNATURE ALGORITHM"; |
+ const char kSerialNumber[] = "SERIAL NUMBER"; |
+ const char kIssuerBlock[] = "ISSUER"; |
+ const char kValidityBlock[] = "VALIDITY"; |
+ const char kSubjectBlock[] = "SUBJECT"; |
+ const char kSpkiBlock[] = "SPKI"; |
+ const char kExtensionsBlock[] = "EXTENSIONS"; |
+ |
+ std::vector<std::string> pem_headers; |
+ pem_headers.push_back(kCertificateBlock); |
+ pem_headers.push_back(kSignatureBlock); |
+ pem_headers.push_back(kSignatureAlgorithmBlock); |
+ pem_headers.push_back(kSerialNumber); |
+ pem_headers.push_back(kIssuerBlock); |
+ pem_headers.push_back(kValidityBlock); |
+ pem_headers.push_back(kSubjectBlock); |
+ pem_headers.push_back(kSpkiBlock); |
+ pem_headers.push_back(kExtensionsBlock); |
+ |
+ PEMTokenizer pem_tokenizer(file_data, pem_headers); |
+ while (pem_tokenizer.GetNext()) { |
+ const std::string& block_type = pem_tokenizer.block_type(); |
+ if (block_type == kCertificateBlock) { |
+ cert->assign(pem_tokenizer.data()); |
+ } else if (block_type == kSignatureBlock) { |
+ cert_expectations->signature.assign(pem_tokenizer.data()); |
+ } else if (block_type == kSignatureAlgorithmBlock) { |
+ cert_expectations->signature_algorithm.assign(pem_tokenizer.data()); |
+ tbs_expectations->signature_algorithm.assign(pem_tokenizer.data()); |
+ } else if (block_type == kSerialNumber) { |
+ tbs_expectations->serial_number.assign(pem_tokenizer.data()); |
+ } else if (block_type == kIssuerBlock) { |
+ tbs_expectations->issuer.assign(pem_tokenizer.data()); |
+ } else if (block_type == kValidityBlock) { |
+ tbs_expectations->validity.assign(pem_tokenizer.data()); |
+ } else if (block_type == kSubjectBlock) { |
+ tbs_expectations->subject.assign(pem_tokenizer.data()); |
+ } else if (block_type == kSpkiBlock) { |
+ tbs_expectations->spki.assign(pem_tokenizer.data()); |
+ } else if (block_type == kExtensionsBlock) { |
+ tbs_expectations->extensions.assign(pem_tokenizer.data()); |
+ } |
+ } |
+ |
+ return ::testing::AssertionSuccess(); |
+} |
+ |
+// TODO(eroman): This function was copied from verify_signed_data_unittest.cc |
+// |
+// Creates a der::Input from an std::string. The lifetimes are a bit subtle |
+// when using this function: |
+// |
+// The returned der::Input() is only valid so long as the input string is alive |
+// and is not mutated. |
+// |
+// Note that the input parameter has been made a pointer to prevent callers |
+// from accidentally passing an r-value. |
+der::Input InputFromString(const std::string* s) { |
+ return der::Input(reinterpret_cast<const uint8_t*>(s->data()), s->size()); |
+} |
+ |
+// Verifies that the given |cert| matches |expectations|. |
+void EnsureParsedCertificate( |
+ const ParsedCertificate& cert, |
+ const ParsedCertificateExpectations& expectations) { |
+ EXPECT_EQ(0, cert.signature_value.unused_bits()); |
+ EXPECT_EQ(InputFromString(&expectations.signature), |
+ cert.signature_value.bytes()); |
+ EXPECT_EQ(InputFromString(&expectations.signature_algorithm), |
+ cert.signature_algorithm_tlv); |
+} |
+ |
+// Verifies that the given |tbs| matches |expectations|. |
+void EnsureParsedTbsCertificate( |
+ const ParsedTbsCertificate& tbs, |
+ const ParsedTbsCertificateExpectations& expectations, |
+ CertificateVersion expected_version) { |
+ EXPECT_EQ(expected_version, tbs.version); |
+ |
+ EXPECT_EQ(InputFromString(&expectations.serial_number), tbs.serial_number); |
+ EXPECT_EQ(InputFromString(&expectations.signature_algorithm), |
+ tbs.signature_algorithm_tlv); |
+ |
+ EXPECT_EQ(InputFromString(&expectations.issuer), tbs.issuer_tlv); |
+ EXPECT_EQ(InputFromString(&expectations.validity), tbs.validity_tlv); |
+ EXPECT_EQ(InputFromString(&expectations.subject), tbs.subject_tlv); |
+ EXPECT_EQ(InputFromString(&expectations.spki), tbs.spki_tlv); |
+ |
+ EXPECT_FALSE(tbs.has_issuer_unique_id); |
+ EXPECT_FALSE(tbs.has_subject_unique_id); |
+ |
+ EXPECT_EQ(InputFromString(&expectations.extensions), tbs.extensions_tlv); |
+ EXPECT_EQ(!expectations.extensions.empty(), tbs.has_extensions); |
+} |
+ |
+// Loads certificate data from the PEM file |file_name| and verifies that both |
+// parsing the Certificate and TBSCertificate succeed. Upon return |cert| and |
+// |tbs| are filled accordingly. |
+void EnsureParsingTbsSucceds(const std::string& file_name, |
+ CertificateVersion expected_version) { |
+ std::string cert_data; |
+ ParsedCertificateExpectations cert_expectations; |
+ ParsedTbsCertificateExpectations tbs_expectations; |
+ |
+ // Read the certificate data and test expectations from a single PEM file. |
+ ASSERT_TRUE(LoadTestCertificatePemData( |
+ file_name, &cert_data, &cert_expectations, &tbs_expectations)); |
+ |
+ // Parsing the certificate should succeed. |
+ ParsedCertificate cert; |
+ ASSERT_TRUE(ParseCertificate(InputFromString(&cert_data), &cert)); |
+ |
+ // Ensure that the ParsedCertificate matches expectations. |
+ EnsureParsedCertificate(cert, cert_expectations); |
+ |
+ ParsedTbsCertificate tbs; |
+ ASSERT_TRUE(ParseTbsCertificate(cert.tbs_certificate_tlv, &tbs)); |
+ |
+ // Ensure that the ParsedTbdCertificate matches expectations. |
+ EnsureParsedTbsCertificate(tbs, tbs_expectations, expected_version); |
+} |
+ |
+// Loads certificate data from the PEM file |file_name| and verifies that the |
+// Certificate parsing succeed, however the TBSCertificate parsing fails. |
+void EnsureParsingTbsFails(const std::string& file_name) { |
+ std::string cert_data; |
+ ParsedCertificateExpectations cert_expectations; |
+ ParsedTbsCertificateExpectations tbs_expectations; |
+ |
+ ASSERT_TRUE(LoadTestCertificatePemData( |
+ file_name, &cert_data, &cert_expectations, &tbs_expectations)); |
+ |
+ ParsedCertificate cert; |
+ ASSERT_TRUE(ParseCertificate(InputFromString(&cert_data), &cert)); |
+ |
+ EnsureParsedCertificate(cert, cert_expectations); |
+ |
+ ParsedTbsCertificate tbs; |
+ ASSERT_FALSE(ParseTbsCertificate(cert.tbs_certificate_tlv, &tbs)); |
+} |
+ |
+// Tests parsing a TBSCertificate with a negative serial number. |
+// |
+// CAs are not supposed to include negative serial numbers, however RFC 5280 |
+// says that consumers should be expected to deal with them (so they are allowed |
+// by ParseTbsCertificate()). |
+TEST(ParseCertificateTest, NegativeSerialNumber) { |
+ EnsureParsingTbsSucceds("negative_serial.pem", CertificateVersion::V3); |
+} |
+ |
+// Tests parsing a TBSCertificate with a serial number that is 21 octets long |
+// (and the first byte is 0). |
+TEST(ParseCertificateTest, SerialNumber21OctetsLeading0) { |
+ EnsureParsingTbsFails("serial_number_21_octets_leading_0.pem"); |
+} |
+ |
+// Tests parsing a TBSCertificate with a serial number that is 26 octets long |
+// (and does not contain a leading 0). |
+TEST(ParseCertificateTest, SerialNumber26Octets) { |
+ EnsureParsingTbsFails("serial_number_26_octets.pem"); |
+} |
+ |
+// Tests parsing a TBSCertificate which lacks a version number (causing it to |
+// default to v1). |
+TEST(ParseCertificateTest, CertificateVersion1) { |
+ EnsureParsingTbsSucceds("version1.pem", CertificateVersion::V1); |
+} |
+ |
+// Tests parsing a TBSCertificate with version 3. |
+TEST(ParseCertificateTest, CertificateVersion3) { |
+ EnsureParsingTbsSucceds("version3.pem", CertificateVersion::V3); |
+} |
+ |
+} // namespace |
+ |
+} // namespace net |