bug 406999, Enhance EV performance; Never combine EV with Cert Overrides r=rrelyea, blocking1.9=mtschrep
authorkaie@kuix.de
Mon, 14 Jan 2008 07:45:07 -0800
changeset 10258 f3f1a898aeb235ee8a332e1bcfc4da667aa356e3
parent 10257 2630a67dc51be324ca2efb83198f4ce85caa208a
child 10259 f8d82e8fe534cb2c3a858391f285bc8bca1ba5fa
push idunknown
push userunknown
push dateunknown
reviewersrrelyea
bugs406999
milestone1.9b3pre
bug 406999, Enhance EV performance; Never combine EV with Cert Overrides r=rrelyea, blocking1.9=mtschrep
security/manager/ssl/src/nsIdentityChecking.cpp
security/manager/ssl/src/nsNSSCallbacks.cpp
security/manager/ssl/src/nsNSSCertificate.cpp
security/manager/ssl/src/nsNSSCertificate.h
security/manager/ssl/src/nsNSSIOLayer.cpp
security/manager/ssl/src/nsNSSIOLayer.h
--- a/security/manager/ssl/src/nsIdentityChecking.cpp
+++ b/security/manager/ssl/src/nsIdentityChecking.cpp
@@ -51,16 +51,20 @@
 #include "nsNSSCleaner.h"
 
 #ifdef DEBUG
 #ifndef PSM_ENABLE_TEST_EV_ROOTS
 #define PSM_ENABLE_TEST_EV_ROOTS
 #endif
 #endif
 
+#ifdef PR_LOGGING
+extern PRLogModuleInfo* gPIPNSSLog;
+#endif
+
 NSSCleanupAutoPtrClass(CERTCertificate, CERT_DestroyCertificate)
 NSSCleanupAutoPtrClass(CERTCertList, CERT_DestroyCertList)
 NSSCleanupAutoPtrClass_WithParam(SECItem, SECITEM_FreeItem, TrueParam, PR_TRUE)
 
 #define CONST_OID static const unsigned char
 #define OI(x) { siDEROID, (unsigned char *)x, sizeof x }
 
 struct nsMyTrustedEVInfo
@@ -493,35 +497,64 @@ static SECStatus getFirstEVPolicy(CERTCe
         return SECSuccess;
       }
     }
   }
 
   return SECFailure;
 }
 
+PRBool
+nsNSSSocketInfo::hasCertErrors()
+{
+  if (!mSSLStatus || !mSSLStatus->mHaveCertStatus) {
+    // if the status is unknown, assume the cert is bad :-)
+    return PR_TRUE;
+  }
+
+  return mSSLStatus->mIsDomainMismatch ||
+         mSSLStatus->mIsNotValidAtThisTime ||
+         mSSLStatus->mIsUntrusted;
+}
+
 NS_IMETHODIMP
 nsNSSSocketInfo::GetIsExtendedValidation(PRBool* aIsEV)
 {
   NS_ENSURE_ARG(aIsEV);
   *aIsEV = PR_FALSE;
 
   if (!mCert)
     return NS_OK;
 
-  return mCert->GetIsExtendedValidation(aIsEV);
+  if (hasCertErrors())
+    return NS_OK;
+
+  nsresult rv;
+  nsCOMPtr<nsIIdentityInfo> idinfo = do_QueryInterface(mCert, &rv);
+  if (NS_FAILED(rv))
+    return rv;
+
+  return idinfo->GetIsExtendedValidation(aIsEV);
 }
 
 NS_IMETHODIMP
 nsNSSSocketInfo::GetValidEVPolicyOid(nsACString &outDottedOid)
 {
   if (!mCert)
     return NS_OK;
 
-  return mCert->GetValidEVPolicyOid(outDottedOid);
+  if (hasCertErrors())
+    return NS_OK;
+
+  nsresult rv;
+  nsCOMPtr<nsIIdentityInfo> idinfo = do_QueryInterface(mCert, &rv);
+  if (NS_FAILED(rv))
+    return rv;
+
+  return idinfo->GetValidEVPolicyOid(outDottedOid);
 }
 
 nsresult
 nsNSSCertificate::hasValidEVOidTag(SECOidTag &resultOidTag, PRBool &validEV)
 {
   nsNSSShutDownPreventionLock locker;
   if (isAlreadyShutDown())
     return NS_ERROR_NOT_AVAILABLE;
@@ -554,55 +587,81 @@ nsNSSCertificate::hasValidEVOidTag(SECOi
                             | CERT_REV_FLAG_CRL
                             ;
   cvin[2].type = cert_pi_end;
 
   CERTValOutParam cvout[2];
   cvout[0].type = cert_po_trustAnchor;
   cvout[1].type = cert_po_end;
 
+  PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("calling CERT_PKIXVerifyCert nss cert %p\n", mCert));
   rv = CERT_PKIXVerifyCert(mCert, certificateUsageSSLServer,
                            cvin, cvout, nsnull);
   if (rv != SECSuccess)
     return NS_OK;
 
   CERTCertificate *issuerCert = cvout[0].value.pointer.cert;
   CERTCertificateCleaner issuerCleaner(issuerCert);
 
   validEV = isApprovedForEV(oid_tag, issuerCert);
   if (validEV)
     resultOidTag = oid_tag;
  
   return NS_OK;
 }
 
