diff options
Diffstat (limited to 'security/nss/lib/pkcs12/p12exp.c')
-rw-r--r-- | security/nss/lib/pkcs12/p12exp.c | 1374 |
1 files changed, 1374 insertions, 0 deletions
diff --git a/security/nss/lib/pkcs12/p12exp.c b/security/nss/lib/pkcs12/p12exp.c new file mode 100644 index 000000000..b9cb07656 --- /dev/null +++ b/security/nss/lib/pkcs12/p12exp.c @@ -0,0 +1,1374 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "plarena.h" +#include "secitem.h" +#include "secoid.h" +#include "seccomon.h" +#include "secport.h" +#include "cert.h" +#include "pkcs12.h" +#include "p12local.h" +#include "secpkcs7.h" +#include "secasn1.h" +#include "secerr.h" +#include "p12plcy.h" + +/* release the memory taken up by the list of nicknames */ +static void +sec_pkcs12_destroy_nickname_list(SECItem **nicknames) +{ + int i = 0; + + if (nicknames == NULL) { + return; + } + + while (nicknames[i] != NULL) { + SECITEM_FreeItem(nicknames[i], PR_FALSE); + i++; + } + + PORT_Free(nicknames); +} + +/* release the memory taken up by the list of certificates */ +static void +sec_pkcs12_destroy_certificate_list(CERTCertificate **ref_certs) +{ + int i = 0; + + if (ref_certs == NULL) { + return; + } + + while (ref_certs[i] != NULL) { + CERT_DestroyCertificate(ref_certs[i]); + i++; + } +} + +static void +sec_pkcs12_destroy_cinfos_for_cert_bags(SEC_PKCS12CertAndCRLBag *certBag) +{ + int j = 0; + j = 0; + while (certBag->certAndCRLs[j] != NULL) { + SECOidTag certType = SECOID_FindOIDTag(&certBag->certAndCRLs[j]->BagID); + if (certType == SEC_OID_PKCS12_X509_CERT_CRL_BAG) { + SEC_PKCS12X509CertCRL *x509; + x509 = certBag->certAndCRLs[j]->value.x509; + SEC_PKCS7DestroyContentInfo(&x509->certOrCRL); + } + j++; + } +} + +/* destroy all content infos since they were not allocated in common + * pool + */ +static void +sec_pkcs12_destroy_cert_content_infos(SEC_PKCS12SafeContents *safe, + SEC_PKCS12Baggage *baggage) +{ + int i, j; + + if ((safe != NULL) && (safe->contents != NULL)) { + i = 0; + while (safe->contents[i] != NULL) { + SECOidTag bagType = SECOID_FindOIDTag(&safe->contents[i]->safeBagType); + if (bagType == SEC_OID_PKCS12_CERT_AND_CRL_BAG_ID) { + SEC_PKCS12CertAndCRLBag *certBag; + certBag = safe->contents[i]->safeContent.certAndCRLBag; + sec_pkcs12_destroy_cinfos_for_cert_bags(certBag); + } + i++; + } + } + + if ((baggage != NULL) && (baggage->bags != NULL)) { + i = 0; + while (baggage->bags[i] != NULL) { + if (baggage->bags[i]->unencSecrets != NULL) { + j = 0; + while (baggage->bags[i]->unencSecrets[j] != NULL) { + SECOidTag bagType; + bagType = SECOID_FindOIDTag(&baggage->bags[i]->unencSecrets[j]->safeBagType); + if (bagType == SEC_OID_PKCS12_CERT_AND_CRL_BAG_ID) { + SEC_PKCS12CertAndCRLBag *certBag; + certBag = baggage->bags[i]->unencSecrets[j]->safeContent.certAndCRLBag; + sec_pkcs12_destroy_cinfos_for_cert_bags(certBag); + } + j++; + } + } + i++; + } + } +} + +/* convert the nickname list from a NULL termincated Char list + * to a NULL terminated SECItem list + */ +static SECItem ** +sec_pkcs12_convert_nickname_list(char **nicknames) +{ + SECItem **nicks; + int i, j; + PRBool error = PR_FALSE; + + if (nicknames == NULL) { + return NULL; + } + + i = j = 0; + while (nicknames[i] != NULL) { + i++; + } + + /* allocate the space and copy the data */ + nicks = (SECItem **)PORT_ZAlloc(sizeof(SECItem *) * (i + 1)); + if (nicks != NULL) { + for (j = 0; ((j < i) && (error == PR_FALSE)); j++) { + nicks[j] = (SECItem *)PORT_ZAlloc(sizeof(SECItem)); + if (nicks[j] != NULL) { + nicks[j]->data = + (unsigned char *)PORT_ZAlloc(PORT_Strlen(nicknames[j]) + 1); + if (nicks[j]->data != NULL) { + nicks[j]->len = PORT_Strlen(nicknames[j]); + PORT_Memcpy(nicks[j]->data, nicknames[j], nicks[j]->len); + nicks[j]->data[nicks[j]->len] = 0; + } else { + error = PR_TRUE; + } + } else { + error = PR_TRUE; + } + } + } + + if (error == PR_TRUE) { + for (i = 0; i < j; i++) { + SECITEM_FreeItem(nicks[i], PR_TRUE); + } + PORT_Free(nicks); + nicks = NULL; + } + + return nicks; +} + +/* package the certificate add_cert into PKCS12 structures, + * retrieve the certificate chain for the cert and return + * the packaged contents. + * poolp -- common memory pool; + * add_cert -- certificate to package up + * nickname for the certificate + * a return of NULL indicates an error + */ +static SEC_PKCS12CertAndCRL * +sec_pkcs12_get_cert(PLArenaPool *poolp, + CERTCertificate *add_cert, + SECItem *nickname) +{ + SEC_PKCS12CertAndCRL *cert; + SEC_PKCS7ContentInfo *cinfo; + SGNDigestInfo *t_di; + void *mark; + SECStatus rv; + + if ((poolp == NULL) || (add_cert == NULL) || (nickname == NULL)) { + return NULL; + } + mark = PORT_ArenaMark(poolp); + + cert = sec_pkcs12_new_cert_crl(poolp, SEC_OID_PKCS12_X509_CERT_CRL_BAG); + if (cert != NULL) { + + /* copy the nickname */ + rv = SECITEM_CopyItem(poolp, &cert->nickname, nickname); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + cert = NULL; + } else { + + /* package the certificate and cert chain into a NULL signer + * PKCS 7 SignedData content Info and prepare it for encoding + * since we cannot use DER_ANY_TEMPLATE + */ + cinfo = SEC_PKCS7CreateCertsOnly(add_cert, PR_TRUE, NULL); + rv = SEC_PKCS7PrepareForEncode(cinfo, NULL, NULL, NULL); + + /* thumbprint the certificate */ + if ((cinfo != NULL) && (rv == SECSuccess)) { + PORT_Memcpy(&cert->value.x509->certOrCRL, cinfo, sizeof(*cinfo)); + t_di = sec_pkcs12_compute_thumbprint(&add_cert->derCert); + if (t_di != NULL) { + /* test */ + rv = SGN_CopyDigestInfo(poolp, &cert->value.x509->thumbprint, + t_di); + if (rv != SECSuccess) { + cert = NULL; + PORT_SetError(SEC_ERROR_NO_MEMORY); + } + SGN_DestroyDigestInfo(t_di); + } else + cert = NULL; + } + } + } + + if (cert == NULL) { + PORT_ArenaRelease(poolp, mark); + } else { + PORT_ArenaUnmark(poolp, mark); + } + + return cert; +} + +/* package the private key associated with the certificate and + * return the appropriate PKCS 12 structure + * poolp common memory pool + * nickname key nickname + * cert -- cert to look up + * wincx -- window handle + * an error is indicated by a return of NULL + */ +static SEC_PKCS12PrivateKey * +sec_pkcs12_get_private_key(PLArenaPool *poolp, + SECItem *nickname, + CERTCertificate *cert, + void *wincx) +{ + SECKEYPrivateKeyInfo *pki; + SEC_PKCS12PrivateKey *pk; + SECStatus rv; + void *mark; + + if ((poolp == NULL) || (nickname == NULL)) { + return NULL; + } + + mark = PORT_ArenaMark(poolp); + + /* retrieve key from the data base */ + pki = PK11_ExportPrivateKeyInfo(nickname, cert, wincx); + if (pki == NULL) { + PORT_ArenaRelease(poolp, mark); + PORT_SetError(SEC_ERROR_PKCS12_UNABLE_TO_EXPORT_KEY); + return NULL; + } + + pk = (SEC_PKCS12PrivateKey *)PORT_ArenaZAlloc(poolp, + sizeof(SEC_PKCS12PrivateKey)); + if (pk != NULL) { + rv = sec_pkcs12_init_pvk_data(poolp, &pk->pvkData); + + if (rv == SECSuccess) { + /* copy the key into poolp memory space */ + rv = SECKEY_CopyPrivateKeyInfo(poolp, &pk->pkcs8data, pki); + if (rv == SECSuccess) { + rv = SECITEM_CopyItem(poolp, &pk->pvkData.nickname, nickname); + } + } + + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + pk = NULL; + } + } else { + PORT_SetError(SEC_ERROR_NO_MEMORY); + } + + /* destroy private key, zeroing out data */ + SECKEY_DestroyPrivateKeyInfo(pki, PR_TRUE); + if (pk == NULL) { + PORT_ArenaRelease(poolp, mark); + } else { + PORT_ArenaUnmark(poolp, mark); + } + + return pk; +} + +/* get a shrouded key item associated with a certificate + * return the appropriate PKCS 12 structure + * poolp common memory pool + * nickname key nickname + * cert -- cert to look up + * wincx -- window handle + * an error is indicated by a return of NULL + */ +static SEC_PKCS12ESPVKItem * +sec_pkcs12_get_shrouded_key(PLArenaPool *poolp, + SECItem *nickname, + CERTCertificate *cert, + SECOidTag algorithm, + SECItem *pwitem, + PKCS12UnicodeConvertFunction unicodeFn, + void *wincx) +{ + SECKEYEncryptedPrivateKeyInfo *epki; + SEC_PKCS12ESPVKItem *pk; + void *mark; + SECStatus rv; + PK11SlotInfo *slot = NULL; + PRBool swapUnicodeBytes = PR_FALSE; + +#ifdef IS_LITTLE_ENDIAN + swapUnicodeBytes = PR_TRUE; +#endif + + if ((poolp == NULL) || (nickname == NULL)) + return NULL; + + mark = PORT_ArenaMark(poolp); + + /* use internal key slot */ + slot = PK11_GetInternalKeySlot(); + + /* retrieve encrypted prviate key */ + epki = PK11_ExportEncryptedPrivateKeyInfo(slot, algorithm, pwitem, + nickname, cert, 1, 0, NULL); + PK11_FreeSlot(slot); + if (epki == NULL) { + PORT_SetError(SEC_ERROR_PKCS12_UNABLE_TO_EXPORT_KEY); + PORT_ArenaRelease(poolp, mark); + return NULL; + } + + /* create a private key and store the data into the poolp memory space */ + pk = sec_pkcs12_create_espvk(poolp, SEC_OID_PKCS12_PKCS8_KEY_SHROUDING); + if (pk != NULL) { + rv = sec_pkcs12_init_pvk_data(poolp, &pk->espvkData); + rv = SECITEM_CopyItem(poolp, &pk->espvkData.nickname, nickname); + pk->espvkCipherText.pkcs8KeyShroud = + (SECKEYEncryptedPrivateKeyInfo *)PORT_ArenaZAlloc(poolp, + sizeof(SECKEYEncryptedPrivateKeyInfo)); + if ((pk->espvkCipherText.pkcs8KeyShroud != NULL) && (rv == SECSuccess)) { + rv = SECKEY_CopyEncryptedPrivateKeyInfo(poolp, + pk->espvkCipherText.pkcs8KeyShroud, epki); + if (rv == SECSuccess) { + rv = (*unicodeFn)(poolp, &pk->espvkData.uniNickName, nickname, + PR_TRUE, swapUnicodeBytes); + } + } + + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + pk = NULL; + } + } + + SECKEY_DestroyEncryptedPrivateKeyInfo(epki, PR_TRUE); + if (pk == NULL) { + PORT_ArenaRelease(poolp, mark); + } else { + PORT_ArenaUnmark(poolp, mark); + } + + return pk; +} + +/* add a thumbprint to a private key associated certs list + * pvk is the area where the list is stored + * thumb is the thumbprint to copy + * a return of SECFailure indicates an error + */ +static SECStatus +sec_pkcs12_add_thumbprint(SEC_PKCS12PVKSupportingData *pvk, + SGNDigestInfo *thumb) +{ + SGNDigestInfo **thumb_list = NULL; + int nthumbs, size; + void *mark, *dummy; + SECStatus rv = SECFailure; + + if ((pvk == NULL) || (thumb == NULL)) { + return SECFailure; + } + + mark = PORT_ArenaMark(pvk->poolp); + + thumb_list = pvk->assocCerts; + nthumbs = pvk->nThumbs; + + /* allocate list space needed -- either growing or allocating + * list must be NULL terminated + */ + size = sizeof(SGNDigestInfo *); + dummy = PORT_ArenaGrow(pvk->poolp, thumb_list, (size * (nthumbs + 1)), + (size * (nthumbs + 2))); + thumb_list = dummy; + if (dummy != NULL) { + thumb_list[nthumbs] = (SGNDigestInfo *)PORT_ArenaZAlloc(pvk->poolp, + sizeof(SGNDigestInfo)); + if (thumb_list[nthumbs] != NULL) { + SGN_CopyDigestInfo(pvk->poolp, thumb_list[nthumbs], thumb); + nthumbs += 1; + thumb_list[nthumbs] = 0; + } else { + dummy = NULL; + } + } + + if (dummy == NULL) { + PORT_ArenaRelease(pvk->poolp, mark); + return SECFailure; + } + + pvk->assocCerts = thumb_list; + pvk->nThumbs = nthumbs; + + PORT_ArenaUnmark(pvk->poolp, mark); + return SECSuccess; +} + +/* search the list of shrouded keys in the baggage for the desired + * name. return a pointer to the item. a return of NULL indicates + * that no match was present or that an error occurred. + */ +static SEC_PKCS12ESPVKItem * +sec_pkcs12_get_espvk_by_name(SEC_PKCS12Baggage *luggage, + SECItem *name) +{ + PRBool found = PR_FALSE; + SEC_PKCS12ESPVKItem *espvk = NULL; + int i, j; + SECComparison rv = SECEqual; + SECItem *t_name; + SEC_PKCS12BaggageItem *bag; + + if ((luggage == NULL) || (name == NULL)) { + return NULL; + } + + i = 0; + while ((found == PR_FALSE) && (i < luggage->luggage_size)) { + j = 0; + bag = luggage->bags[i]; + while ((found == PR_FALSE) && (j < bag->nEspvks)) { + espvk = bag->espvks[j]; + if (espvk->poolp == NULL) { + espvk->poolp = luggage->poolp; + } + t_name = SECITEM_DupItem(&espvk->espvkData.nickname); + if (t_name != NULL) { + rv = SECITEM_CompareItem(name, t_name); + if (rv == SECEqual) { + found = PR_TRUE; + } + SECITEM_FreeItem(t_name, PR_TRUE); + } else { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return NULL; + } + j++; + } + i++; + } + + if (found != PR_TRUE) { + PORT_SetError(SEC_ERROR_PKCS12_UNABLE_TO_LOCATE_OBJECT_BY_NAME); + return NULL; + } + + return espvk; +} + +/* locates a certificate and copies the thumbprint to the + * appropriate private key + */ +static SECStatus +sec_pkcs12_propagate_thumbprints(SECItem **nicknames, + CERTCertificate **ref_certs, + SEC_PKCS12SafeContents *safe, + SEC_PKCS12Baggage *baggage) +{ + SEC_PKCS12CertAndCRL *cert; + SEC_PKCS12PrivateKey *key; + SEC_PKCS12ESPVKItem *espvk; + int i; + PRBool error = PR_FALSE; + SECStatus rv = SECFailure; + + if ((nicknames == NULL) || (safe == NULL)) { + return SECFailure; + } + + i = 0; + while ((nicknames[i] != NULL) && (error == PR_FALSE)) { + /* process all certs */ + cert = (SEC_PKCS12CertAndCRL *)sec_pkcs12_find_object(safe, baggage, + SEC_OID_PKCS12_CERT_AND_CRL_BAG_ID, + nicknames[i], NULL); + if (cert != NULL) { + /* locate key and copy thumbprint */ + key = (SEC_PKCS12PrivateKey *)sec_pkcs12_find_object(safe, baggage, + SEC_OID_PKCS12_KEY_BAG_ID, + nicknames[i], NULL); + if (key != NULL) { + key->pvkData.poolp = key->poolp; + rv = sec_pkcs12_add_thumbprint(&key->pvkData, + &cert->value.x509->thumbprint); + if (rv == SECFailure) + error = PR_TRUE; /* XXX Set error? */ + } + + /* look in the baggage as well...*/ + if ((baggage != NULL) && (error == PR_FALSE)) { + espvk = sec_pkcs12_get_espvk_by_name(baggage, nicknames[i]); + if (espvk != NULL) { + espvk->espvkData.poolp = espvk->poolp; + rv = sec_pkcs12_add_thumbprint(&espvk->espvkData, + &cert->value.x509->thumbprint); + if (rv == SECFailure) + error = PR_TRUE; /* XXX Set error? */ + } + } + } + i++; + } + + if (error == PR_TRUE) { + return SECFailure; + } + + return SECSuccess; +} + +/* append a safe bag to the end of the safe contents list */ +SECStatus +sec_pkcs12_append_safe_bag(SEC_PKCS12SafeContents *safe, + SEC_PKCS12SafeBag *bag) +{ + int size; + void *mark = NULL, *dummy = NULL; + + if ((bag == NULL) || (safe == NULL)) + return SECFailure; + + mark = PORT_ArenaMark(safe->poolp); + + size = (safe->safe_size * sizeof(SEC_PKCS12SafeBag *)); + + if (safe->safe_size > 0) { + dummy = (SEC_PKCS12SafeBag **)PORT_ArenaGrow(safe->poolp, + safe->contents, + size, + (size + sizeof(SEC_PKCS12SafeBag *))); + safe->contents = dummy; + } else { + safe->contents = (SEC_PKCS12SafeBag **)PORT_ArenaZAlloc(safe->poolp, + (2 * sizeof(SEC_PKCS12SafeBag *))); + dummy = safe->contents; + } + + if (dummy == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + safe->contents[safe->safe_size] = bag; + safe->safe_size++; + safe->contents[safe->safe_size] = NULL; + + PORT_ArenaUnmark(safe->poolp, mark); + return SECSuccess; + +loser: + PORT_ArenaRelease(safe->poolp, mark); + return SECFailure; +} + +/* append a certificate onto the end of a cert bag */ +static SECStatus +sec_pkcs12_append_cert_to_bag(PLArenaPool *arena, + SEC_PKCS12SafeBag *safebag, + CERTCertificate *cert, + SECItem *nickname) +{ + int size; + void *dummy = NULL, *mark = NULL; + SEC_PKCS12CertAndCRL *p12cert; + SEC_PKCS12CertAndCRLBag *bag; + + if ((arena == NULL) || (safebag == NULL) || + (cert == NULL) || (nickname == NULL)) { + return SECFailure; + } + + bag = safebag->safeContent.certAndCRLBag; + if (bag == NULL) { + return SECFailure; + } + + mark = PORT_ArenaMark(arena); + + p12cert = sec_pkcs12_get_cert(arena, cert, nickname); + if (p12cert == NULL) { + PORT_ArenaRelease(bag->poolp, mark); + return SECFailure; + } + + size = bag->bag_size * sizeof(SEC_PKCS12CertAndCRL *); + if (bag->bag_size > 0) { + dummy = (SEC_PKCS12CertAndCRL **)PORT_ArenaGrow(bag->poolp, + bag->certAndCRLs, + size, + size + sizeof(SEC_PKCS12CertAndCRL *)); + bag->certAndCRLs = dummy; + } else { + bag->certAndCRLs = (SEC_PKCS12CertAndCRL **)PORT_ArenaZAlloc(bag->poolp, + (2 * sizeof(SEC_PKCS12CertAndCRL *))); + dummy = bag->certAndCRLs; + } + + if (dummy == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + bag->certAndCRLs[bag->bag_size] = p12cert; + bag->bag_size++; + bag->certAndCRLs[bag->bag_size] = NULL; + + PORT_ArenaUnmark(bag->poolp, mark); + return SECSuccess; + +loser: + PORT_ArenaRelease(bag->poolp, mark); + return SECFailure; +} + +/* append a key onto the end of a list of keys in a key bag */ +SECStatus +sec_pkcs12_append_key_to_bag(SEC_PKCS12SafeBag *safebag, + SEC_PKCS12PrivateKey *pk) +{ + void *mark, *dummy; + SEC_PKCS12PrivateKeyBag *bag; + int size; + + if ((safebag == NULL) || (pk == NULL)) + return SECFailure; + + bag = safebag->safeContent.keyBag; + if (bag == NULL) { + return SECFailure; + } + + mark = PORT_ArenaMark(bag->poolp); + + size = (bag->bag_size * sizeof(SEC_PKCS12PrivateKey *)); + + if (bag->bag_size > 0) { + dummy = (SEC_PKCS12PrivateKey **)PORT_ArenaGrow(bag->poolp, + bag->privateKeys, + size, + size + sizeof(SEC_PKCS12PrivateKey *)); + bag->privateKeys = dummy; + } else { + bag->privateKeys = (SEC_PKCS12PrivateKey **)PORT_ArenaZAlloc(bag->poolp, + (2 * sizeof(SEC_PKCS12PrivateKey *))); + dummy = bag->privateKeys; + } + + if (dummy == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + bag->privateKeys[bag->bag_size] = pk; + bag->bag_size++; + bag->privateKeys[bag->bag_size] = NULL; + + PORT_ArenaUnmark(bag->poolp, mark); + return SECSuccess; + +loser: + /* XXX Free memory? */ + PORT_ArenaRelease(bag->poolp, mark); + return SECFailure; +} + +/* append a safe bag to the baggage area */ +static SECStatus +sec_pkcs12_append_unshrouded_bag(SEC_PKCS12BaggageItem *bag, + SEC_PKCS12SafeBag *u_bag) +{ + int size; + void *mark = NULL, *dummy = NULL; + + if ((bag == NULL) || (u_bag == NULL)) + return SECFailure; + + mark = PORT_ArenaMark(bag->poolp); + + /* dump things into the first bag */ + size = (bag->nSecrets + 1) * sizeof(SEC_PKCS12SafeBag *); + dummy = PORT_ArenaGrow(bag->poolp, + bag->unencSecrets, size, + size + sizeof(SEC_PKCS12SafeBag *)); + bag->unencSecrets = dummy; + if (dummy == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + bag->unencSecrets[bag->nSecrets] = u_bag; + bag->nSecrets++; + bag->unencSecrets[bag->nSecrets] = NULL; + + PORT_ArenaUnmark(bag->poolp, mark); + return SECSuccess; + +loser: + PORT_ArenaRelease(bag->poolp, mark); + return SECFailure; +} + +/* gather up all certificates and keys and package them up + * in the safe, baggage, or both. + * nicknames is the list of nicknames and corresponding certs in ref_certs + * ref_certs a null terminated list of certificates + * rSafe, rBaggage -- return areas for safe and baggage + * shroud_keys -- store keys externally + * pwitem -- password for computing integrity mac and encrypting contents + * wincx -- window handle + * + * if a failure occurs, an error is set and SECFailure returned. + */ +static SECStatus +sec_pkcs12_package_certs_and_keys(SECItem **nicknames, + CERTCertificate **ref_certs, + PRBool unencryptedCerts, + SEC_PKCS12SafeContents **rSafe, + SEC_PKCS12Baggage **rBaggage, + PRBool shroud_keys, + SECOidTag shroud_alg, + SECItem *pwitem, + PKCS12UnicodeConvertFunction unicodeFn, + void *wincx) +{ + PLArenaPool *permArena; + SEC_PKCS12SafeContents *safe = NULL; + SEC_PKCS12Baggage *baggage = NULL; + + SECStatus rv = SECFailure; + PRBool problem = PR_FALSE; + + SEC_PKCS12ESPVKItem *espvk = NULL; + SEC_PKCS12PrivateKey *pk = NULL; + CERTCertificate *add_cert = NULL; + SEC_PKCS12SafeBag *certbag = NULL, *keybag = NULL; + SEC_PKCS12BaggageItem *external_bag = NULL; + int ncerts = 0, nkeys = 0; + int i; + + if ((nicknames == NULL) || (rSafe == NULL) || (rBaggage == NULL)) { + return SECFailure; + } + + *rBaggage = baggage; + *rSafe = safe; + + permArena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE); + if (permArena == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return SECFailure; + } + + /* allocate structures */ + safe = sec_pkcs12_create_safe_contents(permArena); + if (safe == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + rv = SECFailure; + goto loser; + } + + certbag = sec_pkcs12_create_safe_bag(permArena, + SEC_OID_PKCS12_CERT_AND_CRL_BAG_ID); + if (certbag == NULL) { + rv = SECFailure; + goto loser; + } + + if (shroud_keys != PR_TRUE) { + keybag = sec_pkcs12_create_safe_bag(permArena, + SEC_OID_PKCS12_KEY_BAG_ID); + if (keybag == NULL) { + rv = SECFailure; + goto loser; + } + } + + if ((shroud_keys == PR_TRUE) || (unencryptedCerts == PR_TRUE)) { + baggage = sec_pkcs12_create_baggage(permArena); + if (baggage == NULL) { + rv = SECFailure; + goto loser; + } + external_bag = sec_pkcs12_create_external_bag(baggage); + } + + /* package keys and certs */ + i = 0; + while ((nicknames[i] != NULL) && (problem == PR_FALSE)) { + if (ref_certs[i] != NULL) { + /* append cert to bag o certs */ + rv = sec_pkcs12_append_cert_to_bag(permArena, certbag, + ref_certs[i], + nicknames[i]); + if (rv == SECFailure) { + problem = PR_FALSE; + } else { + ncerts++; + } + + if (rv == SECSuccess) { + /* package up them keys */ + if (shroud_keys == PR_TRUE) { + espvk = sec_pkcs12_get_shrouded_key(permArena, + nicknames[i], + ref_certs[i], + shroud_alg, + pwitem, unicodeFn, + wincx); + if (espvk != NULL) { + rv = sec_pkcs12_append_shrouded_key(external_bag, espvk); + SECITEM_CopyItem(permArena, &espvk->derCert, + &ref_certs[i]->derCert); + } else { + rv = SECFailure; + } + } else { + pk = sec_pkcs12_get_private_key(permArena, nicknames[i], + ref_certs[i], wincx); + if (pk != NULL) { + rv = sec_pkcs12_append_key_to_bag(keybag, pk); + SECITEM_CopyItem(permArena, &espvk->derCert, + &ref_certs[i]->derCert); + } else { + rv = SECFailure; + } + } + + if (rv == SECFailure) { + problem = PR_TRUE; + } else { + nkeys++; + } + } + } else { + /* handle only keys here ? */ + problem = PR_TRUE; + } + i++; + } + +/* let success fall through */ +loser: + if (problem == PR_FALSE) { + /* if we have certs, we want to append the cert bag to the + * appropriate area + */ + if (ncerts > 0) { + if (unencryptedCerts != PR_TRUE) { + rv = sec_pkcs12_append_safe_bag(safe, certbag); + } else { + rv = sec_pkcs12_append_unshrouded_bag(external_bag, certbag); + } + } else { + rv = SECSuccess; + } + + /* append key bag, if they are stored in safe contents */ + if ((rv == SECSuccess) && (shroud_keys == PR_FALSE) && (nkeys > 0)) { + rv = sec_pkcs12_append_safe_bag(safe, keybag); + } + } else { + rv = SECFailure; + } + + /* if baggage not used, NULLify it */ + if ((shroud_keys == PR_TRUE) || (unencryptedCerts == PR_TRUE)) { + if (((unencryptedCerts == PR_TRUE) && (ncerts == 0)) && + ((shroud_keys == PR_TRUE) && (nkeys == 0))) + baggage = NULL; + } else { + baggage = NULL; + } + + if ((problem == PR_TRUE) || (rv == SECFailure)) { + PORT_FreeArena(permArena, PR_TRUE); + rv = SECFailure; + baggage = NULL; + safe = NULL; + } + + *rBaggage = baggage; + *rSafe = safe; + + return rv; +} + +/* DER encode the safe contents and return a SECItem. if an error + * occurs, NULL is returned. + */ +static SECItem * +sec_pkcs12_encode_safe_contents(SEC_PKCS12SafeContents *safe) +{ + SECItem *dsafe = NULL, *tsafe; + void *dummy = NULL; + PLArenaPool *arena; + + if (safe == NULL) { + return NULL; + } + + /* rv = sec_pkcs12_prepare_for_der_code_safe(safe, PR_TRUE); + if(rv != SECSuccess) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return NULL; + }*/ + + arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE); + if (arena == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return NULL; + } + + tsafe = (SECItem *)PORT_ArenaZAlloc(arena, sizeof(SECItem)); + if (tsafe != NULL) { + dummy = SEC_ASN1EncodeItem(arena, tsafe, safe, + SEC_PKCS12SafeContentsTemplate); + if (dummy != NULL) { + dsafe = SECITEM_DupItem(tsafe); + } else { + PORT_SetError(SEC_ERROR_NO_MEMORY); + } + } else { + PORT_SetError(SEC_ERROR_NO_MEMORY); + } + + PORT_FreeArena(arena, PR_TRUE); + + return dsafe; +} + +/* prepare the authenicated safe for encoding and encode it. + * baggage is copied to the appropriate area, safe is encoded and + * encrypted. the version and transport mode are set on the asafe. + * the whole ball of wax is then der encoded and packaged up into + * data content info + * safe -- container of certs and keys, is encrypted. + * baggage -- container of certs and keys, keys assumed to be encrypted by + * another method, certs are in the clear + * algorithm -- algorithm by which to encrypt safe + * pwitem -- password for encryption + * wincx - window handle + * + * return of NULL is an error condition. + */ +static SEC_PKCS7ContentInfo * +sec_pkcs12_get_auth_safe(SEC_PKCS12SafeContents *safe, + SEC_PKCS12Baggage *baggage, + SECOidTag algorithm, + SECItem *pwitem, + PKCS12UnicodeConvertFunction unicodeFn, + void *wincx) +{ + SECItem *src = NULL, *dest = NULL, *psalt = NULL; + PLArenaPool *poolp; + SEC_PKCS12AuthenticatedSafe *asafe; + SEC_PKCS7ContentInfo *safe_cinfo = NULL; + SEC_PKCS7ContentInfo *asafe_cinfo = NULL; + void *dummy; + SECStatus rv = SECSuccess; + PRBool swapUnicodeBytes = PR_FALSE; + +#ifdef IS_LITTLE_ENDIAN + swapUnicodeBytes = PR_TRUE; +#endif + + if (((safe != NULL) && (pwitem == NULL)) && (baggage == NULL)) + return NULL; + + poolp = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE); + if (poolp == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return NULL; + } + + /* prepare authenticated safe for encode */ + asafe = sec_pkcs12_new_asafe(poolp); + if (asafe != NULL) { + + /* set version */ + dummy = SEC_ASN1EncodeInteger(asafe->poolp, &asafe->version, + SEC_PKCS12_PFX_VERSION); + if (dummy == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + rv = SECFailure; + goto loser; + } + + /* generate the privacy salt used to create virtual pwd */ + psalt = sec_pkcs12_generate_salt(); + if (psalt != NULL) { + rv = SECITEM_CopyItem(asafe->poolp, &asafe->privacySalt, + psalt); + if (rv == SECSuccess) { + asafe->privacySalt.len *= 8; + } else { + SECITEM_ZfreeItem(psalt, PR_TRUE); + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + } + + if ((psalt == NULL) || (rv == SECFailure)) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + rv = SECFailure; + goto loser; + } + + /* package up safe contents */ + if (safe != NULL) { + safe_cinfo = SEC_PKCS7CreateEncryptedData(algorithm, NULL, wincx); + if ((safe_cinfo != NULL) && (safe->safe_size > 0)) { + /* encode the safe and encrypt the contents of the + * content info + */ + src = sec_pkcs12_encode_safe_contents(safe); + + if (src != NULL) { + rv = SEC_PKCS7SetContent(safe_cinfo, (char *)src->data, src->len); + SECITEM_ZfreeItem(src, PR_TRUE); + if (rv == SECSuccess) { + SECItem *vpwd; + vpwd = sec_pkcs12_create_virtual_password(pwitem, psalt, + unicodeFn, swapUnicodeBytes); + if (vpwd != NULL) { + rv = SEC_PKCS7EncryptContents(NULL, safe_cinfo, + vpwd, wincx); + SECITEM_ZfreeItem(vpwd, PR_TRUE); + } else { + rv = SECFailure; + PORT_SetError(SEC_ERROR_NO_MEMORY); + } + } else { + PORT_SetError(SEC_ERROR_NO_MEMORY); + } + } else { + rv = SECFailure; + } + } else if (safe->safe_size > 0) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } else { + /* case where there is NULL content in the safe contents */ + rv = SEC_PKCS7SetContent(safe_cinfo, NULL, 0); + if (rv != SECFailure) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + } + } + + if (rv != SECSuccess) { + SEC_PKCS7DestroyContentInfo(safe_cinfo); + safe_cinfo = NULL; + goto loser; + } + + asafe->safe = safe_cinfo; + /* + PORT_Memcpy(&asafe->safe, safe_cinfo, sizeof(*safe_cinfo)); + */ + } + + /* copy the baggage to the authenticated safe baggage if present */ + if (baggage != NULL) { + PORT_Memcpy(&asafe->baggage, baggage, sizeof(*baggage)); + } + + /* encode authenticated safe and store it in a Data content info */ + dest = (SECItem *)PORT_ArenaZAlloc(poolp, sizeof(SECItem)); + if (dest != NULL) { + dummy = SEC_ASN1EncodeItem(poolp, dest, asafe, + SEC_PKCS12AuthenticatedSafeTemplate); + if (dummy != NULL) { + asafe_cinfo = SEC_PKCS7CreateData(); + if (asafe_cinfo != NULL) { + rv = SEC_PKCS7SetContent(asafe_cinfo, + (char *)dest->data, + dest->len); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + SEC_PKCS7DestroyContentInfo(asafe_cinfo); + asafe_cinfo = NULL; + } + } + } else { + PORT_SetError(SEC_ERROR_NO_MEMORY); + rv = SECFailure; + } + } + } + +loser: + PORT_FreeArena(poolp, PR_TRUE); + if (safe_cinfo != NULL) { + SEC_PKCS7DestroyContentInfo(safe_cinfo); + } + if (psalt != NULL) { + SECITEM_ZfreeItem(psalt, PR_TRUE); + } + + if (rv == SECFailure) { + return NULL; + } + + return asafe_cinfo; +} + +/* generates the PFX and computes the mac on the authenticated safe + * NULL implies an error + */ +static SEC_PKCS12PFXItem * +sec_pkcs12_get_pfx(SEC_PKCS7ContentInfo *cinfo, + PRBool do_mac, + SECItem *pwitem, PKCS12UnicodeConvertFunction unicodeFn) +{ + SECItem *dest = NULL, *mac = NULL, *salt = NULL, *key = NULL; + SEC_PKCS12PFXItem *pfx; + SECStatus rv = SECFailure; + SGNDigestInfo *di; + SECItem *vpwd; + PRBool swapUnicodeBytes = PR_FALSE; + +#ifdef IS_LITTLE_ENDIAN + swapUnicodeBytes = PR_TRUE; +#endif + + if ((cinfo == NULL) || ((do_mac == PR_TRUE) && (pwitem == NULL))) { + return NULL; + } + + /* allocate new pfx structure */ + pfx = sec_pkcs12_new_pfx(); + if (pfx == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return NULL; + } + + PORT_Memcpy(&pfx->authSafe, cinfo, sizeof(*cinfo)); + if (do_mac == PR_TRUE) { + + /* salt for computing mac */ + salt = sec_pkcs12_generate_salt(); + if (salt != NULL) { + rv = SECITEM_CopyItem(pfx->poolp, &pfx->macData.macSalt, salt); + pfx->macData.macSalt.len *= 8; + + vpwd = sec_pkcs12_create_virtual_password(pwitem, salt, + unicodeFn, swapUnicodeBytes); + if (vpwd == NULL) { + rv = SECFailure; + key = NULL; + } else { + key = sec_pkcs12_generate_key_from_password(SEC_OID_SHA1, + salt, vpwd); + SECITEM_ZfreeItem(vpwd, PR_TRUE); + } + + if ((key != NULL) && (rv == SECSuccess)) { + dest = SEC_PKCS7GetContent(cinfo); + if (dest != NULL) { + + /* compute mac on data -- for password integrity mode */ + mac = sec_pkcs12_generate_mac(key, dest, PR_FALSE); + if (mac != NULL) { + di = SGN_CreateDigestInfo(SEC_OID_SHA1, + mac->data, mac->len); + if (di != NULL) { + rv = SGN_CopyDigestInfo(pfx->poolp, + &pfx->macData.safeMac, di); + SGN_DestroyDigestInfo(di); + } else { + PORT_SetError(SEC_ERROR_NO_MEMORY); + } + SECITEM_ZfreeItem(mac, PR_TRUE); + } + } else { + rv = SECFailure; + } + } else { + PORT_SetError(SEC_ERROR_NO_MEMORY); + rv = SECFailure; + } + + if (key != NULL) { + SECITEM_ZfreeItem(key, PR_TRUE); + } + SECITEM_ZfreeItem(salt, PR_TRUE); + } + } + + if (rv == SECFailure) { + SEC_PKCS12DestroyPFX(pfx); + pfx = NULL; + } + + return pfx; +} + +/* der encode the pfx */ +static SECItem * +sec_pkcs12_encode_pfx(SEC_PKCS12PFXItem *pfx) +{ + SECItem *dest; + void *dummy; + + if (pfx == NULL) { + return NULL; + } + + dest = (SECItem *)PORT_ZAlloc(sizeof(SECItem)); + if (dest == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return NULL; + } + + dummy = SEC_ASN1EncodeItem(NULL, dest, pfx, SEC_PKCS12PFXItemTemplate); + if (dummy == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + SECITEM_ZfreeItem(dest, PR_TRUE); + dest = NULL; + } + + return dest; +} + +SECItem * +SEC_PKCS12GetPFX(char **nicknames, + CERTCertificate **ref_certs, + PRBool shroud_keys, + SEC_PKCS5GetPBEPassword pbef, + void *pbearg, + PKCS12UnicodeConvertFunction unicodeFn, + void *wincx) +{ + SECItem **nicks = NULL; + SEC_PKCS12PFXItem *pfx = NULL; + SEC_PKCS12Baggage *baggage = NULL; + SEC_PKCS12SafeContents *safe = NULL; + SEC_PKCS7ContentInfo *cinfo = NULL; + SECStatus rv = SECFailure; + SECItem *dest = NULL, *pwitem = NULL; + PRBool problem = PR_FALSE; + PRBool unencryptedCerts; + SECOidTag shroud_alg, safe_alg; + + /* how should we encrypt certs ? */ + unencryptedCerts = !SEC_PKCS12IsEncryptionAllowed(); + if (!unencryptedCerts) { + safe_alg = SEC_PKCS12GetPreferredEncryptionAlgorithm(); + if (safe_alg == SEC_OID_UNKNOWN) { + safe_alg = SEC_PKCS12GetStrongestAllowedAlgorithm(); + } + if (safe_alg == SEC_OID_UNKNOWN) { + unencryptedCerts = PR_TRUE; + /* for export where no encryption is allowed, we still need + * to encrypt the NULL contents per the spec. encrypted info + * is known plaintext, so it shouldn't be a problem. + */ + safe_alg = SEC_OID_PKCS12_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC; + } + } else { + /* for export where no encryption is allowed, we still need + * to encrypt the NULL contents per the spec. encrypted info + * is known plaintext, so it shouldn't be a problem. + */ + safe_alg = SEC_OID_PKCS12_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC; + } + + /* keys are always stored with triple DES */ + shroud_alg = SEC_OID_PKCS12_PBE_WITH_SHA1_AND_TRIPLE_DES_CBC; + + /* check for FIPS, if so, do not encrypt certs */ + if (PK11_IsFIPS() && !unencryptedCerts) { + unencryptedCerts = PR_TRUE; + } + + if ((nicknames == NULL) || (pbef == NULL) || (ref_certs == NULL)) { + problem = PR_TRUE; + goto loser; + } + + /* get password */ + pwitem = (*pbef)(pbearg); + if (pwitem == NULL) { + problem = PR_TRUE; + goto loser; + } + nicks = sec_pkcs12_convert_nickname_list(nicknames); + + /* get safe and baggage */ + rv = sec_pkcs12_package_certs_and_keys(nicks, ref_certs, unencryptedCerts, + &safe, &baggage, shroud_keys, + shroud_alg, pwitem, unicodeFn, wincx); + if (rv == SECFailure) { + problem = PR_TRUE; + } + + if ((safe != NULL) && (problem == PR_FALSE)) { + /* copy thumbprints */ + rv = sec_pkcs12_propagate_thumbprints(nicks, ref_certs, safe, baggage); + + /* package everything up into AuthenticatedSafe */ + cinfo = sec_pkcs12_get_auth_safe(safe, baggage, + safe_alg, pwitem, unicodeFn, wincx); + + sec_pkcs12_destroy_cert_content_infos(safe, baggage); + + /* get the pfx and mac it */ + if (cinfo != NULL) { + pfx = sec_pkcs12_get_pfx(cinfo, PR_TRUE, pwitem, unicodeFn); + if (pfx != NULL) { + dest = sec_pkcs12_encode_pfx(pfx); + SEC_PKCS12DestroyPFX(pfx); + } + SEC_PKCS7DestroyContentInfo(cinfo); + } + + if (safe != NULL) { + PORT_FreeArena(safe->poolp, PR_TRUE); + } + } else { + if (safe != NULL) { + PORT_FreeArena(safe->poolp, PR_TRUE); + } + } + +loser: + if (nicks != NULL) { + sec_pkcs12_destroy_nickname_list(nicks); + } + + if (ref_certs != NULL) { + sec_pkcs12_destroy_certificate_list(ref_certs); + } + + if (pwitem != NULL) { + SECITEM_ZfreeItem(pwitem, PR_TRUE); + } + + if (problem == PR_TRUE) { + dest = NULL; + } + + return dest; +} |