Bug 921886: Add certificate policiy support to insanity::pkix, r=keeler, r=cviecco
authorBrian Smith <brian@briansmith.org>
Mon, 24 Feb 2014 12:37:45 -0800
changeset 171067 e50c326ad721ba006716daa4f0a43c8e1584c06d
parent 171066 3d4a094ac17e373b5b242a636d0bb7d0686519ee
child 171068 b7030189c2ca5697c8fba43220511ddc39fcce98
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewerskeeler, cviecco
bugs921886
milestone30.0a1
Bug 921886: Add certificate policiy support to insanity::pkix, r=keeler, r=cviecco
security/apps/AppSignatureVerification.cpp
security/apps/AppTrustDomain.cpp
security/apps/AppTrustDomain.h
security/certverifier/CertVerifier.cpp
security/certverifier/NSSCertDBTrustDomain.cpp
security/certverifier/NSSCertDBTrustDomain.h
security/insanity/include/insanity/pkix.h
security/insanity/include/insanity/pkixtypes.h
security/insanity/lib/pkixbuild.cpp
security/insanity/lib/pkixcheck.cpp
security/insanity/lib/pkixcheck.h
security/insanity/lib/pkixocsp.cpp
security/insanity/lib/pkixutil.h
--- a/security/apps/AppSignatureVerification.cpp
+++ b/security/apps/AppSignatureVerification.cpp
@@ -600,17 +600,18 @@ 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,
-                     nullptr, builtChain) != SECSuccess) {
+                     SEC_OID_X509_ANY_POLICY, 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
@@ -107,23 +107,25 @@ AppTrustDomain::FindPotentialIssuers(con
     return SECFailure;
   }
 
   return SECSuccess;
 }
 
 SECStatus
 AppTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA,
+                             SECOidTag policy,
                              const CERTCertificate* candidateCert,
                      /*out*/ TrustLevel* trustLevel)
 {
+  MOZ_ASSERT(policy == SEC_OID_X509_ANY_POLICY);
   MOZ_ASSERT(candidateCert);
   MOZ_ASSERT(trustLevel);
   MOZ_ASSERT(mTrustedRoot);
-  if (!candidateCert || !trustLevel) {
+  if (!candidateCert || !trustLevel || policy != SEC_OID_X509_ANY_POLICY) {
     PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
     return SECFailure;
   }
   if (!mTrustedRoot) {
     PR_SetError(PR_INVALID_STATE_ERROR, 0);
     return SECFailure;
   }
 
--- a/security/apps/AppTrustDomain.h
+++ b/security/apps/AppTrustDomain.h
@@ -16,16 +16,17 @@ namespace mozilla { namespace psm {
 class AppTrustDomain MOZ_FINAL : public insanity::pkix::TrustDomain
 {
 public:
   AppTrustDomain(void* pinArg);
 
   SECStatus SetTrustedRoot(AppTrustedRoot trustedRoot);
 
   SECStatus GetCertTrust(insanity::pkix::EndEntityOrCA endEntityOrCA,
+                         SECOidTag policy,
                          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;
--- a/security/certverifier/CertVerifier.cpp
+++ b/security/certverifier/CertVerifier.cpp
@@ -189,32 +189,36 @@ destroyCertListThatShouldNotExist(CERTCe
   }
 }
 #endif
 
 static SECStatus
 BuildCertChainForOneKeyUsage(TrustDomain& trustDomain, CERTCertificate* cert,
                              PRTime time, KeyUsages ku1, KeyUsages ku2,
                              KeyUsages ku3, SECOidTag eku,
+                             SECOidTag requiredPolicy,
                              const SECItem* stapledOCSPResponse,
                              ScopedCERTCertList& builtChain)
 {
   PR_ASSERT(ku1);
   PR_ASSERT(ku2);
 
   SECStatus rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity,
-                                ku1, eku, stapledOCSPResponse, builtChain);
+                                ku1, eku, requiredPolicy, stapledOCSPResponse,
+                                builtChain);
   if (rv != SECSuccess && ku2 &&
       PR_GetError() == SEC_ERROR_INADEQUATE_KEY_USAGE) {
     rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity,
-                        ku2, eku, stapledOCSPResponse, builtChain);
+                        ku2, eku, requiredPolicy, stapledOCSPResponse,
+                        builtChain);
     if (rv != SECSuccess && ku3 &&
         PR_GetError() == SEC_ERROR_INADEQUATE_KEY_USAGE) {
       rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity,
-                          ku3, eku, stapledOCSPResponse, builtChain);
+                          ku3, eku, requiredPolicy, stapledOCSPResponse,
+                          builtChain);
       if (rv != SECSuccess) {
         PR_SetError(SEC_ERROR_INADEQUATE_KEY_USAGE, 0);
       }
     }
   }
   return rv;
 }
 
