Bug 1029155 - Store peer certificate chain from failed connections on TransportSecurityInfo r=keeler
authorGarrett Robinson <grobinson@mozilla.com>
Fri, 15 Aug 2014 11:27:22 -0700
changeset 199908 8a00db3bafbd2ddd477a370ae96a54a40659bb3e
parent 199907 fd08e61066de9722826b84e2dd3dded073aa95d3
child 199909 229181c88a3c57bdcd9df78077a2e172d767a463
push id9784
push userryanvm@gmail.com
push dateSat, 16 Aug 2014 21:45:40 +0000
treeherderb2g-inbound@94ba78a42305 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskeeler
bugs1029155
milestone34.0a1
Bug 1029155 - Store peer certificate chain from failed connections on TransportSecurityInfo r=keeler
config/external/nss/nss.def
netwerk/socket/nsITransportSecurityInfo.idl
security/manager/ssl/src/SSLServerCertVerification.cpp
security/manager/ssl/src/TransportSecurityInfo.cpp
security/manager/ssl/src/TransportSecurityInfo.h
security/manager/ssl/src/nsNSSCertificate.cpp
security/manager/ssl/src/nsNSSCertificate.h
--- a/config/external/nss/nss.def
+++ b/config/external/nss/nss.def
@@ -642,16 +642,17 @@ SSL_GetSRTPCipher
 SSL_HandshakeCallback
 SSL_HandshakeNegotiatedExtension
 SSL_ImplementedCiphers DATA
 SSL_ImportFD
 SSL_NumImplementedCiphers DATA
 SSL_OptionSet
 SSL_OptionSetDefault
 SSL_PeerCertificate
+SSL_PeerCertificateChain
 SSL_PeerStapledOCSPResponses
 SSL_ResetHandshake
 SSL_SetCanFalseStartCallback
 SSL_SetNextProtoNego
 SSL_SetPKCS11PinArg
 SSL_SetSockPeerID
 SSL_SetSRTPCiphers
 SSL_SetStapledOCSPResponses
--- a/netwerk/socket/nsITransportSecurityInfo.idl
+++ b/netwerk/socket/nsITransportSecurityInfo.idl
@@ -1,14 +1,23 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
-[scriptable, uuid(8813d03b-e76c-4240-9691-d327d9b91e88)]
+interface nsIX509CertList;
+
+[scriptable, uuid(154898ce-e14f-4981-bfc5-00a41722f44b)]
 interface nsITransportSecurityInfo : nsISupports {
     readonly attribute unsigned long    securityState;
     readonly attribute wstring          errorMessage;
+
+    /**
+     * If certificate verification failed, this will be the peer certificate
+     * chain provided in the handshake, so it can be used for error reporting.
+     * If verification succeeded, this will be null.
+     */
+    readonly attribute nsIX509CertList failedCertChain;
 };
 
