1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "EnterpriseRoots.h"
9 #include "PKCS11ModuleDB.h"
10 #include "mozilla/ArrayUtils.h"
11 #include "mozilla/IntegerPrintfMacros.h"
12 #include "mozilla/Casting.h"
13 #include "mozilla/Logging.h"
14 #include "mozilla/Unused.h"
15 #include "mozpkix/Result.h"
17 #include "nsThreadUtils.h"
19 #ifdef MOZ_WIDGET_ANDROID
20 # include "mozilla/java/EnterpriseRootsWrappers.h"
21 #endif // MOZ_WIDGET_ANDROID
24 # include <Security/Security.h>
25 # include "KeychainSecret.h"
30 # include <wincrypt.h>
33 extern mozilla::LazyLogModule gPIPNSSLog
;
35 using namespace mozilla
;
38 void EnterpriseCert::CopyBytes(nsTArray
<uint8_t>& dest
) const {
42 pkix::Result
EnterpriseCert::GetInput(pkix::Input
& input
) const {
43 return input
.Init(mDER
.Elements(), mDER
.Length());
46 bool EnterpriseCert::GetIsRoot() const { return mIsRoot
; }
48 bool EnterpriseCert::IsKnownRoot(UniqueSECMODModule
& rootsModule
) {
53 SECItem certItem
= {siBuffer
, mDER
.Elements(),
54 static_cast<unsigned int>(mDER
.Length())};
55 AutoSECMODListReadLock lock
;
56 for (int i
= 0; i
< rootsModule
->slotCount
; i
++) {
57 PK11SlotInfo
* slot
= rootsModule
->slots
[i
];
58 if (PK11_FindEncodedCertInSlot(slot
, &certItem
, nullptr) !=
67 struct CertStoreLocation
{
71 CertStoreLocation(const wchar_t* name
, bool isRoot
)
72 : mName(name
), mIsRoot(isRoot
) {}
75 // The documentation doesn't make this clear, but the certificate location
76 // identified by "ROOT" contains trusted root certificates. The certificate
77 // location identified by "CA" contains intermediate certificates.
78 MOZ_RUNINIT
const CertStoreLocation kCertStoreLocations
[] = {
79 CertStoreLocation(L
"ROOT", true), CertStoreLocation(L
"CA", false)};
81 // Because HCERTSTORE is just a typedef void*, we can't use any of the nice
82 // scoped or unique pointer templates. To elaborate, any attempt would
83 // instantiate those templates with T = void. When T gets used in the context
84 // of T&, this results in void&, which isn't legal.
85 class ScopedCertStore final
{
87 explicit ScopedCertStore(HCERTSTORE certstore
) : certstore(certstore
) {}
89 ~ScopedCertStore() { CertCloseStore(certstore
, 0); }
91 HCERTSTORE
get() { return certstore
; }
94 ScopedCertStore(const ScopedCertStore
&) = delete;
95 ScopedCertStore
& operator=(const ScopedCertStore
&) = delete;
99 // To determine if a certificate would be useful when verifying a server
100 // certificate for TLS server auth, Windows provides the function
101 // `CertGetEnhancedKeyUsage`, which combines the extended key usage extension
102 // with something called "enhanced key usage", which appears to be a Microsoft
104 static bool CertCanBeUsedForTLSServerAuth(PCCERT_CONTEXT certificate
) {
106 if (!CertGetEnhancedKeyUsage(certificate
, 0, NULL
, &usageSize
)) {
109 nsTArray
<uint8_t> usageBytes
;
110 usageBytes
.SetLength(usageSize
);
111 PCERT_ENHKEY_USAGE
usage(
112 reinterpret_cast<PCERT_ENHKEY_USAGE
>(usageBytes
.Elements()));
113 if (!CertGetEnhancedKeyUsage(certificate
, 0, usage
, &usageSize
)) {
116 // https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certgetenhancedkeyusage:
117 // "If the cUsageIdentifier member is zero, the certificate might be valid
118 // for all uses or the certificate might have no valid uses. The return from
119 // a call to GetLastError can be used to determine whether the certificate is
120 // good for all uses or for none. If GetLastError returns CRYPT_E_NOT_FOUND,
121 // the certificate is good for all uses. If it returns zero, the certificate
122 // has no valid uses."
123 if (usage
->cUsageIdentifier
== 0) {
124 return GetLastError() == static_cast<DWORD
>(CRYPT_E_NOT_FOUND
);
126 for (DWORD i
= 0; i
< usage
->cUsageIdentifier
; i
++) {
127 if (!nsCRT::strcmp(usage
->rgpszUsageIdentifier
[i
],
128 szOID_PKIX_KP_SERVER_AUTH
) ||
129 !nsCRT::strcmp(usage
->rgpszUsageIdentifier
[i
],
130 szOID_ANY_ENHANCED_KEY_USAGE
)) {
137 // Loads the enterprise roots at the registry location corresponding to the
138 // given location flag.
139 // Supported flags are:
140 // CERT_SYSTEM_STORE_LOCAL_MACHINE
141 // (for HKLM\SOFTWARE\Microsoft\SystemCertificates)
142 // CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY
143 // (for HKLM\SOFTWARE\Policy\Microsoft\SystemCertificates)
144 // CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE
145 // (for HKLM\SOFTWARE\Microsoft\EnterpriseCertificates)
146 // CERT_SYSTEM_STORE_CURRENT_USER
147 // (for HKCU\SOFTWARE\Microsoft\SystemCertificates)
148 // CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY
149 // (for HKCU\SOFTWARE\Policy\Microsoft\SystemCertificates)
150 static void GatherEnterpriseCertsForLocation(DWORD locationFlag
,
151 nsTArray
<EnterpriseCert
>& certs
,
152 UniqueSECMODModule
& rootsModule
) {
153 MOZ_ASSERT(locationFlag
== CERT_SYSTEM_STORE_LOCAL_MACHINE
||
154 locationFlag
== CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY
||
155 locationFlag
== CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE
||
156 locationFlag
== CERT_SYSTEM_STORE_CURRENT_USER
||
157 locationFlag
== CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY
,
158 "unexpected locationFlag for GatherEnterpriseRootsForLocation");
159 if (!(locationFlag
== CERT_SYSTEM_STORE_LOCAL_MACHINE
||
160 locationFlag
== CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY
||
161 locationFlag
== CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE
||
162 locationFlag
== CERT_SYSTEM_STORE_CURRENT_USER
||
163 locationFlag
== CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY
)) {
168 locationFlag
| CERT_STORE_OPEN_EXISTING_FLAG
| CERT_STORE_READONLY_FLAG
;
169 // The certificate store being opened should consist only of certificates
170 // added by a user or administrator and not any certificates that are part
171 // of Microsoft's root store program.
172 // The 3rd parameter to CertOpenStore should be NULL according to
173 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa376559%28v=vs.85%29.aspx
174 for (const auto& location
: kCertStoreLocations
) {
175 ScopedCertStore
certStore(CertOpenStore(CERT_STORE_PROV_SYSTEM_REGISTRY_W
,
176 0, NULL
, flags
, location
.mName
));
177 if (!certStore
.get()) {
178 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
179 ("failed to open certificate store"));
182 PCCERT_CONTEXT certificate
= nullptr;
183 uint32_t numImported
= 0;
184 while ((certificate
= CertFindCertificateInStore(
185 certStore
.get(), X509_ASN_ENCODING
, 0, CERT_FIND_ANY
, nullptr,
187 if (!CertCanBeUsedForTLSServerAuth(certificate
)) {
188 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
189 ("skipping cert not relevant for TLS server auth"));
192 EnterpriseCert
enterpriseCert(certificate
->pbCertEncoded
,
193 certificate
->cbCertEncoded
,
195 if (enterpriseCert
.GetIsRoot() ||
196 !enterpriseCert
.IsKnownRoot(rootsModule
)) {
197 certs
.AppendElement(std::move(enterpriseCert
));
200 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
201 ("skipping intermediate that is a known root cert"));
204 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
205 ("imported %u certs from %S", numImported
, location
.mName
));
209 static void GatherEnterpriseCertsWindows(nsTArray
<EnterpriseCert
>& certs
,
210 UniqueSECMODModule
& rootsModule
) {
211 GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_LOCAL_MACHINE
, certs
,
213 GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY
,
215 GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE
,
217 GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_CURRENT_USER
, certs
,
219 GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY
,
225 enum class CertificateTrustResult
{
226 CanUseAsIntermediate
,
231 ScopedCFType
<CFArrayRef
> GetCertificateTrustSettingsInDomain(
232 const SecCertificateRef certificate
, SecTrustSettingsDomain domain
) {
233 CFArrayRef trustSettingsRaw
;
235 SecTrustSettingsCopyTrustSettings(certificate
, domain
, &trustSettingsRaw
);
236 if (rv
!= errSecSuccess
|| !trustSettingsRaw
) {
237 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
238 (" SecTrustSettingsCopyTrustSettings failed (or not found) for "
243 ScopedCFType
<CFArrayRef
> trustSettings(trustSettingsRaw
);
244 return trustSettings
;
247 // This function processes trust settings returned by
248 // SecTrustSettingsCopyTrustSettings. See the documentation at
249 // https://developer.apple.com/documentation/security/1400261-sectrustsettingscopytrustsetting
250 // `trustSettings` is an array of CFDictionaryRef. Each dictionary may impose
252 CertificateTrustResult
ProcessCertificateTrustSettings(
253 ScopedCFType
<CFArrayRef
>& trustSettings
) {
254 // If the array is empty, the certificate is a trust anchor.
255 const CFIndex numTrustDictionaries
= CFArrayGetCount(trustSettings
.get());
256 if (numTrustDictionaries
== 0) {
257 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
258 (" empty trust settings -> trust anchor"));
259 return CertificateTrustResult::CanUseAsTrustAnchor
;
261 CertificateTrustResult currentTrustSettings
=
262 CertificateTrustResult::CanUseAsIntermediate
;
263 for (CFIndex i
= 0; i
< numTrustDictionaries
; i
++) {
264 CFDictionaryRef trustDictionary
= reinterpret_cast<CFDictionaryRef
>(
265 CFArrayGetValueAtIndex(trustSettings
.get(), i
));
266 // kSecTrustSettingsApplication specifies an external application that
267 // determines the certificate's trust settings.
268 // kSecTrustSettingsPolicyString appears to be a mechanism like name
270 // These are not supported, so conservatively assume this certificate is
271 // distrusted if either are present.
272 if (CFDictionaryContainsKey(trustDictionary
,
273 kSecTrustSettingsApplication
) ||
274 CFDictionaryContainsKey(trustDictionary
,
275 kSecTrustSettingsPolicyString
)) {
276 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
277 (" found unsupported policy -> assuming distrusted"));
278 return CertificateTrustResult::DoNotUse
;
281 // kSecTrustSettingsKeyUsage seems to be essentially the equivalent of the
282 // x509 keyUsage extension. For parity, we allow
283 // kSecTrustSettingsKeyUseSignature, kSecTrustSettingsKeyUseSignCert, and
284 // kSecTrustSettingsKeyUseAny.
285 if (CFDictionaryContainsKey(trustDictionary
, kSecTrustSettingsKeyUsage
)) {
286 CFNumberRef keyUsage
= (CFNumberRef
)CFDictionaryGetValue(
287 trustDictionary
, kSecTrustSettingsKeyUsage
);
288 int32_t keyUsageValue
;
290 CFNumberGetValue(keyUsage
, kCFNumberSInt32Type
, &keyUsageValue
) ||
292 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
293 (" no trust settings key usage or couldn't get value"));
294 return CertificateTrustResult::DoNotUse
;
296 switch ((uint64_t)keyUsageValue
) {
297 case kSecTrustSettingsKeyUseSignature
: // fall-through
298 case kSecTrustSettingsKeyUseSignCert
: // fall-through
299 case kSecTrustSettingsKeyUseAny
:
302 return CertificateTrustResult::DoNotUse
;
306 // If there is a specific policy, ensure that it's for the
307 // 'kSecPolicyAppleSSL' policy, which is the TLS server auth policy (i.e.
308 // x509 + domain name checking).
309 if (CFDictionaryContainsKey(trustDictionary
, kSecTrustSettingsPolicy
)) {
310 SecPolicyRef policy
= (SecPolicyRef
)CFDictionaryGetValue(
311 trustDictionary
, kSecTrustSettingsPolicy
);
313 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
314 (" kSecTrustSettingsPolicy present, but null?"));
317 ScopedCFType
<CFDictionaryRef
> policyProperties(
318 SecPolicyCopyProperties(policy
));
319 CFStringRef policyOid
= (CFStringRef
)CFDictionaryGetValue(
320 policyProperties
.get(), kSecPolicyOid
);
321 if (!CFEqual(policyOid
, kSecPolicyAppleSSL
)) {
322 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
, (" policy doesn't match"));
327 // By default, the trust setting result value is
328 // kSecTrustSettingsResultTrustRoot.
329 int32_t trustSettingsValue
= kSecTrustSettingsResultTrustRoot
;
330 if (CFDictionaryContainsKey(trustDictionary
, kSecTrustSettingsResult
)) {
331 CFNumberRef trustSetting
= (CFNumberRef
)CFDictionaryGetValue(
332 trustDictionary
, kSecTrustSettingsResult
);
333 if (!trustSetting
|| !CFNumberGetValue(trustSetting
, kCFNumberSInt32Type
,
334 &trustSettingsValue
)) {
335 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
336 (" no trust settings result or couldn't get value"));
340 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
341 (" trust setting: %d", trustSettingsValue
));
342 if (trustSettingsValue
== kSecTrustSettingsResultDeny
) {
343 return CertificateTrustResult::DoNotUse
;
345 if (trustSettingsValue
== kSecTrustSettingsResultTrustRoot
||
346 trustSettingsValue
== kSecTrustSettingsResultTrustAsRoot
) {
347 currentTrustSettings
= CertificateTrustResult::CanUseAsTrustAnchor
;
350 return currentTrustSettings
;
353 CertificateTrustResult
GetCertificateTrustResult(
354 const SecCertificateRef certificate
) {
355 ScopedCFType
<CFStringRef
> subject(
356 SecCertificateCopySubjectSummary(certificate
));
357 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
358 ("determining trust for '%s'",
359 CFStringGetCStringPtr(subject
.get(), kCFStringEncodingUTF8
)));
360 // There are three trust settings domains: kSecTrustSettingsDomainUser,
361 // kSecTrustSettingsDomainAdmin, and kSecTrustSettingsDomainSystem. User
362 // overrides admin and admin overrides system. However, if the given
363 // certificate has trust settings in the system domain, it shipped with the
364 // OS, so we don't want to use it.
365 ScopedCFType
<CFArrayRef
> systemTrustSettings(
366 GetCertificateTrustSettingsInDomain(certificate
,
367 kSecTrustSettingsDomainSystem
));
368 if (systemTrustSettings
) {
369 return CertificateTrustResult::DoNotUse
;
372 // At this point, if there is no trust information regarding this
373 // certificate, it can be used as an intermediate.
374 CertificateTrustResult certificateTrustResult
=
375 CertificateTrustResult::CanUseAsIntermediate
;
377 // Process trust information in the user domain, if any.
378 ScopedCFType
<CFArrayRef
> userTrustSettings(
379 GetCertificateTrustSettingsInDomain(certificate
,
380 kSecTrustSettingsDomainUser
));
381 if (userTrustSettings
) {
382 certificateTrustResult
= ProcessCertificateTrustSettings(userTrustSettings
);
383 // If there is definite information one way or another (either indicating
384 // this is a trusted root or a distrusted certificate), use that
386 if (certificateTrustResult
!=
387 CertificateTrustResult::CanUseAsIntermediate
) {
388 return certificateTrustResult
;
392 // Process trust information in the admin domain, if any.
393 ScopedCFType
<CFArrayRef
> adminTrustSettings(
394 GetCertificateTrustSettingsInDomain(certificate
,
395 kSecTrustSettingsDomainAdmin
));
396 if (adminTrustSettings
) {
397 certificateTrustResult
=
398 ProcessCertificateTrustSettings(adminTrustSettings
);
401 // Use whatever result we ended up with.
402 return certificateTrustResult
;
405 OSStatus
GatherEnterpriseCertsMacOS(nsTArray
<EnterpriseCert
>& certs
,
406 UniqueSECMODModule
& rootsModule
) {
407 // The following builds a search dictionary corresponding to:
408 // { class: "certificate",
409 // match limit: "match all" }
410 // This operates on items that have been added to the keychain and thus gives
411 // us all 3rd party certificates. Unfortunately, if a root that shipped with
412 // the OS has had its trust settings changed, it can also be returned from
413 // this query. Further work (below) filters such certificates out.
414 const CFStringRef keys
[] = {kSecClass
, kSecMatchLimit
};
415 const void* values
[] = {kSecClassCertificate
, kSecMatchLimitAll
};
416 static_assert(std::size(keys
) == std::size(values
),
417 "mismatched SecItemCopyMatching key/value array sizes");
418 // https://developer.apple.com/documentation/corefoundation/1516782-cfdictionarycreate
419 ScopedCFType
<CFDictionaryRef
> searchDictionary(CFDictionaryCreate(
420 nullptr, (const void**)&keys
, (const void**)&values
, std::size(keys
),
421 &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
));
423 // https://developer.apple.com/documentation/security/1398306-secitemcopymatching
424 OSStatus rv
= SecItemCopyMatching(searchDictionary
.get(), &items
);
425 if (rv
!= errSecSuccess
) {
426 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
, ("SecItemCopyMatching failed"));
429 // If given a match limit greater than 1 (which we did), SecItemCopyMatching
430 // returns a CFArrayRef.
431 ScopedCFType
<CFArrayRef
> arr(reinterpret_cast<CFArrayRef
>(items
));
432 CFIndex count
= CFArrayGetCount(arr
.get());
433 uint32_t numImported
= 0;
434 for (CFIndex i
= 0; i
< count
; i
++) {
435 // Because we asked for certificates, each CFTypeRef in the array is really
436 // a SecCertificateRef.
437 const SecCertificateRef certificate
=
438 (const SecCertificateRef
)CFArrayGetValueAtIndex(arr
.get(), i
);
439 CertificateTrustResult certificateTrustResult
=
440 GetCertificateTrustResult(certificate
);
441 if (certificateTrustResult
== CertificateTrustResult::DoNotUse
) {
442 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
, ("skipping distrusted cert"));
445 ScopedCFType
<CFDataRef
> der(SecCertificateCopyData(certificate
));
447 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
448 ("couldn't get bytes of certificate?"));
452 certificateTrustResult
== CertificateTrustResult::CanUseAsTrustAnchor
;
453 EnterpriseCert
enterpriseCert(CFDataGetBytePtr(der
.get()),
454 CFDataGetLength(der
.get()), isRoot
);
455 if (enterpriseCert
.GetIsRoot() ||
456 !enterpriseCert
.IsKnownRoot(rootsModule
)) {
457 certs
.AppendElement(std::move(enterpriseCert
));
459 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
460 ("importing as %s", isRoot
? "root" : "intermediate"));
462 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
463 ("skipping intermediate that is a known root cert"));
466 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
, ("imported %u certs", numImported
));
467 return errSecSuccess
;
471 #ifdef MOZ_WIDGET_ANDROID
472 void GatherEnterpriseCertsAndroid(nsTArray
<EnterpriseCert
>& certs
,
473 UniqueSECMODModule
& rootsModule
) {
474 if (!jni::IsAvailable()) {
475 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
, ("JNI not available"));
478 jni::ObjectArray::LocalRef roots
=
479 java::EnterpriseRoots::GatherEnterpriseRoots();
480 uint32_t numImported
= 0;
481 for (size_t i
= 0; i
< roots
->Length(); i
++) {
482 jni::ByteArray::LocalRef root
= roots
->GetElement(i
);
483 // Currently we treat all certificates gleaned from the Android
484 // CA store as roots.
485 EnterpriseCert
enterpriseCert(
486 reinterpret_cast<uint8_t*>(root
->GetElements().Elements()),
487 root
->Length(), true);
488 if (enterpriseCert
.GetIsRoot() ||
489 !enterpriseCert
.IsKnownRoot(rootsModule
)) {
490 certs
.AppendElement(std::move(enterpriseCert
));
493 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
494 ("skipping intermediate that is a known root cert"));
497 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
, ("imported %u certs", numImported
));
499 #endif // MOZ_WIDGET_ANDROID
501 nsresult
GatherEnterpriseCerts(nsTArray
<EnterpriseCert
>& certs
) {
502 MOZ_ASSERT(!NS_IsMainThread());
503 if (NS_IsMainThread()) {
504 return NS_ERROR_NOT_SAME_THREAD
;
508 UniqueSECMODModule
rootsModule(SECMOD_FindModule(kRootModuleName
.get()));
510 GatherEnterpriseCertsWindows(certs
, rootsModule
);
513 OSStatus rv
= GatherEnterpriseCertsMacOS(certs
, rootsModule
);
514 if (rv
!= errSecSuccess
) {
515 return NS_ERROR_FAILURE
;
518 #ifdef MOZ_WIDGET_ANDROID
519 GatherEnterpriseCertsAndroid(certs
, rootsModule
);
520 #endif // MOZ_WIDGET_ANDROID