Bug 915931, Part 3: Integrate insanity::pkix OCSP support, r=keeler, r=cviecco
authorBrian Smith <brian@briansmith.org>
Sun, 16 Feb 2014 17:35:40 -0800
changeset 169819 302def56019a278411ed9d71e3de7126d1729811
parent 169818 853227869c6b0c60dc2ac4f1808ca4ff4fe4ce8b
child 169820 b8fd613d94d560daeab160b6e8efbbb33668a971
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewerskeeler, cviecco
bugs915931
milestone30.0a1
Bug 915931, Part 3: Integrate insanity::pkix OCSP support, r=keeler, r=cviecco
security/apps/AppSignatureVerification.cpp
security/apps/AppTrustDomain.cpp
security/apps/AppTrustDomain.h
security/certverifier/CertVerifier.cpp
security/certverifier/CertVerifier.h
security/certverifier/NSSCertDBTrustDomain.cpp
security/certverifier/NSSCertDBTrustDomain.h
--- a/security/apps/AppSignatureVerification.cpp
+++ b/security/apps/AppSignatureVerification.cpp
@@ -600,17 +600,17 @@ VerifySignature(AppTrustedRoot trustedRo
 
   // Verify certificate.
   AppTrustDomain trustDomain(nullptr); // TODO: null pinArg
   if (trustDomain.SetTrustedRoot(trustedRoot) != SECSuccess) {
     return MapSECStatus(SECFailure);
   }
   if (BuildCertChain(trustDomain, signerCert, PR_Now(), MustBeEndEntity,
                      KU_DIGITAL_SIGNATURE, SEC_OID_EXT_KEY_USAGE_CODE_SIGN,
-                     builtChain) != SECSuccess) {
+                     nullptr, builtChain) != SECSuccess) {
     return MapSECStatus(SECFailure);
   }
 
   // See NSS_CMSContentInfo_GetContentTypeOID, which isn't exported from NSS.
   SECOidData* contentTypeOidData =
     SECOID_FindOID(&signedData->contentInfo.contentType);
   if (!contentTypeOidData) {
     return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
--- a/security/apps/AppTrustDomain.cpp
+++ b/security/apps/AppTrustDomain.cpp
@@ -173,9 +173,21 @@ AppTrustDomain::GetCertTrust(EndEntityOr
 
 SECStatus
 AppTrustDomain::VerifySignedData(const CERTSignedData* signedData,
                                   const CERTCertificate* cert)
 {
   return ::insanity::pkix::VerifySignedData(signedData, cert, mPinArg);
 }
 
+SECStatus
+AppTrustDomain::CheckRevocation(EndEntityOrCA,
+                                const CERTCertificate*,
+                                /*const*/ CERTCertificate*,
+                                PRTime time,
+                                /*optional*/ const SECItem*)
+{
+  // We don't currently do revocation checking. If we need to distrust an Apps
+  // certificate, we will use the active distrust mechanism.
+  return SECSuccess;
+}
+
 } }
--- a/security/apps/AppTrustDomain.h
+++ b/security/apps/AppTrustDomain.h
@@ -24,17 +24,21 @@ public:
                          const CERTCertificate* candidateCert,
                  /*out*/ TrustLevel* trustLevel) MOZ_OVERRIDE;
   SECStatus FindPotentialIssuers(const SECItem* encodedIssuerName,
                                  PRTime time,
                          /*out*/ insanity::pkix::ScopedCERTCertList& results)
                                  MOZ_OVERRIDE;
   SECStatus VerifySignedData(const CERTSignedData* signedData,
                              const CERTCertificate* cert) MOZ_OVERRIDE;
-
+  SECStatus CheckRevocation(insanity::pkix::EndEntityOrCA endEntityOrCA,
+                            const CERTCertificate* cert,
+                            /*const*/ CERTCertificate* issuerCertToDup,
+                            PRTime time,
+                            /*optional*/ const SECItem* stapledOCSPresponse);
 private:
   void* mPinArg; // non-owning!
   insanity::pkix::ScopedCERTCertificate mTrustedRoot;
 };
 
 } } // namespace mozilla::psm
 
 #endif // mozilla_psm_AppsTrustDomain_h
