Index: net/cert/x509_util_openssl.cc |
diff --git a/net/cert/x509_util_openssl.cc b/net/cert/x509_util_openssl.cc |
index 2327deddcf29cde90ab173a4e2a4da0a3d74d5d0..af83ed5d43fd1f8cac5cff9905682bb32d43742b 100644 |
--- a/net/cert/x509_util_openssl.cc |
+++ b/net/cert/x509_util_openssl.cc |
@@ -10,6 +10,7 @@ |
#include <openssl/mem.h> |
#include <algorithm> |
+#include <map> |
#include <memory> |
#include "base/lazy_instance.h" |
@@ -17,6 +18,7 @@ |
#include "base/macros.h" |
#include "base/strings/string_piece.h" |
#include "base/strings/string_util.h" |
+#include "base/synchronization/lock.h" |
#include "crypto/ec_private_key.h" |
#include "crypto/openssl_util.h" |
#include "crypto/rsa_private_key.h" |
@@ -172,34 +174,83 @@ bool SignAndDerEncodeCert(X509* cert, |
return DerEncodeCert(cert, der_encoded); |
} |
-struct DERCache { |
- std::string data; |
-}; |
+// Re-encoding the DER data via i2d_X509 is an expensive operation, but |
+// it's necessary for comparing two certificates. Re-encode at most once |
+// per certificate and cache the pointer to the data within the X509 cert |
+// using X509_set_ex_data. For memory efficiency the DER data itself is |
+// stored globally, shared between certificates and is deleted once all |
+// certificates that reference it are deleted. |
+class DerCache { |
+ public: |
+ // Returns nullptr only if DerEncodeCert() has failed. |
+ static const std::string* Get(X509* cert) { |
+ const std::string* cached_der = GetInstance().Retrieve(cert); |
+ if (cached_der) { |
+ return cached_der; |
+ } |
+ std::string der; |
+ if (!DerEncodeCert(cert, &der)) { |
+ return nullptr; |
+ } |
+ return GetInstance().Put(cert, std::move(der)); |
+ } |
-void DERCache_free(void* parent, void* ptr, CRYPTO_EX_DATA* ad, int idx, |
- long argl, void* argp) { |
- DERCache* der_cache = static_cast<DERCache*>(ptr); |
- delete der_cache; |
-} |
+ private: |
+ friend struct base::DefaultLazyInstanceTraits<DerCache>; |
-class DERCacheInitSingleton { |
- public: |
- DERCacheInitSingleton() { |
+ DerCache() { |
crypto::EnsureOpenSSLInit(); |
- der_cache_ex_index_ = X509_get_ex_new_index(0, 0, 0, 0, DERCache_free); |
- DCHECK_NE(-1, der_cache_ex_index_); |
+ x509_value_index_ = |
+ X509_get_ex_new_index(0, nullptr, nullptr, nullptr, &free_x509_value); |
+ DCHECK_NE(-1, x509_value_index_); |
} |
- int der_cache_ex_index() const { return der_cache_ex_index_; } |
+ const std::string* Retrieve(X509* cert) const { |
+ return static_cast<const std::string*>( |
+ X509_get_ex_data(cert, x509_value_index_)); |
+ } |
- private: |
- int der_cache_ex_index_; |
+ const std::string* Put(X509* cert, std::string der) { |
+ base::AutoLock lock(lock_); |
+ auto der_iter = count_by_der_.emplace(std::move(der), 0).first; |
+ der_iter->second++; // add a reference |
+ const std::string* cached_der = &der_iter->first; |
+ X509_set_ex_data(cert, x509_value_index_, |
+ const_cast<std::string*>(cached_der)); |
+ return cached_der; |
+ } |
- DISALLOW_COPY_AND_ASSIGN(DERCacheInitSingleton); |
-}; |
+ void Remove(const std::string& der) { |
+ base::AutoLock lock(lock_); |
+ auto der_iter = count_by_der_.find(der); |
+ DCHECK(count_by_der_.end() != der_iter); |
+ der_iter->second--; // remove a reference |
+ if (der_iter->second == 0) { |
+ count_by_der_.erase(der_iter); |
+ } |
+ } |
+ |
+ static DerCache& GetInstance() { |
+ static base::LazyInstance<DerCache>::Leaky instance = |
+ LAZY_INSTANCE_INITIALIZER; |
+ return instance.Get(); |
+ } |
-base::LazyInstance<DERCacheInitSingleton>::Leaky g_der_cache_singleton = |
- LAZY_INSTANCE_INITIALIZER; |
+ static void free_x509_value(void* parent, |
+ void* value, |
+ CRYPTO_EX_DATA* ad, |
+ int idx, |
+ long argl, |
+ void* argp) { |
+ const std::string* cached_der = static_cast<const std::string*>(value); |
+ DCHECK(cached_der); |
+ GetInstance().Remove(*cached_der); |
+ } |
+ |
+ int x509_value_index_; |
+ std::map<std::string, size_t> count_by_der_; |
+ base::Lock lock_; |
+}; |
} // namespace |
@@ -284,27 +335,15 @@ bool ParseDate(ASN1_TIME* x509_time, base::Time* time) { |
return ParseCertificateDate(str_date, format, time); |
} |
-// Returns true if |der_cache| points to valid data, false otherwise. |
-// (note: the DER-encoded data in |der_cache| is owned by |cert|, callers should |
+// Returns true if |out_der| points to valid data, false otherwise. |
+// (note: the DER-encoded data in |out_der| is owned by |cert|, callers should |
// not free it). |
-bool GetDER(X509* x509, base::StringPiece* der_cache) { |
- int x509_der_cache_index = |
- g_der_cache_singleton.Get().der_cache_ex_index(); |
- |
- // Re-encoding the DER data via i2d_X509 is an expensive operation, |
- // but it's necessary for comparing two certificates. Re-encode at |
- // most once per certificate and cache the data within the X509 cert |
- // using X509_set_ex_data. |
- DERCache* internal_cache = static_cast<DERCache*>( |
- X509_get_ex_data(x509, x509_der_cache_index)); |
- if (!internal_cache) { |
- std::unique_ptr<DERCache> new_cache(new DERCache); |
- if (!DerEncodeCert(x509, &new_cache->data)) |
- return false; |
- internal_cache = new_cache.get(); |
- X509_set_ex_data(x509, x509_der_cache_index, new_cache.release()); |
+bool GetDER(X509* x509, base::StringPiece* out_der) { |
+ const std::string* cached_der = DerCache::Get(x509); |
+ if (!cached_der) { |
+ return false; |
} |
- *der_cache = base::StringPiece(internal_cache->data); |
+ *out_der = base::StringPiece(*cached_der); |
return true; |
} |