--- a/security/manager/ssl/nsCertTree.cpp
+++ b/security/manager/ssl/nsCertTree.cpp
@@ -11,16 +11,17 @@
#include "nsHashKeys.h"
#include "nsISupportsPrimitives.h"
#include "nsITreeColumns.h"
#include "nsIX509CertDB.h"
#include "nsIX509Cert.h"
#include "nsIX509CertValidity.h"
#include "nsNSSCertHelper.h"
#include "nsNSSCertificate.h"
+#include "nsNSSCertificateDB.h"
#include "nsNSSComponent.h" // for PIPNSS string bundle calls.
#include "nsNSSHelper.h"
#include "nsReadableUtils.h"
#include "nsTHashtable.h"
#include "nsUnicharUtils.h"
#include "nsXPCOMCID.h"
#include "nsXPIDLString.h"
#include "pkix/pkixtypes.h"
@@ -795,18 +796,18 @@ nsCertTree::DeleteEntryObject(uint32_t i
UniqueCERTCertificate nsscert(cert->GetCert());
if (nsscert) {
CERTCertTrust trust;
memset((void*)&trust, 0, sizeof(trust));
SECStatus srv = CERT_DecodeTrustString(&trust, ""); // no override
if (srv == SECSuccess) {
- CERT_ChangeCertTrust(CERT_GetDefaultCertDB(), nsscert.get(),
- &trust);
+ ChangeCertTrustWithPossibleAuthentication(nsscert, trust,
+ nullptr);
}
}
}
else {
canRemoveEntry = true;
}
}
}
--- a/security/manager/ssl/nsNSSCertTrust.h
+++ b/security/manager/ssl/nsNSSCertTrust.h
@@ -52,18 +52,17 @@ public:
bool ca, bool tCA, bool tClientCA,
bool user, bool warn);
/* set c <--> CT */
void AddCATrust(bool ssl, bool email, bool objSign);
/* set p <--> P */
void AddPeerTrust(bool ssl, bool email, bool objSign);
- /* get it (const?) (shallow?) */
- CERTCertTrust * GetTrust() { return &mTrust; }
+ CERTCertTrust& GetTrust() { return mTrust; }
private:
void addTrust(unsigned int *t, unsigned int v);
void removeTrust(unsigned int *t, unsigned int v);
bool hasTrust(unsigned int t, unsigned int v);
CERTCertTrust mTrust;
};
--- a/security/manager/ssl/nsNSSCertificateDB.cpp
+++ b/security/manager/ssl/nsNSSCertificateDB.cpp
@@ -224,16 +224,48 @@ nsNSSCertificateDB::getCertsFromPackage(
if (CERT_DecodeCertPackage(BitwiseCast<char*, uint8_t*>(data), length,
collect_certs, collectArgs) != SECSuccess) {
return nullptr;
}
return collectArgs;
}
+// When using the sql-backed softoken, trust settings are authenticated using a
+// key in the secret database. Thus, if the user has a password, we need to
+// authenticate to the token in order to be able to change trust settings.
+SECStatus
+ChangeCertTrustWithPossibleAuthentication(const UniqueCERTCertificate& cert,
+ CERTCertTrust& trust, void* ctx)
+{
+ MOZ_ASSERT(cert, "cert must be non-null");
+ if (!cert) {
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+ // NSS ignores the first argument to CERT_ChangeCertTrust
+ SECStatus srv = CERT_ChangeCertTrust(nullptr, cert.get(), &trust);
+ if (srv == SECSuccess || PR_GetError() != SEC_ERROR_TOKEN_NOT_LOGGED_IN) {
+ return srv;
+ }
+ if (cert->slot) {
+ // If this certificate is on an external PKCS#11 token, we have to
+ // authenticate to that token.
+ srv = PK11_Authenticate(cert->slot, PR_TRUE, ctx);
+ } else {
+ // Otherwise, the certificate is on the internal module.
+ UniquePK11SlotInfo internalSlot(PK11_GetInternalKeySlot());
+ srv = PK11_Authenticate(internalSlot.get(), PR_TRUE, ctx);
+ }
+ if (srv != SECSuccess) {
+ return srv;
+ }
+ return CERT_ChangeCertTrust(nullptr, cert.get(), &trust);
+}
+
nsresult
nsNSSCertificateDB::handleCACertDownload(NotNull<nsIArray*> x509Certs,
nsIInterfaceRequestor *ctx,
const nsNSSShutDownPreventionLock &proofOfLock)
{
// First thing we have to do is figure out which certificate we're
// gonna present to the user. The CA may have sent down a list of
// certs which may or may not be a chained list of certs. Until
@@ -348,18 +380,18 @@ nsNSSCertificateDB::handleCACertDownload
UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
SECStatus srv = PK11_ImportCert(slot.get(), tmpCert.get(), CK_INVALID_HANDLE,
nickname.get(),
false); // this parameter is ignored by NSS
if (srv != SECSuccess) {
return MapSECStatus(srv);
}
- // NSS ignores the first argument to CERT_ChangeCertTrust
- srv = CERT_ChangeCertTrust(nullptr, tmpCert.get(), trust.GetTrust());
+ srv = ChangeCertTrustWithPossibleAuthentication(tmpCert, trust.GetTrust(),
+ ctx);
if (srv != SECSuccess) {
return MapSECStatus(srv);
}
// Import additional delivered certificates that can be verified.
// build a CertList for filtering
UniqueCERTCertList certList(CERT_NewCertList());
@@ -790,70 +822,65 @@ nsNSSCertificateDB::DeleteCertificate(ns
// To delete a cert of a slot (builtin, most likely), mark it as
// completely untrusted. This way we keep a copy cached in the
// local database, and next time we try to load it off of the
// external token/slot, we'll know not to trust it. We don't
// want to do that with user certs, because a user may re-store
// the cert onto the card again at which point we *will* want to
// trust that cert if it chains up properly.
nsNSSCertTrust trust(0, 0, 0);
- srv = CERT_ChangeCertTrust(CERT_GetDefaultCertDB(),
- cert.get(), trust.GetTrust());
+ srv = ChangeCertTrustWithPossibleAuthentication(cert, trust.GetTrust(),
+ nullptr);
}
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("cert deleted: %d", srv));
return (srv) ? NS_ERROR_FAILURE : NS_OK;
}
NS_IMETHODIMP
nsNSSCertificateDB::SetCertTrust(nsIX509Cert *cert,
uint32_t type,
uint32_t trusted)
{
NS_ENSURE_ARG_POINTER(cert);
nsNSSShutDownPreventionLock locker;
if (isAlreadyShutDown()) {
return NS_ERROR_NOT_AVAILABLE;
}
- nsNSSCertTrust trust;
- nsresult rv;
- UniqueCERTCertificate nsscert(cert->GetCert());
- rv = attemptToLogInWithDefaultPassword();
+ nsresult rv = attemptToLogInWithDefaultPassword();
if (NS_WARN_IF(rv != NS_OK)) {
return rv;
}
- SECStatus srv;
- if (type == nsIX509Cert::CA_CERT) {
- // always start with untrusted and move up
- trust.SetValidCA();
- trust.AddCATrust(!!(trusted & nsIX509CertDB::TRUSTED_SSL),
- !!(trusted & nsIX509CertDB::TRUSTED_EMAIL),
- !!(trusted & nsIX509CertDB::TRUSTED_OBJSIGN));
- srv = CERT_ChangeCertTrust(CERT_GetDefaultCertDB(),
- nsscert.get(),
- trust.GetTrust());
- } else if (type == nsIX509Cert::SERVER_CERT) {
- // always start with untrusted and move up
- trust.SetValidPeer();
- trust.AddPeerTrust(trusted & nsIX509CertDB::TRUSTED_SSL, 0, 0);
- srv = CERT_ChangeCertTrust(CERT_GetDefaultCertDB(),
- nsscert.get(),
- trust.GetTrust());
- } else if (type == nsIX509Cert::EMAIL_CERT) {
- // always start with untrusted and move up
- trust.SetValidPeer();
- trust.AddPeerTrust(0, !!(trusted & nsIX509CertDB::TRUSTED_EMAIL), 0);
- srv = CERT_ChangeCertTrust(CERT_GetDefaultCertDB(),
- nsscert.get(),
- trust.GetTrust());
- } else {
- // ignore user certs
- return NS_OK;
+ nsNSSCertTrust trust;
+ switch (type) {
+ case nsIX509Cert::CA_CERT:
+ trust.SetValidCA();
+ trust.AddCATrust(!!(trusted & nsIX509CertDB::TRUSTED_SSL),
+ !!(trusted & nsIX509CertDB::TRUSTED_EMAIL),
+ !!(trusted & nsIX509CertDB::TRUSTED_OBJSIGN));
+ break;
+ case nsIX509Cert::SERVER_CERT:
+ trust.SetValidPeer();
+ trust.AddPeerTrust(trusted & nsIX509CertDB::TRUSTED_SSL, false, false);
+ break;
+ case nsIX509Cert::EMAIL_CERT:
+ trust.SetValidPeer();
+ trust.AddPeerTrust(false, !!(trusted & nsIX509CertDB::TRUSTED_EMAIL),
+ false);
+ break;
+ default:
+ // Ignore any other type of certificate (including invalid types).
+ return NS_OK;
}
+
+ UniqueCERTCertificate nsscert(cert->GetCert());
+ SECStatus srv = ChangeCertTrustWithPossibleAuthentication(nsscert,
+ trust.GetTrust(),
+ nullptr);
return MapSECStatus(srv);
}
NS_IMETHODIMP
nsNSSCertificateDB::IsCertTrusted(nsIX509Cert *cert,
uint32_t certType,
uint32_t trustType,
bool *_isTrusted)
@@ -1231,17 +1258,17 @@ nsNSSCertificateDB::AddCertFromBase64(co
*addedCertificate = nullptr;
nsNSSShutDownPreventionLock locker;
if (isAlreadyShutDown()) {
return NS_ERROR_NOT_AVAILABLE;
}
nsNSSCertTrust trust;
- if (CERT_DecodeTrustString(trust.GetTrust(), PromiseFlatCString(aTrust).get())
+ if (CERT_DecodeTrustString(&trust.GetTrust(), PromiseFlatCString(aTrust).get())
!= SECSuccess) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIX509Cert> newCert;
nsresult rv = ConstructX509FromBase64(aBase64, getter_AddRefs(newCert));
if (NS_FAILED(rv)) {
return rv;
@@ -1274,18 +1301,18 @@ nsNSSCertificateDB::AddCertFromBase64(co
UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
SECStatus srv = PK11_ImportCert(slot.get(), tmpCert.get(), CK_INVALID_HANDLE,
nickname.get(),
false); // this parameter is ignored by NSS
if (srv != SECSuccess) {
return MapSECStatus(srv);
}
- // NSS ignores the first argument to CERT_ChangeCertTrust
- srv = CERT_ChangeCertTrust(nullptr, tmpCert.get(), trust.GetTrust());
+ srv = ChangeCertTrustWithPossibleAuthentication(tmpCert, trust.GetTrust(),
+ nullptr);
if (srv != SECSuccess) {
return MapSECStatus(srv);
}
newCert.forget(addedCertificate);
return NS_OK;
}
NS_IMETHODIMP
@@ -1313,17 +1340,17 @@ nsNSSCertificateDB::SetCertTrustFromStri
}
UniqueCERTCertificate nssCert(cert->GetCert());
nsresult rv = attemptToLogInWithDefaultPassword();
if (NS_WARN_IF(rv != NS_OK)) {
return rv;
}
- srv = CERT_ChangeCertTrust(CERT_GetDefaultCertDB(), nssCert.get(), &trust);
+ srv = ChangeCertTrustWithPossibleAuthentication(nssCert, trust, nullptr);
return MapSECStatus(srv);
}
NS_IMETHODIMP
nsNSSCertificateDB::GetCerts(nsIX509CertList **_retval)
{
nsNSSShutDownPreventionLock locker;
if (isAlreadyShutDown()) {
--- a/security/manager/ssl/nsNSSCertificateDB.h
+++ b/security/manager/ssl/nsNSSCertificateDB.h
@@ -69,9 +69,13 @@ private:
#define NS_X509CERTDB_CID { /* fb0bbc5c-452e-4783-b32c-80124693d871 */ \
0xfb0bbc5c, \
0x452e, \
0x4783, \
{0xb3, 0x2c, 0x80, 0x12, 0x46, 0x93, 0xd8, 0x71} \
}
+SECStatus
+ChangeCertTrustWithPossibleAuthentication(
+ const mozilla::UniqueCERTCertificate& cert, CERTCertTrust& trust, void* ctx);
+
#endif // nsNSSCertificateDB_h
--- a/security/manager/ssl/nsNSSComponent.cpp
+++ b/security/manager/ssl/nsNSSComponent.cpp
@@ -638,18 +638,18 @@ nsNSSComponent::MaybeImportFamilySafetyR
("subject name is '%s'", subjectName.get()));
if (kMicrosoftFamilySafetyCN.Equals(subjectName.get())) {
wasFamilySafetyRoot = true;
CERTCertTrust trust = {
CERTDB_TRUSTED_CA | CERTDB_VALID_CA | CERTDB_USER,
0,
0
};
- if (CERT_ChangeCertTrust(nullptr, nssCertificate.get(), &trust)
- != SECSuccess) {
+ if (ChangeCertTrustWithPossibleAuthentication(nssCertificate, trust,
+ nullptr) != SECSuccess) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("couldn't trust certificate for TLS server auth"));
return NS_ERROR_FAILURE;
}
MOZ_ASSERT(!mFamilySafetyRoot);
mFamilySafetyRoot = Move(nssCertificate);
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("added Family Safety root"));
}
@@ -726,18 +726,18 @@ nsNSSComponent::UnloadFamilySafetyRoot()
// It would be intuitive to set the trust to { 0, 0, 0 } here. However, this
// doesn't work for temporary certificates because CERT_ChangeCertTrust first
// looks up the current trust settings in the permanent cert database, finds
// that such trust doesn't exist, considers the current trust to be
// { 0, 0, 0 }, and decides that it doesn't need to update the trust since
// they're the same. To work around this, we set a non-zero flag to ensure
// that the trust will get updated.
CERTCertTrust trust = { CERTDB_USER, 0, 0 };
- if (CERT_ChangeCertTrust(nullptr, mFamilySafetyRoot.get(), &trust)
- != SECSuccess) {
+ if (ChangeCertTrustWithPossibleAuthentication(mFamilySafetyRoot, trust,
+ nullptr) != SECSuccess) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("couldn't untrust certificate for TLS server auth"));
}
mFamilySafetyRoot = nullptr;
}
#endif // XP_WIN
@@ -869,17 +869,19 @@ nsNSSComponent::UnloadEnterpriseRoots(co
CERTCertTrust trust = { CERTDB_USER, 0, 0 };
for (CERTCertListNode* n = CERT_LIST_HEAD(mEnterpriseRoots.get());
!CERT_LIST_END(n, mEnterpriseRoots.get()); n = CERT_LIST_NEXT(n)) {
if (!n || !n->cert) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("library failure: CERTCertListNode null or lacks cert"));
continue;
}
- if (CERT_ChangeCertTrust(nullptr, n->cert, &trust) != SECSuccess) {
+ UniqueCERTCertificate cert(CERT_DupCertificate(n->cert));
+ if (ChangeCertTrustWithPossibleAuthentication(cert, trust, nullptr)
+ != SECSuccess) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("couldn't untrust certificate for TLS server auth"));
}
}
mEnterpriseRoots = nullptr;
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("unloaded enterprise roots"));
}
@@ -1030,18 +1032,18 @@ nsNSSComponent::ImportEnterpriseRootsFor
if (!mEnterpriseRoots) {
return;
}
if (CERT_AddCertToListTail(mEnterpriseRoots.get(), nssCertificate.get())
!= SECSuccess) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't add cert to list"));
continue;
}
- if (CERT_ChangeCertTrust(nullptr, nssCertificate.get(), &trust)
- != SECSuccess) {
+ if (ChangeCertTrustWithPossibleAuthentication(nssCertificate, trust,
+ nullptr) != SECSuccess) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("couldn't trust certificate for TLS server auth"));
}
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("Imported '%s'", subjectName.get()));
numImported++;
// now owned by mEnterpriseRoots
Unused << nssCertificate.release();
}
copy from security/manager/ssl/tests/unit/test_certDB_import.js
copy to security/manager/ssl/tests/unit/test_certDB_import_with_master_password.js
--- a/security/manager/ssl/tests/unit/test_certDB_import.js
+++ b/security/manager/ssl/tests/unit/test_certDB_import_with_master_password.js
@@ -1,22 +1,22 @@
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/publicdomain/zero/1.0/
"use strict";
-// Tests the various nsIX509CertDB import methods.
+// Tests that a CA certificate can still be imported if the user has a master
+// password set.
do_get_profile();
const gCertDB = Cc["@mozilla.org/security/x509certdb;1"]
.getService(Ci.nsIX509CertDB);
const CA_CERT_COMMON_NAME = "importedCA";
-const TEST_EMAIL_ADDRESS = "test@example.com";
let gCACertImportDialogCount = 0;
// Mock implementation of nsICertificateDialogs.
const gCertificateDialogs = {
confirmDownloadCACert: (ctx, cert, trust) => {
gCACertImportDialogCount++;
equal(cert.commonName, CA_CERT_COMMON_NAME,
@@ -35,24 +35,41 @@ const gCertificateDialogs = {
viewCert: (ctx, cert) => {
// This shouldn't be called for import methods.
ok(false, "viewCert() should not have been called");
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsICertificateDialogs])
};
-// Implements nsIInterfaceRequestor. Mostly serves to mock nsIPrompt.
-const gInterfaceRequestor = {
- alert: (title, text) => {
- // We don't test anything that calls this method yet.
- ok(false, `alert() should not have been called: ${text}`);
+var gMockPrompter = {
+ passwordToTry: "password",
+ numPrompts: 0,
+
+ // This intentionally does not use arrow function syntax to avoid an issue
+ // where in the context of the arrow function, |this != gMockPrompter| due to
+ // how objects get wrapped when going across xpcom boundaries.
+ promptPassword(dialogTitle, text, password, checkMsg, checkValue) {
+ this.numPrompts++;
+ if (this.numPrompts > 1) { // don't keep retrying a bad password
+ return false;
+ }
+ equal(text,
+ "Please enter the master password for the Software Security Device.",
+ "password prompt text should be as expected");
+ equal(checkMsg, null, "checkMsg should be null");
+ ok(this.passwordToTry, "passwordToTry should be non-null");
+ password.value = this.passwordToTry;
+ return true;
},
- getInterface: iid => {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPrompt]),
+
+ // Again with the arrow function issue.
+ getInterface(iid) {
if (iid.equals(Ci.nsIPrompt)) {
return this;
}
throw new Error(Cr.NS_ERROR_NO_INTERFACE);
}
};
@@ -74,55 +91,40 @@ function findCertByCommonName(commonName
let cert = certEnumerator.getNext().QueryInterface(Ci.nsIX509Cert);
if (cert.commonName == commonName) {
return cert;
}
}
return null;
}
-function testImportCACert() {
+function run_test() {
+ let certificateDialogsCID =
+ MockRegistrar.register("@mozilla.org/nsCertificateDialogs;1",
+ gCertificateDialogs);
+ do_register_cleanup(() => {
+ MockRegistrar.unregister(certificateDialogsCID);
+ });
+
+ // Set a master password.
+ let tokenDB = Cc["@mozilla.org/security/pk11tokendb;1"]
+ .getService(Ci.nsIPK11TokenDB);
+ let token = tokenDB.getInternalKeyToken();
+ token.initPassword("password");
+ token.logoutSimple();
+
// Sanity check the CA cert is missing.
equal(findCertByCommonName(CA_CERT_COMMON_NAME), null,
"CA cert should not be in the database before import");
// Import and check for success.
let caArray = getCertAsByteArray("test_certDB_import/importedCA.pem");
gCertDB.importCertificates(caArray, caArray.length, Ci.nsIX509Cert.CA_CERT,
- gInterfaceRequestor);
+ gMockPrompter);
equal(gCACertImportDialogCount, 1,
"Confirmation dialog for the CA cert should only be shown once");
let caCert = findCertByCommonName(CA_CERT_COMMON_NAME);
notEqual(caCert, null, "CA cert should now be found in the database");
ok(gCertDB.isCertTrusted(caCert, Ci.nsIX509Cert.CA_CERT,
Ci.nsIX509CertDB.TRUSTED_EMAIL),
"CA cert should be trusted for e-mail");
}
-
-function run_test() {
- // We have to set a password and login before we attempt to import anything.
- // In particular, the SQL NSS DB requires the user to be authenticated to set
- // certificate trust settings, which we do when we import CA certs.
- loginToDBWithDefaultPassword();
-
- let certificateDialogsCID =
- MockRegistrar.register("@mozilla.org/nsCertificateDialogs;1",
- gCertificateDialogs);
- do_register_cleanup(() => {
- MockRegistrar.unregister(certificateDialogsCID);
- });
-
- // Sanity check the e-mail cert is missing.
- throws(() => gCertDB.findCertByEmailAddress(TEST_EMAIL_ADDRESS),
- /NS_ERROR_FAILURE/,
- "E-mail cert should not be in the database before import");
-
- // Import the CA cert so that the e-mail import succeeds.
- testImportCACert();
-
- // Import the e-mail cert and check for success.
- let emailArray = getCertAsByteArray("test_certDB_import/emailEE.pem");
- gCertDB.importEmailCertificate(emailArray, emailArray.length,
- gInterfaceRequestor);
- notEqual(gCertDB.findCertByEmailAddress(TEST_EMAIL_ADDRESS), null,
- "E-mail cert should now be found in the database");
-}
--- a/security/manager/ssl/tests/unit/xpcshell.ini
+++ b/security/manager/ssl/tests/unit/xpcshell.ini
@@ -58,16 +58,17 @@ run-sequentially = hardcoded ports
[test_cert_override_bits_mismatches.js]
run-sequentially = hardcoded ports
[test_cert_sha1.js]
[test_cert_signatures.js]
[test_cert_trust.js]
[test_cert_version.js]
[test_certDB_import.js]
[test_certDB_import_pkcs12.js]
+[test_certDB_import_with_master_password.js]
[test_certviewer_invalid_oids.js]
skip-if = toolkit == 'android'
[test_constructX509FromBase64.js]
[test_content_signing.js]
[test_ct.js]
# Requires hard-coded debug-only data
skip-if = !debug
run-sequentially = hardcoded ports