Bug 1006041: Use mozilla::pkix::der for decoding the extended key usage extension, r=keeler
authorBrian Smith <brian@briansmith.org>
Wed, 14 May 2014 01:02:34 -0700
changeset 183488 b9eff37173e1219027e2b5cb6822cb33504106b6
parent 183487 3714913ab8c45c83d6fe89d21ad4df1e8c209f24
child 183489 fe7bffe6bb06fbe0cf047f1c979acb14875d3d1c
push idunknown
push userunknown
push dateunknown
reviewerskeeler
bugs1006041
milestone32.0a1
Bug 1006041: Use mozilla::pkix::der for decoding the extended key usage extension, r=keeler
security/apps/AppSignatureVerification.cpp
security/certverifier/CertVerifier.cpp
security/pkix/include/pkix/bind.h
security/pkix/include/pkix/pkix.h
security/pkix/include/pkix/pkixtypes.h
security/pkix/lib/pkixbind.cpp
security/pkix/lib/pkixbuild.cpp
security/pkix/lib/pkixcheck.cpp
security/pkix/lib/pkixcheck.h
security/pkix/lib/pkixder.h
security/pkix/lib/pkixocsp.cpp
security/pkix/test/gtest/pkixder_input_tests.cpp
--- a/security/apps/AppSignatureVerification.cpp
+++ b/security/apps/AppSignatureVerification.cpp
@@ -600,18 +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(),
                      EndEntityOrCA::MustBeEndEntity, KU_DIGITAL_SIGNATURE,
