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 /security/nss/cmd | |
parent | eaa056d41046b41fee0c3b8d6fa93714a6e5474a (diff) | |
download | nss-hg-cd4705729f6adbb75446f795242faafbb5f1e916.tar.gz |
Merge smimetk_branch to tip...
Diffstat (limited to 'security/nss/cmd')
-rw-r--r-- | security/nss/cmd/manifest.mn | 1 | ||||
-rw-r--r-- | security/nss/cmd/platlibs.mk | 2 | ||||
-rw-r--r-- | security/nss/cmd/smimetools/Makefile | 73 | ||||
-rw-r--r-- | security/nss/cmd/smimetools/cmsutil.c | 841 | ||||
-rw-r--r-- | security/nss/cmd/smimetools/manifest.mn | 45 | ||||
-rw-r--r-- | security/nss/cmd/smimetools/rules.mk | 37 | ||||
-rwxr-xr-x | security/nss/cmd/smimetools/smime | 325 |
7 files changed, 1324 insertions, 0 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; |