// Copyright (c) 2006-2008 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 #include "base/logging.h" #include "base/metrics/histogram.h" #include "base/singleton.h" #include "base/string_piece.h" #include "base/time.h" #include "net/base/pem_tokenizer.h" namespace net { namespace { // Returns true if this cert fingerprint is the null (all zero) fingerprint. // We use this as a bogus fingerprint value. bool IsNullFingerprint(const SHA1Fingerprint& fingerprint) { for (size_t i = 0; i < arraysize(fingerprint.data); ++i) { if (fingerprint.data[i] != 0) return false; } return true; } // Indicates the order to use when trying to decode binary data, which is // based on (speculation) as to what will be most common -> least common const X509Certificate::Format kFormatDecodePriority[] = { X509Certificate::FORMAT_SINGLE_CERTIFICATE, X509Certificate::FORMAT_PKCS7 }; // The PEM block header used for DER certificates const char kCertificateHeader[] = "CERTIFICATE"; // The PEM block header used for PKCS#7 data const char kPKCS7Header[] = "PKCS7"; } // namespace bool X509Certificate::LessThan::operator()(X509Certificate* lhs, X509Certificate* rhs) const { if (lhs == rhs) return false; SHA1FingerprintLessThan fingerprint_functor; return fingerprint_functor(lhs->fingerprint_, rhs->fingerprint_); } // A thread-safe cache for X509Certificate objects. // // The cache does not hold a reference to the certificate objects. The objects // must |Remove| themselves from the cache upon destruction (or else the cache // will be holding dead pointers to the objects). // TODO(rsleevi): There exists a chance of a use-after-free, due to a race // between Find() and Remove(). See http://crbug.com/49377 class X509Certificate::Cache { public: static Cache* GetInstance(); void Insert(X509Certificate* cert); void Remove(X509Certificate* cert); X509Certificate* Find(const SHA1Fingerprint& fingerprint); private: typedef std::map CertMap; // Obtain an instance of X509Certificate::Cache via GetInstance(). Cache() {} friend struct DefaultSingletonTraits; // You must acquire this lock before using any private data of this object. // You must not block while holding this lock. Lock lock_; // The certificate cache. You must acquire |lock_| before using |cache_|. CertMap cache_; DISALLOW_COPY_AND_ASSIGN(Cache); }; // Get the singleton object for the cache. // static X509Certificate::Cache* X509Certificate::Cache::GetInstance() { return Singleton::get(); } // Insert |cert| into the cache. The cache does NOT AddRef |cert|. // Any existing certificate with the same fingerprint will be replaced. void X509Certificate::Cache::Insert(X509Certificate* cert) { AutoLock lock(lock_); DCHECK(!IsNullFingerprint(cert->fingerprint())) << "Only insert certs with real fingerprints."; cache_[cert->fingerprint()] = cert; }; // Remove |cert| from the cache. The cache does not assume that |cert| is // already in the cache. void X509Certificate::Cache::Remove(X509Certificate* cert) { AutoLock lock(lock_); CertMap::iterator pos(cache_.find(cert->fingerprint())); if (pos == cache_.end()) return; // It is not an error to remove a cert that is not in the cache. cache_.erase(pos); }; // Find a certificate in the cache with the given fingerprint. If one does // not exist, this method returns NULL. X509Certificate* X509Certificate::Cache::Find( const SHA1Fingerprint& fingerprint) { AutoLock lock(lock_); CertMap::iterator pos(cache_.find(fingerprint)); if (pos == cache_.end()) return NULL; return pos->second; }; // static X509Certificate* X509Certificate::CreateFromHandle( OSCertHandle cert_handle, Source source, const OSCertHandles& intermediates) { DCHECK(cert_handle); DCHECK(source != SOURCE_UNUSED); // Check if we already have this certificate in memory. X509Certificate::Cache* cache = X509Certificate::Cache::GetInstance(); X509Certificate* cached_cert = cache->Find(CalculateFingerprint(cert_handle)); if (cached_cert) { DCHECK(cached_cert->source_ != SOURCE_UNUSED); if (cached_cert->source_ > source || (cached_cert->source_ == source && cached_cert->HasIntermediateCertificates(intermediates))) { // Return the certificate with the same fingerprint from our cache. DHISTOGRAM_COUNTS("X509CertificateReuseCount", 1); return cached_cert; } // Else the new cert is better and will replace the old one in the cache. } // Otherwise, allocate and cache a new object. X509Certificate* cert = new X509Certificate(cert_handle, source, intermediates); cache->Insert(cert); return cert; } // static X509Certificate* X509Certificate::CreateFromBytes(const char* data, int length) { OSCertHandle cert_handle = CreateOSCertHandleFromBytes(data, length); if (!cert_handle) return NULL; X509Certificate* cert = CreateFromHandle(cert_handle, SOURCE_LONE_CERT_IMPORT, OSCertHandles()); FreeOSCertHandle(cert_handle); return cert; } CertificateList X509Certificate::CreateCertificateListFromBytes( const char* data, int length, int format) { OSCertHandles certificates; // Check to see if it is in a PEM-encoded form. This check is performed // first, as both OS X and NSS will both try to convert if they detect // PEM encoding, except they don't do it consistently between the two. base::StringPiece data_string(data, length); std::vector pem_headers; // To maintain compatibility with NSS/Firefox, CERTIFICATE is a universally // valid PEM block header for any format. pem_headers.push_back(kCertificateHeader); if (format & FORMAT_PKCS7) pem_headers.push_back(kPKCS7Header); PEMTokenizer pem_tok(data_string, pem_headers); while (pem_tok.GetNext()) { std::string decoded(pem_tok.data()); OSCertHandle handle = NULL; if (format & FORMAT_PEM_CERT_SEQUENCE) handle = CreateOSCertHandleFromBytes(decoded.c_str(), decoded.size()); if (handle != NULL) { // Parsed a DER encoded certificate. All PEM blocks that follow must // also be DER encoded certificates wrapped inside of PEM blocks. format = FORMAT_PEM_CERT_SEQUENCE; certificates.push_back(handle); continue; } // If the first block failed to parse as a DER certificate, and // formats other than PEM are acceptable, check to see if the decoded // data is one of the accepted formats. if (format & ~FORMAT_PEM_CERT_SEQUENCE) { for (size_t i = 0; certificates.empty() && i < arraysize(kFormatDecodePriority); ++i) { if (format & kFormatDecodePriority[i]) { certificates = CreateOSCertHandlesFromBytes(decoded.c_str(), decoded.size(), kFormatDecodePriority[i]); } } } // Stop parsing after the first block for any format but a sequence of // PEM-encoded DER certificates. The case of FORMAT_PEM_CERT_SEQUENCE // is handled above, and continues processing until a certificate fails // to parse. break; } // Try each of the formats, in order of parse preference, to see if |data| // contains the binary representation of a Format, if it failed to parse // as a PEM certificate/chain. for (size_t i = 0; certificates.empty() && i < arraysize(kFormatDecodePriority); ++i) { if (format & kFormatDecodePriority[i]) certificates = CreateOSCertHandlesFromBytes(data, length, kFormatDecodePriority[i]); } CertificateList results; // No certificates parsed. if (certificates.empty()) return results; for (OSCertHandles::iterator it = certificates.begin(); it != certificates.end(); ++it) { X509Certificate* result = CreateFromHandle(*it, SOURCE_LONE_CERT_IMPORT, OSCertHandles()); results.push_back(scoped_refptr(result)); FreeOSCertHandle(*it); } return results; } X509Certificate::X509Certificate(OSCertHandle cert_handle, Source source, const OSCertHandles& intermediates) : cert_handle_(DupOSCertHandle(cert_handle)), source_(source) { #if defined(OS_MACOSX) || defined(OS_WIN) || defined(USE_OPENSSL) // Copy/retain the intermediate cert handles. for (size_t i = 0; i < intermediates.size(); ++i) intermediate_ca_certs_.push_back(DupOSCertHandle(intermediates[i])); #endif // Platform-specific initialization. Initialize(); } X509Certificate::X509Certificate(const std::string& subject, const std::string& issuer, base::Time start_date, base::Time expiration_date) : subject_(subject), issuer_(issuer), valid_start_(start_date), valid_expiry_(expiration_date), cert_handle_(NULL), source_(SOURCE_UNUSED) { memset(fingerprint_.data, 0, sizeof(fingerprint_.data)); } 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_) FreeOSCertHandle(cert_handle_); #if defined(OS_MACOSX) || defined(OS_WIN) for (size_t i = 0; i < intermediate_ca_certs_.size(); ++i) FreeOSCertHandle(intermediate_ca_certs_[i]); #endif } bool X509Certificate::HasExpired() const { return base::Time::Now() > valid_expiry(); } bool X509Certificate::HasIntermediateCertificate(OSCertHandle cert) { #if defined(OS_MACOSX) || defined(OS_WIN) || defined(USE_OPENSSL) for (size_t i = 0; i < intermediate_ca_certs_.size(); ++i) { if (IsSameOSCert(cert, intermediate_ca_certs_[i])) return true; } return false; #else return true; #endif } bool X509Certificate::HasIntermediateCertificates(const OSCertHandles& certs) { for (size_t i = 0; i < certs.size(); ++i) { if (!HasIntermediateCertificate(certs[i])) return false; } return true; } } // namespace net