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