Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/security/manager/ssl/nsPKCS12Blob.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 "nsPKCS12Blob.h"
6
7
#include "ScopedNSSTypes.h"
8
#include "mozilla/Assertions.h"
9
#include "mozilla/Casting.h"
10
#include "mozilla/Unused.h"
11
#include "nsICertificateDialogs.h"
12
#include "nsIFile.h"
13
#include "nsIInputStream.h"
14
#include "nsIPrompt.h"
15
#include "nsIWindowWatcher.h"
16
#include "nsIX509CertDB.h"
17
#include "nsNSSCertHelper.h"
18
#include "nsNSSCertificate.h"
19
#include "nsNSSHelper.h"
20
#include "nsNetUtil.h"
21
#include "nsReadableUtils.h"
22
#include "nsThreadUtils.h"
23
#include "p12plcy.h"
24
#include "pkix/pkixtypes.h"
25
#include "secerr.h"
26
27
using namespace mozilla;
28
extern LazyLogModule gPIPNSSLog;
29
30
0
#define PIP_PKCS12_BUFFER_SIZE 2048
31
#define PIP_PKCS12_NOSMARTCARD_EXPORT 4
32
#define PIP_PKCS12_RESTORE_FAILED 5
33
#define PIP_PKCS12_BACKUP_FAILED 6
34
#define PIP_PKCS12_NSS_ERROR 7
35
36
nsPKCS12Blob::nsPKCS12Blob()
37
  : mUIContext(new PipUIContext())
38
0
{
39
0
}
40
41
// Given a file handle, read a PKCS#12 blob from that file, decode it, and
42
// import the results into the internal database.
43
nsresult
44
nsPKCS12Blob::ImportFromFile(nsIFile* aFile, const nsAString& aPassword, uint32_t& aError)
45
0
{
46
0
  uint32_t passwordBufferLength;
47
0
  UniquePtr<uint8_t[]> passwordBuffer;
48
0
49
0
  UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
50
0
  if (!slot) {
51
0
    return NS_ERROR_FAILURE;
52
0
  }
53
0
54
0
  passwordBuffer = stringToBigEndianBytes(aPassword, passwordBufferLength);
55
0
56
0
  // initialize the decoder
57
0
  SECItem unicodePw = { siBuffer, passwordBuffer.get(), passwordBufferLength };
58
0
  UniqueSEC_PKCS12DecoderContext dcx(
59
0
    SEC_PKCS12DecoderStart(&unicodePw, slot.get(), nullptr, nullptr, nullptr,
60
0
                           nullptr, nullptr, nullptr));
61
0
  if (!dcx) {
62
0
    return NS_ERROR_FAILURE;
63
0
  }
64
0
  // read input aFile and feed it to the decoder
65
0
  PRErrorCode nssError;
66
0
  nsresult rv = inputToDecoder(dcx, aFile, nssError);
67
0
  if (NS_FAILED(rv)) {
68
0
    return rv;
69
0
  }
70
0
  if (nssError != 0) {
71
0
    aError = handlePRErrorCode(nssError);
72
0
    return NS_OK;
73
0
  }
74
0
  // verify the blob
75
0
  SECStatus srv = SEC_PKCS12DecoderVerify(dcx.get());
76
0
  if (srv != SECSuccess) {
77
0
    aError = handlePRErrorCode(PR_GetError());
78
0
    return NS_OK;
79
0
  }
80
0
  // validate bags
81
0
  srv = SEC_PKCS12DecoderValidateBags(dcx.get(), nicknameCollision);
82
0
  if (srv != SECSuccess) {
83
0
    aError = handlePRErrorCode(PR_GetError());
84
0
    return NS_OK;
85
0
  }
86
0
  // import cert and key
87
0
  srv = SEC_PKCS12DecoderImportBags(dcx.get());
88
0
  if (srv != SECSuccess) {
89
0
    aError = handlePRErrorCode(PR_GetError());
90
0
    return NS_OK;
91
0
  }
92
0
  aError = nsIX509CertDB::Success;
93
0
  return NS_OK;
94
0
}
95
96
static bool
97
isExtractable(UniqueSECKEYPrivateKey& privKey)
98
0
{
99
0
  ScopedAutoSECItem value;
100
0
  SECStatus rv = PK11_ReadRawAttribute(
101
0
    PK11_TypePrivKey, privKey.get(), CKA_EXTRACTABLE, &value);
102
0
  if (rv != SECSuccess) {
103
0
    return false;
104
0
  }
105
0
106
0
  bool isExtractable = false;
107
0
  if ((value.len == 1) && value.data) {
108
0
    isExtractable = !!(*(CK_BBOOL*)value.data);
109
0
  }
110
0
  return isExtractable;
111
0
}
112
113
// Having already loaded the certs, form them into a blob (loading the keys
114
// also), encode the blob, and stuff it into the file.
115
nsresult
116
nsPKCS12Blob::ExportToFile(nsIFile* aFile, nsIX509Cert** aCerts, int aNumCerts,
117
                           const nsAString& aPassword, uint32_t& aError)