--- a/security/certverifier/CertVerifier.cpp
+++ b/security/certverifier/CertVerifier.cpp
@@ -18,17 +18,17 @@
 #include "sslerr.h"
 
 // ScopedXXX in this file are insanity::pkix::ScopedXXX, not
 // mozilla::ScopedXXX.
 using namespace insanity::pkix;
 using namespace mozilla::psm;
 
 #ifdef PR_LOGGING
-static PRLogModuleInfo* gCertVerifierLog = nullptr;
+PRLogModuleInfo* gCertVerifierLog = nullptr;
 #endif
 
 namespace mozilla { namespace psm {
 
 const CertVerifier::Flags CertVerifier::FLAG_LOCAL_ONLY = 1;
 const CertVerifier::Flags CertVerifier::FLAG_MUST_BE_EV = 2;
 
 CertVerifier::CertVerifier(implementation_config ic,
@@ -189,47 +189,48 @@ destroyCertListThatShouldNotExist(CERTCe
   }
 }
 #endif
 
 static SECStatus
 BuildCertChainForOneKeyUsage(TrustDomain& trustDomain, CERTCertificate* cert,
                              PRTime time, KeyUsages ku1, KeyUsages ku2,
                              KeyUsages ku3, SECOidTag eku,
+                             const SECItem* stapledOCSPResponse,
                              ScopedCERTCertList& builtChain)
 {
   PR_ASSERT(ku1);
   PR_ASSERT(ku2);
 
   SECStatus rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity,
-                                ku1, eku, builtChain);
+                                ku1, eku, stapledOCSPResponse, builtChain);
   if (rv != SECSuccess && ku2 &&
       PR_GetError() == SEC_ERROR_INADEQUATE_KEY_USAGE) {
     rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity,
-                        ku2, eku, builtChain);
+                        ku2, eku, stapledOCSPResponse, builtChain);
     if (rv != SECSuccess && ku3 &&
         PR_GetError() == SEC_ERROR_INADEQUATE_KEY_USAGE) {
       rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity,
-                          ku3, eku, builtChain);
+                          ku3, eku, stapledOCSPResponse, builtChain);
       if (rv != SECSuccess) {
         PR_SetError(SEC_ERROR_INADEQUATE_KEY_USAGE, 0);
       }
     }
   }
   return rv;
 }
 
 SECStatus
 CertVerifier::InsanityVerifyCert(
                    CERTCertificate* cert,
-      /*optional*/ const SECItem* /*TODO: stapledOCSPResponse*/,
                    const SECCertificateUsage usage,
                    const PRTime time,
                    void* pinArg,
                    const Flags flags,
+      /*optional*/ const SECItem* stapledOCSPResponse,
   /*optional out*/ insanity::pkix::ScopedCERTCertList* validationChain)
 {
   PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("Top of InsanityVerifyCert\n"));
   SECStatus rv;
 
   // TODO(bug 970750): anyExtendedKeyUsage
   // TODO: encipherOnly/decipherOnly
   // S/MIME Key Usage: http://tools.ietf.org/html/rfc3850#section-4.4.2
