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

Unified Diff: net/cert/ct_objects_extractor_nss.cc

Issue 92443002: Extract Certificate Transparency SCTs from stapled OCSP responses (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@extract_scts
Patch Set: Fix C++11 compile error Created 7 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « net/cert/ct_objects_extractor.h ('k') | net/cert/ct_objects_extractor_openssl.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
index 0f353489e3d33a585dca01befc6006ff00ef28bd..c29b34c10c63aba50acc8850d754412947fc7c76 100644
--- a/net/cert/ct_objects_extractor_nss.cc
+++ b/net/cert/ct_objects_extractor_nss.cc
@@ -10,6 +10,7 @@
#include <secoid.h>
#include "base/lazy_instance.h"
+#include "base/sha1.h"
#include "crypto/scoped_nss_types.h"
#include "crypto/sha2.h"
#include "net/cert/asn1_util.h"
@@ -22,6 +23,12 @@ namespace ct {
namespace {
+// NSS black magic to get the address of externally defined template at runtime.
+SEC_ASN1_MKSUB(SEC_IntegerTemplate)
+SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate)
+SEC_ASN1_MKSUB(SEC_GeneralizedTimeTemplate)
+SEC_ASN1_MKSUB(CERT_SequenceOfCertExtensionTemplate)
+
// 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
@@ -62,6 +69,27 @@ const char kEmbeddedSCTDescription[] =
"X.509v3 Certificate Transparency Embedded Signed Certificate Timestamp "
"List";
+// The wire form of the OID 1.3.6.1.4.1.11129.2.4.5 - OCSP SingleExtension for
+// X.509v3 Certificate Transparency Signed Certificate Timestamp List, see
+// Section 3.3 of RFC6962.
+const unsigned char kOCSPExtensionOid[] = {0x2B, 0x06, 0x01, 0x04, 0x01,
+ 0xD6, 0x79, 0x02, 0x04, 0x05};
+
+const SECItem kOCSPExtensionOidItem = {
+ siBuffer, const_cast<unsigned char*>(kOCSPExtensionOid),
+ sizeof(kOCSPExtensionOid)
+};
+
+// id-ad-ocsp: 1.3.6.1.5.5.7.48.1.1
+const unsigned char kBasicOCSPResponseOid[] = {0x2B, 0x06, 0x01, 0x05, 0x05,
+ 0x07, 0x30, 0x01, 0x01};
+
+const SECItem kBasicOCSPResponseOidItem = {
+ siBuffer, const_cast<unsigned char*>(kBasicOCSPResponseOid),
+ sizeof(kBasicOCSPResponseOid)
+};
+
+
// Initializes the necessary NSS internals for use with Certificate
// Transparency.
class CTInitSingleton {
@@ -108,12 +136,12 @@ 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 of OCTET STRING.
-bool GetOctetStringExtension(CERTCertificate* cert,
- SECOidTag oid,
- std::string* extension_data) {
+// and encoded as an OCTET STRING. Returns true if the extension was found in
+// the certificate, updating |ext_data| to be the extension data after removing
+// the DER encoding of OCTET STRING.
+bool GetCertOctetStringExtension(CERTCertificate* cert,
+ SECOidTag oid,
+ std::string* extension_data) {
SECItem extension;
SECStatus rv = CERT_FindCertExtension(cert, oid, &extension);
if (rv != SECSuccess)
@@ -133,6 +161,48 @@ bool GetOctetStringExtension(CERTCertificate* cert,
return rv == SECSuccess;
}
+// NSS offers CERT_FindCertExtension for certificates, but that only accepts
+// CERTCertificate* inputs, so the method below extracts the SCT extension
+// directly from the CERTCertExtension** of an OCSP response.
+//
+// Obtains the data for an OCSP extension identified by kOCSPExtensionOidItem
+// and encoded as an OCTET STRING. Returns true if the extension was found in
+// |extensions|, updating |extension_data| to be the extension data after
+// removing the DER encoding of OCTET STRING.
+bool GetSCTListFromOCSPExtension(PLArenaPool* arena,
+ const CERTCertExtension* const* extensions,
+ std::string* extension_data) {
+ if (!extensions)
+ return false;
+
+ const CERTCertExtension* match = NULL;
+
+ for (const CERTCertExtension* const* exts = extensions; *exts; ++exts) {
+ const CERTCertExtension* ext = *exts;
+ if (SECITEM_ItemsAreEqual(&kOCSPExtensionOidItem, &ext->id)) {
+ match = ext;
+ break;
+ }
+ }
+
+ if (!match)
+ return false;
+
+ SECItem contents;
+ // SEC_QuickDERDecodeItem sets |contents| to point to |match|, so it is not
+ // necessary to free the contents of |contents|.
+ SECStatus rv = SEC_QuickDERDecodeItem(arena, &contents,
+ SEC_ASN1_GET(SEC_OctetStringTemplate),
+ &match->value);
+ if (rv != SECSuccess)
+ return false;
+
+ base::StringPiece parsed_data(reinterpret_cast<char*>(contents.data),
+ contents.len);
+ parsed_data.CopyToString(extension_data);
+ return true;
+}
+
// 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)
@@ -180,6 +250,179 @@ bool ExtractTBSCertWithoutSCTs(CERTCertificate* cert,
return true;
}
+// The following code is adapted from the NSS OCSP module, in order to expose
+// the internal structure of an OCSP response.
+
+// ResponseBytes ::= SEQUENCE {
+// responseType OBJECT IDENTIFIER,
+// response OCTET STRING }
+struct ResponseBytes {
+ SECItem response_type;
+ SECItem der_response;
+};
+
+const SEC_ASN1Template kResponseBytesTemplate[] = {
+ { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(ResponseBytes) },
+ { SEC_ASN1_OBJECT_ID, offsetof(ResponseBytes, response_type) },
+ { SEC_ASN1_OCTET_STRING, offsetof(ResponseBytes, der_response) },
+ { 0 }
+};
+
+// OCSPResponse ::= SEQUENCE {
+// responseStatus OCSPResponseStatus,
+// responseBytes [0] EXPLICIT ResponseBytes OPTIONAL }
+struct OCSPResponse {
+ SECItem response_status;
+ // This indirection is needed because |response_bytes| is an optional
+ // component and we need a way to determine if it is missing.
+ ResponseBytes* response_bytes;
+};
+
+const SEC_ASN1Template kPointerToResponseBytesTemplate[] = {
+ { SEC_ASN1_POINTER, 0, kResponseBytesTemplate }
+};
+
+const SEC_ASN1Template kOCSPResponseTemplate[] = {
+ { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(OCSPResponse) },
+ { SEC_ASN1_ENUMERATED, offsetof(OCSPResponse, response_status) },
+ { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED |
+ SEC_ASN1_CONTEXT_SPECIFIC | 0, offsetof(OCSPResponse, response_bytes),
+ kPointerToResponseBytesTemplate },
+ { 0 }
+};
+
+// CertID ::= SEQUENCE {
+// hashAlgorithm AlgorithmIdentifier,
+// issuerNameHash OCTET STRING, -- Hash of Issuer's DN
+// issuerKeyHash OCTET STRING, -- Hash of Issuers public key
+// serialNumber CertificateSerialNumber }
+struct CertID {
+ SECAlgorithmID hash_algorithm;
+ SECItem issuer_name_hash;
+ SECItem issuer_key_hash;
+ SECItem serial_number;
+};
+
+const SEC_ASN1Template kCertIDTemplate[] = {
+ { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CertID) },
+ { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(CertID, hash_algorithm),
+ SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
+ { SEC_ASN1_OCTET_STRING, offsetof(CertID, issuer_name_hash) },
+ { SEC_ASN1_OCTET_STRING, offsetof(CertID, issuer_key_hash) },
+ { SEC_ASN1_INTEGER, offsetof(CertID, serial_number) },
+ { 0 }
+};
+
+// SingleResponse ::= SEQUENCE {
+// certID CertID,
+// certStatus CertStatus,
+// thisUpdate GeneralizedTime,
+// nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL,
+// singleExtensions [1] EXPLICIT Extensions OPTIONAL }
+struct SingleResponse {
+ CertID cert_id;
+ // The following three fields are not used.
+ SECItem der_cert_status;
+ SECItem this_update;
+ SECItem next_update;
+ CERTCertExtension** single_extensions;
+};
+
+const SEC_ASN1Template kSingleResponseTemplate[] = {
+ { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SingleResponse) },
+ { SEC_ASN1_INLINE, offsetof(SingleResponse, cert_id), kCertIDTemplate },
+ // Really a CHOICE but we make it an ANY because we don't care about the
+ // contents of this field.
+ // TODO(ekasper): use SEC_ASN1_CHOICE.
+ { SEC_ASN1_ANY, offsetof(SingleResponse, der_cert_status) },
+ { SEC_ASN1_GENERALIZED_TIME, offsetof(SingleResponse, this_update) },
+ { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT |
+ SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0,
+ offsetof(SingleResponse, next_update),
+ SEC_ASN1_SUB(SEC_GeneralizedTimeTemplate) },
+ { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED |
+ SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 1,
+ offsetof(SingleResponse, single_extensions),
+ SEC_ASN1_SUB(CERT_SequenceOfCertExtensionTemplate) },
+ { 0 }
+};
+
+// ResponseData ::= SEQUENCE {
+// version [0] EXPLICIT Version DEFAULT v1,
+// responderID ResponderID,
+// producedAt GeneralizedTime,
+// responses SEQUENCE OF SingleResponse,
+// responseExtensions [1] EXPLICIT Extensions OPTIONAL }
+struct ResponseData {
+ // The first three fields are not used.
+ SECItem version;
+ SECItem der_responder_id;
+ SECItem produced_at;
+ SingleResponse** single_responses;
+ // Skip extensions.
+};
+
+const SEC_ASN1Template kResponseDataTemplate[] = {
+ { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(ResponseData) },
+ { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED |
+ SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0,
+ offsetof(ResponseData, version), SEC_ASN1_SUB(SEC_IntegerTemplate) },
+ // Really a CHOICE but we make it an ANY because we don't care about the
+ // contents of this field.
+ // TODO(ekasper): use SEC_ASN1_CHOICE.
+ { SEC_ASN1_ANY, offsetof(ResponseData, der_responder_id) },
+ { SEC_ASN1_GENERALIZED_TIME, offsetof(ResponseData, produced_at) },
+ { SEC_ASN1_SEQUENCE_OF, offsetof(ResponseData, single_responses),
+ kSingleResponseTemplate },
+ { SEC_ASN1_SKIP_REST },
+ { 0 }
+};
+
+// BasicOCSPResponse ::= SEQUENCE {
+// tbsResponseData ResponseData,
+// signatureAlgorithm AlgorithmIdentifier,
+// signature BIT STRING,
+// certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
+struct BasicOCSPResponse {
+ ResponseData tbs_response_data;
+ // We do not care about the rest.
+};
+
+const SEC_ASN1Template kBasicOCSPResponseTemplate[] = {
+ { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(BasicOCSPResponse) },
+ { SEC_ASN1_INLINE, offsetof(BasicOCSPResponse, tbs_response_data),
+ kResponseDataTemplate },
+ { SEC_ASN1_SKIP_REST },
+ { 0 }
+};
+
+bool StringEqualToSECItem(const std::string& value1, const SECItem& value2) {
+ if (value1.size() != value2.len)
+ return false;
+ return memcmp(value1.data(), value2.data, value2.len) == 0;
+}
+
+// TODO(ekasper): also use the issuer name hash in matching.
+bool CertIDMatches(const CertID& cert_id,
+ const std::string& serial_number,
+ const std::string& issuer_key_sha1_hash,
+ const std::string& issuer_key_sha256_hash) {
+ if (!StringEqualToSECItem(serial_number, cert_id.serial_number))
+ return false;
+
+ SECOidTag hash_alg = SECOID_FindOIDTag(&cert_id.hash_algorithm.algorithm);
+ switch (hash_alg) {
+ case SEC_OID_SHA1:
+ return StringEqualToSECItem(issuer_key_sha1_hash,
+ cert_id.issuer_key_hash);
+ case SEC_OID_SHA256:
+ return StringEqualToSECItem(issuer_key_sha256_hash,
+ cert_id.issuer_key_hash);
+ default:
+ return false;
+ }
+}
+
} // namespace
bool ExtractEmbeddedSCTList(X509Certificate::OSCertHandle cert,
@@ -190,8 +433,9 @@ bool ExtractEmbeddedSCTList(X509Certificate::OSCertHandle cert,
if (!leaf_cert.cert)
return false;
- return GetOctetStringExtension(
- leaf_cert.cert.get(), g_ct_singleton.Get().embedded_oid(), sct_list);
+ return GetCertOctetStringExtension(leaf_cert.cert.get(),
+ g_ct_singleton.Get().embedded_oid(),
+ sct_list);
}
bool GetPrecertLogEntry(X509Certificate::OSCertHandle leaf,
@@ -269,6 +513,105 @@ bool GetX509LogEntry(X509Certificate::OSCertHandle leaf, LogEntry* result) {
return true;
}
+bool ExtractSCTListFromOCSPResponse(X509Certificate::OSCertHandle issuer,
+ const std::string& cert_serial_number,
+ const std::string& ocsp_response,
+ std::string* sct_list) {
+ DCHECK(issuer);
+
+ // Any OCSP response is unlikely to be even close to 2^24 bytes; further, CT
+ // only uses stapled OCSP responses which have this limit imposed by the TLS
+ // protocol.
+ if (ocsp_response.size() > 0xffffff)
+ return false;
+
+ crypto::ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+
+ OCSPResponse response;
+ memset(&response, 0, sizeof(response));
+
+ SECItem src = { siBuffer,
+ reinterpret_cast<unsigned char*>(const_cast<char*>(
+ ocsp_response.data())),
+ static_cast<unsigned int>(ocsp_response.size()) };
+
+ // |response| will point directly into |src|, so it's not necessary to
+ // free the |response| contents, but they may only be used while |src|
+ // is valid (i.e., in this method).
+ SECStatus rv = SEC_QuickDERDecodeItem(arena.get(), &response,
+ kOCSPResponseTemplate, &src);
+ if (rv != SECSuccess)
+ return false;
+
+ if (!response.response_bytes)
+ return false;
+
+ if (!SECITEM_ItemsAreEqual(&kBasicOCSPResponseOidItem,
+ &response.response_bytes->response_type)) {
+ return false;
+ }
+
+ BasicOCSPResponse basic_response;
+ memset(&basic_response, 0, sizeof(basic_response));
+
+ rv = SEC_QuickDERDecodeItem(arena.get(), &basic_response,
+ kBasicOCSPResponseTemplate,
+ &response.response_bytes->der_response);
+ if (rv != SECSuccess)
+ return false;
+
+ SingleResponse** responses =
+ basic_response.tbs_response_data.single_responses;
+ if (!responses)
+ return false;
+
+ std::string issuer_der;
+ if (!X509Certificate::GetDEREncoded(issuer, &issuer_der))
+ return false;
+
+ base::StringPiece issuer_spki;
+ if (!asn1::ExtractSPKIFromDERCert(issuer_der, &issuer_spki))
+ return false;
+
+ // In OCSP, only the key itself is under hash.
+ base::StringPiece issuer_spk;
+ if (!asn1::ExtractSubjectPublicKeyFromSPKI(issuer_spki, &issuer_spk))
+ return false;
+
+ // ExtractSubjectPublicKey... does not remove the initial octet encoding the
+ // number of unused bits in the ASN.1 BIT STRING so we do it here. For public
+ // keys, the bitstring is in practice always byte-aligned.
+ if (issuer_spk.empty() || issuer_spk[0] != 0)
+ return false;
+ issuer_spk.remove_prefix(1);
+
+ // NSS OCSP lib recognizes SHA1, MD5 and MD2; MD5 and MD2 are dead but
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=663315 will add SHA-256
+ // and SHA-384.
+ // TODO(ekasper): add SHA-384 to crypto/sha2.h and here if it proves
+ // necessary.
+ // TODO(ekasper): only compute the hashes on demand.
+ std::string issuer_key_sha256_hash = crypto::SHA256HashString(issuer_spk);
+ std::string issuer_key_sha1_hash = base::SHA1HashString(
+ issuer_spk.as_string());
+
+ const SingleResponse* match = NULL;
+ for (const SingleResponse* const* resps = responses; *resps; ++resps) {
+ const SingleResponse* resp = *resps;
+ if (CertIDMatches(resp->cert_id, cert_serial_number,
+ issuer_key_sha1_hash, issuer_key_sha256_hash)) {
+ match = resp;
+ break;
+ }
+ }
+
+ if (!match)
+ return false;
+
+ return GetSCTListFromOCSPExtension(arena.get(), match->single_extensions,
+ sct_list);
+}
+
} // namespace ct
} // namespace net
« no previous file with comments | « net/cert/ct_objects_extractor.h ('k') | net/cert/ct_objects_extractor_openssl.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698