--- 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);
+});