@@ -243,78 +244,78 @@ CertVerifier::InsanityVerifyCert(
     case certificateUsageSSLClient: {
       // XXX: We don't really have a trust bit for SSL client authentication so
       // just use trustEmail as it is the closest alternative.
       NSSCertDBTrustDomain trustDomain(trustEmail, mOCSPDownloadEnabled,
                                        mOCSPStrict, pinArg);
       rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity,
                           KU_DIGITAL_SIGNATURE,
                           SEC_OID_EXT_KEY_USAGE_CLIENT_AUTH,
-                          builtChain);
+                          stapledOCSPResponse, builtChain);
       break;
     }
 
     case certificateUsageSSLServer: {
       // TODO: When verifying a certificate in an SSL handshake, we should
       // restrict the acceptable key usage based on the key exchange method
       // chosen by the server.
       NSSCertDBTrustDomain trustDomain(trustSSL, mOCSPDownloadEnabled,
                                        mOCSPStrict, pinArg);
       rv = BuildCertChainForOneKeyUsage(trustDomain, cert, time,
                                         KU_DIGITAL_SIGNATURE, // ECDHE/DHE
                                         KU_KEY_ENCIPHERMENT, // RSA
                                         KU_KEY_AGREEMENT, // ECDH/DH
                                         SEC_OID_EXT_KEY_USAGE_SERVER_AUTH,
-                                        builtChain);
+                                        stapledOCSPResponse, builtChain);
       break;
     }
 
     case certificateUsageSSLCA: {
       NSSCertDBTrustDomain trustDomain(trustSSL, mOCSPDownloadEnabled,
                                        mOCSPStrict, pinArg);
       rv = BuildCertChain(trustDomain, cert, time, MustBeCA,
                           KU_KEY_CERT_SIGN,
                           SEC_OID_EXT_KEY_USAGE_SERVER_AUTH,
-                          builtChain);
+                          stapledOCSPResponse, builtChain);
       break;
     }
 
     case certificateUsageEmailSigner: {
       NSSCertDBTrustDomain trustDomain(trustEmail, mOCSPDownloadEnabled,
                                        mOCSPStrict, pinArg);
       rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity,
                           KU_DIGITAL_SIGNATURE,
                           SEC_OID_EXT_KEY_USAGE_EMAIL_PROTECT,
-                          builtChain);
+                          stapledOCSPResponse, builtChain);
       break;
     }
 
     case certificateUsageEmailRecipient: {
       // TODO: The higher level S/MIME processing should pass in which key
       // usage it is trying to verify for, and base its algorithm choices
       // based on the result of the verification(s).
       NSSCertDBTrustDomain trustDomain(trustEmail, mOCSPDownloadEnabled,
                                        mOCSPStrict, pinArg);
       rv = BuildCertChainForOneKeyUsage(trustDomain, cert, time,
                                         KU_KEY_ENCIPHERMENT, // RSA
                                         KU_KEY_AGREEMENT, // ECDH/DH
                                         0,
                                         SEC_OID_EXT_KEY_USAGE_EMAIL_PROTECT,
-                                        builtChain);
+                                        stapledOCSPResponse, builtChain);
       break;
     }
 
     case certificateUsageObjectSigner: {
       NSSCertDBTrustDomain trustDomain(trustObjectSigning,
                                        mOCSPDownloadEnabled, mOCSPStrict,
                                        pinArg);
       rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity,
                           KU_DIGITAL_SIGNATURE,
                           SEC_OID_EXT_KEY_USAGE_CODE_SIGN,
-                          builtChain);
+                          stapledOCSPResponse, builtChain);
       break;
     }
 
     case certificateUsageVerifyCA:
     case certificateUsageStatusResponder: {
       // XXX This is a pretty useless way to verify a certificate. It is used
       // by the implementation of window.crypto.importCertificates and in the
       // certificate viewer UI. Because we don't know what trust bit is
@@ -330,28 +331,28 @@ CertVerifier::InsanityVerifyCert(
         endEntityOrCA = MustBeEndEntity;
         keyUsage = KU_DIGITAL_SIGNATURE;
         eku = SEC_OID_OCSP_RESPONDER;
       }
 
       NSSCertDBTrustDomain sslTrust(trustSSL,
                                     mOCSPDownloadEnabled, mOCSPStrict, pinArg);
       rv = BuildCertChain(sslTrust, cert, time, endEntityOrCA,
-                          keyUsage, eku, builtChain);
+                          keyUsage, eku, stapledOCSPResponse, builtChain);
       if (rv == SECFailure && PR_GetError() == SEC_ERROR_UNKNOWN_ISSUER) {
         NSSCertDBTrustDomain emailTrust(trustEmail, mOCSPDownloadEnabled,
                                         mOCSPStrict, pinArg);
         rv = BuildCertChain(emailTrust, cert, time, endEntityOrCA, keyUsage,
-                            eku, builtChain);
+                            eku, stapledOCSPResponse, builtChain);
         if (rv == SECFailure && SEC_ERROR_UNKNOWN_ISSUER) {
           NSSCertDBTrustDomain objectSigningTrust(trustObjectSigning,
                                                   mOCSPDownloadEnabled,
                                                   mOCSPStrict, pinArg);
           rv = BuildCertChain(objectSigningTrust, cert, time, endEntityOrCA,
-                              keyUsage, eku, builtChain);
+                              keyUsage, eku, stapledOCSPResponse, builtChain);
         }
       }
 
       break;
     }
 
     default:
       PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