--- a/security/manager/ssl/src/SSLServerCertVerification.cpp
+++ b/security/manager/ssl/src/SSLServerCertVerification.cpp
@@ -616,52 +616,56 @@ private:
 class SSLServerCertVerificationJob : public nsRunnable
 {
 public:
   // Must be called only on the socket transport thread
   static SECStatus Dispatch(const RefPtr<SharedCertVerifier>& certVerifier,
                             const void* fdForLogging,
                             TransportSecurityInfo* infoObject,
                             CERTCertificate* serverCert,
+                            ScopedCERTCertList& peerCertChain,
                             SECItem* stapledOCSPResponse,
                             uint32_t providerFlags,
                             Time time,
                             PRTime prtime);
 private:
   NS_DECL_NSIRUNNABLE
 
   // Must be called only on the socket transport thread
   SSLServerCertVerificationJob(const RefPtr<SharedCertVerifier>& certVerifier,
                                const void* fdForLogging,
                                TransportSecurityInfo* infoObject,
                                CERTCertificate* cert,
+                               CERTCertList* peerCertChain,
                                SECItem* stapledOCSPResponse,
                                uint32_t providerFlags,
                                Time time,
                                PRTime prtime);
   const RefPtr<SharedCertVerifier> mCertVerifier;
   const void* const mFdForLogging;
   const RefPtr<TransportSecurityInfo> mInfoObject;
   const ScopedCERTCertificate mCert;
+  ScopedCERTCertList mPeerCertChain;
   const uint32_t mProviderFlags;
   const Time mTime;
   const PRTime mPRTime;
   const TimeStamp mJobStartTime;
   const ScopedSECItem mStapledOCSPResponse;
 };
 
 SSLServerCertVerificationJob::SSLServerCertVerificationJob(
     const RefPtr<SharedCertVerifier>& certVerifier, const void* fdForLogging,
     TransportSecurityInfo* infoObject, CERTCertificate* cert,
-    SECItem* stapledOCSPResponse, uint32_t providerFlags,
-    Time time, PRTime prtime)
+    CERTCertList* peerCertChain, SECItem* stapledOCSPResponse,
+    uint32_t providerFlags, Time time, PRTime prtime)
   : mCertVerifier(certVerifier)
   , mFdForLogging(fdForLogging)
   , mInfoObject(infoObject)
   , mCert(CERT_DupCertificate(cert))
+  , mPeerCertChain(peerCertChain)
   , mProviderFlags(providerFlags)
   , mTime(time)
   , mPRTime(prtime)
   , mJobStartTime(TimeStamp::Now())
   , mStapledOCSPResponse(SECITEM_DupItem(stapledOCSPResponse))
 {
 }
 
@@ -728,31 +732,34 @@ BlockServerCertChangeForSpdy(nsNSSSocket
   // Report an error - changed cert is confirmed
   PR_LOG(gPIPNSSLog, PR_LOG_DEBUG,
          ("SPDY Refused to allow new cert during renegotiation\n"));
   PR_SetError(SSL_ERROR_RENEGOTIATION_NOT_ALLOWED, 0);
   return SECFailure;
 }
 
 SECStatus
