gecko-dev/security/nss/lib/pkcs12/p12exp.c
2000-03-31 19:16:26 +00:00

1408 lines
36 KiB
C

/*
* The contents of this file are subject to the Mozilla Public
* License Version 1.1 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* The Original Code is the Netscape security libraries.
*
* The Initial Developer of the Original Code is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1994-2000 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the
* terms of the GNU General Public License Version 2 or later (the
* "GPL"), in which case the provisions of the GPL are applicable
* instead of those above. If you wish to allow use of your
* version of this file only under the terms of the GPL and not to
* allow others to use your version of this file under the MPL,
* indicate your decision by deleting the provisions above and
* replace them with the notice and other provisions required by
* the GPL. If you do not delete the provisions above, a recipient
* may use your version of this file under either the MPL or the
* GPL.
*/
#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(PRArenaPool *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(PRArenaPool *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(PRArenaPool *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(PRArenaPool *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)
{
PRArenaPool *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;
PRArenaPool *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;
PRArenaPool *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;
}