118
0
{
119
0
120
0
  // get file password (unicode)
121
0
  uint32_t passwordBufferLength;
122
0
  UniquePtr<uint8_t[]> passwordBuffer;
123
0
  passwordBuffer = stringToBigEndianBytes(aPassword, passwordBufferLength);
124
0
  aError = nsIX509CertDB::Success;
125
0
  if (!passwordBuffer) {
126
0
    return NS_OK;
127
0
  }
128
0
  UniqueSEC_PKCS12ExportContext ecx(
129
0
    SEC_PKCS12CreateExportContext(nullptr, nullptr, nullptr, nullptr));
130
0
  if (!ecx) {
131
0
    aError = nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED;
132
0
    return NS_OK;
133
0
  }
134
0
  // add password integrity
135
0
  SECItem unicodePw = { siBuffer, passwordBuffer.get(), passwordBufferLength };
136
0
  SECStatus srv = SEC_PKCS12AddPasswordIntegrity(ecx.get(), &unicodePw,
137
0
                                                 SEC_OID_SHA1);
138
0
  if (srv != SECSuccess) {
139
0
    aError = nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED;
140
0
    return NS_OK;
141
0
  }
142
0
  for (int i = 0; i < aNumCerts; i++) {
143
0
    UniqueCERTCertificate nssCert(aCerts[i]->GetCert());
144
0
    if (!nssCert) {
145
0
      aError = nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED;
146
0
      return NS_OK;
147
0
    }
148
0
    // We can probably only successfully export certs that are on the internal
149
0
    // token. Most, if not all, smart card vendors won't let you extract the
150
0
    // private key (in any way shape or form) from the card. So let's punt if
151
0
    // the cert is not in the internal db.
152
0
    if (nssCert->slot && !PK11_IsInternal(nssCert->slot)) {
153
0
      // We aren't the internal token, see if the key is extractable.
154
0
      UniqueSECKEYPrivateKey privKey(
155
0
        PK11_FindKeyByDERCert(nssCert->slot, nssCert.get(), mUIContext));
156
0
      if (privKey && !isExtractable(privKey)) {
157
0
        // This is informative.  If a serious error occurs later it will
158
0
        // override it later and return.
159
0
        aError = nsIX509CertDB::ERROR_PKCS12_NOSMARTCARD_EXPORT;
160
0
        continue;
161
0
      }
162
0
    }
163
0
164
0
    // certSafe and keySafe are owned by ecx.
165
0
    SEC_PKCS12SafeInfo* certSafe;
166
0
    SEC_PKCS12SafeInfo* keySafe = SEC_PKCS12CreateUnencryptedSafe(ecx.get());
167
0
    if (!SEC_PKCS12IsEncryptionAllowed() || PK11_IsFIPS()) {
168
0
      certSafe = keySafe;
169
0
    } else {
170
0
      certSafe = SEC_PKCS12CreatePasswordPrivSafe(
171
0
        ecx.get(), &unicodePw,
172
0
        SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC);
173
0
    }
174
0
    if (!certSafe || !keySafe) {
175
0
      aError = nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED;
176
0
      return NS_OK;
177
0
    }
178
0
    // add the cert and key to the blob
179
0
    srv = SEC_PKCS12AddCertAndKey(
180
0
      ecx.get(),
181
0
      certSafe,
182
0
      nullptr,
183
0
      nssCert.get(),
184
0
      CERT_GetDefaultCertDB(),
185
0
      keySafe,
186
0
      nullptr,
187
0
      true,
188
0
      &unicodePw,
189
0
      SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_3KEY_TRIPLE_DES_CBC);
190
0
    if (srv != SECSuccess) {
191
0
      aError = nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED;
192
0
      return NS_OK;
193
0
    }
194
0
  }
195
0
196
0
  UniquePRFileDesc prFile;
197
0
  PRFileDesc* rawPRFile;
198
0
  nsresult rv = aFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE,
199
0
                                        0664, &rawPRFile);