@@ -244,77 +248,83 @@ 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,
+                          SEC_OID_X509_ANY_POLICY,
                           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,
+                                        SEC_OID_X509_ANY_POLICY,
                                         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,
+                          SEC_OID_X509_ANY_POLICY,
                           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,
+                          SEC_OID_X509_ANY_POLICY,
                           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,
+                                        SEC_OID_X509_ANY_POLICY,
                                         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,
+                          SEC_OID_X509_ANY_POLICY,
                           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
@@ -331,28 +341,31 @@ 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, stapledOCSPResponse, builtChain);
+                          keyUsage, eku, SEC_OID_X509_ANY_POLICY,
+                          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, stapledOCSPResponse, builtChain);
+                            eku, SEC_OID_X509_ANY_POLICY,
+                            stapledOCSPResponse, builtChain);
         if (rv == SECFailure && SEC_ERROR_UNKNOWN_ISSUER) {
           NSSCertDBTrustDomain objectSigningTrust(trustObjectSigning,
                                                   mOCSPDownloadEnabled,
                                                   mOCSPStrict, pinArg);
           rv = BuildCertChain(objectSigningTrust, cert, time, endEntityOrCA,
-                              keyUsage, eku, stapledOCSPResponse, builtChain);
+                              keyUsage, eku, SEC_OID_X509_ANY_POLICY,
+                              stapledOCSPResponse, builtChain);
         }
       }
 
       break;
     }
 
     default:
       PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
--- a/security/certverifier/NSSCertDBTrustDomain.cpp
+++ b/security/certverifier/NSSCertDBTrustDomain.cpp
@@ -67,23 +67,30 @@ NSSCertDBTrustDomain::FindPotentialIssue
     return SECFailure;
   }
 
   return SECSuccess;
 }
 
 SECStatus
 NSSCertDBTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA,
