summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--net/base/pem_tokenizer.cc95
-rw-r--r--net/base/pem_tokenizer.h79
-rw-r--r--net/base/pem_tokenizer_unittest.cc169
-rw-r--r--net/base/x509_certificate.cc89
-rw-r--r--net/base/x509_certificate.h38
-rw-r--r--net/base/x509_certificate_mac.cc80
-rw-r--r--net/base/x509_certificate_nss.cc71
-rw-r--r--net/base/x509_certificate_unittest.cc162
-rw-r--r--net/base/x509_certificate_win.cc69
-rw-r--r--net/data/ssl/certificates/google.binary.p7bbin0 -> 1661 bytes
-rw-r--r--net/data/ssl/certificates/google.chain.pem38
-rw-r--r--net/data/ssl/certificates/google.pem_cert.p7b37
-rw-r--r--net/data/ssl/certificates/google.pem_pkcs7.p7b37
-rw-r--r--net/data/ssl/certificates/google.single.derbin0 -> 805 bytes
-rw-r--r--net/data/ssl/certificates/google.single.pem19
-rw-r--r--net/data/ssl/certificates/thawte.single.pem19
-rw-r--r--net/net.gyp3
17 files changed, 986 insertions, 19 deletions
diff --git a/net/base/pem_tokenizer.cc b/net/base/pem_tokenizer.cc
new file mode 100644
index 0000000..0abe5db
--- /dev/null
+++ b/net/base/pem_tokenizer.cc
@@ -0,0 +1,95 @@
+// Copyright (c) 2010 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/base/pem_tokenizer.h"
+
+#include "base/base64.h"
+#include "base/string_util.h"
+
+namespace {
+
+const char kPEMSearchBlock[] = "-----BEGIN ";
+const char kPEMBeginBlock[] = "-----BEGIN %s-----";
+const char kPEMEndBlock[] = "-----END %s-----";
+
+} // namespace
+
+namespace net {
+
+using base::StringPiece;
+
+PEMTokenizer::PEMTokenizer(
+ const StringPiece& str,
+ const std::vector<std::string>& allowed_block_types) {
+ Init(str, allowed_block_types);
+}
+
+bool PEMTokenizer::GetNext() {
+ while (pos_ != StringPiece::npos) {
+ // Scan for the beginning of the next PEM encoded block.
+ pos_ = str_.find(kPEMSearchBlock, pos_);
+ if (pos_ == StringPiece::npos)
+ return false; // No more PEM blocks
+
+ std::vector<PEMType>::const_iterator it;
+ // Check to see if it is of an acceptable block type.
+ for (it = block_types_.begin(); it != block_types_.end(); ++it) {
+ if (!str_.substr(pos_).starts_with(it->header))
+ continue;
+
+ // Look for a footer matching the header. If none is found, then all
+ // data following this point is invalid and should not be parsed.
+ StringPiece::size_type footer_pos = str_.find(it->footer, pos_);
+ if (footer_pos == StringPiece::npos) {
+ pos_ = StringPiece::npos;
+ return false;
+ }
+
+ // Chop off the header and footer and parse the data in between.
+ StringPiece::size_type data_begin = pos_ + it->header.size();
+ pos_ = footer_pos + it->footer.size();
+ block_type_ = it->type;
+
+ StringPiece encoded = str_.substr(data_begin,
+ footer_pos - data_begin);
+ if (!base::Base64Decode(CollapseWhitespaceASCII(encoded.as_string(),
+ true), &data_)) {
+ // The most likely cause for a decode failure is a datatype that
+ // includes PEM headers, which are not supported.
+ break;
+ }
+
+ return true;
+ }
+
+ // If the block did not match any acceptable type, move past it and
+ // continue the search. Otherwise, |pos_| has been updated to the most
+ // appropriate search position to continue searching from and should not
+ // be adjusted.
+ if (it == block_types_.end())
+ pos_ += sizeof(kPEMSearchBlock);
+ }
+
+ return false;
+}
+
+void PEMTokenizer::Init(
+ const StringPiece& str,
+ const std::vector<std::string>& allowed_block_types) {
+ str_ = str;
+ pos_ = 0;
+
+ // Construct PEM header/footer strings for all the accepted types, to
+ // reduce parsing later.
+ for (std::vector<std::string>::const_iterator it =
+ allowed_block_types.begin(); it != allowed_block_types.end(); ++it) {
+ PEMType allowed_type;
+ allowed_type.type = *it;
+ allowed_type.header = StringPrintf(kPEMBeginBlock, it->c_str());
+ allowed_type.footer = StringPrintf(kPEMEndBlock, it->c_str());
+ block_types_.push_back(allowed_type);
+ }
+}
+
+} // namespace net
diff --git a/net/base/pem_tokenizer.h b/net/base/pem_tokenizer.h
new file mode 100644
index 0000000..eebba2d
--- /dev/null
+++ b/net/base/pem_tokenizer.h
@@ -0,0 +1,79 @@
+// Copyright (c) 2010 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_BASE_PEM_TOKENIZER_H_
+#define NET_BASE_PEM_TOKENIZER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/string_piece.h"
+
+namespace net {
+
+// PEMTokenizer is a utility class for the parsing of data encapsulated
+// using RFC 1421, Privacy Enhancement for Internet Electronic Mail. It
+// does not implement the full specification, most notably it does not
+// support the Encapsulated Header Portion described in Section 4.4.
+class PEMTokenizer {
+ public:
+ // Create a new PEMTokenizer that iterates through |str| searching for
+ // instances of PEM encoded blocks that are of the |allowed_block_types|.
+ // |str| must remain valid for the duration of the PEMTokenizer.
+ PEMTokenizer(const base::StringPiece& str,
+ const std::vector<std::string>& allowed_block_types);
+
+ // Attempts to decode the next PEM block in the string. Returns false if no
+ // PEM blocks can be decoded. The decoded PEM block will be available via
+ // data().
+ bool GetNext();
+
+ // Returns the PEM block type (eg: CERTIFICATE) of the last successfully
+ // decoded PEM block.
+ // GetNext() must have returned true before calling this method.
+ const std::string& block_type() const { return block_type_; }
+
+ // Returns the raw, Base64-decoded data of the last successfully decoded
+ // PEM block.
+ // GetNext() must have returned true before calling this method.
+ const std::string& data() const { return data_; }
+
+ private:
+ void Init(const base::StringPiece& str,
+ const std::vector<std::string>& allowed_block_types);
+
+ // A simple cache of the allowed PEM header and footer for a given PEM
+ // block type, so that it is only computed once.
+ struct PEMType {
+ std::string type;
+ std::string header;
+ std::string footer;
+ };
+
+ // The string to search, which must remain valid for as long as this class
+ // is around.
+ base::StringPiece str_;
+
+ // The current position within |str_| that searching should begin from,
+ // or StringPiece::npos if iteration is complete
+ base::StringPiece::size_type pos_;
+
+ // The type of data that was encoded, as indicated in the PEM
+ // Pre-Encapsulation Boundary (eg: CERTIFICATE, PKCS7, or
+ // PRIVACY-ENHANCED MESSAGE).
+ std::string block_type_;
+
+ // The types of PEM blocks that are allowed. PEM blocks that are not of
+ // one of these types will be skipped.
+ std::vector<PEMType> block_types_;
+
+ // The raw (Base64-decoded) data of the last successfully decoded block.
+ std::string data_;
+
+ DISALLOW_COPY_AND_ASSIGN(PEMTokenizer);
+};
+
+} // namespace net
+
+#endif // NET_BASE_PEM_TOKENIZER_H_
diff --git a/net/base/pem_tokenizer_unittest.cc b/net/base/pem_tokenizer_unittest.cc
new file mode 100644
index 0000000..af2446c
--- /dev/null
+++ b/net/base/pem_tokenizer_unittest.cc
@@ -0,0 +1,169 @@
+// Copyright (c) 2010 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/base/pem_tokenizer.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+TEST(PEMTokenizerTest, BasicParsing) {
+ const char data[] =
+ "-----BEGIN EXPECTED-BLOCK-----\n"
+ "TWF0Y2hlc0FjY2VwdGVkQmxvY2tUeXBl\n"
+ "-----END EXPECTED-BLOCK-----\n";
+ base::StringPiece string_piece(data);
+ std::vector<std::string> accepted_types;
+ accepted_types.push_back("EXPECTED-BLOCK");
+
+ PEMTokenizer tokenizer(string_piece, accepted_types);
+ EXPECT_TRUE(tokenizer.GetNext());
+
+ EXPECT_EQ("EXPECTED-BLOCK", tokenizer.block_type());
+ EXPECT_EQ("MatchesAcceptedBlockType", tokenizer.data());
+
+ EXPECT_FALSE(tokenizer.GetNext());
+}
+
+TEST(PEMTokenizerTest, CarriageReturnLineFeeds) {
+ const char data[] =
+ "-----BEGIN EXPECTED-BLOCK-----\r\n"
+ "TWF0Y2hlc0FjY2VwdGVkQmxvY2tUeXBl\r\n"
+ "-----END EXPECTED-BLOCK-----\r\n";
+ base::StringPiece string_piece(data);
+ std::vector<std::string> accepted_types;
+ accepted_types.push_back("EXPECTED-BLOCK");
+
+ PEMTokenizer tokenizer(string_piece, accepted_types);
+ EXPECT_TRUE(tokenizer.GetNext());
+
+ EXPECT_EQ("EXPECTED-BLOCK", tokenizer.block_type());
+ EXPECT_EQ("MatchesAcceptedBlockType", tokenizer.data());
+
+ EXPECT_FALSE(tokenizer.GetNext());
+}
+
+TEST(PEMTokenizerTest, NoAcceptedBlockTypes) {
+ const char data[] =
+ "-----BEGIN UNEXPECTED-BLOCK-----\n"
+ "SWdub3Jlc1JlamVjdGVkQmxvY2tUeXBl\n"
+ "-----END UNEXPECTED-BLOCK-----\n";
+ base::StringPiece string_piece(data);
+ std::vector<std::string> accepted_types;
+ accepted_types.push_back("EXPECTED-BLOCK");
+
+ PEMTokenizer tokenizer(string_piece, accepted_types);
+ EXPECT_FALSE(tokenizer.GetNext());
+}
+
+TEST(PEMTokenizerTest, MultipleAcceptedBlockTypes) {
+ const char data[] =
+ "-----BEGIN BLOCK-ONE-----\n"
+ "RW5jb2RlZERhdGFPbmU=\n"
+ "-----END BLOCK-ONE-----\n"
+ "-----BEGIN BLOCK-TWO-----\n"
+ "RW5jb2RlZERhdGFUd28=\n"
+ "-----END BLOCK-TWO-----\n";
+ base::StringPiece string_piece(data);
+ std::vector<std::string> accepted_types;
+ accepted_types.push_back("BLOCK-ONE");
+ accepted_types.push_back("BLOCK-TWO");
+
+ PEMTokenizer tokenizer(string_piece, accepted_types);
+ EXPECT_TRUE(tokenizer.GetNext());
+
+ EXPECT_EQ("BLOCK-ONE", tokenizer.block_type());
+ EXPECT_EQ("EncodedDataOne", tokenizer.data());
+
+ EXPECT_TRUE(tokenizer.GetNext());
+
+ EXPECT_EQ("BLOCK-TWO", tokenizer.block_type());
+ EXPECT_EQ("EncodedDataTwo", tokenizer.data());
+
+ EXPECT_FALSE(tokenizer.GetNext());
+}
+
+TEST(PEMTokenizerTest, MissingFooter) {
+ const char data[] =
+ "-----BEGIN MISSING-FOOTER-----\n"
+ "RW5jb2RlZERhdGFPbmU=\n"
+ "-----END MISSING-FOOTER-----\n"
+ "-----BEGIN MISSING-FOOTER-----\n"
+ "RW5jb2RlZERhdGFUd28=\n";
+ base::StringPiece string_piece(data);
+ std::vector<std::string> accepted_types;
+ accepted_types.push_back("MISSING-FOOTER");
+
+ PEMTokenizer tokenizer(string_piece, accepted_types);
+ EXPECT_TRUE(tokenizer.GetNext());
+
+ EXPECT_EQ("MISSING-FOOTER", tokenizer.block_type());
+ EXPECT_EQ("EncodedDataOne", tokenizer.data());
+
+ EXPECT_FALSE(tokenizer.GetNext());
+}
+
+TEST(PEMTokenizerTest, NestedEncoding) {
+ const char data[] =
+ "-----BEGIN BLOCK-ONE-----\n"
+ "RW5jb2RlZERhdGFPbmU=\n"
+ "-----BEGIN BLOCK-TWO-----\n"
+ "RW5jb2RlZERhdGFUd28=\n"
+ "-----END BLOCK-TWO-----\n"
+ "-----END BLOCK-ONE-----\n"
+ "-----BEGIN BLOCK-ONE-----\n"
+ "RW5jb2RlZERhdGFUaHJlZQ==\n"
+ "-----END BLOCK-ONE-----\n";
+ base::StringPiece string_piece(data);
+ std::vector<std::string> accepted_types;
+ accepted_types.push_back("BLOCK-ONE");
+
+ PEMTokenizer tokenizer(string_piece, accepted_types);
+ EXPECT_TRUE(tokenizer.GetNext());
+
+ EXPECT_EQ("BLOCK-ONE", tokenizer.block_type());
+ EXPECT_EQ("EncodedDataThree", tokenizer.data());
+
+ EXPECT_FALSE(tokenizer.GetNext());
+}
+
+TEST(PEMTokenizerTest, EmptyAcceptedTypes) {
+ const char data[] =
+ "-----BEGIN BLOCK-ONE-----\n"
+ "RW5jb2RlZERhdGFPbmU=\n"
+ "-----END BLOCK-ONE-----\n";
+ base::StringPiece string_piece(data);
+ std::vector<std::string> accepted_types;
+
+ PEMTokenizer tokenizer(string_piece, accepted_types);
+ EXPECT_FALSE(tokenizer.GetNext());
+}
+
+TEST(PEMTokenizerTest, BlockWithHeader) {
+ const char data[] =
+ "-----BEGIN BLOCK-ONE-----\n"
+ "Header-One: Data data data\n"
+ "Header-Two: \n"
+ " continuation\n"
+ "Header-Three: Mix-And,Match\n"
+ "\n"
+ "RW5jb2RlZERhdGFPbmU=\n"
+ "-----END BLOCK-ONE-----\n"
+ "-----BEGIN BLOCK-ONE-----\n"
+ "RW5jb2RlZERhdGFUd28=\n"
+ "-----END BLOCK-ONE-----\n";
+ base::StringPiece string_piece(data);
+ std::vector<std::string> accepted_types;
+ accepted_types.push_back("BLOCK-ONE");
+
+ PEMTokenizer tokenizer(string_piece, accepted_types);
+ EXPECT_TRUE(tokenizer.GetNext());
+
+ EXPECT_EQ("BLOCK-ONE", tokenizer.block_type());
+ EXPECT_EQ("EncodedDataTwo", tokenizer.data());
+
+ EXPECT_FALSE(tokenizer.GetNext());
+}
+
+} // namespace net
diff --git a/net/base/x509_certificate.cc b/net/base/x509_certificate.cc
index f5b28a6..1230f27 100644
--- a/net/base/x509_certificate.cc
+++ b/net/base/x509_certificate.cc
@@ -15,7 +15,9 @@
#include "base/histogram.h"
#include "base/logging.h"
#include "base/singleton.h"
+#include "base/string_piece.h"
#include "base/time.h"
+#include "net/base/pem_tokenizer.h"
namespace net {
@@ -31,6 +33,18 @@ bool IsNullFingerprint(const SHA1Fingerprint& fingerprint) {
return true;
}
+// Indicates the order to use when trying to decode binary data, which is
+// based on (speculation) as to what will be most common -> least common
+const X509Certificate::Format kFormatDecodePriority[] = {
+ X509Certificate::FORMAT_DER,
+ X509Certificate::FORMAT_PKCS7
+};
+
+// The PEM block header used for DER certificates
+const char kCertificateHeader[] = "CERTIFICATE";
+// The PEM block header used for PKCS#7 data
+const char kPKCS7Header[] = "PKCS7";
+
} // namespace
// static
@@ -186,6 +200,81 @@ X509Certificate* X509Certificate::CreateFromBytes(const char* data,
return cert;
}
+CertificateList X509Certificate::CreateCertificateListFromBytes(
+ const char* data, int length, int format) {
+ OSCertHandles certificates;
+
+ // Try each of the formats, in order of parse preference, to see if |data|
+ // contains the binary representation of a Format.
+ for (size_t i = 0; certificates.empty() &&
+ i < arraysize(kFormatDecodePriority); ++i) {
+ if (format & kFormatDecodePriority[i])
+ certificates = CreateOSCertHandlesFromBytes(data, length,
+ kFormatDecodePriority[i]);
+ }
+
+ // No certs were read. Check to see if it is in a PEM-encoded form.
+ if (certificates.empty()) {
+ base::StringPiece data_string(data, length);
+ std::vector<std::string> pem_headers;
+
+ // To maintain compatibility with NSS/Firefox, CERTIFICATE is a universally
+ // valid PEM block header for any format.
+ pem_headers.push_back(kCertificateHeader);
+ if (format & FORMAT_PKCS7)
+ pem_headers.push_back(kPKCS7Header);
+
+ PEMTokenizer pem_tok(data_string, pem_headers);
+ while (pem_tok.GetNext()) {
+ std::string decoded(pem_tok.data());
+
+ OSCertHandle handle = NULL;
+ if (format & FORMAT_PEM)
+ handle = CreateOSCertHandleFromBytes(decoded.c_str(), decoded.size());
+ if (handle != NULL) {
+ // Parsed a DER encoded certificate. All PEM blocks that follow must
+ // also be DER encoded certificates wrapped inside of PEM blocks.
+ format = FORMAT_PEM;
+ certificates.push_back(handle);
+ continue;
+ }
+
+ // If the first block failed to parse as a DER certificate, and
+ // formats other than PEM are acceptable, check to see if the decoded
+ // data is one of the accepted formats.
+ if (format & ~FORMAT_PEM) {
+ for (size_t i = 0; certificates.empty() &&
+ i < arraysize(kFormatDecodePriority); ++i) {
+ if (format & kFormatDecodePriority[i]) {
+ certificates = CreateOSCertHandlesFromBytes(decoded.c_str(),
+ decoded.size(), kFormatDecodePriority[i]);
+ }
+ }
+ }
+
+ // Stop parsing after the first block for any format but a sequence of
+ // PEM-encoded DER certificates. The case of FORMAT_PEM is handled
+ // above, and continues processing until a certificate fails to parse.
+ break;
+ }
+ }
+
+ CertificateList results;
+ // No certificates parsed.
+ if (certificates.empty())
+ return results;
+
+ for (OSCertHandles::iterator it = certificates.begin();
+ it != certificates.end(); ++it) {
+ X509Certificate* result = CreateFromHandle(*it, SOURCE_LONE_CERT_IMPORT,
+ OSCertHandles());
+ results.push_back(scoped_refptr<X509Certificate>(result));
+ FreeOSCertHandle(*it);
+ }
+
+ return results;
+}
+
X509Certificate::X509Certificate(OSCertHandle cert_handle,
Source source,
const OSCertHandles& intermediates)
diff --git a/net/base/x509_certificate.h b/net/base/x509_certificate.h
index d6b3447..284d2fb 100644
--- a/net/base/x509_certificate.h
+++ b/net/base/x509_certificate.h
@@ -32,6 +32,8 @@ namespace net {
class CertVerifyResult;
+typedef std::vector<scoped_refptr<X509Certificate> > CertificateList;
+
// X509Certificate represents an X.509 certificate used by SSL.
class X509Certificate : public base::RefCountedThreadSafe<X509Certificate> {
public:
@@ -72,6 +74,27 @@ class X509Certificate : public base::RefCountedThreadSafe<X509Certificate> {
VERIFY_EV_CERT = 1 << 1,
};
+ enum Format {
+ // The data contains a single DER-encoded certificate, or a PEM-encoded
+ // DER certificate with the PEM encoding block name of "CERTIFICATE".
+ // Any subsequent blocks will be ignored.
+ FORMAT_DER = 1 << 0,
+
+ // The data contains a sequence of one or more PEM-encoded, DER
+ // certificates, with the PEM encoding block name of "CERTIFICATE".
+ // All PEM blocks will be parsed, until the first error is encountered.
+ FORMAT_PEM = 1 << 1,
+
+ // The data contains a PKCS#7 SignedData structure, whose certificates
+ // member is to be used to initialize the certificate and intermediates.
+ // The data my further be encoding using PEM, specifying block names of
+ // either "PKCS7" or "CERTIFICATE".
+ FORMAT_PKCS7 = 1 << 2,
+
+ // Automatically detect the format.
+ FORMAT_AUTO = FORMAT_DER | FORMAT_PEM | FORMAT_PKCS7,
+ };
+
// Create an X509Certificate from a handle to the certificate object in the
// underlying crypto library. |source| specifies where |cert_handle| comes
// from. Given two certificate handles for the same certificate, our
@@ -84,7 +107,7 @@ class X509Certificate : public base::RefCountedThreadSafe<X509Certificate> {
Source source,
const OSCertHandles& intermediates);
- // Create an X509Certificate from the BER-encoded representation.
+ // Create an X509Certificate from the DER-encoded representation.
// Returns NULL on failure.
//
// The returned pointer must be stored in a scoped_refptr<X509Certificate>.
@@ -99,6 +122,14 @@ class X509Certificate : public base::RefCountedThreadSafe<X509Certificate> {
static X509Certificate* CreateFromPickle(const Pickle& pickle,
void** pickle_iter);
+ // Parses all of the certificates possible from |data|. |format| is a
+ // bit-wise OR of Format, indicating the possible formats the
+ // certificates may have been serialized as. If an error occurs, an empty
+ // collection will be returned.
+ static CertificateList CreateCertificateListFromBytes(const char* data,
+ int length,
+ int format);
+
// Creates a X509Certificate from the ground up. Used by tests that simulate
// SSL connections.
X509Certificate(const std::string& subject, const std::string& issuer,
@@ -203,6 +234,11 @@ class X509Certificate : public base::RefCountedThreadSafe<X509Certificate> {
static OSCertHandle CreateOSCertHandleFromBytes(const char* data,
int length);
+ // Creates all possible OS certificate handles from |data| encoded in a
+ // specific |format|. Returns an empty collection on failure.
+ static OSCertHandles CreateOSCertHandlesFromBytes(
+ const char* data, int length, Format format);
+
// Duplicates (or adds a reference to) an OS certificate handle.
static OSCertHandle DupOSCertHandle(OSCertHandle cert_handle);
diff --git a/net/base/x509_certificate_mac.cc b/net/base/x509_certificate_mac.cc
index ed46adc..727fde9 100644
--- a/net/base/x509_certificate_mac.cc
+++ b/net/base/x509_certificate_mac.cc
@@ -8,9 +8,9 @@
#include <Security/Security.h>
#include <time.h>
-#include "base/scoped_cftyperef.h"
#include "base/logging.h"
#include "base/pickle.h"
+#include "base/scoped_cftyperef.h"
#include "base/sys_string_conversions.h"
#include "net/base/cert_status_flags.h"
#include "net/base/cert_verify_result.h"
@@ -372,6 +372,44 @@ bool ExtendedKeyUsageAllows(const CE_ExtendedKeyUsage* usage,
return false;
}
+// Parses |data| of length |length|, attempting to decode it as the specified
+// |format|. If |data| is in the specified format, any certificates contained
+// within are stored into |output|.
+void AddCertificatesFromBytes(const char* data, size_t length,
+ SecExternalFormat format,
+ X509Certificate::OSCertHandles* output) {
+ SecExternalFormat input_format = format;
+ scoped_cftyperef<CFDataRef> local_data(CFDataCreateWithBytesNoCopy(
+ kCFAllocatorDefault, reinterpret_cast<const UInt8*>(data),
+ length, kCFAllocatorNull));
+
+ CFArrayRef items = NULL;
+ OSStatus status = SecKeychainItemImport(local_data, NULL, &input_format,
+ NULL, 0, NULL, NULL, &items);
+ if (status) {
+ DLOG(WARNING) << status << " Unable to import items from data of length "
+ << length;
+ return;
+ }
+
+ scoped_cftyperef<CFArrayRef> scoped_items(items);
+ CFTypeID cert_type_id = SecCertificateGetTypeID();
+
+ for (CFIndex i = 0; i < CFArrayGetCount(items); ++i) {
+ SecKeychainItemRef item = reinterpret_cast<SecKeychainItemRef>(
+ const_cast<void*>(CFArrayGetValueAtIndex(items, i)));
+
+ // While inputFormat implies only certificates will be imported, if/when
+ // other formats (eg: PKCS#12) are supported, this may also include
+ // private keys or other items types, so filter appropriately.
+ if (CFGetTypeID(item) == cert_type_id) {
+ SecCertificateRef cert = reinterpret_cast<SecCertificateRef>(item);
+ CFRetain(cert);
+ output->push_back(cert);
+ }
+ }
+}
+
} // namespace
void X509Certificate::Initialize() {
@@ -669,15 +707,53 @@ X509Certificate::OSCertHandle X509Certificate::CreateOSCertHandleFromBytes(
OSCertHandle cert_handle = NULL;
OSStatus status = SecCertificateCreateFromData(&cert_data,
CSSM_CERT_X_509v3,
- CSSM_CERT_ENCODING_BER,
+ CSSM_CERT_ENCODING_DER,
&cert_handle);
if (status)
return NULL;
+ // SecCertificateCreateFromData() unfortunately will not return any
+ // errors, as long as simply all pointers are present. The actual decoding
+ // of the certificate does not happen until an API that requires a CDSA
+ // handle is called. While SecCertificateGetCLHandle is the most likely
+ // candidate, as it initializes the parsing, it does not check whether the
+ // parsing was successful. Instead, SecCertificateGetSubject is used
+ // (supported since 10.3), as a means to double-check that the parsed
+ // parsed certificate is valid.
+ const CSSM_X509_NAME* sanity_check = NULL;
+ status = SecCertificateGetSubject(cert_handle, &sanity_check);
+ if (status || !sanity_check) {
+ CFRelease(cert_handle);
+ return NULL;
+ }
+
return cert_handle;
}
// static
+X509Certificate::OSCertHandles X509Certificate::CreateOSCertHandlesFromBytes(
+ const char* data, int length, Format format) {
+ OSCertHandles results;
+
+ switch (format) {
+ case FORMAT_DER: {
+ OSCertHandle handle = CreateOSCertHandleFromBytes(data, length);
+ if (handle)
+ results.push_back(handle);
+ break;
+ }
+ case FORMAT_PKCS7:
+ AddCertificatesFromBytes(data, length, kSecFormatPKCS7, &results);
+ break;
+ default:
+ NOTREACHED() << "Certificate format " << format << " unimplemented";
+ break;
+ }
+
+ return results;
+}
+
+// static
X509Certificate::OSCertHandle X509Certificate::DupOSCertHandle(
OSCertHandle handle) {
if (!handle)
diff --git a/net/base/x509_certificate_nss.cc b/net/base/x509_certificate_nss.cc
index 8eb337f..dbc4d18 100644
--- a/net/base/x509_certificate_nss.cc
+++ b/net/base/x509_certificate_nss.cc
@@ -16,6 +16,7 @@
#include "base/logging.h"
#include "base/pickle.h"
+#include "base/scoped_ptr.h"
#include "base/time.h"
#include "base/nss_util.h"
#include "net/base/cert_status_flags.h"
@@ -571,6 +572,22 @@ bool CheckCertPolicies(X509Certificate::OSCertHandle cert_handle,
return false;
}
+SECStatus PR_CALLBACK
+CollectCertsCallback(void* arg, SECItem** certs, int num_certs) {
+ X509Certificate::OSCertHandles* results =
+ reinterpret_cast<X509Certificate::OSCertHandles*>(arg);
+
+ for (int i = 0; i < num_certs; ++i) {
+ X509Certificate::OSCertHandle handle =
+ X509Certificate::CreateOSCertHandleFromBytes(
+ reinterpret_cast<char*>(certs[i]->data), certs[i]->len);
+ if (handle)
+ results->push_back(handle);
+ }
+
+ return SECSuccess;
+}
+
} // namespace
void X509Certificate::Initialize() {
@@ -721,21 +738,59 @@ bool X509Certificate::VerifyEV() const {
// static
X509Certificate::OSCertHandle X509Certificate::CreateOSCertHandleFromBytes(
const char* data, int length) {
+ if (length < 0)
+ return NULL;
+
base::EnsureNSSInit();
if (!NSS_IsInitialized())
return NULL;
- // Make a copy of |data| since CERT_DecodeCertPackage might modify it.
- char* data_copy = new char[length];
- memcpy(data_copy, data, length);
+ SECItem der_cert;
+ der_cert.data = reinterpret_cast<unsigned char*>(const_cast<char*>(data));
+ der_cert.len = length;
+ der_cert.type = siDERCertBuffer;
// Parse into a certificate structure.
- CERTCertificate* cert = CERT_DecodeCertFromPackage(data_copy, length);
- delete [] data_copy;
- if (!cert)
- LOG(ERROR) << "Couldn't parse a certificate from " << length << " bytes";
- return cert;
+ return CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &der_cert, NULL,
+ PR_FALSE, PR_TRUE);
+}
+
+// static
+X509Certificate::OSCertHandles X509Certificate::CreateOSCertHandlesFromBytes(
+ const char* data, int length, Format format) {
+ OSCertHandles results;
+ if (length < 0)
+ return results;
+
+ base::EnsureNSSInit();
+
+ if (!NSS_IsInitialized())
+ return results;
+
+ switch (format) {
+ case FORMAT_DER: {
+ OSCertHandle handle = CreateOSCertHandleFromBytes(data, length);
+ if (handle)
+ results.push_back(handle);
+ break;
+ }
+ case FORMAT_PKCS7: {
+ // Make a copy since CERT_DecodeCertPackage may modify it
+ std::vector<char> data_copy(data, data + length);
+
+ SECStatus result = CERT_DecodeCertPackage(&data_copy[0],
+ length, CollectCertsCallback, &results);
+ if (result != SECSuccess)
+ results.clear();
+ break;
+ }
+ default:
+ NOTREACHED() << "Certificate format " << format << " unimplemented";
+ break;
+ }
+
+ return results;
}
// static
diff --git a/net/base/x509_certificate_unittest.cc b/net/base/x509_certificate_unittest.cc
index 63eec15..6becea0 100644
--- a/net/base/x509_certificate_unittest.cc
+++ b/net/base/x509_certificate_unittest.cc
@@ -76,6 +76,93 @@ unsigned char unosoft_hu_fingerprint[] = {
0x25, 0x66, 0xf2, 0xec, 0x8b, 0x0f, 0xbf, 0xd8
};
+// The fingerprint of the Google certificate used in the parsing tests,
+// which is newer than the one included in the x509_certificate_data.h
+unsigned char google_parse_fingerprint[] = {
+ 0x40, 0x50, 0x62, 0xe5, 0xbe, 0xfd, 0xe4, 0xaf, 0x97, 0xe9, 0x38, 0x2a,
+ 0xf1, 0x6c, 0xc8, 0x7c, 0x8f, 0xb7, 0xc4, 0xe2
+};
+
+// The fingerprint for the Thawte SGC certificate
+unsigned char thawte_parse_fingerprint[] = {
+ 0xec, 0x07, 0x10, 0x03, 0xd8, 0xf5, 0xa3, 0x7f, 0x42, 0xc4, 0x55, 0x7f,
+ 0x65, 0x6a, 0xae, 0x86, 0x65, 0xfa, 0x4b, 0x02
+};
+
+// Dec 18 00:00:00 2009 GMT
+const double kGoogleParseValidFrom = 1261094400;
+// Dec 18 23:59:59 2011 GMT
+const double kGoogleParseValidTo = 1324252799;
+
+struct CertificateFormatTestData {
+ const char* file_name;
+ X509Certificate::Format format;
+ unsigned char* chain_fingerprints[3];
+};
+
+const CertificateFormatTestData FormatTestData[] = {
+ // DER Parsing - single certificate, DER encoded
+ { "google.single.der", X509Certificate::FORMAT_DER,
+ { google_parse_fingerprint,
+ NULL, } },
+ // DER parsing - single certificate, PEM encoded
+ { "google.single.pem", X509Certificate::FORMAT_DER,
+ { google_parse_fingerprint,
+ NULL, } },
+ // PEM parsing - single certificate, PEM encoded with a PEB of
+ // "CERTIFICATE"
+ { "google.single.pem", X509Certificate::FORMAT_PEM,
+ { google_parse_fingerprint,
+ NULL, } },
+ // PEM parsing - sequence of certificates, PEM encoded with a PEB of
+ // "CERTIFICATE"
+ { "google.chain.pem", X509Certificate::FORMAT_PEM,
+ { google_parse_fingerprint,
+ thawte_parse_fingerprint,
+ NULL, } },
+ // PKCS#7 parsing - "degenerate" SignedData collection of certificates, DER
+ // encoding
+ { "google.binary.p7b", X509Certificate::FORMAT_PKCS7,
+ { google_parse_fingerprint,
+ thawte_parse_fingerprint,
+ NULL, } },
+ // PKCS#7 parsing - "degenerate" SignedData collection of certificates, PEM
+ // encoded with a PEM PEB of "CERTIFICATE"
+ { "google.pem_cert.p7b", X509Certificate::FORMAT_PKCS7,
+ { google_parse_fingerprint,
+ thawte_parse_fingerprint,
+ NULL, } },
+ // PKCS#7 parsing - "degenerate" SignedData collection of certificates, PEM
+ // encoded with a PEM PEB of "PKCS7"
+ { "google.pem_pkcs7.p7b", X509Certificate::FORMAT_PKCS7,
+ { google_parse_fingerprint,
+ thawte_parse_fingerprint,
+ NULL, } },
+ // All of the above, this time using auto-detection
+ { "google.single.der", X509Certificate::FORMAT_AUTO,
+ { google_parse_fingerprint,
+ NULL, } },
+ { "google.single.pem", X509Certificate::FORMAT_AUTO,
+ { google_parse_fingerprint,
+ NULL, } },
+ { "google.chain.pem", X509Certificate::FORMAT_AUTO,
+ { google_parse_fingerprint,
+ thawte_parse_fingerprint,
+ NULL, } },
+ { "google.binary.p7b", X509Certificate::FORMAT_AUTO,
+ { google_parse_fingerprint,
+ thawte_parse_fingerprint,
+ NULL, } },
+ { "google.pem_cert.p7b", X509Certificate::FORMAT_AUTO,
+ { google_parse_fingerprint,
+ thawte_parse_fingerprint,
+ NULL, } },
+ { "google.pem_pkcs7.p7b", X509Certificate::FORMAT_AUTO,
+ { google_parse_fingerprint,
+ thawte_parse_fingerprint,
+ NULL, } },
+};
+
// Returns a FilePath object representing the src/net/data/ssl/certificates
// directory in the source tree.
FilePath GetTestCertsDirectory() {
@@ -100,12 +187,22 @@ X509Certificate* ImportCertFromFile(const FilePath& certs_dir,
return X509Certificate::CreateFromBytes(cert_data.data(), cert_data.size());
}
-} // namespace
-
-TEST(X509CertificateTest, GoogleCertParsing) {
- scoped_refptr<X509Certificate> google_cert = X509Certificate::CreateFromBytes(
- reinterpret_cast<const char*>(google_der), sizeof(google_der));
+CertificateList CreateCertificateListFromFile(
+ const FilePath& certs_dir,
+ const std::string& cert_file,
+ int format) {
+ FilePath cert_path = certs_dir.AppendASCII(cert_file);
+ std::string cert_data;
+ if (!file_util::ReadFileToString(cert_path, &cert_data))
+ return CertificateList();
+ return X509Certificate::CreateCertificateListFromBytes(cert_data.data(),
+ cert_data.size(),
+ format);
+}
+void CheckGoogleCert(const scoped_refptr<X509Certificate>& google_cert,
+ unsigned char* expected_fingerprint,
+ double valid_from, double valid_to) {
ASSERT_NE(static_cast<X509Certificate*>(NULL), google_cert);
const CertPrincipal& subject = google_cert->subject();
@@ -132,14 +229,14 @@ TEST(X509CertificateTest, GoogleCertParsing) {
// Use DoubleT because its epoch is the same on all platforms
const Time& valid_start = google_cert->valid_start();
- EXPECT_EQ(1238192407, valid_start.ToDoubleT()); // Mar 27 22:20:07 2009 GMT
+ EXPECT_EQ(valid_from, valid_start.ToDoubleT());
const Time& valid_expiry = google_cert->valid_expiry();
- EXPECT_EQ(1269728407, valid_expiry.ToDoubleT()); // Mar 27 22:20:07 2010 GMT
+ EXPECT_EQ(valid_to, valid_expiry.ToDoubleT());
const SHA1Fingerprint& fingerprint = google_cert->fingerprint();
for (size_t i = 0; i < 20; ++i)
- EXPECT_EQ(google_fingerprint[i], fingerprint.data[i]);
+ EXPECT_EQ(expected_fingerprint[i], fingerprint.data[i]);
std::vector<std::string> dns_names;
google_cert->GetDNSNames(&dns_names);
@@ -156,6 +253,18 @@ TEST(X509CertificateTest, GoogleCertParsing) {
#endif
}
+} // namespace
+
+TEST(X509CertificateTest, GoogleCertParsing) {
+ scoped_refptr<X509Certificate> google_cert =
+ X509Certificate::CreateFromBytes(
+ reinterpret_cast<const char*>(google_der), sizeof(google_der));
+
+ CheckGoogleCert(google_cert, google_fingerprint,
+ 1238192407, // Mar 27 22:20:07 2009 GMT
+ 1269728407); // Mar 27 22:20:07 2010 GMT
+}
+
TEST(X509CertificateTest, WebkitCertParsing) {
scoped_refptr<X509Certificate> webkit_cert = X509Certificate::CreateFromBytes(
reinterpret_cast<const char*>(webkit_der), sizeof(webkit_der));
@@ -528,4 +637,41 @@ TEST(X509CertificateTest, IntermediateCertificates) {
}
#endif
+class X509CertificateParseTest
+ : public testing::TestWithParam<CertificateFormatTestData> {
+ public:
+ virtual ~X509CertificateParseTest() {}
+ virtual void SetUp() {
+ test_data_ = GetParam();
+ }
+ virtual void TearDown() {}
+
+ protected:
+ CertificateFormatTestData test_data_;
+};
+
+TEST_P(X509CertificateParseTest, CanParseFormat) {
+ FilePath certs_dir = GetTestCertsDirectory();
+ CertificateList certs = CreateCertificateListFromFile(
+ certs_dir, test_data_.file_name, test_data_.format);
+ ASSERT_FALSE(certs.empty());
+ ASSERT_LE(certs.size(), arraysize(test_data_.chain_fingerprints));
+ CheckGoogleCert(certs.front(), google_parse_fingerprint,
+ kGoogleParseValidFrom, kGoogleParseValidTo);
+
+ size_t i;
+ for (i = 0; i < arraysize(test_data_.chain_fingerprints) &&
+ i < certs.size() && test_data_.chain_fingerprints[i] != NULL; ++i) {
+ const X509Certificate* cert = certs[i];
+ const SHA1Fingerprint& actual_fingerprint = cert->fingerprint();
+ unsigned char* expected_fingerprint = test_data_.chain_fingerprints[i];
+
+ for (size_t j = 0; j < 20; ++j)
+ EXPECT_EQ(expected_fingerprint[j], actual_fingerprint.data[j]);
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(, X509CertificateParseTest,
+ testing::ValuesIn(FormatTestData));
+
} // namespace net
diff --git a/net/base/x509_certificate_win.cc b/net/base/x509_certificate_win.cc
index 901c0a6..faa8871 100644
--- a/net/base/x509_certificate_win.cc
+++ b/net/base/x509_certificate_win.cc
@@ -434,6 +434,54 @@ void ParsePrincipal(const std::string& description,
}
}
+void AddCertsFromStore(HCERTSTORE store,
+ X509Certificate::OSCertHandles* results) {
+ PCCERT_CONTEXT cert = NULL;
+
+ while ((cert = CertEnumCertificatesInStore(store, cert)) != NULL) {
+ PCCERT_CONTEXT to_add = NULL;
+ if (CertAddCertificateContextToStore(
+ NULL, // The cert won't be persisted in any cert store. This breaks
+ // any association the context currently has to |store|, which
+ // allows us, the caller, to safely close |store| without
+ // releasing the cert handles.
+ cert,
+ CERT_STORE_ADD_USE_EXISTING,
+ &to_add) && to_add != NULL) {
+ // When processing stores generated from PKCS#7/PKCS#12 files, it
+ // appears that the order returned is the inverse of the order that it
+ // appeared in the file.
+ // TODO(rsleevi): Ensure this order is consistent across all Win
+ // versions
+ results->insert(results->begin(), to_add);
+ }
+ }
+}
+
+X509Certificate::OSCertHandles ParsePKCS7(const char* data, size_t length) {
+ X509Certificate::OSCertHandles results;
+ CERT_BLOB data_blob;
+ data_blob.cbData = length;
+ data_blob.pbData = reinterpret_cast<BYTE*>(const_cast<char*>(data));
+
+ HCERTSTORE out_store = NULL;
+
+ DWORD expected_types = CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED |
+ CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED |
+ CERT_QUERY_CONTENT_FLAG_PKCS7_UNSIGNED;
+
+ if (!CryptQueryObject(CERT_QUERY_OBJECT_BLOB, &data_blob, expected_types,
+ CERT_QUERY_FORMAT_FLAG_BINARY, 0, NULL, NULL, NULL,
+ &out_store, NULL, NULL) || out_store == NULL) {
+ return results;
+ }
+
+ AddCertsFromStore(out_store, &results);
+ CertCloseStore(out_store, CERT_CLOSE_STORE_CHECK_FLAG);
+
+ return results;
+}
+
} // namespace
void X509Certificate::Initialize() {
@@ -753,6 +801,27 @@ X509Certificate::OSCertHandle X509Certificate::CreateOSCertHandleFromBytes(
return cert_handle;
}
+X509Certificate::OSCertHandles X509Certificate::CreateOSCertHandlesFromBytes(
+ const char* data, int length, Format format) {
+ OSCertHandles results;
+ switch (format) {
+ case FORMAT_DER: {
+ OSCertHandle handle = CreateOSCertHandleFromBytes(data, length);
+ if (handle != NULL)
+ results.push_back(handle);
+ break;
+ }
+ case FORMAT_PKCS7:
+ results = ParsePKCS7(data, length);
+ break;
+ default:
+ NOTREACHED() << "Certificate format " << format << " unimplemented";
+ break;
+ }
+
+ return results;
+}
+
// static
X509Certificate::OSCertHandle X509Certificate::DupOSCertHandle(
diff --git a/net/data/ssl/certificates/google.binary.p7b b/net/data/ssl/certificates/google.binary.p7b
new file mode 100644
index 0000000..052e388
--- /dev/null
+++ b/net/data/ssl/certificates/google.binary.p7b
Binary files differ
diff --git a/net/data/ssl/certificates/google.chain.pem b/net/data/ssl/certificates/google.chain.pem
new file mode 100644
index 0000000..e78af71
--- /dev/null
+++ b/net/data/ssl/certificates/google.chain.pem
@@ -0,0 +1,38 @@
+-----BEGIN CERTIFICATE-----
+MIIDITCCAoqgAwIBAgIQL9+89q6RUm0PmqPfQDQ+mjANBgkqhkiG9w0BAQUFADBM
+MQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkg
+THRkLjEWMBQGA1UEAxMNVGhhd3RlIFNHQyBDQTAeFw0wOTEyMTgwMDAwMDBaFw0x
+MTEyMTgyMzU5NTlaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlh
+MRYwFAYDVQQHFA1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKFApHb29nbGUgSW5jMRcw
+FQYDVQQDFA53d3cuZ29vZ2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC
+gYEA6PmGD5D6htffvXImttdEAoN4c9kCKO+IRTn7EOh8rqk41XXGOOsKFQebg+jN
+gtXj9xVoRaELGYW84u+E593y17iYwqG7tcFR39SDAqc9BkJb4SLD3muFXxzW2k6L
+05vuuWciKh0R73mkszeK9P4Y/bz5RiNQl/Os/CRGK1w7t0UCAwEAAaOB5zCB5DAM
+BgNVHRMBAf8EAjAAMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwudGhhd3Rl
+LmNvbS9UaGF3dGVTR0NDQS5jcmwwKAYDVR0lBCEwHwYIKwYBBQUHAwEGCCsGAQUF
+BwMCBglghkgBhvhCBAEwcgYIKwYBBQUHAQEEZjBkMCIGCCsGAQUFBzABhhZodHRw
+Oi8vb2NzcC50aGF3dGUuY29tMD4GCCsGAQUFBzAChjJodHRwOi8vd3d3LnRoYXd0
+ZS5jb20vcmVwb3NpdG9yeS9UaGF3dGVfU0dDX0NBLmNydDANBgkqhkiG9w0BAQUF
+AAOBgQCfQ89bxFApsb/isJr/aiEdLRLDLE5a+RLizrmCUi3nHX4adpaQedEkUjh5
+u2ONgJd8IyAPkU0Wueru9G2Jysa9zCRo1kNbzipYvzwY4OA8Ys+WAi0oR1A04Se6
+z5nRUP8pJcA2NhUzUnC+MY+f6H/nEQyNv4SgQhqAibAxWEEHXw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDIzCCAoygAwIBAgIEMAAAAjANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJV
+UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsTLkNsYXNzIDMgUHVi
+bGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNTEzMDAw
+MDAwWhcNMTQwNTEyMjM1OTU5WjBMMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhh
+d3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEWMBQGA1UEAxMNVGhhd3RlIFNHQyBD
+QTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1NNn0I0Vf67NMf59HZGhPwtx
+PKzMyGT7Y/wySweUvW+Aui/hBJPAM/wJMyPpC3QrccQDxtLN4i/1CWPN/0ilAL/g
+5/OIty0y3pg25gqtAHvEZEo7hHUD8nCSfQ5i9SGraTaEMXWQ+L/HbIgbBpV8yeWo
+3nWhLHpo39XKHIdYYBkCAwEAAaOB/jCB+zASBgNVHRMBAf8ECDAGAQH/AgEAMAsG
+A1UdDwQEAwIBBjARBglghkgBhvhCAQEEBAMCAQYwKAYDVR0RBCEwH6QdMBsxGTAX
+BgNVBAMTEFByaXZhdGVMYWJlbDMtMTUwMQYDVR0fBCowKDAmoCSgIoYgaHR0cDov
+L2NybC52ZXJpc2lnbi5jb20vcGNhMy5jcmwwMgYIKwYBBQUHAQEEJjAkMCIGCCsG
+AQUFBzABhhZodHRwOi8vb2NzcC50aGF3dGUuY29tMDQGA1UdJQQtMCsGCCsGAQUF
+BwMBBggrBgEFBQcDAgYJYIZIAYb4QgQBBgpghkgBhvhFAQgBMA0GCSqGSIb3DQEB
+BQUAA4GBAFWsY+reod3SkF+fC852vhNRj5PZBSvIG3dLrWlQoe7e3P3bB+noOZTc
+q3J5Lwa/q4FwxKjt6lM07e8eU9kGx1Yr0Vz00YqOtCuxN5BICEIlxT6Ky3/rbwTR
+bcV0oveifHtgPHfNDs5IAn8BL7abN+AqKjbc1YXWrOU/VG+WHgWv
+-----END CERTIFICATE----- \ No newline at end of file
diff --git a/net/data/ssl/certificates/google.pem_cert.p7b b/net/data/ssl/certificates/google.pem_cert.p7b
new file mode 100644
index 0000000..ba80fb0
--- /dev/null
+++ b/net/data/ssl/certificates/google.pem_cert.p7b
@@ -0,0 +1,37 @@
+-----BEGIN CERTIFICATE-----
+MIIGeQYJKoZIhvcNAQcCoIIGajCCBmYCAQExADALBgkqhkiG9w0BBwGgggZMMIID
+ITCCAoqgAwIBAgIQL9+89q6RUm0PmqPfQDQ+mjANBgkqhkiG9w0BAQUFADBMMQsw
+CQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkgTHRk
+LjEWMBQGA1UEAxMNVGhhd3RlIFNHQyBDQTAeFw0wOTEyMTgwMDAwMDBaFw0xMTEy
+MTgyMzU5NTlaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYw
+FAYDVQQHFA1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKFApHb29nbGUgSW5jMRcwFQYD
+VQQDFA53d3cuZ29vZ2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA
+6PmGD5D6htffvXImttdEAoN4c9kCKO+IRTn7EOh8rqk41XXGOOsKFQebg+jNgtXj
+9xVoRaELGYW84u+E593y17iYwqG7tcFR39SDAqc9BkJb4SLD3muFXxzW2k6L05vu
+uWciKh0R73mkszeK9P4Y/bz5RiNQl/Os/CRGK1w7t0UCAwEAAaOB5zCB5DAMBgNV
+HRMBAf8EAjAAMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwudGhhd3RlLmNv
+bS9UaGF3dGVTR0NDQS5jcmwwKAYDVR0lBCEwHwYIKwYBBQUHAwEGCCsGAQUFBwMC
+BglghkgBhvhCBAEwcgYIKwYBBQUHAQEEZjBkMCIGCCsGAQUFBzABhhZodHRwOi8v
+b2NzcC50aGF3dGUuY29tMD4GCCsGAQUFBzAChjJodHRwOi8vd3d3LnRoYXd0ZS5j
+b20vcmVwb3NpdG9yeS9UaGF3dGVfU0dDX0NBLmNydDANBgkqhkiG9w0BAQUFAAOB
+gQCfQ89bxFApsb/isJr/aiEdLRLDLE5a+RLizrmCUi3nHX4adpaQedEkUjh5u2ON
+gJd8IyAPkU0Wueru9G2Jysa9zCRo1kNbzipYvzwY4OA8Ys+WAi0oR1A04Se6z5nR
+UP8pJcA2NhUzUnC+MY+f6H/nEQyNv4SgQhqAibAxWEEHXzCCAyMwggKMoAMCAQIC
+BDAAAAIwDQYJKoZIhvcNAQEFBQAwXzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZl
+cmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENl
+cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDUxMzAwMDAwMFoXDTE0MDUxMjIz
+NTk1OVowTDELMAkGA1UEBhMCWkExJTAjBgNVBAoTHFRoYXd0ZSBDb25zdWx0aW5n
+IChQdHkpIEx0ZC4xFjAUBgNVBAMTDVRoYXd0ZSBTR0MgQ0EwgZ8wDQYJKoZIhvcN
+AQEBBQADgY0AMIGJAoGBANTTZ9CNFX+uzTH+fR2RoT8LcTyszMhk+2P8MksHlL1v
+gLov4QSTwDP8CTMj6Qt0K3HEA8bSzeIv9Qljzf9IpQC/4OfziLctMt6YNuYKrQB7
+xGRKO4R1A/Jwkn0OYvUhq2k2hDF1kPi/x2yIGwaVfMnlqN51oSx6aN/VyhyHWGAZ
+AgMBAAGjgf4wgfswEgYDVR0TAQH/BAgwBgEB/wIBADALBgNVHQ8EBAMCAQYwEQYJ
+YIZIAYb4QgEBBAQDAgEGMCgGA1UdEQQhMB+kHTAbMRkwFwYDVQQDExBQcml2YXRl
+TGFiZWwzLTE1MDEGA1UdHwQqMCgwJqAkoCKGIGh0dHA6Ly9jcmwudmVyaXNpZ24u
+Y29tL3BjYTMuY3JsMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcwAYYWaHR0cDov
+L29jc3AudGhhd3RlLmNvbTA0BgNVHSUELTArBggrBgEFBQcDAQYIKwYBBQUHAwIG
+CWCGSAGG+EIEAQYKYIZIAYb4RQEIATANBgkqhkiG9w0BAQUFAAOBgQBVrGPq3qHd
+0pBfnwvOdr4TUY+T2QUryBt3S61pUKHu3tz92wfp6DmU3KtyeS8Gv6uBcMSo7epT
+NO3vHlPZBsdWK9Fc9NGKjrQrsTeQSAhCJcU+ist/628E0W3FdKL3onx7YDx3zQ7O
+SAJ/AS+2mzfgKio23NWF1qzlP1Rvlh4Fr6EAMQA=
+-----END CERTIFICATE-----
diff --git a/net/data/ssl/certificates/google.pem_pkcs7.p7b b/net/data/ssl/certificates/google.pem_pkcs7.p7b
new file mode 100644
index 0000000..49e2eec
--- /dev/null
+++ b/net/data/ssl/certificates/google.pem_pkcs7.p7b
@@ -0,0 +1,37 @@
+-----BEGIN PKCS7-----
+MIIGeQYJKoZIhvcNAQcCoIIGajCCBmYCAQExADALBgkqhkiG9w0BBwGgggZMMIID
+ITCCAoqgAwIBAgIQL9+89q6RUm0PmqPfQDQ+mjANBgkqhkiG9w0BAQUFADBMMQsw
+CQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkgTHRk
+LjEWMBQGA1UEAxMNVGhhd3RlIFNHQyBDQTAeFw0wOTEyMTgwMDAwMDBaFw0xMTEy
+MTgyMzU5NTlaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYw
+FAYDVQQHFA1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKFApHb29nbGUgSW5jMRcwFQYD
+VQQDFA53d3cuZ29vZ2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA
+6PmGD5D6htffvXImttdEAoN4c9kCKO+IRTn7EOh8rqk41XXGOOsKFQebg+jNgtXj
+9xVoRaELGYW84u+E593y17iYwqG7tcFR39SDAqc9BkJb4SLD3muFXxzW2k6L05vu
+uWciKh0R73mkszeK9P4Y/bz5RiNQl/Os/CRGK1w7t0UCAwEAAaOB5zCB5DAMBgNV
+HRMBAf8EAjAAMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwudGhhd3RlLmNv
+bS9UaGF3dGVTR0NDQS5jcmwwKAYDVR0lBCEwHwYIKwYBBQUHAwEGCCsGAQUFBwMC
+BglghkgBhvhCBAEwcgYIKwYBBQUHAQEEZjBkMCIGCCsGAQUFBzABhhZodHRwOi8v
+b2NzcC50aGF3dGUuY29tMD4GCCsGAQUFBzAChjJodHRwOi8vd3d3LnRoYXd0ZS5j
+b20vcmVwb3NpdG9yeS9UaGF3dGVfU0dDX0NBLmNydDANBgkqhkiG9w0BAQUFAAOB
+gQCfQ89bxFApsb/isJr/aiEdLRLDLE5a+RLizrmCUi3nHX4adpaQedEkUjh5u2ON
+gJd8IyAPkU0Wueru9G2Jysa9zCRo1kNbzipYvzwY4OA8Ys+WAi0oR1A04Se6z5nR
+UP8pJcA2NhUzUnC+MY+f6H/nEQyNv4SgQhqAibAxWEEHXzCCAyMwggKMoAMCAQIC
+BDAAAAIwDQYJKoZIhvcNAQEFBQAwXzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZl
+cmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENl
+cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDUxMzAwMDAwMFoXDTE0MDUxMjIz
+NTk1OVowTDELMAkGA1UEBhMCWkExJTAjBgNVBAoTHFRoYXd0ZSBDb25zdWx0aW5n
+IChQdHkpIEx0ZC4xFjAUBgNVBAMTDVRoYXd0ZSBTR0MgQ0EwgZ8wDQYJKoZIhvcN
+AQEBBQADgY0AMIGJAoGBANTTZ9CNFX+uzTH+fR2RoT8LcTyszMhk+2P8MksHlL1v
+gLov4QSTwDP8CTMj6Qt0K3HEA8bSzeIv9Qljzf9IpQC/4OfziLctMt6YNuYKrQB7
+xGRKO4R1A/Jwkn0OYvUhq2k2hDF1kPi/x2yIGwaVfMnlqN51oSx6aN/VyhyHWGAZ
+AgMBAAGjgf4wgfswEgYDVR0TAQH/BAgwBgEB/wIBADALBgNVHQ8EBAMCAQYwEQYJ
+YIZIAYb4QgEBBAQDAgEGMCgGA1UdEQQhMB+kHTAbMRkwFwYDVQQDExBQcml2YXRl
+TGFiZWwzLTE1MDEGA1UdHwQqMCgwJqAkoCKGIGh0dHA6Ly9jcmwudmVyaXNpZ24u
+Y29tL3BjYTMuY3JsMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcwAYYWaHR0cDov
+L29jc3AudGhhd3RlLmNvbTA0BgNVHSUELTArBggrBgEFBQcDAQYIKwYBBQUHAwIG
+CWCGSAGG+EIEAQYKYIZIAYb4RQEIATANBgkqhkiG9w0BAQUFAAOBgQBVrGPq3qHd
+0pBfnwvOdr4TUY+T2QUryBt3S61pUKHu3tz92wfp6DmU3KtyeS8Gv6uBcMSo7epT
+NO3vHlPZBsdWK9Fc9NGKjrQrsTeQSAhCJcU+ist/628E0W3FdKL3onx7YDx3zQ7O
+SAJ/AS+2mzfgKio23NWF1qzlP1Rvlh4Fr6EAMQA=
+-----END PKCS7-----
diff --git a/net/data/ssl/certificates/google.single.der b/net/data/ssl/certificates/google.single.der
new file mode 100644
index 0000000..f73df17
--- /dev/null
+++ b/net/data/ssl/certificates/google.single.der
Binary files differ
diff --git a/net/data/ssl/certificates/google.single.pem b/net/data/ssl/certificates/google.single.pem
new file mode 100644
index 0000000..a03adc4
--- /dev/null
+++ b/net/data/ssl/certificates/google.single.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDITCCAoqgAwIBAgIQL9+89q6RUm0PmqPfQDQ+mjANBgkqhkiG9w0BAQUFADBM
+MQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkg
+THRkLjEWMBQGA1UEAxMNVGhhd3RlIFNHQyBDQTAeFw0wOTEyMTgwMDAwMDBaFw0x
+MTEyMTgyMzU5NTlaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlh
+MRYwFAYDVQQHFA1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKFApHb29nbGUgSW5jMRcw
+FQYDVQQDFA53d3cuZ29vZ2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC
+gYEA6PmGD5D6htffvXImttdEAoN4c9kCKO+IRTn7EOh8rqk41XXGOOsKFQebg+jN
+gtXj9xVoRaELGYW84u+E593y17iYwqG7tcFR39SDAqc9BkJb4SLD3muFXxzW2k6L
+05vuuWciKh0R73mkszeK9P4Y/bz5RiNQl/Os/CRGK1w7t0UCAwEAAaOB5zCB5DAM
+BgNVHRMBAf8EAjAAMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwudGhhd3Rl
+LmNvbS9UaGF3dGVTR0NDQS5jcmwwKAYDVR0lBCEwHwYIKwYBBQUHAwEGCCsGAQUF
+BwMCBglghkgBhvhCBAEwcgYIKwYBBQUHAQEEZjBkMCIGCCsGAQUFBzABhhZodHRw
+Oi8vb2NzcC50aGF3dGUuY29tMD4GCCsGAQUFBzAChjJodHRwOi8vd3d3LnRoYXd0
+ZS5jb20vcmVwb3NpdG9yeS9UaGF3dGVfU0dDX0NBLmNydDANBgkqhkiG9w0BAQUF
+AAOBgQCfQ89bxFApsb/isJr/aiEdLRLDLE5a+RLizrmCUi3nHX4adpaQedEkUjh5
+u2ONgJd8IyAPkU0Wueru9G2Jysa9zCRo1kNbzipYvzwY4OA8Ys+WAi0oR1A04Se6
+z5nRUP8pJcA2NhUzUnC+MY+f6H/nEQyNv4SgQhqAibAxWEEHXw==
+-----END CERTIFICATE----- \ No newline at end of file
diff --git a/net/data/ssl/certificates/thawte.single.pem b/net/data/ssl/certificates/thawte.single.pem
new file mode 100644
index 0000000..d326459
--- /dev/null
+++ b/net/data/ssl/certificates/thawte.single.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDIzCCAoygAwIBAgIEMAAAAjANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJV
+UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsTLkNsYXNzIDMgUHVi
+bGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNTEzMDAw
+MDAwWhcNMTQwNTEyMjM1OTU5WjBMMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhh
+d3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEWMBQGA1UEAxMNVGhhd3RlIFNHQyBD
+QTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1NNn0I0Vf67NMf59HZGhPwtx
+PKzMyGT7Y/wySweUvW+Aui/hBJPAM/wJMyPpC3QrccQDxtLN4i/1CWPN/0ilAL/g
+5/OIty0y3pg25gqtAHvEZEo7hHUD8nCSfQ5i9SGraTaEMXWQ+L/HbIgbBpV8yeWo
+3nWhLHpo39XKHIdYYBkCAwEAAaOB/jCB+zASBgNVHRMBAf8ECDAGAQH/AgEAMAsG
+A1UdDwQEAwIBBjARBglghkgBhvhCAQEEBAMCAQYwKAYDVR0RBCEwH6QdMBsxGTAX
+BgNVBAMTEFByaXZhdGVMYWJlbDMtMTUwMQYDVR0fBCowKDAmoCSgIoYgaHR0cDov
+L2NybC52ZXJpc2lnbi5jb20vcGNhMy5jcmwwMgYIKwYBBQUHAQEEJjAkMCIGCCsG
+AQUFBzABhhZodHRwOi8vb2NzcC50aGF3dGUuY29tMDQGA1UdJQQtMCsGCCsGAQUF
+BwMBBggrBgEFBQcDAgYJYIZIAYb4QgQBBgpghkgBhvhFAQgBMA0GCSqGSIb3DQEB
+BQUAA4GBAFWsY+reod3SkF+fC852vhNRj5PZBSvIG3dLrWlQoe7e3P3bB+noOZTc
+q3J5Lwa/q4FwxKjt6lM07e8eU9kGx1Yr0Vz00YqOtCuxN5BICEIlxT6Ky3/rbwTR
+bcV0oveifHtgPHfNDs5IAn8BL7abN+AqKjbc1YXWrOU/VG+WHgWv
+-----END CERTIFICATE----- \ No newline at end of file
diff --git a/net/net.gyp b/net/net.gyp
index 98dc526..ea740ff 100644
--- a/net/net.gyp
+++ b/net/net.gyp
@@ -125,6 +125,8 @@
'base/network_change_notifier_win.h',
'base/nss_memio.c',
'base/nss_memio.h',
+ 'base/pem_tokenizer.cc',
+ 'base/pem_tokenizer.h',
'base/platform_mime_util.h',
# TODO(tc): gnome-vfs? xdgmime? /etc/mime.types?
'base/platform_mime_util_linux.cc',
@@ -674,6 +676,7 @@
'base/net_test_constants.h',
'base/net_test_suite.h',
'base/net_util_unittest.cc',
+ 'base/pem_tokenizer_unittest.cc',
'base/registry_controlled_domain_unittest.cc',
'base/run_all_unittests.cc',
'base/sdch_filter_unittest.cc',