diff options
author | chrisk%netscape.com <devnull@localhost> | 2000-06-13 21:56:37 +0000 |
---|---|---|
committer | chrisk%netscape.com <devnull@localhost> | 2000-06-13 21:56:37 +0000 |
commit | cd4705729f6adbb75446f795242faafbb5f1e916 (patch) | |
tree | 21662a7a130dea2cba0c2e99c0048242f97a0fc0 | |
parent | eaa056d41046b41fee0c3b8d6fa93714a6e5474a (diff) | |
download | nss-hg-cd4705729f6adbb75446f795242faafbb5f1e916.tar.gz |
Merge smimetk_branch to tip...
43 files changed, 13100 insertions, 31 deletions
diff --git a/security/nss/cmd/manifest.mn b/security/nss/cmd/manifest.mn index 2b96271f7..8a8b5caa1 100644 --- a/security/nss/cmd/manifest.mn +++ b/security/nss/cmd/manifest.mn @@ -60,6 +60,7 @@ DIRS = lib \ strsclnt \ swfort \ tstclnt \ + smimetools \ $(NULL) TEMPORARILY_DONT_BUILD = \ diff --git a/security/nss/cmd/platlibs.mk b/security/nss/cmd/platlibs.mk index c32653f91..63e206806 100644 --- a/security/nss/cmd/platlibs.mk +++ b/security/nss/cmd/platlibs.mk @@ -43,6 +43,7 @@ ifdef MOZILLA_BSAFE_BUILD endif EXTRA_LIBS += \ + $(DIST)/lib/smime.lib \ $(DIST)/lib/ssl.lib \ $(DIST)/lib/jar.lib \ $(DIST)/lib/zlib.lib \ @@ -81,6 +82,7 @@ ifdef MOZILLA_BSAFE_BUILD CRYPTOLIB=$(DIST)/lib/libbsafe.a endif EXTRA_LIBS += \ + $(DIST)/lib/libsmime.a \ $(DIST)/lib/libssl.a \ $(DIST)/lib/libjar.a \ $(DIST)/lib/libzlib.a \ diff --git a/security/nss/cmd/smimetools/Makefile b/security/nss/cmd/smimetools/Makefile new file mode 100644 index 000000000..9e3263d43 --- /dev/null +++ b/security/nss/cmd/smimetools/Makefile @@ -0,0 +1,73 @@ +#! gmake +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Netscape security libraries. +# +# The Initial Developer of the Original Code is Netscape +# Communications Corporation. Portions created by Netscape are +# Copyright (C) 1994-2000 Netscape Communications Corporation. All +# Rights Reserved. +# +# Contributor(s): +# +# Alternatively, the contents of this file may be used under the +# terms of the GNU General Public License Version 2 or later (the +# "GPL"), in which case the provisions of the GPL are applicable +# instead of those above. If you wish to allow use of your +# version of this file only under the terms of the GPL and not to +# allow others to use your version of this file under the MPL, +# indicate your decision by deleting the provisions above and +# replace them with the notice and other provisions required by +# the GPL. If you do not delete the provisions above, a recipient +# may use your version of this file under either the MPL or the +# GPL. +# + +####################################################################### +# (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 rules.mk + +include ../platrules.mk diff --git a/security/nss/cmd/smimetools/cmsutil.c b/security/nss/cmd/smimetools/cmsutil.c new file mode 100644 index 000000000..ed070f2b6 --- /dev/null +++ b/security/nss/cmd/smimetools/cmsutil.c @@ -0,0 +1,841 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * cmsutil -- A command to work with CMS data + * + * $Id$ + */ + +#include "nspr.h" +#include "secutil.h" +#include "plgetopt.h" +#include "secpkcs7.h" +#include "cert.h" +#include "certdb.h" +#include "cdbhdl.h" +#include "secoid.h" +#include "cms.h" +#include "smime.h" + +#if defined(XP_UNIX) +#include <unistd.h> +#endif + +#include <stdio.h> +#include <string.h> + +extern void SEC_Init(void); /* XXX */ + +static SECStatus +DigestFile(PLArenaPool *poolp, SECItem ***digests, FILE *inFile, SECAlgorithmID **algids) +{ + NSSCMSDigestContext *digcx; + int nb; + char ibuf[4096]; + SECStatus rv; + + digcx = NSS_CMSDigestContext_StartMultiple(algids); + if (digcx == NULL) + return SECFailure; + + for (;;) { + if (feof(inFile)) break; + nb = fread(ibuf, 1, sizeof(ibuf), inFile); + if (nb == 0) { + if (ferror(inFile)) { + PORT_SetError(SEC_ERROR_IO); + NSS_CMSDigestContext_Cancel(digcx); + return SECFailure; + } + /* eof */ + break; + } + NSS_CMSDigestContext_Update(digcx, (const unsigned char *)ibuf, nb); + } + + rv = NSS_CMSDigestContext_FinishMultiple(digcx, poolp, digests); + + return rv; +} + + +static void +Usage(char *progName) +{ + fprintf(stderr, "Usage: %s [-D|-S|-E] [<options>] [-d dbdir] [-u certusage]\n", progName); + fprintf(stderr, " -i infile use infile as source of data (default: stdin)\n"); + fprintf(stderr, " -o outfile use outfile as destination of data (default: stdout)\n"); + fprintf(stderr, " -d dbdir key/cert database directory (default: ~/.netscape)\n"); + fprintf(stderr, " -p password use password as key db password (default: prompt)\n"); + fprintf(stderr, " -u certusage set type of certificate usage (default: certUsageEmailSigner)\n"); + fprintf(stderr, "\n"); + fprintf(stderr, " -D decode a CMS message\n"); + fprintf(stderr, " -c content use this detached content\n"); + fprintf(stderr, " -n suppress output of content\n"); + fprintf(stderr, " -h num generate email headers with info about CMS message\n"); + fprintf(stderr, " -S create a CMS signed message\n"); + fprintf(stderr, " -N nick use certificate named \"nick\" for signing\n"); + fprintf(stderr, " -T do not include content in CMS message\n"); + fprintf(stderr, " -G include a signing time attribute\n"); + fprintf(stderr, " -P include a SMIMECapabilities attribute\n"); + fprintf(stderr, " -Y nick include a EncryptionKeyPreference attribute with cert\n"); + fprintf(stderr, " -E create a CMS enveloped message (NYI)\n"); + fprintf(stderr, " -r id,... create envelope for these recipients,\n"); + fprintf(stderr, " where id can be a certificate nickname or email address\n"); + fprintf(stderr, "\nCert usage codes:\n"); + fprintf(stderr, "%-25s 0 - certUsageSSLClient\n", " "); + fprintf(stderr, "%-25s 1 - certUsageSSLServer\n", " "); + fprintf(stderr, "%-25s 2 - certUsageSSLServerWithStepUp\n", " "); + fprintf(stderr, "%-25s 3 - certUsageSSLCA\n", " "); + fprintf(stderr, "%-25s 4 - certUsageEmailSigner\n", " "); + fprintf(stderr, "%-25s 5 - certUsageEmailRecipient\n", " "); + fprintf(stderr, "%-25s 6 - certUsageObjectSigner\n", " "); + fprintf(stderr, "%-25s 7 - certUsageUserCertImport\n", " "); + fprintf(stderr, "%-25s 8 - certUsageVerifyCA\n", " "); + fprintf(stderr, "%-25s 9 - certUsageProtectedObjectSigner\n", " "); + fprintf(stderr, "%-25s 10 - certUsageStatusResponder\n", " "); + fprintf(stderr, "%-25s 11 - certUsageAnyCA\n", " "); + + exit(-1); +} + +static CERTCertDBHandle certHandleStatic; /* avoid having to allocate */ + +static CERTCertDBHandle * +OpenCertDB(char *progName) +{ + CERTCertDBHandle *certHandle; + SECStatus rv; + + certHandle = &certHandleStatic; + rv = CERT_OpenCertDB(certHandle, PR_FALSE, SECU_CertDBNameCallback, NULL); + if (rv != SECSuccess) { + SECU_PrintError(progName, "could not open cert database"); + return NULL; + } + + return certHandle; +} + +char * +ownpw(PK11SlotInfo *info, PRBool retry, void *arg) +{ + char * passwd = NULL; + + if ( (!retry) && arg ) { + passwd = PL_strdup((char *)arg); + } + + return passwd; +} + +struct optionsStr { + char *password; + SECCertUsage certUsage; + CERTCertDBHandle *certHandle; +}; + +struct decodeOptionsStr { + FILE *contentFile; + int headerLevel; + PRBool suppressContent; +}; + +struct signOptionsStr { + char *nickname; + char *encryptionKeyPreferenceNick; + PRBool signingTime; + PRBool smimeProfile; + PRBool detached; +}; + +struct envelopeOptionsStr { + char **recipients; +}; + +static int +decode(FILE *out, FILE *infile, char *progName, struct optionsStr options, struct decodeOptionsStr decodeOptions) +{ + NSSCMSDecoderContext *dcx; + NSSCMSMessage *cmsg; + NSSCMSContentInfo *cinfo; + NSSCMSSignedData *sigd = NULL; + NSSCMSEnvelopedData *envd; + SECAlgorithmID **digestalgs; + unsigned char buffer[32]; + int nlevels, i, nsigners, j; + char *signercn; + NSSCMSSignerInfo *si; + SECOidTag typetag; + SECItem **digests; + PLArenaPool *poolp; + int nb; + char ibuf[4096]; + PK11PasswordFunc pwcb; + void *pwcb_arg; + SECItem *item; + + pwcb = (options.password != NULL) ? ownpw : NULL; + pwcb_arg = (options.password != NULL) ? (void *)options.password : NULL; + + dcx = NSS_CMSDecoder_Start(NULL, + NULL, NULL, /* content callback */ + pwcb, pwcb_arg, /* password callback */ + NULL, NULL); /* decrypt key callback */ + + for (;;) { + if (feof(infile)) break; + nb = fread(ibuf, 1, sizeof(ibuf), infile); + if (nb == 0) { + if (ferror(infile)) { + fprintf(stderr, "ERROR: file i/o error.\n"); + NSS_CMSDecoder_Cancel(dcx); + return SECFailure; + } + /* eof */ + break; + } + (void)NSS_CMSDecoder_Update(dcx, (const char *)ibuf, nb); + } + cmsg = NSS_CMSDecoder_Finish(dcx); + if (cmsg == NULL) + return -1; + + if (decodeOptions.headerLevel >= 0) { + fprintf(out, "SMIME: ", decodeOptions.headerLevel, i); + } + + nlevels = NSS_CMSMessage_ContentLevelCount(cmsg); + for (i = 0; i < nlevels; i++) { + cinfo = NSS_CMSMessage_ContentLevel(cmsg, i); + typetag = NSS_CMSContentInfo_GetContentTypeTag(cinfo); + + if (decodeOptions.headerLevel >= 0) + fprintf(out, "\tlevel=%d.%d; ", decodeOptions.headerLevel, nlevels - i); + + switch (typetag) { + case SEC_OID_PKCS7_SIGNED_DATA: + if (decodeOptions.headerLevel >= 0) + fprintf(out, "type=signedData; "); + sigd = (NSSCMSSignedData *)NSS_CMSContentInfo_GetContent(cinfo); + if (sigd == NULL) { + SECU_PrintError(progName, "problem finding signedData component"); + return -1; + } + + /* if we have a content file, but no digests for this signedData */ + if (decodeOptions.contentFile != NULL && !NSS_CMSSignedData_HasDigests(sigd)) { + if ((poolp = PORT_NewArena(1024)) == NULL) { + fprintf(stderr, "Out of memory.\n"); + return -1; + } + digestalgs = NSS_CMSSignedData_GetDigestAlgs(sigd); + if (DigestFile (poolp, &digests, decodeOptions.contentFile, digestalgs) != SECSuccess) { + SECU_PrintError(progName, "problem computing message digest"); + return -1; + } + if (NSS_CMSSignedData_SetDigests(sigd, digestalgs, digests) != SECSuccess) { + + SECU_PrintError(progName, "problem setting message digests"); + return -1; + } + PORT_FreeArena(poolp, PR_FALSE); + } + + /* still no digests? */ + if (!NSS_CMSSignedData_HasDigests(sigd)) { + SECU_PrintError(progName, "no message digests"); + return -1; + } + + /* find out about signers */ + nsigners = NSS_CMSSignedData_SignerInfoCount(sigd); + if (decodeOptions.headerLevel >= 0) + fprintf(out, "nsigners=%d; ", nsigners); + if (nsigners == 0) { + /* must be a cert transport message */ + } else { + /* import the certificates */ + if (NSS_CMSSignedData_ImportCerts(sigd, options.certHandle, options.certUsage, PR_FALSE) != SECSuccess) { + SECU_PrintError(progName, "cert import failed"); + return -1; + } + + for (j = 0; j < nsigners; j++) { + si = NSS_CMSSignedData_GetSignerInfo(sigd, j); + + signercn = NSS_CMSSignerInfo_GetSignerCommonName(si); + if (signercn == NULL) + signercn = ""; + if (decodeOptions.headerLevel >= 0) + fprintf(out, "\n\t\tsigner%d.id=\"%s\"; ", j, signercn); + (void)NSS_CMSSignedData_VerifySignerInfo(sigd, j, options.certHandle, options.certUsage); + + if (decodeOptions.headerLevel >= 0) + fprintf(out, "signer%d.status=%s; ", j, NSS_CMSUtil_VerificationStatusToString(NSS_CMSSignerInfo_GetVerificationStatus(si))); + /* XXX what do we do if we don't print headers? */ + } + } + break; + case SEC_OID_PKCS7_ENVELOPED_DATA: + if (decodeOptions.headerLevel >= 0) + fprintf(out, "type=envelopedData; "); + envd = (NSSCMSEnvelopedData *)NSS_CMSContentInfo_GetContent(cinfo); + break; + case SEC_OID_PKCS7_DATA: + if (decodeOptions.headerLevel >= 0) + fprintf(out, "type=data; "); + break; + default: + break; + } + if (decodeOptions.headerLevel >= 0) + fprintf(out, "\n"); + } + + if (!decodeOptions.suppressContent) { + /* XXX only if we do not have detached content... */ + if ((item = NSS_CMSMessage_GetContent(cmsg)) != NULL) { + fwrite(item->data, item->len, 1, out); + } + } + + NSS_CMSMessage_Destroy(cmsg); + return 0; +} + +static void +writeout(void *arg, const char *buf, unsigned long len) +{ + FILE *f = (FILE *)arg; + + if (f != NULL && buf != NULL) + (void)fwrite(buf, len, 1, f); +} + +static int +sign(FILE *out, FILE *infile, char *progName, struct optionsStr options, struct signOptionsStr signOptions) +{ + NSSCMSEncoderContext *ecx; + NSSCMSMessage *cmsg; + NSSCMSContentInfo *cinfo; + NSSCMSSignedData *sigd; + NSSCMSSignerInfo *signerinfo; + int nb; + char ibuf[4096]; + PK11PasswordFunc pwcb; + void *pwcb_arg; + CERTCertificate *cert; + + if (signOptions.nickname == NULL) { + fprintf(stderr, "ERROR: please indicate the nickname of a certificate to sign with.\n"); + return SECFailure; + } + + if ((cert = CERT_FindCertByNickname(options.certHandle, signOptions.nickname)) == NULL) { + SECU_PrintError(progName, "the corresponding cert for key \"%s\" does not exist", + signOptions.nickname); + return SECFailure; + } + + /* + * create the message object + */ + cmsg = NSS_CMSMessage_Create(NULL); /* create a message on its own pool */ + if (cmsg == NULL) { + fprintf(stderr, "ERROR: cannot create CMS message.\n"); + return SECFailure; + } + /* + * build chain of objects: message->signedData->data + */ + if ((sigd = NSS_CMSSignedData_Create(cmsg)) == NULL) { + fprintf(stderr, "ERROR: cannot create CMS signedData object.\n"); + NSS_CMSMessage_Destroy(cmsg); + return SECFailure; + } + cinfo = NSS_CMSMessage_GetContentInfo(cmsg); + if (NSS_CMSContentInfo_SetContent_SignedData(cmsg, cinfo, sigd) != SECSuccess) { + fprintf(stderr, "ERROR: cannot attach CMS signedData object.\n"); + NSS_CMSMessage_Destroy(cmsg); + return SECFailure; + } + + cinfo = NSS_CMSSignedData_GetContentInfo(sigd); + /* we're always passing data in and detaching optionally */ + if (NSS_CMSContentInfo_SetContent_Data(cmsg, cinfo, NULL, signOptions.detached) != SECSuccess) { + fprintf(stderr, "ERROR: cannot attach CMS data object.\n"); + NSS_CMSMessage_Destroy(cmsg); + return SECFailure; + } + + /* + * create & attach signer information + */ + if ((signerinfo = NSS_CMSSignerInfo_Create(cmsg, cert, SEC_OID_SHA1)) == NULL) { + fprintf(stderr, "ERROR: cannot create CMS signerInfo object.\n"); + NSS_CMSMessage_Destroy(cmsg); + return SECFailure; + } + + /* we want the cert chain included for this one */ + if (NSS_CMSSignerInfo_IncludeCerts(signerinfo, NSSCMSCM_CertChain, options.certUsage) != SECSuccess) { + fprintf(stderr, "ERROR: cannot find cert chain.\n"); + NSS_CMSMessage_Destroy(cmsg); + return SECFailure; + } + + if (signOptions.signingTime) { + if (NSS_CMSSignerInfo_AddSigningTime(signerinfo, PR_Now()) != SECSuccess) { + fprintf(stderr, "ERROR: cannot create CMS signerInfo object.\n"); + NSS_CMSMessage_Destroy(cmsg); + return SECFailure; + } + } + if (signOptions.smimeProfile) { + /* TBD */ + } + if (signOptions.encryptionKeyPreferenceNick) { + /* TBD */ + /* get the cert, add it to the message */ + } + + if (NSS_CMSSignedData_AddSignerInfo(sigd, signerinfo) != SECSuccess) { + fprintf(stderr, "ERROR: cannot add CMS signerInfo object.\n"); + NSS_CMSMessage_Destroy(cmsg); + return SECFailure; + } + + /* + * do not add signer independent certificates + */ + + pwcb = (options.password != NULL) ? ownpw : NULL; + pwcb_arg = (options.password != NULL) ? (void *)options.password : NULL; + + ecx = NSS_CMSEncoder_Start(cmsg, + writeout, out, /* DER output callback */ + NULL, NULL, /* destination storage */ + pwcb, pwcb_arg, /* password callback */ + NULL, NULL, /* decrypt key callback */ + NULL, NULL); /* detached digests (not used, we feed) */ + + for (;;) { + if (feof(infile)) break; + nb = fread(ibuf, 1, sizeof(ibuf), infile); + if (nb == 0) { + if (ferror(infile)) { + fprintf(stderr, "ERROR: file i/o error.\n"); + NSS_CMSEncoder_Cancel(ecx); + return SECFailure; + } + /* eof */ + break; + } + (void)NSS_CMSEncoder_Update(ecx, (const char *)ibuf, nb); + } + if (NSS_CMSEncoder_Finish(ecx) != SECSuccess) { + fprintf(stderr, "ERROR: DER encoding problem.\n"); + return SECFailure; + } + + NSS_CMSMessage_Destroy(cmsg); + return SECSuccess; +} + +static int +envelope(FILE *out, FILE *infile, char *progName, struct optionsStr options, struct envelopeOptionsStr envelopeOptions) +{ + SECStatus retval = SECFailure; + NSSCMSEncoderContext *ecx; + NSSCMSMessage *cmsg = NULL; + NSSCMSContentInfo *cinfo; + NSSCMSEnvelopedData *envd; + NSSCMSRecipientInfo *recipientinfo; + int cnt; + int nb; + char ibuf[4096]; + PK11PasswordFunc pwcb; + void *pwcb_arg; + CERTCertificate **recipientcerts; + PLArenaPool *tmppoolp = NULL; + SECOidTag bulkalgtag; + int keysize, i; + + if ((cnt = NSS_CMSArray_Count(envelopeOptions.recipients)) == 0) { + fprintf(stderr, "ERROR: please indicate the nickname of a certificate to sign with.\n"); + goto loser; + } + + if ((tmppoolp = PORT_NewArena (1024)) == NULL) { + fprintf(stderr, "ERROR: out of memory.\n"); + goto loser; + } + + /* XXX find the recipient's certs by email address or nickname */ + if ((recipientcerts = (CERTCertificate **)NSS_CMSArray_Alloc(tmppoolp, cnt)) == NULL) { + fprintf(stderr, "ERROR: out of memory.\n"); + goto loser; + } + + for (i=0; envelopeOptions.recipients[i] != NULL; i++) { + if ((recipientcerts[i] = CERT_FindCertByNicknameOrEmailAddr(options.certHandle, envelopeOptions.recipients[i])) == NULL) { + SECU_PrintError(progName, "cannot find certificate for \"%s\"", envelopeOptions.recipients[i]); + goto loser; + } + } + recipientcerts[i] = NULL; + + /* find a nice bulk algorithm */ + if (NSS_SMIMEUtil_FindBulkAlgForRecipients(recipientcerts, &bulkalgtag, &keysize) != SECSuccess) { + fprintf(stderr, "ERROR: cannot find common bulk algorithm.\n"); + goto loser; + } + + /* + * create the message object + */ + cmsg = NSS_CMSMessage_Create(NULL); /* create a message on its own pool */ + if (cmsg == NULL) { + fprintf(stderr, "ERROR: cannot create CMS message.\n"); + goto loser; + } + /* + * build chain of objects: message->envelopedData->data + */ + if ((envd = NSS_CMSEnvelopedData_Create(cmsg, bulkalgtag, keysize)) == NULL) { + fprintf(stderr, "ERROR: cannot create CMS envelopedData object.\n"); + goto loser; + } + cinfo = NSS_CMSMessage_GetContentInfo(cmsg); + if (NSS_CMSContentInfo_SetContent_EnvelopedData(cmsg, cinfo, envd) != SECSuccess) { + fprintf(stderr, "ERROR: cannot attach CMS envelopedData object.\n"); + goto loser; + } + cinfo = NSS_CMSEnvelopedData_GetContentInfo(envd); + /* we're always passing data in, so the content is NULL */ + if (NSS_CMSContentInfo_SetContent_Data(cmsg, cinfo, NULL, PR_FALSE) != SECSuccess) { + fprintf(stderr, "ERROR: cannot attach CMS data object.\n"); + goto loser; + } + + /* + * create & attach recipient information + */ + for (i = 0; recipientcerts[i] != NULL; i++) { + if ((recipientinfo = NSS_CMSRecipientInfo_Create(cmsg, recipientcerts[i])) == NULL) { + fprintf(stderr, "ERROR: cannot create CMS recipientInfo object.\n"); + goto loser; + } + if (NSS_CMSEnvelopedData_AddRecipient(envd, recipientinfo) != SECSuccess) { + fprintf(stderr, "ERROR: cannot add CMS recipientInfo object.\n"); + goto loser; + } + } + + /* we might need a password for diffie hellman key agreement, so set it up... */ + pwcb = (options.password != NULL) ? ownpw : NULL; + pwcb_arg = (options.password != NULL) ? (void *)options.password : NULL; + + ecx = NSS_CMSEncoder_Start(cmsg, + writeout, out, /* DER output callback */ + NULL, NULL, /* destination storage */ + pwcb, pwcb_arg, /* password callback */ + NULL, NULL, /* decrypt key callback (not used) */ + NULL, NULL); /* detached digests (not used) */ + + for (;;) { + if (feof(infile)) break; + nb = fread(ibuf, 1, sizeof(ibuf), infile); + if (nb == 0) { + if (ferror(infile)) { + fprintf(stderr, "ERROR: file i/o error.\n"); + NSS_CMSEncoder_Cancel(ecx); + goto loser; + } + /* eof */ + break; + } + (void)NSS_CMSEncoder_Update(ecx, (const char *)ibuf, nb); + } + if (NSS_CMSEncoder_Finish(ecx) != SECSuccess) { + fprintf(stderr, "ERROR: DER encoding problem.\n"); + goto loser; + } + + retval = SECSuccess; + +loser: + if (cmsg) + NSS_CMSMessage_Destroy(cmsg); + if (tmppoolp) + PORT_FreeArena(tmppoolp, PR_FALSE); + return retval; +} + +typedef enum { UNKNOWN, DECODE, SIGN, ENCRYPT } Mode; + +int +main(int argc, char **argv) +{ + char *progName; + FILE *outFile, *inFile; + PLOptState *optstate; + PLOptStatus status; + Mode mode = UNKNOWN; + struct decodeOptionsStr decodeOptions = { 0 }; + struct signOptionsStr signOptions = { 0 }; + struct envelopeOptionsStr envelopeOptions = { 0 }; + struct optionsStr options = { 0 }; + int exitstatus; + static char *ptrarray[128] = { 0 }; + int nrecipients = 0; + + progName = strrchr(argv[0], '/'); + progName = progName ? progName+1 : argv[0]; + + inFile = stdin; + outFile = stdout; + mode = UNKNOWN; + decodeOptions.contentFile = NULL; + decodeOptions.suppressContent = PR_FALSE; + decodeOptions.headerLevel = -1; + options.certUsage = certUsageEmailSigner; + options.password = NULL; + signOptions.nickname = NULL; + signOptions.detached = PR_FALSE; + signOptions.signingTime = PR_FALSE; + signOptions.smimeProfile = PR_FALSE; + signOptions.encryptionKeyPreferenceNick = NULL; + envelopeOptions.recipients = NULL; + + /* + * Parse command line arguments + */ + optstate = PL_CreateOptState(argc, argv, "DSEnN:TGPY:h:p:i:c:d:o:s:u:r:"); + while ((status = PL_GetNextOpt(optstate)) == PL_OPT_OK) { + switch (optstate->option) { + case '?': + Usage(progName); + break; + + case 'D': + mode = DECODE; + break; + case 'S': + mode = SIGN; + break; + case 'E': + mode = ENCRYPT; + break; + + case 'n': + if (mode != DECODE) { + fprintf(stderr, "%s: option -n only supported with option -D.\n", progName); + Usage(progName); + exit(1); + } + decodeOptions.suppressContent = PR_TRUE; + break; + + case 'N': + if (mode != SIGN) { + fprintf(stderr, "%s: option -N only supported with option -S.\n", progName); + Usage(progName); + exit(1); + } + signOptions.nickname = strdup(optstate->value); + break; + + case 'Y': + if (mode != SIGN) { + fprintf(stderr, "%s: option -Y only supported with option -S.\n", progName); + Usage(progName); + exit(1); + } + signOptions.encryptionKeyPreferenceNick = strdup(optstate->value); + break; + + case 'T': + if (mode != SIGN) { + fprintf(stderr, "%s: option -T only supported with option -S.\n", progName); + Usage(progName); + exit(1); + } + signOptions.detached = PR_TRUE; + break; + + case 'G': + if (mode != SIGN) { + fprintf(stderr, "%s: option -G only supported with option -S.\n", progName); + Usage(progName); + exit(1); + } + signOptions.signingTime = PR_TRUE; + break; + + case 'P': + if (mode != SIGN) { + fprintf(stderr, "%s: option -P only supported with option -S.\n", progName); + Usage(progName); + exit(1); + } + signOptions.smimeProfile = PR_TRUE; + break; + + case 'h': + if (mode != DECODE) { + fprintf(stderr, "%s: option -h only supported with option -D.\n", progName); + Usage(progName); + exit(1); + } + decodeOptions.headerLevel = atoi(optstate->value); + if (decodeOptions.headerLevel < 0) { + fprintf(stderr, "option -h cannot have a negative value.\n"); + exit(1); + } + break; + + case 'p': + if (!optstate->value) { + fprintf(stderr, "%s: option -p must have a value.\n", progName); + Usage(progName); + exit(1); + } + + options.password = strdup(optstate->value); + break; + + case 'i': + if ((inFile = fopen(optstate->value, "r")) == NULL) { + fprintf(stderr, "%s: unable to open \"%s\" for reading\n", + progName, optstate->value); + exit(1); + } + break; + + case 'c': + if (mode != DECODE) { + fprintf(stderr, "%s: option -c only supported with option -D.\n", progName); + Usage(progName); + exit(1); + } + if ((decodeOptions.contentFile = fopen(optstate->value, "r")) == NULL) { + fprintf(stderr, "%s: unable to open \"%s\" for reading.\n", + progName, optstate->value); + exit(1); + } + break; + + case 'o': + if ((outFile = fopen(optstate->value, "w")) == NULL) { + fprintf(stderr, "%s: unable to open \"%s\" for writing\n", + progName, optstate->value); + exit(1); + } + break; + + case 'r': + if (!optstate->value) { + fprintf(stderr, "%s: option -r must have a value.\n", progName); + Usage(progName); + exit(1); + } +#if 0 + fprintf(stderr, "recipient = %s\n", optstate->value); +#endif + envelopeOptions.recipients = ptrarray; + envelopeOptions.recipients[nrecipients++] = strdup(optstate->value); + envelopeOptions.recipients[nrecipients] = NULL; + break; + + case 'd': + SECU_ConfigDirectory(optstate->value); + break; + + case 'u': { + int usageType; + + usageType = atoi (strdup(optstate->value)); + if (usageType < certUsageSSLClient || usageType > certUsageAnyCA) + return -1; + options.certUsage = (SECCertUsage)usageType; + break; + } + + } + } + + /* Call the libsec initialization routines */ + PR_Init(PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1); + SECU_PKCS11Init(PR_FALSE); + SEC_Init(); + + /* open cert database */ + options.certHandle = OpenCertDB(progName); + if (options.certHandle == NULL) { + return -1; + } + CERT_SetDefaultCertDB(options.certHandle); + + exitstatus = 0; + switch (mode) { + case DECODE: + if (decode(outFile, inFile, progName, options, decodeOptions)) { + SECU_PrintError(progName, "problem decoding"); + exitstatus = 1; + } + break; + case SIGN: + if (sign(outFile, inFile, progName, options, signOptions)) { + SECU_PrintError(progName, "problem signing"); + exitstatus = 1; + } + break; + case ENCRYPT: + if (envelope(outFile, inFile, progName, options, envelopeOptions)) { + SECU_PrintError(progName, "problem encrypting"); + exitstatus = 1; + } + break; + default: + fprintf(stderr, "One of options -D, -S or -E must be set.\n"); + Usage(progName); + exitstatus = 1; + } + if (outFile != stdout) + fclose(outFile); + + exit(exitstatus); +} diff --git a/security/nss/cmd/smimetools/manifest.mn b/security/nss/cmd/smimetools/manifest.mn new file mode 100644 index 000000000..9ed96c658 --- /dev/null +++ b/security/nss/cmd/smimetools/manifest.mn @@ -0,0 +1,45 @@ +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Netscape security libraries. +# +# The Initial Developer of the Original Code is Netscape +# Communications Corporation. Portions created by Netscape are +# Copyright (C) 1994-2000 Netscape Communications Corporation. All +# Rights Reserved. +# +# Contributor(s): +# +# Alternatively, the contents of this file may be used under the +# terms of the GNU General Public License Version 2 or later (the +# "GPL"), in which case the provisions of the GPL are applicable +# instead of those above. If you wish to allow use of your +# version of this file only under the terms of the GPL and not to +# allow others to use your version of this file under the MPL, +# indicate your decision by deleting the provisions above and +# replace them with the notice and other provisions required by +# the GPL. If you do not delete the provisions above, a recipient +# may use your version of this file under either the MPL or the +# GPL. +# + +CORE_DEPTH = ../../.. + +MODULE = security + +CSRCS = cmsutil.c + +MYLIB = $(DIST)/lib/libsmime.a + +REQUIRES = seccmd dbm + +PROGRAM = cmsutil +SCRIPTS = smime diff --git a/security/nss/cmd/smimetools/rules.mk b/security/nss/cmd/smimetools/rules.mk new file mode 100644 index 000000000..51e0baaed --- /dev/null +++ b/security/nss/cmd/smimetools/rules.mk @@ -0,0 +1,37 @@ +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Netscape security libraries. +# +# The Initial Developer of the Original Code is Netscape +# Communications Corporation. Portions created by Netscape are +# Copyright (C) 1994-2000 Netscape Communications Corporation. All +# Rights Reserved. +# +# Contributor(s): +# +# Alternatively, the contents of this file may be used under the +# terms of the GNU General Public License Version 2 or later (the +# "GPL"), in which case the provisions of the GPL are applicable +# instead of those above. If you wish to allow use of your +# version of this file only under the terms of the GPL and not to +# allow others to use your version of this file under the MPL, +# indicate your decision by deleting the provisions above and +# replace them with the notice and other provisions required by +# the GPL. If you do not delete the provisions above, a recipient +# may use your version of this file under either the MPL or the +# GPL. +# +# $Id$ +# + +install:: + $(INSTALL) -m 755 $(SCRIPTS) $(SOURCE_BIN_DIR) diff --git a/security/nss/cmd/smimetools/smime b/security/nss/cmd/smimetools/smime new file mode 100755 index 000000000..d70f3d358 --- /dev/null +++ b/security/nss/cmd/smimetools/smime @@ -0,0 +1,325 @@ +#!/usr/local/bin/perl + +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Netscape security libraries. +# +# The Initial Developer of the Original Code is Netscape +# Communications Corporation. Portions created by Netscape are +# Copyright (C) 1994-2000 Netscape Communications Corporation. All +# Rights Reserved. +# +# Contributor(s): +# +# Alternatively, the contents of this file may be used under the +# terms of the GNU General Public License Version 2 or later (the +# "GPL"), in which case the provisions of the GPL are applicable +# instead of those above. If you wish to allow use of your +# version of this file only under the terms of the GPL and not to +# allow others to use your version of this file under the MPL, +# indicate your decision by deleting the provisions above and +# replace them with the notice and other provisions required by +# the GPL. If you do not delete the provisions above, a recipient +# may use your version of this file under either the MPL or the +# GPL. + +# +# smime.pl - frontend for S/MIME message generation +# +# $Id$ +# + +use Getopt::Std; + +@boundarychars = ( "0" .. "9", "A" .. "F" ); + +# path to cmsutil +$cmsutilpath = "cmsutil"; + +# +# Thanks to Gisle Aas <gisle@aas.no> for the encode_base64 function +# originally taken from MIME-Base64-2.11 at www.cpan.org +# +sub encode_base64($) +{ + my $res = ""; + pos($_[0]) = 0; # ensure start at the beginning + while ($_[0] =~ /(.{1,45})/gs) { + $res .= substr(pack('u', $1), 1); # get rid of length byte after packing + chop($res); + } + $res =~ tr|` -_|AA-Za-z0-9+/|; + # fix padding at the end + my $padding = (3 - length($_[0]) % 3) % 3; + $res =~ s/.{$padding}$/'=' x $padding/e if $padding; + # break encoded string into lines of no more than 76 characters each + $res =~ s/(.{1,76})/$1\n/g; + $res; +} + +# +# encryptentity($entity, $options) - encrypt an S/MIME entity +# +# entity - string containing entire S/MIME entity to encrypt +# options - options for cmsutil +# +# this will generate and return a new multipart/signed entity consisting +# of the canonicalized original content, plus a signature block. +# +sub encryptentity($$) +{ + my ($entity, $cmsutiloptions) = @_; + my $out = ""; + my $boundary; + + $tmpencfile = "/tmp/encryptentity.$$"; + + # + # generate a random boundary string + # + $boundary = "------------ms" . join("", @boundarychars[map{rand @boundarychars }( 1 .. 24 )]); + + # + # tell cmsutil to generate a signed CMS message using the canonicalized data + # The signedData has detached content (-T) and includes a signing time attribute (-G) + # + # if we do not provide a password on the command line, here's where we would be asked for it + # + open(CMS, "|$cmsutilpath -E $cmsutiloptions -o $tmpencfile") or die "ERROR: cannot pipe to cmsutil"; + print CMS $entity; + unless (close(CMS)) { + print STDERR "ERROR: encryption failed.\n"; + unlink($tmpsigfile); + exit 1; + } + + open (ENC, $tmpencfile) or die "ERROR: cannot find newly generated encrypted content"; + + # + # construct a new multipart/signed MIME entity consisting of the original content and + # the signature + # + # (we assume that cmsutil generates a SHA1 digest) + $out .= "Content-Type: application/pkcs7-mime; smime-type=enveloped-data; name=smime.p7m\n"; + $out .= "Content-Transfer-Encoding: base64\n"; + $out .= "Content-Disposition: attachment; filename=smime.p7m\n"; + $out .= "\n"; # end of entity header + + local($/) = undef; # slurp whole file + $out .= encode_base64(<ENC>), "\n"; # append base64-encoded signature + + close(ENC); + unlink($tmpencfile); + + $out; +} + +# +# signentity($entity, $options) - sign an S/MIME entity +# +# entity - string containing entire S/MIME entity to sign +# options - options for cmsutil +# +# this will generate and return a new multipart/signed entity consisting +# of the canonicalized original content, plus a signature block. +# +sub signentity($$) +{ + my ($entity, $cmsutiloptions) = @_; + my $out = ""; + my $boundary; + + $tmpsigfile = "/tmp/signentity.$$"; + + # + # generate a random boundary string + # + $boundary = "------------ms" . join("", @boundarychars[map{rand @boundarychars }( 1 .. 24 )]); + + # + # tell cmsutil to generate a signed CMS message using the canonicalized data + # The signedData has detached content (-T) and includes a signing time attribute (-G) + # + # if we do not provide a password on the command line, here's where we would be asked for it + # + open(CMS, "|$cmsutilpath -S -T -G $cmsutiloptions -o $tmpsigfile") or die "ERROR: cannot pipe to cmsutil"; + print CMS $entity; + unless (close(CMS)) { + print STDERR "ERROR: signature generation failed.\n"; + unlink($tmpsigfile); + exit 1; + } + + open (SIG, $tmpsigfile) or die "ERROR: cannot find newly generated signature"; + + # + # construct a new multipart/signed MIME entity consisting of the original content and + # the signature + # + # (we assume that cmsutil generates a SHA1 digest) + $out .= "Content-Type: multipart/signed; protocol=\"application/pkcs7-signature\"; micalg=sha1; boundary=\"${boundary}\"\n"; + $out .= "\n"; # end of entity header + $out .= "This is a cryptographically signed message in MIME format.\n\n"; # explanatory comment + $out .= "--${boundary}\n"; + $out .= "$entity\n"; # the trailing \n seems to be important + $out .= "--${boundary}\n"; + $out .= "Content-Type: application/pkcs7-signature; name=smime.p7s\n"; + $out .= "Content-Transfer-Encoding: base64\n"; + $out .= "Content-Disposition: attachment; filename=smime.p7s\n"; + $out .= "Content-Description: S/MIME Cryptographic Signature\n"; + $out .= "\n"; # end of signature subentity header + + local($/) = undef; # slurp whole file + $out .= encode_base64(<SIG>), "\n"; # append base64-encoded signature + $out .= "--${boundary}--\n"; + + close(SIG); + unlink($tmpsigfile); + + $out; +} + +sub usage { + print STDERR "usage: smime [options]\n"; + print STDERR " options:\n"; + print STDERR " -S nick generate signed message, use certificate named \"nick\"\n"; + print STDERR " -p passwd use \"passwd\" as security module password\n"; + print STDERR " -E rec1[,rec2...] generate encrypted message for recipients\n"; + print STDERR " -C pathname set pathname of \"cmsutil\"\n"; + print STDERR "\nWith -S or -E, smime will take a regular RFC822 message or MIME entity\n"; + print STDERR "and generate a signed or encrypted S/MIME message with the same headers\n"; + print STDERR "and content from it. The output can be used as input to a MTA.\n"; +} + +# +# start of main procedures +# + +# +# process command line options +# +unless (getopts('S:E:p:C:D')) { + usage(); + exit 1; +} + +unless (defined($opt_S) or defined($opt_E)) { + print STDERR "ERROR: -S and/or -E must be specified.\n"; + usage(); + exit 1; +} + +$signopts = ""; +$encryptopts = ""; + +if (defined($opt_S)) { + $signopts .= "-N \"$opt_S\" "; +} + +if (defined($opt_p)) { + $signopts .= "-p \"$opt_p\" "; +} + +if (defined($opt_E)) { + @recipients = split(",", $opt_E); + $encryptopts .= "-r "; + $encryptopts .= join (" -r ", @recipients); +} + +if (defined($opt_C)) { + $cmsutilpath = $opt_C; +} + +# +# split headers into mime entity headers and RFC822 headers +# The RFC822 headers are preserved and stay on the outer layer of the message +# +$rfc822headers = ""; +$mimeentity = ""; +while (<STDIN>) { + last if (/^$/); + if (/^content-\S+: /i) { + $mimeentity .= $_; + } elsif (/^mime-version: /i) { + ; # skip it + } else { + $rfc822headers .= $_; + } +} + +# +# if there are no MIME entity headers, generate some default ones +# +if ($mimeentity eq "") { + $mimeentity .= "Content-Type: text/plain; charset=us-ascii\n"; + $mimeentity .= "Content-Transfer-Encoding: 7bit\n"; +} + +# +# generate end of header-LF/LF pair +# +$mimeentity .= "\n"; + +# +# slurp in the entity body +# +$saveRS = $/; +$/ = undef; +$mimeentity .= <STDIN>; +$/ = $saveRS; + +if (defined $opt_D) { + # + # decode + # + + + +} else { + # + # encode + # + + # + # canonicalize inner entity (rudimentary yet) + # convert single LFs to CRLF + # if no Content-Transfer-Encoding header present: + # if 8 bit chars present, use Content-Transfer-Encoding: quoted-printable + # otherwise, use Content-Transfer-Encoding: 7bit + # + $mimeentity =~ s/\n/\r\n/mg; + + # + # now do the wrapping + # we sign first, then encrypt because that's what Communicator needs + # + if (defined($opt_S)) { + $mimeentity = signentity($mimeentity, $signopts); + } + + if (defined($opt_E)) { + $mimeentity = encryptentity($mimeentity, $encryptopts); + } + + # + # XXX sign again to do triple wrapping (RFC2634) + # + + # + # now write out the RFC822 headers + # followed by the final $mimeentity + # + print $rfc822headers; + print "MIME-Version: 1.0 (NSS SMIME - http://www.mozilla.org/projects/security)\n"; # set up the flag + print $mimeentity; +} + +exit 0; diff --git a/security/nss/lib/certdb/cert.h b/security/nss/lib/certdb/cert.h index a390af7f3..c12b77489 100644 --- a/security/nss/lib/certdb/cert.h +++ b/security/nss/lib/certdb/cert.h @@ -902,6 +902,9 @@ extern CERTCertificateList * CERT_CertChainFromCert(CERTCertificate *cert, SECCertUsage usage, PRBool includeRoot); +extern CERTCertificateList * +CERT_CertListFromCert(CERTCertificate *cert); + extern void CERT_DestroyCertificateList(CERTCertificateList *list); /* is cert a newer than cert b? */ diff --git a/security/nss/lib/certhigh/certhigh.c b/security/nss/lib/certhigh/certhigh.c index c01de0b81..c75f97a6e 100644 --- a/security/nss/lib/certhigh/certhigh.c +++ b/security/nss/lib/certhigh/certhigh.c @@ -1013,6 +1013,38 @@ loser: return NULL; } +CERTCertificateList * +CERT_CertListFromCert(CERTCertificate *cert) +{ + CERTCertificateList *chain = NULL; + int rv; + PRArenaPool *arena; + + /* arena for SecCertificateList */ + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) goto no_memory; + + /* build the CERTCertificateList */ + chain = (CERTCertificateList *)PORT_ArenaAlloc(arena, sizeof(CERTCertificateList)); + if (chain == NULL) goto no_memory; + chain->certs = (SECItem*)PORT_ArenaAlloc(arena, 1 * sizeof(SECItem)); + if (chain->certs == NULL) goto no_memory; + rv = SECITEM_CopyItem(arena, chain->certs, &(cert->derCert)); + if (rv < 0) goto loser; + chain->len = 1; + chain->arena = arena; + + return chain; + +no_memory: + PORT_SetError(SEC_ERROR_NO_MEMORY); +loser: + if (arena != NULL) { + PORT_FreeArena(arena, PR_FALSE); + } + return NULL; +} + void CERT_DestroyCertificateList(CERTCertificateList *list) { diff --git a/security/nss/lib/manifest.mn b/security/nss/lib/manifest.mn index c1c709025..1c699b99c 100644 --- a/security/nss/lib/manifest.mn +++ b/security/nss/lib/manifest.mn @@ -36,7 +36,8 @@ DEPTH = ../.. DIRS = pkcs7 ssl nss crmf jar \ certhigh pk11wrap cryptohi \ softoken certdb crypto \ - util freebl pkcs12 fortcrypt + util freebl pkcs12 fortcrypt \ + smime # # these dirs are not built at the moment # diff --git a/security/nss/lib/pk11wrap/pk11cert.c b/security/nss/lib/pk11wrap/pk11cert.c index da77fc09b..5304b1248 100644 --- a/security/nss/lib/pk11wrap/pk11cert.c +++ b/security/nss/lib/pk11wrap/pk11cert.c @@ -46,6 +46,7 @@ #include "hasht.h" #include "secoid.h" #include "pkcs7t.h" +#include "cmsreclist.h" #include "certdb.h" #include "secerr.h" @@ -1478,6 +1479,105 @@ pk11_FindCertObjectByTemplate(PK11SlotInfo **slotPtr, /* * We're looking for a cert which we have the private key for that's on the * list of recipients. This searches one slot. + * this is the new version for NSS SMIME code + * this stuff should REALLY be in the SMIME code, but some things in here are not public + * (they should be!) + */ +static CK_OBJECT_HANDLE +pk11_FindCertObjectByRecipientNew(PK11SlotInfo *slot, NSSCMSRecipient **recipientlist, int *rlIndex) +{ + CK_OBJECT_HANDLE certHandle; + CK_OBJECT_CLASS certClass = CKO_CERTIFICATE; + CK_OBJECT_CLASS peerClass ; + CK_ATTRIBUTE searchTemplate[] = { + { CKA_CLASS, NULL, 0 }, + { CKA_ISSUER, NULL, 0 }, + { CKA_SERIAL_NUMBER, NULL, 0} + }; + int count = sizeof(searchTemplate)/sizeof(CK_ATTRIBUTE); + NSSCMSRecipient *ri = NULL; + CK_ATTRIBUTE *attrs; + int i; + + peerClass = CKO_PRIVATE_KEY; + if (!PK11_IsLoggedIn(slot,NULL) && PK11_NeedLogin(slot)) { + peerClass = CKO_PUBLIC_KEY; + } + + for (i=0; (ri = recipientlist[i]) != NULL; i++) { + /* XXXXX fixme - not yet implemented! */ + if (ri->kind == RLSubjKeyID) + continue; + + attrs = searchTemplate; + + PK11_SETATTRS(attrs, CKA_CLASS, &certClass,sizeof(certClass)); attrs++; + PK11_SETATTRS(attrs, CKA_ISSUER, ri->id.issuerAndSN->derIssuer.data, + ri->id.issuerAndSN->derIssuer.len); attrs++; + PK11_SETATTRS(attrs, CKA_SERIAL_NUMBER, + ri->id.issuerAndSN->serialNumber.data,ri->id.issuerAndSN->serialNumber.len); + + certHandle = pk11_FindObjectByTemplate(slot,searchTemplate,count); + if (certHandle != CK_INVALID_KEY) { + CERTCertificate *cert = pk11_fastCert(slot,certHandle,NULL,NULL); + if (PK11_IsUserCert(slot,cert,certHandle)) { + /* we've found a cert handle, now let's see if there is a key + * associated with it... */ + ri->slot = PK11_ReferenceSlot(slot); + *rlIndex = i; + + CERT_DestroyCertificate(cert); + return certHandle; + } + CERT_DestroyCertificate(cert); + } + } + *rlIndex = -1; + return CK_INVALID_KEY; +} + +/* + * This function is the same as above, but it searches all the slots. + * this is the new version for NSS SMIME code + * this stuff should REALLY be in the SMIME code, but some things in here are not public + * (they should be!) + */ +static CK_OBJECT_HANDLE +pk11_AllFindCertObjectByRecipientNew(NSSCMSRecipient **recipientlist, void *wincx, int *rlIndex) +{ + PK11SlotList *list; + PK11SlotListElement *le; + CK_OBJECT_HANDLE certHandle = CK_INVALID_KEY; + PK11SlotInfo *slot = NULL; + SECStatus rv; + + /* get them all! */ + list = PK11_GetAllTokens(CKM_INVALID_MECHANISM,PR_FALSE,PR_TRUE,wincx); + if (list == NULL) { + if (list) PK11_FreeSlotList(list); + return CK_INVALID_KEY; + } + + /* Look for the slot that holds the Key */ + for (le = list->head ; le; le = le->next) { + if ( !PK11_IsFriendly(le->slot)) { + rv = PK11_Authenticate(le->slot, PR_TRUE, wincx); + if (rv != SECSuccess) continue; + } + + certHandle = pk11_FindCertObjectByRecipientNew(le->slot, recipientlist, rlIndex); + if (certHandle != CK_INVALID_KEY) + break; + } + + PK11_FreeSlotList(list); + + return (le == NULL) ? CK_INVALID_KEY : certHandle; +} + +/* + * We're looking for a cert which we have the private key for that's on the + * list of recipients. This searches one slot. */ static CK_OBJECT_HANDLE pk11_FindCertObjectByRecipient(PK11SlotInfo *slot, @@ -1632,6 +1732,63 @@ PK11_FindCertAndKeyByRecipientList(PK11SlotInfo **slotPtr, return cert; } +/* + * This is the new version of the above function for NSS SMIME code + * this stuff should REALLY be in the SMIME code, but some things in here are not public + * (they should be!) + */ +int +PK11_FindCertAndKeyByRecipientListNew(NSSCMSRecipient **recipientlist, void *wincx) +{ + CK_OBJECT_HANDLE certHandle = CK_INVALID_KEY; + CK_OBJECT_HANDLE keyHandle = CK_INVALID_KEY; + SECStatus rv; + NSSCMSRecipient *rl; + int rlIndex; + + certHandle = pk11_AllFindCertObjectByRecipientNew(recipientlist, wincx, &rlIndex); + if (certHandle == CK_INVALID_KEY) { + return NULL; + } + + rl = recipientlist[rlIndex]; + + /* at this point, rl->slot is set */ + + /* authenticate to the token */ + if (PK11_Authenticate(rl->slot, PR_TRUE, wincx) != SECSuccess) { + PK11_FreeSlot(rl->slot); + rl->slot = NULL; + return -1; + } + + /* try to get a private key handle for the cert we found */ + keyHandle = PK11_MatchItem(rl->slot, certHandle, CKO_PRIVATE_KEY); + if (keyHandle == CK_INVALID_KEY) { + PK11_FreeSlot(rl->slot); + rl->slot = NULL; + return -1; + } + + /* make a private key out of the handle */ + rl->privkey = PK11_MakePrivKey(rl->slot, nullKey, PR_TRUE, keyHandle, wincx); + if (rl->privkey == NULL) { + PK11_FreeSlot(rl->slot); + rl->slot = NULL; + return -1; + } + /* make a cert from the cert handle */ + rl->cert = PK11_MakeCertFromHandle(rl->slot, certHandle, NULL); + if (rl->cert == NULL) { + PK11_FreeSlot(rl->slot); + SECKEY_DestroyPrivateKey(rl->privkey); + rl->slot = NULL; + rl->privkey = NULL; + return NULL; + } + return rlIndex; +} + CERTCertificate * PK11_FindCertByIssuerAndSN(PK11SlotInfo **slotPtr, CERTIssuerAndSN *issuerSN, void *wincx) diff --git a/security/nss/lib/pk11wrap/pk11func.h b/security/nss/lib/pk11wrap/pk11func.h index 99310d48d..47d06d0f5 100644 --- a/security/nss/lib/pk11wrap/pk11func.h +++ b/security/nss/lib/pk11wrap/pk11func.h @@ -46,6 +46,7 @@ #include "secmodt.h" #include "seccomon.h" #include "pkcs7t.h" +#include "cmsreclist.h" SEC_BEGIN_PROTOS @@ -353,6 +354,8 @@ CERTCertificate * PK11_FindCertByIssuerAndSN(PK11SlotInfo **slot, CERTCertificate * PK11_FindCertAndKeyByRecipientList(PK11SlotInfo **slot, SEC_PKCS7RecipientInfo **array, SEC_PKCS7RecipientInfo **rip, SECKEYPrivateKey**privKey, void *wincx); +int PK11_FindCertAndKeyByRecipientListNew(NSSCMSRecipient **recipientlist, + void *wincx); CK_BBOOL PK11_HasAttributeSet( PK11SlotInfo *slot, CK_OBJECT_HANDLE id, CK_ATTRIBUTE_TYPE type ); diff --git a/security/nss/lib/smime/Makefile b/security/nss/lib/smime/Makefile new file mode 100644 index 000000000..cb85677bc --- /dev/null +++ b/security/nss/lib/smime/Makefile @@ -0,0 +1,76 @@ +#! gmake +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Netscape security libraries. +# +# The Initial Developer of the Original Code is Netscape +# Communications Corporation. Portions created by Netscape are +# Copyright (C) 1994-2000 Netscape Communications Corporation. All +# Rights Reserved. +# +# Contributor(s): +# +# Alternatively, the contents of this file may be used under the +# terms of the GNU General Public License Version 2 or later (the +# "GPL"), in which case the provisions of the GPL are applicable +# instead of those above. If you wish to allow use of your +# version of this file only under the terms of the GPL and not to +# allow others to use your version of this file under the MPL, +# indicate your decision by deleting the provisions above and +# replace them with the notice and other provisions required by +# the GPL. If you do not delete the provisions above, a recipient +# may use your version of this file under either the MPL or the +# GPL. +# + +####################################################################### +# (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 config.mk + +####################################################################### +# (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/smime/cms.h b/security/nss/lib/smime/cms.h new file mode 100644 index 000000000..091e96ea9 --- /dev/null +++ b/security/nss/lib/smime/cms.h @@ -0,0 +1,1054 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * Interfaces of the CMS implementation. + * + * $Id$ + */ + +#ifndef _CMS_H_ +#define _CMS_H_ + +#include "seccomon.h" +#include "mcom_db.h" /* needed by certt.h */ + +#include "secoidt.h" +#include "secder.h" /* needed by certt.h; XXX go away when possible */ +#include "certt.h" +#include "keyt.h" +#include "hasht.h" +#include "cmst.h" + +/************************************************************************/ +SEC_BEGIN_PROTOS + +/************************************************************************ + * cmsdecode.c - CMS decoding + ************************************************************************/ + +/* + * NSS_CMSDecoder_Start - set up decoding of a DER-encoded CMS message + * + * "poolp" - pointer to arena for message, or NULL if new pool should be created + * "cb", "cb_arg" - callback function and argument for delivery of inner content + * inner content will be stored in the message if cb is NULL. + * "pwfn", pwfn_arg" - callback function for getting token password + * "decrypt_key_cb", "decrypt_key_cb_arg" - callback function for getting bulk key for encryptedData + */ +extern NSSCMSDecoderContext * +NSS_CMSDecoder_Start(PRArenaPool *poolp, + NSSCMSContentCallback cb, void *cb_arg, + PK11PasswordFunc pwfn, void *pwfn_arg, + NSSCMSGetDecryptKeyCallback decrypt_key_cb, void *decrypt_key_cb_arg); + +/* + * NSS_CMSDecoder_Update - feed DER-encoded data to decoder + */ +extern SECStatus +NSS_CMSDecoder_Update(NSSCMSDecoderContext *p7dcx, const char *buf, unsigned long len); + +/* + * NSS_CMSDecoder_Cancel - cancel a decoding process + */ +extern void +NSS_CMSDecoder_Cancel(NSSCMSDecoderContext *p7dcx); + +/* + * NSS_CMSDecoder_Finish - mark the end of inner content and finish decoding + */ +extern NSSCMSMessage * +NSS_CMSDecoder_Finish(NSSCMSDecoderContext *p7dcx); + +/* + * NSS_CMSMessage_CreateFromDER - decode a CMS message from DER encoded data + */ +extern NSSCMSMessage * +NSS_CMSMessage_CreateFromDER(SECItem *DERmessage, + NSSCMSContentCallback cb, void *cb_arg, + PK11PasswordFunc pwfn, void *pwfn_arg, + NSSCMSGetDecryptKeyCallback decrypt_key_cb, void *decrypt_key_cb_arg); + +/************************************************************************ + * cmsencode.c - CMS encoding + ************************************************************************/ + +/* + * NSS_CMSEncoder_Start - set up encoding of a CMS message + * + * "cmsg" - message to encode + * "outputfn", "outputarg" - callback function for delivery of DER-encoded output + * will not be called if NULL. + * "dest" - if non-NULL, pointer to SECItem that will hold the DER-encoded output + * "destpoolp" - pool to allocate DER-encoded output in + * "pwfn", pwfn_arg" - callback function for getting token password + * "decrypt_key_cb", "decrypt_key_cb_arg" - callback function for getting bulk key for encryptedData + * "detached_digestalgs", "detached_digests" - digests from detached content + */ +extern NSSCMSEncoderContext * +NSS_CMSEncoder_Start(NSSCMSMessage *cmsg, + NSSCMSContentCallback outputfn, void *outputarg, + SECItem *dest, PLArenaPool *destpoolp, + PK11PasswordFunc pwfn, void *pwfn_arg, + NSSCMSGetDecryptKeyCallback decrypt_key_cb, void *decrypt_key_cb_arg, + SECAlgorithmID **detached_digestalgs, SECItem **detached_digests); + +/* + * NSS_CMSEncoder_Update - take content data delivery from the user + * + * "p7ecx" - encoder context + * "data" - content data + * "len" - length of content data + */ +extern SECStatus +NSS_CMSEncoder_Update(NSSCMSEncoderContext *p7ecx, const char *data, unsigned long len); + +/* + * NSS_CMSEncoder_Finish - signal the end of data + * + * we need to walk down the chain of encoders and the finish them from the innermost out + */ +extern SECStatus +NSS_CMSEncoder_Finish(NSSCMSEncoderContext *p7ecx); + +/************************************************************************ + * cmsmessage.c - CMS message object + ************************************************************************/ + +/* + * NSS_CMSMessage_Create - create a CMS message object + * + * "poolp" - arena to allocate memory from, or NULL if new arena should be created + */ +extern NSSCMSMessage * +NSS_CMSMessage_Create(PLArenaPool *poolp); + +/* + * NSS_CMSMessage_SetEncodingParams - set up a CMS message object for encoding or decoding + * + * "cmsg" - message object + * "pwfn", pwfn_arg" - callback function for getting token password + * "decrypt_key_cb", "decrypt_key_cb_arg" - callback function for getting bulk key for encryptedData + * "detached_digestalgs", "detached_digests" - digests from detached content + * + * used internally. + */ +extern void +NSS_CMSMessage_SetEncodingParams(NSSCMSMessage *cmsg, + PK11PasswordFunc pwfn, void *pwfn_arg, + NSSCMSGetDecryptKeyCallback decrypt_key_cb, void *decrypt_key_cb_arg, + SECAlgorithmID **detached_digestalgs, SECItem **detached_digests); + +/* + * NSS_CMSMessage_Destroy - destroy a CMS message and all of its sub-pieces. + */ +extern void +NSS_CMSMessage_Destroy(NSSCMSMessage *cmsg); + +/* + * NSS_CMSMessage_Copy - return a copy of the given message. + * + * The copy may be virtual or may be real -- either way, the result needs + * to be passed to NSS_CMSMessage_Destroy later (as does the original). + */ +extern NSSCMSMessage * +NSS_CMSMessage_Copy(NSSCMSMessage *cmsg); + +/* + * NSS_CMSMessage_GetArena - return a pointer to the message's arena pool + */ +extern PLArenaPool * +NSS_CMSMessage_GetArena(NSSCMSMessage *cmsg); + +/* + * NSS_CMSMessage_GetContentInfo - return a pointer to the top level contentInfo + */ +extern NSSCMSContentInfo * +NSS_CMSMessage_GetContentInfo(NSSCMSMessage *cmsg); + +/* + * Return a pointer to the actual content. + * In the case of those types which are encrypted, this returns the *plain* content. + * In case of nested contentInfos, this descends and retrieves the innermost content. + */ +extern SECItem * +NSS_CMSMessage_GetContent(NSSCMSMessage *cmsg); + +/* + * NSS_CMSMessage_ContentLevelCount - count number of levels of CMS content objects in this message + * + * CMS data content objects do not count. + */ +extern int +NSS_CMSMessage_ContentLevelCount(NSSCMSMessage *cmsg); + +/* + * NSS_CMSMessage_ContentLevel - find content level #n + * + * CMS data content objects do not count. + */ +extern NSSCMSContentInfo * +NSS_CMSMessage_ContentLevel(NSSCMSMessage *cmsg, int n); + +/* + * NSS_CMSMessage_ContainsCertsOrCrls - see if message contains certs along the way + */ +extern PRBool +NSS_CMSMessage_ContainsCertsOrCrls(NSSCMSMessage *cmsg); + +/* + * NSS_CMSMessage_IsEncrypted - see if message contains a encrypted submessage + */ +extern PRBool +NSS_CMSMessage_IsEncrypted(NSSCMSMessage *cmsg); + +/* + * NSS_CMSMessage_IsSigned - see if message contains a signed submessage + * + * If the CMS message has a SignedData with a signature (not just a SignedData) + * return true; false otherwise. This can/should be called before calling + * VerifySignature, which will always indicate failure if no signature is + * present, but that does not mean there even was a signature! + * Note that the content itself can be empty (detached content was sent + * another way); it is the presence of the signature that matters. + */ +extern PRBool +NSS_CMSMessage_IsSigned(NSSCMSMessage *cmsg); + +/* + * NSS_CMSMessage_IsContentEmpty - see if content is empty + * + * returns PR_TRUE is innermost content length is < minLen + * XXX need the encrypted content length (why?) + */ +extern PRBool +NSS_CMSMessage_IsContentEmpty(NSSCMSMessage *cmsg, unsigned int minLen); + +/************************************************************************ + * cmscinfo.c - CMS contentInfo methods + ************************************************************************/ + +/* + * NSS_CMSContentInfo_Destroy - destroy a CMS contentInfo and all of its sub-pieces. + */ +extern void +NSS_CMSContentInfo_Destroy(NSSCMSContentInfo *cinfo); + +/* + * NSS_CMSContentInfo_GetChildContentInfo - get content's contentInfo (if it exists) + */ +extern NSSCMSContentInfo * +NSS_CMSContentInfo_GetChildContentInfo(NSSCMSContentInfo *cinfo); + +/* + * NSS_CMSContentInfo_SetContent - set cinfo's content type & content to CMS object + */ +extern SECStatus +NSS_CMSContentInfo_SetContent(NSSCMSMessage *cmsg, NSSCMSContentInfo *cinfo, SECOidTag type, void *ptr); + +/* + * NSS_CMSContentInfo_SetContent_XXXX - typesafe wrappers for NSS_CMSContentInfo_SetType + * set cinfo's content type & content to CMS object + */ +extern SECStatus +NSS_CMSContentInfo_SetContent_Data(NSSCMSMessage *cmsg, NSSCMSContentInfo *cinfo, SECItem *data, PRBool detached); + +extern SECStatus +NSS_CMSContentInfo_SetContent_SignedData(NSSCMSMessage *cmsg, NSSCMSContentInfo *cinfo, NSSCMSSignedData *sigd); + +extern SECStatus +NSS_CMSContentInfo_SetContent_EnvelopedData(NSSCMSMessage *cmsg, NSSCMSContentInfo *cinfo, NSSCMSEnvelopedData *envd); + +extern SECStatus +NSS_CMSContentInfo_SetContent_DigestedData(NSSCMSMessage *cmsg, NSSCMSContentInfo *cinfo, NSSCMSDigestedData *digd); + +extern SECStatus +NSS_CMSContentInfo_SetContent_EncryptedData(NSSCMSMessage *cmsg, NSSCMSContentInfo *cinfo, NSSCMSEncryptedData *encd); + +/* + * NSS_CMSContentInfo_GetContent - get pointer to inner content + * + * needs to be casted... + */ +extern void * +NSS_CMSContentInfo_GetContent(NSSCMSContentInfo *cinfo); + +/* + * NSS_CMSContentInfo_GetInnerContent - get pointer to innermost content + * + * this is typically only called by NSS_CMSMessage_GetContent() + */ +extern SECItem * +NSS_CMSContentInfo_GetInnerContent(NSSCMSContentInfo *cinfo); + +/* + * NSS_CMSContentInfo_GetContentType{Tag,OID} - find out (saving pointer to lookup result + * for future reference) and return the inner content type. + */ +extern SECOidTag +NSS_CMSContentInfo_GetContentTypeTag(NSSCMSContentInfo *cinfo); + +extern SECItem * +NSS_CMSContentInfo_GetContentTypeOID(NSSCMSContentInfo *cinfo); + +/* + * NSS_CMSContentInfo_GetContentEncAlgTag - find out (saving pointer to lookup result + * for future reference) and return the content encryption algorithm tag. + */ +extern SECOidTag +NSS_CMSContentInfo_GetContentEncAlgTag(NSSCMSContentInfo *cinfo); + +/* + * NSS_CMSContentInfo_GetContentEncAlg - find out and return the content encryption algorithm tag. + */ +extern SECAlgorithmID * +NSS_CMSContentInfo_GetContentEncAlg(NSSCMSContentInfo *cinfo); + +extern SECStatus +NSS_CMSContentInfo_SetContentEncAlg(PLArenaPool *poolp, NSSCMSContentInfo *cinfo, + SECOidTag bulkalgtag, SECItem *parameters, int keysize); + +extern SECStatus +NSS_CMSContentInfo_SetContentEncAlgID(PLArenaPool *poolp, NSSCMSContentInfo *cinfo, + SECAlgorithmID *algid, int keysize); + +extern void +NSS_CMSContentInfo_SetBulkKey(NSSCMSContentInfo *cinfo, PK11SymKey *bulkkey); + +extern PK11SymKey * +NSS_CMSContentInfo_GetBulkKey(NSSCMSContentInfo *cinfo); + +extern int +NSS_CMSContentInfo_GetBulkKeySize(NSSCMSContentInfo *cinfo); + +/************************************************************************ + * cmsutil.c - CMS misc utility functions + ************************************************************************/ + +/* + * NSS_CMSArray_SortByDER - sort array of objects by objects' DER encoding + * + * make sure that the order of the objects guarantees valid DER (which must be + * in lexigraphically ascending order for a SET OF); if reordering is necessary it + * will be done in place (in objs). + */ +extern SECStatus +NSS_CMSArray_SortByDER(void **objs, const SEC_ASN1Template *objtemplate, void **objs2); + +/* + * NSS_CMSUtil_DERCompare - for use with NSS_CMSArray_Sort to + * sort arrays of SECItems containing DER + */ +extern int +NSS_CMSUtil_DERCompare(void *a, void *b); + +/* + * NSS_CMSAlgArray_GetIndexByAlgID - find a specific algorithm in an array of + * algorithms. + * + * algorithmArray - array of algorithm IDs + * algid - algorithmid of algorithm to pick + * + * Returns: + * An integer containing the index of the algorithm in the array or -1 if + * algorithm was not found. + */ +extern int +NSS_CMSAlgArray_GetIndexByAlgID(SECAlgorithmID **algorithmArray, SECAlgorithmID *algid); + +/* + * NSS_CMSAlgArray_GetIndexByAlgID - find a specific algorithm in an array of + * algorithms. + * + * algorithmArray - array of algorithm IDs + * algiddata - id of algorithm to pick + * + * Returns: + * An integer containing the index of the algorithm in the array or -1 if + * algorithm was not found. + */ +extern int +NSS_CMSAlgArray_GetIndexByAlgTag(SECAlgorithmID **algorithmArray, SECOidTag algtag); + +extern SECHashObject * +NSS_CMSUtil_GetHashObjByAlgID(SECAlgorithmID *algid); + +/* + * XXX I would *really* like to not have to do this, but the current + * signing interface gives me little choice. + */ +extern SECOidTag +NSS_CMSUtil_MakeSignatureAlgorithm(SECOidTag hashalg, SECOidTag encalg); + +extern const SEC_ASN1Template * +NSS_CMSUtil_GetTemplateByTypeTag(SECOidTag type); + +extern size_t +NSS_CMSUtil_GetSizeByTypeTag(SECOidTag type); + +extern NSSCMSContentInfo * +NSS_CMSContent_GetContentInfo(void *msg, SECOidTag type); + +extern const char * +NSS_CMSUtil_VerificationStatusToString(NSSCMSVerificationStatus vs); + +/************************************************************************ + * cmssigdata.c - CMS signedData methods + ************************************************************************/ + +extern NSSCMSSignedData * +NSS_CMSSignedData_Create(NSSCMSMessage *cmsg); + +extern void +NSS_CMSSignedData_Destroy(NSSCMSSignedData *sigd); + +/* + * NSS_CMSSignedData_Encode_BeforeStart - do all the necessary things to a SignedData + * before start of encoding. + * + * In detail: + * - find out about the right value to put into sigd->version + * - come up with a list of digestAlgorithms (which should be the union of the algorithms + * in the signerinfos). + * If we happen to have a pre-set list of algorithms (and digest values!), we + * check if we have all the signerinfos' algorithms. If not, this is an error. + */ +extern SECStatus +NSS_CMSSignedData_Encode_BeforeStart(NSSCMSSignedData *sigd); + +extern SECStatus +NSS_CMSSignedData_Encode_BeforeData(NSSCMSSignedData *sigd); + +/* + * NSS_CMSSignedData_Encode_AfterData - do all the necessary things to a SignedData + * after all the encapsulated data was passed through the encoder. + * + * In detail: + * - create the signatures in all the SignerInfos + * + * Please note that nothing is done to the Certificates and CRLs in the message - this + * is entirely the responsibility of our callers. + */ +extern SECStatus +NSS_CMSSignedData_Encode_AfterData(NSSCMSSignedData *sigd); + +extern SECStatus +NSS_CMSSignedData_Decode_BeforeData(NSSCMSSignedData *sigd); + +/* + * NSS_CMSSignedData_Decode_AfterData - do all the necessary things to a SignedData + * after all the encapsulated data was passed through the decoder. + */ +extern SECStatus +NSS_CMSSignedData_Decode_AfterData(NSSCMSSignedData *sigd); + +/* + * NSS_CMSSignedData_Decode_AfterEnd - do all the necessary things to a SignedData + * after all decoding is finished. + */ +extern SECStatus +NSS_CMSSignedData_Decode_AfterEnd(NSSCMSSignedData *sigd); + +/* + * NSS_CMSSignedData_GetSignerInfos - retrieve the SignedData's signer list + */ +extern NSSCMSSignerInfo ** +NSS_CMSSignedData_GetSignerInfos(NSSCMSSignedData *sigd); + +extern int +NSS_CMSSignedData_SignerInfoCount(NSSCMSSignedData *sigd); + +extern NSSCMSSignerInfo * +NSS_CMSSignedData_GetSignerInfo(NSSCMSSignedData *sigd, int i); + +/* + * NSS_CMSSignedData_GetDigestAlgs - retrieve the SignedData's digest algorithm list + */ +extern SECAlgorithmID ** +NSS_CMSSignedData_GetDigestAlgs(NSSCMSSignedData *sigd); + +/* + * NSS_CMSSignedData_GetContentInfo - return pointer to this signedData's contentinfo + */ +extern NSSCMSContentInfo * +NSS_CMSSignedData_GetContentInfo(NSSCMSSignedData *sigd); + +/* + * NSS_CMSSignedData_GetCertificateList - retrieve the SignedData's certificate list + */ +extern SECItem ** +NSS_CMSSignedData_GetCertificateList(NSSCMSSignedData *sigd); + +extern SECStatus +NSS_CMSSignedData_ImportCerts(NSSCMSSignedData *sigd, CERTCertDBHandle *certdb, + SECCertUsage certusage, PRBool keepcerts); + +/* + * NSS_CMSSignedData_HasDigests - see if we have digests in place + */ +extern PRBool +NSS_CMSSignedData_HasDigests(NSSCMSSignedData *sigd); + +/* + * NSS_CMSSignedData_VerifySignerInfo - check a signature. + * + * The digests were either calculated during decoding (and are stored in the + * signedData itself) or set after decoding using NSS_CMSSignedData_SetDigests. + * + * The verification checks if the signing cert is valid and has a trusted chain + * for the purpose specified by "certusage". + */ +extern SECStatus +NSS_CMSSignedData_VerifySignerInfo(NSSCMSSignedData *sigd, int i, CERTCertDBHandle *certdb, + SECCertUsage certusage); + +extern SECStatus +NSS_CMSSignedData_AddCertList(NSSCMSSignedData *sigd, CERTCertificateList *certlist); + +/* + * NSS_CMSSignedData_AddCertChain - add cert and its entire chain to the set of certs + */ +extern SECStatus +NSS_CMSSignedData_AddCertChain(NSSCMSSignedData *sigd, CERTCertificate *cert); + +extern SECStatus +NSS_CMSSignedData_AddCertificate(NSSCMSSignedData *sigd, CERTCertificate *cert); + +extern PRBool +NSS_CMSSignedData_ContainsCertsOrCrls(NSSCMSSignedData *sigd); + +extern SECStatus +NSS_CMSSignedData_AddSignerInfo(NSSCMSSignedData *sigd, + NSSCMSSignerInfo *signerinfo); + +extern SECItem * +NSS_CMSSignedData_GetDigestByAlgTag(NSSCMSSignedData *sigd, SECOidTag algtag); + +extern SECStatus +NSS_CMSSignedData_SetDigests(NSSCMSSignedData *sigd, + SECAlgorithmID **digestalgs, + SECItem **digests); + +extern SECStatus +NSS_CMSSignedData_SetDigestValue(NSSCMSSignedData *sigd, + SECOidTag digestalgtag, + SECItem *digestdata); + +extern SECStatus +NSS_CMSSignedData_AddDigest(PRArenaPool *poolp, + NSSCMSSignedData *sigd, + SECOidTag digestalgtag, + SECItem *digest); + +extern SECItem * +NSS_CMSSignedData_GetDigestValue(NSSCMSSignedData *sigd, SECOidTag digestalgtag); + +/* + * NSS_CMSSignedData_CreateCertsOnly - create a certs-only SignedData. + * + * cert - base certificates that will be included + * include_chain - if true, include the complete cert chain for cert + * + * More certs and chains can be added via AddCertificate and AddCertChain. + * + * An error results in a return value of NULL and an error set. + */ +extern NSSCMSSignedData * +NSS_CMSSignedData_CreateCertsOnly(NSSCMSMessage *cmsg, CERTCertificate *cert, PRBool include_chain); + +/************************************************************************ + * cmssiginfo.c - signerinfo methods + ************************************************************************/ + +extern NSSCMSSignerInfo * +NSS_CMSSignerInfo_Create(NSSCMSMessage *cmsg, CERTCertificate *cert, SECOidTag digestalgtag); + +/* + * NSS_CMSSignerInfo_Destroy - destroy a SignerInfo data structure + */ +extern void +NSS_CMSSignerInfo_Destroy(NSSCMSSignerInfo *si); + +/* + * NSS_CMSSignerInfo_Sign - sign something + * + */ +extern SECStatus +NSS_CMSSignerInfo_Sign(NSSCMSSignerInfo *signerinfo, SECItem *digest, SECItem *contentType); + +extern SECStatus +NSS_CMSSignerInfo_VerifyCertificate(NSSCMSSignerInfo *signerinfo, CERTCertDBHandle *certdb, + SECCertUsage certusage); + +/* + * NSS_CMSSignerInfo_Verify - verify the signature of a single SignerInfo + * + * Just verifies the signature. The assumption is that verification of the certificate + * is done already. + */ +extern SECStatus +NSS_CMSSignerInfo_Verify(NSSCMSSignerInfo *signerinfo, SECItem *digest, SECItem *contentType); + +extern NSSCMSVerificationStatus +NSS_CMSSignerInfo_GetVerificationStatus(NSSCMSSignerInfo *signerinfo); + +extern SECOidData * +NSS_CMSSignerInfo_GetDigestAlg(NSSCMSSignerInfo *signerinfo); + +extern SECOidTag +NSS_CMSSignerInfo_GetDigestAlgTag(NSSCMSSignerInfo *signerinfo); + +extern int +NSS_CMSSignerInfo_GetVersion(NSSCMSSignerInfo *signerinfo); + +extern CERTCertificateList * +NSS_CMSSignerInfo_GetCertList(NSSCMSSignerInfo *signerinfo); + +/* + * NSS_CMSSignerInfo_GetSigningTime - return the signing time, + * in UTCTime format, of a CMS signerInfo. + * + * sinfo - signerInfo data for this signer + * + * Returns a pointer to XXXX (what?) + * A return value of NULL is an error. + */ +extern SECStatus +NSS_CMSSignerInfo_GetSigningTime(NSSCMSSignerInfo *sinfo, PRTime *stime); + +/* + * Return the signing cert of a CMS signerInfo. + * + * the certs in the enclosing SignedData must have been imported already + */ +extern CERTCertificate * +NSS_CMSSignerInfo_GetSigningCertificate(NSSCMSSignerInfo *signerinfo, CERTCertDBHandle *certdb); + +/* + * NSS_CMSSignerInfo_GetSignerCommonName - return the common name of the signer + * + * sinfo - signerInfo data for this signer + * + * Returns a pointer to allocated memory, which must be freed. + * A return value of NULL is an error. + */ +extern char * +NSS_CMSSignerInfo_GetSignerCommonName(NSSCMSSignerInfo *sinfo); + +/* + * NSS_CMSSignerInfo_GetSignerEmailAddress - return the common name of the signer + * + * sinfo - signerInfo data for this signer + * + * Returns a pointer to allocated memory, which must be freed. + * A return value of NULL is an error. + */ +extern char * +NSS_CMSSignerInfo_GetSignerEmailAddress(NSSCMSSignerInfo *sinfo); + +/* + * NSS_CMSSignerInfo_AddAuthAttr - add an attribute to the + * authenticated (i.e. signed) attributes of "signerinfo". + */ +extern SECStatus +NSS_CMSSignerInfo_AddAuthAttr(NSSCMSSignerInfo *signerinfo, NSSCMSAttribute *attr); + +/* + * NSS_CMSSignerInfo_AddUnauthAttr - add an attribute to the + * unauthenticated attributes of "signerinfo". + */ +extern SECStatus +NSS_CMSSignerInfo_AddUnauthAttr(NSSCMSSignerInfo *signerinfo, NSSCMSAttribute *attr); + +/* + * NSS_CMSSignerInfo_AddSigningTime - add the signing time to the + * authenticated (i.e. signed) attributes of "signerinfo". + * + * This is expected to be included in outgoing signed + * messages for email (S/MIME) but is likely useful in other situations. + * + * This should only be added once; a second call will do nothing. + * + * XXX This will probably just shove the current time into "signerinfo" + * but it will not actually get signed until the entire item is + * processed for encoding. Is this (expected to be small) delay okay? + */ +extern SECStatus +NSS_CMSSignerInfo_AddSigningTime(NSSCMSSignerInfo *signerinfo, PRTime t); + +/* + * NSS_CMSSignerInfo_AddCounterSignature - countersign a signerinfo + * + * 1. digest the DER-encoded signature value of the original signerinfo + * 2. create new signerinfo with correct version, sid, digestAlg + * 3. add message-digest authAttr, but NO content-type + * 4. sign the authAttrs + * 5. add the whole thing to original signerInfo's unAuthAttrs + * + * XXXX give back the new signerinfo? + */ +extern SECStatus +NSS_CMSSignerInfo_AddCounterSignature(NSSCMSSignerInfo *signerinfo, + SECOidTag digestalg, CERTCertificate signingcert); + +/* + * XXXX the following needs to be done in the S/MIME layer code + * after signature of a signerinfo is verified + */ +extern SECStatus +NSS_SMIMESignerInfo_SaveSMIMEProfile(NSSCMSSignerInfo *signerinfo); + +/* + * NSS_CMSSignerInfo_IncludeCerts - set cert chain inclusion mode for this signer + */ +extern SECStatus +NSS_CMSSignerInfo_IncludeCerts(NSSCMSSignerInfo *signerinfo, NSSCMSCertChainMode cm, SECCertUsage usage); + +/************************************************************************ + * cmsenvdata.c - CMS envelopedData methods + ************************************************************************/ + +/* + * NSS_CMSEnvelopedData_Create - create an enveloped data message + */ +extern NSSCMSEnvelopedData * +NSS_CMSEnvelopedData_Create(NSSCMSMessage *cmsg, SECOidTag algorithm, int keysize); + +/* + * NSS_CMSEnvelopedData_Destroy - destroy an enveloped data message + */ +extern void +NSS_CMSEnvelopedData_Destroy(NSSCMSEnvelopedData *edp); + +/* + * NSS_CMSEnvelopedData_GetContentInfo - return pointer to this envelopedData's contentinfo + */ +extern NSSCMSContentInfo * +NSS_CMSEnvelopedData_GetContentInfo(NSSCMSEnvelopedData *envd); + +/* + * NSS_CMSEnvelopedData_AddRecipient - add a recipientinfo to the enveloped data msg + * + * rip must be created on the same pool as edp - this is not enforced, though. + */ +extern SECStatus +NSS_CMSEnvelopedData_AddRecipient(NSSCMSEnvelopedData *edp, NSSCMSRecipientInfo *rip); + +/* + * NSS_CMSEnvelopedData_Encode_BeforeStart - prepare this envelopedData for encoding + * + * at this point, we need + * - recipientinfos set up with recipient's certificates + * - a content encryption algorithm (if none, 3DES will be used) + * + * this function will generate a random content encryption key (aka bulk key), + * initialize the recipientinfos with certificate identification and wrap the bulk key + * using the proper algorithm for every certificiate. + * it will finally set the bulk algorithm and key so that the encode step can find it. + */ +extern SECStatus +NSS_CMSEnvelopedData_Encode_BeforeStart(NSSCMSEnvelopedData *envd); + +/* + * NSS_CMSEnvelopedData_Encode_BeforeData - set up encryption + */ +extern SECStatus +NSS_CMSEnvelopedData_Encode_BeforeData(NSSCMSEnvelopedData *envd); + +/* + * NSS_CMSEnvelopedData_Encode_AfterData - finalize this envelopedData for encoding + */ +extern SECStatus +NSS_CMSEnvelopedData_Encode_AfterData(NSSCMSEnvelopedData *envd); + +/* + * NSS_CMSEnvelopedData_Decode_BeforeData - find our recipientinfo, + * derive bulk key & set up our contentinfo + */ +extern SECStatus +NSS_CMSEnvelopedData_Decode_BeforeData(NSSCMSEnvelopedData *envd); + +/* + * NSS_CMSEnvelopedData_Decode_AfterData - finish decrypting this envelopedData's content + */ +extern SECStatus +NSS_CMSEnvelopedData_Decode_AfterData(NSSCMSEnvelopedData *envd); + +/* + * NSS_CMSEnvelopedData_Decode_AfterEnd - finish decoding this envelopedData + */ +extern SECStatus +NSS_CMSEnvelopedData_Decode_AfterEnd(NSSCMSEnvelopedData *envd); + + +/************************************************************************ + * cmsrecinfo.c - CMS recipientInfo methods + ************************************************************************/ + +/* + * NSS_CMSRecipientInfo_Create - create a recipientinfo + * + * we currently do not create KeyAgreement recipientinfos with multiple recipientEncryptedKeys + * the certificate is supposed to have been verified by the caller + */ +extern NSSCMSRecipientInfo * +NSS_CMSRecipientInfo_Create(NSSCMSMessage *cmsg, CERTCertificate *cert); + +extern void +NSS_CMSRecipientInfo_Destroy(NSSCMSRecipientInfo *ri); + +extern int +NSS_CMSRecipientInfo_GetVersion(NSSCMSRecipientInfo *ri); + +extern SECItem * +NSS_CMSRecipientInfo_GetEncryptedKey(NSSCMSRecipientInfo *ri, int subIndex); + + +extern SECOidTag +NSS_CMSRecipientInfo_GetKeyEncryptionAlgorithmTag(NSSCMSRecipientInfo *ri); + +extern SECStatus +NSS_CMSRecipientInfo_WrapBulkKey(NSSCMSRecipientInfo *ri, PK11SymKey *bulkkey, SECOidTag bulkalgtag); + +extern PK11SymKey * +NSS_CMSRecipientInfo_UnwrapBulkKey(NSSCMSRecipientInfo *ri, int subIndex, + CERTCertificate *cert, SECKEYPrivateKey *privkey, SECOidTag bulkalgtag); + +/************************************************************************ + * cmsencdata.c - CMS encryptedData methods + ************************************************************************/ +/* + * NSS_CMSEncryptedData_Create - create an empty encryptedData object. + * + * "algorithm" specifies the bulk encryption algorithm to use. + * "keysize" is the key size. + * + * An error results in a return value of NULL and an error set. + * (Retrieve specific errors via PORT_GetError()/XP_GetError().) + */ +extern NSSCMSEncryptedData * +NSS_CMSEncryptedData_Create(NSSCMSMessage *cmsg, SECOidTag algorithm, int keysize); + +/* + * NSS_CMSEncryptedData_Destroy - destroy an encryptedData object + */ +extern void +NSS_CMSEncryptedData_Destroy(NSSCMSEncryptedData *encd); + +/* + * NSS_CMSEncryptedData_GetContentInfo - return pointer to encryptedData object's contentInfo + */ +extern NSSCMSContentInfo * +NSS_CMSEncryptedData_GetContentInfo(NSSCMSEncryptedData *encd); + +/* + * NSS_CMSEncryptedData_Encode_BeforeStart - do all the necessary things to a EncryptedData + * before encoding begins. + * + * In particular: + * - set the correct version value. + * - get the encryption key + */ +extern SECStatus +NSS_CMSEncryptedData_Encode_BeforeStart(NSSCMSEncryptedData *encd); + +/* + * NSS_CMSEncryptedData_Encode_BeforeData - set up encryption + */ +extern SECStatus +NSS_CMSEncryptedData_Encode_BeforeData(NSSCMSEncryptedData *encd); + +/* + * NSS_CMSEncryptedData_Encode_AfterData - finalize this encryptedData for encoding + */ +extern SECStatus +NSS_CMSEncryptedData_Encode_AfterData(NSSCMSEncryptedData *encd); + +/* + * NSS_CMSEncryptedData_Decode_BeforeData - find bulk key & set up decryption + */ +extern SECStatus +NSS_CMSEncryptedData_Decode_BeforeData(NSSCMSEncryptedData *encd); + +/* + * NSS_CMSEncryptedData_Decode_AfterData - finish decrypting this encryptedData's content + */ +extern SECStatus +NSS_CMSEncryptedData_Decode_AfterData(NSSCMSEncryptedData *encd); + +/* + * NSS_CMSEncryptedData_Decode_AfterEnd - finish decoding this encryptedData + */ +extern SECStatus +NSS_CMSEncryptedData_Decode_AfterEnd(NSSCMSEncryptedData *encd); + +/************************************************************************ + * cmsdigdata.c - CMS encryptedData methods + ************************************************************************/ +/* + * NSS_CMSDigestedData_Create - create a digestedData object (presumably for encoding) + * + * version will be set by NSS_CMSDigestedData_Encode_BeforeStart + * digestAlg is passed as parameter + * contentInfo must be filled by the user + * digest will be calculated while encoding + */ +extern NSSCMSDigestedData * +NSS_CMSDigestedData_Create(NSSCMSMessage *cmsg, SECAlgorithmID *digestalg); + +/* + * NSS_CMSDigestedData_Destroy - destroy a digestedData object + */ +extern void +NSS_CMSDigestedData_Destroy(NSSCMSDigestedData *digd); + +/* + * NSS_CMSDigestedData_GetContentInfo - return pointer to digestedData object's contentInfo + */ +extern NSSCMSContentInfo * +NSS_CMSDigestedData_GetContentInfo(NSSCMSDigestedData *digd); + +/* + * NSS_CMSDigestedData_Encode_BeforeStart - do all the necessary things to a DigestedData + * before encoding begins. + * + * In particular: + * - set the right version number. The contentInfo's content type must be set up already. + */ +extern SECStatus +NSS_CMSDigestedData_Encode_BeforeStart(NSSCMSDigestedData *digd); + +/* + * NSS_CMSDigestedData_Encode_BeforeData - do all the necessary things to a DigestedData + * before the encapsulated data is passed through the encoder. + * + * In detail: + * - set up the digests if necessary + */ +extern SECStatus +NSS_CMSDigestedData_Encode_BeforeData(NSSCMSDigestedData *digd); + +/* + * NSS_CMSDigestedData_Encode_AfterData - do all the necessary things to a DigestedData + * after all the encapsulated data was passed through the encoder. + * + * In detail: + * - finish the digests + */ +extern SECStatus +NSS_CMSDigestedData_Encode_AfterData(NSSCMSDigestedData *digd); + +/* + * NSS_CMSDigestedData_Decode_BeforeData - do all the necessary things to a DigestedData + * before the encapsulated data is passed through the encoder. + * + * In detail: + * - set up the digests if necessary + */ +extern SECStatus +NSS_CMSDigestedData_Decode_BeforeData(NSSCMSDigestedData *digd); + +/* + * NSS_CMSDigestedData_Decode_AfterData - do all the necessary things to a DigestedData + * after all the encapsulated data was passed through the encoder. + * + * In detail: + * - finish the digests + */ +extern SECStatus +NSS_CMSDigestedData_Decode_AfterData(NSSCMSDigestedData *digd); + +/* + * NSS_CMSDigestedData_Decode_AfterEnd - finalize a digestedData. + * + * In detail: + * - check the digests for equality + */ +extern SECStatus +NSS_CMSDigestedData_Decode_AfterEnd(NSSCMSDigestedData *digd); + +/************************************************************************ + * cmsdigest.c - digestion routines + ************************************************************************/ + +/* + * NSS_CMSDigestContext_StartMultiple - start digest calculation using all the + * digest algorithms in "digestalgs" in parallel. + */ +extern NSSCMSDigestContext * +NSS_CMSDigestContext_StartMultiple(SECAlgorithmID **digestalgs); + +/* + * NSS_CMSDigestContext_StartSingle - same as NSS_CMSDigestContext_StartMultiple, but + * only one algorithm. + */ +extern NSSCMSDigestContext * +NSS_CMSDigestContext_StartSingle(SECAlgorithmID *digestalg); + +/* + * NSS_CMSDigestContext_Update - feed more data into the digest machine + */ +extern void +NSS_CMSDigestContext_Update(NSSCMSDigestContext *cmsdigcx, const unsigned char *data, int len); + +/* + * NSS_CMSDigestContext_Cancel - cancel digesting operation + */ +extern void +NSS_CMSDigestContext_Cancel(NSSCMSDigestContext *cmsdigcx); + +/* + * NSS_CMSDigestContext_FinishMultiple - finish the digests and put them + * into an array of SECItems (allocated on poolp) + */ +extern SECStatus +NSS_CMSDigestContext_FinishMultiple(NSSCMSDigestContext *cmsdigcx, PLArenaPool *poolp, + SECItem ***digestsp); + +/* + * NSS_CMSDigestContext_FinishSingle - same as NSS_CMSDigestContext_FinishMultiple, + * but for one digest. + */ +extern SECStatus +NSS_CMSDigestContext_FinishSingle(NSSCMSDigestContext *cmsdigcx, PLArenaPool *poolp, + SECItem *digest); + +/************************************************************************ + * + ************************************************************************/ + + +/************************************************************************/ +SEC_END_PROTOS + +#endif /* _CMS_H_ */ diff --git a/security/nss/lib/smime/cmsarray.c b/security/nss/lib/smime/cmsarray.c new file mode 100644 index 000000000..9ac16908d --- /dev/null +++ b/security/nss/lib/smime/cmsarray.c @@ -0,0 +1,205 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * CMS array functions. + * + * $Id$ + */ + +#include "cmslocal.h" + +#include "secerr.h" + +/* + * ARRAY FUNCTIONS + * + * In NSS, arrays are rather primitive arrays of pointers. + * Makes it easy to walk the array, but hard to count elements + * and manage the storage. + * + * This is a feeble attempt to encapsulate the functionality + * and get rid of hundreds of lines of similar code + */ + +/* + * NSS_CMSArray_Alloc - allocate an array in an arena + */ +void ** +NSS_CMSArray_Alloc(PRArenaPool *poolp, int n) +{ + return (void **)PORT_ArenaZAlloc(poolp, n * sizeof(void *)); +} + +/* + * NSS_CMSArray_Add - add an element to the end of an array + */ +SECStatus +NSS_CMSArray_Add(PRArenaPool *poolp, void ***array, void *obj) +{ + void **p; + int n; + void **dest; + + PORT_Assert(array != NULL); + if (array == NULL) + return SECFailure; + + if (*array == NULL) { + dest = (void **)PORT_ArenaAlloc(poolp, 2 * sizeof(void *)); + n = 0; + } else { + n = 0; p = *array; + while (*p++) + n++; + dest = (void **)PORT_ArenaGrow (poolp, + *array, + (n + 1) * sizeof(void *), + (n + 2) * sizeof(void *)); + } + dest[n] = obj; + dest[n+1] = NULL; + *array = dest; + return SECSuccess; +} + +/* + * NSS_CMSArray_IsEmpty - check if array is empty + */ +PRBool +NSS_CMSArray_IsEmpty(void **array) +{ + return (array == NULL || array[0] == NULL); +} + +/* + * NSS_CMSArray_IsEmpty - count number of elements in array + */ +int +NSS_CMSArray_Count(void **array) +{ + int n = 0; + + if (array == NULL) + return 0; + + while (*array++ != NULL) + n++; + + return n; +} + +/* + * NSS_CMSArray_Sort - sort an array ascending, in place + * + * If "secondary" is not NULL, the same reordering gets applied to it. + * If "tertiary" is not NULL, the same reordering gets applied to it. + * "compare" is a function that returns + * < 0 when the first element is less than the second + * = 0 when the first element is equal to the second + * > 0 when the first element is greater than the second + */ +void +NSS_CMSArray_Sort(void **primary, int (*compare)(void *,void *), void **secondary, void **tertiary) +{ + int n, i, limit, lastxchg; + void *tmp; + + if (primary == NULL) + return; + + n = NSS_CMSArray_Count(primary); + if (n <= 1) /* ordering is fine */ + return; + + /* yes, ladies and gentlemen, it's BUBBLE SORT TIME! */ + limit = n - 1; + while (1) { + lastxchg = 0; + for (i = 0; i < limit; i++) { + if ((*compare)(primary[i], primary[i+1]) > 0) { + /* exchange the neighbours */ + tmp = primary[i+1]; + primary[i+1] = primary[i]; + primary[i] = tmp; + if (secondary) { /* secondary array? */ + tmp = secondary[i+1]; /* exchange there as well */ + secondary[i+1] = secondary[i]; + secondary[i] = tmp; + } + if (tertiary) { /* tertiary array? */ + tmp = tertiary[i+1]; /* exchange there as well */ + tertiary[i+1] = tertiary[i]; + tertiary[i] = tmp; + } + lastxchg = i+1; /* index of the last element bubbled up */ + } + } + if (lastxchg == 0) /* no exchanges, so array is sorted */ + break; /* we're done */ + limit = lastxchg; /* array is sorted up to [limit] */ + } +} + +#if 0 + +/* array iterator stuff... not used */ + +typedef void **NSSCMSArrayIterator; + +/* iterator */ +NSSCMSArrayIterator +NSS_CMSArray_First(void **array) +{ + if (array == NULL || array[0] == NULL) + return NULL; + return (NSSCMSArrayIterator)&(array[0]); +} + +void * +NSS_CMSArray_Obj(NSSCMSArrayIterator iter) +{ + void **p = (void **)iter; + + return *iter; /* which is NULL if we are at the end of the array */ +} + +NSSCMSArrayIterator +NSS_CMSArray_Next(NSSCMSArrayIterator iter) +{ + void **p = (void **)iter; + + return (NSSCMSArrayIterator)(p + 1); +} + +#endif diff --git a/security/nss/lib/smime/cmsasn1.c b/security/nss/lib/smime/cmsasn1.c new file mode 100644 index 000000000..1c8695d8c --- /dev/null +++ b/security/nss/lib/smime/cmsasn1.c @@ -0,0 +1,560 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * CMS ASN.1 templates + * + * $Id$ + */ + +#include "cmslocal.h" + +#include "cert.h" +#include "key.h" +#include "secasn1.h" +#include "secitem.h" +#include "secoid.h" +#include "prtime.h" +#include "secerr.h" + + +extern const SEC_ASN1Template nss_cms_set_of_attribute_template[]; + +/* ----------------------------------------------------------------------------- + * MESSAGE + * (uses NSSCMSContentInfo) + */ + +/* forward declaration */ +static const SEC_ASN1Template * +nss_cms_choose_content_template(void *src_or_dest, PRBool encoding); + +static SEC_ChooseASN1TemplateFunc nss_cms_chooser + = nss_cms_choose_content_template; + +const SEC_ASN1Template NSSCMSMessageTemplate[] = { + { SEC_ASN1_SEQUENCE | SEC_ASN1_MAY_STREAM, + 0, NULL, sizeof(NSSCMSMessage) }, + { SEC_ASN1_OBJECT_ID, + offsetof(NSSCMSMessage,contentInfo.contentType) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_DYNAMIC | SEC_ASN1_MAY_STREAM + | SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(NSSCMSMessage,contentInfo.content), + &nss_cms_chooser }, + { 0 } +}; + +static const SEC_ASN1Template NSS_PointerToCMSMessageTemplate[] = { + { SEC_ASN1_POINTER, 0, NSSCMSMessageTemplate } +}; + +/* ----------------------------------------------------------------------------- + * ENCAPSULATED & ENCRYPTED CONTENTINFO + * (both use a NSSCMSContentInfo) + */ +static const SEC_ASN1Template NSSCMSEncapsulatedContentInfoTemplate[] = { + { SEC_ASN1_SEQUENCE | SEC_ASN1_MAY_STREAM, + 0, NULL, sizeof(NSSCMSContentInfo) }, + { SEC_ASN1_OBJECT_ID, + offsetof(NSSCMSContentInfo,contentType) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | SEC_ASN1_MAY_STREAM | + SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(NSSCMSContentInfo,rawContent), + SEC_PointerToOctetStringTemplate }, + { 0 } +}; + +static const SEC_ASN1Template NSSCMSEncryptedContentInfoTemplate[] = { + { SEC_ASN1_SEQUENCE | SEC_ASN1_MAY_STREAM, + 0, NULL, sizeof(NSSCMSContentInfo) }, + { SEC_ASN1_OBJECT_ID, + offsetof(NSSCMSContentInfo,contentType) }, + { SEC_ASN1_INLINE, + offsetof(NSSCMSContentInfo,contentEncAlg), + SECOID_AlgorithmIDTemplate }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_POINTER | SEC_ASN1_MAY_STREAM | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(NSSCMSContentInfo,rawContent), + SEC_OctetStringTemplate }, + { 0 } +}; + +/* ----------------------------------------------------------------------------- + * SIGNED DATA + */ + +const SEC_ASN1Template NSSCMSSignerInfoTemplate[]; + +const SEC_ASN1Template NSSCMSSignedDataTemplate[] = { + { SEC_ASN1_SEQUENCE | SEC_ASN1_MAY_STREAM, + 0, NULL, sizeof(NSSCMSSignedData) }, + { SEC_ASN1_INTEGER, + offsetof(NSSCMSSignedData,version) }, + { SEC_ASN1_SET_OF, + offsetof(NSSCMSSignedData,digestAlgorithms), + SECOID_AlgorithmIDTemplate }, + { SEC_ASN1_INLINE, + offsetof(NSSCMSSignedData,contentInfo), + NSSCMSEncapsulatedContentInfoTemplate }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(NSSCMSSignedData,rawCerts), + SEC_SetOfAnyTemplate }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1, + offsetof(NSSCMSSignedData,crls), + CERT_SetOfSignedCrlTemplate }, + { SEC_ASN1_SET_OF, + offsetof(NSSCMSSignedData,signerInfos), + NSSCMSSignerInfoTemplate }, + { 0 } +}; + +const SEC_ASN1Template NSS_PointerToCMSSignedDataTemplate[] = { + { SEC_ASN1_POINTER, 0, NSSCMSSignedDataTemplate } +}; + +/* ----------------------------------------------------------------------------- + * signeridentifier + */ + +static const SEC_ASN1Template NSSCMSSignerIdentifierTemplate[] = { + { SEC_ASN1_CHOICE, + offsetof(NSSCMSSignerIdentifier,identifierType), NULL, + sizeof(NSSCMSSignerIdentifier) }, + { SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(NSSCMSSignerIdentifier,id.subjectKeyID), + SEC_PointerToOctetStringTemplate, + NSSCMSRecipientID_SubjectKeyID }, + { SEC_ASN1_POINTER, + offsetof(NSSCMSSignerIdentifier,id.issuerAndSN), + CERT_IssuerAndSNTemplate, + NSSCMSRecipientID_IssuerSN }, + { 0 } +}; + +/* ----------------------------------------------------------------------------- + * signerinfo + */ + +const SEC_ASN1Template NSSCMSSignerInfoTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(NSSCMSSignerInfo) }, + { SEC_ASN1_INTEGER, + offsetof(NSSCMSSignerInfo,version) }, + { SEC_ASN1_INLINE, + offsetof(NSSCMSSignerInfo,signerIdentifier), + NSSCMSSignerIdentifierTemplate }, + { SEC_ASN1_INLINE, + offsetof(NSSCMSSignerInfo,digestAlg), + SECOID_AlgorithmIDTemplate }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(NSSCMSSignerInfo,authAttr), + nss_cms_set_of_attribute_template }, + { SEC_ASN1_INLINE, + offsetof(NSSCMSSignerInfo,digestEncAlg), + SECOID_AlgorithmIDTemplate }, + { SEC_ASN1_OCTET_STRING, + offsetof(NSSCMSSignerInfo,encDigest) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1, + offsetof(NSSCMSSignerInfo,unAuthAttr), + nss_cms_set_of_attribute_template }, + { 0 } +}; + +/* ----------------------------------------------------------------------------- + * ENVELOPED DATA + */ + +static const SEC_ASN1Template NSSCMSOriginatorInfoTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(NSSCMSOriginatorInfo) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(NSSCMSOriginatorInfo,rawCerts), + SEC_SetOfAnyTemplate }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1, + offsetof(NSSCMSOriginatorInfo,crls), + CERT_SetOfSignedCrlTemplate }, + { 0 } +}; + +const SEC_ASN1Template NSSCMSRecipientInfoTemplate[]; + +const SEC_ASN1Template NSSCMSEnvelopedDataTemplate[] = { + { SEC_ASN1_SEQUENCE | SEC_ASN1_MAY_STREAM, + 0, NULL, sizeof(NSSCMSEnvelopedData) }, + { SEC_ASN1_INTEGER, + offsetof(NSSCMSEnvelopedData,version) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_POINTER | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(NSSCMSEnvelopedData,originatorInfo), + NSSCMSOriginatorInfoTemplate }, + { SEC_ASN1_SET_OF, + offsetof(NSSCMSEnvelopedData,recipientInfos), + NSSCMSRecipientInfoTemplate }, + { SEC_ASN1_INLINE, + offsetof(NSSCMSEnvelopedData,contentInfo), + NSSCMSEncryptedContentInfoTemplate }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1, + offsetof(NSSCMSEnvelopedData,unprotectedAttr), + nss_cms_set_of_attribute_template }, + { 0 } +}; + +const SEC_ASN1Template NSS_PointerToCMSEnvelopedDataTemplate[] = { + { SEC_ASN1_POINTER, 0, NSSCMSEnvelopedDataTemplate } +}; + +/* here come the 15 gazillion templates for all the v3 varieties of RecipientInfo */ + +/* ----------------------------------------------------------------------------- + * key transport recipient info + */ + +static const SEC_ASN1Template NSSCMSRecipientIdentifierTemplate[] = { + { SEC_ASN1_CHOICE, + offsetof(NSSCMSRecipientIdentifier,identifierType), NULL, + sizeof(NSSCMSRecipientIdentifier) }, + { SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(NSSCMSRecipientIdentifier,id.subjectKeyID), + SEC_PointerToOctetStringTemplate, + NSSCMSRecipientID_SubjectKeyID }, + { SEC_ASN1_POINTER, + offsetof(NSSCMSRecipientIdentifier,id.issuerAndSN), + CERT_IssuerAndSNTemplate, + NSSCMSRecipientID_IssuerSN }, + { 0 } +}; + + +static const SEC_ASN1Template NSSCMSKeyTransRecipientInfoTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(NSSCMSKeyTransRecipientInfo) }, + { SEC_ASN1_INTEGER, + offsetof(NSSCMSKeyTransRecipientInfo,version) }, + { SEC_ASN1_INLINE, + offsetof(NSSCMSKeyTransRecipientInfo,recipientIdentifier), + NSSCMSRecipientIdentifierTemplate }, + { SEC_ASN1_INLINE, + offsetof(NSSCMSKeyTransRecipientInfo,keyEncAlg), + SECOID_AlgorithmIDTemplate }, + { SEC_ASN1_OCTET_STRING, + offsetof(NSSCMSKeyTransRecipientInfo,encKey) }, + { 0 } +}; + +/* ----------------------------------------------------------------------------- + * key agreement recipient info + */ + +static const SEC_ASN1Template NSSCMSOriginatorPublicKeyTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(NSSCMSOriginatorPublicKey) }, + { SEC_ASN1_INLINE, + offsetof(NSSCMSOriginatorPublicKey,algorithmIdentifier), + SECOID_AlgorithmIDTemplate }, + { SEC_ASN1_INLINE, + offsetof(NSSCMSOriginatorPublicKey,publicKey), + SEC_BitStringTemplate }, + { 0 } +}; + + +static const SEC_ASN1Template NSSCMSOriginatorIdentifierOrKeyTemplate[] = { + { SEC_ASN1_CHOICE, + offsetof(NSSCMSOriginatorIdentifierOrKey,identifierType), NULL, + sizeof(NSSCMSOriginatorIdentifierOrKey) }, + { SEC_ASN1_POINTER, + offsetof(NSSCMSOriginatorIdentifierOrKey,id.issuerAndSN), + CERT_IssuerAndSNTemplate, + NSSCMSOriginatorIDOrKey_IssuerSN }, + { SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1, + offsetof(NSSCMSOriginatorIdentifierOrKey,id.subjectKeyID), + SEC_PointerToOctetStringTemplate, + NSSCMSOriginatorIDOrKey_SubjectKeyID }, + { SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 2, + offsetof(NSSCMSOriginatorIdentifierOrKey,id.originatorPublicKey), + NSSCMSOriginatorPublicKeyTemplate, + NSSCMSOriginatorIDOrKey_OriginatorPublicKey }, + { 0 } +}; + +static const SEC_ASN1Template NSSCMSRecipientKeyIdentifierTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(NSSCMSRecipientKeyIdentifier) }, + { SEC_ASN1_OCTET_STRING, + offsetof(NSSCMSRecipientKeyIdentifier,subjectKeyIdentifier) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_OCTET_STRING, + offsetof(NSSCMSRecipientKeyIdentifier,date) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_OCTET_STRING, + offsetof(NSSCMSRecipientKeyIdentifier,other) }, + { 0 } +}; + + +static const SEC_ASN1Template NSSCMSKeyAgreeRecipientIdentifierTemplate[] = { + { SEC_ASN1_CHOICE, + offsetof(NSSCMSKeyAgreeRecipientIdentifier,identifierType), NULL, + sizeof(NSSCMSKeyAgreeRecipientIdentifier) }, + { SEC_ASN1_POINTER, + offsetof(NSSCMSKeyAgreeRecipientIdentifier,id.issuerAndSN), + CERT_IssuerAndSNTemplate, + NSSCMSKeyAgreeRecipientID_IssuerSN }, + { SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(NSSCMSKeyAgreeRecipientIdentifier,id.recipientKeyIdentifier), + NSSCMSRecipientKeyIdentifierTemplate, + NSSCMSKeyAgreeRecipientID_RKeyID }, + { 0 } +}; + +static const SEC_ASN1Template NSSCMSRecipientEncryptedKeyTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(NSSCMSRecipientEncryptedKey) }, + { SEC_ASN1_INLINE, + offsetof(NSSCMSRecipientEncryptedKey,recipientIdentifier), + NSSCMSKeyAgreeRecipientIdentifierTemplate }, + { SEC_ASN1_INLINE, + offsetof(NSSCMSRecipientEncryptedKey,encKey), + SEC_BitStringTemplate }, + { 0 } +}; + +static const SEC_ASN1Template NSSCMSKeyAgreeRecipientInfoTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(NSSCMSKeyAgreeRecipientInfo) }, + { SEC_ASN1_INTEGER, + offsetof(NSSCMSKeyAgreeRecipientInfo,version) }, + { SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(NSSCMSKeyAgreeRecipientInfo,originatorIdentifierOrKey), + NSSCMSOriginatorIdentifierOrKeyTemplate }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | + SEC_ASN1_CONTEXT_SPECIFIC | 1, + offsetof(NSSCMSKeyAgreeRecipientInfo,ukm), + SEC_OctetStringTemplate }, + { SEC_ASN1_INLINE, + offsetof(NSSCMSKeyAgreeRecipientInfo,keyEncAlg), + SECOID_AlgorithmIDTemplate }, + { SEC_ASN1_SEQUENCE_OF, + offsetof(NSSCMSKeyAgreeRecipientInfo,recipientEncryptedKeys), + NSSCMSRecipientEncryptedKeyTemplate }, + { 0 } +}; + +/* ----------------------------------------------------------------------------- + * KEK recipient info + */ + +static const SEC_ASN1Template NSSCMSKEKIdentifierTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(NSSCMSKEKIdentifier) }, + { SEC_ASN1_OCTET_STRING, + offsetof(NSSCMSKEKIdentifier,keyIdentifier) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_OCTET_STRING, + offsetof(NSSCMSKEKIdentifier,date) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_OCTET_STRING, + offsetof(NSSCMSKEKIdentifier,other) }, + { 0 } +}; + +static const SEC_ASN1Template NSSCMSKEKRecipientInfoTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(NSSCMSKEKRecipientInfo) }, + { SEC_ASN1_INTEGER, + offsetof(NSSCMSKEKRecipientInfo,version) }, + { SEC_ASN1_INLINE, + offsetof(NSSCMSKEKRecipientInfo,kekIdentifier), + NSSCMSKEKIdentifierTemplate }, + { SEC_ASN1_INLINE, + offsetof(NSSCMSKEKRecipientInfo,keyEncAlg), + SECOID_AlgorithmIDTemplate }, + { SEC_ASN1_OCTET_STRING, + offsetof(NSSCMSKEKRecipientInfo,encKey) }, + { 0 } +}; + +/* ----------------------------------------------------------------------------- + * recipient info + */ +const SEC_ASN1Template NSSCMSRecipientInfoTemplate[] = { + { SEC_ASN1_CHOICE, + offsetof(NSSCMSRecipientInfo,recipientInfoType), NULL, + sizeof(NSSCMSRecipientInfo) }, + { SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1, + offsetof(NSSCMSRecipientInfo,ri.keyAgreeRecipientInfo), + NSSCMSKeyAgreeRecipientInfoTemplate, + NSSCMSRecipientInfoID_KeyAgree }, + { SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 2, + offsetof(NSSCMSRecipientInfo,ri.kekRecipientInfo), + NSSCMSKEKRecipientInfoTemplate, + NSSCMSRecipientInfoID_KEK }, + { SEC_ASN1_INLINE, + offsetof(NSSCMSRecipientInfo,ri.keyTransRecipientInfo), + NSSCMSKeyTransRecipientInfoTemplate, + NSSCMSRecipientInfoID_KeyTrans }, + { 0 } +}; + +/* ----------------------------------------------------------------------------- + * + */ + +const SEC_ASN1Template NSSCMSDigestedDataTemplate[] = { + { SEC_ASN1_SEQUENCE | SEC_ASN1_MAY_STREAM, + 0, NULL, sizeof(NSSCMSDigestedData) }, + { SEC_ASN1_INTEGER, + offsetof(NSSCMSDigestedData,version) }, + { SEC_ASN1_INLINE, + offsetof(NSSCMSDigestedData,digestAlg), + SECOID_AlgorithmIDTemplate }, + { SEC_ASN1_INLINE, + offsetof(NSSCMSDigestedData,contentInfo), + NSSCMSEncapsulatedContentInfoTemplate }, + { SEC_ASN1_OCTET_STRING, + offsetof(NSSCMSDigestedData,digest) }, + { 0 } +}; + +const SEC_ASN1Template NSS_PointerToCMSDigestedDataTemplate[] = { + { SEC_ASN1_POINTER, 0, NSSCMSDigestedDataTemplate } +}; + +const SEC_ASN1Template NSSCMSEncryptedDataTemplate[] = { + { SEC_ASN1_SEQUENCE | SEC_ASN1_MAY_STREAM, + 0, NULL, sizeof(NSSCMSEncryptedData) }, + { SEC_ASN1_INTEGER, + offsetof(NSSCMSEncryptedData,version) }, + { SEC_ASN1_INLINE, + offsetof(NSSCMSEncryptedData,contentInfo), + NSSCMSEncryptedContentInfoTemplate }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1, + offsetof(NSSCMSEncryptedData,unprotectedAttr), + nss_cms_set_of_attribute_template }, + { 0 } +}; + +const SEC_ASN1Template NSS_PointerToCMSEncryptedDataTemplate[] = { + { SEC_ASN1_POINTER, 0, NSSCMSEncryptedDataTemplate } +}; + +/* ----------------------------------------------------------------------------- + * FORTEZZA KEA + */ +const SEC_ASN1Template NSS_SMIMEKEAParamTemplateSkipjack[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(NSSCMSSMIMEKEAParameters) }, + { SEC_ASN1_OCTET_STRING /* | SEC_ASN1_OPTIONAL */, + offsetof(NSSCMSSMIMEKEAParameters,originatorKEAKey) }, + { SEC_ASN1_OCTET_STRING, + offsetof(NSSCMSSMIMEKEAParameters,originatorRA) }, + { 0 } +}; + +const SEC_ASN1Template NSS_SMIMEKEAParamTemplateNoSkipjack[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(NSSCMSSMIMEKEAParameters) }, + { SEC_ASN1_OCTET_STRING /* | SEC_ASN1_OPTIONAL */, + offsetof(NSSCMSSMIMEKEAParameters,originatorKEAKey) }, + { SEC_ASN1_OCTET_STRING, + offsetof(NSSCMSSMIMEKEAParameters,originatorRA) }, + { SEC_ASN1_OCTET_STRING | SEC_ASN1_OPTIONAL , + offsetof(NSSCMSSMIMEKEAParameters,nonSkipjackIV) }, + { 0 } +}; + +const SEC_ASN1Template NSS_SMIMEKEAParamTemplateAllParams[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(NSSCMSSMIMEKEAParameters) }, + { SEC_ASN1_OCTET_STRING /* | SEC_ASN1_OPTIONAL */, + offsetof(NSSCMSSMIMEKEAParameters,originatorKEAKey) }, + { SEC_ASN1_OCTET_STRING, + offsetof(NSSCMSSMIMEKEAParameters,originatorRA) }, + { SEC_ASN1_OCTET_STRING | SEC_ASN1_OPTIONAL , + offsetof(NSSCMSSMIMEKEAParameters,nonSkipjackIV) }, + { SEC_ASN1_OCTET_STRING | SEC_ASN1_OPTIONAL , + offsetof(NSSCMSSMIMEKEAParameters,bulkKeySize) }, + { 0 } +}; + +const SEC_ASN1Template * +nss_cms_get_kea_template(NSSCMSKEATemplateSelector whichTemplate) +{ + const SEC_ASN1Template *returnVal = NULL; + + switch(whichTemplate) + { + case NSSCMSKEAUsesNonSkipjack: + returnVal = NSS_SMIMEKEAParamTemplateNoSkipjack; + break; + case NSSCMSKEAUsesSkipjack: + returnVal = NSS_SMIMEKEAParamTemplateSkipjack; + break; + case NSSCMSKEAUsesNonSkipjackWithPaddedEncKey: + default: + returnVal = NSS_SMIMEKEAParamTemplateAllParams; + break; + } + return returnVal; +} + +/* ----------------------------------------------------------------------------- + * + */ +static const SEC_ASN1Template * +nss_cms_choose_content_template(void *src_or_dest, PRBool encoding) +{ + const SEC_ASN1Template *theTemplate; + NSSCMSContentInfo *cinfo; + + PORT_Assert (src_or_dest != NULL); + if (src_or_dest == NULL) + return NULL; + + cinfo = (NSSCMSContentInfo *)src_or_dest; + switch (NSS_CMSContentInfo_GetContentTypeTag(cinfo)) { + default: + theTemplate = SEC_PointerToAnyTemplate; + break; + case SEC_OID_PKCS7_DATA: + theTemplate = SEC_PointerToOctetStringTemplate; + break; + case SEC_OID_PKCS7_SIGNED_DATA: + theTemplate = NSS_PointerToCMSSignedDataTemplate; + break; + case SEC_OID_PKCS7_ENVELOPED_DATA: + theTemplate = NSS_PointerToCMSEnvelopedDataTemplate; + break; + case SEC_OID_PKCS7_DIGESTED_DATA: + theTemplate = NSS_PointerToCMSDigestedDataTemplate; + break; + case SEC_OID_PKCS7_ENCRYPTED_DATA: + theTemplate = NSS_PointerToCMSEncryptedDataTemplate; + break; + } + return theTemplate; +} diff --git a/security/nss/lib/smime/cmsattr.c b/security/nss/lib/smime/cmsattr.c new file mode 100644 index 000000000..e0433fd18 --- /dev/null +++ b/security/nss/lib/smime/cmsattr.c @@ -0,0 +1,450 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * CMS attributes. + * + * $Id$ + */ + +#include "cmslocal.h" + +#include "secasn1.h" +#include "secitem.h" +#include "secoid.h" +#include "pk11func.h" +#include "prtime.h" +#include "secerr.h" + +/* + * ------------------------------------------------------------------- + * XXX The following Attribute stuff really belongs elsewhere. + * The Attribute type is *not* part of CMS but rather X.501. + * But for now, since CMS is the only customer of attributes, + * we define them here. Once there is a use outside of CMS, + * then change the attribute types and functions from internal + * to external naming convention, and move them elsewhere! + */ + + +/* + * NSS_CMSAttribute_Create - create an attribute + * + * if value is NULL, the attribute won't have a value. It can be added later + * with NSS_CMSAttribute_AddValue. + */ +NSSCMSAttribute * +NSS_CMSAttribute_Create(PRArenaPool *poolp, SECOidTag oidtag, SECItem *value, PRBool encoded) +{ + NSSCMSAttribute *attr; + SECItem *copiedvalue; + void *mark; + + PORT_Assert (poolp != NULL); + + mark = PORT_ArenaMark (poolp); + + attr = (NSSCMSAttribute *)PORT_ArenaZAlloc(poolp, sizeof(NSSCMSAttribute)); + if (attr == NULL) + goto loser; + + attr->typeTag = SECOID_FindOIDByTag(oidtag); + if (attr->typeTag == NULL) + goto loser; + + if (SECITEM_CopyItem(poolp, &(attr->type), &(attr->typeTag->oid)) != SECSuccess) + goto loser; + + if (value != NULL) { + if ((copiedvalue = SECITEM_AllocItem(poolp, NULL, value->len)) == NULL) + goto loser; + + if (SECITEM_CopyItem(poolp, copiedvalue, value) != SECSuccess) + goto loser; + + NSS_CMSArray_Add(poolp, (void ***)&(attr->values), (void *)copiedvalue); + } + + attr->encoded = encoded; + + PORT_ArenaUnmark (poolp, mark); + + return attr; + +loser: + PORT_Assert (mark != NULL); + PORT_ArenaRelease (poolp, mark); + return NULL; +} + +/* + * NSS_CMSAttribute_AddValue - add another value to an attribute + */ +SECStatus +NSS_CMSAttribute_AddValue(PLArenaPool *poolp, NSSCMSAttribute *attr, SECItem *value) +{ + SECItem copiedvalue; + void *mark; + + PORT_Assert (poolp != NULL); + + mark = PORT_ArenaMark(poolp); + + /* XXX we need an object memory model #$%#$%! */ + if (SECITEM_CopyItem(poolp, &copiedvalue, value) != SECSuccess) + goto loser; + + if (NSS_CMSArray_Add(poolp, (void ***)&(attr->values), (void *)&copiedvalue) != SECSuccess) + goto loser; + + PORT_ArenaUnmark(poolp, mark); + return SECSuccess; + +loser: + PORT_Assert (mark != NULL); + PORT_ArenaRelease (poolp, mark); + return SECFailure; +} + +/* + * NSS_CMSAttribute_GetType - return the OID tag + */ +SECOidTag +NSS_CMSAttribute_GetType(NSSCMSAttribute *attr) +{ + SECOidData *typetag; + + typetag = SECOID_FindOID(&(attr->type)); + if (typetag == NULL) + return SEC_OID_UNKNOWN; + + return typetag->offset; +} + +/* + * NSS_CMSAttribute_GetValue - return the first attribute value + * + * We do some sanity checking first: + * - Multiple values are *not* expected. + * - Empty values are *not* expected. + */ +SECItem * +NSS_CMSAttribute_GetValue(NSSCMSAttribute *attr) +{ + SECItem *value; + + if (attr == NULL) + return NULL; + + value = attr->values[0]; + + if (value == NULL || value->data == NULL || value->len == 0) + return NULL; + + if (attr->values[1] != NULL) + return NULL; + + return value; +} + +/* + * NSS_CMSAttribute_CompareValue - compare the attribute's first value against data + */ +PRBool +NSS_CMSAttribute_CompareValue(NSSCMSAttribute *attr, SECItem *av) +{ + SECItem *value; + + if (attr == NULL) + return PR_FALSE; + + value = NSS_CMSAttribute_GetValue(attr); + + return (value != NULL && value->len == av->len && + PORT_Memcmp (value->data, av->data, value->len) == 0); +} + +/* + * templates and functions for separate ASN.1 encoding of attributes + * + * used in NSS_CMSAttributeArray_Reorder + */ + +/* + * helper function for dynamic template determination of the attribute value + */ +static const SEC_ASN1Template * +cms_attr_choose_attr_value_template(void *src_or_dest, PRBool encoding) +{ + const SEC_ASN1Template *theTemplate; + NSSCMSAttribute *attribute; + SECOidData *oiddata; + PRBool encoded; + + PORT_Assert (src_or_dest != NULL); + if (src_or_dest == NULL) + return NULL; + + attribute = (NSSCMSAttribute *)src_or_dest; + + if (encoding && attribute->encoded) + /* we're encoding, and the attribute value is already encoded. */ + return SEC_AnyTemplate; + + /* get attribute's typeTag */ + oiddata = attribute->typeTag; + if (oiddata == NULL) { + oiddata = SECOID_FindOID(&attribute->type); + attribute->typeTag = oiddata; + } + + if (oiddata == NULL) { + /* still no OID tag? OID is unknown then. en/decode value as ANY. */ + encoded = PR_TRUE; + theTemplate = SEC_AnyTemplate; + } else { + switch (oiddata->offset) { + default: + /* same goes for OIDs that are not handled here */ + encoded = PR_TRUE; + theTemplate = SEC_AnyTemplate; + break; + /* otherwise choose proper template */ + case SEC_OID_PKCS9_EMAIL_ADDRESS: + case SEC_OID_RFC1274_MAIL: + case SEC_OID_PKCS9_UNSTRUCTURED_NAME: + encoded = PR_FALSE; + theTemplate = SEC_IA5StringTemplate; + break; + case SEC_OID_PKCS9_CONTENT_TYPE: + encoded = PR_FALSE; + theTemplate = SEC_ObjectIDTemplate; + break; + case SEC_OID_PKCS9_MESSAGE_DIGEST: + encoded = PR_FALSE; + theTemplate = SEC_OctetStringTemplate; + break; + case SEC_OID_PKCS9_SIGNING_TIME: + encoded = PR_FALSE; + theTemplate = SEC_UTCTimeTemplate; + break; + /* XXX Want other types here, too */ + } + } + + if (encoding) { + /* + * If we are encoding and we think we have an already-encoded value, + * then the code which initialized this attribute should have set + * the "encoded" property to true (and we would have returned early, + * up above). No devastating error, but that code should be fixed. + * (It could indicate that the resulting encoded bytes are wrong.) + */ + PORT_Assert (!encoded); + } else { + /* + * We are decoding; record whether the resulting value is + * still encoded or not. + */ + attribute->encoded = encoded; + } + return theTemplate; +} + +static SEC_ChooseASN1TemplateFunc cms_attr_chooser + = cms_attr_choose_attr_value_template; + +const SEC_ASN1Template nss_cms_attribute_template[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(NSSCMSAttribute) }, + { SEC_ASN1_OBJECT_ID, + offsetof(NSSCMSAttribute,type) }, + { SEC_ASN1_DYNAMIC | SEC_ASN1_SET_OF, + offsetof(NSSCMSAttribute,values), + &cms_attr_chooser }, + { 0 } +}; + +const SEC_ASN1Template nss_cms_set_of_attribute_template[] = { + { SEC_ASN1_SET_OF, 0, nss_cms_attribute_template }, +}; + +/* ============================================================================= + * Attribute Array methods + */ + +/* + * NSS_CMSAttributeArray_Encode - encode an Attribute array as SET OF Attributes + * + * If you are wondering why this routine does not reorder the attributes + * first, and might be tempted to make it do so, see the comment by the + * call to ReorderAttributes in cmsencode.c. (Or, see who else calls this + * and think long and hard about the implications of making it always + * do the reordering.) + */ +SECItem * +NSS_CMSAttributeArray_Encode(PRArenaPool *poolp, NSSCMSAttribute ***attrs, SECItem *dest) +{ + return SEC_ASN1EncodeItem (poolp, dest, (void *)attrs, nss_cms_set_of_attribute_template); +} + +/* + * NSS_CMSAttributeArray_Reorder - sort attribute array by attribute's DER encoding + * + * make sure that the order of the attributes guarantees valid DER (which must be + * in lexigraphically ascending order for a SET OF); if reordering is necessary it + * will be done in place (in attrs). + */ +SECStatus +NSS_CMSAttributeArray_Reorder(NSSCMSAttribute **attrs) +{ + return NSS_CMSArray_SortByDER((void **)attrs, nss_cms_attribute_template, NULL); +} + +/* + * NSS_CMSAttributeArray_FindAttrByOidTag - look through a set of attributes and + * find one that matches the specified object ID. + * + * If "only" is true, then make sure that there is not more than one attribute + * of the same type. Otherwise, just return the first one found. (XXX Does + * anybody really want that first-found behavior? It was like that when I found it...) + */ +NSSCMSAttribute * +NSS_CMSAttributeArray_FindAttrByOidTag(NSSCMSAttribute **attrs, SECOidTag oidtag, PRBool only) +{ + SECOidData *oid; + NSSCMSAttribute *attr1, *attr2; + + if (attrs == NULL) + return NULL; + + oid = SECOID_FindOIDByTag(oidtag); + if (oid == NULL) + return NULL; + + while ((attr1 = *attrs++) != NULL) { + if (attr1->type.len == oid->oid.len && PORT_Memcmp (attr1->type.data, + oid->oid.data, + oid->oid.len) == 0) + break; + } + + if (attr1 == NULL) + return NULL; + + if (!only) + return attr1; + + while ((attr2 = *attrs++) != NULL) { + if (attr2->type.len == oid->oid.len && PORT_Memcmp (attr2->type.data, + oid->oid.data, + oid->oid.len) == 0) + break; + } + + if (attr2 != NULL) + return NULL; + + return attr1; +} + +/* + * NSS_CMSAttributeArray_AddAttr - add an attribute to an + * array of attributes. + */ +SECStatus +NSS_CMSAttributeArray_AddAttr(PLArenaPool *poolp, NSSCMSAttribute ***attrs, NSSCMSAttribute *attr) +{ + NSSCMSAttribute *oattr; + void *mark; + SECOidTag type; + + mark = PORT_ArenaMark(poolp); + + /* find oidtag of attr */ + type = NSS_CMSAttribute_GetType(attr); + + /* see if we have one already */ + oattr = NSS_CMSAttributeArray_FindAttrByOidTag(*attrs, type, PR_FALSE); + PORT_Assert (oattr == NULL); + if (oattr != NULL) + goto loser; /* XXX or would it be better to replace it? */ + + /* no, shove it in */ + if (NSS_CMSArray_Add(poolp, (void ***)attrs, (void *)attr) != SECSuccess) + goto loser; + + PORT_ArenaUnmark(poolp, mark); + return SECSuccess; + +loser: + PORT_ArenaRelease(poolp, mark); + return SECFailure; +} + +/* + * NSS_CMSAttributeArray_SetAttr - set an attribute's value in a set of attributes + */ +SECStatus +NSS_CMSAttributeArray_SetAttr(PLArenaPool *poolp, NSSCMSAttribute ***attrs, SECOidTag type, SECItem *value, PRBool encoded) +{ + NSSCMSAttribute *attr; + void *mark; + + mark = PORT_ArenaMark(poolp); + + /* see if we have one already */ + attr = NSS_CMSAttributeArray_FindAttrByOidTag(*attrs, type, PR_FALSE); + if (attr == NULL) { + /* not found? create one! */ + attr = NSS_CMSAttribute_Create(poolp, type, value, encoded); + if (attr == NULL) + goto loser; + /* and add it to the list */ + if (NSS_CMSArray_Add(poolp, (void ***)attrs, (void *)attr) != SECSuccess) + goto loser; + } else { + /* found, shove it in */ + /* XXX we need a decent memory model @#$#$!#!!! */ + attr->values[0] = value; + attr->encoded = encoded; + } + + PORT_ArenaUnmark (poolp, mark); + return SECSuccess; + +loser: + PORT_ArenaRelease (poolp, mark); + return SECFailure; +} + diff --git a/security/nss/lib/smime/cmscinfo.c b/security/nss/lib/smime/cmscinfo.c new file mode 100644 index 000000000..9e4a2dc5e --- /dev/null +++ b/security/nss/lib/smime/cmscinfo.c @@ -0,0 +1,327 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * CMS contentInfo methods. + * + * $Id$ + */ + +#include "cmslocal.h" + +#include "pk11func.h" +#include "secitem.h" +#include "secoid.h" +#include "secerr.h" + +/* + * NSS_CMSContentInfo_Create - create a content info + * + * version is set in the _Finalize procedures for each content type + */ + +/* + * NSS_CMSContentInfo_Destroy - destroy a CMS contentInfo and all of its sub-pieces. + */ +void +NSS_CMSContentInfo_Destroy(NSSCMSContentInfo *cinfo) +{ + SECOidTag kind; + + kind = NSS_CMSContentInfo_GetContentTypeTag(cinfo); + switch (kind) { + case SEC_OID_PKCS7_ENVELOPED_DATA: + NSS_CMSEnvelopedData_Destroy(cinfo->content.envelopedData); + break; + case SEC_OID_PKCS7_SIGNED_DATA: + NSS_CMSSignedData_Destroy(cinfo->content.signedData); + break; + case SEC_OID_PKCS7_ENCRYPTED_DATA: + NSS_CMSEncryptedData_Destroy(cinfo->content.encryptedData); + break; + case SEC_OID_PKCS7_DIGESTED_DATA: + NSS_CMSDigestedData_Destroy(cinfo->content.digestedData); + break; + default: + /* XXX Anything else that needs to be "manually" freed/destroyed? */ + break; + } + if (cinfo->bulkkey) + PK11_FreeSymKey(cinfo->bulkkey); + + /* we live in a pool, so no need to worry about storage */ +} + +/* + * NSS_CMSContentInfo_GetChildContentInfo - get content's contentInfo (if it exists) + */ +NSSCMSContentInfo * +NSS_CMSContentInfo_GetChildContentInfo(NSSCMSContentInfo *cinfo) +{ + switch (NSS_CMSContentInfo_GetContentTypeTag(cinfo)) { + case SEC_OID_PKCS7_DATA: + return NULL; + case SEC_OID_PKCS7_SIGNED_DATA: + return &(cinfo->content.signedData->contentInfo); + case SEC_OID_PKCS7_ENVELOPED_DATA: + return &(cinfo->content.envelopedData->contentInfo); + case SEC_OID_PKCS7_DIGESTED_DATA: + return &(cinfo->content.digestedData->contentInfo); + case SEC_OID_PKCS7_ENCRYPTED_DATA: + return &(cinfo->content.encryptedData->contentInfo); + default: + return NULL; + } +} + +/* + * NSS_CMSContentInfo_SetContent - set content type & content + */ +SECStatus +NSS_CMSContentInfo_SetContent(NSSCMSMessage *cmsg, NSSCMSContentInfo *cinfo, SECOidTag type, void *ptr) +{ + SECStatus rv; + + cinfo->contentTypeTag = SECOID_FindOIDByTag(type); + if (cinfo->contentTypeTag == NULL) + return SECFailure; + + /* do not copy the oid, just create a reference */ + rv = SECITEM_CopyItem (cmsg->poolp, &(cinfo->contentType), &(cinfo->contentTypeTag->oid)); + if (rv != SECSuccess) + return SECFailure; + + cinfo->content.pointer = ptr; + + if (type != SEC_OID_PKCS7_DATA) { + /* as we always have some inner data, + * we need to set it to something, just to fool the encoder enough to work on it + * and get us into nss_cms_encoder_notify at that point */ + cinfo->rawContent = SECITEM_AllocItem(cmsg->poolp, NULL, 1); + if (cinfo->rawContent == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return SECFailure; + } + } + + return SECSuccess; +} + +/* + * NSS_CMSContentInfo_SetContent_XXXX - typesafe wrappers for NSS_CMSContentInfo_SetContent + */ + +/* + * data == NULL -> pass in data via NSS_CMSEncoder_Update + * data != NULL -> take this data + */ +SECStatus +NSS_CMSContentInfo_SetContent_Data(NSSCMSMessage *cmsg, NSSCMSContentInfo *cinfo, SECItem *data, PRBool detached) +{ + if (NSS_CMSContentInfo_SetContent(cmsg, cinfo, SEC_OID_PKCS7_DATA, (void *)data) != SECSuccess) + return SECFailure; + cinfo->rawContent = (detached) ? + NULL : (data) ? + data : SECITEM_AllocItem(cmsg->poolp, NULL, 1); + return SECSuccess; +} + +SECStatus +NSS_CMSContentInfo_SetContent_SignedData(NSSCMSMessage *cmsg, NSSCMSContentInfo *cinfo, NSSCMSSignedData *sigd) +{ + return NSS_CMSContentInfo_SetContent(cmsg, cinfo, SEC_OID_PKCS7_SIGNED_DATA, (void *)sigd); +} + +SECStatus +NSS_CMSContentInfo_SetContent_EnvelopedData(NSSCMSMessage *cmsg, NSSCMSContentInfo *cinfo, NSSCMSEnvelopedData *envd) +{ + return NSS_CMSContentInfo_SetContent(cmsg, cinfo, SEC_OID_PKCS7_ENVELOPED_DATA, (void *)envd); +} + +SECStatus +NSS_CMSContentInfo_SetContent_DigestedData(NSSCMSMessage *cmsg, NSSCMSContentInfo *cinfo, NSSCMSDigestedData *digd) +{ + return NSS_CMSContentInfo_SetContent(cmsg, cinfo, SEC_OID_PKCS7_DIGESTED_DATA, (void *)digd); +} + +SECStatus +NSS_CMSContentInfo_SetContent_EncryptedData(NSSCMSMessage *cmsg, NSSCMSContentInfo *cinfo, NSSCMSEncryptedData *encd) +{ + return NSS_CMSContentInfo_SetContent(cmsg, cinfo, SEC_OID_PKCS7_ENCRYPTED_DATA, (void *)encd); +} + +/* + * NSS_CMSContentInfo_GetContent - get pointer to inner content + * + * needs to be casted... + */ +void * +NSS_CMSContentInfo_GetContent(NSSCMSContentInfo *cinfo) +{ + switch (cinfo->contentTypeTag->offset) { + case SEC_OID_PKCS7_DATA: + case SEC_OID_PKCS7_SIGNED_DATA: + case SEC_OID_PKCS7_ENVELOPED_DATA: + case SEC_OID_PKCS7_DIGESTED_DATA: + case SEC_OID_PKCS7_ENCRYPTED_DATA: + return cinfo->content.pointer; + default: + return NULL; + } +} + +/* + * NSS_CMSContentInfo_GetInnerContent - get pointer to innermost content + * + * this is typically only called by NSS_CMSMessage_GetContent() + */ +SECItem * +NSS_CMSContentInfo_GetInnerContent(NSSCMSContentInfo *cinfo) +{ + NSSCMSContentInfo *ccinfo; + + switch (NSS_CMSContentInfo_GetContentTypeTag(cinfo)) { + case SEC_OID_PKCS7_DATA: + return cinfo->content.data; /* end of recursion - every message has to have a data cinfo */ + case SEC_OID_PKCS7_DIGESTED_DATA: + case SEC_OID_PKCS7_ENCRYPTED_DATA: + case SEC_OID_PKCS7_ENVELOPED_DATA: + case SEC_OID_PKCS7_SIGNED_DATA: + if ((ccinfo = NSS_CMSContentInfo_GetChildContentInfo(cinfo)) == NULL) + break; + return NSS_CMSContentInfo_GetContent(ccinfo); + default: + PORT_Assert(0); + break; + } + return NULL; +} + +/* + * NSS_CMSContentInfo_GetContentType{Tag,OID} - find out (saving pointer to lookup result + * for future reference) and return the inner content type. + */ +SECOidTag +NSS_CMSContentInfo_GetContentTypeTag(NSSCMSContentInfo *cinfo) +{ + if (cinfo->contentTypeTag == NULL) + cinfo->contentTypeTag = SECOID_FindOID(&(cinfo->contentType)); + + if (cinfo->contentTypeTag == NULL) + return SEC_OID_UNKNOWN; + + return cinfo->contentTypeTag->offset; +} + +SECItem * +NSS_CMSContentInfo_GetContentTypeOID(NSSCMSContentInfo *cinfo) +{ + if (cinfo->contentTypeTag == NULL) + cinfo->contentTypeTag = SECOID_FindOID(&(cinfo->contentType)); + + if (cinfo->contentTypeTag == NULL) + return NULL; + + return &(cinfo->contentTypeTag->oid); +} + +/* + * NSS_CMSContentInfo_GetContentEncAlgTag - find out (saving pointer to lookup result + * for future reference) and return the content encryption algorithm tag. + */ +SECOidTag +NSS_CMSContentInfo_GetContentEncAlgTag(NSSCMSContentInfo *cinfo) +{ + if (cinfo->contentEncAlgTag == SEC_OID_UNKNOWN) + cinfo->contentEncAlgTag = SECOID_GetAlgorithmTag(&(cinfo->contentEncAlg)); + + return cinfo->contentEncAlgTag; +} + +/* + * NSS_CMSContentInfo_GetContentEncAlg - find out and return the content encryption algorithm tag. + */ +SECAlgorithmID * +NSS_CMSContentInfo_GetContentEncAlg(NSSCMSContentInfo *cinfo) +{ + return &(cinfo->contentEncAlg); +} + +SECStatus +NSS_CMSContentInfo_SetContentEncAlg(PLArenaPool *poolp, NSSCMSContentInfo *cinfo, + SECOidTag bulkalgtag, SECItem *parameters, int keysize) +{ + SECStatus rv; + + rv = SECOID_SetAlgorithmID(poolp, &(cinfo->contentEncAlg), bulkalgtag, parameters); + if (rv != SECSuccess) + return SECFailure; + cinfo->keysize = keysize; + return SECSuccess; +} + +SECStatus +NSS_CMSContentInfo_SetContentEncAlgID(PLArenaPool *poolp, NSSCMSContentInfo *cinfo, + SECAlgorithmID *algid, int keysize) +{ + SECStatus rv; + + rv = SECOID_CopyAlgorithmID(poolp, &(cinfo->contentEncAlg), algid); + if (rv != SECSuccess) + return SECFailure; + if (keysize >= 0) + cinfo->keysize = keysize; + return SECSuccess; +} + +void +NSS_CMSContentInfo_SetBulkKey(NSSCMSContentInfo *cinfo, PK11SymKey *bulkkey) +{ + cinfo->bulkkey = PK11_ReferenceSymKey(bulkkey); + cinfo->keysize = PK11_GetKeyStrength(cinfo->bulkkey, &(cinfo->contentEncAlg)); +} + +PK11SymKey * +NSS_CMSContentInfo_GetBulkKey(NSSCMSContentInfo *cinfo) +{ + if (cinfo->bulkkey == NULL) + return NULL; + + return PK11_ReferenceSymKey(cinfo->bulkkey); +} + +int +NSS_CMSContentInfo_GetBulkKeySize(NSSCMSContentInfo *cinfo) +{ + return cinfo->keysize; +} diff --git a/security/nss/lib/smime/cmscipher.c b/security/nss/lib/smime/cmscipher.c new file mode 100644 index 000000000..27e5668aa --- /dev/null +++ b/security/nss/lib/smime/cmscipher.c @@ -0,0 +1,792 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * Encryption/decryption routines for CMS implementation, none of which are exported. + * + * $Id$ + */ + +#include "cmslocal.h" + +#include "secoid.h" +#include "secitem.h" +#include "pk11func.h" +#include "secerr.h" +#include "secpkcs5.h" + +/* + * ------------------------------------------------------------------- + * Cipher stuff. + */ + +typedef SECStatus (*nss_cms_cipher_function) (void *, unsigned char *, unsigned int *, + unsigned int, const unsigned char *, unsigned int); +typedef SECStatus (*nss_cms_cipher_destroy) (void *, PRBool); + +#define BLOCK_SIZE 4096 + +struct NSSCMSCipherContextStr { + void * cx; /* PK11 cipher context */ + nss_cms_cipher_function doit; + nss_cms_cipher_destroy destroy; + PRBool encrypt; /* encrypt / decrypt switch */ + int block_size; /* block & pad sizes for cipher */ + int pad_size; + int pending_count; /* pending data (not yet en/decrypted */ + unsigned char pending_buf[BLOCK_SIZE];/* because of blocking */ +}; + +/* + * NSS_CMSCipherContext_StartDecrypt - create a cipher context to do decryption + * based on the given bulk * encryption key and algorithm identifier (which may include an iv). + * + * XXX Once both are working, it might be nice to combine this and the + * function below (for starting up encryption) into one routine, and just + * have two simple cover functions which call it. + */ +NSSCMSCipherContext * +NSS_CMSCipherContext_StartDecrypt(PK11SymKey *key, SECAlgorithmID *algid) +{ + NSSCMSCipherContext *cc; + void *ciphercx; + CK_MECHANISM_TYPE mechanism; + SECItem *param; + PK11SlotInfo *slot; + SECOidTag algtag; + + algtag = SECOID_GetAlgorithmTag(algid); + + /* set param and mechanism */ + if (SEC_PKCS5IsAlgorithmPBEAlg(algid)) { + CK_MECHANISM pbeMech, cryptoMech; + SECItem *pbeParams; + SEC_PKCS5KeyAndPassword *keyPwd; + + PORT_Memset(&pbeMech, 0, sizeof(CK_MECHANISM)); + PORT_Memset(&cryptoMech, 0, sizeof(CK_MECHANISM)); + + /* HACK ALERT! + * in this case, key is not actually a PK11SymKey *, but a SEC_PKCS5KeyAndPassword * + */ + keyPwd = (SEC_PKCS5KeyAndPassword *)key; + key = keyPwd->key; + + /* find correct PK11 mechanism and parameters to initialize pbeMech */ + pbeMech.mechanism = PK11_AlgtagToMechanism(algtag); + pbeParams = PK11_ParamFromAlgid(algid); + if (!pbeParams) + return NULL; + pbeMech.pParameter = pbeParams->data; + pbeMech.ulParameterLen = pbeParams->len; + + /* now map pbeMech to cryptoMech */ + if (PK11_MapPBEMechanismToCryptoMechanism(&pbeMech, &cryptoMech, keyPwd->pwitem, + PR_FALSE) != CKR_OK) { + SECITEM_ZfreeItem(pbeParams, PR_TRUE); + return NULL; + } + SECITEM_ZfreeItem(pbeParams, PR_TRUE); + + /* and use it to initialize param & mechanism */ + if ((param = (SECItem *)PORT_ZAlloc(sizeof(SECItem))) == NULL) + return NULL; + + param->data = (unsigned char *)cryptoMech.pParameter; + param->len = cryptoMech.ulParameterLen; + mechanism = cryptoMech.mechanism; + } else { + mechanism = PK11_AlgtagToMechanism(algtag); + if ((param = PK11_ParamFromAlgid(algid)) == NULL) + return NULL; + } + + cc = (NSSCMSCipherContext *)PORT_ZAlloc(sizeof(NSSCMSCipherContext)); + if (cc == NULL) { + SECITEM_FreeItem(param,PR_TRUE); + return NULL; + } + + /* figure out pad and block sizes */ + cc->pad_size = PK11_GetBlockSize(mechanism, param); + slot = PK11_GetSlotFromKey(key); + cc->block_size = PK11_IsHW(slot) ? BLOCK_SIZE : cc->pad_size; + PK11_FreeSlot(slot); + + /* create PK11 cipher context */ + ciphercx = PK11_CreateContextBySymKey(mechanism, CKA_DECRYPT, key, param); + SECITEM_FreeItem(param, PR_TRUE); + if (ciphercx == NULL) { + PORT_Free (cc); + return NULL; + } + + cc->cx = ciphercx; + cc->doit = (nss_cms_cipher_function) PK11_CipherOp; + cc->destroy = (nss_cms_cipher_destroy) PK11_DestroyContext; + cc->encrypt = PR_FALSE; + cc->pending_count = 0; + + return cc; +} + +/* + * NSS_CMSCipherContext_StartEncrypt - create a cipher object to do encryption, + * based on the given bulk encryption key and algorithm tag. Fill in the algorithm + * identifier (which may include an iv) appropriately. + * + * XXX Once both are working, it might be nice to combine this and the + * function above (for starting up decryption) into one routine, and just + * have two simple cover functions which call it. + */ +NSSCMSCipherContext * +NSS_CMSCipherContext_StartEncrypt(PRArenaPool *poolp, PK11SymKey *key, SECAlgorithmID *algid) +{ + NSSCMSCipherContext *cc; + void *ciphercx; + SECItem *param; + SECStatus rv; + CK_MECHANISM_TYPE mechanism; + PK11SlotInfo *slot; + PRBool needToEncodeAlgid = PR_FALSE; + SECOidTag algtag = SECOID_GetAlgorithmTag(algid); + + /* set param and mechanism */ + if (SEC_PKCS5IsAlgorithmPBEAlg(algid)) { + CK_MECHANISM pbeMech, cryptoMech; + SECItem *pbeParams; + SEC_PKCS5KeyAndPassword *keyPwd; + + PORT_Memset(&pbeMech, 0, sizeof(CK_MECHANISM)); + PORT_Memset(&cryptoMech, 0, sizeof(CK_MECHANISM)); + + /* HACK ALERT! + * in this case, key is not actually a PK11SymKey *, but a SEC_PKCS5KeyAndPassword * + */ + keyPwd = (SEC_PKCS5KeyAndPassword *)key; + key = keyPwd->key; + + /* find correct PK11 mechanism and parameters to initialize pbeMech */ + pbeMech.mechanism = PK11_AlgtagToMechanism(algtag); + pbeParams = PK11_ParamFromAlgid(algid); + if (!pbeParams) + return NULL; + pbeMech.pParameter = pbeParams->data; + pbeMech.ulParameterLen = pbeParams->len; + + /* now map pbeMech to cryptoMech */ + if (PK11_MapPBEMechanismToCryptoMechanism(&pbeMech, &cryptoMech, keyPwd->pwitem, + PR_FALSE) != CKR_OK) { + SECITEM_ZfreeItem(pbeParams, PR_TRUE); + return NULL; + } + SECITEM_ZfreeItem(pbeParams, PR_TRUE); + + /* and use it to initialize param & mechanism */ + if ((param = (SECItem *)PORT_ZAlloc(sizeof(SECItem))) == NULL) + return NULL; + + param->data = (unsigned char *)cryptoMech.pParameter; + param->len = cryptoMech.ulParameterLen; + mechanism = cryptoMech.mechanism; + } else { + mechanism = PK11_AlgtagToMechanism(algtag); + if ((param = PK11_GenerateNewParam(mechanism, key)) == NULL) + return NULL; + needToEncodeAlgid = PR_TRUE; + } + + cc = (NSSCMSCipherContext *)PORT_ZAlloc(sizeof(NSSCMSCipherContext)); + if (cc == NULL) + return NULL; + + /* now find pad and block sizes for our mechanism */ + cc->pad_size = PK11_GetBlockSize(mechanism,param); + slot = PK11_GetSlotFromKey(key); + cc->block_size = PK11_IsHW(slot) ? BLOCK_SIZE : cc->pad_size; + PK11_FreeSlot(slot); + + /* and here we go, creating a PK11 cipher context */ + ciphercx = PK11_CreateContextBySymKey(mechanism, CKA_ENCRYPT, key, param); + if (ciphercx == NULL) { + PORT_Free(cc); + cc = NULL; + goto loser; + } + + /* + * These are placed after the CreateContextBySymKey() because some + * mechanisms have to generate their IVs from their card (i.e. FORTEZZA). + * Don't move it from here. + * XXX is that right? the purpose of this is to get the correct algid + * containing the IVs etc. for encoding. this means we need to set this up + * BEFORE encoding the algid in the contentInfo, right? + */ + if (needToEncodeAlgid) { + rv = PK11_ParamToAlgid(algtag, param, poolp, algid); + if(rv != SECSuccess) { + PORT_Free(cc); + cc = NULL; + goto loser; + } + } + + cc->cx = ciphercx; + cc->doit = (nss_cms_cipher_function)PK11_CipherOp; + cc->destroy = (nss_cms_cipher_destroy)PK11_DestroyContext; + cc->encrypt = PR_TRUE; + cc->pending_count = 0; + +loser: + SECITEM_FreeItem(param, PR_TRUE); + + return cc; +} + +void +NSS_CMSCipherContext_Destroy(NSSCMSCipherContext *cc) +{ + PORT_Assert(cc != NULL); + if (cc == NULL) + return; + (*cc->destroy)(cc->cx, PR_TRUE); + PORT_Free(cc); +} + +/* + * NSS_CMSCipherContext_DecryptLength - find the output length of the next call to decrypt. + * + * cc - the cipher context + * input_len - number of bytes used as input + * final - true if this is the final chunk of data + * + * Result can be used to perform memory allocations. Note that the amount + * is exactly accurate only when not doing a block cipher or when final + * is false, otherwise it is an upper bound on the amount because until + * we see the data we do not know how many padding bytes there are + * (always between 1 and bsize). + * + * Note that this can return zero, which does not mean that the decrypt + * operation can be skipped! (It simply means that there are not enough + * bytes to make up an entire block; the bytes will be reserved until + * there are enough to encrypt/decrypt at least one block.) However, + * if zero is returned it *does* mean that no output buffer need be + * passed in to the subsequent decrypt operation, as no output bytes + * will be stored. + */ +unsigned int +NSS_CMSCipherContext_DecryptLength(NSSCMSCipherContext *cc, unsigned int input_len, PRBool final) +{ + int blocks, block_size; + + PORT_Assert (! cc->encrypt); + + block_size = cc->block_size; + + /* + * If this is not a block cipher, then we always have the same + * number of output bytes as we had input bytes. + */ + if (block_size == 0) + return input_len; + + /* + * On the final call, we will always use up all of the pending + * bytes plus all of the input bytes, *but*, there will be padding + * at the end and we cannot predict how many bytes of padding we + * will end up removing. The amount given here is actually known + * to be at least 1 byte too long (because we know we will have + * at least 1 byte of padding), but seemed clearer/better to me. + */ + if (final) + return cc->pending_count + input_len; + + /* + * Okay, this amount is exactly what we will output on the + * next cipher operation. We will always hang onto the last + * 1 - block_size bytes for non-final operations. That is, + * we will do as many complete blocks as we can *except* the + * last block (complete or partial). (This is because until + * we know we are at the end, we cannot know when to interpret + * and removing the padding byte(s), which are guaranteed to + * be there.) + */ + blocks = (cc->pending_count + input_len - 1) / block_size; + return blocks * block_size; +} + +/* + * NSS_CMSCipherContext_EncryptLength - find the output length of the next call to encrypt. + * + * cc - the cipher context + * input_len - number of bytes used as input + * final - true if this is the final chunk of data + * + * Result can be used to perform memory allocations. + * + * Note that this can return zero, which does not mean that the encrypt + * operation can be skipped! (It simply means that there are not enough + * bytes to make up an entire block; the bytes will be reserved until + * there are enough to encrypt/decrypt at least one block.) However, + * if zero is returned it *does* mean that no output buffer need be + * passed in to the subsequent encrypt operation, as no output bytes + * will be stored. + */ +unsigned int +NSS_CMSCipherContext_EncryptLength(NSSCMSCipherContext *cc, unsigned int input_len, PRBool final) +{ + int blocks, block_size; + int pad_size; + + PORT_Assert (cc->encrypt); + + block_size = cc->block_size; + pad_size = cc->pad_size; + + /* + * If this is not a block cipher, then we always have the same + * number of output bytes as we had input bytes. + */ + if (block_size == 0) + return input_len; + + /* + * On the final call, we only send out what we need for + * remaining bytes plus the padding. (There is always padding, + * so even if we have an exact number of blocks as input, we + * will add another full block that is just padding.) + */ + if (final) { + if (pad_size == 0) { + return cc->pending_count + input_len; + } else { + blocks = (cc->pending_count + input_len) / pad_size; + blocks++; + return blocks*pad_size; + } + } + + /* + * Now, count the number of complete blocks of data we have. + */ + blocks = (cc->pending_count + input_len) / block_size; + + + return blocks * block_size; +} + + +/* + * NSS_CMSCipherContext_Decrypt - do the decryption + * + * cc - the cipher context + * output - buffer for decrypted result bytes + * output_len_p - number of bytes in output + * max_output_len - upper bound on bytes to put into output + * input - pointer to input bytes + * input_len - number of input bytes + * final - true if this is the final chunk of data + * + * Decrypts a given length of input buffer (starting at "input" and + * containing "input_len" bytes), placing the decrypted bytes in + * "output" and storing the output length in "*output_len_p". + * "cc" is the return value from NSS_CMSCipher_StartDecrypt. + * When "final" is true, this is the last of the data to be decrypted. + * + * This is much more complicated than it sounds when the cipher is + * a block-type, meaning that the decryption function will only + * operate on whole blocks. But our caller is operating stream-wise, + * and can pass in any number of bytes. So we need to keep track + * of block boundaries. We save excess bytes between calls in "cc". + * We also need to determine which bytes are padding, and remove + * them from the output. We can only do this step when we know we + * have the final block of data. PKCS #7 specifies that the padding + * used for a block cipher is a string of bytes, each of whose value is + * the same as the length of the padding, and that all data is padded. + * (Even data that starts out with an exact multiple of blocks gets + * added to it another block, all of which is padding.) + */ +SECStatus +NSS_CMSCipherContext_Decrypt(NSSCMSCipherContext *cc, unsigned char *output, + unsigned int *output_len_p, unsigned int max_output_len, + const unsigned char *input, unsigned int input_len, + PRBool final) +{ + int blocks, bsize, pcount, padsize; + unsigned int max_needed, ifraglen, ofraglen, output_len; + unsigned char *pbuf; + SECStatus rv; + + PORT_Assert (! cc->encrypt); + + /* + * Check that we have enough room for the output. Our caller should + * already handle this; failure is really an internal error (i.e. bug). + */ + max_needed = NSS_CMSCipherContext_DecryptLength(cc, input_len, final); + PORT_Assert (max_output_len >= max_needed); + if (max_output_len < max_needed) { + /* PORT_SetError (XXX); */ + return SECFailure; + } + + /* + * hardware encryption does not like small decryption sizes here, so we + * allow both blocking and padding. + */ + bsize = cc->block_size; + padsize = cc->pad_size; + + /* + * When no blocking or padding work to do, we can simply call the + * cipher function and we are done. + */ + if (bsize == 0) { + return (* cc->doit) (cc->cx, output, output_len_p, max_output_len, + input, input_len); + } + + pcount = cc->pending_count; + pbuf = cc->pending_buf; + + output_len = 0; + + if (pcount) { + /* + * Try to fill in an entire block, starting with the bytes + * we already have saved away. + */ + while (input_len && pcount < bsize) { + pbuf[pcount++] = *input++; + input_len--; + } + /* + * If we have at most a whole block and this is not our last call, + * then we are done for now. (We do not try to decrypt a lone + * single block because we cannot interpret the padding bytes + * until we know we are handling the very last block of all input.) + */ + if (input_len == 0 && !final) { + cc->pending_count = pcount; + if (output_len_p) + *output_len_p = 0; + return SECSuccess; + } + /* + * Given the logic above, we expect to have a full block by now. + * If we do not, there is something wrong, either with our own + * logic or with (length of) the data given to us. + */ + PORT_Assert ((padsize == 0) || (pcount % padsize) == 0); + if ((padsize != 0) && (pcount % padsize) != 0) { + PORT_Assert (final); + PORT_SetError (SEC_ERROR_BAD_DATA); + return SECFailure; + } + /* + * Decrypt the block. + */ + rv = (*cc->doit)(cc->cx, output, &ofraglen, max_output_len, + pbuf, pcount); + if (rv != SECSuccess) + return rv; + + /* + * For now anyway, all of our ciphers have the same number of + * bytes of output as they do input. If this ever becomes untrue, + * then NSS_CMSCipherContext_DecryptLength needs to be made smarter! + */ + PORT_Assert(ofraglen == pcount); + + /* + * Account for the bytes now in output. + */ + max_output_len -= ofraglen; + output_len += ofraglen; + output += ofraglen; + } + + /* + * If this is our last call, we expect to have an exact number of + * blocks left to be decrypted; we will decrypt them all. + * + * If not our last call, we always save between 1 and bsize bytes + * until next time. (We must do this because we cannot be sure + * that none of the decrypted bytes are padding bytes until we + * have at least another whole block of data. You cannot tell by + * looking -- the data could be anything -- you can only tell by + * context, knowing you are looking at the last block.) We could + * decrypt a whole block now but it is easier if we just treat it + * the same way we treat partial block bytes. + */ + if (final) { + if (padsize) { + blocks = input_len / padsize; + ifraglen = blocks * padsize; + } else ifraglen = input_len; + PORT_Assert (ifraglen == input_len); + + if (ifraglen != input_len) { + PORT_SetError(SEC_ERROR_BAD_DATA); + return SECFailure; + } + } else { + blocks = (input_len - 1) / bsize; + ifraglen = blocks * bsize; + PORT_Assert (ifraglen < input_len); + + pcount = input_len - ifraglen; + PORT_Memcpy (pbuf, input + ifraglen, pcount); + cc->pending_count = pcount; + } + + if (ifraglen) { + rv = (* cc->doit)(cc->cx, output, &ofraglen, max_output_len, + input, ifraglen); + if (rv != SECSuccess) + return rv; + + /* + * For now anyway, all of our ciphers have the same number of + * bytes of output as they do input. If this ever becomes untrue, + * then sec_PKCS7DecryptLength needs to be made smarter! + */ + PORT_Assert (ifraglen == ofraglen); + if (ifraglen != ofraglen) { + PORT_SetError(SEC_ERROR_BAD_DATA); + return SECFailure; + } + + output_len += ofraglen; + } else { + ofraglen = 0; + } + + /* + * If we just did our very last block, "remove" the padding by + * adjusting the output length. + */ + if (final && (padsize != 0)) { + unsigned int padlen = *(output + ofraglen - 1); + PORT_Assert (padlen > 0 && padlen <= padsize); + if (padlen == 0 || padlen > padsize) { + PORT_SetError(SEC_ERROR_BAD_DATA); + return SECFailure; + } + output_len -= padlen; + } + + PORT_Assert (output_len_p != NULL || output_len == 0); + if (output_len_p != NULL) + *output_len_p = output_len; + + return SECSuccess; +} + +/* + * NSS_CMSCipherContext_Encrypt - do the encryption + * + * cc - the cipher context + * output - buffer for decrypted result bytes + * output_len_p - number of bytes in output + * max_output_len - upper bound on bytes to put into output + * input - pointer to input bytes + * input_len - number of input bytes + * final - true if this is the final chunk of data + * + * Encrypts a given length of input buffer (starting at "input" and + * containing "input_len" bytes), placing the encrypted bytes in + * "output" and storing the output length in "*output_len_p". + * "cc" is the return value from NSS_CMSCipher_StartEncrypt. + * When "final" is true, this is the last of the data to be encrypted. + * + * This is much more complicated than it sounds when the cipher is + * a block-type, meaning that the encryption function will only + * operate on whole blocks. But our caller is operating stream-wise, + * and can pass in any number of bytes. So we need to keep track + * of block boundaries. We save excess bytes between calls in "cc". + * We also need to add padding bytes at the end. PKCS #7 specifies + * that the padding used for a block cipher is a string of bytes, + * each of whose value is the same as the length of the padding, + * and that all data is padded. (Even data that starts out with + * an exact multiple of blocks gets added to it another block, + * all of which is padding.) + * + * XXX I would kind of like to combine this with the function above + * which does decryption, since they have a lot in common. But the + * tricky parts about padding and filling blocks would be much + * harder to read that way, so I left them separate. At least for + * now until it is clear that they are right. + */ +SECStatus +NSS_CMSCipherContext_Encrypt(NSSCMSCipherContext *cc, unsigned char *output, + unsigned int *output_len_p, unsigned int max_output_len, + const unsigned char *input, unsigned int input_len, + PRBool final) +{ + int blocks, bsize, padlen, pcount, padsize; + unsigned int max_needed, ifraglen, ofraglen, output_len; + unsigned char *pbuf; + SECStatus rv; + + PORT_Assert (cc->encrypt); + + /* + * Check that we have enough room for the output. Our caller should + * already handle this; failure is really an internal error (i.e. bug). + */ + max_needed = NSS_CMSCipherContext_EncryptLength (cc, input_len, final); + PORT_Assert (max_output_len >= max_needed); + if (max_output_len < max_needed) { + /* PORT_SetError (XXX); */ + return SECFailure; + } + + bsize = cc->block_size; + padsize = cc->pad_size; + + /* + * When no blocking and padding work to do, we can simply call the + * cipher function and we are done. + */ + if (bsize == 0) { + return (*cc->doit)(cc->cx, output, output_len_p, max_output_len, + input, input_len); + } + + pcount = cc->pending_count; + pbuf = cc->pending_buf; + + output_len = 0; + + if (pcount) { + /* + * Try to fill in an entire block, starting with the bytes + * we already have saved away. + */ + while (input_len && pcount < bsize) { + pbuf[pcount++] = *input++; + input_len--; + } + /* + * If we do not have a full block and we know we will be + * called again, then we are done for now. + */ + if (pcount < bsize && !final) { + cc->pending_count = pcount; + if (output_len_p != NULL) + *output_len_p = 0; + return SECSuccess; + } + /* + * If we have a whole block available, encrypt it. + */ + if ((padsize == 0) || (pcount % padsize) == 0) { + rv = (* cc->doit) (cc->cx, output, &ofraglen, max_output_len, + pbuf, pcount); + if (rv != SECSuccess) + return rv; + + /* + * For now anyway, all of our ciphers have the same number of + * bytes of output as they do input. If this ever becomes untrue, + * then sec_PKCS7EncryptLength needs to be made smarter! + */ + PORT_Assert (ofraglen == pcount); + + /* + * Account for the bytes now in output. + */ + max_output_len -= ofraglen; + output_len += ofraglen; + output += ofraglen; + + pcount = 0; + } + } + + if (input_len) { + PORT_Assert (pcount == 0); + + blocks = input_len / bsize; + ifraglen = blocks * bsize; + + if (ifraglen) { + rv = (* cc->doit) (cc->cx, output, &ofraglen, max_output_len, + input, ifraglen); + if (rv != SECSuccess) + return rv; + + /* + * For now anyway, all of our ciphers have the same number of + * bytes of output as they do input. If this ever becomes untrue, + * then sec_PKCS7EncryptLength needs to be made smarter! + */ + PORT_Assert (ifraglen == ofraglen); + + max_output_len -= ofraglen; + output_len += ofraglen; + output += ofraglen; + } + + pcount = input_len - ifraglen; + PORT_Assert (pcount < bsize); + if (pcount) + PORT_Memcpy (pbuf, input + ifraglen, pcount); + } + + if (final) { + padlen = padsize - (pcount % padsize); + PORT_Memset (pbuf + pcount, padlen, padlen); + rv = (* cc->doit) (cc->cx, output, &ofraglen, max_output_len, + pbuf, pcount+padlen); + if (rv != SECSuccess) + return rv; + + /* + * For now anyway, all of our ciphers have the same number of + * bytes of output as they do input. If this ever becomes untrue, + * then sec_PKCS7EncryptLength needs to be made smarter! + */ + PORT_Assert (ofraglen == (pcount+padlen)); + output_len += ofraglen; + } else { + cc->pending_count = pcount; + } + + PORT_Assert (output_len_p != NULL || output_len == 0); + if (output_len_p != NULL) + *output_len_p = output_len; + + return SECSuccess; +} diff --git a/security/nss/lib/smime/cmsdecode.c b/security/nss/lib/smime/cmsdecode.c new file mode 100644 index 000000000..c1b78cc3e --- /dev/null +++ b/security/nss/lib/smime/cmsdecode.c @@ -0,0 +1,688 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * CMS decoding. + * + * $Id$ + */ + +#include "cmslocal.h" + +#include "cert.h" +#include "key.h" +#include "secasn1.h" +#include "secitem.h" +#include "secoid.h" +#include "prtime.h" +#include "secerr.h" + +struct NSSCMSDecoderContextStr { + SEC_ASN1DecoderContext * dcx; /* ASN.1 decoder context */ + NSSCMSMessage * cmsg; /* backpointer to the root message */ + SECOidTag type; /* type of message */ + NSSCMSContent content; /* pointer to message */ + NSSCMSDecoderContext * childp7dcx; /* inner CMS decoder context */ + PRBool saw_contents; + int error; + NSSCMSContentCallback cb; + void * cb_arg; +}; + +static void nss_cms_decoder_update_filter (void *arg, const char *data, unsigned long len, + int depth, SEC_ASN1EncodingPart data_kind); +static SECStatus nss_cms_before_data(NSSCMSDecoderContext *p7dcx); +static SECStatus nss_cms_after_data(NSSCMSDecoderContext *p7dcx); +static SECStatus nss_cms_after_end(NSSCMSDecoderContext *p7dcx); +static void nss_cms_decoder_work_data(NSSCMSDecoderContext *p7dcx, + const unsigned char *data, unsigned long len, PRBool final); + +extern const SEC_ASN1Template NSSCMSMessageTemplate[]; + +/* + * nss_cms_decoder_notify - + * this is the driver of the decoding process. It gets called by the ASN.1 + * decoder before and after an object is decoded. + * at various points in the decoding process, we intercept to set up and do + * further processing. + */ +static void +nss_cms_decoder_notify(void *arg, PRBool before, void *dest, int depth) +{ + NSSCMSDecoderContext *p7dcx; + NSSCMSContentInfo *rootcinfo, *cinfo; + PRBool after = !before; + + p7dcx = (NSSCMSDecoderContext *)arg; + rootcinfo = &(p7dcx->cmsg->contentInfo); + + /* XXX error handling: need to set p7dcx->error */ + +#ifdef CMSDEBUG + fprintf(stderr, "%6.6s, dest = 0x%08x, depth = %d\n", before ? "before" : "after", dest, depth); +#endif + + /* so what are we working on right now? */ + switch (p7dcx->type) { + case SEC_OID_UNKNOWN: + /* + * right now, we are still decoding the OUTER (root) cinfo + * As soon as we know the inner content type, set up the info, + * but NO inner decoder or filter. The root decoder handles the first + * level children by itself - only for encapsulated contents (which + * are encoded as DER inside of an OCTET STRING) we need to set up a + * child decoder... + */ + if (after && dest == &(rootcinfo->contentType)) { + p7dcx->type = NSS_CMSContentInfo_GetContentTypeTag(rootcinfo); + p7dcx->content = rootcinfo->content; /* is this ready already ? need to alloc? */ + /* XXX yes we need to alloc -- continue here */ + } + break; + case SEC_OID_PKCS7_DATA: + /* this can only happen if the outermost cinfo has DATA in it */ + /* otherwise, we handle this type implicitely in the inner decoders */ + + if (before && dest == &(rootcinfo->content)) { + /* fake it to cause the filter to put the data in the right place... */ + /* we want the ASN.1 decoder to deliver the decoded bytes to us from now on */ + SEC_ASN1DecoderSetFilterProc(p7dcx->dcx, + nss_cms_decoder_update_filter, + p7dcx, + (PRBool)(p7dcx->cb != NULL)); + break; + } + + if (after && dest == &(rootcinfo->content.data)) { + /* remove the filter */ + SEC_ASN1DecoderClearFilterProc(p7dcx->dcx); + } + break; + + case SEC_OID_PKCS7_SIGNED_DATA: + case SEC_OID_PKCS7_ENVELOPED_DATA: + case SEC_OID_PKCS7_DIGESTED_DATA: + case SEC_OID_PKCS7_ENCRYPTED_DATA: + + if (before && dest == &(rootcinfo->content)) + break; /* we're not there yet */ + + if (p7dcx->content.pointer == NULL) + p7dcx->content = rootcinfo->content; + + /* get this data type's inner contentInfo */ + cinfo = NSS_CMSContent_GetContentInfo(p7dcx->content.pointer, p7dcx->type); + + if (before && dest == &(cinfo->contentType)) { + /* at this point, set up the &%$&$ back pointer */ + /* we cannot do it later, because the content itself is optional! */ + /* please give me C++ */ + switch (p7dcx->type) { + case SEC_OID_PKCS7_SIGNED_DATA: + p7dcx->content.signedData->cmsg = p7dcx->cmsg; + break; + case SEC_OID_PKCS7_DIGESTED_DATA: + p7dcx->content.digestedData->cmsg = p7dcx->cmsg; + break; + case SEC_OID_PKCS7_ENVELOPED_DATA: + p7dcx->content.envelopedData->cmsg = p7dcx->cmsg; + break; + case SEC_OID_PKCS7_ENCRYPTED_DATA: + p7dcx->content.encryptedData->cmsg = p7dcx->cmsg; + break; + } + } + + if (before && dest == &(cinfo->rawContent)) { + /* we want the ASN.1 decoder to deliver the decoded bytes to us from now on */ + SEC_ASN1DecoderSetFilterProc(p7dcx->dcx, nss_cms_decoder_update_filter, + p7dcx, (PRBool)(p7dcx->cb != NULL)); + + + /* we're right in front of the data */ + if (nss_cms_before_data(p7dcx) != SECSuccess) { + SEC_ASN1DecoderClearFilterProc(p7dcx->dcx); /* stop all processing */ + p7dcx->error = PORT_GetError(); + } + } + if (after && dest == &(cinfo->rawContent)) { + /* we're right after of the data */ + if (nss_cms_after_data(p7dcx) != SECSuccess) + p7dcx->error = PORT_GetError(); + + /* we don't need to see the contents anymore */ + SEC_ASN1DecoderClearFilterProc(p7dcx->dcx); + } + break; + +#if 0 /* NIH */ + case SEC_OID_PKCS7_AUTHENTICATED_DATA: +#endif + default: + /* unsupported or unknown message type - fail (more or less) gracefully */ + p7dcx->error = SEC_ERROR_UNSUPPORTED_MESSAGE_TYPE; + break; + } +} + +/* + * nss_cms_before_data - set up the current encoder to receive data + */ +static SECStatus +nss_cms_before_data(NSSCMSDecoderContext *p7dcx) +{ + SECStatus rv; + SECOidTag childtype; + PLArenaPool *poolp; + NSSCMSDecoderContext *childp7dcx; + NSSCMSContentInfo *cinfo; + const SEC_ASN1Template *template; + void *mark = NULL; + size_t size; + + poolp = p7dcx->cmsg->poolp; + + /* call _Decode_BeforeData handlers */ + switch (p7dcx->type) { + case SEC_OID_PKCS7_SIGNED_DATA: + /* we're decoding a signedData, so set up the digests */ + rv = NSS_CMSSignedData_Decode_BeforeData(p7dcx->content.signedData); + if (rv != SECSuccess) + return SECFailure; + break; + case SEC_OID_PKCS7_DIGESTED_DATA: + /* we're encoding a digestedData, so set up the digest */ + rv = NSS_CMSDigestedData_Decode_BeforeData(p7dcx->content.digestedData); + if (rv != SECSuccess) + return SECFailure; + break; + case SEC_OID_PKCS7_ENVELOPED_DATA: + rv = NSS_CMSEnvelopedData_Decode_BeforeData(p7dcx->content.envelopedData); + if (rv != SECSuccess) + return SECFailure; + break; + case SEC_OID_PKCS7_ENCRYPTED_DATA: + rv = NSS_CMSEncryptedData_Decode_BeforeData(p7dcx->content.encryptedData); + if (rv != SECSuccess) + return SECFailure; + break; + default: + return SECFailure; + } + + /* ok, now we have a pointer to cinfo */ + /* find out what kind of data is encapsulated */ + + cinfo = NSS_CMSContent_GetContentInfo(p7dcx->content.pointer, p7dcx->type); + childtype = NSS_CMSContentInfo_GetContentTypeTag(cinfo); + + if (childtype == SEC_OID_PKCS7_DATA) { + cinfo->content.data = SECITEM_AllocItem(poolp, NULL, 0); + if (cinfo->content.data == NULL) + /* set memory error */ + return SECFailure; + + p7dcx->childp7dcx = NULL; + return SECSuccess; + } + + /* set up inner decoder */ + + if ((template = NSS_CMSUtil_GetTemplateByTypeTag(childtype)) == NULL) + return SECFailure; + + childp7dcx = (NSSCMSDecoderContext *)PORT_ZAlloc(sizeof(NSSCMSDecoderContext)); + if (childp7dcx == NULL) + return SECFailure; + + mark = PORT_ArenaMark(poolp); + + /* allocate space for the stuff we're creating */ + size = NSS_CMSUtil_GetSizeByTypeTag(childtype); + childp7dcx->content.pointer = (void *)PORT_ArenaZAlloc(poolp, size); + if (childp7dcx->content.pointer == NULL) + goto loser; + + /* start the child decoder */ + childp7dcx->dcx = SEC_ASN1DecoderStart(poolp, childp7dcx->content.pointer, template); + if (childp7dcx->dcx == NULL) + goto loser; + + /* the new decoder needs to notify, too */ + SEC_ASN1DecoderSetNotifyProc(childp7dcx->dcx, nss_cms_decoder_notify, childp7dcx); + + /* tell the parent decoder that it needs to feed us the content data */ + p7dcx->childp7dcx = childp7dcx; + + childp7dcx->type = childtype; /* our type */ + + childp7dcx->cmsg = p7dcx->cmsg; /* backpointer to root message */ + + /* should the child decoder encounter real data, it needs to give it to the caller */ + childp7dcx->cb = p7dcx->cb; + childp7dcx->cb_arg = p7dcx->cb_arg; + + /* now set up the parent to hand decoded data to the next level decoder */ + p7dcx->cb = (NSSCMSContentCallback)NSS_CMSDecoder_Update; + p7dcx->cb_arg = childp7dcx; + + PORT_ArenaUnmark(poolp, mark); + + return SECSuccess; + +loser: + if (mark) + PORT_ArenaRelease(poolp, mark); + if (childp7dcx) + PORT_Free(childp7dcx); + p7dcx->childp7dcx = NULL; + return SECFailure; +} + +static SECStatus +nss_cms_after_data(NSSCMSDecoderContext *p7dcx) +{ + PLArenaPool *poolp; + NSSCMSDecoderContext *childp7dcx; + SECStatus rv; + + poolp = p7dcx->cmsg->poolp; + + /* Handle last block. This is necessary to flush out the last bytes + * of a possibly incomplete block */ + nss_cms_decoder_work_data(p7dcx, NULL, 0, PR_TRUE); + + /* finish any "inner" decoders - there's no more data coming... */ + if (p7dcx->childp7dcx != NULL) { + childp7dcx = p7dcx->childp7dcx; + if (childp7dcx->dcx != NULL) { + if (SEC_ASN1DecoderFinish(childp7dcx->dcx) != SECSuccess) { + /* do what? free content? */ + rv = SECFailure; + } else { + rv = nss_cms_after_end(childp7dcx); + } + if (rv != SECSuccess) + goto done; + } + PORT_Free(p7dcx->childp7dcx); + p7dcx->childp7dcx = NULL; + } + + switch (p7dcx->type) { + case SEC_OID_PKCS7_SIGNED_DATA: + /* this will finish the digests and verify */ + rv = NSS_CMSSignedData_Decode_AfterData(p7dcx->content.signedData); + break; + case SEC_OID_PKCS7_ENVELOPED_DATA: + rv = NSS_CMSEnvelopedData_Decode_AfterData(p7dcx->content.envelopedData); + break; + case SEC_OID_PKCS7_DIGESTED_DATA: + rv = NSS_CMSDigestedData_Decode_AfterData(p7dcx->content.digestedData); + break; + case SEC_OID_PKCS7_ENCRYPTED_DATA: + rv = NSS_CMSEncryptedData_Decode_AfterData(p7dcx->content.encryptedData); + break; + case SEC_OID_PKCS7_DATA: + /* do nothing */ + break; + default: + rv = SECFailure; + break; + } +done: + return rv; +} + +static SECStatus +nss_cms_after_end(NSSCMSDecoderContext *p7dcx) +{ + SECStatus rv; + PLArenaPool *poolp; + + poolp = p7dcx->cmsg->poolp; + + switch (p7dcx->type) { + case SEC_OID_PKCS7_SIGNED_DATA: + rv = NSS_CMSSignedData_Decode_AfterEnd(p7dcx->content.signedData); + break; + case SEC_OID_PKCS7_ENVELOPED_DATA: + rv = NSS_CMSEnvelopedData_Decode_AfterEnd(p7dcx->content.envelopedData); + break; + case SEC_OID_PKCS7_DIGESTED_DATA: + rv = NSS_CMSDigestedData_Decode_AfterEnd(p7dcx->content.digestedData); + break; + case SEC_OID_PKCS7_ENCRYPTED_DATA: + rv = NSS_CMSEncryptedData_Decode_AfterEnd(p7dcx->content.encryptedData); + break; + case SEC_OID_PKCS7_DATA: + rv = SECSuccess; + break; + default: + rv = SECFailure; /* we should not have got that far... */ + break; + } + return rv; +} + +/* + * nss_cms_decoder_work_data - handle decoded data bytes. + * + * This function either decrypts the data if needed, and/or calculates digests + * on it, then either stores it or passes it on to the next level decoder. + */ +static void +nss_cms_decoder_work_data(NSSCMSDecoderContext *p7dcx, + const unsigned char *data, unsigned long len, + PRBool final) +{ + NSSCMSContentInfo *cinfo; + unsigned char *buf = NULL; + unsigned char *dest; + unsigned int offset; + SECStatus rv; + SECItem *storage; + + /* + * We should really have data to process, or we should be trying + * to finish/flush the last block. (This is an overly paranoid + * check since all callers are in this file and simple inspection + * proves they do it right. But it could find a bug in future + * modifications/development, that is why it is here.) + */ + PORT_Assert ((data != NULL && len) || final); + + cinfo = NSS_CMSContent_GetContentInfo(p7dcx->content.pointer, p7dcx->type); + + if (cinfo->ciphcx != NULL) { + /* + * we are decrypting. + * + * XXX If we get an error, we do not want to do the digest or callback, + * but we want to keep decoding. Or maybe we want to stop decoding + * altogether if there is a callback, because obviously we are not + * sending the data back and they want to know that. + */ + + unsigned int outlen = 0; /* length of decrypted data */ + unsigned int buflen; /* length available for decrypted data */ + + /* find out about the length of decrypted data */ + buflen = NSS_CMSCipherContext_DecryptLength(cinfo->ciphcx, len, final); + + /* + * it might happen that we did not provide enough data for a full + * block (decryption unit), and that there is no output available + */ + + /* no output available, AND no input? */ + if (buflen == 0 && len == 0) + goto loser; /* bail out */ + + /* + * have inner decoder: pass the data on (means inner content type is NOT data) + * no inner decoder: we have DATA in here: either call callback or store + */ + if (buflen != 0) { + /* there will be some output - need to make room for it */ + /* allocate buffer from the heap */ + buf = (unsigned char *)PORT_Alloc(buflen); + if (buf == NULL) { + p7dcx->error = SEC_ERROR_NO_MEMORY; + goto loser; + } + } + + /* + * decrypt incoming data + * buf can still be NULL here (and buflen == 0) here if we don't expect + * any output (see above), but we still need to call NSS_CMSCipherContext_Decrypt to + * keep track of incoming data + */ + rv = NSS_CMSCipherContext_Decrypt(cinfo->ciphcx, buf, &outlen, buflen, + data, len, final); + if (rv != SECSuccess) { + p7dcx->error = PORT_GetError(); + goto loser; + } + + PORT_Assert (final || outlen == buflen); + + /* swap decrypted data in */ + data = buf; + len = outlen; + } + + if (len == 0) + return; /* nothing more to do */ + + /* + * Update the running digests with plaintext bytes (if we need to). + */ + if (cinfo->digcx) + NSS_CMSDigestContext_Update(cinfo->digcx, data, len); + + /* at this point, we have the plain decoded & decrypted data */ + /* which is either more encoded DER which we need to hand to the child decoder */ + /* or data we need to hand back to our caller */ + + /* pass the content back to our caller or */ + /* feed our freshly decrypted and decoded data into child decoder */ + if (p7dcx->cb != NULL) { + (*p7dcx->cb)(p7dcx->cb_arg, (const char *)data, len); + } +#if 1 + else +#endif + if (NSS_CMSContentInfo_GetContentTypeTag(cinfo) == SEC_OID_PKCS7_DATA) { + /* store it in "inner" data item as well */ + /* find the DATA item in the encapsulated cinfo and store it there */ + storage = cinfo->content.data; + + offset = storage->len; + if (storage->len == 0) { + dest = (unsigned char *)PORT_ArenaAlloc(p7dcx->cmsg->poolp, len); + } else { + dest = (unsigned char *)PORT_ArenaGrow(p7dcx->cmsg->poolp, + storage->data, + storage->len, + storage->len + len); + } + if (dest == NULL) { + p7dcx->error = SEC_ERROR_NO_MEMORY; + goto loser; + } + + storage->data = dest; + storage->len += len; + + /* copy it in */ + PORT_Memcpy(storage->data + offset, data, len); + } + +loser: + if (buf) + PORT_Free (buf); +} + +/* + * nss_cms_decoder_update_filter - process ASN.1 data + * + * once we have set up a filter in nss_cms_decoder_notify(), + * all data processed by the ASN.1 decoder is also passed through here. + * we pass the content bytes (as opposed to length and tag bytes) on to + * nss_cms_decoder_work_data(). + */ +static void +nss_cms_decoder_update_filter (void *arg, const char *data, unsigned long len, + int depth, SEC_ASN1EncodingPart data_kind) +{ + NSSCMSDecoderContext *p7dcx; + + PORT_Assert (len); /* paranoia */ + if (len == 0) + return; + + p7dcx = (NSSCMSDecoderContext*)arg; + + p7dcx->saw_contents = PR_TRUE; + + /* pass on the content bytes only */ + if (data_kind == SEC_ASN1_Contents) + nss_cms_decoder_work_data(p7dcx, (const unsigned char *) data, len, PR_FALSE); +} + +/* + * NSS_CMSDecoder_Start - set up decoding of a DER-encoded CMS message + * + * "poolp" - pointer to arena for message, or NULL if new pool should be created + * "cb", "cb_arg" - callback function and argument for delivery of inner content + * "pwfn", pwfn_arg" - callback function for getting token password + * "decrypt_key_cb", "decrypt_key_cb_arg" - callback function for getting bulk key for encryptedData + */ +NSSCMSDecoderContext * +NSS_CMSDecoder_Start(PRArenaPool *poolp, + NSSCMSContentCallback cb, void *cb_arg, + PK11PasswordFunc pwfn, void *pwfn_arg, + NSSCMSGetDecryptKeyCallback decrypt_key_cb, void *decrypt_key_cb_arg) +{ + NSSCMSDecoderContext *p7dcx; + NSSCMSMessage *cmsg; + + cmsg = NSS_CMSMessage_Create(poolp); + if (cmsg == NULL) + return NULL; + + NSS_CMSMessage_SetEncodingParams(cmsg, pwfn, pwfn_arg, decrypt_key_cb, decrypt_key_cb_arg, + NULL, NULL); + + p7dcx = (NSSCMSDecoderContext*)PORT_ZAlloc(sizeof(NSSCMSDecoderContext)); + if (p7dcx == NULL) { + NSS_CMSMessage_Destroy(cmsg); + return NULL; + } + + p7dcx->dcx = SEC_ASN1DecoderStart(cmsg->poolp, cmsg, NSSCMSMessageTemplate); + if (p7dcx->dcx == NULL) { + PORT_Free (p7dcx); + NSS_CMSMessage_Destroy(cmsg); + return NULL; + } + + SEC_ASN1DecoderSetNotifyProc (p7dcx->dcx, nss_cms_decoder_notify, p7dcx); + + p7dcx->cmsg = cmsg; + p7dcx->type = SEC_OID_UNKNOWN; + + p7dcx->cb = cb; + p7dcx->cb_arg = cb_arg; + + return p7dcx; +} + +/* + * NSS_CMSDecoder_Update - feed DER-encoded data to decoder + */ +SECStatus +NSS_CMSDecoder_Update(NSSCMSDecoderContext *p7dcx, const char *buf, unsigned long len) +{ + if (p7dcx->dcx != NULL && p7dcx->error == 0) { /* if error is set already, don't bother */ + if (SEC_ASN1DecoderUpdate (p7dcx->dcx, buf, len) != SECSuccess) { + p7dcx->error = PORT_GetError(); + PORT_Assert (p7dcx->error); + if (p7dcx->error == 0) + p7dcx->error = -1; + } + } + + if (p7dcx->error == 0) + return SECSuccess; + + /* there has been a problem, let's finish the decoder */ + if (p7dcx->dcx != NULL) { + (void) SEC_ASN1DecoderFinish (p7dcx->dcx); + p7dcx->dcx = NULL; + } + PORT_SetError (p7dcx->error); + + return SECFailure; +} + +/* + * NSS_CMSDecoder_Cancel - stop decoding in case of error + */ +void +NSS_CMSDecoder_Cancel(NSSCMSDecoderContext *p7dcx) +{ + /* XXXX what about inner decoders? running digests? decryption? */ + /* XXXX there's a leak here! */ + NSS_CMSMessage_Destroy(p7dcx->cmsg); + (void)SEC_ASN1DecoderFinish(p7dcx->dcx); + PORT_Free(p7dcx); +} + +/* + * NSS_CMSDecoder_Finish - mark the end of inner content and finish decoding + */ +NSSCMSMessage * +NSS_CMSDecoder_Finish(NSSCMSDecoderContext *p7dcx) +{ + NSSCMSMessage *cmsg; + + cmsg = p7dcx->cmsg; + + if (p7dcx->dcx == NULL || SEC_ASN1DecoderFinish(p7dcx->dcx) != SECSuccess || + nss_cms_after_end(p7dcx) != SECSuccess) + { + NSS_CMSMessage_Destroy(cmsg); /* needs to get rid of pool if it's ours */ + cmsg = NULL; + } + + PORT_Free(p7dcx); + return cmsg; +} + +NSSCMSMessage * +NSS_CMSMessage_CreateFromDER(SECItem *DERmessage, + NSSCMSContentCallback cb, void *cb_arg, + PK11PasswordFunc pwfn, void *pwfn_arg, + NSSCMSGetDecryptKeyCallback decrypt_key_cb, void *decrypt_key_cb_arg) +{ + NSSCMSDecoderContext *p7dcx; + + /* first arg(poolp) == NULL => create our own pool */ + p7dcx = NSS_CMSDecoder_Start(NULL, cb, cb_arg, pwfn, pwfn_arg, decrypt_key_cb, decrypt_key_cb_arg); + (void) NSS_CMSDecoder_Update(p7dcx, (char *)DERmessage->data, DERmessage->len); + return NSS_CMSDecoder_Finish(p7dcx); +} + diff --git a/security/nss/lib/smime/cmsdigdata.c b/security/nss/lib/smime/cmsdigdata.c new file mode 100644 index 000000000..04a670b68 --- /dev/null +++ b/security/nss/lib/smime/cmsdigdata.c @@ -0,0 +1,223 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * CMS digestedData methods. + * + * $Id$ + */ + +#include "cmslocal.h" + +#include "secitem.h" +#include "secasn1.h" +#include "secoid.h" +#include "secerr.h" + +/* + * NSS_CMSDigestedData_Create - create a digestedData object (presumably for encoding) + * + * version will be set by NSS_CMSDigestedData_Encode_BeforeStart + * digestAlg is passed as parameter + * contentInfo must be filled by the user + * digest will be calculated while encoding + */ +NSSCMSDigestedData * +NSS_CMSDigestedData_Create(NSSCMSMessage *cmsg, SECAlgorithmID *digestalg) +{ + void *mark; + NSSCMSDigestedData *digd; + PLArenaPool *poolp; + + poolp = cmsg->poolp; + + mark = PORT_ArenaMark(poolp); + + digd = (NSSCMSDigestedData *)PORT_ArenaZAlloc(poolp, sizeof(NSSCMSDigestedData)); + if (digd == NULL) + goto loser; + + digd->cmsg = cmsg; + + if (SECOID_CopyAlgorithmID (poolp, &(digd->digestAlg), digestalg) != SECSuccess) + goto loser; + + PORT_ArenaUnmark(poolp, mark); + return digd; + +loser: + PORT_ArenaRelease(poolp, mark); + return NULL; +} + +/* + * NSS_CMSDigestedData_Destroy - destroy a digestedData object + */ +void +NSS_CMSDigestedData_Destroy(NSSCMSDigestedData *digd) +{ + /* everything's in a pool, so don't worry about the storage */ + return; +} + +/* + * NSS_CMSDigestedData_GetContentInfo - return pointer to digestedData object's contentInfo + */ +NSSCMSContentInfo * +NSS_CMSDigestedData_GetContentInfo(NSSCMSDigestedData *digd) +{ + return &(digd->contentInfo); +} + +/* + * NSS_CMSDigestedData_Encode_BeforeStart - do all the necessary things to a DigestedData + * before encoding begins. + * + * In particular: + * - set the right version number. The contentInfo's content type must be set up already. + */ +SECStatus +NSS_CMSDigestedData_Encode_BeforeStart(NSSCMSDigestedData *digd) +{ + unsigned long version; + SECItem *dummy; + + version = NSS_CMS_DIGESTED_DATA_VERSION_DATA; + if (NSS_CMSContentInfo_GetContentTypeTag(&(digd->contentInfo)) != SEC_OID_PKCS7_DATA) + version = NSS_CMS_DIGESTED_DATA_VERSION_ENCAP; + + dummy = SEC_ASN1EncodeInteger(digd->cmsg->poolp, &(digd->version), version); + return (dummy == NULL) ? SECFailure : SECSuccess; +} + +/* + * NSS_CMSDigestedData_Encode_BeforeData - do all the necessary things to a DigestedData + * before the encapsulated data is passed through the encoder. + * + * In detail: + * - set up the digests if necessary + */ +SECStatus +NSS_CMSDigestedData_Encode_BeforeData(NSSCMSDigestedData *digd) +{ + /* set up the digests */ + if (digd->digestAlg.algorithm.len != 0 && digd->digest.len == 0) { + /* if digest is already there, do nothing */ + digd->contentInfo.digcx = NSS_CMSDigestContext_StartSingle(&(digd->digestAlg)); + if (digd->contentInfo.digcx == NULL) + return SECFailure; + } + return SECSuccess; +} + +/* + * NSS_CMSDigestedData_Encode_AfterData - do all the necessary things to a DigestedData + * after all the encapsulated data was passed through the encoder. + * + * In detail: + * - finish the digests + */ +SECStatus +NSS_CMSDigestedData_Encode_AfterData(NSSCMSDigestedData *digd) +{ + /* did we have digest calculation going on? */ + if (digd->contentInfo.digcx) { + if (NSS_CMSDigestContext_FinishSingle(digd->contentInfo.digcx, + digd->cmsg->poolp, &(digd->digest)) != SECSuccess) + return SECFailure; /* error has been set by NSS_CMSDigestContext_FinishSingle */ + digd->contentInfo.digcx = NULL; + } + + return SECSuccess; +} + +/* + * NSS_CMSDigestedData_Decode_BeforeData - do all the necessary things to a DigestedData + * before the encapsulated data is passed through the encoder. + * + * In detail: + * - set up the digests if necessary + */ +SECStatus +NSS_CMSDigestedData_Decode_BeforeData(NSSCMSDigestedData *digd) +{ + /* is there a digest algorithm yet? */ + if (digd->digestAlg.algorithm.len == 0) + return SECFailure; + + digd->contentInfo.digcx = NSS_CMSDigestContext_StartSingle(&(digd->digestAlg)); + if (digd->contentInfo.digcx == NULL) + return SECFailure; + + return SECSuccess; +} + +/* + * NSS_CMSDigestedData_Decode_AfterData - do all the necessary things to a DigestedData + * after all the encapsulated data was passed through the encoder. + * + * In detail: + * - finish the digests + */ +SECStatus +NSS_CMSDigestedData_Decode_AfterData(NSSCMSDigestedData *digd) +{ + /* did we have digest calculation going on? */ + if (digd->contentInfo.digcx) { + if (NSS_CMSDigestContext_FinishSingle(digd->contentInfo.digcx, + digd->cmsg->poolp, &(digd->cdigest)) != SECSuccess) + return SECFailure; /* error has been set by NSS_CMSDigestContext_FinishSingle */ + digd->contentInfo.digcx = NULL; + } + + return SECSuccess; +} + +/* + * NSS_CMSDigestedData_Decode_AfterEnd - finalize a digestedData. + * + * In detail: + * - check the digests for equality + */ +SECStatus +NSS_CMSDigestedData_Decode_AfterEnd(NSSCMSDigestedData *digd) +{ + /* did we have digest calculation going on? */ + if (digd->cdigest.len != 0) { + /* XXX comparision btw digest & cdigest */ + /* XXX set status */ + /* TODO!!!! */ + } + + return SECSuccess; +} diff --git a/security/nss/lib/smime/cmsdigest.c b/security/nss/lib/smime/cmsdigest.c new file mode 100644 index 000000000..ce3f59228 --- /dev/null +++ b/security/nss/lib/smime/cmsdigest.c @@ -0,0 +1,259 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * CMS digesting. + * + * $Id$ + */ + +#include "cmslocal.h" + +#include "cert.h" +#include "key.h" +#include "secitem.h" +#include "secoid.h" +#include "pk11func.h" +#include "prtime.h" +#include "secerr.h" + + +struct NSSCMSDigestContextStr { + PRBool saw_contents; + int digcnt; + void ** digcxs; + SECHashObject ** digobjs; +}; + +/* + * NSS_CMSDigestContext_StartMultiple - start digest calculation using all the + * digest algorithms in "digestalgs" in parallel. + */ +NSSCMSDigestContext * +NSS_CMSDigestContext_StartMultiple(SECAlgorithmID **digestalgs) +{ + NSSCMSDigestContext *cmsdigcx; + SECHashObject *digobj; + void *digcx; + int digcnt; + int i; + + digcnt = (digestalgs == NULL) ? 0 : NSS_CMSArray_Count((void **)digestalgs); + + cmsdigcx = (NSSCMSDigestContext *)PORT_Alloc(sizeof(NSSCMSDigestContext)); + if (cmsdigcx == NULL) + return NULL; + + if (digcnt > 0) { + cmsdigcx->digcxs = (void **)PORT_Alloc(digcnt * sizeof (void *)); + cmsdigcx->digobjs = (SECHashObject **)PORT_Alloc(digcnt * sizeof(SECHashObject *)); + if (cmsdigcx->digcxs == NULL || cmsdigcx->digobjs == NULL) + goto loser; + } + + cmsdigcx->digcnt = 0; + + /* + * Create a digest object context for each algorithm. + */ + for (i = 0; i < digcnt; i++) { + digobj = NSS_CMSUtil_GetHashObjByAlgID(digestalgs[i]); + /* + * Skip any algorithm we do not even recognize; obviously, + * this could be a problem, but if it is critical then the + * result will just be that the signature does not verify. + * We do not necessarily want to error out here, because + * the particular algorithm may not actually be important, + * but we cannot know that until later. + */ + if (digobj == NULL) + continue; + + digcx = (*digobj->create)(); + if (digcx != NULL) { + (*digobj->begin) (digcx); + cmsdigcx->digobjs[cmsdigcx->digcnt] = digobj; + cmsdigcx->digcxs[cmsdigcx->digcnt] = digcx; + cmsdigcx->digcnt++; + } + } + + cmsdigcx->saw_contents = PR_FALSE; + + return cmsdigcx; + +loser: + if (cmsdigcx) { + if (cmsdigcx->digobjs) + PORT_Free(cmsdigcx->digobjs); + if (cmsdigcx->digcxs) + PORT_Free(cmsdigcx->digcxs); + } + return NULL; +} + +/* + * NSS_CMSDigestContext_StartSingle - same as NSS_CMSDigestContext_StartMultiple, but + * only one algorithm. + */ +NSSCMSDigestContext * +NSS_CMSDigestContext_StartSingle(SECAlgorithmID *digestalg) +{ + SECAlgorithmID *digestalgs[] = { NULL, NULL }; /* fake array */ + + digestalgs[0] = digestalg; + return NSS_CMSDigestContext_StartMultiple(digestalgs); +} + +/* + * NSS_CMSDigestContext_Update - feed more data into the digest machine + */ +void +NSS_CMSDigestContext_Update(NSSCMSDigestContext *cmsdigcx, const unsigned char *data, int len) +{ + int i; + + cmsdigcx->saw_contents = PR_TRUE; + + for (i = 0; i < cmsdigcx->digcnt; i++) + (*cmsdigcx->digobjs[i]->update)(cmsdigcx->digcxs[i], data, len); +} + +/* + * NSS_CMSDigestContext_Cancel - cancel digesting operation + */ +void +NSS_CMSDigestContext_Cancel(NSSCMSDigestContext *cmsdigcx) +{ + int i; + + for (i = 0; i < cmsdigcx->digcnt; i++) + (*cmsdigcx->digobjs[i]->destroy)(cmsdigcx->digcxs[i], PR_TRUE); +} + +/* + * NSS_CMSDigestContext_FinishMultiple - finish the digests and put them + * into an array of SECItems (allocated on poolp) + */ +SECStatus +NSS_CMSDigestContext_FinishMultiple(NSSCMSDigestContext *cmsdigcx, PLArenaPool *poolp, + SECItem ***digestsp) +{ + SECHashObject *digobj; + void *digcx; + SECItem **digests, *digest; + int i; + void *mark; + SECStatus rv = SECFailure; + + /* no contents? do not update digests */ + if (digestsp == NULL || !cmsdigcx->saw_contents) { + for (i = 0; i < cmsdigcx->digcnt; i++) + (*cmsdigcx->digobjs[i]->destroy)(cmsdigcx->digcxs[i], PR_TRUE); + rv = SECSuccess; + goto cleanup; + } + + mark = PORT_ArenaMark (poolp); + + /* allocate digest array & SECItems on arena */ + digests = (SECItem **)PORT_ArenaAlloc(poolp, (cmsdigcx->digcnt+1) * sizeof(SECItem *)); + digest = (SECItem *)PORT_ArenaZAlloc(poolp, cmsdigcx->digcnt * sizeof(SECItem)); + if (digests == NULL || digest == NULL) { + goto loser; + } + + for (i = 0; i < cmsdigcx->digcnt; i++, digest++) { + digcx = cmsdigcx->digcxs[i]; + digobj = cmsdigcx->digobjs[i]; + + digest->data = (unsigned char*)PORT_ArenaAlloc(poolp, digobj->length); + if (digest->data == NULL) + goto loser; + digest->len = digobj->length; + (* digobj->end)(digcx, digest->data, &(digest->len), digest->len); + digests[i] = digest; + (* digobj->destroy)(digcx, PR_TRUE); + } + digests[i] = NULL; + *digestsp = digests; + + rv = SECSuccess; + +loser: + if (rv == SECSuccess) + PORT_ArenaUnmark(poolp, mark); + else + PORT_ArenaRelease(poolp, mark); + +cleanup: + if (cmsdigcx->digcnt > 0) { + PORT_Free(cmsdigcx->digcxs); + PORT_Free(cmsdigcx->digobjs); + } + PORT_Free(cmsdigcx); + + return rv; +} + +/* + * NSS_CMSDigestContext_FinishSingle - same as NSS_CMSDigestContext_FinishMultiple, + * but for one digest. + */ +SECStatus +NSS_CMSDigestContext_FinishSingle(NSSCMSDigestContext *cmsdigcx, PLArenaPool *poolp, + SECItem *digest) +{ + SECStatus rv = SECFailure; + SECItem **dp; + PLArenaPool *arena = NULL; + + if ((arena = PORT_NewArena(1024)) == NULL) + goto loser; + + /* get the digests into arena, then copy the first digest into poolp */ + if (NSS_CMSDigestContext_FinishMultiple(cmsdigcx, arena, &dp) != SECSuccess) + goto loser; + + /* now copy it into poolp */ + if (SECITEM_CopyItem(poolp, digest, dp[0]) != SECSuccess) + goto loser; + + rv = SECSuccess; + +loser: + if (arena) + PORT_FreeArena(arena, PR_FALSE); + + return rv; +} diff --git a/security/nss/lib/smime/cmsencdata.c b/security/nss/lib/smime/cmsencdata.c new file mode 100644 index 000000000..0dc23a317 --- /dev/null +++ b/security/nss/lib/smime/cmsencdata.c @@ -0,0 +1,279 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * CMS encryptedData methods. + * + * $Id$ + */ + +#include "cmslocal.h" + +#include "key.h" +#include "secasn1.h" +#include "secitem.h" +#include "secoid.h" +#include "pk11func.h" +#include "prtime.h" +#include "secerr.h" + +/* + * NSS_CMSEncryptedData_Create - create an empty encryptedData object. + * + * "algorithm" specifies the bulk encryption algorithm to use. + * "keysize" is the key size. + * + * An error results in a return value of NULL and an error set. + * (Retrieve specific errors via PORT_GetError()/XP_GetError().) + */ +NSSCMSEncryptedData * +NSS_CMSEncryptedData_Create(NSSCMSMessage *cmsg, SECOidTag algorithm, int keysize) +{ + void *mark; + NSSCMSEncryptedData *encd; + PLArenaPool *poolp; + SECAlgorithmID *pbe_algid; + SECStatus rv; + + poolp = cmsg->poolp; + + mark = PORT_ArenaMark(poolp); + + encd = (NSSCMSEncryptedData *)PORT_ArenaZAlloc(poolp, sizeof(NSSCMSEncryptedData)); + if (encd == NULL) + goto loser; + + encd->cmsg = cmsg; + + /* version is set in NSS_CMSEncryptedData_Encode_BeforeStart() */ + + switch (algorithm) { + /* XXX hmmm... hardcoded algorithms? */ + case SEC_OID_RC2_CBC: + case SEC_OID_DES_EDE3_CBC: + case SEC_OID_DES_CBC: + rv = NSS_CMSContentInfo_SetContentEncAlg(poolp, &(encd->contentInfo), algorithm, NULL, keysize); + break; + default: + /* Assume password-based-encryption. At least, try that. */ + pbe_algid = PK11_CreatePBEAlgorithmID(algorithm, 1, NULL); + if (pbe_algid == NULL) { + rv = SECFailure; + break; + } + rv = NSS_CMSContentInfo_SetContentEncAlgID(poolp, &(encd->contentInfo), pbe_algid, keysize); + SECOID_DestroyAlgorithmID (pbe_algid, PR_TRUE); + break; + } + if (rv != SECSuccess) + goto loser; + + PORT_ArenaUnmark(poolp, mark); + return encd; + +loser: + PORT_ArenaRelease(poolp, mark); + return NULL; +} + +/* + * NSS_CMSEncryptedData_Destroy - destroy an encryptedData object + */ +void +NSS_CMSEncryptedData_Destroy(NSSCMSEncryptedData *encd) +{ + /* everything's in a pool, so don't worry about the storage */ + return; +} + +/* + * NSS_CMSEncryptedData_GetContentInfo - return pointer to encryptedData object's contentInfo + */ +NSSCMSContentInfo * +NSS_CMSEncryptedData_GetContentInfo(NSSCMSEncryptedData *encd) +{ + return &(encd->contentInfo); +} + +/* + * NSS_CMSEncryptedData_Encode_BeforeStart - do all the necessary things to a EncryptedData + * before encoding begins. + * + * In particular: + * - set the correct version value. + * - get the encryption key + */ +SECStatus +NSS_CMSEncryptedData_Encode_BeforeStart(NSSCMSEncryptedData *encd) +{ + int version; + PK11SymKey *bulkkey = NULL; + SECItem *dummy; + NSSCMSContentInfo *cinfo = &(encd->contentInfo); + + if (NSS_CMSArray_IsEmpty((void **)encd->unprotectedAttr)) + version = NSS_CMS_ENCRYPTED_DATA_VERSION; + else + version = NSS_CMS_ENCRYPTED_DATA_VERSION_UPATTR; + + dummy = SEC_ASN1EncodeInteger (encd->cmsg->poolp, &(encd->version), version); + if (dummy == NULL) + return SECFailure; + + /* now get content encryption key (bulk key) by using our cmsg callback */ + if (encd->cmsg->decrypt_key_cb) + bulkkey = (*encd->cmsg->decrypt_key_cb)(encd->cmsg->decrypt_key_cb_arg, + NSS_CMSContentInfo_GetContentEncAlg(cinfo)); + if (bulkkey == NULL) + return SECFailure; + + /* store the bulk key in the contentInfo so that the encoder can find it */ + NSS_CMSContentInfo_SetBulkKey(cinfo, bulkkey); + + return SECSuccess; +} + +/* + * NSS_CMSEncryptedData_Encode_BeforeData - set up encryption + */ +SECStatus +NSS_CMSEncryptedData_Encode_BeforeData(NSSCMSEncryptedData *encd) +{ + NSSCMSContentInfo *cinfo; + PK11SymKey *bulkkey; + SECAlgorithmID *algid; + + cinfo = &(encd->contentInfo); + + /* find bulkkey and algorithm - must have been set by NSS_CMSEncryptedData_Encode_BeforeStart */ + bulkkey = NSS_CMSContentInfo_GetBulkKey(cinfo); + if (bulkkey == NULL) + return SECFailure; + algid = NSS_CMSContentInfo_GetContentEncAlg(cinfo); + if (algid == NULL) + return SECFailure; + + /* this may modify algid (with IVs generated in a token). + * it is therefore essential that algid is a pointer to the "real" contentEncAlg, + * not just to a copy */ + cinfo->ciphcx = NSS_CMSCipherContext_StartEncrypt(encd->cmsg->poolp, bulkkey, algid); + PK11_FreeSymKey(bulkkey); + if (cinfo->ciphcx == NULL) + return SECFailure; + + return SECSuccess; +} + +/* + * NSS_CMSEncryptedData_Encode_AfterData - finalize this encryptedData for encoding + */ +SECStatus +NSS_CMSEncryptedData_Encode_AfterData(NSSCMSEncryptedData *encd) +{ + if (encd->contentInfo.ciphcx) + NSS_CMSCipherContext_Destroy(encd->contentInfo.ciphcx); + + /* nothing to do after data */ + return SECSuccess; +} + + +/* + * NSS_CMSEncryptedData_Decode_BeforeData - find bulk key & set up decryption + */ +SECStatus +NSS_CMSEncryptedData_Decode_BeforeData(NSSCMSEncryptedData *encd) +{ + PK11SymKey *bulkkey = NULL; + NSSCMSContentInfo *cinfo; + SECAlgorithmID *bulkalg; + SECStatus rv = SECFailure; + + cinfo = &(encd->contentInfo); + + bulkalg = NSS_CMSContentInfo_GetContentEncAlg(cinfo); + + if (encd->cmsg->decrypt_key_cb == NULL) /* no callback? no key../ */ + goto loser; + + bulkkey = (*encd->cmsg->decrypt_key_cb)(encd->cmsg->decrypt_key_cb_arg, bulkalg); + if (bulkkey == NULL) + /* no success finding a bulk key */ + goto loser; + + NSS_CMSContentInfo_SetBulkKey(cinfo, bulkkey); + + cinfo->ciphcx = NSS_CMSCipherContext_StartDecrypt(bulkkey, bulkalg); + if (cinfo->ciphcx == NULL) + goto loser; /* error has been set by NSS_CMSCipherContext_StartDecrypt */ + + /* + * HACK ALERT!! + * For PKCS5 Encryption Algorithms, the bulkkey is actually a different + * structure. Therefore, we need to set the bulkkey to the actual key + * prior to freeing it. + */ + if (SEC_PKCS5IsAlgorithmPBEAlg(bulkalg)) { + SEC_PKCS5KeyAndPassword *keyPwd = (SEC_PKCS5KeyAndPassword *)bulkkey; + bulkkey = keyPwd->key; + } + + /* we are done with (this) bulkkey now. */ + PK11_FreeSymKey(bulkkey); + + rv = SECSuccess; + +loser: + return rv; +} + +/* + * NSS_CMSEncryptedData_Decode_AfterData - finish decrypting this encryptedData's content + */ +SECStatus +NSS_CMSEncryptedData_Decode_AfterData(NSSCMSEncryptedData *encd) +{ + NSS_CMSCipherContext_Destroy(encd->contentInfo.ciphcx); + + return SECSuccess; +} + +/* + * NSS_CMSEncryptedData_Decode_AfterEnd - finish decoding this encryptedData + */ +SECStatus +NSS_CMSEncryptedData_Decode_AfterEnd(NSSCMSEncryptedData *encd) +{ + /* apply final touches */ + return SECSuccess; +} diff --git a/security/nss/lib/smime/cmsencode.c b/security/nss/lib/smime/cmsencode.c new file mode 100644 index 000000000..cc0303cd6 --- /dev/null +++ b/security/nss/lib/smime/cmsencode.c @@ -0,0 +1,740 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * CMS encoding. + * + * $Id$ + */ + +#include "cmslocal.h" + +#include "cert.h" +#include "key.h" +#include "secasn1.h" +#include "secoid.h" +#include "secrng.h" +#include "secitem.h" +#include "pk11func.h" +#include "secerr.h" + +struct nss_cms_encoder_output { + NSSCMSContentCallback outputfn; + void *outputarg; + PLArenaPool *destpoolp; + SECItem *dest; +}; + +struct NSSCMSEncoderContextStr { + SEC_ASN1EncoderContext * ecx; /* ASN.1 encoder context */ + PRBool ecxupdated; /* true if data was handed in */ + NSSCMSMessage * cmsg; /* pointer to the root message */ + SECOidTag type; /* type tag of the current content */ + NSSCMSContent content; /* pointer to current content */ + struct nss_cms_encoder_output output; /* output function */ + int error; /* error code */ + NSSCMSEncoderContext * childp7ecx; /* link to child encoder context */ +}; + +static SECStatus nss_cms_before_data(NSSCMSEncoderContext *p7ecx); +static SECStatus nss_cms_after_data(NSSCMSEncoderContext *p7ecx); +static SECStatus nss_cms_encoder_update(NSSCMSEncoderContext *p7ecx, const char *data, unsigned long len); +static SECStatus nss_cms_encoder_work_data(NSSCMSEncoderContext *p7ecx, SECItem *dest, + const unsigned char *data, unsigned long len, + PRBool final, PRBool innermost); + +extern const SEC_ASN1Template NSSCMSMessageTemplate[]; + +/* + * The little output function that the ASN.1 encoder calls to hand + * us bytes which we in turn hand back to our caller (via the callback + * they gave us). + */ +static void +nss_cms_encoder_out(void *arg, const char *buf, unsigned long len, + int depth, SEC_ASN1EncodingPart data_kind) +{ + struct nss_cms_encoder_output *output = (struct nss_cms_encoder_output *)arg; + unsigned char *dest; + unsigned long offset; + +#ifdef CMSDEBUG + int i; + + fprintf(stderr, "kind = %d, depth = %d, len = %d\n", data_kind, depth, len); + for (i=0; i < len; i++) { + fprintf(stderr, " %02x%s", (unsigned int)buf[i] & 0xff, ((i % 16) == 15) ? "\n" : ""); + } + if ((i % 16) != 0) + fprintf(stderr, "\n"); +#endif + + if (output->outputfn != NULL) + /* call output callback with DER data */ + output->outputfn(output->outputarg, buf, len); + + if (output->dest != NULL) { + /* store DER data in SECItem */ + offset = output->dest->len; + if (offset == 0) { + dest = (unsigned char *)PORT_ArenaAlloc(output->destpoolp, len); + } else { + dest = (unsigned char *)PORT_ArenaGrow(output->destpoolp, + output->dest->data, + output->dest->len, + output->dest->len + len); + } + if (dest == NULL) + /* oops */ + return; + + output->dest->data = dest; + output->dest->len += len; + + /* copy it in */ + PORT_Memcpy(output->dest->data + offset, buf, len); + } +} + +/* + * nss_cms_encoder_notify - ASN.1 encoder callback + * + * this function is called by the ASN.1 encoder before and after the encoding of + * every object. here, it is used to keep track of data structures, set up + * encryption and/or digesting and possibly set up child encoders. + */ +static void +nss_cms_encoder_notify(void *arg, PRBool before, void *dest, int depth) +{ + NSSCMSEncoderContext *p7ecx; + NSSCMSContentInfo *rootcinfo, *cinfo; + PRBool after = !before; + PLArenaPool *poolp; + SECOidTag childtype; + SECItem *item; + + p7ecx = (NSSCMSEncoderContext *)arg; + PORT_Assert(p7ecx != NULL); + + rootcinfo = &(p7ecx->cmsg->contentInfo); + poolp = p7ecx->cmsg->poolp; + +#ifdef CMSDEBUG + fprintf(stderr, "%6.6s, dest = 0x%08x, depth = %d\n", before ? "before" : "after", dest, depth); +#endif + + /* + * Watch for the content field, at which point we want to instruct + * the ASN.1 encoder to start taking bytes from the buffer. + */ + switch (p7ecx->type) { + default: + case SEC_OID_UNKNOWN: + /* we're still in the root message */ + if (after && dest == &(rootcinfo->contentType)) { + /* got the content type OID now - so find out the type tag */ + p7ecx->type = NSS_CMSContentInfo_GetContentTypeTag(rootcinfo); + /* set up a pointer to our current content */ + p7ecx->content = rootcinfo->content; + } + break; + + case SEC_OID_PKCS7_DATA: + if (before && dest == &(rootcinfo->rawContent)) { + /* just set up encoder to grab from user - no encryption or digesting */ + if ((item = rootcinfo->content.data) != NULL) + (void)nss_cms_encoder_work_data(p7ecx, NULL, item->data, item->len, PR_TRUE, PR_TRUE); + else + SEC_ASN1EncoderSetTakeFromBuf(p7ecx->ecx); + SEC_ASN1EncoderClearNotifyProc(p7ecx->ecx); /* no need to get notified anymore */ + } + break; + + case SEC_OID_PKCS7_SIGNED_DATA: + case SEC_OID_PKCS7_ENVELOPED_DATA: + case SEC_OID_PKCS7_DIGESTED_DATA: + case SEC_OID_PKCS7_ENCRYPTED_DATA: + + /* when we know what the content is, we encode happily until we reach the inner content */ + cinfo = NSS_CMSContent_GetContentInfo(p7ecx->content.pointer, p7ecx->type); + childtype = NSS_CMSContentInfo_GetContentTypeTag(cinfo); + + if (after && dest == &(cinfo->contentType)) { + /* we're right before encoding the data (if we have some or not) */ + /* (for encrypted data, we're right before the contentEncAlg which may change */ + /* in nss_cms_before_data because of IV calculation when setting up encryption) */ + if (nss_cms_before_data(p7ecx) != SECSuccess) + p7ecx->error = PORT_GetError(); + } + if (before && dest == &(cinfo->rawContent)) { + if (childtype == SEC_OID_PKCS7_DATA && (item = cinfo->content.data) != NULL) + /* we have data - feed it in */ + (void)nss_cms_encoder_work_data(p7ecx, NULL, item->data, item->len, PR_TRUE, PR_TRUE); + else + /* else try to get it from user */ + SEC_ASN1EncoderSetTakeFromBuf(p7ecx->ecx); + } + if (after && dest == &(cinfo->rawContent)) { + if (nss_cms_after_data(p7ecx) != SECSuccess) + p7ecx->error = PORT_GetError(); + SEC_ASN1EncoderClearNotifyProc(p7ecx->ecx); /* no need to get notified anymore */ + } + break; + } +} + +/* + * nss_cms_before_data - setup the current encoder to receive data + */ +static SECStatus +nss_cms_before_data(NSSCMSEncoderContext *p7ecx) +{ + SECStatus rv; + SECOidTag childtype; + NSSCMSContentInfo *cinfo; + PLArenaPool *poolp; + NSSCMSEncoderContext *childp7ecx; + const SEC_ASN1Template *template; + + poolp = p7ecx->cmsg->poolp; + + /* call _Encode_BeforeData handlers */ + switch (p7ecx->type) { + case SEC_OID_PKCS7_SIGNED_DATA: + /* we're encoding a signedData, so set up the digests */ + rv = NSS_CMSSignedData_Encode_BeforeData(p7ecx->content.signedData); + break; + case SEC_OID_PKCS7_DIGESTED_DATA: + /* we're encoding a digestedData, so set up the digest */ + rv = NSS_CMSDigestedData_Encode_BeforeData(p7ecx->content.digestedData); + break; + case SEC_OID_PKCS7_ENVELOPED_DATA: + rv = NSS_CMSEnvelopedData_Encode_BeforeData(p7ecx->content.envelopedData); + break; + case SEC_OID_PKCS7_ENCRYPTED_DATA: + rv = NSS_CMSEncryptedData_Encode_BeforeData(p7ecx->content.encryptedData); + break; + default: + rv = SECFailure; + } + if (rv != SECSuccess) + return SECFailure; + + /* ok, now we have a pointer to cinfo */ + /* find out what kind of data is encapsulated */ + + cinfo = NSS_CMSContent_GetContentInfo(p7ecx->content.pointer, p7ecx->type); + childtype = NSS_CMSContentInfo_GetContentTypeTag(cinfo); + + switch (childtype) { + case SEC_OID_PKCS7_SIGNED_DATA: + case SEC_OID_PKCS7_ENVELOPED_DATA: + case SEC_OID_PKCS7_ENCRYPTED_DATA: + case SEC_OID_PKCS7_DIGESTED_DATA: +#if 0 + case SEC_OID_PKCS7_DATA: /* XXX here also??? maybe yes! */ +#endif + /* in these cases, we need to set up a child encoder! */ + /* create new encoder context */ + childp7ecx = PORT_ZAlloc(sizeof(NSSCMSEncoderContext)); + if (childp7ecx == NULL) + return SECFailure; + + /* the CHILD encoder needs to hand its encoded data to the CURRENT encoder + * (which will encrypt and/or digest it) + * this needs to route back into our update function + * which finds the lowest encoding context & encrypts and computes digests */ + childp7ecx->type = childtype; + childp7ecx->content = cinfo->content; + /* use the non-recursive update function here, of course */ + childp7ecx->output.outputfn = (NSSCMSContentCallback)nss_cms_encoder_update; + childp7ecx->output.outputarg = p7ecx; + childp7ecx->output.destpoolp = NULL; + childp7ecx->output.dest = NULL; + childp7ecx->cmsg = p7ecx->cmsg; + + template = NSS_CMSUtil_GetTemplateByTypeTag(childtype); + if (template == NULL) + goto loser; /* cannot happen */ + + /* now initialize the data for encoding the first third */ + switch (childp7ecx->type) { + case SEC_OID_PKCS7_SIGNED_DATA: + rv = NSS_CMSSignedData_Encode_BeforeStart(cinfo->content.signedData); + break; + case SEC_OID_PKCS7_ENVELOPED_DATA: + rv = NSS_CMSEnvelopedData_Encode_BeforeStart(cinfo->content.envelopedData); + break; + case SEC_OID_PKCS7_DIGESTED_DATA: + rv = NSS_CMSDigestedData_Encode_BeforeStart(cinfo->content.digestedData); + break; + case SEC_OID_PKCS7_ENCRYPTED_DATA: + rv = NSS_CMSEncryptedData_Encode_BeforeStart(cinfo->content.encryptedData); + break; + case SEC_OID_PKCS7_DATA: + rv = SECSuccess; + break; + } + if (rv != SECSuccess) + goto loser; + + /* + * Initialize the BER encoder. + */ + childp7ecx->ecx = SEC_ASN1EncoderStart(cinfo->content.pointer, template, + nss_cms_encoder_out, &(childp7ecx->output)); + if (childp7ecx->ecx == NULL) + goto loser; + + childp7ecx->ecxupdated = PR_FALSE; + + /* + * Indicate that we are streaming. We will be streaming until we + * get past the contents bytes. + */ + SEC_ASN1EncoderSetStreaming(childp7ecx->ecx); + + /* + * The notify function will watch for the contents field. + */ + SEC_ASN1EncoderSetNotifyProc(childp7ecx->ecx, nss_cms_encoder_notify, childp7ecx); + + /* please note that we are NOT calling SEC_ASN1EncoderUpdate here to kick off the */ + /* encoding process - we'll do that from the update function instead */ + /* otherwise we'd be encoding data from a call of the notify function of the */ + /* parent encoder (which would not work) */ + + /* this will kick off the encoding process & encode everything up to the content bytes, + * at which point the notify function sets streaming mode (and possibly creates + * another child encoder). */ + if (SEC_ASN1EncoderUpdate(childp7ecx->ecx, NULL, 0) != SECSuccess) + goto loser; + + p7ecx->childp7ecx = childp7ecx; + break; + + case SEC_OID_PKCS7_DATA: + p7ecx->childp7ecx = NULL; + break; + default: + /* we do not know this type */ + p7ecx->error = SEC_ERROR_BAD_DER; + break; + } + + return SECSuccess; + +loser: + if (childp7ecx) { + if (childp7ecx->ecx) + SEC_ASN1EncoderFinish(childp7ecx->ecx); + PORT_Free(childp7ecx); + } + return SECFailure; +} + +static SECStatus +nss_cms_after_data(NSSCMSEncoderContext *p7ecx) +{ + SECStatus rv; + + switch (p7ecx->type) { + case SEC_OID_PKCS7_SIGNED_DATA: + /* this will finish the digests and sign */ + rv = NSS_CMSSignedData_Encode_AfterData(p7ecx->content.signedData); + break; + case SEC_OID_PKCS7_ENVELOPED_DATA: + rv = NSS_CMSEnvelopedData_Encode_AfterData(p7ecx->content.envelopedData); + break; + case SEC_OID_PKCS7_DIGESTED_DATA: + rv = NSS_CMSDigestedData_Encode_AfterData(p7ecx->content.digestedData); + break; + case SEC_OID_PKCS7_ENCRYPTED_DATA: + rv = NSS_CMSEncryptedData_Encode_AfterData(p7ecx->content.encryptedData); + break; + case SEC_OID_PKCS7_DATA: + /* do nothing */ + break; + default: + rv = SECFailure; + break; + } + return rv; +} + +/* + * nss_cms_encoder_work_data - process incoming data + * + * (from the user or the next encoding layer) + * Here, we need to digest and/or encrypt, then pass it on + */ +static SECStatus +nss_cms_encoder_work_data(NSSCMSEncoderContext *p7ecx, SECItem *dest, + const unsigned char *data, unsigned long len, + PRBool final, PRBool innermost) +{ + unsigned char *buf = NULL; + SECStatus rv; + NSSCMSContentInfo *cinfo; + + rv = SECSuccess; /* may as well be optimistic */ + + /* + * We should really have data to process, or we should be trying + * to finish/flush the last block. (This is an overly paranoid + * check since all callers are in this file and simple inspection + * proves they do it right. But it could find a bug in future + * modifications/development, that is why it is here.) + */ + PORT_Assert ((data != NULL && len) || final); + + /* we got data (either from the caller, or from a lower level encoder) */ + cinfo = NSS_CMSContent_GetContentInfo(p7ecx->content.pointer, p7ecx->type); + + /* Update the running digest. */ + if (len && cinfo->digcx != NULL) + NSS_CMSDigestContext_Update(cinfo->digcx, data, len); + + /* Encrypt this chunk. */ + if (cinfo->ciphcx != NULL) { + unsigned int inlen; /* length of data being encrypted */ + unsigned int outlen; /* length of encrypted data */ + unsigned int buflen; /* length available for encrypted data */ + + inlen = len; + buflen = NSS_CMSCipherContext_EncryptLength(cinfo->ciphcx, inlen, final); + if (buflen == 0) { + /* + * No output is expected, but the input data may be buffered + * so we still have to call Encrypt. + */ + rv = NSS_CMSCipherContext_Encrypt(cinfo->ciphcx, NULL, NULL, 0, + data, inlen, final); + if (final) { + len = 0; + goto done; + } + return rv; + } + + if (dest != NULL) + buf = (unsigned char*)PORT_ArenaAlloc(p7ecx->cmsg->poolp, buflen); + else + buf = (unsigned char*)PORT_Alloc(buflen); + + if (buf == NULL) { + rv = SECFailure; + } else { + rv = NSS_CMSCipherContext_Encrypt(cinfo->ciphcx, buf, &outlen, buflen, + data, inlen, final); + data = buf; + len = outlen; + } + if (rv != SECSuccess) + /* encryption or malloc failed? */ + return rv; + } + + + /* + * at this point (data,len) has everything we'd like to give to the CURRENT encoder + * (which will encode it, then hand it back to the user or the parent encoder) + * We don't encode the data if we're innermost and we're told not to include the data + */ + if (p7ecx->ecx != NULL && len && (!innermost || cinfo->rawContent != NULL)) + rv = SEC_ASN1EncoderUpdate(p7ecx->ecx, (const char *)data, len); + +done: + + if (cinfo->ciphcx != NULL) { + if (dest != NULL) { + dest->data = buf; + dest->len = len; + } else if (buf != NULL) { + PORT_Free (buf); + } + } + return rv; +} + +/* + * nss_cms_encoder_update - deliver encoded data to the next higher level + * + * no recursion here because we REALLY want to end up at the next higher encoder! + */ +static SECStatus +nss_cms_encoder_update(NSSCMSEncoderContext *p7ecx, const char *data, unsigned long len) +{ + /* XXX Error handling needs help. Return what? Do "Finish" on failure? */ + return nss_cms_encoder_work_data (p7ecx, NULL, (const unsigned char *)data, len, PR_FALSE, PR_FALSE); +} + +/* + * NSS_CMSEncoder_Start - set up encoding of a CMS message + * + * "cmsg" - message to encode + * "outputfn", "outputarg" - callback function for delivery of DER-encoded output + * will not be called if NULL. + * "dest" - if non-NULL, pointer to SECItem that will hold the DER-encoded output + * "destpoolp" - pool to allocate DER-encoded output in + * "pwfn", pwfn_arg" - callback function for getting token password + * "decrypt_key_cb", "decrypt_key_cb_arg" - callback function for getting bulk key for encryptedData + * "detached_digestalgs", "detached_digests" - digests from detached content + */ +NSSCMSEncoderContext * +NSS_CMSEncoder_Start(NSSCMSMessage *cmsg, + NSSCMSContentCallback outputfn, void *outputarg, + SECItem *dest, PLArenaPool *destpoolp, + PK11PasswordFunc pwfn, void *pwfn_arg, + NSSCMSGetDecryptKeyCallback decrypt_key_cb, void *decrypt_key_cb_arg, + SECAlgorithmID **detached_digestalgs, SECItem **detached_digests) +{ + NSSCMSEncoderContext *p7ecx; + SECStatus rv; + NSSCMSContentInfo *cinfo; + + NSS_CMSMessage_SetEncodingParams(cmsg, pwfn, pwfn_arg, decrypt_key_cb, decrypt_key_cb_arg, + detached_digestalgs, detached_digests); + + p7ecx = (NSSCMSEncoderContext *)PORT_ZAlloc(sizeof(NSSCMSEncoderContext)); + if (p7ecx == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return NULL; + } + + p7ecx->cmsg = cmsg; + p7ecx->output.outputfn = outputfn; + p7ecx->output.outputarg = outputarg; + p7ecx->output.dest = dest; + p7ecx->output.destpoolp = destpoolp; + p7ecx->type = SEC_OID_UNKNOWN; + + cinfo = NSS_CMSMessage_GetContentInfo(cmsg); + + switch (NSS_CMSContentInfo_GetContentTypeTag(cinfo)) { + case SEC_OID_PKCS7_SIGNED_DATA: + rv = NSS_CMSSignedData_Encode_BeforeStart(cinfo->content.signedData); + break; + case SEC_OID_PKCS7_ENVELOPED_DATA: + rv = NSS_CMSEnvelopedData_Encode_BeforeStart(cinfo->content.envelopedData); + break; + case SEC_OID_PKCS7_DIGESTED_DATA: + rv = NSS_CMSDigestedData_Encode_BeforeStart(cinfo->content.digestedData); + break; + case SEC_OID_PKCS7_ENCRYPTED_DATA: + rv = NSS_CMSEncryptedData_Encode_BeforeStart(cinfo->content.encryptedData); + break; + default: + rv = SECFailure; + break; + } + if (rv != SECSuccess) + return NULL; + + /* Initialize the BER encoder. + * Note that this will not encode anything until the first call to SEC_ASN1EncoderUpdate */ + p7ecx->ecx = SEC_ASN1EncoderStart(cmsg, NSSCMSMessageTemplate, + nss_cms_encoder_out, &(p7ecx->output)); + if (p7ecx->ecx == NULL) { + PORT_Free (p7ecx); + return NULL; + } + p7ecx->ecxupdated = PR_FALSE; + + /* + * Indicate that we are streaming. We will be streaming until we + * get past the contents bytes. + */ + SEC_ASN1EncoderSetStreaming(p7ecx->ecx); + + /* + * The notify function will watch for the contents field. + */ + SEC_ASN1EncoderSetNotifyProc(p7ecx->ecx, nss_cms_encoder_notify, p7ecx); + + /* this will kick off the encoding process & encode everything up to the content bytes, + * at which point the notify function sets streaming mode (and possibly creates + * a child encoder). */ + if (SEC_ASN1EncoderUpdate(p7ecx->ecx, NULL, 0) != SECSuccess) { + PORT_Free (p7ecx); + return NULL; + } + + return p7ecx; +} + +/* + * NSS_CMSEncoder_Update - take content data delivery from the user + * + * "p7ecx" - encoder context + * "data" - content data + * "len" - length of content data + * + * need to find the lowest level (and call SEC_ASN1EncoderUpdate on the way down), + * then hand the data to the work_data fn + */ +SECStatus +NSS_CMSEncoder_Update(NSSCMSEncoderContext *p7ecx, const char *data, unsigned long len) +{ + SECStatus rv; + NSSCMSContentInfo *cinfo; + SECOidTag childtype; + + if (p7ecx->error) + return SECFailure; + + /* hand data to the innermost decoder */ + if (p7ecx->childp7ecx) { + /* recursion here */ + rv = NSS_CMSEncoder_Update(p7ecx->childp7ecx, data, len); + } else { + /* we are at innermost decoder */ + /* find out about our inner content type - must be data */ + cinfo = NSS_CMSContent_GetContentInfo(p7ecx->content.pointer, p7ecx->type); + childtype = NSS_CMSContentInfo_GetContentTypeTag(cinfo); + if (childtype != SEC_OID_PKCS7_DATA) + return SECFailure; + /* and we must not have preset data */ + if (cinfo->content.data != NULL) + return SECFailure; + + /* hand it the data so it can encode it (let DER trickle up the chain) */ + rv = nss_cms_encoder_work_data(p7ecx, NULL, (const unsigned char *)data, len, PR_FALSE, PR_TRUE); + } + return rv; +} + +/* + * NSS_CMSEncoder_Cancel - stop all encoding + * + * we need to walk down the chain of encoders and the finish them from the innermost out + */ +SECStatus +NSS_CMSEncoder_Cancel(NSSCMSEncoderContext *p7ecx) +{ + SECStatus rv = SECFailure; + + /* XXX do this right! */ + + /* + * Finish any inner decoders before us so that all the encoded data is flushed + * This basically finishes all the decoders from the innermost to the outermost. + * Finishing an inner decoder may result in data being updated to the outer decoder + * while we are already in NSS_CMSEncoder_Finish, but that's allright. + */ + if (p7ecx->childp7ecx) { + rv = NSS_CMSEncoder_Cancel(p7ecx->childp7ecx); /* frees p7ecx->childp7ecx */ + /* remember rv for now */ + } + + /* + * On the way back up, there will be no more data (if we had an + * inner encoder, it is done now!) + * Flush out any remaining data and/or finish digests. + */ + rv = nss_cms_encoder_work_data(p7ecx, NULL, NULL, 0, PR_TRUE, (p7ecx->childp7ecx == NULL)); + if (rv != SECSuccess) + goto loser; + + p7ecx->childp7ecx = NULL; + + /* kick the encoder back into working mode again. + * We turn off streaming stuff (which will cause the encoder to continue + * encoding happily, now that we have all the data (like digests) ready for it). + */ + SEC_ASN1EncoderClearTakeFromBuf(p7ecx->ecx); + SEC_ASN1EncoderClearStreaming(p7ecx->ecx); + + /* now that TakeFromBuf is off, this will kick this encoder to finish encoding */ + rv = SEC_ASN1EncoderUpdate(p7ecx->ecx, NULL, 0); + +loser: + SEC_ASN1EncoderFinish(p7ecx->ecx); + PORT_Free (p7ecx); + return rv; +} + +/* + * NSS_CMSEncoder_Finish - signal the end of data + * + * we need to walk down the chain of encoders and the finish them from the innermost out + */ +SECStatus +NSS_CMSEncoder_Finish(NSSCMSEncoderContext *p7ecx) +{ + SECStatus rv = SECFailure; + NSSCMSContentInfo *cinfo; + SECOidTag childtype; + + /* + * Finish any inner decoders before us so that all the encoded data is flushed + * This basically finishes all the decoders from the innermost to the outermost. + * Finishing an inner decoder may result in data being updated to the outer decoder + * while we are already in NSS_CMSEncoder_Finish, but that's allright. + */ + if (p7ecx->childp7ecx) { + rv = NSS_CMSEncoder_Finish(p7ecx->childp7ecx); /* frees p7ecx->childp7ecx */ + if (rv != SECSuccess) + goto loser; + } + + /* + * On the way back up, there will be no more data (if we had an + * inner encoder, it is done now!) + * Flush out any remaining data and/or finish digests. + */ + rv = nss_cms_encoder_work_data(p7ecx, NULL, NULL, 0, PR_TRUE, (p7ecx->childp7ecx == NULL)); + if (rv != SECSuccess) + goto loser; + + p7ecx->childp7ecx = NULL; + + /* find out about our inner content type - must be data */ + cinfo = NSS_CMSContent_GetContentInfo(p7ecx->content.pointer, p7ecx->type); + childtype = NSS_CMSContentInfo_GetContentTypeTag(cinfo); + if (childtype == SEC_OID_PKCS7_DATA && cinfo->content.data == NULL) { + SEC_ASN1EncoderClearTakeFromBuf(p7ecx->ecx); + /* now that TakeFromBuf is off, this will kick this encoder to finish encoding */ + rv = SEC_ASN1EncoderUpdate(p7ecx->ecx, NULL, 0); + } + + SEC_ASN1EncoderClearStreaming(p7ecx->ecx); + + if (p7ecx->error) + rv = SECFailure; + +loser: + SEC_ASN1EncoderFinish(p7ecx->ecx); + PORT_Free (p7ecx); + return rv; +} diff --git a/security/nss/lib/smime/cmsenvdata.c b/security/nss/lib/smime/cmsenvdata.c new file mode 100644 index 000000000..d6e45a10a --- /dev/null +++ b/security/nss/lib/smime/cmsenvdata.c @@ -0,0 +1,416 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * CMS envelopedData methods. + * + * $Id$ + */ + +#include "cmslocal.h" + +#include "cert.h" +#include "key.h" +#include "secasn1.h" +#include "secitem.h" +#include "secoid.h" +#include "pk11func.h" +#include "secerr.h" + +/* + * NSS_CMSEnvelopedData_Create - create an enveloped data message + */ +NSSCMSEnvelopedData * +NSS_CMSEnvelopedData_Create(NSSCMSMessage *cmsg, SECOidTag algorithm, int keysize) +{ + void *mark; + NSSCMSEnvelopedData *envd; + PLArenaPool *poolp; + SECStatus rv; + + poolp = cmsg->poolp; + + mark = PORT_ArenaMark(poolp); + + envd = (NSSCMSEnvelopedData *)PORT_ArenaZAlloc(poolp, sizeof(NSSCMSEnvelopedData)); + if (envd == NULL) + goto loser; + + envd->cmsg = cmsg; + + /* version is set in NSS_CMSEnvelopedData_Encode_BeforeStart() */ + + rv = NSS_CMSContentInfo_SetContentEncAlg(poolp, &(envd->contentInfo), algorithm, NULL, keysize); + if (rv != SECSuccess) + goto loser; + + PORT_ArenaUnmark(poolp, mark); + return envd; + +loser: + PORT_ArenaRelease(poolp, mark); + return NULL; +} + +/* + * NSS_CMSEnvelopedData_Destroy - destroy an enveloped data message + */ +void +NSS_CMSEnvelopedData_Destroy(NSSCMSEnvelopedData *edp) +{ + NSSCMSRecipientInfo **recipientinfos; + NSSCMSRecipientInfo *ri; + + if (edp == NULL) + return; + + recipientinfos = edp->recipientInfos; + if (recipientinfos == NULL) + return; + + while ((ri = *recipientinfos++) != NULL) + NSS_CMSRecipientInfo_Destroy(ri); + + /* XXX storage ??? */ +} + +/* + * NSS_CMSEnvelopedData_GetContentInfo - return pointer to this envelopedData's contentinfo + */ +NSSCMSContentInfo * +NSS_CMSEnvelopedData_GetContentInfo(NSSCMSEnvelopedData *envd) +{ + return &(envd->contentInfo); +} + +/* + * NSS_CMSEnvelopedData_AddRecipient - add a recipientinfo to the enveloped data msg + * + * rip must be created on the same pool as edp - this is not enforced, though. + */ +SECStatus +NSS_CMSEnvelopedData_AddRecipient(NSSCMSEnvelopedData *edp, NSSCMSRecipientInfo *rip) +{ + void *mark; + SECStatus rv; + + /* XXX compare pools, if not same, copy rip into edp's pool */ + + PR_ASSERT(edp != NULL); + PR_ASSERT(rip != NULL); + + mark = PORT_ArenaMark(edp->cmsg->poolp); + + rv = NSS_CMSArray_Add(edp->cmsg->poolp, (void ***)&(edp->recipientInfos), (void *)rip); + if (rv != SECSuccess) { + PORT_ArenaRelease(edp->cmsg->poolp, mark); + return SECFailure; + } + + PORT_ArenaUnmark (edp->cmsg->poolp, mark); + return SECSuccess; +} + +/* + * NSS_CMSEnvelopedData_Encode_BeforeStart - prepare this envelopedData for encoding + * + * at this point, we need + * - recipientinfos set up with recipient's certificates + * - a content encryption algorithm (if none, 3DES will be used) + * + * this function will generate a random content encryption key (aka bulk key), + * initialize the recipientinfos with certificate identification and wrap the bulk key + * using the proper algorithm for every certificiate. + * it will finally set the bulk algorithm and key so that the encode step can find it. + */ +SECStatus +NSS_CMSEnvelopedData_Encode_BeforeStart(NSSCMSEnvelopedData *envd) +{ + int version; + NSSCMSRecipientInfo **recipientinfos; + NSSCMSContentInfo *cinfo; + PK11SymKey *bulkkey = NULL; + SECOidTag bulkalgtag; + CK_MECHANISM_TYPE type; + PK11SlotInfo *slot; + SECStatus rv; + SECItem *dummy; + PLArenaPool *poolp; + extern const SEC_ASN1Template NSSCMSRecipientInfoTemplate[]; + void *mark = NULL; + int i; + + poolp = envd->cmsg->poolp; + cinfo = &(envd->contentInfo); + + recipientinfos = envd->recipientInfos; + if (recipientinfos == NULL) { + PORT_SetError(SEC_ERROR_BAD_DATA); +#if 0 + PORT_SetErrorString("Cannot find recipientinfos to encode."); +#endif + goto loser; + } + + version = NSS_CMS_ENVELOPED_DATA_VERSION_REG; + if (envd->originatorInfo != NULL || envd->unprotectedAttr != NULL) { + version = NSS_CMS_ENVELOPED_DATA_VERSION_ADV; + } else { + for (i = 0; recipientinfos[i] != NULL; i++) { + if (NSS_CMSRecipientInfo_GetVersion(recipientinfos[i]) != 0) { + version = NSS_CMS_ENVELOPED_DATA_VERSION_ADV; + break; + } + } + } + dummy = SEC_ASN1EncodeInteger(poolp, &(envd->version), version); + if (dummy == NULL) + goto loser; + + /* now we need to have a proper content encryption algorithm + * on the SMIME level, we would figure one out by looking at SMIME capabilities + * we cannot do that on our level, so if none is set already, we'll just go + * with one of the mandatory algorithms (3DES) */ + if ((bulkalgtag = NSS_CMSContentInfo_GetContentEncAlgTag(cinfo)) == SEC_OID_UNKNOWN) { + rv = NSS_CMSContentInfo_SetContentEncAlg(poolp, cinfo, SEC_OID_DES_EDE3_CBC, NULL, 168); + if (rv != SECSuccess) + goto loser; + bulkalgtag = SEC_OID_DES_EDE3_CBC; + } + + /* generate a random bulk key suitable for content encryption alg */ + type = PK11_AlgtagToMechanism(bulkalgtag); + slot = PK11_GetBestSlot(type, envd->cmsg->pwfn_arg); + if (slot == NULL) + goto loser; /* error has been set by PK11_GetBestSlot */ + + /* this is expensive... */ + bulkkey = PK11_KeyGen(slot, type, NULL, NSS_CMSContentInfo_GetBulkKeySize(cinfo) / 8, envd->cmsg->pwfn_arg); + PK11_FreeSlot(slot); + if (bulkkey == NULL) + goto loser; /* error has been set by PK11_KeyGen */ + + mark = PORT_ArenaMark(poolp); + + /* Encrypt the bulk key with the public key of each recipient. */ + for (i = 0; recipientinfos[i] != NULL; i++) { + rv = NSS_CMSRecipientInfo_WrapBulkKey(recipientinfos[i], bulkkey, bulkalgtag); + if (rv != SECSuccess) + goto loser; /* error has been set by NSS_CMSRecipientInfo_EncryptBulkKey */ + /* could be: alg not supported etc. */ + } + + /* the recipientinfos are all finished. now sort them by DER for SET OF encoding */ + rv = NSS_CMSArray_SortByDER((void **)envd->recipientInfos, NSSCMSRecipientInfoTemplate, NULL); + if (rv != SECSuccess) + goto loser; /* error has been set by NSS_CMSArray_SortByDER */ + + /* store the bulk key in the contentInfo so that the encoder can find it */ + NSS_CMSContentInfo_SetBulkKey(cinfo, bulkkey); + + PORT_ArenaUnmark(poolp, mark); + + PK11_FreeSymKey(bulkkey); + + return SECSuccess; + +loser: + if (mark != NULL) + PORT_ArenaRelease (poolp, mark); + if (bulkkey) + PK11_FreeSymKey(bulkkey); + + return SECFailure; +} + +/* + * NSS_CMSEnvelopedData_Encode_BeforeData - set up encryption + * + * it is essential that this is called before the contentEncAlg is encoded, because + * setting up the encryption may generate IVs and thus change it! + */ +SECStatus +NSS_CMSEnvelopedData_Encode_BeforeData(NSSCMSEnvelopedData *envd) +{ + NSSCMSContentInfo *cinfo; + PK11SymKey *bulkkey; + SECAlgorithmID *algid; + + cinfo = &(envd->contentInfo); + + /* find bulkkey and algorithm - must have been set by NSS_CMSEnvelopedData_Encode_BeforeStart */ + bulkkey = NSS_CMSContentInfo_GetBulkKey(cinfo); + if (bulkkey == NULL) + return SECFailure; + algid = NSS_CMSContentInfo_GetContentEncAlg(cinfo); + if (algid == NULL) + return SECFailure; + + /* this may modify algid (with IVs generated in a token). + * it is essential that algid is a pointer to the contentEncAlg data, not a + * pointer to a copy! */ + cinfo->ciphcx = NSS_CMSCipherContext_StartEncrypt(envd->cmsg->poolp, bulkkey, algid); + PK11_FreeSymKey(bulkkey); + if (cinfo->ciphcx == NULL) + return SECFailure; + + return SECSuccess; +} + +/* + * NSS_CMSEnvelopedData_Encode_AfterData - finalize this envelopedData for encoding + */ +SECStatus +NSS_CMSEnvelopedData_Encode_AfterData(NSSCMSEnvelopedData *envd) +{ + if (envd->contentInfo.ciphcx) { + NSS_CMSCipherContext_Destroy(envd->contentInfo.ciphcx); + envd->contentInfo.ciphcx = NULL; + } + + /* nothing else to do after data */ + return SECSuccess; +} + +/* + * NSS_CMSEnvelopedData_Decode_BeforeData - find our recipientinfo, + * derive bulk key & set up our contentinfo + */ +SECStatus +NSS_CMSEnvelopedData_Decode_BeforeData(NSSCMSEnvelopedData *envd) +{ + NSSCMSRecipientInfo *ri; + PK11SymKey *bulkkey = NULL; + SECOidTag bulkalgtag; + SECAlgorithmID *bulkalg; + SECStatus rv = SECFailure; + NSSCMSContentInfo *cinfo; + NSSCMSRecipient **recipient_list; + int rlIndex; + + if (NSS_CMSArray_Count((void **)envd->recipientInfos) == 0) { + PORT_SetError(SEC_ERROR_BAD_DATA); +#if 0 + PORT_SetErrorString("No recipient data in envelope."); +#endif + goto loser; + } + + /* look if one of OUR cert's issuerSN is on the list of recipients, and if so, */ + /* get the cert and private key for it right away */ + recipient_list = nss_cms_recipient_list_create(envd->recipientInfos); + if (recipient_list == NULL) + goto loser; + + /* what about multiple recipientInfos that match? + * especially if, for some reason, we could not produce a bulk key with the first match?! + * we could loop & feed partial recipient_list to PK11_FindCertAndKeyByRecipientList... + * maybe later... */ + rlIndex = PK11_FindCertAndKeyByRecipientListNew(recipient_list, envd->cmsg->pwfn_arg); + + /* if that fails, then we're not an intended recipient and cannot decrypt */ + if (rlIndex < 0) { + PORT_SetError(SEC_ERROR_NOT_A_RECIPIENT); +#if 0 + PORT_SetErrorString("Cannot decrypt data because proper key cannot be found."); +#endif + goto loser; + } + + /* get a pointer to "our" recipientinfo */ + ri = envd->recipientInfos[recipient_list[rlIndex]->riIndex]; + + cinfo = &(envd->contentInfo); + bulkalgtag = NSS_CMSContentInfo_GetContentEncAlgTag(cinfo); + bulkkey = NSS_CMSRecipientInfo_UnwrapBulkKey(ri,recipient_list[rlIndex]->subIndex, + recipient_list[rlIndex]->cert, + recipient_list[rlIndex]->privkey, + bulkalgtag); + if (bulkkey == NULL) { + /* no success finding a bulk key */ + goto loser; + } + + NSS_CMSContentInfo_SetBulkKey(cinfo, bulkkey); + + bulkalg = NSS_CMSContentInfo_GetContentEncAlg(cinfo); + + cinfo->ciphcx = NSS_CMSCipherContext_StartDecrypt(bulkkey, bulkalg); + if (cinfo->ciphcx == NULL) + goto loser; /* error has been set by NSS_CMSCipherContext_StartDecrypt */ + + /* + * HACK ALERT!! + * For PKCS5 Encryption Algorithms, the bulkkey is actually a different + * structure. Therefore, we need to set the bulkkey to the actual key + * prior to freeing it. + */ + if (SEC_PKCS5IsAlgorithmPBEAlg(bulkalg)) { + SEC_PKCS5KeyAndPassword *keyPwd = (SEC_PKCS5KeyAndPassword *)bulkkey; + bulkkey = keyPwd->key; + } + + rv = SECSuccess; + +loser: + if (bulkkey) + PK11_FreeSymKey(bulkkey); + if (recipient_list != NULL) + nss_cms_recipient_list_destroy(recipient_list); + return rv; +} + +/* + * NSS_CMSEnvelopedData_Decode_AfterData - finish decrypting this envelopedData's content + */ +SECStatus +NSS_CMSEnvelopedData_Decode_AfterData(NSSCMSEnvelopedData *envd) +{ + if (envd->contentInfo.ciphcx) { + NSS_CMSCipherContext_Destroy(envd->contentInfo.ciphcx); + envd->contentInfo.ciphcx = NULL; + } + + return SECSuccess; +} + +/* + * NSS_CMSEnvelopedData_Decode_AfterEnd - finish decoding this envelopedData + */ +SECStatus +NSS_CMSEnvelopedData_Decode_AfterEnd(NSSCMSEnvelopedData *envd) +{ + /* apply final touches */ + return SECSuccess; +} + diff --git a/security/nss/lib/smime/cmslocal.h b/security/nss/lib/smime/cmslocal.h new file mode 100644 index 000000000..e7f15c4e1 --- /dev/null +++ b/security/nss/lib/smime/cmslocal.h @@ -0,0 +1,330 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * Support routines for CMS implementation, none of which are exported. + * + * Do not export this file! If something in here is really needed outside + * of smime code, first try to add a CMS interface which will do it for + * you. If that has a problem, then just move out what you need, changing + * its name as appropriate! + * + * $Id$ + */ + +#ifndef _CMSLOCAL_H_ +#define _CMSLOCAL_H_ + +#include "cms.h" +#include "cmsreclist.h" +#include "secasn1t.h" + +extern const SEC_ASN1Template NSSCMSContentInfoTemplate[]; + +/************************************************************************/ +SEC_BEGIN_PROTOS + +/*********************************************************************** + * cmscipher.c - en/decryption routines + ***********************************************************************/ + +/* + * NSS_CMSCipherContext_StartDecrypt - create a cipher context to do decryption + * based on the given bulk * encryption key and algorithm identifier (which may include an iv). + */ +extern NSSCMSCipherContext * +NSS_CMSCipherContext_StartDecrypt(PK11SymKey *key, SECAlgorithmID *algid); + +/* + * NSS_CMSCipherContext_StartEncrypt - create a cipher object to do encryption, + * based on the given bulk encryption key and algorithm tag. Fill in the algorithm + * identifier (which may include an iv) appropriately. + */ +extern NSSCMSCipherContext * +NSS_CMSCipherContext_StartEncrypt(PRArenaPool *poolp, PK11SymKey *key, SECAlgorithmID *algid); + +extern void +NSS_CMSCipherContext_Destroy(NSSCMSCipherContext *cc); + +/* + * NSS_CMSCipherContext_DecryptLength - find the output length of the next call to decrypt. + * + * cc - the cipher context + * input_len - number of bytes used as input + * final - true if this is the final chunk of data + * + * Result can be used to perform memory allocations. Note that the amount + * is exactly accurate only when not doing a block cipher or when final + * is false, otherwise it is an upper bound on the amount because until + * we see the data we do not know how many padding bytes there are + * (always between 1 and bsize). + */ +extern unsigned int +NSS_CMSCipherContext_DecryptLength(NSSCMSCipherContext *cc, unsigned int input_len, PRBool final); + +/* + * NSS_CMSCipherContext_EncryptLength - find the output length of the next call to encrypt. + * + * cc - the cipher context + * input_len - number of bytes used as input + * final - true if this is the final chunk of data + * + * Result can be used to perform memory allocations. + */ +extern unsigned int +NSS_CMSCipherContext_EncryptLength(NSSCMSCipherContext *cc, unsigned int input_len, PRBool final); + +/* + * NSS_CMSCipherContext_Decrypt - do the decryption + * + * cc - the cipher context + * output - buffer for decrypted result bytes + * output_len_p - number of bytes in output + * max_output_len - upper bound on bytes to put into output + * input - pointer to input bytes + * input_len - number of input bytes + * final - true if this is the final chunk of data + * + * Decrypts a given length of input buffer (starting at "input" and + * containing "input_len" bytes), placing the decrypted bytes in + * "output" and storing the output length in "*output_len_p". + * "cc" is the return value from NSS_CMSCipher_StartDecrypt. + * When "final" is true, this is the last of the data to be decrypted. + */ +extern SECStatus +NSS_CMSCipherContext_Decrypt(NSSCMSCipherContext *cc, unsigned char *output, + unsigned int *output_len_p, unsigned int max_output_len, + const unsigned char *input, unsigned int input_len, + PRBool final); + +/* + * NSS_CMSCipherContext_Encrypt - do the encryption + * + * cc - the cipher context + * output - buffer for decrypted result bytes + * output_len_p - number of bytes in output + * max_output_len - upper bound on bytes to put into output + * input - pointer to input bytes + * input_len - number of input bytes + * final - true if this is the final chunk of data + * + * Encrypts a given length of input buffer (starting at "input" and + * containing "input_len" bytes), placing the encrypted bytes in + * "output" and storing the output length in "*output_len_p". + * "cc" is the return value from NSS_CMSCipher_StartEncrypt. + * When "final" is true, this is the last of the data to be encrypted. + */ +extern SECStatus +NSS_CMSCipherContext_Encrypt(NSSCMSCipherContext *cc, unsigned char *output, + unsigned int *output_len_p, unsigned int max_output_len, + const unsigned char *input, unsigned int input_len, + PRBool final); + +/************************************************************************ + * cmspubkey.c - public key operations + ************************************************************************/ + +/* + * NSS_CMSUtil_EncryptSymKey_RSA - wrap a symmetric key with RSA + * + * this function takes a symmetric key and encrypts it using an RSA public key + * according to PKCS#1 and RFC2633 (S/MIME) + */ +extern SECStatus +NSS_CMSUtil_EncryptSymKey_RSA(PLArenaPool *poolp, CERTCertificate *cert, PK11SymKey *key, + SECItem *encKey); + +/* + * NSS_CMSUtil_DecryptSymKey_RSA - unwrap a RSA-wrapped symmetric key + * + * this function takes an RSA-wrapped symmetric key and unwraps it, returning a symmetric + * key handle. Please note that the actual unwrapped key data may not be allowed to leave + * a hardware token... + */ +extern PK11SymKey * +NSS_CMSUtil_DecryptSymKey_RSA(SECKEYPrivateKey *privkey, SECItem *encKey, SECOidTag bulkalgtag); + +extern SECStatus +NSS_CMSUtil_EncryptSymKey_MISSI(PLArenaPool *poolp, CERTCertificate *cert, PK11SymKey *key, + SECOidTag symalgtag, SECItem *encKey, SECItem **pparams, void *pwfn_arg); + +extern PK11SymKey * +NSS_CMSUtil_DecryptSymKey_MISSI(SECKEYPrivateKey *privkey, SECItem *encKey, + SECAlgorithmID *keyEncAlg, SECOidTag bulkalgtag, void *pwfn_arg); + +extern SECStatus +NSS_CMSUtil_EncryptSymKey_ESDH(PLArenaPool *poolp, CERTCertificate *cert, PK11SymKey *key, + SECItem *encKey, SECItem **ukm, SECAlgorithmID *keyEncAlg, + SECItem *originatorPubKey); + +extern PK11SymKey * +NSS_CMSUtil_DecryptSymKey_ESDH(SECKEYPrivateKey *privkey, SECItem *encKey, + SECAlgorithmID *keyEncAlg, SECOidTag bulkalgtag, void *pwfn_arg); + +/************************************************************************ + * cmsreclist.c - recipient list stuff + ************************************************************************/ +extern NSSCMSRecipient **nss_cms_recipient_list_create(NSSCMSRecipientInfo **recipientinfos); +extern void nss_cms_recipient_list_destroy(NSSCMSRecipient **recipient_list); +extern NSSCMSRecipientEncryptedKey *NSS_CMSRecipientEncryptedKey_Create(PLArenaPool *poolp); + +/************************************************************************ + * cmsarray.c - misc array functions + ************************************************************************/ +/* + * NSS_CMSArray_Alloc - allocate an array in an arena + */ +extern void ** +NSS_CMSArray_Alloc(PRArenaPool *poolp, int n); + +/* + * NSS_CMSArray_Add - add an element to the end of an array + */ +extern SECStatus +NSS_CMSArray_Add(PRArenaPool *poolp, void ***array, void *obj); + +/* + * NSS_CMSArray_IsEmpty - check if array is empty + */ +extern PRBool +NSS_CMSArray_IsEmpty(void **array); + +/* + * NSS_CMSArray_Count - count number of elements in array + */ +extern int +NSS_CMSArray_Count(void **array); + +/* + * NSS_CMSArray_Sort - sort an array ascending, in place + * + * If "secondary" is not NULL, the same reordering gets applied to it. + * If "tertiary" is not NULL, the same reordering gets applied to it. + * "compare" is a function that returns + * < 0 when the first element is less than the second + * = 0 when the first element is equal to the second + * > 0 when the first element is greater than the second + */ +extern void +NSS_CMSArray_Sort(void **primary, int (*compare)(void *,void *), void **secondary, void **tertiary); + +/************************************************************************ + * cmsattr.c - misc attribute functions + ************************************************************************/ +/* + * NSS_CMSAttribute_Create - create an attribute + * + * if value is NULL, the attribute won't have a value. It can be added later + * with NSS_CMSAttribute_AddValue. + */ +extern NSSCMSAttribute * +NSS_CMSAttribute_Create(PRArenaPool *poolp, SECOidTag oidtag, SECItem *value, PRBool encoded); + +/* + * NSS_CMSAttribute_AddValue - add another value to an attribute + */ +extern SECStatus +NSS_CMSAttribute_AddValue(PLArenaPool *poolp, NSSCMSAttribute *attr, SECItem *value); + +/* + * NSS_CMSAttribute_GetType - return the OID tag + */ +extern SECOidTag +NSS_CMSAttribute_GetType(NSSCMSAttribute *attr); + +/* + * NSS_CMSAttribute_GetValue - return the first attribute value + * + * We do some sanity checking first: + * - Multiple values are *not* expected. + * - Empty values are *not* expected. + */ +extern SECItem * +NSS_CMSAttribute_GetValue(NSSCMSAttribute *attr); + +/* + * NSS_CMSAttribute_CompareValue - compare the attribute's first value against data + */ +extern PRBool +NSS_CMSAttribute_CompareValue(NSSCMSAttribute *attr, SECItem *av); + +/* + * NSS_CMSAttributeArray_Encode - encode an Attribute array as SET OF Attributes + * + * If you are wondering why this routine does not reorder the attributes + * first, and might be tempted to make it do so, see the comment by the + * call to ReorderAttributes in cmsencode.c. (Or, see who else calls this + * and think long and hard about the implications of making it always + * do the reordering.) + */ +extern SECItem * +NSS_CMSAttributeArray_Encode(PRArenaPool *poolp, NSSCMSAttribute ***attrs, SECItem *dest); + +/* + * NSS_CMSAttributeArray_Reorder - sort attribute array by attribute's DER encoding + * + * make sure that the order of the attributes guarantees valid DER (which must be + * in lexigraphically ascending order for a SET OF); if reordering is necessary it + * will be done in place (in attrs). + */ +extern SECStatus +NSS_CMSAttributeArray_Reorder(NSSCMSAttribute **attrs); + +/* + * NSS_CMSAttributeArray_FindAttrByOidTag - look through a set of attributes and + * find one that matches the specified object ID. + * + * If "only" is true, then make sure that there is not more than one attribute + * of the same type. Otherwise, just return the first one found. (XXX Does + * anybody really want that first-found behavior? It was like that when I found it...) + */ +extern NSSCMSAttribute * +NSS_CMSAttributeArray_FindAttrByOidTag(NSSCMSAttribute **attrs, SECOidTag oidtag, PRBool only); + +/* + * NSS_CMSAttributeArray_AddAttr - add an attribute to an + * array of attributes. + */ +extern SECStatus +NSS_CMSAttributeArray_AddAttr(PLArenaPool *poolp, NSSCMSAttribute ***attrs, NSSCMSAttribute *attr); + +/* + * NSS_CMSAttributeArray_SetAttr - set an attribute's value in a set of attributes + */ +extern SECStatus +NSS_CMSAttributeArray_SetAttr(PLArenaPool *poolp, NSSCMSAttribute ***attrs, SECOidTag type, SECItem *value, PRBool encoded); + +/************************************************************************/ +SEC_END_PROTOS + +#endif /* _CMSLOCAL_H_ */ diff --git a/security/nss/lib/smime/cmsmessage.c b/security/nss/lib/smime/cmsmessage.c new file mode 100644 index 000000000..a25445627 --- /dev/null +++ b/security/nss/lib/smime/cmsmessage.c @@ -0,0 +1,313 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * CMS message methods. + * + * $Id$ + */ + +#include "cmslocal.h" + +#include "cert.h" +#include "secasn1.h" +#include "secitem.h" +#include "secoid.h" +#include "pk11func.h" +#include "secerr.h" + +/* + * NSS_CMSMessage_Create - create a CMS message object + * + * "poolp" - arena to allocate memory from, or NULL if new arena should be created + */ +NSSCMSMessage * +NSS_CMSMessage_Create(PLArenaPool *poolp) +{ + void *mark; + NSSCMSMessage *cmsg; + PRBool poolp_is_ours = PR_FALSE; + + if (poolp == NULL) { + poolp = PORT_NewArena (1024); /* XXX what is right value? */ + if (poolp == NULL) + return NULL; + poolp_is_ours = PR_TRUE; + } + + if (!poolp_is_ours) + mark = PORT_ArenaMark(poolp); + + cmsg = (NSSCMSMessage *)PORT_ArenaZAlloc (poolp, sizeof(NSSCMSMessage)); + if (cmsg == NULL) { + if (!poolp_is_ours) + PORT_ArenaRelease(poolp, mark); + else + PORT_FreeArena(poolp, PR_FALSE); + return NULL; + } + + cmsg->poolp = poolp; + cmsg->poolp_is_ours = poolp_is_ours; + cmsg->refCount = 1; + + if (!poolp_is_ours) + PORT_ArenaUnmark(poolp, mark); + + return cmsg; +} + +/* + * NSS_CMSMessage_SetEncodingParams - set up a CMS message object for encoding or decoding + * + * "cmsg" - message object + * "pwfn", pwfn_arg" - callback function for getting token password + * "decrypt_key_cb", "decrypt_key_cb_arg" - callback function for getting bulk key for encryptedData + * "detached_digestalgs", "detached_digests" - digests from detached content + */ +void +NSS_CMSMessage_SetEncodingParams(NSSCMSMessage *cmsg, + PK11PasswordFunc pwfn, void *pwfn_arg, + NSSCMSGetDecryptKeyCallback decrypt_key_cb, void *decrypt_key_cb_arg, + SECAlgorithmID **detached_digestalgs, SECItem **detached_digests) +{ + if (pwfn) + PK11_SetPasswordFunc(pwfn); + cmsg->pwfn_arg = pwfn_arg; + cmsg->decrypt_key_cb = decrypt_key_cb; + cmsg->decrypt_key_cb_arg = decrypt_key_cb_arg; + cmsg->detached_digestalgs = detached_digestalgs; + cmsg->detached_digests = detached_digests; +} + +/* + * NSS_CMSMessage_Destroy - destroy a CMS message and all of its sub-pieces. + */ +void +NSS_CMSMessage_Destroy(NSSCMSMessage *cmsg) +{ + PORT_Assert (cmsg->refCount > 0); + if (cmsg->refCount <= 0) /* oops */ + return; + + cmsg->refCount--; /* thread safety? */ + if (cmsg->refCount > 0) + return; + + NSS_CMSContentInfo_Destroy(&(cmsg->contentInfo)); + + /* if poolp is not NULL, cmsg is the owner of its arena */ + if (cmsg->poolp_is_ours) + PORT_FreeArena (cmsg->poolp, PR_FALSE); /* XXX clear it? */ +} + +/* + * NSS_CMSMessage_Copy - return a copy of the given message. + * + * The copy may be virtual or may be real -- either way, the result needs + * to be passed to NSS_CMSMessage_Destroy later (as does the original). + */ +NSSCMSMessage * +NSS_CMSMessage_Copy(NSSCMSMessage *cmsg) +{ + if (cmsg == NULL) + return NULL; + + PORT_Assert (cmsg->refCount > 0); + + cmsg->refCount++; /* XXX chrisk thread safety? */ + return cmsg; +} + +/* + * NSS_CMSMessage_GetArena - return a pointer to the message's arena pool + */ +PLArenaPool * +NSS_CMSMessage_GetArena(NSSCMSMessage *cmsg) +{ + return cmsg->poolp; +} + +/* + * NSS_CMSMessage_GetContentInfo - return a pointer to the top level contentInfo + */ +NSSCMSContentInfo * +NSS_CMSMessage_GetContentInfo(NSSCMSMessage *cmsg) +{ + return &(cmsg->contentInfo); +} + +/* + * Return a pointer to the actual content. + * In the case of those types which are encrypted, this returns the *plain* content. + * In case of nested contentInfos, this descends and retrieves the innermost content. + */ +SECItem * +NSS_CMSMessage_GetContent(NSSCMSMessage *cmsg) +{ + /* this is a shortcut */ + return NSS_CMSContentInfo_GetInnerContent(NSS_CMSMessage_GetContentInfo(cmsg)); +} + +/* + * NSS_CMSMessage_ContentLevelCount - count number of levels of CMS content objects in this message + * + * CMS data content objects do not count. + */ +int +NSS_CMSMessage_ContentLevelCount(NSSCMSMessage *cmsg) +{ + int count = 0; + NSSCMSContentInfo *cinfo; + + /* walk down the chain of contentinfos */ + for (cinfo = &(cmsg->contentInfo); cinfo != NULL; cinfo = NSS_CMSContentInfo_GetChildContentInfo(cinfo)) { + count++; + } + return count; +} + +/* + * NSS_CMSMessage_ContentLevel - find content level #n + * + * CMS data content objects do not count. + */ +NSSCMSContentInfo * +NSS_CMSMessage_ContentLevel(NSSCMSMessage *cmsg, int n) +{ + int count = 0; + NSSCMSContentInfo *cinfo; + + /* walk down the chain of contentinfos */ + for (cinfo = &(cmsg->contentInfo); cinfo != NULL && count < n; cinfo = NSS_CMSContentInfo_GetChildContentInfo(cinfo)) { + count++; + } + + return cinfo; +} + +/* + * NSS_CMSMessage_ContainsCertsOrCrls - see if message contains certs along the way + */ +PRBool +NSS_CMSMessage_ContainsCertsOrCrls(NSSCMSMessage *cmsg) +{ + NSSCMSContentInfo *cinfo; + + /* descend into CMS message */ + for (cinfo = &(cmsg->contentInfo); cinfo != NULL; cinfo = NSS_CMSContentInfo_GetChildContentInfo(cinfo)) { + if (NSS_CMSContentInfo_GetContentTypeTag(cinfo) != SEC_OID_PKCS7_SIGNED_DATA) + continue; /* next level */ + + if (NSS_CMSSignedData_ContainsCertsOrCrls(cinfo->content.signedData)) + return PR_TRUE; + } + return PR_FALSE; +} + +/* + * NSS_CMSMessage_IsEncrypted - see if message contains a encrypted submessage + */ +PRBool +NSS_CMSMessage_IsEncrypted(NSSCMSMessage *cmsg) +{ + NSSCMSContentInfo *cinfo; + + /* walk down the chain of contentinfos */ + for (cinfo = &(cmsg->contentInfo); cinfo != NULL; cinfo = NSS_CMSContentInfo_GetChildContentInfo(cinfo)) + { + switch (NSS_CMSContentInfo_GetContentTypeTag(cinfo)) { + case SEC_OID_PKCS7_ENVELOPED_DATA: + case SEC_OID_PKCS7_ENCRYPTED_DATA: + return PR_TRUE; + default: + break; + } + } + return PR_FALSE; +} + +/* + * NSS_CMSMessage_IsSigned - see if message contains a signed submessage + * + * If the CMS message has a SignedData with a signature (not just a SignedData) + * return true; false otherwise. This can/should be called before calling + * VerifySignature, which will always indicate failure if no signature is + * present, but that does not mean there even was a signature! + * Note that the content itself can be empty (detached content was sent + * another way); it is the presence of the signature that matters. + */ +PRBool +NSS_CMSMessage_IsSigned(NSSCMSMessage *cmsg) +{ + NSSCMSContentInfo *cinfo; + + /* walk down the chain of contentinfos */ + for (cinfo = &(cmsg->contentInfo); cinfo != NULL; cinfo = NSS_CMSContentInfo_GetChildContentInfo(cinfo)) + { + switch (NSS_CMSContentInfo_GetContentTypeTag(cinfo)) { + case SEC_OID_PKCS7_SIGNED_DATA: + if (!NSS_CMSArray_IsEmpty((void **)cinfo->content.signedData->signerInfos)) + return PR_TRUE; + break; + default: + break; + } + } + return PR_FALSE; +} + +/* + * NSS_CMSMessage_IsContentEmpty - see if content is empty + * + * returns PR_TRUE is innermost content length is < minLen + * XXX need the encrypted content length (why?) + */ +PRBool +NSS_CMSMessage_IsContentEmpty(NSSCMSMessage *cmsg, unsigned int minLen) +{ + SECItem *item = NULL; + + if (cmsg == NULL) + return PR_TRUE; + + item = NSS_CMSContentInfo_GetContent(NSS_CMSMessage_GetContentInfo(cmsg)); + + if (!item) { + return PR_TRUE; + } else if(item->len <= minLen) { + return PR_TRUE; + } + + return PR_FALSE; +} diff --git a/security/nss/lib/smime/cmspubkey.c b/security/nss/lib/smime/cmspubkey.c new file mode 100644 index 000000000..9654ef26c --- /dev/null +++ b/security/nss/lib/smime/cmspubkey.c @@ -0,0 +1,531 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * CMS public key crypto + * + * $Id$ + */ + +#include "cmslocal.h" + +#include "cert.h" +#include "key.h" +#include "secasn1.h" +#include "secitem.h" +#include "secoid.h" +#include "pk11func.h" +#include "secerr.h" + +/* ====== RSA ======================================================================= */ + +/* + * NSS_CMSUtil_EncryptSymKey_RSA - wrap a symmetric key with RSA + * + * this function takes a symmetric key and encrypts it using an RSA public key + * according to PKCS#1 and RFC2633 (S/MIME) + */ +SECStatus +NSS_CMSUtil_EncryptSymKey_RSA(PLArenaPool *poolp, CERTCertificate *cert, PK11SymKey *bulkkey, + SECItem *encKey) +{ + SECOidTag certalgtag; /* the certificate's encryption algorithm */ + SECOidTag encalgtag; /* the algorithm used for key exchange/agreement */ + SECStatus rv; + SECKEYPublicKey *publickey; + int data_len; + void *mark; + + /* sanity check */ + certalgtag = SECOID_GetAlgorithmTag(&(cert->subjectPublicKeyInfo.algorithm)); + PORT_Assert(certalgtag == SEC_OID_PKCS1_RSA_ENCRYPTION); + + encalgtag = SEC_OID_PKCS1_RSA_ENCRYPTION; + publickey = CERT_ExtractPublicKey(cert); + if (publickey == NULL) + goto loser; + + mark = PORT_ArenaMark(poolp); + + /* allocate memory for the encrypted key */ + data_len = SECKEY_PublicKeyStrength(publickey); /* block size (assumed to be > keylen) */ + encKey->data = (unsigned char*)PORT_ArenaAlloc(poolp, data_len); + encKey->len = data_len; + if (encKey->data == NULL) + goto loser; + + /* encrypt the key now */ + rv = PK11_PubWrapSymKey(PK11_AlgtagToMechanism(SEC_OID_PKCS1_RSA_ENCRYPTION), + publickey, bulkkey, encKey); + + SECKEY_DestroyPublicKey(publickey); + if (rv != SECSuccess) + goto loser; + + PORT_ArenaUnmark(poolp, mark); + return SECSuccess; + +loser: + PORT_ArenaRelease(poolp, mark); + return SECFailure; +} + +/* + * NSS_CMSUtil_DecryptSymKey_RSA - unwrap a RSA-wrapped symmetric key + * + * this function takes an RSA-wrapped symmetric key and unwraps it, returning a symmetric + * key handle. Please note that the actual unwrapped key data may not be allowed to leave + * a hardware token... + */ +PK11SymKey * +NSS_CMSUtil_DecryptSymKey_RSA(SECKEYPrivateKey *privkey, SECItem *encKey, SECOidTag bulkalgtag) +{ + /* that's easy */ + return PK11_PubUnwrapSymKey(privkey, encKey, PK11_AlgtagToMechanism(bulkalgtag), CKA_DECRYPT, 0); +} + +/* ====== MISSI (Fortezza) ========================================================== */ + +extern const SEC_ASN1Template NSS_SMIMEKEAParamTemplateAllParams[]; + +SECStatus +NSS_CMSUtil_EncryptSymKey_MISSI(PLArenaPool *poolp, CERTCertificate *cert, PK11SymKey *bulkkey, + SECOidTag symalgtag, SECItem *encKey, SECItem **pparams, void *pwfn_arg) +{ + SECOidTag certalgtag; /* the certificate's encryption algorithm */ + SECOidTag encalgtag; /* the algorithm used for key exchange/agreement */ + SECStatus rv = SECFailure; + SECItem *params = NULL; + SECStatus err; + PK11SymKey *tek; + CERTCertificate *ourCert; + SECKEYPublicKey *ourPubKey, *publickey = NULL; + SECKEYPrivateKey *ourPrivKey = NULL; + NSSCMSKEATemplateSelector whichKEA; + NSSCMSSMIMEKEAParameters keaParams; + PLArenaPool *arena; + extern const SEC_ASN1Template *nss_cms_get_kea_template(NSSCMSKEATemplateSelector whichTemplate); + + /* Clear keaParams, since cleanup code checks the lengths */ + (void) memset(&keaParams, 0, sizeof(keaParams)); + + certalgtag = SECOID_GetAlgorithmTag(&(cert->subjectPublicKeyInfo.algorithm)); + PORT_Assert(certalgtag == SEC_OID_MISSI_KEA_DSS_OLD || + certalgtag == SEC_OID_MISSI_KEA_DSS || + certalgtag == SEC_OID_MISSI_KEA); + +#define SMIME_FORTEZZA_RA_LENGTH 128 +#define SMIME_FORTEZZA_IV_LENGTH 24 +#define SMIME_FORTEZZA_MAX_KEY_SIZE 256 + + /* We really want to show our KEA tag as the key exchange algorithm tag. */ + encalgtag = SEC_OID_NETSCAPE_SMIME_KEA; + + /* Get the public key of the recipient. */ + publickey = CERT_ExtractPublicKey(cert); + if (publickey == NULL) goto loser; + + /* Find our own cert, and extract its keys. */ + ourCert = PK11_FindBestKEAMatch(cert, pwfn_arg); + if (ourCert == NULL) goto loser; + + arena = PORT_NewArena(1024); + if (arena == NULL) + goto loser; + + ourPubKey = CERT_ExtractPublicKey(ourCert); + if (ourPubKey == NULL) { + CERT_DestroyCertificate(ourCert); + goto loser; + } + + /* While we're here, copy the public key into the outgoing + * KEA parameters. */ + SECITEM_CopyItem(arena, &(keaParams.originatorKEAKey), &(ourPubKey->u.fortezza.KEAKey)); + SECKEY_DestroyPublicKey(ourPubKey); + ourPubKey = NULL; + + /* Extract our private key in order to derive the KEA key. */ + ourPrivKey = PK11_FindKeyByAnyCert(ourCert, pwfn_arg); + CERT_DestroyCertificate(ourCert); /* we're done with this */ + if (!ourPrivKey) + goto loser; + + /* Prepare raItem with 128 bytes (filled with zeros). */ + keaParams.originatorRA.data = (unsigned char *)PORT_ArenaAlloc(arena,SMIME_FORTEZZA_RA_LENGTH); + keaParams.originatorRA.len = SMIME_FORTEZZA_RA_LENGTH; + + /* Generate the TEK (token exchange key) which we use + * to wrap the bulk encryption key. (keaparams.originatorRA) will be + * filled with a random seed which we need to send to + * the recipient. (user keying material in RFC2630/DSA speak) */ + tek = PK11_PubDerive(ourPrivKey, publickey, PR_TRUE, + &keaParams.originatorRA, NULL, + CKM_KEA_KEY_DERIVE, CKM_SKIPJACK_WRAP, + CKA_WRAP, 0, pwfn_arg); + + SECKEY_DestroyPublicKey(publickey); + SECKEY_DestroyPrivateKey(ourPrivKey); + publickey = NULL; + ourPrivKey = NULL; + + if (!tek) + goto loser; + + /* allocate space for the wrapped key data */ + encKey->data = (unsigned char *)PORT_ArenaAlloc(poolp, SMIME_FORTEZZA_MAX_KEY_SIZE); + encKey->len = SMIME_FORTEZZA_MAX_KEY_SIZE; + + if (encKey->data == NULL) { + PK11_FreeSymKey(tek); + goto loser; + } + + /* Wrap the bulk key. What we do with the resulting data + depends on whether we're using Skipjack to wrap the key. */ + switch (PK11_AlgtagToMechanism(symalgtag)) { + case CKM_SKIPJACK_CBC64: + case CKM_SKIPJACK_ECB64: + case CKM_SKIPJACK_OFB64: + case CKM_SKIPJACK_CFB64: + case CKM_SKIPJACK_CFB32: + case CKM_SKIPJACK_CFB16: + case CKM_SKIPJACK_CFB8: + /* SKIPJACK, we use the wrap mechanism because we can do it on the hardware */ + err = PK11_WrapSymKey(CKM_SKIPJACK_WRAP, NULL, tek, bulkkey, encKey); + whichKEA = NSSCMSKEAUsesSkipjack; + break; + default: + /* Not SKIPJACK, we encrypt the raw key data */ + keaParams.nonSkipjackIV.data = + (unsigned char *)PORT_ArenaAlloc(arena, SMIME_FORTEZZA_IV_LENGTH); + keaParams.nonSkipjackIV.len = SMIME_FORTEZZA_IV_LENGTH; + err = PK11_WrapSymKey(CKM_SKIPJACK_CBC64, &keaParams.nonSkipjackIV, tek, bulkkey, encKey); + if (err != SECSuccess) + goto loser; + + if (encKey->len != PK11_GetKeyLength(bulkkey)) { + /* The size of the encrypted key is not the same as + that of the original bulk key, presumably due to + padding. Encode and store the real size of the + bulk key. */ + if (SEC_ASN1EncodeInteger(arena, &keaParams.bulkKeySize, PK11_GetKeyLength(bulkkey)) == NULL) + err = (SECStatus)PORT_GetError(); + else + /* use full template for encoding */ + whichKEA = NSSCMSKEAUsesNonSkipjackWithPaddedEncKey; + } + else + /* enc key length == bulk key length */ + whichKEA = NSSCMSKEAUsesNonSkipjack; + break; + } + + PK11_FreeSymKey(tek); + if (err != SECSuccess) + goto loser; + + /* Encode the KEA parameters into the recipient info. */ + params = SEC_ASN1EncodeItem(poolp, NULL, &keaParams, nss_cms_get_kea_template(whichKEA)); + if (params == NULL) + goto loser; + + /* pass back the algorithm params */ + *pparams = params; + + rv = SECSuccess; + +loser: + if (arena) + PORT_FreeArena(arena, PR_FALSE); + if (publickey) + SECKEY_DestroyPublicKey(publickey); + if (ourPrivKey) + SECKEY_DestroyPrivateKey(ourPrivKey); + return rv; +} + +PK11SymKey * +NSS_CMSUtil_DecryptSymKey_MISSI(SECKEYPrivateKey *privkey, SECItem *encKey, SECAlgorithmID *keyEncAlg, SECOidTag bulkalgtag, void *pwfn_arg) +{ + /* fortezza: do a key exchange */ + SECStatus err; + CK_MECHANISM_TYPE bulkType; + PK11SymKey *tek; + SECKEYPublicKey *originatorPubKey; + NSSCMSSMIMEKEAParameters keaParams; + PK11SymKey *bulkkey; + int bulkLength; + + (void) memset(&keaParams, 0, sizeof(keaParams)); + + /* NOTE: this uses the SMIME v2 recipientinfo for compatibility. + All additional KEA parameters are DER-encoded in the encryption algorithm parameters */ + + /* Decode the KEA algorithm parameters. */ + err = SEC_ASN1DecodeItem(NULL, &keaParams, NSS_SMIMEKEAParamTemplateAllParams, + &(keyEncAlg->parameters)); + if (err != SECSuccess) + goto loser; + + /* get originator's public key */ + originatorPubKey = PK11_MakeKEAPubKey(keaParams.originatorKEAKey.data, + keaParams.originatorKEAKey.len); + if (originatorPubKey == NULL) + goto loser; + + /* Generate the TEK (token exchange key) which we use to unwrap the bulk encryption key. + The Derive function generates a shared secret and combines it with the originatorRA + data to come up with an unique session key */ + tek = PK11_PubDerive(privkey, originatorPubKey, PR_FALSE, + &keaParams.originatorRA, NULL, + CKM_KEA_KEY_DERIVE, CKM_SKIPJACK_WRAP, + CKA_WRAP, 0, pwfn_arg); + SECKEY_DestroyPublicKey(originatorPubKey); /* not needed anymore */ + if (tek == NULL) + goto loser; + + /* Now that we have the TEK, unwrap the bulk key + with which to decrypt the message. We have to + do one of two different things depending on + whether Skipjack was used for *bulk* encryption + of the message. */ + bulkType = PK11_AlgtagToMechanism(bulkalgtag); + switch (bulkType) { + case CKM_SKIPJACK_CBC64: + case CKM_SKIPJACK_ECB64: + case CKM_SKIPJACK_OFB64: + case CKM_SKIPJACK_CFB64: + case CKM_SKIPJACK_CFB32: + case CKM_SKIPJACK_CFB16: + case CKM_SKIPJACK_CFB8: + /* Skipjack is being used as the bulk encryption algorithm.*/ + /* Unwrap the bulk key. */ + bulkkey = PK11_UnwrapSymKey(tek, CKM_SKIPJACK_WRAP, NULL, + encKey, CKM_SKIPJACK_CBC64, CKA_DECRYPT, 0); + break; + default: + /* Skipjack was not used for bulk encryption of this + message. Use Skipjack CBC64, with the nonSkipjackIV + part of the KEA key parameters, to decrypt + the bulk key. If the optional parameter bulkKeySize is present, + bulk key size is different than the encrypted key size */ + if (keaParams.bulkKeySize.len > 0) { + err = SEC_ASN1DecodeItem(NULL, &bulkLength, + SEC_IntegerTemplate, + &keaParams.bulkKeySize); + if (err != SECSuccess) + goto loser; + } + + bulkkey = PK11_UnwrapSymKey(tek, CKM_SKIPJACK_CBC64, &keaParams.nonSkipjackIV, + encKey, bulkType, CKA_DECRYPT, bulkLength); + break; + } + return bulkkey; +loser: + return NULL; +} + +/* ====== ESDH (Ephemeral-Static Diffie-Hellman) ==================================== */ + +SECStatus +NSS_CMSUtil_EncryptSymKey_ESDH(PLArenaPool *poolp, CERTCertificate *cert, PK11SymKey *key, + SECItem *encKey, SECItem **ukm, SECAlgorithmID *keyEncAlg, + SECItem *pubKey) +{ +#if 0 /* not yet done */ + SECOidTag certalgtag; /* the certificate's encryption algorithm */ + SECOidTag encalgtag; /* the algorithm used for key exchange/agreement */ + SECStatus rv; + SECItem *params = NULL; + int data_len; + SECStatus err; + PK11SymKey *tek; + CERTCertificate *ourCert; + SECKEYPublicKey *ourPubKey; + NSSCMSKEATemplateSelector whichKEA; + + certalgtag = SECOID_GetAlgorithmTag(&(cert->subjectPublicKeyInfo.algorithm)); + PORT_Assert(certalgtag == SEC_OID_X942_DIFFIE_HELMAN_KEY); + + /* We really want to show our KEA tag as the key exchange algorithm tag. */ + encalgtag = SEC_OID_CMS_EPHEMERAL_STATIC_DIFFIE_HELLMAN; + + /* Get the public key of the recipient. */ + publickey = CERT_ExtractPublicKey(cert); + if (publickey == NULL) goto loser; + + /* XXXX generate a DH key pair on a PKCS11 module (XXX which parameters?) */ + /* XXXX */ourCert = PK11_FindBestKEAMatch(cert, wincx); + if (ourCert == NULL) goto loser; + + arena = PORT_NewArena(1024); + if (arena == NULL) goto loser; + + /* While we're here, extract the key pair's public key data and copy it into */ + /* the outgoing parameters. */ + /* XXXX */ourPubKey = CERT_ExtractPublicKey(ourCert); + if (ourPubKey == NULL) + { + goto loser; + } + SECITEM_CopyItem(arena, pubKey, /* XXX */&(ourPubKey->u.fortezza.KEAKey)); + SECKEY_DestroyPublicKey(ourPubKey); /* we only need the private key from now on */ + ourPubKey = NULL; + + /* Extract our private key in order to derive the KEA key. */ + ourPrivKey = PK11_FindKeyByAnyCert(ourCert,wincx); + CERT_DestroyCertificate(ourCert); /* we're done with this */ + if (!ourPrivKey) goto loser; + + /* If ukm desired, prepare it - allocate enough space (filled with zeros). */ + if (ukm) { + ukm->data = (unsigned char*)PORT_ArenaZAlloc(arena,/* XXXX */); + ukm->len = /* XXXX */; + } + + /* Generate the KEK (key exchange key) according to RFC2631 which we use + * to wrap the bulk encryption key. */ + kek = PK11_PubDerive(ourPrivKey, publickey, PR_TRUE, + ukm, NULL, + /* XXXX */CKM_KEA_KEY_DERIVE, /* XXXX */CKM_SKIPJACK_WRAP, + CKA_WRAP, 0, wincx); + + SECKEY_DestroyPublicKey(publickey); + SECKEY_DestroyPrivateKey(ourPrivKey); + publickey = NULL; + ourPrivKey = NULL; + + if (!kek) + goto loser; + + /* allocate space for the encrypted CEK (bulk key) */ + encKey->data = (unsigned char*)PORT_ArenaAlloc(poolp, SMIME_FORTEZZA_MAX_KEY_SIZE); + encKey->len = SMIME_FORTEZZA_MAX_KEY_SIZE; + + if (encKey->data == NULL) + { + PK11_FreeSymKey(kek); + goto loser; + } + + + /* Wrap the bulk key using CMSRC2WRAP or CMS3DESWRAP, depending on the */ + /* bulk encryption algorithm */ + switch (/* XXXX */PK11_AlgtagToMechanism(enccinfo->encalg)) + { + case /* XXXX */CKM_SKIPJACK_CFB8: + err = PK11_WrapSymKey(/* XXXX */CKM_CMS3DES_WRAP, NULL, kek, bulkkey, encKey); + whichKEA = NSSCMSKEAUsesSkipjack; + break; + case /* XXXX */CKM_SKIPJACK_CFB8: + err = PK11_WrapSymKey(/* XXXX */CKM_CMSRC2_WRAP, NULL, kek, bulkkey, encKey); + whichKEA = NSSCMSKEAUsesSkipjack; + break; + default: + /* XXXX what do we do here? Neither RC2 nor 3DES... */ + break; + } + + PK11_FreeSymKey(kek); /* we do not need the KEK anymore */ + if (err != SECSuccess) + goto loser; + + /* see RFC2630 12.3.1.1 "keyEncryptionAlgorithm must be ..." */ + /* params is the DER encoded key wrap algorithm (with parameters!) (XXX) */ + params = SEC_ASN1EncodeItem(arena, NULL, &keaParams, sec_pkcs7_get_kea_template(whichKEA)); + if (params == NULL) + goto loser; + + /* now set keyEncAlg */ + rv = SECOID_SetAlgorithmID(poolp, keyEncAlg, SEC_OID_CMS_EPHEMERAL_STATIC_DIFFIE_HELLMAN, params); + if (rv != SECSuccess) + goto loser; + + /* XXXXXXX this is not right yet */ +loser: + if (arena) { + PORT_FreeArena(arena, PR_FALSE); + } + if (publickey) { + SECKEY_DestroyPublicKey(publickey); + } + if (ourPrivKey) { + SECKEY_DestroyPrivateKey(ourPrivKey); + } +#endif + return SECFailure; +} + +PK11SymKey * +NSS_CMSUtil_DecryptSymKey_ESDH(SECKEYPrivateKey *privkey, SECItem *encKey, SECAlgorithmID *keyEncAlg, SECOidTag bulkalgtag, void *pwfn_arg) +{ +#if 0 /* not yet done */ + SECStatus err; + CK_MECHANISM_TYPE bulkType; + PK11SymKey *tek; + SECKEYPublicKey *originatorPubKey; + NSSCMSSMIMEKEAParameters keaParams; + + /* XXXX get originator's public key */ + originatorPubKey = PK11_MakeKEAPubKey(keaParams.originatorKEAKey.data, + keaParams.originatorKEAKey.len); + if (originatorPubKey == NULL) + goto loser; + + /* Generate the TEK (token exchange key) which we use to unwrap the bulk encryption key. + The Derive function generates a shared secret and combines it with the originatorRA + data to come up with an unique session key */ + tek = PK11_PubDerive(privkey, originatorPubKey, PR_FALSE, + &keaParams.originatorRA, NULL, + CKM_KEA_KEY_DERIVE, CKM_SKIPJACK_WRAP, + CKA_WRAP, 0, pwfn_arg); + SECKEY_DestroyPublicKey(originatorPubKey); /* not needed anymore */ + if (tek == NULL) + goto loser; + + /* Now that we have the TEK, unwrap the bulk key + with which to decrypt the message. */ + /* Skipjack is being used as the bulk encryption algorithm.*/ + /* Unwrap the bulk key. */ + bulkkey = PK11_UnwrapSymKey(tek, CKM_SKIPJACK_WRAP, NULL, + encKey, CKM_SKIPJACK_CBC64, CKA_DECRYPT, 0); + + return bulkkey; + +loser: +#endif + return NULL; +} + diff --git a/security/nss/lib/smime/cmsrecinfo.c b/security/nss/lib/smime/cmsrecinfo.c new file mode 100644 index 000000000..45ac16658 --- /dev/null +++ b/security/nss/lib/smime/cmsrecinfo.c @@ -0,0 +1,421 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * CMS recipientInfo methods. + * + * $Id$ + */ + +#include "cmslocal.h" + +#include "cert.h" +#include "key.h" +#include "secasn1.h" +#include "secitem.h" +#include "secoid.h" +#include "pk11func.h" +#include "secerr.h" + +/* + * NSS_CMSRecipientInfo_Create - create a recipientinfo + * + * we currently do not create KeyAgreement recipientinfos with multiple recipientEncryptedKeys + * the certificate is supposed to have been verified by the caller + */ +NSSCMSRecipientInfo * +NSS_CMSRecipientInfo_Create(NSSCMSMessage *cmsg, CERTCertificate *cert) +{ + NSSCMSRecipientInfo *ri; + void *mark; + SECOidTag certalgtag; + SECStatus rv; + NSSCMSRecipientEncryptedKey *rek; + NSSCMSOriginatorIdentifierOrKey *oiok; + unsigned long version; + SECItem *dummy; + PLArenaPool *poolp; + + poolp = cmsg->poolp; + + mark = PORT_ArenaMark(poolp); + + ri = (NSSCMSRecipientInfo *)PORT_ArenaZAlloc(poolp, sizeof(NSSCMSRecipientInfo)); + if (ri == NULL) + goto loser; + + ri->cmsg = cmsg; + ri->cert = CERT_DupCertificate(cert); + if (ri->cert == NULL) + goto loser; + + certalgtag = SECOID_GetAlgorithmTag(&(cert->subjectPublicKeyInfo.algorithm)); + + switch (certalgtag) { + case SEC_OID_PKCS1_RSA_ENCRYPTION: + ri->recipientInfoType = NSSCMSRecipientInfoID_KeyTrans; + /* hardcoded issuerSN choice for now */ + ri->ri.keyTransRecipientInfo.recipientIdentifier.identifierType = NSSCMSRecipientID_IssuerSN; + ri->ri.keyTransRecipientInfo.recipientIdentifier.id.issuerAndSN = CERT_GetCertIssuerAndSN(poolp, cert); + if (ri->ri.keyTransRecipientInfo.recipientIdentifier.id.issuerAndSN == NULL) { + rv = SECFailure; + break; + } + break; + case SEC_OID_MISSI_KEA_DSS_OLD: + case SEC_OID_MISSI_KEA_DSS: + case SEC_OID_MISSI_KEA: + /* backward compatibility - this is not really a keytrans operation */ + ri->recipientInfoType = NSSCMSRecipientInfoID_KeyTrans; + /* hardcoded issuerSN choice for now */ + ri->ri.keyTransRecipientInfo.recipientIdentifier.identifierType = NSSCMSRecipientID_IssuerSN; + ri->ri.keyTransRecipientInfo.recipientIdentifier.id.issuerAndSN = CERT_GetCertIssuerAndSN(poolp, cert); + if (ri->ri.keyTransRecipientInfo.recipientIdentifier.id.issuerAndSN == NULL) { + rv = SECFailure; + break; + } + break; + case SEC_OID_X942_DIFFIE_HELMAN_KEY: /* dh-public-number */ + /* a key agreement op */ + ri->recipientInfoType = NSSCMSRecipientInfoID_KeyAgree; + + if (ri->ri.keyTransRecipientInfo.recipientIdentifier.id.issuerAndSN == NULL) { + rv = SECFailure; + break; + } + /* we do not support the case where multiple recipients + * share the same KeyAgreeRecipientInfo and have multiple RecipientEncryptedKeys + * in this case, we would need to walk all the recipientInfos, take the + * ones that do KeyAgreement algorithms and join them, algorithm by algorithm + * Then, we'd generate ONE ukm and OriginatorIdentifierOrKey */ + + /* only epheremal-static Diffie-Hellman is supported for now + * this is the only form of key agreement that provides potential anonymity + * of the sender, plus we do not have to include certs in the message */ + + /* force single recipientEncryptedKey for now */ + if ((rek = NSS_CMSRecipientEncryptedKey_Create(poolp)) == NULL) { + rv = SECFailure; + break; + } + + /* hardcoded IssuerSN choice for now */ + rek->recipientIdentifier.identifierType = NSSCMSKeyAgreeRecipientID_IssuerSN; + if ((rek->recipientIdentifier.id.issuerAndSN = CERT_GetCertIssuerAndSN(poolp, cert)) == NULL) { + rv = SECFailure; + break; + } + + oiok = &(ri->ri.keyAgreeRecipientInfo.originatorIdentifierOrKey); + + /* see RFC2630 12.3.1.1 */ + oiok->identifierType = NSSCMSOriginatorIDOrKey_OriginatorPublicKey; + + rv = NSS_CMSArray_Add(poolp, (void ***)&ri->ri.keyAgreeRecipientInfo.recipientEncryptedKeys, + (void *)rek); + + break; + default: + /* other algorithms not supported yet */ + /* NOTE that we do not support any KEK algorithm */ + PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); + rv = SECFailure; + break; + } + + if (rv == SECFailure) + goto loser; + + /* set version */ + switch (ri->recipientInfoType) { + case NSSCMSRecipientInfoID_KeyTrans: + if (ri->ri.keyTransRecipientInfo.recipientIdentifier.identifierType == NSSCMSRecipientID_IssuerSN) + version = NSS_CMS_KEYTRANS_RECIPIENT_INFO_VERSION_ISSUERSN; + else + version = NSS_CMS_KEYTRANS_RECIPIENT_INFO_VERSION_SUBJKEY; + dummy = SEC_ASN1EncodeInteger(poolp, &(ri->ri.keyTransRecipientInfo.version), version); + if (dummy == NULL) + goto loser; + break; + case NSSCMSRecipientInfoID_KeyAgree: + dummy = SEC_ASN1EncodeInteger(poolp, &(ri->ri.keyAgreeRecipientInfo.version), + NSS_CMS_KEYAGREE_RECIPIENT_INFO_VERSION); + if (dummy == NULL) + goto loser; + break; + case NSSCMSRecipientInfoID_KEK: + /* NOTE: this cannot happen as long as we do not support any KEK algorithm */ + dummy = SEC_ASN1EncodeInteger(poolp, &(ri->ri.kekRecipientInfo.version), + NSS_CMS_KEK_RECIPIENT_INFO_VERSION); + if (dummy == NULL) + goto loser; + break; + + } + + PORT_ArenaUnmark (poolp, mark); + return ri; + +loser: + PORT_ArenaRelease (poolp, mark); + return NULL; +} + +void +NSS_CMSRecipientInfo_Destroy(NSSCMSRecipientInfo *ri) +{ + /* version was allocated on the pool, so no need to destroy it */ + /* issuerAndSN was allocated on the pool, so no need to destroy it */ + if (ri->cert != NULL) + CERT_DestroyCertificate(ri->cert); + /* recipientInfo structure itself was allocated on the pool, so no need to destroy it */ + /* we're done. */ +} + +int +NSS_CMSRecipientInfo_GetVersion(NSSCMSRecipientInfo *ri) +{ + unsigned long version; + SECItem *versionitem; + + switch (ri->recipientInfoType) { + case NSSCMSRecipientInfoID_KeyTrans: + /* ignore subIndex */ + versionitem = &(ri->ri.keyTransRecipientInfo.version); + break; + case NSSCMSRecipientInfoID_KEK: + /* ignore subIndex */ + versionitem = &(ri->ri.kekRecipientInfo.version); + break; + case NSSCMSRecipientInfoID_KeyAgree: + versionitem = &(ri->ri.keyAgreeRecipientInfo.version); + break; + } + /* always take apart the SECItem */ + if (SEC_ASN1DecodeInteger(versionitem, &version) != SECSuccess) + return 0; + else + return (int)version; +} + +SECItem * +NSS_CMSRecipientInfo_GetEncryptedKey(NSSCMSRecipientInfo *ri, int subIndex) +{ + SECItem *enckey; + + switch (ri->recipientInfoType) { + case NSSCMSRecipientInfoID_KeyTrans: + /* ignore subIndex */ + enckey = &(ri->ri.keyTransRecipientInfo.encKey); + break; + case NSSCMSRecipientInfoID_KEK: + /* ignore subIndex */ + enckey = &(ri->ri.kekRecipientInfo.encKey); + break; + case NSSCMSRecipientInfoID_KeyAgree: + enckey = &(ri->ri.keyAgreeRecipientInfo.recipientEncryptedKeys[subIndex]->encKey); + break; + } + return enckey; +} + + +SECOidTag +NSS_CMSRecipientInfo_GetKeyEncryptionAlgorithmTag(NSSCMSRecipientInfo *ri) +{ + SECOidTag encalgtag; + + switch (ri->recipientInfoType) { + case NSSCMSRecipientInfoID_KeyTrans: + encalgtag = SECOID_GetAlgorithmTag(&(ri->ri.keyTransRecipientInfo.keyEncAlg)); + break; + case NSSCMSRecipientInfoID_KeyAgree: + encalgtag = SECOID_GetAlgorithmTag(&(ri->ri.keyAgreeRecipientInfo.keyEncAlg)); + break; + case NSSCMSRecipientInfoID_KEK: + encalgtag = SECOID_GetAlgorithmTag(&(ri->ri.kekRecipientInfo.keyEncAlg)); + break; + } + return encalgtag; +} + +SECStatus +NSS_CMSRecipientInfo_WrapBulkKey(NSSCMSRecipientInfo *ri, PK11SymKey *bulkkey, SECOidTag bulkalgtag) +{ + CERTCertificate *cert; + SECOidTag certalgtag; + SECStatus rv; + SECItem *params = NULL; + NSSCMSRecipientEncryptedKey *rek; + NSSCMSOriginatorIdentifierOrKey *oiok; + PLArenaPool *poolp; + + poolp = ri->cmsg->poolp; + cert = ri->cert; + PORT_Assert (cert != NULL); + if (cert == NULL) + return SECFailure; + + /* XXX set ri->recipientInfoType to the proper value here */ + /* or should we look if it's been set already ? */ + + certalgtag = SECOID_GetAlgorithmTag(&(cert->subjectPublicKeyInfo.algorithm)); + switch (certalgtag) { + case SEC_OID_PKCS1_RSA_ENCRYPTION: + /* wrap the symkey */ + if (NSS_CMSUtil_EncryptSymKey_RSA(poolp, cert, bulkkey, &ri->ri.keyTransRecipientInfo.encKey) != SECSuccess) { + rv = SECFailure; + break; + } + + rv = SECOID_SetAlgorithmID(poolp, &(ri->ri.keyTransRecipientInfo.keyEncAlg), certalgtag, NULL); + break; + case SEC_OID_MISSI_KEA_DSS_OLD: + case SEC_OID_MISSI_KEA_DSS: + case SEC_OID_MISSI_KEA: + rv = NSS_CMSUtil_EncryptSymKey_MISSI(poolp, cert, bulkkey, + bulkalgtag, + &ri->ri.keyTransRecipientInfo.encKey, + ¶ms, ri->cmsg->pwfn_arg); + if (rv != SECSuccess) + break; + + /* here, we DO need to pass the params to the wrap function because, with + * RSA, there is no funny stuff going on with generation of IV vectors or so */ + rv = SECOID_SetAlgorithmID(poolp, &(ri->ri.keyTransRecipientInfo.keyEncAlg), certalgtag, params); + break; + case SEC_OID_X942_DIFFIE_HELMAN_KEY: /* dh-public-number */ + rek = ri->ri.keyAgreeRecipientInfo.recipientEncryptedKeys[0]; + if (rek == NULL) { + rv = SECFailure; + break; + } + + oiok = &(ri->ri.keyAgreeRecipientInfo.originatorIdentifierOrKey); + PORT_Assert(oiok->identifierType == NSSCMSOriginatorIDOrKey_OriginatorPublicKey); + + /* see RFC2630 12.3.1.1 */ + if (SECOID_SetAlgorithmID(poolp, &oiok->id.originatorPublicKey.algorithmIdentifier, + SEC_OID_X942_DIFFIE_HELMAN_KEY, NULL) != SECSuccess) { + rv = SECFailure; + break; + } + + /* this will generate a key pair, compute the shared secret, */ + /* derive a key and ukm for the keyEncAlg out of it, encrypt the bulk key with */ + /* the keyEncAlg, set encKey, keyEncAlg, publicKey etc. */ + rv = NSS_CMSUtil_EncryptSymKey_ESDH(poolp, cert, bulkkey, + &rek->encKey, + &ri->ri.keyAgreeRecipientInfo.ukm, + &ri->ri.keyAgreeRecipientInfo.keyEncAlg, + &oiok->id.originatorPublicKey.publicKey); + + break; + default: + /* other algorithms not supported yet */ + /* NOTE that we do not support any KEK algorithm */ + PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); + rv = SECFailure; + break; + } + return rv; +} + +PK11SymKey * +NSS_CMSRecipientInfo_UnwrapBulkKey(NSSCMSRecipientInfo *ri, int subIndex, CERTCertificate *cert, SECKEYPrivateKey *privkey, SECOidTag bulkalgtag) +{ + PK11SymKey *bulkkey = NULL; + SECAlgorithmID *encalg; + SECOidTag encalgtag; + SECItem *enckey; + int error; + + ri->cert = cert; /* mark the recipientInfo so we can find it later */ + + switch (ri->recipientInfoType) { + case NSSCMSRecipientInfoID_KeyTrans: + encalg = &(ri->ri.keyTransRecipientInfo.keyEncAlg); + encalgtag = SECOID_GetAlgorithmTag(&(ri->ri.keyTransRecipientInfo.keyEncAlg)); + enckey = &(ri->ri.keyTransRecipientInfo.encKey); /* ignore subIndex */ + switch (encalgtag) { + case SEC_OID_PKCS1_RSA_ENCRYPTION: + /* RSA encryption algorithm: */ + /* get the symmetric (bulk) key by unwrapping it using our private key */ + bulkkey = NSS_CMSUtil_DecryptSymKey_RSA(privkey, enckey, bulkalgtag); + break; + case SEC_OID_NETSCAPE_SMIME_KEA: + /* FORTEZZA key exchange algorithm */ + /* the supplemental data is in the parameters of encalg */ + bulkkey = NSS_CMSUtil_DecryptSymKey_MISSI(privkey, enckey, encalg, bulkalgtag, ri->cmsg->pwfn_arg); + break; + default: + error = SEC_ERROR_UNSUPPORTED_KEYALG; + goto loser; + } + break; + case NSSCMSRecipientInfoID_KeyAgree: + encalg = &(ri->ri.keyAgreeRecipientInfo.keyEncAlg); + encalgtag = SECOID_GetAlgorithmTag(&(ri->ri.keyAgreeRecipientInfo.keyEncAlg)); + enckey = &(ri->ri.keyAgreeRecipientInfo.recipientEncryptedKeys[subIndex]->encKey); + switch (encalgtag) { + case SEC_OID_X942_DIFFIE_HELMAN_KEY: + /* Diffie-Helman key exchange */ + /* XXX not yet implemented */ + /* XXX problem: SEC_OID_X942_DIFFIE_HELMAN_KEY points to a PKCS3 mechanism! */ + /* we support ephemeral-static DH only, so if the recipientinfo */ + /* has originator stuff in it, we punt (or do we? shouldn't be that hard...) */ + /* first, we derive the KEK (a symkey!) using a Derive operation, then we get the */ + /* content encryption key using a Unwrap op */ + /* the derive operation has to generate the key using the algorithm in RFC2631 */ + error = SEC_ERROR_UNSUPPORTED_KEYALG; + break; + default: + error = SEC_ERROR_UNSUPPORTED_KEYALG; + goto loser; + } + break; + case NSSCMSRecipientInfoID_KEK: + encalg = &(ri->ri.kekRecipientInfo.keyEncAlg); + encalgtag = SECOID_GetAlgorithmTag(&(ri->ri.kekRecipientInfo.keyEncAlg)); + enckey = &(ri->ri.kekRecipientInfo.encKey); + /* not supported yet */ + error = SEC_ERROR_UNSUPPORTED_KEYALG; + goto loser; + break; + } + /* XXXX continue here */ + return bulkkey; + +loser: + return NULL; +} diff --git a/security/nss/lib/smime/cmsreclist.c b/security/nss/lib/smime/cmsreclist.c new file mode 100644 index 000000000..b6fc62ee4 --- /dev/null +++ b/security/nss/lib/smime/cmsreclist.c @@ -0,0 +1,187 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * CMS recipient list functions + * + * $Id$ + */ + +#include "cmslocal.h" + +#include "cert.h" +#include "key.h" +#include "secasn1.h" +#include "secitem.h" +#include "secoid.h" +#include "pk11func.h" +#include "prtime.h" +#include "secerr.h" + +static int +nss_cms_recipients_traverse(NSSCMSRecipientInfo **recipientinfos, NSSCMSRecipient **recipient_list) +{ + int count = 0; + int rlindex = 0; + int i, j; + NSSCMSRecipient *rle; + NSSCMSRecipientInfo *ri; + NSSCMSRecipientEncryptedKey *rek; + + for (i = 0; recipientinfos[i] != NULL; i++) { + ri = recipientinfos[i]; + switch (ri->recipientInfoType) { + case NSSCMSRecipientInfoID_KeyTrans: + if (recipient_list) { + /* alloc one & fill it out */ + rle = (NSSCMSRecipient *)PORT_ZAlloc(sizeof(NSSCMSRecipient)); + if (rle == NULL) + return -1; + + rle->riIndex = i; + rle->subIndex = -1; + switch (ri->ri.keyTransRecipientInfo.recipientIdentifier.identifierType) { + case NSSCMSRecipientID_IssuerSN: + rle->kind = RLIssuerSN; + rle->id.issuerAndSN = ri->ri.keyTransRecipientInfo.recipientIdentifier.id.issuerAndSN; + break; + case NSSCMSRecipientID_SubjectKeyID: + rle->kind = RLSubjKeyID; + rle->id.subjectKeyID = ri->ri.keyTransRecipientInfo.recipientIdentifier.id.subjectKeyID; + break; + } + recipient_list[rlindex++] = rle; + } else { + count++; + } + break; + case NSSCMSRecipientInfoID_KeyAgree: + if (ri->ri.keyAgreeRecipientInfo.recipientEncryptedKeys == NULL) + break; + for (j=0; ri->ri.keyAgreeRecipientInfo.recipientEncryptedKeys[j] != NULL; j++) { + if (recipient_list) { + rek = ri->ri.keyAgreeRecipientInfo.recipientEncryptedKeys[j]; + /* alloc one & fill it out */ + rle = (NSSCMSRecipient *)PORT_ZAlloc(sizeof(NSSCMSRecipient)); + if (rle == NULL) + return -1; + + rle->riIndex = i; + rle->subIndex = j; + switch (rek->recipientIdentifier.identifierType) { + case NSSCMSKeyAgreeRecipientID_IssuerSN: + rle->kind = RLIssuerSN; + rle->id.issuerAndSN = rek->recipientIdentifier.id.issuerAndSN; + break; + case NSSCMSKeyAgreeRecipientID_RKeyID: + rle->kind = RLSubjKeyID; + rle->id.subjectKeyID = rek->recipientIdentifier.id.recipientKeyIdentifier.subjectKeyIdentifier; + break; + } + recipient_list[rlindex++] = rle; + } else { + count++; + } + } + break; + case NSSCMSRecipientInfoID_KEK: + /* KEK is not implemented */ + break; + } + } + /* if we have a recipient list, we return on success (-1, above, on failure) */ + /* otherwise, we return the count. */ + if (recipient_list) { + recipient_list[rlindex] = NULL; + return 0; + } else { + return count; + } +} + +NSSCMSRecipient ** +nss_cms_recipient_list_create(NSSCMSRecipientInfo **recipientinfos) +{ + int count, rv; + NSSCMSRecipient **recipient_list; + + /* count the number of recipient identifiers */ + count = nss_cms_recipients_traverse(recipientinfos, NULL); + if (count <= 0) { + /* no recipients? */ + PORT_SetError(SEC_ERROR_BAD_DATA); +#if 0 + PORT_SetErrorString("Cannot find recipient data in envelope."); +#endif + return NULL; + } + + /* allocate an array of pointers */ + recipient_list = (NSSCMSRecipient **) + PORT_ZAlloc((count + 1) * sizeof(NSSCMSRecipient *)); + if (recipient_list == NULL) + return NULL; + + /* now fill in the recipient_list */ + rv = nss_cms_recipients_traverse(recipientinfos, recipient_list); + if (rv < 0) { + nss_cms_recipient_list_destroy(recipient_list); + return NULL; + } + return recipient_list; +} + +void +nss_cms_recipient_list_destroy(NSSCMSRecipient **recipient_list) +{ + int i; + NSSCMSRecipient *recipient; + + for (i=0; recipient_list[i] != NULL; i++) { + recipient = recipient_list[i]; + if (recipient->cert) + CERT_DestroyCertificate(recipient->cert); + if (recipient->privkey) + SECKEY_DestroyPrivateKey(recipient->privkey); + if (recipient->slot) + PK11_FreeSlot(recipient->slot); + PORT_Free(recipient); + } + PORT_Free(recipient_list); +} + +NSSCMSRecipientEncryptedKey * +NSS_CMSRecipientEncryptedKey_Create(PLArenaPool *poolp) +{ + return (NSSCMSRecipientEncryptedKey *)PORT_ArenaZAlloc(poolp, sizeof(NSSCMSRecipientEncryptedKey)); +} diff --git a/security/nss/lib/smime/cmsreclist.h b/security/nss/lib/smime/cmsreclist.h new file mode 100644 index 000000000..32ff188ef --- /dev/null +++ b/security/nss/lib/smime/cmsreclist.h @@ -0,0 +1,59 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * $Id$ + */ + +#ifndef _CMSRECLIST_H +#define _CMSRECLIST_H + +struct NSSCMSRecipientStr { + int riIndex; /* this recipient's index in recipientInfo array */ + int subIndex; /* index into recipientEncryptedKeys */ + /* (only in NSSCMSKeyAgreeRecipientInfoStr) */ + enum {RLIssuerSN, RLSubjKeyID} kind; /* for conversion recipientinfos -> recipientlist */ + union { + CERTIssuerAndSN * issuerAndSN; + SECItem * subjectKeyID; + } id; + + /* result data (filled out for each recipient that's us) */ + CERTCertificate * cert; + SECKEYPrivateKey * privkey; + PK11SlotInfo * slot; +}; + +typedef struct NSSCMSRecipientStr NSSCMSRecipient; + +#endif /* _CMSRECLIST_H */ diff --git a/security/nss/lib/smime/cmssigdata.c b/security/nss/lib/smime/cmssigdata.c new file mode 100644 index 000000000..9b39871cd --- /dev/null +++ b/security/nss/lib/smime/cmssigdata.c @@ -0,0 +1,850 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * CMS signedData methods. + * + * $Id$ + */ + +#include "cmslocal.h" + +#include "cert.h" +#include "cdbhdl.h" +#include "secasn1.h" +#include "secitem.h" +#include "secoid.h" +#include "pk11func.h" +#include "secerr.h" + +NSSCMSSignedData * +NSS_CMSSignedData_Create(NSSCMSMessage *cmsg) +{ + void *mark; + NSSCMSSignedData *sigd; + PLArenaPool *poolp; + + poolp = cmsg->poolp; + + mark = PORT_ArenaMark(poolp); + + sigd = (NSSCMSSignedData *)PORT_ArenaZAlloc (poolp, sizeof(NSSCMSSignedData)); + if (sigd == NULL) + goto loser; + + sigd->cmsg = cmsg; + + /* signerInfos, certs, certlists, crls are all empty */ + /* version is set in NSS_CMSSignedData_Finalize() */ + + PORT_ArenaUnmark(poolp, mark); + return sigd; + +loser: + PORT_ArenaRelease(poolp, mark); + return NULL; +} + +void +NSS_CMSSignedData_Destroy(NSSCMSSignedData *sigd) +{ + CERTCertificate **certs, *cert; + CERTCertificateList **certlists, *certlist; + NSSCMSSignerInfo **signerinfos, *si; + + if (sigd == NULL) + return; + + certs = sigd->certs; + certlists = sigd->certLists; + signerinfos = sigd->signerInfos; + + if (certs != NULL) { + while ((cert = *certs++) != NULL) + CERT_DestroyCertificate (cert); + } + + if (certlists != NULL) { + while ((certlist = *certlists++) != NULL) + CERT_DestroyCertificateList (certlist); + } + + if (signerinfos != NULL) { + while ((si = *signerinfos++) != NULL) + NSS_CMSSignerInfo_Destroy(si); + } + + /* everything's in a pool, so don't worry about the storage */ +} + +/* + * NSS_CMSSignedData_Encode_BeforeStart - do all the necessary things to a SignedData + * before start of encoding. + * + * In detail: + * - find out about the right value to put into sigd->version + * - come up with a list of digestAlgorithms (which should be the union of the algorithms + * in the signerinfos). + * If we happen to have a pre-set list of algorithms (and digest values!), we + * check if we have all the signerinfos' algorithms. If not, this is an error. + */ +SECStatus +NSS_CMSSignedData_Encode_BeforeStart(NSSCMSSignedData *sigd) +{ + NSSCMSSignerInfo **signerinfos, *signerinfo; + SECOidTag digestalgtag; + SECItem *dummy; + int version; + SECStatus rv; + PRBool haveDigests = PR_FALSE; + int n, i; + PLArenaPool *poolp; + + poolp = sigd->cmsg->poolp; + + /* we assume that we have precomputed digests if there is a list of algorithms, and */ + /* a chunk of data for each of those algorithms */ + if (sigd->digestAlgorithms != NULL && sigd->digests != NULL) { + for (i=0; sigd->digestAlgorithms[i] != NULL; i++) { + if (sigd->digests[i] == NULL) + break; + } + if (sigd->digestAlgorithms[i] == NULL) /* reached the end of the array? */ + haveDigests = PR_TRUE; /* yes: we must have all the digests */ + } + + version = NSS_CMS_SIGNED_DATA_VERSION_BASIC; + + /* RFC2630 5.1 "version is the syntax version number..." */ + if (NSS_CMSContentInfo_GetContentTypeTag(&(sigd->contentInfo)) != SEC_OID_PKCS7_DATA) + version = NSS_CMS_SIGNED_DATA_VERSION_EXT; + + signerinfos = sigd->signerInfos; + /* prepare all the SignerInfos */ + for (i = 0; signerinfos[i] != NULL; i++) { + signerinfo = signerinfos[i]; + + /* RFC2630 5.1 "version is the syntax version number..." */ + if (NSS_CMSSignerInfo_GetVersion(signerinfo) != NSS_CMS_SIGNER_INFO_VERSION_ISSUERSN) + version = NSS_CMS_SIGNED_DATA_VERSION_EXT; + + /* collect digestAlgorithms from SignerInfos */ + /* (we need to know which algorithms we have when the content comes in) */ + /* do not overwrite any existing digestAlgorithms (and digest) */ + digestalgtag = NSS_CMSSignerInfo_GetDigestAlgTag(signerinfo); + n = NSS_CMSAlgArray_GetIndexByAlgTag(sigd->digestAlgorithms, digestalgtag); + if (n < 0 && haveDigests) { + /* oops, there is a digestalg we do not have a digest for */ + /* but we were supposed to have all the digests already... */ + goto loser; + } else if (n < 0) { + /* add the digestAlgorithm & a NULL digest */ + rv = NSS_CMSSignedData_AddDigest(poolp, sigd, digestalgtag, NULL); + if (rv != SECSuccess) + goto loser; + } else { + /* found it, nothing to do */ + } + } + + dummy = SEC_ASN1EncodeInteger(poolp, &(sigd->version), (long)version); + if (dummy == NULL) + return SECFailure; + + /* this is a SET OF, so we need to sort them guys */ + rv = NSS_CMSArray_SortByDER((void **)sigd->digestAlgorithms, SECOID_AlgorithmIDTemplate, + (void **)sigd->digests); + if (rv != SECSuccess) + return SECFailure; + + return SECSuccess; + +loser: + return SECFailure; +} + +SECStatus +NSS_CMSSignedData_Encode_BeforeData(NSSCMSSignedData *sigd) +{ + /* set up the digests */ + if (sigd->digestAlgorithms != NULL) { + sigd->contentInfo.digcx = NSS_CMSDigestContext_StartMultiple(sigd->digestAlgorithms); + if (sigd->contentInfo.digcx == NULL) + return SECFailure; + } + return SECSuccess; +} + +/* + * NSS_CMSSignedData_Encode_AfterData - do all the necessary things to a SignedData + * after all the encapsulated data was passed through the encoder. + * + * In detail: + * - create the signatures in all the SignerInfos + * + * Please note that nothing is done to the Certificates and CRLs in the message - this + * is entirely the responsibility of our callers. + */ +SECStatus +NSS_CMSSignedData_Encode_AfterData(NSSCMSSignedData *sigd) +{ + NSSCMSSignerInfo **signerinfos, *signerinfo; + NSSCMSContentInfo *cinfo; + SECOidTag digestalgtag; + SECStatus ret = SECFailure; + SECStatus rv; + SECItem *contentType; + int certcount; + int i, ci, cli, n, rci, si; + PLArenaPool *poolp; + CERTCertificateList *certlist; + extern const SEC_ASN1Template NSSCMSSignerInfoTemplate[]; + + poolp = sigd->cmsg->poolp; + cinfo = &(sigd->contentInfo); + + /* did we have digest calculation going on? */ + if (cinfo->digcx) { + rv = NSS_CMSDigestContext_FinishMultiple(cinfo->digcx, poolp, &(sigd->digests)); + if (rv != SECSuccess) + goto loser; /* error has been set by NSS_CMSDigestContext_FinishMultiple */ + cinfo->digcx = NULL; + } + + signerinfos = sigd->signerInfos; + certcount = 0; + + /* prepare all the SignerInfos */ + for (i = 0; signerinfos[i] != NULL; i++) { + signerinfo = signerinfos[i]; + + /* find correct digest for this signerinfo */ + digestalgtag = NSS_CMSSignerInfo_GetDigestAlgTag(signerinfo); + n = NSS_CMSAlgArray_GetIndexByAlgTag(sigd->digestAlgorithms, digestalgtag); + if (n < 0 || sigd->digests == NULL || sigd->digests[n] == NULL) { + /* oops - digest not found */ + PORT_SetError(SEC_ERROR_DIGEST_NOT_FOUND); + goto loser; + } + + /* XXX if our content is anything else but data, we need to force the + * presence of signed attributes (RFC2630 5.3 "signedAttributes is a + * collection...") */ + + /* pass contentType here as we want a contentType attribute */ + if ((contentType = NSS_CMSContentInfo_GetContentTypeOID(cinfo)) == NULL) + goto loser; + + /* sign the thing */ + rv = NSS_CMSSignerInfo_Sign(signerinfo, sigd->digests[n], contentType); + if (rv != SECSuccess) + goto loser; + + /* while we're at it, count number of certs in certLists */ + certlist = NSS_CMSSignerInfo_GetCertList(signerinfo); + if (certlist) + certcount += certlist->len; + } + + /* this is a SET OF, so we need to sort them guys */ + rv = NSS_CMSArray_SortByDER((void **)signerinfos, NSSCMSSignerInfoTemplate, NULL); + if (rv != SECSuccess) + goto loser; + + /* + * now prepare certs & crls + */ + + /* count the rest of the certs */ + if (sigd->certs != NULL) { + for (ci = 0; sigd->certs[ci] != NULL; ci++) + certcount++; + } + + if (sigd->certLists != NULL) { + for (cli = 0; sigd->certLists[cli] != NULL; cli++) + certcount += sigd->certLists[cli]->len; + } + + if (certcount == 0) { + sigd->rawCerts = NULL; + } else { + /* + * Combine all of the certs and cert chains into rawcerts. + * Note: certcount is an upper bound; we may not need that many slots + * but we will allocate anyway to avoid having to do another pass. + * (The temporary space saving is not worth it.) + * + * XXX ARGH - this NEEDS to be fixed. need to come up with a decent + * SetOfDERcertficates implementation + */ + sigd->rawCerts = (SECItem **)PORT_ArenaAlloc(poolp, (certcount + 1) * sizeof(SECItem *)); + if (sigd->rawCerts == NULL) + return SECFailure; + + /* + * XXX Want to check for duplicates and not add *any* cert that is + * already in the set. This will be more important when we start + * dealing with larger sets of certs, dual-key certs (signing and + * encryption), etc. For the time being we can slide by... + * + * XXX ARGH - this NEEDS to be fixed. need to come up with a decent + * SetOfDERcertficates implementation + */ + rci = 0; + if (signerinfos != NULL) { + for (si = 0; signerinfos[si] != NULL; si++) { + signerinfo = signerinfos[si]; + for (ci = 0; ci < signerinfo->certList->len; ci++) + sigd->rawCerts[rci++] = &(signerinfo->certList->certs[ci]); + } + } + + if (sigd->certs != NULL) { + for (ci = 0; sigd->certs[ci] != NULL; ci++) + sigd->rawCerts[rci++] = &(sigd->certs[ci]->derCert); + } + + if (sigd->certLists != NULL) { + for (cli = 0; sigd->certLists[cli] != NULL; cli++) { + for (ci = 0; ci < sigd->certLists[cli]->len; ci++) + sigd->rawCerts[rci++] = &(sigd->certLists[cli]->certs[ci]); + } + } + + sigd->rawCerts[rci] = NULL; + + /* this is a SET OF, so we need to sort them guys - we have the DER already, though */ + NSS_CMSArray_Sort((void **)sigd->rawCerts, NSS_CMSUtil_DERCompare, NULL, NULL); + } + + ret = SECSuccess; + +loser: + return ret; +} + +SECStatus +NSS_CMSSignedData_Decode_BeforeData(NSSCMSSignedData *sigd) +{ + /* set up the digests */ + if (sigd->digestAlgorithms != NULL && sigd->digests == NULL) { + /* if digests are already there, do nothing */ + sigd->contentInfo.digcx = NSS_CMSDigestContext_StartMultiple(sigd->digestAlgorithms); + if (sigd->contentInfo.digcx == NULL) + return SECFailure; + } + return SECSuccess; +} + +/* + * NSS_CMSSignedData_Decode_AfterData - do all the necessary things to a SignedData + * after all the encapsulated data was passed through the decoder. + */ +SECStatus +NSS_CMSSignedData_Decode_AfterData(NSSCMSSignedData *sigd) +{ + /* did we have digest calculation going on? */ + if (sigd->contentInfo.digcx) { + if (NSS_CMSDigestContext_FinishMultiple(sigd->contentInfo.digcx, sigd->cmsg->poolp, &(sigd->digests)) != SECSuccess) + return SECFailure; /* error has been set by NSS_CMSDigestContext_FinishMultiple */ + sigd->contentInfo.digcx = NULL; + } + return SECSuccess; +} + +/* + * NSS_CMSSignedData_Decode_AfterEnd - do all the necessary things to a SignedData + * after all decoding is finished. + */ +SECStatus +NSS_CMSSignedData_Decode_AfterEnd(NSSCMSSignedData *sigd) +{ + NSSCMSSignerInfo **signerinfos; + int i; + + /* set cmsg for all the signerinfos */ + signerinfos = sigd->signerInfos; + + /* set cmsg for all the signerinfos */ + for (i = 0; signerinfos[i] != NULL; i++) + signerinfos[i]->cmsg = sigd->cmsg; + + return SECSuccess; +} + +/* + * NSS_CMSSignedData_GetSignerInfos - retrieve the SignedData's signer list + */ +NSSCMSSignerInfo ** +NSS_CMSSignedData_GetSignerInfos(NSSCMSSignedData *sigd) +{ + return sigd->signerInfos; +} + +int +NSS_CMSSignedData_SignerInfoCount(NSSCMSSignedData *sigd) +{ + return NSS_CMSArray_Count((void **)sigd->signerInfos); +} + +NSSCMSSignerInfo * +NSS_CMSSignedData_GetSignerInfo(NSSCMSSignedData *sigd, int i) +{ + return sigd->signerInfos[i]; +} + +/* + * NSS_CMSSignedData_GetDigestAlgs - retrieve the SignedData's digest algorithm list + */ +SECAlgorithmID ** +NSS_CMSSignedData_GetDigestAlgs(NSSCMSSignedData *sigd) +{ + return sigd->digestAlgorithms; +} + +/* + * NSS_CMSSignedData_GetContentInfo - return pointer to this signedData's contentinfo + */ +NSSCMSContentInfo * +NSS_CMSSignedData_GetContentInfo(NSSCMSSignedData *sigd) +{ + return &(sigd->contentInfo); +} + +/* + * NSS_CMSSignedData_GetCertificateList - retrieve the SignedData's certificate list + */ +SECItem ** +NSS_CMSSignedData_GetCertificateList(NSSCMSSignedData *sigd) +{ + return sigd->rawCerts; +} + +SECStatus +NSS_CMSSignedData_ImportCerts(NSSCMSSignedData *sigd, CERTCertDBHandle *certdb, + SECCertUsage certusage, PRBool keepcerts) +{ + int certcount; + SECStatus rv; + int i; + + certcount = NSS_CMSArray_Count((void **)sigd->rawCerts); + + rv = CERT_ImportCerts(certdb, certusage, certcount, sigd->rawCerts, NULL, + keepcerts, PR_FALSE, NULL); + + /* XXX CRL handling */ + + if (sigd->signerInfos != NULL) { + /* fill in all signerinfo's certs */ + for (i = 0; sigd->signerInfos[i] != NULL; i++) + (void)NSS_CMSSignerInfo_GetSigningCertificate(sigd->signerInfos[i], certdb); + } + + return rv; +} + +/* + * XXX the digests need to be passed in BETWEEN the decoding and the verification in case + * of external signatures! + */ + +/* + * NSS_CMSSignedData_VerifySignerInfo - check the signatures. + * + * The digests were either calculated during decoding (and are stored in the + * signedData itself) or set after decoding using NSS_CMSSignedData_SetDigests. + * + * The verification checks if the signing cert is valid and has a trusted chain + * for the purpose specified by "certusage". + */ +SECStatus +NSS_CMSSignedData_VerifySignerInfo(NSSCMSSignedData *sigd, int i, + CERTCertDBHandle *certdb, SECCertUsage certusage) +{ + NSSCMSSignerInfo *signerinfo; + NSSCMSContentInfo *cinfo; + SECOidData *algiddata; + SECItem *contentType, *digest; + + cinfo = &(sigd->contentInfo); + + signerinfo = sigd->signerInfos[i]; + + /* verify certificate */ + if (NSS_CMSSignerInfo_VerifyCertificate(signerinfo, certdb, certusage) != SECSuccess) + return SECFailure; /* error is set by NSS_CMSSignerInfo_VerifyCertificate */ + + /* find digest and contentType for signerinfo */ + algiddata = NSS_CMSSignerInfo_GetDigestAlg(signerinfo); + digest = NSS_CMSSignedData_GetDigestByAlgTag(sigd, algiddata->offset); + contentType = NSS_CMSContentInfo_GetContentTypeOID(cinfo); + + /* now verify signature */ + return NSS_CMSSignerInfo_Verify(signerinfo, digest, contentType); +} + +/* + * NSS_CMSSignedData_HasDigests - see if we have digests in place + */ +PRBool +NSS_CMSSignedData_HasDigests(NSSCMSSignedData *sigd) +{ + return (sigd->digests != NULL); +} + +SECStatus +NSS_CMSSignedData_AddCertList(NSSCMSSignedData *sigd, CERTCertificateList *certlist) +{ + SECStatus rv; + + PORT_Assert(certlist != NULL); + + if (certlist == NULL) + return SECFailure; + + /* XXX memory?? a certlist has an arena of its own and is not refcounted!?!? */ + rv = NSS_CMSArray_Add(sigd->cmsg->poolp, (void ***)&(sigd->certLists), (void *)certlist); + + return rv; +} + +/* + * NSS_CMSSignedData_AddCertChain - add cert and its entire chain to the set of certs + */ +SECStatus +NSS_CMSSignedData_AddCertChain(NSSCMSSignedData *sigd, CERTCertificate *cert) +{ + CERTCertificateList *certlist; + SECCertUsage usage; + SECStatus rv; + + usage = certUsageEmailSigner; + + /* do not include root */ + certlist = CERT_CertChainFromCert(cert, usage, PR_FALSE); + if (certlist == NULL) + return SECFailure; + + rv = NSS_CMSSignedData_AddCertList(sigd, certlist); + CERT_DestroyCertificateList(certlist); + + return rv; +} + +SECStatus +NSS_CMSSignedData_AddCertificate(NSSCMSSignedData *sigd, CERTCertificate *cert) +{ + CERTCertificate *c; + SECStatus rv; + + PORT_Assert(cert != NULL); + + if (cert == NULL) + return SECFailure; + + c = CERT_DupCertificate(cert); + rv = NSS_CMSArray_Add(sigd->cmsg->poolp, (void ***)&(sigd->certs), (void *)c); + return rv; +} + +PRBool +NSS_CMSSignedData_ContainsCertsOrCrls(NSSCMSSignedData *sigd) +{ + if (sigd->rawCerts != NULL && sigd->rawCerts[0] != NULL) + return PR_TRUE; + else if (sigd->crls != NULL && sigd->crls[0] != NULL) + return PR_TRUE; + else + return PR_FALSE; +} + +SECStatus +NSS_CMSSignedData_AddSignerInfo(NSSCMSSignedData *sigd, + NSSCMSSignerInfo *signerinfo) +{ + void *mark; + SECStatus rv; + SECOidTag digestalgtag; + PLArenaPool *poolp; + + poolp = sigd->cmsg->poolp; + + mark = PORT_ArenaMark(poolp); + + /* add signerinfo */ + rv = NSS_CMSArray_Add(poolp, (void ***)&(sigd->signerInfos), (void *)signerinfo); + if (rv != SECSuccess) + goto loser; + + /* + * add empty digest + * Empty because we don't have it yet. Either it gets created during encoding + * (if the data is present) or has to be set externally. + * XXX maybe pass it in optionally? + */ + digestalgtag = NSS_CMSSignerInfo_GetDigestAlgTag(signerinfo); + rv = NSS_CMSSignedData_SetDigestValue(sigd, digestalgtag, NULL); + if (rv != SECSuccess) + goto loser; + + /* + * The last thing to get consistency would be adding the digest. + */ + + PORT_ArenaUnmark(poolp, mark); + return SECSuccess; + +loser: + PORT_ArenaRelease (poolp, mark); + return SECFailure; +} + +SECItem * +NSS_CMSSignedData_GetDigestByAlgTag(NSSCMSSignedData *sigd, SECOidTag algtag) +{ + int idx; + + idx = NSS_CMSAlgArray_GetIndexByAlgTag(sigd->digestAlgorithms, algtag); + return sigd->digests[idx]; +} + +/* + * NSS_CMSSignedData_SetDigests - set a signedData's digests member + * + * "digestalgs" - array of digest algorithm IDs + * "digests" - array of digests corresponding to the digest algorithms + */ +SECStatus +NSS_CMSSignedData_SetDigests(NSSCMSSignedData *sigd, + SECAlgorithmID **digestalgs, + SECItem **digests) +{ + int cnt, i, idx; + + if (sigd->digestAlgorithms == NULL) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + /* we assume that the digests array is just not there yet */ + PORT_Assert(sigd->digests == NULL); + if (sigd->digests != NULL) { + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + + /* now allocate one (same size as digestAlgorithms) */ + cnt = NSS_CMSArray_Count((void **)sigd->digestAlgorithms); + sigd->digests = PORT_ArenaZAlloc(sigd->cmsg->poolp, (cnt + 1) * sizeof(SECItem *)); + if (sigd->digests == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return SECFailure; + } + + for (i = 0; sigd->digestAlgorithms[i] != NULL; i++) { + /* try to find the sigd's i'th digest algorithm in the array we passed in */ + idx = NSS_CMSAlgArray_GetIndexByAlgID(digestalgs, sigd->digestAlgorithms[i]); + if (idx < 0) { + PORT_SetError(SEC_ERROR_DIGEST_NOT_FOUND); + return SECFailure; + } + + /* found it - now set it */ + if ((sigd->digests[i] = SECITEM_AllocItem(sigd->cmsg->poolp, NULL, 0)) == NULL || + SECITEM_CopyItem(sigd->cmsg->poolp, sigd->digests[i], digests[idx]) != SECSuccess) + { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return SECFailure; + } + } + return SECSuccess; +} + +SECStatus +NSS_CMSSignedData_SetDigestValue(NSSCMSSignedData *sigd, + SECOidTag digestalgtag, + SECItem *digestdata) +{ + SECItem *digest = NULL; + PLArenaPool *poolp; + void *mark; + int n; + + poolp = sigd->cmsg->poolp; + + mark = PORT_ArenaMark(poolp); + + if (digestdata) { + /* copy digestdata item to arena (in case we have it and are not only making room) */ + if (SECITEM_CopyItem(poolp, digest, digestdata) != SECSuccess) + goto loser; + } + + n = -1; + if (sigd->digestAlgorithms != NULL) + n = NSS_CMSAlgArray_GetIndexByAlgTag(sigd->digestAlgorithms, digestalgtag); + + /* if not found, add a digest */ + if (n < 0) { + if (NSS_CMSSignedData_AddDigest(poolp, sigd, digestalgtag, digest) != SECSuccess) + goto loser; + } else { + /* replace NULL pointer with digest item (and leak previous value) */ + sigd->digests[n] = digest; + } + + PORT_ArenaUnmark(poolp, mark); + return SECSuccess; + +loser: + PORT_ArenaRelease(poolp, mark); + return SECFailure; +} + +SECStatus +NSS_CMSSignedData_AddDigest(PRArenaPool *poolp, + NSSCMSSignedData *sigd, + SECOidTag digestalgtag, + SECItem *digest) +{ + SECAlgorithmID *digestalg; + void *mark; + + mark = PORT_ArenaMark(poolp); + + digestalg = PORT_ArenaZAlloc(poolp, sizeof(SECAlgorithmID)); + if (digestalg == NULL) + goto loser; + + if (SECOID_SetAlgorithmID (poolp, digestalg, digestalgtag, NULL) != SECSuccess) /* no params */ + goto loser; + + if (NSS_CMSArray_Add(poolp, (void ***)&(sigd->digestAlgorithms), (void *)digestalg) != SECSuccess || + /* even if digest is NULL, add dummy to have same-size array */ + NSS_CMSArray_Add(poolp, (void ***)&(sigd->digests), (void *)digest) != SECSuccess) + { + goto loser; + } + + PORT_ArenaUnmark(poolp, mark); + return SECSuccess; + +loser: + PORT_ArenaRelease(poolp, mark); + return SECFailure; +} + +SECItem * +NSS_CMSSignedData_GetDigestValue(NSSCMSSignedData *sigd, SECOidTag digestalgtag) +{ + int n; + + if (sigd->digestAlgorithms == NULL) + return NULL; + + n = NSS_CMSAlgArray_GetIndexByAlgTag(sigd->digestAlgorithms, digestalgtag); + + return (n < 0) ? NULL : sigd->digests[n]; +} + +/* ============================================================================= + * Misc. utility functions + */ + +/* + * NSS_CMSSignedData_CreateCertsOnly - create a certs-only SignedData. + * + * cert - base certificates that will be included + * include_chain - if true, include the complete cert chain for cert + * + * More certs and chains can be added via AddCertificate and AddCertChain. + * + * An error results in a return value of NULL and an error set. + * + * XXXX CRLs + */ +NSSCMSSignedData * +NSS_CMSSignedData_CreateCertsOnly(NSSCMSMessage *cmsg, CERTCertificate *cert, PRBool include_chain) +{ + NSSCMSSignedData *sigd; + void *mark; + PLArenaPool *poolp; + SECStatus rv; + + poolp = cmsg->poolp; + mark = PORT_ArenaMark(poolp); + + sigd = NSS_CMSSignedData_Create(cmsg); + if (sigd == NULL) + goto loser; + + /* no signerinfos, thus no digestAlgorithms */ + + /* but certs */ + if (include_chain) { + rv = NSS_CMSSignedData_AddCertChain(sigd, cert); + } else { + rv = NSS_CMSSignedData_AddCertificate(sigd, cert); + } + if (rv != SECSuccess) + goto loser; + + /* RFC2630 5.2 sez: + * In the degenerate case where there are no signers, the + * EncapsulatedContentInfo value being "signed" is irrelevant. In this + * case, the content type within the EncapsulatedContentInfo value being + * "signed" should be id-data (as defined in section 4), and the content + * field of the EncapsulatedContentInfo value should be omitted. + */ + rv = NSS_CMSContentInfo_SetContent_Data(cmsg, &(sigd->contentInfo), NULL, PR_TRUE); + if (rv != SECSuccess) + goto loser; + + PORT_ArenaUnmark(poolp, mark); + return sigd; + +loser: + if (sigd) + NSS_CMSSignedData_Destroy(sigd); + PORT_ArenaRelease(poolp, mark); + return NULL; +} + +/* TODO: + * NSS_CMSSignerInfo_GetReceiptRequest() + * NSS_CMSSignedData_HasReceiptRequest() + * easy way to iterate over signers + */ + diff --git a/security/nss/lib/smime/cmssiginfo.c b/security/nss/lib/smime/cmssiginfo.c new file mode 100644 index 000000000..03be0ddea --- /dev/null +++ b/security/nss/lib/smime/cmssiginfo.c @@ -0,0 +1,752 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * CMS signerInfo methods. + * + * $Id$ + */ + +#include "cmslocal.h" + +#include "cert.h" +#include "key.h" +#include "secasn1.h" +#include "secitem.h" +#include "secoid.h" +#include "pk11func.h" +#include "prtime.h" +#include "secerr.h" +#include "cryptohi.h" + +/* ============================================================================= + * SIGNERINFO + */ + +NSSCMSSignerInfo * +NSS_CMSSignerInfo_Create(NSSCMSMessage *cmsg, CERTCertificate *cert, SECOidTag digestalgtag) +{ + void *mark; + NSSCMSSignerInfo *signerinfo; + int version; + PLArenaPool *poolp; + + poolp = cmsg->poolp; + + mark = PORT_ArenaMark(poolp); + + signerinfo = (NSSCMSSignerInfo *)PORT_ArenaZAlloc(poolp, sizeof(NSSCMSSignerInfo)); + if (signerinfo == NULL) { + PORT_ArenaRelease(poolp, mark); + return NULL; + } + + if ((signerinfo->cert = CERT_DupCertificate(cert)) == NULL) + goto loser; + + signerinfo->cmsg = cmsg; + + signerinfo->signerIdentifier.identifierType = NSSCMSSignerID_IssuerSN; /* hardcoded for now */ + if ((signerinfo->signerIdentifier.id.issuerAndSN = CERT_GetCertIssuerAndSN(poolp, cert)) == NULL) + goto loser; + + /* set version right now */ + version = NSS_CMS_SIGNER_INFO_VERSION_ISSUERSN; + /* RFC2630 5.3 "version is the syntax version number. If the .... " */ + if (signerinfo->signerIdentifier.identifierType == NSSCMSSignerID_SubjectKeyID) + version = NSS_CMS_SIGNER_INFO_VERSION_SUBJKEY; + (void)SEC_ASN1EncodeInteger(poolp, &(signerinfo->version), (long)version); + + if (SECOID_SetAlgorithmID(poolp, &signerinfo->digestAlg, digestalgtag, NULL) != SECSuccess) + goto loser; + + PORT_ArenaUnmark(poolp, mark); + return signerinfo; + +loser: + PORT_ArenaRelease(poolp, mark); + return NULL; +} + +/* + * NSS_CMSSignerInfo_Destroy - destroy a SignerInfo data structure + */ +void +NSS_CMSSignerInfo_Destroy(NSSCMSSignerInfo *si) +{ + if (si->cert != NULL) + CERT_DestroyCertificate(si->cert); + + /* XXX storage ??? */ +} + +/* + * NSS_CMSSignerInfo_Sign - sign something + * + */ +SECStatus +NSS_CMSSignerInfo_Sign(NSSCMSSignerInfo *signerinfo, SECItem *digest, SECItem *contentType) +{ + CERTCertificate *cert; + SECKEYPrivateKey *privkey = NULL; + SECOidTag digestalgtag; + SECOidTag signalgtag; + SECItem signature = { 0 }; + SECStatus rv; + PLArenaPool *poolp, *tmppoolp; + + PORT_Assert (digest != NULL); + + poolp = signerinfo->cmsg->poolp; + + cert = signerinfo->cert; + + if ((privkey = PK11_FindKeyByAnyCert(cert, signerinfo->cmsg->pwfn_arg)) == NULL) + goto loser; + + digestalgtag = NSS_CMSSignerInfo_GetDigestAlgTag(signerinfo); + /* + * XXX I think there should be a cert-level interface for this, + * so that I do not have to know about subjectPublicKeyInfo... + */ + signalgtag = SECOID_GetAlgorithmTag(&(cert->subjectPublicKeyInfo.algorithm)); + + /* Fortezza MISSI have weird signature formats. Map them to standard DSA formats */ + signalgtag = PK11_FortezzaMapSig(signalgtag); + + if (signerinfo->authAttr != NULL) { + SECItem encoded_attrs; + + /* find and fill in the message digest attribute. */ + rv = NSS_CMSAttributeArray_SetAttr(poolp, &(signerinfo->authAttr), SEC_OID_PKCS9_MESSAGE_DIGEST, digest, PR_FALSE); + if (rv != SECSuccess) + goto loser; + + if (contentType != NULL) { + /* if the caller wants us to, find and fill in the content type attribute. */ + rv = NSS_CMSAttributeArray_SetAttr(poolp, &(signerinfo->authAttr), SEC_OID_PKCS9_CONTENT_TYPE, contentType, PR_FALSE); + if (rv != SECSuccess) + goto loser; + } + + if ((tmppoolp = PORT_NewArena (1024)) == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + /* + * Before encoding, reorder the attributes so that when they + * are encoded, they will be conforming DER, which is required + * to have a specific order and that is what must be used for + * the hash/signature. We do this here, rather than building + * it into EncodeAttributes, because we do not want to do + * such reordering on incoming messages (which also uses + * EncodeAttributes) or our old signatures (and other "broken" + * implementations) will not verify. So, we want to guarantee + * that we send out good DER encodings of attributes, but not + * to expect to receive them. + */ + if (NSS_CMSAttributeArray_Reorder(signerinfo->authAttr) != SECSuccess) + goto loser; + + encoded_attrs.data = NULL; + encoded_attrs.len = 0; + if (NSS_CMSAttributeArray_Encode(tmppoolp, &(signerinfo->authAttr), &encoded_attrs) == NULL) + goto loser; + + rv = SEC_SignData(&signature, encoded_attrs.data, encoded_attrs.len, privkey, + NSS_CMSUtil_MakeSignatureAlgorithm(digestalgtag, signalgtag)); + PORT_FreeArena(tmppoolp, PR_FALSE); /* awkward memory management :-( */ + } else { + rv = SGN_Digest(privkey, digestalgtag, &signature, digest); + } + + SECKEY_DestroyPrivateKey(privkey); + privkey = NULL; + + if (rv != SECSuccess) + goto loser; + + if (SECITEM_CopyItem(poolp, &(signerinfo->encDigest), &signature) != SECSuccess) + goto loser; + + SECITEM_FreeItem(&signature, PR_FALSE); + + if (SECOID_SetAlgorithmID(poolp, &(signerinfo->digestEncAlg), signalgtag, NULL) != SECSuccess) + goto loser; + + return SECSuccess; + +loser: + if (signature.len != 0) + SECITEM_FreeItem (&signature, PR_FALSE); + if (privkey) + SECKEY_DestroyPrivateKey(privkey); + return SECFailure; +} + +SECStatus +NSS_CMSSignerInfo_VerifyCertificate(NSSCMSSignerInfo *signerinfo, CERTCertDBHandle *certdb, + SECCertUsage certusage) +{ + CERTCertificate *cert; + int64 stime; + + if ((cert = NSS_CMSSignerInfo_GetSigningCertificate(signerinfo, certdb)) == NULL) { + signerinfo->verificationStatus = NSSCMSVS_SigningCertNotFound; + return SECFailure; + } + + /* + * Get and convert the signing time; if available, it will be used + * both on the cert verification and for importing the sender + * email profile. + */ + if (NSS_CMSSignerInfo_GetSigningTime (signerinfo, &stime) != SECSuccess) + stime = PR_Now(); /* not found or conversion failed, so check against now */ + + /* + * XXX This uses the signing time, if available. Additionally, we + * might want to, if there is no signing time, get the message time + * from the mail header itself, and use that. That would require + * a change to our interface though, and for S/MIME callers to pass + * in a time (and for non-S/MIME callers to pass in nothing, or + * maybe make them pass in the current time, always?). + */ + if (CERT_VerifyCert(certdb, cert, PR_TRUE, certusage, stime, signerinfo->cmsg->pwfn_arg, NULL) != SECSuccess) { + signerinfo->verificationStatus = NSSCMSVS_SigningCertNotTrusted; + return SECFailure; + } + return SECSuccess; +} + +/* + * NSS_CMSSignerInfo_Verify - verify the signature of a single SignerInfo + * + * Just verifies the signature. The assumption is that verification of the certificate + * is done already. + */ +SECStatus +NSS_CMSSignerInfo_Verify(NSSCMSSignerInfo *signerinfo, SECItem *digest, SECItem *contentType) +{ + SECKEYPublicKey *publickey = NULL; + NSSCMSAttribute *attr; + SECItem encoded_attrs; + CERTCertificate *cert; + NSSCMSVerificationStatus vs = NSSCMSVS_Unverified; + PLArenaPool *poolp; + + if (signerinfo == NULL) + return SECFailure; + + /* NSS_CMSSignerInfo_GetSigningCertificate will fail if 2nd parm is NULL and */ + /* cert has not been verified */ + if ((cert = NSS_CMSSignerInfo_GetSigningCertificate(signerinfo, NULL)) == NULL) { + vs = NSSCMSVS_SigningCertNotFound; + goto loser; + } + + if ((publickey = CERT_ExtractPublicKey(cert)) == NULL) { + vs = NSSCMSVS_ProcessingError; + goto loser; + } + + /* + * XXX This may not be the right set of algorithms to check. + * I'd prefer to trust that just calling VFY_Verify{Data,Digest} + * would do the right thing (and set an error if it could not); + * then additional algorithms could be handled by that code + * and we would Just Work. So this check should just be removed, + * but not until the VFY code is better at setting errors. + */ + switch (SECOID_GetAlgorithmTag(&(signerinfo->digestEncAlg))) { + case SEC_OID_PKCS1_RSA_ENCRYPTION: + case SEC_OID_ANSIX9_DSA_SIGNATURE: + case SEC_OID_ANSIX9_DSA_SIGNATURE_WITH_SHA1_DIGEST: + /* ok */ + break; + case SEC_OID_UNKNOWN: + vs = NSSCMSVS_SignatureAlgorithmUnknown; + goto loser; + default: + vs = NSSCMSVS_SignatureAlgorithmUnsupported; + goto loser; + } + + if (!NSS_CMSArray_IsEmpty((void **)signerinfo->authAttr)) { + if (contentType) { + /* + * Check content type + * + * RFC2630 sez that if there are any authenticated attributes, + * then there must be one for content type which matches the + * content type of the content being signed, and there must + * be one for message digest which matches our message digest. + * So check these things first. + */ + if ((attr = NSS_CMSAttributeArray_FindAttrByOidTag(signerinfo->authAttr, + SEC_OID_PKCS9_CONTENT_TYPE, PR_TRUE)) == NULL) + { + vs = NSSCMSVS_MalformedSignature; + goto loser; + } + + if (NSS_CMSAttribute_CompareValue(attr, contentType) == PR_FALSE) { + vs = NSSCMSVS_MalformedSignature; + goto loser; + } + } + + /* + * Check digest + */ + if ((attr = NSS_CMSAttributeArray_FindAttrByOidTag(signerinfo->authAttr, SEC_OID_PKCS9_MESSAGE_DIGEST, PR_TRUE)) == NULL) + { + vs = NSSCMSVS_MalformedSignature; + goto loser; + } + if (NSS_CMSAttribute_CompareValue(attr, digest) == PR_FALSE) { + vs = NSSCMSVS_DigestMismatch; + goto loser; + } + + if ((poolp = PORT_NewArena (1024)) == NULL) { + vs = NSSCMSVS_ProcessingError; + goto loser; + } + + /* + * Check signature + * + * The signature is based on a digest of the DER-encoded authenticated + * attributes. So, first we encode and then we digest/verify. + * we trust the decoder to have the attributes in the right (sorted) order + */ + encoded_attrs.data = NULL; + encoded_attrs.len = 0; + + if (NSS_CMSAttributeArray_Encode(poolp, &(signerinfo->authAttr), &encoded_attrs) == NULL || + encoded_attrs.data == NULL || encoded_attrs.len == 0) + { + vs = NSSCMSVS_ProcessingError; + goto loser; + } + + vs = (VFY_VerifyData (encoded_attrs.data, encoded_attrs.len, + publickey, &(signerinfo->encDigest), + SECOID_GetAlgorithmTag(&(signerinfo->digestEncAlg)), + signerinfo->cmsg->pwfn_arg) != SECSuccess) ? NSSCMSVS_BadSignature : NSSCMSVS_GoodSignature; + + PORT_FreeArena(poolp, PR_FALSE); /* awkward memory management :-( */ + + } else { + SECItem *sig; + + /* No authenticated attributes. The signature is based on the plain message digest. */ + sig = &(signerinfo->encDigest); + if (sig->len == 0) + goto loser; + + vs = (VFY_VerifyDigest(digest, publickey, sig, + SECOID_GetAlgorithmTag(&(signerinfo->digestEncAlg)), + signerinfo->cmsg->pwfn_arg) != SECSuccess) ? NSSCMSVS_BadSignature : NSSCMSVS_GoodSignature; + } + + if (vs == NSSCMSVS_BadSignature) { + /* + * XXX Change the generic error into our specific one, because + * in that case we get a better explanation out of the Security + * Advisor. This is really a bug in our error strings (the + * "generic" error has a lousy/wrong message associated with it + * which assumes the signature verification was done for the + * purposes of checking the issuer signature on a certificate) + * but this is at least an easy workaround and/or in the + * Security Advisor, which specifically checks for the error + * SEC_ERROR_PKCS7_BAD_SIGNATURE and gives more explanation + * in that case but does not similarly check for + * SEC_ERROR_BAD_SIGNATURE. It probably should, but then would + * probably say the wrong thing in the case that it *was* the + * certificate signature check that failed during the cert + * verification done above. Our error handling is really a mess. + */ + if (PORT_GetError() == SEC_ERROR_BAD_SIGNATURE) + PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE); + } + + if (publickey != NULL) + SECKEY_DestroyPublicKey (publickey); + + signerinfo->verificationStatus = vs; + + return (vs == NSSCMSVS_GoodSignature) ? SECSuccess : SECFailure; + +loser: + if (publickey != NULL) + SECKEY_DestroyPublicKey (publickey); + + signerinfo->verificationStatus = vs; + + PORT_SetError (SEC_ERROR_PKCS7_BAD_SIGNATURE); + return SECFailure; +} + +NSSCMSVerificationStatus +NSS_CMSSignerInfo_GetVerificationStatus(NSSCMSSignerInfo *signerinfo) +{ + return signerinfo->verificationStatus; +} + +SECOidData * +NSS_CMSSignerInfo_GetDigestAlg(NSSCMSSignerInfo *signerinfo) +{ + return SECOID_FindOID (&(signerinfo->digestAlg.algorithm)); +} + +SECOidTag +NSS_CMSSignerInfo_GetDigestAlgTag(NSSCMSSignerInfo *signerinfo) +{ + SECOidData *algdata; + + algdata = SECOID_FindOID (&(signerinfo->digestAlg.algorithm)); + if (algdata != NULL) + return algdata->offset; + else + return SEC_OID_UNKNOWN; +} + +CERTCertificateList * +NSS_CMSSignerInfo_GetCertList(NSSCMSSignerInfo *signerinfo) +{ + return signerinfo->certList; +} + +int +NSS_CMSSignerInfo_GetVersion(NSSCMSSignerInfo *signerinfo) +{ + unsigned long version; + + /* always take apart the SECItem */ + if (SEC_ASN1DecodeInteger(&(signerinfo->version), &version) != SECSuccess) + return 0; + else + return (int)version; +} + +/* + * NSS_CMSSignerInfo_GetSigningTime - return the signing time, + * in UTCTime format, of a CMS signerInfo. + * + * sinfo - signerInfo data for this signer + * + * Returns a pointer to XXXX (what?) + * A return value of NULL is an error. + */ +SECStatus +NSS_CMSSignerInfo_GetSigningTime(NSSCMSSignerInfo *sinfo, PRTime *stime) +{ + NSSCMSAttribute *attr; + SECItem *value; + + if (sinfo == NULL) + return SECFailure; + + if (sinfo->signingTime != 0) { + *stime = sinfo->signingTime; /* cached copy */ + return SECSuccess; + } + + attr = NSS_CMSAttributeArray_FindAttrByOidTag(sinfo->authAttr, SEC_OID_PKCS9_SIGNING_TIME, PR_TRUE); + /* XXXX multi-valued attributes NIH */ + if (attr == NULL || (value = NSS_CMSAttribute_GetValue(attr)) == NULL) + return SECFailure; + if (DER_UTCTimeToTime(stime, value) != SECSuccess) + return SECFailure; + sinfo->signingTime = *stime; /* make cached copy */ + return SECSuccess; +} + +/* + * Return the signing cert of a CMS signerInfo. + * + * the certs in the enclosing SignedData must have been imported already + */ +CERTCertificate * +NSS_CMSSignerInfo_GetSigningCertificate(NSSCMSSignerInfo *signerinfo, CERTCertDBHandle *certdb) +{ + CERTCertificate *cert; + + if (signerinfo->cert != NULL) + return signerinfo->cert; + + /* no certdb, and cert hasn't been set yet? */ + if (certdb == NULL) + return NULL; + + /* + * This cert will also need to be freed, but since we save it + * in signerinfo for later, we do not want to destroy it when + * we leave this function -- we let the clean-up of the entire + * cinfo structure later do the destroy of this cert. + */ + switch (signerinfo->signerIdentifier.identifierType) { + case NSSCMSSignerID_IssuerSN: + cert = CERT_FindCertByIssuerAndSN(certdb, signerinfo->signerIdentifier.id.issuerAndSN); + break; + case NSSCMSSignerID_SubjectKeyID: +#if 0 /* not yet implemented */ + cert = CERT_FindCertBySubjectKeyID(certdb, signerinfo->signerIdentifier.id.subjectKeyID); +#else + cert = NULL; +#endif + break; + default: + cert = NULL; + break; + } + + /* cert can be NULL at that point */ + signerinfo->cert = cert; /* earmark it */ + + return cert; +} + +/* + * NSS_CMSSignerInfo_GetSignerCommonName - return the common name of the signer + * + * sinfo - signerInfo data for this signer + * + * Returns a pointer to allocated memory, which must be freed. + * A return value of NULL is an error. + */ +char * +NSS_CMSSignerInfo_GetSignerCommonName(NSSCMSSignerInfo *sinfo) +{ + CERTCertificate *signercert; + + /* will fail if cert is not verified */ + if ((signercert = NSS_CMSSignerInfo_GetSigningCertificate(sinfo, NULL)) == NULL) + return NULL; + + return (CERT_GetCommonName(&signercert->subject)); +} + +/* + * NSS_CMSSignerInfo_GetSignerEmailAddress - return the common name of the signer + * + * sinfo - signerInfo data for this signer + * + * Returns a pointer to allocated memory, which must be freed. + * A return value of NULL is an error. + */ +char * +NSS_CMSSignerInfo_GetSignerEmailAddress(NSSCMSSignerInfo *sinfo) +{ + CERTCertificate *signercert; + + if ((signercert = NSS_CMSSignerInfo_GetSigningCertificate(sinfo, NULL)) == NULL) + return NULL; + + if (signercert->emailAddr == NULL) + return NULL; + + return (PORT_Strdup(signercert->emailAddr)); +} + +/* + * NSS_CMSSignerInfo_AddAuthAttr - add an attribute to the + * authenticated (i.e. signed) attributes of "signerinfo". + */ +SECStatus +NSS_CMSSignerInfo_AddAuthAttr(NSSCMSSignerInfo *signerinfo, NSSCMSAttribute *attr) +{ + return NSS_CMSAttributeArray_AddAttr(signerinfo->cmsg->poolp, &(signerinfo->authAttr), attr); +} + +/* + * NSS_CMSSignerInfo_AddUnauthAttr - add an attribute to the + * unauthenticated attributes of "signerinfo". + */ +SECStatus +NSS_CMSSignerInfo_AddUnauthAttr(NSSCMSSignerInfo *signerinfo, NSSCMSAttribute *attr) +{ + return NSS_CMSAttributeArray_AddAttr(signerinfo->cmsg->poolp, &(signerinfo->unAuthAttr), attr); +} + +/* + * NSS_CMSSignerInfo_AddSigningTime - add the signing time to the + * authenticated (i.e. signed) attributes of "signerinfo". + * + * This is expected to be included in outgoing signed + * messages for email (S/MIME) but is likely useful in other situations. + * + * This should only be added once; a second call will do nothing. + * + * XXX This will probably just shove the current time into "signerinfo" + * but it will not actually get signed until the entire item is + * processed for encoding. Is this (expected to be small) delay okay? + */ +SECStatus +NSS_CMSSignerInfo_AddSigningTime(NSSCMSSignerInfo *signerinfo, PRTime t) +{ + NSSCMSAttribute *attr; + SECItem stime; + void *mark; + PLArenaPool *poolp; + + poolp = signerinfo->cmsg->poolp; + + mark = PORT_ArenaMark(poolp); + + /* create new signing time attribute */ + if (DER_TimeToUTCTime(&stime, t) != SECSuccess) + goto loser; + + if ((attr = NSS_CMSAttribute_Create(poolp, SEC_OID_PKCS9_SIGNING_TIME, &stime, PR_FALSE)) == NULL) { + SECITEM_FreeItem (&stime, PR_FALSE); + goto loser; + } + + SECITEM_FreeItem (&stime, PR_FALSE); + + if (NSS_CMSSignerInfo_AddAuthAttr(signerinfo, attr) != SECSuccess) + goto loser; + + PORT_ArenaUnmark (poolp, mark); + + return SECSuccess; + +loser: + PORT_ArenaRelease (poolp, mark); + return SECFailure; +} + +/* + * NSS_CMSSignerInfo_AddCounterSignature - countersign a signerinfo + * + * 1. digest the DER-encoded signature value of the original signerinfo + * 2. create new signerinfo with correct version, sid, digestAlg + * 3. add message-digest authAttr, but NO content-type + * 4. sign the authAttrs + * 5. add the whole thing to original signerInfo's unAuthAttrs + * + * XXXX give back the new signerinfo? + */ +SECStatus +NSS_CMSSignerInfo_AddCounterSignature(NSSCMSSignerInfo *signerinfo, + SECOidTag digestalg, CERTCertificate signingcert) +{ + /* XXXX TBD XXXX */ + return SECFailure; +} + +/* + * XXXX the following needs to be done in the S/MIME layer code + * after signature of a signerinfo is verified + */ +SECStatus +NSS_SMIMESignerInfo_SaveSMIMEProfile(NSSCMSSignerInfo *signerinfo) +{ + CERTCertificate *cert; + SECItem *profile = NULL; + NSSCMSAttribute *attr; + SECItem *utc_stime; + int save_error; + SECStatus rv; + + /* XXX sanity check - see if verification status is ok */ + /* XXX 0 = unverified */ + + /* XXX find preferred encyption cert */ + + /* find the cert the signerinfo is signed with */ + cert = NSS_CMSSignerInfo_GetSigningCertificate(signerinfo, NULL); + if (cert == NULL || cert->emailAddr == NULL) + return SECFailure; + + /* + * Remember the current error set because we do not care about + * anything set by the functions we are about to call. + */ + save_error = PORT_GetError(); + + if (!NSS_CMSArray_IsEmpty((void **)signerinfo->authAttr)) { + attr = NSS_CMSAttributeArray_FindAttrByOidTag(signerinfo->authAttr, + SEC_OID_PKCS9_SMIME_CAPABILITIES, + PR_TRUE); + profile = NSS_CMSAttribute_GetValue(attr); + attr = NSS_CMSAttributeArray_FindAttrByOidTag(signerinfo->authAttr, + SEC_OID_PKCS9_SIGNING_TIME, + PR_TRUE); + utc_stime = NSS_CMSAttribute_GetValue(attr); + } + + rv = CERT_SaveSMimeProfile (cert, profile, utc_stime); + + /* + * Restore the saved error in case the calls above set a new + * one that we do not actually care about. + */ + PORT_SetError (save_error); + + return rv; +} + +/* + * NSS_CMSSignerInfo_IncludeCerts - set cert chain inclusion mode for this signer + */ +SECStatus +NSS_CMSSignerInfo_IncludeCerts(NSSCMSSignerInfo *signerinfo, NSSCMSCertChainMode cm, SECCertUsage usage) +{ + if (signerinfo->cert == NULL) + return SECFailure; + + switch (cm) { + case NSSCMSCM_None: + signerinfo->certList = NULL; + break; + case NSSCMSCM_CertOnly: + signerinfo->certList = CERT_CertListFromCert(signerinfo->cert); + break; + case NSSCMSCM_CertChain: + signerinfo->certList = CERT_CertChainFromCert(signerinfo->cert, usage, PR_FALSE); + break; + case NSSCMSCM_CertChainWithRoot: + signerinfo->certList = CERT_CertChainFromCert(signerinfo->cert, usage, PR_TRUE); + break; + } + + if (cm != NSSCMSCM_None && signerinfo->certList == NULL) + return SECFailure; + + return SECSuccess; +} diff --git a/security/nss/lib/smime/cmst.h b/security/nss/lib/smime/cmst.h new file mode 100644 index 000000000..5f7fd0984 --- /dev/null +++ b/security/nss/lib/smime/cmst.h @@ -0,0 +1,489 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * Header for CMS types. + * + * $Id$ + */ + +#ifndef _CMST_H_ +#define _CMST_H_ + +#include "seccomon.h" +#include "secoidt.h" +#include "certt.h" +#include "secmodt.h" +#include "secmodt.h" + +#include "plarena.h" + +/* Non-opaque objects. NOTE, though: I want them to be treated as + * opaque as much as possible. If I could hide them completely, + * I would. (I tried, but ran into trouble that was taking me too + * much time to get out of.) I still intend to try to do so. + * In fact, the only type that "outsiders" should even *name* is + * NSSCMSMessage, and they should not reference its fields. + */ +/* rjr: PKCS #11 cert handling (pk11cert.c) does use NSSCMSRecipientInfo's. + * This is because when we search the recipient list for the cert and key we + * want, we need to invert the order of the loops we used to have. The old + * loops were: + * + * For each recipient { + * find_cert = PK11_Find_AllCert(recipient->issuerSN); + * [which unrolls to... ] + * For each slot { + * Log into slot; + * search slot for cert; + * } + * } + * + * the new loop searchs all the recipients at once on a slot. this allows + * PKCS #11 to order slots in such a way that logout slots don't get checked + * if we can find the cert on a logged in slot. This eliminates lots of + * spurious password prompts when smart cards are installed... so why this + * comment? If you make NSSCMSRecipientInfo completely opaque, you need + * to provide a non-opaque list of issuerSN's (the only field PKCS#11 needs + * and fix up pk11cert.c first. NOTE: Only S/MIME calls this special PKCS #11 + * function. + */ + +typedef struct NSSCMSMessageStr NSSCMSMessage; + +typedef union NSSCMSContentUnion NSSCMSContent; +typedef struct NSSCMSContentInfoStr NSSCMSContentInfo; + +typedef struct NSSCMSSignedDataStr NSSCMSSignedData; +typedef struct NSSCMSSignerInfoStr NSSCMSSignerInfo; +typedef struct NSSCMSSignerIdentifierStr NSSCMSSignerIdentifier; + +typedef struct NSSCMSEnvelopedDataStr NSSCMSEnvelopedData; +typedef struct NSSCMSOriginatorInfoStr NSSCMSOriginatorInfo; +typedef struct NSSCMSRecipientInfoStr NSSCMSRecipientInfo; + +typedef struct NSSCMSDigestedDataStr NSSCMSDigestedData; +typedef struct NSSCMSEncryptedDataStr NSSCMSEncryptedData; + +typedef struct NSSCMSSMIMEKEAParametersStr NSSCMSSMIMEKEAParameters; + +typedef struct NSSCMSAttributeStr NSSCMSAttribute; + +typedef struct NSSCMSDecoderContextStr NSSCMSDecoderContext; +typedef struct NSSCMSEncoderContextStr NSSCMSEncoderContext; + +typedef struct NSSCMSCipherContextStr NSSCMSCipherContext; +typedef struct NSSCMSDigestContextStr NSSCMSDigestContext; + +/* + * Type of function passed to NSSCMSDecode or NSSCMSDecoderStart. + * If specified, this is where the content bytes (only) will be "sent" + * as they are recovered during the decoding. + * And: + * Type of function passed to NSSCMSEncode or NSSCMSEncoderStart. + * This is where the DER-encoded bytes will be "sent". + * + * XXX Should just combine this with NSSCMSEncoderContentCallback type + * and use a simpler, common name. + */ +typedef void (*NSSCMSContentCallback)(void *arg, const char *buf, unsigned long len); + +/* + * Type of function passed to NSSCMSDecode or NSSCMSDecoderStart + * to retrieve the decryption key. This function is intended to be + * used for EncryptedData content info's which do not have a key available + * in a certificate, etc. + */ +typedef PK11SymKey *(*NSSCMSGetDecryptKeyCallback)(void *arg, SECAlgorithmID *algid); + + +/* ============================================================================= + * ENCAPSULATED CONTENTINFO & CONTENTINFO + */ + +union NSSCMSContentUnion { + /* either unstructured */ + SECItem * data; + /* or structured data */ + NSSCMSDigestedData * digestedData; + NSSCMSEncryptedData * encryptedData; + NSSCMSEnvelopedData * envelopedData; + NSSCMSSignedData * signedData; + /* or anonymous pointer to something */ + void * pointer; +}; + +struct NSSCMSContentInfoStr { + SECItem contentType; + NSSCMSContent content; + /* --------- local; not part of encoding --------- */ + SECOidData * contentTypeTag; + + /* additional info for encryptedData and envelopedData */ + /* we waste this space for signedData and digestedData. sue me. */ + + SECAlgorithmID contentEncAlg; + SECItem * rawContent; /* encrypted DER, optional */ + /* XXXX bytes not encrypted, but encoded? */ + /* --------- local; not part of encoding --------- */ + PK11SymKey * bulkkey; /* bulk encryption key */ + int keysize; /* size of bulk encryption key + * (only used by creation code) */ + SECOidTag contentEncAlgTag; /* oid tag of encryption algorithm + * (only used by creation code) */ + NSSCMSCipherContext *ciphcx; /* context for en/decryption going on */ + NSSCMSDigestContext *digcx; /* context for digesting going on */ +}; + +/* ============================================================================= + * MESSAGE + */ + +struct NSSCMSMessageStr { + NSSCMSContentInfo contentInfo; /* "outer" cinfo */ + /* --------- local; not part of encoding --------- */ + PLArenaPool * poolp; + PRBool poolp_is_ours; + int refCount; + /* properties of the "inner" data */ + SECAlgorithmID ** detached_digestalgs; + SECItem ** detached_digests; + void * pwfn_arg; + NSSCMSGetDecryptKeyCallback decrypt_key_cb; + void * decrypt_key_cb_arg; +}; + +/* ============================================================================= + * SIGNEDDATA + */ + +struct NSSCMSSignedDataStr { + SECItem version; + SECAlgorithmID ** digestAlgorithms; + NSSCMSContentInfo contentInfo; + SECItem ** rawCerts; + CERTSignedCrl ** crls; + NSSCMSSignerInfo ** signerInfos; + /* --------- local; not part of encoding --------- */ + NSSCMSMessage * cmsg; /* back pointer to message */ + SECItem ** digests; + CERTCertificate ** certs; + CERTCertificateList ** certLists; +}; +#define NSS_CMS_SIGNED_DATA_VERSION_BASIC 1 /* what we *create* */ +#define NSS_CMS_SIGNED_DATA_VERSION_EXT 3 /* what we *create* */ + +typedef enum { + NSSCMSVS_Unverified = 0, + NSSCMSVS_GoodSignature, + NSSCMSVS_BadSignature, + NSSCMSVS_DigestMismatch, + NSSCMSVS_SigningCertNotFound, + NSSCMSVS_SigningCertNotTrusted, + NSSCMSVS_SignatureAlgorithmUnknown, + NSSCMSVS_SignatureAlgorithmUnsupported, + NSSCMSVS_MalformedSignature, + NSSCMSVS_ProcessingError +} NSSCMSVerificationStatus; + +typedef enum { + NSSCMSSignerID_IssuerSN, + NSSCMSSignerID_SubjectKeyID +} NSSCMSSignerIDSelector; + +struct NSSCMSSignerIdentifierStr { + NSSCMSSignerIDSelector identifierType; + union { + CERTIssuerAndSN *issuerAndSN; + SECItem *subjectKeyID; + } id; +}; + +struct NSSCMSSignerInfoStr { + SECItem version; + NSSCMSSignerIdentifier signerIdentifier; + SECAlgorithmID digestAlg; + NSSCMSAttribute ** authAttr; + SECAlgorithmID digestEncAlg; + SECItem encDigest; + NSSCMSAttribute ** unAuthAttr; + /* --------- local; not part of encoding --------- */ + NSSCMSMessage * cmsg; /* back pointer to message */ + CERTCertificate * cert; + CERTCertificateList * certList; + PRTime signingTime; + NSSCMSVerificationStatus verificationStatus; +}; +#define NSS_CMS_SIGNER_INFO_VERSION_ISSUERSN 1 /* what we *create* */ +#define NSS_CMS_SIGNER_INFO_VERSION_SUBJKEY 3 /* what we *create* */ + +typedef enum { + NSSCMSCM_None = 0, + NSSCMSCM_CertOnly, + NSSCMSCM_CertChain, + NSSCMSCM_CertChainWithRoot +} NSSCMSCertChainMode; + +/* ============================================================================= + * ENVELOPED DATA + */ +struct NSSCMSEnvelopedDataStr { + SECItem version; + NSSCMSOriginatorInfo * originatorInfo; /* optional */ + NSSCMSRecipientInfo ** recipientInfos; + NSSCMSContentInfo contentInfo; + NSSCMSAttribute ** unprotectedAttr; + /* --------- local; not part of encoding --------- */ + NSSCMSMessage * cmsg; /* back pointer to message */ +}; +#define NSS_CMS_ENVELOPED_DATA_VERSION_REG 0 /* what we *create* */ +#define NSS_CMS_ENVELOPED_DATA_VERSION_ADV 2 /* what we *create* */ + +struct NSSCMSOriginatorInfoStr { + SECItem ** rawCerts; + CERTSignedCrl ** crls; + /* --------- local; not part of encoding --------- */ + CERTCertificate ** certs; +}; + +/* ----------------------------------------------------------------------------- + * key transport recipient info + */ +typedef enum { + NSSCMSRecipientID_IssuerSN, + NSSCMSRecipientID_SubjectKeyID +} NSSCMSRecipientIDSelector; + +struct NSSCMSRecipientIdentifierStr { + NSSCMSRecipientIDSelector identifierType; + union { + CERTIssuerAndSN *issuerAndSN; + SECItem *subjectKeyID; + } id; +}; +typedef struct NSSCMSRecipientIdentifierStr NSSCMSRecipientIdentifier; + +struct NSSCMSKeyTransRecipientInfoStr { + SECItem version; + NSSCMSRecipientIdentifier recipientIdentifier; + SECAlgorithmID keyEncAlg; + SECItem encKey; +}; +typedef struct NSSCMSKeyTransRecipientInfoStr NSSCMSKeyTransRecipientInfo; + +#define NSS_CMS_KEYTRANS_RECIPIENT_INFO_VERSION_ISSUERSN 0 /* what we *create* */ +#define NSS_CMS_KEYTRANS_RECIPIENT_INFO_VERSION_SUBJKEY 2 /* what we *create* */ + +/* ----------------------------------------------------------------------------- + * key agreement recipient info + */ +struct NSSCMSOriginatorPublicKeyStr { + SECAlgorithmID algorithmIdentifier; + SECItem publicKey; /* bit string! */ +}; +typedef struct NSSCMSOriginatorPublicKeyStr NSSCMSOriginatorPublicKey; + +typedef enum { + NSSCMSOriginatorIDOrKey_IssuerSN, + NSSCMSOriginatorIDOrKey_SubjectKeyID, + NSSCMSOriginatorIDOrKey_OriginatorPublicKey +} NSSCMSOriginatorIDOrKeySelector; + +struct NSSCMSOriginatorIdentifierOrKeyStr { + NSSCMSOriginatorIDOrKeySelector identifierType; + union { + CERTIssuerAndSN *issuerAndSN; /* static-static */ + SECItem *subjectKeyID; /* static-static */ + NSSCMSOriginatorPublicKey originatorPublicKey; /* ephemeral-static */ + } id; +}; +typedef struct NSSCMSOriginatorIdentifierOrKeyStr NSSCMSOriginatorIdentifierOrKey; + +struct NSSCMSRecipientKeyIdentifierStr { + SECItem * subjectKeyIdentifier; + SECItem * date; /* optional */ + SECItem * other; /* optional */ +}; +typedef struct NSSCMSRecipientKeyIdentifierStr NSSCMSRecipientKeyIdentifier; + +typedef enum { + NSSCMSKeyAgreeRecipientID_IssuerSN, + NSSCMSKeyAgreeRecipientID_RKeyID +} NSSCMSKeyAgreeRecipientIDSelector; + +struct NSSCMSKeyAgreeRecipientIdentifierStr { + NSSCMSKeyAgreeRecipientIDSelector identifierType; + union { + CERTIssuerAndSN *issuerAndSN; + NSSCMSRecipientKeyIdentifier recipientKeyIdentifier; + } id; +}; +typedef struct NSSCMSKeyAgreeRecipientIdentifierStr NSSCMSKeyAgreeRecipientIdentifier; + +struct NSSCMSRecipientEncryptedKeyStr { + NSSCMSKeyAgreeRecipientIdentifier recipientIdentifier; + SECItem encKey; +}; +typedef struct NSSCMSRecipientEncryptedKeyStr NSSCMSRecipientEncryptedKey; + +struct NSSCMSKeyAgreeRecipientInfoStr { + SECItem version; + NSSCMSOriginatorIdentifierOrKey originatorIdentifierOrKey; + SECItem * ukm; /* optional */ + SECAlgorithmID keyEncAlg; + NSSCMSRecipientEncryptedKey ** recipientEncryptedKeys; +}; +typedef struct NSSCMSKeyAgreeRecipientInfoStr NSSCMSKeyAgreeRecipientInfo; + +#define NSS_CMS_KEYAGREE_RECIPIENT_INFO_VERSION 3 /* what we *create* */ + +/* ----------------------------------------------------------------------------- + * KEK recipient info + */ +struct NSSCMSKEKIdentifierStr { + SECItem keyIdentifier; + SECItem * date; /* optional */ + SECItem * other; /* optional */ +}; +typedef struct NSSCMSKEKIdentifierStr NSSCMSKEKIdentifier; + +struct NSSCMSKEKRecipientInfoStr { + SECItem version; + NSSCMSKEKIdentifier kekIdentifier; + SECAlgorithmID keyEncAlg; + SECItem encKey; +}; +typedef struct NSSCMSKEKRecipientInfoStr NSSCMSKEKRecipientInfo; + +#define NSS_CMS_KEK_RECIPIENT_INFO_VERSION 4 /* what we *create* */ + +/* ----------------------------------------------------------------------------- + * recipient info + */ + +typedef enum { + NSSCMSRecipientInfoID_KeyTrans, + NSSCMSRecipientInfoID_KeyAgree, + NSSCMSRecipientInfoID_KEK +} NSSCMSRecipientInfoIDSelector; + +struct NSSCMSRecipientInfoStr { + NSSCMSRecipientInfoIDSelector recipientInfoType; + union { + NSSCMSKeyTransRecipientInfo keyTransRecipientInfo; + NSSCMSKeyAgreeRecipientInfo keyAgreeRecipientInfo; + NSSCMSKEKRecipientInfo kekRecipientInfo; + } ri; + /* --------- local; not part of encoding --------- */ + NSSCMSMessage * cmsg; /* back pointer to message */ + CERTCertificate * cert; /* recipient's certificate */ +}; + +/* ============================================================================= + * DIGESTED DATA + */ +struct NSSCMSDigestedDataStr { + SECItem version; + SECAlgorithmID digestAlg; + NSSCMSContentInfo contentInfo; + SECItem digest; + /* --------- local; not part of encoding --------- */ + NSSCMSMessage * cmsg; /* back pointer */ + SECItem cdigest; /* calculated digest */ +}; +#define NSS_CMS_DIGESTED_DATA_VERSION_DATA 0 /* what we *create* */ +#define NSS_CMS_DIGESTED_DATA_VERSION_ENCAP 2 /* what we *create* */ + +/* ============================================================================= + * ENCRYPTED DATA + */ +struct NSSCMSEncryptedDataStr { + SECItem version; + NSSCMSContentInfo contentInfo; + NSSCMSAttribute ** unprotectedAttr; /* optional */ + /* --------- local; not part of encoding --------- */ + NSSCMSMessage * cmsg; /* back pointer */ +}; +#define NSS_CMS_ENCRYPTED_DATA_VERSION 0 /* what we *create* */ +#define NSS_CMS_ENCRYPTED_DATA_VERSION_UPATTR 2 /* what we *create* */ + +/* ============================================================================= + * FORTEZZA KEA + */ + +/* An enumerated type used to select templates based on the encryption + scenario and data specifics. */ +typedef enum { + NSSCMSKEAUsesSkipjack, + NSSCMSKEAUsesNonSkipjack, + NSSCMSKEAUsesNonSkipjackWithPaddedEncKey +} NSSCMSKEATemplateSelector; + +/* ### mwelch - S/MIME KEA parameters. These don't really fit here, + but I cannot think of a more appropriate place at this time. */ +struct NSSCMSSMIMEKEAParametersStr { + SECItem originatorKEAKey; /* sender KEA key (encrypted?) */ + SECItem originatorRA; /* random number generated by sender */ + SECItem nonSkipjackIV; /* init'n vector for SkipjackCBC64 + decryption of KEA key if Skipjack + is not the bulk algorithm used on + the message */ + SECItem bulkKeySize; /* if Skipjack is not the bulk + algorithm used on the message, + and the size of the bulk encryption + key is not the same as that of + originatorKEAKey (due to padding + perhaps), this field will contain + the real size of the bulk encryption + key. */ +}; + +/* + * ***************************************************************************** + * ***************************************************************************** + * ***************************************************************************** + */ + +/* + * See comment above about this type not really belonging to CMS. + */ +struct NSSCMSAttributeStr { + /* The following fields make up an encoded Attribute: */ + SECItem type; + SECItem ** values; /* data may or may not be encoded */ + /* The following fields are not part of an encoded Attribute: */ + SECOidData * typeTag; + PRBool encoded; /* when true, values are encoded */ +}; + +#endif /* _CMST_H_ */ diff --git a/security/nss/lib/smime/cmsutil.c b/security/nss/lib/smime/cmsutil.c new file mode 100644 index 000000000..c71d144f6 --- /dev/null +++ b/security/nss/lib/smime/cmsutil.c @@ -0,0 +1,362 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * CMS miscellaneous utility functions. + * + * $Id$ + */ + +#include "cmslocal.h" + +#include "cert.h" +#include "key.h" +#include "secasn1.h" +#include "secitem.h" +#include "secoid.h" +#include "pk11func.h" +#include "secerr.h" + +/* + * NSS_CMSArray_SortByDER - sort array of objects by objects' DER encoding + * + * make sure that the order of the objects guarantees valid DER (which must be + * in lexigraphically ascending order for a SET OF); if reordering is necessary it + * will be done in place (in objs). + */ +SECStatus +NSS_CMSArray_SortByDER(void **objs, const SEC_ASN1Template *objtemplate, void **objs2) +{ + PRArenaPool *poolp; + int num_objs; + SECItem **enc_objs; + SECStatus rv = SECFailure; + int i; + + if (objs == NULL) /* already sorted */ + return SECSuccess; + + num_objs = NSS_CMSArray_Count((void **)objs); + if (num_objs == 0 || num_objs == 1) /* already sorted. */ + return SECSuccess; + + poolp = PORT_NewArena (1024); /* arena for temporaries */ + if (poolp == NULL) + return SECFailure; /* no memory; nothing we can do... */ + + /* + * Allocate arrays to hold the individual encodings which we will use + * for comparisons and the reordered attributes as they are sorted. + */ + enc_objs = (SECItem **)PORT_ArenaZAlloc(poolp, (num_objs + 1) * sizeof(SECItem *)); + if (enc_objs == NULL) + goto loser; + + /* DER encode each individual object. */ + for (i = 0; i < num_objs; i++) { + enc_objs[i] = SEC_ASN1EncodeItem(poolp, NULL, objs[i], objtemplate); + if (enc_objs[i] == NULL) + goto loser; + } + enc_objs[num_objs] = NULL; + + /* now compare and sort objs by the order of enc_objs */ + NSS_CMSArray_Sort((void **)enc_objs, NSS_CMSUtil_DERCompare, objs, objs2); + + rv = SECSuccess; + +loser: + PORT_FreeArena (poolp, PR_FALSE); + return rv; +} + +/* + * NSS_CMSUtil_DERCompare - for use with NSS_CMSArray_Sort to + * sort arrays of SECItems containing DER + */ +int +NSS_CMSUtil_DERCompare(void *a, void *b) +{ + SECItem *der1 = (SECItem *)a; + SECItem *der2 = (SECItem *)b; + int j; + + /* + * Find the lowest (lexigraphically) encoding. One that is + * shorter than all the rest is known to be "less" because each + * attribute is of the same type (a SEQUENCE) and so thus the + * first octet of each is the same, and the second octet is + * the length (or the length of the length with the high bit + * set, followed by the length, which also works out to always + * order the shorter first). Two (or more) that have the + * same length need to be compared byte by byte until a mismatch + * is found. + */ + if (der1->len != der2->len) + return (der1->len < der2->len) ? -1 : 1; + + for (j = 0; j < der1->len; j++) { + if (der1->data[j] == der2->data[j]) + continue; + return (der1->data[j] < der2->data[j]) ? -1 : 1; + } + return 0; +} + +/* + * NSS_CMSAlgArray_GetIndexByAlgID - find a specific algorithm in an array of + * algorithms. + * + * algorithmArray - array of algorithm IDs + * algid - algorithmid of algorithm to pick + * + * Returns: + * An integer containing the index of the algorithm in the array or -1 if + * algorithm was not found. + */ +int +NSS_CMSAlgArray_GetIndexByAlgID(SECAlgorithmID **algorithmArray, SECAlgorithmID *algid) +{ + int i; + + if (algorithmArray == NULL || algorithmArray[0] == NULL) + return -1; + + for (i = 0; algorithmArray[i] != NULL; i++) { + if (SECOID_CompareAlgorithmID(algorithmArray[i], algid) == SECEqual) + break; /* bingo */ + } + + if (algorithmArray[i] == NULL) + return -1; /* not found */ + + return i; +} + +/* + * NSS_CMSAlgArray_GetIndexByAlgTag - find a specific algorithm in an array of + * algorithms. + * + * algorithmArray - array of algorithm IDs + * algtag - algorithm tag of algorithm to pick + * + * Returns: + * An integer containing the index of the algorithm in the array or -1 if + * algorithm was not found. + */ +int +NSS_CMSAlgArray_GetIndexByAlgTag(SECAlgorithmID **algorithmArray, SECOidTag algtag) +{ + SECOidData *algid; + int i; + + if (algorithmArray == NULL || algorithmArray[0] == NULL) + return -1; + + for (i = 0; algorithmArray[i] != NULL; i++) { + algid = SECOID_FindOID(&(algorithmArray[i]->algorithm)); + if (algid->offset == algtag) + break; /* bingo */ + } + + if (algorithmArray[i] == NULL) + return -1; /* not found */ + + return i; +} + +SECHashObject * +NSS_CMSUtil_GetHashObjByAlgID(SECAlgorithmID *algid) +{ + SECOidData *oiddata; + SECHashObject *digobj; + + /* here are the algorithms we know */ + oiddata = SECOID_FindOID(&(algid->algorithm)); + if (oiddata == NULL) { + digobj = NULL; + } else { + switch (oiddata->offset) { + case SEC_OID_MD2: + digobj = &SECHashObjects[HASH_AlgMD2]; + break; + case SEC_OID_MD5: + digobj = &SECHashObjects[HASH_AlgMD5]; + break; + case SEC_OID_SHA1: + digobj = &SECHashObjects[HASH_AlgSHA1]; + break; + default: + digobj = NULL; + break; + } + } + return digobj; +} + +/* + * XXX I would *really* like to not have to do this, but the current + * signing interface gives me little choice. + */ +SECOidTag +NSS_CMSUtil_MakeSignatureAlgorithm(SECOidTag hashalg, SECOidTag encalg) +{ + switch (encalg) { + case SEC_OID_PKCS1_RSA_ENCRYPTION: + switch (hashalg) { + case SEC_OID_MD2: + return SEC_OID_PKCS1_MD2_WITH_RSA_ENCRYPTION; + case SEC_OID_MD5: + return SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION; + case SEC_OID_SHA1: + return SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION; + default: + return SEC_OID_UNKNOWN; + } + case SEC_OID_ANSIX9_DSA_SIGNATURE: + case SEC_OID_MISSI_KEA_DSS: + case SEC_OID_MISSI_DSS: + switch (hashalg) { + case SEC_OID_SHA1: + return SEC_OID_ANSIX9_DSA_SIGNATURE_WITH_SHA1_DIGEST; + default: + return SEC_OID_UNKNOWN; + } + default: + break; + } + + return encalg; /* maybe it is already the right algid */ +} + +const SEC_ASN1Template * +NSS_CMSUtil_GetTemplateByTypeTag(SECOidTag type) +{ + const SEC_ASN1Template *template; + extern const SEC_ASN1Template NSSCMSSignedDataTemplate[]; + extern const SEC_ASN1Template NSSCMSEnvelopedDataTemplate[]; + extern const SEC_ASN1Template NSSCMSEncryptedDataTemplate[]; + extern const SEC_ASN1Template NSSCMSDigestedDataTemplate[]; + + switch (type) { + case SEC_OID_PKCS7_SIGNED_DATA: + template = NSSCMSSignedDataTemplate; + break; + case SEC_OID_PKCS7_ENVELOPED_DATA: + template = NSSCMSEnvelopedDataTemplate; + break; + case SEC_OID_PKCS7_ENCRYPTED_DATA: + template = NSSCMSEncryptedDataTemplate; + break; + case SEC_OID_PKCS7_DIGESTED_DATA: + template = NSSCMSDigestedDataTemplate; + break; + default: + case SEC_OID_PKCS7_DATA: + template = NULL; + break; + } + return template; +} + +size_t +NSS_CMSUtil_GetSizeByTypeTag(SECOidTag type) +{ + size_t size; + + switch (type) { + case SEC_OID_PKCS7_SIGNED_DATA: + size = sizeof(NSSCMSSignedData); + break; + case SEC_OID_PKCS7_ENVELOPED_DATA: + size = sizeof(NSSCMSEnvelopedData); + break; + case SEC_OID_PKCS7_ENCRYPTED_DATA: + size = sizeof(NSSCMSEncryptedData); + break; + case SEC_OID_PKCS7_DIGESTED_DATA: + size = sizeof(NSSCMSDigestedData); + break; + default: + case SEC_OID_PKCS7_DATA: + size = 0; + break; + } + return size; +} + +NSSCMSContentInfo * +NSS_CMSContent_GetContentInfo(void *msg, SECOidTag type) +{ + NSSCMSContent c; + NSSCMSContentInfo *cinfo; + + PORT_Assert(msg != NULL); + + c.pointer = msg; + switch (type) { + case SEC_OID_PKCS7_SIGNED_DATA: + cinfo = &(c.signedData->contentInfo); + break; + case SEC_OID_PKCS7_ENVELOPED_DATA: + cinfo = &(c.envelopedData->contentInfo); + break; + case SEC_OID_PKCS7_ENCRYPTED_DATA: + cinfo = &(c.encryptedData->contentInfo); + break; + case SEC_OID_PKCS7_DIGESTED_DATA: + cinfo = &(c.digestedData->contentInfo); + break; + default: + cinfo = NULL; + } + return cinfo; +} + +const char * +NSS_CMSUtil_VerificationStatusToString(NSSCMSVerificationStatus vs) +{ + switch (vs) { + case NSSCMSVS_Unverified: return "Unverified"; + case NSSCMSVS_GoodSignature: return "GoodSignature"; + case NSSCMSVS_BadSignature: return "BadSignature"; + case NSSCMSVS_DigestMismatch: return "DigestMismatch"; + case NSSCMSVS_SigningCertNotFound: return "SigningCertNotFound"; + case NSSCMSVS_SigningCertNotTrusted: return "SigningCertNotTrusted"; + case NSSCMSVS_SignatureAlgorithmUnknown: return "SignatureAlgorithmUnknown"; + case NSSCMSVS_SignatureAlgorithmUnsupported: return "SignatureAlgorithmUnsupported"; + case NSSCMSVS_MalformedSignature: return "MalformedSignature"; + case NSSCMSVS_ProcessingError: return "ProcessingError"; + default: return "Unknown"; + } +} diff --git a/security/nss/lib/smime/config.mk b/security/nss/lib/smime/config.mk new file mode 100644 index 000000000..a73a1086e --- /dev/null +++ b/security/nss/lib/smime/config.mk @@ -0,0 +1,44 @@ +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Netscape security libraries. +# +# The Initial Developer of the Original Code is Netscape +# Communications Corporation. Portions created by Netscape are +# Copyright (C) 1994-2000 Netscape Communications Corporation. All +# Rights Reserved. +# +# Contributor(s): +# +# Alternatively, the contents of this file may be used under the +# terms of the GNU General Public License Version 2 or later (the +# "GPL"), in which case the provisions of the GPL are applicable +# instead of those above. If you wish to allow use of your +# version of this file only under the terms of the GPL and not to +# allow others to use your version of this file under the MPL, +# indicate your decision by deleting the provisions above and +# replace them with the notice and other provisions required by +# the GPL. If you do not delete the provisions above, a recipient +# may use your version of this file under either the MPL or the +# GPL. +# + +# +# Override TARGETS variable so that only static libraries +# are specifed as dependencies within rules.mk. +# + +TARGETS = $(LIBRARY) +SHARED_LIBRARY = +IMPORT_LIBRARY = +PURE_LIBRARY = +PROGRAM = + diff --git a/security/nss/lib/smime/manifest.mn b/security/nss/lib/smime/manifest.mn new file mode 100644 index 000000000..c7fef112b --- /dev/null +++ b/security/nss/lib/smime/manifest.mn @@ -0,0 +1,74 @@ +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Netscape security libraries. +# +# The Initial Developer of the Original Code is Netscape +# Communications Corporation. Portions created by Netscape are +# Copyright (C) 1994-2000 Netscape Communications Corporation. All +# Rights Reserved. +# +# Contributor(s): +# +# Alternatively, the contents of this file may be used under the +# terms of the GNU General Public License Version 2 or later (the +# "GPL"), in which case the provisions of the GPL are applicable +# instead of those above. If you wish to allow use of your +# version of this file only under the terms of the GPL and not to +# allow others to use your version of this file under the MPL, +# indicate your decision by deleting the provisions above and +# replace them with the notice and other provisions required by +# the GPL. If you do not delete the provisions above, a recipient +# may use your version of this file under either the MPL or the +# GPL. +# + +CORE_DEPTH = ../../.. + +EXPORTS = \ + cms.h \ + cmst.h \ + smime.h \ + cmsreclist.h \ + $(NULL) + +PRIVATE_EXPORTS = \ + cmslocal.h \ + $(NULL) + +MODULE = security + +CSRCS = \ + cmsarray.c \ + cmsasn1.c \ + cmsattr.c \ + cmscinfo.c \ + cmscipher.c \ + cmsdecode.c \ + cmsdigdata.c \ + cmsdigest.c \ + cmsencdata.c \ + cmsencode.c \ + cmsenvdata.c \ + cmsmessage.c \ + cmspubkey.c \ + cmsrecinfo.c \ + cmsreclist.c \ + cmssigdata.c \ + cmssiginfo.c \ + cmsutil.c \ + smimemessage.c \ + smimeutil.c \ + $(NULL) + +REQUIRES = security dbm + +LIBRARY_NAME = smime diff --git a/security/nss/lib/smime/smime.h b/security/nss/lib/smime/smime.h new file mode 100644 index 000000000..dd4cc6b76 --- /dev/null +++ b/security/nss/lib/smime/smime.h @@ -0,0 +1,139 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * Header file for routines specific to S/MIME. Keep things that are pure + * pkcs7 out of here; this is for S/MIME policy, S/MIME interoperability, etc. + * + * $Id$ + */ + +#ifndef _SECMIME_H_ +#define _SECMIME_H_ 1 + +#include "cms.h" + + +/************************************************************************/ +SEC_BEGIN_PROTOS + +/* + * Initialize the local recording of the user S/MIME cipher preferences. + * This function is called once for each cipher, the order being + * important (first call records greatest preference, and so on). + * When finished, it is called with a "which" of CIPHER_FAMILID_MASK. + * If the function is called again after that, it is assumed that + * the preferences are being reset, and the old preferences are + * discarded. + * + * XXX This is for a particular user, and right now the storage is + * XXX local, static. The preference should be stored elsewhere to allow + * XXX for multiple uses of one library? How does SSL handle this; + * XXX it has something similar? + * + * - The "which" values are defined in ciferfam.h (the SMIME_* values, + * for example SMIME_DES_CBC_56). + * - If "on" is non-zero then the named cipher is enabled, otherwise + * it is disabled. (It is not necessary to call the function for + * ciphers that are disabled, however, as that is the default.) + * + * If the cipher preference is successfully recorded, SECSuccess + * is returned. Otherwise SECFailure is returned. The only errors + * are due to failure allocating memory or bad parameters/calls: + * SEC_ERROR_XXX ("which" is not in the S/MIME cipher family) + * SEC_ERROR_XXX (function is being called more times than there + * are known/expected ciphers) + */ +extern SECStatus NSS_SMIMEUtil_EnableCipher(long which, int on); + +/* + * Initialize the local recording of the S/MIME policy. + * This function is called to allow/disallow a particular cipher. + * + * XXX This is for a the current module, I think, so local, static storage + * XXX is okay. Is that correct, or could multiple uses of the same + * XXX library expect to operate under different policies? + * + * - The "which" values are defined in ciferfam.h (the SMIME_* values, + * for example SMIME_DES_CBC_56). + * - If "on" is non-zero then the named cipher is enabled, otherwise + * it is disabled. + */ +extern SECStatus NSS_SMIMEUtils_AllowCipher(long which, int on); + +/* + * Does the current policy allow S/MIME decryption of this particular + * algorithm and keysize? + */ +extern PRBool NSS_SMIMEUtil_DecryptionAllowed(SECAlgorithmID *algid, PK11SymKey *key); + +/* + * Does the current policy allow *any* S/MIME encryption (or decryption)? + * + * This tells whether or not *any* S/MIME encryption can be done, + * according to policy. Callers may use this to do nicer user interface + * (say, greying out a checkbox so a user does not even try to encrypt + * a message when they are not allowed to) or for any reason they want + * to check whether S/MIME encryption (or decryption, for that matter) + * may be done. + * + * It takes no arguments. The return value is a simple boolean: + * PR_TRUE means encryption (or decryption) is *possible* + * (but may still fail due to other reasons, like because we cannot + * find all the necessary certs, etc.; PR_TRUE is *not* a guarantee) + * PR_FALSE means encryption (or decryption) is not permitted + * + * There are no errors from this routine. + */ +extern PRBool NSS_SMIMEUtil_EncryptionPossible(void); + +/* + * NSS_SMIMEUtil_GetSMIMECapabilities - get S/MIME capabilities for this instance of NSS + * + * scans the list of allowed and enabled ciphers and construct a PKCS9-compliant + * S/MIME capabilities attribute value. + * + * "cert" - sender's certificate + */ +extern SECItem *NSS_SMIMEUtil_GetSMIMECapabilities(CERTCertificate *cert); + +/* + * NSS_SMIMEUtil_FindBulkAlgForRecipients - find bulk algorithm suitable for all recipients + */ +extern SECStatus +NSS_SMIMEUtil_FindBulkAlgForRecipients(CERTCertificate **rcerts, SECOidTag *bulkalgtag, int *keysize); + +/************************************************************************/ +SEC_END_PROTOS + +#endif /* _SECMIME_H_ */ diff --git a/security/nss/lib/smime/smimemessage.c b/security/nss/lib/smime/smimemessage.c new file mode 100644 index 000000000..cd6d079d6 --- /dev/null +++ b/security/nss/lib/smime/smimemessage.c @@ -0,0 +1,233 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * SMIME message methods + * + * $Id$ + */ + +#include "cmslocal.h" +#include "smime.h" + +#include "cert.h" +#include "key.h" +#include "secasn1.h" +#include "secitem.h" +#include "secoid.h" +#include "pk11func.h" +#include "prtime.h" +#include "secerr.h" + + +SECStatus +NSS_SMIMESignerInfo_AddSMIMEProfile(NSSCMSSignerInfo *signerinfo, CERTCertificate *cert) +{ + SECItem *smimecapsdata; + NSSCMSAttribute *smimecapsattr; + + if ((smimecapsdata = NSS_SMIMEUtil_GetSMIMECapabilities(cert)) == NULL) + return SECFailure; + + smimecapsattr = NSS_CMSAttribute_Create(signerinfo->cmsg->poolp, + SEC_OID_PKCS9_SMIME_CAPABILITIES, + smimecapsdata, PR_TRUE); + if (smimecapsattr == NULL) + return SECFailure; + + return NSS_CMSSignerInfo_AddAuthAttr(signerinfo, smimecapsattr); +} + +#if 0 +/* + * NSS_SMIMEMessage_CreateEncrypted - start an S/MIME encrypting context. + * + * "scert" is the cert for the sender. It will be checked for validity. + * "rcerts" are the certs for the recipients. They will also be checked. + * + * "certdb" is the cert database to use for verifying the certs. + * It can be NULL if a default database is available (like in the client). + * + * This function already does all of the stuff specific to S/MIME protocol + * and local policy; the return value just needs to be passed to + * SEC_PKCS7Encode() or to SEC_PKCS7EncoderStart() to create the encoded data, + * and finally to SEC_PKCS7DestroyContentInfo(). + * + * An error results in a return value of NULL and an error set. + * (Retrieve specific errors via PORT_GetError()/XP_GetError().) + */ +NSSCMSMessage * +NSS_SMIMEMessage_CreateEncrypted(CERTCertificate *scert, + CERTCertificate **rcerts, + CERTCertDBHandle *certdb, + PK11PasswordFunc pwfn, + void *pwfn_arg) +{ + NSSCMSMessage *cmsg; + long cipher; + SECOidTag encalg; + int keysize; + int mapi, rci; + + cipher = smime_choose_cipher (scert, rcerts); + if (cipher < 0) + return NULL; + + mapi = smime_mapi_by_cipher (cipher); + if (mapi < 0) + return NULL; + + /* + * XXX This is stretching it -- CreateEnvelopedData should probably + * take a cipher itself of some sort, because we cannot know what the + * future will bring in terms of parameters for each type of algorithm. + * For example, just an algorithm and keysize is *not* sufficient to + * fully specify the usage of RC5 (which also needs to know rounds and + * block size). Work this out into a better API! + */ + encalg = smime_cipher_map[mapi].algtag; + keysize = smime_keysize_by_cipher (cipher); + if (keysize < 0) + return NULL; + + cinfo = SEC_PKCS7CreateEnvelopedData (scert, certUsageEmailRecipient, + certdb, encalg, keysize, + pwfn, pwfn_arg); + if (cinfo == NULL) + return NULL; + + for (rci = 0; rcerts[rci] != NULL; rci++) { + if (rcerts[rci] == scert) + continue; + if (SEC_PKCS7AddRecipient (cinfo, rcerts[rci], certUsageEmailRecipient, + NULL) != SECSuccess) { + SEC_PKCS7DestroyContentInfo (cinfo); + return NULL; + } + } + + return cinfo; +} +#endif + + +/* + * Start an S/MIME signing context. + * + * "scert" is the cert that will be used to sign the data. It will be + * checked for validity. + * + * "ecert" is the signer's encryption cert. If it is different from + * scert, then it will be included in the signed message so that the + * recipient can save it for future encryptions. + * + * "certdb" is the cert database to use for verifying the cert. + * It can be NULL if a default database is available (like in the client). + * + * "digestalg" names the digest algorithm (e.g. SEC_OID_SHA1). + * XXX There should be SECMIME functions for hashing, or the hashing should + * be built into this interface, which we would like because we would + * support more smartcards that way, and then this argument should go away.) + * + * "digest" is the actual digest of the data. It must be provided in + * the case of detached data or NULL if the content will be included. + * + * This function already does all of the stuff specific to S/MIME protocol + * and local policy; the return value just needs to be passed to + * SEC_PKCS7Encode() or to SEC_PKCS7EncoderStart() to create the encoded data, + * and finally to SEC_PKCS7DestroyContentInfo(). + * + * An error results in a return value of NULL and an error set. + * (Retrieve specific errors via PORT_GetError()/XP_GetError().) + */ + +NSSCMSMessage * +NSS_SMIMEMessage_CreateSigned(CERTCertificate *scert, + CERTCertificate *ecert, + CERTCertDBHandle *certdb, + SECOidTag digestalgtag, + SECItem *digest, + PK11PasswordFunc pwfn, + void *pwfn_arg) +{ + NSSCMSMessage *cmsg; + NSSCMSSignedData *sigd; + NSSCMSSignerInfo *signerinfo; + + /* See note in header comment above about digestalg. */ + PORT_Assert (digestalgtag == SEC_OID_SHA1); + + cmsg = NSS_CMSMessage_Create(NULL); + if (cmsg == NULL) + return NULL; + + sigd = NSS_CMSSignedData_Create(cmsg); + if (sigd == NULL) + goto loser; + + /* create just one signerinfo */ + signerinfo = NSS_CMSSignerInfo_Create(cmsg, scert, digestalgtag); + if (signerinfo == NULL) + goto loser; + + /* Add the signing time to the signerinfo. */ + if (NSS_CMSSignerInfo_AddSigningTime(signerinfo, PR_Now()) != SECSuccess) + goto loser; + + /* and add the SMIME profile */ + if (NSS_SMIMESignerInfo_AddSMIMEProfile(signerinfo, scert) != SECSuccess) + goto loser; + + /* now add the signerinfo to the signeddata */ + if (NSS_CMSSignedData_AddSignerInfo(sigd, signerinfo) != SECSuccess) + goto loser; + + /* include the signing cert and its entire chain */ + /* note that there are no checks for duplicate certs in place, as all the */ + /* essential data structures (like set of certificate) are not there */ + if (NSS_CMSSignedData_AddCertChain(sigd, scert) != SECSuccess) + goto loser; + + /* If the encryption cert and the signing cert differ, then include + * the encryption cert too. */ + if ( ( ecert != NULL ) && ( ecert != scert ) ) { + if (NSS_CMSSignedData_AddCertificate(sigd, ecert) != SECSuccess) + goto loser; + } + + return cmsg; +loser: + if (cmsg) + NSS_CMSMessage_Destroy(cmsg); + return NULL; +} diff --git a/security/nss/lib/smime/smimeutil.c b/security/nss/lib/smime/smimeutil.c new file mode 100644 index 000000000..849dc703c --- /dev/null +++ b/security/nss/lib/smime/smimeutil.c @@ -0,0 +1,675 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * Stuff specific to S/MIME policy and interoperability. + * + * $Id$ + */ + +#include "secmime.h" +#include "secoid.h" +#include "pk11func.h" +#include "ciferfam.h" /* for CIPHER_FAMILY symbols */ +#include "secasn1.h" +#include "secitem.h" +#include "cert.h" +#include "key.h" +#include "secerr.h" + +/* various integer's ASN.1 encoding */ +static unsigned char asn1_int40[] = { SEC_ASN1_INTEGER, 0x01, 0x28 }; +static unsigned char asn1_int64[] = { SEC_ASN1_INTEGER, 0x01, 0x40 }; +static unsigned char asn1_int128[] = { SEC_ASN1_INTEGER, 0x02, 0x00, 0x80 }; + +/* RC2 algorithm parameters (used in smime_cipher_map) */ +static SECItem param_int40 = { siBuffer, asn1_int40, sizeof(asn1_int40) }; +static SECItem param_int64 = { siBuffer, asn1_int64, sizeof(asn1_int64) }; +static SECItem param_int128 = { siBuffer, asn1_int128, sizeof(asn1_int128) }; + +/* + * XXX Would like the "parameters" field to be a SECItem *, but the + * encoder is having trouble with optional pointers to an ANY. Maybe + * once that is fixed, can change this back... + */ +typedef struct { + SECItem capabilityID; + SECItem parameters; + long cipher; /* optimization */ +} NSSSMIMECapability; + +static const SEC_ASN1Template smime_capability_template[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(NSSSMIMECapability) }, + { SEC_ASN1_OBJECT_ID, + offsetof(NSSSMIMECapability,capabilityID), }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_ANY, + offsetof(NSSSMIMECapability,parameters), }, + { 0, } +}; + +static const SEC_ASN1Template smime_capabilities_template[] = { + { SEC_ASN1_SEQUENCE_OF, 0, smime_capability_template } +}; + +/* smime_cipher_map - map of SMIME symmetric "ciphers" to algtag & parameters */ +typedef struct { + unsigned long cipher; + SECOidTag algtag; + SECItem *parms; + PRBool enabled; /* in the user's preferences */ + PRBool allowed; /* per export policy */ +} smime_cipher_map_entry; + +/* global: list of supported SMIME symmetric ciphers, ordered roughly by increasing strength */ +static smime_cipher_map_entry smime_cipher_map[] = { +/* cipher algtag parms enabled allowed */ +/* ---------------------------------------------------------------------------------- */ + { SMIME_RC2_CBC_40, SEC_OID_RC2_CBC, ¶m_int40, PR_TRUE, PR_TRUE }, + { SMIME_DES_CBC_56, SEC_OID_DES_CBC, NULL, PR_TRUE, PR_TRUE }, + { SMIME_RC2_CBC_64, SEC_OID_RC2_CBC, ¶m_int64, PR_TRUE, PR_TRUE }, + { SMIME_RC2_CBC_128, SEC_OID_RC2_CBC, ¶m_int128, PR_TRUE, PR_TRUE }, + { SMIME_DES_EDE3_168, SEC_OID_DES_EDE3_CBC, NULL, PR_TRUE, PR_TRUE }, + { SMIME_FORTEZZA, SEC_OID_FORTEZZA_SKIPJACK, NULL, PR_TRUE, PR_TRUE } +}; +static const int smime_cipher_map_count = sizeof(smime_cipher_map) / sizeof(smime_cipher_map_entry); + +/* the other global variables */ +static PRBool smime_prefs_changed = PR_TRUE; +static NSSSMIMECapability **smime_capabilities; +static SECItem *smime_encoded_caps; +static PRBool lastUsedFortezza; + +/* + * smime_mapi_by_cipher - find index into smime_cipher_map by cipher + */ +static int +smime_mapi_by_cipher(unsigned long cipher) +{ + int i; + + for (i = 0; i < smime_cipher_map_count; i++) { + if (smime_cipher_map[i].cipher == cipher) + return i; /* bingo */ + } + return -1; /* should not happen if we're consistent, right? */ +} + +/* + * NSS_SMIME_EnableCipher - this function locally records the user's preference + */ +SECStatus +NSS_SMIMEUtil_EnableCipher(unsigned long which, PRBool on) +{ + unsigned long mask; + int mapi; + + mask = which & CIPHER_FAMILYID_MASK; + + PORT_Assert (mask == CIPHER_FAMILYID_SMIME); + if (mask != CIPHER_FAMILYID_SMIME) + /* XXX set an error! */ + return SECFailure; + + mapi = smime_mapi_by_cipher(which); + if (mapi < 0) + /* XXX set an error */ + return SECFailure; + + /* do we try to turn on a forbidden cipher? */ + if (!smime_cipher_map[mapi].allowed && on) { + PORT_SetError (SEC_ERROR_BAD_EXPORT_ALGORITHM); + return SECFailure; + } + + if (smime_cipher_map[mapi].enabled != on) { + smime_cipher_map[mapi].enabled = on; + smime_prefs_changed = PR_TRUE; + } + return SECSuccess; +} + + +/* + * this function locally records the export policy + */ +SECStatus +NSS_SMIMEUtil_AllowCipher(unsigned long which, PRBool on) +{ + unsigned long mask; + int mapi; + + mask = which & CIPHER_FAMILYID_MASK; + + PORT_Assert (mask == CIPHER_FAMILYID_SMIME); + if (mask != CIPHER_FAMILYID_SMIME) + /* XXX set an error! */ + return SECFailure; + + mapi = smime_mapi_by_cipher(which); + if (mapi < 0) + /* XXX set an error */ + return SECFailure; + + if (smime_cipher_map[mapi].allowed != on) { + smime_cipher_map[mapi].allowed = on; + smime_prefs_changed = PR_TRUE; + } + return SECSuccess; +} + +/* + * Based on the given algorithm (including its parameters, in some cases!) + * and the given key (may or may not be inspected, depending on the + * algorithm), find the appropriate policy algorithm specification + * and return it. If no match can be made, -1 is returned. + */ +static SECStatus +nss_smime_get_cipher_for_alg_and_key(SECAlgorithmID *algid, PK11SymKey *key, unsigned long *cipher) +{ + SECOidTag algtag; + unsigned int keylen_bits; + SECStatus rv = SECSuccess; + unsigned long c; + + algtag = SECOID_GetAlgorithmTag(algid); + switch (algtag) { + case SEC_OID_RC2_CBC: + keylen_bits = PK11_GetKeyStrength(key, algid); + switch (keylen_bits) { + case 40: + c = SMIME_RC2_CBC_40; + case 64: + c = SMIME_RC2_CBC_64; + case 128: + c = SMIME_RC2_CBC_128; + default: + rv = SECFailure; + break; + } + break; + case SEC_OID_DES_CBC: + c = SMIME_DES_CBC_56; + case SEC_OID_DES_EDE3_CBC: + c = SMIME_DES_EDE3_168; + case SEC_OID_FORTEZZA_SKIPJACK: + c = SMIME_FORTEZZA; + default: + rv = SECFailure; + } + if (rv == SECSuccess) + *cipher = c; + return rv; +} + +static PRBool +nss_smime_cipher_allowed(unsigned long which) +{ + int mapi; + + mapi = smime_mapi_by_cipher(which); + if (mapi < 0) + return PR_FALSE; + return smime_cipher_map[mapi].allowed; +} + +PRBool +NSS_SMIMEUtil_DecryptionAllowed(SECAlgorithmID *algid, PK11SymKey *key) +{ + unsigned long which; + + if (nss_smime_get_cipher_for_alg_and_key(algid, key, &which) != SECSuccess) + return PR_FALSE; + + return nss_smime_cipher_allowed(which); +} + + +/* + * NSS_SMIME_EncryptionPossible - check if any encryption is allowed + * + * This tells whether or not *any* S/MIME encryption can be done, + * according to policy. Callers may use this to do nicer user interface + * (say, greying out a checkbox so a user does not even try to encrypt + * a message when they are not allowed to) or for any reason they want + * to check whether S/MIME encryption (or decryption, for that matter) + * may be done. + * + * It takes no arguments. The return value is a simple boolean: + * PR_TRUE means encryption (or decryption) is *possible* + * (but may still fail due to other reasons, like because we cannot + * find all the necessary certs, etc.; PR_TRUE is *not* a guarantee) + * PR_FALSE means encryption (or decryption) is not permitted + * + * There are no errors from this routine. + */ +PRBool +NSS_SMIMEUtil_EncryptionPossible(void) +{ + int i; + + for (i = 0; i < smime_cipher_map_count; i++) { + if (smime_cipher_map[i].allowed) + return PR_TRUE; + } + return PR_FALSE; +} + + +static int +nss_SMIME_FindCipherForSMIMECap(NSSSMIMECapability *cap) +{ + int i; + SECOidTag capIDTag; + + /* we need the OIDTag here */ + capIDTag = SECOID_FindOIDTag(&(cap->capabilityID)); + + /* go over all the SMIME ciphers we know and see if we find a match */ + for (i = 0; i < smime_cipher_map_count; i++) { + if (smime_cipher_map[i].algtag != capIDTag) + continue; + /* + * XXX If SECITEM_CompareItem allowed NULLs as arguments (comparing + * 2 NULLs as equal and NULL and non-NULL as not equal), we could + * use that here instead of all of the following comparison code. + */ + if (cap->parameters.data == NULL && smime_cipher_map[i].parms == NULL) + break; /* both empty: bingo */ + + if (cap->parameters.data != NULL && smime_cipher_map[i].parms != NULL && + cap->parameters.len == smime_cipher_map[i].parms->len && + PORT_Memcmp (cap->parameters.data, smime_cipher_map[i].parms->data, + cap->parameters.len) == 0) + { + break; /* both not empty, same length & equal content: bingo */ + } + } + + if (i == smime_cipher_map_count) + return 0; /* no match found */ + else + return smime_cipher_map[i].cipher; /* match found, point to cipher */ +} + +/* + * smime_choose_cipher - choose a cipher that works for all the recipients + * + * "scert" - sender's certificate + * "rcerts" - recipient's certificates + */ +static long +smime_choose_cipher(CERTCertificate *scert, CERTCertificate **rcerts) +{ + PRArenaPool *poolp; + long cipher; + long chosen_cipher; + int *cipher_abilities; + int *cipher_votes; + int weak_mapi; + int strong_mapi; + int rcount, mapi, max, i; + PRBool scert_is_fortezza = (scert == NULL) ? PR_FALSE : PK11_FortezzaHasKEA(scert); + + chosen_cipher = SMIME_RC2_CBC_40; /* the default, LCD */ + weak_mapi = smime_mapi_by_cipher(chosen_cipher); + + poolp = PORT_NewArena (1024); /* XXX what is right value? */ + if (poolp == NULL) + goto done; + + cipher_abilities = (int *)PORT_ArenaZAlloc(poolp, smime_cipher_map_count * sizeof(int)); + cipher_votes = (int *)PORT_ArenaZAlloc(poolp, smime_cipher_map_count * sizeof(int)); + if (cipher_votes == NULL || cipher_abilities == NULL) + goto done; + + /* If the user has the Fortezza preference turned on, make + * that the strong cipher. Otherwise, use triple-DES. */ + strong_mapi = smime_mapi_by_cipher (SMIME_DES_EDE3_168); + if (scert_is_fortezza) { + mapi = smime_mapi_by_cipher(SMIME_FORTEZZA); + if (mapi >= 0 && smime_cipher_map[mapi].enabled) + strong_mapi = mapi; + } + + /* walk all the recipient's certs */ + for (rcount = 0; rcerts[rcount] != NULL; rcount++) { + SECItem *profile; + NSSSMIMECapability **caps; + int pref; + + /* the first cipher that matches in the user's SMIME profile gets + * "smime_cipher_map_count" votes; the next one gets "smime_cipher_map_count" - 1 + * and so on. If every cipher matches, the last one gets 1 (one) vote */ + pref = smime_cipher_map_count; + + /* find recipient's SMIME profile */ + profile = CERT_FindSMimeProfile(rcerts[rcount]); + + if (profile != NULL && profile->data != NULL && profile->len > 0) { + /* we have a profile */ + caps = NULL; + /* decode it */ + if (SEC_ASN1DecodeItem(poolp, &caps, smime_capabilities_template, profile) == SECSuccess && + caps != NULL) + { + /* walk the SMIME capabilities for this recipient */ + for (i = 0; caps[i] != NULL; i++) { + cipher = nss_SMIME_FindCipherForSMIMECap(caps[i]); + mapi = smime_mapi_by_cipher(cipher); + if (mapi >= 0) { + /* found the cipher */ + cipher_abilities[mapi]++; + cipher_votes[mapi] += pref; + --pref; + } + } + } + } else { + /* no profile found - so we can only assume that the user can do + * the mandatory algorithms which is RC2-40 (weak crypto) and 3DES (strong crypto) */ + SECKEYPublicKey *key; + unsigned int pklen_bits; + + /* + * if recipient's public key length is > 512, vote for a strong cipher + * please not that the side effect of this is that if only one recipient + * has an export-level public key, the strong cipher is disabled. + * + * XXX This is probably only good for RSA keys. What I would + * really like is a function to just say; Is the public key in + * this cert an export-length key? Then I would not have to + * know things like the value 512, or the kind of key, or what + * a subjectPublicKeyInfo is, etc. + */ + key = CERT_ExtractPublicKey(rcerts[rcount]); + pklen_bits = 0; + if (key != NULL) { + pklen_bits = SECKEY_PublicKeyStrength (key) * 8; + SECKEY_DestroyPublicKey (key); + } + + if (pklen_bits > 512) { + /* cast votes for the strong algorithm */ + cipher_abilities[strong_mapi]++; + cipher_votes[strong_mapi] += pref; + pref--; + } + + /* always cast (possibly less) votes for the weak algorithm */ + cipher_abilities[weak_mapi]++; + cipher_votes[weak_mapi] += pref; + } + if (profile != NULL) + SECITEM_FreeItem(profile, PR_TRUE); + } + + /* find cipher that is agreeable by all recipients and that has the most votes */ + max = 0; + for (mapi = 0; mapi < smime_cipher_map_count; mapi++) { + /* if not all of the recipients can do this, forget it */ + if (cipher_abilities[mapi] != rcount) + continue; + /* if cipher is not enabled or not allowed by policy, forget it */ + if (!smime_cipher_map[mapi].enabled || !smime_cipher_map[mapi].allowed) + continue; + /* if we're not doing fortezza, but the cipher is fortezza, forget it */ + if (!scert_is_fortezza && (smime_cipher_map[mapi].cipher == SMIME_FORTEZZA)) + continue; + /* now see if this one has more votes than the last best one */ + if (cipher_votes[mapi] >= max) { + /* if equal number of votes, prefer the ones further down in the list */ + /* with the expectation that these are higher rated ciphers */ + chosen_cipher = smime_cipher_map[mapi].cipher; + max = cipher_votes[mapi]; + } + } + /* if no common cipher was found, chosen_cipher stays at the default */ + +done: + if (poolp != NULL) + PORT_FreeArena (poolp, PR_FALSE); + + return chosen_cipher; +} + +/* + * XXX This is a hack for now to satisfy our current interface. + * Eventually, with more parameters needing to be specified, just + * looking up the keysize is not going to be sufficient. + */ +static int +smime_keysize_by_cipher (unsigned long which) +{ + int keysize; + + switch (which) { + case SMIME_RC2_CBC_40: + keysize = 40; + break; + case SMIME_RC2_CBC_64: + keysize = 64; + break; + case SMIME_RC2_CBC_128: + keysize = 128; + break; + case SMIME_DES_CBC_56: + case SMIME_DES_EDE3_168: + case SMIME_FORTEZZA: + /* + * These are special; since the key size is fixed, we actually + * want to *avoid* specifying a key size. + */ + keysize = 0; + break; + default: + keysize = -1; + break; + } + + return keysize; +} + +/* + * NSS_SMIMEUtil_FindBulkAlgForRecipients - find bulk algorithm suitable for all recipients + * + * it would be great for UI purposes if there would be a way to find out which recipients + * prevented a strong cipher from being used... + */ +SECStatus +NSS_SMIMEUtil_FindBulkAlgForRecipients(CERTCertificate **rcerts, SECOidTag *bulkalgtag, int *keysize) +{ + unsigned long cipher; + int mapi; + + cipher = smime_choose_cipher(NULL, rcerts); + mapi = smime_mapi_by_cipher(cipher); + + *bulkalgtag = smime_cipher_map[mapi].algtag; + *keysize = smime_keysize_by_cipher(smime_cipher_map[mapi].algtag); + + return SECSuccess; +} + +static SECStatus +smime_init_caps(PRBool isFortezza) +{ + NSSSMIMECapability *cap; + smime_cipher_map_entry *map; + SECOidData *oiddata; + SECStatus rv; + int i, capIndex; + + /* if we have caps, and the prefs did not change, and we are using fortezza as last time */ + /* we're done */ + if (smime_encoded_caps != NULL && (!smime_prefs_changed) && lastUsedFortezza == isFortezza) + return SECSuccess; + + /* ok, we need to cook up new caps. So throw the old ones away */ + if (smime_encoded_caps != NULL) { + SECITEM_FreeItem (smime_encoded_caps, PR_TRUE); + smime_encoded_caps = NULL; + } + + /* if we have an old NSSSMIMECapability array, we'll reuse it (has the right size) */ + if (smime_capabilities == NULL) { + smime_capabilities = (NSSSMIMECapability **)PORT_ZAlloc((smime_cipher_map_count + 1) + * sizeof(NSSSMIMECapability *)); + if (smime_capabilities == NULL) + return SECFailure; + } + + rv = SECFailure; + + /* + The process of creating the encoded CMS cipher capability list + involves two basic steps: + + (a) Convert our internal representation of cipher preferences + (smime_prefs) into an array containing cipher OIDs and + parameter data (smime_capabilities). This step is + performed here. + + (b) Encode, using ASN.1, the cipher information in + smime_capabilities, leaving the encoded result in + smime_encoded_caps. + + (In the process of performing (a), Lisa put in some optimizations + which allow us to avoid needlessly re-populating elements in + smime_capabilities as we walk through smime_prefs.) + + We want to use separate loop variables for smime_prefs and + smime_capabilities because in the case where the Skipjack cipher + is turned on in the prefs, but where we don't want to include + Skipjack in the encoded capabilities (presumably due to using a + non-fortezza cert when sending a message), we want to avoid creating + an empty element in smime_capabilities. This would otherwise cause + the encoding step to produce an empty set, since Skipjack happens + to be the first cipher in smime_prefs, if it is turned on. + */ + capIndex = 0; + for (i = 0; i < smime_cipher_map_count; i++) { + /* Find the corresponding entry in the cipher map. */ + map = &(smime_cipher_map[i]); + + if (!map->enabled) + continue; + + /* If we're using a non-Fortezza cert, only advertise non-Fortezza + capabilities. (We advertise all capabilities if we have a + Fortezza cert.) */ + if ((!isFortezza) && (map->cipher == SMIME_FORTEZZA)) + continue; + + /* get next SMIME capability */ + cap = smime_capabilities[capIndex]; + if (cap == NULL) { + cap = (NSSSMIMECapability *)PORT_ZAlloc(sizeof(NSSSMIMECapability)); + if (cap == NULL) + break; + smime_capabilities[capIndex] = cap; + } + capIndex++; + + if (cap->cipher == smime_cipher_map[i].cipher) + continue; /* no change to this one */ + + oiddata = SECOID_FindOIDByTag(map->algtag); + if (oiddata == NULL) + break; + + if (cap->capabilityID.data != NULL) { + SECITEM_FreeItem (&(cap->capabilityID), PR_FALSE); + cap->capabilityID.data = NULL; + cap->capabilityID.len = 0; + } + + rv = SECITEM_CopyItem(NULL, &(cap->capabilityID), &(oiddata->oid)); + if (rv != SECSuccess) + break; + + if (map->parms == NULL) { + cap->parameters.data = NULL; + cap->parameters.len = 0; + } else { + cap->parameters.data = map->parms->data; + cap->parameters.len = map->parms->len; + } + + cap->cipher = smime_cipher_map[i].cipher; + } + + while (capIndex < smime_cipher_map_count) { + cap = smime_capabilities[capIndex]; + if (cap != NULL) { + SECITEM_FreeItem(&(cap->capabilityID), PR_FALSE); + PORT_Free(cap); + } + smime_capabilities[capIndex] = NULL; + capIndex++; + } + smime_capabilities[capIndex] = NULL; /* last one */ + + smime_encoded_caps = SEC_ASN1EncodeItem (NULL, NULL, &smime_capabilities, + smime_capabilities_template); + if (smime_encoded_caps == NULL) + return SECFailure; + + lastUsedFortezza = isFortezza; + + return SECSuccess; +} + +/* + * NSS_SMIMEUtil_GetSMIMECapabilities - get S/MIME capabilities for this instance of NSS + * + * scans the list of allowed and enabled ciphers and construct a PKCS9-compliant + * S/MIME capabilities attribute value. + * + * "cert" - sender's certificate + */ +SECItem * +NSS_SMIMEUtil_GetSMIMECapabilities(CERTCertificate *cert) +{ + + PRBool isFortezza = PR_FALSE; + + /* See if the sender's cert specifies Fortezza key exchange. */ + if (cert != NULL) + isFortezza = PK11_FortezzaHasKEA(cert); + + if (smime_init_caps(isFortezza) != SECSuccess) + return NULL; + + return smime_encoded_caps; +} diff --git a/security/nss/lib/util/secasn1d.c b/security/nss/lib/util/secasn1d.c index cbc31d9cf..d1f9fd642 100644 --- a/security/nss/lib/util/secasn1d.c +++ b/security/nss/lib/util/secasn1d.c @@ -2121,6 +2121,9 @@ sec_asn1d_during_choice PORT_Assert((sec_asn1d_state *)NULL != child); if( child->missing ) { + unsigned char child_found_tag_modifiers = 0; + unsigned long child_found_tag_number = 0; + child->theTemplate++; if( 0 == child->theTemplate->kind ) { @@ -2149,14 +2152,24 @@ sec_asn1d_during_choice child->consumed = 0; sec_asn1d_scrub_state(child); + + /* move it on top again */ + state->top->current = child; + + child_found_tag_modifiers = child->found_tag_modifiers; + child_found_tag_number = child->found_tag_number; + child = sec_asn1d_init_state_based_on_template(child); if( (sec_asn1d_state *)NULL == child ) { return (sec_asn1d_state *)NULL; } + /* copy our findings to the new top */ + child->found_tag_modifiers = child_found_tag_modifiers; + child->found_tag_number = child_found_tag_number; + child->optional = PR_TRUE; child->place = afterIdentifier; - state->top->current = child; return child; } else { diff --git a/security/nss/lib/util/secasn1e.c b/security/nss/lib/util/secasn1e.c index 88a0e2136..bc1be4e47 100644 --- a/security/nss/lib/util/secasn1e.c +++ b/security/nss/lib/util/secasn1e.c @@ -476,7 +476,7 @@ sec_asn1e_contents_length (const SEC_ASN1Template *theTemplate, void *src, PRBool *noheaderp) { unsigned long encode_kind, underlying_kind; - PRBool explicit, optional, universal; + PRBool explicit, optional, universal, may_stream; unsigned long len; encode_kind = theTemplate->kind; @@ -492,7 +492,7 @@ sec_asn1e_contents_length (const SEC_ASN1Template *theTemplate, void *src, PORT_Assert (!(explicit && universal)); /* bad templates */ - /* We have already decided we are not streaming. */ + may_stream = (encode_kind & SEC_ASN1_MAY_STREAM) ? PR_TRUE : PR_FALSE; encode_kind &= ~SEC_ASN1_MAY_STREAM; /* Just clear this to get it out of the way; we do not need it here */ @@ -548,14 +548,12 @@ sec_asn1e_contents_length (const SEC_ASN1Template *theTemplate, void *src, if (len == 0 && optional) { *noheaderp = PR_TRUE; } else if (*noheaderp) { - /* - * Okay, *we* do not want to add in a header, but our - * caller still does. - */ + /* Okay, *we* do not want to add in a header, but our caller still does. */ *noheaderp = PR_FALSE; } else { - /* - * XXX The 1 below is the presumed length of the identifier; + /* if the inner content exists, our length is + * len(identifier) + len(length) + len(innercontent) + * XXX we currently assume len(identifier) == 1; * to support a high-tag-number this would need to be smarter. */ len += 1 + SEC_ASN1LengthLength (len); @@ -661,6 +659,8 @@ sec_asn1e_contents_length (const SEC_ASN1Template *theTemplate, void *src, default: len = ((SECItem *)src)->len; + if (may_stream && len == 0) + len = 1; /* if we're streaming, we may have a secitem w/len 0 as placeholder */ break; } @@ -678,6 +678,7 @@ sec_asn1e_write_header (sec_asn1e_state *state) { unsigned long contents_length; unsigned char tag_number, tag_modifiers; + PRBool noheader; PORT_Assert (state->place == beforeHeader); @@ -713,6 +714,29 @@ sec_asn1e_write_header (sec_asn1e_state *state) return; } + /* + * We are doing a definite-length encoding. First we have to + * walk the data structure to calculate the entire contents length. + */ + contents_length = sec_asn1e_contents_length (state->theTemplate, + state->src, &noheader); + /* + * We might be told explicitly not to put out a header. + * But it can also be the case, via a pushed subtemplate, that + * sec_asn1e_contents_length could not know that this field is + * really optional. So check for that explicitly, too. + */ + if (noheader || (contents_length == 0 && state->optional)) { + state->place = afterContents; + if (state->top->streaming && state->may_stream && state->top->from_buf) + /* we did not find an optional indefinite string, so we don't encode it. + * However, if TakeFromBuf is on, we stop here anyway to give our caller + * a chance to intercept at the same point where we would stop if the + * field were present. */ + state->top->status = needBytes; + return; + } + if (state->top->streaming && state->may_stream && (state->top->from_buf || !state->is_string)) { /* @@ -730,26 +754,7 @@ sec_asn1e_write_header (sec_asn1e_state *state) || state->is_string); tag_modifiers |= SEC_ASN1_CONSTRUCTED; contents_length = 0; - } else { - PRBool noheader; - - /* - * We are doing a definite-length encoding. First we have to - * walk the data structure to calculate the entire contents length. - */ - contents_length = sec_asn1e_contents_length (state->theTemplate, - state->src, &noheader); - /* - * We might be told explicitly not to put out a header. - * But it can also be the case, via a pushed subtemplate, that - * sec_asn1e_contents_length could not know that this field is - * really optional. So check for that explicitly, too. - */ - if (noheader || (contents_length == 0 && state->optional)) { - state->place = afterContents; - return; - } - } + } sec_asn1e_write_identifier_bytes (state, tag_number | tag_modifiers); sec_asn1e_write_length_bytes (state, contents_length, state->indefinite); diff --git a/security/nss/lib/util/secerr.h b/security/nss/lib/util/secerr.h index b0aab7894..8b152f8e4 100644 --- a/security/nss/lib/util/secerr.h +++ b/security/nss/lib/util/secerr.h @@ -176,7 +176,10 @@ SEC_ERROR_OCSP_NO_DEFAULT_RESPONDER = (SEC_ERROR_BASE + 128), SEC_ERROR_OCSP_MALFORMED_RESPONSE = (SEC_ERROR_BASE + 129), SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE = (SEC_ERROR_BASE + 130), SEC_ERROR_OCSP_FUTURE_RESPONSE = (SEC_ERROR_BASE + 131), -SEC_ERROR_OCSP_OLD_RESPONSE = (SEC_ERROR_BASE + 132) +SEC_ERROR_OCSP_OLD_RESPONSE = (SEC_ERROR_BASE + 132), +/* smime stuff */ +SEC_ERROR_DIGEST_NOT_FOUND = (SEC_ERROR_BASE + 133), +SEC_ERROR_UNSUPPORTED_MESSAGE_TYPE = (SEC_ERROR_BASE + 134) } SECErrorCodes; #endif /* NO_SECURITY_ERROR_ENUM */ |