diff options
author | droger@chromium.org <droger@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-09-18 09:04:33 +0000 |
---|---|---|
committer | droger@chromium.org <droger@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-09-18 09:04:33 +0000 |
commit | d4158950608f079e527a1e9cb085440ab98aff08 (patch) | |
tree | dd21bacc22a9fda3d18a4a8f100a24ea1effa640 /net | |
parent | 47263cb1a4253ff51e157749499b293c8399dd48 (diff) | |
download | chromium_src-d4158950608f079e527a1e9cb085440ab98aff08.zip chromium_src-d4158950608f079e527a1e9cb085440ab98aff08.tar.gz chromium_src-d4158950608f079e527a1e9cb085440ab98aff08.tar.bz2 |
Support x509 certificate on iOS.
The iOS implementation uses both NSS and system API. This CL creates
x509_certificate_nss_util to allow iOS to share code with the NSS
implementation. The iOS specific code is in x509_certificate_ios.
BUG=145954
Review URL: https://chromiumcodereview.appspot.com/10928107
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@157325 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r-- | net/base/ev_root_ca_metadata.cc | 6 | ||||
-rw-r--r-- | net/base/ev_root_ca_metadata.h | 8 | ||||
-rw-r--r-- | net/base/x509_certificate_ios.cc | 230 | ||||
-rw-r--r-- | net/base/x509_certificate_nss.cc | 209 | ||||
-rw-r--r-- | net/base/x509_certificate_unittest.cc | 5 | ||||
-rw-r--r-- | net/base/x509_util_ios.cc | 77 | ||||
-rw-r--r-- | net/base/x509_util_ios.h | 43 | ||||
-rw-r--r-- | net/base/x509_util_nss.cc | 224 | ||||
-rw-r--r-- | net/base/x509_util_nss.h | 44 | ||||
-rw-r--r-- | net/net.gyp | 67 |
10 files changed, 684 insertions, 229 deletions
diff --git a/net/base/ev_root_ca_metadata.cc b/net/base/ev_root_ca_metadata.cc index 9901ff3..23378ca 100644 --- a/net/base/ev_root_ca_metadata.cc +++ b/net/base/ev_root_ca_metadata.cc @@ -4,7 +4,7 @@ #include "net/base/ev_root_ca_metadata.h" -#if defined(USE_NSS) +#if defined(USE_NSS) || defined(OS_IOS) #include <cert.h> #include <pkcs11n.h> #include <secerr.h> @@ -15,7 +15,7 @@ #include "base/lazy_instance.h" #include "base/logging.h" -#if defined(USE_NSS) +#if defined(USE_NSS) || defined(OS_IOS) #include "crypto/nss_util.h" #endif @@ -319,7 +319,7 @@ EVRootCAMetadata* EVRootCAMetadata::GetInstance() { return g_ev_root_ca_metadata.Pointer(); } -#if defined(USE_NSS) +#if defined(USE_NSS) || defined(OS_IOS) bool EVRootCAMetadata::IsEVPolicyOID(PolicyOID policy_oid) const { return policy_oids_.find(policy_oid) != policy_oids_.end(); } diff --git a/net/base/ev_root_ca_metadata.h b/net/base/ev_root_ca_metadata.h index 57ebe80..292c5ea 100644 --- a/net/base/ev_root_ca_metadata.h +++ b/net/base/ev_root_ca_metadata.h @@ -7,7 +7,7 @@ #include "build/build_config.h" -#if defined(USE_NSS) +#if defined(USE_NSS) || defined(OS_IOS) #include <secoidt.h> #endif @@ -30,7 +30,7 @@ namespace net { // extended-validation (EV) certificates. class NET_EXPORT_PRIVATE EVRootCAMetadata { public: -#if defined(USE_NSS) +#if defined(USE_NSS) || defined(OS_IOS) typedef SECOidTag PolicyOID; #elif defined(OS_WIN) typedef const char* PolicyOID; @@ -38,7 +38,7 @@ class NET_EXPORT_PRIVATE EVRootCAMetadata { static EVRootCAMetadata* GetInstance(); -#if defined(USE_NSS) || defined(OS_WIN) +#if defined(USE_NSS) || defined(OS_WIN) || defined(OS_IOS) // Returns true if policy_oid is an EV policy OID of some root CA. bool IsEVPolicyOID(PolicyOID policy_oid) const; @@ -63,7 +63,7 @@ class NET_EXPORT_PRIVATE EVRootCAMetadata { EVRootCAMetadata(); ~EVRootCAMetadata(); -#if defined(USE_NSS) +#if defined(USE_NSS) || defined(OS_IOS) typedef std::map<SHA1HashValue, std::vector<PolicyOID>, SHA1HashValueLessThan> PolicyOIDMap; diff --git a/net/base/x509_certificate_ios.cc b/net/base/x509_certificate_ios.cc new file mode 100644 index 0000000..0286203 --- /dev/null +++ b/net/base/x509_certificate_ios.cc @@ -0,0 +1,230 @@ +// Copyright (c) 2012 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/base/x509_certificate.h" + +#include <CommonCrypto/CommonDigest.h> +#include <Security/Security.h> +#include <vector> + +#include <cryptohi.h> +#include <cert.h> +#include <keyhi.h> +#include <nss.h> +#include <pk11pub.h> +#include <prerror.h> +#include <prtime.h> +#include <prtypes.h> +#include <secder.h> +#include <secerr.h> +#include <sslerr.h> + +#include "base/logging.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/memory/scoped_ptr.h" +#include "base/pickle.h" +#include "base/time.h" +#include "crypto/nss_util.h" +#include "crypto/scoped_nss_types.h" +#include "net/base/asn1_util.h" +#include "net/base/cert_status_flags.h" +#include "net/base/cert_verify_result.h" +#include "net/base/ev_root_ca_metadata.h" +#include "net/base/net_errors.h" +#include "net/base/x509_util_ios.h" +#include "net/base/x509_util_nss.h" + +using base::mac::ScopedCFTypeRef; + +namespace net { +namespace { +// Returns true if a given |cert_handle| is actually a valid X.509 certificate +// handle. +// +// SecCertificateCreateFromData() does not always force the immediate parsing of +// the certificate, and as such, may return a SecCertificateRef for an +// invalid/unparsable certificate. Force parsing to occur to ensure that the +// SecCertificateRef is correct. On later versions where +// SecCertificateCreateFromData() immediately parses, rather than lazily, this +// call is cheap, as the subject is cached. +bool IsValidOSCertHandle(SecCertificateRef cert_handle) { + ScopedCFTypeRef<CFStringRef> sanity_check( + SecCertificateCopySubjectSummary(cert_handle)); + return sanity_check != NULL; +} +} // namespace + +void X509Certificate::Initialize() { + x509_util_ios::NSSCertificate nss_cert(cert_handle_); + CERTCertificate* cert_handle = nss_cert.cert_handle(); + if (cert_handle) { + x509_util::ParsePrincipal(&cert_handle->subject, &subject_); + x509_util::ParsePrincipal(&cert_handle->issuer, &issuer_); + x509_util::ParseDate(&cert_handle->validity.notBefore, &valid_start_); + x509_util::ParseDate(&cert_handle->validity.notAfter, &valid_expiry_); + serial_number_ = x509_util::ParseSerialNumber(cert_handle); + } + fingerprint_ = CalculateFingerprint(cert_handle_); + ca_fingerprint_ = CalculateCAFingerprint(intermediate_ca_certs_); +} + +// static +X509Certificate* X509Certificate::CreateSelfSigned( + crypto::RSAPrivateKey* key, + const std::string& subject, + uint32 serial_number, + base::TimeDelta valid_duration) { + DCHECK(key); + DCHECK(!subject.empty()); + NOTIMPLEMENTED(); + return NULL; +} + +void X509Certificate::GetSubjectAltName( + std::vector<std::string>* dns_names, + std::vector<std::string>* ip_addrs) const { + x509_util_ios::NSSCertificate nss_cert(cert_handle_); + CERTCertificate* cert_handle = nss_cert.cert_handle(); + if (!cert_handle) { + if (dns_names) + dns_names->clear(); + if (ip_addrs) + ip_addrs->clear(); + return; + } + x509_util::GetSubjectAltName(cert_handle, dns_names, ip_addrs); +} + +// static +bool X509Certificate::GetDEREncoded(OSCertHandle cert_handle, + std::string* encoded) { + ScopedCFTypeRef<CFDataRef> der_data(SecCertificateCopyData(cert_handle)); + if (!der_data) + return false; + encoded->assign(reinterpret_cast<const char*>(CFDataGetBytePtr(der_data)), + CFDataGetLength(der_data)); + return true; +} + +// static +bool X509Certificate::IsSameOSCert(X509Certificate::OSCertHandle a, + X509Certificate::OSCertHandle b) { + DCHECK(a && b); + if (a == b) + return true; + if (CFEqual(a, b)) + return true; + ScopedCFTypeRef<CFDataRef> a_data(SecCertificateCopyData(a)); + ScopedCFTypeRef<CFDataRef> b_data(SecCertificateCopyData(b)); + return a_data && b_data && + CFDataGetLength(a_data) == CFDataGetLength(b_data) && + memcmp(CFDataGetBytePtr(a_data), CFDataGetBytePtr(b_data), + CFDataGetLength(a_data)) == 0; +} + +// static +X509Certificate::OSCertHandle X509Certificate::CreateOSCertHandleFromBytes( + const char* data, int length) { + ScopedCFTypeRef<CFDataRef> cert_data(CFDataCreateWithBytesNoCopy( + kCFAllocatorDefault, reinterpret_cast<const UInt8 *>(data), length, + kCFAllocatorNull)); + if (!cert_data) + return NULL; + OSCertHandle cert_handle = SecCertificateCreateWithData(NULL, cert_data); + if (!cert_handle) + return NULL; + if (!IsValidOSCertHandle(cert_handle)) { + CFRelease(cert_handle); + return NULL; + } + return cert_handle; +} + +// static +X509Certificate::OSCertHandles X509Certificate::CreateOSCertHandlesFromBytes( + const char* data, + int length, + Format format) { + return x509_util::CreateOSCertHandlesFromBytes(data, length, format); +} + +// static +X509Certificate::OSCertHandle X509Certificate::DupOSCertHandle( + OSCertHandle handle) { + if (!handle) + return NULL; + return reinterpret_cast<OSCertHandle>(const_cast<void*>(CFRetain(handle))); +} + +// static +void X509Certificate::FreeOSCertHandle(OSCertHandle cert_handle) { + CFRelease(cert_handle); +} + +// static +SHA1HashValue X509Certificate::CalculateFingerprint( + OSCertHandle cert) { + SHA1HashValue sha1; + memset(sha1.data, 0, sizeof(sha1.data)); + + ScopedCFTypeRef<CFDataRef> cert_data(SecCertificateCopyData(cert)); + if (!cert_data) + return sha1; + DCHECK(CFDataGetBytePtr(cert_data)); + DCHECK_NE(0, CFDataGetLength(cert_data)); + CC_SHA1(CFDataGetBytePtr(cert_data), CFDataGetLength(cert_data), sha1.data); + + return sha1; +} + +// static +SHA1HashValue X509Certificate::CalculateCAFingerprint( + const OSCertHandles& intermediates) { + SHA1HashValue sha1; + memset(sha1.data, 0, sizeof(sha1.data)); + + // The CC_SHA(3cc) man page says all CC_SHA1_xxx routines return 1, so + // we don't check their return values. + CC_SHA1_CTX sha1_ctx; + CC_SHA1_Init(&sha1_ctx); + for (size_t i = 0; i < intermediates.size(); ++i) { + ScopedCFTypeRef<CFDataRef> + cert_data(SecCertificateCopyData(intermediates[i])); + if (!cert_data) + return sha1; + CC_SHA1_Update(&sha1_ctx, + CFDataGetBytePtr(cert_data), + CFDataGetLength(cert_data)); + } + CC_SHA1_Final(sha1.data, &sha1_ctx); + return sha1; +} + +// static +X509Certificate::OSCertHandle +X509Certificate::ReadOSCertHandleFromPickle(PickleIterator* pickle_iter) { + return x509_util::ReadOSCertHandleFromPickle(pickle_iter); +} + +// static +bool X509Certificate::WriteOSCertHandleToPickle(OSCertHandle cert_handle, + Pickle* pickle) { + ScopedCFTypeRef<CFDataRef> cert_data(SecCertificateCopyData(cert_handle)); + if (!cert_data) + return false; + + return pickle->WriteData( + reinterpret_cast<const char*>(CFDataGetBytePtr(cert_data)), + CFDataGetLength(cert_data)); +} + +// static +void X509Certificate::GetPublicKeyInfo(OSCertHandle cert_handle, + size_t* size_bits, + PublicKeyType* type) { + x509_util_ios::NSSCertificate nss_cert(cert_handle); + x509_util::GetPublicKeyInfo(nss_cert.cert_handle(), size_bits, type); +} + +} // namespace net diff --git a/net/base/x509_certificate_nss.cc b/net/base/x509_certificate_nss.cc index 060f4f7..54d2197 100644 --- a/net/base/x509_certificate_nss.cc +++ b/net/base/x509_certificate_nss.cc @@ -19,110 +19,21 @@ #include "base/time.h" #include "crypto/nss_util.h" #include "crypto/rsa_private_key.h" -#include "crypto/scoped_nss_types.h" #include "net/base/x509_util_nss.h" namespace net { -namespace { - -void ParsePrincipal(CERTName* name, - CertPrincipal* principal) { - typedef char* (*CERTGetNameFunc)(CERTName* name); - - // TODO(jcampan): add business_category and serial_number. - // TODO(wtc): NSS has the CERT_GetOrgName, CERT_GetOrgUnitName, and - // CERT_GetDomainComponentName functions, but they return only the most - // general (the first) RDN. NSS doesn't have a function for the street - // address. - static const SECOidTag kOIDs[] = { - SEC_OID_AVA_STREET_ADDRESS, - SEC_OID_AVA_ORGANIZATION_NAME, - SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME, - SEC_OID_AVA_DC }; - - std::vector<std::string>* values[] = { - &principal->street_addresses, - &principal->organization_names, - &principal->organization_unit_names, - &principal->domain_components }; - DCHECK(arraysize(kOIDs) == arraysize(values)); - - CERTRDN** rdns = name->rdns; - for (size_t rdn = 0; rdns[rdn]; ++rdn) { - CERTAVA** avas = rdns[rdn]->avas; - for (size_t pair = 0; avas[pair] != 0; ++pair) { - SECOidTag tag = CERT_GetAVATag(avas[pair]); - for (size_t oid = 0; oid < arraysize(kOIDs); ++oid) { - if (kOIDs[oid] == tag) { - SECItem* decode_item = CERT_DecodeAVAValue(&avas[pair]->value); - if (!decode_item) - break; - // TODO(wtc): Pass decode_item to CERT_RFC1485_EscapeAndQuote. - std::string value(reinterpret_cast<char*>(decode_item->data), - decode_item->len); - values[oid]->push_back(value); - SECITEM_FreeItem(decode_item, PR_TRUE); - break; - } - } - } - } - - // Get CN, L, S, and C. - CERTGetNameFunc get_name_funcs[4] = { - CERT_GetCommonName, CERT_GetLocalityName, - CERT_GetStateName, CERT_GetCountryName }; - std::string* single_values[4] = { - &principal->common_name, &principal->locality_name, - &principal->state_or_province_name, &principal->country_name }; - for (size_t i = 0; i < arraysize(get_name_funcs); ++i) { - char* value = get_name_funcs[i](name); - if (value) { - single_values[i]->assign(value); - PORT_Free(value); - } - } -} - -void ParseDate(SECItem* der_date, base::Time* result) { - PRTime prtime; - SECStatus rv = DER_DecodeTimeChoice(&prtime, der_date); - DCHECK_EQ(SECSuccess, rv); - *result = crypto::PRTimeToBaseTime(prtime); -} - -SECStatus PR_CALLBACK -CollectCertsCallback(void* arg, SECItem** certs, int num_certs) { - X509Certificate::OSCertHandles* results = - reinterpret_cast<X509Certificate::OSCertHandles*>(arg); - - for (int i = 0; i < num_certs; ++i) { - X509Certificate::OSCertHandle handle = - X509Certificate::CreateOSCertHandleFromBytes( - reinterpret_cast<char*>(certs[i]->data), certs[i]->len); - if (handle) - results->push_back(handle); - } - - return SECSuccess; -} - -} // namespace - void X509Certificate::Initialize() { - ParsePrincipal(&cert_handle_->subject, &subject_); - ParsePrincipal(&cert_handle_->issuer, &issuer_); + x509_util::ParsePrincipal(&cert_handle_->subject, &subject_); + x509_util::ParsePrincipal(&cert_handle_->issuer, &issuer_); - ParseDate(&cert_handle_->validity.notBefore, &valid_start_); - ParseDate(&cert_handle_->validity.notAfter, &valid_expiry_); + x509_util::ParseDate(&cert_handle_->validity.notBefore, &valid_start_); + x509_util::ParseDate(&cert_handle_->validity.notAfter, &valid_expiry_); fingerprint_ = CalculateFingerprint(cert_handle_); ca_fingerprint_ = CalculateCAFingerprint(intermediate_ca_certs_); - serial_number_ = std::string( - reinterpret_cast<char*>(cert_handle_->serialNumber.data), - cert_handle_->serialNumber.len); + serial_number_ = x509_util::ParseSerialNumber(cert_handle_); } // static @@ -216,7 +127,6 @@ X509Certificate* X509Certificate::CreateSelfSigned( uint32 serial_number, base::TimeDelta valid_duration) { DCHECK(key); - base::Time not_valid_before = base::Time::Now(); base::Time not_valid_after = not_valid_before + valid_duration; CERTCertificate* cert = x509_util::CreateSelfSignedCert(key->public_key(), @@ -225,7 +135,6 @@ X509Certificate* X509Certificate::CreateSelfSigned( serial_number, not_valid_before, not_valid_after); - if (!cert) return NULL; @@ -238,44 +147,7 @@ X509Certificate* X509Certificate::CreateSelfSigned( void X509Certificate::GetSubjectAltName( std::vector<std::string>* dns_names, std::vector<std::string>* ip_addrs) const { - if (dns_names) - dns_names->clear(); - if (ip_addrs) - ip_addrs->clear(); - - SECItem alt_name; - SECStatus rv = CERT_FindCertExtension(cert_handle_, - SEC_OID_X509_SUBJECT_ALT_NAME, - &alt_name); - if (rv != SECSuccess) - return; - - PLArenaPool* arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); - DCHECK(arena != NULL); - - CERTGeneralName* alt_name_list; - alt_name_list = CERT_DecodeAltNameExtension(arena, &alt_name); - SECITEM_FreeItem(&alt_name, PR_FALSE); - - CERTGeneralName* name = alt_name_list; - while (name) { - // DNSName and IPAddress are encoded as IA5String and OCTET STRINGs - // respectively, both of which can be byte copied from - // SECItemType::data into the appropriate output vector. - if (dns_names && name->type == certDNSName) { - dns_names->push_back(std::string( - reinterpret_cast<char*>(name->name.other.data), - name->name.other.len)); - } else if (ip_addrs && name->type == certIPAddress) { - ip_addrs->push_back(std::string( - reinterpret_cast<char*>(name->name.other.data), - name->name.other.len)); - } - name = CERT_GetNextGeneralName(name); - if (name == alt_name_list) - break; - } - PORT_FreeArena(arena, PR_FALSE); + x509_util::GetSubjectAltName(cert_handle_, dns_names, ip_addrs); } bool X509Certificate::VerifyNameMatch(const std::string& hostname) const { @@ -338,38 +210,7 @@ X509Certificate::OSCertHandles X509Certificate::CreateOSCertHandlesFromBytes( const char* data, int length, Format format) { - OSCertHandles results; - if (length < 0) - return results; - - crypto::EnsureNSSInit(); - - if (!NSS_IsInitialized()) - return results; - - switch (format) { - case FORMAT_SINGLE_CERTIFICATE: { - OSCertHandle handle = CreateOSCertHandleFromBytes(data, length); - if (handle) - results.push_back(handle); - break; - } - case FORMAT_PKCS7: { - // Make a copy since CERT_DecodeCertPackage may modify it - std::vector<char> data_copy(data, data + length); - - SECStatus result = CERT_DecodeCertPackage(&data_copy[0], - length, CollectCertsCallback, &results); - if (result != SECSuccess) - results.clear(); - break; - } - default: - NOTREACHED() << "Certificate format " << format << " unimplemented"; - break; - } - - return results; + return x509_util::CreateOSCertHandlesFromBytes(data, length, format); } // static @@ -423,12 +264,7 @@ SHA1HashValue X509Certificate::CalculateCAFingerprint( // static X509Certificate::OSCertHandle X509Certificate::ReadOSCertHandleFromPickle(PickleIterator* pickle_iter) { - const char* data; - int length; - if (!pickle_iter->ReadData(&data, &length)) - return NULL; - - return CreateOSCertHandleFromBytes(data, length); + return x509_util::ReadOSCertHandleFromPickle(pickle_iter); } // static @@ -443,34 +279,7 @@ bool X509Certificate::WriteOSCertHandleToPickle(OSCertHandle cert_handle, void X509Certificate::GetPublicKeyInfo(OSCertHandle cert_handle, size_t* size_bits, PublicKeyType* type) { - // Since we might fail, set the output parameters to default values first. - *type = kPublicKeyTypeUnknown; - *size_bits = 0; - - crypto::ScopedSECKEYPublicKey key(CERT_ExtractPublicKey(cert_handle)); - if (!key.get()) - return; - - *size_bits = SECKEY_PublicKeyStrengthInBits(key.get()); - - switch (key->keyType) { - case rsaKey: - *type = kPublicKeyTypeRSA; - break; - case dsaKey: - *type = kPublicKeyTypeDSA; - break; - case dhKey: - *type = kPublicKeyTypeDH; - break; - case ecKey: - *type = kPublicKeyTypeECDSA; - break; - default: - *type = kPublicKeyTypeUnknown; - *size_bits = 0; - break; - } + x509_util::GetPublicKeyInfo(cert_handle, size_bits, type); } } // namespace net diff --git a/net/base/x509_certificate_unittest.cc b/net/base/x509_certificate_unittest.cc index f8f43fc..c16ad87 100644 --- a/net/base/x509_certificate_unittest.cc +++ b/net/base/x509_certificate_unittest.cc @@ -680,6 +680,8 @@ TEST(X509CertificateTest, IntermediateCertificates) { X509Certificate::FreeOSCertHandle(google_handle); } +#if !defined(OS_IOS) +// TODO(ios): Not yet implemented on iOS. #if defined(OS_MACOSX) TEST(X509CertificateTest, IsIssuedBy) { FilePath certs_dir = GetTestCertsDirectory(); @@ -726,7 +728,9 @@ TEST(X509CertificateTest, IsIssuedBy) { EXPECT_FALSE(mit_davidben_cert->IsIssuedBy(foaf_issuers)); } #endif // defined(OS_MACOSX) +#endif // !defined(OS_IOS) +#if !defined(OS_IOS) // TODO(ios): Unable to create certificates. #if defined(USE_NSS) || defined(OS_WIN) || defined(OS_MACOSX) // This test creates a self-signed cert from a private key and then verify the // content of the certificate. @@ -850,6 +854,7 @@ TEST(X509CertificateTest, GetDEREncoded) { EXPECT_FALSE(der_cert.empty()); } #endif +#endif // !defined(OS_IOS) class X509CertificateParseTest : public testing::TestWithParam<CertificateFormatTestData> { diff --git a/net/base/x509_util_ios.cc b/net/base/x509_util_ios.cc new file mode 100644 index 0000000..a5be412 --- /dev/null +++ b/net/base/x509_util_ios.cc @@ -0,0 +1,77 @@ +// Copyright (c) 2012 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/base/x509_util_ios.h" + +#include <cert.h> +#include <nss.h> +#include <prtypes.h> + +#include "base/mac/scoped_cftyperef.h" +#include "crypto/nss_util.h" +#include "net/base/x509_certificate.h" + +using base::mac::ScopedCFTypeRef; + +namespace net { +namespace x509_util_ios { + +namespace { + +// Creates an NSS certificate handle from |data|, which is |length| bytes in +// size. +CERTCertificate* CreateNSSCertHandleFromBytes(const char* data, + int length) { + if (length < 0) + return NULL; + + crypto::EnsureNSSInit(); + + if (!NSS_IsInitialized()) + return NULL; + + SECItem der_cert; + der_cert.data = reinterpret_cast<unsigned char*>(const_cast<char*>(data)); + der_cert.len = length; + der_cert.type = siDERCertBuffer; + + // Parse into a certificate structure. + return CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &der_cert, NULL, + PR_FALSE, PR_TRUE); +} + +} // namespace + +CERTCertificate* CreateNSSCertHandleFromOSHandle( + SecCertificateRef cert_handle) { + ScopedCFTypeRef<CFDataRef> cert_data(SecCertificateCopyData(cert_handle)); + return CreateNSSCertHandleFromBytes( + reinterpret_cast<const char*>(CFDataGetBytePtr(cert_data)), + CFDataGetLength(cert_data)); +} + +SecCertificateRef CreateOSCertHandleFromNSSHandle( + CERTCertificate* nss_cert_handle) { + return X509Certificate::CreateOSCertHandleFromBytes( + reinterpret_cast<const char*>(nss_cert_handle->derCert.data), + nss_cert_handle->derCert.len); +} + +NSSCertificate::NSSCertificate(SecCertificateRef cert_handle) { + nss_cert_handle_ = CreateNSSCertHandleFromOSHandle(cert_handle); + DLOG_IF(INFO, cert_handle && !nss_cert_handle_) + << "Could not convert SecCertificateRef to CERTCertificate*"; +} + +NSSCertificate::~NSSCertificate() { + CERT_DestroyCertificate(nss_cert_handle_); +} + +CERTCertificate* NSSCertificate::cert_handle() { + return nss_cert_handle_; +} + +} // namespace x509_util_ios +} // namespace net + diff --git a/net/base/x509_util_ios.h b/net/base/x509_util_ios.h new file mode 100644 index 0000000..641ddba --- /dev/null +++ b/net/base/x509_util_ios.h @@ -0,0 +1,43 @@ +// Copyright (c) 2012 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. + +// This file contains functions for iOS to glue NSS and Security.framework +// together. + +#ifndef NET_BASE_X509_UTIL_IOS_H_ +#define NET_BASE_X509_UTIL_IOS_H_ + +#include <Security/Security.h> + +// Forward declaration; real one in <cert.h> +typedef struct CERTCertificateStr CERTCertificate; + +namespace net { +namespace x509_util_ios { + +// Converts a Security.framework certificate handle (SecCertificateRef) into +// an NSS certificate handle (CERTCertificate*). +CERTCertificate* CreateNSSCertHandleFromOSHandle(SecCertificateRef cert_handle); + +// Converts an NSS certificate handle (CERTCertificate*) into a +// Security.framework handle (SecCertificateRef) +SecCertificateRef CreateOSCertHandleFromNSSHandle( + CERTCertificate* nss_cert_handle); + +// This is a wrapper class around the native NSS certificate handle. +// The constructor copies the certificate data from |cert_handle| and +// uses the NSS library to parse it. +class NSSCertificate { + public: + explicit NSSCertificate(SecCertificateRef cert_handle); + ~NSSCertificate(); + CERTCertificate* cert_handle(); + private: + CERTCertificate* nss_cert_handle_; +}; + +} // namespace x509_util_ios +} // namespace net + +#endif // NET_BASE_X509_UTIL_IOS_H_ diff --git a/net/base/x509_util_nss.cc b/net/base/x509_util_nss.cc index 1234d94..a4eaf6f 100644 --- a/net/base/x509_util_nss.cc +++ b/net/base/x509_util_nss.cc @@ -7,8 +7,10 @@ #include <cert.h> #include <cryptohi.h> +#include <nss.h> #include <pk11pub.h> #include <prerror.h> +#include <secder.h> #include <secmod.h> #include <secport.h> @@ -16,11 +18,15 @@ #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/memory/singleton.h" +#include "base/pickle.h" #include "crypto/ec_private_key.h" #include "crypto/nss_util.h" #include "crypto/nss_util_internal.h" #include "crypto/scoped_nss_types.h" #include "crypto/third_party/nss/chromium-nss.h" +#include "net/base/x509_certificate.h" + +namespace net { namespace { @@ -248,9 +254,26 @@ bool CreateDomainBoundCertInternal( return true; } -} // namespace +// Callback for CERT_DecodeCertPackage(), used in +// CreateOSCertHandlesFromBytes(). +SECStatus PR_CALLBACK CollectCertsCallback(void* arg, + SECItem** certs, + int num_certs) { + X509Certificate::OSCertHandles* results = + reinterpret_cast<X509Certificate::OSCertHandles*>(arg); + + for (int i = 0; i < num_certs; ++i) { + X509Certificate::OSCertHandle handle = + X509Certificate::CreateOSCertHandleFromBytes( + reinterpret_cast<char*>(certs[i]->data), certs[i]->len); + if (handle) + results->push_back(handle); + } -namespace net { + return SECSuccess; +} + +} // namespace namespace x509_util { @@ -307,6 +330,203 @@ bool CreateDomainBoundCertEC( der_cert); } +#if defined(USE_NSS) || defined(OS_IOS) +void ParsePrincipal(CERTName* name, CertPrincipal* principal) { + typedef char* (*CERTGetNameFunc)(CERTName* name); + + // TODO(jcampan): add business_category and serial_number. + // TODO(wtc): NSS has the CERT_GetOrgName, CERT_GetOrgUnitName, and + // CERT_GetDomainComponentName functions, but they return only the most + // general (the first) RDN. NSS doesn't have a function for the street + // address. + static const SECOidTag kOIDs[] = { + SEC_OID_AVA_STREET_ADDRESS, + SEC_OID_AVA_ORGANIZATION_NAME, + SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME, + SEC_OID_AVA_DC }; + + std::vector<std::string>* values[] = { + &principal->street_addresses, + &principal->organization_names, + &principal->organization_unit_names, + &principal->domain_components }; + DCHECK_EQ(arraysize(kOIDs), arraysize(values)); + + CERTRDN** rdns = name->rdns; + for (size_t rdn = 0; rdns[rdn]; ++rdn) { + CERTAVA** avas = rdns[rdn]->avas; + for (size_t pair = 0; avas[pair] != 0; ++pair) { + SECOidTag tag = CERT_GetAVATag(avas[pair]); + for (size_t oid = 0; oid < arraysize(kOIDs); ++oid) { + if (kOIDs[oid] == tag) { + SECItem* decode_item = CERT_DecodeAVAValue(&avas[pair]->value); + if (!decode_item) + break; + // TODO(wtc): Pass decode_item to CERT_RFC1485_EscapeAndQuote. + std::string value(reinterpret_cast<char*>(decode_item->data), + decode_item->len); + values[oid]->push_back(value); + SECITEM_FreeItem(decode_item, PR_TRUE); + break; + } + } + } + } + + // Get CN, L, S, and C. + CERTGetNameFunc get_name_funcs[4] = { + CERT_GetCommonName, CERT_GetLocalityName, + CERT_GetStateName, CERT_GetCountryName }; + std::string* single_values[4] = { + &principal->common_name, &principal->locality_name, + &principal->state_or_province_name, &principal->country_name }; + for (size_t i = 0; i < arraysize(get_name_funcs); ++i) { + char* value = get_name_funcs[i](name); + if (value) { + single_values[i]->assign(value); + PORT_Free(value); + } + } +} + +void ParseDate(const SECItem* der_date, base::Time* result) { + PRTime prtime; + SECStatus rv = DER_DecodeTimeChoice(&prtime, der_date); + DCHECK_EQ(SECSuccess, rv); + *result = crypto::PRTimeToBaseTime(prtime); +} + +std::string ParseSerialNumber(const CERTCertificate* certificate) { + return std::string(reinterpret_cast<char*>(certificate->serialNumber.data), + certificate->serialNumber.len); +} + +void GetSubjectAltName(CERTCertificate* cert_handle, + std::vector<std::string>* dns_names, + std::vector<std::string>* ip_addrs) { + if (dns_names) + dns_names->clear(); + if (ip_addrs) + ip_addrs->clear(); + + SECItem alt_name; + SECStatus rv = CERT_FindCertExtension(cert_handle, + SEC_OID_X509_SUBJECT_ALT_NAME, + &alt_name); + if (rv != SECSuccess) + return; + + PLArenaPool* arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + DCHECK(arena != NULL); + + CERTGeneralName* alt_name_list; + alt_name_list = CERT_DecodeAltNameExtension(arena, &alt_name); + SECITEM_FreeItem(&alt_name, PR_FALSE); + + CERTGeneralName* name = alt_name_list; + while (name) { + // DNSName and IPAddress are encoded as IA5String and OCTET STRINGs + // respectively, both of which can be byte copied from + // SECItemType::data into the appropriate output vector. + if (dns_names && name->type == certDNSName) { + dns_names->push_back(std::string( + reinterpret_cast<char*>(name->name.other.data), + name->name.other.len)); + } else if (ip_addrs && name->type == certIPAddress) { + ip_addrs->push_back(std::string( + reinterpret_cast<char*>(name->name.other.data), + name->name.other.len)); + } + name = CERT_GetNextGeneralName(name); + if (name == alt_name_list) + break; + } + PORT_FreeArena(arena, PR_FALSE); +} + +X509Certificate::OSCertHandles CreateOSCertHandlesFromBytes( + const char* data, + int length, + X509Certificate::Format format) { + X509Certificate::OSCertHandles results; + if (length < 0) + return results; + + crypto::EnsureNSSInit(); + + if (!NSS_IsInitialized()) + return results; + + switch (format) { + case X509Certificate::FORMAT_SINGLE_CERTIFICATE: { + X509Certificate::OSCertHandle handle = + X509Certificate::CreateOSCertHandleFromBytes(data, length); + if (handle) + results.push_back(handle); + break; + } + case X509Certificate::FORMAT_PKCS7: { + // Make a copy since CERT_DecodeCertPackage may modify it + std::vector<char> data_copy(data, data + length); + + SECStatus result = CERT_DecodeCertPackage(&data_copy[0], + length, CollectCertsCallback, &results); + if (result != SECSuccess) + results.clear(); + break; + } + default: + NOTREACHED() << "Certificate format " << format << " unimplemented"; + break; + } + + return results; +} + +X509Certificate::OSCertHandle ReadOSCertHandleFromPickle( + PickleIterator* pickle_iter) { + const char* data; + int length; + if (!pickle_iter->ReadData(&data, &length)) + return NULL; + + return X509Certificate::CreateOSCertHandleFromBytes(data, length); +} + +void GetPublicKeyInfo(CERTCertificate* handle, + size_t* size_bits, + X509Certificate::PublicKeyType* type) { + // Since we might fail, set the output parameters to default values first. + *type = X509Certificate::kPublicKeyTypeUnknown; + *size_bits = 0; + + crypto::ScopedSECKEYPublicKey key(CERT_ExtractPublicKey(handle)); + if (!key.get()) + return; + + *size_bits = SECKEY_PublicKeyStrengthInBits(key.get()); + + switch (key->keyType) { + case rsaKey: + *type = X509Certificate::kPublicKeyTypeRSA; + break; + case dsaKey: + *type = X509Certificate::kPublicKeyTypeDSA; + break; + case dhKey: + *type = X509Certificate::kPublicKeyTypeDH; + break; + case ecKey: + *type = X509Certificate::kPublicKeyTypeECDSA; + break; + default: + *type = X509Certificate::kPublicKeyTypeUnknown; + *size_bits = 0; + break; + } +} +#endif // defined(USE_NSS) || defined(OS_IOS) + } // namespace x509_util } // namespace net diff --git a/net/base/x509_util_nss.h b/net/base/x509_util_nss.h index 7685167..f00c4ab 100644 --- a/net/base/x509_util_nss.h +++ b/net/base/x509_util_nss.h @@ -6,14 +6,19 @@ #define NET_BASE_X509_UTIL_NSS_H_ #include <string> +#include <vector> #include "base/time.h" +#include "net/base/x509_certificate.h" + +class PickleIterator; typedef struct CERTCertificateStr CERTCertificate; +typedef struct CERTNameStr CERTName; typedef struct SECKEYPrivateKeyStr SECKEYPrivateKey; +typedef struct SECItemStr SECItem; typedef struct SECKEYPublicKeyStr SECKEYPublicKey; - namespace net { namespace x509_util { @@ -30,6 +35,43 @@ CERTCertificate* CreateSelfSignedCert( base::Time not_valid_before, base::Time not_valid_after); +#if defined(USE_NSS) || defined(OS_IOS) +// Parses the Principal attribute from |name| and outputs the result in +// |principal|. +void ParsePrincipal(CERTName* name, + CertPrincipal* principal); + +// Parses the date from |der_date| and outputs the result in |result|. +void ParseDate(const SECItem* der_date, base::Time* result); + +// Parses the serial number from |certificate|. +std::string ParseSerialNumber(const CERTCertificate* certificate); + +// Gets the subjectAltName extension field from the certificate, if any. +void GetSubjectAltName(CERTCertificate* cert_handle, + std::vector<std::string>* dns_names, + std::vector<std::string>* ip_addrs); + +// Creates all possible OS certificate handles from |data| encoded in a specific +// |format|. Returns an empty collection on failure. +X509Certificate::OSCertHandles CreateOSCertHandlesFromBytes( + const char* data, + int length, + X509Certificate::Format format); + +// Reads a single certificate from |pickle_iter| and returns a platform-specific +// certificate handle. Returns an invalid handle, NULL, on failure. +X509Certificate::OSCertHandle ReadOSCertHandleFromPickle( + PickleIterator* pickle_iter); + +// Sets |*size_bits| to be the length of the public key in bits, and sets +// |*type| to one of the |PublicKeyType| values. In case of +// |kPublicKeyTypeUnknown|, |*size_bits| will be set to 0. +void GetPublicKeyInfo(CERTCertificate* handle, + size_t* size_bits, + X509Certificate::PublicKeyType* type); +#endif // defined(USE_NSS) || defined(OS_IOS) + } // namespace x509_util } // namespace net diff --git a/net/net.gyp b/net/net.gyp index ee97bba..2857c1a 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -299,6 +299,7 @@ 'base/x509_cert_types_win.cc', 'base/x509_certificate.cc', 'base/x509_certificate.h', + 'base/x509_certificate_ios.cc', 'base/x509_certificate_mac.cc', 'base/x509_certificate_net_log_param.cc', 'base/x509_certificate_net_log_param.h', @@ -306,6 +307,8 @@ 'base/x509_certificate_openssl.cc', 'base/x509_certificate_win.cc', 'base/x509_util.h', + 'base/x509_util_ios.cc', + 'base/x509_util_ios.h', 'base/x509_util_mac.cc', 'base/x509_util_mac.h', 'base/x509_util_nss.cc', @@ -1078,10 +1081,15 @@ }, ], [ 'OS == "ios"', { + 'dependencies': [ + '../third_party/nss/nss.gyp:nss', + 'third_party/nss/ssl.gyp:libssl', + ], 'link_settings': { 'libraries': [ '$(SDKROOT)/System/Library/Frameworks/CFNetwork.framework', '$(SDKROOT)/System/Library/Frameworks/MobileCoreServices.framework', + '$(SDKROOT)/System/Library/Frameworks/Security.framework', '$(SDKROOT)/System/Library/Frameworks/SystemConfiguration.framework', '$(SDKROOT)/usr/lib/libresolv.dylib', ], @@ -1091,8 +1099,10 @@ # compiled on iOS, just enough to bring up the dependencies needed # by the ui target. ['exclude', '.*'], + ['include', '^base/asn1_util\\.'], ['include', '^base/dns_util\\.'], ['include', '^base/escape\\.'], + ['include', '^base/ev_root_ca_metadata\\.'], ['include', '^base/ip_endpoint\\.'], ['include', '^base/mime_util\\.'], ['include', '^base/net_errors\\.'], @@ -1104,7 +1114,13 @@ ['include', '^base/net_util\\.'], ['include', '^base/net_util_posix\\.cc$'], ['include', '^base/platform_mime_util\\.h$'], + ['include', '^base/pem_tokenizer\\.cc$'], + ['include', '^base/pem_tokenizer\\.h$'], ['include', '^base/registry_controlled_domains/registry_controlled_domain\\.'], + ['include', '^base/x509_certificate\\.'], + ['include', '^base/x509_certificate_ios\\.'], + ['include', '^base/x509_cert_types\\.'], + ['include', '^base/x509_util_ios\\.'], ['include', '^http/http_byte_range\\.'], ['include', '^http/http_content_disposition\\.'], ['include', '^http/http_util\\.'], @@ -1167,6 +1183,11 @@ ['include', 'base/network_config_watcher_mac\\.cc$'], ['include', 'base/platform_mime_util_mac\\.mm$'], ['include', 'proxy/proxy_resolver_mac\\.cc$'], + # The iOS implementation only partially uses NSS and thus does not + # defines |use_nss|. In particular the |USE_NSS| preprocessor + # definition is not used. The following files are needed though: + ['include', 'base/x509_util_nss\\.cc$'], + ['include', 'base/x509_util_nss\\.h$'], ], }], ], @@ -1536,25 +1557,28 @@ ], }, ], - ['OS == "ios"', { - # TODO: For now this only tests the subset of code that is enabled in - # the net target. - 'dependencies': [ - '../testing/gtest.gyp:gtest_main', - ], - 'sources/': [ - ['exclude', '.*'], - ['include', '^base/dns_util_unittest\\.cc$'], - ['include', '^base/escape_unittest\\.cc$'], - ['include', '^base/ip_endpoint_unittest\\.cc$'], - ['include', '^base/mime_util_unittest\\.cc$'], - ['include', '^base/net_log_unittest\\.cc$'], - ['include', '^base/registry_controlled_domains/registry_controlled_domain_unittest\\.cc$'], - ['include', '^http/http_byte_range_unittest\\.cc$'], - ['include', '^http/http_content_disposition_unittest\\.cc$'], - ['include', '^http/http_util_unittest\\.cc$'], - ['include', '^proxy/proxy_config_service_common_unittest\\.cc$'], - ], + [ 'OS == "ios"', { + # TODO: For now this only tests the subset of code that is enabled + # in the net target. + 'dependencies': [ + '../third_party/nss/nss.gyp:nss', + '../testing/gtest.gyp:gtest_main', + ], + 'sources/': [ + ['exclude', '.*'], + ['include', '^base/dns_util_unittest\\.cc$'], + ['include', '^base/escape_unittest\\.cc$'], + ['include', '^base/ip_endpoint_unittest\\.cc$'], + ['include', '^base/mime_util_unittest\\.cc$'], + ['include', '^base/net_log_unittest\\.cc$'], + ['include', '^base/pem_tokenizer_unittest\\.cc$'], + ['include', '^base/registry_controlled_domains/registry_controlled_domain_unittest\\.cc$'], + ['include', '^base/x509_certificate_unittest\\.cc$'], + ['include', '^http/http_byte_range_unittest\\.cc$'], + ['include', '^http/http_content_disposition_unittest\\.cc$'], + ['include', '^http/http_util_unittest\\.cc$'], + ['include', '^proxy/proxy_config_service_common_unittest\\.cc$'], + ], }], [ 'OS == "linux"', { 'dependencies': [ @@ -1740,6 +1764,11 @@ 'test/spawner_communicator.h', ], }], + ['OS == "ios"', { + 'dependencies': [ + '../third_party/nss/nss.gyp:nss', + ], + }], [ 'use_v8_in_net==1', { 'dependencies': [ 'net_with_v8', |