| Index: net/cert/ct_objects_extractor_nss.cc
|
| diff --git a/net/cert/ct_objects_extractor_nss.cc b/net/cert/ct_objects_extractor_nss.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..e5f1fab6531d726f4fab6345dcc80c3b468ddf5e
|
| --- /dev/null
|
| +++ b/net/cert/ct_objects_extractor_nss.cc
|
| @@ -0,0 +1,276 @@
|
| +// Copyright (c) 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_objects_extractor.h"
|
| +
|
| +#include <cert.h>
|
| +#include <secasn1.h>
|
| +#include <secitem.h>
|
| +#include <secoid.h>
|
| +
|
| +#include "base/lazy_instance.h"
|
| +#include "crypto/scoped_nss_types.h"
|
| +#include "crypto/sha2.h"
|
| +#include "net/cert/asn1_util.h"
|
| +#include "net/cert/signed_certificate_timestamp.h"
|
| +
|
| +namespace net {
|
| +
|
| +namespace ct {
|
| +
|
| +namespace {
|
| +
|
| +struct FreeCERTCertificate {
|
| + public:
|
| + inline void operator()(CERTCertificate* x) const {
|
| + CERT_DestroyCertificate(x);
|
| + }
|
| +};
|
| +
|
| +typedef scoped_ptr_malloc<CERTCertificate, FreeCERTCertificate>
|
| + ScopedCERTCertificate;
|
| +
|
| +// Wrapper class to convert a X509Certificate::OSCertHandle directly
|
| +// into a CERTCertificate* usable with other NSS functions. This is used for
|
| +// systems where X509Certificate::OSCertHandle refers to a different type
|
| +// than a CERTCertificate*.
|
| +struct NSSCertWrapper {
|
| + explicit NSSCertWrapper(X509Certificate::OSCertHandle cert_handle);
|
| + ~NSSCertWrapper() {}
|
| +
|
| + ScopedCERTCertificate cert;
|
| +};
|
| +
|
| +NSSCertWrapper::NSSCertWrapper(X509Certificate::OSCertHandle cert_handle) {
|
| +#if defined(USE_NSS)
|
| + cert.reset(CERT_DupCertificate(cert_handle));
|
| +#else
|
| + SECItem der_cert;
|
| + std::string der_data;
|
| + if (!X509Certificate::GetDEREncoded(cert_handle, &der_data))
|
| + return;
|
| + der_cert.data = reinterpret_cast<unsigned char*>(
|
| + const_cast<char*>(der_data.data()));
|
| + der_cert.len = der_data.size();
|
| +
|
| + // Note: CERT_NewTempCertificate may return NULL if the certificate
|
| + // shares a serial number with another cert, which is not supposed
|
| + // to happen.
|
| + cert.reset(CERT_NewTempCertificate(
|
| + CERT_GetDefaultCertDB(), &der_cert, NULL, PR_FALSE, PR_TRUE));
|
| +#endif
|
| + DCHECK(cert.get() != NULL);
|
| +}
|
| +
|
| +// The wire form of the OID 1.3.6.1.4.1.11129.2.4.2. See Section 3.3 of
|
| +// RFC6962.
|
| +const unsigned char kEmbeddedSCTOid[] =
|
| + { 0x2B, 0x06, 0x01, 0x04, 0x01, 0xD6, 0x79, 0x02, 0x04, 0x02 };
|
| +const char kEmbeddedSCTDescription[] =
|
| + "X.509v3 Certificate Transparency Embedded Signed Certificate Timestamp "
|
| + "List";
|
| +
|
| +// Initializes the necessary NSS internals for use with Certificate
|
| +// Transparency.
|
| +class CTInitSingleton {
|
| + public:
|
| + SECOidTag embedded_oid() const { return embedded_oid_; }
|
| +
|
| + private:
|
| + friend struct base::DefaultLazyInstanceTraits<CTInitSingleton>;
|
| +
|
| + CTInitSingleton()
|
| + : embedded_oid_(SEC_OID_UNKNOWN) {
|
| + embedded_oid_ = RegisterOid(kEmbeddedSCTOid, sizeof(kEmbeddedSCTOid),
|
| + kEmbeddedSCTDescription);
|
| + }
|
| +
|
| + ~CTInitSingleton() {}
|
| +
|
| + SECOidTag RegisterOid(const unsigned char* oid,
|
| + unsigned int oid_len,
|
| + const char* description) {
|
| + SECOidData oid_data;
|
| + oid_data.oid.len = oid_len;
|
| + oid_data.oid.data = const_cast<unsigned char*>(oid);
|
| + oid_data.offset = SEC_OID_UNKNOWN;
|
| + oid_data.desc = description;
|
| + oid_data.mechanism = CKM_INVALID_MECHANISM;
|
| + // Setting this to SUPPORTED_CERT_EXTENSION ensures that if a certificate
|
| + // contains this extension with the critical bit set, NSS will not reject
|
| + // it. However, because verification of this extension happens after NSS,
|
| + // it is currently left as INVALID_CERT_EXTENSION.
|
| + oid_data.supportedExtension = INVALID_CERT_EXTENSION;
|
| +
|
| + SECOidTag result = SECOID_AddEntry(&oid_data);
|
| + CHECK_NE(SEC_OID_UNKNOWN, result);
|
| +
|
| + return result;
|
| + }
|
| +
|
| + SECOidTag embedded_oid_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(CTInitSingleton);
|
| +};
|
| +
|
| +base::LazyInstance<CTInitSingleton>::Leaky g_ct_singleton =
|
| + LAZY_INSTANCE_INITIALIZER;
|
| +
|
| +// Obtains the data for an X.509v3 certificate extension identified by |oid|
|
| +// and encoded as an OCTET STRING. Returns true if the extension was found,
|
| +// updating |ext_data| to be the extension data after removing the DER
|
| +// encoding.
|
| +bool GetOctetStringExtension(CERTCertificate* cert,
|
| + SECOidTag oid,
|
| + std::string* extension_data) {
|
| + SECItem extension;
|
| + SECStatus rv = CERT_FindCertExtension(cert, oid, &extension);
|
| + if (rv != SECSuccess)
|
| + return false;
|
| +
|
| + base::StringPiece raw_data(reinterpret_cast<char*>(extension.data),
|
| + extension.len);
|
| + base::StringPiece parsed_data;
|
| + if (!asn1::GetElement(&raw_data, asn1::kOCTETSTRING, &parsed_data)) {
|
| + rv = SECFailure;
|
| + } else {
|
| + parsed_data.CopyToString(extension_data);
|
| + }
|
| +
|
| + SECITEM_FreeItem(&extension, PR_FALSE);
|
| + return rv == SECSuccess;
|
| +}
|
| +
|
| +bool ExtractTBSCertWithoutSCTs(CERTCertificate* cert,
|
| + std::string* to_be_signed) {
|
| + SECOidData* oid = SECOID_FindOIDByTag(g_ct_singleton.Get().embedded_oid());
|
| + if (!oid)
|
| + return false;
|
| +
|
| + // This is a giant hack, due to the fact that NSS does not expose a good API
|
| + // for simply removing certificate fields from existing certificates.
|
| + CERTCertificateStr temp_cert;
|
| + temp_cert = *cert;
|
| + temp_cert.extensions = NULL;
|
| +
|
| + // Strip out the embedded SCT OID from the new certificate by directly
|
| + // mutating the extensions in place.
|
| + std::vector<CERTCertExtension*> new_extensions;
|
| + if (cert->extensions) {
|
| + for (CERTCertExtension** exts = cert->extensions; *exts; ++exts) {
|
| + CERTCertExtension* ext = *exts;
|
| + SECComparison result = SECITEM_CompareItem(&oid->oid, &ext->id);
|
| + if (result != SECEqual)
|
| + new_extensions.push_back(ext);
|
| + }
|
| + }
|
| + if (!new_extensions.empty()) {
|
| + new_extensions.push_back(NULL);
|
| + // XXX(eranm): Should probabry convert to a proper array here.
|
| + temp_cert.extensions = &new_extensions[0];
|
| + }
|
| +
|
| + crypto::ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
|
| +
|
| + SECItem tbs_data;
|
| + tbs_data.len = 0;
|
| + tbs_data.data = NULL;
|
| + void* result = SEC_ASN1EncodeItem(arena.get(),
|
| + &tbs_data,
|
| + &temp_cert,
|
| + SEC_ASN1_GET(CERT_CertificateTemplate));
|
| + if (!result || tbs_data.len == 0)
|
| + return false;
|
| +
|
| + to_be_signed->assign(reinterpret_cast<char*>(tbs_data.data), tbs_data.len);
|
| + return true;
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +bool ExtractEmbeddedSCTs(X509Certificate::OSCertHandle cert,
|
| + std::string* sct_list) {
|
| + DCHECK(cert);
|
| +
|
| + NSSCertWrapper leaf_cert(cert);
|
| + if (!leaf_cert.cert)
|
| + return false;
|
| +
|
| + return GetOctetStringExtension(
|
| + leaf_cert.cert.get(), g_ct_singleton.Get().embedded_oid(), sct_list);
|
| +}
|
| +
|
| +bool GetPrecertLogEntry(X509Certificate::OSCertHandle leaf,
|
| + X509Certificate::OSCertHandle issuer,
|
| + LogEntry* result) {
|
| + DCHECK(leaf);
|
| + DCHECK(issuer);
|
| +
|
| + NSSCertWrapper leaf_cert(leaf);
|
| + NSSCertWrapper issuer_cert(issuer);
|
| +
|
| + // XXX(rsleevi): This check may be overkill, since we should be able to
|
| + // generate precerts for certs without the extension. For now, just a sanity
|
| + // check to match the reference implementation.
|
| + SECItem extension;
|
| + SECStatus rv = CERT_FindCertExtension(
|
| + leaf_cert.cert.get(), g_ct_singleton.Get().embedded_oid(), &extension);
|
| + if (rv != SECSuccess)
|
| + return false;
|
| + SECITEM_FreeItem(&extension, PR_FALSE);
|
| +
|
| + std::string to_be_signed;
|
| + if (!ExtractTBSCertWithoutSCTs(leaf_cert.cert.get(), &to_be_signed))
|
| + return false;
|
| +
|
| + result->Reset();
|
| + result->type = ct::LogEntry::LOG_ENTRY_TYPE_PRECERT;
|
| + result->tbs_certificate.swap(to_be_signed);
|
| + if (!issuer_cert.cert) {
|
| + // This can happen when the issuer and leaf certs share the same serial
|
| + // number, which should never be the case (but happened with bad test
|
| + // certs).
|
| + VLOG(1) << "Issuer cert is null, failing SCT verification.";
|
| + return false;
|
| + }
|
| +
|
| + // Hash the entire SPKI, not just the public key.
|
| + // See https://codereview.appspot.com/8269046/
|
| + SECKEYPublicKey *issuer_pub_key =
|
| + SECKEY_ExtractPublicKey(&(issuer_cert.cert->subjectPublicKeyInfo));
|
| + SECItem *encoded_issuer_pubKey =
|
| + SECKEY_EncodeDERSubjectPublicKeyInfo(issuer_pub_key);
|
| +
|
| + crypto::SHA256HashString(
|
| + base::StringPiece(
|
| + reinterpret_cast<char*>(encoded_issuer_pubKey->data),
|
| + encoded_issuer_pubKey->len),
|
| + result->issuer_key_hash.data,
|
| + sizeof(result->issuer_key_hash.data));
|
| +
|
| + SECITEM_FreeItem(encoded_issuer_pubKey, PR_TRUE);
|
| + encoded_issuer_pubKey = NULL;
|
| + SECKEY_DestroyPublicKey(issuer_pub_key);
|
| + issuer_pub_key = NULL;
|
| +
|
| + return true;
|
| +}
|
| +
|
| +bool GetAsn1CertLogEntry(X509Certificate::OSCertHandle leaf,
|
| + LogEntry* result) {
|
| + DCHECK(leaf);
|
| +
|
| + std::string encoded;
|
| + if (!X509Certificate::GetDEREncoded(leaf, &encoded))
|
| + return false;
|
| +
|
| + result->Reset();
|
| + result->type = ct::LogEntry::LOG_ENTRY_TYPE_X509;
|
| + result->leaf_certificate.swap(encoded);
|
| + return true;
|
| +}
|
| +
|
| +} // namespace ct
|
| +
|
| +} // namespace net
|
|
|