1 /* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
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/
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
14 * The Original Code is the Netscape security libraries.
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.
22 * Ian McGreer <mcgreer@netscape.com>
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.
36 * ***** END LICENSE BLOCK ***** */
38 #include "net/third_party/mozilla_security_manager/nsPKCS12Blob.h"
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"
52 namespace mozilla_security_manager {
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?
61 void unicodeToItem(const PRUnichar *uni, SECItem *item)
64 while (uni[len++] != 0);
65 SECITEM_AllocItem(NULL, item, sizeof(PRUnichar) * len);
66 #ifdef IS_LITTLE_ENDIAN
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]);
73 memcpy(item->data, uni, item->len);
78 // write bytes to the exported PKCS#12 data buffer
79 void 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);
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
88 nickname_collision(SECItem *old_nick, PRBool *cancel, void *wincx)
91 SECItem *ret_nick = NULL;
92 CERTCertificate* cert = (CERTCertificate*)wincx;
94 if (!cancel || !cert) {
95 // pk12util calls this error user cancelled?
100 VLOG(1) << "no nickname for cert in PKCS12 file.";
102 nick = CERT_MakeCANickname(cert);
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)) {
111 PORT_SetError(SEC_ERROR_IO);
115 VLOG(1) << "using nickname " << nick;
116 ret_nick = PORT_ZNew(SECItem);
117 if(ret_nick == NULL) {
122 ret_nick->data = (unsigned char *)nick;
123 ret_nick->len = PORT_Strlen(nick);
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.
132 pip_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,
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);
147 // Based on nsPKCS12Blob::ImportFromFileHelper.
149 nsPKCS12Blob_ImportHelper(const char* pkcs12_data,
151 const base::string16& password,
153 bool try_zero_length_secitem,
155 net::CertificateList* imported_certs)
159 int import_result = net::ERR_PKCS12_IMPORT_FAILED;
160 SECStatus srv = SECSuccess;
161 SEC_PKCS12DecoderContext *dcx = NULL;
163 SECItem attribute_value;
164 CK_BBOOL attribute_data = CK_FALSE;
165 const SEC_PKCS12DecoderItem* decoder_item = NULL;
167 unicodePw.type = siBuffer;
169 unicodePw.data = NULL;
170 if (!try_zero_length_secitem) {
171 unicodeToItem(password.c_str(), &unicodePw);
174 // Initialize the decoder
175 dcx = SEC_PKCS12DecoderStart(&unicodePw, slot,
178 // dOpen, dClose, dRead, dWrite, dArg: NULL
179 // specifies default impl using memory buffer.
180 NULL, NULL, NULL, NULL, NULL);
185 // feed input to the decoder
186 srv = SEC_PKCS12DecoderUpdate(dcx,
187 (unsigned char*)pkcs12_data,
189 if (srv) goto finish;
191 srv = SEC_PKCS12DecoderVerify(dcx);
192 if (srv) goto finish;
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;
200 attribute_value.data = &attribute_data;
201 attribute_value.len = sizeof(attribute_data);
203 srv = SEC_PKCS12DecoderIterateInit(dcx);
204 if (srv) goto finish;
207 imported_certs->clear();
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)
215 CERTCertificate* cert = PK11_FindCertFromDERCertItem(
216 slot, decoder_item->der,
219 LOG(ERROR) << "Could not grab a handle to the certificate in the slot "
220 << "from the corresponding PKCS#12 DER certificate.";
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));
232 // Once we have determined that the imported certificate has an
233 // associated private key too, only then can we mark the key as
235 if (!decoder_item->hasKey) {
236 CERT_DestroyCertificate(cert);
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,
246 // Mark the private key as non-extractable.
247 srv = PK11_WriteRawAttribute(PK11_TypePrivKey, privKey, CKA_EXTRACTABLE,
249 SECKEY_DestroyPrivateKey(privKey);
251 LOG(ERROR) << "Could not set CKA_EXTRACTABLE attribute on private "
253 CERT_DestroyCertificate(cert);
258 CERT_DestroyCertificate(cert);
259 if (srv) goto finish;
261 import_result = net::OK;
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;
270 case SEC_ERROR_BAD_PASSWORD:
271 case SEC_ERROR_PKCS12_PRIVACY_PASSWORD_INCORRECT:
272 import_result = net::ERR_PKCS12_IMPORT_BAD_PASSWORD;
274 case SEC_ERROR_PKCS12_INVALID_MAC:
275 import_result = net::ERR_PKCS12_IMPORT_INVALID_MAC;
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;
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;
289 import_result = net::ERR_PKCS12_IMPORT_FAILED;
293 // Finish the decoder
295 SEC_PKCS12DecoderFinish(dcx);
296 SECITEM_ZfreeItem(&unicodePw, PR_FALSE);
297 return import_result;
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
305 isExtractable(SECKEYPrivateKey *privKey, PRBool *extractable)
310 rv=PK11_ReadRawAttribute(PK11_TypePrivKey, privKey, CKA_EXTRACTABLE, &value);
311 if (rv != SECSuccess)
314 if ((value.len == 1) && (value.data != NULL))
315 *extractable = !!(*(CK_BBOOL*)value.data);
318 SECITEM_FreeItem(&value, PR_FALSE);
322 class PKCS12InitSingleton {
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);
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);
343 static base::LazyInstance<PKCS12InitSingleton> g_pkcs12_init_singleton =
344 LAZY_INSTANCE_INITIALIZER;
348 void EnsurePKCS12Init() {
349 g_pkcs12_init_singleton.Get();
352 // Based on nsPKCS12Blob::ImportFromFile.
353 int nsPKCS12Blob_Import(PK11SlotInfo* slot,
354 const char* pkcs12_data,
356 const base::string16& password,
358 net::CertificateList* imported_certs) {
360 int rv = nsPKCS12Blob_ImportHelper(pkcs12_data, pkcs12_len, password,
361 is_extractable, false, slot,
364 // When the user entered a zero length password:
365 // An empty password should be represented as an empty
366 // string (a SECItem that contains a single terminating
367 // NULL UTF16 character), but some applications use a
368 // zero length SECItem.
369 // We try both variations, zero length item and empty string,
370 // without giving a user prompt when trying the different empty password
372 if (rv == net::ERR_PKCS12_IMPORT_BAD_PASSWORD && password.empty()) {
373 rv = nsPKCS12Blob_ImportHelper(pkcs12_data, pkcs12_len, password,
374 is_extractable, true, slot, imported_certs);
379 // Based on nsPKCS12Blob::ExportToFile
381 // Having already loaded the certs, form them into a blob (loading the keys
382 // also), encode the blob, and stuff it into the file.
384 // TODO: handle slots correctly
385 // mirror "slotToUse" behavior from PSM 1.x
386 // verify the cert array to start off with?
387 // set appropriate error codes
389 nsPKCS12Blob_Export(std::string* output,
390 const net::CertificateList& certs,
391 const base::string16& password)
393 int return_count = 0;
394 SECStatus srv = SECSuccess;
395 SEC_PKCS12ExportContext *ecx = NULL;
396 SEC_PKCS12SafeInfo *certSafe = NULL, *keySafe = NULL;
398 unicodePw.type = siBuffer;
400 unicodePw.data = NULL;
402 int numCertsExported = 0;
404 // get file password (unicode)
405 unicodeToItem(password.c_str(), &unicodePw);
407 // what about slotToUse in psm 1.x ???
408 // create export context
409 ecx = SEC_PKCS12CreateExportContext(NULL, NULL, NULL /*slot*/, NULL);
414 // add password integrity
415 srv = SEC_PKCS12AddPasswordIntegrity(ecx, &unicodePw, SEC_OID_SHA1);
416 if (srv) goto finish;
418 for (size_t i=0; i<certs.size(); i++) {
419 DCHECK(certs[i].get());
420 CERTCertificate* nssCert = certs[i]->os_cert_handle();
423 // We only allow certificate and private key extraction if the corresponding
424 // CKA_EXTRACTABLE private key attribute is set to CK_TRUE. Most hardware
425 // tokens including smartcards enforce this behavior. An internal (soft)
426 // token may ignore this attribute (and hence still be able to export) but
427 // we still refuse to attempt an export.
428 // In addition, some tokens may not support this attribute, in which case
429 // we still attempt the export and let the token implementation dictate
430 // the export behavior.
432 SECKEYPrivateKey *privKey=PK11_FindKeyByDERCert(nssCert->slot,
436 PRBool privKeyIsExtractable = PR_FALSE;
437 SECStatus rv = isExtractable(privKey, &privKeyIsExtractable);
438 SECKEY_DestroyPrivateKey(privKey);
440 if (rv == SECSuccess && !privKeyIsExtractable) {
441 LOG(ERROR) << "Private key is not extractable";
447 // XXX this is why, to verify the slot is the same
448 // PK11_FindObjectForCert(nssCert, NULL, slot);
449 // create the cert and key safes
450 keySafe = SEC_PKCS12CreateUnencryptedSafe(ecx);
451 if (!SEC_PKCS12IsEncryptionAllowed() || PK11_IsFIPS()) {
454 certSafe = SEC_PKCS12CreatePasswordPrivSafe(ecx, &unicodePw,
455 SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC);
457 if (!certSafe || !keySafe) {
458 LOG(ERROR) << "!certSafe || !keySafe " << certSafe << " " << keySafe;
462 // add the cert and key to the blob
463 srv = SEC_PKCS12AddCertAndKey(ecx, certSafe, NULL, nssCert,
464 CERT_GetDefaultCertDB(),
465 keySafe, NULL, PR_TRUE, &unicodePw,
466 SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_3KEY_TRIPLE_DES_CBC);
467 if (srv) goto finish;
471 if (!numCertsExported) goto finish;
474 srv = SEC_PKCS12Encode(ecx, write_export_data, output);
475 if (srv) goto finish;
476 return_count = numCertsExported;
479 LOG(ERROR) << "PKCS#12 export failed with error " << PORT_GetError();
481 SEC_PKCS12DestroyExportContext(ecx);
482 SECITEM_ZfreeItem(&unicodePw, PR_FALSE);
486 } // namespace mozilla_security_manager