bug 1023621 - add asynchronous certificate verification API r?Cykesiopka draft
authorDavid Keeler <dkeeler@mozilla.com>
Thu, 30 Jun 2016 14:09:18 -0700
changeset 384582 2a93413afe02741cbdf6a162e73277c87fde271c
parent 384476 95ffbc4ff63584631c408e8d9912961fcf68bb09
child 524738 0dbbfaff58396241709fbbd546e82ca136daf70b
push id22309
push userdkeeler@mozilla.com
push dateWed, 06 Jul 2016 17:14:21 +0000
reviewersCykesiopka
bugs1023621
milestone50.0a1
bug 1023621 - add asynchronous certificate verification API r?Cykesiopka This API (nsIX509CertDB.asyncVerifyCertAtTime) will eventually replace nsIX509Cert.getUsagesArray, nsIX509Cert.requestUsagesArrayAsync, and nsIX509Cert.getUsagesString because those APIs are architecturally problematic and don't give very precise information in any case. MozReview-Commit-ID: OzQaBnDRIo
security/manager/ssl/nsIX509CertDB.idl
security/manager/ssl/nsNSSCertificateDB.cpp
security/manager/ssl/tests/unit/head_psm.js
security/manager/ssl/tests/unit/test_cert_keyUsage.js
security/manager/ssl/tests/unit/test_intermediate_basic_usage_constraints.js
--- a/security/manager/ssl/nsIX509CertDB.idl
+++ b/security/manager/ssl/nsIX509CertDB.idl
@@ -38,16 +38,32 @@ interface nsIVerifySignedDirectoryCallba
 [scriptable, function, uuid(3d6a9c87-5c5f-46fc-9410-96da6092f0f2)]
 interface nsIVerifySignedManifestCallback : nsISupports
 {
   void verifySignedManifestFinished(in nsresult rv,
                                     in nsIX509Cert aSignerCert);
 };
 
 /**
+ * Callback type for use with asyncVerifyCertAtTime.
+ * If aPRErrorCode is PRErrorCodeSuccess (i.e. 0), aVerifiedChain represents the
+ * verified certificate chain determined by asyncVerifyCertAtTime. aHasEVPolicy
+ * represents whether or not the end-entity certificate verified as EV.
+ * If aPRErrorCode is non-zero, it represents the error encountered during
+ * verification. aVerifiedChain is null in that case and aHasEVPolicy has no
+ * meaning.
+ */
+[scriptable, function, uuid(49e16fc8-efac-4f57-8361-956ef6b960a4)]
+interface nsICertVerificationCallback : nsISupports {
+  void verifyCertFinished(in int32_t aPRErrorCode,
+                          in nsIX509CertList aVerifiedChain,
+                          in bool aHasEVPolicy);
+};
+
+/**
  * This represents a service to access and manipulate
  * X.509 certificates stored in a database.
  */
 [scriptable, uuid(5c16cd9b-5a73-47f1-ab0f-11ede7495cce)]
 interface nsIX509CertDB : nsISupports {
 
   /**
    *  Constants that define which usages a certificate
@@ -366,16 +382,27 @@ interface nsIX509CertDB : nsISupports {
   int32_t /*PRErrorCode*/
     verifyCertNow(in nsIX509Cert aCert,
                   in int64_t /*SECCertificateUsage*/ aUsage,
                   in uint32_t aFlags,
                   in string aHostname,
                   out nsIX509CertList aVerifiedChain,
                   out bool aHasEVPolicy);
 
+  /**
+   * Similar to the above, but asynchronous. As a result, use of this API is not
+   * limited to tests.
+   */
+  void asyncVerifyCertAtTime(in nsIX509Cert aCert,
+                             in int64_t /*SECCertificateUsage*/ aUsage,
+                             in uint32_t aFlags,
+                             in string aHostname,
+                             in uint64_t aTime,
+                             in nsICertVerificationCallback aCallback);
+
   // Clears the OCSP cache for the current certificate verification
   // implementation.
   void clearOCSPCache();
 
   /*
    * Add a cert to a cert DB from a base64 encoded string.
    *
    * @param base64 The raw representation of a certificate,
--- a/security/manager/ssl/nsNSSCertificateDB.cpp
+++ b/security/manager/ssl/nsNSSCertificateDB.cpp
@@ -1,15 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsNSSCertificateDB.h"
 
 #include "CertVerifier.h"
+#include "CryptoTask.h"
 #include "ExtendedValidation.h"
 #include "NSSCertDBTrustDomain.h"
 #include "SharedSSLState.h"
 #include "mozilla/Base64.h"
 #include "mozilla/Casting.h"
 #include "mozilla/unused.h"
 #include "nsArray.h"
 #include "nsArrayUtils.h"
@@ -26,16 +27,17 @@
 #include "nsNSSCertHelper.h"
 #include "nsNSSCertTrust.h"
 #include "nsNSSCertificate.h"
 #include "nsNSSComponent.h"
 #include "nsNSSHelper.h"
 #include "nsNSSShutDown.h"
 #include "nsPK11TokenDB.h"
 #include "nsPKCS12Blob.h"
+#include "nsProxyRelease.h"
 #include "nsReadableUtils.h"
 #include "nsThreadUtils.h"
 #include "pkix/Time.h"
 #include "pkix/pkixtypes.h"
 
 #include "nspr.h"
 #include "certdb.h"
 #include "secerr.h"
@@ -1612,16 +1614,95 @@ nsNSSCertificateDB::VerifyCertAtTime(nsI
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   return ::VerifyCertAtTime(aCert, aUsage, aFlags, aHostname,
                             mozilla::pkix::TimeFromEpochInSeconds(aTime),
                             aVerifiedChain, aHasEVPolicy, _retval, locker);
 }
 
+class VerifyCertAtTimeTask final : public CryptoTask
+{
+public:
+  VerifyCertAtTimeTask(nsIX509Cert* aCert, int64_t aUsage, uint32_t aFlags,
+                       const char* aHostname, uint64_t aTime,
+                       nsICertVerificationCallback* aCallback)
+    : mCert(aCert)
+    , mUsage(aUsage)
+    , mFlags(aFlags)
+    , mHostname(aHostname)
+    , mTime(aTime)
+    , mCallback(new nsMainThreadPtrHolder<nsICertVerificationCallback>(aCallback))
+    , mPRErrorCode(SEC_ERROR_LIBRARY_FAILURE)
+    , mVerifiedCertList(nullptr)
+    , mHasEVPolicy(false)
+  {
+  }
+
+private:
+  virtual nsresult CalculateResult() override
+  {
+    nsCOMPtr<nsIX509CertDB> certDB = do_GetService(NS_X509CERTDB_CONTRACTID);
+    if (!certDB) {
+      return NS_ERROR_FAILURE;
+    }
+    // Unfortunately mHostname will have made the empty string out of a null
+    // pointer passed in the constructor. If we pass the empty string on to
+    // VerifyCertAtTime with the usage certificateUsageSSLServer, it will call
+    // VerifySSLServerCert, which expects a non-empty hostname. To avoid this,
+    // check the length and use nullptr if appropriate.
+    const char* hostname = mHostname.Length() > 0 ? mHostname.get() : nullptr;
+    return certDB->VerifyCertAtTime(mCert, mUsage, mFlags, hostname, mTime,
+                                    getter_AddRefs(mVerifiedCertList),
+                                    &mHasEVPolicy, &mPRErrorCode);
+  }
+
+  // No NSS resources are directly held, so there is nothing to release.
+  virtual void ReleaseNSSResources() override { }
+
+  virtual void CallCallback(nsresult rv) override
+  {
+    if (NS_FAILED(rv)) {
+      Unused << mCallback->VerifyCertFinished(SEC_ERROR_LIBRARY_FAILURE,
+                                              nullptr, false);
+    } else {
+      Unused << mCallback->VerifyCertFinished(mPRErrorCode, mVerifiedCertList,
+                                              mHasEVPolicy);
+    }
+  }
+
+  nsCOMPtr<nsIX509Cert> mCert;
+  int64_t mUsage;
+  uint32_t mFlags;
+  nsCString mHostname;
+  uint64_t mTime;
+  nsMainThreadPtrHandle<nsICertVerificationCallback> mCallback;
+  int32_t mPRErrorCode;
+  nsCOMPtr<nsIX509CertList> mVerifiedCertList;
+  bool mHasEVPolicy;
+};
+
+NS_IMETHODIMP
+nsNSSCertificateDB::AsyncVerifyCertAtTime(nsIX509Cert* aCert,
+                                          int64_t /*SECCertificateUsage*/ aUsage,
+                                          uint32_t aFlags,
+                                          const char* aHostname,
+                                          uint64_t aTime,
+                                          nsICertVerificationCallback* aCallback)
+{
+  nsNSSShutDownPreventionLock locker;
+  if (isAlreadyShutDown()) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+  RefPtr<VerifyCertAtTimeTask> task(new VerifyCertAtTimeTask(aCert, aUsage,
+                                                             aFlags, aHostname,
+                                                             aTime, aCallback));
+  return task->Dispatch("VerifyCert");
+}
+
 NS_IMETHODIMP
 nsNSSCertificateDB::ClearOCSPCache()
 {
   nsNSSShutDownPreventionLock locker;
   if (isAlreadyShutDown()) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
--- a/security/manager/ssl/tests/unit/head_psm.js
+++ b/security/manager/ssl/tests/unit/head_psm.js
@@ -87,16 +87,30 @@ const certificateUsageSSLClient         
 const certificateUsageSSLServer              = 0x0002;
 const certificateUsageSSLCA                  = 0x0008;
 const certificateUsageEmailSigner            = 0x0010;
 const certificateUsageEmailRecipient         = 0x0020;
 const certificateUsageObjectSigner           = 0x0040;
 const certificateUsageVerifyCA               = 0x0100;
 const certificateUsageStatusResponder        = 0x0400;
 
+// A map from the name of a certificate usage to the value of the usage.
+// Useful for printing debugging information and for enumerating all supported
+// usages.
+const allCertificateUsages = {
+  certificateUsageSSLClient,
+  certificateUsageSSLServer,
+  certificateUsageSSLCA,
+  certificateUsageEmailSigner,
+  certificateUsageEmailRecipient,
+  certificateUsageObjectSigner,
+  certificateUsageVerifyCA,
+  certificateUsageStatusResponder
+};
+
 const NO_FLAGS = 0;
 
 // Commonly certificates are represented as PEM. The format is roughly as
 // follows:
 //
 // -----BEGIN CERTIFICATE-----
 // [some lines of base64, each typically 64 characters long]
 // -----END CERTIFICATE-----
@@ -719,8 +733,63 @@ function add_prevented_cert_override_tes
 
 function loginToDBWithDefaultPassword() {
   let tokenDB = Cc["@mozilla.org/security/pk11tokendb;1"]
                   .getService(Ci.nsIPK11TokenDB);
   let token = tokenDB.getInternalKeyToken();
   token.initPassword("");
   token.login(/*force*/ false);
 }
+
+// Helper for asyncTestCertificateUsages.
+class CertVerificationResult {
+  constructor(certName, usageString, successExpected, resolve) {
+    this.certName = certName;
+    this.usageString = usageString;
+    this.successExpected = successExpected;
+    this.resolve = resolve;
+  }
+
+  verifyCertFinished(aPRErrorCode, aVerifiedChain, aHasEVPolicy) {
+    if (this.successExpected) {
+      equal(aPRErrorCode, PRErrorCodeSuccess,
+            `verifying ${this.certName} for ${this.usageString} should succeed`);
+    } else {
+      notEqual(aPRErrorCode, PRErrorCodeSuccess,
+               `verifying ${this.certName} for ${this.usageString} should fail`);
+    }
+    this.resolve();
+  }
+}
+
+/**
+ * Asynchronously attempts to verify the given certificate for all supported
+ * usages (see allCertificateUsages). Verifies that the results match the
+ * expected successful usages. Returns a promise that will resolve when all
+ * verifications have been performed.
+ * Verification happens "now" with no specified flags or hostname.
+ *
+ * @param {nsIX509CertDB} certdb
+ *   The certificate database to use to verify the certificate.
+ * @param {nsIX509Cert} cert
+ *   The certificate to be verified.
+ * @param {Number[]} expectedUsages
+ *   A list of usages (as their integer values) that are expected to verify
+ *   successfully.
+ * @return {Promise}
+ *   A promise that will resolve with no value when all asynchronous operations
+ *   have completed.
+ */
+function asyncTestCertificateUsages(certdb, cert, expectedUsages) {
+  let now = (new Date()).getTime() / 1000;
+  let promises = [];
+  Object.keys(allCertificateUsages).forEach(usageString => {
+    let promise = new Promise((resolve, reject) => {
+      let usage = allCertificateUsages[usageString];
+      let successExpected = expectedUsages.includes(usage);
+      let result = new CertVerificationResult(cert.commonName, usageString,
+                                              successExpected, resolve);
+      certdb.asyncVerifyCertAtTime(cert, usage, 0, null, now, result);
+    });
+    promises.push(promise);
+  });
+  return Promise.all(promises);
+}
--- a/security/manager/ssl/tests/unit/test_cert_keyUsage.js
+++ b/security/manager/ssl/tests/unit/test_cert_keyUsage.js
@@ -4,58 +4,54 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 do_get_profile(); // must be called before getting nsIX509CertDB
 var certdb = Cc["@mozilla.org/security/x509certdb;1"]
                .getService(Ci.nsIX509CertDB);
 
-var caList = ["ca-no-keyUsage-extension", "ca-missing-keyCertSign",
-              "ca-all-usages"];
-var eeList = ["ee-no-keyUsage-extension", "ee-keyCertSign-only",
-              "ee-keyEncipherment-only", "ee-keyCertSign-and-keyEncipherment"];
+const caList = [ "ca-no-keyUsage-extension", "ca-missing-keyCertSign",
+                 "ca-all-usages" ];
+const eeList = [ "ee-no-keyUsage-extension", "ee-keyCertSign-only",
+                 "ee-keyEncipherment-only", "ee-keyCertSign-and-keyEncipherment" ];
 
-var caUsage = "SSL CA";
-var allEEUsages = "Client,Server,Sign,Encrypt,Object Signer";
-var serverEEUsages = "Server,Encrypt";
+const caUsage = [ certificateUsageSSLCA, certificateUsageVerifyCA ];
+const allEEUsages = [ certificateUsageSSLClient, certificateUsageSSLServer,
+                      certificateUsageEmailSigner, certificateUsageEmailRecipient,
+                      certificateUsageObjectSigner ];
+const serverEEUsages = [ certificateUsageSSLServer,
+                         certificateUsageEmailRecipient ];
 
-var expectedUsagesMap = {
+const expectedUsagesMap = {
   "ca-no-keyUsage-extension": caUsage,
-  "ca-missing-keyCertSign": "",
+  "ca-missing-keyCertSign": [],
   "ca-all-usages": caUsage,
 
   "ee-no-keyUsage-extension-ca-no-keyUsage-extension": allEEUsages,
-  "ee-no-keyUsage-extension-ca-missing-keyCertSign": "",
+  "ee-no-keyUsage-extension-ca-missing-keyCertSign": [],
   "ee-no-keyUsage-extension-ca-all-usages": allEEUsages,
 
-  "ee-keyCertSign-only-ca-no-keyUsage-extension": "",
-  "ee-keyCertSign-only-ca-missing-keyCertSign": "",
-  "ee-keyCertSign-only-ca-all-usages": "",
+  "ee-keyCertSign-only-ca-no-keyUsage-extension": [],
+  "ee-keyCertSign-only-ca-missing-keyCertSign": [],
+  "ee-keyCertSign-only-ca-all-usages": [],
 
   "ee-keyEncipherment-only-ca-no-keyUsage-extension": serverEEUsages,
-  "ee-keyEncipherment-only-ca-missing-keyCertSign": "",
+  "ee-keyEncipherment-only-ca-missing-keyCertSign": [],
   "ee-keyEncipherment-only-ca-all-usages": serverEEUsages,
 
   "ee-keyCertSign-and-keyEncipherment-ca-no-keyUsage-extension": serverEEUsages,
-  "ee-keyCertSign-and-keyEncipherment-ca-missing-keyCertSign": "",
+  "ee-keyCertSign-and-keyEncipherment-ca-missing-keyCertSign": [],
   "ee-keyCertSign-and-keyEncipherment-ca-all-usages": serverEEUsages,
 };
 
-function run_test() {
-  caList.forEach(function(ca) {
-    addCertFromFile(certdb, "test_cert_keyUsage/" + ca + ".pem",
-                    "CTu,CTu,CTu");
-    let caCert = certdb.findCertByNickname(ca);
-    let usages = {};
-    caCert.getUsagesString(true, {}, usages); // true indicates local-only
-    equal(usages.value, expectedUsagesMap[ca],
-          "Actual and expected CA usages should match");
-    eeList.forEach(function(ee) {
+add_task(function* () {
+  for (let ca of caList) {
+    addCertFromFile(certdb, "test_cert_keyUsage/" + ca + ".pem", "CTu,CTu,CTu");
+    let cert = constructCertFromFile("test_cert_keyUsage/" + ca + ".pem");
+    yield asyncTestCertificateUsages(certdb, cert, expectedUsagesMap[ca]);
+    for (let ee of eeList) {
       let eeFullName = ee + "-" + ca;
-      let cert = constructCertFromFile(
-        "test_cert_keyUsage/" + eeFullName + ".pem");
-      cert.getUsagesString(true, {}, usages); // true indicates local-only
-      equal(usages.value, expectedUsagesMap[eeFullName],
-            "Actual and expected EE usages should match");
-    });
-  });
-}
+      let cert = constructCertFromFile("test_cert_keyUsage/" + eeFullName + ".pem");
+      yield asyncTestCertificateUsages(certdb, cert, expectedUsagesMap[eeFullName]);
+    }
+  }
+});
--- a/security/manager/ssl/tests/unit/test_intermediate_basic_usage_constraints.js
+++ b/security/manager/ssl/tests/unit/test_intermediate_basic_usage_constraints.js
@@ -4,109 +4,108 @@ do_get_profile(); // must be called befo
 const certdb = Cc["@mozilla.org/security/x509certdb;1"]
                  .getService(Ci.nsIX509CertDB);
 
 function load_cert(name, trust) {
   let filename = "test_intermediate_basic_usage_constraints/" + name + ".pem";
   addCertFromFile(certdb, filename, trust);
 }
 
-function test_cert_for_usages(certChainNicks, expected_usages_string) {
+function test_cert_for_usages(certChainNicks, expected_usages) {
   let certs = [];
   for (let i in certChainNicks) {
     let certNick = certChainNicks[i];
     let certPEM = readFile(
                     do_get_file("test_intermediate_basic_usage_constraints/"
                                 + certNick + ".pem"), false);
     certs.push(certdb.constructX509FromBase64(pemToBase64(certPEM)));
   }
 
   let cert = certs[0];
-  let verified = {};
-  let usages = {};
-  cert.getUsagesString(true, verified, usages);
-  equal(expected_usages_string, usages.value,
-        "Expected and actual usages string should match");
+  return asyncTestCertificateUsages(certdb, cert, expected_usages);
 }
 
-function run_test() {
-  let ee_usage1 = 'Client,Server,Sign,Encrypt,Object Signer';
-  let ca_usage1 = "SSL CA";
+add_task(function* () {
+  let ee_usages = [ certificateUsageSSLClient, certificateUsageSSLServer,
+                    certificateUsageEmailSigner, certificateUsageEmailRecipient,
+                    certificateUsageObjectSigner ];
+  let ca_usages = [ certificateUsageSSLCA, certificateUsageVerifyCA ];
+  let eku_usages = [ certificateUsageSSLClient, certificateUsageSSLServer ];
 
   // Load the ca into mem
   let ca_name = "ca";
   load_cert(ca_name, "CTu,CTu,CTu");
-  do_print("ca_name = " + ca_name);
-  test_cert_for_usages([ca_name], ca_usage1);
+  yield test_cert_for_usages([ca_name], ca_usages);
 
   // A certificate with no basicConstraints extension is considered an EE.
-  test_cert_for_usages(["int-no-extensions"], ee_usage1);
+  yield test_cert_for_usages(["int-no-extensions"], ee_usages);
 
   // int-no-extensions is an EE (see previous case), so no certs can chain to
   // it.
-  test_cert_for_usages(["ee-int-no-extensions", "int-no-extensions"], "");
+  yield test_cert_for_usages(["ee-int-no-extensions", "int-no-extensions"], []);
 
   // a certificate with basicConstraints.cA==false is considered an EE.
-  test_cert_for_usages(["int-not-a-ca"], ee_usage1);
+  yield test_cert_for_usages(["int-not-a-ca"], ee_usages);
 
   // int-not-a-ca is an EE (see previous case), so no certs can chain to it.
-  test_cert_for_usages(["ee-int-not-a-ca", "int-not-a-ca"], "");
+  yield test_cert_for_usages(["ee-int-not-a-ca", "int-not-a-ca"], []);
 
   // a certificate with basicConstraints.cA==false but with the keyCertSign
   // key usage may not act as a CA (it can act like an end-entity).
-  test_cert_for_usages(["int-cA-FALSE-asserts-keyCertSign"], ee_usage1);
-  test_cert_for_usages(["ee-int-cA-FALSE-asserts-keyCertSign",
-                        "int-cA-FALSE-asserts-keyCertSign"], "");
+  yield test_cert_for_usages(["int-cA-FALSE-asserts-keyCertSign"], ee_usages);
+  yield test_cert_for_usages(["ee-int-cA-FALSE-asserts-keyCertSign",
+                              "int-cA-FALSE-asserts-keyCertSign"], []);
 
 
   // int-limited-depth has cA==true and a path length constraint of zero.
-  test_cert_for_usages(["int-limited-depth"], ca_usage1);
+  yield test_cert_for_usages(["int-limited-depth"], ca_usages);
 
   // path length constraints do not affect the ability of a non-CA cert to
   // chain to to the CA cert.
-  test_cert_for_usages(["ee-int-limited-depth", "int-limited-depth"],
-                       ee_usage1);
+  yield test_cert_for_usages(["ee-int-limited-depth", "int-limited-depth"],
+                             ee_usages);
 
   // ca
   //   int-limited-depth (cA==true, pathLenConstraint==0)
   //      int-limited-depth-invalid (cA==true)
   //
-  test_cert_for_usages(["int-limited-depth-invalid", "int-limited-depth"], "");
-  test_cert_for_usages(["ee-int-limited-depth-invalid",
-                        "int-limited-depth-invalid",
-                        "int-limited-depth"],
-                       "");
+  yield test_cert_for_usages(["int-limited-depth-invalid", "int-limited-depth"],
+                             []);
+  yield test_cert_for_usages(["ee-int-limited-depth-invalid",
+                              "int-limited-depth-invalid", "int-limited-depth"],
+                             []);
 
   // int-valid-ku-no-eku has keyCertSign
-  test_cert_for_usages(["int-valid-ku-no-eku"], "SSL CA");
-  test_cert_for_usages(["ee-int-valid-ku-no-eku", "int-valid-ku-no-eku"],
-                       ee_usage1);
+  yield test_cert_for_usages(["int-valid-ku-no-eku"], ca_usages);
+  yield test_cert_for_usages(["ee-int-valid-ku-no-eku", "int-valid-ku-no-eku"],
+                             ee_usages);
 
   // int-bad-ku-no-eku has basicConstraints.cA==true and has a KU extension
   // but the KU extension is missing keyCertSign. Note that mozilla::pkix
   // doesn't validate certificates with basicConstraints.Ca==true for non-CA
   // uses.
-  test_cert_for_usages(["int-bad-ku-no-eku"], "");
-  test_cert_for_usages(["ee-int-bad-ku-no-eku", "int-bad-ku-no-eku"], "");
+  yield test_cert_for_usages(["int-bad-ku-no-eku"], []);
+  yield test_cert_for_usages(["ee-int-bad-ku-no-eku", "int-bad-ku-no-eku"], []);
 
   // int-no-ku-no-eku has basicConstraints.cA==true and no KU extension.
   // We treat a missing KU as "any key usage is OK".
-  test_cert_for_usages(["int-no-ku-no-eku"], ca_usage1);
-  test_cert_for_usages(["ee-int-no-ku-no-eku", "int-no-ku-no-eku"], ee_usage1);
+  yield test_cert_for_usages(["int-no-ku-no-eku"], ca_usages);
+  yield test_cert_for_usages(["ee-int-no-ku-no-eku", "int-no-ku-no-eku"],
+                             ee_usages);
 
   // int-valid-ku-server-eku has basicConstraints.cA==true, keyCertSign in KU,
   // and EKU=={id-kp-serverAuth,id-kp-clientAuth}.
-  test_cert_for_usages(["int-valid-ku-server-eku"], "SSL CA");
-  test_cert_for_usages(["ee-int-valid-ku-server-eku",
-                        "int-valid-ku-server-eku"], "Client,Server");
+  yield test_cert_for_usages(["int-valid-ku-server-eku"], ca_usages);
+  yield test_cert_for_usages(["ee-int-valid-ku-server-eku",
+                              "int-valid-ku-server-eku"], eku_usages);
 
   // int-bad-ku-server-eku has basicConstraints.cA==true, a KU without
   // keyCertSign, and EKU=={id-kp-serverAuth,id-kp-clientAuth}.
-  test_cert_for_usages(["int-bad-ku-server-eku"], "");
-  test_cert_for_usages(["ee-int-bad-ku-server-eku", "int-bad-ku-server-eku"],
-                       "");
+  yield test_cert_for_usages(["int-bad-ku-server-eku"], []);
+  yield test_cert_for_usages(["ee-int-bad-ku-server-eku",
+                              "int-bad-ku-server-eku"], []);
 
   // int-bad-ku-server-eku has basicConstraints.cA==true, no KU, and
   // EKU=={id-kp-serverAuth,id-kp-clientAuth}.
-  test_cert_for_usages(["int-no-ku-server-eku"], "SSL CA");
-  test_cert_for_usages(["ee-int-no-ku-server-eku", "int-no-ku-server-eku"],
-                       "Client,Server");
-}
+  yield test_cert_for_usages(["int-no-ku-server-eku"], ca_usages);
+  yield test_cert_for_usages(["ee-int-no-ku-server-eku",
+                              "int-no-ku-server-eku"], eku_usages);
+});