1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
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/. */
6
7 #include "EnterpriseRoots.h"
8
9 #include "mozilla/ArrayUtils.h"
10 #include "mozilla/Logging.h"
11 #include "mozilla/Unused.h"
12 #include "mozpkix/Result.h"
13 #include "nsThreadUtils.h"
14
15 #ifdef MOZ_WIDGET_ANDROID
16 # include "mozilla/java/EnterpriseRootsWrappers.h"
17 #endif // MOZ_WIDGET_ANDROID
18
19 #ifdef XP_MACOSX
20 # include <Security/Security.h>
21 # include "KeychainSecret.h" // for ScopedCFType
22
23 # include "nsCocoaFeatures.h"
24 #endif // XP_MACOSX
25
26 #ifdef XP_WIN
27 # include <windows.h>
28 # include <wincrypt.h>
29 #endif // XP_WIN
30
31 extern mozilla::LazyLogModule gPIPNSSLog;
32
33 using namespace mozilla;
34
Init(const uint8_t * data,size_t len,bool isRoot)35 nsresult EnterpriseCert::Init(const uint8_t* data, size_t len, bool isRoot) {
36 mDER.clear();
37 if (!mDER.append(data, len)) {
38 return NS_ERROR_OUT_OF_MEMORY;
39 }
40 mIsRoot = isRoot;
41
42 return NS_OK;
43 }
44
Init(const EnterpriseCert & orig)45 nsresult EnterpriseCert::Init(const EnterpriseCert& orig) {
46 return Init(orig.mDER.begin(), orig.mDER.length(), orig.mIsRoot);
47 }
48
CopyBytes(nsTArray<uint8_t> & dest) const49 nsresult EnterpriseCert::CopyBytes(nsTArray<uint8_t>& dest) const {
50 dest.Clear();
51 // XXX(Bug 1631371) Check if this should use a fallible operation as it
52 // pretended earlier, or change the return type to void.
53 dest.AppendElements(mDER.begin(), mDER.length());
54 return NS_OK;
55 }
56
GetInput(pkix::Input & input) const57 pkix::Result EnterpriseCert::GetInput(pkix::Input& input) const {
58 return input.Init(mDER.begin(), mDER.length());
59 }
60
GetIsRoot() const61 bool EnterpriseCert::GetIsRoot() const { return mIsRoot; }
62
63 #ifdef XP_WIN
64 const wchar_t* kWindowsDefaultRootStoreNames[] = {L"ROOT", L"CA"};
65
66 // Helper function to determine if the OS considers the given certificate to be
67 // a trust anchor for TLS server auth certificates. This is to be used in the
68 // context of importing what are presumed to be root certificates from the OS.
69 // If this function returns true but it turns out that the given certificate is
70 // in some way unsuitable to issue certificates, mozilla::pkix will never build
71 // a valid chain that includes the certificate, so importing it even if it
72 // isn't a valid CA poses no risk.
CertIsTrustAnchorForTLSServerAuth(PCCERT_CONTEXT certificate,bool & isTrusted,bool & isRoot)73 static void CertIsTrustAnchorForTLSServerAuth(PCCERT_CONTEXT certificate,
74 bool& isTrusted, bool& isRoot) {
75 isTrusted = false;
76 isRoot = false;
77 MOZ_ASSERT(certificate);
78 if (!certificate) {
79 return;
80 }
81
82 PCCERT_CHAIN_CONTEXT pChainContext = nullptr;
83 CERT_ENHKEY_USAGE enhkeyUsage;
84 memset(&enhkeyUsage, 0, sizeof(CERT_ENHKEY_USAGE));
85 LPCSTR identifiers[] = {
86 "1.3.6.1.5.5.7.3.1", // id-kp-serverAuth
87 };
88 enhkeyUsage.cUsageIdentifier = ArrayLength(identifiers);
89 enhkeyUsage.rgpszUsageIdentifier =
90 const_cast<LPSTR*>(identifiers); // -Wwritable-strings
91 CERT_USAGE_MATCH certUsage;
92 memset(&certUsage, 0, sizeof(CERT_USAGE_MATCH));
93 certUsage.dwType = USAGE_MATCH_TYPE_AND;
94 certUsage.Usage = enhkeyUsage;
95 CERT_CHAIN_PARA chainPara;
96 memset(&chainPara, 0, sizeof(CERT_CHAIN_PARA));
97 chainPara.cbSize = sizeof(CERT_CHAIN_PARA);
98 chainPara.RequestedUsage = certUsage;
99 // Disable anything that could result in network I/O.
100 DWORD flags = CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY |
101 CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL |
102 CERT_CHAIN_DISABLE_AUTH_ROOT_AUTO_UPDATE |
103 CERT_CHAIN_DISABLE_AIA;
104 if (!CertGetCertificateChain(nullptr, certificate, nullptr, nullptr,
105 &chainPara, flags, nullptr, &pChainContext)) {
106 MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("CertGetCertificateChain failed"));
107 return;
108 }
109 isTrusted = pChainContext->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR;
110 if (isTrusted && pChainContext->cChain > 0) {
111 // The so-called "final chain" is what we're after:
112 // https://docs.microsoft.com/en-us/windows/desktop/api/wincrypt/ns-wincrypt-_cert_chain_context
113 CERT_SIMPLE_CHAIN* finalChain =
114 pChainContext->rgpChain[pChainContext->cChain - 1];
115 // This is a root if the final chain consists of only one certificate (i.e.
116 // this one).
117 isRoot = finalChain->cElement == 1;
118 }
119 CertFreeCertificateChain(pChainContext);
120 }
121
122 // Because HCERTSTORE is just a typedef void*, we can't use any of the nice
123 // scoped or unique pointer templates. To elaborate, any attempt would
124 // instantiate those templates with T = void. When T gets used in the context
125 // of T&, this results in void&, which isn't legal.
126 class ScopedCertStore final {
127 public:
ScopedCertStore(HCERTSTORE certstore)128 explicit ScopedCertStore(HCERTSTORE certstore) : certstore(certstore) {}
129
~ScopedCertStore()130 ~ScopedCertStore() { CertCloseStore(certstore, 0); }
131
get()132 HCERTSTORE get() { return certstore; }
133
134 private:
135 ScopedCertStore(const ScopedCertStore&) = delete;
136 ScopedCertStore& operator=(const ScopedCertStore&) = delete;
137 HCERTSTORE certstore;
138 };
139
140 // Loads the enterprise roots at the registry location corresponding to the
141 // given location flag.
142 // Supported flags are:
143 // CERT_SYSTEM_STORE_LOCAL_MACHINE
144 // (for HKLM\SOFTWARE\Microsoft\SystemCertificates)
145 // CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY
146 // (for HKLM\SOFTWARE\Policy\Microsoft\SystemCertificates)
147 // CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE
148 // (for HKLM\SOFTWARE\Microsoft\EnterpriseCertificates)
149 // CERT_SYSTEM_STORE_CURRENT_USER
150 // (for HKCU\SOFTWARE\Microsoft\SystemCertificates)
151 // CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY
152 // (for HKCU\SOFTWARE\Policy\Microsoft\SystemCertificates)
GatherEnterpriseCertsForLocation(DWORD locationFlag,Vector<EnterpriseCert> & certs)153 static void GatherEnterpriseCertsForLocation(DWORD locationFlag,
154 Vector<EnterpriseCert>& certs) {
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)) {
166 return;
167 }
168
169 DWORD flags =
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"));
182 continue;
183 }
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))) {
189 bool isTrusted;
190 bool isRoot;
191 CertIsTrustAnchorForTLSServerAuth(certificate, isTrusted, isRoot);
192 if (!isTrusted) {
193 MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
194 ("skipping cert not trusted for TLS server auth"));
195 continue;
196 }
197 EnterpriseCert enterpriseCert;
198 if (NS_FAILED(enterpriseCert.Init(certificate->pbCertEncoded,
199 certificate->cbCertEncoded, isRoot))) {
200 // Best-effort. We probably ran out of memory.
201 continue;
202 }
203 if (!certs.append(std::move(enterpriseCert))) {
204 // Best-effort again.
205 continue;
206 }
207 numImported++;
208 }
209 MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
210 ("imported %u certs from %S", numImported, name));
211 }
212 }
213
GatherEnterpriseCertsWindows(Vector<EnterpriseCert> & certs)214 static void GatherEnterpriseCertsWindows(Vector<EnterpriseCert>& certs) {
215 GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_LOCAL_MACHINE, certs);
216 GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY,
217 certs);
218 GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE,
219 certs);
220 GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_CURRENT_USER, certs);
221 GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY,
222 certs);
223 }
224 #endif // XP_WIN
225
226 #ifdef XP_MACOSX
GatherEnterpriseCertsMacOS(Vector<EnterpriseCert> & certs)227 OSStatus GatherEnterpriseCertsMacOS(Vector<EnterpriseCert>& certs) {
228 // The following builds a search dictionary corresponding to:
229 // { class: "certificate",
230 // match limit: "match all",
231 // policy: "SSL (TLS)",
232 // only include trusted certificates: true }
233 // This operates on items that have been added to the keychain and thus gives
234 // us all 3rd party certificates that have been trusted for SSL (TLS), which
235 // is what we want (thus we don't import built-in root certificates that ship
236 // with the OS).
237 const CFStringRef keys[] = {kSecClass, kSecMatchLimit, kSecMatchPolicy,
238 kSecMatchTrustedOnly};
239 // https://developer.apple.com/documentation/security/1392592-secpolicycreatessl
240 ScopedCFType<SecPolicyRef> sslPolicy(SecPolicyCreateSSL(true, nullptr));
241 const void* values[] = {kSecClassCertificate, kSecMatchLimitAll,
242 sslPolicy.get(), kCFBooleanTrue};
243 static_assert(ArrayLength(keys) == ArrayLength(values),
244 "mismatched SecItemCopyMatching key/value array sizes");
245 // https://developer.apple.com/documentation/corefoundation/1516782-cfdictionarycreate
246 ScopedCFType<CFDictionaryRef> searchDictionary(CFDictionaryCreate(
247 nullptr, (const void**)&keys, (const void**)&values, ArrayLength(keys),
248 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
249 CFTypeRef items;
250 // https://developer.apple.com/documentation/security/1398306-secitemcopymatching
251 OSStatus rv = SecItemCopyMatching(searchDictionary.get(), &items);
252 if (rv != errSecSuccess) {
253 MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("SecItemCopyMatching failed"));
254 return rv;
255 }
256 // If given a match limit greater than 1 (which we did), SecItemCopyMatching
257 // returns a CFArrayRef.
258 ScopedCFType<CFArrayRef> arr(reinterpret_cast<CFArrayRef>(items));
259 CFIndex count = CFArrayGetCount(arr.get());
260 uint32_t numImported = 0;
261 for (CFIndex i = 0; i < count; i++) {
262 const CFTypeRef c = CFArrayGetValueAtIndex(arr.get(), i);
263 SecTrustRef trust;
264 rv = SecTrustCreateWithCertificates(c, sslPolicy.get(), &trust);
265 if (rv != errSecSuccess) {
266 MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
267 ("SecTrustCreateWithCertificates failed"));
268 continue;
269 }
270 ScopedCFType<SecTrustRef> trustHandle(trust);
271 // Disable AIA chasing to avoid network I/O.
272 rv = SecTrustSetNetworkFetchAllowed(trustHandle.get(), false);
273 if (rv != errSecSuccess) {
274 MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
275 ("SecTrustSetNetworkFetchAllowed failed"));
276 continue;
277 }
278 bool isTrusted = false;
279 bool fallBackToDeprecatedAPI = true;
280 # if defined MAC_OS_X_VERSION_10_14
281 if (nsCocoaFeatures::OnMojaveOrLater()) {
282 // This is an awkward way to express what we want, but the compiler
283 // complains if we try to put __builtin_available in a compound logical
284 // statement.
285 if (__builtin_available(macOS 10.14, *)) {
286 isTrusted = SecTrustEvaluateWithError(trustHandle.get(), nullptr);
287 fallBackToDeprecatedAPI = false;
288 }
289 }
290 # endif // MAC_OS_X_VERSION_10_14
291 if (fallBackToDeprecatedAPI) {
292 SecTrustResultType result;
293 rv = SecTrustEvaluate(trustHandle.get(), &result);
294 if (rv != errSecSuccess) {
295 MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("SecTrustEvaluate failed"));
296 continue;
297 }
298 // 'kSecTrustResultProceed' means the trust evaluation succeeded and that
299 // this is a trusted certificate.
300 // 'kSecTrustResultUnspecified' means the trust evaluation succeeded and
301 // that this certificate inherits its trust.
302 isTrusted = result == kSecTrustResultProceed ||
303 result == kSecTrustResultUnspecified;
304 }
305 if (!isTrusted) {
306 MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("skipping cert not trusted"));
307 continue;
308 }
309 CFIndex count = SecTrustGetCertificateCount(trustHandle.get());
310 bool isRoot = count == 1;
311
312 // Because we asked for certificates, each CFTypeRef in the array is really
313 // a SecCertificateRef.
314 const SecCertificateRef s = (const SecCertificateRef)c;
315 ScopedCFType<CFDataRef> der(SecCertificateCopyData(s));
316 EnterpriseCert enterpriseCert;
317 if (NS_FAILED(enterpriseCert.Init(CFDataGetBytePtr(der.get()),
318 CFDataGetLength(der.get()), isRoot))) {
319 // Best-effort. We probably ran out of memory.
320 continue;
321 }
322 if (!certs.append(std::move(enterpriseCert))) {
323 // Best-effort again.
324 continue;
325 }
326 numImported++;
327 }
328 MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("imported %u certs", numImported));
329 return errSecSuccess;
330 }
331 #endif // XP_MACOSX
332
333 #ifdef MOZ_WIDGET_ANDROID
GatherEnterpriseCertsAndroid(Vector<EnterpriseCert> & certs)334 void GatherEnterpriseCertsAndroid(Vector<EnterpriseCert>& certs) {
335 if (!jni::IsAvailable()) {
336 MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("JNI not available"));
337 return;
338 }
339 jni::ObjectArray::LocalRef roots =
340 java::EnterpriseRoots::GatherEnterpriseRoots();
341 for (size_t i = 0; i < roots->Length(); i++) {
342 jni::ByteArray::LocalRef root = roots->GetElement(i);
343 EnterpriseCert cert;
344 // Currently we treat all certificates gleaned from the Android
345 // CA store as roots.
346 if (NS_SUCCEEDED(cert.Init(
347 reinterpret_cast<uint8_t*>(root->GetElements().Elements()),
348 root->Length(), true))) {
349 Unused << certs.append(std::move(cert));
350 }
351 }
352 }
353 #endif // MOZ_WIDGET_ANDROID
354
GatherEnterpriseCerts(Vector<EnterpriseCert> & certs)355 nsresult GatherEnterpriseCerts(Vector<EnterpriseCert>& certs) {
356 MOZ_ASSERT(!NS_IsMainThread());
357 if (NS_IsMainThread()) {
358 return NS_ERROR_NOT_SAME_THREAD;
359 }
360
361 certs.clear();
362 #ifdef XP_WIN
363 GatherEnterpriseCertsWindows(certs);
364 #endif // XP_WIN
365 #ifdef XP_MACOSX
366 OSStatus rv = GatherEnterpriseCertsMacOS(certs);
367 if (rv != errSecSuccess) {
368 return NS_ERROR_FAILURE;
369 }
370 #endif // XP_MACOSX
371 #ifdef MOZ_WIDGET_ANDROID
372 GatherEnterpriseCertsAndroid(certs);
373 #endif // MOZ_WIDGET_ANDROID
374 return NS_OK;
375 }
376