-                     SEC_OID_EXT_KEY_USAGE_CODE_SIGN,
-                     SEC_OID_X509_ANY_POLICY, nullptr, builtChain)
+                     KeyPurposeId::id_kp_codeSigning, 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) {
--- a/security/certverifier/CertVerifier.cpp
+++ b/security/certverifier/CertVerifier.cpp
@@ -295,17 +295,17 @@ destroyCertListThatShouldNotExist(CERTCe
     *certChain = nullptr;
   }
 }
 #endif
 
 static SECStatus
 BuildCertChainForOneKeyUsage(TrustDomain& trustDomain, CERTCertificate* cert,
                              PRTime time, KeyUsages ku1, KeyUsages ku2,
-                             KeyUsages ku3, SECOidTag eku,
+                             KeyUsages ku3, KeyPurposeId eku,
                              SECOidTag requiredPolicy,
                              const SECItem* stapledOCSPResponse,
                              ScopedCERTCertList& builtChain)
 {
   PR_ASSERT(ku1);
   PR_ASSERT(ku2);
 
   SECStatus rv = BuildCertChain(trustDomain, cert, time,
@@ -386,19 +386,19 @@ CertVerifier::MozillaPKIXVerifyCert(
   switch (usage) {
     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, ocspFetching, mOCSPCache,
                                        pinArg);
       rv = BuildCertChain(trustDomain, cert, time,
                           EndEntityOrCA::MustBeEndEntity, KU_DIGITAL_SIGNATURE,
-                          SEC_OID_EXT_KEY_USAGE_CLIENT_AUTH,
-                          SEC_OID_X509_ANY_POLICY,
-                          stapledOCSPResponse, builtChain);
+                          KeyPurposeId::id_kp_clientAuth,
+                          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.
 
@@ -412,17 +412,17 @@ CertVerifier::MozillaPKIXVerifyCert(
                       ocspFetching == NSSCertDBTrustDomain::NeverFetchOCSP
                         ? NSSCertDBTrustDomain::LocalOnlyOCSPForEV
                         : NSSCertDBTrustDomain::FetchOCSPForEV,
                       mOCSPCache, pinArg, &callbackContainer);
         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,
+                                          KeyPurposeId::id_kp_serverAuth,
                                           evPolicy, stapledOCSPResponse,
                                           builtChain);
         if (rv == SECSuccess) {
           if (evOidPolicy) {
             *evOidPolicy = evPolicy;
           }
           break;
         }
@@ -438,88 +438,85 @@ CertVerifier::MozillaPKIXVerifyCert(
 
       // Now try non-EV.
       NSSCertDBTrustDomain trustDomain(trustSSL, ocspFetching, mOCSPCache,
                                        pinArg, &callbackContainer);
       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,
+                                        KeyPurposeId::id_kp_serverAuth,
                                         SEC_OID_X509_ANY_POLICY,
                                         stapledOCSPResponse, builtChain);
       break;
     }
 
     case certificateUsageSSLCA: {
       NSSCertDBTrustDomain trustDomain(trustSSL, ocspFetching, mOCSPCache,
                                        pinArg);
       rv = BuildCertChain(trustDomain, cert, time, EndEntityOrCA::MustBeCA,
-                          KU_KEY_CERT_SIGN,
-                          SEC_OID_EXT_KEY_USAGE_SERVER_AUTH,
+                          KU_KEY_CERT_SIGN, KeyPurposeId::id_kp_serverAuth,
                           SEC_OID_X509_ANY_POLICY,
                           stapledOCSPResponse, builtChain);
       break;
     }
 
     case certificateUsageEmailSigner: {
       NSSCertDBTrustDomain trustDomain(trustEmail, ocspFetching, mOCSPCache,
                                        pinArg);
       rv = BuildCertChain(trustDomain, cert, time,
                           EndEntityOrCA::MustBeEndEntity, KU_DIGITAL_SIGNATURE,
-                          SEC_OID_EXT_KEY_USAGE_EMAIL_PROTECT,
-                          SEC_OID_X509_ANY_POLICY,
+                          KeyPurposeId::id_kp_emailProtection, 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, ocspFetching, mOCSPCache,
                                        pinArg);
       rv = BuildCertChainForOneKeyUsage(trustDomain, cert, time,
                                         KU_KEY_ENCIPHERMENT, // RSA
                                         KU_KEY_AGREEMENT, // ECDH/DH
                                         0,
-                                        SEC_OID_EXT_KEY_USAGE_EMAIL_PROTECT,
+                                        KeyPurposeId::id_kp_emailProtection,
                                         SEC_OID_X509_ANY_POLICY,
                                         stapledOCSPResponse, builtChain);
       break;
     }
 
     case certificateUsageObjectSigner: {
       NSSCertDBTrustDomain trustDomain(trustObjectSigning, ocspFetching,
                                        mOCSPCache, pinArg);
       rv = BuildCertChain(trustDomain, cert, time,
                           EndEntityOrCA::MustBeEndEntity, KU_DIGITAL_SIGNATURE,
-                          SEC_OID_EXT_KEY_USAGE_CODE_SIGN,
-                          SEC_OID_X509_ANY_POLICY,
+                          KeyPurposeId::id_kp_codeSigning, 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
       // certificate viewer UI. Because we don't know what trust bit is
       // interesting, we just try them all.
       mozilla::pkix::EndEntityOrCA endEntityOrCA;
       mozilla::pkix::KeyUsages keyUsage;
-      SECOidTag eku;
+      KeyPurposeId eku;
       if (usage == certificateUsageVerifyCA) {
         endEntityOrCA = EndEntityOrCA::MustBeCA;
         keyUsage = KU_KEY_CERT_SIGN;
-        eku = SEC_OID_UNKNOWN;
+        eku = KeyPurposeId::anyExtendedKeyUsage;
       } else {
         endEntityOrCA = EndEntityOrCA::MustBeEndEntity;
         keyUsage = KU_DIGITAL_SIGNATURE;
-        eku = SEC_OID_OCSP_RESPONDER;
+        eku = KeyPurposeId::id_kp_OCSPSigning;
       }
 
       NSSCertDBTrustDomain sslTrust(trustSSL, ocspFetching, mOCSPCache,
                                     pinArg);
       rv = BuildCertChain(sslTrust, cert, time, endEntityOrCA,
                           keyUsage, eku, SEC_OID_X509_ANY_POLICY,
                           stapledOCSPResponse, builtChain);
       if (rv == SECFailure && PR_GetError() == SEC_ERROR_UNKNOWN_ISSUER) {
--- a/security/pkix/include/pkix/bind.h
+++ b/security/pkix/include/pkix/bind.h
@@ -19,36 +19,45 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 // Work around missing std::bind, std::ref, std::cref in older compilers. This
 // implementation isn't intended to be complete; rather, it is the minimal
-// implementation needed to make our use of std::bind work.
+// implementation needed to make our use of std::bind work for compilers that
+// lack both C++11 and TR1 support for these features. We cannot even assume
+// that rvalue references work, which means we don't get perfect forwarding
+// and thus we basically have to define a new overload for every distinct call
+// signature.
+//
+// A positive side-effect of this code is improved debugging usability; it is
+// much more convenient to step through code that uses this polyfill than it is
+// to step through the many nested layers of a real std::bind implementation.
+//
+// Build with MOZILLA_PKIX_USE_REAL_FUNCTIONAL defined in order to use the
+// compiler's definitions of these functions. This is helpful in order to
+// ensure that the calling code is actually compatible with the real std::bind
+// and friends.
 
 #ifndef mozilla_pkix__bind_h
 #define mozilla_pkix__bind_h
 
-#ifdef _MSC_VER
-#pragma warning(disable:4275) //Suppress spurious MSVC warning
-#endif
+#ifdef MOZILLA_PKIX_USE_REAL_FUNCTIONAL
 #include <functional>
-#ifdef _MSC_VER
-#pragma warning(default:4275)
 #endif
 
 namespace mozilla { namespace pkix {
 
-#ifdef _MSC_VER
+#ifdef MOZILLA_PKIX_USE_REAL_FUNCTIONAL
 
 using std::bind;
+using std::cref;
 using std::ref;
-using std::cref;
 using std::placeholders::_1;
 
 #else
 
 class Placeholder1 { };
 extern Placeholder1 _1;
 
 template <typename V>       V&  ref(V& v)       { return v; }
@@ -61,44 +70,95 @@ class Bind1
 {
 public:
   typedef R (*F)(P1 &, B1 &);
   Bind1(F f, B1 & b1) : f(f), b1(b1) { }
   R operator()(P1 & p1) const { return f(p1, b1); }
 private:
   const F f;
   B1& b1;
+  void operator=(const Bind1&) /*= delete*/;
 };
 
 template <typename R, typename P1, typename B1, typename B2>
 class Bind2
 {
 public:
   typedef R (*F)(P1&, B1&, B2&);
   Bind2(F f, B1& b1, B2& b2) : f(f), b1(b1), b2(b2) { }
   R operator()(P1& p1) const { return f(p1, b1, b2); }
 private:
   const F f;
   B1& b1;
   B2& b2;
+  void operator=(const Bind2&) /*= delete*/;
+};
+
+template <typename R, typename P1, typename B1, typename B2, typename B3>
+class Bind3
+{
+public:
+  typedef R (*F)(P1&, B1&, B2&, B3&);
+  Bind3(F f, B1& b1, B2& b2, B3& b3) : f(f), b1(b1), b2(b2), b3(b3) { }
+  R operator()(P1& p1) const { return f(p1, b1, b2, b3); }
+private:
+  const F f;
+  B1& b1;
+  B2& b2;
+  B3& b3;
+  void operator=(const Bind3&) /*= delete*/;
+};
+
+template <typename R, typename P1, typename B1, typename B2, typename B3,
+          typename B4>
+class Bind4
+{
+public:
+  typedef R (*F)(P1&, B1, B2, B3&, B4&);
+  Bind4(F f, B1& b1, B2& b2, B3& b3, B4& b4)
+    : f(f), b1(b1), b2(b2), b3(b3), b4(b4) { }
+  R operator()(P1& p1) const { return f(p1, b1, b2, b3, b4); }
+private:
+  const F f;
+  B1& b1;
+  B2& b2;
+  B3& b3;
+  B4& b4;
+  void operator=(const Bind4&) /*= delete*/;
 };
 
 } // namespace internal
 
 template <typename R, typename P1, typename B1>
 inline internal::Bind1<R, P1, B1>
 bind(R (*f)(P1&, B1&), Placeholder1&, B1& b1)
 {
   return internal::Bind1<R, P1, B1>(f, b1);
 }
 
 template <typename R, typename P1, typename B1, typename B2>
 inline internal::Bind2<R, P1, B1, B2>
-bind(R (*f)(P1&, B1&, B2&), Placeholder1 &, B1 & b1, B2 & b2)
+bind(R (*f)(P1&, B1&, B2&), Placeholder1&, B1& b1, B2& b2)
 {
   return internal::Bind2<R, P1, B1, B2>(f, b1, b2);
 }
 
-#endif // _MSC_VER
+template <typename R, typename P1, typename B1, typename B2, typename B3>
+inline internal::Bind3<R, P1, B1, B2, B3>
+bind(R (*f)(P1&, B1&, B2&, B3&), Placeholder1&, B1& b1, B2& b2, B3& b3)
+{
+  return internal::Bind3<R, P1, B1, B2, B3>(f, b1, b2, b3);
+}
+
+template <typename R, typename P1, typename B1, typename B2, typename B3,
+          typename B4>
+inline internal::Bind4<R, P1, const B1, const B2, B3, B4>
+bind(R (*f)(P1&, B1, B2, B3&, B4&), Placeholder1&, const B1& b1, const B2& b2,
+     B3& b3, B4& b4)
+{
+  return internal::Bind4<R, P1, const B1, const B2, B3, B4>(f, b1, b2, b3, b4);
+}
+
+#endif
 
 } } // namespace mozilla::pkix
 
 #endif // mozilla_pkix__bind_h
--- a/security/pkix/include/pkix/pkix.h
+++ b/security/pkix/include/pkix/pkix.h
@@ -89,17 +89,17 @@ namespace mozilla { namespace pkix {
 //                            distrust.
 // TODO(bug 968451): Document more of these.
 
 SECStatus BuildCertChain(TrustDomain& trustDomain,
                          CERTCertificate* cert,
                          PRTime time,
                          EndEntityOrCA endEntityOrCA,
             /*optional*/ KeyUsages requiredKeyUsagesIfPresent,
-            /*optional*/ SECOidTag requiredEKUIfPresent,
+                         KeyPurposeId 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,
--- a/security/pkix/include/pkix/pkixtypes.h
+++ b/security/pkix/include/pkix/pkixtypes.h
@@ -36,19 +36,28 @@ namespace mozilla { namespace pkix {
 typedef ScopedPtr<PLArenaPool, PL_FreeArenaPool> ScopedPLArenaPool;
 
 typedef ScopedPtr<CERTCertificate, CERT_DestroyCertificate>
         ScopedCERTCertificate;
 typedef ScopedPtr<CERTCertList, CERT_DestroyCertList> ScopedCERTCertList;
 typedef ScopedPtr<SECKEYPublicKey, SECKEY_DestroyPublicKey>
         ScopedSECKEYPublicKey;
 
+MOZILLA_PKIX_ENUM_CLASS EndEntityOrCA { MustBeEndEntity = 0, MustBeCA = 1 };
+
 typedef unsigned int KeyUsages;
 
-MOZILLA_PKIX_ENUM_CLASS EndEntityOrCA { MustBeEndEntity = 0, MustBeCA = 1 };
+MOZILLA_PKIX_ENUM_CLASS KeyPurposeId {
+  anyExtendedKeyUsage = 0,
+  id_kp_serverAuth = 1,           // id-kp-serverAuth
+  id_kp_clientAuth = 2,           // id-kp-clientAuth
+  id_kp_codeSigning = 3,          // id-kp-codeSigning
+  id_kp_emailProtection = 4,      // id-kp-emailProtection
+  id_kp_OCSPSigning = 9,          // id-kp-OCSPSigning
+};
 
 MOZILLA_PKIX_ENUM_CLASS TrustLevel {
   TrustAnchor = 1,        // certificate is a trusted root CA certificate or
                           // equivalent *for the given policy*.
   ActivelyDistrusted = 2, // certificate is known to be bad
   InheritsTrust = 3       // certificate must chain to a trust anchor
 };
 
--- a/security/pkix/lib/pkixbind.cpp
+++ b/security/pkix/lib/pkixbind.cpp
@@ -17,19 +17,19 @@
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-#ifndef _MSC_VER
+#ifndef MOZILLA_PKIX_USE_REAL_FUNCTIONAL
 
 #include "pkix/bind.h"
 
 namespace mozilla { namespace pkix {
 
 Placeholder1 _1;
 
 } } // namespace mozilla::pkix
 
-#endif // _MSC_VER
+#endif
--- a/security/pkix/lib/pkixbuild.cpp
+++ b/security/pkix/lib/pkixbuild.cpp
@@ -109,29 +109,29 @@ BackCert::Init()
   return Success;
 }
 
 static Result BuildForward(TrustDomain& trustDomain,
                            BackCert& subject,
                            PRTime time,
                            EndEntityOrCA endEntityOrCA,
                            KeyUsages requiredKeyUsagesIfPresent,
-                           SECOidTag requiredEKUIfPresent,
+                           KeyPurposeId 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,
+                  KeyPurposeId requiredEKUIfPresent,
                   SECOidTag requiredPolicy,
                   CERTCertificate* potentialIssuerCertToDup,
                   unsigned int subCACount,
                   ScopedCERTCertList& results)
 {
   PORT_Assert(potentialIssuerCertToDup);
 
   BackCert potentialIssuer(potentialIssuerCertToDup, &subject,
@@ -191,17 +191,17 @@ BuildForwardInner(TrustDomain& trustDoma
 // chain has multiple problems. See the error ranking documentation in
 // pkix/pkix.h.
 static Result
 BuildForward(TrustDomain& trustDomain,
              BackCert& subject,
              PRTime time,
              EndEntityOrCA endEntityOrCA,
              KeyUsages requiredKeyUsagesIfPresent,
-             SECOidTag requiredEKUIfPresent,
+             KeyPurposeId 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;
@@ -331,17 +331,17 @@ BuildForward(TrustDomain& trustDomain,
 }
 
 SECStatus
 BuildCertChain(TrustDomain& trustDomain,
                CERTCertificate* certToDup,
                PRTime time,
                EndEntityOrCA endEntityOrCA,
                /*optional*/ KeyUsages requiredKeyUsagesIfPresent,
-               /*optional*/ SECOidTag requiredEKUIfPresent,
+               /*optional*/ KeyPurposeId requiredEKUIfPresent,
                /*optional*/ SECOidTag requiredPolicy,
                /*optional*/ const SECItem* stapledOCSPResponse,
                /*out*/ ScopedCERTCertList& results)
 {
   PORT_Assert(certToDup);
 
   if (!certToDup) {
     PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
@@ -350,17 +350,17 @@ BuildCertChain(TrustDomain& trustDomain,
 
   // The only non-const operation on the cert we are allowed to do is
   // CERT_DupCertificate.
 
   // XXX: Support the legacy use of the subject CN field for indicating the
   // domain name the certificate is valid for.
   BackCert::IncludeCN includeCN
     = endEntityOrCA == EndEntityOrCA::MustBeEndEntity &&
-      requiredEKUIfPresent == SEC_OID_EXT_KEY_USAGE_SERVER_AUTH
+      requiredEKUIfPresent == KeyPurposeId::id_kp_serverAuth
     ? BackCert::IncludeCN::Yes
     : BackCert::IncludeCN::No;
 
   BackCert cert(certToDup, nullptr, includeCN);
   Result rv = cert.Init();
   if (rv != Success) {
     return SECFailure;
   }
--- a/security/pkix/lib/pkixcheck.cpp
+++ b/security/pkix/lib/pkixcheck.cpp
@@ -330,61 +330,130 @@ CheckNameConstraints(BackCert& cert)
       currentName = CERT_GetNextGeneralName(currentName);
     } while (currentName != names);
   }
 
   return Success;
 }
 
 // 4.2.1.12. Extended Key Usage (id-ce-extKeyUsage)
-// 4.2.1.12. Extended Key Usage (id-ce-extKeyUsage)
-Result
-CheckExtendedKeyUsage(EndEntityOrCA endEntityOrCA, const SECItem* encodedEKUs,
-                      SECOidTag requiredEKU)
+
+static der::Result
+MatchEKU(der::Input& value, KeyPurposeId requiredEKU,
+         EndEntityOrCA endEntityOrCA, /*in/out*/ bool& found,
+         /*in/out*/ bool& foundOCSPSigning)
 {
-  // TODO: Either do not allow anyExtendedKeyUsage to be passed as requiredEKU,
-  // or require that callers pass anyExtendedKeyUsage instead of
-  // SEC_OID_UNKNWON and disallow SEC_OID_UNKNWON.
+  // See Section 5.9 of "A Layman's Guide to a Subset of ASN.1, BER, and DER"
+  // for a description of ASN.1 DER encoding of OIDs.
+
+  // id-pkix  OBJECT IDENTIFIER  ::=
+  //            { iso(1) identified-organization(3) dod(6) internet(1)
+  //                    security(5) mechanisms(5) pkix(7) }
+  // id-kp OBJECT IDENTIFIER ::= { id-pkix 3 }
+  // id-kp-serverAuth      OBJECT IDENTIFIER ::= { id-kp 1 }
+  // id-kp-clientAuth      OBJECT IDENTIFIER ::= { id-kp 2 }
+  // id-kp-codeSigning     OBJECT IDENTIFIER ::= { id-kp 3 }
+  // id-kp-emailProtection OBJECT IDENTIFIER ::= { id-kp 4 }
+  // id-kp-OCSPSigning     OBJECT IDENTIFIER ::= { id-kp 9 }
+  static const uint8_t server[] = { (40*1)+3, 6, 1, 5, 5, 7, 3, 1 };
+  static const uint8_t client[] = { (40*1)+3, 6, 1, 5, 5, 7, 3, 2 };
+  static const uint8_t code  [] = { (40*1)+3, 6, 1, 5, 5, 7, 3, 3 };
+  static const uint8_t email [] = { (40*1)+3, 6, 1, 5, 5, 7, 3, 4 };
+  static const uint8_t ocsp  [] = { (40*1)+3, 6, 1, 5, 5, 7, 3, 9 };
+
+  // id-Netscape        OBJECT IDENTIFIER ::= { 2 16 840 1 113730 }
+  // id-Netscape-policy OBJECT IDENTIFIER ::= { id-Netscape 4 }
+  // id-Netscape-stepUp OBJECT IDENTIFIER ::= { id-Netscape-policy 1 }
+  static const uint8_t serverStepUp[] =
+    { (40*2)+16, 128+6,72, 1, 128+6,128+120,66, 4, 1 };
+
+  bool match = false;
+
+  if (!found) {
+    switch (requiredEKU) {
+      case KeyPurposeId::id_kp_serverAuth:
+        // Treat CA certs with step-up OID as also having SSL server type.
+        // Comodo has issued certificates that require this behavior that don't
+        // expire until June 2020! TODO(bug 982932): Limit this exception to
+        // old certificates.
+        match = value.MatchBytes(server) ||
+                (endEntityOrCA == EndEntityOrCA::MustBeCA &&
+                 value.MatchBytes(serverStepUp));
+        break;
+
+      case KeyPurposeId::id_kp_clientAuth:
+        match = value.MatchBytes(client);
+        break;
 
+      case KeyPurposeId::id_kp_codeSigning:
+        match = value.MatchBytes(code);
+        break;
+
+      case KeyPurposeId::id_kp_emailProtection:
+        match = value.MatchBytes(email);
+        break;
+
+      case KeyPurposeId::id_kp_OCSPSigning:
+        match = value.MatchBytes(ocsp);
+        break;
+
+      case KeyPurposeId::anyExtendedKeyUsage:
+        PR_NOT_REACHED("anyExtendedKeyUsage should start with found==true");
+        return der::Fail(SEC_ERROR_LIBRARY_FAILURE);
+
+      default:
+        PR_NOT_REACHED("unrecognized EKU");
+        return der::Fail(SEC_ERROR_LIBRARY_FAILURE);
+    }
+  }
+
+  if (match) {
+    if (value.AtEnd()) {
+      found = true;
+      if (requiredEKU == KeyPurposeId::id_kp_OCSPSigning) {
+        foundOCSPSigning = true;
+      }
+    }
+  } else if (value.MatchBytes(ocsp) && value.AtEnd()) {
+    foundOCSPSigning = true;
+  }
+
+  value.SkipToEnd(); // ignore unmatched OIDs.
+
+  return der::Success;
+}
+
+Result
+CheckExtendedKeyUsage(EndEntityOrCA endEntityOrCA,
+                      const SECItem* encodedExtendedKeyUsage,
+                      KeyPurposeId requiredEKU)
+{
   // XXX: We're using SEC_ERROR_INADEQUATE_CERT_TYPE here so that callers can
   // distinguish EKU mismatch from KU mismatch from basic constraints mismatch.
   // We should probably add a new error code that is more clear for this type
   // of problem.
 
   bool foundOCSPSigning = false;
 
-  if (encodedEKUs) {
-    ScopedPtr<CERTOidSequence, CERT_DestroyOidSequence>
-      seq(CERT_DecodeOidSequence(encodedEKUs));
-    if (!seq) {
-      PR_SetError(SEC_ERROR_INADEQUATE_CERT_TYPE, 0);
-      return RecoverableError;
-    }
-
-    bool found = false;
+  if (encodedExtendedKeyUsage) {
+    bool found = requiredEKU == KeyPurposeId::anyExtendedKeyUsage;
 
-    // XXX: We allow duplicate entries.
-    for (const SECItem* const* oids = seq->oids; oids && *oids; ++oids) {
-      SECOidTag oidTag = SECOID_FindOIDTag(*oids);
-      if (requiredEKU != SEC_OID_UNKNOWN && oidTag == requiredEKU) {
-        found = true;
-      } else {
-        // Treat CA certs with step-up OID as also having SSL server type.
-        // COMODO has issued certificates that require this behavior
-        // that don't expire until June 2020!
-        // TODO 982932: Limit this expection to old certificates
-        if (endEntityOrCA == EndEntityOrCA::MustBeCA &&
-            requiredEKU == SEC_OID_EXT_KEY_USAGE_SERVER_AUTH &&
-            oidTag == SEC_OID_NS_KEY_USAGE_GOVT_APPROVED) {
-          found = true;
-        }
-      }
-      if (oidTag == SEC_OID_OCSP_RESPONDER) {
-        foundOCSPSigning = true;
-      }
+    der::Input input;
+    if (input.Init(encodedExtendedKeyUsage->data,
+                   encodedExtendedKeyUsage->len) != der::Success) {
+      return Fail(RecoverableError, SEC_ERROR_INADEQUATE_CERT_TYPE);
+    }
+    if (der::NestedOf(input, der::SEQUENCE, der::OIDTag, der::EmptyAllowed::No,
+                      bind(MatchEKU, _1, requiredEKU, endEntityOrCA,
+                           ref(found), ref(foundOCSPSigning)))
+          != der::Success) {
+      return Fail(RecoverableError, SEC_ERROR_INADEQUATE_CERT_TYPE);
+    }
+    if (der::End(input) != der::Success) {
+      return Fail(RecoverableError, SEC_ERROR_INADEQUATE_CERT_TYPE);
     }
 
     // If the EKU extension was included, then the required EKU must be in the
     // list.
     if (!found) {
       return Fail(RecoverableError, SEC_ERROR_INADEQUATE_CERT_TYPE);
     }
   }
@@ -399,45 +468,43 @@ CheckExtendedKeyUsage(EndEntityOrCA endE
     // if not for this check.
     // That said, we accept CA certificates with id-kp-OCSPSigning because
     // some CAs in Mozilla's CA program have issued such intermediate
     // certificates, and because some CAs have reported some Microsoft server
     // software wrongly requires CA certificates to have id-kp-OCSPSigning.
     // Allowing this exception does not cause any security issues because we
     // require delegated OCSP response signing certificates to be end-entity
     // certificates.
-    if (foundOCSPSigning && requiredEKU != SEC_OID_OCSP_RESPONDER) {
-      PR_SetError(SEC_ERROR_INADEQUATE_CERT_TYPE, 0);
-      return RecoverableError;
+    if (foundOCSPSigning && requiredEKU != KeyPurposeId::id_kp_OCSPSigning) {
+      return Fail(RecoverableError, SEC_ERROR_INADEQUATE_CERT_TYPE);
     }
     // http://tools.ietf.org/html/rfc6960#section-4.2.2.2:
     // "OCSP signing delegation SHALL be designated by the inclusion of
     // id-kp-OCSPSigning in an extended key usage certificate extension
     // included in the OCSP response signer's certificate."
     //
     // id-kp-OCSPSigning is the only EKU that isn't implicitly assumed when the
     // EKU extension is missing from an end-entity certificate. However, any CA
     // certificate can issue a delegated OCSP response signing certificate, so
     // we can't require the EKU be explicitly included for CA certificates.
-    if (!foundOCSPSigning && requiredEKU == SEC_OID_OCSP_RESPONDER) {
-      PR_SetError(SEC_ERROR_INADEQUATE_CERT_TYPE, 0);
-      return RecoverableError;
+    if (!foundOCSPSigning && requiredEKU == KeyPurposeId::id_kp_OCSPSigning) {
+      return Fail(RecoverableError, SEC_ERROR_INADEQUATE_CERT_TYPE);
     }
   }
 
   return Success;
 }
 
 Result
 CheckIssuerIndependentProperties(TrustDomain& trustDomain,
                                  BackCert& cert,
                                  PRTime time,
                                  EndEntityOrCA endEntityOrCA,
                                  KeyUsages requiredKeyUsagesIfPresent,
-                                 SECOidTag requiredEKUIfPresent,
+                                 KeyPurposeId requiredEKUIfPresent,
                                  SECOidTag requiredPolicy,
                                  unsigned int subCACount,
                 /*optional out*/ TrustLevel* trustLevelOut)
 {
   Result rv;
 
   TrustLevel trustLevel;
   rv = MapSECStatus(trustDomain.GetCertTrust(endEntityOrCA,
--- a/security/pkix/lib/pkixcheck.h
+++ b/security/pkix/lib/pkixcheck.h
@@ -20,28 +20,29 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #ifndef mozilla_pkix__pkixcheck_h
 #define mozilla_pkix__pkixcheck_h
 
+#include "pkix/pkixtypes.h"
 #include "pkixutil.h"
 #include "certt.h"
 
 namespace mozilla { namespace pkix {
 
 Result CheckIssuerIndependentProperties(
           TrustDomain& trustDomain,
           BackCert& cert,
           PRTime time,
           EndEntityOrCA endEntityOrCA,
           KeyUsages requiredKeyUsagesIfPresent,
-          SECOidTag requiredEKUIfPresent,
+          KeyPurposeId requiredEKUIfPresent,
           SECOidTag requiredPolicy,
           unsigned int subCACount,
           /*optional out*/ TrustLevel* trustLevel = nullptr);
 
 Result CheckNameConstraints(BackCert& cert);
 
 } } // namespace mozilla::pkix
 
--- a/security/pkix/lib/pkixder.h
+++ b/security/pkix/lib/pkixder.h
@@ -133,16 +133,29 @@ public:
       return Fail(SEC_ERROR_BAD_DER);
     }
     out = *input++;
     out <<= 8u;
     out |= *input++;
     return Success;
   }
 
+  template <uint16_t N>
+  bool MatchBytes(const uint8_t (&toMatch)[N])
+  {
+    if (EnsureLength(N) != Success) {
+      return false;
+    }
+    if (memcmp(input, toMatch, N)) {
+      return false;
+    }
+    input += N;
+    return true;
+  }
+
   Result Skip(uint16_t len)
   {
     if (EnsureLength(len) != Success) {
       return Fail(SEC_ERROR_BAD_DER);
     }
     input += len;
     return Success;
   }
--- a/security/pkix/lib/pkixocsp.cpp
+++ b/security/pkix/lib/pkixocsp.cpp
@@ -141,17 +141,17 @@ 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,
                                         EndEntityOrCA::MustBeEndEntity, 0,
-                                        SEC_OID_OCSP_RESPONDER,
+                                        KeyPurposeId::id_kp_OCSPSigning,
                                         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
--- a/security/pkix/test/gtest/pkixder_input_tests.cpp
+++ b/security/pkix/test/gtest/pkixder_input_tests.cpp
@@ -599,9 +599,62 @@ TEST_F(pkixder_input_tests, NestedOfWith
   std::vector<uint8_t> readValues;
   ASSERT_EQ(Failure,
     NestedOf(input, SEQUENCE, INTEGER, EmptyAllowed::No,
              mozilla::pkix::bind(NestedOfHelper, mozilla::pkix::_1,
                                  mozilla::pkix::ref(readValues))));
   ASSERT_EQ(SEC_ERROR_BAD_DER, PR_GetError());
   ASSERT_EQ((size_t) 0, readValues.size());
 }
+
+TEST_F(pkixder_input_tests, MatchBytesAtEnd)
+{
+  Input input;
+  static const uint8_t der[1] = { };
+  ASSERT_EQ(Success, input.Init(der, 0));
+  ASSERT_TRUE(input.AtEnd());
+  static const uint8_t toMatch[] = { 1 };
+  ASSERT_FALSE(input.MatchBytes(toMatch));
+}
+
+TEST_F(pkixder_input_tests, MatchBytes1Match)
+{
+  Input input;
+  static const uint8_t der[] = { 1 };
+  ASSERT_EQ(Success, input.Init(der, sizeof der));
+  ASSERT_FALSE(input.AtEnd());
+  ASSERT_TRUE(input.MatchBytes(der));
+  ASSERT_TRUE(input.AtEnd());
+}
+
+TEST_F(pkixder_input_tests, MatchBytes1Mismatch)
+{
+  Input input;
+  static const uint8_t der[] = { 1 };
+  ASSERT_EQ(Success, input.Init(der, sizeof der));
+  static const uint8_t toMatch[] = { 2 };
+  ASSERT_FALSE(input.MatchBytes(toMatch));
+  ASSERT_FALSE(input.AtEnd());
+}
+
+TEST_F(pkixder_input_tests, MatchBytes2Match)
+{
+  Input input;
+  static const uint8_t der[] = { 1, 2, 3 };
+  ASSERT_EQ(Success, input.Init(der, sizeof der));
+  static const uint8_t toMatch[] = { 1, 2 };
+  ASSERT_TRUE(input.MatchBytes(toMatch));
+  uint8_t followingByte;
+  ASSERT_EQ(Success, input.Read(followingByte));
+  ASSERT_EQ(3, followingByte);
+}
+
+TEST_F(pkixder_input_tests, MatchBytes2Mismatch)
+{
+  Input input;
+  static const uint8_t der[] = { 1, 2, 3 };
+  ASSERT_EQ(Success, input.Init(der, sizeof der));
+  static const uint8_t toMatchMismatch[] = { 1, 3 };
+  ASSERT_FALSE(input.MatchBytes(toMatchMismatch));
+  ASSERT_TRUE(input.MatchBytes(der));
+}
+
 } // unnamed namespace