diff options
author | eranm@google.com <eranm@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-01 01:42:03 +0000 |
---|---|---|
committer | eranm@google.com <eranm@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-01 01:42:03 +0000 |
commit | 95ac16b0adc0c0b85dd2f2e66cd930bfaceaf30f (patch) | |
tree | fe723da6a2d400e8f681989b65ee05d987f94450 | |
parent | c457dcf77264057729d025b3f85921c669fc0e20 (diff) | |
download | chromium_src-95ac16b0adc0c0b85dd2f2e66cd930bfaceaf30f.zip chromium_src-95ac16b0adc0c0b85dd2f2e66cd930bfaceaf30f.tar.gz chromium_src-95ac16b0adc0c0b85dd2f2e66cd930bfaceaf30f.tar.bz2 |
CT: First step towards supporting Certificate Transparency in Chrome.
This patch adds Signed Certificate Timestamp (SCT) encoding/decoding.
SCT is the Certificate Transparency (CT) structure containing a proof
of a public log's commitment to adding a certificate to its public
repository.
The next patches would be extracting the SCTs when embedded in
certificates and verifying the signature from the SCT over them.
BUG=309578
Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=232131
Review URL: https://codereview.chromium.org/37633002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@232267 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | net/cert/ct_serialization.cc | 375 | ||||
-rw-r--r-- | net/cert/ct_serialization.h | 72 | ||||
-rw-r--r-- | net/cert/ct_serialization_unittest.cc | 170 | ||||
-rw-r--r-- | net/cert/signed_certificate_timestamp.cc | 31 | ||||
-rw-r--r-- | net/cert/signed_certificate_timestamp.h | 102 | ||||
-rw-r--r-- | net/net.gyp | 9 | ||||
-rw-r--r-- | net/test/ct_test_util.cc | 112 | ||||
-rw-r--r-- | net/test/ct_test_util.h | 35 |
8 files changed, 905 insertions, 1 deletions
diff --git a/net/cert/ct_serialization.cc b/net/cert/ct_serialization.cc new file mode 100644 index 0000000..f670439 --- /dev/null +++ b/net/cert/ct_serialization.cc @@ -0,0 +1,375 @@ +// Copyright 2013 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/ct_serialization.h" + +#include "base/basictypes.h" +#include "base/logging.h" + +namespace net { + +namespace ct { + +namespace { + +// Note: length is always specified in bytes. +// Signed Certificate Timestamp (SCT) Version length +const size_t kVersionLength = 1; + +// Members of a V1 SCT +const size_t kLogIdLength = 32; +const size_t kTimestampLength = 8; +const size_t kExtensionsLengthBytes = 2; +const size_t kHashAlgorithmLength = 1; +const size_t kSigAlgorithmLength = 1; +const size_t kSignatureLengthBytes = 2; + +// Members of the digitally-signed struct of a V1 SCT +const size_t kSignatureTypeLength = 1; +const size_t kLogEntryTypeLength = 2; +const size_t kAsn1CertificateLengthBytes = 3; +const size_t kTbsCertificateLengthBytes = 3; + +const size_t kSCTListLengthBytes = 2; +const size_t kSerializedSCTLengthBytes = 2; + +enum SignatureType { + SIGNATURE_TYPE_CERTIFICATE_TIMESTAMP = 0, + TREE_HASH = 1, +}; + +// Reads a TLS-encoded variable length unsigned integer from |in|. +// The integer is expected to be in big-endian order, which is used by TLS. +// The bytes read from |in| are discarded (i.e. |in|'s prefix removed) +// |length| indicates the size (in bytes) of the integer. On success, returns +// true and stores the result in |*out|. +template <typename T> +bool ReadUint(size_t length, base::StringPiece* in, T* out) { + if (in->size() < length) + return false; + DCHECK_LE(length, sizeof(T)); + + T result = 0; + for (size_t i = 0; i < length; ++i) { + result = (result << 8) | static_cast<unsigned char>((*in)[i]); + } + in->remove_prefix(length); + *out = result; + return true; +} + +// Reads a TLS-encoded field length from |in|. +// The bytes read from |in| are discarded (i.e. |in|'s prefix removed) +// |prefix_length| indicates the bytes needed to represent the length (e.g. 3) +// success, returns true and stores the result in |*out|. +bool ReadLength(size_t prefix_length, base::StringPiece* in, size_t* out) { + size_t length; + if (!ReadUint(prefix_length, in, &length)) + return false; + *out = length; + return true; +} + +// Reads |length| bytes from |*in|. If |*in| is too small, returns false. +// The bytes read from |in| are discarded (i.e. |in|'s prefix removed) +bool ReadFixedBytes(size_t length, + base::StringPiece* in, + base::StringPiece* out) { + if (in->length() < length) + return false; + out->set(in->data(), length); + in->remove_prefix(length); + return true; +} + +// Reads a length-prefixed variable amount of bytes from |in|, updating |out| +// on success. |prefix_length| indicates the number of bytes needed to represent +// the length. +// The bytes read from |in| are discarded (i.e. |in|'s prefix removed) +bool ReadVariableBytes(size_t prefix_length, + base::StringPiece* in, + base::StringPiece* out) { + size_t length; + if (!ReadLength(prefix_length, in, &length)) + return false; + return ReadFixedBytes(length, in, out); +} + +// Reads a variable-length list that has been TLS encoded. +// The bytes read from |in| are discarded (i.e. |in|'s prefix removed) +// |max_list_length| contains the overall length of the encoded list. +// |max_item_length| contains the maximum length of a single item. +// On success, returns true and updates |*out| with the encoded list. +bool ReadList(size_t max_list_length, + size_t max_item_length, + base::StringPiece* in, + std::vector<base::StringPiece>* out) { + std::vector<base::StringPiece> result; + + base::StringPiece list_data; + if (!ReadVariableBytes(max_list_length, in, &list_data)) + return false; + + while (!list_data.empty()) { + base::StringPiece list_item; + if (!ReadVariableBytes(max_item_length, &list_data, &list_item)) { + DVLOG(1) << "Failed to read item in list."; + return false; + } + if (list_item.empty()) { + DVLOG(1) << "Empty item in list"; + return false; + } + result.push_back(list_item); + } + + result.swap(*out); + return true; +} + +// Checks and converts a hash algorithm. +// |in| is the numeric representation of the algorithm. +// If the hash algorithm value is in a set of known values, fills in |out| and +// returns true. Otherwise, returns false. +bool ConvertHashAlgorithm(unsigned in, DigitallySigned::HashAlgorithm* out) { + switch (in) { + case DigitallySigned::HASH_ALGO_NONE: + case DigitallySigned::HASH_ALGO_MD5: + case DigitallySigned::HASH_ALGO_SHA1: + case DigitallySigned::HASH_ALGO_SHA224: + case DigitallySigned::HASH_ALGO_SHA256: + case DigitallySigned::HASH_ALGO_SHA384: + case DigitallySigned::HASH_ALGO_SHA512: + break; + default: + return false; + } + *out = static_cast<DigitallySigned::HashAlgorithm>(in); + return true; +} + +// Checks and converts a signing algorithm. +// |in| is the numeric representation of the algorithm. +// If the signing algorithm value is in a set of known values, fills in |out| +// and returns true. Otherwise, returns false. +bool ConvertSignatureAlgorithm( + unsigned in, + DigitallySigned::SignatureAlgorithm* out) { + switch (in) { + case DigitallySigned::SIG_ALGO_ANONYMOUS: + case DigitallySigned::SIG_ALGO_RSA: + case DigitallySigned::SIG_ALGO_DSA: + case DigitallySigned::SIG_ALGO_ECDSA: + break; + default: + return false; + } + *out = static_cast<DigitallySigned::SignatureAlgorithm>(in); + return true; +} + +// Checks and converts a log entry type. +// |in| the numeric representation of the log type. +// If the log type is 0 (X.509 cert) or 1 (PreCertificate), fills in |out| and +// returns true. Otherwise, returns false. +bool ConvertLogEntryType(int in, LogEntry::Type* out) { + switch (in) { + case LogEntry::LOG_ENTRY_TYPE_X509: + case LogEntry::LOG_ENTRY_TYPE_PRECERT: + break; + default: + return false; + } + *out = static_cast<LogEntry::Type>(in); + return true; +} + +// Writes a TLS-encoded variable length unsigned integer to |output|. +// |length| indicates the size (in bytes) of the integer. +// |value| the value itself to be written. +template <typename T> +void WriteUint(size_t length, T value, std::string* output) { + DCHECK_LE(length, sizeof(T)); + DCHECK(length == sizeof(T) || value >> (length * 8) == 0); + + for (; length > 0; --length) { + output->push_back((value >> ((length - 1)* 8)) & 0xFF); + } +} + +// Writes an array to |output| from |input|. +// Should be used in one of two cases: +// * The length of |input| has already been encoded into the |output| stream. +// * The length of |input| is fixed and the reader is expected to specify that +// length when reading. +// If the length of |input| is dynamic and data is expected to follow it, +// WriteVariableBytes must be used. +void WriteEncodedBytes(const base::StringPiece& input, std::string* output) { + input.AppendToString(output); +} + +// Writes a variable-length array to |output|. +// |prefix_length| indicates the number of bytes needed to represnt the length. +// |input| is the array itself. +// If the size of |input| is less than 2^|prefix_length| - 1, encode the +// length and data and return true. Otherwise, return false. +bool WriteVariableBytes(size_t prefix_length, + const base::StringPiece& input, + std::string* output) { + size_t input_size = input.size(); + size_t max_allowed_input_size = + static_cast<size_t>(((1 << (prefix_length * 8)) - 1)); + if (input_size > max_allowed_input_size) + return false; + + WriteUint(prefix_length, input.size(), output); + WriteEncodedBytes(input, output); + + return true; +} + +// Writes a LogEntry of type X.509 cert to |output|. +// |input| is the LogEntry containing the certificate. +// Returns true if the leaf_certificate in the LogEntry does not exceed +// kMaxAsn1CertificateLength and so can be written to |output|. +bool EncodeAsn1CertLogEntry(const LogEntry& input, std::string* output) { + return WriteVariableBytes(kAsn1CertificateLengthBytes, + input.leaf_certificate, output); +} + +// Writes a LogEntry of type PreCertificate to |output|. +// |input| is the LogEntry containing the TBSCertificate and issuer key hash. +// Returns true if the TBSCertificate component in the LogEntry does not +// exceed kMaxTbsCertificateLength and so can be written to |output|. +bool EncodePrecertLogEntry(const LogEntry& input, std::string* output) { + WriteEncodedBytes( + base::StringPiece( + reinterpret_cast<const char*>(input.issuer_key_hash.data), + kLogIdLength), + output); + return WriteVariableBytes(kTbsCertificateLengthBytes, + input.tbs_certificate, output); +} + +} // namespace + +bool EncodeDigitallySigned(const DigitallySigned& input, + std::string* output) { + WriteUint(kHashAlgorithmLength, input.hash_algorithm, output); + WriteUint(kSigAlgorithmLength, input.signature_algorithm, + output); + return WriteVariableBytes(kSignatureLengthBytes, input.signature_data, + output); +} + +bool DecodeDigitallySigned(base::StringPiece* input, + DigitallySigned* output) { + unsigned hash_algo; + unsigned sig_algo; + base::StringPiece sig_data; + + if (!ReadUint(kHashAlgorithmLength, input, &hash_algo) || + !ReadUint(kSigAlgorithmLength, input, &sig_algo) || + !ReadVariableBytes(kSignatureLengthBytes, input, &sig_data)) { + return false; + } + + DigitallySigned result; + if (!ConvertHashAlgorithm(hash_algo, &result.hash_algorithm)) { + DVLOG(1) << "Invalid hash algorithm " << hash_algo; + return false; + } + if (!ConvertSignatureAlgorithm(sig_algo, &result.signature_algorithm)) { + DVLOG(1) << "Invalid signature algorithm " << sig_algo; + return false; + } + sig_data.CopyToString(&result.signature_data); + + *output = result; + return true; +} + +bool EncodeLogEntry(const LogEntry& input, std::string* output) { + WriteUint(kLogEntryTypeLength, input.type, output); + switch (input.type) { + case LogEntry::LOG_ENTRY_TYPE_X509: + return EncodeAsn1CertLogEntry(input, output); + case LogEntry::LOG_ENTRY_TYPE_PRECERT: + return EncodePrecertLogEntry(input, output); + } + return false; +} + +bool EncodeV1SCTSignedData(const base::Time& timestamp, + const std::string& serialized_log_entry, + const std::string& extensions, + std::string* output) { + WriteUint(kVersionLength, SignedCertificateTimestamp::SCT_VERSION_1, + output); + WriteUint(kSignatureTypeLength, SIGNATURE_TYPE_CERTIFICATE_TIMESTAMP, + output); + base::TimeDelta time_since_epoch = timestamp - base::Time::UnixEpoch(); + WriteUint(kTimestampLength, time_since_epoch.InMilliseconds(), + output); + // NOTE: serialized_log_entry must already be serialized and contain the + // length as the prefix. + WriteEncodedBytes(serialized_log_entry, output); + return WriteVariableBytes(kExtensionsLengthBytes, extensions, output); +} + +bool DecodeSCTList(base::StringPiece* input, + std::vector<base::StringPiece>* output) { + std::vector<base::StringPiece> result; + if (!ReadList(kSCTListLengthBytes, kSerializedSCTLengthBytes, + input, &result)) { + return false; + } + + if (!input->empty() || result.empty()) + return false; + output->swap(result); + return true; +} + +bool DecodeSignedCertificateTimestamp(base::StringPiece* input, + SignedCertificateTimestamp* output) { + SignedCertificateTimestamp result; + unsigned version; + if (!ReadUint(kVersionLength, input, &version)) + return false; + if (version != SignedCertificateTimestamp::SCT_VERSION_1) { + DVLOG(1) << "Unsupported/invalid version " << version; + return false; + } + + result.version = SignedCertificateTimestamp::SCT_VERSION_1; + uint64 timestamp; + base::StringPiece log_id; + base::StringPiece extensions; + if (!ReadFixedBytes(kLogIdLength, input, &log_id) || + !ReadUint(kTimestampLength, input, ×tamp) || + !ReadVariableBytes(kExtensionsLengthBytes, input, + &extensions) || + !DecodeDigitallySigned(input, &result.signature)) { + return false; + } + + if (timestamp > static_cast<uint64>(kint64max)) { + DVLOG(1) << "Timestamp value too big to cast to int64: " << timestamp; + return false; + } + + log_id.CopyToString(&result.log_id); + extensions.CopyToString(&result.extensions); + result.timestamp = + base::Time::UnixEpoch() + + base::TimeDelta::FromMilliseconds(static_cast<int64>(timestamp)); + + *output = result; + return true; +} + +} // namespace ct + +} // namespace net diff --git a/net/cert/ct_serialization.h b/net/cert/ct_serialization.h new file mode 100644 index 0000000..4fc74f7 --- /dev/null +++ b/net/cert/ct_serialization.h @@ -0,0 +1,72 @@ +// Copyright 2013 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. + +#ifndef NET_CERT_CT_SERIALIZATION_H_ +#define NET_CERT_CT_SERIALIZATION_H_ + +#include <string> +#include <vector> + +#include "base/strings/string_piece.h" +#include "net/base/net_export.h" +#include "net/cert/signed_certificate_timestamp.h" + +namespace net { + +// Utility functions for encoding/decoding structures used by Certificate +// Transparency to/from the TLS wire format encoding. +namespace ct { + +// If |input.signature_data| is less than kMaxSignatureLength, encodes the +// |input| to |output| and returns true. Otherwise, returns false. +NET_EXPORT_PRIVATE bool EncodeDigitallySigned(const DigitallySigned& input, + std::string* output); + +// Reads and decodes a DigitallySigned object from |input|. +// The bytes read from |input| are discarded (i.e. |input|'s prefix removed) +// Returns true and fills |output| if all fields can be read, false otherwise. +NET_EXPORT_PRIVATE bool DecodeDigitallySigned(base::StringPiece* input, + DigitallySigned* output); + +// Encodes the |input| LogEntry to |output|. Returns true if the entry size +// does not exceed allowed size in RFC6962, false otherwise. +NET_EXPORT_PRIVATE bool EncodeLogEntry(const LogEntry& input, + std::string* output); + +// Encodes the data signed by a Signed Certificate Timestamp (SCT) into +// |output|. The signature included in the SCT is then verified over these +// bytes. +// |timestamp| timestamp from the SCT. +// |serialized_log_entry| the log entry signed by the SCT. +// |extensions| CT extensions. +// Returns true if the extensions' length does not exceed +// kMaxExtensionsLength, false otherwise. +NET_EXPORT_PRIVATE bool EncodeV1SCTSignedData( + const base::Time& timestamp, + const std::string& serialized_log_entry, + const std::string& extensions, + std::string* output); + +// Decode a list of Signed Certificate Timestamps +// (SignedCertificateTimestampList as defined in RFC6962): from a single +// string in |input| to a vector of individually-encoded SCTs |output|. +// This list is typically obtained from the CT extension in a certificate. +// Returns true if the list could be read and decoded successfully, false +// otherwise (note that the validity of each individual SCT should be checked +// separately). +NET_EXPORT_PRIVATE bool DecodeSCTList(base::StringPiece* input, + std::vector<base::StringPiece>* output); + +// Decodes a single SCT from |input| to |output|. +// Returns true if all fields in the SCT could be read and decoded, false +// otherwise. +NET_EXPORT_PRIVATE bool DecodeSignedCertificateTimestamp( + base::StringPiece* input, + ct::SignedCertificateTimestamp* output); + +} // namespace ct + +} // namespace net + +#endif // NET_CERT_CT_SERIALIZATION_H_ diff --git a/net/cert/ct_serialization_unittest.cc b/net/cert/ct_serialization_unittest.cc new file mode 100644 index 0000000..9692fb9 --- /dev/null +++ b/net/cert/ct_serialization_unittest.cc @@ -0,0 +1,170 @@ +// Copyright 2013 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/ct_serialization.h" + +#include <string> + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "net/base/net_log.h" +#include "net/base/test_completion_callback.h" +#include "net/base/test_data_directory.h" +#include "net/cert/x509_certificate.h" +#include "net/test/cert_test_util.h" +#include "net/test/ct_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +class CtSerializationTest : public ::testing::Test { + public: + virtual void SetUp() OVERRIDE { + test_digitally_signed_ = ct::GetTestDigitallySigned(); + } + + protected: + std::string test_digitally_signed_; +}; + +TEST_F(CtSerializationTest, DecodesDigitallySigned) { + base::StringPiece digitally_signed(test_digitally_signed_); + ct::DigitallySigned parsed; + + ASSERT_TRUE(ct::DecodeDigitallySigned(&digitally_signed, &parsed)); + EXPECT_EQ( + ct::DigitallySigned::HASH_ALGO_SHA256, + parsed.hash_algorithm); + + EXPECT_EQ( + ct::DigitallySigned::SIG_ALGO_ECDSA, + parsed.signature_algorithm); + + // The encoded data contains the signature itself from the 4th byte. + // The first bytes are: + // 1 byte of hash algorithm + // 1 byte of signature algorithm + // 2 bytes - prefix containing length of the signature data. + EXPECT_EQ( + test_digitally_signed_.substr(4), + parsed.signature_data); +} + + +TEST_F(CtSerializationTest, FailsToDecodePartialDigitallySigned) { + base::StringPiece digitally_signed(test_digitally_signed_); + base::StringPiece partial_digitally_signed( + digitally_signed.substr(0, test_digitally_signed_.size() - 5)); + ct::DigitallySigned parsed; + + ASSERT_FALSE(ct::DecodeDigitallySigned(&partial_digitally_signed, &parsed)); +} + + +TEST_F(CtSerializationTest, EncodesDigitallySigned) { + ct::DigitallySigned digitally_signed; + digitally_signed.hash_algorithm = ct::DigitallySigned::HASH_ALGO_SHA256; + digitally_signed.signature_algorithm = ct::DigitallySigned::SIG_ALGO_ECDSA; + digitally_signed.signature_data = test_digitally_signed_.substr(4); + + std::string encoded; + + ASSERT_TRUE(ct::EncodeDigitallySigned(digitally_signed, &encoded)); + EXPECT_EQ(test_digitally_signed_, encoded); +} + + +TEST_F(CtSerializationTest, EncodesLogEntryForX509Cert) { + ct::LogEntry entry; + GetX509CertLogEntry(&entry); + + std::string encoded; + ASSERT_TRUE(ct::EncodeLogEntry(entry, &encoded)); + EXPECT_EQ((718U + 5U), encoded.size()); + // First two bytes are log entry type. Next, length: + // Length is 718 which is 512 + 206, which is 0x2ce + std::string expected_prefix("\0\0\0\x2\xCE", 5); + // Note we use std::string comparison rather than ASSERT_STREQ due + // to null characters in the buffer. + EXPECT_EQ(expected_prefix, encoded.substr(0, 5)); +} + +TEST_F(CtSerializationTest, EncodesV1SCTSignedData) { + base::Time timestamp = base::Time::UnixEpoch() + + base::TimeDelta::FromMilliseconds(1348589665525); + std::string dummy_entry("abc"); + std::string empty_extensions(""); + // For now, no known failure cases. + std::string encoded; + ASSERT_TRUE(ct::EncodeV1SCTSignedData( + timestamp, + dummy_entry, + empty_extensions, + &encoded)); + EXPECT_EQ((size_t) 15, encoded.size()); + // Byte 0 is version, byte 1 is signature type + // Bytes 2-10 are timestamp + // Bytes 11-14 are the log signature + // Byte 15 is the empty extension + //EXPECT_EQ(0, timestamp.ToTimeT()); + std::string expected_buffer( + "\x0\x0\x0\x0\x1\x39\xFE\x35\x3C\xF5\x61\x62\x63\x0\x0", 15); + EXPECT_EQ(expected_buffer, encoded); +} + +TEST_F(CtSerializationTest, DecodesSCTList) { + // Two items in the list: "abc", "def" + base::StringPiece encoded("\x0\xa\x0\x3\x61\x62\x63\x0\x3\x64\x65\x66", 12); + std::vector<base::StringPiece> decoded; + + ASSERT_TRUE(ct::DecodeSCTList(&encoded, &decoded)); + ASSERT_STREQ("abc", decoded[0].data()); + ASSERT_STREQ("def", decoded[1].data()); +} + +TEST_F(CtSerializationTest, FailsDecodingInvalidSCTList) { + // A list with one item that's too short + base::StringPiece encoded("\x0\xa\x0\x3\x61\x62\x63\x0\x5\x64\x65\x66", 12); + std::vector<base::StringPiece> decoded; + + ASSERT_FALSE(ct::DecodeSCTList(&encoded, &decoded)); +} + +TEST_F(CtSerializationTest, DecodesSignedCertificateTimestamp) { + std::string encoded_test_sct(ct::GetTestSignedCertificateTimestamp()); + base::StringPiece encoded_sct(encoded_test_sct); + + ct::SignedCertificateTimestamp sct; + ASSERT_TRUE(ct::DecodeSignedCertificateTimestamp(&encoded_sct, &sct)); + EXPECT_EQ(0, sct.version); + std::string expected_log_key( + "\xdf\x1c\x2e\xc1\x15\x00\x94\x52\x47\xa9\x61\x68\x32\x5d\xdc\x5c\x79\x59" + "\xe8\xf7\xc6\xd3\x88\xfc\x00\x2e\x0b\xbd\x3f\x74\xd7\x64", + 32); + EXPECT_EQ(expected_log_key, sct.log_id); + base::Time expected_time = base::Time::UnixEpoch() + + base::TimeDelta::FromMilliseconds(1365181456089); + EXPECT_EQ(expected_time, sct.timestamp); + // Subtracting 4 bytes for signature data (hash & sig algs), + // actual signature data should be 71 bytes. + EXPECT_EQ((size_t) 71, sct.signature.signature_data.size()); + EXPECT_EQ(std::string(""), sct.extensions); +} + +TEST_F(CtSerializationTest, FailsDecodingInvalidSignedCertificateTimestamp) { + // Invalid version + base::StringPiece invalid_version_sct("\x2\x0", 2); + ct::SignedCertificateTimestamp sct; + + ASSERT_FALSE( + ct::DecodeSignedCertificateTimestamp(&invalid_version_sct, &sct)); + + // Valid version, invalid length (missing data) + base::StringPiece invalid_length_sct("\x0\xa\xb\xc", 4); + ASSERT_FALSE( + ct::DecodeSignedCertificateTimestamp(&invalid_length_sct, &sct)); +} + +} // namespace net + diff --git a/net/cert/signed_certificate_timestamp.cc b/net/cert/signed_certificate_timestamp.cc new file mode 100644 index 0000000..0be511d --- /dev/null +++ b/net/cert/signed_certificate_timestamp.cc @@ -0,0 +1,31 @@ +// Copyright 2013 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/signed_certificate_timestamp.h" + +namespace net { + +namespace ct { + +SignedCertificateTimestamp::SignedCertificateTimestamp() {} + +SignedCertificateTimestamp::~SignedCertificateTimestamp() {} + +LogEntry::LogEntry() {} + +LogEntry::~LogEntry() {} + +void LogEntry::Reset() { + type = LogEntry::LOG_ENTRY_TYPE_X509; + leaf_certificate.clear(); + tbs_certificate.clear(); +} + +DigitallySigned::DigitallySigned() {} + +DigitallySigned::~DigitallySigned() {} + +} // namespace ct + +} // namespace net diff --git a/net/cert/signed_certificate_timestamp.h b/net/cert/signed_certificate_timestamp.h new file mode 100644 index 0000000..6ed225c --- /dev/null +++ b/net/cert/signed_certificate_timestamp.h @@ -0,0 +1,102 @@ +// Copyright 2013 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. + +#ifndef NET_CERT_SIGNED_CERTIFICATE_TIMESTAMP_H_ +#define NET_CERT_SIGNED_CERTIFICATE_TIMESTAMP_H_ + +#include <string> +#include <vector> + +#include "base/time/time.h" +#include "net/base/hash_value.h" +#include "net/base/net_export.h" + +namespace net { + +// Structures related to Certificate Transparency (RFC6962). +namespace ct { + +// LogEntry struct in RFC 6962, Section 3.1 +struct NET_EXPORT LogEntry { + // LogEntryType enum in RFC 6962, Section 3.1 + enum Type { + LOG_ENTRY_TYPE_X509 = 0, + LOG_ENTRY_TYPE_PRECERT = 1 + }; + + LogEntry(); + ~LogEntry(); + void Reset(); + + Type type; + + // Set if type == LOG_ENTRY_TYPE_X509 + std::string leaf_certificate; + + // Set if type == LOG_ENTRY_TYPE_PRECERT + SHA256HashValue issuer_key_hash; + std::string tbs_certificate; +}; + +// Helper structure to represent Digitally Signed data, as described in +// Sections 4.7 and 7.4.1.4.1 of RFC 5246. +struct NET_EXPORT_PRIVATE DigitallySigned { + enum HashAlgorithm { + HASH_ALGO_NONE = 0, + HASH_ALGO_MD5 = 1, + HASH_ALGO_SHA1 = 2, + HASH_ALGO_SHA224 = 3, + HASH_ALGO_SHA256 = 4, + HASH_ALGO_SHA384 = 5, + HASH_ALGO_SHA512 = 6, + }; + + enum SignatureAlgorithm { + SIG_ALGO_ANONYMOUS = 0, + SIG_ALGO_RSA = 1, + SIG_ALGO_DSA = 2, + SIG_ALGO_ECDSA = 3 + }; + + DigitallySigned(); + ~DigitallySigned(); + + HashAlgorithm hash_algorithm; + SignatureAlgorithm signature_algorithm; + // 'signature' field. + std::string signature_data; +}; + +// SignedCertificateTimestamp struct in RFC 6962, Section 3.2. +struct NET_EXPORT SignedCertificateTimestamp { + // Version enum in RFC 6962, Section 3.2. + enum Version { + SCT_VERSION_1 = 0, + }; + + // Source of the SCT - supplementary, not defined in CT RFC. + enum Origin { + SCT_EMBEDDED = 0, + SCT_FROM_TLS_HANDSHAKE = 1, + SCT_FROM_OCSP_RESPONSE = 2, + }; + + SignedCertificateTimestamp(); + ~SignedCertificateTimestamp(); + + Version version; + std::string log_id; + base::Time timestamp; + std::string extensions; + DigitallySigned signature; + // The origin should not participate in equality checks + // as the same SCT can be provided from multiple sources. + Origin origin; +}; + +} // namespace ct + +} // namespace net + +#endif // NET_CERT_SIGNED_CERTIFICATE_TIMESTAMP_H_ diff --git a/net/net.gyp b/net/net.gyp index 9d511d1..3729a55 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -270,6 +270,8 @@ 'cert/cert_verify_result.h', 'cert/crl_set.cc', 'cert/crl_set.h', + 'cert/ct_serialization.cc', + 'cert/ct_serialization.h', 'cert/ev_root_ca_metadata.cc', 'cert/ev_root_ca_metadata.h', 'cert/jwk_serializer_nss.cc', @@ -281,14 +283,16 @@ 'cert/nss_cert_database.h', 'cert/pem_tokenizer.cc', 'cert/pem_tokenizer.h', + 'cert/signed_certificate_timestamp.cc', + 'cert/signed_certificate_timestamp.h', 'cert/single_request_cert_verifier.cc', 'cert/single_request_cert_verifier.h', 'cert/test_root_certs.cc', 'cert/test_root_certs.h', + 'cert/test_root_certs_android.cc', 'cert/test_root_certs_mac.cc', 'cert/test_root_certs_nss.cc', 'cert/test_root_certs_openssl.cc', - 'cert/test_root_certs_android.cc', 'cert/test_root_certs_win.cc', 'cert/x509_cert_types.cc', 'cert/x509_cert_types.h', @@ -1575,6 +1579,7 @@ 'base/url_util_unittest.cc', 'cert/cert_verify_proc_unittest.cc', 'cert/crl_set_unittest.cc', + 'cert/ct_serialization_unittest.cc', 'cert/ev_root_ca_metadata_unittest.cc', 'cert/jwk_serializer_unittest.cc', 'cert/multi_threaded_cert_verifier_unittest.cc', @@ -2311,6 +2316,8 @@ 'socket/socket_test_util.h', 'test/cert_test_util.cc', 'test/cert_test_util.h', + 'test/ct_test_util.cc', + 'test/ct_test_util.h', 'test/embedded_test_server/embedded_test_server.cc', 'test/embedded_test_server/embedded_test_server.h', 'test/embedded_test_server/http_connection.cc', diff --git a/net/test/ct_test_util.cc b/net/test/ct_test_util.cc new file mode 100644 index 0000000..cd014e4 --- /dev/null +++ b/net/test/ct_test_util.cc @@ -0,0 +1,112 @@ +// Copyright 2013 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/test/ct_test_util.h" + +#include <string> +#include <vector> + +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" +#include "base/strings/string_util.h" +#include "net/cert/ct_serialization.h" +#include "net/cert/signed_certificate_timestamp.h" +#include "net/cert/x509_certificate.h" + +namespace net { + +namespace ct { + +namespace { + +std::string HexToBytes(const char* hex_data) { + std::vector<uint8> output; + std::string result; + if (base::HexStringToBytes(hex_data, &output)) + result.assign(reinterpret_cast<const char*>(&output[0]), output.size()); + return result; +} + +// The following test vectors are from +// http://code.google.com/p/certificate-transparency + +const char kDefaultDerCert[] = + "308202ca30820233a003020102020106300d06092a864886f70d01010505003055310b3009" + "06035504061302474231243022060355040a131b4365727469666963617465205472616e73" + "706172656e6379204341310e300c0603550408130557616c65733110300e06035504071307" + "4572772057656e301e170d3132303630313030303030305a170d3232303630313030303030" + "305a3052310b30090603550406130247423121301f060355040a1318436572746966696361" + "7465205472616e73706172656e6379310e300c0603550408130557616c65733110300e0603" + "55040713074572772057656e30819f300d06092a864886f70d010101050003818d00308189" + "02818100b1fa37936111f8792da2081c3fe41925008531dc7f2c657bd9e1de4704160b4c9f" + "19d54ada4470404c1c51341b8f1f7538dddd28d9aca48369fc5646ddcc7617f8168aae5b41" + "d43331fca2dadfc804d57208949061f9eef902ca47ce88c644e000f06eeeccabdc9dd2f68a" + "22ccb09dc76e0dbc73527765b1a37a8c676253dcc10203010001a381ac3081a9301d060355" + "1d0e041604146a0d982a3b62c44b6d2ef4e9bb7a01aa9cb798e2307d0603551d2304763074" + "80145f9d880dc873e654d4f80dd8e6b0c124b447c355a159a4573055310b30090603550406" + "1302474231243022060355040a131b4365727469666963617465205472616e73706172656e" + "6379204341310e300c0603550408130557616c65733110300e060355040713074572772057" + "656e82010030090603551d1304023000300d06092a864886f70d010105050003818100171c" + "d84aac414a9a030f22aac8f688b081b2709b848b4e5511406cd707fed028597a9faefc2eee" + "2978d633aaac14ed3235197da87e0f71b8875f1ac9e78b281749ddedd007e3ecf50645f8cb" + "f667256cd6a1647b5e13203bb8582de7d6696f656d1c60b95f456b7fcf338571908f1c6972" + "7d24c4fccd249295795814d1dac0e6"; + +const char kDefaultKeyHash[] = + "2518ce9dcf869f18562d21cf7d040cbacc75371f019f8bea8cbe2f5f6619472d"; + +const char kDefaultDerTbsCert[] = + "30820233a003020102020107300d06092a864886f70d01010505003055310b300906035504" + "061302474231243022060355040a131b4365727469666963617465205472616e7370617265" + "6e6379204341310e300c0603550408130557616c65733110300e0603550407130745727720" + "57656e301e170d3132303630313030303030305a170d3232303630313030303030305a3052" + "310b30090603550406130247423121301f060355040a131843657274696669636174652054" + "72616e73706172656e6379310e300c0603550408130557616c65733110300e060355040713" + "074572772057656e30819f300d06092a864886f70d010101050003818d0030818902818100" + "bed8893cc8f177efc548df4961443f999aeda90471992f818bf8b61d0df19d6eec3d596c9b" + "43e60033a501c8cffcc438f49f5edb3662aaaecf180e7c9b59fc4bd465c18c406b3b70cdde" + "52d5dec42aaef913c2173592c76130f2399de6ccd6e75e04ccea7d7e4bdf4bacb16b5fe697" + "2974bca8bcb3e8468dec941e945fdf98310203010001a381ac3081a9301d0603551d0e0416" + "0414a4998f6b0abefd0e549bd56f221da976d0ce57d6307d0603551d230476307480143633" + "1299dbdc389d1cccfe31c08b8932501a8f7ca159a4573055310b3009060355040613024742" + "31243022060355040a131b4365727469666963617465205472616e73706172656e63792043" + "41310e300c0603550408130557616c65733110300e060355040713074572772057656e8201" + "0030090603551d1304023000"; + +const char kTestDigitallySigned[] = + "0403004730450220606e10ae5c2d5a1b0aed49dc4937f48de71a4e9784e9c208dfbfe9ef53" + "6cf7f2022100beb29c72d7d06d61d06bdb38a069469aa86fe12e18bb7cc45689a2c0187ef5" + "a5"; + +const char kTestSignedCertificateTimestamp[] = + "00df1c2ec11500945247a96168325ddc5c7959e8f7c6d388fc002e0bbd3f74d7640000013d" + "db27ded900000403004730450220606e10ae5c2d5a1b0aed49dc4937f48de71a4e9784e9c2" + "08dfbfe9ef536cf7f2022100beb29c72d7d06d61d06bdb38a069469aa86fe12e18bb7cc456" + "89a2c0187ef5a5"; + +} // namespace + +void GetX509CertLogEntry(LogEntry* entry) { + entry->type = ct::LogEntry::LOG_ENTRY_TYPE_X509; + entry->leaf_certificate = HexToBytes(kDefaultDerCert); +} + +void GetPrecertLogEntry(LogEntry* entry) { + entry->type = ct::LogEntry::LOG_ENTRY_TYPE_PRECERT; + std::string issuer_hash(HexToBytes(kDefaultKeyHash)); + memcpy(entry->issuer_key_hash.data, issuer_hash.data(), issuer_hash.size()); + entry->tbs_certificate = HexToBytes(kDefaultDerTbsCert); +} + +std::string GetTestDigitallySigned() { + return HexToBytes(kTestDigitallySigned); +} + +std::string GetTestSignedCertificateTimestamp() { + return HexToBytes(kTestSignedCertificateTimestamp); +} + +} // namespace ct + +} // namespace net diff --git a/net/test/ct_test_util.h b/net/test/ct_test_util.h new file mode 100644 index 0000000..79881ce --- /dev/null +++ b/net/test/ct_test_util.h @@ -0,0 +1,35 @@ +// Copyright 2013 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. + +#ifndef NET_CERT_CT_TEST_UTIL_H_ +#define NET_CERT_CT_TEST_UTIL_H_ + +#include <string> + +namespace net { + +class X509Certificate; + +namespace ct { + +struct LogEntry; +struct SignedCertificateTimestamp; + +// Fills |entry| with test data for an X.509 entry. +void GetX509CertLogEntry(LogEntry* entry); + +// Fills |entry| with test data for a Precertificate entry. +void GetPrecertLogEntry(LogEntry* entry); + +// Returns the binary representation of a test DigitallySigned +std::string GetTestDigitallySigned(); + +// Returns the binary representation of a test serialized SCT. +std::string GetTestSignedCertificateTimestamp(); + +} // namespace ct + +} // namespace net + +#endif // NET_CERT_CT_TEST_UTIL_H_ |