diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /security/nss/lib/pkcs12 | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | security/nss/lib/pkcs12/Makefile | 47 | ||||
-rw-r--r-- | security/nss/lib/pkcs12/exports.gyp | 29 | ||||
-rw-r--r-- | security/nss/lib/pkcs12/manifest.mn | 32 | ||||
-rw-r--r-- | security/nss/lib/pkcs12/p12.h | 236 | ||||
-rw-r--r-- | security/nss/lib/pkcs12/p12creat.c | 218 | ||||
-rw-r--r-- | security/nss/lib/pkcs12/p12d.c | 3626 | ||||
-rw-r--r-- | security/nss/lib/pkcs12/p12dec.c | 670 | ||||
-rw-r--r-- | security/nss/lib/pkcs12/p12e.c | 2087 | ||||
-rw-r--r-- | security/nss/lib/pkcs12/p12exp.c | 1374 | ||||
-rw-r--r-- | security/nss/lib/pkcs12/p12local.c | 1417 | ||||
-rw-r--r-- | security/nss/lib/pkcs12/p12local.h | 70 | ||||
-rw-r--r-- | security/nss/lib/pkcs12/p12plcy.c | 106 | ||||
-rw-r--r-- | security/nss/lib/pkcs12/p12plcy.h | 25 | ||||
-rw-r--r-- | security/nss/lib/pkcs12/p12t.h | 156 | ||||
-rw-r--r-- | security/nss/lib/pkcs12/p12tmpl.c | 290 | ||||
-rw-r--r-- | security/nss/lib/pkcs12/pkcs12.gyp | 29 | ||||
-rw-r--r-- | security/nss/lib/pkcs12/pkcs12.h | 41 | ||||
-rw-r--r-- | security/nss/lib/pkcs12/pkcs12t.h | 341 |
18 files changed, 10794 insertions, 0 deletions
diff --git a/security/nss/lib/pkcs12/Makefile b/security/nss/lib/pkcs12/Makefile new file mode 100644 index 0000000000..3fd2f50b12 --- /dev/null +++ b/security/nss/lib/pkcs12/Makefile @@ -0,0 +1,47 @@ +#! gmake +# +# 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/. + + +####################################################################### +# (1) Include initial platform-independent assignments (MANDATORY). # +####################################################################### + +include manifest.mn + +####################################################################### +# (2) Include "global" configuration information. (OPTIONAL) # +####################################################################### + +include $(CORE_DEPTH)/coreconf/config.mk + +####################################################################### +# (3) Include "component" configuration information. (OPTIONAL) # +####################################################################### + + + +####################################################################### +# (4) Include "local" platform-dependent assignments (OPTIONAL). # +####################################################################### + +####################################################################### +# (5) Execute "global" rules. (OPTIONAL) # +####################################################################### + +include $(CORE_DEPTH)/coreconf/rules.mk + +####################################################################### +# (6) Execute "component" rules. (OPTIONAL) # +####################################################################### + + + +####################################################################### +# (7) Execute "local" rules. (OPTIONAL). # +####################################################################### + + + diff --git a/security/nss/lib/pkcs12/exports.gyp b/security/nss/lib/pkcs12/exports.gyp new file mode 100644 index 0000000000..274ef68d67 --- /dev/null +++ b/security/nss/lib/pkcs12/exports.gyp @@ -0,0 +1,29 @@ +# 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/. +{ + 'includes': [ + '../../coreconf/config.gypi' + ], + 'targets': [ + { + 'target_name': 'lib_pkcs12_exports', + 'type': 'none', + 'copies': [ + { + 'files': [ + 'p12.h', + 'p12plcy.h', + 'p12t.h', + 'pkcs12.h', + 'pkcs12t.h' + ], + 'destination': '<(nss_public_dist_dir)/<(module)' + } + ] + } + ], + 'variables': { + 'module': 'nss' + } +} diff --git a/security/nss/lib/pkcs12/manifest.mn b/security/nss/lib/pkcs12/manifest.mn new file mode 100644 index 0000000000..ab54bec43a --- /dev/null +++ b/security/nss/lib/pkcs12/manifest.mn @@ -0,0 +1,32 @@ +# +# 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/. + +CORE_DEPTH = ../.. + +EXPORTS = \ + pkcs12t.h \ + pkcs12.h \ + p12plcy.h \ + p12.h \ + p12t.h \ + $(NULL) + +MODULE = nss + +CSRCS = \ + p12local.c \ + p12creat.c \ + p12dec.c \ + p12plcy.c \ + p12tmpl.c \ + p12e.c \ + p12d.c \ + $(NULL) + +LIBRARY_NAME = pkcs12 +SHARED_LIBRARY = $(NULL) + +# This part of the code, including all sub-dirs, can be optimized for size +export ALLOW_OPT_CODE_SIZE = 1 diff --git a/security/nss/lib/pkcs12/p12.h b/security/nss/lib/pkcs12/p12.h new file mode 100644 index 0000000000..495bbf6c49 --- /dev/null +++ b/security/nss/lib/pkcs12/p12.h @@ -0,0 +1,236 @@ +/* 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/. */ + +#ifndef _P12_H_ +#define _P12_H_ + +#include "secoid.h" +#include "keyhi.h" +#include "secpkcs7.h" +#include "p12t.h" + +typedef int(PR_CALLBACK *PKCS12OpenFunction)(void *arg); +typedef int(PR_CALLBACK *PKCS12ReadFunction)(void *arg, + unsigned char *buffer, + unsigned int *lenRead, + unsigned int maxLen); +typedef int(PR_CALLBACK *PKCS12WriteFunction)(void *arg, + unsigned char *buffer, + unsigned int *bufLen, + unsigned int *lenWritten); +typedef int(PR_CALLBACK *PKCS12CloseFunction)(void *arg); +typedef SECStatus(PR_CALLBACK *PKCS12UnicodeConvertFunction)( + PLArenaPool *arena, + SECItem *dest, SECItem *src, + PRBool toUnicode, + PRBool swapBytes); +typedef void(PR_CALLBACK *SEC_PKCS12EncoderOutputCallback)( + void *arg, const char *buf, + unsigned long len); +typedef void(PR_CALLBACK *SEC_PKCS12DecoderOutputCallback)( + void *arg, const char *buf, + unsigned long len); +/* + * In NSS 3.12 or later, 'arg' actually points to a CERTCertificate, + * the 'leafCert' variable in sec_pkcs12_validate_cert in p12d.c. + * See r1.35 of p12d.c ("Patch 2" in bug 321584). + * + * This callback might be called by SEC_PKCS12DecoderValidateBags each time + * a nickname collission is detected. The callback must return a new + * nickname. The returned SECItem should be of type siAsciiString, + * it should be allocated using: + * SECITEM_AllocItem(NULL, NULL, LENGTH_OF_NEW_NICKNAME + 1) + * and data must contain the new nickname as a zero terminated string. + */ +typedef SECItem *(PR_CALLBACK *SEC_PKCS12NicknameCollisionCallback)( + SECItem *old_nickname, + PRBool *cancel, + void *arg); +/* + * This callback is called by SEC_PKCS12DecoderRenameCertNicknames for each + * certificate found in the p12 source data. + * + * cert: A decoded certificate. + * default_nickname: The nickname as found in the source data. + * Will be NULL if source data doesn't have nickname. + * new_nickname: Output parameter that may contain the renamed nickname. + * arg: The user data that was passed to SEC_PKCS12DecoderRenameCertNicknames. + * + * If the callback accept that NSS will use a nickname based on the + * default_nickname (potentially resolving conflicts), then the callback + * must set *new_nickname to NULL. + * + * If the callback wishes to override the nickname, it must set *new_nickname + * to a new SECItem which should be allocated using + * SECITEM_AllocItem(NULL, NULL, LENGTH_OF_NEW_NICKNAME + 1) + * new_nickname->type should be set to siAsciiString, and new_nickname->data + * must contain the new nickname as a zero terminated string. + * + * A return value of SECFailure indicates that the renaming operation failed, + * and callback should release new_nickname before returning if it's already + * being allocated. + * Otherwise, the callback function must return SECSuccess, including use + * default nickname as mentioned above. + */ +typedef SECStatus(PR_CALLBACK *SEC_PKCS12NicknameRenameCallback)( + const CERTCertificate *cert, + const SECItem *default_nickname, + SECItem **new_nickname, + void *arg); + +typedef SECStatus(PR_CALLBACK *digestOpenFn)(void *arg, PRBool readData); +typedef SECStatus(PR_CALLBACK *digestCloseFn)(void *arg, PRBool removeFile); +typedef int(PR_CALLBACK *digestIOFn)(void *arg, unsigned char *buf, + unsigned long len); + +typedef struct SEC_PKCS12ExportContextStr SEC_PKCS12ExportContext; +typedef struct SEC_PKCS12SafeInfoStr SEC_PKCS12SafeInfo; +typedef struct SEC_PKCS12DecoderContextStr SEC_PKCS12DecoderContext; +typedef struct SEC_PKCS12DecoderItemStr SEC_PKCS12DecoderItem; + +struct sec_PKCS12PasswordModeInfo { + SECItem *password; + SECOidTag algorithm; +}; + +struct sec_PKCS12PublicKeyModeInfo { + CERTCertificate *cert; + CERTCertDBHandle *certDb; + SECOidTag algorithm; + int keySize; +}; + +struct SEC_PKCS12DecoderItemStr { + SECItem *der; + SECOidTag type; + PRBool hasKey; + SECItem *friendlyName; /* UTF-8 string */ + SECAlgorithmID *shroudAlg; +}; + +SEC_BEGIN_PROTOS + +SEC_PKCS12SafeInfo * +SEC_PKCS12CreatePubKeyEncryptedSafe(SEC_PKCS12ExportContext *p12ctxt, + CERTCertDBHandle *certDb, + CERTCertificate *signer, + CERTCertificate **recipients, + SECOidTag algorithm, int keysize); + +extern SEC_PKCS12SafeInfo * +SEC_PKCS12CreatePasswordPrivSafe(SEC_PKCS12ExportContext *p12ctxt, + SECItem *pwitem, SECOidTag privAlg); + +extern SEC_PKCS12SafeInfo * +SEC_PKCS12CreateUnencryptedSafe(SEC_PKCS12ExportContext *p12ctxt); + +extern SECStatus +SEC_PKCS12AddPasswordIntegrity(SEC_PKCS12ExportContext *p12ctxt, + SECItem *pwitem, SECOidTag integAlg); +extern SECStatus +SEC_PKCS12AddPublicKeyIntegrity(SEC_PKCS12ExportContext *p12ctxt, + CERTCertificate *cert, CERTCertDBHandle *certDb, + SECOidTag algorithm, int keySize); + +extern SEC_PKCS12ExportContext * +SEC_PKCS12CreateExportContext(SECKEYGetPasswordKey pwfn, void *pwfnarg, + PK11SlotInfo *slot, void *wincx); + +extern SECStatus +SEC_PKCS12AddCert(SEC_PKCS12ExportContext *p12ctxt, + SEC_PKCS12SafeInfo *safe, void *nestedDest, + CERTCertificate *cert, CERTCertDBHandle *certDb, + SECItem *keyId, PRBool includeCertChain); + +extern SECStatus +SEC_PKCS12AddKeyForCert(SEC_PKCS12ExportContext *p12ctxt, + SEC_PKCS12SafeInfo *safe, + void *nestedDest, CERTCertificate *cert, + PRBool shroudKey, SECOidTag algorithm, SECItem *pwitem, + SECItem *keyId, SECItem *nickName); + +extern SECStatus +SEC_PKCS12AddCertOrChainAndKey(SEC_PKCS12ExportContext *p12ctxt, + void *certSafe, void *certNestedDest, + CERTCertificate *cert, CERTCertDBHandle *certDb, + void *keySafe, void *keyNestedDest, PRBool shroudKey, + SECItem *pwitem, SECOidTag algorithm, + PRBool includeCertChain); + +extern SECStatus +SEC_PKCS12AddCertAndKey(SEC_PKCS12ExportContext *p12ctxt, + void *certSafe, void *certNestedDest, + CERTCertificate *cert, CERTCertDBHandle *certDb, + void *keySafe, void *keyNestedDest, + PRBool shroudKey, SECItem *pwitem, SECOidTag algorithm); + +extern void * +SEC_PKCS12CreateNestedSafeContents(SEC_PKCS12ExportContext *p12ctxt, + void *baseSafe, void *nestedDest); + +extern SECStatus +SEC_PKCS12Encode(SEC_PKCS12ExportContext *p12exp, + SEC_PKCS12EncoderOutputCallback output, void *outputarg); + +extern void +SEC_PKCS12DestroyExportContext(SEC_PKCS12ExportContext *p12exp); + +extern SEC_PKCS12DecoderContext * +SEC_PKCS12DecoderStart(SECItem *pwitem, PK11SlotInfo *slot, void *wincx, + digestOpenFn dOpen, digestCloseFn dClose, + digestIOFn dRead, digestIOFn dWrite, void *dArg); + +extern SECStatus +SEC_PKCS12DecoderSetTargetTokenCAs(SEC_PKCS12DecoderContext *p12dcx, + SECPKCS12TargetTokenCAs tokenCAs); + +extern SECStatus +SEC_PKCS12DecoderUpdate(SEC_PKCS12DecoderContext *p12dcx, unsigned char *data, + unsigned long len); + +extern void +SEC_PKCS12DecoderFinish(SEC_PKCS12DecoderContext *p12dcx); + +extern SECStatus +SEC_PKCS12DecoderVerify(SEC_PKCS12DecoderContext *p12dcx); + +extern SECStatus +SEC_PKCS12DecoderValidateBags(SEC_PKCS12DecoderContext *p12dcx, + SEC_PKCS12NicknameCollisionCallback nicknameCb); + +/* + * SEC_PKCS12DecoderRenameCertNicknames() can be used to change + * certificate nicknames in SEC_PKCS12DecoderContext, prior to calling + * SEC_PKCS12DecoderImportBags. + * + * arg: User-defined data that will be passed to nicknameCb. + * + * If SEC_PKCS12DecoderRenameCertNicknames() is called after calling + * SEC_PKCS12DecoderValidateBags(), then only the certificate nickname + * will be changed. + * If SEC_PKCS12DecoderRenameCertNicknames() is called prior to calling + * SEC_PKCS12DecoderValidateBags(), then SEC_PKCS12DecoderValidateBags() + * will change the nickname of the corresponding private key, too. + */ +extern SECStatus +SEC_PKCS12DecoderRenameCertNicknames(SEC_PKCS12DecoderContext *p12dcx, + SEC_PKCS12NicknameRenameCallback nicknameCb, + void *arg); + +extern SECStatus +SEC_PKCS12DecoderImportBags(SEC_PKCS12DecoderContext *p12dcx); + +CERTCertList * +SEC_PKCS12DecoderGetCerts(SEC_PKCS12DecoderContext *p12dcx); + +SECStatus +SEC_PKCS12DecoderIterateInit(SEC_PKCS12DecoderContext *p12dcx); + +SECStatus +SEC_PKCS12DecoderIterateNext(SEC_PKCS12DecoderContext *p12dcx, + const SEC_PKCS12DecoderItem **ipp); + +SEC_END_PROTOS + +#endif diff --git a/security/nss/lib/pkcs12/p12creat.c b/security/nss/lib/pkcs12/p12creat.c new file mode 100644 index 0000000000..7cc72cf159 --- /dev/null +++ b/security/nss/lib/pkcs12/p12creat.c @@ -0,0 +1,218 @@ +/* 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 "pkcs12.h" +#include "secitem.h" +#include "secport.h" +#include "secder.h" +#include "secoid.h" +#include "p12local.h" +#include "secerr.h" + +/* allocate space for a PFX structure and set up initial + * arena pool. pfx structure is cleared and a pointer to + * the new structure is returned. + */ +SEC_PKCS12PFXItem * +sec_pkcs12_new_pfx(void) +{ + SEC_PKCS12PFXItem *pfx = NULL; + PLArenaPool *poolp = NULL; + + poolp = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE); /* XXX Different size? */ + if (poolp == NULL) + goto loser; + + pfx = (SEC_PKCS12PFXItem *)PORT_ArenaZAlloc(poolp, + sizeof(SEC_PKCS12PFXItem)); + if (pfx == NULL) + goto loser; + pfx->poolp = poolp; + + return pfx; + +loser: + PORT_FreeArena(poolp, PR_TRUE); + return NULL; +} + +/* allocate space for a PFX structure and set up initial + * arena pool. pfx structure is cleared and a pointer to + * the new structure is returned. + */ +SEC_PKCS12AuthenticatedSafe * +sec_pkcs12_new_asafe(PLArenaPool *poolp) +{ + SEC_PKCS12AuthenticatedSafe *asafe = NULL; + void *mark; + + mark = PORT_ArenaMark(poolp); + asafe = (SEC_PKCS12AuthenticatedSafe *)PORT_ArenaZAlloc(poolp, + sizeof(SEC_PKCS12AuthenticatedSafe)); + if (asafe == NULL) + goto loser; + asafe->poolp = poolp; + PORT_Memset(&asafe->old_baggage, 0, sizeof(SEC_PKCS12Baggage_OLD)); + + PORT_ArenaUnmark(poolp, mark); + return asafe; + +loser: + PORT_ArenaRelease(poolp, mark); + return NULL; +} + +/* create a safe contents structure with a list of + * length 0 with the first element being NULL + */ +SEC_PKCS12SafeContents * +sec_pkcs12_create_safe_contents(PLArenaPool *poolp) +{ + SEC_PKCS12SafeContents *safe; + void *mark; + + if (poolp == NULL) + return NULL; + + /* allocate structure */ + mark = PORT_ArenaMark(poolp); + safe = (SEC_PKCS12SafeContents *)PORT_ArenaZAlloc(poolp, + sizeof(SEC_PKCS12SafeContents)); + if (safe == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + PORT_ArenaRelease(poolp, mark); + return NULL; + } + + /* init list */ + safe->contents = (SEC_PKCS12SafeBag **)PORT_ArenaZAlloc(poolp, + sizeof(SEC_PKCS12SafeBag *)); + if (safe->contents == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + PORT_ArenaRelease(poolp, mark); + return NULL; + } + safe->contents[0] = NULL; + safe->poolp = poolp; + safe->safe_size = 0; + PORT_ArenaUnmark(poolp, mark); + return safe; +} + +/* create a new external bag which is appended onto the list + * of bags in baggage. the bag is created in the same arena + * as baggage + */ +SEC_PKCS12BaggageItem * +sec_pkcs12_create_external_bag(SEC_PKCS12Baggage *luggage) +{ + void *dummy, *mark; + SEC_PKCS12BaggageItem *bag; + + if (luggage == NULL) { + return NULL; + } + + mark = PORT_ArenaMark(luggage->poolp); + + /* allocate space for null terminated bag list */ + if (luggage->bags == NULL) { + luggage->bags = (SEC_PKCS12BaggageItem **)PORT_ArenaZAlloc(luggage->poolp, + sizeof(SEC_PKCS12BaggageItem *)); + if (luggage->bags == NULL) { + goto loser; + } + luggage->luggage_size = 0; + } + + /* grow the list */ + dummy = PORT_ArenaGrow(luggage->poolp, luggage->bags, + sizeof(SEC_PKCS12BaggageItem *) * (luggage->luggage_size + 1), + sizeof(SEC_PKCS12BaggageItem *) * (luggage->luggage_size + 2)); + if (dummy == NULL) { + goto loser; + } + luggage->bags = (SEC_PKCS12BaggageItem **)dummy; + + luggage->bags[luggage->luggage_size] = + (SEC_PKCS12BaggageItem *)PORT_ArenaZAlloc(luggage->poolp, + sizeof(SEC_PKCS12BaggageItem)); + if (luggage->bags[luggage->luggage_size] == NULL) { + goto loser; + } + + /* create new bag and append it to the end */ + bag = luggage->bags[luggage->luggage_size]; + bag->espvks = (SEC_PKCS12ESPVKItem **)PORT_ArenaZAlloc( + luggage->poolp, + sizeof(SEC_PKCS12ESPVKItem *)); + bag->unencSecrets = (SEC_PKCS12SafeBag **)PORT_ArenaZAlloc( + luggage->poolp, + sizeof(SEC_PKCS12SafeBag *)); + if ((bag->espvks == NULL) || (bag->unencSecrets == NULL)) { + goto loser; + } + + bag->poolp = luggage->poolp; + luggage->luggage_size++; + luggage->bags[luggage->luggage_size] = NULL; + bag->espvks[0] = NULL; + bag->unencSecrets[0] = NULL; + bag->nEspvks = bag->nSecrets = 0; + + PORT_ArenaUnmark(luggage->poolp, mark); + return bag; + +loser: + PORT_ArenaRelease(luggage->poolp, mark); + PORT_SetError(SEC_ERROR_NO_MEMORY); + return NULL; +} + +/* creates a baggage witha NULL terminated 0 length list */ +SEC_PKCS12Baggage * +sec_pkcs12_create_baggage(PLArenaPool *poolp) +{ + SEC_PKCS12Baggage *luggage; + void *mark; + + if (poolp == NULL) + return NULL; + + mark = PORT_ArenaMark(poolp); + + /* allocate bag */ + luggage = (SEC_PKCS12Baggage *)PORT_ArenaZAlloc(poolp, + sizeof(SEC_PKCS12Baggage)); + if (luggage == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + PORT_ArenaRelease(poolp, mark); + return NULL; + } + + /* init list */ + luggage->bags = (SEC_PKCS12BaggageItem **)PORT_ArenaZAlloc(poolp, + sizeof(SEC_PKCS12BaggageItem *)); + if (luggage->bags == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + PORT_ArenaRelease(poolp, mark); + return NULL; + } + + luggage->bags[0] = NULL; + luggage->luggage_size = 0; + luggage->poolp = poolp; + + PORT_ArenaUnmark(poolp, mark); + return luggage; +} + +/* free pfx structure and associated items in the arena */ +void +SEC_PKCS12DestroyPFX(SEC_PKCS12PFXItem *pfx) +{ + if (pfx != NULL && pfx->poolp != NULL) { + PORT_FreeArena(pfx->poolp, PR_TRUE); + } +} diff --git a/security/nss/lib/pkcs12/p12d.c b/security/nss/lib/pkcs12/p12d.c new file mode 100644 index 0000000000..615b123db0 --- /dev/null +++ b/security/nss/lib/pkcs12/p12d.c @@ -0,0 +1,3626 @@ +/* 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 "nss.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; + PRBool forceUnicode; + + /* 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 (p12dcx->forceUnicode) { + if (SECITEM_CopyItem(NULL, &pwitem, p12dcx->pwitem) != SECSuccess) { + PK11_FreeSlot(slot); + return NULL; + } + } else { + if (!sec_pkcs12_decode_password(NULL, &pwitem, algorithm, p12dcx->pwitem)) { + PK11_FreeSlot(slot); + 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; + + if (!safeContentsCtx || !safeContentsCtx->p12dcx || !safeContentsCtx->currentSafeBagA1Dcx) { + return; + } + p12dcx = safeContentsCtx->p12dcx; + + /* make sure that there are no errors and we are not skipping the current safeBag */ + if (p12dcx->error || safeContentsCtx->skipCurrentSafeBag) { + goto loser; + } + + rv = SEC_ASN1DecoderUpdate(safeContentsCtx->currentSafeBagA1Dcx, data, len); + if (rv != SECSuccess) { + p12dcx->errorValue = PORT_GetError(); + p12dcx->error = PR_TRUE; + goto loser; + } + + /* The update may have set safeContentsCtx->skipCurrentSafeBag, and we + * may not get another opportunity to clean up the decoder context. + */ + if (safeContentsCtx->skipCurrentSafeBag) { + goto loser; + } + + return; + +loser: + /* 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. + */ + 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_ASN1DecoderClearFilterProc(p12dcx->aSafeA1Dcx); + 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; + PRInt32 forceUnicode = PR_FALSE; + SECStatus rv; + + 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 + rv = NSS_OptionGet(__NSS_PKCS12_DECODE_FORCE_UNICODE, &forceUnicode); + if (rv != SECSuccess) { + goto loser; + } + p12dcx->forceUnicode = forceUnicode; + 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); + integrityMech = sec_pkcs12_algtag_to_keygen_mech(algtag); + if (integrityMech == CKM_INVALID_MECHANISM) { + 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, PRBool forceUnicode, 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 (forceUnicode) { + if (SECITEM_CopyItem(NULL, &pwitem, key->pwitem) != SECSuccess) { + key->error = SEC_ERROR_PKCS12_UNABLE_TO_IMPORT_KEY; + key->problem = PR_TRUE; + return SECFailure; + } + } else { + if (!sec_pkcs12_decode_password(NULL, &pwitem, algorithm, + key->pwitem)) { + key->error = SEC_ERROR_PKCS12_UNABLE_TO_IMPORT_KEY; + key->problem = PR_TRUE; + 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, probCnt, 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; + } + + probCnt = 0; + i = 0; + while (p12dcx->safeBags[i]) { + if (p12dcx->safeBags[i]->problem) { + probCnt++; + errorVal = p12dcx->safeBags[i]->error; + } + i++; + } + + 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, PRBool forceUnicode, + 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, + forceUnicode, 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) +{ + PRBool forceUnicode = PR_FALSE; + SECStatus rv; + + if (!p12dcx || p12dcx->error) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (!p12dcx->bagsVerified) { + return SECFailure; + } + + /* We need to check the option here as well as in + * SEC_PKCS12DecoderStart, because different PBE's could be used + * for PKCS #7 and PKCS #8 */ + rv = NSS_OptionGet(__NSS_PKCS12_DECODE_FORCE_UNICODE, &forceUnicode); + if (rv != SECSuccess) { + return SECFailure; + } + + return sec_pkcs12_install_bags(p12dcx->safeBags, forceUnicode, + 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; +} diff --git a/security/nss/lib/pkcs12/p12dec.c b/security/nss/lib/pkcs12/p12dec.c new file mode 100644 index 0000000000..5a94392631 --- /dev/null +++ b/security/nss/lib/pkcs12/p12dec.c @@ -0,0 +1,670 @@ +/* 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 "pkcs12.h" +#include "plarena.h" +#include "secpkcs7.h" +#include "p12local.h" +#include "secoid.h" +#include "secitem.h" +#include "secport.h" +#include "secasn1.h" +#include "secder.h" +#include "secerr.h" +#include "cert.h" +#include "certdb.h" +#include "p12plcy.h" +#include "p12.h" +#include "secpkcs5.h" + +/* PFX extraction and validation routines */ + +/* decode the DER encoded PFX item. if unable to decode, check to see if it + * is an older PFX item. If that fails, assume the file was not a valid + * pfx file. + * the returned pfx structure should be destroyed using SEC_PKCS12DestroyPFX + */ +static SEC_PKCS12PFXItem * +sec_pkcs12_decode_pfx(SECItem *der_pfx) +{ + SEC_PKCS12PFXItem *pfx; + SECStatus rv; + + if (der_pfx == NULL) { + return NULL; + } + + /* allocate the space for a new PFX item */ + pfx = sec_pkcs12_new_pfx(); + if (pfx == NULL) { + return NULL; + } + + rv = SEC_ASN1DecodeItem(pfx->poolp, pfx, SEC_PKCS12PFXItemTemplate, + der_pfx); + + /* if a failure occurred, check for older version... + * we also get rid of the old pfx structure, because we don't + * know where it failed and what data in may contain + */ + if (rv != SECSuccess) { + SEC_PKCS12DestroyPFX(pfx); + pfx = sec_pkcs12_new_pfx(); + if (pfx == NULL) { + return NULL; + } + rv = SEC_ASN1DecodeItem(pfx->poolp, pfx, SEC_PKCS12PFXItemTemplate_OLD, + der_pfx); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_PKCS12_DECODING_PFX); + PORT_FreeArena(pfx->poolp, PR_TRUE); + return NULL; + } + pfx->old = PR_TRUE; + rv = SGN_CopyDigestInfo(pfx->poolp, &pfx->macData.safeMac, &pfx->old_safeMac); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + PORT_FreeArena(pfx->poolp, PR_TRUE); + return NULL; + } + rv = SECITEM_CopyItem(pfx->poolp, &pfx->macData.macSalt, &pfx->old_macSalt); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + PORT_FreeArena(pfx->poolp, PR_TRUE); + return NULL; + } + } else { + pfx->old = PR_FALSE; + } + + /* convert bit string from bits to bytes */ + pfx->macData.macSalt.len /= 8; + + return pfx; +} + +/* validate the integrity MAC used in the PFX. The MAC is generated + * per the PKCS 12 document. If the MAC is incorrect, it is most likely + * due to an invalid password. + * pwitem is the integrity password + * pfx is the decoded pfx item + */ +static PRBool +sec_pkcs12_check_pfx_mac(SEC_PKCS12PFXItem *pfx, + SECItem *pwitem) +{ + SECItem *key = NULL, *mac = NULL, *data = NULL; + SECItem *vpwd = NULL; + SECOidTag algorithm; + PRBool ret = PR_FALSE; + + if (pfx == NULL) { + return PR_FALSE; + } + + algorithm = SECOID_GetAlgorithmTag(&pfx->macData.safeMac.digestAlgorithm); + switch (algorithm) { + /* only SHA1 hashing supported as a MACing algorithm */ + case SEC_OID_SHA1: + if (pfx->old == PR_FALSE) { + pfx->swapUnicode = PR_FALSE; + } + + recheckUnicodePassword: + vpwd = sec_pkcs12_create_virtual_password(pwitem, + &pfx->macData.macSalt, + pfx->swapUnicode); + if (vpwd == NULL) { + return PR_FALSE; + } + + key = sec_pkcs12_generate_key_from_password(algorithm, + &pfx->macData.macSalt, + (pfx->old ? pwitem : vpwd)); + /* free vpwd only for newer PFX */ + if (vpwd) { + SECITEM_ZfreeItem(vpwd, PR_TRUE); + } + if (key == NULL) { + return PR_FALSE; + } + + data = SEC_PKCS7GetContent(&pfx->authSafe); + if (data == NULL) { + break; + } + + /* check MAC */ + mac = sec_pkcs12_generate_mac(key, data, pfx->old); + ret = PR_TRUE; + if (mac) { + SECItem *safeMac = &pfx->macData.safeMac.digest; + if (SECITEM_CompareItem(mac, safeMac) != SECEqual) { + + /* if we encounter an invalid mac, lets invert the + * password in case of unicode changes + */ + if (((!pfx->old) && pfx->swapUnicode) || (pfx->old)) { + PORT_SetError(SEC_ERROR_PKCS12_INVALID_MAC); + ret = PR_FALSE; + } else { + SECITEM_ZfreeItem(mac, PR_TRUE); + pfx->swapUnicode = PR_TRUE; + goto recheckUnicodePassword; + } + } + SECITEM_ZfreeItem(mac, PR_TRUE); + } else { + ret = PR_FALSE; + } + break; + default: + PORT_SetError(SEC_ERROR_PKCS12_UNSUPPORTED_MAC_ALGORITHM); + ret = PR_FALSE; + break; + } + + /* let success fall through */ + if (key != NULL) + SECITEM_ZfreeItem(key, PR_TRUE); + + return ret; +} + +/* check the validity of the pfx structure. we currently only support + * password integrity mode, so we check the MAC. + */ +static PRBool +sec_pkcs12_validate_pfx(SEC_PKCS12PFXItem *pfx, + SECItem *pwitem) +{ + SECOidTag contentType; + + contentType = SEC_PKCS7ContentType(&pfx->authSafe); + switch (contentType) { + case SEC_OID_PKCS7_DATA: + return sec_pkcs12_check_pfx_mac(pfx, pwitem); + break; + case SEC_OID_PKCS7_SIGNED_DATA: + default: + PORT_SetError(SEC_ERROR_PKCS12_UNSUPPORTED_TRANSPORT_MODE); + break; + } + + return PR_FALSE; +} + +/* decode and return the valid PFX. if the PFX item is not valid, + * NULL is returned. + */ +static SEC_PKCS12PFXItem * +sec_pkcs12_get_pfx(SECItem *pfx_data, + SECItem *pwitem) +{ + SEC_PKCS12PFXItem *pfx; + PRBool valid_pfx; + + if ((pfx_data == NULL) || (pwitem == NULL)) { + return NULL; + } + + pfx = sec_pkcs12_decode_pfx(pfx_data); + if (pfx == NULL) { + return NULL; + } + + valid_pfx = sec_pkcs12_validate_pfx(pfx, pwitem); + if (valid_pfx != PR_TRUE) { + SEC_PKCS12DestroyPFX(pfx); + pfx = NULL; + } + + return pfx; +} + +/* authenticated safe decoding, validation, and access routines + */ + +/* convert dogbert beta 3 authenticated safe structure to a post + * beta three structure, so that we don't have to change more routines. + */ +static SECStatus +sec_pkcs12_convert_old_auth_safe(SEC_PKCS12AuthenticatedSafe *asafe) +{ + SEC_PKCS12Baggage *baggage; + SEC_PKCS12BaggageItem *bag; + SECStatus rv = SECSuccess; + + if (asafe->old_baggage.espvks == NULL) { + /* XXX should the ASN1 engine produce a single NULL element list + * rather than setting the pointer to NULL? + * There is no need to return an error -- assume that the list + * was empty. + */ + return SECSuccess; + } + + baggage = sec_pkcs12_create_baggage(asafe->poolp); + if (!baggage) { + return SECFailure; + } + bag = sec_pkcs12_create_external_bag(baggage); + if (!bag) { + return SECFailure; + } + + PORT_Memcpy(&asafe->baggage, baggage, sizeof(SEC_PKCS12Baggage)); + + /* if there are shrouded keys, append them to the bag */ + rv = SECSuccess; + if (asafe->old_baggage.espvks[0] != NULL) { + int nEspvk = 0; + rv = SECSuccess; + while ((asafe->old_baggage.espvks[nEspvk] != NULL) && + (rv == SECSuccess)) { + rv = sec_pkcs12_append_shrouded_key(bag, + asafe->old_baggage.espvks[nEspvk]); + nEspvk++; + } + } + + return rv; +} + +/* decodes the authenticated safe item. a return of NULL indicates + * an error. however, the error will have occurred either in memory + * allocation or in decoding the authenticated safe. + * + * if an old PFX item has been found, we want to convert the + * old authenticated safe to the new one. + */ +static SEC_PKCS12AuthenticatedSafe * +sec_pkcs12_decode_authenticated_safe(SEC_PKCS12PFXItem *pfx) +{ + SECItem *der_asafe = NULL; + SEC_PKCS12AuthenticatedSafe *asafe = NULL; + SECStatus rv; + + if (pfx == NULL) { + return NULL; + } + + der_asafe = SEC_PKCS7GetContent(&pfx->authSafe); + if (der_asafe == NULL) { + /* XXX set error ? */ + goto loser; + } + + asafe = sec_pkcs12_new_asafe(pfx->poolp); + if (asafe == NULL) { + goto loser; + } + + if (pfx->old == PR_FALSE) { + rv = SEC_ASN1DecodeItem(pfx->poolp, asafe, + SEC_PKCS12AuthenticatedSafeTemplate, + der_asafe); + asafe->old = PR_FALSE; + asafe->swapUnicode = pfx->swapUnicode; + } else { + /* handle beta exported files */ + rv = SEC_ASN1DecodeItem(pfx->poolp, asafe, + SEC_PKCS12AuthenticatedSafeTemplate_OLD, + der_asafe); + asafe->safe = &(asafe->old_safe); + rv = sec_pkcs12_convert_old_auth_safe(asafe); + asafe->old = PR_TRUE; + } + + if (rv != SECSuccess) { + goto loser; + } + + asafe->poolp = pfx->poolp; + + return asafe; + +loser: + return NULL; +} + +/* validates the safe within the authenticated safe item. + * in order to be valid: + * 1. the privacy salt must be present + * 2. the encryption algorithm must be supported (including + * export policy) + * PR_FALSE indicates an error, PR_TRUE indicates a valid safe + */ +static PRBool +sec_pkcs12_validate_encrypted_safe(SEC_PKCS12AuthenticatedSafe *asafe) +{ + PRBool valid = PR_FALSE; + SECAlgorithmID *algid; + + if (asafe == NULL) { + return PR_FALSE; + } + + /* if mode is password privacy, then privacySalt is assumed + * to be non-zero. + */ + if (asafe->privacySalt.len != 0) { + valid = PR_TRUE; + asafe->privacySalt.len /= 8; + } else { + PORT_SetError(SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE); + return PR_FALSE; + } + + /* until spec changes, content will have between 2 and 8 bytes depending + * upon the algorithm used if certs are unencrypted... + * also want to support case where content is empty -- which we produce + */ + if (SEC_PKCS7IsContentEmpty(asafe->safe, 8) == PR_TRUE) { + asafe->emptySafe = PR_TRUE; + return PR_TRUE; + } + + asafe->emptySafe = PR_FALSE; + + /* make sure that a pbe algorithm is being used */ + algid = SEC_PKCS7GetEncryptionAlgorithm(asafe->safe); + if (algid != NULL) { + if (SEC_PKCS5IsAlgorithmPBEAlg(algid)) { + valid = SEC_PKCS12DecryptionAllowed(algid); + + if (valid == PR_FALSE) { + PORT_SetError(SEC_ERROR_BAD_EXPORT_ALGORITHM); + } + } else { + PORT_SetError(SEC_ERROR_PKCS12_UNSUPPORTED_PBE_ALGORITHM); + valid = PR_FALSE; + } + } else { + valid = PR_FALSE; + PORT_SetError(SEC_ERROR_PKCS12_UNSUPPORTED_PBE_ALGORITHM); + } + + return valid; +} + +/* validates authenticates safe: + * 1. checks that the version is supported + * 2. checks that only password privacy mode is used (currently) + * 3. further, makes sure safe has appropriate policies per above function + * PR_FALSE indicates failure. + */ +static PRBool +sec_pkcs12_validate_auth_safe(SEC_PKCS12AuthenticatedSafe *asafe) +{ + PRBool valid = PR_TRUE; + SECOidTag safe_type; + int version; + + if (asafe == NULL) { + return PR_FALSE; + } + + /* check version, since it is default it may not be present. + * therefore, assume ok + */ + if ((asafe->version.len > 0) && (asafe->old == PR_FALSE)) { + version = DER_GetInteger(&asafe->version); + if (version > SEC_PKCS12_PFX_VERSION) { + PORT_SetError(SEC_ERROR_PKCS12_UNSUPPORTED_VERSION); + return PR_FALSE; + } + } + + /* validate password mode is being used */ + safe_type = SEC_PKCS7ContentType(asafe->safe); + switch (safe_type) { + case SEC_OID_PKCS7_ENCRYPTED_DATA: + valid = sec_pkcs12_validate_encrypted_safe(asafe); + break; + case SEC_OID_PKCS7_ENVELOPED_DATA: + default: + PORT_SetError(SEC_ERROR_PKCS12_UNSUPPORTED_TRANSPORT_MODE); + valid = PR_FALSE; + break; + } + + return valid; +} + +/* retrieves the authenticated safe item from the PFX item + * before returning the authenticated safe, the validity of the + * authenticated safe is checked and if valid, returned. + * a return of NULL indicates that an error occurred. + */ +static SEC_PKCS12AuthenticatedSafe * +sec_pkcs12_get_auth_safe(SEC_PKCS12PFXItem *pfx) +{ + SEC_PKCS12AuthenticatedSafe *asafe; + PRBool valid_safe; + + if (pfx == NULL) { + return NULL; + } + + asafe = sec_pkcs12_decode_authenticated_safe(pfx); + if (asafe == NULL) { + return NULL; + } + + valid_safe = sec_pkcs12_validate_auth_safe(asafe); + if (valid_safe != PR_TRUE) { + asafe = NULL; + } else if (asafe) { + asafe->baggage.poolp = asafe->poolp; + } + + return asafe; +} + +/* decrypts the authenticated safe. + * a return of anything but SECSuccess indicates an error. the + * password is not known to be valid until the call to the + * function sec_pkcs12_get_safe_contents. If decoding the safe + * fails, it is assumed the password was incorrect and the error + * is set then. any failure here is assumed to be due to + * internal problems in SEC_PKCS7DecryptContents or below. + */ +static SECStatus +sec_pkcs12_decrypt_auth_safe(SEC_PKCS12AuthenticatedSafe *asafe, + SECItem *pwitem, + void *wincx) +{ + SECStatus rv = SECFailure; + SECItem *vpwd = NULL; + + if ((asafe == NULL) || (pwitem == NULL)) { + return SECFailure; + } + + if (asafe->old == PR_FALSE) { + vpwd = sec_pkcs12_create_virtual_password(pwitem, &asafe->privacySalt, + asafe->swapUnicode); + if (vpwd == NULL) { + return SECFailure; + } + } + + rv = SEC_PKCS7DecryptContents(asafe->poolp, asafe->safe, + (asafe->old ? pwitem : vpwd), wincx); + + if (asafe->old == PR_FALSE) { + SECITEM_ZfreeItem(vpwd, PR_TRUE); + } + + return rv; +} + +/* extract the safe from the authenticated safe. + * if we are unable to decode the safe, then it is likely that the + * safe has not been decrypted or the password used to decrypt + * the safe was invalid. we assume that the password was invalid and + * set an error accordingly. + * a return of NULL indicates that an error occurred. + */ +static SEC_PKCS12SafeContents * +sec_pkcs12_get_safe_contents(SEC_PKCS12AuthenticatedSafe *asafe) +{ + SECItem *src = NULL; + SEC_PKCS12SafeContents *safe = NULL; + SECStatus rv = SECFailure; + + if (asafe == NULL) { + return NULL; + } + + safe = (SEC_PKCS12SafeContents *)PORT_ArenaZAlloc(asafe->poolp, + sizeof(SEC_PKCS12SafeContents)); + if (safe == NULL) { + return NULL; + } + safe->poolp = asafe->poolp; + safe->old = asafe->old; + safe->swapUnicode = asafe->swapUnicode; + + src = SEC_PKCS7GetContent(asafe->safe); + if (src != NULL) { + const SEC_ASN1Template *theTemplate; + if (asafe->old != PR_TRUE) { + theTemplate = SEC_PKCS12SafeContentsTemplate; + } else { + theTemplate = SEC_PKCS12SafeContentsTemplate_OLD; + } + + rv = SEC_ASN1DecodeItem(asafe->poolp, safe, theTemplate, src); + + /* if we could not decode the item, password was probably invalid */ + if (rv != SECSuccess) { + safe = NULL; + PORT_SetError(SEC_ERROR_PKCS12_PRIVACY_PASSWORD_INCORRECT); + } + } else { + PORT_SetError(SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE); + rv = SECFailure; + } + + return safe; +} + +/* import PFX item + * der_pfx is the der encoded pfx structure + * pbef and pbearg are the integrity/encryption password call back + * ncCall is the nickname collision calllback + * slot is the destination token + * wincx window handler + * + * on error, error code set and SECFailure returned + */ +SECStatus +SEC_PKCS12PutPFX(SECItem *der_pfx, SECItem *pwitem, + SEC_PKCS12NicknameCollisionCallback ncCall, + PK11SlotInfo *slot, + void *wincx) +{ + SEC_PKCS12PFXItem *pfx; + SEC_PKCS12AuthenticatedSafe *asafe; + SEC_PKCS12SafeContents *safe_contents = NULL; + SECStatus rv; + + if (!der_pfx || !pwitem || !slot) { + return SECFailure; + } + + /* decode and validate each section */ + rv = SECFailure; + + pfx = sec_pkcs12_get_pfx(der_pfx, pwitem); + if (pfx != NULL) { + asafe = sec_pkcs12_get_auth_safe(pfx); + if (asafe != NULL) { + + /* decrypt safe -- only if not empty */ + if (asafe->emptySafe != PR_TRUE) { + rv = sec_pkcs12_decrypt_auth_safe(asafe, pwitem, wincx); + if (rv == SECSuccess) { + safe_contents = sec_pkcs12_get_safe_contents(asafe); + if (safe_contents == NULL) { + rv = SECFailure; + } + } + } else { + safe_contents = sec_pkcs12_create_safe_contents(asafe->poolp); + if (safe_contents == NULL) { + rv = SECFailure; + } else { + safe_contents->swapUnicode = pfx->swapUnicode; + rv = SECSuccess; + } + } + + /* get safe contents and begin import */ + if (rv == SECSuccess) { + SEC_PKCS12DecoderContext *p12dcx; + + p12dcx = sec_PKCS12ConvertOldSafeToNew(pfx->poolp, slot, + pfx->swapUnicode, + pwitem, wincx, safe_contents, + &asafe->baggage); + if (!p12dcx) { + rv = SECFailure; + goto loser; + } + + if (SEC_PKCS12DecoderValidateBags(p12dcx, ncCall) != SECSuccess) { + rv = SECFailure; + goto loser; + } + + rv = SEC_PKCS12DecoderImportBags(p12dcx); + } + } + } + +loser: + + if (pfx) { + SEC_PKCS12DestroyPFX(pfx); + } + + return rv; +} + +PRBool +SEC_PKCS12ValidData(char *buf, int bufLen, long int totalLength) +{ + int lengthLength; + + PRBool valid = PR_FALSE; + + if (buf == NULL) { + return PR_FALSE; + } + + /* check for constructed sequence identifier tag */ + if (*buf == (SEC_ASN1_CONSTRUCTED | SEC_ASN1_SEQUENCE)) { + totalLength--; /* header byte taken care of */ + buf++; + + lengthLength = (long int)SEC_ASN1LengthLength(totalLength - 1); + if (totalLength > 0x7f) { + lengthLength--; + *buf &= 0x7f; /* remove bit 8 indicator */ + if ((*buf - (char)lengthLength) == 0) { + valid = PR_TRUE; + } + } else { + lengthLength--; + if ((*buf - (char)lengthLength) == 0) { + valid = PR_TRUE; + } + } + } + + return valid; +} diff --git a/security/nss/lib/pkcs12/p12e.c b/security/nss/lib/pkcs12/p12e.c new file mode 100644 index 0000000000..2b8654698b --- /dev/null +++ b/security/nss/lib/pkcs12/p12e.c @@ -0,0 +1,2087 @@ +/* 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 "p12t.h" +#include "p12.h" +#include "plarena.h" +#include "secitem.h" +#include "secoid.h" +#include "seccomon.h" +#include "secport.h" +#include "cert.h" +#include "secpkcs5.h" +#include "secpkcs7.h" +#include "secasn1.h" +#include "secerr.h" +#include "sechash.h" +#include "pk11func.h" +#include "p12plcy.h" +#include "p12local.h" +#include "prcpucfg.h" + +extern const int NSS_PBE_DEFAULT_ITERATION_COUNT; /* defined in p7create.c */ + +/* +** This PKCS12 file encoder uses numerous nested ASN.1 and PKCS7 encoder +** contexts. It can be difficult to keep straight. Here's a picture: +** +** "outer" ASN.1 encoder. The output goes to the library caller's CB. +** "middle" PKCS7 encoder. Feeds the "outer" ASN.1 encoder. +** "middle" ASN1 encoder. Encodes the encrypted aSafes. +** Feeds the "middle" P7 encoder above. +** "inner" PKCS7 encoder. Encrypts the "authenticated Safes" (aSafes) +** Feeds the "middle" ASN.1 encoder above. +** "inner" ASN.1 encoder. Encodes the unencrypted aSafes. +** Feeds the "inner" P7 enocder above. +** +** Buffering has been added at each point where the output of an ASN.1 +** encoder feeds the input of a PKCS7 encoder. +*/ + +/********************************* + * Output buffer object, used to buffer output from ASN.1 encoder + * before passing data on down to the next PKCS7 encoder. + *********************************/ + +#define PK12_OUTPUT_BUFFER_SIZE 8192 + +struct sec_pkcs12OutputBufferStr { + SEC_PKCS7EncoderContext *p7eCx; + PK11Context *hmacCx; + unsigned int numBytes; + unsigned int bufBytes; + char buf[PK12_OUTPUT_BUFFER_SIZE]; +}; +typedef struct sec_pkcs12OutputBufferStr sec_pkcs12OutputBuffer; + +/********************************* + * Structures used in exporting the PKCS 12 blob + *********************************/ + +/* A SafeInfo is used for each ContentInfo which makes up the + * sequence of safes in the AuthenticatedSafe portion of the + * PFX structure. + */ +struct SEC_PKCS12SafeInfoStr { + PLArenaPool *arena; + + /* information for setting up password encryption */ + SECItem pwitem; + SECOidTag algorithm; + PK11SymKey *encryptionKey; + + /* how many items have been stored in this safe, + * we will skip any safe which does not contain any + * items + */ + unsigned int itemCount; + + /* the content info for the safe */ + SEC_PKCS7ContentInfo *cinfo; + + sec_PKCS12SafeContents *safe; +}; + +/* An opaque structure which contains information needed for exporting + * certificates and keys through PKCS 12. + */ +struct SEC_PKCS12ExportContextStr { + PLArenaPool *arena; + PK11SlotInfo *slot; + void *wincx; + + /* integrity information */ + PRBool integrityEnabled; + PRBool pwdIntegrity; + union { + struct sec_PKCS12PasswordModeInfo pwdInfo; + struct sec_PKCS12PublicKeyModeInfo pubkeyInfo; + } integrityInfo; + + /* helper functions */ + /* retrieve the password call back */ + SECKEYGetPasswordKey pwfn; + void *pwfnarg; + + /* safe contents bags */ + SEC_PKCS12SafeInfo **safeInfos; + unsigned int safeInfoCount; + + /* the sequence of safes */ + sec_PKCS12AuthenticatedSafe authSafe; + + /* information needing deletion */ + CERTCertificate **certList; +}; + +/* structures for passing information to encoder callbacks when processing + * data through the ASN1 engine. + */ +struct sec_pkcs12_encoder_output { + SEC_PKCS12EncoderOutputCallback outputfn; + void *outputarg; +}; + +struct sec_pkcs12_hmac_and_output_info { + void *arg; + struct sec_pkcs12_encoder_output output; +}; + +/* An encoder context which is used for the actual encoding + * portion of PKCS 12. + */ +typedef struct sec_PKCS12EncoderContextStr { + PLArenaPool *arena; + SEC_PKCS12ExportContext *p12exp; + + /* encoder information - this is set up based on whether + * password based or public key pased privacy is being used + */ + SEC_ASN1EncoderContext *outerA1ecx; + union { + struct sec_pkcs12_hmac_and_output_info hmacAndOutputInfo; + struct sec_pkcs12_encoder_output encOutput; + } output; + + /* structures for encoding of PFX and MAC */ + sec_PKCS12PFXItem pfx; + sec_PKCS12MacData mac; + + /* authenticated safe encoding tracking information */ + SEC_PKCS7ContentInfo *aSafeCinfo; + SEC_PKCS7EncoderContext *middleP7ecx; + SEC_ASN1EncoderContext *middleA1ecx; + unsigned int currentSafe; + + /* hmac context */ + PK11Context *hmacCx; + + /* output buffers */ + sec_pkcs12OutputBuffer middleBuf; + sec_pkcs12OutputBuffer innerBuf; + +} sec_PKCS12EncoderContext; + +/********************************* + * Export setup routines + *********************************/ + +/* SEC_PKCS12CreateExportContext + * Creates an export context and sets the unicode and password retrieval + * callbacks. This is the first call which must be made when exporting + * a PKCS 12 blob. + * + * pwfn, pwfnarg - password retrieval callback and argument. these are + * required for password-authentication mode. + */ +SEC_PKCS12ExportContext * +SEC_PKCS12CreateExportContext(SECKEYGetPasswordKey pwfn, void *pwfnarg, + PK11SlotInfo *slot, void *wincx) +{ + PLArenaPool *arena = NULL; + SEC_PKCS12ExportContext *p12ctxt = NULL; + + /* allocate the arena and create the context */ + arena = PORT_NewArena(4096); + if (!arena) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return NULL; + } + + p12ctxt = (SEC_PKCS12ExportContext *)PORT_ArenaZAlloc(arena, + sizeof(SEC_PKCS12ExportContext)); + if (!p12ctxt) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + /* password callback for key retrieval */ + p12ctxt->pwfn = pwfn; + p12ctxt->pwfnarg = pwfnarg; + + p12ctxt->integrityEnabled = PR_FALSE; + p12ctxt->arena = arena; + p12ctxt->wincx = wincx; + p12ctxt->slot = (slot) ? PK11_ReferenceSlot(slot) : PK11_GetInternalSlot(); + + return p12ctxt; + +loser: + if (arena) { + PORT_FreeArena(arena, PR_TRUE); + } + + return NULL; +} + +/* + * Adding integrity mode + */ + +/* SEC_PKCS12AddPasswordIntegrity + * Add password integrity to the exported data. If an integrity method + * has already been set, then return an error. + * + * p12ctxt - the export context + * pwitem - the password for integrity mode + * integAlg - the integrity algorithm to use for authentication. + */ +SECStatus +SEC_PKCS12AddPasswordIntegrity(SEC_PKCS12ExportContext *p12ctxt, + SECItem *pwitem, SECOidTag integAlg) +{ + if (!p12ctxt || p12ctxt->integrityEnabled) { + return SECFailure; + } + + /* set up integrity information */ + p12ctxt->pwdIntegrity = PR_TRUE; + p12ctxt->integrityInfo.pwdInfo.password = + (SECItem *)PORT_ArenaZAlloc(p12ctxt->arena, sizeof(SECItem)); + if (!p12ctxt->integrityInfo.pwdInfo.password) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return SECFailure; + } + if (SECITEM_CopyItem(p12ctxt->arena, + p12ctxt->integrityInfo.pwdInfo.password, pwitem) != SECSuccess) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return SECFailure; + } + p12ctxt->integrityInfo.pwdInfo.algorithm = integAlg; + p12ctxt->integrityEnabled = PR_TRUE; + + return SECSuccess; +} + +/* SEC_PKCS12AddPublicKeyIntegrity + * Add public key integrity to the exported data. If an integrity method + * has already been set, then return an error. The certificate must be + * allowed to be used as a signing cert. + * + * p12ctxt - the export context + * cert - signer certificate + * certDb - the certificate database + * algorithm - signing algorithm + * keySize - size of the signing key (?) + */ +SECStatus +SEC_PKCS12AddPublicKeyIntegrity(SEC_PKCS12ExportContext *p12ctxt, + CERTCertificate *cert, CERTCertDBHandle *certDb, + SECOidTag algorithm, int keySize) +{ + if (!p12ctxt) { + return SECFailure; + } + + p12ctxt->integrityInfo.pubkeyInfo.cert = cert; + p12ctxt->integrityInfo.pubkeyInfo.certDb = certDb; + p12ctxt->integrityInfo.pubkeyInfo.algorithm = algorithm; + p12ctxt->integrityInfo.pubkeyInfo.keySize = keySize; + p12ctxt->integrityEnabled = PR_TRUE; + + return SECSuccess; +} + +/* + * Adding safes - encrypted (password/public key) or unencrypted + * Each of the safe creation routines return an opaque pointer which + * are later passed into the routines for exporting certificates and + * keys. + */ + +/* append the newly created safeInfo to list of safeInfos in the export + * context. + */ +static SECStatus +sec_pkcs12_append_safe_info(SEC_PKCS12ExportContext *p12ctxt, SEC_PKCS12SafeInfo *info) +{ + void *mark = NULL, *dummy1 = NULL, *dummy2 = NULL; + + if (!p12ctxt || !info) { + return SECFailure; + } + + mark = PORT_ArenaMark(p12ctxt->arena); + + /* if no safeInfos have been set, create the list, otherwise expand it. */ + if (!p12ctxt->safeInfoCount) { + p12ctxt->safeInfos = (SEC_PKCS12SafeInfo **)PORT_ArenaZAlloc(p12ctxt->arena, + 2 * sizeof(SEC_PKCS12SafeInfo *)); + dummy1 = p12ctxt->safeInfos; + p12ctxt->authSafe.encodedSafes = (SECItem **)PORT_ArenaZAlloc(p12ctxt->arena, + 2 * sizeof(SECItem *)); + dummy2 = p12ctxt->authSafe.encodedSafes; + } else { + dummy1 = PORT_ArenaGrow(p12ctxt->arena, p12ctxt->safeInfos, + (p12ctxt->safeInfoCount + 1) * sizeof(SEC_PKCS12SafeInfo *), + (p12ctxt->safeInfoCount + 2) * sizeof(SEC_PKCS12SafeInfo *)); + p12ctxt->safeInfos = (SEC_PKCS12SafeInfo **)dummy1; + dummy2 = PORT_ArenaGrow(p12ctxt->arena, p12ctxt->authSafe.encodedSafes, + (p12ctxt->authSafe.safeCount + 1) * sizeof(SECItem *), + (p12ctxt->authSafe.safeCount + 2) * sizeof(SECItem *)); + p12ctxt->authSafe.encodedSafes = (SECItem **)dummy2; + } + if (!dummy1 || !dummy2) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + /* append the new safeInfo and null terminate the list */ + p12ctxt->safeInfos[p12ctxt->safeInfoCount] = info; + p12ctxt->safeInfos[++p12ctxt->safeInfoCount] = NULL; + p12ctxt->authSafe.encodedSafes[p12ctxt->authSafe.safeCount] = + (SECItem *)PORT_ArenaZAlloc(p12ctxt->arena, sizeof(SECItem)); + if (!p12ctxt->authSafe.encodedSafes[p12ctxt->authSafe.safeCount]) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + p12ctxt->authSafe.encodedSafes[++p12ctxt->authSafe.safeCount] = NULL; + + PORT_ArenaUnmark(p12ctxt->arena, mark); + return SECSuccess; + +loser: + PORT_ArenaRelease(p12ctxt->arena, mark); + return SECFailure; +} + +/* SEC_PKCS12CreatePasswordPrivSafe + * Create a password privacy safe to store exported information in. + * + * p12ctxt - export context + * pwitem - password for encryption + * privAlg - pbe algorithm through which encryption is done. + */ +SEC_PKCS12SafeInfo * +SEC_PKCS12CreatePasswordPrivSafe(SEC_PKCS12ExportContext *p12ctxt, + SECItem *pwitem, SECOidTag privAlg) +{ + SEC_PKCS12SafeInfo *safeInfo = NULL; + void *mark = NULL; + PK11SlotInfo *slot = NULL; + SECAlgorithmID *algId; + SECItem uniPwitem = { siBuffer, NULL, 0 }; + + if (!p12ctxt) { + return NULL; + } + + /* allocate the safe info */ + mark = PORT_ArenaMark(p12ctxt->arena); + safeInfo = (SEC_PKCS12SafeInfo *)PORT_ArenaZAlloc(p12ctxt->arena, + sizeof(SEC_PKCS12SafeInfo)); + if (!safeInfo) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + PORT_ArenaRelease(p12ctxt->arena, mark); + return NULL; + } + + safeInfo->itemCount = 0; + + /* create the encrypted safe */ + if (!SEC_PKCS5IsAlgorithmPBEAlgTag(privAlg)) { + SECOidTag prfAlg = SEC_OID_UNKNOWN; + /* if we have password integrity set, use that to set the integrity + * hash algorithm to set our password PRF. If we haven't set it, just + * let the low level code pick it */ + if (p12ctxt->integrityEnabled && p12ctxt->pwdIntegrity) { + prfAlg = HASH_GetHMACOidTagByHashOidTag( + p12ctxt->integrityInfo.pwdInfo.algorithm); + } + safeInfo->cinfo = SEC_PKCS7CreateEncryptedDataWithPBEV2(SEC_OID_PKCS5_PBES2, + privAlg, + prfAlg, + 0, + p12ctxt->pwfn, + p12ctxt->pwfnarg); + } else { + safeInfo->cinfo = SEC_PKCS7CreateEncryptedData(privAlg, 0, p12ctxt->pwfn, + p12ctxt->pwfnarg); + } + if (!safeInfo->cinfo) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + safeInfo->arena = p12ctxt->arena; + + if (!sec_pkcs12_encode_password(NULL, &uniPwitem, privAlg, pwitem)) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + if (SECITEM_CopyItem(p12ctxt->arena, &safeInfo->pwitem, &uniPwitem) != SECSuccess) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + /* generate the encryption key */ + slot = PK11_ReferenceSlot(p12ctxt->slot); + if (!slot) { + slot = PK11_GetInternalKeySlot(); + if (!slot) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + } + + algId = SEC_PKCS7GetEncryptionAlgorithm(safeInfo->cinfo); + safeInfo->encryptionKey = PK11_PBEKeyGen(slot, algId, &uniPwitem, + PR_FALSE, p12ctxt->wincx); + if (!safeInfo->encryptionKey) { + goto loser; + } + + safeInfo->arena = p12ctxt->arena; + safeInfo->safe = NULL; + if (sec_pkcs12_append_safe_info(p12ctxt, safeInfo) != SECSuccess) { + goto loser; + } + + if (uniPwitem.data) { + SECITEM_ZfreeItem(&uniPwitem, PR_FALSE); + } + PORT_ArenaUnmark(p12ctxt->arena, mark); + + if (slot) { + PK11_FreeSlot(slot); + } + return safeInfo; + +loser: + if (slot) { + PK11_FreeSlot(slot); + } + if (safeInfo->cinfo) { + SEC_PKCS7DestroyContentInfo(safeInfo->cinfo); + } + + if (uniPwitem.data) { + SECITEM_ZfreeItem(&uniPwitem, PR_FALSE); + } + + PORT_ArenaRelease(p12ctxt->arena, mark); + return NULL; +} + +/* SEC_PKCS12CreateUnencryptedSafe + * Creates an unencrypted safe within the export context. + * + * p12ctxt - the export context + */ +SEC_PKCS12SafeInfo * +SEC_PKCS12CreateUnencryptedSafe(SEC_PKCS12ExportContext *p12ctxt) +{ + SEC_PKCS12SafeInfo *safeInfo = NULL; + void *mark = NULL; + + if (!p12ctxt) { + return NULL; + } + + /* create the safe info */ + mark = PORT_ArenaMark(p12ctxt->arena); + safeInfo = (SEC_PKCS12SafeInfo *)PORT_ArenaZAlloc(p12ctxt->arena, + sizeof(SEC_PKCS12SafeInfo)); + if (!safeInfo) { + PORT_ArenaRelease(p12ctxt->arena, mark); + PORT_SetError(SEC_ERROR_NO_MEMORY); + return NULL; + } + + safeInfo->itemCount = 0; + + /* create the safe content */ + safeInfo->cinfo = SEC_PKCS7CreateData(); + if (!safeInfo->cinfo) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + if (sec_pkcs12_append_safe_info(p12ctxt, safeInfo) != SECSuccess) { + goto loser; + } + + PORT_ArenaUnmark(p12ctxt->arena, mark); + return safeInfo; + +loser: + if (safeInfo->cinfo) { + SEC_PKCS7DestroyContentInfo(safeInfo->cinfo); + } + + PORT_ArenaRelease(p12ctxt->arena, mark); + return NULL; +} + +/* SEC_PKCS12CreatePubKeyEncryptedSafe + * Creates a safe which is protected by public key encryption. + * + * p12ctxt - the export context + * certDb - the certificate database + * signer - the signer's certificate + * recipients - the list of recipient certificates. + * algorithm - the encryption algorithm to use + * keysize - the algorithms key size (?) + */ +SEC_PKCS12SafeInfo * +SEC_PKCS12CreatePubKeyEncryptedSafe(SEC_PKCS12ExportContext *p12ctxt, + CERTCertDBHandle *certDb, + CERTCertificate *signer, + CERTCertificate **recipients, + SECOidTag algorithm, int keysize) +{ + SEC_PKCS12SafeInfo *safeInfo = NULL; + void *mark = NULL; + + if (!p12ctxt || !signer || !recipients || !(*recipients)) { + return NULL; + } + + /* allocate the safeInfo */ + mark = PORT_ArenaMark(p12ctxt->arena); + safeInfo = (SEC_PKCS12SafeInfo *)PORT_ArenaZAlloc(p12ctxt->arena, + sizeof(SEC_PKCS12SafeInfo)); + if (!safeInfo) { + PORT_ArenaRelease(p12ctxt->arena, mark); + PORT_SetError(SEC_ERROR_NO_MEMORY); + return NULL; + } + + safeInfo->itemCount = 0; + safeInfo->arena = p12ctxt->arena; + + /* create the enveloped content info using certUsageEmailSigner currently. + * XXX We need to eventually use something other than certUsageEmailSigner + */ + safeInfo->cinfo = SEC_PKCS7CreateEnvelopedData(signer, certUsageEmailSigner, + certDb, algorithm, keysize, + p12ctxt->pwfn, p12ctxt->pwfnarg); + if (!safeInfo->cinfo) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + /* add recipients */ + if (recipients) { + unsigned int i = 0; + while (recipients[i] != NULL) { + SECStatus rv = SEC_PKCS7AddRecipient(safeInfo->cinfo, recipients[i], + certUsageEmailRecipient, certDb); + if (rv != SECSuccess) { + goto loser; + } + i++; + } + } + + if (sec_pkcs12_append_safe_info(p12ctxt, safeInfo) != SECSuccess) { + goto loser; + } + + PORT_ArenaUnmark(p12ctxt->arena, mark); + return safeInfo; + +loser: + if (safeInfo->cinfo) { + SEC_PKCS7DestroyContentInfo(safeInfo->cinfo); + safeInfo->cinfo = NULL; + } + + PORT_ArenaRelease(p12ctxt->arena, mark); + return NULL; +} + +/********************************* + * Routines to handle the exporting of the keys and certificates + *********************************/ + +/* creates a safe contents which safeBags will be appended to */ +sec_PKCS12SafeContents * +sec_PKCS12CreateSafeContents(PLArenaPool *arena) +{ + sec_PKCS12SafeContents *safeContents; + + if (arena == NULL) { + return NULL; + } + + /* create the safe contents */ + safeContents = (sec_PKCS12SafeContents *)PORT_ArenaZAlloc(arena, + sizeof(sec_PKCS12SafeContents)); + if (!safeContents) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + /* set up the internal contents info */ + safeContents->safeBags = NULL; + safeContents->arena = arena; + safeContents->bagCount = 0; + + return safeContents; + +loser: + return NULL; +} + +/* appends a safe bag to a safeContents using the specified arena. + */ +SECStatus +sec_pkcs12_append_bag_to_safe_contents(PLArenaPool *arena, + sec_PKCS12SafeContents *safeContents, + sec_PKCS12SafeBag *safeBag) +{ + void *mark = NULL, *dummy = NULL; + + if (!arena || !safeBag || !safeContents) { + return SECFailure; + } + + mark = PORT_ArenaMark(arena); + if (!mark) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return SECFailure; + } + + /* allocate space for the list, or reallocate to increase space */ + if (!safeContents->safeBags) { + safeContents->safeBags = (sec_PKCS12SafeBag **)PORT_ArenaZAlloc(arena, + (2 * sizeof(sec_PKCS12SafeBag *))); + dummy = safeContents->safeBags; + safeContents->bagCount = 0; + } else { + dummy = PORT_ArenaGrow(arena, safeContents->safeBags, + (safeContents->bagCount + 1) * sizeof(sec_PKCS12SafeBag *), + (safeContents->bagCount + 2) * sizeof(sec_PKCS12SafeBag *)); + safeContents->safeBags = (sec_PKCS12SafeBag **)dummy; + } + + if (!dummy) { + PORT_ArenaRelease(arena, mark); + PORT_SetError(SEC_ERROR_NO_MEMORY); + return SECFailure; + } + + /* append the bag at the end and null terminate the list */ + safeContents->safeBags[safeContents->bagCount++] = safeBag; + safeContents->safeBags[safeContents->bagCount] = NULL; + + PORT_ArenaUnmark(arena, mark); + + return SECSuccess; +} + +/* appends a safeBag to a specific safeInfo. + */ +SECStatus +sec_pkcs12_append_bag(SEC_PKCS12ExportContext *p12ctxt, + SEC_PKCS12SafeInfo *safeInfo, sec_PKCS12SafeBag *safeBag) +{ + sec_PKCS12SafeContents *dest; + SECStatus rv = SECFailure; + + if (!p12ctxt || !safeBag || !safeInfo) { + return SECFailure; + } + + if (!safeInfo->safe) { + safeInfo->safe = sec_PKCS12CreateSafeContents(p12ctxt->arena); + if (!safeInfo->safe) { + return SECFailure; + } + } + + dest = safeInfo->safe; + rv = sec_pkcs12_append_bag_to_safe_contents(p12ctxt->arena, dest, safeBag); + if (rv == SECSuccess) { + safeInfo->itemCount++; + } + + return rv; +} + +/* Creates a safeBag of the specified type, and if bagData is specified, + * the contents are set. The contents could be set later by the calling + * routine. + */ +sec_PKCS12SafeBag * +sec_PKCS12CreateSafeBag(SEC_PKCS12ExportContext *p12ctxt, SECOidTag bagType, + void *bagData) +{ + sec_PKCS12SafeBag *safeBag; + void *mark = NULL; + SECStatus rv = SECSuccess; + SECOidData *oidData = NULL; + + if (!p12ctxt) { + return NULL; + } + + mark = PORT_ArenaMark(p12ctxt->arena); + if (!mark) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return NULL; + } + + safeBag = (sec_PKCS12SafeBag *)PORT_ArenaZAlloc(p12ctxt->arena, + sizeof(sec_PKCS12SafeBag)); + if (!safeBag) { + PORT_ArenaRelease(p12ctxt->arena, mark); + PORT_SetError(SEC_ERROR_NO_MEMORY); + return NULL; + } + + /* set the bags content based upon bag type */ + switch (bagType) { + case SEC_OID_PKCS12_V1_KEY_BAG_ID: + safeBag->safeBagContent.pkcs8KeyBag = + (SECKEYPrivateKeyInfo *)bagData; + break; + case SEC_OID_PKCS12_V1_CERT_BAG_ID: + safeBag->safeBagContent.certBag = (sec_PKCS12CertBag *)bagData; + break; + case SEC_OID_PKCS12_V1_CRL_BAG_ID: + safeBag->safeBagContent.crlBag = (sec_PKCS12CRLBag *)bagData; + break; + case SEC_OID_PKCS12_V1_SECRET_BAG_ID: + safeBag->safeBagContent.secretBag = (sec_PKCS12SecretBag *)bagData; + break; + case SEC_OID_PKCS12_V1_PKCS8_SHROUDED_KEY_BAG_ID: + safeBag->safeBagContent.pkcs8ShroudedKeyBag = + (SECKEYEncryptedPrivateKeyInfo *)bagData; + break; + case SEC_OID_PKCS12_V1_SAFE_CONTENTS_BAG_ID: + safeBag->safeBagContent.safeContents = + (sec_PKCS12SafeContents *)bagData; + break; + default: + goto loser; + } + + oidData = SECOID_FindOIDByTag(bagType); + if (oidData) { + rv = SECITEM_CopyItem(p12ctxt->arena, &safeBag->safeBagType, &oidData->oid); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + } else { + goto loser; + } + + safeBag->arena = p12ctxt->arena; + PORT_ArenaUnmark(p12ctxt->arena, mark); + + return safeBag; + +loser: + if (mark) { + PORT_ArenaRelease(p12ctxt->arena, mark); + } + + return NULL; +} + +/* Creates a new certificate bag and returns a pointer to it. If an error + * occurs NULL is returned. + */ +sec_PKCS12CertBag * +sec_PKCS12NewCertBag(PLArenaPool *arena, SECOidTag certType) +{ + sec_PKCS12CertBag *certBag = NULL; + SECOidData *bagType = NULL; + SECStatus rv; + void *mark = NULL; + + if (!arena) { + return NULL; + } + + mark = PORT_ArenaMark(arena); + certBag = (sec_PKCS12CertBag *)PORT_ArenaZAlloc(arena, + sizeof(sec_PKCS12CertBag)); + if (!certBag) { + PORT_ArenaRelease(arena, mark); + PORT_SetError(SEC_ERROR_NO_MEMORY); + return NULL; + } + + bagType = SECOID_FindOIDByTag(certType); + if (!bagType) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + rv = SECITEM_CopyItem(arena, &certBag->bagID, &bagType->oid); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + PORT_ArenaUnmark(arena, mark); + return certBag; + +loser: + PORT_ArenaRelease(arena, mark); + return NULL; +} + +/* Creates a new CRL bag and returns a pointer to it. If an error + * occurs NULL is returned. + */ +sec_PKCS12CRLBag * +sec_PKCS12NewCRLBag(PLArenaPool *arena, SECOidTag crlType) +{ + sec_PKCS12CRLBag *crlBag = NULL; + SECOidData *bagType = NULL; + SECStatus rv; + void *mark = NULL; + + if (!arena) { + return NULL; + } + + mark = PORT_ArenaMark(arena); + crlBag = (sec_PKCS12CRLBag *)PORT_ArenaZAlloc(arena, + sizeof(sec_PKCS12CRLBag)); + if (!crlBag) { + PORT_ArenaRelease(arena, mark); + PORT_SetError(SEC_ERROR_NO_MEMORY); + return NULL; + } + + bagType = SECOID_FindOIDByTag(crlType); + if (!bagType) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + rv = SECITEM_CopyItem(arena, &crlBag->bagID, &bagType->oid); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + PORT_ArenaUnmark(arena, mark); + return crlBag; + +loser: + PORT_ArenaRelease(arena, mark); + return NULL; +} + +/* sec_PKCS12AddAttributeToBag + * adds an attribute to a safeBag. currently, the only attributes supported + * are those which are specified within PKCS 12. + * + * p12ctxt - the export context + * safeBag - the safeBag to which attributes are appended + * attrType - the attribute type + * attrData - the attribute data + */ +SECStatus +sec_PKCS12AddAttributeToBag(SEC_PKCS12ExportContext *p12ctxt, + sec_PKCS12SafeBag *safeBag, SECOidTag attrType, + SECItem *attrData) +{ + sec_PKCS12Attribute *attribute; + void *mark = NULL, *dummy = NULL; + SECOidData *oiddata = NULL; + SECItem unicodeName = { siBuffer, NULL, 0 }; + void *src = NULL; + unsigned int nItems = 0; + SECStatus rv; + + PORT_Assert(p12ctxt->arena == safeBag->arena); + if (!safeBag || !p12ctxt || p12ctxt->arena != safeBag->arena) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + mark = PORT_ArenaMark(safeBag->arena); + + /* allocate the attribute */ + attribute = (sec_PKCS12Attribute *)PORT_ArenaZAlloc(safeBag->arena, + sizeof(sec_PKCS12Attribute)); + if (!attribute) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + /* set up the attribute */ + oiddata = SECOID_FindOIDByTag(attrType); + if (!oiddata) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + if (SECITEM_CopyItem(p12ctxt->arena, &attribute->attrType, &oiddata->oid) != + SECSuccess) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + nItems = 1; + switch (attrType) { + case SEC_OID_PKCS9_LOCAL_KEY_ID: { + src = attrData; + break; + } + case SEC_OID_PKCS9_FRIENDLY_NAME: { + if (!sec_pkcs12_convert_item_to_unicode(p12ctxt->arena, + &unicodeName, attrData, PR_FALSE, + PR_FALSE, PR_TRUE)) { + goto loser; + } + src = &unicodeName; + break; + } + default: + goto loser; + } + + /* append the attribute to the attribute value list */ + attribute->attrValue = (SECItem **)PORT_ArenaZAlloc(p12ctxt->arena, + ((nItems + 1) * sizeof(SECItem *))); + if (!attribute->attrValue) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + /* XXX this will need to be changed if attributes requiring more than + * one element are ever used. + */ + attribute->attrValue[0] = (SECItem *)PORT_ArenaZAlloc(p12ctxt->arena, + sizeof(SECItem)); + if (!attribute->attrValue[0]) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + attribute->attrValue[1] = NULL; + + rv = SECITEM_CopyItem(p12ctxt->arena, attribute->attrValue[0], + (SECItem *)src); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + /* append the attribute to the safeBag attributes */ + if (safeBag->nAttribs) { + dummy = PORT_ArenaGrow(p12ctxt->arena, safeBag->attribs, + ((safeBag->nAttribs + 1) * sizeof(sec_PKCS12Attribute *)), + ((safeBag->nAttribs + 2) * sizeof(sec_PKCS12Attribute *))); + safeBag->attribs = (sec_PKCS12Attribute **)dummy; + } else { + safeBag->attribs = (sec_PKCS12Attribute **)PORT_ArenaZAlloc(p12ctxt->arena, + 2 * sizeof(sec_PKCS12Attribute *)); + dummy = safeBag->attribs; + } + if (!dummy) { + goto loser; + } + + safeBag->attribs[safeBag->nAttribs] = attribute; + safeBag->attribs[++safeBag->nAttribs] = NULL; + + PORT_ArenaUnmark(p12ctxt->arena, mark); + return SECSuccess; + +loser: + if (mark) { + PORT_ArenaRelease(p12ctxt->arena, mark); + } + + return SECFailure; +} + +/* SEC_PKCS12AddCert + * Adds a certificate to the data being exported. + * + * p12ctxt - the export context + * safe - the safeInfo to which the certificate is placed + * nestedDest - if the cert is to be placed within a nested safeContents then, + * this value is to be specified with the destination + * cert - the cert to export + * certDb - the certificate database handle + * keyId - a unique identifier to associate a certificate/key pair + * includeCertChain - PR_TRUE if the certificate chain is to be included. + */ +SECStatus +SEC_PKCS12AddCert(SEC_PKCS12ExportContext *p12ctxt, SEC_PKCS12SafeInfo *safe, + void *nestedDest, CERTCertificate *cert, + CERTCertDBHandle *certDb, SECItem *keyId, + PRBool includeCertChain) +{ + sec_PKCS12CertBag *certBag; + sec_PKCS12SafeBag *safeBag; + void *mark; + SECStatus rv; + SECItem nick = { siBuffer, NULL, 0 }; + + if (!p12ctxt || !cert) { + return SECFailure; + } + mark = PORT_ArenaMark(p12ctxt->arena); + + /* allocate the cert bag */ + certBag = sec_PKCS12NewCertBag(p12ctxt->arena, + SEC_OID_PKCS9_X509_CERT); + if (!certBag) { + goto loser; + } + + if (SECITEM_CopyItem(p12ctxt->arena, &certBag->value.x509Cert, + &cert->derCert) != SECSuccess) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + /* if the cert chain is to be included, we should only be exporting + * the cert from our internal database. + */ + if (includeCertChain) { + CERTCertificateList *certList = CERT_CertChainFromCert(cert, + certUsageSSLClient, + PR_TRUE); + unsigned int count = 0; + if (!certList) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + /* add cert chain */ + for (count = 0; count < (unsigned int)certList->len; count++) { + if (SECITEM_CompareItem(&certList->certs[count], &cert->derCert) != SECEqual) { + CERTCertificate *tempCert; + + /* decode the certificate */ + /* XXX + * This was rather silly. The chain is constructed above + * by finding all of the CERTCertificate's in the database. + * Then the chain is put into a CERTCertificateList, which only + * contains the DER. Finally, the DER was decoded, and the + * decoded cert was sent recursively back to this function. + * Beyond being inefficent, this causes data loss (specifically, + * the nickname). Instead, for 3.4, we'll do a lookup by the + * DER, which should return the cached entry. + */ + tempCert = CERT_FindCertByDERCert(CERT_GetDefaultCertDB(), + &certList->certs[count]); + if (!tempCert) { + CERT_DestroyCertificateList(certList); + goto loser; + } + + /* add the certificate */ + if (SEC_PKCS12AddCert(p12ctxt, safe, nestedDest, tempCert, + certDb, NULL, PR_FALSE) != SECSuccess) { + CERT_DestroyCertificate(tempCert); + CERT_DestroyCertificateList(certList); + goto loser; + } + CERT_DestroyCertificate(tempCert); + } + } + CERT_DestroyCertificateList(certList); + } + + /* if the certificate has a nickname, we will set the friendly name + * to that. + */ + if (cert->nickname) { + if (cert->slot && !PK11_IsInternal(cert->slot)) { + /* + * The cert is coming off of an external token, + * let's strip the token name from the nickname + * and only add what comes after the colon as the + * nickname. -javi + */ + char *delimit; + + delimit = PORT_Strchr(cert->nickname, ':'); + if (delimit == NULL) { + nick.data = (unsigned char *)cert->nickname; + nick.len = PORT_Strlen(cert->nickname); + } else { + delimit++; + nick.data = (unsigned char *)PORT_ArenaStrdup(p12ctxt->arena, + delimit); + nick.len = PORT_Strlen(delimit); + } + } else { + nick.data = (unsigned char *)cert->nickname; + nick.len = PORT_Strlen(cert->nickname); + } + } + + safeBag = sec_PKCS12CreateSafeBag(p12ctxt, SEC_OID_PKCS12_V1_CERT_BAG_ID, + certBag); + if (!safeBag) { + goto loser; + } + + /* add the friendly name and keyId attributes, if necessary */ + if (nick.data) { + if (sec_PKCS12AddAttributeToBag(p12ctxt, safeBag, + SEC_OID_PKCS9_FRIENDLY_NAME, &nick) != SECSuccess) { + goto loser; + } + } + + if (keyId) { + if (sec_PKCS12AddAttributeToBag(p12ctxt, safeBag, SEC_OID_PKCS9_LOCAL_KEY_ID, + keyId) != SECSuccess) { + goto loser; + } + } + + /* append the cert safeBag */ + if (nestedDest) { + rv = sec_pkcs12_append_bag_to_safe_contents(p12ctxt->arena, + (sec_PKCS12SafeContents *)nestedDest, + safeBag); + } else { + rv = sec_pkcs12_append_bag(p12ctxt, safe, safeBag); + } + + if (rv != SECSuccess) { + goto loser; + } + + PORT_ArenaUnmark(p12ctxt->arena, mark); + return SECSuccess; + +loser: + if (mark) { + PORT_ArenaRelease(p12ctxt->arena, mark); + } + + return SECFailure; +} + +/* SEC_PKCS12AddKeyForCert + * Extracts the key associated with a particular certificate and exports + * it. + * + * p12ctxt - the export context + * safe - the safeInfo to place the key in + * nestedDest - the nested safeContents to place a key + * cert - the certificate which the key belongs to + * shroudKey - encrypt the private key for export. This value should + * always be true. lower level code will not allow the export + * of unencrypted private keys. + * algorithm - the algorithm with which to encrypt the private key + * pwitem - the password to encrypt the private key with + * keyId - the keyID attribute + * nickName - the nickname attribute + */ +SECStatus +SEC_PKCS12AddKeyForCert(SEC_PKCS12ExportContext *p12ctxt, SEC_PKCS12SafeInfo *safe, + void *nestedDest, CERTCertificate *cert, + PRBool shroudKey, SECOidTag algorithm, SECItem *pwitem, + SECItem *keyId, SECItem *nickName) +{ + void *mark; + void *keyItem; + SECOidTag keyType; + SECStatus rv = SECFailure; + SECItem nickname = { siBuffer, NULL, 0 }, uniPwitem = { siBuffer, NULL, 0 }; + sec_PKCS12SafeBag *returnBag; + + if (!p12ctxt || !cert || !safe) { + return SECFailure; + } + + mark = PORT_ArenaMark(p12ctxt->arena); + + /* retrieve the key based upon the type that it is and + * specify the type of safeBag to store the key in + */ + if (!shroudKey) { + + /* extract the key unencrypted. this will most likely go away */ + SECKEYPrivateKeyInfo *pki = PK11_ExportPrivateKeyInfo(cert, + p12ctxt->wincx); + if (!pki) { + PORT_ArenaRelease(p12ctxt->arena, mark); + PORT_SetError(SEC_ERROR_PKCS12_UNABLE_TO_EXPORT_KEY); + return SECFailure; + } + keyItem = PORT_ArenaZAlloc(p12ctxt->arena, sizeof(SECKEYPrivateKeyInfo)); + if (!keyItem) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + rv = SECKEY_CopyPrivateKeyInfo(p12ctxt->arena, + (SECKEYPrivateKeyInfo *)keyItem, pki); + keyType = SEC_OID_PKCS12_V1_KEY_BAG_ID; + SECKEY_DestroyPrivateKeyInfo(pki, PR_TRUE); + } else { + + /* extract the key encrypted */ + SECKEYEncryptedPrivateKeyInfo *epki = NULL; + PK11SlotInfo *slot = NULL; + SECOidTag prfAlg = SEC_OID_UNKNOWN; + + if (!sec_pkcs12_encode_password(p12ctxt->arena, &uniPwitem, algorithm, + pwitem)) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + /* if we have password integrity set, use that to set the integrity + * hash algorithm to set our password PRF. If we haven't set it, just + * let the low level code pick it */ + if (p12ctxt->integrityEnabled && p12ctxt->pwdIntegrity) { + prfAlg = HASH_GetHMACOidTagByHashOidTag( + p12ctxt->integrityInfo.pwdInfo.algorithm); + } + + /* we want to make sure to take the key out of the key slot */ + if (PK11_IsInternal(p12ctxt->slot)) { + slot = PK11_GetInternalKeySlot(); + } else { + slot = PK11_ReferenceSlot(p12ctxt->slot); + } + + /* passing algorithm as the pbe will force the PBE code to + * automatically handle the selection between using the algorithm + * as a the pbe algorithm, or using the algorithm as a cipher + * and building a pkcs5 pbe */ + epki = PK11_ExportEncryptedPrivateKeyInfoV2(slot, algorithm, + SEC_OID_UNKNOWN, prfAlg, + &uniPwitem, cert, + NSS_PBE_DEFAULT_ITERATION_COUNT, + p12ctxt->wincx); + PK11_FreeSlot(slot); + if (!epki) { + PORT_SetError(SEC_ERROR_PKCS12_UNABLE_TO_EXPORT_KEY); + goto loser; + } + + keyItem = PORT_ArenaZAlloc(p12ctxt->arena, + sizeof(SECKEYEncryptedPrivateKeyInfo)); + if (!keyItem) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + rv = SECKEY_CopyEncryptedPrivateKeyInfo(p12ctxt->arena, + (SECKEYEncryptedPrivateKeyInfo *)keyItem, + epki); + keyType = SEC_OID_PKCS12_V1_PKCS8_SHROUDED_KEY_BAG_ID; + SECKEY_DestroyEncryptedPrivateKeyInfo(epki, PR_TRUE); + } + + if (rv != SECSuccess) { + goto loser; + } + + /* if no nickname specified, let's see if the certificate has a + * nickname. + */ + if (!nickName) { + if (cert->nickname) { + nickname.data = (unsigned char *)cert->nickname; + nickname.len = PORT_Strlen(cert->nickname); + nickName = &nickname; + } + } + + /* create the safe bag and set any attributes */ + returnBag = sec_PKCS12CreateSafeBag(p12ctxt, keyType, keyItem); + if (!returnBag) { + rv = SECFailure; + goto loser; + } + + if (nickName) { + if (sec_PKCS12AddAttributeToBag(p12ctxt, returnBag, + SEC_OID_PKCS9_FRIENDLY_NAME, nickName) != SECSuccess) { + goto loser; + } + } + + if (keyId) { + if (sec_PKCS12AddAttributeToBag(p12ctxt, returnBag, SEC_OID_PKCS9_LOCAL_KEY_ID, + keyId) != SECSuccess) { + goto loser; + } + } + + if (nestedDest) { + rv = sec_pkcs12_append_bag_to_safe_contents(p12ctxt->arena, + (sec_PKCS12SafeContents *)nestedDest, + returnBag); + } else { + rv = sec_pkcs12_append_bag(p12ctxt, safe, returnBag); + } + +loser: + + if (rv != SECSuccess) { + PORT_ArenaRelease(p12ctxt->arena, mark); + } else { + PORT_ArenaUnmark(p12ctxt->arena, mark); + } + + return rv; +} + +/* SEC_PKCS12AddCertOrChainAndKey + * Add a certificate and key pair to be exported. + * + * p12ctxt - the export context + * certSafe - the safeInfo where the cert is stored + * certNestedDest - the nested safeContents to store the cert + * keySafe - the safeInfo where the key is stored + * keyNestedDest - the nested safeContents to store the key + * shroudKey - extract the private key encrypted? + * pwitem - the password with which the key is encrypted + * algorithm - the algorithm with which the key is encrypted + * includeCertChain - also add certs from chain to bag. + */ +SECStatus +SEC_PKCS12AddCertOrChainAndKey(SEC_PKCS12ExportContext *p12ctxt, + void *certSafe, void *certNestedDest, + CERTCertificate *cert, CERTCertDBHandle *certDb, + void *keySafe, void *keyNestedDest, + PRBool shroudKey, SECItem *pwitem, + SECOidTag algorithm, PRBool includeCertChain) +{ + SECStatus rv = SECFailure; + SGNDigestInfo *digest = NULL; + void *mark = NULL; + + if (!p12ctxt || !certSafe || !keySafe || !cert) { + return SECFailure; + } + + mark = PORT_ArenaMark(p12ctxt->arena); + + /* generate the thumbprint of the cert to use as a keyId */ + digest = sec_pkcs12_compute_thumbprint(&cert->derCert); + if (!digest) { + PORT_ArenaRelease(p12ctxt->arena, mark); + return SECFailure; + } + + /* add the certificate */ + rv = SEC_PKCS12AddCert(p12ctxt, (SEC_PKCS12SafeInfo *)certSafe, + (SEC_PKCS12SafeInfo *)certNestedDest, cert, certDb, + &digest->digest, includeCertChain); + if (rv != SECSuccess) { + goto loser; + } + + /* add the key */ + rv = SEC_PKCS12AddKeyForCert(p12ctxt, (SEC_PKCS12SafeInfo *)keySafe, + keyNestedDest, cert, + shroudKey, algorithm, pwitem, + &digest->digest, NULL); + if (rv != SECSuccess) { + goto loser; + } + + SGN_DestroyDigestInfo(digest); + + PORT_ArenaUnmark(p12ctxt->arena, mark); + return SECSuccess; + +loser: + SGN_DestroyDigestInfo(digest); + PORT_ArenaRelease(p12ctxt->arena, mark); + + return SECFailure; +} + +/* like SEC_PKCS12AddCertOrChainAndKey, but always adds cert chain */ +SECStatus +SEC_PKCS12AddCertAndKey(SEC_PKCS12ExportContext *p12ctxt, + void *certSafe, void *certNestedDest, + CERTCertificate *cert, CERTCertDBHandle *certDb, + void *keySafe, void *keyNestedDest, + PRBool shroudKey, SECItem *pwItem, SECOidTag algorithm) +{ + return SEC_PKCS12AddCertOrChainAndKey(p12ctxt, certSafe, certNestedDest, + cert, certDb, keySafe, keyNestedDest, shroudKey, pwItem, + algorithm, PR_TRUE); +} + +/* SEC_PKCS12CreateNestedSafeContents + * Allows nesting of safe contents to be implemented. No limit imposed on + * depth. + * + * p12ctxt - the export context + * baseSafe - the base safeInfo + * nestedDest - a parent safeContents (?) + */ +void * +SEC_PKCS12CreateNestedSafeContents(SEC_PKCS12ExportContext *p12ctxt, + void *baseSafe, void *nestedDest) +{ + sec_PKCS12SafeContents *newSafe; + sec_PKCS12SafeBag *safeContentsBag; + void *mark; + SECStatus rv; + + if (!p12ctxt || !baseSafe) { + return NULL; + } + + mark = PORT_ArenaMark(p12ctxt->arena); + + newSafe = sec_PKCS12CreateSafeContents(p12ctxt->arena); + if (!newSafe) { + PORT_ArenaRelease(p12ctxt->arena, mark); + PORT_SetError(SEC_ERROR_NO_MEMORY); + return NULL; + } + + /* create the safeContents safeBag */ + safeContentsBag = sec_PKCS12CreateSafeBag(p12ctxt, + SEC_OID_PKCS12_V1_SAFE_CONTENTS_BAG_ID, + newSafe); + if (!safeContentsBag) { + goto loser; + } + + /* append the safeContents to the appropriate area */ + if (nestedDest) { + rv = sec_pkcs12_append_bag_to_safe_contents(p12ctxt->arena, + (sec_PKCS12SafeContents *)nestedDest, + safeContentsBag); + } else { + rv = sec_pkcs12_append_bag(p12ctxt, (SEC_PKCS12SafeInfo *)baseSafe, + safeContentsBag); + } + if (rv != SECSuccess) { + goto loser; + } + + PORT_ArenaUnmark(p12ctxt->arena, mark); + return newSafe; + +loser: + PORT_ArenaRelease(p12ctxt->arena, mark); + return NULL; +} + +/********************************* + * Encoding routines + *********************************/ + +/* Clean up the resources allocated by a sec_PKCS12EncoderContext. */ +static void +sec_pkcs12_encoder_destroy_context(sec_PKCS12EncoderContext *p12enc) +{ + if (p12enc) { + if (p12enc->outerA1ecx) { + SEC_ASN1EncoderFinish(p12enc->outerA1ecx); + p12enc->outerA1ecx = NULL; + } + if (p12enc->aSafeCinfo) { + SEC_PKCS7DestroyContentInfo(p12enc->aSafeCinfo); + p12enc->aSafeCinfo = NULL; + } + if (p12enc->middleP7ecx) { + SEC_PKCS7EncoderFinish(p12enc->middleP7ecx, p12enc->p12exp->pwfn, + p12enc->p12exp->pwfnarg); + p12enc->middleP7ecx = NULL; + } + if (p12enc->middleA1ecx) { + SEC_ASN1EncoderFinish(p12enc->middleA1ecx); + p12enc->middleA1ecx = NULL; + } + if (p12enc->hmacCx) { + PK11_DestroyContext(p12enc->hmacCx, PR_TRUE); + p12enc->hmacCx = NULL; + } + } +} + +/* set up the encoder context based on information in the export context + * and return the newly allocated enocoder context. A return of NULL + * indicates an error occurred. + */ +static sec_PKCS12EncoderContext * +sec_pkcs12_encoder_start_context(SEC_PKCS12ExportContext *p12exp) +{ + sec_PKCS12EncoderContext *p12enc = NULL; + unsigned int i, nonEmptyCnt; + SECStatus rv; + SECItem ignore = { 0 }; + void *mark; + SECItem *salt = NULL; + SECItem *params = NULL; + + if (!p12exp || !p12exp->safeInfos) { + return NULL; + } + + /* check for any empty safes and skip them */ + i = nonEmptyCnt = 0; + while (p12exp->safeInfos[i]) { + if (p12exp->safeInfos[i]->itemCount) { + nonEmptyCnt++; + } + i++; + } + if (nonEmptyCnt == 0) { + return NULL; + } + p12exp->authSafe.encodedSafes[nonEmptyCnt] = NULL; + + /* allocate the encoder context */ + mark = PORT_ArenaMark(p12exp->arena); + p12enc = PORT_ArenaZNew(p12exp->arena, sec_PKCS12EncoderContext); + if (!p12enc) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return NULL; + } + + p12enc->arena = p12exp->arena; + p12enc->p12exp = p12exp; + + /* set up the PFX version and information */ + PORT_Memset(&p12enc->pfx, 0, sizeof(sec_PKCS12PFXItem)); + if (!SEC_ASN1EncodeInteger(p12exp->arena, &(p12enc->pfx.version), + SEC_PKCS12_VERSION)) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + /* set up the authenticated safe content info based on the + * type of integrity being used. this should be changed to + * enforce integrity mode, but will not be implemented until + * it is confirmed that integrity must be in place + */ + if (p12exp->integrityEnabled && !p12exp->pwdIntegrity) { + /* create public key integrity mode */ + p12enc->aSafeCinfo = SEC_PKCS7CreateSignedData( + p12exp->integrityInfo.pubkeyInfo.cert, + certUsageEmailSigner, + p12exp->integrityInfo.pubkeyInfo.certDb, + p12exp->integrityInfo.pubkeyInfo.algorithm, + NULL, + p12exp->pwfn, + p12exp->pwfnarg); + if (!p12enc->aSafeCinfo) { + goto loser; + } + if (SEC_PKCS7IncludeCertChain(p12enc->aSafeCinfo, NULL) != SECSuccess) { + goto loser; + } + PORT_CheckSuccess(SEC_PKCS7AddSigningTime(p12enc->aSafeCinfo)); + } else { + p12enc->aSafeCinfo = SEC_PKCS7CreateData(); + + /* init password pased integrity mode */ + if (p12exp->integrityEnabled) { + SECItem pwd = { siBuffer, NULL, 0 }; + PK11SymKey *symKey; + CK_MECHANISM_TYPE integrityMechType; + CK_MECHANISM_TYPE hmacMechType; + salt = sec_pkcs12_generate_salt(); + + /* zero out macData and set values */ + PORT_Memset(&p12enc->mac, 0, sizeof(sec_PKCS12MacData)); + + if (!salt) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + if (SECITEM_CopyItem(p12exp->arena, &(p12enc->mac.macSalt), salt) != SECSuccess) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + if (!SEC_ASN1EncodeInteger(p12exp->arena, &(p12enc->mac.iter), + NSS_PBE_DEFAULT_ITERATION_COUNT)) { + goto loser; + } + + /* generate HMAC key */ + if (!sec_pkcs12_convert_item_to_unicode(NULL, &pwd, + p12exp->integrityInfo.pwdInfo.password, PR_TRUE, + PR_TRUE, PR_TRUE)) { + goto loser; + } + /* + * This code only works with PKCS #12 Mac using PKCS #5 v1 + * PBA keygens. PKCS #5 v2 support will require a change to + * the PKCS #12 spec. + */ + params = PK11_CreatePBEParams(salt, &pwd, + NSS_PBE_DEFAULT_ITERATION_COUNT); + SECITEM_ZfreeItem(salt, PR_TRUE); + salt = NULL; + SECITEM_ZfreeItem(&pwd, PR_FALSE); + + /* get the PBA Mechanism to generate the key */ + integrityMechType = sec_pkcs12_algtag_to_keygen_mech( + p12exp->integrityInfo.pwdInfo.algorithm); + if (integrityMechType == CKM_INVALID_MECHANISM) { + goto loser; + } + + /* generate the key */ + symKey = PK11_KeyGen(NULL, integrityMechType, params, 20, NULL); + PK11_DestroyPBEParams(params); + if (!symKey) { + goto loser; + } + + /* initialize HMAC */ + /* Get the HMAC mechanism from the hash OID */ + hmacMechType = sec_pkcs12_algtag_to_mech( + p12exp->integrityInfo.pwdInfo.algorithm); + + p12enc->hmacCx = PK11_CreateContextBySymKey(hmacMechType, + CKA_SIGN, symKey, &ignore); + + PK11_FreeSymKey(symKey); + if (!p12enc->hmacCx) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + rv = PK11_DigestBegin(p12enc->hmacCx); + if (rv != SECSuccess) + goto loser; + } + } + + if (!p12enc->aSafeCinfo) { + goto loser; + } + + PORT_ArenaUnmark(p12exp->arena, mark); + + return p12enc; + +loser: + sec_pkcs12_encoder_destroy_context(p12enc); + if (p12exp->arena != NULL) + PORT_ArenaRelease(p12exp->arena, mark); + if (salt) { + SECITEM_ZfreeItem(salt, PR_TRUE); + } + if (params) { + PK11_DestroyPBEParams(params); + } + + return NULL; +} + +/* The outermost ASN.1 encoder calls this function for output. +** This function calls back to the library caller's output routine, +** which typically writes to a PKCS12 file. + */ +static void +sec_P12A1OutputCB_Outer(void *arg, const char *buf, unsigned long len, + int depth, SEC_ASN1EncodingPart data_kind) +{ + struct sec_pkcs12_encoder_output *output; + + output = (struct sec_pkcs12_encoder_output *)arg; + (*output->outputfn)(output->outputarg, buf, len); +} + +/* The "middle" and "inner" ASN.1 encoders call this function to output. +** This function does HMACing, if appropriate, and then buffers the data. +** The buffered data is eventually passed down to the underlying PKCS7 encoder. + */ +static void +sec_P12A1OutputCB_HmacP7Update(void *arg, const char *buf, + unsigned long len, + int depth, + SEC_ASN1EncodingPart data_kind) +{ + sec_pkcs12OutputBuffer *bufcx = (sec_pkcs12OutputBuffer *)arg; + + if (!buf || !len) + return; + + if (bufcx->hmacCx) { + PK11_DigestOp(bufcx->hmacCx, (unsigned char *)buf, len); + } + + /* buffer */ + if (bufcx->numBytes > 0) { + int toCopy; + if (len + bufcx->numBytes <= bufcx->bufBytes) { + memcpy(bufcx->buf + bufcx->numBytes, buf, len); + bufcx->numBytes += len; + if (bufcx->numBytes < bufcx->bufBytes) + return; + SEC_PKCS7EncoderUpdate(bufcx->p7eCx, bufcx->buf, bufcx->bufBytes); + bufcx->numBytes = 0; + return; + } + toCopy = bufcx->bufBytes - bufcx->numBytes; + memcpy(bufcx->buf + bufcx->numBytes, buf, toCopy); + SEC_PKCS7EncoderUpdate(bufcx->p7eCx, bufcx->buf, bufcx->bufBytes); + bufcx->numBytes = 0; + len -= toCopy; + buf += toCopy; + } + /* buffer is presently empty */ + if (len >= bufcx->bufBytes) { + /* Just pass it through */ + SEC_PKCS7EncoderUpdate(bufcx->p7eCx, buf, len); + } else { + /* copy it all into the buffer, and return */ + memcpy(bufcx->buf, buf, len); + bufcx->numBytes = len; + } +} + +void +sec_FlushPkcs12OutputBuffer(sec_pkcs12OutputBuffer *bufcx) +{ + if (bufcx->numBytes > 0) { + SEC_PKCS7EncoderUpdate(bufcx->p7eCx, bufcx->buf, bufcx->numBytes); + bufcx->numBytes = 0; + } +} + +/* Feeds the output of a PKCS7 encoder into the next outward ASN.1 encoder. +** This function is used by both the inner and middle PCS7 encoders. +*/ +static void +sec_P12P7OutputCB_CallA1Update(void *arg, const char *buf, unsigned long len) +{ + SEC_ASN1EncoderContext *cx = (SEC_ASN1EncoderContext *)arg; + + if (!buf || !len) + return; + + SEC_ASN1EncoderUpdate(cx, buf, len); +} + +/* this function encodes content infos which are part of the + * sequence of content infos labeled AuthenticatedSafes + */ +static SECStatus +sec_pkcs12_encoder_asafe_process(sec_PKCS12EncoderContext *p12ecx) +{ + SEC_PKCS7EncoderContext *innerP7ecx; + SEC_PKCS7ContentInfo *cinfo; + PK11SymKey *bulkKey = NULL; + SEC_ASN1EncoderContext *innerA1ecx = NULL; + SECStatus rv = SECSuccess; + + if (p12ecx->currentSafe < p12ecx->p12exp->authSafe.safeCount) { + SEC_PKCS12SafeInfo *safeInfo; + SECOidTag cinfoType; + + safeInfo = p12ecx->p12exp->safeInfos[p12ecx->currentSafe]; + + /* skip empty safes */ + if (safeInfo->itemCount == 0) { + return SECSuccess; + } + + cinfo = safeInfo->cinfo; + cinfoType = SEC_PKCS7ContentType(cinfo); + + /* determine the safe type and set the appropriate argument */ + switch (cinfoType) { + case SEC_OID_PKCS7_DATA: + case SEC_OID_PKCS7_ENVELOPED_DATA: + break; + case SEC_OID_PKCS7_ENCRYPTED_DATA: + bulkKey = safeInfo->encryptionKey; + PK11_SetSymKeyUserData(bulkKey, &safeInfo->pwitem, NULL); + break; + default: + return SECFailure; + } + + /* start the PKCS7 encoder */ + innerP7ecx = SEC_PKCS7EncoderStart(cinfo, + sec_P12P7OutputCB_CallA1Update, + p12ecx->middleA1ecx, bulkKey); + if (!innerP7ecx) { + goto loser; + } + + /* encode safe contents */ + p12ecx->innerBuf.p7eCx = innerP7ecx; + p12ecx->innerBuf.hmacCx = NULL; + p12ecx->innerBuf.numBytes = 0; + p12ecx->innerBuf.bufBytes = sizeof p12ecx->innerBuf.buf; + + innerA1ecx = SEC_ASN1EncoderStart(safeInfo->safe, + sec_PKCS12SafeContentsTemplate, + sec_P12A1OutputCB_HmacP7Update, + &p12ecx->innerBuf); + if (!innerA1ecx) { + goto loser; + } + rv = SEC_ASN1EncoderUpdate(innerA1ecx, NULL, 0); + SEC_ASN1EncoderFinish(innerA1ecx); + sec_FlushPkcs12OutputBuffer(&p12ecx->innerBuf); + innerA1ecx = NULL; + if (rv != SECSuccess) { + goto loser; + } + + /* finish up safe content info */ + rv = SEC_PKCS7EncoderFinish(innerP7ecx, p12ecx->p12exp->pwfn, + p12ecx->p12exp->pwfnarg); + } + memset(&p12ecx->innerBuf, 0, sizeof p12ecx->innerBuf); + return SECSuccess; + +loser: + if (innerP7ecx) { + SEC_PKCS7EncoderFinish(innerP7ecx, p12ecx->p12exp->pwfn, + p12ecx->p12exp->pwfnarg); + } + + if (innerA1ecx) { + SEC_ASN1EncoderFinish(innerA1ecx); + } + memset(&p12ecx->innerBuf, 0, sizeof p12ecx->innerBuf); + return SECFailure; +} + +/* finish the HMAC and encode the macData so that it can be + * encoded. + */ +static SECStatus +sec_Pkcs12FinishMac(sec_PKCS12EncoderContext *p12ecx) +{ + unsigned char hmacData[HASH_LENGTH_MAX]; + unsigned int hmacLen; + SECStatus rv; + SGNDigestInfo *di = NULL; + void *dummy; + + if (!p12ecx) { + return SECFailure; + } + + /* make sure we are using password integrity mode */ + if (!p12ecx->p12exp->integrityEnabled) { + return SECSuccess; + } + + if (!p12ecx->p12exp->pwdIntegrity) { + return SECSuccess; + } + + /* finish the hmac */ + + rv = PK11_DigestFinal(p12ecx->hmacCx, hmacData, &hmacLen, HASH_LENGTH_MAX); + + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + /* create the digest info */ + di = SGN_CreateDigestInfo(p12ecx->p12exp->integrityInfo.pwdInfo.algorithm, + hmacData, hmacLen); + if (!di) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + rv = SECFailure; + goto loser; + } + + rv = SGN_CopyDigestInfo(p12ecx->arena, &p12ecx->mac.safeMac, di); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + /* encode the mac data */ + dummy = SEC_ASN1EncodeItem(p12ecx->arena, &p12ecx->pfx.encodedMacData, + &p12ecx->mac, sec_PKCS12MacDataTemplate); + if (!dummy) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + rv = SECFailure; + } + +loser: + if (di) { + SGN_DestroyDigestInfo(di); + } + PK11_DestroyContext(p12ecx->hmacCx, PR_TRUE); + p12ecx->hmacCx = NULL; + PORT_Memset(hmacData, 0, hmacLen); + + return rv; +} + +/* pfx notify function for ASN1 encoder. + * We want to stop encoding once we reach the authenticated safe. + * At that point, the encoder will be updated via streaming + * as the authenticated safe is encoded. + */ +static void +sec_pkcs12_encoder_pfx_notify(void *arg, PRBool before, void *dest, int real_depth) +{ + sec_PKCS12EncoderContext *p12ecx; + + if (!before) { + return; + } + + /* look for authenticated safe */ + p12ecx = (sec_PKCS12EncoderContext *)arg; + if (dest != &p12ecx->pfx.encodedAuthSafe) { + return; + } + + SEC_ASN1EncoderSetTakeFromBuf(p12ecx->outerA1ecx); + SEC_ASN1EncoderSetStreaming(p12ecx->outerA1ecx); + SEC_ASN1EncoderClearNotifyProc(p12ecx->outerA1ecx); +} + +/* SEC_PKCS12Encode + * Encodes the PFX item and returns it to the output function, via + * callback. the output function must be capable of multiple updates. + * + * p12exp - the export context + * output - the output function callback, will be called more than once, + * must be able to accept streaming data. + * outputarg - argument for the output callback. + */ +SECStatus +SEC_PKCS12Encode(SEC_PKCS12ExportContext *p12exp, + SEC_PKCS12EncoderOutputCallback output, void *outputarg) +{ + sec_PKCS12EncoderContext *p12enc; + struct sec_pkcs12_encoder_output outInfo; + SECStatus rv; + + if (!p12exp || !output) { + return SECFailure; + } + + /* get the encoder context */ + p12enc = sec_pkcs12_encoder_start_context(p12exp); + if (!p12enc) { + return SECFailure; + } + + outInfo.outputfn = output; + outInfo.outputarg = outputarg; + + /* set up PFX encoder, the "outer" encoder. Set it for streaming */ + p12enc->outerA1ecx = SEC_ASN1EncoderStart(&p12enc->pfx, + sec_PKCS12PFXItemTemplate, + sec_P12A1OutputCB_Outer, + &outInfo); + if (!p12enc->outerA1ecx) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + rv = SECFailure; + goto loser; + } + SEC_ASN1EncoderSetStreaming(p12enc->outerA1ecx); + SEC_ASN1EncoderSetNotifyProc(p12enc->outerA1ecx, + sec_pkcs12_encoder_pfx_notify, p12enc); + rv = SEC_ASN1EncoderUpdate(p12enc->outerA1ecx, NULL, 0); + if (rv != SECSuccess) { + rv = SECFailure; + goto loser; + } + + /* set up asafe cinfo - the output of the encoder feeds the PFX encoder */ + p12enc->middleP7ecx = SEC_PKCS7EncoderStart(p12enc->aSafeCinfo, + sec_P12P7OutputCB_CallA1Update, + p12enc->outerA1ecx, NULL); + if (!p12enc->middleP7ecx) { + rv = SECFailure; + goto loser; + } + + /* encode asafe */ + p12enc->middleBuf.p7eCx = p12enc->middleP7ecx; + p12enc->middleBuf.hmacCx = NULL; + p12enc->middleBuf.numBytes = 0; + p12enc->middleBuf.bufBytes = sizeof p12enc->middleBuf.buf; + + /* Setup the "inner ASN.1 encoder for Authenticated Safes. */ + if (p12enc->p12exp->integrityEnabled && + p12enc->p12exp->pwdIntegrity) { + p12enc->middleBuf.hmacCx = p12enc->hmacCx; + } + p12enc->middleA1ecx = SEC_ASN1EncoderStart(&p12enc->p12exp->authSafe, + sec_PKCS12AuthenticatedSafeTemplate, + sec_P12A1OutputCB_HmacP7Update, + &p12enc->middleBuf); + if (!p12enc->middleA1ecx) { + rv = SECFailure; + goto loser; + } + SEC_ASN1EncoderSetStreaming(p12enc->middleA1ecx); + SEC_ASN1EncoderSetTakeFromBuf(p12enc->middleA1ecx); + + /* encode each of the safes */ + while (p12enc->currentSafe != p12enc->p12exp->safeInfoCount) { + sec_pkcs12_encoder_asafe_process(p12enc); + p12enc->currentSafe++; + } + SEC_ASN1EncoderClearTakeFromBuf(p12enc->middleA1ecx); + SEC_ASN1EncoderClearStreaming(p12enc->middleA1ecx); + SEC_ASN1EncoderUpdate(p12enc->middleA1ecx, NULL, 0); + SEC_ASN1EncoderFinish(p12enc->middleA1ecx); + p12enc->middleA1ecx = NULL; + + sec_FlushPkcs12OutputBuffer(&p12enc->middleBuf); + + /* finish the encoding of the authenticated safes */ + rv = SEC_PKCS7EncoderFinish(p12enc->middleP7ecx, p12exp->pwfn, + p12exp->pwfnarg); + p12enc->middleP7ecx = NULL; + if (rv != SECSuccess) { + goto loser; + } + + SEC_ASN1EncoderClearTakeFromBuf(p12enc->outerA1ecx); + SEC_ASN1EncoderClearStreaming(p12enc->outerA1ecx); + + /* update the mac, if necessary */ + rv = sec_Pkcs12FinishMac(p12enc); + if (rv != SECSuccess) { + goto loser; + } + + /* finish encoding the pfx */ + rv = SEC_ASN1EncoderUpdate(p12enc->outerA1ecx, NULL, 0); + + SEC_ASN1EncoderFinish(p12enc->outerA1ecx); + p12enc->outerA1ecx = NULL; + +loser: + sec_pkcs12_encoder_destroy_context(p12enc); + return rv; +} + +void +SEC_PKCS12DestroyExportContext(SEC_PKCS12ExportContext *p12ecx) +{ + int i = 0; + + if (!p12ecx) { + return; + } + + if (p12ecx->safeInfos) { + i = 0; + while (p12ecx->safeInfos[i] != NULL) { + if (p12ecx->safeInfos[i]->encryptionKey) { + PK11_FreeSymKey(p12ecx->safeInfos[i]->encryptionKey); + } + if (p12ecx->safeInfos[i]->cinfo) { + SEC_PKCS7DestroyContentInfo(p12ecx->safeInfos[i]->cinfo); + } + i++; + } + } + + PK11_FreeSlot(p12ecx->slot); + + PORT_FreeArena(p12ecx->arena, PR_TRUE); +} diff --git a/security/nss/lib/pkcs12/p12exp.c b/security/nss/lib/pkcs12/p12exp.c new file mode 100644 index 0000000000..b9cb076566 --- /dev/null +++ b/security/nss/lib/pkcs12/p12exp.c @@ -0,0 +1,1374 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "plarena.h" +#include "secitem.h" +#include "secoid.h" +#include "seccomon.h" +#include "secport.h" +#include "cert.h" +#include "pkcs12.h" +#include "p12local.h" +#include "secpkcs7.h" +#include "secasn1.h" +#include "secerr.h" +#include "p12plcy.h" + +/* release the memory taken up by the list of nicknames */ +static void +sec_pkcs12_destroy_nickname_list(SECItem **nicknames) +{ + int i = 0; + + if (nicknames == NULL) { + return; + } + + while (nicknames[i] != NULL) { + SECITEM_FreeItem(nicknames[i], PR_FALSE); + i++; + } + + PORT_Free(nicknames); +} + +/* release the memory taken up by the list of certificates */ +static void +sec_pkcs12_destroy_certificate_list(CERTCertificate **ref_certs) +{ + int i = 0; + + if (ref_certs == NULL) { + return; + } + + while (ref_certs[i] != NULL) { + CERT_DestroyCertificate(ref_certs[i]); + i++; + } +} + +static void +sec_pkcs12_destroy_cinfos_for_cert_bags(SEC_PKCS12CertAndCRLBag *certBag) +{ + int j = 0; + j = 0; + while (certBag->certAndCRLs[j] != NULL) { + SECOidTag certType = SECOID_FindOIDTag(&certBag->certAndCRLs[j]->BagID); + if (certType == SEC_OID_PKCS12_X509_CERT_CRL_BAG) { + SEC_PKCS12X509CertCRL *x509; + x509 = certBag->certAndCRLs[j]->value.x509; + SEC_PKCS7DestroyContentInfo(&x509->certOrCRL); + } + j++; + } +} + +/* destroy all content infos since they were not allocated in common + * pool + */ +static void +sec_pkcs12_destroy_cert_content_infos(SEC_PKCS12SafeContents *safe, + SEC_PKCS12Baggage *baggage) +{ + int i, j; + + if ((safe != NULL) && (safe->contents != NULL)) { + i = 0; + while (safe->contents[i] != NULL) { + SECOidTag bagType = SECOID_FindOIDTag(&safe->contents[i]->safeBagType); + if (bagType == SEC_OID_PKCS12_CERT_AND_CRL_BAG_ID) { + SEC_PKCS12CertAndCRLBag *certBag; + certBag = safe->contents[i]->safeContent.certAndCRLBag; + sec_pkcs12_destroy_cinfos_for_cert_bags(certBag); + } + i++; + } + } + + if ((baggage != NULL) && (baggage->bags != NULL)) { + i = 0; + while (baggage->bags[i] != NULL) { + if (baggage->bags[i]->unencSecrets != NULL) { + j = 0; + while (baggage->bags[i]->unencSecrets[j] != NULL) { + SECOidTag bagType; + bagType = SECOID_FindOIDTag(&baggage->bags[i]->unencSecrets[j]->safeBagType); + if (bagType == SEC_OID_PKCS12_CERT_AND_CRL_BAG_ID) { + SEC_PKCS12CertAndCRLBag *certBag; + certBag = baggage->bags[i]->unencSecrets[j]->safeContent.certAndCRLBag; + sec_pkcs12_destroy_cinfos_for_cert_bags(certBag); + } + j++; + } + } + i++; + } + } +} + +/* convert the nickname list from a NULL termincated Char list + * to a NULL terminated SECItem list + */ +static SECItem ** +sec_pkcs12_convert_nickname_list(char **nicknames) +{ + SECItem **nicks; + int i, j; + PRBool error = PR_FALSE; + + if (nicknames == NULL) { + return NULL; + } + + i = j = 0; + while (nicknames[i] != NULL) { + i++; + } + + /* allocate the space and copy the data */ + nicks = (SECItem **)PORT_ZAlloc(sizeof(SECItem *) * (i + 1)); + if (nicks != NULL) { + for (j = 0; ((j < i) && (error == PR_FALSE)); j++) { + nicks[j] = (SECItem *)PORT_ZAlloc(sizeof(SECItem)); + if (nicks[j] != NULL) { + nicks[j]->data = + (unsigned char *)PORT_ZAlloc(PORT_Strlen(nicknames[j]) + 1); + if (nicks[j]->data != NULL) { + nicks[j]->len = PORT_Strlen(nicknames[j]); + PORT_Memcpy(nicks[j]->data, nicknames[j], nicks[j]->len); + nicks[j]->data[nicks[j]->len] = 0; + } else { + error = PR_TRUE; + } + } else { + error = PR_TRUE; + } + } + } + + if (error == PR_TRUE) { + for (i = 0; i < j; i++) { + SECITEM_FreeItem(nicks[i], PR_TRUE); + } + PORT_Free(nicks); + nicks = NULL; + } + + return nicks; +} + +/* package the certificate add_cert into PKCS12 structures, + * retrieve the certificate chain for the cert and return + * the packaged contents. + * poolp -- common memory pool; + * add_cert -- certificate to package up + * nickname for the certificate + * a return of NULL indicates an error + */ +static SEC_PKCS12CertAndCRL * +sec_pkcs12_get_cert(PLArenaPool *poolp, + CERTCertificate *add_cert, + SECItem *nickname) +{ + SEC_PKCS12CertAndCRL *cert; + SEC_PKCS7ContentInfo *cinfo; + SGNDigestInfo *t_di; + void *mark; + SECStatus rv; + + if ((poolp == NULL) || (add_cert == NULL) || (nickname == NULL)) { + return NULL; + } + mark = PORT_ArenaMark(poolp); + + cert = sec_pkcs12_new_cert_crl(poolp, SEC_OID_PKCS12_X509_CERT_CRL_BAG); + if (cert != NULL) { + + /* copy the nickname */ + rv = SECITEM_CopyItem(poolp, &cert->nickname, nickname); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + cert = NULL; + } else { + + /* package the certificate and cert chain into a NULL signer + * PKCS 7 SignedData content Info and prepare it for encoding + * since we cannot use DER_ANY_TEMPLATE + */ + cinfo = SEC_PKCS7CreateCertsOnly(add_cert, PR_TRUE, NULL); + rv = SEC_PKCS7PrepareForEncode(cinfo, NULL, NULL, NULL); + + /* thumbprint the certificate */ + if ((cinfo != NULL) && (rv == SECSuccess)) { + PORT_Memcpy(&cert->value.x509->certOrCRL, cinfo, sizeof(*cinfo)); + t_di = sec_pkcs12_compute_thumbprint(&add_cert->derCert); + if (t_di != NULL) { + /* test */ + rv = SGN_CopyDigestInfo(poolp, &cert->value.x509->thumbprint, + t_di); + if (rv != SECSuccess) { + cert = NULL; + PORT_SetError(SEC_ERROR_NO_MEMORY); + } + SGN_DestroyDigestInfo(t_di); + } else + cert = NULL; + } + } + } + + if (cert == NULL) { + PORT_ArenaRelease(poolp, mark); + } else { + PORT_ArenaUnmark(poolp, mark); + } + + return cert; +} + +/* package the private key associated with the certificate and + * return the appropriate PKCS 12 structure + * poolp common memory pool + * nickname key nickname + * cert -- cert to look up + * wincx -- window handle + * an error is indicated by a return of NULL + */ +static SEC_PKCS12PrivateKey * +sec_pkcs12_get_private_key(PLArenaPool *poolp, + SECItem *nickname, + CERTCertificate *cert, + void *wincx) +{ + SECKEYPrivateKeyInfo *pki; + SEC_PKCS12PrivateKey *pk; + SECStatus rv; + void *mark; + + if ((poolp == NULL) || (nickname == NULL)) { + return NULL; + } + + mark = PORT_ArenaMark(poolp); + + /* retrieve key from the data base */ + pki = PK11_ExportPrivateKeyInfo(nickname, cert, wincx); + if (pki == NULL) { + PORT_ArenaRelease(poolp, mark); + PORT_SetError(SEC_ERROR_PKCS12_UNABLE_TO_EXPORT_KEY); + return NULL; + } + + pk = (SEC_PKCS12PrivateKey *)PORT_ArenaZAlloc(poolp, + sizeof(SEC_PKCS12PrivateKey)); + if (pk != NULL) { + rv = sec_pkcs12_init_pvk_data(poolp, &pk->pvkData); + + if (rv == SECSuccess) { + /* copy the key into poolp memory space */ + rv = SECKEY_CopyPrivateKeyInfo(poolp, &pk->pkcs8data, pki); + if (rv == SECSuccess) { + rv = SECITEM_CopyItem(poolp, &pk->pvkData.nickname, nickname); + } + } + + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + pk = NULL; + } + } else { + PORT_SetError(SEC_ERROR_NO_MEMORY); + } + + /* destroy private key, zeroing out data */ + SECKEY_DestroyPrivateKeyInfo(pki, PR_TRUE); + if (pk == NULL) { + PORT_ArenaRelease(poolp, mark); + } else { + PORT_ArenaUnmark(poolp, mark); + } + + return pk; +} + +/* get a shrouded key item associated with a certificate + * return the appropriate PKCS 12 structure + * poolp common memory pool + * nickname key nickname + * cert -- cert to look up + * wincx -- window handle + * an error is indicated by a return of NULL + */ +static SEC_PKCS12ESPVKItem * +sec_pkcs12_get_shrouded_key(PLArenaPool *poolp, + SECItem *nickname, + CERTCertificate *cert, + SECOidTag algorithm, + SECItem *pwitem, + PKCS12UnicodeConvertFunction unicodeFn, + void *wincx) +{ + SECKEYEncryptedPrivateKeyInfo *epki; + SEC_PKCS12ESPVKItem *pk; + void *mark; + SECStatus rv; + PK11SlotInfo *slot = NULL; + PRBool swapUnicodeBytes = PR_FALSE; + +#ifdef IS_LITTLE_ENDIAN + swapUnicodeBytes = PR_TRUE; +#endif + + if ((poolp == NULL) || (nickname == NULL)) + return NULL; + + mark = PORT_ArenaMark(poolp); + + /* use internal key slot */ + slot = PK11_GetInternalKeySlot(); + + /* retrieve encrypted prviate key */ + epki = PK11_ExportEncryptedPrivateKeyInfo(slot, algorithm, pwitem, + nickname, cert, 1, 0, NULL); + PK11_FreeSlot(slot); + if (epki == NULL) { + PORT_SetError(SEC_ERROR_PKCS12_UNABLE_TO_EXPORT_KEY); + PORT_ArenaRelease(poolp, mark); + return NULL; + } + + /* create a private key and store the data into the poolp memory space */ + pk = sec_pkcs12_create_espvk(poolp, SEC_OID_PKCS12_PKCS8_KEY_SHROUDING); + if (pk != NULL) { + rv = sec_pkcs12_init_pvk_data(poolp, &pk->espvkData); + rv = SECITEM_CopyItem(poolp, &pk->espvkData.nickname, nickname); + pk->espvkCipherText.pkcs8KeyShroud = + (SECKEYEncryptedPrivateKeyInfo *)PORT_ArenaZAlloc(poolp, + sizeof(SECKEYEncryptedPrivateKeyInfo)); + if ((pk->espvkCipherText.pkcs8KeyShroud != NULL) && (rv == SECSuccess)) { + rv = SECKEY_CopyEncryptedPrivateKeyInfo(poolp, + pk->espvkCipherText.pkcs8KeyShroud, epki); + if (rv == SECSuccess) { + rv = (*unicodeFn)(poolp, &pk->espvkData.uniNickName, nickname, + PR_TRUE, swapUnicodeBytes); + } + } + + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + pk = NULL; + } + } + + SECKEY_DestroyEncryptedPrivateKeyInfo(epki, PR_TRUE); + if (pk == NULL) { + PORT_ArenaRelease(poolp, mark); + } else { + PORT_ArenaUnmark(poolp, mark); + } + + return pk; +} + +/* add a thumbprint to a private key associated certs list + * pvk is the area where the list is stored + * thumb is the thumbprint to copy + * a return of SECFailure indicates an error + */ +static SECStatus +sec_pkcs12_add_thumbprint(SEC_PKCS12PVKSupportingData *pvk, + SGNDigestInfo *thumb) +{ + SGNDigestInfo **thumb_list = NULL; + int nthumbs, size; + void *mark, *dummy; + SECStatus rv = SECFailure; + + if ((pvk == NULL) || (thumb == NULL)) { + return SECFailure; + } + + mark = PORT_ArenaMark(pvk->poolp); + + thumb_list = pvk->assocCerts; + nthumbs = pvk->nThumbs; + + /* allocate list space needed -- either growing or allocating + * list must be NULL terminated + */ + size = sizeof(SGNDigestInfo *); + dummy = PORT_ArenaGrow(pvk->poolp, thumb_list, (size * (nthumbs + 1)), + (size * (nthumbs + 2))); + thumb_list = dummy; + if (dummy != NULL) { + thumb_list[nthumbs] = (SGNDigestInfo *)PORT_ArenaZAlloc(pvk->poolp, + sizeof(SGNDigestInfo)); + if (thumb_list[nthumbs] != NULL) { + SGN_CopyDigestInfo(pvk->poolp, thumb_list[nthumbs], thumb); + nthumbs += 1; + thumb_list[nthumbs] = 0; + } else { + dummy = NULL; + } + } + + if (dummy == NULL) { + PORT_ArenaRelease(pvk->poolp, mark); + return SECFailure; + } + + pvk->assocCerts = thumb_list; + pvk->nThumbs = nthumbs; + + PORT_ArenaUnmark(pvk->poolp, mark); + return SECSuccess; +} + +/* search the list of shrouded keys in the baggage for the desired + * name. return a pointer to the item. a return of NULL indicates + * that no match was present or that an error occurred. + */ +static SEC_PKCS12ESPVKItem * +sec_pkcs12_get_espvk_by_name(SEC_PKCS12Baggage *luggage, + SECItem *name) +{ + PRBool found = PR_FALSE; + SEC_PKCS12ESPVKItem *espvk = NULL; + int i, j; + SECComparison rv = SECEqual; + SECItem *t_name; + SEC_PKCS12BaggageItem *bag; + + if ((luggage == NULL) || (name == NULL)) { + return NULL; + } + + i = 0; + while ((found == PR_FALSE) && (i < luggage->luggage_size)) { + j = 0; + bag = luggage->bags[i]; + while ((found == PR_FALSE) && (j < bag->nEspvks)) { + espvk = bag->espvks[j]; + if (espvk->poolp == NULL) { + espvk->poolp = luggage->poolp; + } + t_name = SECITEM_DupItem(&espvk->espvkData.nickname); + if (t_name != NULL) { + rv = SECITEM_CompareItem(name, t_name); + if (rv == SECEqual) { + found = PR_TRUE; + } + SECITEM_FreeItem(t_name, PR_TRUE); + } else { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return NULL; + } + j++; + } + i++; + } + + if (found != PR_TRUE) { + PORT_SetError(SEC_ERROR_PKCS12_UNABLE_TO_LOCATE_OBJECT_BY_NAME); + return NULL; + } + + return espvk; +} + +/* locates a certificate and copies the thumbprint to the + * appropriate private key + */ +static SECStatus +sec_pkcs12_propagate_thumbprints(SECItem **nicknames, + CERTCertificate **ref_certs, + SEC_PKCS12SafeContents *safe, + SEC_PKCS12Baggage *baggage) +{ + SEC_PKCS12CertAndCRL *cert; + SEC_PKCS12PrivateKey *key; + SEC_PKCS12ESPVKItem *espvk; + int i; + PRBool error = PR_FALSE; + SECStatus rv = SECFailure; + + if ((nicknames == NULL) || (safe == NULL)) { + return SECFailure; + } + + i = 0; + while ((nicknames[i] != NULL) && (error == PR_FALSE)) { + /* process all certs */ + cert = (SEC_PKCS12CertAndCRL *)sec_pkcs12_find_object(safe, baggage, + SEC_OID_PKCS12_CERT_AND_CRL_BAG_ID, + nicknames[i], NULL); + if (cert != NULL) { + /* locate key and copy thumbprint */ + key = (SEC_PKCS12PrivateKey *)sec_pkcs12_find_object(safe, baggage, + SEC_OID_PKCS12_KEY_BAG_ID, + nicknames[i], NULL); + if (key != NULL) { + key->pvkData.poolp = key->poolp; + rv = sec_pkcs12_add_thumbprint(&key->pvkData, + &cert->value.x509->thumbprint); + if (rv == SECFailure) + error = PR_TRUE; /* XXX Set error? */ + } + + /* look in the baggage as well...*/ + if ((baggage != NULL) && (error == PR_FALSE)) { + espvk = sec_pkcs12_get_espvk_by_name(baggage, nicknames[i]); + if (espvk != NULL) { + espvk->espvkData.poolp = espvk->poolp; + rv = sec_pkcs12_add_thumbprint(&espvk->espvkData, + &cert->value.x509->thumbprint); + if (rv == SECFailure) + error = PR_TRUE; /* XXX Set error? */ + } + } + } + i++; + } + + if (error == PR_TRUE) { + return SECFailure; + } + + return SECSuccess; +} + +/* append a safe bag to the end of the safe contents list */ +SECStatus +sec_pkcs12_append_safe_bag(SEC_PKCS12SafeContents *safe, + SEC_PKCS12SafeBag *bag) +{ + int size; + void *mark = NULL, *dummy = NULL; + + if ((bag == NULL) || (safe == NULL)) + return SECFailure; + + mark = PORT_ArenaMark(safe->poolp); + + size = (safe->safe_size * sizeof(SEC_PKCS12SafeBag *)); + + if (safe->safe_size > 0) { + dummy = (SEC_PKCS12SafeBag **)PORT_ArenaGrow(safe->poolp, + safe->contents, + size, + (size + sizeof(SEC_PKCS12SafeBag *))); + safe->contents = dummy; + } else { + safe->contents = (SEC_PKCS12SafeBag **)PORT_ArenaZAlloc(safe->poolp, + (2 * sizeof(SEC_PKCS12SafeBag *))); + dummy = safe->contents; + } + + if (dummy == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + safe->contents[safe->safe_size] = bag; + safe->safe_size++; + safe->contents[safe->safe_size] = NULL; + + PORT_ArenaUnmark(safe->poolp, mark); + return SECSuccess; + +loser: + PORT_ArenaRelease(safe->poolp, mark); + return SECFailure; +} + +/* append a certificate onto the end of a cert bag */ +static SECStatus +sec_pkcs12_append_cert_to_bag(PLArenaPool *arena, + SEC_PKCS12SafeBag *safebag, + CERTCertificate *cert, + SECItem *nickname) +{ + int size; + void *dummy = NULL, *mark = NULL; + SEC_PKCS12CertAndCRL *p12cert; + SEC_PKCS12CertAndCRLBag *bag; + + if ((arena == NULL) || (safebag == NULL) || + (cert == NULL) || (nickname == NULL)) { + return SECFailure; + } + + bag = safebag->safeContent.certAndCRLBag; + if (bag == NULL) { + return SECFailure; + } + + mark = PORT_ArenaMark(arena); + + p12cert = sec_pkcs12_get_cert(arena, cert, nickname); + if (p12cert == NULL) { + PORT_ArenaRelease(bag->poolp, mark); + return SECFailure; + } + + size = bag->bag_size * sizeof(SEC_PKCS12CertAndCRL *); + if (bag->bag_size > 0) { + dummy = (SEC_PKCS12CertAndCRL **)PORT_ArenaGrow(bag->poolp, + bag->certAndCRLs, + size, + size + sizeof(SEC_PKCS12CertAndCRL *)); + bag->certAndCRLs = dummy; + } else { + bag->certAndCRLs = (SEC_PKCS12CertAndCRL **)PORT_ArenaZAlloc(bag->poolp, + (2 * sizeof(SEC_PKCS12CertAndCRL *))); + dummy = bag->certAndCRLs; + } + + if (dummy == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + bag->certAndCRLs[bag->bag_size] = p12cert; + bag->bag_size++; + bag->certAndCRLs[bag->bag_size] = NULL; + + PORT_ArenaUnmark(bag->poolp, mark); + return SECSuccess; + +loser: + PORT_ArenaRelease(bag->poolp, mark); + return SECFailure; +} + +/* append a key onto the end of a list of keys in a key bag */ +SECStatus +sec_pkcs12_append_key_to_bag(SEC_PKCS12SafeBag *safebag, + SEC_PKCS12PrivateKey *pk) +{ + void *mark, *dummy; + SEC_PKCS12PrivateKeyBag *bag; + int size; + + if ((safebag == NULL) || (pk == NULL)) + return SECFailure; + + bag = safebag->safeContent.keyBag; + if (bag == NULL) { + return SECFailure; + } + + mark = PORT_ArenaMark(bag->poolp); + + size = (bag->bag_size * sizeof(SEC_PKCS12PrivateKey *)); + + if (bag->bag_size > 0) { + dummy = (SEC_PKCS12PrivateKey **)PORT_ArenaGrow(bag->poolp, + bag->privateKeys, + size, + size + sizeof(SEC_PKCS12PrivateKey *)); + bag->privateKeys = dummy; + } else { + bag->privateKeys = (SEC_PKCS12PrivateKey **)PORT_ArenaZAlloc(bag->poolp, + (2 * sizeof(SEC_PKCS12PrivateKey *))); + dummy = bag->privateKeys; + } + + if (dummy == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + bag->privateKeys[bag->bag_size] = pk; + bag->bag_size++; + bag->privateKeys[bag->bag_size] = NULL; + + PORT_ArenaUnmark(bag->poolp, mark); + return SECSuccess; + +loser: + /* XXX Free memory? */ + PORT_ArenaRelease(bag->poolp, mark); + return SECFailure; +} + +/* append a safe bag to the baggage area */ +static SECStatus +sec_pkcs12_append_unshrouded_bag(SEC_PKCS12BaggageItem *bag, + SEC_PKCS12SafeBag *u_bag) +{ + int size; + void *mark = NULL, *dummy = NULL; + + if ((bag == NULL) || (u_bag == NULL)) + return SECFailure; + + mark = PORT_ArenaMark(bag->poolp); + + /* dump things into the first bag */ + size = (bag->nSecrets + 1) * sizeof(SEC_PKCS12SafeBag *); + dummy = PORT_ArenaGrow(bag->poolp, + bag->unencSecrets, size, + size + sizeof(SEC_PKCS12SafeBag *)); + bag->unencSecrets = dummy; + if (dummy == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + bag->unencSecrets[bag->nSecrets] = u_bag; + bag->nSecrets++; + bag->unencSecrets[bag->nSecrets] = NULL; + + PORT_ArenaUnmark(bag->poolp, mark); + return SECSuccess; + +loser: + PORT_ArenaRelease(bag->poolp, mark); + return SECFailure; +} + +/* gather up all certificates and keys and package them up + * in the safe, baggage, or both. + * nicknames is the list of nicknames and corresponding certs in ref_certs + * ref_certs a null terminated list of certificates + * rSafe, rBaggage -- return areas for safe and baggage + * shroud_keys -- store keys externally + * pwitem -- password for computing integrity mac and encrypting contents + * wincx -- window handle + * + * if a failure occurs, an error is set and SECFailure returned. + */ +static SECStatus +sec_pkcs12_package_certs_and_keys(SECItem **nicknames, + CERTCertificate **ref_certs, + PRBool unencryptedCerts, + SEC_PKCS12SafeContents **rSafe, + SEC_PKCS12Baggage **rBaggage, + PRBool shroud_keys, + SECOidTag shroud_alg, + SECItem *pwitem, + PKCS12UnicodeConvertFunction unicodeFn, + void *wincx) +{ + PLArenaPool *permArena; + SEC_PKCS12SafeContents *safe = NULL; + SEC_PKCS12Baggage *baggage = NULL; + + SECStatus rv = SECFailure; + PRBool problem = PR_FALSE; + + SEC_PKCS12ESPVKItem *espvk = NULL; + SEC_PKCS12PrivateKey *pk = NULL; + CERTCertificate *add_cert = NULL; + SEC_PKCS12SafeBag *certbag = NULL, *keybag = NULL; + SEC_PKCS12BaggageItem *external_bag = NULL; + int ncerts = 0, nkeys = 0; + int i; + + if ((nicknames == NULL) || (rSafe == NULL) || (rBaggage == NULL)) { + return SECFailure; + } + + *rBaggage = baggage; + *rSafe = safe; + + permArena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE); + if (permArena == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return SECFailure; + } + + /* allocate structures */ + safe = sec_pkcs12_create_safe_contents(permArena); + if (safe == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + rv = SECFailure; + goto loser; + } + + certbag = sec_pkcs12_create_safe_bag(permArena, + SEC_OID_PKCS12_CERT_AND_CRL_BAG_ID); + if (certbag == NULL) { + rv = SECFailure; + goto loser; + } + + if (shroud_keys != PR_TRUE) { + keybag = sec_pkcs12_create_safe_bag(permArena, + SEC_OID_PKCS12_KEY_BAG_ID); + if (keybag == NULL) { + rv = SECFailure; + goto loser; + } + } + + if ((shroud_keys == PR_TRUE) || (unencryptedCerts == PR_TRUE)) { + baggage = sec_pkcs12_create_baggage(permArena); + if (baggage == NULL) { + rv = SECFailure; + goto loser; + } + external_bag = sec_pkcs12_create_external_bag(baggage); + } + + /* package keys and certs */ + i = 0; + while ((nicknames[i] != NULL) && (problem == PR_FALSE)) { + if (ref_certs[i] != NULL) { + /* append cert to bag o certs */ + rv = sec_pkcs12_append_cert_to_bag(permArena, certbag, + ref_certs[i], + nicknames[i]); + if (rv == SECFailure) { + problem = PR_FALSE; + } else { + ncerts++; + } + + if (rv == SECSuccess) { + /* package up them keys */ + if (shroud_keys == PR_TRUE) { + espvk = sec_pkcs12_get_shrouded_key(permArena, + nicknames[i], + ref_certs[i], + shroud_alg, + pwitem, unicodeFn, + wincx); + if (espvk != NULL) { + rv = sec_pkcs12_append_shrouded_key(external_bag, espvk); + SECITEM_CopyItem(permArena, &espvk->derCert, + &ref_certs[i]->derCert); + } else { + rv = SECFailure; + } + } else { + pk = sec_pkcs12_get_private_key(permArena, nicknames[i], + ref_certs[i], wincx); + if (pk != NULL) { + rv = sec_pkcs12_append_key_to_bag(keybag, pk); + SECITEM_CopyItem(permArena, &espvk->derCert, + &ref_certs[i]->derCert); + } else { + rv = SECFailure; + } + } + + if (rv == SECFailure) { + problem = PR_TRUE; + } else { + nkeys++; + } + } + } else { + /* handle only keys here ? */ + problem = PR_TRUE; + } + i++; + } + +/* let success fall through */ +loser: + if (problem == PR_FALSE) { + /* if we have certs, we want to append the cert bag to the + * appropriate area + */ + if (ncerts > 0) { + if (unencryptedCerts != PR_TRUE) { + rv = sec_pkcs12_append_safe_bag(safe, certbag); + } else { + rv = sec_pkcs12_append_unshrouded_bag(external_bag, certbag); + } + } else { + rv = SECSuccess; + } + + /* append key bag, if they are stored in safe contents */ + if ((rv == SECSuccess) && (shroud_keys == PR_FALSE) && (nkeys > 0)) { + rv = sec_pkcs12_append_safe_bag(safe, keybag); + } + } else { + rv = SECFailure; + } + + /* if baggage not used, NULLify it */ + if ((shroud_keys == PR_TRUE) || (unencryptedCerts == PR_TRUE)) { + if (((unencryptedCerts == PR_TRUE) && (ncerts == 0)) && + ((shroud_keys == PR_TRUE) && (nkeys == 0))) + baggage = NULL; + } else { + baggage = NULL; + } + + if ((problem == PR_TRUE) || (rv == SECFailure)) { + PORT_FreeArena(permArena, PR_TRUE); + rv = SECFailure; + baggage = NULL; + safe = NULL; + } + + *rBaggage = baggage; + *rSafe = safe; + + return rv; +} + +/* DER encode the safe contents and return a SECItem. if an error + * occurs, NULL is returned. + */ +static SECItem * +sec_pkcs12_encode_safe_contents(SEC_PKCS12SafeContents *safe) +{ + SECItem *dsafe = NULL, *tsafe; + void *dummy = NULL; + PLArenaPool *arena; + + if (safe == NULL) { + return NULL; + } + + /* rv = sec_pkcs12_prepare_for_der_code_safe(safe, PR_TRUE); + if(rv != SECSuccess) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return NULL; + }*/ + + arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE); + if (arena == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return NULL; + } + + tsafe = (SECItem *)PORT_ArenaZAlloc(arena, sizeof(SECItem)); + if (tsafe != NULL) { + dummy = SEC_ASN1EncodeItem(arena, tsafe, safe, + SEC_PKCS12SafeContentsTemplate); + if (dummy != NULL) { + dsafe = SECITEM_DupItem(tsafe); + } else { + PORT_SetError(SEC_ERROR_NO_MEMORY); + } + } else { + PORT_SetError(SEC_ERROR_NO_MEMORY); + } + + PORT_FreeArena(arena, PR_TRUE); + + return dsafe; +} + +/* prepare the authenicated safe for encoding and encode it. + * baggage is copied to the appropriate area, safe is encoded and + * encrypted. the version and transport mode are set on the asafe. + * the whole ball of wax is then der encoded and packaged up into + * data content info + * safe -- container of certs and keys, is encrypted. + * baggage -- container of certs and keys, keys assumed to be encrypted by + * another method, certs are in the clear + * algorithm -- algorithm by which to encrypt safe + * pwitem -- password for encryption + * wincx - window handle + * + * return of NULL is an error condition. + */ +static SEC_PKCS7ContentInfo * +sec_pkcs12_get_auth_safe(SEC_PKCS12SafeContents *safe, + SEC_PKCS12Baggage *baggage, + SECOidTag algorithm, + SECItem *pwitem, + PKCS12UnicodeConvertFunction unicodeFn, + void *wincx) +{ + SECItem *src = NULL, *dest = NULL, *psalt = NULL; + PLArenaPool *poolp; + SEC_PKCS12AuthenticatedSafe *asafe; + SEC_PKCS7ContentInfo *safe_cinfo = NULL; + SEC_PKCS7ContentInfo *asafe_cinfo = NULL; + void *dummy; + SECStatus rv = SECSuccess; + PRBool swapUnicodeBytes = PR_FALSE; + +#ifdef IS_LITTLE_ENDIAN + swapUnicodeBytes = PR_TRUE; +#endif + + if (((safe != NULL) && (pwitem == NULL)) && (baggage == NULL)) + return NULL; + + poolp = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE); + if (poolp == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return NULL; + } + + /* prepare authenticated safe for encode */ + asafe = sec_pkcs12_new_asafe(poolp); + if (asafe != NULL) { + + /* set version */ + dummy = SEC_ASN1EncodeInteger(asafe->poolp, &asafe->version, + SEC_PKCS12_PFX_VERSION); + if (dummy == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + rv = SECFailure; + goto loser; + } + + /* generate the privacy salt used to create virtual pwd */ + psalt = sec_pkcs12_generate_salt(); + if (psalt != NULL) { + rv = SECITEM_CopyItem(asafe->poolp, &asafe->privacySalt, + psalt); + if (rv == SECSuccess) { + asafe->privacySalt.len *= 8; + } else { + SECITEM_ZfreeItem(psalt, PR_TRUE); + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + } + + if ((psalt == NULL) || (rv == SECFailure)) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + rv = SECFailure; + goto loser; + } + + /* package up safe contents */ + if (safe != NULL) { + safe_cinfo = SEC_PKCS7CreateEncryptedData(algorithm, NULL, wincx); + if ((safe_cinfo != NULL) && (safe->safe_size > 0)) { + /* encode the safe and encrypt the contents of the + * content info + */ + src = sec_pkcs12_encode_safe_contents(safe); + + if (src != NULL) { + rv = SEC_PKCS7SetContent(safe_cinfo, (char *)src->data, src->len); + SECITEM_ZfreeItem(src, PR_TRUE); + if (rv == SECSuccess) { + SECItem *vpwd; + vpwd = sec_pkcs12_create_virtual_password(pwitem, psalt, + unicodeFn, swapUnicodeBytes); + if (vpwd != NULL) { + rv = SEC_PKCS7EncryptContents(NULL, safe_cinfo, + vpwd, wincx); + SECITEM_ZfreeItem(vpwd, PR_TRUE); + } else { + rv = SECFailure; + PORT_SetError(SEC_ERROR_NO_MEMORY); + } + } else { + PORT_SetError(SEC_ERROR_NO_MEMORY); + } + } else { + rv = SECFailure; + } + } else if (safe->safe_size > 0) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } else { + /* case where there is NULL content in the safe contents */ + rv = SEC_PKCS7SetContent(safe_cinfo, NULL, 0); + if (rv != SECFailure) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + } + } + + if (rv != SECSuccess) { + SEC_PKCS7DestroyContentInfo(safe_cinfo); + safe_cinfo = NULL; + goto loser; + } + + asafe->safe = safe_cinfo; + /* + PORT_Memcpy(&asafe->safe, safe_cinfo, sizeof(*safe_cinfo)); + */ + } + + /* copy the baggage to the authenticated safe baggage if present */ + if (baggage != NULL) { + PORT_Memcpy(&asafe->baggage, baggage, sizeof(*baggage)); + } + + /* encode authenticated safe and store it in a Data content info */ + dest = (SECItem *)PORT_ArenaZAlloc(poolp, sizeof(SECItem)); + if (dest != NULL) { + dummy = SEC_ASN1EncodeItem(poolp, dest, asafe, + SEC_PKCS12AuthenticatedSafeTemplate); + if (dummy != NULL) { + asafe_cinfo = SEC_PKCS7CreateData(); + if (asafe_cinfo != NULL) { + rv = SEC_PKCS7SetContent(asafe_cinfo, + (char *)dest->data, + dest->len); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + SEC_PKCS7DestroyContentInfo(asafe_cinfo); + asafe_cinfo = NULL; + } + } + } else { + PORT_SetError(SEC_ERROR_NO_MEMORY); + rv = SECFailure; + } + } + } + +loser: + PORT_FreeArena(poolp, PR_TRUE); + if (safe_cinfo != NULL) { + SEC_PKCS7DestroyContentInfo(safe_cinfo); + } + if (psalt != NULL) { + SECITEM_ZfreeItem(psalt, PR_TRUE); + } + + if (rv == SECFailure) { + return NULL; + } + + return asafe_cinfo; +} + +/* generates the PFX and computes the mac on the authenticated safe + * NULL implies an error + */ +static SEC_PKCS12PFXItem * +sec_pkcs12_get_pfx(SEC_PKCS7ContentInfo *cinfo, + PRBool do_mac, + SECItem *pwitem, PKCS12UnicodeConvertFunction unicodeFn) +{ + SECItem *dest = NULL, *mac = NULL, *salt = NULL, *key = NULL; + SEC_PKCS12PFXItem *pfx; + SECStatus rv = SECFailure; + SGNDigestInfo *di; + SECItem *vpwd; + PRBool swapUnicodeBytes = PR_FALSE; + +#ifdef IS_LITTLE_ENDIAN + swapUnicodeBytes = PR_TRUE; +#endif + + if ((cinfo == NULL) || ((do_mac == PR_TRUE) && (pwitem == NULL))) { + return NULL; + } + + /* allocate new pfx structure */ + pfx = sec_pkcs12_new_pfx(); + if (pfx == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return NULL; + } + + PORT_Memcpy(&pfx->authSafe, cinfo, sizeof(*cinfo)); + if (do_mac == PR_TRUE) { + + /* salt for computing mac */ + salt = sec_pkcs12_generate_salt(); + if (salt != NULL) { + rv = SECITEM_CopyItem(pfx->poolp, &pfx->macData.macSalt, salt); + pfx->macData.macSalt.len *= 8; + + vpwd = sec_pkcs12_create_virtual_password(pwitem, salt, + unicodeFn, swapUnicodeBytes); + if (vpwd == NULL) { + rv = SECFailure; + key = NULL; + } else { + key = sec_pkcs12_generate_key_from_password(SEC_OID_SHA1, + salt, vpwd); + SECITEM_ZfreeItem(vpwd, PR_TRUE); + } + + if ((key != NULL) && (rv == SECSuccess)) { + dest = SEC_PKCS7GetContent(cinfo); + if (dest != NULL) { + + /* compute mac on data -- for password integrity mode */ + mac = sec_pkcs12_generate_mac(key, dest, PR_FALSE); + if (mac != NULL) { + di = SGN_CreateDigestInfo(SEC_OID_SHA1, + mac->data, mac->len); + if (di != NULL) { + rv = SGN_CopyDigestInfo(pfx->poolp, + &pfx->macData.safeMac, di); + SGN_DestroyDigestInfo(di); + } else { + PORT_SetError(SEC_ERROR_NO_MEMORY); + } + SECITEM_ZfreeItem(mac, PR_TRUE); + } + } else { + rv = SECFailure; + } + } else { + PORT_SetError(SEC_ERROR_NO_MEMORY); + rv = SECFailure; + } + + if (key != NULL) { + SECITEM_ZfreeItem(key, PR_TRUE); + } + SECITEM_ZfreeItem(salt, PR_TRUE); + } + } + + if (rv == SECFailure) { + SEC_PKCS12DestroyPFX(pfx); + pfx = NULL; + } + + return pfx; +} + +/* der encode the pfx */ +static SECItem * +sec_pkcs12_encode_pfx(SEC_PKCS12PFXItem *pfx) +{ + SECItem *dest; + void *dummy; + + if (pfx == NULL) { + return NULL; + } + + dest = (SECItem *)PORT_ZAlloc(sizeof(SECItem)); + if (dest == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return NULL; + } + + dummy = SEC_ASN1EncodeItem(NULL, dest, pfx, SEC_PKCS12PFXItemTemplate); + if (dummy == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + SECITEM_ZfreeItem(dest, PR_TRUE); + dest = NULL; + } + + return dest; +} + +SECItem * +SEC_PKCS12GetPFX(char **nicknames, + CERTCertificate **ref_certs, + PRBool shroud_keys, + SEC_PKCS5GetPBEPassword pbef, + void *pbearg, + PKCS12UnicodeConvertFunction unicodeFn, + void *wincx) +{ + SECItem **nicks = NULL; + SEC_PKCS12PFXItem *pfx = NULL; + SEC_PKCS12Baggage *baggage = NULL; + SEC_PKCS12SafeContents *safe = NULL; + SEC_PKCS7ContentInfo *cinfo = NULL; + SECStatus rv = SECFailure; + SECItem *dest = NULL, *pwitem = NULL; + PRBool problem = PR_FALSE; + PRBool unencryptedCerts; + SECOidTag shroud_alg, safe_alg; + + /* how should we encrypt certs ? */ + unencryptedCerts = !SEC_PKCS12IsEncryptionAllowed(); + if (!unencryptedCerts) { + safe_alg = SEC_PKCS12GetPreferredEncryptionAlgorithm(); + if (safe_alg == SEC_OID_UNKNOWN) { + safe_alg = SEC_PKCS12GetStrongestAllowedAlgorithm(); + } + if (safe_alg == SEC_OID_UNKNOWN) { + unencryptedCerts = PR_TRUE; + /* for export where no encryption is allowed, we still need + * to encrypt the NULL contents per the spec. encrypted info + * is known plaintext, so it shouldn't be a problem. + */ + safe_alg = SEC_OID_PKCS12_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC; + } + } else { + /* for export where no encryption is allowed, we still need + * to encrypt the NULL contents per the spec. encrypted info + * is known plaintext, so it shouldn't be a problem. + */ + safe_alg = SEC_OID_PKCS12_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC; + } + + /* keys are always stored with triple DES */ + shroud_alg = SEC_OID_PKCS12_PBE_WITH_SHA1_AND_TRIPLE_DES_CBC; + + /* check for FIPS, if so, do not encrypt certs */ + if (PK11_IsFIPS() && !unencryptedCerts) { + unencryptedCerts = PR_TRUE; + } + + if ((nicknames == NULL) || (pbef == NULL) || (ref_certs == NULL)) { + problem = PR_TRUE; + goto loser; + } + + /* get password */ + pwitem = (*pbef)(pbearg); + if (pwitem == NULL) { + problem = PR_TRUE; + goto loser; + } + nicks = sec_pkcs12_convert_nickname_list(nicknames); + + /* get safe and baggage */ + rv = sec_pkcs12_package_certs_and_keys(nicks, ref_certs, unencryptedCerts, + &safe, &baggage, shroud_keys, + shroud_alg, pwitem, unicodeFn, wincx); + if (rv == SECFailure) { + problem = PR_TRUE; + } + + if ((safe != NULL) && (problem == PR_FALSE)) { + /* copy thumbprints */ + rv = sec_pkcs12_propagate_thumbprints(nicks, ref_certs, safe, baggage); + + /* package everything up into AuthenticatedSafe */ + cinfo = sec_pkcs12_get_auth_safe(safe, baggage, + safe_alg, pwitem, unicodeFn, wincx); + + sec_pkcs12_destroy_cert_content_infos(safe, baggage); + + /* get the pfx and mac it */ + if (cinfo != NULL) { + pfx = sec_pkcs12_get_pfx(cinfo, PR_TRUE, pwitem, unicodeFn); + if (pfx != NULL) { + dest = sec_pkcs12_encode_pfx(pfx); + SEC_PKCS12DestroyPFX(pfx); + } + SEC_PKCS7DestroyContentInfo(cinfo); + } + + if (safe != NULL) { + PORT_FreeArena(safe->poolp, PR_TRUE); + } + } else { + if (safe != NULL) { + PORT_FreeArena(safe->poolp, PR_TRUE); + } + } + +loser: + if (nicks != NULL) { + sec_pkcs12_destroy_nickname_list(nicks); + } + + if (ref_certs != NULL) { + sec_pkcs12_destroy_certificate_list(ref_certs); + } + + if (pwitem != NULL) { + SECITEM_ZfreeItem(pwitem, PR_TRUE); + } + + if (problem == PR_TRUE) { + dest = NULL; + } + + return dest; +} diff --git a/security/nss/lib/pkcs12/p12local.c b/security/nss/lib/pkcs12/p12local.c new file mode 100644 index 0000000000..f64448664d --- /dev/null +++ b/security/nss/lib/pkcs12/p12local.c @@ -0,0 +1,1417 @@ +/* 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 "pkcs12.h" +#include "secpkcs7.h" +#include "secasn1.h" +#include "seccomon.h" +#include "secoid.h" +#include "sechash.h" +#include "secitem.h" +#include "secerr.h" +#include "pk11func.h" +#include "p12local.h" +#include "p12.h" + +#define SALT_LENGTH 16 + +SEC_ASN1_MKSUB(SECKEY_PrivateKeyInfoTemplate) +SEC_ASN1_MKSUB(sgn_DigestInfoTemplate) + +CK_MECHANISM_TYPE +sec_pkcs12_algtag_to_mech(SECOidTag algtag) +{ + switch (algtag) { + case SEC_OID_MD2: + return CKM_MD2_HMAC; + case SEC_OID_MD5: + return CKM_MD5_HMAC; + case SEC_OID_SHA1: + return CKM_SHA_1_HMAC; + case SEC_OID_SHA224: + return CKM_SHA224_HMAC; + case SEC_OID_SHA256: + return CKM_SHA256_HMAC; + case SEC_OID_SHA384: + return CKM_SHA384_HMAC; + case SEC_OID_SHA512: + return CKM_SHA512_HMAC; + default: + break; + } + return CKM_INVALID_MECHANISM; +} + +CK_MECHANISM_TYPE +sec_pkcs12_algtag_to_keygen_mech(SECOidTag algtag) +{ + switch (algtag) { + case SEC_OID_SHA1: + return CKM_NSS_PBE_SHA1_HMAC_KEY_GEN; + break; + case SEC_OID_MD5: + return CKM_NSS_PBE_MD5_HMAC_KEY_GEN; + break; + case SEC_OID_MD2: + return CKM_NSS_PBE_MD2_HMAC_KEY_GEN; + break; + case SEC_OID_SHA224: + return CKM_NSS_PKCS12_PBE_SHA224_HMAC_KEY_GEN; + break; + case SEC_OID_SHA256: + return CKM_NSS_PKCS12_PBE_SHA256_HMAC_KEY_GEN; + break; + case SEC_OID_SHA384: + return CKM_NSS_PKCS12_PBE_SHA384_HMAC_KEY_GEN; + break; + case SEC_OID_SHA512: + return CKM_NSS_PKCS12_PBE_SHA512_HMAC_KEY_GEN; + break; + default: + break; + } + return CKM_INVALID_MECHANISM; +} + +/* helper functions */ +/* returns proper bag type template based upon object type tag */ +const SEC_ASN1Template * +sec_pkcs12_choose_bag_type_old(void *src_or_dest, PRBool encoding) +{ + const SEC_ASN1Template *theTemplate; + SEC_PKCS12SafeBag *safebag; + SECOidData *oiddata; + + if (src_or_dest == NULL) { + return NULL; + } + + safebag = (SEC_PKCS12SafeBag *)src_or_dest; + + oiddata = safebag->safeBagTypeTag; + if (oiddata == NULL) { + oiddata = SECOID_FindOID(&safebag->safeBagType); + safebag->safeBagTypeTag = oiddata; + } + + switch (oiddata->offset) { + default: + theTemplate = SEC_ASN1_GET(SEC_PointerToAnyTemplate); + break; + case SEC_OID_PKCS12_KEY_BAG_ID: + theTemplate = SEC_PointerToPKCS12KeyBagTemplate; + break; + case SEC_OID_PKCS12_CERT_AND_CRL_BAG_ID: + theTemplate = SEC_PointerToPKCS12CertAndCRLBagTemplate_OLD; + break; + case SEC_OID_PKCS12_SECRET_BAG_ID: + theTemplate = SEC_PointerToPKCS12SecretBagTemplate; + break; + } + return theTemplate; +} + +const SEC_ASN1Template * +sec_pkcs12_choose_bag_type(void *src_or_dest, PRBool encoding) +{ + const SEC_ASN1Template *theTemplate; + SEC_PKCS12SafeBag *safebag; + SECOidData *oiddata; + + if (src_or_dest == NULL) { + return NULL; + } + + safebag = (SEC_PKCS12SafeBag *)src_or_dest; + + oiddata = safebag->safeBagTypeTag; + if (oiddata == NULL) { + oiddata = SECOID_FindOID(&safebag->safeBagType); + safebag->safeBagTypeTag = oiddata; + } + + switch (oiddata->offset) { + default: + theTemplate = SEC_ASN1_GET(SEC_AnyTemplate); + break; + case SEC_OID_PKCS12_KEY_BAG_ID: + theTemplate = SEC_PKCS12PrivateKeyBagTemplate; + break; + case SEC_OID_PKCS12_CERT_AND_CRL_BAG_ID: + theTemplate = SEC_PKCS12CertAndCRLBagTemplate; + break; + case SEC_OID_PKCS12_SECRET_BAG_ID: + theTemplate = SEC_PKCS12SecretBagTemplate; + break; + } + return theTemplate; +} + +/* returns proper cert crl template based upon type tag */ +const SEC_ASN1Template * +sec_pkcs12_choose_cert_crl_type_old(void *src_or_dest, PRBool encoding) +{ + const SEC_ASN1Template *theTemplate; + SEC_PKCS12CertAndCRL *certbag; + SECOidData *oiddata; + + if (src_or_dest == NULL) { + return NULL; + } + + certbag = (SEC_PKCS12CertAndCRL *)src_or_dest; + oiddata = certbag->BagTypeTag; + if (oiddata == NULL) { + oiddata = SECOID_FindOID(&certbag->BagID); + certbag->BagTypeTag = oiddata; + } + + switch (oiddata->offset) { + default: + theTemplate = SEC_ASN1_GET(SEC_PointerToAnyTemplate); + break; + case SEC_OID_PKCS12_X509_CERT_CRL_BAG: + theTemplate = SEC_PointerToPKCS12X509CertCRLTemplate_OLD; + break; + case SEC_OID_PKCS12_SDSI_CERT_BAG: + theTemplate = SEC_PointerToPKCS12SDSICertTemplate; + break; + } + return theTemplate; +} + +const SEC_ASN1Template * +sec_pkcs12_choose_cert_crl_type(void *src_or_dest, PRBool encoding) +{ + const SEC_ASN1Template *theTemplate; + SEC_PKCS12CertAndCRL *certbag; + SECOidData *oiddata; + + if (src_or_dest == NULL) { + return NULL; + } + + certbag = (SEC_PKCS12CertAndCRL *)src_or_dest; + oiddata = certbag->BagTypeTag; + if (oiddata == NULL) { + oiddata = SECOID_FindOID(&certbag->BagID); + certbag->BagTypeTag = oiddata; + } + + switch (oiddata->offset) { + default: + theTemplate = SEC_ASN1_GET(SEC_PointerToAnyTemplate); + break; + case SEC_OID_PKCS12_X509_CERT_CRL_BAG: + theTemplate = SEC_PointerToPKCS12X509CertCRLTemplate; + break; + case SEC_OID_PKCS12_SDSI_CERT_BAG: + theTemplate = SEC_PointerToPKCS12SDSICertTemplate; + break; + } + return theTemplate; +} + +/* returns appropriate shroud template based on object type tag */ +const SEC_ASN1Template * +sec_pkcs12_choose_shroud_type(void *src_or_dest, PRBool encoding) +{ + const SEC_ASN1Template *theTemplate; + SEC_PKCS12ESPVKItem *espvk; + SECOidData *oiddata; + + if (src_or_dest == NULL) { + return NULL; + } + + espvk = (SEC_PKCS12ESPVKItem *)src_or_dest; + oiddata = espvk->espvkTag; + if (oiddata == NULL) { + oiddata = SECOID_FindOID(&espvk->espvkOID); + espvk->espvkTag = oiddata; + } + + switch (oiddata->offset) { + default: + theTemplate = SEC_ASN1_GET(SEC_PointerToAnyTemplate); + break; + case SEC_OID_PKCS12_PKCS8_KEY_SHROUDING: + theTemplate = + SEC_ASN1_GET(SECKEY_PointerToEncryptedPrivateKeyInfoTemplate); + break; + } + return theTemplate; +} + +/* generate SALT placing it into the character array passed in. + * it is assumed that salt_dest is an array of appropriate size + * XXX We might want to generate our own random context + */ +SECItem * +sec_pkcs12_generate_salt(void) +{ + SECItem *salt; + + salt = (SECItem *)PORT_ZAlloc(sizeof(SECItem)); + if (salt == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return NULL; + } + salt->data = (unsigned char *)PORT_ZAlloc(sizeof(unsigned char) * + SALT_LENGTH); + salt->len = SALT_LENGTH; + if (salt->data == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + SECITEM_ZfreeItem(salt, PR_TRUE); + return NULL; + } + + PK11_GenerateRandom(salt->data, salt->len); + + return salt; +} + +/* generate KEYS -- as per PKCS12 section 7. + * only used for MAC + */ +SECItem * +sec_pkcs12_generate_key_from_password(SECOidTag algorithm, + SECItem *salt, + SECItem *password) +{ + unsigned char *pre_hash = NULL; + unsigned char *hash_dest = NULL; + SECStatus res; + PLArenaPool *poolp; + SECItem *key = NULL; + int key_len = 0; + + if ((salt == NULL) || (password == NULL)) { + return NULL; + } + + poolp = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (poolp == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return NULL; + } + + pre_hash = (unsigned char *)PORT_ArenaZAlloc(poolp, sizeof(char) * (salt->len + password->len)); + if (pre_hash == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + hash_dest = (unsigned char *)PORT_ArenaZAlloc(poolp, + sizeof(unsigned char) * SHA1_LENGTH); + if (hash_dest == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + PORT_Memcpy(pre_hash, salt->data, salt->len); + /* handle password of 0 length case */ + if (password->len > 0) { + PORT_Memcpy(&(pre_hash[salt->len]), password->data, password->len); + } + + res = PK11_HashBuf(SEC_OID_SHA1, hash_dest, pre_hash, + (salt->len + password->len)); + if (res == SECFailure) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + switch (algorithm) { + case SEC_OID_SHA1: + if (key_len == 0) + key_len = 16; + key = (SECItem *)PORT_ZAlloc(sizeof(SECItem)); + if (key == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + key->data = (unsigned char *)PORT_ZAlloc(sizeof(unsigned char) * key_len); + if (key->data == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + key->len = key_len; + PORT_Memcpy(key->data, &hash_dest[SHA1_LENGTH - key->len], key->len); + break; + default: + goto loser; + break; + } + + PORT_FreeArena(poolp, PR_TRUE); + return key; + +loser: + PORT_FreeArena(poolp, PR_TRUE); + if (key != NULL) { + SECITEM_ZfreeItem(key, PR_TRUE); + } + return NULL; +} + +/* MAC is generated per PKCS 12 section 6. It is expected that key, msg + * and mac_dest are pre allocated, non-NULL arrays. msg_len is passed in + * because it is not known how long the message actually is. String + * manipulation routines will not necessarily work because msg may have + * imbedded NULLs + */ +static SECItem * +sec_pkcs12_generate_old_mac(SECItem *key, + SECItem *msg) +{ + SECStatus res; + PLArenaPool *temparena = NULL; + unsigned char *hash_dest = NULL, *hash_src1 = NULL, *hash_src2 = NULL; + int i; + SECItem *mac = NULL; + + if ((key == NULL) || (msg == NULL)) + goto loser; + + /* allocate return item */ + mac = (SECItem *)PORT_ZAlloc(sizeof(SECItem)); + if (mac == NULL) + return NULL; + mac->data = (unsigned char *)PORT_ZAlloc(sizeof(unsigned char) * SHA1_LENGTH); + mac->len = SHA1_LENGTH; + if (mac->data == NULL) + goto loser; + + /* allocate temporary items */ + temparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (temparena == NULL) + goto loser; + + hash_src1 = (unsigned char *)PORT_ArenaZAlloc(temparena, + sizeof(unsigned char) * (16 + msg->len)); + if (hash_src1 == NULL) + goto loser; + + hash_src2 = (unsigned char *)PORT_ArenaZAlloc(temparena, + sizeof(unsigned char) * (SHA1_LENGTH + 16)); + if (hash_src2 == NULL) + goto loser; + + hash_dest = (unsigned char *)PORT_ArenaZAlloc(temparena, + sizeof(unsigned char) * SHA1_LENGTH); + if (hash_dest == NULL) + goto loser; + + /* perform mac'ing as per PKCS 12 */ + + /* first round of hashing */ + for (i = 0; i < 16; i++) + hash_src1[i] = key->data[i] ^ 0x36; + PORT_Memcpy(&(hash_src1[16]), msg->data, msg->len); + res = PK11_HashBuf(SEC_OID_SHA1, hash_dest, hash_src1, (16 + msg->len)); + if (res == SECFailure) + goto loser; + + /* second round of hashing */ + for (i = 0; i < 16; i++) + hash_src2[i] = key->data[i] ^ 0x5c; + PORT_Memcpy(&(hash_src2[16]), hash_dest, SHA1_LENGTH); + res = PK11_HashBuf(SEC_OID_SHA1, mac->data, hash_src2, SHA1_LENGTH + 16); + if (res == SECFailure) + goto loser; + + PORT_FreeArena(temparena, PR_TRUE); + return mac; + +loser: + if (temparena != NULL) + PORT_FreeArena(temparena, PR_TRUE); + if (mac != NULL) + SECITEM_ZfreeItem(mac, PR_TRUE); + return NULL; +} + +/* MAC is generated per PKCS 12 section 6. It is expected that key, msg + * and mac_dest are pre allocated, non-NULL arrays. msg_len is passed in + * because it is not known how long the message actually is. String + * manipulation routines will not necessarily work because msg may have + * imbedded NULLs + */ +SECItem * +sec_pkcs12_generate_mac(SECItem *key, + SECItem *msg, + PRBool old_method) +{ + SECStatus res = SECFailure; + SECItem *mac = NULL; + PK11Context *pk11cx = NULL; + SECItem ignore = { 0 }; + + if ((key == NULL) || (msg == NULL)) { + return NULL; + } + + if (old_method == PR_TRUE) { + return sec_pkcs12_generate_old_mac(key, msg); + } + + /* allocate return item */ + mac = SECITEM_AllocItem(NULL, NULL, SHA1_LENGTH); + if (mac == NULL) { + return NULL; + } + + pk11cx = PK11_CreateContextByRawKey(NULL, CKM_SHA_1_HMAC, PK11_OriginDerive, + CKA_SIGN, key, &ignore, NULL); + if (pk11cx == NULL) { + goto loser; + } + + res = PK11_DigestBegin(pk11cx); + if (res == SECFailure) { + goto loser; + } + + res = PK11_DigestOp(pk11cx, msg->data, msg->len); + if (res == SECFailure) { + goto loser; + } + + res = PK11_DigestFinal(pk11cx, mac->data, &mac->len, SHA1_LENGTH); + if (res == SECFailure) { + goto loser; + } + + PK11_DestroyContext(pk11cx, PR_TRUE); + pk11cx = NULL; + +loser: + + if (res != SECSuccess) { + SECITEM_ZfreeItem(mac, PR_TRUE); + mac = NULL; + if (pk11cx) { + PK11_DestroyContext(pk11cx, PR_TRUE); + } + } + + return mac; +} + +/* compute the thumbprint of the DER cert and create a digest info + * to store it in and return the digest info. + * a return of NULL indicates an error. + */ +SGNDigestInfo * +sec_pkcs12_compute_thumbprint(SECItem *der_cert) +{ + SGNDigestInfo *thumb = NULL; + SECItem digest; + PLArenaPool *temparena = NULL; + SECStatus rv = SECFailure; + + if (der_cert == NULL) + return NULL; + + temparena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE); + if (temparena == NULL) { + return NULL; + } + + digest.data = (unsigned char *)PORT_ArenaZAlloc(temparena, + sizeof(unsigned char) * + SHA1_LENGTH); + /* digest data and create digest info */ + if (digest.data != NULL) { + digest.len = SHA1_LENGTH; + rv = PK11_HashBuf(SEC_OID_SHA1, digest.data, der_cert->data, + der_cert->len); + if (rv == SECSuccess) { + thumb = SGN_CreateDigestInfo(SEC_OID_SHA1, + digest.data, + digest.len); + } else { + PORT_SetError(SEC_ERROR_NO_MEMORY); + } + } else { + PORT_SetError(SEC_ERROR_NO_MEMORY); + } + + PORT_FreeArena(temparena, PR_TRUE); + + return thumb; +} + +/* create a virtual password per PKCS 12, the password is converted + * to unicode, the salt is prepended to it, and then the whole thing + * is returned */ +SECItem * +sec_pkcs12_create_virtual_password(SECItem *password, SECItem *salt, + PRBool swap) +{ + SECItem uniPwd = { siBuffer, NULL, 0 }, *retPwd = NULL; + + if ((password == NULL) || (salt == NULL)) { + return NULL; + } + + if (password->len == 0) { + uniPwd.data = (unsigned char *)PORT_ZAlloc(2); + uniPwd.len = 2; + if (!uniPwd.data) { + return NULL; + } + } else { + uniPwd.data = (unsigned char *)PORT_ZAlloc(password->len * 3); + uniPwd.len = password->len * 3; + if (!PORT_UCS2_ASCIIConversion(PR_TRUE, password->data, password->len, + uniPwd.data, uniPwd.len, &uniPwd.len, swap)) { + SECITEM_ZfreeItem(&uniPwd, PR_FALSE); + return NULL; + } + } + + retPwd = (SECItem *)PORT_ZAlloc(sizeof(SECItem)); + if (retPwd == NULL) { + goto loser; + } + + /* allocate space and copy proper data */ + retPwd->len = uniPwd.len + salt->len; + retPwd->data = (unsigned char *)PORT_Alloc(retPwd->len); + if (retPwd->data == NULL) { + PORT_Free(retPwd); + goto loser; + } + + PORT_Memcpy(retPwd->data, salt->data, salt->len); + PORT_Memcpy((retPwd->data + salt->len), uniPwd.data, uniPwd.len); + + SECITEM_ZfreeItem(&uniPwd, PR_FALSE); + + return retPwd; + +loser: + PORT_SetError(SEC_ERROR_NO_MEMORY); + SECITEM_ZfreeItem(&uniPwd, PR_FALSE); + return NULL; +} + +/* appends a shrouded key to a key bag. this is used for exporting + * to store externally wrapped keys. it is used when importing to convert + * old items to new + */ +SECStatus +sec_pkcs12_append_shrouded_key(SEC_PKCS12BaggageItem *bag, + SEC_PKCS12ESPVKItem *espvk) +{ + int size; + void *mark = NULL, *dummy = NULL; + + if ((bag == NULL) || (espvk == NULL)) + return SECFailure; + + mark = PORT_ArenaMark(bag->poolp); + + /* grow the list */ + size = (bag->nEspvks + 1) * sizeof(SEC_PKCS12ESPVKItem *); + dummy = (SEC_PKCS12ESPVKItem **)PORT_ArenaGrow(bag->poolp, + bag->espvks, size, + size + sizeof(SEC_PKCS12ESPVKItem *)); + bag->espvks = (SEC_PKCS12ESPVKItem **)dummy; + if (dummy == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + bag->espvks[bag->nEspvks] = espvk; + bag->nEspvks++; + bag->espvks[bag->nEspvks] = NULL; + + PORT_ArenaUnmark(bag->poolp, mark); + return SECSuccess; + +loser: + PORT_ArenaRelease(bag->poolp, mark); + return SECFailure; +} + +/* search a certificate list for a nickname, a thumbprint, or both + * within a certificate bag. if the certificate could not be + * found or an error occurs, NULL is returned; + */ +static SEC_PKCS12CertAndCRL * +sec_pkcs12_find_cert_in_certbag(SEC_PKCS12CertAndCRLBag *certbag, + SECItem *nickname, SGNDigestInfo *thumbprint) +{ + PRBool search_both = PR_FALSE, search_nickname = PR_FALSE; + int i, j; + + if ((certbag == NULL) || ((nickname == NULL) && (thumbprint == NULL))) { + return NULL; + } + + if (thumbprint && nickname) { + search_both = PR_TRUE; + } + + if (nickname) { + search_nickname = PR_TRUE; + } + +search_again: + i = 0; + while (certbag->certAndCRLs[i] != NULL) { + SEC_PKCS12CertAndCRL *cert = certbag->certAndCRLs[i]; + + if (SECOID_FindOIDTag(&cert->BagID) == SEC_OID_PKCS12_X509_CERT_CRL_BAG) { + + /* check nicknames */ + if (search_nickname) { + if (SECITEM_CompareItem(nickname, &cert->nickname) == SECEqual) { + return cert; + } + } else { + /* check thumbprints */ + SECItem **derCertList; + + /* get pointer to certificate list, does not need to + * be freed since it is within the arena which will + * be freed later. + */ + derCertList = SEC_PKCS7GetCertificateList(&cert->value.x509->certOrCRL); + j = 0; + if (derCertList != NULL) { + while (derCertList[j] != NULL) { + SECComparison eq; + SGNDigestInfo *di; + di = sec_pkcs12_compute_thumbprint(derCertList[j]); + if (di) { + eq = SGN_CompareDigestInfo(thumbprint, di); + SGN_DestroyDigestInfo(di); + if (eq == SECEqual) { + /* copy the derCert for later reference */ + cert->value.x509->derLeafCert = derCertList[j]; + return cert; + } + } else { + /* an error occurred */ + return NULL; + } + j++; + } + } + } + } + + i++; + } + + if (search_both) { + search_both = PR_FALSE; + search_nickname = PR_FALSE; + goto search_again; + } + + return NULL; +} + +/* search a key list for a nickname, a thumbprint, or both + * within a key bag. if the key could not be + * found or an error occurs, NULL is returned; + */ +static SEC_PKCS12PrivateKey * +sec_pkcs12_find_key_in_keybag(SEC_PKCS12PrivateKeyBag *keybag, + SECItem *nickname, SGNDigestInfo *thumbprint) +{ + PRBool search_both = PR_FALSE, search_nickname = PR_FALSE; + int i, j; + + if ((keybag == NULL) || ((nickname == NULL) && (thumbprint == NULL))) { + return NULL; + } + + if (keybag->privateKeys == NULL) { + return NULL; + } + + if (thumbprint && nickname) { + search_both = PR_TRUE; + } + + if (nickname) { + search_nickname = PR_TRUE; + } + +search_again: + i = 0; + while (keybag->privateKeys[i] != NULL) { + SEC_PKCS12PrivateKey *key = keybag->privateKeys[i]; + + /* check nicknames */ + if (search_nickname) { + if (SECITEM_CompareItem(nickname, &key->pvkData.nickname) == SECEqual) { + return key; + } + } else { + /* check digests */ + SGNDigestInfo **assocCerts = key->pvkData.assocCerts; + if ((assocCerts == NULL) || (assocCerts[0] == NULL)) { + return NULL; + } + + j = 0; + while (assocCerts[j] != NULL) { + SECComparison eq; + eq = SGN_CompareDigestInfo(thumbprint, assocCerts[j]); + if (eq == SECEqual) { + return key; + } + j++; + } + } + i++; + } + + if (search_both) { + search_both = PR_FALSE; + search_nickname = PR_FALSE; + goto search_again; + } + + return NULL; +} + +/* seach the safe first then try the baggage bag + * safe and bag contain certs and keys to search + * objType is the object type to look for + * bagType is the type of bag that was found by sec_pkcs12_find_object + * index is the entity in safe->safeContents or bag->unencSecrets which + * is being searched + * nickname and thumbprint are the search criteria + * + * a return of null indicates no match + */ +static void * +sec_pkcs12_try_find(SEC_PKCS12SafeContents *safe, + SEC_PKCS12BaggageItem *bag, + SECOidTag objType, SECOidTag bagType, int index, + SECItem *nickname, SGNDigestInfo *thumbprint) +{ + PRBool searchSafe; + int i = index; + + if ((safe == NULL) && (bag == NULL)) { + return NULL; + } + + searchSafe = (safe == NULL ? PR_FALSE : PR_TRUE); + switch (objType) { + case SEC_OID_PKCS12_CERT_AND_CRL_BAG_ID: + if (objType == bagType) { + SEC_PKCS12CertAndCRLBag *certBag; + + if (searchSafe) { + certBag = safe->contents[i]->safeContent.certAndCRLBag; + } else { + certBag = bag->unencSecrets[i]->safeContent.certAndCRLBag; + } + return sec_pkcs12_find_cert_in_certbag(certBag, nickname, + thumbprint); + } + break; + case SEC_OID_PKCS12_KEY_BAG_ID: + if (objType == bagType) { + SEC_PKCS12PrivateKeyBag *keyBag; + + if (searchSafe) { + keyBag = safe->contents[i]->safeContent.keyBag; + } else { + keyBag = bag->unencSecrets[i]->safeContent.keyBag; + } + return sec_pkcs12_find_key_in_keybag(keyBag, nickname, + thumbprint); + } + break; + default: + break; + } + + return NULL; +} + +/* searches both the baggage and the safe areas looking for + * object of specified type matching either the nickname or the + * thumbprint specified. + * + * safe and baggage store certs and keys + * objType is the OID for the bag type to be searched: + * SEC_OID_PKCS12_KEY_BAG_ID, or + * SEC_OID_PKCS12_CERT_AND_CRL_BAG_ID + * nickname and thumbprint are the search criteria + * + * if no match found, NULL returned and error set + */ +void * +sec_pkcs12_find_object(SEC_PKCS12SafeContents *safe, + SEC_PKCS12Baggage *baggage, + SECOidTag objType, + SECItem *nickname, + SGNDigestInfo *thumbprint) +{ + int i, j; + void *retItem; + + if (((safe == NULL) && (thumbprint == NULL)) || + ((nickname == NULL) && (thumbprint == NULL))) { + return NULL; + } + + i = 0; + if ((safe != NULL) && (safe->contents != NULL)) { + while (safe->contents[i] != NULL) { + SECOidTag bagType = SECOID_FindOIDTag(&safe->contents[i]->safeBagType); + retItem = sec_pkcs12_try_find(safe, NULL, objType, bagType, i, + nickname, thumbprint); + if (retItem != NULL) { + return retItem; + } + i++; + } + } + + if ((baggage != NULL) && (baggage->bags != NULL)) { + i = 0; + while (baggage->bags[i] != NULL) { + SEC_PKCS12BaggageItem *xbag = baggage->bags[i]; + j = 0; + if (xbag->unencSecrets != NULL) { + while (xbag->unencSecrets[j] != NULL) { + SECOidTag bagType; + bagType = SECOID_FindOIDTag(&xbag->unencSecrets[j]->safeBagType); + retItem = sec_pkcs12_try_find(NULL, xbag, objType, bagType, + j, nickname, thumbprint); + if (retItem != NULL) { + return retItem; + } + j++; + } + } + i++; + } + } + + PORT_SetError(SEC_ERROR_PKCS12_UNABLE_TO_LOCATE_OBJECT_BY_NAME); + return NULL; +} + +/* this function converts a password to UCS2 and ensures that the + * required double 0 byte be placed at the end of the string (if zeroTerm + * is set), or the 0 bytes at the end are dropped (if zeroTerm is not set). + * If toUnicode is false, we convert from UCS2 to UTF8/ASCII (latter is a + * proper subset of the former) depending on the state of the asciiCovert + * flag) + */ +PRBool +sec_pkcs12_convert_item_to_unicode(PLArenaPool *arena, SECItem *dest, + SECItem *src, PRBool zeroTerm, + PRBool asciiConvert, PRBool toUnicode) +{ + PRBool success = PR_FALSE; + int bufferSize; + + if (!src || !dest) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return PR_FALSE; + } + + bufferSize = src->len * 3 + 2; + dest->len = bufferSize; + if (arena) { + dest->data = (unsigned char *)PORT_ArenaZAlloc(arena, dest->len); + } else { + dest->data = (unsigned char *)PORT_ZAlloc(dest->len); + } + + if (!dest->data) { + dest->len = 0; + return PR_FALSE; + } + + if (!asciiConvert) { + success = PORT_UCS2_UTF8Conversion(toUnicode, src->data, src->len, dest->data, + dest->len, &dest->len); + } else { +#ifndef IS_LITTLE_ENDIAN + PRBool swapUnicode = PR_FALSE; +#else + PRBool swapUnicode = PR_TRUE; +#endif + success = PORT_UCS2_ASCIIConversion(toUnicode, src->data, src->len, dest->data, + dest->len, &dest->len, swapUnicode); + } + + if (!success) { + if (!arena) { + PORT_Free(dest->data); + dest->data = NULL; + dest->len = 0; + } + return PR_FALSE; + } + + /* in some cases we need to add NULL terminations and in others + * we need to drop null terminations */ + if (zeroTerm) { + /* unicode adds two nulls at the end */ + if (toUnicode) { + if ((dest->len < 2) || dest->data[dest->len - 1] || dest->data[dest->len - 2]) { + /* we've already allocated space for these new NULLs */ + PORT_Assert(dest->len + 2 <= bufferSize); + dest->len += 2; + dest->data[dest->len - 1] = dest->data[dest->len - 2] = 0; + } + /* ascii/utf-8 adds just 1 */ + } else if (!dest->len || dest->data[dest->len - 1]) { + PORT_Assert(dest->len + 1 <= bufferSize); + dest->len++; + dest->data[dest->len - 1] = 0; + } + } else { + /* handle the drop case, no need to do any allocations here. */ + if (toUnicode) { + while ((dest->len >= 2) && !dest->data[dest->len - 1] && + !dest->data[dest->len - 2]) { + dest->len -= 2; + } + } else { + while (dest->len && !dest->data[dest->len - 1]) { + dest->len--; + } + } + } + + return PR_TRUE; +} + +PRBool +sec_pkcs12_is_pkcs12_pbe_algorithm(SECOidTag algorithm) +{ + switch (algorithm) { + case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_3KEY_TRIPLE_DES_CBC: + case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_2KEY_TRIPLE_DES_CBC: + case SEC_OID_PKCS12_PBE_WITH_SHA1_AND_TRIPLE_DES_CBC: + case SEC_OID_PKCS12_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC: + case SEC_OID_PKCS12_PBE_WITH_SHA1_AND_128_BIT_RC2_CBC: + case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_128_BIT_RC2_CBC: + case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC: + case SEC_OID_PKCS12_PBE_WITH_SHA1_AND_40_BIT_RC4: + case SEC_OID_PKCS12_PBE_WITH_SHA1_AND_128_BIT_RC4: + case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_128_BIT_RC4: + case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC4: + /* those are actually PKCS #5 v1.5 PBEs, but we + * historically treat them in the same way as PKCS #12 + * PBEs */ + case SEC_OID_PKCS5_PBE_WITH_MD2_AND_DES_CBC: + case SEC_OID_PKCS5_PBE_WITH_SHA1_AND_DES_CBC: + case SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC: + return PR_TRUE; + default: + return PR_FALSE; + } +} + +/* this function decodes a password from Unicode if necessary, + * according to the PBE algorithm. + * + * we assume that the pwitem is already encoded in Unicode by the + * caller. if the encryption scheme is not the one defined in PKCS + * #12, decode the pwitem back into UTF-8. NOTE: UTF-8 strings are + * used in the PRF without the trailing NULL */ +PRBool +sec_pkcs12_decode_password(PLArenaPool *arena, + SECItem *result, + SECOidTag algorithm, + const SECItem *pwitem) +{ + if (!sec_pkcs12_is_pkcs12_pbe_algorithm(algorithm)) + return sec_pkcs12_convert_item_to_unicode(arena, result, + (SECItem *)pwitem, + PR_FALSE, PR_FALSE, PR_FALSE); + + return SECITEM_CopyItem(arena, result, pwitem) == SECSuccess; +} + +/* this function encodes a password into Unicode if necessary, + * according to the PBE algorithm. + * + * we assume that the pwitem holds a raw password. if the encryption + * scheme is the one defined in PKCS #12, encode the password into + * BMPString. */ +PRBool +sec_pkcs12_encode_password(PLArenaPool *arena, + SECItem *result, + SECOidTag algorithm, + const SECItem *pwitem) +{ + if (sec_pkcs12_is_pkcs12_pbe_algorithm(algorithm)) + return sec_pkcs12_convert_item_to_unicode(arena, result, + (SECItem *)pwitem, + PR_TRUE, PR_TRUE, PR_TRUE); + + return SECITEM_CopyItem(arena, result, pwitem) == SECSuccess; +} + +/* pkcs 12 templates */ +static const SEC_ASN1TemplateChooserPtr sec_pkcs12_shroud_chooser = + sec_pkcs12_choose_shroud_type; + +const SEC_ASN1Template SEC_PKCS12CodedSafeBagTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SEC_PKCS12SafeBag) }, + { SEC_ASN1_OBJECT_ID, offsetof(SEC_PKCS12SafeBag, safeBagType) }, + { SEC_ASN1_ANY, offsetof(SEC_PKCS12SafeBag, derSafeContent) }, + { 0 } +}; + +const SEC_ASN1Template SEC_PKCS12CodedCertBagTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SEC_PKCS12CertAndCRL) }, + { SEC_ASN1_OBJECT_ID, offsetof(SEC_PKCS12CertAndCRL, BagID) }, + { SEC_ASN1_ANY, offsetof(SEC_PKCS12CertAndCRL, derValue) }, + { 0 } +}; + +const SEC_ASN1Template SEC_PKCS12CodedCertAndCRLBagTemplate[] = { + { SEC_ASN1_SET_OF, offsetof(SEC_PKCS12CertAndCRLBag, certAndCRLs), + SEC_PKCS12CodedCertBagTemplate }, +}; + +const SEC_ASN1Template SEC_PKCS12ESPVKItemTemplate_OLD[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SEC_PKCS12ESPVKItem) }, + { SEC_ASN1_OBJECT_ID, offsetof(SEC_PKCS12ESPVKItem, espvkOID) }, + { SEC_ASN1_INLINE, offsetof(SEC_PKCS12ESPVKItem, espvkData), + SEC_PKCS12PVKSupportingDataTemplate_OLD }, + { SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | + SEC_ASN1_DYNAMIC | 0, + offsetof(SEC_PKCS12ESPVKItem, espvkCipherText), + &sec_pkcs12_shroud_chooser }, + { 0 } +}; + +const SEC_ASN1Template SEC_PKCS12ESPVKItemTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SEC_PKCS12ESPVKItem) }, + { SEC_ASN1_OBJECT_ID, offsetof(SEC_PKCS12ESPVKItem, espvkOID) }, + { SEC_ASN1_INLINE, offsetof(SEC_PKCS12ESPVKItem, espvkData), + SEC_PKCS12PVKSupportingDataTemplate }, + { SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | + SEC_ASN1_DYNAMIC | 0, + offsetof(SEC_PKCS12ESPVKItem, espvkCipherText), + &sec_pkcs12_shroud_chooser }, + { 0 } +}; + +const SEC_ASN1Template SEC_PKCS12PVKAdditionalDataTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SEC_PKCS12PVKAdditionalData) }, + { SEC_ASN1_OBJECT_ID, + offsetof(SEC_PKCS12PVKAdditionalData, pvkAdditionalType) }, + { SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(SEC_PKCS12PVKAdditionalData, pvkAdditionalContent) }, + { 0 } +}; + +const SEC_ASN1Template SEC_PKCS12PVKSupportingDataTemplate_OLD[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SEC_PKCS12PVKSupportingData) }, + { SEC_ASN1_SET_OF | SEC_ASN1_XTRN, + offsetof(SEC_PKCS12PVKSupportingData, assocCerts), + SEC_ASN1_SUB(sgn_DigestInfoTemplate) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_BOOLEAN, + offsetof(SEC_PKCS12PVKSupportingData, regenerable) }, + { SEC_ASN1_PRINTABLE_STRING, + offsetof(SEC_PKCS12PVKSupportingData, nickname) }, + { SEC_ASN1_ANY | SEC_ASN1_OPTIONAL, + offsetof(SEC_PKCS12PVKSupportingData, pvkAdditionalDER) }, + { 0 } +}; + +const SEC_ASN1Template SEC_PKCS12PVKSupportingDataTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SEC_PKCS12PVKSupportingData) }, + { SEC_ASN1_SET_OF | SEC_ASN1_XTRN, + offsetof(SEC_PKCS12PVKSupportingData, assocCerts), + SEC_ASN1_SUB(sgn_DigestInfoTemplate) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_BOOLEAN, + offsetof(SEC_PKCS12PVKSupportingData, regenerable) }, + { SEC_ASN1_BMP_STRING, + offsetof(SEC_PKCS12PVKSupportingData, uniNickName) }, + { SEC_ASN1_ANY | SEC_ASN1_OPTIONAL, + offsetof(SEC_PKCS12PVKSupportingData, pvkAdditionalDER) }, + { 0 } +}; + +const SEC_ASN1Template SEC_PKCS12BaggageItemTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SEC_PKCS12BaggageItem) }, + { SEC_ASN1_SET_OF, offsetof(SEC_PKCS12BaggageItem, espvks), + SEC_PKCS12ESPVKItemTemplate }, + { SEC_ASN1_SET_OF, offsetof(SEC_PKCS12BaggageItem, unencSecrets), + SEC_PKCS12SafeBagTemplate }, + /*{ SEC_ASN1_SET_OF, offsetof(SEC_PKCS12BaggageItem, unencSecrets), + SEC_PKCS12CodedSafeBagTemplate }, */ + { 0 } +}; + +const SEC_ASN1Template SEC_PKCS12BaggageTemplate[] = { + { SEC_ASN1_SET_OF, offsetof(SEC_PKCS12Baggage, bags), + SEC_PKCS12BaggageItemTemplate }, +}; + +const SEC_ASN1Template SEC_PKCS12BaggageTemplate_OLD[] = { + { SEC_ASN1_SET_OF, offsetof(SEC_PKCS12Baggage_OLD, espvks), + SEC_PKCS12ESPVKItemTemplate_OLD }, +}; + +static const SEC_ASN1TemplateChooserPtr sec_pkcs12_bag_chooser = + sec_pkcs12_choose_bag_type; + +static const SEC_ASN1TemplateChooserPtr sec_pkcs12_bag_chooser_old = + sec_pkcs12_choose_bag_type_old; + +const SEC_ASN1Template SEC_PKCS12SafeBagTemplate_OLD[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SEC_PKCS12SafeBag) }, + { SEC_ASN1_OBJECT_ID, offsetof(SEC_PKCS12SafeBag, safeBagType) }, + { SEC_ASN1_DYNAMIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | + SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(SEC_PKCS12SafeBag, safeContent), + &sec_pkcs12_bag_chooser_old }, + { 0 } +}; + +const SEC_ASN1Template SEC_PKCS12SafeBagTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SEC_PKCS12SafeBag) }, + { SEC_ASN1_OBJECT_ID, offsetof(SEC_PKCS12SafeBag, safeBagType) }, + { SEC_ASN1_DYNAMIC | SEC_ASN1_POINTER, + offsetof(SEC_PKCS12SafeBag, safeContent), + &sec_pkcs12_bag_chooser }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_BMP_STRING, + offsetof(SEC_PKCS12SafeBag, uniSafeBagName) }, + { 0 } +}; + +const SEC_ASN1Template SEC_PKCS12SafeContentsTemplate_OLD[] = { + { SEC_ASN1_SET_OF, + offsetof(SEC_PKCS12SafeContents, contents), + SEC_PKCS12SafeBagTemplate_OLD } +}; + +const SEC_ASN1Template SEC_PKCS12SafeContentsTemplate[] = { + { SEC_ASN1_SET_OF, + offsetof(SEC_PKCS12SafeContents, contents), + SEC_PKCS12SafeBagTemplate } /* here */ +}; + +const SEC_ASN1Template SEC_PKCS12PrivateKeyTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SEC_PKCS12PrivateKey) }, + { SEC_ASN1_INLINE, offsetof(SEC_PKCS12PrivateKey, pvkData), + SEC_PKCS12PVKSupportingDataTemplate }, + { SEC_ASN1_INLINE | SEC_ASN1_XTRN, + offsetof(SEC_PKCS12PrivateKey, pkcs8data), + SEC_ASN1_SUB(SECKEY_PrivateKeyInfoTemplate) }, + { 0 } +}; + +const SEC_ASN1Template SEC_PKCS12PrivateKeyBagTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SEC_PKCS12PrivateKeyBag) }, + { SEC_ASN1_SET_OF, offsetof(SEC_PKCS12PrivateKeyBag, privateKeys), + SEC_PKCS12PrivateKeyTemplate }, + { 0 } +}; + +const SEC_ASN1Template SEC_PKCS12X509CertCRLTemplate_OLD[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SEC_PKCS12X509CertCRL) }, + { SEC_ASN1_INLINE, offsetof(SEC_PKCS12X509CertCRL, certOrCRL), + sec_PKCS7ContentInfoTemplate }, + { SEC_ASN1_INLINE | SEC_ASN1_XTRN, + offsetof(SEC_PKCS12X509CertCRL, thumbprint), + SEC_ASN1_SUB(sgn_DigestInfoTemplate) }, + { 0 } +}; + +const SEC_ASN1Template SEC_PKCS12X509CertCRLTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SEC_PKCS12X509CertCRL) }, + { SEC_ASN1_INLINE, offsetof(SEC_PKCS12X509CertCRL, certOrCRL), + sec_PKCS7ContentInfoTemplate }, + { 0 } +}; + +const SEC_ASN1Template SEC_PKCS12SDSICertTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SEC_PKCS12X509CertCRL) }, + { SEC_ASN1_IA5_STRING, offsetof(SEC_PKCS12SDSICert, value) }, + { 0 } +}; + +static const SEC_ASN1TemplateChooserPtr sec_pkcs12_cert_crl_chooser_old = + sec_pkcs12_choose_cert_crl_type_old; + +static const SEC_ASN1TemplateChooserPtr sec_pkcs12_cert_crl_chooser = + sec_pkcs12_choose_cert_crl_type; + +const SEC_ASN1Template SEC_PKCS12CertAndCRLTemplate_OLD[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SEC_PKCS12CertAndCRL) }, + { SEC_ASN1_OBJECT_ID, offsetof(SEC_PKCS12CertAndCRL, BagID) }, + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_EXPLICIT | + SEC_ASN1_DYNAMIC | SEC_ASN1_CONSTRUCTED | 0, + offsetof(SEC_PKCS12CertAndCRL, value), + &sec_pkcs12_cert_crl_chooser_old }, + { 0 } +}; + +const SEC_ASN1Template SEC_PKCS12CertAndCRLTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SEC_PKCS12CertAndCRL) }, + { SEC_ASN1_OBJECT_ID, offsetof(SEC_PKCS12CertAndCRL, BagID) }, + { SEC_ASN1_DYNAMIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | + SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(SEC_PKCS12CertAndCRL, value), + &sec_pkcs12_cert_crl_chooser }, + { 0 } +}; + +const SEC_ASN1Template SEC_PKCS12CertAndCRLBagTemplate[] = { + { SEC_ASN1_SET_OF, offsetof(SEC_PKCS12CertAndCRLBag, certAndCRLs), + SEC_PKCS12CertAndCRLTemplate }, +}; + +const SEC_ASN1Template SEC_PKCS12CertAndCRLBagTemplate_OLD[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SEC_PKCS12CertAndCRLBag) }, + { SEC_ASN1_SET_OF, offsetof(SEC_PKCS12CertAndCRLBag, certAndCRLs), + SEC_PKCS12CertAndCRLTemplate_OLD }, + { 0 } +}; + +const SEC_ASN1Template SEC_PKCS12SecretAdditionalTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SEC_PKCS12SecretAdditional) }, + { SEC_ASN1_OBJECT_ID, + offsetof(SEC_PKCS12SecretAdditional, secretAdditionalType) }, + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_EXPLICIT, + offsetof(SEC_PKCS12SecretAdditional, secretAdditionalContent) }, + { 0 } +}; + +const SEC_ASN1Template SEC_PKCS12SecretTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SEC_PKCS12Secret) }, + { SEC_ASN1_BMP_STRING, offsetof(SEC_PKCS12Secret, uniSecretName) }, + { SEC_ASN1_ANY, offsetof(SEC_PKCS12Secret, value) }, + { SEC_ASN1_INLINE | SEC_ASN1_OPTIONAL, + offsetof(SEC_PKCS12Secret, secretAdditional), + SEC_PKCS12SecretAdditionalTemplate }, + { 0 } +}; + +const SEC_ASN1Template SEC_PKCS12SecretItemTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SEC_PKCS12Secret) }, + { SEC_ASN1_INLINE | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(SEC_PKCS12SecretItem, secret), SEC_PKCS12SecretTemplate }, + { SEC_ASN1_INLINE | SEC_ASN1_CONTEXT_SPECIFIC | 1, + offsetof(SEC_PKCS12SecretItem, subFolder), SEC_PKCS12SafeBagTemplate }, + { 0 } +}; + +const SEC_ASN1Template SEC_PKCS12SecretBagTemplate[] = { + { SEC_ASN1_SET_OF, offsetof(SEC_PKCS12SecretBag, secrets), + SEC_PKCS12SecretItemTemplate }, +}; + +const SEC_ASN1Template SEC_PKCS12MacDataTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SEC_PKCS12PFXItem) }, + { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(SEC_PKCS12MacData, safeMac), + SEC_ASN1_SUB(sgn_DigestInfoTemplate) }, + { SEC_ASN1_BIT_STRING, offsetof(SEC_PKCS12MacData, macSalt) }, + { 0 } +}; + +const SEC_ASN1Template SEC_PKCS12PFXItemTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SEC_PKCS12PFXItem) }, + { SEC_ASN1_OPTIONAL | + SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(SEC_PKCS12PFXItem, macData), SEC_PKCS12MacDataTemplate }, + { SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1, + offsetof(SEC_PKCS12PFXItem, authSafe), + sec_PKCS7ContentInfoTemplate }, + { 0 } +}; + +const SEC_ASN1Template SEC_PKCS12PFXItemTemplate_OLD[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SEC_PKCS12PFXItem) }, + { SEC_ASN1_OPTIONAL | + SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0, + offsetof(SEC_PKCS12PFXItem, old_safeMac), + SEC_ASN1_SUB(sgn_DigestInfoTemplate) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_BIT_STRING, + offsetof(SEC_PKCS12PFXItem, old_macSalt) }, + { SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1, + offsetof(SEC_PKCS12PFXItem, authSafe), + sec_PKCS7ContentInfoTemplate }, + { 0 } +}; + +const SEC_ASN1Template SEC_PKCS12AuthenticatedSafeTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SEC_PKCS12AuthenticatedSafe) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_INTEGER, + offsetof(SEC_PKCS12AuthenticatedSafe, version) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_OBJECT_ID, + offsetof(SEC_PKCS12AuthenticatedSafe, transportMode) }, + { SEC_ASN1_BIT_STRING | SEC_ASN1_OPTIONAL, + offsetof(SEC_PKCS12AuthenticatedSafe, privacySalt) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_SET_OF, + offsetof(SEC_PKCS12AuthenticatedSafe, baggage.bags), + SEC_PKCS12BaggageItemTemplate }, + { SEC_ASN1_POINTER, + offsetof(SEC_PKCS12AuthenticatedSafe, safe), + sec_PKCS7ContentInfoTemplate }, + { 0 } +}; + +const SEC_ASN1Template SEC_PKCS12AuthenticatedSafeTemplate_OLD[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SEC_PKCS12AuthenticatedSafe) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_INTEGER, + offsetof(SEC_PKCS12AuthenticatedSafe, version) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_INTEGER, + offsetof(SEC_PKCS12AuthenticatedSafe, transportMode) }, + { SEC_ASN1_BIT_STRING, + offsetof(SEC_PKCS12AuthenticatedSafe, privacySalt) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | + SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(SEC_PKCS12AuthenticatedSafe, old_baggage), + SEC_PKCS12BaggageTemplate_OLD }, + { SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1, + offsetof(SEC_PKCS12AuthenticatedSafe, old_safe), + sec_PKCS7ContentInfoTemplate }, + { 0 } +}; + +const SEC_ASN1Template SEC_PointerToPKCS12KeyBagTemplate[] = { + { SEC_ASN1_POINTER, 0, SEC_PKCS12PrivateKeyBagTemplate } +}; + +const SEC_ASN1Template SEC_PointerToPKCS12CertAndCRLBagTemplate_OLD[] = { + { SEC_ASN1_POINTER, 0, SEC_PKCS12CertAndCRLBagTemplate_OLD } +}; + +const SEC_ASN1Template SEC_PointerToPKCS12CertAndCRLBagTemplate[] = { + { SEC_ASN1_POINTER, 0, SEC_PKCS12CertAndCRLBagTemplate } +}; + +const SEC_ASN1Template SEC_PointerToPKCS12SecretBagTemplate[] = { + { SEC_ASN1_POINTER, 0, SEC_PKCS12SecretBagTemplate } +}; + +const SEC_ASN1Template SEC_PointerToPKCS12X509CertCRLTemplate_OLD[] = { + { SEC_ASN1_POINTER, 0, SEC_PKCS12X509CertCRLTemplate_OLD } +}; + +const SEC_ASN1Template SEC_PointerToPKCS12X509CertCRLTemplate[] = { + { SEC_ASN1_POINTER, 0, SEC_PKCS12X509CertCRLTemplate } +}; + +const SEC_ASN1Template SEC_PointerToPKCS12SDSICertTemplate[] = { + { SEC_ASN1_POINTER, 0, SEC_PKCS12SDSICertTemplate } +}; diff --git a/security/nss/lib/pkcs12/p12local.h b/security/nss/lib/pkcs12/p12local.h new file mode 100644 index 0000000000..99068e21ef --- /dev/null +++ b/security/nss/lib/pkcs12/p12local.h @@ -0,0 +1,70 @@ +/* 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/. */ + +#ifndef _P12LOCAL_H_ +#define _P12LOCAL_H_ + +#include "plarena.h" +#include "secoidt.h" +#include "secasn1.h" +#include "secder.h" +#include "certt.h" +#include "secpkcs7.h" +#include "pkcs12.h" +#include "p12.h" + +/* helper functions */ +extern const SEC_ASN1Template * +sec_pkcs12_choose_bag_type(void *src_or_dest, PRBool encoding); +extern const SEC_ASN1Template * +sec_pkcs12_choose_cert_crl_type(void *src_or_dest, PRBool encoding); +extern const SEC_ASN1Template * +sec_pkcs12_choose_shroud_type(void *src_or_dest, PRBool encoding); +extern SECItem *sec_pkcs12_generate_salt(void); +extern SECItem *sec_pkcs12_generate_key_from_password(SECOidTag algorithm, + SECItem *salt, SECItem *password); +extern SECItem *sec_pkcs12_generate_mac(SECItem *key, SECItem *msg, + PRBool old_method); +extern SGNDigestInfo *sec_pkcs12_compute_thumbprint(SECItem *der_cert); +extern SECItem *sec_pkcs12_create_virtual_password(SECItem *password, + SECItem *salt, PRBool swapUnicodeBytes); +extern SECStatus sec_pkcs12_append_shrouded_key(SEC_PKCS12BaggageItem *bag, + SEC_PKCS12ESPVKItem *espvk); +extern void *sec_pkcs12_find_object(SEC_PKCS12SafeContents *safe, + SEC_PKCS12Baggage *baggage, SECOidTag objType, + SECItem *nickname, SGNDigestInfo *thumbprint); +extern PRBool sec_pkcs12_convert_item_to_unicode(PLArenaPool *arena, SECItem *dest, + SECItem *src, PRBool zeroTerm, + PRBool asciiConvert, PRBool toUnicode); +extern CK_MECHANISM_TYPE sec_pkcs12_algtag_to_mech(SECOidTag algtag); +extern CK_MECHANISM_TYPE sec_pkcs12_algtag_to_keygen_mech(SECOidTag algtag); + +/* create functions */ +extern SEC_PKCS12PFXItem *sec_pkcs12_new_pfx(void); +extern SEC_PKCS12SafeContents *sec_pkcs12_create_safe_contents( + PLArenaPool *poolp); +extern SEC_PKCS12Baggage *sec_pkcs12_create_baggage(PLArenaPool *poolp); +extern SEC_PKCS12BaggageItem *sec_pkcs12_create_external_bag(SEC_PKCS12Baggage *luggage); +extern void SEC_PKCS12DestroyPFX(SEC_PKCS12PFXItem *pfx); +extern SEC_PKCS12AuthenticatedSafe *sec_pkcs12_new_asafe(PLArenaPool *poolp); + +/* conversion from old to new */ +extern SEC_PKCS12DecoderContext * +sec_PKCS12ConvertOldSafeToNew(PLArenaPool *arena, PK11SlotInfo *slot, + PRBool swapUnicode, SECItem *pwitem, + void *wincx, SEC_PKCS12SafeContents *safe, + SEC_PKCS12Baggage *baggage); + +extern PRBool sec_pkcs12_is_pkcs12_pbe_algorithm(SECOidTag algorithm); + +extern PRBool sec_pkcs12_decode_password(PLArenaPool *arena, + SECItem *result, + SECOidTag algorithm, + const SECItem *pwitem); +extern PRBool sec_pkcs12_encode_password(PLArenaPool *arena, + SECItem *result, + SECOidTag algorithm, + const SECItem *pwitem); + +#endif diff --git a/security/nss/lib/pkcs12/p12plcy.c b/security/nss/lib/pkcs12/p12plcy.c new file mode 100644 index 0000000000..5c1754dce1 --- /dev/null +++ b/security/nss/lib/pkcs12/p12plcy.c @@ -0,0 +1,106 @@ +/* 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 "p12plcy.h" +#include "secoid.h" +#include "secport.h" +#include "secpkcs5.h" +#include "secerr.h" + +#define PKCS12_NULL 0x0000 + +typedef struct pkcs12SuiteMapStr { + SECOidTag algTag; + unsigned int keyLengthBits; /* in bits */ + unsigned long suite; + PRBool allowed; + PRBool preferred; +} pkcs12SuiteMap; + +static pkcs12SuiteMap pkcs12SuiteMaps[] = { + { SEC_OID_RC4, 40, PKCS12_RC4_40, PR_FALSE, PR_FALSE }, + { SEC_OID_RC4, 128, PKCS12_RC4_128, PR_FALSE, PR_FALSE }, + { SEC_OID_RC2_CBC, 40, PKCS12_RC2_CBC_40, PR_FALSE, PR_TRUE }, + { SEC_OID_RC2_CBC, 128, PKCS12_RC2_CBC_128, PR_FALSE, PR_FALSE }, + { SEC_OID_DES_CBC, 64, PKCS12_DES_56, PR_FALSE, PR_FALSE }, + { SEC_OID_DES_EDE3_CBC, 192, PKCS12_DES_EDE3_168, PR_FALSE, PR_FALSE }, + { SEC_OID_AES_128_CBC, 128, PKCS12_AES_CBC_128, PR_FALSE, PR_FALSE }, + { SEC_OID_AES_192_CBC, 192, PKCS12_AES_CBC_192, PR_FALSE, PR_FALSE }, + { SEC_OID_AES_256_CBC, 256, PKCS12_AES_CBC_256, PR_FALSE, PR_FALSE }, + { SEC_OID_UNKNOWN, 0, PKCS12_NULL, PR_FALSE, PR_FALSE }, + { SEC_OID_UNKNOWN, 0, 0L, PR_FALSE, PR_FALSE } +}; + +/* determine if algid is an algorithm which is allowed */ +static PRBool +sec_PKCS12Allowed(SECOidTag alg) +{ + PRUint32 policy; + SECStatus rv; + + rv = NSS_GetAlgorithmPolicy(alg, &policy); + if (rv != SECSuccess) { + return PR_FALSE; + } + if (policy & NSS_USE_ALG_IN_PKCS12) { + return PR_TRUE; + } + return PR_FALSE; +} + +PRBool +SEC_PKCS12DecryptionAllowed(SECAlgorithmID *algid) +{ + SECOidTag algId; + + algId = SEC_PKCS5GetCryptoAlgorithm(algid); + if (algId == SEC_OID_UNKNOWN) { + return PR_FALSE; + } + return sec_PKCS12Allowed(algId); +} + +/* is any encryption allowed? */ +PRBool +SEC_PKCS12IsEncryptionAllowed(void) +{ + int i; + + for (i = 0; pkcs12SuiteMaps[i].algTag != SEC_OID_UNKNOWN; i++) { + /* we're going to return true here if any of the traditional + * algorithms are enabled */ + if (sec_PKCS12Allowed(pkcs12SuiteMaps[i].algTag)) { + return PR_TRUE; + } + } + + return PR_FALSE; +} + +/* keep the traditional enable/disable for old ciphers so old applications + * continue to work. This only works for the traditional pkcs12 values, + * you need to use NSS_SetAlgorithmPolicy directly for other ciphers. */ +SECStatus +SEC_PKCS12EnableCipher(long which, int on) +{ + int i; + PRUint32 set = on ? NSS_USE_ALG_IN_PKCS12 : 0; + PRUint32 clear = on ? 0 : NSS_USE_ALG_IN_PKCS12; + + for (i = 0; pkcs12SuiteMaps[i].suite != 0L; i++) { + if (pkcs12SuiteMaps[i].suite == (unsigned long)which) { + return NSS_SetAlgorithmPolicy(pkcs12SuiteMaps[i].algTag, set, clear); + } + } + PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); + return SECFailure; +} + +SECStatus +SEC_PKCS12SetPreferredCipher(long which, int on) +{ + /* nothing looked at the preferences in the suite maps, so this function + * has always been a noop */ + return SECSuccess; +} diff --git a/security/nss/lib/pkcs12/p12plcy.h b/security/nss/lib/pkcs12/p12plcy.h new file mode 100644 index 0000000000..d3f818d49a --- /dev/null +++ b/security/nss/lib/pkcs12/p12plcy.h @@ -0,0 +1,25 @@ +/* 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/. */ +#ifndef _P12PLCY_H_ +#define _P12PLCY_H_ + +#include "secoid.h" +#include "ciferfam.h" + +SEC_BEGIN_PROTOS + +/* for the algid specified, can we decrypt it ? */ +extern PRBool SEC_PKCS12DecryptionAllowed(SECAlgorithmID *algid); + +/* is encryption allowed? */ +extern PRBool SEC_PKCS12IsEncryptionAllowed(void); + +/* enable a cipher for encryption/decryption */ +extern SECStatus SEC_PKCS12EnableCipher(long which, int on); + +/* return the preferred cipher for encryption */ +extern SECStatus SEC_PKCS12SetPreferredCipher(long which, int on); + +SEC_END_PROTOS +#endif diff --git a/security/nss/lib/pkcs12/p12t.h b/security/nss/lib/pkcs12/p12t.h new file mode 100644 index 0000000000..d449afdd89 --- /dev/null +++ b/security/nss/lib/pkcs12/p12t.h @@ -0,0 +1,156 @@ +/* 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/. */ + +#ifndef _P12T_H_ +#define _P12T_H_ + +#include "secoid.h" +#include "keythi.h" +#include "pkcs11.h" +#include "secpkcs7.h" +#include "secdig.h" /* for SGNDigestInfo */ +#include "pkcs12t.h" + +#define SEC_PKCS12_VERSION 3 + +/* structure declarations */ +typedef struct sec_PKCS12PFXItemStr sec_PKCS12PFXItem; +typedef struct sec_PKCS12MacDataStr sec_PKCS12MacData; +typedef struct sec_PKCS12AuthenticatedSafeStr sec_PKCS12AuthenticatedSafe; +typedef struct sec_PKCS12SafeContentsStr sec_PKCS12SafeContents; +typedef struct sec_PKCS12SafeBagStr sec_PKCS12SafeBag; +typedef struct sec_PKCS12PKCS8ShroudedKeyBagStr sec_PKCS12PKCS8ShroudedKeyBag; +typedef struct sec_PKCS12CertBagStr sec_PKCS12CertBag; +typedef struct sec_PKCS12CRLBagStr sec_PKCS12CRLBag; +typedef struct sec_PKCS12SecretBag sec_PKCS12SecretBag; +typedef struct sec_PKCS12AttributeStr sec_PKCS12Attribute; + +struct sec_PKCS12CertBagStr { + /* what type of cert is stored? */ + SECItem bagID; + + /* certificate information */ + union { + SECItem x509Cert; + SECItem SDSICert; + } value; +}; + +struct sec_PKCS12CRLBagStr { + /* what type of cert is stored? */ + SECItem bagID; + + /* certificate information */ + union { + SECItem x509CRL; + } value; +}; + +struct sec_PKCS12SecretBag { + /* what type of secret? */ + SECItem secretType; + + /* secret information. ssshhhh be vewy vewy quiet. */ + SECItem secretContent; +}; + +struct sec_PKCS12AttributeStr { + SECItem attrType; + SECItem **attrValue; +}; + +struct sec_PKCS12SafeBagStr { + + /* What type of bag are we using? */ + SECItem safeBagType; + + /* Dependent upon the type of bag being used. */ + union { + SECKEYPrivateKeyInfo *pkcs8KeyBag; + SECKEYEncryptedPrivateKeyInfo *pkcs8ShroudedKeyBag; + sec_PKCS12CertBag *certBag; + sec_PKCS12CRLBag *crlBag; + sec_PKCS12SecretBag *secretBag; + sec_PKCS12SafeContents *safeContents; + SECItem *unknownBag; + } safeBagContent; + + sec_PKCS12Attribute **attribs; + + /* used locally */ + SECOidData *bagTypeTag; + PLArenaPool *arena; + unsigned int nAttribs; + + /* used for validation/importing */ + PRBool problem, noInstall, validated, hasKey, unused, installed; + int error; + + PRBool swapUnicodeBytes; + PK11SlotInfo *slot; + SECItem *pwitem; + PRBool oldBagType; + SECPKCS12TargetTokenCAs tokenCAs; +}; + +struct sec_PKCS12SafeContentsStr { + sec_PKCS12SafeBag **safeBags; + SECItem **encodedSafeBags; + + /* used locally */ + PLArenaPool *arena; + unsigned int bagCount; +}; + +struct sec_PKCS12MacDataStr { + SGNDigestInfo safeMac; + SECItem macSalt; + SECItem iter; +}; + +struct sec_PKCS12PFXItemStr { + + SECItem version; + + /* Content type will either be Data (password integrity mode) + * or signedData (public-key integrity mode) + */ + SEC_PKCS7ContentInfo *authSafe; + SECItem encodedAuthSafe; + + /* Only present in password integrity mode */ + sec_PKCS12MacData macData; + SECItem encodedMacData; +}; + +struct sec_PKCS12AuthenticatedSafeStr { + /* Content type will either be encryptedData (password privacy mode) + * or envelopedData (public-key privacy mode) + */ + SEC_PKCS7ContentInfo **safes; + SECItem **encodedSafes; + + /* used locally */ + unsigned int safeCount; + SECItem dummySafe; +}; + +extern const SEC_ASN1Template sec_PKCS12PFXItemTemplate[]; +extern const SEC_ASN1Template sec_PKCS12MacDataTemplate[]; +extern const SEC_ASN1Template sec_PKCS12AuthenticatedSafeTemplate[]; +extern const SEC_ASN1Template sec_PKCS12SafeContentsTemplate[]; +extern const SEC_ASN1Template sec_PKCS12SafeContentsDecodeTemplate[]; +extern const SEC_ASN1Template sec_PKCS12NestedSafeContentsDecodeTemplate[]; +extern const SEC_ASN1Template sec_PKCS12CertBagTemplate[]; +extern const SEC_ASN1Template sec_PKCS12CRLBagTemplate[]; +extern const SEC_ASN1Template sec_PKCS12SecretBagTemplate[]; +extern const SEC_ASN1Template sec_PKCS12PointerToCertBagTemplate[]; +extern const SEC_ASN1Template sec_PKCS12PointerToCRLBagTemplate[]; +extern const SEC_ASN1Template sec_PKCS12PointerToSecretBagTemplate[]; +extern const SEC_ASN1Template sec_PKCS12PointerToSafeContentsTemplate[]; +extern const SEC_ASN1Template sec_PKCS12AttributeTemplate[]; +extern const SEC_ASN1Template sec_PKCS12PointerToContentInfoTemplate[]; +extern const SEC_ASN1Template sec_PKCS12SafeBagTemplate[]; + +#endif diff --git a/security/nss/lib/pkcs12/p12tmpl.c b/security/nss/lib/pkcs12/p12tmpl.c new file mode 100644 index 0000000000..b08384f66a --- /dev/null +++ b/security/nss/lib/pkcs12/p12tmpl.c @@ -0,0 +1,290 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "plarena.h" +#include "secitem.h" +#include "secoid.h" +#include "seccomon.h" +#include "secport.h" +#include "cert.h" +#include "secpkcs7.h" +#include "secasn1.h" +#include "p12t.h" + +SEC_ASN1_MKSUB(SEC_AnyTemplate) +SEC_ASN1_MKSUB(sgn_DigestInfoTemplate) + +static const SEC_ASN1Template * +sec_pkcs12_choose_safe_bag_type(void *src_or_dest, PRBool encoding) +{ + const SEC_ASN1Template *theTemplate; + sec_PKCS12SafeBag *safeBag; + SECOidData *oiddata; + + if (src_or_dest == NULL) { + return NULL; + } + + safeBag = (sec_PKCS12SafeBag *)src_or_dest; + + oiddata = SECOID_FindOID(&safeBag->safeBagType); + if (oiddata == NULL) { + return SEC_ASN1_GET(SEC_PointerToAnyTemplate); + } + + switch (oiddata->offset) { + default: + theTemplate = SEC_ASN1_GET(SEC_PointerToAnyTemplate); + break; + case SEC_OID_PKCS12_V1_KEY_BAG_ID: + theTemplate = SEC_ASN1_GET(SECKEY_PointerToPrivateKeyInfoTemplate); + break; + case SEC_OID_PKCS12_V1_CERT_BAG_ID: + theTemplate = sec_PKCS12PointerToCertBagTemplate; + break; + case SEC_OID_PKCS12_V1_CRL_BAG_ID: + theTemplate = sec_PKCS12PointerToCRLBagTemplate; + break; + case SEC_OID_PKCS12_V1_SECRET_BAG_ID: + theTemplate = sec_PKCS12PointerToSecretBagTemplate; + break; + case SEC_OID_PKCS12_V1_PKCS8_SHROUDED_KEY_BAG_ID: + theTemplate = + SEC_ASN1_GET(SECKEY_PointerToEncryptedPrivateKeyInfoTemplate); + break; + case SEC_OID_PKCS12_V1_SAFE_CONTENTS_BAG_ID: + if (encoding) { + theTemplate = sec_PKCS12PointerToSafeContentsTemplate; + } else { + theTemplate = SEC_ASN1_GET(SEC_PointerToAnyTemplate); + } + break; + } + return theTemplate; +} + +static const SEC_ASN1Template * +sec_pkcs12_choose_crl_bag_type(void *src_or_dest, PRBool encoding) +{ + const SEC_ASN1Template *theTemplate; + sec_PKCS12CRLBag *crlbag; + SECOidData *oiddata; + + if (src_or_dest == NULL) { + return NULL; + } + + crlbag = (sec_PKCS12CRLBag *)src_or_dest; + + oiddata = SECOID_FindOID(&crlbag->bagID); + if (oiddata == NULL) { + return SEC_ASN1_GET(SEC_AnyTemplate); + } + + switch (oiddata->offset) { + default: + theTemplate = SEC_ASN1_GET(SEC_AnyTemplate); + break; + case SEC_OID_PKCS9_X509_CRL: + theTemplate = SEC_ASN1_GET(SEC_OctetStringTemplate); + break; + } + return theTemplate; +} + +static const SEC_ASN1Template * +sec_pkcs12_choose_cert_bag_type(void *src_or_dest, PRBool encoding) +{ + const SEC_ASN1Template *theTemplate; + sec_PKCS12CertBag *certbag; + SECOidData *oiddata; + + if (src_or_dest == NULL) { + return NULL; + } + + certbag = (sec_PKCS12CertBag *)src_or_dest; + + oiddata = SECOID_FindOID(&certbag->bagID); + if (oiddata == NULL) { + return SEC_ASN1_GET(SEC_AnyTemplate); + } + + switch (oiddata->offset) { + default: + theTemplate = SEC_ASN1_GET(SEC_AnyTemplate); + break; + case SEC_OID_PKCS9_X509_CERT: + theTemplate = SEC_ASN1_GET(SEC_OctetStringTemplate); + break; + case SEC_OID_PKCS9_SDSI_CERT: + theTemplate = SEC_ASN1_GET(SEC_IA5StringTemplate); + break; + } + return theTemplate; +} + +static const SEC_ASN1Template * +sec_pkcs12_choose_attr_type(void *src_or_dest, PRBool encoding) +{ + const SEC_ASN1Template *theTemplate; + sec_PKCS12Attribute *attr; + SECOidData *oiddata; + + if (src_or_dest == NULL) { + return NULL; + } + + attr = (sec_PKCS12Attribute *)src_or_dest; + + oiddata = SECOID_FindOID(&attr->attrType); + if (oiddata == NULL) { + return SEC_ASN1_GET(SEC_AnyTemplate); + } + + switch (oiddata->offset) { + default: + theTemplate = SEC_ASN1_GET(SEC_AnyTemplate); + break; + case SEC_OID_PKCS9_FRIENDLY_NAME: + theTemplate = SEC_ASN1_GET(SEC_BMPStringTemplate); + break; + case SEC_OID_PKCS9_LOCAL_KEY_ID: + theTemplate = SEC_ASN1_GET(SEC_OctetStringTemplate); + break; + case SEC_OID_PKCS12_KEY_USAGE: + theTemplate = SEC_ASN1_GET(SEC_BitStringTemplate); + break; + } + + return theTemplate; +} + +const SEC_ASN1Template sec_PKCS12PointerToContentInfoTemplate[] = { + { SEC_ASN1_POINTER | SEC_ASN1_MAY_STREAM, 0, sec_PKCS7ContentInfoTemplate } +}; + +static const SEC_ASN1TemplateChooserPtr sec_pkcs12_crl_bag_chooser = + sec_pkcs12_choose_crl_bag_type; + +static const SEC_ASN1TemplateChooserPtr sec_pkcs12_cert_bag_chooser = + sec_pkcs12_choose_cert_bag_type; + +static const SEC_ASN1TemplateChooserPtr sec_pkcs12_safe_bag_chooser = + sec_pkcs12_choose_safe_bag_type; + +static const SEC_ASN1TemplateChooserPtr sec_pkcs12_attr_chooser = + sec_pkcs12_choose_attr_type; + +const SEC_ASN1Template sec_PKCS12PointerToCertBagTemplate[] = { + { SEC_ASN1_POINTER, 0, sec_PKCS12CertBagTemplate } +}; + +const SEC_ASN1Template sec_PKCS12PointerToCRLBagTemplate[] = { + { SEC_ASN1_POINTER, 0, sec_PKCS12CRLBagTemplate } +}; + +const SEC_ASN1Template sec_PKCS12PointerToSecretBagTemplate[] = { + { SEC_ASN1_POINTER, 0, sec_PKCS12SecretBagTemplate } +}; + +const SEC_ASN1Template sec_PKCS12PointerToSafeContentsTemplate[] = { + { SEC_ASN1_POINTER, 0, sec_PKCS12SafeContentsTemplate } +}; + +const SEC_ASN1Template sec_PKCS12PFXItemTemplate[] = { + { SEC_ASN1_SEQUENCE | SEC_ASN1_MAY_STREAM, 0, NULL, + sizeof(sec_PKCS12PFXItem) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_INTEGER, + offsetof(sec_PKCS12PFXItem, version) }, + { SEC_ASN1_ANY | SEC_ASN1_MAY_STREAM, + offsetof(sec_PKCS12PFXItem, encodedAuthSafe) }, + { SEC_ASN1_ANY | SEC_ASN1_MAY_STREAM, + offsetof(sec_PKCS12PFXItem, encodedMacData) }, + { 0 } +}; + +const SEC_ASN1Template sec_PKCS12MacDataTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(sec_PKCS12MacData) }, + { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(sec_PKCS12MacData, safeMac), + SEC_ASN1_SUB(sgn_DigestInfoTemplate) }, + { SEC_ASN1_OCTET_STRING, offsetof(sec_PKCS12MacData, macSalt) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_INTEGER, offsetof(sec_PKCS12MacData, iter) }, + { 0 } +}; + +const SEC_ASN1Template sec_PKCS12AuthenticatedSafeTemplate[] = { + { SEC_ASN1_SEQUENCE_OF | SEC_ASN1_MAY_STREAM | SEC_ASN1_XTRN, + offsetof(sec_PKCS12AuthenticatedSafe, encodedSafes), + SEC_ASN1_SUB(SEC_AnyTemplate) } +}; + +const SEC_ASN1Template sec_PKCS12SafeBagTemplate[] = { + { SEC_ASN1_SEQUENCE | SEC_ASN1_MAY_STREAM, 0, NULL, + sizeof(sec_PKCS12SafeBag) }, + { SEC_ASN1_OBJECT_ID, offsetof(sec_PKCS12SafeBag, safeBagType) }, + { SEC_ASN1_EXPLICIT | SEC_ASN1_DYNAMIC | SEC_ASN1_CONSTRUCTED | + SEC_ASN1_MAY_STREAM | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(sec_PKCS12SafeBag, safeBagContent), + &sec_pkcs12_safe_bag_chooser }, + { SEC_ASN1_SET_OF | SEC_ASN1_OPTIONAL, offsetof(sec_PKCS12SafeBag, attribs), + sec_PKCS12AttributeTemplate }, + { 0 } +}; + +const SEC_ASN1Template sec_PKCS12SafeContentsTemplate[] = { + { SEC_ASN1_SEQUENCE_OF | SEC_ASN1_MAY_STREAM, + offsetof(sec_PKCS12SafeContents, safeBags), + sec_PKCS12SafeBagTemplate } +}; + +const SEC_ASN1Template sec_PKCS12SequenceOfAnyTemplate[] = { + { SEC_ASN1_SEQUENCE_OF | SEC_ASN1_MAY_STREAM | SEC_ASN1_XTRN, 0, + SEC_ASN1_SUB(SEC_AnyTemplate) } +}; + +const SEC_ASN1Template sec_PKCS12NestedSafeContentsDecodeTemplate[] = { + { SEC_ASN1_EXPLICIT | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | 0, + offsetof(sec_PKCS12SafeContents, encodedSafeBags), + sec_PKCS12SequenceOfAnyTemplate } +}; + +const SEC_ASN1Template sec_PKCS12SafeContentsDecodeTemplate[] = { + { SEC_ASN1_SEQUENCE_OF | SEC_ASN1_MAY_STREAM | SEC_ASN1_XTRN, + offsetof(sec_PKCS12SafeContents, encodedSafeBags), + SEC_ASN1_SUB(SEC_AnyTemplate) } +}; + +const SEC_ASN1Template sec_PKCS12CRLBagTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(sec_PKCS12CRLBag) }, + { SEC_ASN1_OBJECT_ID, offsetof(sec_PKCS12CRLBag, bagID) }, + { SEC_ASN1_DYNAMIC | SEC_ASN1_POINTER, + offsetof(sec_PKCS12CRLBag, value), &sec_pkcs12_crl_bag_chooser }, + { 0 } +}; + +const SEC_ASN1Template sec_PKCS12CertBagTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(sec_PKCS12CertBag) }, + { SEC_ASN1_OBJECT_ID, offsetof(sec_PKCS12CertBag, bagID) }, + { SEC_ASN1_DYNAMIC | SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED | + SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(sec_PKCS12CertBag, value), &sec_pkcs12_cert_bag_chooser }, + { 0 } +}; + +const SEC_ASN1Template sec_PKCS12SecretBagTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(sec_PKCS12SecretBag) }, + { SEC_ASN1_OBJECT_ID, offsetof(sec_PKCS12SecretBag, secretType) }, + { SEC_ASN1_ANY, offsetof(sec_PKCS12SecretBag, secretContent) }, + { 0 } +}; + +const SEC_ASN1Template sec_PKCS12AttributeTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(sec_PKCS12Attribute) }, + { SEC_ASN1_OBJECT_ID, offsetof(sec_PKCS12Attribute, attrType) }, + { SEC_ASN1_SET_OF | SEC_ASN1_DYNAMIC, + offsetof(sec_PKCS12Attribute, attrValue), + &sec_pkcs12_attr_chooser }, + { 0 } +}; diff --git a/security/nss/lib/pkcs12/pkcs12.gyp b/security/nss/lib/pkcs12/pkcs12.gyp new file mode 100644 index 0000000000..5b296cc56c --- /dev/null +++ b/security/nss/lib/pkcs12/pkcs12.gyp @@ -0,0 +1,29 @@ +# 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/. +{ + 'includes': [ + '../../coreconf/config.gypi' + ], + 'targets': [ + { + 'target_name': 'pkcs12', + 'type': 'static_library', + 'sources': [ + 'p12creat.c', + 'p12d.c', + 'p12dec.c', + 'p12e.c', + 'p12local.c', + 'p12plcy.c', + 'p12tmpl.c' + ], + 'dependencies': [ + '<(DEPTH)/exports.gyp:nss_exports' + ] + } + ], + 'variables': { + 'module': 'nss' + } +}
\ No newline at end of file diff --git a/security/nss/lib/pkcs12/pkcs12.h b/security/nss/lib/pkcs12/pkcs12.h new file mode 100644 index 0000000000..57b3263fb9 --- /dev/null +++ b/security/nss/lib/pkcs12/pkcs12.h @@ -0,0 +1,41 @@ +/* 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/. */ + +#ifndef _PKCS12_H_ +#define _PKCS12_H_ + +#include "pkcs12t.h" +#include "p12.h" + +SEC_BEGIN_PROTOS + +typedef SECItem *(*SEC_PKCS12GetPassword)(void *arg); + +/* Decode functions */ +/* Import a PFX item. + * der_pfx is the der-encoded pfx item to import. + * pbef, and pbefarg are used to retrieve passwords for the HMAC, + * and any passwords needed for passing to PKCS5 encryption + * routines. + * algorithm is the algorithm by which private keys are stored in + * the key database. this could be a specific algorithm or could + * be based on a global setting. + * slot is the slot to where the certificates will be placed. if NULL, + * the internal key slot is used. + * If the process is successful, a SECSuccess is returned, otherwise + * a failure occurred. + */ +SECStatus +SEC_PKCS12PutPFX(SECItem *der_pfx, SECItem *pwitem, + SEC_PKCS12NicknameCollisionCallback ncCall, + PK11SlotInfo *slot, void *wincx); + +/* check the first two bytes of a file to make sure that it matches + * the desired header for a PKCS 12 file + */ +PRBool SEC_PKCS12ValidData(char *buf, int bufLen, long int totalLength); + +SEC_END_PROTOS + +#endif diff --git a/security/nss/lib/pkcs12/pkcs12t.h b/security/nss/lib/pkcs12/pkcs12t.h new file mode 100644 index 0000000000..db10d28af7 --- /dev/null +++ b/security/nss/lib/pkcs12/pkcs12t.h @@ -0,0 +1,341 @@ +/* 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/. */ + +#ifndef _PKCS12T_H_ +#define _PKCS12T_H_ + +#include "seccomon.h" +#include "secoid.h" +#include "cert.h" +#include "keythi.h" +#include "plarena.h" +#include "secpkcs7.h" +#include "secdig.h" /* for SGNDigestInfo */ + +typedef enum { + SECPKCS12TargetTokenNoCAs, /* CA get loaded intothe fixed token, + * User certs go to target token */ + SECPKCS12TargetTokenIntermediateCAs, /* User certs and intermediates go to + * target token, root certs got to + * fixed token */ + SECPKCS12TargetTokenAllCAs /* All certs go to target token */ +} SECPKCS12TargetTokenCAs; + +/* PKCS12 Structures */ +typedef struct SEC_PKCS12PFXItemStr SEC_PKCS12PFXItem; +typedef struct SEC_PKCS12MacDataStr SEC_PKCS12MacData; +typedef struct SEC_PKCS12AuthenticatedSafeStr SEC_PKCS12AuthenticatedSafe; +typedef struct SEC_PKCS12BaggageItemStr SEC_PKCS12BaggageItem; +typedef struct SEC_PKCS12BaggageStr SEC_PKCS12Baggage; +typedef struct SEC_PKCS12Baggage_OLDStr SEC_PKCS12Baggage_OLD; +typedef struct SEC_PKCS12ESPVKItemStr SEC_PKCS12ESPVKItem; +typedef struct SEC_PKCS12PVKSupportingDataStr SEC_PKCS12PVKSupportingData; +typedef struct SEC_PKCS12PVKAdditionalDataStr SEC_PKCS12PVKAdditionalData; +typedef struct SEC_PKCS12SafeContentsStr SEC_PKCS12SafeContents; +typedef struct SEC_PKCS12SafeBagStr SEC_PKCS12SafeBag; +typedef struct SEC_PKCS12PrivateKeyStr SEC_PKCS12PrivateKey; +typedef struct SEC_PKCS12PrivateKeyBagStr SEC_PKCS12PrivateKeyBag; +typedef struct SEC_PKCS12CertAndCRLBagStr SEC_PKCS12CertAndCRLBag; +typedef struct SEC_PKCS12CertAndCRLStr SEC_PKCS12CertAndCRL; +typedef struct SEC_PKCS12X509CertCRLStr SEC_PKCS12X509CertCRL; +typedef struct SEC_PKCS12SDSICertStr SEC_PKCS12SDSICert; +typedef struct SEC_PKCS12SecretStr SEC_PKCS12Secret; +typedef struct SEC_PKCS12SecretAdditionalStr SEC_PKCS12SecretAdditional; +typedef struct SEC_PKCS12SecretItemStr SEC_PKCS12SecretItem; +typedef struct SEC_PKCS12SecretBagStr SEC_PKCS12SecretBag; + +typedef SECItem *(*SEC_PKCS12PasswordFunc)(SECItem *args); + +/* PKCS12 types */ + +/* stores shrouded keys */ +struct SEC_PKCS12BaggageStr { + PLArenaPool *poolp; + SEC_PKCS12BaggageItem **bags; + + int luggage_size; /* used locally */ +}; + +/* additional data to be associated with keys. currently there + * is nothing defined to be stored here. allows future expansion. + */ +struct SEC_PKCS12PVKAdditionalDataStr { + PLArenaPool *poolp; + SECOidData *pvkAdditionalTypeTag; /* used locally */ + SECItem pvkAdditionalType; + SECItem pvkAdditionalContent; +}; + +/* cert and other supporting data for private keys. used + * for both shrouded and non-shrouded keys. + */ +struct SEC_PKCS12PVKSupportingDataStr { + PLArenaPool *poolp; + SGNDigestInfo **assocCerts; + SECItem regenerable; + SECItem nickname; + SEC_PKCS12PVKAdditionalData pvkAdditional; + SECItem pvkAdditionalDER; + + SECItem uniNickName; + /* used locally */ + int nThumbs; +}; + +/* shrouded key structure. supports only pkcs8 shrouding + * currently. + */ +struct SEC_PKCS12ESPVKItemStr { + PLArenaPool *poolp; /* used locally */ + SECOidData *espvkTag; /* used locally */ + SECItem espvkOID; + SEC_PKCS12PVKSupportingData espvkData; + union { + SECKEYEncryptedPrivateKeyInfo *pkcs8KeyShroud; + } espvkCipherText; + + PRBool duplicate; /* used locally */ + PRBool problem_cert; /* used locally */ + PRBool single_cert; /* used locally */ + int nCerts; /* used locally */ + SECItem derCert; /* used locally */ +}; + +/* generic bag store for the safe. safeBagType identifies + * the type of bag stored. + */ +struct SEC_PKCS12SafeBagStr { + PLArenaPool *poolp; + SECOidData *safeBagTypeTag; /* used locally */ + SECItem safeBagType; + union { + SEC_PKCS12PrivateKeyBag *keyBag; + SEC_PKCS12CertAndCRLBag *certAndCRLBag; + SEC_PKCS12SecretBag *secretBag; + } safeContent; + + SECItem derSafeContent; + SECItem safeBagName; + + SECItem uniSafeBagName; +}; + +/* stores private keys and certificates in a list. each safebag + * has an ID identifying the type of content stored. + */ +struct SEC_PKCS12SafeContentsStr { + PLArenaPool *poolp; + SEC_PKCS12SafeBag **contents; + + /* used for tracking purposes */ + int safe_size; + PRBool old; + PRBool swapUnicode; + PRBool possibleSwapUnicode; +}; + +/* private key structure which holds encrypted private key and + * supporting data including nickname and certificate thumbprint. + */ +struct SEC_PKCS12PrivateKeyStr { + PLArenaPool *poolp; + SEC_PKCS12PVKSupportingData pvkData; + SECKEYPrivateKeyInfo pkcs8data; /* borrowed from PKCS 8 */ + + PRBool duplicate; /* used locally */ + PRBool problem_cert; /* used locally */ + PRBool single_cert; /* used locally */ + int nCerts; /* used locally */ + SECItem derCert; /* used locally */ +}; + +/* private key bag, holds a (null terminated) list of private key + * structures. + */ +struct SEC_PKCS12PrivateKeyBagStr { + PLArenaPool *poolp; + SEC_PKCS12PrivateKey **privateKeys; + + int bag_size; /* used locally */ +}; + +/* container to hold certificates. currently supports x509 + * and sdsi certificates + */ +struct SEC_PKCS12CertAndCRLStr { + PLArenaPool *poolp; + SECOidData *BagTypeTag; /* used locally */ + SECItem BagID; + union { + SEC_PKCS12X509CertCRL *x509; + SEC_PKCS12SDSICert *sdsi; + } value; + + SECItem derValue; + SECItem nickname; /* used locally */ + PRBool duplicate; /* used locally */ +}; + +/* x509 certificate structure. typically holds the der encoding + * of the x509 certificate. thumbprint contains a digest of the + * certificate + */ +struct SEC_PKCS12X509CertCRLStr { + PLArenaPool *poolp; + SEC_PKCS7ContentInfo certOrCRL; + SGNDigestInfo thumbprint; + + SECItem *derLeafCert; /* used locally */ +}; + +/* sdsi certificate structure. typically holds the der encoding + * of the sdsi certificate. thumbprint contains a digest of the + * certificate + */ +struct SEC_PKCS12SDSICertStr { + PLArenaPool *poolp; + SECItem value; + SGNDigestInfo thumbprint; +}; + +/* contains a null terminated list of certs and crls */ +struct SEC_PKCS12CertAndCRLBagStr { + PLArenaPool *poolp; + SEC_PKCS12CertAndCRL **certAndCRLs; + + int bag_size; /* used locally */ +}; + +/* additional secret information. currently no information + * stored in this structure. + */ +struct SEC_PKCS12SecretAdditionalStr { + PLArenaPool *poolp; + SECOidData *secretTypeTag; /* used locally */ + SECItem secretAdditionalType; + SECItem secretAdditionalContent; +}; + +/* secrets container. this will be used to contain currently + * unspecified secrets. (it's a secret) + */ +struct SEC_PKCS12SecretStr { + PLArenaPool *poolp; + SECItem secretName; + SECItem value; + SEC_PKCS12SecretAdditional secretAdditional; + + SECItem uniSecretName; +}; + +struct SEC_PKCS12SecretItemStr { + PLArenaPool *poolp; + SEC_PKCS12Secret secret; + SEC_PKCS12SafeBag subFolder; +}; + +/* a bag of secrets. holds a null terminated list of secrets. + */ +struct SEC_PKCS12SecretBagStr { + PLArenaPool *poolp; + SEC_PKCS12SecretItem **secrets; + + int bag_size; /* used locally */ +}; + +struct SEC_PKCS12MacDataStr { + SGNDigestInfo safeMac; + SECItem macSalt; +}; + +/* outer transfer unit */ +struct SEC_PKCS12PFXItemStr { + PLArenaPool *poolp; + SEC_PKCS12MacData macData; + SEC_PKCS7ContentInfo authSafe; + + /* for compatibility with beta */ + PRBool old; + SGNDigestInfo old_safeMac; + SECItem old_macSalt; + + /* compatibility between platforms for unicode swapping */ + PRBool swapUnicode; +}; + +struct SEC_PKCS12BaggageItemStr { + PLArenaPool *poolp; + SEC_PKCS12ESPVKItem **espvks; + SEC_PKCS12SafeBag **unencSecrets; + + int nEspvks; + int nSecrets; +}; + +/* stores shrouded keys */ +struct SEC_PKCS12Baggage_OLDStr { + PLArenaPool *poolp; + SEC_PKCS12ESPVKItem **espvks; + + int luggage_size; /* used locally */ +}; + +/* authenticated safe, stores certs, keys, and shrouded keys */ +struct SEC_PKCS12AuthenticatedSafeStr { + PLArenaPool *poolp; + SECItem version; + SECOidData *transportTypeTag; /* local not part of encoding*/ + SECItem transportMode; + SECItem privacySalt; + SEC_PKCS12Baggage baggage; + SEC_PKCS7ContentInfo *safe; + + /* used for beta compatibility */ + PRBool old; + PRBool emptySafe; + SEC_PKCS12Baggage_OLD old_baggage; + SEC_PKCS7ContentInfo old_safe; + PRBool swapUnicode; +}; +#define SEC_PKCS12_PFX_VERSION 1 /* what we create */ + +/* PKCS 12 Templates */ +extern const SEC_ASN1Template SEC_PKCS12PFXItemTemplate_OLD[]; +extern const SEC_ASN1Template SEC_PKCS12AuthenticatedSafeTemplate_OLD[]; +extern const SEC_ASN1Template SEC_PKCS12BaggageTemplate_OLD[]; +extern const SEC_ASN1Template SEC_PKCS12PFXItemTemplate[]; +extern const SEC_ASN1Template SEC_PKCS12MacDataTemplate[]; +extern const SEC_ASN1Template SEC_PKCS12AuthenticatedSafeTemplate[]; +extern const SEC_ASN1Template SEC_PKCS12BaggageTemplate[]; +extern const SEC_ASN1Template SEC_PKCS12ESPVKItemTemplate[]; +extern const SEC_ASN1Template SEC_PKCS12PVKSupportingDataTemplate[]; +extern const SEC_ASN1Template SEC_PKCS12PVKAdditionalTemplate[]; +extern const SEC_ASN1Template SEC_PKCS12SafeContentsTemplate_OLD[]; +extern const SEC_ASN1Template SEC_PKCS12SafeContentsTemplate[]; +extern const SEC_ASN1Template SEC_PKCS12SafeBagTemplate[]; +extern const SEC_ASN1Template SEC_PKCS12PrivateKeyTemplate[]; +extern const SEC_ASN1Template SEC_PKCS12PrivateKeyBagTemplate[]; +extern const SEC_ASN1Template SEC_PKCS12CertAndCRLTemplate[]; +extern const SEC_ASN1Template SEC_PKCS12CertAndCRLBagTemplate[]; +extern const SEC_ASN1Template SEC_PKCS12X509CertCRLTemplate_OLD[]; +extern const SEC_ASN1Template SEC_PKCS12X509CertCRLTemplate[]; +extern const SEC_ASN1Template SEC_PKCS12SDSICertTemplate[]; +extern const SEC_ASN1Template SEC_PKCS12SecretBagTemplate[]; +extern const SEC_ASN1Template SEC_PKCS12SecretTemplate[]; +extern const SEC_ASN1Template SEC_PKCS12SecretItemTemplate[]; +extern const SEC_ASN1Template SEC_PKCS12SecretAdditionalTemplate[]; +extern const SEC_ASN1Template SGN_DigestInfoTemplate[]; +extern const SEC_ASN1Template SEC_PointerToPKCS12KeyBagTemplate[]; +extern const SEC_ASN1Template SEC_PointerToPKCS12CertAndCRLBagTemplate[]; +extern const SEC_ASN1Template SEC_PointerToPKCS12CertAndCRLBagTemplate_OLD[]; +extern const SEC_ASN1Template SEC_PointerToPKCS12SecretBagTemplate[]; +extern const SEC_ASN1Template SEC_PointerToPKCS12X509CertCRLTemplate_OLD[]; +extern const SEC_ASN1Template SEC_PointerToPKCS12X509CertCRLTemplate[]; +extern const SEC_ASN1Template SEC_PointerToPKCS12SDSICertTemplate[]; +extern const SEC_ASN1Template SEC_PKCS12CodedSafeBagTemplate[]; +extern const SEC_ASN1Template SEC_PKCS12CodedCertBagTemplate[]; +extern const SEC_ASN1Template SEC_PKCS12CodedCertAndCRLBagTemplate[]; +extern const SEC_ASN1Template SEC_PKCS12PVKSupportingDataTemplate_OLD[]; +extern const SEC_ASN1Template SEC_PKCS12ESPVKItemTemplate_OLD[]; +#endif |