/* 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; SECItem pwitem = { 0 }; SECOidTag algorithm; 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(); } algorithm = SECOID_GetAlgorithmTag(algid); if (!sec_pkcs12_decode_password(NULL, &pwitem, algorithm, p12dcx->pwitem)) return NULL; bulkKey = PK11_PBEKeyGen(slot, algid, &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, &pwitem, PR_FALSE, p12dcx->wincx); } PK11_FreeSlot(slot); /* set the password data on the key */ if (bulkKey) { PK11_SetSymKeyUserData(bulkKey, p12dcx->pwitem, NULL); } if (pwitem.data) { SECITEM_ZfreeItem(&pwitem, PR_FALSE); } 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; case SEC_OID_SHA224: integrityMech = CKM_NSS_PKCS12_PBE_SHA224_HMAC_KEY_GEN; break; case SEC_OID_SHA256: integrityMech = CKM_NSS_PKCS12_PBE_SHA256_HMAC_KEY_GEN; break; case SEC_OID_SHA384: integrityMech = CKM_NSS_PKCS12_PBE_SHA384_HMAC_KEY_GEN; break; case SEC_OID_SHA512: integrityMech = CKM_NSS_PKCS12_PBE_SHA512_HMAC_KEY_GEN; break; default: goto loser; } symKey = PK11_KeyGen(NULL, integrityMech, params, 0, 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: { SECItem pwitem = { 0 }; SECAlgorithmID *algid = &key->safeBagContent.pkcs8ShroudedKeyBag->algorithm; SECOidTag algorithm = SECOID_GetAlgorithmTag(algid); if (!sec_pkcs12_decode_password(NULL, &pwitem, algorithm, key->pwitem)) return SECFailure; rv = PK11_ImportEncryptedPrivateKeyInfo(key->slot, key->safeBagContent.pkcs8ShroudedKeyBag, &pwitem, nickName, publicValue, PR_TRUE, PR_TRUE, keyType, keyUsage, wincx); if (pwitem.data) { SECITEM_ZfreeItem(&pwitem, PR_FALSE); } 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; }