Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/security/manager/ssl/LocalCertService.cpp
Line
Count
Source (jump to first uncovered line)
1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
 * License, v. 2.0. If a copy of the MPL was not distributed with this
3
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
#include "LocalCertService.h"
6
7
#include "CryptoTask.h"
8
#include "ScopedNSSTypes.h"
9
#include "cert.h"
10
#include "mozilla/Casting.h"
11
#include "mozilla/ModuleUtils.h"
12
#include "mozilla/RefPtr.h"
13
#include "nsIPK11Token.h"
14
#include "nsIPK11TokenDB.h"
15
#include "nsIX509Cert.h"
16
#include "nsIX509CertValidity.h"
17
#include "nsLiteralString.h"
18
#include "nsProxyRelease.h"
19
#include "nsServiceManagerUtils.h"
20
#include "nsString.h"
21
#include "pk11pub.h"
22
23
namespace mozilla {
24
25
// Given a name, searches the internal certificate/key database for a
26
// self-signed certificate with subject and issuer distinguished name equal to
27
// "CN={name}". This assumes that the user has already authenticated to the
28
// internal DB if necessary.
29
static nsresult
30
FindLocalCertByName(const nsACString& aName,
31
            /*out*/ UniqueCERTCertificate& aResult)
32
0
{
33
0
  aResult.reset(nullptr);
34
0
  NS_NAMED_LITERAL_CSTRING(commonNamePrefix, "CN=");
35
0
  nsAutoCString expectedDistinguishedName(commonNamePrefix + aName);
36
0
  UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
37
0
  if (!slot) {
38
0
    return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
39
0
  }
40
0
  UniqueCERTCertList certList(PK11_ListCertsInSlot(slot.get()));
41
0
  if (!certList) {
42
0
    return NS_ERROR_UNEXPECTED;
43
0
  }
44
0
  for (const CERTCertListNode* node = CERT_LIST_HEAD(certList);
45
0
       !CERT_LIST_END(node, certList); node = CERT_LIST_NEXT(node)) {
46
0
    // If this isn't a self-signed cert, it's not what we're interested in.
47
0
    if (!node->cert->isRoot) {
48
0
      continue;
49
0
    }
50
0
    if (!expectedDistinguishedName.Equals(node->cert->subjectName)) {
51
0
      continue; // Subject should match nickname
52
0
    }
53
0
    if (!expectedDistinguishedName.Equals(node->cert->issuerName)) {
54
0
      continue; // Issuer should match nickname
55
0
    }
56
0
    // We found a match.
57
0
    aResult.reset(CERT_DupCertificate(node->cert));
58
0
    return NS_OK;
59
0
  }
60
0
  return NS_OK;
61
0
}
62
63
class LocalCertTask : public CryptoTask
64
{
65
protected:
66
  explicit LocalCertTask(const nsACString& aNickname)
67
    : mNickname(aNickname)
68
0
  {
69
0
  }
70
71
  nsresult RemoveExisting()
72
0
  {
73
0
    // Search for any existing self-signed certs with this name and remove them
74
0
    for (;;) {
75
0
      UniqueCERTCertificate cert;
76
0
      nsresult rv = FindLocalCertByName(mNickname, cert);
77
0
      if (NS_FAILED(rv)) {
78
0
        return rv;
79
0
      }
80
0
      // If we didn't find a match, we're done.
81
0
      if (!cert) {
82
0
        return NS_OK;
83
0
      }
84
0
      rv = MapSECStatus(PK11_DeleteTokenCertAndKey(cert.get(), nullptr));
85
0
      if (NS_FAILED(rv)) {
86
0
        return rv;
87
0
      }
88
0
    }
89
0
  }
90
91
  nsCString mNickname;
92
};
93
94
class LocalCertGetTask final : public LocalCertTask
95
{
96
public:
97
  LocalCertGetTask(const nsACString& aNickname,
98
                   nsILocalCertGetCallback* aCallback)
99
    : LocalCertTask(aNickname)
100
    , mCallback(new nsMainThreadPtrHolder<nsILocalCertGetCallback>(
101
        "LocalCertGetTask::mCallback", aCallback))
102
    , mCert(nullptr)
103
0
  {
104
0
  }
105
106
private:
107
  virtual nsresult CalculateResult() override
108
0
  {
109
0
    // Try to lookup an existing cert in the DB
110
0
    nsresult rv = GetFromDB();
111
0
    // Make a new one if getting fails
112
0
    if (NS_FAILED(rv)) {
113
0
      rv = Generate();
114
0
    }
115
0
    // If generation fails, we're out of luck
116
0
    if (NS_FAILED(rv)) {
117
0
      return rv;
118
0
    }
119
0
120
0
    // Validate cert, make a new one if it fails
121
0
    rv = Validate();
122
0
    if (NS_FAILED(rv)) {
123
0
      rv = Generate();
124
0
    }
125
0
    // If generation fails, we're out of luck
126
0
    if (NS_FAILED(rv)) {
127
0
      return rv;
128
0
    }
129
0
130
0
    return NS_OK;
131
0
  }
132
133
  nsresult Generate()
134
0
  {
135
0
    nsresult rv;
136
0
137
0
    // Get the key slot for generation later
138
0
    UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
139
0
    if (!slot) {
140
0
      return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
141
0
    }
142
0
143
0
    // Remove existing certs with this name (if any)
144
0
    rv = RemoveExisting();
145
0
    if (NS_FAILED(rv)) {
146
0
      return rv;
147
0
    }
148
0
149
0
    // Generate a new cert
150
0
    NS_NAMED_LITERAL_CSTRING(commonNamePrefix, "CN=");
151
0
    nsAutoCString subjectNameStr(commonNamePrefix + mNickname);
152
0
    UniqueCERTName subjectName(CERT_AsciiToName(subjectNameStr.get()));
153
0
    if (!subjectName) {
154
0
      return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
155
0
    }
156
0
157
0
    // Use the well-known NIST P-256 curve
158
0
    SECOidData* curveOidData = SECOID_FindOIDByTag(SEC_OID_SECG_EC_SECP256R1);
159
0
    if (!curveOidData) {
160
0
      return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
161
0
    }
162
0
163
0
    // Get key params from the curve
164
0
    ScopedAutoSECItem keyParams(2 + curveOidData->oid.len);
165
0
    keyParams.data[0] = SEC_ASN1_OBJECT_ID;
166
0
    keyParams.data[1] = curveOidData->oid.len;
167
0
    memcpy(keyParams.data + 2, curveOidData->oid.data, curveOidData->oid.len);
168
0
169
0
    // Generate cert key pair
170
0
    SECKEYPublicKey* tempPublicKey;
171
0
    UniqueSECKEYPrivateKey privateKey(
172
0
      PK11_GenerateKeyPair(slot.get(), CKM_EC_KEY_PAIR_GEN, &keyParams,
173
0
                           &tempPublicKey, true /* token */,
174
0
                           true /* sensitive */, nullptr));
175
0
    UniqueSECKEYPublicKey publicKey(tempPublicKey);
176
0
    tempPublicKey = nullptr;
177
0
    if (!privateKey || !publicKey) {
178
0
      return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
179
0
    }
180
0
181
0
    // Create subject public key info and cert request
182
0
    UniqueCERTSubjectPublicKeyInfo spki(
183
0
      SECKEY_CreateSubjectPublicKeyInfo(publicKey.get()));
184
0
    if (!spki) {
185
0
      return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
186
0
    }
187
0
    UniqueCERTCertificateRequest certRequest(
188
0
      CERT_CreateCertificateRequest(subjectName.get(), spki.get(), nullptr));
189
0
    if (!certRequest) {
190
0
      return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
191
0
    }
192
0
193
0
    // Valid from one day before to 1 year after
194
0
    static const PRTime oneDay = PRTime(PR_USEC_PER_SEC)
195
0
                               * PRTime(60)  // sec
196
0
                               * PRTime(60)  // min
197
0
                               * PRTime(24); // hours
198
0
199
0
    PRTime now = PR_Now();
200
0
    PRTime notBefore = now - oneDay;
201
0
    PRTime notAfter = now + (PRTime(365) * oneDay);
202
0
    UniqueCERTValidity validity(CERT_CreateValidity(notBefore, notAfter));
203
0
    if (!validity) {
204
0
      return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
205
0
    }
206
0
207
0
    // Generate random serial
208
0
    unsigned long serial;
209
0
    // This serial in principle could collide, but it's unlikely
210
0
    rv = MapSECStatus(PK11_GenerateRandomOnSlot(
211
0
           slot.get(), BitwiseCast<unsigned char*, unsigned long*>(&serial),
212
0
           sizeof(serial)));
213
0
    if (NS_FAILED(rv)) {
214
0
      return rv;
215
0
    }
216
0
217
0
    // Create the cert from these pieces
218
0
    UniqueCERTCertificate cert(
219
0
      CERT_CreateCertificate(serial, subjectName.get(), validity.get(),
220
0
                             certRequest.get()));
221
0
    if (!cert) {
222
0
      return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
223
0
    }
224
0
225
0
    // Update the cert version to X509v3
226
0
    if (!cert->version.data) {
227
0
      return NS_ERROR_INVALID_POINTER;
228
0
    }
229
0
    *(cert->version.data) = SEC_CERTIFICATE_VERSION_3;
230
0
    cert->version.len = 1;
231
0
232
0
    // Set cert signature algorithm
233
0
    PLArenaPool* arena = cert->arena;
234
0
    if (!arena) {
235
0
      return NS_ERROR_INVALID_POINTER;
236
0
    }
237
0
    rv = MapSECStatus(
238
0
           SECOID_SetAlgorithmID(arena, &cert->signature,
239
0
                                 SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, 0));
240
0
    if (NS_FAILED(rv)) {
241
0
      return rv;
242
0
    }
243
0
244
0
    // Encode and self-sign the cert
245
0
    UniqueSECItem certDER(
246
0
      SEC_ASN1EncodeItem(nullptr, nullptr, cert.get(),
247
0
                         SEC_ASN1_GET(CERT_CertificateTemplate)));
248
0
    if (!certDER) {
249
0
      return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
250
0
    }
251
0
    rv = MapSECStatus(
252
0
           SEC_DerSignData(arena, &cert->derCert, certDER->data, certDER->len,
253
0
                           privateKey.get(),
254
0
                           SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE));
255
0
    if (NS_FAILED(rv)) {
256
0
      return rv;
257
0
    }
258
0
259
0
    // Create a CERTCertificate from the signed data
260
0
    UniqueCERTCertificate certFromDER(
261
0
      CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &cert->derCert, nullptr,
262
0
                              true /* perm */, true /* copyDER */));
263
0
    if (!certFromDER) {
264
0
      return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
265
0
    }
266
0
267
0
    // Save the cert in the DB
268
0
    rv = MapSECStatus(PK11_ImportCert(slot.get(), certFromDER.get(),
269
0
                                      CK_INVALID_HANDLE, mNickname.get(),
270
0
                                      false /* unused */));
271
0
    if (NS_FAILED(rv)) {
272
0
      return rv;
273
0
    }
274
0
275
0
    // We should now have cert in the DB, read it back in nsIX509Cert form
276
0
    return GetFromDB();
277
0
  }
278
279
  nsresult GetFromDB()
280
0
  {
281
0
    UniqueCERTCertificate cert;
282
0
    nsresult rv = FindLocalCertByName(mNickname, cert);
283
0
    if (NS_FAILED(rv)) {
284
0
      return rv;
285
0
    }
286
0
    if (!cert) {
287
0
      return NS_ERROR_FAILURE;
288
0
    }
289
0
    mCert = nsNSSCertificate::Create(cert.get());
290
0
    return NS_OK;
291
0
  }
292
293
  nsresult Validate()
294
0
  {
295
0
    // Verify cert is self-signed
296
0
    bool selfSigned;
297
0
    nsresult rv = mCert->GetIsSelfSigned(&selfSigned);
298
0
    if (NS_FAILED(rv)) {
299
0
      return rv;
300
0
    }
301
0
    if (!selfSigned) {
302
0
      return NS_ERROR_FAILURE;
303
0
    }
304
0
305
0
    // Check that subject and issuer match nickname
306
0
    nsAutoString subjectName;
307
0
    nsAutoString issuerName;
308
0
    mCert->GetSubjectName(subjectName);
309
0
    mCert->GetIssuerName(issuerName);
310
0
    if (!subjectName.Equals(issuerName)) {
311
0
      return NS_ERROR_FAILURE;
312
0
    }
313
0
    NS_NAMED_LITERAL_STRING(commonNamePrefix, "CN=");
314
0
    nsAutoString subjectNameFromNickname(
315
0
      commonNamePrefix + NS_ConvertASCIItoUTF16(mNickname));
316
0
    if (!subjectName.Equals(subjectNameFromNickname)) {
317
0
      return NS_ERROR_FAILURE;
318
0
    }
319
0
320
0
    nsCOMPtr<nsIX509CertValidity> validity;
321
0
    mCert->GetValidity(getter_AddRefs(validity));
322
0
323
0
    PRTime notBefore, notAfter;
324
0
    validity->GetNotBefore(&notBefore);
325
0
    validity->GetNotAfter(&notAfter);
326
0
327
0
    // Ensure cert will last at least one more day
328
0
    static const PRTime oneDay = PRTime(PR_USEC_PER_SEC)
329
0
                               * PRTime(60)  // sec
330
0
                               * PRTime(60)  // min
331
0
                               * PRTime(24); // hours
332
0
    PRTime now = PR_Now();
333
0
    if (notBefore > now ||
334
0
        notAfter < (now - oneDay)) {
335
0
      return NS_ERROR_FAILURE;
336
0
    }
337
0
338
0
    return NS_OK;
339
0
  }
340
341
  virtual void CallCallback(nsresult rv) override
342
0
  {
343
0
    (void) mCallback->HandleCert(mCert, rv);
344
0
  }
345
346
  nsMainThreadPtrHandle<nsILocalCertGetCallback> mCallback;
347
  nsCOMPtr<nsIX509Cert> mCert; // out
348
};
349
350
class LocalCertRemoveTask final : public LocalCertTask
351
{
352
public:
353
  LocalCertRemoveTask(const nsACString& aNickname,
354
                      nsILocalCertCallback* aCallback)
355
    : LocalCertTask(aNickname)
356
    , mCallback(new nsMainThreadPtrHolder<nsILocalCertCallback>(
357
        "LocalCertRemoveTask::mCallback", aCallback))
358
0
  {
359
0
  }
360
361
private:
362
  virtual nsresult CalculateResult() override
363
0
  {
364
0
    return RemoveExisting();
365
0
  }
366
367
  virtual void CallCallback(nsresult rv) override
368
0
  {
369
0
    (void) mCallback->HandleResult(rv);
370
0
  }
371
372
  nsMainThreadPtrHandle<nsILocalCertCallback> mCallback;
373
};
374
375
NS_IMPL_ISUPPORTS(LocalCertService, nsILocalCertService)
376
377
LocalCertService::LocalCertService()
378
0
{
379
0
}
380
381
LocalCertService::~LocalCertService()
382
0
{
383
0
}
384
385
nsresult
386
LocalCertService::LoginToKeySlot()
387
0
{
388
0
  nsresult rv;
389
0
390
0
  // Get access to key slot
391
0
  UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
392
0
  if (!slot) {
393
0
    return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
394
0
  }
395
0
396
0
  // If no user password yet, set it an empty one
397
0
  if (PK11_NeedUserInit(slot.get())) {
398
0
    rv = MapSECStatus(PK11_InitPin(slot.get(), "", ""));
399
0
    if (NS_FAILED(rv)) {
400
0
      return rv;
401
0
    }
402
0
  }
403
0
404
0
  // If user has a password set, prompt to login
405
0
  if (PK11_NeedLogin(slot.get()) && !PK11_IsLoggedIn(slot.get(), nullptr)) {
406
0
    // Switching to XPCOM to get the UI prompt that PSM owns
407
0
    nsCOMPtr<nsIPK11TokenDB> tokenDB =
408
0
      do_GetService(NS_PK11TOKENDB_CONTRACTID);
409
0
    if (!tokenDB) {
410
0
      return NS_ERROR_FAILURE;
411
0
    }
412
0
    nsCOMPtr<nsIPK11Token> keyToken;
413
0
    tokenDB->GetInternalKeyToken(getter_AddRefs(keyToken));
414
0
    if (!keyToken) {
415
0
      return NS_ERROR_FAILURE;
416
0
    }
417
0
    // Prompt the user to login
418
0
    return keyToken->Login(false /* force */);
419
0
  }
420
0
421
0
  return NS_OK;
422
0
}
423
424
NS_IMETHODIMP
425
LocalCertService::GetOrCreateCert(const nsACString& aNickname,
426
                                  nsILocalCertGetCallback* aCallback)
