OLD | NEW |
| (Empty) |
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 | |
52 namespace mozilla_security_manager { | |
53 | |
54 namespace { | |
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? | |
61 void 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 | |
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); | |
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 | |
87 SECItem* PR_CALLBACK | |
88 nickname_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. | |
131 PRBool | |
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, | |
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. | |
148 int | |
149 nsPKCS12Blob_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; | |
262 finish: | |
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. | |
304 SECStatus | |
305 isExtractable(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 | |
322 class 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. | |
345 static base::LazyInstance<PKCS12InitSingleton>::Leaky g_pkcs12_init_singleton = | |
346 LAZY_INSTANCE_INITIALIZER; | |
347 | |
348 } // namespace | |
349 | |
350 void EnsurePKCS12Init() { | |
351 g_pkcs12_init_singleton.Get(); | |
352 } | |
353 | |
354 // Based on nsPKCS12Blob::ImportFromFile. | |
355 int 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 | |
390 int | |
391 nsPKCS12Blob_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; | |
479 finish: | |
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 | |
OLD | NEW |