зеркало из https://github.com/mozilla/gecko-dev.git
bug 1265113 - implement platform support for enterprise roots r=Cykesiopka,mhowell,rbarnes
MozReview-Commit-ID: JKxwCjoH0Oa --HG-- extra : rebase_source : 9eaf3f1c5371e7b4b4df304bc6ce132ade5775da
This commit is contained in:
Родитель
4f13f0068e
Коммит
8ba29d1473
|
@ -390,4 +390,10 @@ interface nsIX509CertDB : nsISupports {
|
||||||
* Get all the known certs in the database
|
* Get all the known certs in the database
|
||||||
*/
|
*/
|
||||||
nsIX509CertList getCerts();
|
nsIX509CertList getCerts();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get a list of imported enterprise root certificates (currently only
|
||||||
|
* implemented on Windows).
|
||||||
|
*/
|
||||||
|
nsIX509CertList getEnterpriseRoots();
|
||||||
};
|
};
|
||||||
|
|
|
@ -1476,6 +1476,32 @@ nsNSSCertificateDB::GetCerts(nsIX509CertList **_retval)
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
nsNSSCertificateDB::GetEnterpriseRoots(nsIX509CertList** enterpriseRoots)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
|
if (!NS_IsMainThread()) {
|
||||||
|
return NS_ERROR_NOT_SAME_THREAD;
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_ENSURE_ARG_POINTER(enterpriseRoots);
|
||||||
|
|
||||||
|
nsNSSShutDownPreventionLock locker;
|
||||||
|
if (isAlreadyShutDown()) {
|
||||||
|
return NS_ERROR_NOT_AVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef XP_WIN
|
||||||
|
nsCOMPtr<nsINSSComponent> psm(do_GetService(PSM_COMPONENT_CONTRACTID));
|
||||||
|
if (!psm) {
|
||||||
|
return NS_ERROR_FAILURE;
|
||||||
|
}
|
||||||
|
return psm->GetEnterpriseRoots(enterpriseRoots);
|
||||||
|
#else
|
||||||
|
return NS_ERROR_NOT_IMPLEMENTED;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
nsresult
|
nsresult
|
||||||
VerifyCertAtTime(nsIX509Cert* aCert,
|
VerifyCertAtTime(nsIX509Cert* aCert,
|
||||||
int64_t /*SECCertificateUsage*/ aUsage,
|
int64_t /*SECCertificateUsage*/ aUsage,
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include "SharedSSLState.h"
|
#include "SharedSSLState.h"
|
||||||
#include "cert.h"
|
#include "cert.h"
|
||||||
#include "certdb.h"
|
#include "certdb.h"
|
||||||
|
#include "mozilla/ArrayUtils.h"
|
||||||
#include "mozilla/Casting.h"
|
#include "mozilla/Casting.h"
|
||||||
#include "mozilla/Preferences.h"
|
#include "mozilla/Preferences.h"
|
||||||
#include "mozilla/PublicSSL.h"
|
#include "mozilla/PublicSSL.h"
|
||||||
|
@ -19,6 +20,7 @@
|
||||||
#include "mozilla/StaticPtr.h"
|
#include "mozilla/StaticPtr.h"
|
||||||
#include "mozilla/SyncRunnable.h"
|
#include "mozilla/SyncRunnable.h"
|
||||||
#include "mozilla/Telemetry.h"
|
#include "mozilla/Telemetry.h"
|
||||||
|
#include "mozilla/unused.h"
|
||||||
#include "nsAppDirectoryServiceDefs.h"
|
#include "nsAppDirectoryServiceDefs.h"
|
||||||
#include "nsCRT.h"
|
#include "nsCRT.h"
|
||||||
#include "nsCertVerificationThread.h"
|
#include "nsCertVerificationThread.h"
|
||||||
|
@ -442,7 +444,7 @@ GetUserSid(nsAString& sidString)
|
||||||
}
|
}
|
||||||
char sid_buffer[SECURITY_MAX_SID_SIZE];
|
char sid_buffer[SECURITY_MAX_SID_SIZE];
|
||||||
SID* sid = BitwiseCast<SID*, char*>(sid_buffer);
|
SID* sid = BitwiseCast<SID*, char*>(sid_buffer);
|
||||||
DWORD cbSid = MOZ_ARRAY_LENGTH(sid_buffer);
|
DWORD cbSid = ArrayLength(sid_buffer);
|
||||||
SID_NAME_USE eUse;
|
SID_NAME_USE eUse;
|
||||||
// There doesn't appear to be a defined maximum length for the domain name
|
// There doesn't appear to be a defined maximum length for the domain name
|
||||||
// here. To deal with this, we start with a reasonable buffer length and
|
// here. To deal with this, we start with a reasonable buffer length and
|
||||||
|
@ -652,31 +654,50 @@ AccountHasFamilySafetyEnabled(bool& enabled)
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* kImportedFamilySafetyRootPref =
|
// It would be convenient to just use nsIX509CertDB in the following code.
|
||||||
"security.family_safety.imported_root.db_key";
|
// However, since nsIX509CertDB depends on nsNSSComponent initialization (and
|
||||||
|
// since this code runs during that initialization), we can't use it. Instead,
|
||||||
|
// we can use NSS APIs directly (as long as we're called late enough in
|
||||||
|
// nsNSSComponent initialization such that those APIs are safe to use).
|
||||||
|
|
||||||
static nsresult
|
// Helper function to convert a PCCERT_CONTEXT (i.e. a certificate obtained via
|
||||||
MaybeImportFamilySafetyRoot(PCCERT_CONTEXT certificate,
|
// a Windows API) to a temporary CERTCertificate (i.e. a certificate for use
|
||||||
bool& wasFamilySafetyRoot)
|
// with NSS APIs).
|
||||||
|
static UniqueCERTCertificate
|
||||||
|
PCCERT_CONTEXTToCERTCertificate(PCCERT_CONTEXT pccert)
|
||||||
{
|
{
|
||||||
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("MaybeImportFamilySafetyRoot"));
|
MOZ_ASSERT(pccert);
|
||||||
wasFamilySafetyRoot = false;
|
if (!pccert) {
|
||||||
|
return nullptr;
|
||||||
// It would be convenient to just use nsIX509CertDB here. However, since
|
}
|
||||||
// nsIX509CertDB depends on nsNSSComponent initialization, we can't use it.
|
|
||||||
// Instead, we can use NSS APIs directly (as long as we're called late enough
|
|
||||||
// in nsNSSComponent initialization such that those APIs are safe to use).
|
|
||||||
|
|
||||||
SECItem derCert = {
|
SECItem derCert = {
|
||||||
siBuffer,
|
siBuffer,
|
||||||
certificate->pbCertEncoded,
|
pccert->pbCertEncoded,
|
||||||
certificate->cbCertEncoded
|
pccert->cbCertEncoded
|
||||||
};
|
};
|
||||||
UniqueCERTCertificate nssCertificate(
|
return UniqueCERTCertificate(
|
||||||
CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &derCert,
|
CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &derCert,
|
||||||
nullptr, // nickname unnecessary
|
nullptr, // nickname unnecessary
|
||||||
false, // not permanent
|
false, // not permanent
|
||||||
true)); // copy DER
|
true)); // copy DER
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* kMicrosoftFamilySafetyCN = "Microsoft Family Safety";
|
||||||
|
|
||||||
|
nsresult
|
||||||
|
nsNSSComponent::MaybeImportFamilySafetyRoot(PCCERT_CONTEXT certificate,
|
||||||
|
bool& wasFamilySafetyRoot)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
|
if (!NS_IsMainThread()) {
|
||||||
|
return NS_ERROR_NOT_SAME_THREAD;
|
||||||
|
}
|
||||||
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("MaybeImportFamilySafetyRoot"));
|
||||||
|
wasFamilySafetyRoot = false;
|
||||||
|
|
||||||
|
UniqueCERTCertificate nssCertificate(
|
||||||
|
PCCERT_CONTEXTToCERTCertificate(certificate));
|
||||||
if (!nssCertificate) {
|
if (!nssCertificate) {
|
||||||
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't decode certificate"));
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't decode certificate"));
|
||||||
return NS_ERROR_FAILURE;
|
return NS_ERROR_FAILURE;
|
||||||
|
@ -685,34 +706,30 @@ MaybeImportFamilySafetyRoot(PCCERT_CONTEXT certificate,
|
||||||
UniquePORTString subjectName(CERT_GetCommonName(&nssCertificate->subject));
|
UniquePORTString subjectName(CERT_GetCommonName(&nssCertificate->subject));
|
||||||
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
|
||||||
("subject name is '%s'", subjectName.get()));
|
("subject name is '%s'", subjectName.get()));
|
||||||
if (nsCRT::strcmp(subjectName.get(), "Microsoft Family Safety") == 0) {
|
if (nsCRT::strcmp(subjectName.get(), kMicrosoftFamilySafetyCN) == 0) {
|
||||||
wasFamilySafetyRoot = true;
|
wasFamilySafetyRoot = true;
|
||||||
CERTCertTrust trust = {
|
CERTCertTrust trust = {
|
||||||
CERTDB_TRUSTED_CA | CERTDB_VALID_CA | CERTDB_USER,
|
CERTDB_TRUSTED_CA | CERTDB_VALID_CA | CERTDB_USER,
|
||||||
0,
|
0,
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
SECStatus srv = __CERT_AddTempCertToPerm(
|
if (CERT_ChangeCertTrust(nullptr, nssCertificate.get(), &trust)
|
||||||
nssCertificate.get(), "Microsoft Family Safety", &trust);
|
!= SECSuccess) {
|
||||||
if (srv != SECSuccess) {
|
|
||||||
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
|
||||||
("couldn't permanently add certificate"));
|
("couldn't trust certificate for TLS server auth"));
|
||||||
return NS_ERROR_FAILURE;
|
return NS_ERROR_FAILURE;
|
||||||
}
|
}
|
||||||
nsAutoCString dbKey;
|
MOZ_ASSERT(!mFamilySafetyRoot);
|
||||||
nsresult rv = nsNSSCertificate::GetDbKey(nssCertificate, dbKey);
|
mFamilySafetyRoot = Move(nssCertificate);
|
||||||
if (NS_FAILED(rv)) {
|
|
||||||
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("GetDbKey failed"));
|
|
||||||
return rv;
|
|
||||||
}
|
|
||||||
Preferences::SetCString(kImportedFamilySafetyRootPref, dbKey);
|
|
||||||
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("added Family Safety root"));
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("added Family Safety root"));
|
||||||
}
|
}
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Because HCERTSTORE is just a typedef void*, we can't use any of the nice
|
// Because HCERTSTORE is just a typedef void*, we can't use any of the nice
|
||||||
// scoped pointer templates.
|
// scoped or unique pointer templates. To elaborate, any attempt would
|
||||||
|
// instantiate those templates with T = void. When T gets used in the context
|
||||||
|
// of T&, this results in void&, which isn't legal.
|
||||||
class ScopedCertStore final
|
class ScopedCertStore final
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -734,25 +751,25 @@ private:
|
||||||
HCERTSTORE certstore;
|
HCERTSTORE certstore;
|
||||||
};
|
};
|
||||||
|
|
||||||
static const wchar_t* WindowsDefaultRootStoreName = L"ROOT";
|
static const wchar_t* kWindowsDefaultRootStoreName = L"ROOT";
|
||||||
|
|
||||||
static nsresult
|
nsresult
|
||||||
LoadFamilySafetyRoot()
|
nsNSSComponent::LoadFamilySafetyRoot()
|
||||||
{
|
{
|
||||||
ScopedCertStore certstore(
|
ScopedCertStore certstore(
|
||||||
CertOpenSystemStore(0, WindowsDefaultRootStoreName));
|
CertOpenSystemStore(0, kWindowsDefaultRootStoreName));
|
||||||
if (!certstore.get()) {
|
if (!certstore.get()) {
|
||||||
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
|
||||||
("couldn't get certstore '%S'", WindowsDefaultRootStoreName));
|
("couldn't get certstore '%S'", kWindowsDefaultRootStoreName));
|
||||||
return NS_ERROR_FAILURE;
|
return NS_ERROR_FAILURE;
|
||||||
}
|
}
|
||||||
// Any resources held by the certificate are released by the next call to
|
// Any resources held by the certificate are released by the next call to
|
||||||
// CertFindCertificateInStore.
|
// CertFindCertificateInStore.
|
||||||
PCCERT_CONTEXT certificate = nullptr;
|
PCCERT_CONTEXT certificate = nullptr;
|
||||||
while (certificate = CertFindCertificateInStore(certstore.get(),
|
while ((certificate = CertFindCertificateInStore(certstore.get(),
|
||||||
X509_ASN_ENCODING, 0,
|
X509_ASN_ENCODING, 0,
|
||||||
CERT_FIND_ANY, nullptr,
|
CERT_FIND_ANY, nullptr,
|
||||||
certificate)) {
|
certificate))) {
|
||||||
bool wasFamilySafetyRoot = false;
|
bool wasFamilySafetyRoot = false;
|
||||||
nsresult rv = MaybeImportFamilySafetyRoot(certificate,
|
nsresult rv = MaybeImportFamilySafetyRoot(certificate,
|
||||||
wasFamilySafetyRoot);
|
wasFamilySafetyRoot);
|
||||||
|
@ -763,38 +780,32 @@ LoadFamilySafetyRoot()
|
||||||
return NS_ERROR_FAILURE;
|
return NS_ERROR_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
void
|
||||||
UnloadFamilySafetyRoot()
|
nsNSSComponent::UnloadFamilySafetyRoot()
|
||||||
{
|
{
|
||||||
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
|
if (!NS_IsMainThread()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("UnloadFamilySafetyRoot"));
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("UnloadFamilySafetyRoot"));
|
||||||
nsAdoptingCString dbKey = Preferences::GetCString(
|
if (!mFamilySafetyRoot) {
|
||||||
kImportedFamilySafetyRootPref);
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("Family Safety Root wasn't present"));
|
||||||
if (!dbKey || dbKey.IsEmpty()) {
|
|
||||||
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
|
|
||||||
("Family Safety root wasn't previously imported"));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
UniqueCERTCertificate cert;
|
// It would be intuitive to set the trust to { 0, 0, 0 } here. However, this
|
||||||
nsresult rv = nsNSSCertificateDB::FindCertByDBKey(dbKey, cert);
|
// doesn't work for temporary certificates because CERT_ChangeCertTrust first
|
||||||
if (NS_FAILED(rv)) {
|
// 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) {
|
||||||
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
|
||||||
("finding previously-imported Family Safety root failed"));
|
("couldn't untrust certificate for TLS server auth"));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (!cert) {
|
mFamilySafetyRoot = nullptr;
|
||||||
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
|
|
||||||
("previously-imported Family Safety root not found"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
SECStatus srv = SEC_DeletePermCertificate(cert.get());
|
|
||||||
if (srv != SECSuccess) {
|
|
||||||
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
|
|
||||||
("couldn't delete previously-imported Family Safety root"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
|
|
||||||
("deleted previously-imported Family Safety root"));
|
|
||||||
Preferences::ClearUser(kImportedFamilySafetyRootPref);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // XP_WIN
|
#endif // XP_WIN
|
||||||
|
@ -812,8 +823,8 @@ const char* kFamilySafetyModePref = "security.family_safety.mode";
|
||||||
// 5: Family Safety was enabled
|
// 5: Family Safety was enabled
|
||||||
// 6: failed to import the Family Safety root
|
// 6: failed to import the Family Safety root
|
||||||
// 7: successfully imported the root
|
// 7: successfully imported the root
|
||||||
static void
|
void
|
||||||
MaybeEnableFamilySafetyCompatibility()
|
nsNSSComponent::MaybeEnableFamilySafetyCompatibility()
|
||||||
{
|
{
|
||||||
#ifdef XP_WIN
|
#ifdef XP_WIN
|
||||||
UnloadFamilySafetyRoot();
|
UnloadFamilySafetyRoot();
|
||||||
|
@ -853,6 +864,211 @@ MaybeEnableFamilySafetyCompatibility()
|
||||||
#endif // XP_WIN
|
#endif // XP_WIN
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef XP_WIN
|
||||||
|
// Helper function to determine if the OS considers the given certificate to be
|
||||||
|
// a trust anchor for TLS server auth certificates. This is to be used in the
|
||||||
|
// context of importing what are presumed to be root certificates from the OS.
|
||||||
|
// If this function returns true but it turns out that the given certificate is
|
||||||
|
// in some way unsuitable to issue certificates, mozilla::pkix will never build
|
||||||
|
// a valid chain that includes the certificate, so importing it even if it
|
||||||
|
// isn't a valid CA poses no risk.
|
||||||
|
static bool
|
||||||
|
CertIsTrustAnchorForTLSServerAuth(PCCERT_CONTEXT certificate)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(certificate);
|
||||||
|
if (!certificate) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PCCERT_CHAIN_CONTEXT pChainContext = nullptr;
|
||||||
|
CERT_ENHKEY_USAGE enhkeyUsage;
|
||||||
|
memset(&enhkeyUsage, 0, sizeof(CERT_ENHKEY_USAGE));
|
||||||
|
LPSTR identifiers[] = {
|
||||||
|
"1.3.6.1.5.5.7.3.1", // id-kp-serverAuth
|
||||||
|
};
|
||||||
|
enhkeyUsage.cUsageIdentifier = ArrayLength(identifiers);
|
||||||
|
enhkeyUsage.rgpszUsageIdentifier = identifiers;
|
||||||
|
CERT_USAGE_MATCH certUsage;
|
||||||
|
memset(&certUsage, 0, sizeof(CERT_USAGE_MATCH));
|
||||||
|
certUsage.dwType = USAGE_MATCH_TYPE_AND;
|
||||||
|
certUsage.Usage = enhkeyUsage;
|
||||||
|
CERT_CHAIN_PARA chainPara;
|
||||||
|
memset(&chainPara, 0, sizeof(CERT_CHAIN_PARA));
|
||||||
|
chainPara.cbSize = sizeof(CERT_CHAIN_PARA);
|
||||||
|
chainPara.RequestedUsage = certUsage;
|
||||||
|
|
||||||
|
if (!CertGetCertificateChain(nullptr, certificate, nullptr, nullptr,
|
||||||
|
&chainPara, 0, nullptr, &pChainContext)) {
|
||||||
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("CertGetCertificateChain failed"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool trusted = pChainContext->TrustStatus.dwErrorStatus ==
|
||||||
|
CERT_TRUST_NO_ERROR;
|
||||||
|
bool isRoot = pChainContext->cChain == 1;
|
||||||
|
CertFreeCertificateChain(pChainContext);
|
||||||
|
if (trusted && isRoot) {
|
||||||
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
|
||||||
|
("certificate is trust anchor for TLS server auth"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
|
||||||
|
("certificate not trust anchor for TLS server auth"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
nsNSSComponent::UnloadEnterpriseRoots()
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
|
if (!NS_IsMainThread()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("UnloadEnterpriseRoots"));
|
||||||
|
if (!mEnterpriseRoots) {
|
||||||
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("no enterprise roots were present"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 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 };
|
||||||
|
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) {
|
||||||
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
|
||||||
|
("couldn't untrust certificate for TLS server auth"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mEnterpriseRoots = nullptr;
|
||||||
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("unloaded enterprise roots"));
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
nsNSSComponent::GetEnterpriseRoots(nsIX509CertList** enterpriseRoots)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
|
if (!NS_IsMainThread()) {
|
||||||
|
return NS_ERROR_NOT_SAME_THREAD;
|
||||||
|
}
|
||||||
|
NS_ENSURE_ARG_POINTER(enterpriseRoots);
|
||||||
|
|
||||||
|
nsNSSShutDownPreventionLock lock;
|
||||||
|
// nsNSSComponent isn't a nsNSSShutDownObject, so we can't check
|
||||||
|
// isAlreadyShutDown(). However, since mEnterpriseRoots is cleared when NSS
|
||||||
|
// shuts down, we can use that as a proxy for checking for NSS shutdown.
|
||||||
|
// (Of course, it may also be the case that no enterprise roots were imported,
|
||||||
|
// so we should just return a null list and NS_OK in this case.)
|
||||||
|
if (!mEnterpriseRoots) {
|
||||||
|
*enterpriseRoots = nullptr;
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
UniqueCERTCertList enterpriseRootsCopy(
|
||||||
|
nsNSSCertList::DupCertList(mEnterpriseRoots, lock));
|
||||||
|
if (!enterpriseRootsCopy) {
|
||||||
|
return NS_ERROR_FAILURE;
|
||||||
|
}
|
||||||
|
nsCOMPtr<nsIX509CertList> enterpriseRootsCertList(
|
||||||
|
new nsNSSCertList(Move(enterpriseRootsCopy), lock));
|
||||||
|
if (!enterpriseRootsCertList) {
|
||||||
|
return NS_ERROR_FAILURE;
|
||||||
|
}
|
||||||
|
enterpriseRootsCertList.forget(enterpriseRoots);
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
#endif // XP_WIN
|
||||||
|
|
||||||
|
static const char* kEnterpriseRootModePref = "security.enterprise_roots.enabled";
|
||||||
|
|
||||||
|
void
|
||||||
|
nsNSSComponent::MaybeImportEnterpriseRoots()
|
||||||
|
{
|
||||||
|
#ifdef XP_WIN
|
||||||
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
|
if (!NS_IsMainThread()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UnloadEnterpriseRoots();
|
||||||
|
bool importEnterpriseRoots = Preferences::GetBool(kEnterpriseRootModePref,
|
||||||
|
false);
|
||||||
|
if (!importEnterpriseRoots) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DWORD flags = CERT_SYSTEM_STORE_LOCAL_MACHINE |
|
||||||
|
CERT_STORE_OPEN_EXISTING_FLAG |
|
||||||
|
CERT_STORE_READONLY_FLAG;
|
||||||
|
// The certificate store being opened should consist only of certificates
|
||||||
|
// added by a user or administrator and not any certificates that are part
|
||||||
|
// of Microsoft's root store program.
|
||||||
|
// The 3rd parameter to CertOpenStore should be NULL according to
|
||||||
|
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa376559%28v=vs.85%29.aspx
|
||||||
|
ScopedCertStore enterpriseRootStore(CertOpenStore(
|
||||||
|
CERT_STORE_PROV_SYSTEM_REGISTRY_W, 0, NULL, flags,
|
||||||
|
kWindowsDefaultRootStoreName));
|
||||||
|
if (!enterpriseRootStore.get()) {
|
||||||
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("failed to open enterprise root store"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MOZ_ASSERT(!mEnterpriseRoots);
|
||||||
|
mEnterpriseRoots.reset(CERT_NewCertList());
|
||||||
|
CERTCertTrust trust = {
|
||||||
|
CERTDB_TRUSTED_CA | CERTDB_VALID_CA | CERTDB_USER,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
PCCERT_CONTEXT certificate = nullptr;
|
||||||
|
uint32_t numImported = 0;
|
||||||
|
while ((certificate = CertFindCertificateInStore(enterpriseRootStore.get(),
|
||||||
|
X509_ASN_ENCODING, 0,
|
||||||
|
CERT_FIND_ANY, nullptr,
|
||||||
|
certificate))) {
|
||||||
|
if (!CertIsTrustAnchorForTLSServerAuth(certificate)) {
|
||||||
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
|
||||||
|
("skipping cert not trust anchor for TLS server auth"));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
UniqueCERTCertificate nssCertificate(
|
||||||
|
PCCERT_CONTEXTToCERTCertificate(certificate));
|
||||||
|
if (!nssCertificate) {
|
||||||
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't decode certificate"));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Don't import the Microsoft Family Safety root (this prevents the
|
||||||
|
// Enterprise Roots feature from interacting poorly with the Family
|
||||||
|
// Safety support).
|
||||||
|
UniquePORTString subjectName(
|
||||||
|
CERT_GetCommonName(&nssCertificate->subject));
|
||||||
|
if (nsCRT::strcmp(subjectName.get(), kMicrosoftFamilySafetyCN) == 0) {
|
||||||
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("skipping Family Safety Root"));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("imported %u roots", numImported));
|
||||||
|
#endif // XP_WIN
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
nsNSSComponent::LoadLoadableRoots()
|
nsNSSComponent::LoadLoadableRoots()
|
||||||
{
|
{
|
||||||
|
@ -1559,6 +1775,7 @@ nsNSSComponent::InitializeNSS()
|
||||||
LoadLoadableRoots();
|
LoadLoadableRoots();
|
||||||
|
|
||||||
MaybeEnableFamilySafetyCompatibility();
|
MaybeEnableFamilySafetyCompatibility();
|
||||||
|
MaybeImportEnterpriseRoots();
|
||||||
|
|
||||||
ConfigureTLSSessionIdentifiers();
|
ConfigureTLSSessionIdentifiers();
|
||||||
|
|
||||||
|
@ -1657,6 +1874,11 @@ nsNSSComponent::ShutdownNSS()
|
||||||
|
|
||||||
Preferences::RemoveObserver(this, "security.");
|
Preferences::RemoveObserver(this, "security.");
|
||||||
|
|
||||||
|
#ifdef XP_WIN
|
||||||
|
mFamilySafetyRoot = nullptr;
|
||||||
|
mEnterpriseRoots = nullptr;
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef MOZ_NO_SMART_CARDS
|
#ifndef MOZ_NO_SMART_CARDS
|
||||||
ShutdownSmartCardThreads();
|
ShutdownSmartCardThreads();
|
||||||
#endif
|
#endif
|
||||||
|
@ -1800,6 +2022,8 @@ nsNSSComponent::Observe(nsISupports* aSubject, const char* aTopic,
|
||||||
MutexAutoLock lock(mutex);
|
MutexAutoLock lock(mutex);
|
||||||
mContentSigningRootHash =
|
mContentSigningRootHash =
|
||||||
Preferences::GetString("security.content.signature.root_hash");
|
Preferences::GetString("security.content.signature.root_hash");
|
||||||
|
} else if (prefName.Equals(kEnterpriseRootModePref)) {
|
||||||
|
MaybeImportEnterpriseRoots();
|
||||||
} else {
|
} else {
|
||||||
clearSessionCache = false;
|
clearSessionCache = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,18 +7,25 @@
|
||||||
#ifndef _nsNSSComponent_h_
|
#ifndef _nsNSSComponent_h_
|
||||||
#define _nsNSSComponent_h_
|
#define _nsNSSComponent_h_
|
||||||
|
|
||||||
|
#include "ScopedNSSTypes.h"
|
||||||
|
#include "SharedCertVerifier.h"
|
||||||
#include "mozilla/Mutex.h"
|
#include "mozilla/Mutex.h"
|
||||||
#include "mozilla/RefPtr.h"
|
#include "mozilla/RefPtr.h"
|
||||||
#include "nsCOMPtr.h"
|
#include "nsCOMPtr.h"
|
||||||
#include "nsIStringBundle.h"
|
|
||||||
#include "nsIObserver.h"
|
#include "nsIObserver.h"
|
||||||
|
#include "nsIStringBundle.h"
|
||||||
#include "nsNSSCallbacks.h"
|
#include "nsNSSCallbacks.h"
|
||||||
#include "SharedCertVerifier.h"
|
|
||||||
#include "prerror.h"
|
#include "prerror.h"
|
||||||
#include "sslt.h"
|
#include "sslt.h"
|
||||||
|
|
||||||
|
#ifdef XP_WIN
|
||||||
|
#include "windows.h" // this needs to be before the following includes
|
||||||
|
#include "wincrypt.h"
|
||||||
|
#endif // XP_WIN
|
||||||
|
|
||||||
class nsIDOMWindow;
|
class nsIDOMWindow;
|
||||||
class nsIPrompt;
|
class nsIPrompt;
|
||||||
|
class nsIX509CertList;
|
||||||
class SmartCardThreadList;
|
class SmartCardThreadList;
|
||||||
|
|
||||||
namespace mozilla { namespace psm {
|
namespace mozilla { namespace psm {
|
||||||
|
@ -87,6 +94,10 @@ public:
|
||||||
|
|
||||||
NS_IMETHOD IsCertContentSigningRoot(CERTCertificate* cert, bool& result) = 0;
|
NS_IMETHOD IsCertContentSigningRoot(CERTCertificate* cert, bool& result) = 0;
|
||||||
|
|
||||||
|
#ifdef XP_WIN
|
||||||
|
NS_IMETHOD GetEnterpriseRoots(nsIX509CertList** enterpriseRoots) = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
virtual ::already_AddRefed<mozilla::psm::SharedCertVerifier>
|
virtual ::already_AddRefed<mozilla::psm::SharedCertVerifier>
|
||||||
GetDefaultCertVerifier() = 0;
|
GetDefaultCertVerifier() = 0;
|
||||||
};
|
};
|
||||||
|
@ -141,6 +152,10 @@ public:
|
||||||
|
|
||||||
NS_IMETHOD IsCertContentSigningRoot(CERTCertificate* cert, bool& result) override;
|
NS_IMETHOD IsCertContentSigningRoot(CERTCertificate* cert, bool& result) override;
|
||||||
|
|
||||||
|
#ifdef XP_WIN
|
||||||
|
NS_IMETHOD GetEnterpriseRoots(nsIX509CertList** enterpriseRoots) override;
|
||||||
|
#endif
|
||||||
|
|
||||||
::already_AddRefed<mozilla::psm::SharedCertVerifier>
|
::already_AddRefed<mozilla::psm::SharedCertVerifier>
|
||||||
GetDefaultCertVerifier() override;
|
GetDefaultCertVerifier() override;
|
||||||
|
|
||||||
|
@ -171,6 +186,20 @@ private:
|
||||||
|
|
||||||
void DoProfileBeforeChange();
|
void DoProfileBeforeChange();
|
||||||
|
|
||||||
|
void MaybeEnableFamilySafetyCompatibility();
|
||||||
|
void MaybeImportEnterpriseRoots();
|
||||||
|
#ifdef XP_WIN
|
||||||
|
nsresult MaybeImportFamilySafetyRoot(PCCERT_CONTEXT certificate,
|
||||||
|
bool& wasFamilySafetyRoot);
|
||||||
|
nsresult LoadFamilySafetyRoot();
|
||||||
|
void UnloadFamilySafetyRoot();
|
||||||
|
|
||||||
|
void UnloadEnterpriseRoots();
|
||||||
|
|
||||||
|
mozilla::UniqueCERTCertificate mFamilySafetyRoot;
|
||||||
|
mozilla::UniqueCERTCertList mEnterpriseRoots;
|
||||||
|
#endif // XP_WIN
|
||||||
|
|
||||||
mozilla::Mutex mutex;
|
mozilla::Mutex mutex;
|
||||||
|
|
||||||
nsCOMPtr<nsIStringBundle> mPIPNSSBundle;
|
nsCOMPtr<nsIStringBundle> mPIPNSSBundle;
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
||||||
|
// 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/.
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// Tests enterprise root certificate support. When configured to do so, the
|
||||||
|
// platform will attempt to find and import enterprise root certificates. This
|
||||||
|
// feature is specific to Windows.
|
||||||
|
|
||||||
|
do_get_profile(); // must be called before getting nsIX509CertDB
|
||||||
|
|
||||||
|
function check_no_enterprise_roots_imported(certDB, dbKey = undefined) {
|
||||||
|
let enterpriseRoots = certDB.getEnterpriseRoots();
|
||||||
|
equal(enterpriseRoots, null, "should not have imported any enterprise roots");
|
||||||
|
if (dbKey) {
|
||||||
|
let cert = certDB.findCertByDBKey(dbKey);
|
||||||
|
// If the garbage-collector hasn't run, there may be reachable copies of
|
||||||
|
// imported enterprise root certificates. If so, they shouldn't be trusted
|
||||||
|
// to issue TLS server auth certificates.
|
||||||
|
if (cert) {
|
||||||
|
ok(!certDB.isCertTrusted(cert, Ci.nsIX509Cert.CA_CERT,
|
||||||
|
Ci.nsIX509CertDB.TRUSTED_SSL),
|
||||||
|
"previously-imported enterprise root shouldn't be trusted to issue " +
|
||||||
|
"TLS server auth certificates");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function check_some_enterprise_roots_imported(certDB) {
|
||||||
|
let enterpriseRoots = certDB.getEnterpriseRoots();
|
||||||
|
notEqual(enterpriseRoots, null, "should have imported some enterprise roots");
|
||||||
|
let enumerator = enterpriseRoots.getEnumerator();
|
||||||
|
let foundNonBuiltIn = false;
|
||||||
|
let savedDBKey = null;
|
||||||
|
while (enumerator.hasMoreElements()) {
|
||||||
|
let cert = enumerator.getNext().QueryInterface(Ci.nsIX509Cert);
|
||||||
|
if (!cert.isBuiltInRoot && !savedDBKey) {
|
||||||
|
foundNonBuiltIn = true;
|
||||||
|
savedDBKey = cert.dbKey;
|
||||||
|
do_print("saving dbKey from " + cert.commonName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ok(foundNonBuiltIn, "should have found non-built-in root");
|
||||||
|
return savedDBKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
function run_test() {
|
||||||
|
let certDB = Cc["@mozilla.org/security/x509certdb;1"]
|
||||||
|
.getService(Ci.nsIX509CertDB);
|
||||||
|
Services.prefs.setBoolPref("security.enterprise_roots.enabled", false);
|
||||||
|
check_no_enterprise_roots_imported(certDB);
|
||||||
|
Services.prefs.setBoolPref("security.enterprise_roots.enabled", true);
|
||||||
|
let savedDBKey = check_some_enterprise_roots_imported(certDB);
|
||||||
|
Services.prefs.setBoolPref("security.enterprise_roots.enabled", false);
|
||||||
|
check_no_enterprise_roots_imported(certDB, savedDBKey);
|
||||||
|
}
|
|
@ -57,6 +57,8 @@ skip-if = toolkit == 'android' || buildapp == 'b2g'
|
||||||
[test_constructX509FromBase64.js]
|
[test_constructX509FromBase64.js]
|
||||||
[test_content_signing.js]
|
[test_content_signing.js]
|
||||||
[test_datasignatureverifier.js]
|
[test_datasignatureverifier.js]
|
||||||
|
[test_enterprise_roots.js]
|
||||||
|
skip-if = os != 'win' # tests a Windows-specific feature
|
||||||
[test_ev_certs.js]
|
[test_ev_certs.js]
|
||||||
run-sequentially = hardcoded ports
|
run-sequentially = hardcoded ports
|
||||||
[test_getchain.js]
|
[test_getchain.js]
|
||||||
|
|
Загрузка…
Ссылка в новой задаче