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 188023 e50c326ad721ba006716daa4f0a43c8e1584c06d
parent 188022 3d4a094ac17e373b5b242a636d0bb7d0686519ee
child 188024 b7030189c2ca5697c8fba43220511ddc39fcce98
push id3503
push userraliiev@mozilla.com
push dateMon, 28 Apr 2014 18:51:11 +0000
treeherdermozilla-beta@c95ac01e332e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskeeler, cviecco
bugs921886
milestone30.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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