/* * The contents of this file are subject to the Mozilla Public * License Version 1.1 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. * * The Original Code is the Netscape security libraries. * * The Initial Developer of the Original Code is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1994-2000 Netscape Communications Corporation. All * Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the * terms of the GNU General Public License Version 2 or later (the * "GPL"), in which case the provisions of the GPL are applicable * instead of those above. If you wish to allow use of your * version of this file only under the terms of the GPL and not to * allow others to use your version of this file under the MPL, * indicate your decision by deleting the provisions above and * replace them with the notice and other provisions required by * the GPL. If you do not delete the provisions above, a recipient * may use your version of this file under either the MPL or the * GPL. */ #include "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 "alghmac.h" #include "secder.h" #include "secport.h" #include "certdb.h" #include "prcpucfg.h" 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 */ PRArenaPool *arena; /* decoder context and destination for decoding safe contents */ SEC_ASN1DecoderContext *safeContentsDcx; sec_PKCS12SafeContents safeContents; /* information for decoding safe bags within the safe contents. * these variables are updated for each safe bag decoded. */ SEC_ASN1DecoderContext *currentSafeBagDcx; sec_PKCS12SafeBag *currentSafeBag; PRBool skipCurrentSafeBag; /* if the safe contents is nested, the parent is pointed to here. */ sec_PKCS12SafeContentsContext *nestedCtx; }; /* 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 { PRArenaPool *arena; PK11SlotInfo *slot; void *wincx; PRBool error; int errorValue; /* password */ SECItem *pwitem; /* used for decoding the PFX structure */ SEC_ASN1DecoderContext *pfxDcx; sec_PKCS12PFXItem pfx; /* safe bags found during decoding */ sec_PKCS12SafeBag **safeBags; unsigned int safeBagCount; /* state variables for decoding authenticated safes. */ SEC_PKCS7DecoderContext *currentASafeP7Dcx; SEC_PKCS5KeyAndPassword *currentASafeKeyPwd; SEC_ASN1DecoderContext *aSafeDcx; SEC_PKCS7DecoderContext *aSafeP7Dcx; sec_PKCS12AuthenticatedSafe authSafe; SEC_PKCS7ContentInfo *aSafeCinfo; sec_PKCS12SafeContents safeContents; /* safe contents info */ unsigned int safeContentsCnt; sec_PKCS12SafeContentsContext **safeContentsList; /* HMAC info */ sec_PKCS12MacData macData; SEC_ASN1DecoderContext *hmacDcx; /* routines for reading back the data to be hmac'd */ digestOpenFn dOpen; digestCloseFn dClose; digestIOFn dRead, dWrite; void *dArg; /* 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 */ }; /* 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_PKCS5KeyAndPassword *keyPwd = (SEC_PKCS5KeyAndPassword *)arg; if(!keyPwd) { return NULL; } /* if no slot specified, use the internal key slot */ if(!keyPwd->slot) { keyPwd->slot = PK11_GetInternalKeySlot(); } /* retrieve the key */ if(!keyPwd->key) { keyPwd->key = PK11_PBEKeyGen(keyPwd->slot, algid, keyPwd->pwitem, PR_FALSE, keyPwd->wincx); } return (PK11SymKey *)keyPwd; } /* 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. */ if(p12dcx->safeBagCount) { p12dcx->safeBags = (sec_PKCS12SafeBag**)PORT_ArenaGrow(p12dcx->arena,p12dcx->safeBags, (p12dcx->safeBagCount + 1) * sizeof(sec_PKCS12SafeBag *), (p12dcx->safeBagCount + 2) * sizeof(sec_PKCS12SafeBag *)); } else { p12dcx->safeBags = (sec_PKCS12SafeBag**)PORT_ArenaZAlloc(p12dcx->arena, 2 * sizeof(sec_PKCS12SafeBag *)); } if(!p12dcx->safeBags) { p12dcx->errorValue = SEC_ERROR_NO_MEMORY; goto loser; } /* append the bag to the end of the list and update the reference * in the safeContentsCtx. */ p12dcx->safeBags[p12dcx->safeBagCount] = (sec_PKCS12SafeBag*)PORT_ArenaZAlloc(p12dcx->arena, sizeof(sec_PKCS12SafeBag)); safeContentsCtx->currentSafeBag = p12dcx->safeBags[p12dcx->safeBagCount]; p12dcx->safeBags[++p12dcx->safeBagCount] = NULL; if(!safeContentsCtx->currentSafeBag) { p12dcx->errorValue = SEC_ERROR_NO_MEMORY; goto loser; } safeContentsCtx->currentSafeBag->slot = safeContentsCtx->p12dcx->slot; safeContentsCtx->currentSafeBag->pwitem = safeContentsCtx->p12dcx->pwitem; safeContentsCtx->currentSafeBag->swapUnicodeBytes = safeContentsCtx->p12dcx->swapUnicodeBytes; safeContentsCtx->currentSafeBag->arena = safeContentsCtx->p12dcx->arena; 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->currentSafeBagDcx, data, len); if(rv != SECSuccess) { p12dcx->errorValue = SEC_ERROR_NO_MEMORY; 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->currentSafeBagDcx); safeContentsCtx->currentSafeBagDcx = NULL; return; } /* 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); static void sec_pkcs12_decoder_safe_bag_update(void *arg, const char *data, unsigned long len, int depth, SEC_ASN1EncodingPart data_kind); /* 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->safeContentsDcx); SEC_ASN1DecoderFinish(safeContentsCtx->currentSafeBagDcx); safeContentsCtx->currentSafeBagDcx = 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->currentSafeBagDcx = SEC_ASN1DecoderStart(p12dcx->arena, safeContentsCtx->currentSafeBag, sec_PKCS12SafeBagTemplate); if(!safeContentsCtx->currentSafeBagDcx) { p12dcx->errorValue = SEC_ERROR_NO_MEMORY; 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->currentSafeBagDcx, sec_pkcs12_decoder_safe_bag_notify, safeContentsCtx); SEC_ASN1DecoderSetFilterProc(safeContentsCtx->safeContentsDcx, 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->currentSafeBagDcx) { SEC_ASN1DecoderFinish(safeContentsCtx->currentSafeBagDcx); safeContentsCtx->currentSafeBagDcx = NULL; } SEC_ASN1DecoderClearNotifyProc(safeContentsCtx->safeContentsDcx); SEC_ASN1DecoderClearFilterProc(safeContentsCtx->safeContentsDcx); 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. */ if(!p12dcx->safeContentsCnt) { p12dcx->safeContentsList = (sec_PKCS12SafeContentsContext**)PORT_ArenaZAlloc(p12dcx->arena, sizeof(sec_PKCS12SafeContentsContext *)); } else { p12dcx->safeContentsList = (sec_PKCS12SafeContentsContext **) PORT_ArenaGrow(p12dcx->arena, p12dcx->safeContentsList, (p12dcx->safeContentsCnt * sizeof(sec_PKCS12SafeContentsContext *)), (1 + p12dcx->safeContentsCnt * sizeof(sec_PKCS12SafeContentsContext *))); } if(!p12dcx->safeContentsList) { p12dcx->errorValue = SEC_ERROR_NO_MEMORY; goto loser; } p12dcx->safeContentsList[p12dcx->safeContentsCnt] = (sec_PKCS12SafeContentsContext*)PORT_ArenaZAlloc( p12dcx->arena, sizeof(sec_PKCS12SafeContentsContext)); p12dcx->safeContentsList[p12dcx->safeContentsCnt+1] = NULL; if(!p12dcx->safeContentsList[p12dcx->safeContentsCnt]) { p12dcx->errorValue = SEC_ERROR_NO_MEMORY; goto loser; } /* set up the state variables */ safeContentsCtx = p12dcx->safeContentsList[p12dcx->safeContentsCnt]; p12dcx->safeContentsCnt++; 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->safeContentsDcx = SEC_ASN1DecoderStart(p12dcx->arena, &safeContentsCtx->safeContents, theTemplate); if(!safeContentsCtx->safeContentsDcx) { p12dcx->errorValue = SEC_ERROR_NO_MEMORY; goto loser; } /* set the safeContents notify procedure to look for * and start the decode of safeBags. */ SEC_ASN1DecoderSetNotifyProc(safeContentsCtx->safeContentsDcx, 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->safeContentsDcx) { SEC_ASN1DecoderFinish(safeContentsCtx->safeContentsDcx); safeContentsCtx->safeContentsDcx = 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) { 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->safeContentsDcx, buf, len); if(rv != SECSuccess) { p12dcx->errorValue = SEC_ERROR_NO_MEMORY; goto loser; } return; loser: /* handle any errors. If a decoding context is open, close it. */ p12dcx->error = PR_TRUE; if(safeContentsCtx->safeContentsDcx) { SEC_ASN1DecoderFinish(safeContentsCtx->safeContentsDcx); safeContentsCtx->safeContentsDcx = 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->nestedCtx = sec_pkcs12_decoder_safe_contents_init_decode( safeContentsCtx->p12dcx, PR_TRUE); if(!safeContentsCtx->nestedCtx) { return SECFailure; } /* set up new filter proc */ SEC_ASN1DecoderSetNotifyProc(safeContentsCtx->nestedCtx->safeContentsDcx, sec_pkcs12_decoder_safe_contents_notify, safeContentsCtx->nestedCtx); SEC_ASN1DecoderSetFilterProc(safeContentsCtx->currentSafeBagDcx, sec_pkcs12_decoder_nested_safe_contents_update, safeContentsCtx->nestedCtx, 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->currentSafeBagDcx); SEC_ASN1DecoderClearNotifyProc(safeContentsCtx->nestedCtx->safeContentsDcx); SEC_ASN1DecoderFinish(safeContentsCtx->nestedCtx->safeContentsDcx); safeContentsCtx->nestedCtx->safeContentsDcx = NULL; safeContentsCtx->nestedCtx = 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) { return; } p12dcx = safeContentsCtx->p12dcx; /* update the decoder */ rv = SEC_ASN1DecoderUpdate(safeContentsCtx->safeContentsDcx, 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; goto loser; } return; loser: /* set the error and finish the context */ p12dcx->error = PR_TRUE; if(safeContentsCtx->safeContentsDcx) { SEC_ASN1DecoderFinish(safeContentsCtx->safeContentsDcx); safeContentsCtx->safeContentsDcx = 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; } /* set up password and encryption key information */ p12dcx->currentASafeKeyPwd = (SEC_PKCS5KeyAndPassword*)PORT_ArenaZAlloc(p12dcx->arena, sizeof(SEC_PKCS5KeyAndPassword)); if(!p12dcx->currentASafeKeyPwd) { p12dcx->errorValue = SEC_ERROR_NO_MEMORY; goto loser; } p12dcx->currentASafeKeyPwd->pwitem = p12dcx->pwitem; p12dcx->currentASafeKeyPwd->slot = p12dcx->slot; p12dcx->currentASafeKeyPwd->wincx = p12dcx->wincx; /* 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->currentASafeKeyPwd, sec_pkcs12_decoder_decryption_allowed); if(!p12dcx->currentASafeP7Dcx) { p12dcx->errorValue = PORT_GetError(); goto loser; } SEC_ASN1DecoderSetFilterProc(p12dcx->aSafeDcx, sec_pkcs12_decoder_wrap_p7_update, p12dcx->currentASafeP7Dcx, PR_TRUE); } if(!before) { /* if one is being decoded, finish the decode */ if(p12dcx->currentASafeP7Dcx != NULL) { if(!SEC_PKCS7DecoderFinish(p12dcx->currentASafeP7Dcx)) { p12dcx->currentASafeP7Dcx = NULL; p12dcx->errorValue = PORT_GetError(); goto loser; } p12dcx->currentASafeP7Dcx = NULL; } p12dcx->currentASafeP7Dcx = NULL; if(p12dcx->currentASafeKeyPwd->key != NULL) { p12dcx->currentASafeKeyPwd->key = NULL; } } 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->aSafeDcx, buf, len); if(rv != SECSuccess) { p12dcx->error = (PRBool)SEC_ERROR_NO_MEMORY; 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->aSafeDcx); p12dcx->aSafeDcx = 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->aSafeDcx = SEC_ASN1DecoderStart(p12dcx->arena, &p12dcx->authSafe, sec_PKCS12AuthenticatedSafeTemplate); if(!p12dcx->aSafeDcx) { p12dcx->errorValue = SEC_ERROR_NO_MEMORY; goto loser; } /* set the notify function */ SEC_ASN1DecoderSetNotifyProc(p12dcx->aSafeDcx, 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 = SEC_ERROR_NO_MEMORY; goto loser; } /* open the temp file for writing, if the filter functions were set */ if(p12dcx->dOpen && (*p12dcx->dOpen)(p12dcx->dArg, PR_FALSE) != SECSuccess) { p12dcx->errorValue = PORT_GetError(); goto loser; } return SECSuccess; loser: p12dcx->error = PR_TRUE; if(p12dcx->aSafeDcx) { SEC_ASN1DecoderFinish(p12dcx->aSafeDcx); p12dcx->aSafeDcx = 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 occurrs, clear the notifyProc and the filterProc * and continue. */ if(p12dcx->error) { SEC_ASN1DecoderClearNotifyProc(p12dcx->pfxDcx); SEC_ASN1DecoderClearFilterProc(p12dcx->pfxDcx); 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->pfxDcx, 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->pfxDcx); 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) { return -1; } if (!p12cxt->buffer || ((p12cxt->filesize-p12cxt->currentpos)<(long)len) ) { /* trying to read past the end of the buffer */ toread = p12cxt->filesize-p12cxt->currentpos; } memcpy(buf, (void*)((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((void*)((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 hmack recomputed * and verified. doesn't seem to be away 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; PRArenaPool *arena; arena = PORT_NewArena(2048); /* different size? */ if(!arena) { PORT_SetError(SEC_ERROR_NO_MEMORY); return NULL; } /* allocate the decoder context and set the state variables */ p12dcx = (SEC_PKCS12DecoderContext*)PORT_ArenaZAlloc(arena, sizeof(SEC_PKCS12DecoderContext)); if(!p12dcx) { PORT_SetError(SEC_ERROR_NO_MEMORY); goto loser; } 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; #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->pfxDcx = SEC_ASN1DecoderStart(p12dcx->arena, &p12dcx->pfx, sec_PKCS12PFXItemTemplate); if(!p12dcx->pfxDcx) { PORT_SetError(SEC_ERROR_NO_MEMORY); PK11_FreeSlot(p12dcx->slot); goto loser; } SEC_ASN1DecoderSetNotifyProc(p12dcx->pfxDcx, 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; return p12dcx; loser: PORT_FreeArena(arena, PR_TRUE); return NULL; } /* 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) { return SECFailure; } /* update the PFX decoder context */ rv = SEC_ASN1DecoderUpdate(p12dcx->pfxDcx, (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; } /* IN_BUF_LEN should be larger than SHA1_LENGTH */ #define IN_BUF_LEN 80 /* 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) { SECStatus rv = SECFailure; SECStatus lrv; SECItem hmacRes; unsigned char buf[IN_BUF_LEN]; unsigned int bufLen; int iteration; PK11Context *pk11cx = NULL; PK11SymKey *symKey = NULL; SECItem *params = NULL; SECItem ignore = {0}; SECOidTag algtag; CK_MECHANISM_TYPE integrityMech; if(!p12dcx || p12dcx->error) { return SECFailure; } /* 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 occured reading from the * file. */ while(1) { int bytesRead = (*p12dcx->dRead)(p12dcx->dArg, buf, IN_BUF_LEN); if(bytesRead == -1) { goto loser; } lrv = PK11_DigestOp(pk11cx, buf, bytesRead); if (lrv == SECFailure) { goto loser; } if(bytesRead < IN_BUF_LEN) { break; } } /* 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); } if(pk11cx) { PK11_DestroyContext(pk11cx, PR_TRUE); } if (params) { PK11_DestroyPBEParams(params); } if (symKey) { PK11_FreeSymKey(symKey); } 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 occured... */ if(!p12dcx || p12dcx->error) { return SECFailure; } rv = SEC_ASN1DecoderFinish(p12dcx->pfxDcx); p12dcx->pfxDcx = 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); } else { PORT_SetError(SEC_ERROR_NO_MEMORY); } } else { if(SEC_PKCS7VerifySignature(p12dcx->aSafeCinfo, certUsageEmailSigner, PR_FALSE)) { return SECSuccess; } else { 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) { if(!p12dcx) { return; } if(p12dcx->pfxDcx) { SEC_ASN1DecoderFinish(p12dcx->pfxDcx); p12dcx->pfxDcx = NULL; } if(p12dcx->aSafeDcx) { SEC_ASN1DecoderFinish(p12dcx->aSafeDcx); p12dcx->aSafeDcx = NULL; } if(p12dcx->currentASafeP7Dcx) { SEC_PKCS7DecoderFinish(p12dcx->currentASafeP7Dcx); p12dcx->currentASafeP7Dcx = NULL; } if(p12dcx->aSafeP7Dcx) { SEC_PKCS7DecoderFinish(p12dcx->aSafeP7Dcx); } if(p12dcx->hmacDcx) { SEC_ASN1DecoderFinish(p12dcx->hmacDcx); p12dcx->hmacDcx = NULL; } if(p12dcx->slot) { PK11_FreeSlot(p12dcx->slot); p12dcx->slot = NULL; } 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) { return SECFailure; } oid = SECOID_FindOIDByTag(attributeType); if(!oid) { PORT_SetError(SEC_ERROR_NO_MEMORY); return SECFailure; } if(!bag->attribs) { bag->attribs = (sec_PKCS12Attribute**)PORT_ArenaZAlloc(bag->arena, sizeof(sec_PKCS12Attribute *) * 2); } else { while(bag->attribs[i]) i++; bag->attribs = (sec_PKCS12Attribute **)PORT_ArenaGrow(bag->arena, bag->attribs, (i + 1) * sizeof(sec_PKCS12Attribute *), (i + 2) * sizeof(sec_PKCS12Attribute *)); } if(!bag->attribs) { PORT_SetError(SEC_ERROR_NO_MEMORY); return SECFailure; } bag->attribs[i] = (sec_PKCS12Attribute*)PORT_ArenaZAlloc(bag->arena, sizeof(sec_PKCS12Attribute)); if(!bag->attribs) { PORT_SetError(SEC_ERROR_NO_MEMORY); return SECFailure; } bag->attribs[i]->attrValue = (SECItem**)PORT_ArenaZAlloc(bag->arena, sizeof(SECItem *) * 2); if(!bag->attribs[i]->attrValue) { PORT_SetError(SEC_ERROR_NO_MEMORY); return SECFailure; } bag->attribs[i+1] = NULL; bag->attribs[i]->attrValue[0] = attrValue; bag->attribs[i]->attrValue[1] = NULL; if(SECITEM_CopyItem(bag->arena, &bag->attribs[i]->attrType, &oid->oid) != SECSuccess) { PORT_SetError(SEC_ERROR_NO_MEMORY); return SECFailure; } return SECSuccess; } static SECItem * sec_pkcs12_get_attribute_value(sec_PKCS12SafeBag *bag, SECOidTag attributeType) { int i = 0; if(!bag->attribs) { return NULL; } while(bag->attribs[i] != NULL) { if(SECOID_FindOIDTag(&bag->attribs[i]->attrType) == attributeType) { return bag->attribs[i]->attrValue[0]; } i++; } 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; /*Mac breaks without this type cast*/ 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) { bag->problem = PR_TRUE; bag->error = SEC_ERROR_NO_MEMORY; return NULL; } src = sec_pkcs12_get_attribute_value(bag, SEC_OID_PKCS9_FRIENDLY_NAME); if(!src) { 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) { int i = 0; sec_PKCS12Attribute *attr = NULL; SECOidData *oid = SECOID_FindOIDByTag(SEC_OID_PKCS9_FRIENDLY_NAME); if(!bag || !bag->arena || !name) { return SECFailure; } if(!bag->attribs) { if(!oid) { goto loser; } bag->attribs = (sec_PKCS12Attribute**)PORT_ArenaZAlloc(bag->arena, sizeof(sec_PKCS12Attribute *)*2); if(!bag->attribs) { goto loser; } bag->attribs[0] = (sec_PKCS12Attribute*)PORT_ArenaZAlloc(bag->arena, sizeof(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 { while(bag->attribs[i]) { if(SECOID_FindOIDTag(&bag->attribs[i]->attrType) == SEC_OID_PKCS9_FRIENDLY_NAME) { attr = bag->attribs[i]; goto have_attrib; } i++; } if(!attr) { bag->attribs = (sec_PKCS12Attribute **)PORT_ArenaGrow(bag->arena, bag->attribs, (i+1) * sizeof(sec_PKCS12Attribute *), (i+2) * sizeof(sec_PKCS12Attribute *)); if(!bag->attribs) { goto loser; } bag->attribs[i] = (sec_PKCS12Attribute *)PORT_ArenaZAlloc(bag->arena, sizeof(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; } } } have_attrib: PORT_Assert(attr); if(!attr->attrValue) { attr->attrValue = (SECItem **)PORT_ArenaZAlloc(bag->arena, sizeof(SECItem *) * 2); if(!attr->attrValue) { goto loser; } attr->attrValue[0] = (SECItem*)PORT_ArenaZAlloc(bag->arena, sizeof(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 = SEC_ERROR_NO_MEMORY; return SECFailure; } static SECStatus sec_pkcs12_get_key_info(sec_PKCS12SafeBag *key) { int i = 0; SECKEYPrivateKeyInfo *pki = NULL; if(!key) { 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]) { SECItem *attrValue = NULL; if(SECOID_FindOIDTag(&pki->attributes[i]->attrType) == SEC_OID_PKCS9_LOCAL_KEY_ID) { attrValue = sec_pkcs12_get_attribute_value(key, SEC_OID_PKCS9_LOCAL_KEY_ID); if(!attrValue) { if(sec_pkcs12_decoder_set_attribute_value(key, SEC_OID_PKCS9_LOCAL_KEY_ID, pki->attributes[i]->attrValue[0]) != SECSuccess) { key->problem = PR_TRUE; key->error = SEC_ERROR_NO_MEMORY; return SECFailure; } } } if(SECOID_FindOIDTag(&pki->attributes[i]->attrType) == SEC_OID_PKCS9_FRIENDLY_NAME) { attrValue = sec_pkcs12_get_attribute_value(key, SEC_OID_PKCS9_FRIENDLY_NAME); if(!attrValue) { if(sec_pkcs12_decoder_set_attribute_value(key, SEC_OID_PKCS9_FRIENDLY_NAME, pki->attributes[i]->attrValue[0]) != SECSuccess) { key->problem = PR_TRUE; key->error = SEC_ERROR_NO_MEMORY; 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, void *wincx) { SECItem *nickname; if(!cert) { 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) { cert->error = SEC_ERROR_NO_MEMORY; cert->problem = PR_TRUE; if(nickname) { 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, void *wincx) { if(!nickname || !cert) { return SECFailure; } if(sec_pkcs12_set_nickname(cert, nickname) != SECSuccess) { cert->error = SEC_ERROR_NO_MEMORY; cert->problem = PR_TRUE; return SECFailure; } if(key) { if(sec_pkcs12_set_nickname(key, nickname) != SECSuccess) { cert->error = SEC_ERROR_NO_MEMORY; cert->problem = PR_TRUE; return SECFailure; } } return SECSuccess; } /* retrieve the DER cert from the cert bag */ static SECItem * sec_pkcs12_get_der_cert(sec_PKCS12SafeBag *cert) { if(!cert) { 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 { PRArenaPool *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) { return SECFailure; } if(!cert->nickname) { return SECSuccess; } tempNick.data = (unsigned char *)cert->nickname; tempNick.len = PORT_Strlen(cert->nickname) + 1; /* 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_NO_MEMORY; 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 */ if(nickArg->nNicks == 0) { nickArg->nickList = (SECItem **)PORT_ArenaZAlloc(nickArg->arena, 2 * sizeof(SECItem *)); } else { nickArg->nickList = (SECItem **)PORT_ArenaGrow(nickArg->arena, nickArg->nickList, (nickArg->nNicks + 1) * sizeof(SECItem *), (nickArg->nNicks + 2) * sizeof(SECItem *)); } if(!nickArg->nickList) { nickArg->error = SEC_ERROR_NO_MEMORY; return SECFailure; } nickArg->nickList[nickArg->nNicks] = (SECItem *)PORT_ArenaZAlloc(nickArg->arena, sizeof(SECItem)); if(!nickArg->nickList[nickArg->nNicks]) { nickArg->error = SEC_ERROR_NO_MEMORY; return SECFailure; } if(SECITEM_CopyItem(nickArg->arena, nickArg->nickList[nickArg->nNicks], &tempNick) != SECSuccess) { nickArg->error = SEC_ERROR_NO_MEMORY; 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, void *wincx) { struct certNickInfo *nickArg = NULL; SECItem *derCert, *returnDn = NULL; PRArenaPool *arena = NULL; CERTCertificate *tempCert; if(!cert) { 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 = (struct certNickInfo *)PORT_ArenaZAlloc(arena, sizeof(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) { return SECFailure; } (*nCerts)++; return SECSuccess; } static PRBool sec_pkcs12_certs_for_nickname_exist(SECItem *nickname, PK11SlotInfo *slot) { unsigned int nCerts = 0; if(!nickname || !slot) { return PR_TRUE; } /* we want to check the local database first if we are importing to it */ PK11_TraverseCertsForNicknameInSlot(nickname, slot, countCertificate, (void *)&nCerts); if(nCerts) return PR_TRUE; return PR_FALSE; } /* 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, void *wincx) { SECItem *certNickname, *existingDNNick; PRBool setNickname = PR_FALSE, cancel = PR_FALSE; SECItem *newNickname = NULL; if(!cert || !cert->hasKey) { return; } if(!nicknameCb) { cert->problem = PR_TRUE; cert->error = SEC_ERROR_NO_MEMORY; return; } if(cert->hasKey && !key) { cert->problem = PR_TRUE; cert->error = SEC_ERROR_NO_MEMORY; return; } certNickname = sec_pkcs12_get_nickname_for_cert(cert, key, wincx); existingDNNick = sec_pkcs12_get_existing_nick_for_dn(cert, wincx); /* 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) { if(sec_pkcs12_set_nickname_for_cert(cert, key, existingDNNick, wincx) != SECSuccess) { cert->problem = PR_TRUE; cert->error = SEC_ERROR_NO_MEMORY; } 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) { if(certNickname && certNickname->data) { /* we will use the nickname so long as no other certs have the * same nickname. and the nickname is not NULL. */ if(!sec_pkcs12_certs_for_nickname_exist(certNickname, cert->slot)) { if(setNickname) { if(sec_pkcs12_set_nickname_for_cert(cert, key, certNickname, wincx) != SECSuccess) { cert->problem = PR_TRUE; cert->error = SEC_ERROR_NO_MEMORY; } } goto loser; } } setNickname = PR_FALSE; newNickname = (*nicknameCb)(certNickname, &cancel, wincx); if(cancel) { cert->problem = PR_TRUE; cert->error = SEC_ERROR_USER_CANCELLED; goto loser; } if(!newNickname) { cert->problem = PR_TRUE; cert->error = SEC_ERROR_NO_MEMORY; goto loser; } /* 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, void *wincx) { CERTCertificate *leafCert; if(!cert) { return; } cert->validated = PR_TRUE; if(!nicknameCb) { cert->problem = PR_TRUE; cert->error = SEC_ERROR_NO_MEMORY; cert->noInstall = PR_TRUE; 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 = SEC_ERROR_NO_MEMORY; return; } CERT_DestroyCertificate(leafCert); sec_pkcs12_validate_cert_nickname(cert, key, nicknameCb, wincx); } static void sec_pkcs12_validate_key_by_cert(sec_PKCS12SafeBag *cert, sec_PKCS12SafeBag *key, void *wincx) { CERTCertificate *leafCert; SECKEYPrivateKey *privk; if(!key) { 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 = SEC_ERROR_NO_MEMORY; 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; SECStatus rv; if(!cert) { 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; } 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 = SEC_ERROR_NO_MEMORY; cert->problem = PR_TRUE; return SECFailure; } rv = PK11_ImportCertForKeyToSlot(cert->slot, newCert, nickData, PR_TRUE, wincx); CERT_DestroyCertificate(newCert); } else { SECItem *certList[2]; certList[0] = derCert; certList[1] = NULL; rv = CERT_ImportCerts(CERT_GetDefaultCertDB(), certUsageUserCertImport, 1, certList, NULL, PR_TRUE, PR_FALSE, nickData); } cert->installed = PR_TRUE; if(nickName) SECITEM_ZfreeItem(nickName, PR_TRUE); return rv; } static SECStatus sec_pkcs12_add_key(sec_PKCS12SafeBag *key, SECItem *publicValue, KeyType keyType, unsigned int keyUsage, void *wincx) { SECStatus rv; SECItem *nickName; if(!key) { return SECFailure; } if(key->problem || key->noInstall) { return SECSuccess; } nickName = sec_pkcs12_get_nickname(key); 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; } key->installed = PR_TRUE; if(nickName) { SECITEM_ZfreeItem(nickName, PR_TRUE); } if(rv != SECSuccess) { key->error = SEC_ERROR_PKCS12_UNABLE_TO_IMPORT_KEY; key->problem = PR_TRUE; } else { key->installed = PR_TRUE; } return rv; } static SECStatus sec_pkcs12_add_item_to_bag_list(sec_PKCS12SafeBag ***bagList, sec_PKCS12SafeBag *bag) { int i = 0; if(!bagList || !bag) { return SECFailure; } if(!(*bagList)) { (*bagList) = (sec_PKCS12SafeBag **)PORT_ArenaZAlloc(bag->arena, sizeof(sec_PKCS12SafeBag *) * 2); } else { while((*bagList)[i]) i++; (*bagList) = (sec_PKCS12SafeBag **)PORT_ArenaGrow(bag->arena, *bagList, sizeof(sec_PKCS12SafeBag *) * (i + 1), sizeof(sec_PKCS12SafeBag *) * (i + 2)); } if(!(*bagList)) { PORT_SetError(SEC_ERROR_NO_MEMORY); return SECFailure; } (*bagList)[i] = bag; (*bagList)[i+1] = NULL; 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]) { return NULL; } keyId = sec_pkcs12_get_attribute_value(key, SEC_OID_PKCS9_LOCAL_KEY_ID); if(!keyId) { return NULL; } i = 0; certList = NULL; while(safeBags[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) { return NULL; } } } i++; } return certList; } CERTCertList * SEC_PKCS12DecoderGetCerts(SEC_PKCS12DecoderContext *p12dcx) { CERTCertList *certList = NULL; sec_PKCS12SafeBag **safeBags = p12dcx->safeBags; int i; if (!p12dcx || !p12dcx->safeBags || !p12dcx->safeBags[0]) { return NULL; } safeBags = p12dcx->safeBags; i = 0; certList = CERT_NewCertList(); if (certList == NULL) { return NULL; } while(safeBags[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); } i++; } 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]) { return NULL; } i = 0; while(safeBags[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) { return NULL; } break; default: break; } i++; } return keyList; } static SECStatus sec_pkcs12_validate_bags(sec_PKCS12SafeBag **safeBags, SEC_PKCS12NicknameCollisionCallback nicknameCb, void *wincx) { sec_PKCS12SafeBag **keyList; int i; if(!safeBags || !nicknameCb) { return SECFailure; } if(!safeBags[0]) { return SECSuccess; } keyList = sec_pkcs12_get_key_bags(safeBags); if(keyList) { i = 0; while(keyList[i]) { sec_PKCS12SafeBag **certList = sec_pkcs12_find_certs_for_key( safeBags, keyList[i]); if(certList) { int j = 0; if(SECOID_FindOIDTag(&(keyList[i]->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(keyList[i]) != SECSuccess) { keyList[i]->problem = PR_TRUE; keyList[i]->error = SEC_ERROR_NO_MEMORY; return SECFailure; } } sec_pkcs12_validate_key_by_cert(certList[0], keyList[i], wincx); while(certList[j]) { certList[j]->hasKey = PR_TRUE; if(keyList[i]->problem) { certList[j]->problem = PR_TRUE; certList[j]->error = keyList[i]->error; } else { sec_pkcs12_validate_cert(certList[j], keyList[i], nicknameCb, wincx); if(certList[j]->problem) { keyList[i]->problem = certList[j]->problem; keyList[i]->error = certList[j]->error; } } j++; } } i++; } } i = 0; while(safeBags[i]) { if(!safeBags[i]->validated) { SECOidTag bagType = SECOID_FindOIDTag(&safeBags[i]->safeBagType); switch(bagType) { case SEC_OID_PKCS12_V1_CERT_BAG_ID: sec_pkcs12_validate_cert(safeBags[i], NULL, nicknameCb, wincx); break; case SEC_OID_PKCS12_V1_KEY_BAG_ID: case SEC_OID_PKCS12_V1_PKCS8_SHROUDED_KEY_BAG_ID: safeBags[i]->noInstall = PR_TRUE; safeBags[i]->problem = PR_TRUE; safeBags[i]->error = SEC_ERROR_PKCS12_UNABLE_TO_IMPORT_KEY; break; default: safeBags[i]->noInstall = PR_TRUE; } } i++; } return SECSuccess; } SECStatus SEC_PKCS12DecoderValidateBags(SEC_PKCS12DecoderContext *p12dcx, SEC_PKCS12NicknameCollisionCallback nicknameCb) { SECStatus rv; int i, noInstallCnt, probCnt, bagCnt, errorVal = 0; if(!p12dcx || p12dcx->error) { 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++; } if(bagCnt == noInstallCnt) { PORT_SetError(SEC_ERROR_PKCS12_DUPLICATE_DATA); return SECFailure; } if(probCnt) { PORT_SetError(errorVal); return SECFailure; } return rv; } static SECItem * sec_pkcs12_get_public_value_and_type(sec_PKCS12SafeBag *certBag, KeyType *type, unsigned int *usage) { SECKEYPublicKey *pubKey = NULL; CERTCertificate *cert = NULL; SECItem *pubValue; *type = nullKey; *usage = 0; if(!certBag) { return NULL; } 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); if(!pubKey) { return NULL; } *type = pubKey->keyType; switch(pubKey->keyType) { case dsaKey: pubValue = SECITEM_DupItem(&pubKey->u.dsa.publicValue); break; case dhKey: pubValue = SECITEM_DupItem(&pubKey->u.dh.publicValue); break; case rsaKey: pubValue = SECITEM_DupItem(&pubKey->u.rsa.modulus); break; default: pubValue = NULL; } SECKEY_DestroyPublicKey(pubKey); return pubValue; } static SECStatus sec_pkcs12_install_bags(sec_PKCS12SafeBag **safeBags, void *wincx) { sec_PKCS12SafeBag **keyList, **certList; int i; if(!safeBags) { return SECFailure; } if(!safeBags[0]) { return SECSuccess; } keyList = sec_pkcs12_get_key_bags(safeBags); if(keyList) { i = 0; while(keyList[i]) { SECStatus rv; SECItem *publicValue = NULL; KeyType keyType; unsigned int keyUsage; if(keyList[i]->problem) { goto next_key_bag; } certList = sec_pkcs12_find_certs_for_key(safeBags, keyList[i]); if(certList) { publicValue = sec_pkcs12_get_public_value_and_type(certList[0], &keyType, &keyUsage); } rv = sec_pkcs12_add_key(keyList[i], publicValue, keyType, keyUsage, wincx); if(publicValue) { SECITEM_FreeItem(publicValue, PR_TRUE); } if(rv != SECSuccess) { PORT_SetError(keyList[i]->error); return SECFailure; } if(certList) { int j = 0; while(certList[j]) { SECStatus certRv; if(rv != SECSuccess) { certList[j]->problem = keyList[i]->problem; certList[j]->error = keyList[i]->error; certList[j]->noInstall = PR_TRUE; goto next_cert_bag; } certRv = sec_pkcs12_add_cert(certList[j], certList[j]->hasKey, wincx); if(certRv != SECSuccess) { keyList[i]->problem = certList[j]->problem; keyList[i]->error = certList[j]->error; PORT_SetError(certList[j]->error); return SECFailure; } next_cert_bag: j++; } } next_key_bag: i++; } } i = 0; while(safeBags[i]) { if(!safeBags[i]->installed) { SECStatus rv; SECOidTag bagType = SECOID_FindOIDTag(&(safeBags[i]->safeBagType)); switch(bagType) { case SEC_OID_PKCS12_V1_CERT_BAG_ID: rv = sec_pkcs12_add_cert(safeBags[i], safeBags[i]->hasKey, wincx); if(rv != SECSuccess) { PORT_SetError(safeBags[i]->error); return SECFailure; } break; case SEC_OID_PKCS12_V1_KEY_BAG_ID: case SEC_OID_PKCS12_V1_PKCS8_SHROUDED_KEY_BAG_ID: default: break; } } i++; } return SECSuccess; } SECStatus SEC_PKCS12DecoderImportBags(SEC_PKCS12DecoderContext *p12dcx) { if(!p12dcx || p12dcx->error) { return SECFailure; } if(!p12dcx->bagsVerified) { return SECFailure; } return sec_pkcs12_install_bags(p12dcx->safeBags, p12dcx->wincx); } static SECStatus sec_pkcs12_decoder_append_bag_to_context(SEC_PKCS12DecoderContext *p12dcx, sec_PKCS12SafeBag *bag) { if(!p12dcx || p12dcx->error) { return SECFailure; } if(!p12dcx->safeBagCount) { p12dcx->safeBags = (sec_PKCS12SafeBag **)PORT_ArenaZAlloc(p12dcx->arena, sizeof(sec_PKCS12SafeBag *) * 2); } else { p12dcx->safeBags = (sec_PKCS12SafeBag **)PORT_ArenaGrow(p12dcx->arena, p12dcx->safeBags, (p12dcx->safeBagCount + 1) * sizeof(sec_PKCS12SafeBag *), (p12dcx->safeBagCount + 2) * sizeof(sec_PKCS12SafeBag *)); } 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) { return NULL; } newNickName =(SECItem *)PORT_ArenaZAlloc(p12dcx->arena, sizeof(SECItem)); keyBag = (sec_PKCS12SafeBag *)PORT_ArenaZAlloc(p12dcx->arena, sizeof(sec_PKCS12SafeBag)); if(!keyBag || !newNickName) { PORT_SetError(SEC_ERROR_NO_MEMORY); return NULL; } keyBag->swapUnicodeBytes = p12dcx->swapUnicodeBytes; keyBag->slot = p12dcx->slot; keyBag->arena = p12dcx->arena; keyBag->pwitem = p12dcx->pwitem; 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) { PORT_SetError(SEC_ERROR_NO_MEMORY); return NULL; } if(SECITEM_CopyItem(p12dcx->arena, &keyBag->safeBagType, &oid->oid) != SECSuccess) { PORT_SetError(SEC_ERROR_NO_MEMORY); 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)) { PORT_SetError(SEC_ERROR_NO_MEMORY); 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)) { PORT_SetError(SEC_ERROR_NO_MEMORY); 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) { return NULL; } keyId = (SECItem *)PORT_ArenaZAlloc(p12dcx->arena, sizeof(SECItem)); if(!keyId) { PORT_SetError(SEC_ERROR_NO_MEMORY); 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 = (sec_PKCS12SafeBag *)PORT_ArenaZAlloc(p12dcx->arena, sizeof(sec_PKCS12SafeBag)); if(!certBag || !oid || (SECITEM_CopyItem(p12dcx->arena, &certBag->safeBagType, &oid->oid) != SECSuccess)) { PORT_SetError(SEC_ERROR_NO_MEMORY); return NULL; } certBag->slot = p12dcx->slot; certBag->pwitem = p12dcx->pwitem; certBag->swapUnicodeBytes = p12dcx->swapUnicodeBytes; certBag->arena = p12dcx->arena; oid = SECOID_FindOIDByTag(SEC_OID_PKCS9_X509_CERT); certBag->safeBagContent.certBag = (sec_PKCS12CertBag *)PORT_ArenaZAlloc(p12dcx->arena, sizeof(sec_PKCS12CertBag)); if(!certBag->safeBagContent.certBag || !oid || (SECITEM_CopyItem(p12dcx->arena, &certBag->safeBagContent.certBag->bagID, &oid->oid) != SECSuccess)) { PORT_SetError(SEC_ERROR_NO_MEMORY); return NULL; } if(SECITEM_CopyItem(p12dcx->arena, &(certBag->safeBagContent.certBag->value.x509Cert), derCert) != SECSuccess) { PORT_SetError(SEC_ERROR_NO_MEMORY); 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) { return NULL; } derCertList = SEC_PKCS7GetCertificateList(&oldCert->value.x509->certOrCRL); if(!derCertList) { return NULL; } i = 0; while(derCertList[i]) i++; certList = (sec_PKCS12SafeBag **)PORT_ArenaZAlloc(p12dcx->arena, (i + 1) * sizeof(sec_PKCS12SafeBag *)); if(!certList) { PORT_SetError(SEC_ERROR_NO_MEMORY); 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) { 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(PRArenaPool *arena, PK11SlotInfo *slot, PRBool swapUnicode, SECItem *pwitem, void *wincx, SEC_PKCS12SafeContents *safe, SEC_PKCS12Baggage *baggage) { SEC_PKCS12DecoderContext *p12dcx; if(!arena || !slot || !pwitem) { return NULL; } if(!safe && !baggage) { return NULL; } p12dcx = (SEC_PKCS12DecoderContext *)PORT_ArenaZAlloc(arena, sizeof(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; if(sec_pkcs12_decoder_convert_old_safe_to_bags(p12dcx, safe, baggage) != SECSuccess) { p12dcx->error = PR_TRUE; return NULL; } return p12dcx; }