OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "net/cert/ct_objects_extractor.h" |
| 6 |
| 7 #include <cert.h> |
| 8 #include <secasn1.h> |
| 9 #include <secitem.h> |
| 10 #include <secoid.h> |
| 11 |
| 12 #include "base/lazy_instance.h" |
| 13 #include "crypto/scoped_nss_types.h" |
| 14 #include "crypto/sha2.h" |
| 15 #include "net/cert/asn1_util.h" |
| 16 #include "net/cert/signed_certificate_timestamp.h" |
| 17 |
| 18 namespace net { |
| 19 |
| 20 namespace ct { |
| 21 |
| 22 namespace { |
| 23 |
| 24 struct FreeCERTCertificate { |
| 25 public: |
| 26 inline void operator()(CERTCertificate* x) const { |
| 27 CERT_DestroyCertificate(x); |
| 28 } |
| 29 }; |
| 30 |
| 31 typedef scoped_ptr_malloc<CERTCertificate, FreeCERTCertificate> |
| 32 ScopedCERTCertificate; |
| 33 |
| 34 // Wrapper class to convert a X509Certificate::OSCertHandle directly |
| 35 // into a CERTCertificate* usable with other NSS functions. This is used for |
| 36 // systems where X509Certificate::OSCertHandle refers to a different type |
| 37 // than a CERTCertificate*. |
| 38 struct NSSCertWrapper { |
| 39 explicit NSSCertWrapper(X509Certificate::OSCertHandle cert_handle); |
| 40 ~NSSCertWrapper() {} |
| 41 |
| 42 ScopedCERTCertificate cert; |
| 43 }; |
| 44 |
| 45 NSSCertWrapper::NSSCertWrapper(X509Certificate::OSCertHandle cert_handle) { |
| 46 #if defined(USE_NSS) |
| 47 cert.reset(CERT_DupCertificate(cert_handle)); |
| 48 #else |
| 49 SECItem der_cert; |
| 50 std::string der_data; |
| 51 if (!X509Certificate::GetDEREncoded(cert_handle, &der_data)) |
| 52 return; |
| 53 der_cert.data = reinterpret_cast<unsigned char*>( |
| 54 const_cast<char*>(der_data.data())); |
| 55 der_cert.len = der_data.size(); |
| 56 |
| 57 // Note: CERT_NewTempCertificate may return NULL if the certificate |
| 58 // shares a serial number with another cert, which is not supposed |
| 59 // to happen. |
| 60 cert.reset(CERT_NewTempCertificate( |
| 61 CERT_GetDefaultCertDB(), &der_cert, NULL, PR_FALSE, PR_TRUE)); |
| 62 #endif |
| 63 DCHECK(cert.get() != NULL); |
| 64 } |
| 65 |
| 66 // The wire form of the OID 1.3.6.1.4.1.11129.2.4.2. See Section 3.3 of |
| 67 // RFC6962. |
| 68 const unsigned char kEmbeddedSCTOid[] = |
| 69 { 0x2B, 0x06, 0x01, 0x04, 0x01, 0xD6, 0x79, 0x02, 0x04, 0x02 }; |
| 70 const char kEmbeddedSCTDescription[] = |
| 71 "X.509v3 Certificate Transparency Embedded Signed Certificate Timestamp " |
| 72 "List"; |
| 73 |
| 74 // Initializes the necessary NSS internals for use with Certificate |
| 75 // Transparency. |
| 76 class CTInitSingleton { |
| 77 public: |
| 78 SECOidTag embedded_oid() const { return embedded_oid_; } |
| 79 |
| 80 private: |
| 81 friend struct base::DefaultLazyInstanceTraits<CTInitSingleton>; |
| 82 |
| 83 CTInitSingleton() |
| 84 : embedded_oid_(SEC_OID_UNKNOWN) { |
| 85 embedded_oid_ = RegisterOid(kEmbeddedSCTOid, sizeof(kEmbeddedSCTOid), |
| 86 kEmbeddedSCTDescription); |
| 87 } |
| 88 |
| 89 ~CTInitSingleton() {} |
| 90 |
| 91 SECOidTag RegisterOid(const unsigned char* oid, |
| 92 unsigned int oid_len, |
| 93 const char* description) { |
| 94 SECOidData oid_data; |
| 95 oid_data.oid.len = oid_len; |
| 96 oid_data.oid.data = const_cast<unsigned char*>(oid); |
| 97 oid_data.offset = SEC_OID_UNKNOWN; |
| 98 oid_data.desc = description; |
| 99 oid_data.mechanism = CKM_INVALID_MECHANISM; |
| 100 // Setting this to SUPPORTED_CERT_EXTENSION ensures that if a certificate |
| 101 // contains this extension with the critical bit set, NSS will not reject |
| 102 // it. However, because verification of this extension happens after NSS, |
| 103 // it is currently left as INVALID_CERT_EXTENSION. |
| 104 oid_data.supportedExtension = INVALID_CERT_EXTENSION; |
| 105 |
| 106 SECOidTag result = SECOID_AddEntry(&oid_data); |
| 107 CHECK_NE(SEC_OID_UNKNOWN, result); |
| 108 |
| 109 return result; |
| 110 } |
| 111 |
| 112 SECOidTag embedded_oid_; |
| 113 |
| 114 DISALLOW_COPY_AND_ASSIGN(CTInitSingleton); |
| 115 }; |
| 116 |
| 117 base::LazyInstance<CTInitSingleton>::Leaky g_ct_singleton = |
| 118 LAZY_INSTANCE_INITIALIZER; |
| 119 |
| 120 // Obtains the data for an X.509v3 certificate extension identified by |oid| |
| 121 // and encoded as an OCTET STRING. Returns true if the extension was found, |
| 122 // updating |ext_data| to be the extension data after removing the DER |
| 123 // encoding. |
| 124 bool GetOctetStringExtension(CERTCertificate* cert, |
| 125 SECOidTag oid, |
| 126 std::string* extension_data) { |
| 127 SECItem extension; |
| 128 SECStatus rv = CERT_FindCertExtension(cert, oid, &extension); |
| 129 if (rv != SECSuccess) |
| 130 return false; |
| 131 |
| 132 base::StringPiece raw_data(reinterpret_cast<char*>(extension.data), |
| 133 extension.len); |
| 134 base::StringPiece parsed_data; |
| 135 if (!asn1::GetElement(&raw_data, asn1::kOCTETSTRING, &parsed_data)) { |
| 136 rv = SECFailure; |
| 137 } else { |
| 138 parsed_data.CopyToString(extension_data); |
| 139 } |
| 140 |
| 141 SECITEM_FreeItem(&extension, PR_FALSE); |
| 142 return rv == SECSuccess; |
| 143 } |
| 144 |
| 145 bool ExtractTBSCertWithoutSCTs(CERTCertificate* cert, |
| 146 std::string* to_be_signed) { |
| 147 SECOidData* oid = SECOID_FindOIDByTag(g_ct_singleton.Get().embedded_oid()); |
| 148 if (!oid) |
| 149 return false; |
| 150 |
| 151 // This is a giant hack, due to the fact that NSS does not expose a good API |
| 152 // for simply removing certificate fields from existing certificates. |
| 153 CERTCertificateStr temp_cert; |
| 154 temp_cert = *cert; |
| 155 temp_cert.extensions = NULL; |
| 156 |
| 157 // Strip out the embedded SCT OID from the new certificate by directly |
| 158 // mutating the extensions in place. |
| 159 std::vector<CERTCertExtension*> new_extensions; |
| 160 if (cert->extensions) { |
| 161 for (CERTCertExtension** exts = cert->extensions; *exts; ++exts) { |
| 162 CERTCertExtension* ext = *exts; |
| 163 SECComparison result = SECITEM_CompareItem(&oid->oid, &ext->id); |
| 164 if (result != SECEqual) |
| 165 new_extensions.push_back(ext); |
| 166 } |
| 167 } |
| 168 if (!new_extensions.empty()) { |
| 169 new_extensions.push_back(NULL); |
| 170 // XXX(eranm): Should probabry convert to a proper array here. |
| 171 temp_cert.extensions = &new_extensions[0]; |
| 172 } |
| 173 |
| 174 crypto::ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); |
| 175 |
| 176 SECItem tbs_data; |
| 177 tbs_data.len = 0; |
| 178 tbs_data.data = NULL; |
| 179 void* result = SEC_ASN1EncodeItem(arena.get(), |
| 180 &tbs_data, |
| 181 &temp_cert, |
| 182 SEC_ASN1_GET(CERT_CertificateTemplate)); |
| 183 if (!result || tbs_data.len == 0) |
| 184 return false; |
| 185 |
| 186 to_be_signed->assign(reinterpret_cast<char*>(tbs_data.data), tbs_data.len); |
| 187 return true; |
| 188 } |
| 189 |
| 190 } // namespace |
| 191 |
| 192 bool ExtractEmbeddedSCTs(X509Certificate::OSCertHandle cert, |
| 193 std::string* sct_list) { |
| 194 DCHECK(cert); |
| 195 |
| 196 NSSCertWrapper leaf_cert(cert); |
| 197 if (!leaf_cert.cert) |
| 198 return false; |
| 199 |
| 200 return GetOctetStringExtension( |
| 201 leaf_cert.cert.get(), g_ct_singleton.Get().embedded_oid(), sct_list); |
| 202 } |
| 203 |
| 204 bool GetPrecertLogEntry(X509Certificate::OSCertHandle leaf, |
| 205 X509Certificate::OSCertHandle issuer, |
| 206 LogEntry* result) { |
| 207 DCHECK(leaf); |
| 208 DCHECK(issuer); |
| 209 |
| 210 NSSCertWrapper leaf_cert(leaf); |
| 211 NSSCertWrapper issuer_cert(issuer); |
| 212 |
| 213 // XXX(rsleevi): This check may be overkill, since we should be able to |
| 214 // generate precerts for certs without the extension. For now, just a sanity |
| 215 // check to match the reference implementation. |
| 216 SECItem extension; |
| 217 SECStatus rv = CERT_FindCertExtension( |
| 218 leaf_cert.cert.get(), g_ct_singleton.Get().embedded_oid(), &extension); |
| 219 if (rv != SECSuccess) |
| 220 return false; |
| 221 SECITEM_FreeItem(&extension, PR_FALSE); |
| 222 |
| 223 std::string to_be_signed; |
| 224 if (!ExtractTBSCertWithoutSCTs(leaf_cert.cert.get(), &to_be_signed)) |
| 225 return false; |
| 226 |
| 227 result->Reset(); |
| 228 result->type = ct::LogEntry::LOG_ENTRY_TYPE_PRECERT; |
| 229 result->tbs_certificate.swap(to_be_signed); |
| 230 if (!issuer_cert.cert) { |
| 231 // This can happen when the issuer and leaf certs share the same serial |
| 232 // number, which should never be the case (but happened with bad test |
| 233 // certs). |
| 234 VLOG(1) << "Issuer cert is null, failing SCT verification."; |
| 235 return false; |
| 236 } |
| 237 |
| 238 // Hash the entire SPKI, not just the public key. |
| 239 // See https://codereview.appspot.com/8269046/ |
| 240 SECKEYPublicKey *issuer_pub_key = |
| 241 SECKEY_ExtractPublicKey(&(issuer_cert.cert->subjectPublicKeyInfo)); |
| 242 SECItem *encoded_issuer_pubKey = |
| 243 SECKEY_EncodeDERSubjectPublicKeyInfo(issuer_pub_key); |
| 244 |
| 245 crypto::SHA256HashString( |
| 246 base::StringPiece( |
| 247 reinterpret_cast<char*>(encoded_issuer_pubKey->data), |
| 248 encoded_issuer_pubKey->len), |
| 249 result->issuer_key_hash.data, |
| 250 sizeof(result->issuer_key_hash.data)); |
| 251 |
| 252 SECITEM_FreeItem(encoded_issuer_pubKey, PR_TRUE); |
| 253 encoded_issuer_pubKey = NULL; |
| 254 SECKEY_DestroyPublicKey(issuer_pub_key); |
| 255 issuer_pub_key = NULL; |
| 256 |
| 257 return true; |
| 258 } |
| 259 |
| 260 bool GetAsn1CertLogEntry(X509Certificate::OSCertHandle leaf, |
| 261 LogEntry* result) { |
| 262 DCHECK(leaf); |
| 263 |
| 264 std::string encoded; |
| 265 if (!X509Certificate::GetDEREncoded(leaf, &encoded)) |
| 266 return false; |
| 267 |
| 268 result->Reset(); |
| 269 result->type = ct::LogEntry::LOG_ENTRY_TYPE_X509; |
| 270 result->leaf_certificate.swap(encoded); |
| 271 return true; |
| 272 } |
| 273 |
| 274 } // namespace ct |
| 275 |
| 276 } // namespace net |
OLD | NEW |