-AuthCertificate(CertVerifier& certVerifier, TransportSecurityInfo* infoObject,
-                CERTCertificate* cert, SECItem* stapledOCSPResponse,
-                uint32_t providerFlags, Time time)
+AuthCertificate(CertVerifier& certVerifier,
+                TransportSecurityInfo* infoObject,
+                CERTCertificate* cert,
+                ScopedCERTCertList& peerCertChain,
+                SECItem* stapledOCSPResponse,
+                uint32_t providerFlags,
+                Time time)
 {
   MOZ_ASSERT(infoObject);
   MOZ_ASSERT(cert);
 
   SECStatus rv;
 
   // We want to avoid storing any intermediate cert information when browsing
   // in private, transient contexts.
   bool saveIntermediates =
     !(providerFlags & nsISocketProvider::NO_PERMANENT_STORAGE);
 
-  ScopedCERTCertList certList;
   SECOidTag evOidPolicy;
   rv = certVerifier.VerifySSLServerCert(cert, stapledOCSPResponse,
                                         time, infoObject,
                                         infoObject->GetHostNameRaw(),
                                         saveIntermediates, nullptr,
                                         &evOidPolicy);
 
   // We want to remember the CA certs in the temp db, so that the application can find the
@@ -794,41 +801,57 @@ AuthCertificate(CertVerifier& certVerifi
 
     if (status && !status->mServerCert) {
       status->mServerCert = nsc;
       PR_LOG(gPIPNSSLog, PR_LOG_DEBUG,
              ("AuthCertificate setting NEW cert %p\n", status->mServerCert.get()));
     }
   }
 
+  if (rv != SECSuccess) {
+    // Certificate validation failed; store the peer certificate chain on
+    // infoObject so it can be used for error reporting. Note: infoObject
+    // indirectly takes ownership of peerCertChain.
+    infoObject->SetFailedCertChain(peerCertChain);
+  }
+
   return rv;
 }
 
 /*static*/ SECStatus
 SSLServerCertVerificationJob::Dispatch(
   const RefPtr<SharedCertVerifier>& certVerifier,
   const void* fdForLogging,
   TransportSecurityInfo* infoObject,
   CERTCertificate* serverCert,
+  ScopedCERTCertList& peerCertChain,
   SECItem* stapledOCSPResponse,
   uint32_t providerFlags,
   Time time,
   PRTime prtime)
 {
   // Runs on the socket transport thread
   if (!certVerifier || !infoObject || !serverCert) {
     NS_ERROR("Invalid parameters for SSL server cert validation");
     PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
     return SECFailure;
   }
 
+  // Copy the certificate list so the runnable can take ownership of it in the
+  // constructor.
+  // We can safely skip checking if NSS has already shut down here since we're
+  // in the middle of verifying a certificate.
+  nsNSSShutDownPreventionLock lock;
+  CERTCertList* peerCertChainCopy = nsNSSCertList::DupCertList(peerCertChain, lock);
+
   RefPtr<SSLServerCertVerificationJob> job(
     new SSLServerCertVerificationJob(certVerifier, fdForLogging, infoObject,
-                                     serverCert, stapledOCSPResponse,
-                                     providerFlags, time, prtime));
+                                     serverCert, peerCertChainCopy,
+                                     stapledOCSPResponse, providerFlags,
+                                     time, prtime));
 
   nsresult nrv;
   if (!gCertVerificationThreadPool) {
     nrv = NS_ERROR_NOT_INITIALIZED;
   } else {
     nrv = gCertVerificationThreadPool->Dispatch(job, NS_DISPATCH_NORMAL);
   }
   if (NS_FAILED(nrv)) {
@@ -868,18 +891,18 @@ SSLServerCertVerificationJob::Run()
       = Telemetry::SSL_SUCCESFUL_CERT_VALIDATION_TIME_MOZILLAPKIX;
     Telemetry::ID failureTelemetry
       = Telemetry::SSL_INITIAL_FAILED_CERT_VALIDATION_TIME_MOZILLAPKIX;
 
     // Reset the error code here so we can detect if AuthCertificate fails to
     // set the error code if/when it fails.
     PR_SetError(0, 0);
     SECStatus rv = AuthCertificate(*mCertVerifier, mInfoObject, mCert.get(),
-                                   mStapledOCSPResponse, mProviderFlags,
-                                   mTime);
+                                   mPeerCertChain, mStapledOCSPResponse,
+                                   mProviderFlags, mTime);
     if (rv == SECSuccess) {
       uint32_t interval = (uint32_t) ((TimeStamp::Now() - mJobStartTime).ToMilliseconds());
       RefPtr<SSLServerCertVerificationResult> restart(
         new SSLServerCertVerificationResult(mInfoObject, 0,
                                             successTelemetry, interval));
       restart->Dispatch();
       Telemetry::Accumulate(Telemetry::SSL_CERT_ERROR_OVERRIDES, 1);
       return NS_OK;
@@ -970,16 +993,19 @@ AuthCertificateHook(void* arg, PRFileDes
 
   ScopedCERTCertificate serverCert(SSL_PeerCertificate(fd));
 
   if (!checkSig || isServer || !socketInfo || !serverCert) {
       PR_SetError(PR_INVALID_STATE_ERROR, 0);
       return SECFailure;
   }
 
+  // Get the peer certificate chain for error reporting
+  ScopedCERTCertList peerCertChain(SSL_PeerCertificateChain(fd));
+
   socketInfo->SetFullHandshake();
 
   Time now(Now());
   PRTime prnow(PR_Now());
 
   if (BlockServerCertChangeForSpdy(socketInfo, serverCert) != SECSuccess)
     return SECFailure;
 
@@ -1015,28 +1041,29 @@ AuthCertificateHook(void* arg, PRFileDes
 
     // We *must* do certificate verification on a background thread because
     // we need the socket transport thread to be free for our OCSP requests,
     // and we *want* to do certificate verification on a background thread
     // because of the performance benefits of doing so.
     socketInfo->SetCertVerificationWaiting();
     SECStatus rv = SSLServerCertVerificationJob::Dispatch(
                      certVerifier, static_cast<const void*>(fd), socketInfo,
-                     serverCert, stapledOCSPResponse, providerFlags, now,
-                     prnow);
+                     serverCert, peerCertChain, stapledOCSPResponse,
+                     providerFlags, now, prnow);
     return rv;
   }
 
   // We can't do certificate verification on a background thread, because the
   // thread doing the network I/O may not interrupt its network I/O on receipt
   // of our SSLServerCertVerificationResult event, and/or it might not even be
   // a non-blocking socket.
 
   SECStatus rv = AuthCertificate(*certVerifier, socketInfo, serverCert,
-                                 stapledOCSPResponse, providerFlags, now);
+                                 peerCertChain, stapledOCSPResponse,
+                                 providerFlags, now);
   if (rv == SECSuccess) {
     Telemetry::Accumulate(Telemetry::SSL_CERT_ERROR_OVERRIDES, 1);
     return SECSuccess;
   }
 
   PRErrorCode error = PR_GetError();
   if (error != 0) {
     RefPtr<CertErrorRunnable> runnable(
--- a/security/manager/ssl/src/TransportSecurityInfo.cpp
+++ b/security/manager/ssl/src/TransportSecurityInfo.cpp
@@ -284,18 +284,18 @@ TransportSecurityInfo::GetInterface(cons
   }
   return rv;
 }
 
 // This is a new magic value. However, it re-uses the first 4 bytes
 // of the previous value. This is so when older versions attempt to
 // read a newer serialized TransportSecurityInfo, they will actually
 // fail and return NS_ERROR_FAILURE instead of silently failing.
-#define TRANSPORTSECURITYINFOMAGIC { 0xa9863a23, 0x28ea, 0x45d2, \
-  { 0xa2, 0x5a, 0x35, 0x7c, 0xae, 0xfa, 0x7f, 0x82 } }
+#define TRANSPORTSECURITYINFOMAGIC { 0xa9863a23, 0xf40a, 0x4060, \
+    { 0xb2, 0xe1, 0x62, 0xab, 0x2b, 0x85, 0x26, 0xa9 } }
 static NS_DEFINE_CID(kTransportSecurityInfoMagic, TRANSPORTSECURITYINFOMAGIC);
 
 NS_IMETHODIMP
 TransportSecurityInfo::Write(nsIObjectOutputStream* stream)
 {
   nsresult rv = stream->WriteID(kTransportSecurityInfoMagic);
   if (NS_FAILED(rv)) {
     return rv;
@@ -326,16 +326,25 @@ TransportSecurityInfo::Write(nsIObjectOu
     return rv;
   }
   nsCOMPtr<nsISerializable> serializable(mSSLStatus);
   rv = stream->WriteCompoundObject(serializable, NS_GET_IID(nsISSLStatus),
                                    true);
   if (NS_FAILED(rv)) {
     return rv;
   }
+
+  rv = NS_WriteOptionalCompoundObject(stream,
+                                      mFailedCertChain,
+                                      NS_GET_IID(nsIX509CertList),
+                                      true);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TransportSecurityInfo::Read(nsIObjectInputStream* stream)
 {
   nsID id;
   nsresult rv = stream->ReadID(&id);
@@ -381,16 +390,24 @@ TransportSecurityInfo::Read(nsIObjectInp
   rv = stream->ReadObject(true, getter_AddRefs(supports));
   if (NS_FAILED(rv)) {
     return rv;
   }
   mSSLStatus = reinterpret_cast<nsSSLStatus*>(supports.get());
   if (!mSSLStatus) {
     return NS_ERROR_FAILURE;
   }
+
+  nsCOMPtr<nsISupports> failedCertChainSupports;
+  rv = NS_ReadOptionalObject(stream, true, getter_AddRefs(failedCertChainSupports));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  mFailedCertChain = do_QueryInterface(failedCertChainSupports);
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TransportSecurityInfo::GetInterfaces(uint32_t *count, nsIID * **array)
 {
   *count = 0;
   *array = nullptr;
@@ -1067,9 +1084,35 @@ TransportSecurityInfo::SetStatusErrorBit
   mSSLStatus->mIsUntrusted = 
     collected_errors & nsICertOverrideService::ERROR_UNTRUSTED;
 
   RememberCertErrorsTable::GetInstance().RememberCertHasError(this,
                                                               mSSLStatus,
                                                               SECFailure);
 }
 
+NS_IMETHODIMP
+TransportSecurityInfo::GetFailedCertChain(nsIX509CertList** _result)
+{
+  NS_ASSERTION(_result, "non-NULL destination required");
+
+  *_result = mFailedCertChain;
+  NS_IF_ADDREF(*_result);
+
+  return NS_OK;
+}
+
+nsresult
+TransportSecurityInfo::SetFailedCertChain(ScopedCERTCertList& certList)
+{
+  nsNSSShutDownPreventionLock lock;
+  if (isAlreadyShutDown()) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsCOMPtr<nsIX509CertList> comCertList;
+  // nsNSSCertList takes ownership of certList
+  mFailedCertChain = new nsNSSCertList(certList, lock);
+
+  return NS_OK;
+}
+
 } } // namespace mozilla::psm
--- a/security/manager/ssl/src/TransportSecurityInfo.h
+++ b/security/manager/ssl/src/TransportSecurityInfo.h
@@ -2,26 +2,28 @@
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef _MOZILLA_PSM_TRANSPORTSECURITYINFO_H
 #define _MOZILLA_PSM_TRANSPORTSECURITYINFO_H
 
+#include "ScopedNSSTypes.h"
 #include "certt.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/RefPtr.h"
+#include "nsDataHashtable.h"
+#include "nsIAssociatedContentSecurity.h"
 #include "nsIInterfaceRequestor.h"
-#include "nsITransportSecurityInfo.h"
-#include "nsSSLStatus.h"
 #include "nsISSLStatusProvider.h"
-#include "nsIAssociatedContentSecurity.h"
+#include "nsITransportSecurityInfo.h"
 #include "nsNSSShutDown.h"
-#include "nsDataHashtable.h"
+#include "nsSSLStatus.h"
+#include "pkix/pkixtypes.h"
 
 namespace mozilla { namespace psm {
 
 enum SSLErrorMessageType {
   OverridableCertErrorMessage  = 1, // for *overridable* certificate errors
   PlainErrorMessage = 2             // all other errors (or "no error")
 };
 
@@ -69,16 +71,18 @@ public:
   void SetCanceled(PRErrorCode errorCode,
                    ::mozilla::psm::SSLErrorMessageType errorMessageType);
   
   /* Set SSL Status values */
   nsresult SetSSLStatus(nsSSLStatus *aSSLStatus);
   nsSSLStatus* SSLStatus() { return mSSLStatus; }
   void SetStatusErrorBits(nsIX509Cert & cert, uint32_t collected_errors);
 
+  nsresult SetFailedCertChain(ScopedCERTCertList& certList);
+
 private:
   mutable ::mozilla::Mutex mMutex;
 
 protected:
   nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
 
 private:
   uint32_t mSecurityState;
@@ -95,16 +99,19 @@ private:
                               nsString &result);
 
   int32_t mPort;
   nsXPIDLCString mHostName;
 
   /* SSL Status */
   mozilla::RefPtr<nsSSLStatus> mSSLStatus;
 
+  /* Peer cert chain for failed connections (for error reporting) */
+  nsCOMPtr<nsIX509CertList> mFailedCertChain;
+
   virtual void virtualDestroyNSSReference();
   void destructorSafeDestroyNSSReference();
 };
 
 class RememberCertErrorsTable
 {
 private:
   RememberCertErrorsTable();
--- a/security/manager/ssl/src/nsNSSCertificate.cpp
+++ b/security/manager/ssl/src/nsNSSCertificate.cpp
@@ -1540,17 +1540,19 @@ ConstructCERTCertListFromReversedDERArra
     cert.forget(); // cert is now owned by certList.
   }
 
   return SECSuccess;
 }
 
 } // namespace mozilla
 
