summaryrefslogtreecommitdiffstats
path: root/net/cert
diff options
context:
space:
mode:
authordavidben <davidben@chromium.org>2014-09-03 17:25:50 -0700
committerCommit bot <commit-bot@chromium.org>2014-09-04 00:34:46 +0000
commit8ea27e621946a33a7337ef84c2307c3dfa0eb4db (patch)
tree2f52f05ef737b42295267f4c2b0a9261ecbb9806 /net/cert
parent3299e1cf26232b831441987ae90d442b21ab6def (diff)
downloadchromium_src-8ea27e621946a33a7337ef84c2307c3dfa0eb4db.zip
chromium_src-8ea27e621946a33a7337ef84c2307c3dfa0eb4db.tar.gz
chromium_src-8ea27e621946a33a7337ef84c2307c3dfa0eb4db.tar.bz2
Implement ct_objects_extractor for OpenSSL.
With this, CTObjectsExtractorTest and MultiLogCTVerifierTest pass on OpenSSL builds. BUG=408687 Review URL: https://codereview.chromium.org/519473002 Cr-Commit-Position: refs/heads/master@{#293235}
Diffstat (limited to 'net/cert')
-rw-r--r--net/cert/ct_objects_extractor_openssl.cc339
1 files changed, 331 insertions, 8 deletions
diff --git a/net/cert/ct_objects_extractor_openssl.cc b/net/cert/ct_objects_extractor_openssl.cc
index f6bd8b0..ccc94fe 100644
--- a/net/cert/ct_objects_extractor_openssl.cc
+++ b/net/cert/ct_objects_extractor_openssl.cc
@@ -4,36 +4,359 @@
#include "net/cert/ct_objects_extractor.h"
+#include <string.h>
+
+#include <openssl/bytestring.h>
+#include <openssl/obj.h>
+#include <openssl/x509.h>
+
#include "base/logging.h"
+#include "base/sha1.h"
+#include "base/strings/string_util.h"
+#include "crypto/scoped_openssl_types.h"
+#include "crypto/sha2.h"
+#include "net/cert/asn1_util.h"
+#include "net/cert/signed_certificate_timestamp.h"
namespace net {
namespace ct {
+namespace {
+
+typedef crypto::ScopedOpenSSL<X509, X509_free>::Type ScopedX509;
+
+void FreeX509_EXTENSIONS(X509_EXTENSIONS* ptr) {
+ sk_X509_EXTENSION_pop_free(ptr, X509_EXTENSION_free);
+}
+
+typedef crypto::ScopedOpenSSL<X509_EXTENSIONS, FreeX509_EXTENSIONS>::Type
+ ScopedX509_EXTENSIONS;
+
+// The wire form of the OID 1.3.6.1.4.1.11129.2.4.2. See Section 3.3 of
+// RFC6962.
+const uint8_t kEmbeddedSCTOid[] = {0x2B, 0x06, 0x01, 0x04, 0x01, 0xD6, 0x79,
+ 0x02, 0x04, 0x02};
+
+// 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 uint8_t kOCSPExtensionOid[] = {0x2B, 0x06, 0x01, 0x04, 0x01, 0xD6, 0x79,
+ 0x02, 0x04, 0x05};
+
+bool StringEqualToCBS(const std::string& value1, const CBS* value2) {
+ if (CBS_len(value2) != value1.size())
+ return false;
+ return memcmp(value1.data(), CBS_data(value2), CBS_len(value2)) == 0;
+}
+
+ScopedX509 OSCertHandleToOpenSSL(X509Certificate::OSCertHandle os_handle) {
+#if defined(USE_OPENSSL_CERTS)
+ return ScopedX509(X509Certificate::DupOSCertHandle(os_handle));
+#else
+ std::string der_encoded;
+ if (!X509Certificate::GetDEREncoded(os_handle, &der_encoded))
+ return ScopedX509();
+ const uint8_t* bytes = reinterpret_cast<const uint8_t*>(der_encoded.data());
+ return ScopedX509(d2i_X509(NULL, &bytes, der_encoded.size()));
+#endif
+}
+
+// Finds the SignedCertificateTimestampList in an extension with OID |oid| in
+// |x509_exts|. If found, returns true and sets |*out_sct_list| to the encoded
+// SCT list. |out_sct_list| may be NULL.
+bool GetSCTListFromX509_EXTENSIONS(const X509_EXTENSIONS* x509_exts,
+ const uint8_t* oid,
+ size_t oid_len,
+ std::string* out_sct_list) {
+ for (size_t i = 0; i < sk_X509_EXTENSION_num(x509_exts); i++) {
+ X509_EXTENSION* x509_ext = sk_X509_EXTENSION_value(x509_exts, i);
+ if (static_cast<size_t>(x509_ext->object->length) == oid_len &&
+ memcmp(x509_ext->object->data, oid, oid_len) == 0) {
+ // The SCT list is an OCTET STRING inside the extension.
+ CBS ext_value, sct_list;
+ CBS_init(&ext_value, x509_ext->value->data, x509_ext->value->length);
+ if (!CBS_get_asn1(&ext_value, &sct_list, CBS_ASN1_OCTETSTRING) ||
+ CBS_len(&ext_value) != 0) {
+ return false;
+ }
+ if (out_sct_list) {
+ *out_sct_list = std::string(
+ reinterpret_cast<const char*>(CBS_data(&sct_list)),
+ CBS_len(&sct_list));
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+// Finds the SingleResponse in |responses| which matches |issuer| and
+// |cert_serial_number|. On success, returns true and sets
+// |*out_single_response| to the body of the SingleResponse starting at the
+// |certStatus| field.
+bool FindMatchingSingleResponse(CBS* responses,
+ X509Certificate::OSCertHandle issuer,
+ const std::string& cert_serial_number,
+ CBS* out_single_response) {
+ 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);
+
+ // 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());
+
+ while (CBS_len(responses) > 0) {
+ CBS single_response, cert_id;
+ if (!CBS_get_asn1(responses, &single_response, CBS_ASN1_SEQUENCE) ||
+ !CBS_get_asn1(&single_response, &cert_id, CBS_ASN1_SEQUENCE)) {
+ return false;
+ }
+
+ CBS hash_algorithm, hash, serial_number, issuer_name_hash, issuer_key_hash;
+ if (!CBS_get_asn1(&cert_id, &hash_algorithm, CBS_ASN1_SEQUENCE) ||
+ !CBS_get_asn1(&hash_algorithm, &hash, CBS_ASN1_OBJECT) ||
+ !CBS_get_asn1(&cert_id, &issuer_name_hash, CBS_ASN1_OCTETSTRING) ||
+ !CBS_get_asn1(&cert_id, &issuer_key_hash, CBS_ASN1_OCTETSTRING) ||
+ !CBS_get_asn1(&cert_id, &serial_number, CBS_ASN1_INTEGER) ||
+ CBS_len(&cert_id) != 0) {
+ return false;
+ }
+
+ // Check the serial number matches.
+ if (!StringEqualToCBS(cert_serial_number, &serial_number))
+ continue;
+
+ // Check if the issuer_key_hash matches.
+ // TODO(ekasper): also use the issuer name hash in matching.
+ switch (OBJ_cbs2nid(&hash)) {
+ case NID_sha1:
+ if (StringEqualToCBS(issuer_key_sha1_hash, &issuer_key_hash)) {
+ *out_single_response = single_response;
+ return true;
+ }
+ break;
+ case NID_sha256:
+ if (StringEqualToCBS(issuer_key_sha256_hash, &issuer_key_hash)) {
+ *out_single_response = single_response;
+ return true;
+ }
+ break;
+ }
+ }
+
+ return false;
+}
+
+} // namespace
+
bool ExtractEmbeddedSCTList(X509Certificate::OSCertHandle cert,
std::string* sct_list) {
- NOTIMPLEMENTED();
- return false;
+ ScopedX509 x509(OSCertHandleToOpenSSL(cert));
+ if (!x509)
+ return false;
+ X509_EXTENSIONS* x509_exts = x509->cert_info->extensions;
+ if (!x509_exts)
+ return false;
+ return GetSCTListFromX509_EXTENSIONS(
+ x509->cert_info->extensions, kEmbeddedSCTOid, sizeof(kEmbeddedSCTOid),
+ sct_list);
}
bool GetPrecertLogEntry(X509Certificate::OSCertHandle leaf,
X509Certificate::OSCertHandle issuer,
LogEntry* result) {
- NOTIMPLEMENTED();
- return false;
+ result->Reset();
+
+ ScopedX509 leaf_x509(OSCertHandleToOpenSSL(leaf));
+ if (!leaf_x509)
+ return false;
+
+ // 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.
+ if (!leaf_x509->cert_info->extensions ||
+ !GetSCTListFromX509_EXTENSIONS(leaf_x509->cert_info->extensions,
+ kEmbeddedSCTOid, sizeof(kEmbeddedSCTOid),
+ NULL)) {
+ return false;
+ }
+
+ // The Precertificate log entry is the final certificate's TBSCertificate
+ // without the SCT extension (RFC6962, section 3.2).
+ ScopedX509 leaf_copy(X509_dup(leaf_x509.get()));
+ if (!leaf_copy || !leaf_copy->cert_info->extensions) {
+ NOTREACHED();
+ return false;
+ }
+ X509_EXTENSIONS* leaf_copy_exts = leaf_copy->cert_info->extensions;
+ for (size_t i = 0; i < sk_X509_EXTENSION_num(leaf_copy_exts); i++) {
+ X509_EXTENSION* ext = sk_X509_EXTENSION_value(leaf_copy_exts, i);
+ if (static_cast<size_t>(ext->object->length) == sizeof(kEmbeddedSCTOid) &&
+ memcmp(ext->object->data,
+ kEmbeddedSCTOid, sizeof(kEmbeddedSCTOid)) == 0) {
+ X509_EXTENSION_free(sk_X509_EXTENSION_delete(leaf_copy_exts, i));
+ X509_CINF_set_modified(leaf_copy->cert_info);
+ break;
+ }
+ }
+
+ std::string to_be_signed;
+ int len = i2d_X509_CINF(leaf_copy->cert_info, NULL);
+ if (len < 0)
+ return false;
+ uint8_t* ptr = reinterpret_cast<uint8_t*>(WriteInto(&to_be_signed, len + 1));
+ if (i2d_X509_CINF(leaf_copy->cert_info, &ptr) < 0)
+ return false;
+
+ // Extract the issuer's public key.
+ std::string issuer_der;
+ if (!X509Certificate::GetDEREncoded(issuer, &issuer_der))
+ return ScopedX509();
+ base::StringPiece issuer_key;
+ if (!asn1::ExtractSPKIFromDERCert(issuer_der, &issuer_key))
+ return false;
+
+ // Fill in the LogEntry.
+ result->type = ct::LogEntry::LOG_ENTRY_TYPE_PRECERT;
+ result->tbs_certificate.swap(to_be_signed);
+ crypto::SHA256HashString(issuer_key,
+ result->issuer_key_hash.data,
+ sizeof(result->issuer_key_hash.data));
+
+ return true;
}
bool GetX509LogEntry(X509Certificate::OSCertHandle leaf, LogEntry* result) {
- NOTIMPLEMENTED();
- return false;
+ 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) {
- NOTIMPLEMENTED();
- return false;
+ // The input is an OCSPResponse. See RFC2560, section 4.2.1. The SCT list is
+ // in the extensions field of the SingleResponse which matches the input
+ // certificate.
+ CBS cbs;
+ CBS_init(&cbs,
+ reinterpret_cast<const uint8_t*>(ocsp_response.data()),
+ ocsp_response.size());
+
+ // Parse down to the ResponseBytes. The ResponseBytes is optional, but if it's
+ // missing, this can't include an SCT list.
+ CBS sequence, response_status, tagged_response_bytes, response_bytes;
+ CBS response_type, response;
+ if (!CBS_get_asn1(&cbs, &sequence, CBS_ASN1_SEQUENCE) ||
+ CBS_len(&cbs) != 0 ||
+ !CBS_get_asn1(&sequence, &response_status, CBS_ASN1_ENUMERATED) ||
+ !CBS_get_asn1(&sequence, &tagged_response_bytes,
+ CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0) ||
+ CBS_len(&sequence) != 0 ||
+ !CBS_get_asn1(&tagged_response_bytes, &response_bytes,
+ CBS_ASN1_SEQUENCE) ||
+ CBS_len(&tagged_response_bytes) != 0 ||
+ !CBS_get_asn1(&response_bytes, &response_type, CBS_ASN1_OBJECT) ||
+ !CBS_get_asn1(&response_bytes, &response, CBS_ASN1_OCTETSTRING) ||
+ CBS_len(&response_bytes) != 0) {
+ return false;
+ }
+
+ // The only relevant ResponseType is id-pkix-ocsp-basic.
+ if (OBJ_cbs2nid(&response_type) != NID_id_pkix_OCSP_basic)
+ return false;
+
+ // Parse the ResponseData out of the BasicOCSPResponse. Ignore the rest.
+ CBS basic_response, response_data, responses;
+ if (!CBS_get_asn1(&response, &basic_response, CBS_ASN1_SEQUENCE) ||
+ CBS_len(&response) != 0 ||
+ !CBS_get_asn1(&basic_response, &response_data, CBS_ASN1_SEQUENCE)) {
+ }
+
+ // Skip the optional version.
+ const int kVersionTag =
+ CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0;
+ if (CBS_len(&response_data) > 0 &&
+ CBS_data(&response_data)[0] == kVersionTag &&
+ !CBS_get_asn1(&response_data, NULL /* version */, kVersionTag)) {
+ return false;
+ }
+
+ // Extract the list of SingleResponses.
+ if (!CBS_get_any_asn1_element(&response_data,
+ NULL /* responderID */, NULL, NULL) ||
+ !CBS_get_any_asn1_element(&response_data,
+ NULL /* producedAt */, NULL, NULL) ||
+ !CBS_get_asn1(&response_data, &responses, CBS_ASN1_SEQUENCE)) {
+ return false;
+ }
+
+ CBS single_response;
+ if (!FindMatchingSingleResponse(&responses, issuer, cert_serial_number,
+ &single_response)) {
+ return false;
+ }
+
+ // Skip the certStatus and thisUpdate fields.
+ if (!CBS_get_any_asn1_element(&single_response,
+ NULL /* certStatus */, NULL, NULL) ||
+ !CBS_get_any_asn1_element(&single_response,
+ NULL /* thisUpdate */, NULL, NULL)) {
+ return false;
+ }
+
+ const int kNextUpdateTag =
+ CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0;
+ const int kSingleExtensionsTag =
+ CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 1;
+
+ // Skip the optional nextUpdate field.
+ if (CBS_len(&single_response) > 0 &&
+ CBS_data(&single_response)[0] == kNextUpdateTag &&
+ !CBS_get_asn1(&single_response, NULL /* nextUpdate */, kNextUpdateTag)) {
+ return false;
+ }
+
+ CBS extensions;
+ if (!CBS_get_asn1(&single_response, &extensions, kSingleExtensionsTag))
+ return false;
+ const uint8_t* ptr = CBS_data(&extensions);
+ ScopedX509_EXTENSIONS x509_exts(
+ d2i_X509_EXTENSIONS(NULL, &ptr, CBS_len(&extensions)));
+ if (!x509_exts || ptr != CBS_data(&extensions) + CBS_len(&extensions))
+ return false;
+
+ return GetSCTListFromX509_EXTENSIONS(
+ x509_exts.get(), kOCSPExtensionOid, sizeof(kOCSPExtensionOid), sct_list);
}
} // namespace ct