+nsresult
+nsNSSCertificate::getValidEVOidTag(SECOidTag &resultOidTag, PRBool &validEV)
+{
+  if (mCachedEVStatus != ev_status_unknown) {
+    validEV = (mCachedEVStatus == ev_status_valid);
+    if (validEV)
+      resultOidTag = mCachedEVOidTag;
+    return NS_OK;
+  }
+
+  nsresult rv = hasValidEVOidTag(resultOidTag, validEV);
+  if (NS_SUCCEEDED(rv)) {
+    if (validEV) {
+      mCachedEVOidTag = resultOidTag;
+    }
+    mCachedEVStatus = validEV ? ev_status_valid : ev_status_invalid;
+  }
+  return rv;
+}
+
 NS_IMETHODIMP
 nsNSSCertificate::GetIsExtendedValidation(PRBool* aIsEV)
 {
   nsNSSShutDownPreventionLock locker;
   if (isAlreadyShutDown())
     return NS_ERROR_NOT_AVAILABLE;
 
   NS_ENSURE_ARG(aIsEV);
   *aIsEV = PR_FALSE;
 
+  if (mCachedEVStatus != ev_status_unknown) {
+    *aIsEV = (mCachedEVStatus == ev_status_valid);
+    return NS_OK;
+  }
+
   SECOidTag oid_tag;
-  return hasValidEVOidTag(oid_tag, *aIsEV);
+  return getValidEVOidTag(oid_tag, *aIsEV);
 }
 
 NS_IMETHODIMP
 nsNSSCertificate::GetValidEVPolicyOid(nsACString &outDottedOid)
 {
   nsNSSShutDownPreventionLock locker;
   if (isAlreadyShutDown())
     return NS_ERROR_NOT_AVAILABLE;
 
   SECOidTag oid_tag;
   PRBool valid;
-  nsresult rv = hasValidEVOidTag(oid_tag, valid);
+  nsresult rv = getValidEVOidTag(oid_tag, valid);
   if (NS_FAILED(rv))
     return rv;
 
   if (valid) {
     SECOidData *oid_data = SECOID_FindOIDByTag(oid_tag);
     if (!oid_data)
       return NS_ERROR_FAILURE;
 
--- a/security/manager/ssl/src/nsNSSCallbacks.cpp
+++ b/security/manager/ssl/src/nsNSSCallbacks.cpp
@@ -845,19 +845,46 @@ void PR_CALLBACK HandshakeCallback(PRFil
       infoObject->SetSSLStatus(status);
     }
 
     CERTCertificate *serverCert = SSL_PeerCertificate(fd);
     if (serverCert) {
       nsRefPtr<nsNSSCertificate> nssc = new nsNSSCertificate(serverCert);
       CERT_DestroyCertificate(serverCert);
       serverCert = nsnull;
-      infoObject->SetCert(nssc);
-      if (!status->mServerCert) {
-        status->mServerCert = nssc;
+
+      nsCOMPtr<nsIX509Cert> prevcert;
+      infoObject->GetPreviousCert(getter_AddRefs(prevcert));
+
+      PRBool equals_previous = PR_FALSE;
+      if (prevcert) {
+        nsresult rv = nssc->Equals(prevcert, &equals_previous);
+        if (NS_FAILED(rv)) {
+          equals_previous = PR_FALSE;
+        }
+      }
+
+      if (equals_previous) {
+        PR_LOG(gPIPNSSLog, PR_LOG_DEBUG,
+               ("HandshakeCallback using PREV cert %p\n", prevcert.get()));
+        infoObject->SetCert(prevcert);
+        status->mServerCert = prevcert;
+      }
+      else {
+        if (status->mServerCert) {
+          PR_LOG(gPIPNSSLog, PR_LOG_DEBUG,
+                 ("HandshakeCallback KEEPING cert %p\n", status->mServerCert.get()));
+          infoObject->SetCert(status->mServerCert);
+        }
+        else {
+          PR_LOG(gPIPNSSLog, PR_LOG_DEBUG,
+                 ("HandshakeCallback using NEW cert %p\n", nssc.get()));
+          infoObject->SetCert(nssc);
+          status->mServerCert = nssc;
+        }
       }
     }
 
     status->mHaveKeyLengthAndCipher = PR_TRUE;
     status->mKeyLength = keyLength;
     status->mSecretKeyLength = encryptBits;
     status->mCipherName.Adopt(cipherName);
   }
@@ -927,15 +954,17 @@ SECStatus PR_CALLBACK AuthCertificateCal
     // to the caller that contains at least the cert and its status.
     nsNSSSocketInfo* infoObject = (nsNSSSocketInfo*) fd->higher->secret;
 
     nsRefPtr<nsSSLStatus> status = infoObject->SSLStatus();
     if (!status) {
       status = new nsSSLStatus();
       infoObject->SetSSLStatus(status);
     }
-    if (status) {
+    if (status && !status->mServerCert) {
       status->mServerCert = new nsNSSCertificate(serverCert);
+      PR_LOG(gPIPNSSLog, PR_LOG_DEBUG,
+             ("AuthCertificateCallback setting NEW cert %p\n", status->mServerCert.get()));
     }
   }
 
   return rv;
 }
--- a/security/manager/ssl/src/nsNSSCertificate.cpp
+++ b/security/manager/ssl/src/nsNSSCertificate.cpp
@@ -152,30 +152,32 @@ nsNSSCertificate::InitFromDER(char *cert
 
   mCert = aCert;
   return PR_TRUE;
 }
 
 nsNSSCertificate::nsNSSCertificate(CERTCertificate *cert) : 
                                            mCert(nsnull),
                                            mPermDelete(PR_FALSE),
-                                           mCertType(CERT_TYPE_NOT_YET_INITIALIZED)
+                                           mCertType(CERT_TYPE_NOT_YET_INITIALIZED),
+                                           mCachedEVStatus(ev_status_unknown)
 {
   nsNSSShutDownPreventionLock locker;
   if (isAlreadyShutDown())
     return;
 
   if (cert) 
     mCert = CERT_DupCertificate(cert);
 }
 
 nsNSSCertificate::nsNSSCertificate() : 
   mCert(nsnull),
   mPermDelete(PR_FALSE),
-  mCertType(CERT_TYPE_NOT_YET_INITIALIZED)
+  mCertType(CERT_TYPE_NOT_YET_INITIALIZED),
+  mCachedEVStatus(ev_status_unknown)
 {
 }
 
 nsNSSCertificate::~nsNSSCertificate()
 {
   nsNSSShutDownPreventionLock locker;
   if (isAlreadyShutDown())
     return;
--- a/security/manager/ssl/src/nsNSSCertificate.h
+++ b/security/manager/ssl/src/nsNSSCertificate.h
@@ -93,17 +93,23 @@ private:
   nsCOMPtr<nsIASN1Object> mASN1Structure;
   nsresult CreateASN1Struct();
   nsresult CreateTBSCertificateASN1Struct(nsIASN1Sequence **retSequence,
                                           nsINSSComponent *nssComponent);
   nsresult GetSortableDate(PRTime aTime, nsAString &_aSortableDate);
   virtual void virtualDestroyNSSReference();
   void destructorSafeDestroyNSSReference();
   PRBool InitFromDER(char* certDER, int derLen);  // return false on failure
+
+  enum { 
+    ev_status_unknown = -1, ev_status_invalid = 0, ev_status_valid = 1
+  } mCachedEVStatus;
+  SECOidTag mCachedEVOidTag;
   nsresult hasValidEVOidTag(SECOidTag &resultOidTag, PRBool &validEV);
+  nsresult getValidEVOidTag(SECOidTag &resultOidTag, PRBool &validEV);
 };
 
 class nsNSSCertList: public nsIX509CertList
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIX509CERTLIST
 
--- a/security/manager/ssl/src/nsNSSIOLayer.cpp
+++ b/security/manager/ssl/src/nsNSSIOLayer.cpp
@@ -343,16 +343,32 @@ nsNSSSocketInfo::SetNotificationCallback
                          getter_AddRefs(proxiedDocShell));
     nsISecureBrowserUI* secureUI;
     proxiedDocShell->GetSecurityUI(&secureUI);
     if (secureUI)
     {
       nsCOMPtr<nsIThread> mainThread(do_GetMainThread());
       NS_ProxyRelease(mainThread, secureUI, PR_FALSE);
       mExternalErrorReporting = PR_TRUE;
+
+      // If this socket is associated to a docshell, let's try to remember
+      // the currently used cert. If this socket gets a notification from NSS
+      // having the same raw socket, we can keep the PSM wrapper object
+      // and all the data it has cached (like verification results).
+      nsCOMPtr<nsISSLStatusProvider> statprov = do_QueryInterface(secureUI);
+      if (statprov) {
+        nsCOMPtr<nsISupports> isup_stat;
+        statprov->GetSSLStatus(getter_AddRefs(isup_stat));
+        if (isup_stat) {
+          nsCOMPtr<nsISSLStatus> sslstat = do_QueryInterface(isup_stat);
+          if (sslstat) {
+            sslstat->GetServerCert(getter_AddRefs(mPreviousCert));
+          }
+        }
+      }
     }
   }
 
   return NS_OK;
 }
 
 nsresult
 nsNSSSocketInfo::GetExternalErrorReporting(PRBool* state)
