diff options
Diffstat (limited to 'security/nss/lib/pkcs12/p12d.c')
-rw-r--r-- | security/nss/lib/pkcs12/p12d.c | 3576 |
1 files changed, 3576 insertions, 0 deletions
diff --git a/security/nss/lib/pkcs12/p12d.c b/security/nss/lib/pkcs12/p12d.c new file mode 100644 index 000000000..d0b647615 --- /dev/null +++ b/security/nss/lib/pkcs12/p12d.c @@ -0,0 +1,3576 @@ +/* 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 "nssrenam.h" +#include "p12t.h" +#include "p12.h" +#include "plarena.h" +#include "secitem.h" +#include "secoid.h" +#include "seccomon.h" +#include "secport.h" +#include "cert.h" +#include "secpkcs7.h" +#include "secasn1.h" +#include "secerr.h" +#include "pk11func.h" +#include "p12plcy.h" +#include "p12local.h" +#include "secder.h" +#include "secport.h" + +#include "certdb.h" + +#include "prcpucfg.h" + +/* This belongs in secport.h */ +#define PORT_ArenaGrowArray(poolp, oldptr, type, oldnum, newnum) \ + (type *)PORT_ArenaGrow((poolp), (oldptr), \ + (oldnum) * sizeof(type), (newnum) * sizeof(type)) + +typedef struct sec_PKCS12SafeContentsContextStr sec_PKCS12SafeContentsContext; + +/* Opaque structure for decoding SafeContents. These are used + * for each authenticated safe as well as any nested safe contents. + */ +struct sec_PKCS12SafeContentsContextStr { + /* the parent decoder context */ + SEC_PKCS12DecoderContext *p12dcx; + + /* memory arena to allocate space from */ + PLArenaPool *arena; + + /* decoder context and destination for decoding safe contents */ + SEC_ASN1DecoderContext *safeContentsA1Dcx; + sec_PKCS12SafeContents safeContents; + + /* information for decoding safe bags within the safe contents. + * these variables are updated for each safe bag decoded. + */ + SEC_ASN1DecoderContext *currentSafeBagA1Dcx; + sec_PKCS12SafeBag *currentSafeBag; + PRBool skipCurrentSafeBag; + + /* if the safe contents is nested, the parent is pointed to here. */ + sec_PKCS12SafeContentsContext *nestedSafeContentsCtx; +}; + +/* opaque decoder context structure. information for decoding a pkcs 12 + * PDU are stored here as well as decoding pointers for intermediary + * structures which are part of the PKCS 12 PDU. Upon a successful + * decode, the safe bags containing certificates and keys encountered. + */ +struct SEC_PKCS12DecoderContextStr { + PLArenaPool *arena; + PK11SlotInfo *slot; + void *wincx; + PRBool error; + int errorValue; + + /* password */ + SECItem *pwitem; + + /* used for decoding the PFX structure */ + SEC_ASN1DecoderContext *pfxA1Dcx; + sec_PKCS12PFXItem pfx; + + /* safe bags found during decoding */ + sec_PKCS12SafeBag **safeBags; + unsigned int safeBagCount; + + /* state variables for decoding authenticated safes. */ + SEC_PKCS7DecoderContext *currentASafeP7Dcx; + SEC_ASN1DecoderContext *aSafeA1Dcx; + SEC_PKCS7DecoderContext *aSafeP7Dcx; + SEC_PKCS7ContentInfo *aSafeCinfo; + sec_PKCS12AuthenticatedSafe authSafe; + sec_PKCS12SafeContents safeContents; + + /* safe contents info */ + unsigned int safeContentsCnt; + sec_PKCS12SafeContentsContext **safeContentsList; + + /* HMAC info */ + sec_PKCS12MacData macData; + + /* routines for reading back the data to be hmac'd */ + /* They are called as follows. + * + * Stage 1: decode the aSafes cinfo into a buffer in dArg, + * which p12d.c sometimes refers to as the "temp file". + * This occurs during SEC_PKCS12DecoderUpdate calls. + * + * dOpen(dArg, PR_FALSE) + * dWrite(dArg, buf, len) + * ... + * dWrite(dArg, buf, len) + * dClose(dArg, PR_FALSE) + * + * Stage 2: verify MAC + * This occurs SEC_PKCS12DecoderVerify. + * + * dOpen(dArg, PR_TRUE) + * dRead(dArg, buf, IN_BUF_LEN) + * ... + * dRead(dArg, buf, IN_BUF_LEN) + * dClose(dArg, PR_TRUE) + */ + digestOpenFn dOpen; + digestCloseFn dClose; + digestIOFn dRead, dWrite; + void *dArg; + PRBool dIsOpen; /* is the temp file created? */ + + /* helper functions */ + SECKEYGetPasswordKey pwfn; + void *pwfnarg; + PRBool swapUnicodeBytes; + + /* import information */ + PRBool bagsVerified; + + /* buffer management for the default callbacks implementation */ + void *buffer; /* storage area */ + PRInt32 filesize; /* actual data size */ + PRInt32 allocated; /* total buffer size allocated */ + PRInt32 currentpos; /* position counter */ + SECPKCS12TargetTokenCAs tokenCAs; + sec_PKCS12SafeBag **keyList; /* used by ...IterateNext() */ + unsigned int iteration; + SEC_PKCS12DecoderItem decitem; +}; + +/* forward declarations of functions that are used when decoding + * safeContents bags which are nested and when decoding the + * authenticatedSafes. + */ +static SECStatus +sec_pkcs12_decoder_begin_nested_safe_contents(sec_PKCS12SafeContentsContext + *safeContentsCtx); +static SECStatus +sec_pkcs12_decoder_finish_nested_safe_contents(sec_PKCS12SafeContentsContext + *safeContentsCtx); + +/* make sure that the PFX version being decoded is a version + * which we support. + */ +static PRBool +sec_pkcs12_proper_version(sec_PKCS12PFXItem *pfx) +{ + /* if no version, assume it is not supported */ + if (pfx->version.len == 0) { + return PR_FALSE; + } + + if (DER_GetInteger(&pfx->version) > SEC_PKCS12_VERSION) { + return PR_FALSE; + } + + return PR_TRUE; +} + +/* retrieve the key for decrypting the safe contents */ +static PK11SymKey * +sec_pkcs12_decoder_get_decrypt_key(void *arg, SECAlgorithmID *algid) +{ + SEC_PKCS12DecoderContext *p12dcx = (SEC_PKCS12DecoderContext *)arg; + PK11SlotInfo *slot; + PK11SymKey *bulkKey; + + if (!p12dcx) { + return NULL; + } + + /* if no slot specified, use the internal key slot */ + if (p12dcx->slot) { + slot = PK11_ReferenceSlot(p12dcx->slot); + } else { + slot = PK11_GetInternalKeySlot(); + } + + bulkKey = PK11_PBEKeyGen(slot, algid, p12dcx->pwitem, + PR_FALSE, p12dcx->wincx); + /* some tokens can't generate PBE keys on their own, generate the + * key in the internal slot, and let the Import code deal with it, + * (if the slot can't generate PBEs, then we need to use the internal + * slot anyway to unwrap). */ + if (!bulkKey && !PK11_IsInternal(slot)) { + PK11_FreeSlot(slot); + slot = PK11_GetInternalKeySlot(); + bulkKey = PK11_PBEKeyGen(slot, algid, p12dcx->pwitem, + PR_FALSE, p12dcx->wincx); + } + PK11_FreeSlot(slot); + + /* set the password data on the key */ + if (bulkKey) { + PK11_SetSymKeyUserData(bulkKey, p12dcx->pwitem, NULL); + } + + return bulkKey; +} + +/* XXX this needs to be modified to handle enveloped data. most + * likely, it should mirror the routines for SMIME in that regard. + */ +static PRBool +sec_pkcs12_decoder_decryption_allowed(SECAlgorithmID *algid, + PK11SymKey *bulkkey) +{ + PRBool decryptionAllowed = SEC_PKCS12DecryptionAllowed(algid); + + if (!decryptionAllowed) { + return PR_FALSE; + } + + return PR_TRUE; +} + +/* when we encounter a new safe bag during the decoding, we need + * to allocate space for the bag to be decoded to and set the + * state variables appropriately. all of the safe bags are allocated + * in a buffer in the outer SEC_PKCS12DecoderContext, however, + * a pointer to the safeBag is also used in the sec_PKCS12SafeContentsContext + * for the current bag. + */ +static SECStatus +sec_pkcs12_decoder_init_new_safe_bag(sec_PKCS12SafeContentsContext + *safeContentsCtx) +{ + void *mark = NULL; + SEC_PKCS12DecoderContext *p12dcx; + + /* make sure that the structures are defined, and there has + * not been an error in the decoding + */ + if (!safeContentsCtx || !safeContentsCtx->p12dcx || safeContentsCtx->p12dcx->error) { + return SECFailure; + } + + p12dcx = safeContentsCtx->p12dcx; + mark = PORT_ArenaMark(p12dcx->arena); + + /* allocate a new safe bag, if bags already exist, grow the + * list of bags, otherwise allocate a new list. the list is + * NULL terminated. + */ + p12dcx->safeBags = (!p12dcx->safeBagCount) + ? PORT_ArenaZNewArray(p12dcx->arena, sec_PKCS12SafeBag *, 2) + : PORT_ArenaGrowArray(p12dcx->arena, p12dcx->safeBags, + sec_PKCS12SafeBag *, p12dcx->safeBagCount + 1, + p12dcx->safeBagCount + 2); + + if (!p12dcx->safeBags) { + p12dcx->errorValue = PORT_GetError(); + goto loser; + } + + /* append the bag to the end of the list and update the reference + * in the safeContentsCtx. + */ + p12dcx->safeBags[p12dcx->safeBagCount] = + safeContentsCtx->currentSafeBag = + PORT_ArenaZNew(p12dcx->arena, sec_PKCS12SafeBag); + if (!safeContentsCtx->currentSafeBag) { + p12dcx->errorValue = PORT_GetError(); + goto loser; + } + p12dcx->safeBags[++p12dcx->safeBagCount] = NULL; + + safeContentsCtx->currentSafeBag->slot = safeContentsCtx->p12dcx->slot; + safeContentsCtx->currentSafeBag->pwitem = safeContentsCtx->p12dcx->pwitem; + safeContentsCtx->currentSafeBag->swapUnicodeBytes = + safeContentsCtx->p12dcx->swapUnicodeBytes; + safeContentsCtx->currentSafeBag->arena = safeContentsCtx->p12dcx->arena; + safeContentsCtx->currentSafeBag->tokenCAs = + safeContentsCtx->p12dcx->tokenCAs; + + PORT_ArenaUnmark(p12dcx->arena, mark); + return SECSuccess; + +loser: + + /* if an error occurred, release the memory and set the error flag + * the only possible errors triggered by this function are memory + * related. + */ + if (mark) { + PORT_ArenaRelease(p12dcx->arena, mark); + } + + p12dcx->error = PR_TRUE; + return SECFailure; +} + +/* A wrapper for updating the ASN1 context in which a safeBag is + * being decoded. This function is called as a callback from + * secasn1d when decoding SafeContents structures. + */ +static void +sec_pkcs12_decoder_safe_bag_update(void *arg, const char *data, + unsigned long len, int depth, + SEC_ASN1EncodingPart data_kind) +{ + sec_PKCS12SafeContentsContext *safeContentsCtx = + (sec_PKCS12SafeContentsContext *)arg; + SEC_PKCS12DecoderContext *p12dcx; + SECStatus rv; + + /* make sure that we are not skipping the current safeBag, + * and that there are no errors. If so, just return rather + * than continuing to process. + */ + if (!safeContentsCtx || !safeContentsCtx->p12dcx || + safeContentsCtx->p12dcx->error || safeContentsCtx->skipCurrentSafeBag) { + return; + } + p12dcx = safeContentsCtx->p12dcx; + + rv = SEC_ASN1DecoderUpdate(safeContentsCtx->currentSafeBagA1Dcx, data, len); + if (rv != SECSuccess) { + p12dcx->errorValue = PORT_GetError(); + goto loser; + } + + return; + +loser: + /* set the error, and finish the decoder context. because there + * is not a way of returning an error message, it may be worth + * while to do a check higher up and finish any decoding contexts + * that are still open. + */ + p12dcx->error = PR_TRUE; + SEC_ASN1DecoderFinish(safeContentsCtx->currentSafeBagA1Dcx); + safeContentsCtx->currentSafeBagA1Dcx = NULL; + return; +} + +/* notify function for decoding safeBags. This function is + * used to filter safeBag types which are not supported, + * initiate the decoding of nested safe contents, and decode + * safeBags in general. this function is set when the decoder + * context for the safeBag is first created. + */ +static void +sec_pkcs12_decoder_safe_bag_notify(void *arg, PRBool before, + void *dest, int real_depth) +{ + sec_PKCS12SafeContentsContext *safeContentsCtx = + (sec_PKCS12SafeContentsContext *)arg; + SEC_PKCS12DecoderContext *p12dcx; + sec_PKCS12SafeBag *bag; + PRBool after; + + /* if an error is encountered, return */ + if (!safeContentsCtx || !safeContentsCtx->p12dcx || + safeContentsCtx->p12dcx->error) { + return; + } + p12dcx = safeContentsCtx->p12dcx; + + /* to make things more readable */ + if (before) + after = PR_FALSE; + else + after = PR_TRUE; + + /* have we determined the safeBagType yet? */ + bag = safeContentsCtx->currentSafeBag; + if (bag->bagTypeTag == NULL) { + if (after && (dest == &(bag->safeBagType))) { + bag->bagTypeTag = SECOID_FindOID(&(bag->safeBagType)); + if (bag->bagTypeTag == NULL) { + p12dcx->error = PR_TRUE; + p12dcx->errorValue = SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE; + } + } + return; + } + + /* process the safeBag depending on it's type. those + * which we do not support, are ignored. we start a decoding + * context for a nested safeContents. + */ + switch (bag->bagTypeTag->offset) { + case SEC_OID_PKCS12_V1_KEY_BAG_ID: + case SEC_OID_PKCS12_V1_CERT_BAG_ID: + case SEC_OID_PKCS12_V1_PKCS8_SHROUDED_KEY_BAG_ID: + break; + case SEC_OID_PKCS12_V1_SAFE_CONTENTS_BAG_ID: + /* if we are just starting to decode the safeContents, initialize + * a new safeContentsCtx to process it. + */ + if (before && (dest == &(bag->safeBagContent))) { + sec_pkcs12_decoder_begin_nested_safe_contents(safeContentsCtx); + } else if (after && (dest == &(bag->safeBagContent))) { + /* clean up the nested decoding */ + sec_pkcs12_decoder_finish_nested_safe_contents(safeContentsCtx); + } + break; + case SEC_OID_PKCS12_V1_CRL_BAG_ID: + case SEC_OID_PKCS12_V1_SECRET_BAG_ID: + default: + /* skip any safe bag types we don't understand or handle */ + safeContentsCtx->skipCurrentSafeBag = PR_TRUE; + break; + } + + return; +} + +/* notify function for decoding safe contents. each entry in the + * safe contents is a safeBag which needs to be allocated and + * the decoding context initialized at the beginning and then + * the context needs to be closed and finished at the end. + * + * this function is set when the safeContents decode context is + * initialized. + */ +static void +sec_pkcs12_decoder_safe_contents_notify(void *arg, PRBool before, + void *dest, int real_depth) +{ + sec_PKCS12SafeContentsContext *safeContentsCtx = + (sec_PKCS12SafeContentsContext *)arg; + SEC_PKCS12DecoderContext *p12dcx; + SECStatus rv; + + /* if there is an error we don't want to continue processing, + * just return and keep going. + */ + if (!safeContentsCtx || !safeContentsCtx->p12dcx || + safeContentsCtx->p12dcx->error) { + return; + } + p12dcx = safeContentsCtx->p12dcx; + + /* if we are done with the current safeBag, then we need to + * finish the context and set the state variables appropriately. + */ + if (!before) { + SEC_ASN1DecoderClearFilterProc(safeContentsCtx->safeContentsA1Dcx); + SEC_ASN1DecoderFinish(safeContentsCtx->currentSafeBagA1Dcx); + safeContentsCtx->currentSafeBagA1Dcx = NULL; + safeContentsCtx->skipCurrentSafeBag = PR_FALSE; + } else { + /* we are starting a new safe bag. we need to allocate space + * for the bag and initialize the decoding context. + */ + rv = sec_pkcs12_decoder_init_new_safe_bag(safeContentsCtx); + if (rv != SECSuccess) { + goto loser; + } + + /* set up the decoder context */ + safeContentsCtx->currentSafeBagA1Dcx = + SEC_ASN1DecoderStart(p12dcx->arena, + safeContentsCtx->currentSafeBag, + sec_PKCS12SafeBagTemplate); + if (!safeContentsCtx->currentSafeBagA1Dcx) { + p12dcx->errorValue = PORT_GetError(); + goto loser; + } + + /* set the notify and filter procs so that the safe bag + * data gets sent to the proper location when decoding. + */ + SEC_ASN1DecoderSetNotifyProc(safeContentsCtx->currentSafeBagA1Dcx, + sec_pkcs12_decoder_safe_bag_notify, + safeContentsCtx); + SEC_ASN1DecoderSetFilterProc(safeContentsCtx->safeContentsA1Dcx, + sec_pkcs12_decoder_safe_bag_update, + safeContentsCtx, PR_TRUE); + } + + return; + +loser: + /* in the event of an error, we want to close the decoding + * context and clear the filter and notify procedures. + */ + p12dcx->error = PR_TRUE; + + if (safeContentsCtx->currentSafeBagA1Dcx) { + SEC_ASN1DecoderFinish(safeContentsCtx->currentSafeBagA1Dcx); + safeContentsCtx->currentSafeBagA1Dcx = NULL; + } + + SEC_ASN1DecoderClearNotifyProc(safeContentsCtx->safeContentsA1Dcx); + SEC_ASN1DecoderClearFilterProc(safeContentsCtx->safeContentsA1Dcx); + + return; +} + +/* initialize the safeContents for decoding. this routine + * is used for authenticatedSafes as well as nested safeContents. + */ +static sec_PKCS12SafeContentsContext * +sec_pkcs12_decoder_safe_contents_init_decode(SEC_PKCS12DecoderContext *p12dcx, + PRBool nestedSafe) +{ + sec_PKCS12SafeContentsContext *safeContentsCtx = NULL; + const SEC_ASN1Template *theTemplate; + + if (!p12dcx || p12dcx->error) { + return NULL; + } + + /* allocate a new safeContents list or grow the existing list and + * append the new safeContents onto the end. + */ + p12dcx->safeContentsList = (!p12dcx->safeContentsCnt) + ? PORT_ArenaZNewArray(p12dcx->arena, sec_PKCS12SafeContentsContext *, 2) + : PORT_ArenaGrowArray(p12dcx->arena, p12dcx->safeContentsList, + sec_PKCS12SafeContentsContext *, + 1 + p12dcx->safeContentsCnt, + 2 + p12dcx->safeContentsCnt); + + if (!p12dcx->safeContentsList) { + p12dcx->errorValue = PORT_GetError(); + goto loser; + } + + p12dcx->safeContentsList[p12dcx->safeContentsCnt] = safeContentsCtx = + PORT_ArenaZNew(p12dcx->arena, sec_PKCS12SafeContentsContext); + if (!p12dcx->safeContentsList[p12dcx->safeContentsCnt]) { + p12dcx->errorValue = PORT_GetError(); + goto loser; + } + p12dcx->safeContentsList[++p12dcx->safeContentsCnt] = NULL; + + /* set up the state variables */ + safeContentsCtx->p12dcx = p12dcx; + safeContentsCtx->arena = p12dcx->arena; + + /* begin the decoding -- the template is based on whether we are + * decoding a nested safeContents or not. + */ + if (nestedSafe == PR_TRUE) { + theTemplate = sec_PKCS12NestedSafeContentsDecodeTemplate; + } else { + theTemplate = sec_PKCS12SafeContentsDecodeTemplate; + } + + /* start the decoder context */ + safeContentsCtx->safeContentsA1Dcx = SEC_ASN1DecoderStart(p12dcx->arena, + &safeContentsCtx->safeContents, + theTemplate); + + if (!safeContentsCtx->safeContentsA1Dcx) { + p12dcx->errorValue = PORT_GetError(); + goto loser; + } + + /* set the safeContents notify procedure to look for + * and start the decode of safeBags. + */ + SEC_ASN1DecoderSetNotifyProc(safeContentsCtx->safeContentsA1Dcx, + sec_pkcs12_decoder_safe_contents_notify, + safeContentsCtx); + + return safeContentsCtx; + +loser: + /* in the case of an error, we want to finish the decoder + * context and set the error flag. + */ + if (safeContentsCtx && safeContentsCtx->safeContentsA1Dcx) { + SEC_ASN1DecoderFinish(safeContentsCtx->safeContentsA1Dcx); + safeContentsCtx->safeContentsA1Dcx = NULL; + } + + p12dcx->error = PR_TRUE; + + return NULL; +} + +/* wrapper for updating safeContents. this is set as the filter of + * safeBag when there is a nested safeContents. + */ +static void +sec_pkcs12_decoder_nested_safe_contents_update(void *arg, const char *buf, + unsigned long len, int depth, + SEC_ASN1EncodingPart data_kind) +{ + sec_PKCS12SafeContentsContext *safeContentsCtx = + (sec_PKCS12SafeContentsContext *)arg; + SEC_PKCS12DecoderContext *p12dcx; + SECStatus rv; + + /* check for an error */ + if (!safeContentsCtx || !safeContentsCtx->p12dcx || + safeContentsCtx->p12dcx->error || !safeContentsCtx->safeContentsA1Dcx) { + return; + } + + /* no need to update if no data sent in */ + if (!len || !buf) { + return; + } + + /* update the decoding context */ + p12dcx = safeContentsCtx->p12dcx; + rv = SEC_ASN1DecoderUpdate(safeContentsCtx->safeContentsA1Dcx, buf, len); + if (rv != SECSuccess) { + p12dcx->errorValue = PORT_GetError(); + goto loser; + } + + return; + +loser: + /* handle any errors. If a decoding context is open, close it. */ + p12dcx->error = PR_TRUE; + if (safeContentsCtx->safeContentsA1Dcx) { + SEC_ASN1DecoderFinish(safeContentsCtx->safeContentsA1Dcx); + safeContentsCtx->safeContentsA1Dcx = NULL; + } +} + +/* whenever a new safeContentsSafeBag is encountered, we need + * to init a safeContentsContext. + */ +static SECStatus +sec_pkcs12_decoder_begin_nested_safe_contents(sec_PKCS12SafeContentsContext + *safeContentsCtx) +{ + /* check for an error */ + if (!safeContentsCtx || !safeContentsCtx->p12dcx || + safeContentsCtx->p12dcx->error) { + return SECFailure; + } + + safeContentsCtx->nestedSafeContentsCtx = + sec_pkcs12_decoder_safe_contents_init_decode(safeContentsCtx->p12dcx, + PR_TRUE); + if (!safeContentsCtx->nestedSafeContentsCtx) { + return SECFailure; + } + + /* set up new filter proc */ + SEC_ASN1DecoderSetNotifyProc( + safeContentsCtx->nestedSafeContentsCtx->safeContentsA1Dcx, + sec_pkcs12_decoder_safe_contents_notify, + safeContentsCtx->nestedSafeContentsCtx); + + SEC_ASN1DecoderSetFilterProc(safeContentsCtx->currentSafeBagA1Dcx, + sec_pkcs12_decoder_nested_safe_contents_update, + safeContentsCtx->nestedSafeContentsCtx, + PR_TRUE); + + return SECSuccess; +} + +/* when the safeContents is done decoding, we need to reset the + * proper filter and notify procs and close the decoding context + */ +static SECStatus +sec_pkcs12_decoder_finish_nested_safe_contents(sec_PKCS12SafeContentsContext + *safeContentsCtx) +{ + /* check for error */ + if (!safeContentsCtx || !safeContentsCtx->p12dcx || + safeContentsCtx->p12dcx->error) { + return SECFailure; + } + + /* clean up */ + SEC_ASN1DecoderClearFilterProc(safeContentsCtx->currentSafeBagA1Dcx); + SEC_ASN1DecoderClearNotifyProc( + safeContentsCtx->nestedSafeContentsCtx->safeContentsA1Dcx); + SEC_ASN1DecoderFinish( + safeContentsCtx->nestedSafeContentsCtx->safeContentsA1Dcx); + safeContentsCtx->nestedSafeContentsCtx->safeContentsA1Dcx = NULL; + safeContentsCtx->nestedSafeContentsCtx = NULL; + + return SECSuccess; +} + +/* wrapper for updating safeContents. This is used when decoding + * the nested safeContents and any authenticatedSafes. + */ +static void +sec_pkcs12_decoder_safe_contents_callback(void *arg, const char *buf, + unsigned long len) +{ + SECStatus rv; + sec_PKCS12SafeContentsContext *safeContentsCtx = + (sec_PKCS12SafeContentsContext *)arg; + SEC_PKCS12DecoderContext *p12dcx; + + /* check for error */ + if (!safeContentsCtx || !safeContentsCtx->p12dcx || + safeContentsCtx->p12dcx->error || !safeContentsCtx->safeContentsA1Dcx) { + return; + } + p12dcx = safeContentsCtx->p12dcx; + + /* update the decoder */ + rv = SEC_ASN1DecoderUpdate(safeContentsCtx->safeContentsA1Dcx, buf, len); + if (rv != SECSuccess) { + /* if we fail while trying to decode a 'safe', it's probably because + * we didn't have the correct password. */ + PORT_SetError(SEC_ERROR_BAD_PASSWORD); + p12dcx->errorValue = SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE; + SEC_PKCS7DecoderAbort(p12dcx->currentASafeP7Dcx, SEC_ERROR_BAD_PASSWORD); + goto loser; + } + + return; + +loser: + /* set the error and finish the context */ + p12dcx->error = PR_TRUE; + if (safeContentsCtx->safeContentsA1Dcx) { + SEC_ASN1DecoderFinish(safeContentsCtx->safeContentsA1Dcx); + safeContentsCtx->safeContentsA1Dcx = NULL; + } + + return; +} + +/* this is a wrapper for the ASN1 decoder to call SEC_PKCS7DecoderUpdate + */ +static void +sec_pkcs12_decoder_wrap_p7_update(void *arg, const char *data, + unsigned long len, int depth, + SEC_ASN1EncodingPart data_kind) +{ + SEC_PKCS7DecoderContext *p7dcx = (SEC_PKCS7DecoderContext *)arg; + + SEC_PKCS7DecoderUpdate(p7dcx, data, len); +} + +/* notify function for decoding aSafes. at the beginning, + * of an authenticatedSafe, we start a decode of a safeContents. + * at the end, we clean up the safeContents decoder context and + * reset state variables + */ +static void +sec_pkcs12_decoder_asafes_notify(void *arg, PRBool before, void *dest, + int real_depth) +{ + SEC_PKCS12DecoderContext *p12dcx; + sec_PKCS12SafeContentsContext *safeContentsCtx; + + /* make sure no error occurred. */ + p12dcx = (SEC_PKCS12DecoderContext *)arg; + if (!p12dcx || p12dcx->error) { + return; + } + + if (before) { + + /* init a new safeContentsContext */ + safeContentsCtx = sec_pkcs12_decoder_safe_contents_init_decode(p12dcx, + PR_FALSE); + if (!safeContentsCtx) { + goto loser; + } + + /* initiate the PKCS7ContentInfo decode */ + p12dcx->currentASafeP7Dcx = SEC_PKCS7DecoderStart( + sec_pkcs12_decoder_safe_contents_callback, + safeContentsCtx, + p12dcx->pwfn, p12dcx->pwfnarg, + sec_pkcs12_decoder_get_decrypt_key, p12dcx, + sec_pkcs12_decoder_decryption_allowed); + if (!p12dcx->currentASafeP7Dcx) { + p12dcx->errorValue = PORT_GetError(); + goto loser; + } + SEC_ASN1DecoderSetFilterProc(p12dcx->aSafeA1Dcx, + sec_pkcs12_decoder_wrap_p7_update, + p12dcx->currentASafeP7Dcx, PR_TRUE); + } + + if (!before) { + /* if one is being decoded, finish the decode */ + if (p12dcx->currentASafeP7Dcx != NULL) { + SEC_PKCS7ContentInfo *cinfo; + unsigned int cnt = p12dcx->safeContentsCnt - 1; + safeContentsCtx = p12dcx->safeContentsList[cnt]; + if (safeContentsCtx->safeContentsA1Dcx) { + SEC_ASN1DecoderFinish(safeContentsCtx->safeContentsA1Dcx); + safeContentsCtx->safeContentsA1Dcx = NULL; + } + cinfo = SEC_PKCS7DecoderFinish(p12dcx->currentASafeP7Dcx); + p12dcx->currentASafeP7Dcx = NULL; + if (!cinfo) { + p12dcx->errorValue = PORT_GetError(); + goto loser; + } + SEC_PKCS7DestroyContentInfo(cinfo); /* don't leak it */ + } + } + + return; + +loser: + /* set the error flag */ + p12dcx->error = PR_TRUE; + return; +} + +/* wrapper for updating asafes decoding context. this function + * writes data being decoded to disk, so that a mac can be computed + * later. + */ +static void +sec_pkcs12_decoder_asafes_callback(void *arg, const char *buf, + unsigned long len) +{ + SEC_PKCS12DecoderContext *p12dcx = (SEC_PKCS12DecoderContext *)arg; + SECStatus rv; + + if (!p12dcx || p12dcx->error) { + return; + } + + /* update the context */ + rv = SEC_ASN1DecoderUpdate(p12dcx->aSafeA1Dcx, buf, len); + if (rv != SECSuccess) { + p12dcx->errorValue = PORT_GetError(); + p12dcx->error = PR_TRUE; + goto loser; + } + + /* if we are writing to a file, write out the new information */ + if (p12dcx->dWrite) { + unsigned long writeLen = (*p12dcx->dWrite)(p12dcx->dArg, + (unsigned char *)buf, len); + if (writeLen != len) { + p12dcx->errorValue = PORT_GetError(); + goto loser; + } + } + + return; + +loser: + /* set the error flag */ + p12dcx->error = PR_TRUE; + SEC_ASN1DecoderFinish(p12dcx->aSafeA1Dcx); + p12dcx->aSafeA1Dcx = NULL; + + return; +} + +/* start the decode of an authenticatedSafe contentInfo. + */ +static SECStatus +sec_pkcs12_decode_start_asafes_cinfo(SEC_PKCS12DecoderContext *p12dcx) +{ + if (!p12dcx || p12dcx->error) { + return SECFailure; + } + + /* start the decode context */ + p12dcx->aSafeA1Dcx = SEC_ASN1DecoderStart(p12dcx->arena, + &p12dcx->authSafe, + sec_PKCS12AuthenticatedSafeTemplate); + if (!p12dcx->aSafeA1Dcx) { + p12dcx->errorValue = PORT_GetError(); + goto loser; + } + + /* set the notify function */ + SEC_ASN1DecoderSetNotifyProc(p12dcx->aSafeA1Dcx, + sec_pkcs12_decoder_asafes_notify, p12dcx); + + /* begin the authSafe decoder context */ + p12dcx->aSafeP7Dcx = SEC_PKCS7DecoderStart( + sec_pkcs12_decoder_asafes_callback, p12dcx, + p12dcx->pwfn, p12dcx->pwfnarg, NULL, NULL, NULL); + if (!p12dcx->aSafeP7Dcx) { + p12dcx->errorValue = PORT_GetError(); + goto loser; + } + + /* open the temp file for writing, if the digest functions were set */ + if (p12dcx->dOpen && (*p12dcx->dOpen)(p12dcx->dArg, PR_FALSE) != SECSuccess) { + p12dcx->errorValue = PORT_GetError(); + goto loser; + } + /* dOpen(dArg, PR_FALSE) creates the temp file */ + p12dcx->dIsOpen = PR_TRUE; + + return SECSuccess; + +loser: + p12dcx->error = PR_TRUE; + + if (p12dcx->aSafeA1Dcx) { + SEC_ASN1DecoderFinish(p12dcx->aSafeA1Dcx); + p12dcx->aSafeA1Dcx = NULL; + } + + if (p12dcx->aSafeP7Dcx) { + SEC_PKCS7DecoderFinish(p12dcx->aSafeP7Dcx); + p12dcx->aSafeP7Dcx = NULL; + } + + return SECFailure; +} + +/* wrapper for updating the safeContents. this function is used as + * a filter for the pfx when decoding the authenticated safes + */ +static void +sec_pkcs12_decode_asafes_cinfo_update(void *arg, const char *buf, + unsigned long len, int depth, + SEC_ASN1EncodingPart data_kind) +{ + SEC_PKCS12DecoderContext *p12dcx; + SECStatus rv; + + p12dcx = (SEC_PKCS12DecoderContext *)arg; + if (!p12dcx || p12dcx->error) { + return; + } + + /* update the safeContents decoder */ + rv = SEC_PKCS7DecoderUpdate(p12dcx->aSafeP7Dcx, buf, len); + if (rv != SECSuccess) { + p12dcx->errorValue = SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE; + goto loser; + } + + return; + +loser: + + /* did we find an error? if so, close the context and set the + * error flag. + */ + SEC_PKCS7DecoderFinish(p12dcx->aSafeP7Dcx); + p12dcx->aSafeP7Dcx = NULL; + p12dcx->error = PR_TRUE; +} + +/* notify procedure used while decoding the pfx. When we encounter + * the authSafes, we want to trigger the decoding of authSafes as well + * as when we encounter the macData, trigger the decoding of it. we do + * this because we we are streaming the decoder and not decoding in place. + * the pfx which is the destination, only has the version decoded into it. + */ +static void +sec_pkcs12_decoder_pfx_notify_proc(void *arg, PRBool before, void *dest, + int real_depth) +{ + SECStatus rv; + SEC_PKCS12DecoderContext *p12dcx = (SEC_PKCS12DecoderContext *)arg; + + /* if an error occurs, clear the notifyProc and the filterProc + * and continue. + */ + if (p12dcx->error) { + SEC_ASN1DecoderClearNotifyProc(p12dcx->pfxA1Dcx); + SEC_ASN1DecoderClearFilterProc(p12dcx->pfxA1Dcx); + return; + } + + if (before && (dest == &p12dcx->pfx.encodedAuthSafe)) { + + /* we want to make sure this is a version we support */ + if (!sec_pkcs12_proper_version(&p12dcx->pfx)) { + p12dcx->errorValue = SEC_ERROR_PKCS12_UNSUPPORTED_VERSION; + goto loser; + } + + /* start the decode of the aSafes cinfo... */ + rv = sec_pkcs12_decode_start_asafes_cinfo(p12dcx); + if (rv != SECSuccess) { + goto loser; + } + + /* set the filter proc to update the authenticated safes. */ + SEC_ASN1DecoderSetFilterProc(p12dcx->pfxA1Dcx, + sec_pkcs12_decode_asafes_cinfo_update, + p12dcx, PR_TRUE); + } + + if (!before && (dest == &p12dcx->pfx.encodedAuthSafe)) { + + /* we are done decoding the authenticatedSafes, so we need to + * finish the decoderContext and clear the filter proc + * and close the hmac callback, if present + */ + p12dcx->aSafeCinfo = SEC_PKCS7DecoderFinish(p12dcx->aSafeP7Dcx); + p12dcx->aSafeP7Dcx = NULL; + if (!p12dcx->aSafeCinfo) { + p12dcx->errorValue = PORT_GetError(); + goto loser; + } + SEC_ASN1DecoderClearFilterProc(p12dcx->pfxA1Dcx); + if (p12dcx->dClose && ((*p12dcx->dClose)(p12dcx->dArg, PR_FALSE) != SECSuccess)) { + p12dcx->errorValue = PORT_GetError(); + goto loser; + } + } + + return; + +loser: + p12dcx->error = PR_TRUE; +} + +/* default implementations of the open/close/read/write functions for + SEC_PKCS12DecoderStart +*/ + +#define DEFAULT_TEMP_SIZE 4096 + +static SECStatus +p12u_DigestOpen(void *arg, PRBool readData) +{ + SEC_PKCS12DecoderContext *p12cxt = arg; + + p12cxt->currentpos = 0; + + if (PR_FALSE == readData) { + /* allocate an initial buffer */ + p12cxt->filesize = 0; + p12cxt->allocated = DEFAULT_TEMP_SIZE; + p12cxt->buffer = PORT_Alloc(DEFAULT_TEMP_SIZE); + PR_ASSERT(p12cxt->buffer); + } else { + PR_ASSERT(p12cxt->buffer); + if (!p12cxt->buffer) { + return SECFailure; /* no data to read */ + } + } + + return SECSuccess; +} + +static SECStatus +p12u_DigestClose(void *arg, PRBool removeFile) +{ + SEC_PKCS12DecoderContext *p12cxt = arg; + + PR_ASSERT(p12cxt); + if (!p12cxt) { + return SECFailure; + } + p12cxt->currentpos = 0; + + if (PR_TRUE == removeFile) { + PR_ASSERT(p12cxt->buffer); + if (!p12cxt->buffer) { + return SECFailure; + } + if (p12cxt->buffer) { + PORT_Free(p12cxt->buffer); + p12cxt->buffer = NULL; + p12cxt->allocated = 0; + p12cxt->filesize = 0; + } + } + + return SECSuccess; +} + +static int +p12u_DigestRead(void *arg, unsigned char *buf, unsigned long len) +{ + int toread = len; + SEC_PKCS12DecoderContext *p12cxt = arg; + + if (!buf || len == 0 || !p12cxt->buffer) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return -1; + } + + if ((p12cxt->filesize - p12cxt->currentpos) < (long)len) { + /* trying to read past the end of the buffer */ + toread = p12cxt->filesize - p12cxt->currentpos; + } + memcpy(buf, (char *)p12cxt->buffer + p12cxt->currentpos, toread); + p12cxt->currentpos += toread; + return toread; +} + +static int +p12u_DigestWrite(void *arg, unsigned char *buf, unsigned long len) +{ + SEC_PKCS12DecoderContext *p12cxt = arg; + + if (!buf || len == 0) { + return -1; + } + + if (p12cxt->currentpos + (long)len > p12cxt->filesize) { + p12cxt->filesize = p12cxt->currentpos + len; + } else { + p12cxt->filesize += len; + } + if (p12cxt->filesize > p12cxt->allocated) { + void *newbuffer; + size_t newsize = p12cxt->filesize + DEFAULT_TEMP_SIZE; + newbuffer = PORT_Realloc(p12cxt->buffer, newsize); + if (NULL == newbuffer) { + return -1; /* can't extend the buffer */ + } + p12cxt->buffer = newbuffer; + p12cxt->allocated = newsize; + } + PR_ASSERT(p12cxt->buffer); + memcpy((char *)p12cxt->buffer + p12cxt->currentpos, buf, len); + p12cxt->currentpos += len; + return len; +} + +/* SEC_PKCS12DecoderStart + * Creates a decoder context for decoding a PKCS 12 PDU objct. + * This function sets up the initial decoding context for the + * PFX and sets the needed state variables. + * + * pwitem - the password for the hMac and any encoded safes. + * this should be changed to take a callback which retrieves + * the password. it may be possible for different safes to + * have different passwords. also, the password is already + * in unicode. it should probably be converted down below via + * a unicode conversion callback. + * slot - the slot to import the dataa into should multiple slots + * be supported based on key type and cert type? + * dOpen, dClose, dRead, dWrite - digest routines for writing data + * to a file so it could be read back and the hmac recomputed + * and verified. doesn't seem to be a way for both encoding + * and decoding to be single pass, thus the need for these + * routines. + * dArg - the argument for dOpen, etc. + * + * if NULL == dOpen == dClose == dRead == dWrite == dArg, then default + * implementations using a memory buffer are used + * + * This function returns the decoder context, if it was successful. + * Otherwise, null is returned. + */ +SEC_PKCS12DecoderContext * +SEC_PKCS12DecoderStart(SECItem *pwitem, PK11SlotInfo *slot, void *wincx, + digestOpenFn dOpen, digestCloseFn dClose, + digestIOFn dRead, digestIOFn dWrite, void *dArg) +{ + SEC_PKCS12DecoderContext *p12dcx; + PLArenaPool *arena; + + arena = PORT_NewArena(2048); /* different size? */ + if (!arena) { + return NULL; /* error is already set */ + } + + /* allocate the decoder context and set the state variables */ + p12dcx = PORT_ArenaZNew(arena, SEC_PKCS12DecoderContext); + if (!p12dcx) { + goto loser; /* error is already set */ + } + + if (!dOpen && !dClose && !dRead && !dWrite && !dArg) { + /* use default implementations */ + dOpen = p12u_DigestOpen; + dClose = p12u_DigestClose; + dRead = p12u_DigestRead; + dWrite = p12u_DigestWrite; + dArg = (void *)p12dcx; + } + + p12dcx->arena = arena; + p12dcx->pwitem = pwitem; + p12dcx->slot = (slot ? PK11_ReferenceSlot(slot) + : PK11_GetInternalKeySlot()); + p12dcx->wincx = wincx; + p12dcx->tokenCAs = SECPKCS12TargetTokenNoCAs; +#ifdef IS_LITTLE_ENDIAN + p12dcx->swapUnicodeBytes = PR_TRUE; +#else + p12dcx->swapUnicodeBytes = PR_FALSE; +#endif + p12dcx->errorValue = 0; + p12dcx->error = PR_FALSE; + + /* start the decoding of the PFX and set the notify proc + * for the PFX item. + */ + p12dcx->pfxA1Dcx = SEC_ASN1DecoderStart(p12dcx->arena, &p12dcx->pfx, + sec_PKCS12PFXItemTemplate); + if (!p12dcx->pfxA1Dcx) { + PK11_FreeSlot(p12dcx->slot); + goto loser; + } + + SEC_ASN1DecoderSetNotifyProc(p12dcx->pfxA1Dcx, + sec_pkcs12_decoder_pfx_notify_proc, + p12dcx); + + /* set up digest functions */ + p12dcx->dOpen = dOpen; + p12dcx->dWrite = dWrite; + p12dcx->dClose = dClose; + p12dcx->dRead = dRead; + p12dcx->dArg = dArg; + p12dcx->dIsOpen = PR_FALSE; + + p12dcx->keyList = NULL; + p12dcx->decitem.type = 0; + p12dcx->decitem.der = NULL; + p12dcx->decitem.hasKey = PR_FALSE; + p12dcx->decitem.friendlyName = NULL; + p12dcx->iteration = 0; + + return p12dcx; + +loser: + PORT_FreeArena(arena, PR_TRUE); + return NULL; +} + +SECStatus +SEC_PKCS12DecoderSetTargetTokenCAs(SEC_PKCS12DecoderContext *p12dcx, + SECPKCS12TargetTokenCAs tokenCAs) +{ + if (!p12dcx || p12dcx->error) { + return SECFailure; + } + p12dcx->tokenCAs = tokenCAs; + return SECSuccess; +} + +/* SEC_PKCS12DecoderUpdate + * Streaming update sending more data to the decoder. If + * an error occurs, SECFailure is returned. + * + * p12dcx - the decoder context + * data, len - the data buffer and length of data to send to + * the update functions. + */ +SECStatus +SEC_PKCS12DecoderUpdate(SEC_PKCS12DecoderContext *p12dcx, + unsigned char *data, unsigned long len) +{ + SECStatus rv; + + if (!p12dcx || p12dcx->error) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + /* update the PFX decoder context */ + rv = SEC_ASN1DecoderUpdate(p12dcx->pfxA1Dcx, (const char *)data, len); + if (rv != SECSuccess) { + p12dcx->errorValue = SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE; + goto loser; + } + + return SECSuccess; + +loser: + + p12dcx->error = PR_TRUE; + return SECFailure; +} + +/* This should be a nice sized buffer for reading in data (potentially large +** amounts) to be MACed. It should be MUCH larger than HASH_LENGTH_MAX. +*/ +#define IN_BUF_LEN 1024 +#ifdef DEBUG +static const char bufferEnd[] = { "BufferEnd" }; +#endif +#define FUDGE 128 /* must be as large as bufferEnd or more. */ + +/* verify the hmac by reading the data from the temporary file + * using the routines specified when the decodingContext was + * created and return SECSuccess if the hmac matches. + */ +static SECStatus +sec_pkcs12_decoder_verify_mac(SEC_PKCS12DecoderContext *p12dcx) +{ + PK11Context *pk11cx = NULL; + PK11SymKey *symKey = NULL; + SECItem *params = NULL; + unsigned char *buf; + SECStatus rv = SECFailure; + SECStatus lrv; + unsigned int bufLen; + int iteration; + int bytesRead; + SECOidTag algtag; + SECItem hmacRes; + SECItem ignore = { 0 }; + CK_MECHANISM_TYPE integrityMech; + + if (!p12dcx || p12dcx->error) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + buf = (unsigned char *)PORT_Alloc(IN_BUF_LEN + FUDGE); + if (!buf) + return SECFailure; /* error code has been set. */ + +#ifdef DEBUG + memcpy(buf + IN_BUF_LEN, bufferEnd, sizeof bufferEnd); +#endif + + /* generate hmac key */ + if (p12dcx->macData.iter.data) { + iteration = (int)DER_GetInteger(&p12dcx->macData.iter); + } else { + iteration = 1; + } + + params = PK11_CreatePBEParams(&p12dcx->macData.macSalt, p12dcx->pwitem, + iteration); + + algtag = SECOID_GetAlgorithmTag(&p12dcx->macData.safeMac.digestAlgorithm); + switch (algtag) { + case SEC_OID_SHA1: + integrityMech = CKM_NETSCAPE_PBE_SHA1_HMAC_KEY_GEN; + break; + case SEC_OID_MD5: + integrityMech = CKM_NETSCAPE_PBE_MD5_HMAC_KEY_GEN; + break; + case SEC_OID_MD2: + integrityMech = CKM_NETSCAPE_PBE_MD2_HMAC_KEY_GEN; + break; + default: + goto loser; + } + + symKey = PK11_KeyGen(NULL, integrityMech, params, 20, NULL); + PK11_DestroyPBEParams(params); + params = NULL; + if (!symKey) + goto loser; + /* init hmac */ + pk11cx = PK11_CreateContextBySymKey(sec_pkcs12_algtag_to_mech(algtag), + CKA_SIGN, symKey, &ignore); + if (!pk11cx) { + goto loser; + } + lrv = PK11_DigestBegin(pk11cx); + if (lrv == SECFailure) { + goto loser; + } + + /* try to open the data for readback */ + if (p12dcx->dOpen && ((*p12dcx->dOpen)(p12dcx->dArg, PR_TRUE) != SECSuccess)) { + goto loser; + } + + /* read the data back IN_BUF_LEN bytes at a time and recompute + * the hmac. if fewer bytes are read than are requested, it is + * assumed that the end of file has been reached. if bytesRead + * is returned as -1, then an error occurred reading from the + * file. + */ + do { + bytesRead = (*p12dcx->dRead)(p12dcx->dArg, buf, IN_BUF_LEN); + if (bytesRead < 0) { + PORT_SetError(SEC_ERROR_PKCS12_UNABLE_TO_READ); + goto loser; + } + PORT_Assert(bytesRead <= IN_BUF_LEN); + PORT_Assert(!memcmp(buf + IN_BUF_LEN, bufferEnd, sizeof bufferEnd)); + + if (bytesRead > IN_BUF_LEN) { + /* dRead callback overflowed buffer. */ + PORT_SetError(SEC_ERROR_INPUT_LEN); + goto loser; + } + + if (bytesRead) { + lrv = PK11_DigestOp(pk11cx, buf, bytesRead); + if (lrv == SECFailure) { + goto loser; + } + } + } while (bytesRead == IN_BUF_LEN); + + /* finish the hmac context */ + lrv = PK11_DigestFinal(pk11cx, buf, &bufLen, IN_BUF_LEN); + if (lrv == SECFailure) { + goto loser; + } + + hmacRes.data = buf; + hmacRes.len = bufLen; + + /* is the hmac computed the same as the hmac which was decoded? */ + rv = SECSuccess; + if (SECITEM_CompareItem(&hmacRes, &p12dcx->macData.safeMac.digest) != SECEqual) { + PORT_SetError(SEC_ERROR_PKCS12_INVALID_MAC); + rv = SECFailure; + } + +loser: + /* close the file and remove it */ + if (p12dcx->dClose) { + (*p12dcx->dClose)(p12dcx->dArg, PR_TRUE); + p12dcx->dIsOpen = PR_FALSE; + } + + if (pk11cx) { + PK11_DestroyContext(pk11cx, PR_TRUE); + } + if (params) { + PK11_DestroyPBEParams(params); + } + if (symKey) { + PK11_FreeSymKey(symKey); + } + PORT_ZFree(buf, IN_BUF_LEN + FUDGE); + + return rv; +} + +/* SEC_PKCS12DecoderVerify + * Verify the macData or the signature of the decoded PKCS 12 PDU. + * If the signature or the macData do not match, SECFailure is + * returned. + * + * p12dcx - the decoder context + */ +SECStatus +SEC_PKCS12DecoderVerify(SEC_PKCS12DecoderContext *p12dcx) +{ + SECStatus rv = SECSuccess; + + /* make sure that no errors have occurred... */ + if (!p12dcx) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + if (p12dcx->error) { + /* error code is already set! PORT_SetError(p12dcx->errorValue); */ + return SECFailure; + } + + rv = SEC_ASN1DecoderFinish(p12dcx->pfxA1Dcx); + p12dcx->pfxA1Dcx = NULL; + if (rv != SECSuccess) { + return rv; + } + + /* check the signature or the mac depending on the type of + * integrity used. + */ + if (p12dcx->pfx.encodedMacData.len) { + rv = SEC_ASN1DecodeItem(p12dcx->arena, &p12dcx->macData, + sec_PKCS12MacDataTemplate, + &p12dcx->pfx.encodedMacData); + if (rv == SECSuccess) { + return sec_pkcs12_decoder_verify_mac(p12dcx); + } + return rv; + } + if (SEC_PKCS7VerifySignature(p12dcx->aSafeCinfo, certUsageEmailSigner, + PR_FALSE)) { + return SECSuccess; + } + PORT_SetError(SEC_ERROR_PKCS12_INVALID_MAC); + return SECFailure; +} + +/* SEC_PKCS12DecoderFinish + * Free any open ASN1 or PKCS7 decoder contexts and then + * free the arena pool which everything should be allocated + * from. This function should be called upon completion of + * decoding and installing of a pfx pdu. This should be + * called even if an error occurs. + * + * p12dcx - the decoder context + */ +void +SEC_PKCS12DecoderFinish(SEC_PKCS12DecoderContext *p12dcx) +{ + unsigned int i; + + if (!p12dcx) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return; + } + + if (p12dcx->pfxA1Dcx) { + SEC_ASN1DecoderFinish(p12dcx->pfxA1Dcx); + p12dcx->pfxA1Dcx = NULL; + } + + if (p12dcx->aSafeA1Dcx) { + SEC_ASN1DecoderFinish(p12dcx->aSafeA1Dcx); + p12dcx->aSafeA1Dcx = NULL; + } + + /* cleanup any old ASN1 decoder contexts */ + for (i = 0; i < p12dcx->safeContentsCnt; ++i) { + sec_PKCS12SafeContentsContext *safeContentsCtx, *nested; + safeContentsCtx = p12dcx->safeContentsList[i]; + if (safeContentsCtx) { + nested = safeContentsCtx->nestedSafeContentsCtx; + while (nested) { + if (nested->safeContentsA1Dcx) { + SEC_ASN1DecoderFinish(nested->safeContentsA1Dcx); + nested->safeContentsA1Dcx = NULL; + } + nested = nested->nestedSafeContentsCtx; + } + if (safeContentsCtx->safeContentsA1Dcx) { + SEC_ASN1DecoderFinish(safeContentsCtx->safeContentsA1Dcx); + safeContentsCtx->safeContentsA1Dcx = NULL; + } + } + } + + if (p12dcx->currentASafeP7Dcx && + p12dcx->currentASafeP7Dcx != p12dcx->aSafeP7Dcx) { + SEC_PKCS7ContentInfo *cinfo; + cinfo = SEC_PKCS7DecoderFinish(p12dcx->currentASafeP7Dcx); + if (cinfo) { + SEC_PKCS7DestroyContentInfo(cinfo); /* don't leak it */ + } + } + p12dcx->currentASafeP7Dcx = NULL; + + if (p12dcx->aSafeP7Dcx) { + SEC_PKCS7ContentInfo *cinfo; + cinfo = SEC_PKCS7DecoderFinish(p12dcx->aSafeP7Dcx); + if (cinfo) { + SEC_PKCS7DestroyContentInfo(cinfo); + } + p12dcx->aSafeP7Dcx = NULL; + } + + if (p12dcx->aSafeCinfo) { + SEC_PKCS7DestroyContentInfo(p12dcx->aSafeCinfo); + p12dcx->aSafeCinfo = NULL; + } + + if (p12dcx->decitem.type != 0 && p12dcx->decitem.der != NULL) { + SECITEM_FreeItem(p12dcx->decitem.der, PR_TRUE); + } + if (p12dcx->decitem.friendlyName != NULL) { + SECITEM_FreeItem(p12dcx->decitem.friendlyName, PR_TRUE); + } + + if (p12dcx->slot) { + PK11_FreeSlot(p12dcx->slot); + p12dcx->slot = NULL; + } + + if (p12dcx->dIsOpen && p12dcx->dClose) { + (*p12dcx->dClose)(p12dcx->dArg, PR_TRUE); + p12dcx->dIsOpen = PR_FALSE; + } + + if (p12dcx->arena) { + PORT_FreeArena(p12dcx->arena, PR_TRUE); + } +} + +static SECStatus +sec_pkcs12_decoder_set_attribute_value(sec_PKCS12SafeBag *bag, + SECOidTag attributeType, + SECItem *attrValue) +{ + int i = 0; + SECOidData *oid; + + if (!bag || !attrValue) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + oid = SECOID_FindOIDByTag(attributeType); + if (!oid) { + return SECFailure; + } + + if (!bag->attribs) { + bag->attribs = + PORT_ArenaZNewArray(bag->arena, sec_PKCS12Attribute *, 2); + } else { + while (bag->attribs[i]) + i++; + bag->attribs = PORT_ArenaGrowArray(bag->arena, bag->attribs, + sec_PKCS12Attribute *, i + 1, i + 2); + } + + if (!bag->attribs) { + return SECFailure; + } + + bag->attribs[i] = PORT_ArenaZNew(bag->arena, sec_PKCS12Attribute); + if (!bag->attribs[i]) { + return SECFailure; + } + + bag->attribs[i]->attrValue = PORT_ArenaZNewArray(bag->arena, SECItem *, 2); + if (!bag->attribs[i]->attrValue) { + return SECFailure; + } + + bag->attribs[i + 1] = NULL; + bag->attribs[i]->attrValue[0] = attrValue; + bag->attribs[i]->attrValue[1] = NULL; + + return SECITEM_CopyItem(bag->arena, &bag->attribs[i]->attrType, &oid->oid); +} + +static SECItem * +sec_pkcs12_get_attribute_value(sec_PKCS12SafeBag *bag, + SECOidTag attributeType) +{ + int i; + + if (!bag->attribs) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + for (i = 0; bag->attribs[i] != NULL; i++) { + if (SECOID_FindOIDTag(&bag->attribs[i]->attrType) == attributeType) { + return bag->attribs[i]->attrValue[0]; + } + } + return NULL; +} + +/* For now, this function will merely remove any ":" + * in the nickname which the PK11 functions may have + * placed there. This will keep dual certs from appearing + * twice under "Your" certificates when imported onto smart + * cards. Once with the name "Slot:Cert" and another with + * the nickname "Slot:Slot:Cert" + */ +static void +sec_pkcs12_sanitize_nickname(PK11SlotInfo *slot, SECItem *nick) +{ + char *nickname; + char *delimit; + int delimitlen; + + nickname = (char *)nick->data; + if ((delimit = PORT_Strchr(nickname, ':')) != NULL) { + char *slotName; + int slotNameLen; + + slotNameLen = delimit - nickname; + slotName = PORT_NewArray(char, (slotNameLen + 1)); + PORT_Assert(slotName); + if (slotName == NULL) { + /* What else can we do?*/ + return; + } + PORT_Memcpy(slotName, nickname, slotNameLen); + slotName[slotNameLen] = '\0'; + if (PORT_Strcmp(PK11_GetTokenName(slot), slotName) == 0) { + delimitlen = PORT_Strlen(delimit + 1); + PORT_Memmove(nickname, delimit + 1, delimitlen + 1); + nick->len = delimitlen; + } + PORT_Free(slotName); + } +} + +static SECItem * +sec_pkcs12_get_nickname(sec_PKCS12SafeBag *bag) +{ + SECItem *src, *dest; + + if (!bag) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + src = sec_pkcs12_get_attribute_value(bag, SEC_OID_PKCS9_FRIENDLY_NAME); + + /* The return value src is 16-bit Unicode characters, in big-endian format. + * Check if it is NULL or empty name. + */ + if (!src || !src->data || src->len < 2 || (!src->data[0] && !src->data[1])) { + return NULL; + } + + dest = (SECItem *)PORT_ZAlloc(sizeof(SECItem)); + if (!dest) { + goto loser; + } + if (!sec_pkcs12_convert_item_to_unicode(NULL, dest, src, PR_FALSE, + PR_FALSE, PR_FALSE)) { + goto loser; + } + + sec_pkcs12_sanitize_nickname(bag->slot, dest); + + return dest; + +loser: + if (dest) { + SECITEM_ZfreeItem(dest, PR_TRUE); + } + + bag->problem = PR_TRUE; + bag->error = PORT_GetError(); + return NULL; +} + +static SECStatus +sec_pkcs12_set_nickname(sec_PKCS12SafeBag *bag, SECItem *name) +{ + sec_PKCS12Attribute *attr = NULL; + SECOidData *oid = SECOID_FindOIDByTag(SEC_OID_PKCS9_FRIENDLY_NAME); + + if (!bag || !bag->arena || !name) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (!bag->attribs) { + if (!oid) { + goto loser; + } + + bag->attribs = + PORT_ArenaZNewArray(bag->arena, sec_PKCS12Attribute *, 2); + if (!bag->attribs) { + goto loser; + } + bag->attribs[0] = PORT_ArenaZNew(bag->arena, sec_PKCS12Attribute); + if (!bag->attribs[0]) { + goto loser; + } + bag->attribs[1] = NULL; + + attr = bag->attribs[0]; + if (SECITEM_CopyItem(bag->arena, &attr->attrType, &oid->oid) != SECSuccess) { + goto loser; + } + } else { + int i; + for (i = 0; bag->attribs[i]; i++) { + if (SECOID_FindOIDTag(&bag->attribs[i]->attrType) == SEC_OID_PKCS9_FRIENDLY_NAME) { + attr = bag->attribs[i]; + break; + } + } + if (!attr) { + if (!oid) { + goto loser; + } + bag->attribs = PORT_ArenaGrowArray(bag->arena, bag->attribs, + sec_PKCS12Attribute *, i + 1, i + 2); + if (!bag->attribs) { + goto loser; + } + bag->attribs[i] = PORT_ArenaZNew(bag->arena, sec_PKCS12Attribute); + if (!bag->attribs[i]) { + goto loser; + } + bag->attribs[i + 1] = NULL; + attr = bag->attribs[i]; + if (SECITEM_CopyItem(bag->arena, &attr->attrType, &oid->oid) != SECSuccess) { + goto loser; + } + } + } + + PORT_Assert(attr); + if (!attr->attrValue) { + attr->attrValue = PORT_ArenaZNewArray(bag->arena, SECItem *, 2); + if (!attr->attrValue) { + goto loser; + } + attr->attrValue[0] = PORT_ArenaZNew(bag->arena, SECItem); + if (!attr->attrValue[0]) { + goto loser; + } + attr->attrValue[1] = NULL; + } + + name->len = PORT_Strlen((char *)name->data); + if (!sec_pkcs12_convert_item_to_unicode(bag->arena, attr->attrValue[0], + name, PR_FALSE, PR_FALSE, PR_TRUE)) { + goto loser; + } + + return SECSuccess; + +loser: + bag->problem = PR_TRUE; + bag->error = PORT_GetError(); + return SECFailure; +} + +static SECStatus +sec_pkcs12_get_key_info(sec_PKCS12SafeBag *key) +{ + int i = 0; + SECKEYPrivateKeyInfo *pki = NULL; + + if (!key) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + /* if the bag does *not* contain an unencrypted PrivateKeyInfo + * then we cannot convert the attributes. We are propagating + * attributes within the PrivateKeyInfo to the SafeBag level. + */ + if (SECOID_FindOIDTag(&(key->safeBagType)) != + SEC_OID_PKCS12_V1_KEY_BAG_ID) { + return SECSuccess; + } + + pki = key->safeBagContent.pkcs8KeyBag; + + if (!pki || !pki->attributes) { + return SECSuccess; + } + + while (pki->attributes[i]) { + SECOidTag tag = SECOID_FindOIDTag(&pki->attributes[i]->attrType); + + if (tag == SEC_OID_PKCS9_LOCAL_KEY_ID || + tag == SEC_OID_PKCS9_FRIENDLY_NAME) { + SECItem *attrValue = sec_pkcs12_get_attribute_value(key, tag); + if (!attrValue) { + if (sec_pkcs12_decoder_set_attribute_value(key, tag, + pki->attributes[i]->attrValue[0]) != SECSuccess) { + key->problem = PR_TRUE; + key->error = PORT_GetError(); + return SECFailure; + } + } + } + i++; + } + + return SECSuccess; +} + +/* retrieve the nickname for the certificate bag. first look + * in the cert bag, otherwise get it from the key. + */ +static SECItem * +sec_pkcs12_get_nickname_for_cert(sec_PKCS12SafeBag *cert, + sec_PKCS12SafeBag *key) +{ + SECItem *nickname; + + if (!cert) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + nickname = sec_pkcs12_get_nickname(cert); + if (nickname) { + return nickname; + } + + if (key) { + nickname = sec_pkcs12_get_nickname(key); + + if (nickname && sec_pkcs12_set_nickname(cert, nickname) != SECSuccess) { + SECITEM_ZfreeItem(nickname, PR_TRUE); + return NULL; + } + } + + return nickname; +} + +/* set the nickname for the certificate */ +static SECStatus +sec_pkcs12_set_nickname_for_cert(sec_PKCS12SafeBag *cert, + sec_PKCS12SafeBag *key, + SECItem *nickname) +{ + if (!nickname || !cert) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (sec_pkcs12_set_nickname(cert, nickname) != SECSuccess) { + return SECFailure; + } + + if (key) { + if (sec_pkcs12_set_nickname(key, nickname) != SECSuccess) { + cert->problem = PR_TRUE; + cert->error = key->error; + return SECFailure; + } + } + + return SECSuccess; +} + +/* retrieve the DER cert from the cert bag */ +static SECItem * +sec_pkcs12_get_der_cert(sec_PKCS12SafeBag *cert) +{ + if (!cert) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + if (SECOID_FindOIDTag(&cert->safeBagType) != SEC_OID_PKCS12_V1_CERT_BAG_ID) { + return NULL; + } + + /* only support X509 certs not SDSI */ + if (SECOID_FindOIDTag(&cert->safeBagContent.certBag->bagID) != SEC_OID_PKCS9_X509_CERT) { + return NULL; + } + + return SECITEM_DupItem(&(cert->safeBagContent.certBag->value.x509Cert)); +} + +struct certNickInfo { + PLArenaPool *arena; + unsigned int nNicks; + SECItem **nickList; + unsigned int error; +}; + +/* callback for traversing certificates to gather the nicknames + * used in a particular traversal. for instance, when using + * CERT_TraversePermCertsForSubject, gather the nicknames and + * store them in the certNickInfo for a particular DN. + * + * this handles the case where multiple nicknames are allowed + * for the same dn, which is not currently allowed, but may be + * in the future. + */ +static SECStatus +gatherNicknames(CERTCertificate *cert, void *arg) +{ + struct certNickInfo *nickArg = (struct certNickInfo *)arg; + SECItem tempNick; + unsigned int i; + + if (!cert || !nickArg || nickArg->error) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (!cert->nickname) { + return SECSuccess; + } + + tempNick.data = (unsigned char *)cert->nickname; + tempNick.len = PORT_Strlen(cert->nickname) + 1; + tempNick.type = siAsciiString; + + /* do we already have the nickname in the list? */ + if (nickArg->nNicks > 0) { + + /* nicknames have been encountered, but there is no list -- bad */ + if (!nickArg->nickList) { + nickArg->error = SEC_ERROR_INVALID_ARGS; + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + for (i = 0; i < nickArg->nNicks; i++) { + if (SECITEM_CompareItem(nickArg->nickList[i], &tempNick) == SECEqual) { + return SECSuccess; + } + } + } + + /* add the nickname to the list */ + nickArg->nickList = (nickArg->nNicks == 0) + ? PORT_ArenaZNewArray(nickArg->arena, SECItem *, 2) + : PORT_ArenaGrowArray(nickArg->arena, nickArg->nickList, SECItem *, + nickArg->nNicks + 1, nickArg->nNicks + 2); + + if (!nickArg->nickList) { + nickArg->error = SEC_ERROR_NO_MEMORY; + return SECFailure; + } + + nickArg->nickList[nickArg->nNicks] = + PORT_ArenaZNew(nickArg->arena, SECItem); + if (!nickArg->nickList[nickArg->nNicks]) { + nickArg->error = PORT_GetError(); + return SECFailure; + } + + if (SECITEM_CopyItem(nickArg->arena, nickArg->nickList[nickArg->nNicks], + &tempNick) != SECSuccess) { + nickArg->error = PORT_GetError(); + return SECFailure; + } + + nickArg->nNicks++; + + return SECSuccess; +} + +/* traverses the certs in the data base or in the token for the + * DN to see if any certs currently have a nickname set. + * If so, return it. + */ +static SECItem * +sec_pkcs12_get_existing_nick_for_dn(sec_PKCS12SafeBag *cert) +{ + struct certNickInfo *nickArg = NULL; + SECItem *derCert, *returnDn = NULL; + PLArenaPool *arena = NULL; + CERTCertificate *tempCert; + + if (!cert) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + derCert = sec_pkcs12_get_der_cert(cert); + if (!derCert) { + return NULL; + } + + tempCert = CERT_DecodeDERCertificate(derCert, PR_FALSE, NULL); + if (!tempCert) { + returnDn = NULL; + goto loser; + } + + arena = PORT_NewArena(1024); + if (!arena) { + returnDn = NULL; + goto loser; + } + nickArg = PORT_ArenaZNew(arena, struct certNickInfo); + if (!nickArg) { + returnDn = NULL; + goto loser; + } + nickArg->error = 0; + nickArg->nNicks = 0; + nickArg->nickList = NULL; + nickArg->arena = arena; + + /* if the token is local, first traverse the cert database + * then traverse the token. + */ + if (PK11_TraverseCertsForSubjectInSlot(tempCert, cert->slot, gatherNicknames, + (void *)nickArg) != SECSuccess) { + returnDn = NULL; + goto loser; + } + + if (nickArg->error) { + /* XXX do we want to set the error? */ + returnDn = NULL; + goto loser; + } + + if (nickArg->nNicks == 0) { + returnDn = NULL; + goto loser; + } + + /* set it to the first name, for now. handle multiple names? */ + returnDn = SECITEM_DupItem(nickArg->nickList[0]); + +loser: + if (arena) { + PORT_FreeArena(arena, PR_TRUE); + } + + if (tempCert) { + CERT_DestroyCertificate(tempCert); + } + + if (derCert) { + SECITEM_FreeItem(derCert, PR_TRUE); + } + + return (returnDn); +} + +/* counts certificates found for a given traversal function */ +static SECStatus +countCertificate(CERTCertificate *cert, void *arg) +{ + unsigned int *nCerts = (unsigned int *)arg; + + if (!cert || !arg) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + (*nCerts)++; + return SECSuccess; +} + +static PRBool +sec_pkcs12_certs_for_nickname_exist(SECItem *nickname, PK11SlotInfo *slot) +{ + unsigned int nCerts = 0; + + if (!nickname || !slot) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return PR_TRUE; + } + + /* we want to check the local database first if we are importing to it */ + PK11_TraverseCertsForNicknameInSlot(nickname, slot, countCertificate, + (void *)&nCerts); + return (PRBool)(nCerts != 0); +} + +/* validate cert nickname such that there is a one-to-one relation + * between nicknames and dn's. we want to enforce the case that the + * nickname is non-NULL and that there is only one nickname per DN. + * + * if there is a problem with a nickname or the nickname is not present, + * the user will be prompted for it. + */ +static void +sec_pkcs12_validate_cert_nickname(sec_PKCS12SafeBag *cert, + sec_PKCS12SafeBag *key, + SEC_PKCS12NicknameCollisionCallback nicknameCb, + CERTCertificate *leafCert) +{ + SECItem *certNickname, *existingDNNick; + PRBool setNickname = PR_FALSE, cancel = PR_FALSE; + SECItem *newNickname = NULL; + + if (!cert || !cert->hasKey) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return; + } + + if (!nicknameCb) { + cert->problem = PR_TRUE; + cert->error = SEC_ERROR_INVALID_ARGS; + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return; + } + + if (cert->hasKey && !key) { + cert->problem = PR_TRUE; + cert->error = SEC_ERROR_INVALID_ARGS; + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return; + } + + certNickname = sec_pkcs12_get_nickname_for_cert(cert, key); + existingDNNick = sec_pkcs12_get_existing_nick_for_dn(cert); + + /* nickname is already used w/ this dn, so it is safe to return */ + if (certNickname && existingDNNick && + SECITEM_CompareItem(certNickname, existingDNNick) == SECEqual) { + goto loser; + } + + /* nickname not set in pkcs 12 bags, but a nick is already used for + * this dn. set the nicks in the p12 bags and finish. + */ + if (existingDNNick) { + sec_pkcs12_set_nickname_for_cert(cert, key, existingDNNick); + goto loser; + } + + /* at this point, we have a certificate for which the DN is not located + * on the token. the nickname specified may or may not be NULL. if it + * is not null, we need to make sure that there are no other certificates + * with this nickname in the token for it to be valid. this imposes a + * one to one relationship between DN and nickname. + * + * if the nickname is null, we need the user to enter a nickname for + * the certificate. + * + * once we have a nickname, we make sure that the nickname is unique + * for the DN. if it is not, the user is reprompted to enter a new + * nickname. + * + * in order to exit this loop, the nickname entered is either unique + * or the user hits cancel and the certificate is not imported. + */ + setNickname = PR_FALSE; + while (1) { + /* we will use the nickname so long as no other certs have the + * same nickname. and the nickname is not NULL. + */ + if (certNickname && certNickname->data && + !sec_pkcs12_certs_for_nickname_exist(certNickname, cert->slot)) { + if (setNickname) { + sec_pkcs12_set_nickname_for_cert(cert, key, certNickname); + } + break; + } + + setNickname = PR_FALSE; + newNickname = (*nicknameCb)(certNickname, &cancel, leafCert); + if (cancel) { + cert->problem = PR_TRUE; + cert->error = SEC_ERROR_USER_CANCELLED; + break; + } + + if (!newNickname) { + cert->problem = PR_TRUE; + cert->error = PORT_GetError(); + break; + } + + /* at this point we have a new nickname, if we have an existing + * certNickname, we need to free it and assign the new nickname + * to it to avoid a memory leak. happy? + */ + if (certNickname) { + SECITEM_ZfreeItem(certNickname, PR_TRUE); + certNickname = NULL; + } + + certNickname = newNickname; + setNickname = PR_TRUE; + /* go back and recheck the new nickname */ + } + +loser: + if (certNickname) { + SECITEM_ZfreeItem(certNickname, PR_TRUE); + } + + if (existingDNNick) { + SECITEM_ZfreeItem(existingDNNick, PR_TRUE); + } +} + +static void +sec_pkcs12_validate_cert(sec_PKCS12SafeBag *cert, + sec_PKCS12SafeBag *key, + SEC_PKCS12NicknameCollisionCallback nicknameCb) +{ + CERTCertificate *leafCert; + + if (!cert) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return; + } + + cert->validated = PR_TRUE; + + if (!nicknameCb) { + cert->noInstall = PR_TRUE; + cert->problem = PR_TRUE; + cert->error = SEC_ERROR_INVALID_ARGS; + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return; + } + + if (!cert->safeBagContent.certBag) { + cert->noInstall = PR_TRUE; + cert->problem = PR_TRUE; + cert->error = SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE; + return; + } + + cert->noInstall = PR_FALSE; + cert->unused = PR_FALSE; + cert->problem = PR_FALSE; + cert->error = 0; + + leafCert = CERT_DecodeDERCertificate( + &cert->safeBagContent.certBag->value.x509Cert, PR_FALSE, NULL); + if (!leafCert) { + cert->noInstall = PR_TRUE; + cert->problem = PR_TRUE; + cert->error = PORT_GetError(); + return; + } + + sec_pkcs12_validate_cert_nickname(cert, key, nicknameCb, leafCert); + + CERT_DestroyCertificate(leafCert); +} + +static void +sec_pkcs12_validate_key_by_cert(sec_PKCS12SafeBag *cert, sec_PKCS12SafeBag *key, + void *wincx) +{ + CERTCertificate *leafCert; + SECKEYPrivateKey *privk; + + if (!key) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return; + } + + key->validated = PR_TRUE; + + if (!cert) { + key->problem = PR_TRUE; + key->noInstall = PR_TRUE; + key->error = SEC_ERROR_PKCS12_UNABLE_TO_IMPORT_KEY; + return; + } + + leafCert = CERT_DecodeDERCertificate( + &(cert->safeBagContent.certBag->value.x509Cert), PR_FALSE, NULL); + if (!leafCert) { + key->problem = PR_TRUE; + key->noInstall = PR_TRUE; + key->error = PORT_GetError(); + return; + } + + privk = PK11_FindPrivateKeyFromCert(key->slot, leafCert, wincx); + if (!privk) { + privk = PK11_FindKeyByDERCert(key->slot, leafCert, wincx); + } + + if (privk) { + SECKEY_DestroyPrivateKey(privk); + key->noInstall = PR_TRUE; + } + + CERT_DestroyCertificate(leafCert); +} + +static SECStatus +sec_pkcs12_add_cert(sec_PKCS12SafeBag *cert, PRBool keyExists, void *wincx) +{ + SECItem *derCert, *nickName; + char *nickData = NULL; + PRBool isIntermediateCA; + SECStatus rv; + + if (!cert) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (cert->problem || cert->noInstall || cert->installed) { + return SECSuccess; + } + + derCert = &cert->safeBagContent.certBag->value.x509Cert; + + PORT_Assert(!cert->problem && !cert->noInstall); + + nickName = sec_pkcs12_get_nickname(cert); + if (nickName) { + nickData = (char *)nickName->data; + } + + isIntermediateCA = CERT_IsCADERCert(derCert, NULL) && + !CERT_IsRootDERCert(derCert); + + if (keyExists) { + CERTCertificate *newCert; + + newCert = CERT_NewTempCertificate(CERT_GetDefaultCertDB(), + derCert, NULL, PR_FALSE, PR_FALSE); + if (!newCert) { + if (nickName) + SECITEM_ZfreeItem(nickName, PR_TRUE); + cert->error = PORT_GetError(); + cert->problem = PR_TRUE; + return SECFailure; + } + + rv = PK11_ImportCertForKeyToSlot(cert->slot, newCert, nickData, + PR_TRUE, wincx); + CERT_DestroyCertificate(newCert); + } else if ((cert->tokenCAs == SECPKCS12TargetTokenNoCAs) || + ((cert->tokenCAs == SECPKCS12TargetTokenIntermediateCAs) && + !isIntermediateCA)) { + SECItem *certList[2]; + certList[0] = derCert; + certList[1] = NULL; + + rv = CERT_ImportCerts(CERT_GetDefaultCertDB(), certUsageUserCertImport, + 1, certList, NULL, PR_TRUE, PR_FALSE, nickData); + } else { + rv = PK11_ImportDERCert(cert->slot, derCert, CK_INVALID_HANDLE, + nickData, PR_FALSE); + } + if (rv) { + cert->problem = 1; + cert->error = PORT_GetError(); + } + cert->installed = PR_TRUE; + if (nickName) + SECITEM_ZfreeItem(nickName, PR_TRUE); + return rv; +} + +static SECItem * +sec_pkcs12_get_public_value_and_type(SECKEYPublicKey *pubKey, KeyType *type); + +static SECStatus +sec_pkcs12_add_key(sec_PKCS12SafeBag *key, SECKEYPublicKey *pubKey, + unsigned int keyUsage, + SECItem *nickName, void *wincx) +{ + SECStatus rv; + SECItem *publicValue = NULL; + KeyType keyType; + + /* We should always have values for "key" and "pubKey" + so they can be dereferenced later. */ + if (!key || !pubKey) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (key->problem || key->noInstall) { + return SECSuccess; + } + + /* get the value and type from the public key */ + publicValue = sec_pkcs12_get_public_value_and_type(pubKey, &keyType); + if (!publicValue) { + key->error = SEC_ERROR_PKCS12_UNABLE_TO_IMPORT_KEY; + key->problem = PR_TRUE; + return SECFailure; + } + + switch (SECOID_FindOIDTag(&key->safeBagType)) { + case SEC_OID_PKCS12_V1_KEY_BAG_ID: + rv = PK11_ImportPrivateKeyInfo(key->slot, + key->safeBagContent.pkcs8KeyBag, + nickName, publicValue, PR_TRUE, PR_TRUE, + keyUsage, wincx); + break; + case SEC_OID_PKCS12_V1_PKCS8_SHROUDED_KEY_BAG_ID: + rv = PK11_ImportEncryptedPrivateKeyInfo(key->slot, + key->safeBagContent.pkcs8ShroudedKeyBag, + key->pwitem, nickName, publicValue, + PR_TRUE, PR_TRUE, keyType, keyUsage, + wincx); + break; + default: + key->error = SEC_ERROR_PKCS12_UNSUPPORTED_VERSION; + key->problem = PR_TRUE; + if (nickName) { + SECITEM_ZfreeItem(nickName, PR_TRUE); + } + return SECFailure; + } + + if (rv != SECSuccess) { + key->error = SEC_ERROR_PKCS12_UNABLE_TO_IMPORT_KEY; + key->problem = PR_TRUE; + } else { + /* try to import the public key. Failure to do so is not fatal, + * not all tokens can store the public key */ + if (pubKey) { + PK11_ImportPublicKey(key->slot, pubKey, PR_TRUE); + } + key->installed = PR_TRUE; + } + + return rv; +} + +/* + * The correctness of the code in this file ABSOLUTELY REQUIRES + * that ALL BAGs share a single common arena. + * + * This function allocates the bag list from the arena of whatever bag + * happens to be passed to it. Each time a new bag is handed to it, + * it grows (resizes) the arena of the bag that was handed to it. + * If the bags have different arenas, it will grow the wrong arena. + * + * Worse, if the bags had separate arenas, then while destroying the bags + * in a bag list, when the bag whose arena contained the bag list was + * destroyed, the baglist itself would be destroyed, making it difficult + * or impossible to continue to destroy the bags in the destroyed list. + */ +static SECStatus +sec_pkcs12_add_item_to_bag_list(sec_PKCS12SafeBag ***bagList, + sec_PKCS12SafeBag *bag) +{ + sec_PKCS12SafeBag **newBagList = NULL; + int i = 0; + + if (!bagList || !bag) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (!(*bagList)) { + newBagList = PORT_ArenaZNewArray(bag->arena, sec_PKCS12SafeBag *, 2); + } else { + while ((*bagList)[i]) + i++; + newBagList = PORT_ArenaGrowArray(bag->arena, *bagList, + sec_PKCS12SafeBag *, i + 1, i + 2); + } + + if (!newBagList) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return SECFailure; + } + + newBagList[i] = bag; + newBagList[i + 1] = NULL; + *bagList = newBagList; + + return SECSuccess; +} + +static sec_PKCS12SafeBag ** +sec_pkcs12_find_certs_for_key(sec_PKCS12SafeBag **safeBags, + sec_PKCS12SafeBag *key) +{ + sec_PKCS12SafeBag **certList = NULL; + SECItem *keyId; + int i; + + if (!safeBags || !safeBags[0]) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + keyId = sec_pkcs12_get_attribute_value(key, SEC_OID_PKCS9_LOCAL_KEY_ID); + if (!keyId) { + return NULL; + } + + for (i = 0; safeBags[i]; i++) { + if (SECOID_FindOIDTag(&(safeBags[i]->safeBagType)) == SEC_OID_PKCS12_V1_CERT_BAG_ID) { + SECItem *certKeyId = sec_pkcs12_get_attribute_value(safeBags[i], + SEC_OID_PKCS9_LOCAL_KEY_ID); + + if (certKeyId && (SECITEM_CompareItem(certKeyId, keyId) == SECEqual)) { + if (sec_pkcs12_add_item_to_bag_list(&certList, safeBags[i]) != SECSuccess) { + /* This would leak the partial list of safeBags, + * but that list is allocated from the arena of + * one of the safebags, and will be destroyed when + * that arena is destroyed. So this is not a real leak. + */ + return NULL; + } + } + } + } + + return certList; +} + +CERTCertList * +SEC_PKCS12DecoderGetCerts(SEC_PKCS12DecoderContext *p12dcx) +{ + CERTCertList *certList = NULL; + sec_PKCS12SafeBag **safeBags; + int i; + + if (!p12dcx || !p12dcx->safeBags || !p12dcx->safeBags[0]) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + safeBags = p12dcx->safeBags; + certList = CERT_NewCertList(); + + if (certList == NULL) { + return NULL; + } + + for (i = 0; safeBags[i]; i++) { + if (SECOID_FindOIDTag(&(safeBags[i]->safeBagType)) == SEC_OID_PKCS12_V1_CERT_BAG_ID) { + SECItem *derCert = sec_pkcs12_get_der_cert(safeBags[i]); + CERTCertificate *tempCert = NULL; + + if (derCert == NULL) + continue; + tempCert = CERT_NewTempCertificate(CERT_GetDefaultCertDB(), + derCert, NULL, + PR_FALSE, PR_TRUE); + + if (tempCert) { + CERT_AddCertToListTail(certList, tempCert); + } + SECITEM_FreeItem(derCert, PR_TRUE); + } + /* fixed an infinite loop here, by ensuring that i gets incremented + * if derCert is NULL above. + */ + } + + return certList; +} +static sec_PKCS12SafeBag ** +sec_pkcs12_get_key_bags(sec_PKCS12SafeBag **safeBags) +{ + int i; + sec_PKCS12SafeBag **keyList = NULL; + SECOidTag bagType; + + if (!safeBags || !safeBags[0]) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + for (i = 0; safeBags[i]; i++) { + bagType = SECOID_FindOIDTag(&(safeBags[i]->safeBagType)); + switch (bagType) { + case SEC_OID_PKCS12_V1_KEY_BAG_ID: + case SEC_OID_PKCS12_V1_PKCS8_SHROUDED_KEY_BAG_ID: + if (sec_pkcs12_add_item_to_bag_list(&keyList, safeBags[i]) != SECSuccess) { + /* This would leak, except that keyList is allocated + * from the arena shared by all the safeBags. + */ + return NULL; + } + break; + default: + break; + } + } + + return keyList; +} + +/* This function takes two passes over the bags, validating them + * The two passes are intended to mirror exactly the two passes in + * sec_pkcs12_install_bags. But they don't. :( + */ +static SECStatus +sec_pkcs12_validate_bags(sec_PKCS12SafeBag **safeBags, + SEC_PKCS12NicknameCollisionCallback nicknameCb, + void *wincx) +{ + sec_PKCS12SafeBag **keyList; + int i; + + if (!safeBags || !nicknameCb) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (!safeBags[0]) { + return SECSuccess; + } + + /* First pass. Find all the key bags. + * Find the matching cert(s) for each key. + */ + keyList = sec_pkcs12_get_key_bags(safeBags); + if (keyList) { + for (i = 0; keyList[i]; ++i) { + sec_PKCS12SafeBag *key = keyList[i]; + sec_PKCS12SafeBag **certList = + sec_pkcs12_find_certs_for_key(safeBags, key); + + if (certList) { + int j; + + if (SECOID_FindOIDTag(&(key->safeBagType)) == + SEC_OID_PKCS12_V1_KEY_BAG_ID) { + /* if it is an unencrypted private key then make sure + * the attributes are propageted to the appropriate + * level + */ + if (sec_pkcs12_get_key_info(key) != SECSuccess) { + return SECFailure; + } + } + + sec_pkcs12_validate_key_by_cert(certList[0], key, wincx); + for (j = 0; certList[j]; ++j) { + sec_PKCS12SafeBag *cert = certList[j]; + cert->hasKey = PR_TRUE; + if (key->problem) { + cert->problem = PR_TRUE; + cert->error = key->error; + continue; + } + sec_pkcs12_validate_cert(cert, key, nicknameCb); + if (cert->problem) { + key->problem = cert->problem; + key->error = cert->error; + } + } + } + } + } + + /* Now take a second pass over the safebags and mark for installation any + * certs that were neither installed nor disqualified by the first pass. + */ + for (i = 0; safeBags[i]; ++i) { + sec_PKCS12SafeBag *bag = safeBags[i]; + + if (!bag->validated) { + SECOidTag bagType = SECOID_FindOIDTag(&bag->safeBagType); + + switch (bagType) { + case SEC_OID_PKCS12_V1_CERT_BAG_ID: + sec_pkcs12_validate_cert(bag, NULL, nicknameCb); + break; + case SEC_OID_PKCS12_V1_KEY_BAG_ID: + case SEC_OID_PKCS12_V1_PKCS8_SHROUDED_KEY_BAG_ID: + bag->noInstall = PR_TRUE; + bag->problem = PR_TRUE; + bag->error = SEC_ERROR_PKCS12_UNABLE_TO_IMPORT_KEY; + break; + default: + bag->noInstall = PR_TRUE; + } + } + } + + return SECSuccess; +} + +SECStatus +SEC_PKCS12DecoderValidateBags(SEC_PKCS12DecoderContext *p12dcx, + SEC_PKCS12NicknameCollisionCallback nicknameCb) +{ + SECStatus rv; + int i, noInstallCnt, probCnt, bagCnt, errorVal = 0; + if (!p12dcx || p12dcx->error || !p12dcx->safeBags) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + rv = sec_pkcs12_validate_bags(p12dcx->safeBags, nicknameCb, p12dcx->wincx); + if (rv == SECSuccess) { + p12dcx->bagsVerified = PR_TRUE; + } + + noInstallCnt = probCnt = bagCnt = 0; + i = 0; + while (p12dcx->safeBags[i]) { + bagCnt++; + if (p12dcx->safeBags[i]->noInstall) + noInstallCnt++; + if (p12dcx->safeBags[i]->problem) { + probCnt++; + errorVal = p12dcx->safeBags[i]->error; + } + i++; + } + + /* formerly was erroneous code here that assumed that if all bags + * failed to import, then the problem was duplicated data; + * that is, it assume that the problem must be that the file had + * previously been successfully imported. But importing a + * previously imported file causes NO ERRORS at all, and this + * false assumption caused real errors to be hidden behind false + * errors about duplicated data. + */ + + if (probCnt) { + PORT_SetError(errorVal); + return SECFailure; + } + + return rv; +} + +SECStatus +SEC_PKCS12DecoderRenameCertNicknames(SEC_PKCS12DecoderContext *p12dcx, + SEC_PKCS12NicknameRenameCallback nicknameCb, + void *arg) +{ + int i; + sec_PKCS12SafeBag *safeBag; + CERTCertificate *cert; + SECStatus srv; + + if (!p12dcx || p12dcx->error || !p12dcx->safeBags || !nicknameCb) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + for (i = 0; (safeBag = p12dcx->safeBags[i]); i++) { + SECItem *newNickname = NULL; + SECItem *defaultNickname = NULL; + SECStatus rename_rv; + + if (SECOID_FindOIDTag(&(safeBag->safeBagType)) != + SEC_OID_PKCS12_V1_CERT_BAG_ID) { + continue; + } + + cert = CERT_DecodeDERCertificate( + &safeBag->safeBagContent.certBag->value.x509Cert, + PR_FALSE, NULL); + if (!cert) { + return SECFailure; + } + + defaultNickname = sec_pkcs12_get_nickname(safeBag); + rename_rv = (*nicknameCb)(cert, defaultNickname, &newNickname, arg); + + CERT_DestroyCertificate(cert); + + if (defaultNickname) { + SECITEM_ZfreeItem(defaultNickname, PR_TRUE); + defaultNickname = NULL; + } + + if (rename_rv != SECSuccess) { + return rename_rv; + } + + if (newNickname) { + srv = sec_pkcs12_set_nickname(safeBag, newNickname); + SECITEM_ZfreeItem(newNickname, PR_TRUE); + newNickname = NULL; + if (srv != SECSuccess) { + return SECFailure; + } + } + } + + return SECSuccess; +} + +static SECKEYPublicKey * +sec_pkcs12_get_public_key_and_usage(sec_PKCS12SafeBag *certBag, + unsigned int *usage) +{ + SECKEYPublicKey *pubKey = NULL; + CERTCertificate *cert = NULL; + + if (!certBag || !usage) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + *usage = 0; + + cert = CERT_DecodeDERCertificate( + &certBag->safeBagContent.certBag->value.x509Cert, PR_FALSE, NULL); + if (!cert) { + return NULL; + } + + *usage = cert->keyUsage; + pubKey = CERT_ExtractPublicKey(cert); + CERT_DestroyCertificate(cert); + return pubKey; +} + +static SECItem * +sec_pkcs12_get_public_value_and_type(SECKEYPublicKey *pubKey, + KeyType *type) +{ + SECItem *pubValue = NULL; + + if (!type || !pubKey) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + *type = pubKey->keyType; + switch (pubKey->keyType) { + case dsaKey: + pubValue = &pubKey->u.dsa.publicValue; + break; + case dhKey: + pubValue = &pubKey->u.dh.publicValue; + break; + case rsaKey: + pubValue = &pubKey->u.rsa.modulus; + break; + case ecKey: + pubValue = &pubKey->u.ec.publicValue; + break; + default: + pubValue = NULL; + } + + return pubValue; +} + +/* This function takes two passes over the bags, installing them in the + * desired slot. The two passes are intended to mirror exactly the + * two passes in sec_pkcs12_validate_bags. + */ +static SECStatus +sec_pkcs12_install_bags(sec_PKCS12SafeBag **safeBags, void *wincx) +{ + sec_PKCS12SafeBag **keyList; + int i; + int failedKeys = 0; + + if (!safeBags) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (!safeBags[0]) { + return SECSuccess; + } + + /* First pass. Find all the key bags. + * Try to install them, and any certs associated with them. + */ + keyList = sec_pkcs12_get_key_bags(safeBags); + if (keyList) { + for (i = 0; keyList[i]; i++) { + SECStatus rv; + SECKEYPublicKey *pubKey = NULL; + SECItem *nickName = NULL; + sec_PKCS12SafeBag *key = keyList[i]; + sec_PKCS12SafeBag **certList; + unsigned int keyUsage; + + if (key->problem) { + ++failedKeys; + continue; + } + + certList = sec_pkcs12_find_certs_for_key(safeBags, key); + if (certList && certList[0]) { + pubKey = sec_pkcs12_get_public_key_and_usage(certList[0], + &keyUsage); + /* use the cert's nickname, if it has one, else use the + * key's nickname, else fail. + */ + nickName = sec_pkcs12_get_nickname_for_cert(certList[0], key); + } else { + nickName = sec_pkcs12_get_nickname(key); + } + if (!nickName) { + key->error = SEC_ERROR_BAD_NICKNAME; + key->problem = PR_TRUE; + rv = SECFailure; + } else if (!pubKey) { + key->error = SEC_ERROR_PKCS12_UNABLE_TO_IMPORT_KEY; + key->problem = PR_TRUE; + rv = SECFailure; + } else { + rv = sec_pkcs12_add_key(key, pubKey, keyUsage, nickName, wincx); + } + if (pubKey) { + SECKEY_DestroyPublicKey(pubKey); + pubKey = NULL; + } + if (nickName) { + SECITEM_FreeItem(nickName, PR_TRUE); + nickName = NULL; + } + if (rv != SECSuccess) { + PORT_SetError(key->error); + ++failedKeys; + } + + if (certList) { + int j; + + for (j = 0; certList[j]; j++) { + sec_PKCS12SafeBag *cert = certList[j]; + SECStatus certRv; + + if (!cert) + continue; + if (rv != SECSuccess) { + cert->problem = key->problem; + cert->error = key->error; + cert->noInstall = PR_TRUE; + continue; + } + + certRv = sec_pkcs12_add_cert(cert, cert->hasKey, wincx); + if (certRv != SECSuccess) { + key->problem = cert->problem; + key->error = cert->error; + PORT_SetError(cert->error); + return SECFailure; + } + } + } + } + } + if (failedKeys) + return SECFailure; + + /* Now take a second pass over the safebags and install any certs + * that were neither installed nor disqualified by the first pass. + */ + for (i = 0; safeBags[i]; i++) { + sec_PKCS12SafeBag *bag = safeBags[i]; + + if (!bag->installed && !bag->problem && !bag->noInstall) { + SECStatus rv; + SECOidTag bagType = SECOID_FindOIDTag(&(bag->safeBagType)); + + switch (bagType) { + case SEC_OID_PKCS12_V1_CERT_BAG_ID: + rv = sec_pkcs12_add_cert(bag, bag->hasKey, wincx); + if (rv != SECSuccess) { + PORT_SetError(bag->error); + return SECFailure; + } + break; + case SEC_OID_PKCS12_V1_KEY_BAG_ID: + case SEC_OID_PKCS12_V1_PKCS8_SHROUDED_KEY_BAG_ID: + default: + break; + } + } + } + + return SECSuccess; +} + +SECStatus +SEC_PKCS12DecoderImportBags(SEC_PKCS12DecoderContext *p12dcx) +{ + if (!p12dcx || p12dcx->error) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (!p12dcx->bagsVerified) { + return SECFailure; + } + + return sec_pkcs12_install_bags(p12dcx->safeBags, p12dcx->wincx); +} + +PRBool +sec_pkcs12_bagHasKey(SEC_PKCS12DecoderContext *p12dcx, sec_PKCS12SafeBag *bag) +{ + int i; + SECItem *keyId; + SECItem *certKeyId; + + certKeyId = sec_pkcs12_get_attribute_value(bag, SEC_OID_PKCS9_LOCAL_KEY_ID); + if (certKeyId == NULL) { + return PR_FALSE; + } + + for (i = 0; p12dcx->keyList && p12dcx->keyList[i]; i++) { + keyId = sec_pkcs12_get_attribute_value(p12dcx->keyList[i], + SEC_OID_PKCS9_LOCAL_KEY_ID); + if (!keyId) { + continue; + } + if (SECITEM_CompareItem(certKeyId, keyId) == SECEqual) { + return PR_TRUE; + } + } + return PR_FALSE; +} + +SECItem * +sec_pkcs12_get_friendlyName(sec_PKCS12SafeBag *bag) +{ + SECItem *friendlyName; + SECItem *tempnm; + + tempnm = sec_pkcs12_get_attribute_value(bag, SEC_OID_PKCS9_FRIENDLY_NAME); + friendlyName = (SECItem *)PORT_ZAlloc(sizeof(SECItem)); + if (friendlyName) { + if (!sec_pkcs12_convert_item_to_unicode(NULL, friendlyName, + tempnm, PR_TRUE, PR_FALSE, PR_FALSE)) { + SECITEM_FreeItem(friendlyName, PR_TRUE); + friendlyName = NULL; + } + } + return friendlyName; +} + +/* Following two functions provide access to selected portions of the safe bags. + * Iteration is implemented per decoder context and may be accessed after + * SEC_PKCS12DecoderVerify() returns success. + * When ...DecoderIterateNext() returns SUCCESS a decoder item has been returned + * where item.type is always set; item.friendlyName is set if it is non-null; + * item.der, item.hasKey are set only for SEC_OID_PKCS12_V1_CERT_BAG_ID items. + * ...DecoderIterateNext() returns FAILURE when the list is exhausted or when + * arguments are invalid; PORT_GetError() is 0 at end-of-list. + * Caller has read-only access to decoder items. Any SECItems generated are + * owned by the decoder context and are freed by ...DecoderFinish(). + */ +SECStatus +SEC_PKCS12DecoderIterateInit(SEC_PKCS12DecoderContext *p12dcx) +{ + if (!p12dcx || p12dcx->error) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + p12dcx->iteration = 0; + return SECSuccess; +} + +SECStatus +SEC_PKCS12DecoderIterateNext(SEC_PKCS12DecoderContext *p12dcx, + const SEC_PKCS12DecoderItem **ipp) +{ + sec_PKCS12SafeBag *bag; + + if (!p12dcx || p12dcx->error) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (p12dcx->decitem.type != 0 && p12dcx->decitem.der != NULL) { + SECITEM_FreeItem(p12dcx->decitem.der, PR_TRUE); + } + if (p12dcx->decitem.shroudAlg != NULL) { + SECOID_DestroyAlgorithmID(p12dcx->decitem.shroudAlg, PR_TRUE); + } + if (p12dcx->decitem.friendlyName != NULL) { + SECITEM_FreeItem(p12dcx->decitem.friendlyName, PR_TRUE); + } + p12dcx->decitem.type = 0; + p12dcx->decitem.der = NULL; + p12dcx->decitem.shroudAlg = NULL; + p12dcx->decitem.friendlyName = NULL; + p12dcx->decitem.hasKey = PR_FALSE; + *ipp = NULL; + if (p12dcx->keyList == NULL) { + p12dcx->keyList = sec_pkcs12_get_key_bags(p12dcx->safeBags); + } + + for (; p12dcx->iteration < p12dcx->safeBagCount; p12dcx->iteration++) { + bag = p12dcx->safeBags[p12dcx->iteration]; + if (bag == NULL || bag->problem) { + continue; + } + p12dcx->decitem.type = SECOID_FindOIDTag(&(bag->safeBagType)); + switch (p12dcx->decitem.type) { + case SEC_OID_PKCS12_V1_CERT_BAG_ID: + p12dcx->decitem.der = sec_pkcs12_get_der_cert(bag); + p12dcx->decitem.friendlyName = sec_pkcs12_get_friendlyName(bag); + p12dcx->decitem.hasKey = sec_pkcs12_bagHasKey(p12dcx, bag); + break; + case SEC_OID_PKCS12_V1_PKCS8_SHROUDED_KEY_BAG_ID: + p12dcx->decitem.shroudAlg = PORT_ZNew(SECAlgorithmID); + if (p12dcx->decitem.shroudAlg) { + SECOID_CopyAlgorithmID(NULL, p12dcx->decitem.shroudAlg, + &bag->safeBagContent.pkcs8ShroudedKeyBag->algorithm); + } + /* fall through */ + case SEC_OID_PKCS12_V1_KEY_BAG_ID: + p12dcx->decitem.friendlyName = sec_pkcs12_get_friendlyName(bag); + break; + default: + /* return these even though we don't expect them */ + break; + case SEC_OID_UNKNOWN: + /* ignore these */ + continue; + } + *ipp = &p12dcx->decitem; + p12dcx->iteration++; + break; /* end for() */ + } + + PORT_SetError(0); /* end-of-list is SECFailure with no PORT error */ + return ((p12dcx->decitem.type == 0) ? SECFailure : SECSuccess); +} + +static SECStatus +sec_pkcs12_decoder_append_bag_to_context(SEC_PKCS12DecoderContext *p12dcx, + sec_PKCS12SafeBag *bag) +{ + if (!p12dcx || p12dcx->error) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + p12dcx->safeBags = !p12dcx->safeBagCount + ? PORT_ArenaZNewArray(p12dcx->arena, sec_PKCS12SafeBag *, 2) + : PORT_ArenaGrowArray(p12dcx->arena, p12dcx->safeBags, + sec_PKCS12SafeBag *, p12dcx->safeBagCount + 1, + p12dcx->safeBagCount + 2); + + if (!p12dcx->safeBags) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return SECFailure; + } + + p12dcx->safeBags[p12dcx->safeBagCount] = bag; + p12dcx->safeBags[p12dcx->safeBagCount + 1] = NULL; + p12dcx->safeBagCount++; + + return SECSuccess; +} + +static sec_PKCS12SafeBag * +sec_pkcs12_decoder_convert_old_key(SEC_PKCS12DecoderContext *p12dcx, + void *key, PRBool isEspvk) +{ + sec_PKCS12SafeBag *keyBag; + SECOidData *oid; + SECOidTag keyTag; + SECItem *keyID, *nickName, *newNickName; + + if (!p12dcx || p12dcx->error || !key) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + newNickName = PORT_ArenaZNew(p12dcx->arena, SECItem); + keyBag = PORT_ArenaZNew(p12dcx->arena, sec_PKCS12SafeBag); + if (!keyBag || !newNickName) { + return NULL; + } + + keyBag->swapUnicodeBytes = p12dcx->swapUnicodeBytes; + keyBag->slot = p12dcx->slot; + keyBag->arena = p12dcx->arena; + keyBag->pwitem = p12dcx->pwitem; + keyBag->tokenCAs = p12dcx->tokenCAs; + keyBag->oldBagType = PR_TRUE; + + keyTag = (isEspvk) ? SEC_OID_PKCS12_V1_PKCS8_SHROUDED_KEY_BAG_ID : SEC_OID_PKCS12_V1_KEY_BAG_ID; + oid = SECOID_FindOIDByTag(keyTag); + if (!oid) { + return NULL; + } + + if (SECITEM_CopyItem(p12dcx->arena, &keyBag->safeBagType, &oid->oid) != SECSuccess) { + return NULL; + } + + if (isEspvk) { + SEC_PKCS12ESPVKItem *espvk = (SEC_PKCS12ESPVKItem *)key; + keyBag->safeBagContent.pkcs8ShroudedKeyBag = + espvk->espvkCipherText.pkcs8KeyShroud; + nickName = &(espvk->espvkData.uniNickName); + if (!espvk->espvkData.assocCerts || !espvk->espvkData.assocCerts[0]) { + PORT_SetError(SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE); + return NULL; + } + keyID = &espvk->espvkData.assocCerts[0]->digest; + } else { + SEC_PKCS12PrivateKey *pk = (SEC_PKCS12PrivateKey *)key; + keyBag->safeBagContent.pkcs8KeyBag = &pk->pkcs8data; + nickName = &(pk->pvkData.uniNickName); + if (!pk->pvkData.assocCerts || !pk->pvkData.assocCerts[0]) { + PORT_SetError(SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE); + return NULL; + } + keyID = &pk->pvkData.assocCerts[0]->digest; + } + + if (nickName->len) { + if (nickName->len >= 2) { + if (nickName->data[0] && nickName->data[1]) { + if (!sec_pkcs12_convert_item_to_unicode(p12dcx->arena, newNickName, + nickName, PR_FALSE, PR_FALSE, PR_TRUE)) { + return NULL; + } + nickName = newNickName; + } else if (nickName->data[0] && !nickName->data[1]) { + unsigned int j = 0; + unsigned char t; + for (j = 0; j < nickName->len; j += 2) { + t = nickName->data[j + 1]; + nickName->data[j + 1] = nickName->data[j]; + nickName->data[j] = t; + } + } + } else { + if (!sec_pkcs12_convert_item_to_unicode(p12dcx->arena, newNickName, + nickName, PR_FALSE, PR_FALSE, PR_TRUE)) { + return NULL; + } + nickName = newNickName; + } + } + + if (sec_pkcs12_decoder_set_attribute_value(keyBag, + SEC_OID_PKCS9_FRIENDLY_NAME, + nickName) != SECSuccess) { + return NULL; + } + + if (sec_pkcs12_decoder_set_attribute_value(keyBag, SEC_OID_PKCS9_LOCAL_KEY_ID, + keyID) != SECSuccess) { + return NULL; + } + + return keyBag; +} + +static sec_PKCS12SafeBag * +sec_pkcs12_decoder_create_cert(SEC_PKCS12DecoderContext *p12dcx, + SECItem *derCert) +{ + sec_PKCS12SafeBag *certBag; + SECOidData *oid; + SGNDigestInfo *digest; + SECItem *keyId; + SECStatus rv; + + if (!p12dcx || p12dcx->error || !derCert) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + keyId = PORT_ArenaZNew(p12dcx->arena, SECItem); + if (!keyId) { + return NULL; + } + + digest = sec_pkcs12_compute_thumbprint(derCert); + if (!digest) { + return NULL; + } + + rv = SECITEM_CopyItem(p12dcx->arena, keyId, &digest->digest); + SGN_DestroyDigestInfo(digest); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return NULL; + } + + oid = SECOID_FindOIDByTag(SEC_OID_PKCS12_V1_CERT_BAG_ID); + certBag = PORT_ArenaZNew(p12dcx->arena, sec_PKCS12SafeBag); + if (!certBag || !oid || (SECITEM_CopyItem(p12dcx->arena, + &certBag->safeBagType, &oid->oid) != SECSuccess)) { + return NULL; + } + + certBag->slot = p12dcx->slot; + certBag->pwitem = p12dcx->pwitem; + certBag->swapUnicodeBytes = p12dcx->swapUnicodeBytes; + certBag->arena = p12dcx->arena; + certBag->tokenCAs = p12dcx->tokenCAs; + + oid = SECOID_FindOIDByTag(SEC_OID_PKCS9_X509_CERT); + certBag->safeBagContent.certBag = + PORT_ArenaZNew(p12dcx->arena, sec_PKCS12CertBag); + if (!certBag->safeBagContent.certBag || !oid || + (SECITEM_CopyItem(p12dcx->arena, + &certBag->safeBagContent.certBag->bagID, + &oid->oid) != SECSuccess)) { + return NULL; + } + + if (SECITEM_CopyItem(p12dcx->arena, + &(certBag->safeBagContent.certBag->value.x509Cert), + derCert) != SECSuccess) { + return NULL; + } + + if (sec_pkcs12_decoder_set_attribute_value(certBag, SEC_OID_PKCS9_LOCAL_KEY_ID, + keyId) != SECSuccess) { + return NULL; + } + + return certBag; +} + +static sec_PKCS12SafeBag ** +sec_pkcs12_decoder_convert_old_cert(SEC_PKCS12DecoderContext *p12dcx, + SEC_PKCS12CertAndCRL *oldCert) +{ + sec_PKCS12SafeBag **certList; + SECItem **derCertList; + int i, j; + + if (!p12dcx || p12dcx->error || !oldCert) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + derCertList = SEC_PKCS7GetCertificateList(&oldCert->value.x509->certOrCRL); + if (!derCertList) { + return NULL; + } + + i = 0; + while (derCertList[i]) + i++; + + certList = PORT_ArenaZNewArray(p12dcx->arena, sec_PKCS12SafeBag *, (i + 1)); + if (!certList) { + return NULL; + } + + for (j = 0; j < i; j++) { + certList[j] = sec_pkcs12_decoder_create_cert(p12dcx, derCertList[j]); + if (!certList[j]) { + return NULL; + } + } + + return certList; +} + +static SECStatus +sec_pkcs12_decoder_convert_old_key_and_certs(SEC_PKCS12DecoderContext *p12dcx, + void *oldKey, PRBool isEspvk, + SEC_PKCS12SafeContents *safe, + SEC_PKCS12Baggage *baggage) +{ + sec_PKCS12SafeBag *key, **certList; + SEC_PKCS12CertAndCRL *oldCert; + SEC_PKCS12PVKSupportingData *pvkData; + int i; + SECItem *keyName; + + if (!p12dcx || !oldKey) { + return SECFailure; + } + + if (isEspvk) { + pvkData = &((SEC_PKCS12ESPVKItem *)(oldKey))->espvkData; + } else { + pvkData = &((SEC_PKCS12PrivateKey *)(oldKey))->pvkData; + } + + if (!pvkData->assocCerts || !pvkData->assocCerts[0]) { + PORT_SetError(SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE); + return SECFailure; + } + + oldCert = (SEC_PKCS12CertAndCRL *)sec_pkcs12_find_object(safe, baggage, + SEC_OID_PKCS12_CERT_AND_CRL_BAG_ID, NULL, + pvkData->assocCerts[0]); + if (!oldCert) { + PORT_SetError(SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE); + return SECFailure; + } + + key = sec_pkcs12_decoder_convert_old_key(p12dcx, oldKey, isEspvk); + certList = sec_pkcs12_decoder_convert_old_cert(p12dcx, oldCert); + if (!key || !certList) { + return SECFailure; + } + + if (sec_pkcs12_decoder_append_bag_to_context(p12dcx, key) != SECSuccess) { + return SECFailure; + } + + keyName = sec_pkcs12_get_nickname(key); + if (!keyName) { + return SECFailure; + } + + i = 0; + while (certList[i]) { + if (sec_pkcs12_decoder_append_bag_to_context(p12dcx, certList[i]) != SECSuccess) { + return SECFailure; + } + i++; + } + + certList = sec_pkcs12_find_certs_for_key(p12dcx->safeBags, key); + if (!certList) { + return SECFailure; + } + + i = 0; + while (certList[i] != 0) { + if (sec_pkcs12_set_nickname(certList[i], keyName) != SECSuccess) { + return SECFailure; + } + i++; + } + + return SECSuccess; +} + +static SECStatus +sec_pkcs12_decoder_convert_old_safe_to_bags(SEC_PKCS12DecoderContext *p12dcx, + SEC_PKCS12SafeContents *safe, + SEC_PKCS12Baggage *baggage) +{ + SECStatus rv; + + if (!p12dcx || p12dcx->error) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (safe && safe->contents) { + int i = 0; + while (safe->contents[i] != NULL) { + if (SECOID_FindOIDTag(&safe->contents[i]->safeBagType) == SEC_OID_PKCS12_KEY_BAG_ID) { + int j = 0; + SEC_PKCS12PrivateKeyBag *privBag = + safe->contents[i]->safeContent.keyBag; + + while (privBag->privateKeys[j] != NULL) { + SEC_PKCS12PrivateKey *pk = privBag->privateKeys[j]; + rv = sec_pkcs12_decoder_convert_old_key_and_certs(p12dcx, pk, + PR_FALSE, safe, baggage); + if (rv != SECSuccess) { + goto loser; + } + j++; + } + } + i++; + } + } + + if (baggage && baggage->bags) { + int i = 0; + while (baggage->bags[i] != NULL) { + SEC_PKCS12BaggageItem *bag = baggage->bags[i]; + int j = 0; + + if (!bag->espvks) { + i++; + continue; + } + + while (bag->espvks[j] != NULL) { + SEC_PKCS12ESPVKItem *espvk = bag->espvks[j]; + rv = sec_pkcs12_decoder_convert_old_key_and_certs(p12dcx, espvk, + PR_TRUE, safe, baggage); + if (rv != SECSuccess) { + goto loser; + } + j++; + } + i++; + } + } + + return SECSuccess; + +loser: + return SECFailure; +} + +SEC_PKCS12DecoderContext * +sec_PKCS12ConvertOldSafeToNew(PLArenaPool *arena, PK11SlotInfo *slot, + PRBool swapUnicode, SECItem *pwitem, + void *wincx, SEC_PKCS12SafeContents *safe, + SEC_PKCS12Baggage *baggage) +{ + SEC_PKCS12DecoderContext *p12dcx; + + if (!arena || !slot || !pwitem) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + if (!safe && !baggage) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + p12dcx = PORT_ArenaZNew(arena, SEC_PKCS12DecoderContext); + if (!p12dcx) { + return NULL; + } + + p12dcx->arena = arena; + p12dcx->slot = PK11_ReferenceSlot(slot); + p12dcx->wincx = wincx; + p12dcx->error = PR_FALSE; + p12dcx->swapUnicodeBytes = swapUnicode; + p12dcx->pwitem = pwitem; + p12dcx->tokenCAs = SECPKCS12TargetTokenNoCAs; + + if (sec_pkcs12_decoder_convert_old_safe_to_bags(p12dcx, safe, baggage) != SECSuccess) { + p12dcx->error = PR_TRUE; + return NULL; + } + + return p12dcx; +} |