200
0
  if (NS_FAILED(rv) || !rawPRFile) {
201
0
    aError = nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED;
202
0
    return NS_OK;
203
0
  }
204
0
  prFile.reset(rawPRFile);
205
0
  // encode and write
206
0
  srv = SEC_PKCS12Encode(ecx.get(), writeExportFile, prFile.get());
207
0
  if (srv != SECSuccess) {
208
0
    aError = nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED;
209
0
  }
210
0
  return NS_OK;
211
0
}
212
213
// For the NSS PKCS#12 library, must convert PRUnichars (shorts) to a buffer of
214
// octets. Must handle byte order correctly.
215
UniquePtr<uint8_t[]>
216
nsPKCS12Blob::stringToBigEndianBytes(const nsAString& uni, uint32_t& bytesLength)
217
0
{
218
0
  if (uni.IsVoid()) {
219
0
    bytesLength = 0;
220
0
    return nullptr;
221
0
  }
222
0
223
0
  uint32_t wideLength = uni.Length() + 1; // +1 for the null terminator.
224
0
  bytesLength = wideLength * 2;
225
0
  auto buffer = MakeUnique<uint8_t[]>(bytesLength);
226
0
227
0
  // We have to use a cast here because on Windows, uni.get() returns
228
0
  // char16ptr_t instead of char16_t*.
229
0
  mozilla::NativeEndian::copyAndSwapToBigEndian(
230
0
    buffer.get(), static_cast<const char16_t*>(uni.BeginReading()), wideLength);
231
0
232
0
  return buffer;
233
0
}
234
235
// Given a decoder, read bytes from file and input them to the decoder.
236
nsresult
237
nsPKCS12Blob::inputToDecoder(UniqueSEC_PKCS12DecoderContext& dcx, nsIFile* file,
238
                             PRErrorCode& nssError)
239
0
{
240
0
  nssError = 0;
241
0
242
0
  nsCOMPtr<nsIInputStream> fileStream;
243
0
  nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), file);
244
0
  if (NS_FAILED(rv)) {
245
0
    return rv;
246
0
  }
247
0
248
0
  char buf[PIP_PKCS12_BUFFER_SIZE];
249
0
  uint32_t amount;
250
0
  while (true) {
251
0
    rv = fileStream->Read(buf, PIP_PKCS12_BUFFER_SIZE, &amount);
252
0
    if (NS_FAILED(rv)) {
253
0
      return rv;
254
0
    }
255
0
    // feed the file data into the decoder
256
0
    SECStatus srv = SEC_PKCS12DecoderUpdate(
257
0
      dcx.get(), (unsigned char*)buf, amount);
258
0
    if (srv != SECSuccess) {
259
0
      nssError = PR_GetError();
260
0
      return NS_OK;
261
0
    }
262
0
    if (amount < PIP_PKCS12_BUFFER_SIZE) {
263
0
      break;
264
0
    }
265
0
  }
266
0
  return NS_OK;
