Chromium Code Reviews| 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..2e0fd9f2bed166895a20748694d4004ac0fbe7f3 100644 |
| --- a/net/cert/ct_objects_extractor_nss.cc |
| +++ b/net/cert/ct_objects_extractor_nss.cc |
| @@ -6,10 +6,13 @@ |
| #include <cert.h> |
| #include <secasn1.h> |
| +#include <secasn1t.h> |
| +#include <secdert.h> |
| #include <secitem.h> |
| #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" |
| @@ -62,18 +65,32 @@ 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.2. See Section 3.3 of |
| +// RFC6962. |
| +const unsigned char kOCSPExtensionOid[] = {0x2B, 0x06, 0x01, 0x04, 0x01, 0xD6, |
| + 0x79, 0x02, 0x04, 0x05}; |
| + |
| +const char kOCSPExtensionDescription[] = |
| + "OCSP SingleExtension for X.509v3 Certificate Transparency Signed " |
| + "Certificate Timestamp List "; |
| + |
| // Initializes the necessary NSS internals for use with Certificate |
| // Transparency. |
| class CTInitSingleton { |
| public: |
| SECOidTag embedded_oid() const { return embedded_oid_; } |
| + SECOidTag ocsp_extension_oid() const { return ocsp_extension_oid_; } |
| private: |
| friend struct base::DefaultLazyInstanceTraits<CTInitSingleton>; |
| - CTInitSingleton() : embedded_oid_(SEC_OID_UNKNOWN) { |
| + CTInitSingleton() : embedded_oid_(SEC_OID_UNKNOWN), |
| + ocsp_extension_oid_(SEC_OID_UNKNOWN) { |
| embedded_oid_ = RegisterOid( |
| kEmbeddedSCTOid, sizeof(kEmbeddedSCTOid), kEmbeddedSCTDescription); |
| + ocsp_extension_oid_ = RegisterOid( |
| + kOCSPExtensionOid, sizeof(kOCSPExtensionOid), |
| + kOCSPExtensionDescription); |
| } |
| ~CTInitSingleton() {} |
| @@ -100,6 +117,7 @@ class CTInitSingleton { |
| } |
| SECOidTag embedded_oid_; |
| + SECOidTag ocsp_extension_oid_; |
| DISALLOW_COPY_AND_ASSIGN(CTInitSingleton); |
| }; |
| @@ -180,6 +198,175 @@ 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. |
| + |
| +// Black magic to get the address of the externally defined template at runtime. |
| +SEC_ASN1_MKSUB(SEC_IntegerTemplate) |
| +SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate) |
| +SEC_ASN1_MKSUB(SEC_GeneralizedTimeTemplate) |
| + |
| +// ResponseBytes ::= SEQUENCE { |
| +// responseType OBJECT IDENTIFIER, |
| +// response OCTET STRING } |
| +struct ResponseBytes { |
|
Ryan Sleevi
2013/12/04 20:30:26
nit: OcspResponseBytes? OCSPResponseBytes?
ekasper
2013/12/05 16:26:48
It's ResponseBytes in the RFC so I'd keep it; ther
|
| + 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; |
| + 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 }, |
| + // ANY because (a) NSS ASN.1 doesn't support automatic CHOICE and |
| + // (b) we don't care about the contents of this field anyway. |
| + { 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 | 1, |
| + offsetof(SingleResponse, single_extensions), |
| + 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 { |
| + 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) }, |
| + // ANY because (a) NSS ASN.1 doesn't support automatic CHOICE and |
| + // (b) we don't care about the contents of this field anyway. |
| + { 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 CertIDsMatch(const std::string& serial_number, |
| + const std::string& issuer_key_sha1_hash, |
| + const CertID& cert_id) { |
| + if (serial_number.size() != cert_id.serial_number.len || |
| + memcmp(serial_number.data(), cert_id.serial_number.data, |
| + serial_number.size()) != 0) { |
| + return false; |
| + } |
| + |
| + SECOidTag hash_alg = SECOID_FindOIDTag(&cert_id.hash_algorithm.algorithm); |
| + if (hash_alg != SEC_OID_SHA1) |
| + return false; |
| + |
| + if (issuer_key_sha1_hash.size() != cert_id.issuer_key_hash.len || |
| + memcmp(issuer_key_sha1_hash.data(), cert_id.issuer_key_hash.data, |
| + issuer_key_sha1_hash.size()) != 0) { |
| + return false; |
| + } |
| + |
| + return true; |
| +} |
| + |
| } // namespace |
| bool ExtractEmbeddedSCTList(X509Certificate::OSCertHandle cert, |
| @@ -269,6 +456,94 @@ bool GetX509LogEntry(X509Certificate::OSCertHandle leaf, LogEntry* result) { |
| return true; |
| } |
| +bool ExtractSCTListFromOCSPResponse(const std::string& cert_serial_number, |
| + X509Certificate::OSCertHandle issuer, |
| + const std::string& ocsp_response, |
| + std::string* sct_list) { |
| + DCHECK(issuer); |
| + sct_list->clear(); |
|
Ryan Sleevi
2013/12/04 20:30:26
Just as a minor nit, our normal pattern in net for
ekasper
2013/12/05 16:26:48
Removed the clear() and GetOctetStringExtension do
|
| + crypto::ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); |
| + |
| + // QuickDERDecodeItem returns data that points into |src|. This is |
| + // ok for us as the decoded items never leave the method scope. |
| + SECItem src = { siBuffer, |
| + reinterpret_cast<unsigned char*>(const_cast<char*>( |
| + ocsp_response.data())), ocsp_response.size() }; |
| + |
| + OCSPResponse response; |
| + memset(&response, 0, sizeof(response)); |
| + |
| + SECStatus rv = SEC_QuickDERDecodeItem(arena.get(), &response, |
| + kOCSPResponseTemplate, &src); |
| + if (rv != SECSuccess) |
| + return false; |
| + |
| + // id-ad-ocsp: 1.3.6.1.5.5.7.48.1.1 |
| + static const uint8 kBasicOCSPResponseOID[] = { 0x2b, 0x06, 0x01, 0x05, 0x05, |
| + 0x07, 0x30, 0x01, 0x01 }; |
| + |
| + if (!response.response_bytes) |
| + return false; |
| + |
| + if (response.response_bytes->response_type.len != |
| + sizeof(kBasicOCSPResponseOID) || |
| + memcmp(response.response_bytes->response_type.data, kBasicOCSPResponseOID, |
| + sizeof(kBasicOCSPResponseOID)) != 0) { |
| + 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 == NULL) |
| + return false; |
| + |
| + NSSCertWrapper issuer_cert(issuer); |
| + if (!issuer_cert.cert) |
| + return false; |
|
Ryan Sleevi
2013/12/04 20:30:26
If |issuer| has been verified as trusted already (
ekasper
2013/12/05 16:26:48
Right, I forgot about GetDEREncoded. Done, even th
|
| + |
| + // Cert_GetSPKIDigest exists but is not exported so we do it by hand. |
| + // In OCSP, only the key itself is under hash. |
| + SECItem spk = issuer_cert.cert->subjectPublicKeyInfo.subjectPublicKey; |
| + DER_ConvertBitString(&spk); |
| + |
| + // NSS OCSP lib recognizes SHA1, MD5 and MD2 here, so seems we can restrict |
| + // ourselves to SHA1. |
| + std::string issuer_key_sha1_hash = base::SHA1HashString( |
| + std::string(reinterpret_cast<char*>(spk.data), spk.len)); |
|
Ryan Sleevi
2013/12/04 20:30:26
This may/will change soon ( https://bugzilla.mozil
ekasper
2013/12/05 16:26:48
Aha. Added SHA-256. I hope this code will be comin
|
| + |
| + SingleResponse* match = NULL; |
| + for (int i = 0; responses[i] != NULL; ++i) { |
| + if (CertIDsMatch(cert_serial_number, issuer_key_sha1_hash, |
| + responses[i]->cert_id)) { |
| + match = responses[i]; |
| + break; |
| + } |
| + } |
| + |
| + if (match == NULL) |
|
Ryan Sleevi
2013/12/04 20:30:26
dominant style in net/ and base is
if (!match)
ekasper
2013/12/05 16:26:48
Done.
|
| + return false; |
| + |
| + // Hack: pretend we are a certificate to find the appropriate extension. |
| + CERTCertificate wrap_cert; |
| + memset(&wrap_cert, 0, sizeof(wrap_cert)); |
| + // Shouldn't be needed, but just in case... |
| + wrap_cert.arena = arena.get(); |
| + wrap_cert.extensions = match->single_extensions; |
|
Ryan Sleevi
2013/12/04 20:30:26
I'm really nervous about this, even though it will
ekasper
2013/12/05 16:26:48
I have to admit that, at that point yesterday, I w
|
| + |
| + return GetOctetStringExtension(&wrap_cert, |
| + g_ct_singleton.Get().ocsp_extension_oid(), |
| + sct_list); |
| +} |
| + |
| } // namespace ct |
| } // namespace net |