Bug 1605894 reduce the proliferation of DefaultLoopbackTone to only AudioStreamFlowin...
[gecko.git] / security / manager / ssl / EnterpriseRoots.cpp
blobd4ee873d473d49df7debab4658c2ab38adc7b5cd
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
19 #ifdef XP_MACOSX
20 # include <Security/Security.h>
21 # include "KeychainSecret.h"
22 #endif
24 #ifdef XP_WIN
25 # include <windows.h>
26 # include <wincrypt.h>
27 #endif // XP_WIN
29 extern mozilla::LazyLogModule gPIPNSSLog;
31 using namespace mozilla;
33 nsresult EnterpriseCert::Init(const uint8_t* data, size_t len, bool isRoot) {
34 mDER.clear();
35 if (!mDER.append(data, len)) {
36 return NS_ERROR_OUT_OF_MEMORY;
38 mIsRoot = isRoot;
40 return NS_OK;
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 {
48 dest.Clear();
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());
52 return NS_OK;
55 pkix::Result EnterpriseCert::GetInput(pkix::Input& input) const {
56 return input.Init(mDER.begin(), mDER.length());
59 bool EnterpriseCert::GetIsRoot() const { return mIsRoot; }
61 #ifdef XP_WIN
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) {
73 isTrusted = false;
74 isRoot = false;
75 MOZ_ASSERT(certificate);
76 if (!certificate) {
77 return;
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"));
105 return;
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.
114 // this one).
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 {
125 public:
126 explicit ScopedCertStore(HCERTSTORE certstore) : certstore(certstore) {}
128 ~ScopedCertStore() { CertCloseStore(certstore, 0); }
130 HCERTSTORE get() { return certstore; }
132 private:
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)) {
164 return;
167 DWORD flags =
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"));
180 continue;
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))) {
187 bool isTrusted;
188 bool isRoot;
189 CertIsTrustAnchorForTLSServerAuth(certificate, isTrusted, isRoot);
190 if (!isTrusted) {
191 MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
192 ("skipping cert not trusted for TLS server auth"));
193 continue;
195 EnterpriseCert enterpriseCert;
196 if (NS_FAILED(enterpriseCert.Init(certificate->pbCertEncoded,
197 certificate->cbCertEncoded, isRoot))) {
198 // Best-effort. We probably ran out of memory.
199 continue;
201 if (!certs.append(std::move(enterpriseCert))) {
202 // Best-effort again.
203 continue;
205 numImported++;
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,
215 certs);
216 GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE,
217 certs);
218 GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_CURRENT_USER, certs);
219 GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY,
220 certs);
222 #endif // XP_WIN
224 #ifdef XP_MACOSX
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
234 // with the OS).
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));
247 CFTypeRef items;
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"));
252 return rv;
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);
261 SecTrustRef trust;
262 rv = SecTrustCreateWithCertificates(c, sslPolicy.get(), &trust);
263 if (rv != errSecSuccess) {
264 MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
265 ("SecTrustCreateWithCertificates failed"));
266 continue;
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"));
274 continue;
277 if (!SecTrustEvaluateWithError(trustHandle.get(), nullptr)) {
278 MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("skipping cert not trusted"));
279 continue;
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.
293 continue;
295 if (!certs.append(std::move(enterpriseCert))) {
296 // Best-effort again.
297 continue;
299 numImported++;
301 MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("imported %u certs", numImported));
302 return errSecSuccess;
304 #endif // XP_MACOSX
306 #ifdef MOZ_WIDGET_ANDROID
307 void GatherEnterpriseCertsAndroid(Vector<EnterpriseCert>& certs) {
308 if (!jni::IsAvailable()) {
309 MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("JNI not available"));
310 return;
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);
316 EnterpriseCert cert;
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;
334 certs.clear();
335 #ifdef XP_WIN
336 GatherEnterpriseCertsWindows(certs);
337 #endif // XP_WIN
338 #ifdef XP_MACOSX
339 OSStatus rv = GatherEnterpriseCertsMacOS(certs);
340 if (rv != errSecSuccess) {
341 return NS_ERROR_FAILURE;
343 #endif // XP_MACOSX
344 #ifdef MOZ_WIDGET_ANDROID
345 GatherEnterpriseCertsAndroid(certs);
346 #endif // MOZ_WIDGET_ANDROID
347 return NS_OK;