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 "nsThreadUtils.h"
15 #ifdef MOZ_WIDGET_ANDROID
16 # include "mozilla/java/EnterpriseRootsWrappers.h"
17 #endif // MOZ_WIDGET_ANDROID
20 # include <Security/Security.h>
21 # include "KeychainSecret.h"
26 # include <wincrypt.h>
29 extern mozilla::LazyLogModule gPIPNSSLog
;
31 using namespace mozilla
;
33 nsresult
EnterpriseCert::Init(const uint8_t* data
, size_t len
, bool isRoot
) {
35 if (!mDER
.append(data
, len
)) {
36 return NS_ERROR_OUT_OF_MEMORY
;
43 nsresult
EnterpriseCert::Init(const EnterpriseCert
& orig
) {
44 return Init(orig
.mDER
.begin(), orig
.mDER
.length(), orig
.mIsRoot
);
47 nsresult
EnterpriseCert::CopyBytes(nsTArray
<uint8_t>& dest
) const {
49 // XXX(Bug 1631371) Check if this should use a fallible operation as it
50 // pretended earlier, or change the return type to void.
51 dest
.AppendElements(mDER
.begin(), mDER
.length());
55 pkix::Result
EnterpriseCert::GetInput(pkix::Input
& input
) const {
56 return input
.Init(mDER
.begin(), mDER
.length());
59 bool EnterpriseCert::GetIsRoot() const { return mIsRoot
; }
62 const wchar_t* kWindowsDefaultRootStoreNames
[] = {L
"ROOT", L
"CA"};
64 // Helper function to determine if the OS considers the given certificate to be
65 // a trust anchor for TLS server auth certificates. This is to be used in the
66 // context of importing what are presumed to be root certificates from the OS.
67 // If this function returns true but it turns out that the given certificate is
68 // in some way unsuitable to issue certificates, mozilla::pkix will never build
69 // a valid chain that includes the certificate, so importing it even if it
70 // isn't a valid CA poses no risk.
71 static void CertIsTrustAnchorForTLSServerAuth(PCCERT_CONTEXT certificate
,
72 bool& isTrusted
, bool& isRoot
) {
75 MOZ_ASSERT(certificate
);
80 PCCERT_CHAIN_CONTEXT pChainContext
= nullptr;
81 CERT_ENHKEY_USAGE enhkeyUsage
;
82 memset(&enhkeyUsage
, 0, sizeof(CERT_ENHKEY_USAGE
));
83 LPCSTR identifiers
[] = {
84 "1.3.6.1.5.5.7.3.1", // id-kp-serverAuth
86 enhkeyUsage
.cUsageIdentifier
= ArrayLength(identifiers
);
87 enhkeyUsage
.rgpszUsageIdentifier
=
88 const_cast<LPSTR
*>(identifiers
); // -Wwritable-strings
89 CERT_USAGE_MATCH certUsage
;
90 memset(&certUsage
, 0, sizeof(CERT_USAGE_MATCH
));
91 certUsage
.dwType
= USAGE_MATCH_TYPE_AND
;
92 certUsage
.Usage
= enhkeyUsage
;
93 CERT_CHAIN_PARA chainPara
;
94 memset(&chainPara
, 0, sizeof(CERT_CHAIN_PARA
));
95 chainPara
.cbSize
= sizeof(CERT_CHAIN_PARA
);
96 chainPara
.RequestedUsage
= certUsage
;
97 // Disable anything that could result in network I/O.
98 DWORD flags
= CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY
|
99 CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL
|
100 CERT_CHAIN_DISABLE_AUTH_ROOT_AUTO_UPDATE
|
101 CERT_CHAIN_DISABLE_AIA
;
102 if (!CertGetCertificateChain(nullptr, certificate
, nullptr, nullptr,
103 &chainPara
, flags
, nullptr, &pChainContext
)) {
104 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
, ("CertGetCertificateChain failed"));
107 isTrusted
= pChainContext
->TrustStatus
.dwErrorStatus
== CERT_TRUST_NO_ERROR
;
108 if (isTrusted
&& pChainContext
->cChain
> 0) {
109 // The so-called "final chain" is what we're after:
110 // https://docs.microsoft.com/en-us/windows/desktop/api/wincrypt/ns-wincrypt-_cert_chain_context
111 CERT_SIMPLE_CHAIN
* finalChain
=
112 pChainContext
->rgpChain
[pChainContext
->cChain
- 1];
113 // This is a root if the final chain consists of only one certificate (i.e.
115 isRoot
= finalChain
->cElement
== 1;
117 CertFreeCertificateChain(pChainContext
);
120 // Because HCERTSTORE is just a typedef void*, we can't use any of the nice
121 // scoped or unique pointer templates. To elaborate, any attempt would
122 // instantiate those templates with T = void. When T gets used in the context
123 // of T&, this results in void&, which isn't legal.
124 class ScopedCertStore final
{
126 explicit ScopedCertStore(HCERTSTORE certstore
) : certstore(certstore
) {}
128 ~ScopedCertStore() { CertCloseStore(certstore
, 0); }
130 HCERTSTORE
get() { return certstore
; }
133 ScopedCertStore(const ScopedCertStore
&) = delete;
134 ScopedCertStore
& operator=(const ScopedCertStore
&) = delete;
135 HCERTSTORE certstore
;
138 // Loads the enterprise roots at the registry location corresponding to the
139 // given location flag.
140 // Supported flags are:
141 // CERT_SYSTEM_STORE_LOCAL_MACHINE
142 // (for HKLM\SOFTWARE\Microsoft\SystemCertificates)
143 // CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY
144 // (for HKLM\SOFTWARE\Policy\Microsoft\SystemCertificates)
145 // CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE
146 // (for HKLM\SOFTWARE\Microsoft\EnterpriseCertificates)
147 // CERT_SYSTEM_STORE_CURRENT_USER
148 // (for HKCU\SOFTWARE\Microsoft\SystemCertificates)
149 // CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY
150 // (for HKCU\SOFTWARE\Policy\Microsoft\SystemCertificates)
151 static void GatherEnterpriseCertsForLocation(DWORD locationFlag
,
152 Vector
<EnterpriseCert
>& certs
) {
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 (auto name
: kWindowsDefaultRootStoreNames
) {
175 ScopedCertStore
enterpriseRootStore(
176 CertOpenStore(CERT_STORE_PROV_SYSTEM_REGISTRY_W
, 0, NULL
, flags
, name
));
177 if (!enterpriseRootStore
.get()) {
178 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
179 ("failed to open enterprise root store"));
182 PCCERT_CONTEXT certificate
= nullptr;
183 uint32_t numImported
= 0;
184 while ((certificate
= CertFindCertificateInStore(
185 enterpriseRootStore
.get(), X509_ASN_ENCODING
, 0, CERT_FIND_ANY
,
186 nullptr, certificate
))) {
189 CertIsTrustAnchorForTLSServerAuth(certificate
, isTrusted
, isRoot
);
191 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
192 ("skipping cert not trusted for TLS server auth"));
195 EnterpriseCert enterpriseCert
;
196 if (NS_FAILED(enterpriseCert
.Init(certificate
->pbCertEncoded
,
197 certificate
->cbCertEncoded
, isRoot
))) {
198 // Best-effort. We probably ran out of memory.
201 if (!certs
.append(std::move(enterpriseCert
))) {
202 // Best-effort again.
207 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
208 ("imported %u certs from %S", numImported
, name
));
212 static void GatherEnterpriseCertsWindows(Vector
<EnterpriseCert
>& certs
) {
213 GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_LOCAL_MACHINE
, certs
);
214 GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY
,
216 GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE
,
218 GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_CURRENT_USER
, certs
);
219 GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY
,
225 OSStatus
GatherEnterpriseCertsMacOS(Vector
<EnterpriseCert
>& certs
) {
226 // The following builds a search dictionary corresponding to:
227 // { class: "certificate",
228 // match limit: "match all",
229 // policy: "SSL (TLS)",
230 // only include trusted certificates: true }
231 // This operates on items that have been added to the keychain and thus gives
232 // us all 3rd party certificates that have been trusted for SSL (TLS), which
233 // is what we want (thus we don't import built-in root certificates that ship
235 const CFStringRef keys
[] = {kSecClass
, kSecMatchLimit
, kSecMatchPolicy
,
236 kSecMatchTrustedOnly
};
237 // https://developer.apple.com/documentation/security/1392592-secpolicycreatessl
238 ScopedCFType
<SecPolicyRef
> sslPolicy(SecPolicyCreateSSL(true, nullptr));
239 const void* values
[] = {kSecClassCertificate
, kSecMatchLimitAll
,
240 sslPolicy
.get(), kCFBooleanTrue
};
241 static_assert(ArrayLength(keys
) == ArrayLength(values
),
242 "mismatched SecItemCopyMatching key/value array sizes");
243 // https://developer.apple.com/documentation/corefoundation/1516782-cfdictionarycreate
244 ScopedCFType
<CFDictionaryRef
> searchDictionary(CFDictionaryCreate(
245 nullptr, (const void**)&keys
, (const void**)&values
, ArrayLength(keys
),
246 &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
));
248 // https://developer.apple.com/documentation/security/1398306-secitemcopymatching
249 OSStatus rv
= SecItemCopyMatching(searchDictionary
.get(), &items
);
250 if (rv
!= errSecSuccess
) {
251 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
, ("SecItemCopyMatching failed"));
254 // If given a match limit greater than 1 (which we did), SecItemCopyMatching
255 // returns a CFArrayRef.
256 ScopedCFType
<CFArrayRef
> arr(reinterpret_cast<CFArrayRef
>(items
));
257 CFIndex count
= CFArrayGetCount(arr
.get());
258 uint32_t numImported
= 0;
259 for (CFIndex i
= 0; i
< count
; i
++) {
260 const CFTypeRef c
= CFArrayGetValueAtIndex(arr
.get(), i
);
262 rv
= SecTrustCreateWithCertificates(c
, sslPolicy
.get(), &trust
);
263 if (rv
!= errSecSuccess
) {
264 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
265 ("SecTrustCreateWithCertificates failed"));
268 ScopedCFType
<SecTrustRef
> trustHandle(trust
);
269 // Disable AIA chasing to avoid network I/O.
270 rv
= SecTrustSetNetworkFetchAllowed(trustHandle
.get(), false);
271 if (rv
!= errSecSuccess
) {
272 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
,
273 ("SecTrustSetNetworkFetchAllowed failed"));
277 if (!SecTrustEvaluateWithError(trustHandle
.get(), nullptr)) {
278 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
, ("skipping cert not trusted"));
282 CFIndex count
= SecTrustGetCertificateCount(trustHandle
.get());
283 bool isRoot
= count
== 1;
285 // Because we asked for certificates, each CFTypeRef in the array is really
286 // a SecCertificateRef.
287 const SecCertificateRef s
= (const SecCertificateRef
)c
;
288 ScopedCFType
<CFDataRef
> der(SecCertificateCopyData(s
));
289 EnterpriseCert enterpriseCert
;
290 if (NS_FAILED(enterpriseCert
.Init(CFDataGetBytePtr(der
.get()),
291 CFDataGetLength(der
.get()), isRoot
))) {
292 // Best-effort. We probably ran out of memory.
295 if (!certs
.append(std::move(enterpriseCert
))) {
296 // Best-effort again.
301 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
, ("imported %u certs", numImported
));
302 return errSecSuccess
;
306 #ifdef MOZ_WIDGET_ANDROID
307 void GatherEnterpriseCertsAndroid(Vector
<EnterpriseCert
>& certs
) {
308 if (!jni::IsAvailable()) {
309 MOZ_LOG(gPIPNSSLog
, LogLevel::Debug
, ("JNI not available"));
312 jni::ObjectArray::LocalRef roots
=
313 java::EnterpriseRoots::GatherEnterpriseRoots();
314 for (size_t i
= 0; i
< roots
->Length(); i
++) {
315 jni::ByteArray::LocalRef root
= roots
->GetElement(i
);
317 // Currently we treat all certificates gleaned from the Android
318 // CA store as roots.
319 if (NS_SUCCEEDED(cert
.Init(
320 reinterpret_cast<uint8_t*>(root
->GetElements().Elements()),
321 root
->Length(), true))) {
322 Unused
<< certs
.append(std::move(cert
));
326 #endif // MOZ_WIDGET_ANDROID
328 nsresult
GatherEnterpriseCerts(Vector
<EnterpriseCert
>& certs
) {
329 MOZ_ASSERT(!NS_IsMainThread());
330 if (NS_IsMainThread()) {
331 return NS_ERROR_NOT_SAME_THREAD
;
336 GatherEnterpriseCertsWindows(certs
);
339 OSStatus rv
= GatherEnterpriseCertsMacOS(certs
);
340 if (rv
!= errSecSuccess
) {
341 return NS_ERROR_FAILURE
;
344 #ifdef MOZ_WIDGET_ANDROID
345 GatherEnterpriseCertsAndroid(certs
);
346 #endif // MOZ_WIDGET_ANDROID