427
0
{
428
0
  if (NS_WARN_IF(aNickname.IsEmpty())) {
429
0
    return NS_ERROR_INVALID_ARG;
430
0
  }
431
0
  if (NS_WARN_IF(!aCallback)) {
432
0
    return NS_ERROR_INVALID_POINTER;
433
0
  }
434
0
435
0
  // Before sending off the task, login to key slot if needed
436
0
  nsresult rv = LoginToKeySlot();
437
0
  if (NS_FAILED(rv)) {
438
0
    aCallback->HandleCert(nullptr, rv);
439
0
    return NS_OK;
440
0
  }
441
0
442
0
  RefPtr<LocalCertGetTask> task(new LocalCertGetTask(aNickname, aCallback));
443
0
  return task->Dispatch("LocalCertGet");
444
0
}
445
446
NS_IMETHODIMP
447
LocalCertService::RemoveCert(const nsACString& aNickname,
448
                             nsILocalCertCallback* aCallback)
449
0
{
450
0
  if (NS_WARN_IF(aNickname.IsEmpty())) {
451
0
    return NS_ERROR_INVALID_ARG;
452
0
  }
453
0
  if (NS_WARN_IF(!aCallback)) {
454
0
    return NS_ERROR_INVALID_POINTER;
455
0
  }
456
0
457
0
  // Before sending off the task, login to key slot if needed
458
0
  nsresult rv = LoginToKeySlot();
459
0
  if (NS_FAILED(rv)) {
460
0
    aCallback->HandleResult(rv);
461
0
    return NS_OK;
462
0
  }
463
0
464
0
  RefPtr<LocalCertRemoveTask> task(
465
0
    new LocalCertRemoveTask(aNickname, aCallback));
466
0
  return task->Dispatch("LocalCertRm");
467
0
}
468
469
NS_IMETHODIMP
470
LocalCertService::GetLoginPromptRequired(bool* aRequired)
471
0
{
472
0
  nsresult rv;
473
0
474
0
  // Get access to key slot
475
0
  UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
476
0
  if (!slot) {
477
0
    return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
478
0
  }
479
0
480
0
  // If no user password yet, set it an empty one
481
0
  if (PK11_NeedUserInit(slot.get())) {
482
0
    rv = MapSECStatus(PK11_InitPin(slot.get(), "", ""));
483
0
    if (NS_FAILED(rv)) {
484
0
      return rv;
485
0
    }
486
0
  }
487
0
488
0
  *aRequired = PK11_NeedLogin(slot.get()) &&
489
0
               !PK11_IsLoggedIn(slot.get(), nullptr);
490
0
  return NS_OK;
491
0
}
492
493
#define LOCALCERTSERVICE_CID \
494
{ 0x47402be2, 0xe653, 0x45d0, \
495
  { 0x8d, 0xaa, 0x9f, 0x0d, 0xce, 0x0a, 0xc1, 0x48 } }
496
497
NS_GENERIC_FACTORY_CONSTRUCTOR(LocalCertService)
498
499
NS_DEFINE_NAMED_CID(LOCALCERTSERVICE_CID);
500
501
static const Module::CIDEntry kLocalCertServiceCIDs[] = {
502
  { &kLOCALCERTSERVICE_CID, false, nullptr, LocalCertServiceConstructor },
503
  { nullptr }
504
};
505
506
static const Module::ContractIDEntry kLocalCertServiceContracts[] = {
507
  { LOCALCERTSERVICE_CONTRACTID, &kLOCALCERTSERVICE_CID },
508
  { nullptr }
509
};
510
511
static const Module kLocalCertServiceModule = {
512
  Module::kVersion,
513
  kLocalCertServiceCIDs,
514
  kLocalCertServiceContracts
515
};
516
517
NSMODULE_DEFN(LocalCertServiceModule) = &kLocalCertServiceModule;
518
519
} // namespace mozilla