/src/mozilla-central/security/manager/ssl/EnterpriseRoots.cpp
Line | Count | Source (jump to first uncovered line) |
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 | | |
13 | | #ifdef XP_MACOSX |
14 | | #include <Security/Security.h> |
15 | | #include "KeychainSecret.h" // for ScopedCFType |
16 | | #endif // XP_MACOSX |
17 | | |
18 | | extern LazyLogModule gPIPNSSLog; |
19 | | |
20 | | using namespace mozilla; |
21 | | |
22 | | #ifdef XP_WIN |
23 | | const wchar_t* kWindowsDefaultRootStoreName = L"ROOT"; |
24 | | NS_NAMED_LITERAL_CSTRING(kMicrosoftFamilySafetyCN, "Microsoft Family Safety"); |
25 | | |
26 | | // Helper function to determine if the OS considers the given certificate to be |
27 | | // a trust anchor for TLS server auth certificates. This is to be used in the |
28 | | // context of importing what are presumed to be root certificates from the OS. |
29 | | // If this function returns true but it turns out that the given certificate is |
30 | | // in some way unsuitable to issue certificates, mozilla::pkix will never build |
31 | | // a valid chain that includes the certificate, so importing it even if it |
32 | | // isn't a valid CA poses no risk. |
33 | | static bool |
34 | | CertIsTrustAnchorForTLSServerAuth(PCCERT_CONTEXT certificate) |
35 | | { |
36 | | MOZ_ASSERT(certificate); |
37 | | if (!certificate) { |
38 | | return false; |
39 | | } |
40 | | |
41 | | PCCERT_CHAIN_CONTEXT pChainContext = nullptr; |
42 | | CERT_ENHKEY_USAGE enhkeyUsage; |
43 | | memset(&enhkeyUsage, 0, sizeof(CERT_ENHKEY_USAGE)); |
44 | | LPSTR identifiers[] = { |
45 | | "1.3.6.1.5.5.7.3.1", // id-kp-serverAuth |
46 | | }; |
47 | | enhkeyUsage.cUsageIdentifier = ArrayLength(identifiers); |
48 | | enhkeyUsage.rgpszUsageIdentifier = identifiers; |
49 | | CERT_USAGE_MATCH certUsage; |
50 | | memset(&certUsage, 0, sizeof(CERT_USAGE_MATCH)); |
51 | | certUsage.dwType = USAGE_MATCH_TYPE_AND; |
52 | | certUsage.Usage = enhkeyUsage; |
53 | | CERT_CHAIN_PARA chainPara; |
54 | | memset(&chainPara, 0, sizeof(CERT_CHAIN_PARA)); |
55 | | chainPara.cbSize = sizeof(CERT_CHAIN_PARA); |
56 | | chainPara.RequestedUsage = certUsage; |
57 | | |
58 | | if (!CertGetCertificateChain(nullptr, certificate, nullptr, nullptr, |
59 | | &chainPara, 0, nullptr, &pChainContext)) { |
60 | | MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("CertGetCertificateChain failed")); |
61 | | return false; |
62 | | } |
63 | | bool trusted = pChainContext->TrustStatus.dwErrorStatus == |
64 | | CERT_TRUST_NO_ERROR; |
65 | | bool isRoot = pChainContext->cChain == 1; |
66 | | CertFreeCertificateChain(pChainContext); |
67 | | if (trusted && isRoot) { |
68 | | MOZ_LOG(gPIPNSSLog, LogLevel::Debug, |
69 | | ("certificate is trust anchor for TLS server auth")); |
70 | | return true; |
71 | | } |
72 | | MOZ_LOG(gPIPNSSLog, LogLevel::Debug, |
73 | | ("certificate not trust anchor for TLS server auth")); |
74 | | return false; |
75 | | } |
76 | | |
77 | | // It would be convenient to just use nsIX509CertDB in the following code. |
78 | | // However, since nsIX509CertDB depends on nsNSSComponent initialization (and |
79 | | // since this code runs during that initialization), we can't use it. Instead, |
80 | | // we can use NSS APIs directly (as long as we're called late enough in |
81 | | // nsNSSComponent initialization such that those APIs are safe to use). |
82 | | |
83 | | // Helper function to convert a PCCERT_CONTEXT (i.e. a certificate obtained via |
84 | | // a Windows API) to a temporary CERTCertificate (i.e. a certificate for use |
85 | | // with NSS APIs). |
86 | | UniqueCERTCertificate |
87 | | PCCERT_CONTEXTToCERTCertificate(PCCERT_CONTEXT pccert) |
88 | | { |
89 | | MOZ_ASSERT(pccert); |
90 | | if (!pccert) { |
91 | | return nullptr; |
92 | | } |
93 | | |
94 | | SECItem derCert = { |
95 | | siBuffer, |
96 | | pccert->pbCertEncoded, |
97 | | pccert->cbCertEncoded |
98 | | }; |
99 | | return UniqueCERTCertificate( |
100 | | CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &derCert, |
101 | | nullptr, // nickname unnecessary |
102 | | false, // not permanent |
103 | | true)); // copy DER |
104 | | } |
105 | | |
106 | | // Loads the enterprise roots at the registry location corresponding to the |
107 | | // given location flag. |
108 | | // Supported flags are: |
109 | | // CERT_SYSTEM_STORE_LOCAL_MACHINE |
110 | | // (for HKLM\SOFTWARE\Microsoft\SystemCertificates) |
111 | | // CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY |
112 | | // (for HKLM\SOFTWARE\Policies\Microsoft\SystemCertificates\Root\Certificates) |
113 | | // CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE |
114 | | // (for HKLM\SOFTWARE\Microsoft\EnterpriseCertificates\Root\Certificates) |
115 | | static void |
116 | | GatherEnterpriseRootsForLocation(DWORD locationFlag, UniqueCERTCertList& roots) |
117 | | { |
118 | | MOZ_ASSERT(locationFlag == CERT_SYSTEM_STORE_LOCAL_MACHINE || |
119 | | locationFlag == CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY || |
120 | | locationFlag == CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE, |
121 | | "unexpected locationFlag for GatherEnterpriseRootsForLocation"); |
122 | | if (!(locationFlag == CERT_SYSTEM_STORE_LOCAL_MACHINE || |
123 | | locationFlag == CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY || |
124 | | locationFlag == CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE)) { |
125 | | return; |
126 | | } |
127 | | MOZ_ASSERT(roots, "roots unexpectedly NULL?"); |
128 | | if (!roots) { |
129 | | return; |
130 | | } |
131 | | |
132 | | DWORD flags = locationFlag | |
133 | | CERT_STORE_OPEN_EXISTING_FLAG | |
134 | | CERT_STORE_READONLY_FLAG; |
135 | | // The certificate store being opened should consist only of certificates |
136 | | // added by a user or administrator and not any certificates that are part |
137 | | // of Microsoft's root store program. |
138 | | // The 3rd parameter to CertOpenStore should be NULL according to |
139 | | // https://msdn.microsoft.com/en-us/library/windows/desktop/aa376559%28v=vs.85%29.aspx |
140 | | ScopedCertStore enterpriseRootStore(CertOpenStore( |
141 | | CERT_STORE_PROV_SYSTEM_REGISTRY_W, 0, NULL, flags, |
142 | | kWindowsDefaultRootStoreName)); |
143 | | if (!enterpriseRootStore.get()) { |
144 | | MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("failed to open enterprise root store")); |
145 | | return; |
146 | | } |
147 | | PCCERT_CONTEXT certificate = nullptr; |
148 | | uint32_t numImported = 0; |
149 | | while ((certificate = CertFindCertificateInStore(enterpriseRootStore.get(), |
150 | | X509_ASN_ENCODING, 0, |
151 | | CERT_FIND_ANY, nullptr, |
152 | | certificate))) { |
153 | | if (!CertIsTrustAnchorForTLSServerAuth(certificate)) { |
154 | | MOZ_LOG(gPIPNSSLog, LogLevel::Debug, |
155 | | ("skipping cert not trust anchor for TLS server auth")); |
156 | | continue; |
157 | | } |
158 | | UniqueCERTCertificate nssCertificate( |
159 | | PCCERT_CONTEXTToCERTCertificate(certificate)); |
160 | | if (!nssCertificate) { |
161 | | MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't decode certificate")); |
162 | | continue; |
163 | | } |
164 | | // Don't import the Microsoft Family Safety root (this prevents the |
165 | | // Enterprise Roots feature from interacting poorly with the Family |
166 | | // Safety support). |
167 | | UniquePORTString subjectName( |
168 | | CERT_GetCommonName(&nssCertificate->subject)); |
169 | | if (kMicrosoftFamilySafetyCN.Equals(subjectName.get())) { |
170 | | MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("skipping Family Safety Root")); |
171 | | continue; |
172 | | } |
173 | | if (CERT_AddCertToListTail(roots.get(), nssCertificate.get()) |
174 | | != SECSuccess) { |
175 | | MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't add cert to list")); |
176 | | continue; |
177 | | } |
178 | | MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("Imported '%s'", subjectName.get())); |
179 | | numImported++; |
180 | | // now owned by roots |
181 | | Unused << nssCertificate.release(); |
182 | | } |
183 | | MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("imported %u roots", numImported)); |
184 | | } |
185 | | |
186 | | static void |
187 | | GatherEnterpriseRootsWindows(UniqueCERTCertList& roots) |
188 | | { |
189 | | GatherEnterpriseRootsForLocation(CERT_SYSTEM_STORE_LOCAL_MACHINE, roots); |
190 | | GatherEnterpriseRootsForLocation(CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY, |
191 | | roots); |
192 | | GatherEnterpriseRootsForLocation(CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE, |
193 | | roots); |
194 | | } |
195 | | #endif // XP_WIN |
196 | | |
197 | | #ifdef XP_MACOSX |
198 | | OSStatus |
199 | | GatherEnterpriseRootsOSX(UniqueCERTCertList& roots) |
200 | | { |
201 | | MOZ_ASSERT(roots, "roots unexpectedly NULL?"); |
202 | | if (!roots) { |
203 | | return errSecBadReq; |
204 | | } |
205 | | // The following builds a search dictionary corresponding to: |
206 | | // { class: "certificate", |
207 | | // match limit: "match all", |
208 | | // policy: "SSL (TLS)", |
209 | | // only include trusted certificates: true } |
210 | | // This operates on items that have been added to the keychain and thus gives |
211 | | // us all 3rd party certificates that have been trusted for SSL (TLS), which |
212 | | // is what we want (thus we don't import built-in root certificates that ship |
213 | | // with the OS). |
214 | | const CFStringRef keys[] = { kSecClass, |
215 | | kSecMatchLimit, |
216 | | kSecMatchPolicy, |
217 | | kSecMatchTrustedOnly }; |
218 | | // https://developer.apple.com/documentation/security/1392592-secpolicycreatessl |
219 | | ScopedCFType<SecPolicyRef> sslPolicy(SecPolicyCreateSSL(true, nullptr)); |
220 | | const void* values[] = { kSecClassCertificate, |
221 | | kSecMatchLimitAll, |
222 | | sslPolicy.get(), |
223 | | kCFBooleanTrue }; |
224 | | static_assert(ArrayLength(keys) == ArrayLength(values), |
225 | | "mismatched SecItemCopyMatching key/value array sizes"); |
226 | | // https://developer.apple.com/documentation/corefoundation/1516782-cfdictionarycreate |
227 | | ScopedCFType<CFDictionaryRef> searchDictionary( |
228 | | CFDictionaryCreate(nullptr, (const void**)&keys, (const void**)&values, |
229 | | ArrayLength(keys), &kCFTypeDictionaryKeyCallBacks, |
230 | | &kCFTypeDictionaryValueCallBacks)); |
231 | | CFTypeRef items; |
232 | | // https://developer.apple.com/documentation/security/1398306-secitemcopymatching |
233 | | OSStatus rv = SecItemCopyMatching(searchDictionary.get(), &items); |
234 | | if (rv != errSecSuccess) { |
235 | | MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("SecItemCopyMatching failed")); |
236 | | return rv; |
237 | | } |
238 | | // If given a match limit greater than 1 (which we did), SecItemCopyMatching |
239 | | // returns a CFArrayRef. |
240 | | ScopedCFType<CFArrayRef> arr(reinterpret_cast<CFArrayRef>(items)); |
241 | | CFIndex count = CFArrayGetCount(arr.get()); |
242 | | uint32_t numImported = 0; |
243 | | for (CFIndex i = 0; i < count; i++) { |
244 | | const CFTypeRef c = CFArrayGetValueAtIndex(arr.get(), i); |
245 | | // Because we asked for certificates, each CFTypeRef in the array is really |
246 | | // a SecCertificateRef. |
247 | | const SecCertificateRef s = (const SecCertificateRef)c; |
248 | | ScopedCFType<CFDataRef> der(SecCertificateCopyData(s)); |
249 | | const unsigned char* ptr = CFDataGetBytePtr(der.get()); |
250 | | unsigned int len = CFDataGetLength(der.get()); |
251 | | SECItem derItem = { |
252 | | siBuffer, |
253 | | const_cast<unsigned char*>(ptr), |
254 | | len, |
255 | | }; |
256 | | UniqueCERTCertificate cert( |
257 | | CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &derItem, |
258 | | nullptr, // nickname unnecessary |
259 | | false, // not permanent |
260 | | true)); // copy DER |
261 | | if (!cert) { |
262 | | MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't decode 3rd party root certificate")); |
263 | | continue; |
264 | | } |
265 | | UniquePORTString subjectName(CERT_GetCommonName(&cert->subject)); |
266 | | if (CERT_AddCertToListTail(roots.get(), cert.get()) != SECSuccess) { |
267 | | MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't add cert to list")); |
268 | | continue; |
269 | | } |
270 | | numImported++; |
271 | | MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("Imported '%s'", subjectName.get())); |
272 | | mozilla::Unused << cert.release(); // owned by roots |
273 | | } |
274 | | MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("imported %u roots", numImported)); |
275 | | return errSecSuccess; |
276 | | } |
277 | | #endif // XP_MACOSX |
278 | | |
279 | | nsresult |
280 | | GatherEnterpriseRoots(UniqueCERTCertList& result) |
281 | 0 | { |
282 | 0 | UniqueCERTCertList roots(CERT_NewCertList()); |
283 | 0 | if (!roots) { |
284 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
285 | 0 | } |
286 | | #ifdef XP_WIN |
287 | | GatherEnterpriseRootsWindows(roots); |
288 | | #endif // XP_WIN |
289 | | #ifdef XP_MACOSX |
290 | | OSStatus rv = GatherEnterpriseRootsOSX(roots); |
291 | | if (rv != errSecSuccess) { |
292 | | return NS_ERROR_FAILURE; |
293 | | } |
294 | | #endif // XP_MACOSX |
295 | 0 | result = std::move(roots); |
296 | 0 | return NS_OK; |
297 | 0 | } |