This source file includes following definitions.
- SEC_ASN1_MKSUB
- embedded_oid
- RegisterOid
- GetCertOctetStringExtension
- GetSCTListFromOCSPExtension
- ExtractTBSCertWithoutSCTs
- StringEqualToSECItem
- CertIDMatches
- ExtractEmbeddedSCTList
- GetPrecertLogEntry
- GetX509LogEntry
- ExtractSCTListFromOCSPResponse
#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 "base/sha1.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 {
SEC_ASN1_MKSUB(SEC_IntegerTemplate)
SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate)
SEC_ASN1_MKSUB(SEC_GeneralizedTimeTemplate)
SEC_ASN1_MKSUB(CERT_SequenceOfCertExtensionTemplate)
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();
cert.reset(CERT_NewTempCertificate(
CERT_GetDefaultCertDB(), &der_cert, NULL, PR_FALSE, PR_TRUE));
#endif
DCHECK(cert.get() != NULL);
}
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";
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)
};
const unsigned char kBasicOCSPResponseOid[] = {0x2B, 0x06, 0x01, 0x05, 0x05,
0x07, 0x30, 0x01, 0x01};
const SECItem kBasicOCSPResponseOidItem = {
siBuffer, const_cast<unsigned char*>(kBasicOCSPResponseOid),
sizeof(kBasicOCSPResponseOid)
};
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;
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;
bool GetCertOctetStringExtension(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) ||
raw_data.size() > 0) {
rv = SECFailure;
} else {
parsed_data.CopyToString(extension_data);
}
SECITEM_FreeItem(&extension, PR_FALSE);
return rv == SECSuccess;
}
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;
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;
}
bool ExtractTBSCertWithoutSCTs(CERTCertificate* cert,
std::string* to_be_signed) {
SECOidData* oid = SECOID_FindOIDByTag(g_ct_singleton.Get().embedded_oid());
if (!oid)
return false;
CERTCertificate temp_cert;
temp_cert = *cert;
temp_cert.extensions = NULL;
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);
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<char*>(tbs_data.data), tbs_data.len);
return true;
}
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 }
};
struct OCSPResponse {
SECItem response_status;
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 }
};
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 }
};
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 },
{ 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 }
};
struct ResponseData {
SECItem version;
SECItem der_responder_id;
SECItem produced_at;
SingleResponse** single_responses;
};
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) },
{ 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 }
};
struct BasicOCSPResponse {
ResponseData tbs_response_data;
};
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;
}
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;
}
}
}
bool ExtractEmbeddedSCTList(X509Certificate::OSCertHandle cert,
std::string* sct_list) {
DCHECK(cert);
NSSCertWrapper leaf_cert(cert);
if (!leaf_cert.cert)
return false;
return GetCertOctetStringExtension(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();
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) {
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<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);
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;
}
bool ExtractSCTListFromOCSPResponse(X509Certificate::OSCertHandle issuer,
const std::string& cert_serial_number,
const std::string& ocsp_response,
std::string* sct_list) {
DCHECK(issuer);
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()) };
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;
base::StringPiece issuer_spk;
if (!asn1::ExtractSubjectPublicKeyFromSPKI(issuer_spki, &issuer_spk))
return false;
if (issuer_spk.empty() || issuer_spk[0] != 0)
return false;
issuer_spk.remove_prefix(1);
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);
}
}
}