// 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_objects_extractor.h" #include #include #include #include #include "base/lazy_instance.h" #include "crypto/scoped_nss_types.h" #include "crypto/sha2.h" #include "net/cert/asn1_util.h" #include "net/cert/scoped_nss_types.h" #include "net/cert/signed_certificate_timestamp.h" namespace net { namespace ct { namespace { // Wrapper class to convert a X509Certificate::OSCertHandle directly // into a CERTCertificate* usable with other NSS functions. This is used for // platforms 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(const_cast(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 issued by the same CA, // 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() : 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(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::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 of OCTET STRING. 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(extension.data), extension.len); base::StringPiece parsed_data; if (!asn1::GetElement(&raw_data, asn1::kOCTETSTRING, &parsed_data) || raw_data.size() > 0) { // Decoding failure or raw data left rv = SECFailure; } else { parsed_data.CopyToString(extension_data); } SECITEM_FreeItem(&extension, PR_FALSE); return rv == SECSuccess; } // Given a |cert|, extract the TBSCertificate from this certificate, also // removing the X.509 extension with OID 1.3.6.1.4.1.11129.2.4.2 (that is, // the embedded SCT) 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. CERTCertificate 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 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); 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) return false; to_be_signed->assign(reinterpret_cast(tbs_data.data), tbs_data.len); return true; } } // namespace bool ExtractEmbeddedSCTList(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); result->Reset(); // 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; if (!issuer_cert.cert) { // This can happen when the issuer and leaf certs share the same serial // number and are from the same CA, which should never be the case // (but happened with bad test certs). VLOG(1) << "Issuer cert is null, cannot generate Precert entry."; return false; } SECKEYPublicKey* issuer_pub_key = SECKEY_ExtractPublicKey(&(issuer_cert.cert->subjectPublicKeyInfo)); if (!issuer_pub_key) { VLOG(1) << "Could not extract issuer public key, " << "cannot generate Precert entry."; return false; } SECItem* encoded_issuer_pubKey = SECKEY_EncodeDERSubjectPublicKeyInfo(issuer_pub_key); if (!encoded_issuer_pubKey) { SECKEY_DestroyPublicKey(issuer_pub_key); return false; } result->type = ct::LogEntry::LOG_ENTRY_TYPE_PRECERT; result->tbs_certificate.swap(to_be_signed); crypto::SHA256HashString( base::StringPiece(reinterpret_cast(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); SECKEY_DestroyPublicKey(issuer_pub_key); return true; } bool GetX509LogEntry(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