@@ -595,18 +596,18 @@ CertVerifier::VerifyCert(CERTCertificate
     PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
 #else
     PR_SetError(PR_INVALID_STATE_ERROR, 0);
 #endif
     return SECFailure;
   }
 
   if (mImplementation == insanity) {
-    return InsanityVerifyCert(cert, stapledOCSPResponse, usage, time,
-                              pinArg, flags, validationChain);
+    return InsanityVerifyCert(cert, usage, time, pinArg, flags,
+                              stapledOCSPResponse, validationChain);
   }
 
   if (mImplementation == classic) {
     // XXX: we do not care about the localOnly flag (currently) as the
     // caller that wants localOnly should disable and reenable the fetching.
     return ClassicVerifyCert(cert, usage, time, pinArg, validationChain,
                              verifyLog);
   }
--- a/security/certverifier/CertVerifier.h
+++ b/security/certverifier/CertVerifier.h
@@ -73,20 +73,20 @@ public:
   const bool mCRLDownloadEnabled;
 #endif
   const bool mOCSPDownloadEnabled;
   const bool mOCSPStrict;
   const bool mOCSPGETEnabled;
 
 private:
   SECStatus InsanityVerifyCert(CERTCertificate* cert,
-      /*optional*/ const SECItem* stapledOCSPResponse,
       const SECCertificateUsage usage,
       const PRTime time,
       void* pinArg,
       const Flags flags,
+      /*optional*/ const SECItem* stapledOCSPResponse,
       /*optional out*/ insanity::pkix::ScopedCERTCertList* validationChain);
 };
 
 void InitCertVerifierLog();
 } } // namespace mozilla::psm
 
 #endif // mozilla_psm__CertVerifier_h
--- a/security/certverifier/NSSCertDBTrustDomain.cpp
+++ b/security/certverifier/NSSCertDBTrustDomain.cpp
@@ -33,22 +33,22 @@ namespace {
 
 inline void PORT_Free_string(char* str) { PORT_Free(str); }
 
 typedef ScopedPtr<SECMODModule, SECMOD_DestroyModule> ScopedSECMODModule;
 
 } // unnamed namespace
 
 NSSCertDBTrustDomain::NSSCertDBTrustDomain(SECTrustType certDBTrustType,
-                                           bool /*ocspDownloadEnabled*/,
-                                           bool /*ocspStrict*/,
+                                           bool ocspDownloadEnabled,
+                                           bool ocspStrict,
                                            void* pinArg)
   : mCertDBTrustType(certDBTrustType)
-//  , mOCSPDownloadEnabled(ocspDownloadEnabled)
-//  , mOCSPStrict(ocspStrict)
+  , mOCSPDownloadEnabled(ocspDownloadEnabled)
+  , mOCSPStrict(ocspStrict)
   , mPinArg(pinArg)
 {
 }
 
 SECStatus
 NSSCertDBTrustDomain::FindPotentialIssuers(
   const SECItem* encodedIssuerName, PRTime time,
   /*out*/ insanity::pkix::ScopedCERTCertList& results)
