bug 1300420 - add enterprise root support for OS X r=spohl,franziskus

If the preference security.enterprise_roots.enabled is set to true, the platform will import trusted TLS certificates from the OS X keystore.

Differential Revision: https://phabricator.services.mozilla.com/D2169

--HG--
extra : moz-landing-system : lando
This commit is contained in:
David Keeler 2018-07-20 19:28:09 +00:00
parent 96e550bb6b
commit 85865937f5
4 changed files with 123 additions and 8 deletions

View File

@ -6,14 +6,19 @@
#include "EnterpriseRoots.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/Logging.h"
#include "mozilla/Unused.h"
#ifdef XP_MACOSX
#include <Security/Security.h>
#endif // XP_MACOSX
extern LazyLogModule gPIPNSSLog;
using namespace mozilla;
#ifdef XP_WIN
const wchar_t* kWindowsDefaultRootStoreName = L"ROOT";
NS_NAMED_LITERAL_CSTRING(kMicrosoftFamilySafetyCN, "Microsoft Family Safety");
@ -118,6 +123,10 @@ GatherEnterpriseRootsForLocation(DWORD locationFlag, UniqueCERTCertList& roots)
locationFlag == CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE)) {
return;
}
MOZ_ASSERT(roots, "roots unexpectedly NULL?");
if (!roots) {
return;
}
DWORD flags = locationFlag |
CERT_STORE_OPEN_EXISTING_FLAG |
@ -160,10 +169,6 @@ GatherEnterpriseRootsForLocation(DWORD locationFlag, UniqueCERTCertList& roots)
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("skipping Family Safety Root"));
continue;
}
MOZ_ASSERT(roots, "roots unexpectedly NULL?");
if (!roots) {
return;
}
if (CERT_AddCertToListTail(roots.get(), nssCertificate.get())
!= SECSuccess) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't add cert to list"));
@ -188,6 +193,105 @@ GatherEnterpriseRootsWindows(UniqueCERTCertList& roots)
}
#endif // XP_WIN
#ifdef XP_MACOSX
template<typename T>
class ScopedCFType
{
public:
explicit ScopedCFType(T value)
: mValue(value)
{
}
~ScopedCFType() { CFRelease((CFTypeRef)mValue); }
T get() { return mValue; }
private:
T mValue;
};
OSStatus
GatherEnterpriseRootsOSX(UniqueCERTCertList& roots)
{
MOZ_ASSERT(roots, "roots unexpectedly NULL?");
if (!roots) {
return errSecBadReq;
}
// The following builds a search dictionary corresponding to:
// { class: "certificate",
// match limit: "match all",
// policy: "SSL (TLS)",
// only include trusted certificates: true }
// This operates on items that have been added to the keychain and thus gives
// us all 3rd party certificates that have been trusted for SSL (TLS), which
// is what we want (thus we don't import built-in root certificates that ship
// with the OS).
const CFStringRef keys[] = { kSecClass,
kSecMatchLimit,
kSecMatchPolicy,
kSecMatchTrustedOnly };
// https://developer.apple.com/documentation/security/1392592-secpolicycreatessl
ScopedCFType<SecPolicyRef> sslPolicy(SecPolicyCreateSSL(true, nullptr));
const void* values[] = { kSecClassCertificate,
kSecMatchLimitAll,
sslPolicy.get(),
kCFBooleanTrue };
static_assert(ArrayLength(keys) == ArrayLength(values),
"mismatched SecItemCopyMatching key/value array sizes");
// https://developer.apple.com/documentation/corefoundation/1516782-cfdictionarycreate
ScopedCFType<CFDictionaryRef> searchDictionary(
CFDictionaryCreate(nullptr, (const void**)&keys, (const void**)&values,
ArrayLength(keys), &kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
CFTypeRef items;
// https://developer.apple.com/documentation/security/1398306-secitemcopymatching
OSStatus rv = SecItemCopyMatching(searchDictionary.get(), &items);
if (rv != errSecSuccess) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("SecItemCopyMatching failed"));
return rv;
}
// If given a match limit greater than 1 (which we did), SecItemCopyMatching
// returns a CFArrayRef.
ScopedCFType<CFArrayRef> arr(reinterpret_cast<CFArrayRef>(items));
CFIndex count = CFArrayGetCount(arr.get());
uint32_t numImported = 0;
for (CFIndex i = 0; i < count; i++) {
const CFTypeRef c = CFArrayGetValueAtIndex(arr.get(), i);
// Because we asked for certificates, each CFTypeRef in the array is really
// a SecCertificateRef.
const SecCertificateRef s = (const SecCertificateRef)c;
ScopedCFType<CFDataRef> der(SecCertificateCopyData(s));
const unsigned char* ptr = CFDataGetBytePtr(der.get());
unsigned int len = CFDataGetLength(der.get());
SECItem derItem = {
siBuffer,
const_cast<unsigned char*>(ptr),
len,
};
UniqueCERTCertificate cert(
CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &derItem,
nullptr, // nickname unnecessary
false, // not permanent
true)); // copy DER
if (!cert) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't decode 3rd party root certificate"));
continue;
}
UniquePORTString subjectName(CERT_GetCommonName(&cert->subject));
if (CERT_AddCertToListTail(roots.get(), cert.get()) != SECSuccess) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't add cert to list"));
continue;
}
numImported++;
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("Imported '%s'", subjectName.get()));
mozilla::Unused << cert.release(); // owned by roots
}
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("imported %u roots", numImported));
return errSecSuccess;
}
#endif // XP_MACOSX
nsresult
GatherEnterpriseRoots(UniqueCERTCertList& result)
{
@ -198,6 +302,12 @@ GatherEnterpriseRoots(UniqueCERTCertList& result)
#ifdef XP_WIN
GatherEnterpriseRootsWindows(roots);
#endif // XP_WIN
#ifdef XP_MACOSX
OSStatus rv = GatherEnterpriseRootsOSX(roots);
if (rv != errSecSuccess) {
return NS_ERROR_FAILURE;
}
#endif // XP_MACOSX
result = std::move(roots);
return NS_OK;
}

View File

@ -138,6 +138,11 @@ UNIFIED_SOURCES += [
'TransportSecurityInfo.cpp',
]
if CONFIG['OS_ARCH'] == 'Darwin':
OS_LIBS += [
'-framework Security'
]
IPDL_SOURCES += [
'PPSMContentDownloader.ipdl',
]

View File

@ -1903,13 +1903,11 @@ nsNSSComponent::InitializeNSS()
mMitmDetecionEnabled =
Preferences::GetBool("security.pki.mitm_canary_issuer.enabled", true);
#ifdef XP_WIN
nsCOMPtr<nsINSSComponent> handle(this);
NS_DispatchToCurrentThread(NS_NewRunnableFunction("nsNSSComponent::TrustLoaded3rdPartyRoots",
[handle]() {
MOZ_ALWAYS_SUCCEEDS(handle->TrustLoaded3rdPartyRoots());
}));
#endif // XP_WIN
// TLSServerSocket may be run with the session cache enabled. It is
// necessary to call this once before that can happen. This specifies a

View File

@ -84,7 +84,9 @@ run-sequentially = hardcoded ports
skip-if = toolkit == 'android'
[test_der.js]
[test_enterprise_roots.js]
skip-if = os != 'win' # tests a Windows-specific feature
# This feature is implemented for Windows and OS X. However, we don't currently
# have a way to test it on OS X.
skip-if = os != 'win'
[test_ev_certs.js]
tags = blocklist
run-sequentially = hardcoded ports