267
0
}
268
269
// What to do when the nickname collides with one already in the db.
270
SECItem*
271
nsPKCS12Blob::nicknameCollision(SECItem* oldNick, PRBool* cancel, void* wincx)
272
0
{
273
0
  *cancel = false;
274
0
  int count = 1;
275
0
  nsCString nickname;
276
0
  nsAutoString nickFromProp;
277
0
  nsresult rv = GetPIPNSSBundleString("P12DefaultNickname", nickFromProp);
278
0
  if (NS_FAILED(rv)) {
279
0
    return nullptr;
280
0
  }
281
0
  NS_ConvertUTF16toUTF8 nickFromPropC(nickFromProp);
282
0
  // The user is trying to import a PKCS#12 file that doesn't have the
283
0
  // attribute we use to set the nickname.  So in order to reduce the
284
0
  // number of interactions we require with the user, we'll build a nickname
285
0
  // for the user.  The nickname isn't prominently displayed in the UI,
286
0
  // so it's OK if we generate one on our own here.
287
0
  //   XXX If the NSS API were smarter and actually passed a pointer to
288
0
  //       the CERTCertificate* we're importing we could actually just
289
0
  //       call default_nickname (which is what the issuance code path
290
0
  //       does) and come up with a reasonable nickname.  Alas, the NSS
291
0
  //       API limits our ability to produce a useful nickname without
292
0
  //       bugging the user.  :(
293
0
  while (1) {
294
0
    // If we've gotten this far, that means there isn't a certificate
295
0
    // in the database that has the same subject name as the cert we're
296
0
    // trying to import.  So we need to come up with a "nickname" to
297
0
    // satisfy the NSS requirement or fail in trying to import.
298
0
    // Basically we use a default nickname from a properties file and
299
0
    // see if a certificate exists with that nickname.  If there isn't, then
300
0
    // create update the count by one and append the string '#1' Or
301
0
    // whatever the count currently is, and look for a cert with
302
0
    // that nickname.  Keep updating the count until we find a nickname
303
0
    // without a corresponding cert.
304
0
    //  XXX If a user imports *many* certs without the 'friendly name'
305
0
    //      attribute, then this may take a long time.  :(
306
0
    nickname = nickFromPropC;
307
0
    if (count > 1) {
308
0
      nickname.AppendPrintf(" #%d", count);
309
0
    }
310
0
    UniqueCERTCertificate cert(
311
0
      CERT_FindCertByNickname(CERT_GetDefaultCertDB(), nickname.get()));
312
0
    if (!cert) {
313
0
      break;
314
0
    }
315
0
    count++;
316
0
  }
317
0
  UniqueSECItem newNick(SECITEM_AllocItem(nullptr, nullptr,
318
0
                                          nickname.Length() + 1));
319
0
  if (!newNick) {
320
0
    return nullptr;
321
0
  }
322
0
  memcpy(newNick->data, nickname.get(), nickname.Length());
323
0
  newNick->data[nickname.Length()] = 0;
324
0
325
0
  return newNick.release();
326
0
}
327
328
// write bytes to the exported PKCS#12 file
329
void
330
nsPKCS12Blob::writeExportFile(void* arg, const char* buf, unsigned long len)
331
0
{
332
0
  PRFileDesc* file = static_cast<PRFileDesc*>(arg);
333
0
  MOZ_RELEASE_ASSERT(file);
334
0
  PR_Write(file, buf, len);
335
0
}
336
337
// Translate PRErrorCode to nsIX509CertDB error
338
uint32_t
339
nsPKCS12Blob::handlePRErrorCode(PRErrorCode aPrerr)
340
0
{
341
0
  MOZ_ASSERT(aPrerr != 0);
342
0
  uint32_t error = nsIX509CertDB::ERROR_UNKNOWN;
343
0
  switch (aPrerr) {
344
0
    case SEC_ERROR_PKCS12_CERT_COLLISION:
345
0
      error = nsIX509CertDB::ERROR_PKCS12_DUPLICATE_DATA;
346
0
      break;
347
0
    // INVALID_ARGS is returned on bad password when importing cert
348
0
    // exported from firefox or generated by openssl
349
0
    case SEC_ERROR_INVALID_ARGS:
350
0
    case SEC_ERROR_BAD_PASSWORD:
351
0
      error = nsIX509CertDB::ERROR_BAD_PASSWORD;
352
0
      break;
353
0
    case SEC_ERROR_BAD_DER:
354
0
    case SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE:
355
0
    case SEC_ERROR_PKCS12_INVALID_MAC:
356
0
      error = nsIX509CertDB::ERROR_DECODE_ERROR;
357
0
      break;
358
0
    case SEC_ERROR_PKCS12_DUPLICATE_DATA:
359
0
      error = nsIX509CertDB::ERROR_PKCS12_DUPLICATE_DATA;
360
0
      break;
361
0
  }
362
0
  return error;
363
0
}