@@ -573,27 +589,37 @@ nsresult nsNSSSocketInfo::GetFileDescPtr
 }
 
 nsresult nsNSSSocketInfo::SetFileDescPtr(PRFileDesc* aFilePtr)
 {
   mFd = aFilePtr;
   return NS_OK;
 }
 
-nsresult nsNSSSocketInfo::GetCert(nsNSSCertificate** _result)
+nsresult nsNSSSocketInfo::GetPreviousCert(nsIX509Cert** _result)
+{
+  NS_ENSURE_ARG_POINTER(_result);
+
+  *_result = mPreviousCert;
+  NS_IF_ADDREF(*_result);
+
+  return NS_OK;
+}
+
+nsresult nsNSSSocketInfo::GetCert(nsIX509Cert** _result)
 {
   NS_ENSURE_ARG_POINTER(_result);
 
   *_result = mCert;
   NS_IF_ADDREF(*_result);
 
   return NS_OK;
 }
 
-nsresult nsNSSSocketInfo::SetCert(nsNSSCertificate *aCert)
+nsresult nsNSSSocketInfo::SetCert(nsIX509Cert *aCert)
 {
   mCert = aCert;
 
   return NS_OK;
 }
 
 nsresult nsNSSSocketInfo::GetSSLStatus(nsISupports** _result)
 {
--- a/security/manager/ssl/src/nsNSSIOLayer.h
+++ b/security/manager/ssl/src/nsNSSIOLayer.h
@@ -159,18 +159,20 @@ public:
   nsresult SetHandshakePending(PRBool aHandshakePending);
 
   nsresult GetHostName(char **aHostName);
   nsresult SetHostName(const char *aHostName);
 
   nsresult GetPort(PRInt32 *aPort);
   nsresult SetPort(PRInt32 aPort);
 
-  nsresult GetCert(nsNSSCertificate** _result);
-  nsresult SetCert(nsNSSCertificate *aCert);
+  nsresult GetCert(nsIX509Cert** _result);
+  nsresult SetCert(nsIX509Cert *aCert);
+
+  nsresult GetPreviousCert(nsIX509Cert** _result);
 
   void SetCanceled(PRBool aCanceled);
   PRBool GetCanceled();
   
   void SetHasCleartextPhase(PRBool aHasCleartextPhase);
   PRBool GetHasCleartextPhase();
   
   void SetHandshakeInProgress(PRBool aIsIn);
@@ -182,23 +184,25 @@ public:
   nsresult GetExternalErrorReporting(PRBool* state);
   nsresult SetExternalErrorReporting(PRBool aState);
 
   nsresult RememberCAChain(CERTCertList *aCertList);
 
   /* Set SSL Status values */
   nsresult SetSSLStatus(nsSSLStatus *aSSLStatus);
   nsSSLStatus* SSLStatus() { return mSSLStatus; }
+  PRBool hasCertErrors();
   
   PRStatus CloseSocketAndDestroy();
   
 protected:
   nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
   PRFileDesc* mFd;
-  nsRefPtr<nsNSSCertificate> mCert;
+  nsCOMPtr<nsIX509Cert> mCert;
+  nsCOMPtr<nsIX509Cert> mPreviousCert;
   enum { 
     blocking_state_unknown, is_nonblocking_socket, is_blocking_socket 
   } mBlockingState;
   PRUint32 mSecurityState;
   nsString mShortDesc;
   nsString mErrorMessage;
   PRPackedBool mExternalErrorReporting;
   PRPackedBool mForSTARTTLS;