@@ -119,16 +119,128 @@ NSSCertDBTrustDomain::GetCertTrust(EndEn
 
 SECStatus
 NSSCertDBTrustDomain::VerifySignedData(const CERTSignedData* signedData,
                                        const CERTCertificate* cert)
 {
   return ::insanity::pkix::VerifySignedData(signedData, cert, mPinArg);
 }
 
+SECStatus
+NSSCertDBTrustDomain::CheckRevocation(
+  insanity::pkix::EndEntityOrCA endEntityOrCA,
+  const CERTCertificate* cert,
+  /*const*/ CERTCertificate* issuerCert,
+  PRTime time,
+  /*optional*/ const SECItem* stapledOCSPResponse)
+{
+  // Actively distrusted certificates will have already been blocked by
+  // GetCertTrust.
+
+  // TODO: need to verify that IsRevoked isn't called for trust anchors AND
+  // that that fact is documented in insanity.
+
+  PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
+         ("NSSCertDBTrustDomain: Top of CheckRevocation\n"));
+
+  PORT_Assert(cert);
+  PORT_Assert(issuerCert);
+  if (!cert || !issuerCert) {
+    PORT_SetError(SEC_ERROR_INVALID_ARGS);
+    return SECFailure;
+  }
+
+  // If we have a stapled OCSP response then the verification of that response
+  // determines the result unless the OCSP response is expired. We make an
+  // exception for expired responses because some servers, nginx in particular,
+  // are known to serve expired responses due to bugs.
+  if (stapledOCSPResponse) {
+    PR_ASSERT(endEntityOrCA == MustBeEndEntity);
+    SECStatus rv = VerifyEncodedOCSPResponse(*this, cert, issuerCert, time,
+                                             stapledOCSPResponse);
+    if (rv == SECSuccess) {
+      return rv;
+    }
+    if (PR_GetError() != SEC_ERROR_OCSP_OLD_RESPONSE) {
+      return rv;
+    }
+  }
+
+  // TODO(bug 921885): We need to change this when we add EV support.
+
+  // TODO: when !mOCSPDownloadEnabled, we still need to handle the fallback for
+  // expired responses. But, if/when we disable OCSP fetching by default, it
+  // would be ambiguous whether !mOCSPDownloadEnabled means "I want the default"
+  // or "I really never want you to ever fetch OCSP."
+  if (mOCSPDownloadEnabled) {
+    // We don't do OCSP fetching for intermediates.
+    if (endEntityOrCA == MustBeCA) {
+      PR_ASSERT(!stapledOCSPResponse);
+      return SECSuccess;
+    }
+
+    ScopedPtr<char, PORT_Free_string>
+      url(CERT_GetOCSPAuthorityInfoAccessLocation(cert));
+
+    // Nothing to do if we don't have an OCSP responder URI for the cert; just
+    // assume it is good. Note that this is the confusing, but intended,
+    // interpretation of "strict" revocation checking in the face of a
+    // certificate that lacks an OCSP responder URI.
+    if (!url) {
+      if (stapledOCSPResponse) {
+        PR_SetError(SEC_ERROR_OCSP_OLD_RESPONSE, 0);
+        return SECFailure;
+      }
+      return SECSuccess;
+    }
+
+    ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+    if (!arena) {
+      return SECFailure;
+    }
+
+    const SECItem* request
+      = CreateEncodedOCSPRequest(arena.get(), cert, issuerCert);
+    if (!request) {
+      return SECFailure;
+    }
+
+    const SECItem* response(CERT_PostOCSPRequest(arena.get(), url.get(),
+                                                 request));
+    if (!response) {
+      if (mOCSPStrict) {
+        return SECFailure;
+      }
+      // Soft fail -> success :(
+    } else {
+      SECStatus rv = VerifyEncodedOCSPResponse(*this, cert, issuerCert, time,
+                                               response);
+      if (rv == SECSuccess) {
+        return SECSuccess;
+      }
+      PRErrorCode error = PR_GetError();
+      switch (error) {
+        case SEC_ERROR_OCSP_UNKNOWN_CERT:
+        case SEC_ERROR_REVOKED_CERTIFICATE:
+          return SECFailure;
+        default:
+          if (mOCSPStrict) {
+            return SECFailure;
+          }
+          break; // Soft fail -> success :(
+      }
+    }
+  }
+
+  PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
+         ("NSSCertDBTrustDomain: end of CheckRevocation"));
+
+  return SECSuccess;
+}
+
 namespace {
 
 static char*
 nss_addEscape(const char* string, char quote)
 {
   char* newString = 0;
   int escapes = 0, size = 0;
   const char* src;
--- a/security/certverifier/NSSCertDBTrustDomain.h
+++ b/security/certverifier/NSSCertDBTrustDomain.h
@@ -58,18 +58,24 @@ public:
 
   virtual SECStatus GetCertTrust(insanity::pkix::EndEntityOrCA endEntityOrCA,
                                  const CERTCertificate* candidateCert,
                          /*out*/ TrustLevel* trustLevel);
 
   virtual SECStatus VerifySignedData(const CERTSignedData* signedData,
                                      const CERTCertificate* cert);
 
+  virtual SECStatus CheckRevocation(insanity::pkix::EndEntityOrCA endEntityOrCA,
+                                    const CERTCertificate* cert,
+                          /*const*/ CERTCertificate* issuerCert,
+                                    PRTime time,
+                       /*optional*/ const SECItem* stapledOCSPResponse);
+
 private:
   const SECTrustType mCertDBTrustType;
-//  const bool mOCSPDownloadEnabled;
-//  const bool mOCSPStrict;
+  const bool mOCSPDownloadEnabled;
+  const bool mOCSPStrict;
   void* mPinArg; // non-owning!
 };
 
 } } // namespace mozilla::psm
 
 #endif // mozilla_psm__NSSCertDBTrustDomain_h