зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1630434 - de-duplicate preloaded intermediates that may have been cached in cert9.db r=kjacobs,bbeurdouche
In general, PSM caches intermediates from verified certificate chains in the NSS certdb. Before bug 1619021, this would include preloaded intermediates, which is unnecessary because cert_storage has a copy of those certificates, and so they don't need to take up time and space in the NSS certdb. This patch introduces the intermediate preloading healer, which periodically runs on a background thread, looks for these duplicate intermediates, and removes them from the NSS certdb. Differential Revision: https://phabricator.services.mozilla.com/D77152
This commit is contained in:
Родитель
cfbc528f9c
Коммит
63919c509b
|
@ -194,9 +194,12 @@ pref("security.pki.mitm_detected", false);
|
|||
// Intermediate CA Preloading settings
|
||||
#if defined(MOZ_NEW_CERT_STORAGE) && !defined(MOZ_WIDGET_ANDROID)
|
||||
pref("security.remote_settings.intermediates.enabled", true);
|
||||
pref("security.intermediate_preloading_healer.enabled", true);
|
||||
#else
|
||||
pref("security.remote_settings.intermediates.enabled", false);
|
||||
pref("security.intermediate_preloading_healer.enabled", false);
|
||||
#endif
|
||||
pref("security.intermediate_preloading_healer.timer_interval_ms", 300000);
|
||||
pref("security.remote_settings.intermediates.bucket", "security-state");
|
||||
pref("security.remote_settings.intermediates.collection", "intermediates");
|
||||
pref("security.remote_settings.intermediates.checked", 0);
|
||||
|
|
|
@ -41,6 +41,8 @@
|
|||
#include "nsIOService.h"
|
||||
#include "nsIPrompt.h"
|
||||
#include "nsIProperties.h"
|
||||
#include "nsISerialEventTarget.h"
|
||||
#include "nsITimer.h"
|
||||
#include "nsITokenPasswordDialogs.h"
|
||||
#include "nsIWindowWatcher.h"
|
||||
#include "nsIXULRuntime.h"
|
||||
|
@ -65,6 +67,10 @@
|
|||
#include "prmem.h"
|
||||
#include "GeckoProfiler.h"
|
||||
|
||||
#ifdef MOZ_NEW_CERT_STORAGE
|
||||
# include "cert_storage/src/cert_storage.h"
|
||||
#endif
|
||||
|
||||
#if defined(XP_LINUX) && !defined(ANDROID)
|
||||
# include <linux/magic.h>
|
||||
# include <sys/vfs.h>
|
||||
|
@ -2115,6 +2121,11 @@ void nsNSSComponent::ShutdownNSS() {
|
|||
|
||||
Preferences::RemoveObserver(this, "security.");
|
||||
|
||||
if (mIntermediatePreloadingHealerTimer) {
|
||||
mIntermediatePreloadingHealerTimer->Cancel();
|
||||
mIntermediatePreloadingHealerTimer = nullptr;
|
||||
}
|
||||
|
||||
// Release the default CertVerifier. This will cause any held NSS resources
|
||||
// to be released.
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
@ -2124,6 +2135,138 @@ void nsNSSComponent::ShutdownNSS() {
|
|||
// be any XPCOM objects holding NSS resources).
|
||||
}
|
||||
|
||||
#ifdef MOZ_NEW_CERT_STORAGE
|
||||
// The aim of the intermediate preloading healer is to remove intermediates
|
||||
// that were previously cached by PSM in the NSS certdb that are now preloaded
|
||||
// in cert_storage. When cached by PSM, these certificates will have no
|
||||
// particular trust set - they are intended to inherit their trust. If, upon
|
||||
// examination, these certificates do have trust bits set that affect
|
||||
// certificate validation, they must have been modified by the user, so we want
|
||||
// to leave them alone.
|
||||
bool CertHasDefaultTrust(CERTCertificate* cert) {
|
||||
CERTCertTrust trust;
|
||||
if (CERT_GetCertTrust(cert, &trust) != SECSuccess) {
|
||||
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("CERT_GetCertTrust failed"));
|
||||
return false;
|
||||
}
|
||||
// This is the active distrust test for CA certificates (this is expected to
|
||||
// be an intermediate).
|
||||
if ((trust.sslFlags & (CERTDB_TRUSTED_CA | CERTDB_TERMINAL_RECORD)) ==
|
||||
CERTDB_TERMINAL_RECORD) {
|
||||
return false;
|
||||
}
|
||||
// This is the trust anchor test.
|
||||
if (trust.sslFlags & CERTDB_TRUSTED_CA) {
|
||||
return false;
|
||||
}
|
||||
// This is the active distrust test for CA certificates (this is expected to
|
||||
// be an intermediate).
|
||||
if ((trust.emailFlags & (CERTDB_TRUSTED_CA | CERTDB_TERMINAL_RECORD)) ==
|
||||
CERTDB_TERMINAL_RECORD) {
|
||||
return false;
|
||||
}
|
||||
// This is the trust anchor test.
|
||||
if (trust.emailFlags & CERTDB_TRUSTED_CA) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void IntermediatePreloadingHealerCallback(nsITimer*, void*) {
|
||||
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
|
||||
("IntermediatePreloadingHealerCallback"));
|
||||
// Get the slot corresponding to the NSS certdb.
|
||||
UniquePK11SlotInfo softokenSlot(PK11_GetInternalKeySlot());
|
||||
if (!softokenSlot) {
|
||||
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("PK11_GetInternalKeySlot failed"));
|
||||
return;
|
||||
}
|
||||
// List the certificates in the NSS certdb.
|
||||
UniqueCERTCertList softokenCertificates(
|
||||
PK11_ListCertsInSlot(softokenSlot.get()));
|
||||
if (!softokenCertificates) {
|
||||
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("PK11_ListCertsInSlot failed"));
|
||||
return;
|
||||
}
|
||||
nsCOMPtr<nsICertStorage> certStorage(do_GetService(NS_CERT_STORAGE_CID));
|
||||
if (!certStorage) {
|
||||
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't get cert_storage"));
|
||||
return;
|
||||
}
|
||||
Vector<UniqueCERTCertificate> certsToDelete;
|
||||
// For each certificate, look it up in cert_storage. If there's a match, this
|
||||
// is a preloaded intermediate.
|
||||
for (CERTCertListNode* n = CERT_LIST_HEAD(softokenCertificates);
|
||||
!CERT_LIST_END(n, softokenCertificates); n = CERT_LIST_NEXT(n)) {
|
||||
nsTArray<uint8_t> subject;
|
||||
subject.AppendElements(n->cert->derSubject.data, n->cert->derSubject.len);
|
||||
nsTArray<nsTArray<uint8_t>> certs;
|
||||
nsresult rv = certStorage->FindCertsBySubject(subject, certs);
|
||||
if (NS_FAILED(rv)) {
|
||||
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("FindCertsBySubject failed"));
|
||||
break;
|
||||
}
|
||||
for (const auto& encodedCert : certs) {
|
||||
if (encodedCert.Length() != n->cert->derCert.len) {
|
||||
continue;
|
||||
}
|
||||
if (memcmp(encodedCert.Elements(), n->cert->derCert.data,
|
||||
encodedCert.Length()) != 0) {
|
||||
continue;
|
||||
}
|
||||
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
|
||||
("found preloaded intermediate in certdb"));
|
||||
if (!CertHasDefaultTrust(n->cert)) {
|
||||
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
|
||||
("certificate doesn't have default trust - skipping"));
|
||||
continue;
|
||||
}
|
||||
UniqueCERTCertificate certCopy(CERT_DupCertificate(n->cert));
|
||||
if (!certCopy) {
|
||||
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("CERT_DupCertificate failed"));
|
||||
continue;
|
||||
}
|
||||
// Note that we want to remove this certificate from the NSS certdb
|
||||
// because it also exists in preloaded intermediate storage and is thus
|
||||
// superfluous.
|
||||
if (!certsToDelete.append(std::move(certCopy))) {
|
||||
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
|
||||
("append failed - out of memory?"));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Only delete 20 at a time.
|
||||
if (certsToDelete.length() >= 20) {
|
||||
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
|
||||
("found limit of 20 preloaded intermediates in certdb"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (const auto& certToDelete : certsToDelete) {
|
||||
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
|
||||
("attempting to delete preloaded intermediate '%s'",
|
||||
certToDelete->subjectName));
|
||||
if (SEC_DeletePermCertificate(certToDelete.get()) != SECSuccess) {
|
||||
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
|
||||
("SEC_DeletePermCertificate failed"));
|
||||
}
|
||||
}
|
||||
|
||||
// This is for tests - notify that this ran.
|
||||
nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
|
||||
"IntermediatePreloadingHealerCallbackDone", []() -> void {
|
||||
nsCOMPtr<nsIObserverService> observerService =
|
||||
mozilla::services::GetObserverService();
|
||||
if (observerService) {
|
||||
observerService->NotifyObservers(
|
||||
nullptr, "psm:intermediate-preloading-healer-ran", nullptr);
|
||||
}
|
||||
}));
|
||||
Unused << NS_DispatchToMainThread(runnable.forget());
|
||||
}
|
||||
#endif // MOZ_NEW_CERT_STORAGE
|
||||
|
||||
nsresult nsNSSComponent::Init() {
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
if (!NS_IsMainThread()) {
|
||||
|
@ -2147,7 +2290,64 @@ nsresult nsNSSComponent::Init() {
|
|||
return rv;
|
||||
}
|
||||
|
||||
return RegisterObservers();
|
||||
rv = RegisterObservers();
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = MaybeEnableIntermediatePreloadingHealer();
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult nsNSSComponent::MaybeEnableIntermediatePreloadingHealer() {
|
||||
#ifdef MOZ_NEW_CERT_STORAGE
|
||||
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
|
||||
("nsNSSComponent::MaybeEnableIntermediatePreloadingHealer"));
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (!NS_IsMainThread()) {
|
||||
return NS_ERROR_NOT_SAME_THREAD;
|
||||
}
|
||||
|
||||
if (mIntermediatePreloadingHealerTimer) {
|
||||
mIntermediatePreloadingHealerTimer->Cancel();
|
||||
mIntermediatePreloadingHealerTimer = nullptr;
|
||||
}
|
||||
|
||||
if (!Preferences::GetBool("security.intermediate_preloading_healer.enabled",
|
||||
false)) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (!mIntermediatePreloadingHealerTaskQueue) {
|
||||
nsresult rv = NS_CreateBackgroundTaskQueue(
|
||||
"IntermediatePreloadingHealer",
|
||||
getter_AddRefs(mIntermediatePreloadingHealerTaskQueue));
|
||||
if (NS_FAILED(rv)) {
|
||||
MOZ_LOG(gPIPNSSLog, LogLevel::Error,
|
||||
("NS_CreateBackgroundTaskQueue failed"));
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
uint32_t timerDelayMS = Preferences::GetUint(
|
||||
"security.intermediate_preloading_healer.timer_interval_ms",
|
||||
5 * 60 * 1000);
|
||||
nsresult rv = NS_NewTimerWithFuncCallback(
|
||||
getter_AddRefs(mIntermediatePreloadingHealerTimer),
|
||||
IntermediatePreloadingHealerCallback, nullptr, timerDelayMS,
|
||||
nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY,
|
||||
"IntermediatePreloadingHealer", mIntermediatePreloadingHealerTaskQueue);
|
||||
if (NS_FAILED(rv)) {
|
||||
MOZ_LOG(gPIPNSSLog, LogLevel::Error,
|
||||
("NS_NewTimerWithFuncCallback failed"));
|
||||
return rv;
|
||||
}
|
||||
|
||||
#endif // MOZ_NEW_CERT_STORAGE
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// nsISupports Implementation for the class
|
||||
|
@ -2230,6 +2430,13 @@ nsNSSComponent::Observe(nsISupports* aSubject, const char* aTopic,
|
|||
if (clearSessionCache) {
|
||||
ClearSSLExternalAndInternalSessionCacheNative();
|
||||
}
|
||||
|
||||
// Preferences that don't affect certificate verification.
|
||||
if (prefName.Equals("security.intermediate_preloading_healer.enabled") ||
|
||||
prefName.Equals(
|
||||
"security.intermediate_preloading_healer.timer_interval_ms")) {
|
||||
MaybeEnableIntermediatePreloadingHealer();
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
|
|
|
@ -29,7 +29,8 @@
|
|||
|
||||
class nsIDOMWindow;
|
||||
class nsIPrompt;
|
||||
class SmartCardThreadList;
|
||||
class nsISerialEventTarget;
|
||||
class nsITimer;
|
||||
|
||||
namespace mozilla {
|
||||
namespace psm {
|
||||
|
@ -107,6 +108,8 @@ class nsNSSComponent final : public nsINSSComponent, public nsIObserver {
|
|||
|
||||
bool ShouldEnableEnterpriseRootsForFamilySafety(uint32_t familySafetyMode);
|
||||
|
||||
nsresult MaybeEnableIntermediatePreloadingHealer();
|
||||
|
||||
// mLoadableCertsLoadedMonitor protects mLoadableCertsLoaded.
|
||||
mozilla::Monitor mLoadableCertsLoadedMonitor;
|
||||
bool mLoadableCertsLoaded;
|
||||
|
@ -136,6 +139,13 @@ class nsNSSComponent final : public nsINSSComponent, public nsIObserver {
|
|||
// to complete (because it will never complete) so we use this boolean to keep
|
||||
// track of if we should wait.
|
||||
bool mLoadLoadableCertsTaskDispatched;
|
||||
// If the intermediate preloading healer is enabled, the following timer
|
||||
// periodically dispatches events to the background task queue. Each of these
|
||||
// events scans the NSS certdb for preloaded intermediates that are in
|
||||
// cert_storage and thus can be removed. By default, the interval is 5
|
||||
// minutes.
|
||||
nsCOMPtr<nsISerialEventTarget> mIntermediatePreloadingHealerTaskQueue;
|
||||
nsCOMPtr<nsITimer> mIntermediatePreloadingHealerTimer;
|
||||
};
|
||||
|
||||
inline nsresult BlockUntilLoadableCertsLoaded() {
|
||||
|
|
|
@ -586,6 +586,70 @@ add_task(
|
|||
}
|
||||
);
|
||||
|
||||
function findCertByCommonName(certDB, commonName) {
|
||||
for (let cert of certDB.getCerts()) {
|
||||
if (cert.commonName == commonName) {
|
||||
return cert;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
add_task(
|
||||
{
|
||||
skip_if: () => !AppConstants.MOZ_NEW_CERT_STORAGE,
|
||||
},
|
||||
async function test_healer() {
|
||||
Services.prefs.setBoolPref(INTERMEDIATES_ENABLED_PREF, true);
|
||||
Services.prefs.setIntPref(INTERMEDIATES_DL_PER_POLL_PREF, 100);
|
||||
|
||||
let certDB = Cc["@mozilla.org/security/x509certdb;1"].getService(
|
||||
Ci.nsIX509CertDB
|
||||
);
|
||||
// Add an intermediate as if it had previously been cached.
|
||||
addCertFromFile(certDB, "test_intermediate_preloads/int.pem", ",,");
|
||||
// Add an intermediate with non-default trust settings as if it had been added by the user.
|
||||
addCertFromFile(certDB, "test_intermediate_preloads/int2.pem", "CTu,,");
|
||||
|
||||
let syncResult = await syncAndDownload(["int.pem", "int2.pem"]);
|
||||
equal(syncResult, "success", "Preloading update should have run");
|
||||
|
||||
equal(
|
||||
(await locallyDownloaded()).length,
|
||||
2,
|
||||
"There should have been 2 downloads"
|
||||
);
|
||||
|
||||
let healerRanPromise = TestUtils.topicObserved(
|
||||
"psm:intermediate-preloading-healer-ran"
|
||||
);
|
||||
Services.prefs.setIntPref(
|
||||
"security.intermediate_preloading_healer.timer_interval_ms",
|
||||
500
|
||||
);
|
||||
Services.prefs.setBoolPref(
|
||||
"security.intermediate_preloading_healer.enabled",
|
||||
true
|
||||
);
|
||||
await healerRanPromise;
|
||||
Services.prefs.setBoolPref(
|
||||
"security.intermediate_preloading_healer.enabled",
|
||||
false
|
||||
);
|
||||
|
||||
let intermediate = findCertByCommonName(
|
||||
certDB,
|
||||
"intermediate-preloading-intermediate"
|
||||
);
|
||||
equal(intermediate, null, "should not find intermediate in NSS");
|
||||
let intermediate2 = findCertByCommonName(
|
||||
certDB,
|
||||
"intermediate-preloading-intermediate2"
|
||||
);
|
||||
notEqual(intermediate2, null, "should find second intermediate in NSS");
|
||||
}
|
||||
);
|
||||
|
||||
function run_test() {
|
||||
server = new HttpServer();
|
||||
server.start(-1);
|
||||
|
|
Загрузка…
Ссылка в новой задаче