forked from mirrors/gecko-dev
Bug 1487228 - (2/2) avoid holding CERTCertList instances long-term in nsNSSCertList r=jcj
Each instance of CERTCertList creates a PLArena with a chunk size of 2048 bytes, but only needs space for 3 pointers per certificate in the list. The majority of the time Gecko uses CERTCertList, we'll store ~3 certificates (although in some cases we do store a few hundred, such as in tests or the certificate manager). This is fairly inefficient. This patch starts the process of avoiding using CERTCertList in Gecko by converting nsNSSCertList (i.e. nsIX509CertList) (as well as nsNSSCertListEnumerator) to use a more efficient data structure to hold references to certificates long-term. Future follow-up patches could (and should) update certificate verification APIs in PSM to avoid CERTCertList as well. Depends on D5096 Differential Revision: https://phabricator.services.mozilla.com/D5097 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
8f21632c33
commit
fae63f9b28
3 changed files with 100 additions and 96 deletions
|
|
@ -53,6 +53,26 @@ using namespace mozilla::psm;
|
|||
|
||||
extern LazyLogModule gPIPNSSLog;
|
||||
|
||||
class nsNSSCertListEnumerator : public nsSimpleEnumerator
|
||||
{
|
||||
public:
|
||||
NS_DECL_NSISIMPLEENUMERATOR
|
||||
|
||||
const nsID& DefaultInterface() override { return NS_GET_IID(nsIX509Cert); }
|
||||
|
||||
explicit nsNSSCertListEnumerator(
|
||||
const std::vector<UniqueCERTCertificate>& certs);
|
||||
|
||||
nsNSSCertListEnumerator(const nsNSSCertListEnumerator&) = delete;
|
||||
void operator=(const nsNSSCertListEnumerator&) = delete;
|
||||
|
||||
private:
|
||||
virtual ~nsNSSCertListEnumerator() = default;
|
||||
|
||||
std::vector<UniqueCERTCertificate> mCerts;
|
||||
std::vector<UniqueCERTCertificate>::const_iterator mPosition;
|
||||
};
|
||||
|
||||
// This is being stored in an uint32_t that can otherwise
|
||||
// only take values from nsIX509Cert's list of cert types.
|
||||
// As nsIX509Cert is frozen, we choose a value not contained
|
||||
|
|
@ -837,18 +857,31 @@ NS_IMPL_ISUPPORTS_CI(nsNSSCertList,
|
|||
|
||||
nsNSSCertList::nsNSSCertList(UniqueCERTCertList certList)
|
||||
{
|
||||
if (certList) {
|
||||
mCertList = std::move(certList);
|
||||
} else {
|
||||
mCertList = UniqueCERTCertList(CERT_NewCertList());
|
||||
// Commonly we'll store only 3 certificates. If this is a verified certificate
|
||||
// chain, it may be as many as 8 certificates. If this is a list of all known
|
||||
// certificates, it may be a few hundred. We'll optimize for the common case
|
||||
// (i.e. a verified certificate chain).
|
||||
mCerts.reserve(8);
|
||||
if (certList.get()) {
|
||||
for (CERTCertListNode* node = CERT_LIST_HEAD(certList.get());
|
||||
!CERT_LIST_END(node, certList.get());
|
||||
node = CERT_LIST_NEXT(node)) {
|
||||
UniqueCERTCertificate cert(CERT_DupCertificate(node->cert));
|
||||
mCerts.push_back(std::move(cert));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nsNSSCertList::nsNSSCertList()
|
||||
{
|
||||
mCertList = UniqueCERTCertList(CERT_NewCertList());
|
||||
// Commonly we'll store only 3 certificates. If this is a verified certificate
|
||||
// chain, it may be as many as 8 certificates. If this is a list of all known
|
||||
// certificates, it may be a few hundred. We'll optimize for the common case
|
||||
// (i.e. a verified certificate chain).
|
||||
mCerts.reserve(8);
|
||||
}
|
||||
|
||||
// This is the implementation of nsIX509CertList.getCertList().
|
||||
nsNSSCertList*
|
||||
nsNSSCertList::GetCertList()
|
||||
{
|
||||
|
|
@ -864,15 +897,7 @@ nsNSSCertList::AddCert(nsIX509Cert* aCert)
|
|||
NS_ERROR("Somehow got nullptr for mCertificate in nsNSSCertificate.");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (!mCertList) {
|
||||
NS_ERROR("Somehow got nullptr for mCertList in nsNSSCertList.");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
if (CERT_AddCertToListTail(mCertList.get(), cert.get()) != SECSuccess) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
Unused << cert.release(); // Ownership transferred to the cert list.
|
||||
mCerts.push_back(std::move(cert));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
@ -908,11 +933,6 @@ nsNSSCertList::DupCertList(const UniqueCERTCertList& certList)
|
|||
NS_IMETHODIMP
|
||||
nsNSSCertList::AsPKCS7Blob(/*out*/ nsACString& result)
|
||||
{
|
||||
MOZ_ASSERT(mCertList);
|
||||
if (!mCertList) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
UniqueNSSCMSMessage cmsg(NSS_CMSMessage_Create(nullptr));
|
||||
if (!cmsg) {
|
||||
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
|
||||
|
|
@ -988,27 +1008,12 @@ nsNSSCertList::AsPKCS7Blob(/*out*/ nsACString& result)
|
|||
NS_IMETHODIMP
|
||||
nsNSSCertList::Write(nsIObjectOutputStream* aStream)
|
||||
{
|
||||
NS_ENSURE_STATE(mCertList);
|
||||
nsresult rv = NS_OK;
|
||||
|
||||
// First, enumerate the certs to get the length of the list
|
||||
uint32_t certListLen = 0;
|
||||
CERTCertListNode* node = nullptr;
|
||||
for (node = CERT_LIST_HEAD(mCertList);
|
||||
!CERT_LIST_END(node, mCertList);
|
||||
node = CERT_LIST_NEXT(node), ++certListLen) {
|
||||
}
|
||||
|
||||
// Write the length of the list
|
||||
rv = aStream->Write32(certListLen);
|
||||
nsresult rv = aStream->Write32(mCerts.size());
|
||||
|
||||
// Repeat the loop, and serialize each certificate
|
||||
node = nullptr;
|
||||
for (node = CERT_LIST_HEAD(mCertList);
|
||||
!CERT_LIST_END(node, mCertList);
|
||||
node = CERT_LIST_NEXT(node))
|
||||
{
|
||||
nsCOMPtr<nsIX509Cert> cert = nsNSSCertificate::Create(node->cert);
|
||||
// Serialize each certificate
|
||||
for (const auto& certRef : mCerts) {
|
||||
nsCOMPtr<nsIX509Cert> cert = nsNSSCertificate::Create(certRef.get());
|
||||
if (!cert) {
|
||||
rv = NS_ERROR_OUT_OF_MEMORY;
|
||||
break;
|
||||
|
|
@ -1027,16 +1032,13 @@ nsNSSCertList::Write(nsIObjectOutputStream* aStream)
|
|||
NS_IMETHODIMP
|
||||
nsNSSCertList::Read(nsIObjectInputStream* aStream)
|
||||
{
|
||||
NS_ENSURE_STATE(mCertList);
|
||||
nsresult rv = NS_OK;
|
||||
|
||||
uint32_t certListLen;
|
||||
rv = aStream->Read32(&certListLen);
|
||||
nsresult rv = aStream->Read32(&certListLen);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
for(uint32_t i = 0; i < certListLen; ++i) {
|
||||
for (uint32_t i = 0; i < certListLen; ++i) {
|
||||
nsCOMPtr<nsISupports> certSupports;
|
||||
rv = aStream->ReadObject(true, getter_AddRefs(certSupports));
|
||||
if (NS_FAILED(rv)) {
|
||||
|
|
@ -1056,13 +1058,7 @@ nsNSSCertList::Read(nsIObjectInputStream* aStream)
|
|||
NS_IMETHODIMP
|
||||
nsNSSCertList::GetEnumerator(nsISimpleEnumerator** _retval)
|
||||
{
|
||||
if (!mCertList) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsISimpleEnumerator> enumerator =
|
||||
new nsNSSCertListEnumerator(mCertList);
|
||||
|
||||
nsCOMPtr<nsISimpleEnumerator> enumerator(new nsNSSCertListEnumerator(mCerts));
|
||||
enumerator.forget(_retval);
|
||||
return NS_OK;
|
||||
}
|
||||
|
|
@ -1219,16 +1215,14 @@ nsNSSCertList::GetRootCertificate(/* out */ nsCOMPtr<nsIX509Cert>& aRoot)
|
|||
if (aRoot) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
CERTCertListNode* rootNode = CERT_LIST_TAIL(mCertList);
|
||||
if (!rootNode) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
if (CERT_LIST_END(rootNode, mCertList)) {
|
||||
// Empty list, leave aRoot empty
|
||||
// If the list is empty, leave aRoot empty.
|
||||
if (mCerts.size() < 1) {
|
||||
return NS_OK;
|
||||
}
|
||||
// Duplicates the certificate
|
||||
aRoot = nsNSSCertificate::Create(rootNode->cert);
|
||||
const UniqueCERTCertificate& cert = mCerts.back();
|
||||
// This increases the refcount on the underlying CERTCertificate, which aRoot
|
||||
// will own.
|
||||
aRoot = nsNSSCertificate::Create(cert.get());
|
||||
if (!aRoot) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
|
@ -1236,39 +1230,41 @@ nsNSSCertList::GetRootCertificate(/* out */ nsCOMPtr<nsIX509Cert>& aRoot)
|
|||
}
|
||||
|
||||
nsNSSCertListEnumerator::nsNSSCertListEnumerator(
|
||||
const UniqueCERTCertList& certList)
|
||||
const std::vector<UniqueCERTCertificate>& certs)
|
||||
{
|
||||
MOZ_ASSERT(certList);
|
||||
mCertList = nsNSSCertList::DupCertList(certList);
|
||||
mCerts.reserve(certs.size());
|
||||
// Unfortunately we can't just clone the vector because we have to ensure the
|
||||
// reference counts on the CERTCertificates are accurate.
|
||||
for (const auto& certRef : certs) {
|
||||
UniqueCERTCertificate cert(CERT_DupCertificate(certRef.get()));
|
||||
mCerts.push_back(std::move(cert));
|
||||
}
|
||||
mPosition = mCerts.cbegin();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsNSSCertListEnumerator::HasMoreElements(bool* _retval)
|
||||
{
|
||||
NS_ENSURE_TRUE(mCertList, NS_ERROR_FAILURE);
|
||||
|
||||
*_retval = !CERT_LIST_EMPTY(mCertList);
|
||||
*_retval = mPosition != mCerts.cend();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsNSSCertListEnumerator::GetNext(nsISupports** _retval)
|
||||
{
|
||||
NS_ENSURE_TRUE(mCertList, NS_ERROR_FAILURE);
|
||||
|
||||
CERTCertListNode* node = CERT_LIST_HEAD(mCertList);
|
||||
if (CERT_LIST_END(node, mCertList)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
*_retval = nullptr;
|
||||
if (mPosition == mCerts.cend()) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIX509Cert> nssCert = nsNSSCertificate::Create(node->cert);
|
||||
const UniqueCERTCertificate& certRef = *mPosition;
|
||||
// nsNSSCertificate::Create calls nsNSSCertificate::nsNSSCertificate, which
|
||||
// increases the reference count on the underlying CERTCertificate itself.
|
||||
nsCOMPtr<nsIX509Cert> nssCert = nsNSSCertificate::Create(certRef.get());
|
||||
if (!nssCert) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
nssCert.forget(_retval);
|
||||
|
||||
CERT_RemoveCertListNode(node);
|
||||
mPosition++;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -81,7 +81,12 @@ public:
|
|||
NS_DECL_NSIX509CERTLIST
|
||||
NS_DECL_NSISERIALIZABLE
|
||||
|
||||
// certList is adopted
|
||||
// The only way to call this is with std::move(some cert list) (because the
|
||||
// copy constructor should be deleted for UniqueCERTCertList), so we
|
||||
// effectively take ownership of it. What actually happens is we iterate
|
||||
// through the list getting our own owned reference to each certificate in the
|
||||
// list, and then the UniqueCERTCertList is dropped as it goes out of scope
|
||||
// (thus releasing its own reference to each certificate).
|
||||
explicit nsNSSCertList(mozilla::UniqueCERTCertList certList);
|
||||
|
||||
nsNSSCertList();
|
||||
|
|
@ -117,29 +122,12 @@ public:
|
|||
private:
|
||||
virtual ~nsNSSCertList() {}
|
||||
|
||||
mozilla::UniqueCERTCertList mCertList;
|
||||
std::vector<mozilla::UniqueCERTCertificate> mCerts;
|
||||
|
||||
nsNSSCertList(const nsNSSCertList&) = delete;
|
||||
void operator=(const nsNSSCertList&) = delete;
|
||||
};
|
||||
|
||||
class nsNSSCertListEnumerator : public nsSimpleEnumerator
|
||||
{
|
||||
public:
|
||||
NS_DECL_NSISIMPLEENUMERATOR
|
||||
|
||||
const nsID& DefaultInterface() override { return NS_GET_IID(nsIX509Cert); }
|
||||
|
||||
explicit nsNSSCertListEnumerator(const mozilla::UniqueCERTCertList& certList);
|
||||
private:
|
||||
virtual ~nsNSSCertListEnumerator() {}
|
||||
|
||||
mozilla::UniqueCERTCertList mCertList;
|
||||
|
||||
nsNSSCertListEnumerator(const nsNSSCertListEnumerator&) = delete;
|
||||
void operator=(const nsNSSCertListEnumerator&) = delete;
|
||||
};
|
||||
|
||||
#define NS_X509CERT_CID { /* 660a3226-915c-4ffb-bb20-8985a632df05 */ \
|
||||
0x660a3226, \
|
||||
0x915c, \
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "gtest/gtest.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsISimpleEnumerator.h"
|
||||
#include "nsITransportSecurityInfo.h"
|
||||
#include "nsIX509Cert.h"
|
||||
#include "nsIX509CertList.h"
|
||||
|
|
@ -29,7 +30,8 @@
|
|||
// in service workers. See bug 1248628.
|
||||
void
|
||||
deserializeAndVerify(const nsCString &serializedSecInfo,
|
||||
bool hasFailedCertChain)
|
||||
bool hasFailedCertChain,
|
||||
size_t failedCertChainLength = 0)
|
||||
{
|
||||
nsCOMPtr<nsISupports> secInfo;
|
||||
nsresult rv = NS_DeserializeObject(serializedSecInfo, getter_AddRefs(secInfo));
|
||||
|
|
@ -48,8 +50,26 @@ deserializeAndVerify(const nsCString &serializedSecInfo,
|
|||
rv = securityInfo->GetFailedCertChain(getter_AddRefs(failedChain));
|
||||
ASSERT_EQ(NS_OK, rv);
|
||||
|
||||
if (hasFailedCertChain) {
|
||||
if (hasFailedCertChain) {
|
||||
ASSERT_TRUE(failedChain);
|
||||
nsCOMPtr<nsISimpleEnumerator> enumerator;
|
||||
rv = failedChain->GetEnumerator(getter_AddRefs(enumerator));
|
||||
ASSERT_EQ(NS_OK, rv);
|
||||
bool hasMore;
|
||||
rv = enumerator->HasMoreElements(&hasMore);
|
||||
ASSERT_EQ(NS_OK, rv);
|
||||
size_t actualFailedCertChainLength = 0;
|
||||
while (hasMore) {
|
||||
nsCOMPtr<nsISupports> supports;
|
||||
rv = enumerator->GetNext(getter_AddRefs(supports));
|
||||
ASSERT_EQ(NS_OK, rv);
|
||||
nsCOMPtr<nsIX509Cert> cert(do_QueryInterface(supports));
|
||||
actualFailedCertChainLength++;
|
||||
ASSERT_TRUE(cert);
|
||||
rv = enumerator->HasMoreElements(&hasMore);
|
||||
ASSERT_EQ(NS_OK, rv);
|
||||
}
|
||||
ASSERT_EQ(failedCertChainLength, actualFailedCertChainLength);
|
||||
} else {
|
||||
ASSERT_FALSE(failedChain);
|
||||
}
|
||||
|
|
@ -231,6 +251,6 @@ TEST(psm_DeserializeCert, preSSLStatusConsolidationFailedCertChain)
|
|||
"uN2I5mM3NvMnP2Or19O1Bk//iGD6AyJfiZFcii+FsDrJhbzw6lakEV7O/EnD0kk2l7I0VMtg1xZBbEw7P6+V9zz5cAzaaq7EB0mC"
|
||||
"E+jJckSzSETBN+7lyVD8gwmHYxxZfPnUM/yvPbMU9L3xWD/z6HHwO6r+9m7BT+2pHjBC");
|
||||
|
||||
deserializeAndVerify(base64Serialization, true);
|
||||
deserializeAndVerify(base64Serialization, true, 2);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue