Coverage Report

Created: 2018-09-25 14:53

/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
}