1/* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3 *
4 * The contents of this file are subject to the Mozilla Public License Version
5 * 1.1 (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 * http://www.mozilla.org/MPL/
8 *
9 * Software distributed under the License is distributed on an "AS IS" basis,
10 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 * for the specific language governing rights and limitations under the
12 * License.
13 *
14 * The Original Code is the Netscape security libraries.
15 *
16 * The Initial Developer of the Original Code is
17 * Netscape Communications Corporation.
18 * Portions created by the Initial Developer are Copyright (C) 2000
19 * the Initial Developer. All Rights Reserved.
20 *
21 * Contributor(s):
22 *   Ian McGreer <mcgreer@netscape.com>
23 *
24 * Alternatively, the contents of this file may be used under the terms of
25 * either the GNU General Public License Version 2 or later (the "GPL"), or
26 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 * in which case the provisions of the GPL or the LGPL are applicable instead
28 * of those above. If you wish to allow use of your version of this file only
29 * under the terms of either the GPL or the LGPL, and not to allow others to
30 * use your version of this file under the terms of the MPL, indicate your
31 * decision by deleting the provisions above and replace them with the notice
32 * and other provisions required by the GPL or the LGPL. If you do not delete
33 * the provisions above, a recipient may use your version of this file under
34 * the terms of any one of the MPL, the GPL or the LGPL.
35 *
36 * ***** END LICENSE BLOCK ***** */
37
38#include "net/third_party/mozilla_security_manager/nsPKCS12Blob.h"
39
40#include <pk11pub.h>
41#include <pkcs12.h>
42#include <p12plcy.h>
43#include <secerr.h>
44
45#include "base/lazy_instance.h"
46#include "base/logging.h"
47#include "base/strings/string_util.h"
48#include "crypto/nss_util_internal.h"
49#include "net/base/net_errors.h"
50#include "net/cert/x509_certificate.h"
51
52namespace mozilla_security_manager {
53
54namespace {
55
56// unicodeToItem
57//
58// For the NSS PKCS#12 library, must convert PRUnichars (shorts) to
59// a buffer of octets.  Must handle byte order correctly.
60// TODO: Is there a Mozilla way to do this?  In the string lib?
61void unicodeToItem(const PRUnichar *uni, SECItem *item)
62{
63  int len = 0;
64  while (uni[len++] != 0);
65  SECITEM_AllocItem(NULL, item, sizeof(PRUnichar) * len);
66#ifdef IS_LITTLE_ENDIAN
67  int i = 0;
68  for (i=0; i<len; i++) {
69    item->data[2*i  ] = (unsigned char )(uni[i] << 8);
70    item->data[2*i+1] = (unsigned char )(uni[i]);
71  }
72#else
73  memcpy(item->data, uni, item->len);
74#endif
75}
76
77// write_export_data
78// write bytes to the exported PKCS#12 data buffer
79void write_export_data(void* arg, const char* buf, unsigned long len) {
80  std::string* dest = reinterpret_cast<std::string*>(arg);
81  dest->append(buf, len);
82}
83
84// nickname_collision
85// what to do when the nickname collides with one already in the db.
86// Based on P12U_NicknameCollisionCallback from nss/cmd/pk12util/pk12util.c
87SECItem* PR_CALLBACK
88nickname_collision(SECItem *old_nick, PRBool *cancel, void *wincx)
89{
90  char           *nick     = NULL;
91  SECItem        *ret_nick = NULL;
92  CERTCertificate* cert    = (CERTCertificate*)wincx;
93
94  if (!cancel || !cert) {
95    // pk12util calls this error user cancelled?
96    return NULL;
97  }
98
99  if (!old_nick)
100    VLOG(1) << "no nickname for cert in PKCS12 file.";
101
102  nick = CERT_MakeCANickname(cert);
103  if (!nick) {
104    return NULL;
105  }
106
107  if(old_nick && old_nick->data && old_nick->len &&
108     PORT_Strlen(nick) == old_nick->len &&
109     !PORT_Strncmp((char *)old_nick->data, nick, old_nick->len)) {
110    PORT_Free(nick);
111    PORT_SetError(SEC_ERROR_IO);
112    return NULL;
113  }
114
115  VLOG(1) << "using nickname " << nick;
116  ret_nick = PORT_ZNew(SECItem);
117  if(ret_nick == NULL) {
118    PORT_Free(nick);
119    return NULL;
120  }
121
122  ret_nick->data = (unsigned char *)nick;
123  ret_nick->len = PORT_Strlen(nick);
124
125  return ret_nick;
126}
127
128// pip_ucs2_ascii_conversion_fn
129// required to be set by NSS (to do PKCS#12), but since we've already got
130// unicode make this a no-op.
131PRBool
132pip_ucs2_ascii_conversion_fn(PRBool toUnicode,
133                             unsigned char *inBuf,
134                             unsigned int inBufLen,
135                             unsigned char *outBuf,
136                             unsigned int maxOutBufLen,
137                             unsigned int *outBufLen,
138                             PRBool swapBytes)
139{
140  CHECK_GE(maxOutBufLen, inBufLen);
141  // do a no-op, since I've already got Unicode.  Hah!
142  *outBufLen = inBufLen;
143  memcpy(outBuf, inBuf, inBufLen);
144  return PR_TRUE;
145}
146
147// Based on nsPKCS12Blob::ImportFromFileHelper.
148int
149nsPKCS12Blob_ImportHelper(const char* pkcs12_data,
150                          size_t pkcs12_len,
151                          const base::string16& password,
152                          bool is_extractable,
153                          bool try_zero_length_secitem,
154                          PK11SlotInfo *slot,
155                          net::CertificateList* imported_certs)
156{
157  DCHECK(pkcs12_data);
158  DCHECK(slot);
159  int import_result = net::ERR_PKCS12_IMPORT_FAILED;
160  SECStatus srv = SECSuccess;
161  SEC_PKCS12DecoderContext *dcx = NULL;
162  SECItem unicodePw;
163  SECItem attribute_value;
164  CK_BBOOL attribute_data = CK_FALSE;
165  const SEC_PKCS12DecoderItem* decoder_item = NULL;
166
167  unicodePw.type = siBuffer;
168  unicodePw.len = 0;
169  unicodePw.data = NULL;
170  if (!try_zero_length_secitem) {
171    unicodeToItem(password.c_str(), &unicodePw);
172  }
173
174  // Initialize the decoder
175  dcx = SEC_PKCS12DecoderStart(&unicodePw, slot,
176                               // wincx
177                               NULL,
178                               // dOpen, dClose, dRead, dWrite, dArg: NULL
179                               // specifies default impl using memory buffer.
180                               NULL, NULL, NULL, NULL, NULL);
181  if (!dcx) {
182    srv = SECFailure;
183    goto finish;
184  }
185  // feed input to the decoder
186  srv = SEC_PKCS12DecoderUpdate(dcx,
187                                (unsigned char*)pkcs12_data,
188                                pkcs12_len);
189  if (srv) goto finish;
190  // verify the blob
191  srv = SEC_PKCS12DecoderVerify(dcx);
192  if (srv) goto finish;
193  // validate bags
194  srv = SEC_PKCS12DecoderValidateBags(dcx, nickname_collision);
195  if (srv) goto finish;
196  // import certificate and key
197  srv = SEC_PKCS12DecoderImportBags(dcx);
198  if (srv) goto finish;
199
200  attribute_value.data = &attribute_data;
201  attribute_value.len = sizeof(attribute_data);
202
203  srv = SEC_PKCS12DecoderIterateInit(dcx);
204  if (srv) goto finish;
205
206  if (imported_certs)
207    imported_certs->clear();
208
209  // Collect the list of decoded certificates, and mark private keys
210  // non-extractable if needed.
211  while (SEC_PKCS12DecoderIterateNext(dcx, &decoder_item) == SECSuccess) {
212    if (decoder_item->type != SEC_OID_PKCS12_V1_CERT_BAG_ID)
213      continue;
214
215    CERTCertificate* cert = PK11_FindCertFromDERCertItem(
216        slot, decoder_item->der,
217        NULL);  // wincx
218    if (!cert) {
219      LOG(ERROR) << "Could not grab a handle to the certificate in the slot "
220                 << "from the corresponding PKCS#12 DER certificate.";
221      continue;
222    }
223
224    // Add the cert to the list
225    if (imported_certs) {
226      // Empty list of intermediates.
227      net::X509Certificate::OSCertHandles intermediates;
228      imported_certs->push_back(
229          net::X509Certificate::CreateFromHandle(cert, intermediates));
230    }
231
232    // Once we have determined that the imported certificate has an
233    // associated private key too, only then can we mark the key as
234    // non-extractable.
235    if (!decoder_item->hasKey) {
236      CERT_DestroyCertificate(cert);
237      continue;
238    }
239
240    // Iterate through all the imported PKCS12 items and mark any accompanying
241    // private keys as non-extractable.
242    if (!is_extractable) {
243      SECKEYPrivateKey* privKey = PK11_FindPrivateKeyFromCert(slot, cert,
244                                                              NULL);  // wincx
245      if (privKey) {
246        // Mark the private key as non-extractable.
247        srv = PK11_WriteRawAttribute(PK11_TypePrivKey, privKey, CKA_EXTRACTABLE,
248                                     &attribute_value);
249        SECKEY_DestroyPrivateKey(privKey);
250        if (srv) {
251          LOG(ERROR) << "Could not set CKA_EXTRACTABLE attribute on private "
252                     << "key.";
253          CERT_DestroyCertificate(cert);
254          break;
255        }
256      }
257    }
258    CERT_DestroyCertificate(cert);
259    if (srv) goto finish;
260  }
261  import_result = net::OK;
262finish:
263  // If srv != SECSuccess, NSS probably set a specific error code.
264  // We should use that error code instead of inventing a new one
265  // for every error possible.
266  if (srv != SECSuccess) {
267    int error = PORT_GetError();
268    LOG(ERROR) << "PKCS#12 import failed with error " << error;
269    switch (error) {
270      case SEC_ERROR_BAD_PASSWORD:
271      case SEC_ERROR_PKCS12_PRIVACY_PASSWORD_INCORRECT:
272        import_result = net::ERR_PKCS12_IMPORT_BAD_PASSWORD;
273        break;
274      case SEC_ERROR_PKCS12_INVALID_MAC:
275        import_result = net::ERR_PKCS12_IMPORT_INVALID_MAC;
276        break;
277      case SEC_ERROR_BAD_DER:
278      case SEC_ERROR_PKCS12_DECODING_PFX:
279      case SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE:
280        import_result = net::ERR_PKCS12_IMPORT_INVALID_FILE;
281        break;
282      case SEC_ERROR_PKCS12_UNSUPPORTED_MAC_ALGORITHM:
283      case SEC_ERROR_PKCS12_UNSUPPORTED_TRANSPORT_MODE:
284      case SEC_ERROR_PKCS12_UNSUPPORTED_PBE_ALGORITHM:
285      case SEC_ERROR_PKCS12_UNSUPPORTED_VERSION:
286        import_result = net::ERR_PKCS12_IMPORT_UNSUPPORTED;
287        break;
288      default:
289        import_result = net::ERR_PKCS12_IMPORT_FAILED;
290        break;
291    }
292  }
293  // Finish the decoder
294  if (dcx)
295    SEC_PKCS12DecoderFinish(dcx);
296  SECITEM_ZfreeItem(&unicodePw, PR_FALSE);
297  return import_result;
298}
299
300
301// Attempt to read the CKA_EXTRACTABLE attribute on a private key inside
302// a token. On success, store the attribute in |extractable| and return
303// SECSuccess.
304SECStatus
305isExtractable(SECKEYPrivateKey *privKey, PRBool *extractable)
306{
307  SECItem value;
308  SECStatus rv;
309
310  rv=PK11_ReadRawAttribute(PK11_TypePrivKey, privKey, CKA_EXTRACTABLE, &value);
311  if (rv != SECSuccess)
312    return rv;
313
314  if ((value.len == 1) && (value.data != NULL))
315    *extractable = !!(*(CK_BBOOL*)value.data);
316  else
317    rv = SECFailure;
318  SECITEM_FreeItem(&value, PR_FALSE);
319  return rv;
320}
321
322class PKCS12InitSingleton {
323 public:
324  // From the PKCS#12 section of nsNSSComponent::InitializeNSS in
325  // nsNSSComponent.cpp.
326  PKCS12InitSingleton() {
327    // Enable ciphers for PKCS#12
328    SEC_PKCS12EnableCipher(PKCS12_RC4_40, 1);
329    SEC_PKCS12EnableCipher(PKCS12_RC4_128, 1);
330    SEC_PKCS12EnableCipher(PKCS12_RC2_CBC_40, 1);
331    SEC_PKCS12EnableCipher(PKCS12_RC2_CBC_128, 1);
332    SEC_PKCS12EnableCipher(PKCS12_DES_56, 1);
333    SEC_PKCS12EnableCipher(PKCS12_DES_EDE3_168, 1);
334    SEC_PKCS12SetPreferredCipher(PKCS12_DES_EDE3_168, 1);
335
336    // Set no-op ascii-ucs2 conversion function to work around weird NSS
337    // interface.  Thankfully, PKCS12 appears to be the only thing in NSS that
338    // uses PORT_UCS2_ASCIIConversion, so this doesn't break anything else.
339    PORT_SetUCS2_ASCIIConversionFunction(pip_ucs2_ascii_conversion_fn);
340  }
341};
342
343// Leaky so it can be initialized on worker threads and because there is no
344// cleanup necessary.
345static base::LazyInstance<PKCS12InitSingleton>::Leaky g_pkcs12_init_singleton =
346    LAZY_INSTANCE_INITIALIZER;
347
348}  // namespace
349
350void EnsurePKCS12Init() {
351  g_pkcs12_init_singleton.Get();
352}
353
354// Based on nsPKCS12Blob::ImportFromFile.
355int nsPKCS12Blob_Import(PK11SlotInfo* slot,
356                        const char* pkcs12_data,
357                        size_t pkcs12_len,
358                        const base::string16& password,
359                        bool is_extractable,
360                        net::CertificateList* imported_certs) {
361
362  int rv = nsPKCS12Blob_ImportHelper(pkcs12_data, pkcs12_len, password,
363                                     is_extractable, false, slot,
364                                     imported_certs);
365
366  // When the user entered a zero length password:
367  //   An empty password should be represented as an empty
368  //   string (a SECItem that contains a single terminating
369  //   NULL UTF16 character), but some applications use a
370  //   zero length SECItem.
371  //   We try both variations, zero length item and empty string,
372  //   without giving a user prompt when trying the different empty password
373  //   flavors.
374  if (rv == net::ERR_PKCS12_IMPORT_BAD_PASSWORD && password.empty()) {
375    rv = nsPKCS12Blob_ImportHelper(pkcs12_data, pkcs12_len, password,
376                                   is_extractable, true, slot, imported_certs);
377  }
378  return rv;
379}
380
381// Based on nsPKCS12Blob::ExportToFile
382//
383// Having already loaded the certs, form them into a blob (loading the keys
384// also), encode the blob, and stuff it into the file.
385//
386// TODO: handle slots correctly
387//       mirror "slotToUse" behavior from PSM 1.x
388//       verify the cert array to start off with?
389//       set appropriate error codes
390int
391nsPKCS12Blob_Export(std::string* output,
392                    const net::CertificateList& certs,
393                    const base::string16& password)
394{
395  int return_count = 0;
396  SECStatus srv = SECSuccess;
397  SEC_PKCS12ExportContext *ecx = NULL;
398  SEC_PKCS12SafeInfo *certSafe = NULL, *keySafe = NULL;
399  SECItem unicodePw;
400  unicodePw.type = siBuffer;
401  unicodePw.len = 0;
402  unicodePw.data = NULL;
403
404  int numCertsExported = 0;
405
406  // get file password (unicode)
407  unicodeToItem(password.c_str(), &unicodePw);
408
409  // what about slotToUse in psm 1.x ???
410  // create export context
411  ecx = SEC_PKCS12CreateExportContext(NULL, NULL, NULL /*slot*/, NULL);
412  if (!ecx) {
413    srv = SECFailure;
414    goto finish;
415  }
416  // add password integrity
417  srv = SEC_PKCS12AddPasswordIntegrity(ecx, &unicodePw, SEC_OID_SHA1);
418  if (srv) goto finish;
419
420  for (size_t i=0; i<certs.size(); i++) {
421    DCHECK(certs[i].get());
422    CERTCertificate* nssCert = certs[i]->os_cert_handle();
423    DCHECK(nssCert);
424
425    // We only allow certificate and private key extraction if the corresponding
426    // CKA_EXTRACTABLE private key attribute is set to CK_TRUE. Most hardware
427    // tokens including smartcards enforce this behavior. An internal (soft)
428    // token may ignore this attribute (and hence still be able to export) but
429    // we still refuse to attempt an export.
430    // In addition, some tokens may not support this attribute, in which case
431    // we still attempt the export and let the token implementation dictate
432    // the export behavior.
433    if (nssCert->slot) {
434      SECKEYPrivateKey *privKey=PK11_FindKeyByDERCert(nssCert->slot,
435                                                      nssCert,
436                                                      NULL);  // wincx
437       if (privKey) {
438        PRBool privKeyIsExtractable = PR_FALSE;
439        SECStatus rv = isExtractable(privKey, &privKeyIsExtractable);
440        SECKEY_DestroyPrivateKey(privKey);
441
442        if (rv == SECSuccess && !privKeyIsExtractable) {
443          LOG(ERROR) << "Private key is not extractable";
444          continue;
445        }
446      }
447    }
448
449    // XXX this is why, to verify the slot is the same
450    // PK11_FindObjectForCert(nssCert, NULL, slot);
451    // create the cert and key safes
452    keySafe = SEC_PKCS12CreateUnencryptedSafe(ecx);
453    if (!SEC_PKCS12IsEncryptionAllowed() || PK11_IsFIPS()) {
454      certSafe = keySafe;
455    } else {
456      certSafe = SEC_PKCS12CreatePasswordPrivSafe(ecx, &unicodePw,
457                           SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC);
458    }
459    if (!certSafe || !keySafe) {
460      LOG(ERROR) << "!certSafe || !keySafe " << certSafe << " " << keySafe;
461      srv = SECFailure;
462      goto finish;
463    }
464    // add the cert and key to the blob
465    srv = SEC_PKCS12AddCertAndKey(ecx, certSafe, NULL, nssCert,
466                                  CERT_GetDefaultCertDB(),
467                                  keySafe, NULL, PR_TRUE, &unicodePw,
468                      SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_3KEY_TRIPLE_DES_CBC);
469    if (srv) goto finish;
470    ++numCertsExported;
471  }
472
473  if (!numCertsExported) goto finish;
474
475  // encode and write
476  srv = SEC_PKCS12Encode(ecx, write_export_data, output);
477  if (srv) goto finish;
478  return_count = numCertsExported;
479finish:
480  if (srv)
481    LOG(ERROR) << "PKCS#12 export failed with error " << PORT_GetError();
482  if (ecx)
483    SEC_PKCS12DestroyExportContext(ecx);
484  SECITEM_ZfreeItem(&unicodePw, PR_FALSE);
485  return return_count;
486}
487
488}  // namespace mozilla_security_manager
489