diff options
author | dkegel@google.com <dkegel@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-12-22 01:17:22 +0000 |
---|---|---|
committer | dkegel@google.com <dkegel@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-12-22 01:17:22 +0000 |
commit | eb6c3395cbcf61eb784765f3975ee7aa3b3d81da (patch) | |
tree | 00acbfb160c7f40a0149e852bfb672398e2b207b /net/base | |
parent | 92173830bcdeaa160d73065f99c38f97922dcd50 (diff) | |
download | chromium_src-eb6c3395cbcf61eb784765f3975ee7aa3b3d81da.zip chromium_src-eb6c3395cbcf61eb784765f3975ee7aa3b3d81da.tar.gz chromium_src-eb6c3395cbcf61eb784765f3975ee7aa3b3d81da.tar.bz2 |
Implement part of x509_certificate on linux, enable its unit test.
Review URL: http://codereview.chromium.org/16205
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@7350 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/base')
-rw-r--r-- | net/base/x509_certificate.h | 5 | ||||
-rw-r--r-- | net/base/x509_certificate_nss.cc | 247 |
2 files changed, 247 insertions, 5 deletions
diff --git a/net/base/x509_certificate.h b/net/base/x509_certificate.h index ff6ba31..4e41570 100644 --- a/net/base/x509_certificate.h +++ b/net/base/x509_certificate.h @@ -19,6 +19,9 @@ #include <wincrypt.h> #elif defined(OS_MACOSX) #include <Security/Security.h> +#elif defined(OS_LINUX) +// Forward declaration; real one in <cert.h> +struct CERTCertificateStr; #endif class Pickle; @@ -50,6 +53,8 @@ class X509Certificate : public base::RefCountedThreadSafe<X509Certificate> { typedef PCCERT_CONTEXT OSCertHandle; #elif defined(OS_MACOSX) typedef SecCertificateRef OSCertHandle; +#elif defined(OS_LINUX) + typedef struct CERTCertificateStr* OSCertHandle; #else // TODO(ericroman): not implemented typedef void* OSCertHandle; diff --git a/net/base/x509_certificate_nss.cc b/net/base/x509_certificate_nss.cc index 6dc837f9..ef4b8cd 100644 --- a/net/base/x509_certificate_nss.cc +++ b/net/base/x509_certificate_nss.cc @@ -4,16 +4,227 @@ #include "net/base/x509_certificate.h" +// Work around https://bugzilla.mozilla.org/show_bug.cgi?id=455424 +// until NSS 3.12.2 comes out and we update to it. +#define Lock FOO_NSS_Lock +#include <cert.h> +#include <prtime.h> +#include <secder.h> +#include <sechash.h> +#undef Lock + +#include "base/histogram.h" #include "base/logging.h" +#include "base/time.h" +#include "base/nss_init.h" namespace net { -// TODO(port): finish implementing. -// At the moment, all that's here is just enough to prevent a link error. +// Calculates the SHA-1 fingerprint of the certificate. Returns an empty +// (all zero) fingerprint on failure. +X509Certificate::Fingerprint CalculateFingerprint( + X509Certificate::OSCertHandle cert) { + X509Certificate::Fingerprint sha1; + memset(sha1.data, 0, sizeof(sha1.data)); + + DCHECK(NULL != cert->derCert.data); + DCHECK(0 != cert->derCert.len); + + SECStatus rv = HASH_HashBuf(HASH_AlgSHA1, sha1.data, + cert->derCert.data, cert->derCert.len); + DCHECK(rv == SECSuccess); -X509Certificate::~X509Certificate() { - // We might not be in the cache, but it is safe to remove ourselves anyway. - X509Certificate::Cache::GetInstance()->Remove(this); + return sha1; +} + +namespace { + +// TODO(port): Implement this more simply, and put it in the right place +base::Time PRTimeToBaseTime(PRTime prtime) { + PRExplodedTime prxtime; + PR_ExplodeTime(prtime, PR_GMTParameters, &prxtime); + + base::Time::Exploded exploded; + exploded.year = prxtime.tm_year; + exploded.month = prxtime.tm_month + 1; + exploded.day_of_week = prxtime.tm_wday; + exploded.day_of_month = prxtime.tm_mday; + exploded.hour = prxtime.tm_hour; + exploded.minute = prxtime.tm_min; + exploded.second = prxtime.tm_sec; + exploded.millisecond = prxtime.tm_usec / 1000; + + return base::Time::FromUTCExploded(exploded); +} + +void ParsePrincipal(SECItem *der_name, + X509Certificate::Principal* principal) { + + CERTName name; + PRArenaPool *arena = NULL; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + DCHECK(arena != NULL); + if (arena == NULL) + return; + + // TODO(dkegel): is CERT_NameTemplate what we always want here? + SECStatus rv; + rv = SEC_QuickDERDecodeItem(arena, &name, CERT_NameTemplate, der_name); + DCHECK(rv == SECSuccess); + if ( rv != SECSuccess ) { + PORT_FreeArena(arena, PR_FALSE); + return; + } + + std::vector<std::string> common_names, locality_names, state_names, + country_names; + + // TODO(jcampan): add business_category and serial_number. + static const SECOidTag kOIDs[] = { + SEC_OID_AVA_COMMON_NAME, + SEC_OID_AVA_LOCALITY, + SEC_OID_AVA_STATE_OR_PROVINCE, + SEC_OID_AVA_COUNTRY_NAME, + 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[] = { + &common_names, &locality_names, + &state_names, &country_names, + &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; + std::string value(reinterpret_cast<char *>(decode_item->data), + decode_item->len); + values[oid]->push_back(value); + SECITEM_FreeItem(decode_item, PR_TRUE); + break; + } + } + } + } + + // We don't expect to have more than one CN, L, S, and C. + std::vector<std::string>* single_value_lists[4] = { + &common_names, &locality_names, &state_names, &country_names }; + 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(single_value_lists); ++i) { + DCHECK(single_value_lists[i]->size() <= 1); + if (single_value_lists[i]->size() > 0) + *(single_values[i]) = (*(single_value_lists[i]))[0]; + } + PORT_FreeArena(arena, PR_FALSE); +} + +void ParseDate(SECItem* der_date, base::Time* result) { + PRTime prtime; + SECStatus rv = DER_DecodeTimeChoice(&prtime, der_date); + DCHECK(rv == SECSuccess); + *result = PRTimeToBaseTime(prtime); +} + +void GetCertSubjectAltNamesOfType(X509Certificate::OSCertHandle cert_handle, + CERTGeneralNameType name_type, + std::vector<std::string>* result) { + + SECItem alt_name; + SECStatus rv = CERT_FindCertExtension(cert_handle, + SEC_OID_X509_SUBJECT_ALT_NAME, &alt_name); + if (rv != SECSuccess) + return; + + PRArenaPool* arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + DCHECK(arena != NULL); + + CERTGeneralName* alt_name_list; + alt_name_list = CERT_DecodeAltNameExtension(arena, &alt_name); + + CERTGeneralName* name = alt_name_list; + while (name) { + // For future extension: We're assuming that these values are of types + // RFC822Name, DNSName or URI. See the mac code for notes. + DCHECK(name->type == certRFC822Name || + name->type == certDNSName || + name->type == certURI); + if (name->type == name_type) { + unsigned char *p = name->name.other.data; + int len = name->name.other.len; + std::string value = std::string(reinterpret_cast<char *>(p), len); + result->push_back(value); + } + name = CERT_GetNextGeneralName(name); + if (name == alt_name_list) + break; + } + PORT_FreeArena(arena, PR_FALSE); +} + +} // namespace + +void X509Certificate::Initialize() { + ParsePrincipal(&cert_handle_->derSubject, &subject_); + ParsePrincipal(&cert_handle_->derIssuer, &issuer_); + + ParseDate(&cert_handle_->validity.notBefore, &valid_start_); + ParseDate(&cert_handle_->validity.notAfter, &valid_expiry_); + + fingerprint_ = CalculateFingerprint(cert_handle_); + + // Store the certificate in the cache in case we need it later. + X509Certificate::Cache::GetInstance()->Insert(this); +} + +// static +X509Certificate* X509Certificate::CreateFromHandle(OSCertHandle cert_handle) { + DCHECK(cert_handle); + + // Check if we already have this certificate in memory. + X509Certificate::Cache* cache = X509Certificate::Cache::GetInstance(); + X509Certificate* cert = cache->Find(CalculateFingerprint(cert_handle)); + if (cert) { + // We've found a certificate with the same fingerprint in our cache. We own + // the |cert_handle|, which makes it our job to free it. + CERT_DestroyCertificate(cert_handle); + DHISTOGRAM_COUNTS(L"X509CertificateReuseCount", 1); + return cert; + } + // Otherwise, allocate a new object. + return new X509Certificate(cert_handle); +} + +// static +X509Certificate* X509Certificate::CreateFromBytes(const char* data, + int length) { + base::EnsureNSSInit(); + + SECItem der_cert; + der_cert.data = reinterpret_cast<unsigned char *>(const_cast<char *>(data)); + der_cert.len = length; + OSCertHandle cert_handle = + CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &der_cert, + NULL, PR_FALSE, PR_TRUE); + if (!cert_handle) + return NULL; + + return CreateFromHandle(cert_handle); } // static @@ -23,9 +234,35 @@ X509Certificate* X509Certificate::CreateFromPickle(const Pickle& pickle, return NULL; } +X509Certificate::X509Certificate(OSCertHandle cert_handle) + : cert_handle_(cert_handle) { + Initialize(); +} + void X509Certificate::Persist(Pickle* pickle) { NOTIMPLEMENTED(); } +X509Certificate::~X509Certificate() { + // We might not be in the cache, but it is safe to remove ourselves anyway. + X509Certificate::Cache::GetInstance()->Remove(this); + if (cert_handle_) + CERT_DestroyCertificate(cert_handle_); +} + +void X509Certificate::GetDNSNames(std::vector<std::string>* dns_names) const { + dns_names->clear(); + + // Compare with CERT_VerifyCertName(). + GetCertSubjectAltNamesOfType(cert_handle_, certDNSName, dns_names); + + // TODO(port): suppress nss's support of the obsolete extension + // SEC_OID_NS_CERT_EXT_SSL_SERVER_NAME + // by providing our own authCertificate callback. + + if (dns_names->empty()) + dns_names->push_back(subject_.common_name); +} + } // namespace net |