| // 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 <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" |
| #include "net/ssl/scoped_openssl_types.h" |
| |
| namespace net { |
| |
| namespace ct { |
| |
| namespace { |
| |
| void FreeX509_EXTENSIONS(X509_EXTENSIONS* ptr) { |
| sk_X509_EXTENSION_pop_free(ptr, X509_EXTENSION_free); |
| } |
| |
| using ScopedX509_EXTENSIONS = |
| crypto::ScopedOpenSSL<X509_EXTENSIONS, FreeX509_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) { |
| 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) { |
| 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*>(base::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 false; |
| 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) { |
| 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) { |
| // 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 |
| |
| } // namespace net |