+                                   SECOidTag policy,
                                    const CERTCertificate* candidateCert,
                                    /*out*/ TrustLevel* trustLevel)
 {
-  PORT_Assert(candidateCert);
-  PORT_Assert(trustLevel);
+  PR_ASSERT(candidateCert);
+  PR_ASSERT(trustLevel);
   if (!candidateCert || !trustLevel) {
-    PORT_SetError(SEC_ERROR_INVALID_ARGS);
+    PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
+    return SECFailure;
+  }
+
+  // We don't support validating for a policy yet.
+  if (policy != SEC_OID_X509_ANY_POLICY) {
+    PR_SetError(SEC_ERROR_POLICY_VALIDATION_FAILED, 0);
     return SECFailure;
   }
 
   // XXX: CERT_GetCertTrust seems to be abusing SECStatus as a boolean, where
   // SECSuccess means that there is a trust record and SECFailure means there
   // is not a trust record. I looked at NSS's internal uses of
   // CERT_GetCertTrust, and all that code uses the result as a boolean meaning
   // "We have a trust record."
--- a/security/certverifier/NSSCertDBTrustDomain.h
+++ b/security/certverifier/NSSCertDBTrustDomain.h
@@ -52,16 +52,17 @@ public:
                        void* pinArg);
 
   virtual SECStatus FindPotentialIssuers(
                         const SECItem* encodedIssuerName,
                         PRTime time,
                 /*out*/ insanity::pkix::ScopedCERTCertList& results);
 
   virtual SECStatus GetCertTrust(insanity::pkix::EndEntityOrCA endEntityOrCA,
+                                 SECOidTag policy,
                                  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,
--- a/security/insanity/include/insanity/pkix.h
+++ b/security/insanity/include/insanity/pkix.h
@@ -19,16 +19,33 @@
 #define insanity_pkix__pkix_h
 
 #include "pkixtypes.h"
 #include "prtime.h"
 
 namespace insanity { namespace pkix {
 
 // ----------------------------------------------------------------------------
+// LIMITED SUPPORT FOR CERTIFICATE POLICIES
+//
+// If SEC_OID_X509_ANY_POLICY is passed as the value of the requiredPolicy
+// parameter then all policy validation will be skipped. Otherwise, path
+// building and validation will be done for the given policy.
+//
+// In RFC 5280 terms:
+//
+//    * user-initial-policy-set = { requiredPolicy }.
+//    * initial-explicit-policy = true
+//    * initial-any-policy-inhibit = true
+//
+// Because we force explicit policy and because we prohibit policy mapping, we
+// do not bother processing the policy mapping, policy constraint, or inhibit
+// anyPolicy extensions.
+//
+// ----------------------------------------------------------------------------
 // ERROR RANKING
 //
 // BuildCertChain prioritizes certain checks ahead of others so that when a
 // certificate chain has multiple errors, the "most serious" error is
 // returned. In practice, this ranking of seriousness is tied directly to how
 // Firefox's certificate error override mechanism.
 //
 // The ranking is:
@@ -66,16 +83,17 @@ namespace insanity { namespace pkix {
 // TODO(bug 968451): Document more of these.
 
 SECStatus BuildCertChain(TrustDomain& trustDomain,
                          CERTCertificate* cert,
                          PRTime time,
                          EndEntityOrCA endEntityOrCA,
             /*optional*/ KeyUsages requiredKeyUsagesIfPresent,
             /*optional*/ SECOidTag requiredEKUIfPresent,
+            /*optional*/ SECOidTag requiredPolicy,
             /*optional*/ const SECItem* stapledOCSPResponse,
                  /*out*/ ScopedCERTCertList& results);
 
 // Verify the given signed data using the public key of the given certificate.
 // (EC)DSA parameter inheritance is not supported.
 SECStatus VerifySignedData(const CERTSignedData* sd,
                            const CERTCertificate* cert,
                            void* pkcs11PinArg);
--- a/security/insanity/include/insanity/pkixtypes.h
+++ b/security/insanity/include/insanity/pkixtypes.h
@@ -43,25 +43,35 @@ enum EndEntityOrCA { MustBeEndEntity, Mu
 // distrusted.
 class TrustDomain
 {
 public:
   virtual ~TrustDomain() { }
 
   enum TrustLevel {
     TrustAnchor = 1,        // certificate is a trusted root CA certificate or
-                            // equivalent
+                            // equivalent *for the given policy*.
     ActivelyDistrusted = 2, // certificate is known to be bad
     InheritsTrust = 3       // certificate must chain to a trust anchor
   };
 
   // Determine the level of trust in the given certificate for the given role.
   // This will be called for every certificate encountered during path
   // building.
+  //
+  // When policy == SEC_OID_X509_ANY_POLICY, then no policy-related checking
+  // should be done. When policy != SEC_OID_X509_ANY_POLICY, then GetCertTrust
+  // MUST NOT return with *trustLevel == TrustAnchor unless the given cert is
+  // considered a trust anchor *for that policy*. In particular, if the user
+  // has marked an intermediate certificate as trusted, but that intermediate
+  // isn't in the list of EV roots, then GetCertTrust must result in
+  // *trustLevel == InheritsTrust instead of *trustLevel == TrustAnchor
+  // (assuming the candidate cert is not actively distrusted).
   virtual SECStatus GetCertTrust(EndEntityOrCA endEntityOrCA,
+                                 SECOidTag policy,
                                  const CERTCertificate* candidateCert,
                          /*out*/ TrustLevel* trustLevel) = 0;
 
   // Find all certificates (intermediate and/or root) in the certificate
   // database that have a subject name matching |encodedIssuerName| at
   // the given time. Certificates where the given time is not within the
   // certificate's validity period may be excluded. The results should be
   // added to the |results| certificate list.
--- a/security/insanity/lib/pkixbuild.cpp
+++ b/security/insanity/lib/pkixbuild.cpp
@@ -50,16 +50,17 @@ BackCert::Init()
         ext->id.data[0] == 0x55 && ext->id.data[1] == 0x1d) {
       // { id-ce x }
       switch (ext->id.data[2]) {
         case 14: out = &dummyEncodedSubjectKeyIdentifier; break; // bug 965136
         case 15: out = &encodedKeyUsage; break;
         case 17: out = &dummyEncodedSubjectAltName; break; // bug 970542
         case 19: out = &encodedBasicConstraints; break;
         case 30: out = &encodedNameConstraints; break;
+        case 32: out = &encodedCertificatePolicies; break;
         case 35: out = &dummyEncodedAuthorityKeyIdentifier; break; // bug 965136
         case 37: out = &encodedExtendedKeyUsage; break;
       }
     } else if (ext->id.len == 9 &&
                ext->id.data[0] == 0x2b && ext->id.data[1] == 0x06 &&
                ext->id.data[2] == 0x06 && ext->id.data[3] == 0x01 &&
                ext->id.data[4] == 0x05 && ext->id.data[5] == 0x05 &&
                ext->id.data[6] == 0x07 && ext->id.data[7] == 0x01) {
@@ -91,27 +92,29 @@ BackCert::Init()
 }
 
 static Result BuildForward(TrustDomain& trustDomain,
                            BackCert& subject,
                            PRTime time,
                            EndEntityOrCA endEntityOrCA,
                            KeyUsages requiredKeyUsagesIfPresent,
                            SECOidTag requiredEKUIfPresent,
+                           SECOidTag requiredPolicy,
                            /*optional*/ const SECItem* stapledOCSPResponse,
                            unsigned int subCACount,
                            /*out*/ ScopedCERTCertList& results);
 
 // The code that executes in the inner loop of BuildForward
 static Result
 BuildForwardInner(TrustDomain& trustDomain,
                   BackCert& subject,
                   PRTime time,
                   EndEntityOrCA endEntityOrCA,
                   SECOidTag requiredEKUIfPresent,
+                  SECOidTag requiredPolicy,
                   CERTCertificate* potentialIssuerCertToDup,
                   /*optional*/ const SECItem* stapledOCSPResponse,
                   unsigned int subCACount,
                   ScopedCERTCertList& results)
 {
   PORT_Assert(potentialIssuerCertToDup);
 
   BackCert potentialIssuer(potentialIssuerCertToDup, &subject,
@@ -145,17 +148,17 @@ BuildForwardInner(TrustDomain& trustDoma
 
   unsigned int newSubCACount = subCACount;
   if (endEntityOrCA == MustBeCA) {
     newSubCACount = subCACount + 1;
   } else {
     PR_ASSERT(newSubCACount == 0);
   }
   rv = BuildForward(trustDomain, potentialIssuer, time, MustBeCA,
-                    KU_KEY_CERT_SIGN, requiredEKUIfPresent,
+                    KU_KEY_CERT_SIGN, requiredEKUIfPresent, requiredPolicy,
                     nullptr, newSubCACount, results);
   if (rv != Success) {
     return rv;
   }
 
   if (trustDomain.VerifySignedData(&subject.GetNSSCert()->signatureWrap,
                                    potentialIssuer.GetNSSCert()) != SECSuccess) {
     return MapSECStatus(SECFailure);
@@ -172,16 +175,17 @@ BuildForwardInner(TrustDomain& trustDoma
 // insanity/pkix.h.
 static Result
 BuildForward(TrustDomain& trustDomain,
              BackCert& subject,
              PRTime time,
              EndEntityOrCA endEntityOrCA,
              KeyUsages requiredKeyUsagesIfPresent,
              SECOidTag requiredEKUIfPresent,
+             SECOidTag requiredPolicy,
              /*optional*/ const SECItem* stapledOCSPResponse,
              unsigned int subCACount,
              /*out*/ ScopedCERTCertList& results)
 {
   // Avoid stack overflows and poor performance by limiting cert length.
   // XXX: 6 is not enough for chains.sh anypolicywithlevel.cfg tests
   static const size_t MAX_DEPTH = 8;
   if (subCACount >= MAX_DEPTH - 1) {
@@ -190,18 +194,18 @@ BuildForward(TrustDomain& trustDomain,
 
   Result rv;
 
   TrustDomain::TrustLevel trustLevel;
   bool expiredEndEntity = false;
   rv = CheckIssuerIndependentProperties(trustDomain, subject, time,
                                         endEntityOrCA,
                                         requiredKeyUsagesIfPresent,
-                                        requiredEKUIfPresent, subCACount,
-                                        &trustLevel);
+                                        requiredEKUIfPresent, requiredPolicy,
+                                        subCACount, &trustLevel);
   if (rv != Success) {
     // CheckIssuerIndependentProperties checks for expiration last, so if
     // it returned SEC_ERROR_EXPIRED_CERTIFICATE we know that is the only
     // problem with the cert found so far. Keep going to see if we can build
     // a path; if not, it's better to return the path building failure.
     expiredEndEntity = endEntityOrCA == MustBeEndEntity &&
                        trustLevel != TrustDomain::TrustAnchor &&
                        PR_GetError() == SEC_ERROR_EXPIRED_CERTIFICATE;
@@ -233,17 +237,17 @@ BuildForward(TrustDomain& trustDomain,
     return Fail(RecoverableError, SEC_ERROR_UNKNOWN_ISSUER);
   }
 
   PRErrorCode errorToReturn = 0;
 
   for (CERTCertListNode* n = CERT_LIST_HEAD(candidates);
        !CERT_LIST_END(n, candidates); n = CERT_LIST_NEXT(n)) {
     rv = BuildForwardInner(trustDomain, subject, time, endEntityOrCA,
-                           requiredEKUIfPresent,
+                           requiredEKUIfPresent, requiredPolicy,
                            n->cert, stapledOCSPResponse, subCACount,
                            results);
     if (rv == Success) {
       if (expiredEndEntity) {
         // We deferred returning this error to see if we should return
         // "unknown issuer" instead. Since we found a valid issuer, it's
         // time to return "expired."
         PR_SetError(SEC_ERROR_EXPIRED_CERTIFICATE, 0);
@@ -293,16 +297,17 @@ BuildForward(TrustDomain& trustDomain,
 
 SECStatus
 BuildCertChain(TrustDomain& trustDomain,
                CERTCertificate* certToDup,
                PRTime time,
                EndEntityOrCA endEntityOrCA,
                /*optional*/ KeyUsages requiredKeyUsagesIfPresent,
                /*optional*/ SECOidTag requiredEKUIfPresent,
+               /*optional*/ SECOidTag requiredPolicy,
                /*optional*/ const SECItem* stapledOCSPResponse,
                /*out*/ ScopedCERTCertList& results)
 {
   PORT_Assert(certToDup);
 
   if (!certToDup) {
     PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
     return SECFailure;
@@ -322,17 +327,17 @@ BuildCertChain(TrustDomain& trustDomain,
   BackCert cert(certToDup, nullptr, cnOptions);
   Result rv = cert.Init();
   if (rv != Success) {
     return SECFailure;
   }
 
   rv = BuildForward(trustDomain, cert, time, endEntityOrCA,
                     requiredKeyUsagesIfPresent, requiredEKUIfPresent,
-                    stapledOCSPResponse, 0, results);
+                    requiredPolicy, stapledOCSPResponse, 0, results);
   if (rv != Success) {
     results = nullptr;
     return SECFailure;
   }
 
   return SECSuccess;
 }
 
--- a/security/insanity/lib/pkixcheck.cpp
+++ b/security/insanity/lib/pkixcheck.cpp
@@ -92,16 +92,66 @@ CheckKeyUsage(EndEntityOrCA endEntityOrC
     //  // XXX: better error code.
     //  return Fail(RecoverableError, SEC_ERROR_INADEQUATE_CERT_TYPE);
     //}
   }
 
   return Success;
 }
 
+// RFC5820 4.2.1.4. Certificate Policies
+//
+// "The user-initial-policy-set contains the special value any-policy if the
+// user is not concerned about certificate policy."
+Result
+CheckCertificatePolicies(BackCert& cert, EndEntityOrCA endEntityOrCA,
+                         bool isTrustAnchor, SECOidTag requiredPolicy)
+{
+  if (requiredPolicy == SEC_OID_X509_ANY_POLICY) {
+    return Success;
+  }
+
+  // It is likely some callers will pass SEC_OID_UNKNOWN when they don't care,
+  // instead of passing SEC_OID_X509_ANY_POLICY. Help them out by failing hard.
+  if (requiredPolicy == SEC_OID_UNKNOWN) {
+    PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
+    return FatalError;
+  }
+
+  // The root CA certificate may omit the policies that it has been
+  // trusted for, so we cannot require the policies to be present in those
+  // certificates. Instead, the determination of which roots are trusted for
+  // which policies is made by the TrustDomain's GetCertTrust method.
+  if (isTrustAnchor && endEntityOrCA == MustBeCA) {
+    return Success;
+  }
+
+  if (!cert.encodedCertificatePolicies) {
+    PR_SetError(SEC_ERROR_POLICY_VALIDATION_FAILED, 0);
+    return RecoverableError;
+  }
+
+  ScopedPtr<CERTCertificatePolicies, CERT_DestroyCertificatePoliciesExtension>
+    policies(CERT_DecodeCertificatePoliciesExtension(
+                cert.encodedCertificatePolicies));
+  if (!policies) {
+    return MapSECStatus(SECFailure);
+  }
+
+  for (const CERTPolicyInfo* const* policyInfos = policies->policyInfos;
+       *policyInfos; ++policyInfos) {
+    if ((*policyInfos)->oid == requiredPolicy) {
+      return Success;
+    }
+  }
+
+  PR_SetError(SEC_ERROR_POLICY_VALIDATION_FAILED, 0);
+  return RecoverableError;
+}
+
 // RFC5280 4.2.1.9. Basic Constraints (id-ce-basicConstraints)
 Result
 CheckBasicConstraints(const BackCert& cert,
                       EndEntityOrCA endEntityOrCA,
                       bool isTrustAnchor,
                       unsigned int subCACount)
 {
   CERTBasicConstraints basicConstraints;
@@ -324,23 +374,25 @@ CheckExtendedKeyUsage(EndEntityOrCA endE
 
 Result
 CheckIssuerIndependentProperties(TrustDomain& trustDomain,
                                  BackCert& cert,
                                  PRTime time,
                                  EndEntityOrCA endEntityOrCA,
                                  KeyUsages requiredKeyUsagesIfPresent,
                                  SECOidTag requiredEKUIfPresent,
+                                 SECOidTag requiredPolicy,
                                  unsigned int subCACount,
                 /*optional out*/ TrustDomain::TrustLevel* trustLevelOut)
 {
   Result rv;
 
   TrustDomain::TrustLevel trustLevel;
   rv = MapSECStatus(trustDomain.GetCertTrust(endEntityOrCA,
+                                             requiredPolicy,
                                              cert.GetNSSCert(),
                                              &trustLevel));
   if (rv != Success) {
     return rv;
   }
   if (trustLevel == TrustDomain::ActivelyDistrusted) {
     PORT_SetError(SEC_ERROR_UNTRUSTED_CERT);
     return RecoverableError;
@@ -353,58 +405,74 @@ CheckIssuerIndependentProperties(TrustDo
   }
   if (trustLevelOut) {
     *trustLevelOut = trustLevel;
   }
 
   bool isTrustAnchor = endEntityOrCA == MustBeCA &&
                        trustLevel == TrustDomain::TrustAnchor;
 
-  // 4.2.1.1. Authority Key Identifier dealt with as part of path building
-  // 4.2.1.2. Subject Key Identifier dealt with as part of path building
-
   PLArenaPool* arena = cert.GetArena();
   if (!arena) {
     return FatalError;
   }
 
+  // 4.2.1.1. Authority Key Identifier is ignored (see bug 965136).
+
+  // 4.2.1.2. Subject Key Identifier is ignored (see bug 965136).
+
   // 4.2.1.3. Key Usage
-
   rv = CheckKeyUsage(endEntityOrCA, isTrustAnchor, cert.encodedKeyUsage,
                      requiredKeyUsagesIfPresent, arena);
   if (rv != Success) {
     return rv;
   }
 
   // 4.2.1.4. Certificate Policies
-  // 4.2.1.5. Policy Mappings are rejected in BackCert::Init()
-  // 4.2.1.6. Subject Alternative Name dealt with elsewhere
-  // 4.2.1.7. Issuer Alternative Name is not something that needs checking
-  // 4.2.1.8. Subject Directory Attributes is not something that needs checking
+  rv = CheckCertificatePolicies(cert, endEntityOrCA, isTrustAnchor,
+                                requiredPolicy);
+  if (rv != Success) {
+    return rv;
+  }
+
+  // 4.2.1.5. Policy Mappings are not supported; see the documentation about
+  //          policy enforcement in pkix.h.
 
-  // 4.2.1.9. Basic Constraints. We check basic constraints before other
-  // properties that take endEntityOrCA so that those checks can use
-  // endEntityOrCA as a proxy for the isCA bit from the basic constraints.
+  // 4.2.1.6. Subject Alternative Name dealt with during name constraint
+  //          checking and during name verification (CERT_VerifyCertName).
+
+  // 4.2.1.7. Issuer Alternative Name is not something that needs checking.
+
+  // 4.2.1.8. Subject Directory Attributes is not something that needs
+  //          checking.
+
+  // 4.2.1.9. Basic Constraints.
   rv = CheckBasicConstraints(cert, endEntityOrCA, isTrustAnchor, subCACount);
   if (rv != Success) {
     return rv;
   }
 
-  // 4.2.1.10. Name Constraints
-  // 4.2.1.11. Policy Constraints
+  // 4.2.1.10. Name Constraints is dealt with in during path building.
+
+  // 4.2.1.11. Policy Constraints are implicitly supported; see the
+  //           documentation about policy enforcement in pkix.h.
 
   // 4.2.1.12. Extended Key Usage
   rv = CheckExtendedKeyUsage(endEntityOrCA, cert.encodedExtendedKeyUsage,
                              requiredEKUIfPresent);
   if (rv != Success) {
     return rv;
   }
 
-  // 4.2.1.13. CRL Distribution Points will be dealt with elsewhere
-  // 4.2.1.14. Inhibit anyPolicy
+  // 4.2.1.13. CRL Distribution Points is not supported, though the
+  //           TrustDomain's CheckRevocation method may parse it and process it
+  //           on its own.
+
+  // 4.2.1.14. Inhibit anyPolicy is implicitly supported; see the documentation
+  //           about policy enforcement in pkix.h.
 
   // IMPORTANT: This check must come after the other checks in order for error
   // ranking to work correctly.
   rv = CheckTimes(cert.GetNSSCert(), time);
   if (rv != Success) {
     return rv;
   }
 
--- a/security/insanity/lib/pkixcheck.h
+++ b/security/insanity/lib/pkixcheck.h
@@ -25,16 +25,17 @@ namespace insanity { namespace pkix {
 
 Result CheckIssuerIndependentProperties(
           TrustDomain& trustDomain,
           BackCert& cert,
           PRTime time,
           EndEntityOrCA endEntityOrCA,
           KeyUsages requiredKeyUsagesIfPresent,
           SECOidTag requiredEKUIfPresent,
+          SECOidTag requiredPolicy,
           unsigned int subCACount,
           /*optional out*/ TrustDomain::TrustLevel* trustLevel = nullptr);
 
 Result CheckNameConstraints(BackCert& cert);
 
 } } // namespace insanity::pkix
 
 #endif // insanity__pkixcheck_h
--- a/security/insanity/lib/pkixocsp.cpp
+++ b/security/insanity/lib/pkixocsp.cpp
@@ -110,17 +110,18 @@ CheckOCSPResponseSignerCert(TrustDomain&
   // SEC_OID_OCSP_RESPONDER in the way that the OCSP specification requires us
   // to--in particular, it doesn't allow SEC_OID_OCSP_RESPONDER to be implied
   // by a missing EKU extension, unlike other EKUs.
   //
   // TODO(bug 926261): If we're validating for a policy then the policy OID we
   // are validating for should be passed to CheckIssuerIndependentProperties.
   rv = CheckIssuerIndependentProperties(trustDomain, cert, time,
                                         MustBeEndEntity, 0,
-                                        SEC_OID_OCSP_RESPONDER, 0);
+                                        SEC_OID_OCSP_RESPONDER,
+                                        SEC_OID_X509_ANY_POLICY, 0);
   if (rv != Success) {
     return rv;
   }
 
   // It is possible that there exists a certificate with the same key as the
   // issuer but with a different name, so we need to compare names
   // TODO: needs test
   if (!SECITEM_ItemsAreEqual(&cert.GetNSSCert()->derIssuer,
--- a/security/insanity/lib/pkixutil.h
+++ b/security/insanity/lib/pkixutil.h
@@ -85,29 +85,31 @@ public:
   // its results. IncludeCN means that GetConstrainedNames will include the
   // subject CN in its results.
   enum ConstrainedNameOptions { ExcludeCN = 0, IncludeCN = 1 };
 
   // nssCert and childCert must be valid for the lifetime of BackCert
   BackCert(CERTCertificate* nssCert, BackCert* childCert,
            ConstrainedNameOptions cnOptions)
     : encodedBasicConstraints(nullptr)
+    , encodedCertificatePolicies(nullptr)
     , encodedExtendedKeyUsage(nullptr)
     , encodedKeyUsage(nullptr)
     , encodedNameConstraints(nullptr)
     , childCert(childCert)
     , nssCert(nssCert)
     , constrainedNames(nullptr)
     , cnOptions(cnOptions)
   {
   }
 
   Result Init();
 
   const SECItem* encodedBasicConstraints;
+  const SECItem* encodedCertificatePolicies;
   const SECItem* encodedExtendedKeyUsage;
   const SECItem* encodedKeyUsage;
   const SECItem* encodedNameConstraints;
 
   BackCert* const childCert;
 
   // Only non-const so that we can pass this to TrustDomain::IsRevoked,
   // which only takes a non-const pointer because VerifyEncodedOCSPResponse