Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(132)

Unified Diff: net/cert/internal/parse_certificate_unittest.cc

Issue 1279963003: Add a function for parsing RFC 5280's "TBSCertificate". (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@cert_mapper
Patch Set: Fully move expectations to test data Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698