diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /security/nss/cmd/pk12util | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'security/nss/cmd/pk12util')
-rw-r--r-- | security/nss/cmd/pk12util/Makefile | 48 | ||||
-rw-r--r-- | security/nss/cmd/pk12util/manifest.mn | 23 | ||||
-rw-r--r-- | security/nss/cmd/pk12util/pk12util.c | 1223 | ||||
-rw-r--r-- | security/nss/cmd/pk12util/pk12util.gyp | 30 | ||||
-rw-r--r-- | security/nss/cmd/pk12util/pk12util.h | 40 |
5 files changed, 1364 insertions, 0 deletions
diff --git a/security/nss/cmd/pk12util/Makefile b/security/nss/cmd/pk12util/Makefile new file mode 100644 index 0000000000..74ae200208 --- /dev/null +++ b/security/nss/cmd/pk12util/Makefile @@ -0,0 +1,48 @@ +#! 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). # +####################################################################### + +include ../platlibs.mk + + +####################################################################### +# (5) Execute "global" rules. (OPTIONAL) # +####################################################################### + +include $(CORE_DEPTH)/coreconf/rules.mk + +####################################################################### +# (6) Execute "component" rules. (OPTIONAL) # +####################################################################### + + + +####################################################################### +# (7) Execute "local" rules. (OPTIONAL). # +####################################################################### + + +include ../platrules.mk + diff --git a/security/nss/cmd/pk12util/manifest.mn b/security/nss/cmd/pk12util/manifest.mn new file mode 100644 index 0000000000..5f7e0ab49f --- /dev/null +++ b/security/nss/cmd/pk12util/manifest.mn @@ -0,0 +1,23 @@ +# +# 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 = ../.. + +DEFINES += -DNSPR20 + +# MODULE public and private header directories are implicitly REQUIRED. +MODULE = nss + +CSRCS = \ + pk12util.c \ + $(NULL) + +# The MODULE is always implicitly required. +# Listing it here in REQUIRES makes it appear twice in the cc command line. +REQUIRES = dbm seccmd + +PROGRAM = pk12util + +# USE_STATIC_LIBS = 1 diff --git a/security/nss/cmd/pk12util/pk12util.c b/security/nss/cmd/pk12util/pk12util.c new file mode 100644 index 0000000000..9b88cf0da9 --- /dev/null +++ b/security/nss/cmd/pk12util/pk12util.c @@ -0,0 +1,1223 @@ +/* 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/. */ + +#ifdef _CRTDBG_MAP_ALLOC +#include <stdlib.h> +#include <crtdbg.h> +#endif + +#include "nspr.h" +#include "secutil.h" +#include "pk11func.h" +#include "pkcs12.h" +#include "p12plcy.h" +#include "pk12util.h" +#include "nss.h" +#include "secport.h" +#include "secpkcs5.h" +#include "sechash.h" +#include "certdb.h" + +#define PKCS12_IN_BUFFER_SIZE 200 + +static char *progName; +PRBool pk12_debugging = PR_FALSE; +PRBool dumpRawFile; +static PRBool pk12uForceUnicode; + +PRIntn pk12uErrno = 0; + +static void +Usage() +{ +#define FPS PR_fprintf(PR_STDERR, + FPS "Usage: %s -i importfile [-d certdir] [-P dbprefix] [-h tokenname]\n", + progName); + FPS "\t\t [-k slotpwfile | -K slotpw] [-w p12filepwfile | -W p12filepw]\n"); + FPS "\t\t [-v]\n"); + + FPS "Usage: %s -l listfile [-d certdir] [-P dbprefix] [-h tokenname]\n", + progName); + FPS "\t\t [-k slotpwfile | -K slotpw] [-w p12filepwfile | -W p12filepw]\n"); + FPS "\t\t [-v]\n"); + + FPS "Usage: %s -o exportfile -n certname [-d certdir] [-P dbprefix]\n", + progName); + FPS "\t\t [-c key_cipher] [-C cert_cipher] [-M mac_alg]\n" + "\t\t [-m | --key_len keyLen] [--cert_key_len certKeyLen] [-v]\n"); + FPS "\t\t [-k slotpwfile | -K slotpw]\n" + "\t\t [-w p12filepwfile | -W p12filepw]\n"); + + exit(PK12UERR_USAGE); +} + +static PRBool +p12u_OpenFile(p12uContext *p12cxt, PRBool fileRead) +{ + if (!p12cxt || !p12cxt->filename) { + return PR_FALSE; + } + + if (fileRead) { + p12cxt->file = PR_Open(p12cxt->filename, + PR_RDONLY, 0400); + } else { + p12cxt->file = PR_Open(p12cxt->filename, + PR_CREATE_FILE | PR_RDWR | PR_TRUNCATE, + 0600); + } + + if (!p12cxt->file) { + p12cxt->error = PR_TRUE; + return PR_FALSE; + } + + return PR_TRUE; +} + +static void +p12u_DestroyContext(p12uContext **ppCtx, PRBool removeFile) +{ + if (!ppCtx || !(*ppCtx)) { + return; + } + + if ((*ppCtx)->file != NULL) { + PR_Close((*ppCtx)->file); + } + + if ((*ppCtx)->filename != NULL) { + if (removeFile) { + PR_Delete((*ppCtx)->filename); + } + PL_strfree((*ppCtx)->filename); + (*ppCtx)->filename = NULL; + } + + PR_Free(*ppCtx); + *ppCtx = NULL; +} + +static p12uContext * +p12u_InitContext(PRBool fileImport, char *filename) +{ + p12uContext *p12cxt; + + p12cxt = PORT_ZNew(p12uContext); + if (!p12cxt) { + return NULL; + } + + p12cxt->error = PR_FALSE; + p12cxt->errorValue = 0; + p12cxt->filename = PL_strdup(filename); + + if (!p12u_OpenFile(p12cxt, fileImport)) { + p12u_DestroyContext(&p12cxt, PR_FALSE); + return NULL; + } + + return p12cxt; +} + +SECItem * +P12U_NicknameCollisionCallback(SECItem *old_nick, PRBool *cancel, void *wincx) +{ + char *nick = NULL; + SECItem *ret_nick = NULL; + CERTCertificate *cert = (CERTCertificate *)wincx; + + if (!cancel || !cert) { + pk12uErrno = PK12UERR_USER_CANCELLED; + return NULL; + } + + if (!old_nick) + fprintf(stdout, "pk12util: no nickname for cert in PKCS12 file.\n"); + +#if 0 + /* XXX not handled yet */ + *cancel = PR_TRUE; + return NULL; + +#else + + nick = CERT_MakeCANickname(cert); + if (!nick) { + return NULL; + } + + if (old_nick && old_nick->data && old_nick->len && + PORT_Strlen(nick) == old_nick->len && + !PORT_Strncmp((char *)old_nick->data, nick, old_nick->len)) { + PORT_Free(nick); + PORT_SetError(SEC_ERROR_IO); + return NULL; + } + + fprintf(stdout, "pk12util: using nickname: %s\n", nick); + ret_nick = PORT_ZNew(SECItem); + if (ret_nick == NULL) { + PORT_Free(nick); + return NULL; + } + + ret_nick->data = (unsigned char *)nick; + ret_nick->len = PORT_Strlen(nick); + + return ret_nick; +#endif +} + +static SECStatus +p12u_SwapUnicodeBytes(SECItem *uniItem) +{ + unsigned int i; + unsigned char a; + if ((uniItem == NULL) || (uniItem->len % 2)) { + return SECFailure; + } + for (i = 0; i < uniItem->len; i += 2) { + a = uniItem->data[i]; + uniItem->data[i] = uniItem->data[i + 1]; + uniItem->data[i + 1] = a; + } + return SECSuccess; +} + +static PRBool +p12u_ucs2_ascii_conversion_function(PRBool toUnicode, + unsigned char *inBuf, + unsigned int inBufLen, + unsigned char *outBuf, + unsigned int maxOutBufLen, + unsigned int *outBufLen, + PRBool swapBytes) +{ + SECItem it = { 0 }; + SECItem *dup = NULL; + PRBool ret; + +#ifdef DEBUG_CONVERSION + if (pk12_debugging) { + int i; + printf("Converted from:\n"); + for (i = 0; i < inBufLen; i++) { + printf("%2x ", inBuf[i]); + /*if (i%60 == 0) printf("\n");*/ + } + printf("\n"); + } +#endif + it.data = inBuf; + it.len = inBufLen; + dup = SECITEM_DupItem(&it); + if (!dup) { + return PR_FALSE; + } + /* If converting Unicode to ASCII, swap bytes before conversion + * as neccessary. + */ + if (!toUnicode && swapBytes) { + if (p12u_SwapUnicodeBytes(dup) != SECSuccess) { + SECITEM_ZfreeItem(dup, PR_TRUE); + return PR_FALSE; + } + } + /* Perform the conversion. */ + ret = PORT_UCS2_UTF8Conversion(toUnicode, dup->data, dup->len, + outBuf, maxOutBufLen, outBufLen); + SECITEM_ZfreeItem(dup, PR_TRUE); + +#ifdef DEBUG_CONVERSION + if (pk12_debugging) { + int i; + printf("Converted to:\n"); + for (i = 0; i < *outBufLen; i++) { + printf("%2x ", outBuf[i]); + /*if (i%60 == 0) printf("\n");*/ + } + printf("\n"); + } +#endif + return ret; +} + +SECStatus +P12U_UnicodeConversion(PLArenaPool *arena, SECItem *dest, SECItem *src, + PRBool toUnicode, PRBool swapBytes) +{ + unsigned int allocLen; + if (!dest || !src) { + return SECFailure; + } + allocLen = ((toUnicode) ? (src->len << 2) : src->len); + if (arena) { + dest->data = PORT_ArenaZAlloc(arena, allocLen); + } else { + dest->data = PORT_ZAlloc(allocLen); + } + if (PORT_UCS2_ASCIIConversion(toUnicode, src->data, src->len, + dest->data, allocLen, &dest->len, + swapBytes) == PR_FALSE) { + if (!arena) { + PORT_Free(dest->data); + } + dest->data = NULL; + return SECFailure; + } + return SECSuccess; +} + +/* + * + */ +SECItem * +P12U_GetP12FilePassword(PRBool confirmPw, secuPWData *p12FilePw) +{ + char *p0 = NULL; + SECItem *pwItem = NULL; + + if (p12FilePw == NULL || p12FilePw->source == PW_NONE) { + char *p1 = NULL; + int rc; + for (;;) { + p0 = SECU_GetPasswordString(NULL, + "Enter password for PKCS12 file: "); + if (!confirmPw || p0 == NULL) + break; + p1 = SECU_GetPasswordString(NULL, "Re-enter password: "); + if (p1 == NULL) { + PORT_ZFree(p0, PL_strlen(p0)); + p0 = NULL; + break; + } + rc = PL_strcmp(p0, p1); + PORT_ZFree(p1, PL_strlen(p1)); + if (rc == 0) + break; + PORT_ZFree(p0, PL_strlen(p0)); + } + } else if (p12FilePw->source == PW_FROMFILE) { + p0 = SECU_FilePasswd(NULL, PR_FALSE, p12FilePw->data); + } else { /* Plaintext */ + p0 = PORT_Strdup(p12FilePw->data); + } + + if (p0 == NULL) { + return NULL; + } + pwItem = SECITEM_AllocItem(NULL, NULL, PL_strlen(p0) + 1); + memcpy(pwItem->data, p0, pwItem->len); + + PORT_ZFree(p0, PL_strlen(p0)); + + return pwItem; +} + +SECStatus +P12U_InitSlot(PK11SlotInfo *slot, secuPWData *slotPw) +{ + SECStatus rv; + + /* New databases, initialize keydb password. */ + if (PK11_NeedUserInit(slot)) { + rv = SECU_ChangePW(slot, + (slotPw->source == PW_PLAINTEXT) ? slotPw->data : 0, + (slotPw->source == PW_FROMFILE) ? slotPw->data : 0); + if (rv != SECSuccess) { + SECU_PrintError(progName, "Failed to initialize slot \"%s\"", + PK11_GetSlotName(slot)); + return SECFailure; + } + } + + if (PK11_Authenticate(slot, PR_TRUE, slotPw) != SECSuccess) { + SECU_PrintError(progName, + "Failed to authenticate to PKCS11 slot"); + PORT_SetError(SEC_ERROR_USER_CANCELLED); + pk12uErrno = PK12UERR_USER_CANCELLED; + return SECFailure; + } + + return SECSuccess; +} + +/* This routine takes care of getting the PKCS12 file password, then reading and + * verifying the file. It returns the decoder context and a filled in password. + * (The password is needed by P12U_ImportPKCS12Object() to import the private + * key.) + */ +SEC_PKCS12DecoderContext * +p12U_ReadPKCS12File(SECItem *uniPwp, char *in_file, PK11SlotInfo *slot, + secuPWData *slotPw, secuPWData *p12FilePw) +{ + SEC_PKCS12DecoderContext *p12dcx = NULL; + p12uContext *p12cxt = NULL; + SECItem *pwitem = NULL; + SECItem p12file = { 0 }; + SECStatus rv = SECFailure; + PRBool swapUnicode = PR_FALSE; + PRBool forceUnicode = pk12uForceUnicode; + PRBool trypw; + int error; + +#ifdef IS_LITTLE_ENDIAN + swapUnicode = PR_TRUE; +#endif + + p12cxt = p12u_InitContext(PR_TRUE, in_file); + if (!p12cxt) { + SECU_PrintError(progName, "File Open failed: %s", in_file); + pk12uErrno = PK12UERR_INIT_FILE; + return NULL; + } + + /* get the password */ + pwitem = P12U_GetP12FilePassword(PR_FALSE, p12FilePw); + if (!pwitem) { + pk12uErrno = PK12UERR_USER_CANCELLED; + goto done; + } + + if (P12U_UnicodeConversion(NULL, uniPwp, pwitem, PR_TRUE, + swapUnicode) != SECSuccess) { + SECU_PrintError(progName, "Unicode conversion failed"); + pk12uErrno = PK12UERR_UNICODECONV; + goto done; + } + rv = SECU_FileToItem(&p12file, p12cxt->file); + if (rv != SECSuccess) { + SECU_PrintError(progName, "Failed to read from import file"); + goto done; + } + + do { + trypw = PR_FALSE; /* normally we do this once */ + rv = SECFailure; + /* init the decoder context */ + p12dcx = SEC_PKCS12DecoderStart(uniPwp, slot, slotPw, + NULL, NULL, NULL, NULL, NULL); + if (!p12dcx) { + SECU_PrintError(progName, "PKCS12 decoder start failed"); + pk12uErrno = PK12UERR_PK12DECODESTART; + break; + } + + /* decode the item */ + rv = SEC_PKCS12DecoderUpdate(p12dcx, p12file.data, p12file.len); + + if (rv != SECSuccess) { + error = PR_GetError(); + if (error == SEC_ERROR_DECRYPTION_DISALLOWED) { + PR_SetError(error, 0); + break; + } + SECU_PrintError(progName, "PKCS12 decoding failed"); + pk12uErrno = PK12UERR_DECODE; + } + + /* does the blob authenticate properly? */ + rv = SEC_PKCS12DecoderVerify(p12dcx); + if (rv != SECSuccess) { + if (uniPwp->len == 2) { + /* this is a null PW, try once more with a zero-length PW + instead of a null string */ + SEC_PKCS12DecoderFinish(p12dcx); + uniPwp->len = 0; + trypw = PR_TRUE; + } else if (forceUnicode == pk12uForceUnicode) { + /* try again with a different password encoding */ + forceUnicode = !pk12uForceUnicode; + rv = NSS_OptionSet(__NSS_PKCS12_DECODE_FORCE_UNICODE, + forceUnicode); + if (rv != SECSuccess) { + SECU_PrintError(progName, "PKCS12 decoding failed to set option"); + pk12uErrno = PK12UERR_DECODEVERIFY; + break; + } + SEC_PKCS12DecoderFinish(p12dcx); + trypw = PR_TRUE; + } else { + SECU_PrintError(progName, "PKCS12 decode not verified"); + pk12uErrno = PK12UERR_DECODEVERIFY; + break; + } + } + } while (trypw == PR_TRUE); + + /* revert the option setting */ + if (forceUnicode != pk12uForceUnicode) { + rv = NSS_OptionSet(__NSS_PKCS12_DECODE_FORCE_UNICODE, pk12uForceUnicode); + if (rv != SECSuccess) { + SECU_PrintError(progName, "PKCS12 decoding failed to set option"); + pk12uErrno = PK12UERR_DECODEVERIFY; + } + } + /* rv has been set at this point */ + +done: + if (rv != SECSuccess) { + if (p12dcx != NULL) { + SEC_PKCS12DecoderFinish(p12dcx); + p12dcx = NULL; + } + if (uniPwp->data) { + SECITEM_ZfreeItem(uniPwp, PR_FALSE); + uniPwp->data = NULL; + } + } + PR_Close(p12cxt->file); + p12cxt->file = NULL; + /* PK11_FreeSlot(slot); */ + p12u_DestroyContext(&p12cxt, PR_FALSE); + + if (pwitem) { + SECITEM_ZfreeItem(pwitem, PR_TRUE); + } + SECITEM_ZfreeItem(&p12file, PR_FALSE); + return p12dcx; +} + +/* + * given a filename for pkcs12 file, imports certs and keys + * + * Change: altitude + * I've changed this function so that it takes the keydb and pkcs12 file + * passwords from files. The "pwdKeyDB" and "pwdP12File" + * variables have been added for this purpose. + */ +PRIntn +P12U_ImportPKCS12Object(char *in_file, PK11SlotInfo *slot, + secuPWData *slotPw, secuPWData *p12FilePw) +{ + SEC_PKCS12DecoderContext *p12dcx = NULL; + SECItem uniPwitem = { 0 }; + PRBool forceUnicode = pk12uForceUnicode; + PRBool trypw; + SECStatus rv = SECFailure; + + rv = P12U_InitSlot(slot, slotPw); + if (rv != SECSuccess) { + SECU_PrintError(progName, "Failed to authenticate to \"%s\"", + PK11_GetSlotName(slot)); + pk12uErrno = PK12UERR_PK11GETSLOT; + return rv; + } + + do { + trypw = PR_FALSE; /* normally we do this once */ + rv = SECFailure; + p12dcx = p12U_ReadPKCS12File(&uniPwitem, in_file, slot, slotPw, p12FilePw); + + if (p12dcx == NULL) { + goto loser; + } + + /* make sure the bags are okey dokey -- nicknames correct, etc. */ + rv = SEC_PKCS12DecoderValidateBags(p12dcx, P12U_NicknameCollisionCallback); + if (rv != SECSuccess) { + if (PORT_GetError() == SEC_ERROR_PKCS12_DUPLICATE_DATA) { + pk12uErrno = PK12UERR_CERTALREADYEXISTS; + } else { + pk12uErrno = PK12UERR_DECODEVALIBAGS; + } + SECU_PrintError(progName, "PKCS12 decode validate bags failed"); + goto loser; + } + + /* stuff 'em in */ + if (forceUnicode != pk12uForceUnicode) { + rv = NSS_OptionSet(__NSS_PKCS12_DECODE_FORCE_UNICODE, + forceUnicode); + if (rv != SECSuccess) { + SECU_PrintError(progName, "PKCS12 decode set option failed"); + pk12uErrno = PK12UERR_DECODEIMPTBAGS; + goto loser; + } + } + rv = SEC_PKCS12DecoderImportBags(p12dcx); + if (rv != SECSuccess) { + if (PR_GetError() == SEC_ERROR_PKCS12_UNABLE_TO_IMPORT_KEY && + forceUnicode == pk12uForceUnicode) { + /* try again with a different password encoding */ + forceUnicode = !pk12uForceUnicode; + SEC_PKCS12DecoderFinish(p12dcx); + SECITEM_ZfreeItem(&uniPwitem, PR_FALSE); + trypw = PR_TRUE; + } else { + SECU_PrintError(progName, "PKCS12 decode import bags failed"); + pk12uErrno = PK12UERR_DECODEIMPTBAGS; + goto loser; + } + } + } while (trypw); + + /* revert the option setting */ + if (forceUnicode != pk12uForceUnicode) { + rv = NSS_OptionSet(__NSS_PKCS12_DECODE_FORCE_UNICODE, pk12uForceUnicode); + if (rv != SECSuccess) { + SECU_PrintError(progName, "PKCS12 decode set option failed"); + pk12uErrno = PK12UERR_DECODEIMPTBAGS; + goto loser; + } + } + + fprintf(stdout, "%s: PKCS12 IMPORT SUCCESSFUL\n", progName); + rv = SECSuccess; + +loser: + if (p12dcx) { + SEC_PKCS12DecoderFinish(p12dcx); + } + + if (uniPwitem.data) { + SECITEM_ZfreeItem(&uniPwitem, PR_FALSE); + } + + return rv; +} + +static void +p12u_DoPKCS12ExportErrors() +{ + PRErrorCode error_value; + + error_value = PORT_GetError(); + if ((error_value == SEC_ERROR_PKCS12_UNABLE_TO_EXPORT_KEY) || + (error_value == SEC_ERROR_PKCS12_UNABLE_TO_LOCATE_OBJECT_BY_NAME) || + (error_value == SEC_ERROR_PKCS12_UNABLE_TO_WRITE)) { + fputs(SECU_Strerror(error_value), stderr); + } else if (error_value == SEC_ERROR_USER_CANCELLED) { + ; + } else { + fputs(SECU_Strerror(SEC_ERROR_EXPORTING_CERTIFICATES), stderr); + } +} + +static void +p12u_WriteToExportFile(void *arg, const char *buf, unsigned long len) +{ + p12uContext *p12cxt = arg; + int writeLen; + + if (!p12cxt || (p12cxt->error == PR_TRUE)) { + return; + } + + if (p12cxt->file == NULL) { + p12cxt->errorValue = SEC_ERROR_PKCS12_UNABLE_TO_WRITE; + p12cxt->error = PR_TRUE; + return; + } + + writeLen = PR_Write(p12cxt->file, (unsigned char *)buf, (PRInt32)len); + + if (writeLen != (int)len) { + PR_Close(p12cxt->file); + PL_strfree(p12cxt->filename); + p12cxt->filename = NULL; + p12cxt->file = NULL; + p12cxt->errorValue = SEC_ERROR_PKCS12_UNABLE_TO_WRITE; + p12cxt->error = PR_TRUE; + } +} + +void +P12U_ExportPKCS12Object(char *nn, char *outfile, PK11SlotInfo *inSlot, + SECOidTag cipher, SECOidTag certCipher, SECOidTag hash, + secuPWData *slotPw, secuPWData *p12FilePw) +{ + SEC_PKCS12ExportContext *p12ecx = NULL; + SEC_PKCS12SafeInfo *keySafe = NULL, *certSafe = NULL; + SECItem *pwitem = NULL; + p12uContext *p12cxt = NULL; + CERTCertList *certlist = NULL; + CERTCertListNode *node = NULL; + PK11SlotInfo *slot = NULL; + + if (P12U_InitSlot(inSlot, slotPw) != SECSuccess) { + SECU_PrintError(progName, "Failed to authenticate to \"%s\"", + PK11_GetSlotName(inSlot)); + pk12uErrno = PK12UERR_PK11GETSLOT; + goto loser; + } + certlist = PK11_FindCertsFromNickname(nn, slotPw); + if (!certlist) { + PORT_SetError(SEC_ERROR_UNKNOWN_CERT); + SECU_PrintError(progName, "find user certs from nickname failed"); + pk12uErrno = PK12UERR_FINDCERTBYNN; + return; + } + + if ((SECSuccess != CERT_FilterCertListForUserCerts(certlist)) || + CERT_LIST_EMPTY(certlist)) { + PR_fprintf(PR_STDERR, "%s: no user certs from given nickname\n", + progName); + pk12uErrno = PK12UERR_FINDCERTBYNN; + goto loser; + } + + /* Password to use for PKCS12 file. */ + pwitem = P12U_GetP12FilePassword(PR_TRUE, p12FilePw); + if (!pwitem) { + goto loser; + } + + /* we are passing UTF8, drop the NULL in the normal password value. + * UCS2 conversion will add it back if necessary. This only affects + * password > Blocksize of the Hash function and pkcs5v2 pbe (if password + * <=Blocksize then the password is zero padded anyway, so an extra NULL + * at the end has not effect). This is allows us to work with openssl and + * gnutls. Older versions of NSS already fail to decrypt long passwords + * in this case, so we aren't breaking anyone with this code */ + if ((pwitem->len > 0) && (!pwitem->data[pwitem->len - 1])) { + pwitem->len--; + } + + p12cxt = p12u_InitContext(PR_FALSE, outfile); + if (!p12cxt) { + SECU_PrintError(progName, "Initialization failed: %s", outfile); + pk12uErrno = PK12UERR_INIT_FILE; + goto loser; + } + + if (certlist) { + CERTCertificate *cert = CERT_LIST_HEAD(certlist)->cert; + if (cert) { + slot = cert->slot; /* use the slot from the first matching + certificate to create the context . This is for keygen */ + } + } + if (!slot) { + SECU_PrintError(progName, "cert does not have a slot"); + pk12uErrno = PK12UERR_FINDCERTBYNN; + goto loser; + } + p12ecx = SEC_PKCS12CreateExportContext(NULL, NULL, slot, slotPw); + if (!p12ecx) { + SECU_PrintError(progName, "export context creation failed"); + pk12uErrno = PK12UERR_EXPORTCXCREATE; + goto loser; + } + + if (SEC_PKCS12AddPasswordIntegrity(p12ecx, pwitem, hash) != + SECSuccess) { + SECU_PrintError(progName, "PKCS12 add password integrity failed"); + pk12uErrno = PK12UERR_PK12ADDPWDINTEG; + goto loser; + } + + for (node = CERT_LIST_HEAD(certlist); + !CERT_LIST_END(node, certlist); + node = CERT_LIST_NEXT(node)) { + CERTCertificate *cert = node->cert; + if (!cert->slot) { + SECU_PrintError(progName, "cert does not have a slot"); + pk12uErrno = PK12UERR_FINDCERTBYNN; + goto loser; + } + + keySafe = SEC_PKCS12CreateUnencryptedSafe(p12ecx); + if (certCipher == SEC_OID_UNKNOWN) { + certSafe = keySafe; + } else { + certSafe = + SEC_PKCS12CreatePasswordPrivSafe(p12ecx, pwitem, certCipher); + } + + if (!certSafe || !keySafe) { + SECU_PrintError(progName, "key or cert safe creation failed"); + pk12uErrno = PK12UERR_CERTKEYSAFE; + goto loser; + } + + if (SEC_PKCS12AddCertAndKey(p12ecx, certSafe, NULL, cert, + CERT_GetDefaultCertDB(), keySafe, NULL, + PR_TRUE, pwitem, cipher) != SECSuccess) { + SECU_PrintError(progName, "add cert and key failed"); + pk12uErrno = PK12UERR_ADDCERTKEY; + goto loser; + } + } + + CERT_DestroyCertList(certlist); + certlist = NULL; + + if (SEC_PKCS12Encode(p12ecx, p12u_WriteToExportFile, p12cxt) != + SECSuccess) { + SECU_PrintError(progName, "PKCS12 encode failed"); + pk12uErrno = PK12UERR_ENCODE; + goto loser; + } + + p12u_DestroyContext(&p12cxt, PR_FALSE); + SECITEM_ZfreeItem(pwitem, PR_TRUE); + fprintf(stdout, "%s: PKCS12 EXPORT SUCCESSFUL\n", progName); + SEC_PKCS12DestroyExportContext(p12ecx); + + return; + +loser: + SEC_PKCS12DestroyExportContext(p12ecx); + + if (certlist) { + CERT_DestroyCertList(certlist); + certlist = NULL; + } + + p12u_DestroyContext(&p12cxt, PR_TRUE); + if (pwitem) { + SECITEM_ZfreeItem(pwitem, PR_TRUE); + } + p12u_DoPKCS12ExportErrors(); + return; +} + +PRIntn +P12U_ListPKCS12File(char *in_file, PK11SlotInfo *slot, + secuPWData *slotPw, secuPWData *p12FilePw) +{ + SEC_PKCS12DecoderContext *p12dcx = NULL; + SECItem uniPwitem = { 0 }; + SECStatus rv = SECFailure; + const SEC_PKCS12DecoderItem *dip; + + p12dcx = p12U_ReadPKCS12File(&uniPwitem, in_file, slot, slotPw, p12FilePw); + /* did the blob authenticate properly? */ + if (p12dcx == NULL) { + SECU_PrintError(progName, "PKCS12 decode not verified"); + pk12uErrno = PK12UERR_DECODEVERIFY; + goto loser; + } + rv = SEC_PKCS12DecoderIterateInit(p12dcx); + if (rv != SECSuccess) { + SECU_PrintError(progName, "PKCS12 decode iterate bags failed"); + pk12uErrno = PK12UERR_DECODEIMPTBAGS; + rv = SECFailure; + } else { + int fileCounter = 0; + while (SEC_PKCS12DecoderIterateNext(p12dcx, &dip) == SECSuccess) { + switch (dip->type) { + case SEC_OID_PKCS12_V1_CERT_BAG_ID: + printf("Certificate"); + if (dumpRawFile) { + PRFileDesc *fd; + char fileName[20]; + snprintf(fileName, sizeof(fileName), "file%04d.der", ++fileCounter); + fd = PR_Open(fileName, + PR_CREATE_FILE | PR_RDWR | PR_TRUNCATE, + 0600); + if (!fd) { + SECU_PrintError(progName, + "Cannot create output file"); + } else { + PR_Write(fd, dip->der->data, dip->der->len); + PR_Close(fd); + } + } else if (SECU_PrintSignedData(stdout, dip->der, + (dip->hasKey) ? "(has private key)" + : "", + 0, (SECU_PPFunc)SECU_PrintCertificate) != + 0) { + SECU_PrintError(progName, "PKCS12 print cert bag failed"); + } + if (dip->friendlyName != NULL) { + printf(" Friendly Name: %s\n\n", + dip->friendlyName->data); + } + if (dip->shroudAlg) { + SECU_PrintAlgorithmID(stdout, dip->shroudAlg, + "Encryption algorithm", 1); + } + break; + case SEC_OID_PKCS12_V1_KEY_BAG_ID: + case SEC_OID_PKCS12_V1_PKCS8_SHROUDED_KEY_BAG_ID: + printf("Key"); + if (dip->type == SEC_OID_PKCS12_V1_PKCS8_SHROUDED_KEY_BAG_ID) + printf("(shrouded)"); + printf(":\n"); + if (dip->friendlyName != NULL) { + printf(" Friendly Name: %s\n\n", + dip->friendlyName->data); + } + if (dip->shroudAlg) { + SECU_PrintAlgorithmID(stdout, dip->shroudAlg, + "Encryption algorithm", 1); + } + break; + default: + printf("unknown bag type(%d): %s\n\n", dip->type, + SECOID_FindOIDTagDescription(dip->type)); + break; + } + } + rv = SECSuccess; + } + +loser: + + if (p12dcx) { + SEC_PKCS12DecoderFinish(p12dcx); + } + + if (uniPwitem.data) { + SECITEM_ZfreeItem(&uniPwitem, PR_FALSE); + } + + return rv; +} + +SECOidTag +PKCS12U_FindTagFromString(char *cipherString) +{ + SECOidTag tag; + SECOidData *oid; + + /* future enhancement: accept dotted oid spec? */ + + for (tag = 1; (oid = SECOID_FindOIDByTag(tag)) != NULL; tag++) { + /* only interested in oids that we actually understand */ + if (oid->mechanism == CKM_INVALID_MECHANISM) { + continue; + } + if (PORT_Strcasecmp(oid->desc, cipherString) != 0) { + continue; + } + return tag; + } + return SEC_OID_UNKNOWN; +} + +/* + * use the oid table description to map a user input string to a particular + * oid. + */ +SECOidTag +PKCS12U_MapCipherFromString(char *cipherString, int keyLen) +{ + SECOidTag tag; + SECOidTag cipher; + + /* future enhancement: provide 'friendlier' typed in names for + * pbe mechanisms. + */ + + /* look for the oid tag by Description */ + tag = PKCS12U_FindTagFromString(cipherString); + if (tag == SEC_OID_UNKNOWN) { + return tag; + } + + cipher = SEC_OID_UNKNOWN; + /* we found a match... get the PBE version of this + * cipher... */ + if (!SEC_PKCS5IsAlgorithmPBEAlgTag(tag)) { + cipher = SEC_PKCS5GetPBEAlgorithm(tag, keyLen); + /* no eqivalent PKCS5/PKCS12 cipher, use the raw + * encryption tag we got and pass it directly in, + * pkcs12 will use the pkcsv5 mechanism */ + if (cipher == SEC_OID_PKCS5_PBES2) { + cipher = tag; + } else if (cipher == SEC_OID_PKCS5_PBMAC1) { + /* make sure we have not macing ciphers here */ + cipher = SEC_OID_UNKNOWN; + } + } else { + cipher = tag; + } + return cipher; +} + +SECOidTag +PKCS12U_MapHashFromString(char *hashString) +{ + SECOidTag hashAlg; + + /* look for the oid tag by Description */ + hashAlg = PKCS12U_FindTagFromString(hashString); + if (hashAlg == SEC_OID_UNKNOWN) { + return hashAlg; + } + /* make sure it's a hashing oid */ + if (HASH_GetHashTypeByOidTag(hashAlg) == HASH_AlgNULL) { + return SEC_OID_UNKNOWN; + } + return hashAlg; +} + +static void +p12u_EnableAllCiphers() +{ + SEC_PKCS12EnableCipher(PKCS12_RC4_40, 1); + SEC_PKCS12EnableCipher(PKCS12_RC4_128, 1); + SEC_PKCS12EnableCipher(PKCS12_RC2_CBC_40, 1); + SEC_PKCS12EnableCipher(PKCS12_RC2_CBC_128, 1); + SEC_PKCS12EnableCipher(PKCS12_DES_56, 1); + SEC_PKCS12EnableCipher(PKCS12_DES_EDE3_168, 1); + SEC_PKCS12EnableCipher(PKCS12_AES_CBC_128, 1); + SEC_PKCS12EnableCipher(PKCS12_AES_CBC_192, 1); + SEC_PKCS12EnableCipher(PKCS12_AES_CBC_256, 1); + SEC_PKCS12SetPreferredCipher(PKCS12_AES_CBC_256, 1); +} + +static PRUintn +P12U_Init(char *dir, char *dbprefix, PRBool listonly) +{ + SECStatus rv; + PK11_SetPasswordFunc(SECU_GetModulePassword); + + PR_Init(PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1); + if (listonly && NSS_NoDB_Init("") == SECSuccess) { + rv = SECSuccess; + } else { + rv = NSS_Initialize(dir, dbprefix, dbprefix, "secmod.db", 0); + } + if (rv != SECSuccess) { + SECU_PrintPRandOSError(progName); + exit(-1); + } + + /* setup unicode callback functions */ + PORT_SetUCS2_ASCIIConversionFunction(p12u_ucs2_ascii_conversion_function); + /* use the defaults for UCS4-UTF8 and UCS2-UTF8 */ + + p12u_EnableAllCiphers(); + + return 0; +} + +enum { + opt_CertDir = 0, + opt_TokenName, + opt_Import, + opt_SlotPWFile, + opt_SlotPW, + opt_List, + opt_Nickname, + opt_Export, + opt_Raw, + opt_P12FilePWFile, + opt_P12FilePW, + opt_DBPrefix, + opt_Debug, + opt_Cipher, + opt_CertCipher, + opt_KeyLength, + opt_CertKeyLength, + opt_Mac +}; + +static secuCommandFlag pk12util_options[] = { + { /* opt_CertDir */ 'd', PR_TRUE, 0, PR_FALSE }, + { /* opt_TokenName */ 'h', PR_TRUE, 0, PR_FALSE }, + { /* opt_Import */ 'i', PR_TRUE, 0, PR_FALSE }, + { /* opt_SlotPWFile */ 'k', PR_TRUE, 0, PR_FALSE }, + { /* opt_SlotPW */ 'K', PR_TRUE, 0, PR_FALSE }, + { /* opt_List */ 'l', PR_TRUE, 0, PR_FALSE }, + { /* opt_Nickname */ 'n', PR_TRUE, 0, PR_FALSE }, + { /* opt_Export */ 'o', PR_TRUE, 0, PR_FALSE }, + { /* opt_Raw */ 'r', PR_FALSE, 0, PR_FALSE }, + { /* opt_P12FilePWFile */ 'w', PR_TRUE, 0, PR_FALSE }, + { /* opt_P12FilePW */ 'W', PR_TRUE, 0, PR_FALSE }, + { /* opt_DBPrefix */ 'P', PR_TRUE, 0, PR_FALSE }, + { /* opt_Debug */ 'v', PR_FALSE, 0, PR_FALSE }, + { /* opt_Cipher */ 'c', PR_TRUE, 0, PR_FALSE }, + { /* opt_CertCipher */ 'C', PR_TRUE, 0, PR_FALSE }, + { /* opt_KeyLength */ 'm', PR_TRUE, 0, PR_FALSE, "key_len" }, + { /* opt_CertKeyLength */ 0, PR_TRUE, 0, PR_FALSE, "cert_key_len" }, + { /* opt_Mac */ 'M', PR_TRUE, 0, PR_FALSE, PR_FALSE } +}; + +int +main(int argc, char **argv) +{ + secuPWData slotPw = { PW_NONE, NULL }; + secuPWData p12FilePw = { PW_NONE, NULL }; + PK11SlotInfo *slot; + char *slotname = NULL; + char *import_file = NULL; + char *export_file = NULL; + char *dbprefix = ""; + SECStatus rv; + SECOidTag cipher = SEC_OID_AES_256_CBC; + SECOidTag hash = SEC_OID_SHA256; + SECOidTag certCipher = SEC_OID_AES_128_CBC; + int keyLen = 0; + int certKeyLen = 0; + secuCommand pk12util; + PRInt32 forceUnicode; + +#ifdef _CRTDBG_MAP_ALLOC + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); +#endif + + pk12util.numCommands = 0; + pk12util.commands = 0; + pk12util.numOptions = sizeof(pk12util_options) / sizeof(secuCommandFlag); + pk12util.options = pk12util_options; + + progName = strrchr(argv[0], '/'); + progName = progName ? progName + 1 : argv[0]; + + rv = SECU_ParseCommandLine(argc, argv, progName, &pk12util); + + if (rv != SECSuccess) + Usage(); + + pk12_debugging = pk12util.options[opt_Debug].activated; + + if ((pk12util.options[opt_Import].activated + + pk12util.options[opt_Export].activated + + pk12util.options[opt_List].activated) != 1) { + Usage(); + } + + if (pk12util.options[opt_Export].activated && + !pk12util.options[opt_Nickname].activated) { + Usage(); + } + + rv = NSS_OptionGet(__NSS_PKCS12_DECODE_FORCE_UNICODE, &forceUnicode); + if (rv != SECSuccess) { + SECU_PrintError(progName, + "Failed to get NSS_PKCS12_DECODE_FORCE_UNICODE option"); + Usage(); + } + pk12uForceUnicode = forceUnicode; + + slotname = SECU_GetOptionArg(&pk12util, opt_TokenName); + + import_file = (pk12util.options[opt_List].activated) ? SECU_GetOptionArg(&pk12util, opt_List) + : SECU_GetOptionArg(&pk12util, opt_Import); + export_file = SECU_GetOptionArg(&pk12util, opt_Export); + + if (pk12util.options[opt_P12FilePWFile].activated) { + p12FilePw.source = PW_FROMFILE; + p12FilePw.data = PORT_Strdup(pk12util.options[opt_P12FilePWFile].arg); + } + + if (pk12util.options[opt_P12FilePW].activated) { + p12FilePw.source = PW_PLAINTEXT; + p12FilePw.data = PORT_Strdup(pk12util.options[opt_P12FilePW].arg); + } + + if (pk12util.options[opt_SlotPWFile].activated) { + slotPw.source = PW_FROMFILE; + slotPw.data = PORT_Strdup(pk12util.options[opt_SlotPWFile].arg); + } + + if (pk12util.options[opt_SlotPW].activated) { + slotPw.source = PW_PLAINTEXT; + slotPw.data = PORT_Strdup(pk12util.options[opt_SlotPW].arg); + } + + if (pk12util.options[opt_CertDir].activated) { + SECU_ConfigDirectory(pk12util.options[opt_CertDir].arg); + } + if (pk12util.options[opt_DBPrefix].activated) { + dbprefix = pk12util.options[opt_DBPrefix].arg; + } + if (pk12util.options[opt_Raw].activated) { + dumpRawFile = PR_TRUE; + } + if (pk12util.options[opt_KeyLength].activated) { + keyLen = atoi(pk12util.options[opt_KeyLength].arg); + } + if (pk12util.options[opt_CertKeyLength].activated) { + certKeyLen = atoi(pk12util.options[opt_CertKeyLength].arg); + } + + P12U_Init(SECU_ConfigDirectory(NULL), dbprefix, + pk12util.options[opt_List].activated); + + if (!slotname || PL_strcmp(slotname, "internal") == 0) + slot = PK11_GetInternalKeySlot(); + else + slot = PK11_FindSlotByName(slotname); + + if (!slot) { + SECU_PrintError(progName, "Invalid slot \"%s\"", slotname); + pk12uErrno = PK12UERR_PK11GETSLOT; + goto done; + } + + if (pk12util.options[opt_Cipher].activated) { + char *cipherString = pk12util.options[opt_Cipher].arg; + + cipher = PKCS12U_MapCipherFromString(cipherString, keyLen); + /* We only want encryption PBE's. make sure we don't have + * any MAC pbes */ + if (cipher == SEC_OID_UNKNOWN) { + PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); + SECU_PrintError(progName, "Algorithm: \"%s\"", cipherString); + pk12uErrno = PK12UERR_INVALIDALGORITHM; + goto done; + } + } + + if (pk12util.options[opt_CertCipher].activated) { + char *cipherString = pk12util.options[opt_CertCipher].arg; + + if (PORT_Strcasecmp(cipherString, "none") == 0) { + certCipher = SEC_OID_UNKNOWN; + } else { + certCipher = PKCS12U_MapCipherFromString(cipherString, certKeyLen); + /* If the user requested a cipher and we didn't find it, then + * don't just silently not encrypt. */ + if (certCipher == SEC_OID_UNKNOWN) { + PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); + SECU_PrintError(progName, "Algorithm: \"%s\"", cipherString); + pk12uErrno = PK12UERR_INVALIDALGORITHM; + goto done; + } + } + } + if (pk12util.options[opt_Mac].activated) { + char *hashString = pk12util.options[opt_Mac].arg; + + hash = PKCS12U_MapHashFromString(hashString); + /* We don't support creating Mac-less pkcs 12 files */ + if (hash == SEC_OID_UNKNOWN) { + PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); + SECU_PrintError(progName, "Algorithm: \"%s\"", hashString); + pk12uErrno = PK12UERR_INVALIDALGORITHM; + goto done; + } + } + + if (pk12util.options[opt_Import].activated) { + P12U_ImportPKCS12Object(import_file, slot, &slotPw, &p12FilePw); + + } else if (pk12util.options[opt_Export].activated) { + P12U_ExportPKCS12Object(pk12util.options[opt_Nickname].arg, + export_file, slot, cipher, certCipher, + hash, &slotPw, &p12FilePw); + + } else if (pk12util.options[opt_List].activated) { + P12U_ListPKCS12File(import_file, slot, &slotPw, &p12FilePw); + + } else { + Usage(); + pk12uErrno = PK12UERR_USAGE; + } + +done: + if (import_file != NULL) + PORT_ZFree(import_file, PL_strlen(import_file)); + if (export_file != NULL) + PORT_ZFree(export_file, PL_strlen(export_file)); + if (slotPw.data != NULL) + PORT_ZFree(slotPw.data, PL_strlen(slotPw.data)); + if (p12FilePw.data != NULL) + PORT_ZFree(p12FilePw.data, PL_strlen(p12FilePw.data)); + if (slot) + PK11_FreeSlot(slot); + if (NSS_Shutdown() != SECSuccess) { + pk12uErrno = 1; + } + PL_ArenaFinish(); + PR_Cleanup(); + return pk12uErrno; +} diff --git a/security/nss/cmd/pk12util/pk12util.gyp b/security/nss/cmd/pk12util/pk12util.gyp new file mode 100644 index 0000000000..5eb59e04b0 --- /dev/null +++ b/security/nss/cmd/pk12util/pk12util.gyp @@ -0,0 +1,30 @@ +# 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', + '../../cmd/platlibs.gypi' + ], + 'targets': [ + { + 'target_name': 'pk12util', + 'type': 'executable', + 'sources': [ + 'pk12util.c' + ], + 'dependencies': [ + '<(DEPTH)/exports.gyp:dbm_exports', + '<(DEPTH)/exports.gyp:nss_exports' + ] + } + ], + 'target_defaults': { + 'defines': [ + 'NSPR20' + ] + }, + 'variables': { + 'module': 'nss' + } +}
\ No newline at end of file diff --git a/security/nss/cmd/pk12util/pk12util.h b/security/nss/cmd/pk12util/pk12util.h new file mode 100644 index 0000000000..d1588814ef --- /dev/null +++ b/security/nss/cmd/pk12util/pk12util.h @@ -0,0 +1,40 @@ +/* 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/. */ + +/* + * ERROR codes in pk12util + * - should be organized better later + */ +#define PK12UERR_USER_CANCELLED 1 +#define PK12UERR_USAGE 2 +#define PK12UERR_CERTDB_OPEN 8 +#define PK12UERR_KEYDB_OPEN 9 +#define PK12UERR_INIT_FILE 10 +#define PK12UERR_UNICODECONV 11 +#define PK12UERR_TMPDIGCREATE 12 +#define PK12UERR_PK11GETSLOT 13 +#define PK12UERR_PK12DECODESTART 14 +#define PK12UERR_IMPORTFILEREAD 15 +#define PK12UERR_DECODE 16 +#define PK12UERR_DECODEVERIFY 17 +#define PK12UERR_DECODEVALIBAGS 18 +#define PK12UERR_DECODEIMPTBAGS 19 +#define PK12UERR_CERTALREADYEXISTS 20 +#define PK12UERR_PATCHDB 22 +#define PK12UERR_GETDEFCERTDB 23 +#define PK12UERR_FINDCERTBYNN 24 +#define PK12UERR_EXPORTCXCREATE 25 +#define PK12UERR_PK12ADDPWDINTEG 26 +#define PK12UERR_CERTKEYSAFE 27 +#define PK12UERR_ADDCERTKEY 28 +#define PK12UERR_ENCODE 29 +#define PK12UERR_INVALIDALGORITHM 30 + +/* additions for importing and exporting PKCS 12 files */ +typedef struct p12uContextStr { + char *filename; /* name of file */ + PRFileDesc *file; /* pointer to file */ + PRBool error; /* error occurred? */ + int errorValue; /* which error occurred? */ +} p12uContext; |