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 "mozilla/ArrayUtils.h"
10 #include "mozilla/Logging.h"
11 #include "mozilla/Unused.h"
12 #include "mozpkix/Result.h"
13 #include "nsNSSCertHelper.h"
14 #include "nsThreadUtils.h"
16 #ifdef MOZ_WIDGET_ANDROID
17 # include "mozilla/java/EnterpriseRootsWrappers.h"
18 #endif // MOZ_WIDGET_ANDROID
21 # include <Security/Security.h>
22 # include "KeychainSecret.h"
27 # include <wincrypt.h>
30 extern mozilla::LazyLogModule gPIPNSSLog
;
32 using namespace mozilla
;
34 void EnterpriseCert::CopyBytes(nsTArray
<uint8_t>& dest
) const {
38 pkix::Result
EnterpriseCert::GetInput(pkix::Input
& input
) const {
39 return input
.Init(mDER
.Elements(), mDER
.Length());
42 bool EnterpriseCert::GetIsRoot() const { return mIsRoot
; }
44 bool EnterpriseCert::IsKnownRoot(UniqueSECMODModule
& rootsModule
) {
49 SECItem certItem
= {siBuffer
, mDER
.Elements(),
50 static_cast<unsigned int>(mDER
.Length())};
51 AutoSECMODListReadLock lock
;
52 for (int i
= 0; i
< rootsModule
->slotCount
; i
++) {
53 PK11SlotInfo
* slot
= rootsModule
->slots
[i
];
54 if (PK11_FindEncodedCertInSlot(slot
, &certItem
, nullptr) !=
63 const wchar_t* kWindowsDefaultRootStoreNames
[] = {L
"ROOT", L
"CA"};
65 // Helper function to determine if the OS considers the given certificate to be
66 // a trust anchor for TLS server auth certificates. This is to be used in the
67 // context of importing what are presumed to be root certificates from the OS.
68 // If this function returns true but it turns out that the given certificate is
69 // in some way unsuitable to issue certificates, mozilla::pkix will never build
70 // a valid chain that includes the certificate, so importing it even if it
71 // isn't a valid CA poses no risk.
72 static void CertIsTrustAnchorForTLSServerAuth(PCCERT_CONTEXT certificate
,
73 bool& isTrusted
, bool& isRoot
) {
76 MOZ_ASSERT(certificate
);
81 PCCERT_CHAIN_CONTEXT pChainContext
= nullptr;
82 CERT_ENHKEY_USAGE enhkeyUsage
;
83 memset(&enhkeyUsage
, 0, sizeof(CERT_ENHKEY_USAGE
));
84 LPCSTR identifiers
[] = {
85 "1.3.6.1.5.5.7.3.1", // id-kp-serverAuth
87 enhkeyUsage
.cUsageIdentifier
= ArrayLength(identifiers
);
88 enhkeyUsage
.rgpszUsageIdentifier
=
89 const_cast<LPSTR
*>(identifiers
); // -Wwritable-strings
90 CERT_USAGE_MATCH certUsage
;
91 memset(&certUsage
, 0, sizeof(CERT_USAGE_MATCH
));
92 certUsage
.dwType
= USAGE_MATCH_TYPE_AND
;
93 certUsage
.Usage
= enhkeyUsage
;
94 CERT_CHAIN_PARA chainPara
;
95 memset(&chainPara
, 0, sizeof(CERT_CHAIN_PARA
));
96 chainPara
.cbSize
= sizeof(CERT_CHAIN_PARA
);
97 chainPara
.RequestedUsage
= certUsage
;
98 // Disable anything that could result in network I/O.
99 DWORD flags
= CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY
|
100 CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL
|
101 CERT_CHAIN_DISABLE_AUTH_ROOT_AUTO_UPDATE
|
102 CERT_CHAIN_DISABLE_AIA
;
103 if (!CertGetCertificateChain(nullptr, certificate
, nullptr, nullptr,
104 &chainPara
, flags
, nullptr, &pChainContext
)) {
105 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
, ("CertGetCertificateChain failed"));
108 isTrusted
= pChainContext
->TrustStatus
.dwErrorStatus
== CERT_TRUST_NO_ERROR
;
109 if (isTrusted
&& pChainContext
->cChain
> 0) {
110 // The so-called "final chain" is what we're after:
111 // https://docs.microsoft.com/en-us/windows/desktop/api/wincrypt/ns-wincrypt-_cert_chain_context
112 CERT_SIMPLE_CHAIN
* finalChain
=
113 pChainContext
->rgpChain
[pChainContext
->cChain
- 1];
114 // This is a root if the final chain consists of only one certificate (i.e.
116 isRoot
= finalChain
->cElement
== 1;
118 CertFreeCertificateChain(pChainContext
);
121 // Because HCERTSTORE is just a typedef void*, we can't use any of the nice
122 // scoped or unique pointer templates. To elaborate, any attempt would
123 // instantiate those templates with T = void. When T gets used in the context
124 // of T&, this results in void&, which isn't legal.
125 class ScopedCertStore final
{
127 explicit ScopedCertStore(HCERTSTORE certstore
) : certstore(certstore
) {}
129 ~ScopedCertStore() { CertCloseStore(certstore
, 0); }
131 HCERTSTORE
get() { return certstore
; }
134 ScopedCertStore(const ScopedCertStore
&) = delete;
135 ScopedCertStore
& operator=(const ScopedCertStore
&) = delete;
136 HCERTSTORE certstore
;
139 // Loads the enterprise roots at the registry location corresponding to the
140 // given location flag.
141 // Supported flags are:
142 // CERT_SYSTEM_STORE_LOCAL_MACHINE
143 // (for HKLM\SOFTWARE\Microsoft\SystemCertificates)
144 // CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY
145 // (for HKLM\SOFTWARE\Policy\Microsoft\SystemCertificates)
146 // CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE
147 // (for HKLM\SOFTWARE\Microsoft\EnterpriseCertificates)
148 // CERT_SYSTEM_STORE_CURRENT_USER
149 // (for HKCU\SOFTWARE\Microsoft\SystemCertificates)
150 // CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY
151 // (for HKCU\SOFTWARE\Policy\Microsoft\SystemCertificates)
152 static void GatherEnterpriseCertsForLocation(DWORD locationFlag
,
153 nsTArray
<EnterpriseCert
>& certs
,
154 UniqueSECMODModule
& rootsModule
) {
155 MOZ_ASSERT(locationFlag
== CERT_SYSTEM_STORE_LOCAL_MACHINE
||
156 locationFlag
== CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY
||
157 locationFlag
== CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE
||
158 locationFlag
== CERT_SYSTEM_STORE_CURRENT_USER
||
159 locationFlag
== CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY
,
160 "unexpected locationFlag for GatherEnterpriseRootsForLocation");
161 if (!(locationFlag
== CERT_SYSTEM_STORE_LOCAL_MACHINE
||
162 locationFlag
== CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY
||
163 locationFlag
== CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE
||
164 locationFlag
== CERT_SYSTEM_STORE_CURRENT_USER
||
165 locationFlag
== CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY
)) {
170 locationFlag
| CERT_STORE_OPEN_EXISTING_FLAG
| CERT_STORE_READONLY_FLAG
;
171 // The certificate store being opened should consist only of certificates
172 // added by a user or administrator and not any certificates that are part
173 // of Microsoft's root store program.
174 // The 3rd parameter to CertOpenStore should be NULL according to
175 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa376559%28v=vs.85%29.aspx
176 for (auto name
: kWindowsDefaultRootStoreNames
) {
177 ScopedCertStore
enterpriseRootStore(
178 CertOpenStore(CERT_STORE_PROV_SYSTEM_REGISTRY_W
, 0, NULL
, flags
, name
));
179 if (!enterpriseRootStore
.get()) {
180 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
181 ("failed to open enterprise root store"));
184 PCCERT_CONTEXT certificate
= nullptr;
185 uint32_t numImported
= 0;
186 while ((certificate
= CertFindCertificateInStore(
187 enterpriseRootStore
.get(), X509_ASN_ENCODING
, 0, CERT_FIND_ANY
,
188 nullptr, certificate
))) {
191 CertIsTrustAnchorForTLSServerAuth(certificate
, isTrusted
, isRoot
);
193 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
194 ("skipping cert not trusted for TLS server auth"));
197 EnterpriseCert
enterpriseCert(certificate
->pbCertEncoded
,
198 certificate
->cbCertEncoded
, isRoot
);
199 if (!enterpriseCert
.IsKnownRoot(rootsModule
)) {
200 certs
.AppendElement(std::move(enterpriseCert
));
203 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
, ("skipping known root cert"));
206 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
207 ("imported %u certs from %S", numImported
, name
));
211 static void GatherEnterpriseCertsWindows(nsTArray
<EnterpriseCert
>& certs
,
212 UniqueSECMODModule
& rootsModule
) {
213 GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_LOCAL_MACHINE
, certs
,
215 GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY
,
217 GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE
,
219 GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_CURRENT_USER
, certs
,
221 GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY
,
227 enum class CertificateTrustResult
{
228 CanUseAsIntermediate
,
233 ScopedCFType
<CFArrayRef
> GetCertificateTrustSettingsInDomain(
234 const SecCertificateRef certificate
, SecTrustSettingsDomain domain
) {
235 CFArrayRef trustSettingsRaw
;
237 SecTrustSettingsCopyTrustSettings(certificate
, domain
, &trustSettingsRaw
);
238 if (rv
!= errSecSuccess
|| !trustSettingsRaw
) {
239 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
240 (" SecTrustSettingsCopyTrustSettings failed (or not found) for "
245 ScopedCFType
<CFArrayRef
> trustSettings(trustSettingsRaw
);
246 return trustSettings
;
249 // This function processes trust settings returned by
250 // SecTrustSettingsCopyTrustSettings. See the documentation at
251 // https://developer.apple.com/documentation/security/1400261-sectrustsettingscopytrustsetting
252 // `trustSettings` is an array of CFDictionaryRef. Each dictionary may impose
254 CertificateTrustResult
ProcessCertificateTrustSettings(
255 ScopedCFType
<CFArrayRef
>& trustSettings
) {
256 // If the array is empty, the certificate is a trust anchor.
257 const CFIndex numTrustDictionaries
= CFArrayGetCount(trustSettings
.get());
258 if (numTrustDictionaries
== 0) {
259 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
260 (" empty trust settings -> trust anchor"));
261 return CertificateTrustResult::CanUseAsTrustAnchor
;
263 CertificateTrustResult currentTrustSettings
=
264 CertificateTrustResult::CanUseAsIntermediate
;
265 for (CFIndex i
= 0; i
< numTrustDictionaries
; i
++) {
266 CFDictionaryRef trustDictionary
= reinterpret_cast<CFDictionaryRef
>(
267 CFArrayGetValueAtIndex(trustSettings
.get(), i
));
268 // kSecTrustSettingsApplication specifies an external application that
269 // determines the certificate's trust settings.
270 // kSecTrustSettingsPolicyString appears to be a mechanism like name
272 // These are not supported, so conservatively assume this certificate is
273 // distrusted if either are present.
274 if (CFDictionaryContainsKey(trustDictionary
,
275 kSecTrustSettingsApplication
) ||
276 CFDictionaryContainsKey(trustDictionary
,
277 kSecTrustSettingsPolicyString
)) {
278 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
279 (" found unsupported policy -> assuming distrusted"));
280 return CertificateTrustResult::DoNotUse
;
283 // kSecTrustSettingsKeyUsage seems to be essentially the equivalent of the
284 // x509 keyUsage extension. For parity, we allow
285 // kSecTrustSettingsKeyUseSignature, kSecTrustSettingsKeyUseSignCert, and
286 // kSecTrustSettingsKeyUseAny.
287 if (CFDictionaryContainsKey(trustDictionary
, kSecTrustSettingsKeyUsage
)) {
288 CFNumberRef keyUsage
= (CFNumberRef
)CFDictionaryGetValue(
289 trustDictionary
, kSecTrustSettingsKeyUsage
);
290 int32_t keyUsageValue
;
292 CFNumberGetValue(keyUsage
, kCFNumberSInt32Type
, &keyUsageValue
) ||
294 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
295 (" no trust settings key usage or couldn't get value"));
296 return CertificateTrustResult::DoNotUse
;
298 switch ((uint64_t)keyUsageValue
) {
299 case kSecTrustSettingsKeyUseSignature
: // fall-through
300 case kSecTrustSettingsKeyUseSignCert
: // fall-through
301 case kSecTrustSettingsKeyUseAny
:
304 return CertificateTrustResult::DoNotUse
;
308 // If there is a specific policy, ensure that it's for the
309 // 'kSecPolicyAppleSSL' policy, which is the TLS server auth policy (i.e.
310 // x509 + domain name checking).
311 if (CFDictionaryContainsKey(trustDictionary
, kSecTrustSettingsPolicy
)) {
312 SecPolicyRef policy
= (SecPolicyRef
)CFDictionaryGetValue(
313 trustDictionary
, kSecTrustSettingsPolicy
);
315 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
316 (" kSecTrustSettingsPolicy present, but null?"));
319 ScopedCFType
<CFDictionaryRef
> policyProperties(
320 SecPolicyCopyProperties(policy
));
321 CFStringRef policyOid
= (CFStringRef
)CFDictionaryGetValue(
322 policyProperties
.get(), kSecPolicyOid
);
323 if (!CFEqual(policyOid
, kSecPolicyAppleSSL
)) {
324 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
, (" policy doesn't match"));
329 // By default, the trust setting result value is
330 // kSecTrustSettingsResultTrustRoot.
331 int32_t trustSettingsValue
= kSecTrustSettingsResultTrustRoot
;
332 if (CFDictionaryContainsKey(trustDictionary
, kSecTrustSettingsResult
)) {
333 CFNumberRef trustSetting
= (CFNumberRef
)CFDictionaryGetValue(
334 trustDictionary
, kSecTrustSettingsResult
);
335 if (!trustSetting
|| !CFNumberGetValue(trustSetting
, kCFNumberSInt32Type
,
336 &trustSettingsValue
)) {
337 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
338 (" no trust settings result or couldn't get value"));
342 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
343 (" trust setting: %d", trustSettingsValue
));
344 if (trustSettingsValue
== kSecTrustSettingsResultDeny
) {
345 return CertificateTrustResult::DoNotUse
;
347 if (trustSettingsValue
== kSecTrustSettingsResultTrustRoot
||
348 trustSettingsValue
== kSecTrustSettingsResultTrustAsRoot
) {
349 currentTrustSettings
= CertificateTrustResult::CanUseAsTrustAnchor
;
352 return currentTrustSettings
;
355 CertificateTrustResult
GetCertificateTrustResult(
356 const SecCertificateRef certificate
) {
357 ScopedCFType
<CFStringRef
> subject(
358 SecCertificateCopySubjectSummary(certificate
));
359 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
360 ("determining trust for '%s'",
361 CFStringGetCStringPtr(subject
.get(), kCFStringEncodingUTF8
)));
362 // There are three trust settings domains: kSecTrustSettingsDomainUser,
363 // kSecTrustSettingsDomainAdmin, and kSecTrustSettingsDomainSystem. User
364 // overrides admin and admin overrides system. However, if the given
365 // certificate has trust settings in the system domain, it shipped with the
366 // OS, so we don't want to use it.
367 ScopedCFType
<CFArrayRef
> systemTrustSettings(
368 GetCertificateTrustSettingsInDomain(certificate
,
369 kSecTrustSettingsDomainSystem
));
370 if (systemTrustSettings
) {
371 return CertificateTrustResult::DoNotUse
;
374 // At this point, if there is no trust information regarding this
375 // certificate, it can be used as an intermediate.
376 CertificateTrustResult certificateTrustResult
=
377 CertificateTrustResult::CanUseAsIntermediate
;
379 // Process trust information in the user domain, if any.
380 ScopedCFType
<CFArrayRef
> userTrustSettings(
381 GetCertificateTrustSettingsInDomain(certificate
,
382 kSecTrustSettingsDomainUser
));
383 if (userTrustSettings
) {
384 certificateTrustResult
= ProcessCertificateTrustSettings(userTrustSettings
);
385 // If there is definite information one way or another (either indicating
386 // this is a trusted root or a distrusted certificate), use that
388 if (certificateTrustResult
!=
389 CertificateTrustResult::CanUseAsIntermediate
) {
390 return certificateTrustResult
;
394 // Process trust information in the admin domain, if any.
395 ScopedCFType
<CFArrayRef
> adminTrustSettings(
396 GetCertificateTrustSettingsInDomain(certificate
,
397 kSecTrustSettingsDomainAdmin
));
398 if (adminTrustSettings
) {
399 certificateTrustResult
=
400 ProcessCertificateTrustSettings(adminTrustSettings
);
403 // Use whatever result we ended up with.
404 return certificateTrustResult
;
407 OSStatus
GatherEnterpriseCertsMacOS(nsTArray
<EnterpriseCert
>& certs
,
408 UniqueSECMODModule
& rootsModule
) {
409 // The following builds a search dictionary corresponding to:
410 // { class: "certificate",
411 // match limit: "match all" }
412 // This operates on items that have been added to the keychain and thus gives
413 // us all 3rd party certificates. Unfortunately, if a root that shipped with
414 // the OS has had its trust settings changed, it can also be returned from
415 // this query. Further work (below) filters such certificates out.
416 const CFStringRef keys
[] = {kSecClass
, kSecMatchLimit
};
417 const void* values
[] = {kSecClassCertificate
, kSecMatchLimitAll
};
418 static_assert(ArrayLength(keys
) == ArrayLength(values
),
419 "mismatched SecItemCopyMatching key/value array sizes");
420 // https://developer.apple.com/documentation/corefoundation/1516782-cfdictionarycreate
421 ScopedCFType
<CFDictionaryRef
> searchDictionary(CFDictionaryCreate(
422 nullptr, (const void**)&keys
, (const void**)&values
, ArrayLength(keys
),
423 &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
));
425 // https://developer.apple.com/documentation/security/1398306-secitemcopymatching
426 OSStatus rv
= SecItemCopyMatching(searchDictionary
.get(), &items
);
427 if (rv
!= errSecSuccess
) {
428 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
, ("SecItemCopyMatching failed"));
431 // If given a match limit greater than 1 (which we did), SecItemCopyMatching
432 // returns a CFArrayRef.
433 ScopedCFType
<CFArrayRef
> arr(reinterpret_cast<CFArrayRef
>(items
));
434 CFIndex count
= CFArrayGetCount(arr
.get());
435 uint32_t numImported
= 0;
436 for (CFIndex i
= 0; i
< count
; i
++) {
437 // Because we asked for certificates, each CFTypeRef in the array is really
438 // a SecCertificateRef.
439 const SecCertificateRef certificate
=
440 (const SecCertificateRef
)CFArrayGetValueAtIndex(arr
.get(), i
);
441 CertificateTrustResult certificateTrustResult
=
442 GetCertificateTrustResult(certificate
);
443 if (certificateTrustResult
== CertificateTrustResult::DoNotUse
) {
444 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
, ("skipping distrusted cert"));
447 ScopedCFType
<CFDataRef
> der(SecCertificateCopyData(certificate
));
449 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
450 ("couldn't get bytes of certificate?"));
454 certificateTrustResult
== CertificateTrustResult::CanUseAsTrustAnchor
;
455 EnterpriseCert
enterpriseCert(CFDataGetBytePtr(der
.get()),
456 CFDataGetLength(der
.get()), isRoot
);
457 if (!enterpriseCert
.IsKnownRoot(rootsModule
)) {
458 certs
.AppendElement(std::move(enterpriseCert
));
460 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
461 ("importing as %s", isRoot
? "root" : "intermediate"));
463 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
, ("skipping 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
.IsKnownRoot(rootsModule
)) {
489 certs
.AppendElement(std::move(enterpriseCert
));
492 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
, ("skipping known root cert"));
495 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
, ("imported %u certs", numImported
));
497 #endif // MOZ_WIDGET_ANDROID
499 nsresult
GatherEnterpriseCerts(nsTArray
<EnterpriseCert
>& certs
) {
500 MOZ_ASSERT(!NS_IsMainThread());
501 if (NS_IsMainThread()) {
502 return NS_ERROR_NOT_SAME_THREAD
;
506 UniqueSECMODModule
rootsModule(SECMOD_FindModule(kRootModuleName
));
508 GatherEnterpriseCertsWindows(certs
, rootsModule
);
511 OSStatus rv
= GatherEnterpriseCertsMacOS(certs
, rootsModule
);
512 if (rv
!= errSecSuccess
) {
513 return NS_ERROR_FAILURE
;
516 #ifdef MOZ_WIDGET_ANDROID
517 GatherEnterpriseCertsAndroid(certs
, rootsModule
);
518 #endif // MOZ_WIDGET_ANDROID