-NS_IMPL_ISUPPORTS(nsNSSCertList, nsIX509CertList)
+NS_IMPL_ISUPPORTS(nsNSSCertList,
+                  nsIX509CertList,
+                  nsISerializable)
 
 nsNSSCertList::nsNSSCertList(ScopedCERTCertList& certList,
                              const nsNSSShutDownPreventionLock& proofOfLock)
 {
   if (certList) {
     mCertList = certList.forget();
   } else {
     mCertList = CERT_NewCertList();
@@ -1663,16 +1665,94 @@ void*
 nsNSSCertList::GetRawCertList()
 {
   // This function should only be called after adquiring a
   // nsNSSShutDownPreventionLock
   return mCertList.get();
 }
 
 NS_IMETHODIMP
+nsNSSCertList::Write(nsIObjectOutputStream* aStream)
+{
+  nsNSSShutDownPreventionLock locker;
+  if (isAlreadyShutDown()) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  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);
+
+  // 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);
+    if (!cert) {
+      rv = NS_ERROR_OUT_OF_MEMORY;
+      break;
+    }
+
+    nsCOMPtr<nsISerializable> serializableCert = do_QueryInterface(cert);
+    rv = aStream->WriteCompoundObject(serializableCert, NS_GET_IID(nsIX509Cert), true);
+    if (NS_FAILED(rv)) {
+      break;
+    }
+  }
+
+  return rv;
+}
+
+NS_IMETHODIMP
+nsNSSCertList::Read(nsIObjectInputStream* aStream)
+{
+  nsNSSShutDownPreventionLock locker;
+  if (isAlreadyShutDown()) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  NS_ENSURE_STATE(mCertList);
+  nsresult rv = NS_OK;
+
+  uint32_t certListLen;
+  rv = aStream->Read32(&certListLen);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  for(uint32_t i = 0; i < certListLen; ++i) {
+    nsCOMPtr<nsISupports> certSupports;
+    rv = aStream->ReadObject(true, getter_AddRefs(certSupports));
+    if (NS_FAILED(rv)) {
+      break;
+    }
+
+    nsCOMPtr<nsIX509Cert> cert = do_QueryInterface(certSupports);
+    rv = AddCert(cert);
+    if (NS_FAILED(rv)) {
+      break;
+    }
+  }
+
+  return rv;
+}
+
+NS_IMETHODIMP
 nsNSSCertList::GetEnumerator(nsISimpleEnumerator** _retval)
 {
   nsNSSShutDownPreventionLock locker;
   if (isAlreadyShutDown()) {
     return NS_ERROR_NOT_AVAILABLE;
   }
   nsCOMPtr<nsISimpleEnumerator> enumerator =
     new nsNSSCertListEnumerator(mCertList.get(), locker);
--- a/security/manager/ssl/src/nsNSSCertificate.h
+++ b/security/manager/ssl/src/nsNSSCertificate.h
@@ -77,21 +77,23 @@ namespace mozilla {
 
 SECStatus ConstructCERTCertListFromReversedDERArray(
             const mozilla::pkix::DERArray& certArray,
             /*out*/ mozilla::ScopedCERTCertList& certList);
 
 } // namespcae mozilla
 
 class nsNSSCertList: public nsIX509CertList,
+                     public nsISerializable,
                      public nsNSSShutDownObject
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIX509CERTLIST
+  NS_DECL_NSISERIALIZABLE
 
   // certList is adopted
   nsNSSCertList(mozilla::ScopedCERTCertList& certList,
                 const nsNSSShutDownPreventionLock& proofOfLock);
 
   nsNSSCertList();
 
   static CERTCertList* DupCertList(CERTCertList* aCertList,