summaryrefslogtreecommitdiffstats
path: root/security/nss/cmd/smimetools
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /security/nss/cmd/smimetools
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'security/nss/cmd/smimetools')
-rw-r--r--security/nss/cmd/smimetools/Makefile49
-rw-r--r--security/nss/cmd/smimetools/cmsutil.c1625
-rw-r--r--security/nss/cmd/smimetools/manifest.mn17
-rw-r--r--security/nss/cmd/smimetools/rules.mk7
-rwxr-xr-xsecurity/nss/cmd/smimetools/smime547
-rw-r--r--security/nss/cmd/smimetools/smimetools.gyp25
6 files changed, 2270 insertions, 0 deletions
diff --git a/security/nss/cmd/smimetools/Makefile b/security/nss/cmd/smimetools/Makefile
new file mode 100644
index 0000000000..74139629f3
--- /dev/null
+++ b/security/nss/cmd/smimetools/Makefile
@@ -0,0 +1,49 @@
+#! gmake
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#######################################################################
+# (1) Include initial platform-independent assignments (MANDATORY). #
+#######################################################################
+
+include manifest.mn
+
+#######################################################################
+# (2) Include "global" configuration information. (OPTIONAL) #
+#######################################################################
+
+include $(CORE_DEPTH)/coreconf/config.mk
+
+#######################################################################
+# (3) Include "component" configuration information. (OPTIONAL) #
+#######################################################################
+
+#######################################################################
+# (4) Include "local" platform-dependent assignments (OPTIONAL). #
+#######################################################################
+
+include ../platlibs.mk
+
+ifdef NISCC_TEST
+DEFINES += -DNISCC_TEST
+endif
+
+#######################################################################
+# (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 0000000000..4343695ede
--- /dev/null
+++ b/security/nss/cmd/smimetools/cmsutil.c
@@ -0,0 +1,1625 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * cmsutil -- A command to work with CMS data
+ */
+
+#include "nspr.h"
+#include "secutil.h"
+#include "plgetopt.h"
+#include "secpkcs7.h"
+#include "cert.h"
+#include "certdb.h"
+#include "secoid.h"
+#include "cms.h"
+#include "nss.h"
+#include "smime.h"
+#include "pk11func.h"
+
+#if defined(XP_UNIX)
+#include <unistd.h>
+#endif
+
+#if defined(_WIN32)
+#include "fcntl.h"
+#include "io.h"
+#endif
+
+#include <stdio.h>
+#include <string.h>
+
+char *progName = NULL;
+static int cms_verbose = 0;
+static secuPWData pwdata = { PW_NONE, 0 };
+static PK11PasswordFunc pwcb = NULL;
+static void *pwcb_arg = NULL;
+
+/* XXX stolen from cmsarray.c
+ * nss_CMSArray_Count - 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;
+}
+
+static SECStatus
+DigestFile(PLArenaPool *poolp, SECItem ***digests, SECItem *input,
+ SECAlgorithmID **algids)
+{
+ NSSCMSDigestContext *digcx;
+ SECStatus rv;
+
+ digcx = NSS_CMSDigestContext_StartMultiple(algids);
+ if (digcx == NULL)
+ return SECFailure;
+
+ NSS_CMSDigestContext_Update(digcx, input->data, input->len);
+
+ rv = NSS_CMSDigestContext_FinishMultiple(digcx, poolp, digests);
+ return rv;
+}
+
+static void
+Usage(void)
+{
+ fprintf(stderr,
+ "Usage: %s [-C|-D|-E|-O|-S] [<options>] [-d dbdir] [-u certusage]\n"
+ " -C create a CMS encrypted data message\n"
+ " -D decode a CMS message\n"
+ " -b decode a batch of files named in infile\n"
+ " -c content use this detached content\n"
+ " -n suppress output of content\n"
+ " -h num display num levels of CMS message info as email headers\n"
+ " -k keep decoded encryption certs in perm cert db\n"
+ " -E create a CMS enveloped data message\n"
+ " -r id,... create envelope for these recipients,\n"
+ " where id can be a certificate nickname or email address\n"
+ " -S create a CMS signed data message\n"
+ " -G include a signing time attribute\n"
+ " -H hash use hash (default:SHA256)\n"
+ " -N nick use certificate named \"nick\" for signing\n"
+ " -P include a SMIMECapabilities attribute\n"
+ " -T do not include content in CMS message\n"
+ " -Y nick include a EncryptionKeyPreference attribute with cert\n"
+ " (use \"NONE\" to omit)\n"
+ " -O create a CMS signed message containing only certificates\n"
+ " General Options:\n"
+ " -d dbdir key/cert database directory (default: ~/.netscape)\n"
+ " -e envelope enveloped data message in this file is used for bulk key\n"
+ " -i infile use infile as source of data (default: stdin)\n"
+ " -o outfile use outfile as destination of data (default: stdout)\n"
+ " -p password use password as key db password (default: prompt)\n"
+ " -f pwfile use password file to set password on all PKCS#11 tokens)\n"
+ " -u certusage set type of certificate usage (default: certUsageEmailSigner)\n"
+ " -v print debugging information\n"
+ "\n"
+ "Cert usage codes:\n",
+ progName);
+ 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", " ");
+ fprintf(stderr, "%-25s 12 - certUsageIPsec\n", " ");
+
+ exit(-1);
+}
+
+struct optionsStr {
+ char *pwfile;
+ char *password;
+ SECCertUsage certUsage;
+ CERTCertDBHandle *certHandle;
+};
+
+struct decodeOptionsStr {
+ struct optionsStr *options;
+ SECItem content;
+ int headerLevel;
+ PRBool suppressContent;
+ NSSCMSGetDecryptKeyCallback dkcb;
+ PK11SymKey *bulkkey;
+ PRBool keepCerts;
+};
+
+struct signOptionsStr {
+ struct optionsStr *options;
+ char *nickname;
+ char *encryptionKeyPreferenceNick;
+ PRBool signingTime;
+ PRBool smimeProfile;
+ PRBool detached;
+ SECOidTag hashAlgTag;
+};
+
+struct envelopeOptionsStr {
+ struct optionsStr *options;
+ char **recipients;
+};
+
+struct certsonlyOptionsStr {
+ struct optionsStr *options;
+ char **recipients;
+};
+
+struct encryptOptionsStr {
+ struct optionsStr *options;
+ char **recipients;
+ NSSCMSMessage *envmsg;
+ SECItem *input;
+ FILE *outfile;
+ PRFileDesc *envFile;
+ PK11SymKey *bulkkey;
+ SECOidTag bulkalgtag;
+ int keysize;
+};
+
+static NSSCMSMessage *
+decode(FILE *out, SECItem *input, const struct decodeOptionsStr *decodeOptions)
+{
+ NSSCMSDecoderContext *dcx;
+ SECStatus rv;
+ NSSCMSMessage *cmsg;
+ int nlevels, i;
+ SECItem sitem = { 0, 0, 0 };
+
+ PORT_SetError(0);
+ dcx = NSS_CMSDecoder_Start(NULL,
+ NULL, NULL, /* content callback */
+ pwcb, pwcb_arg, /* password callback */
+ decodeOptions->dkcb, /* decrypt key callback */
+ decodeOptions->bulkkey);
+ if (dcx == NULL) {
+ fprintf(stderr, "%s: failed to set up message decoder.\n", progName);
+ return NULL;
+ }
+ rv = NSS_CMSDecoder_Update(dcx, (char *)input->data, input->len);
+ if (rv != SECSuccess) {
+ fprintf(stderr, "%s: failed to decode message.\n", progName);
+ NSS_CMSDecoder_Cancel(dcx);
+ return NULL;
+ }
+ cmsg = NSS_CMSDecoder_Finish(dcx);
+ if (cmsg == NULL) {
+ fprintf(stderr, "%s: failed to decode message.\n", progName);
+ return NULL;
+ }
+
+ if (decodeOptions->headerLevel >= 0) {
+ /*fprintf(out, "SMIME: ", decodeOptions->headerLevel, i);*/
+ fprintf(out, "SMIME: ");
+ }
+
+ nlevels = NSS_CMSMessage_ContentLevelCount(cmsg);
+ for (i = 0; i < nlevels; i++) {
+ NSSCMSContentInfo *cinfo;
+ SECOidTag typetag;
+
+ 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: {
+ NSSCMSSignedData *sigd = NULL;
+ SECItem **digests = NULL;
+ int nsigners;
+ int j;
+
+ if (decodeOptions->headerLevel >= 0)
+ fprintf(out, "type=signedData; ");
+ sigd = (NSSCMSSignedData *)NSS_CMSContentInfo_GetContent(cinfo);
+ if (sigd == NULL) {
+ SECU_PrintError(progName, "signedData component missing");
+ goto loser;
+ }
+
+ /* if we have a content file, but no digests for this signedData */
+ if (decodeOptions->content.data != NULL &&
+ !NSS_CMSSignedData_HasDigests(sigd)) {
+ PLArenaPool *poolp;
+ SECAlgorithmID **digestalgs;
+
+ /* detached content: grab content file */
+ sitem = decodeOptions->content;
+
+ if ((poolp = PORT_NewArena(1024)) == NULL) {
+ fprintf(stderr, "cmsutil: Out of memory.\n");
+ goto loser;
+ }
+ digestalgs = NSS_CMSSignedData_GetDigestAlgs(sigd);
+ if (DigestFile(poolp, &digests, &sitem, digestalgs) !=
+ SECSuccess) {
+ SECU_PrintError(progName,
+ "problem computing message digest");
+ PORT_FreeArena(poolp, PR_FALSE);
+ goto loser;
+ }
+ if (NSS_CMSSignedData_SetDigests(sigd, digestalgs, digests) !=
+ SECSuccess) {
+ SECU_PrintError(progName,
+ "problem setting message digests");
+ PORT_FreeArena(poolp, PR_FALSE);
+ goto loser;
+ }
+ PORT_FreeArena(poolp, PR_FALSE);
+ }
+
+ /* import the certificates */
+ if (NSS_CMSSignedData_ImportCerts(sigd,
+ decodeOptions->options->certHandle,
+ decodeOptions->options->certUsage,
+ decodeOptions->keepCerts) !=
+ SECSuccess) {
+ SECU_PrintError(progName, "cert import failed");
+ goto loser;
+ }
+
+ /* find out about signers */
+ nsigners = NSS_CMSSignedData_SignerInfoCount(sigd);
+ if (decodeOptions->headerLevel >= 0)
+ fprintf(out, "nsigners=%d; ", nsigners);
+ if (nsigners == 0) {
+ /* Might be a cert transport message
+ ** or might be an invalid message, such as a QA test message
+ ** or a message from an attacker.
+ */
+ rv = NSS_CMSSignedData_VerifyCertsOnly(sigd,
+ decodeOptions->options->certHandle,
+ decodeOptions->options->certUsage);
+ if (rv != SECSuccess) {
+ fprintf(stderr, "cmsutil: Verify certs-only failed!\n");
+ goto loser;
+ }
+ return cmsg;
+ }
+
+ /* still no digests? */
+ if (!NSS_CMSSignedData_HasDigests(sigd)) {
+ SECU_PrintError(progName, "no message digests");
+ goto loser;
+ }
+
+ for (j = 0; j < nsigners; j++) {
+ const char *svs;
+ NSSCMSSignerInfo *si;
+ NSSCMSVerificationStatus vs;
+ SECStatus bad;
+
+ si = NSS_CMSSignedData_GetSignerInfo(sigd, j);
+ if (decodeOptions->headerLevel >= 0) {
+ char *signercn;
+ static char empty[] = { "" };
+
+ signercn = NSS_CMSSignerInfo_GetSignerCommonName(si);
+ if (signercn == NULL)
+ signercn = empty;
+ fprintf(out, "\n\t\tsigner%d.id=\"%s\"; ", j, signercn);
+ if (signercn != empty)
+ PORT_Free(signercn);
+ }
+ bad = NSS_CMSSignedData_VerifySignerInfo(sigd, j,
+ decodeOptions->options->certHandle,
+ decodeOptions->options->certUsage);
+ vs = NSS_CMSSignerInfo_GetVerificationStatus(si);
+ svs = NSS_CMSUtil_VerificationStatusToString(vs);
+ if (decodeOptions->headerLevel >= 0) {
+ fprintf(out, "signer%d.status=%s; ", j, svs);
+ /* goto loser ? */
+ } else if (bad && out) {
+ fprintf(stderr, "signer %d status = %s\n", j, svs);
+ goto loser;
+ }
+ }
+ } break;
+ case SEC_OID_PKCS7_ENVELOPED_DATA: {
+ NSSCMSEnvelopedData *envd;
+ if (decodeOptions->headerLevel >= 0)
+ fprintf(out, "type=envelopedData; ");
+ envd = (NSSCMSEnvelopedData *)NSS_CMSContentInfo_GetContent(cinfo);
+ if (envd == NULL) {
+ SECU_PrintError(progName, "envelopedData component missing");
+ goto loser;
+ }
+ } break;
+ case SEC_OID_PKCS7_ENCRYPTED_DATA: {
+ NSSCMSEncryptedData *encd;
+ if (decodeOptions->headerLevel >= 0)
+ fprintf(out, "type=encryptedData; ");
+ encd = (NSSCMSEncryptedData *)NSS_CMSContentInfo_GetContent(cinfo);
+ if (encd == NULL) {
+ SECU_PrintError(progName, "encryptedData component missing");
+ goto loser;
+ }
+ } 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 && out) {
+ SECItem *item = (sitem.data ? &sitem
+ : NSS_CMSMessage_GetContent(cmsg));
+ if (item && item->data && item->len) {
+ fwrite(item->data, item->len, 1, out);
+ }
+ }
+ return cmsg;
+
+loser:
+ if (cmsg)
+ NSS_CMSMessage_Destroy(cmsg);
+ return NULL;
+}
+
+/* example of a callback function to use with encoder */
+/*
+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 NSSCMSMessage *
+signed_data(struct signOptionsStr *signOptions)
+{
+ NSSCMSMessage *cmsg = NULL;
+ NSSCMSContentInfo *cinfo;
+ NSSCMSSignedData *sigd;
+ NSSCMSSignerInfo *signerinfo;
+ CERTCertificate *cert = NULL, *ekpcert = NULL;
+
+ if (cms_verbose) {
+ fprintf(stderr, "Input to signed_data:\n");
+ if (signOptions->options->password)
+ fprintf(stderr, "password [%s]\n", signOptions->options->password);
+ else if (signOptions->options->pwfile)
+ fprintf(stderr, "password file [%s]\n", signOptions->options->pwfile);
+ else
+ fprintf(stderr, "password [NULL]\n");
+ fprintf(stderr, "certUsage [%d]\n", signOptions->options->certUsage);
+ if (signOptions->options->certHandle)
+ fprintf(stderr, "certdb [%p]\n", signOptions->options->certHandle);
+ else
+ fprintf(stderr, "certdb [NULL]\n");
+ if (signOptions->nickname)
+ fprintf(stderr, "nickname [%s]\n", signOptions->nickname);
+ else
+ fprintf(stderr, "nickname [NULL]\n");
+ }
+ if (signOptions->nickname == NULL) {
+ fprintf(stderr,
+ "ERROR: please indicate the nickname of a certificate to sign with.\n");
+ return NULL;
+ }
+ if ((cert = CERT_FindUserCertByUsage(signOptions->options->certHandle,
+ signOptions->nickname,
+ signOptions->options->certUsage,
+ PR_FALSE,
+ &pwdata)) == NULL) {
+ SECU_PrintError(progName,
+ "the corresponding cert for key \"%s\" does not exist",
+ signOptions->nickname);
+ return NULL;
+ }
+ if (cms_verbose) {
+ fprintf(stderr, "Found certificate for %s\n", signOptions->nickname);
+ }
+ /*
+ * 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 NULL;
+ }
+ /*
+ * build chain of objects: message->signedData->data
+ */
+ if ((sigd = NSS_CMSSignedData_Create(cmsg)) == NULL) {
+ fprintf(stderr, "ERROR: cannot create CMS signedData object.\n");
+ goto loser;
+ }
+ cinfo = NSS_CMSMessage_GetContentInfo(cmsg);
+ if (NSS_CMSContentInfo_SetContent_SignedData(cmsg, cinfo, sigd) !=
+ SECSuccess) {
+ fprintf(stderr, "ERROR: cannot attach CMS signedData object.\n");
+ goto loser;
+ }
+ 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");
+ goto loser;
+ }
+ /*
+ * create & attach signer information
+ */
+ signerinfo = NSS_CMSSignerInfo_Create(cmsg, cert, signOptions->hashAlgTag);
+ if (signerinfo == NULL) {
+ fprintf(stderr, "ERROR: cannot create CMS signerInfo object.\n");
+ goto loser;
+ }
+ if (cms_verbose) {
+ fprintf(stderr,
+ "Created CMS message, added signed data w/ signerinfo\n");
+ }
+ signerinfo->cmsg->pwfn_arg = pwcb_arg;
+ /* we want the cert chain included for this one */
+ if (NSS_CMSSignerInfo_IncludeCerts(signerinfo, NSSCMSCM_CertChain,
+ signOptions->options->certUsage) !=
+ SECSuccess) {
+ fprintf(stderr, "ERROR: cannot find cert chain.\n");
+ goto loser;
+ }
+ if (cms_verbose) {
+ fprintf(stderr, "imported certificate\n");
+ }
+ if (signOptions->signingTime) {
+ if (NSS_CMSSignerInfo_AddSigningTime(signerinfo, PR_Now()) !=
+ SECSuccess) {
+ fprintf(stderr, "ERROR: cannot add signingTime attribute.\n");
+ goto loser;
+ }
+ }
+ if (signOptions->smimeProfile) {
+ if (NSS_CMSSignerInfo_AddSMIMECaps(signerinfo) != SECSuccess) {
+ fprintf(stderr, "ERROR: cannot add SMIMECaps attribute.\n");
+ goto loser;
+ }
+ }
+
+ if (!signOptions->encryptionKeyPreferenceNick) {
+ /* check signing cert for fitness as encryption cert */
+ SECStatus FitForEncrypt = CERT_CheckCertUsage(cert,
+ certUsageEmailRecipient);
+
+ if (SECSuccess == FitForEncrypt) {
+ /* if yes, add signing cert as EncryptionKeyPreference */
+ if (NSS_CMSSignerInfo_AddSMIMEEncKeyPrefs(signerinfo, cert,
+ signOptions->options->certHandle) !=
+ SECSuccess) {
+ fprintf(stderr,
+ "ERROR: cannot add default SMIMEEncKeyPrefs attribute.\n");
+ goto loser;
+ }
+ if (NSS_CMSSignerInfo_AddMSSMIMEEncKeyPrefs(signerinfo, cert,
+ signOptions->options->certHandle) !=
+ SECSuccess) {
+ fprintf(stderr,
+ "ERROR: cannot add default MS SMIMEEncKeyPrefs attribute.\n");
+ goto loser;
+ }
+ } else {
+ /* this is a dual-key cert case, we need to look for the encryption
+ certificate under the same nickname as the signing cert */
+ /* get the cert, add it to the message */
+ if ((ekpcert = CERT_FindUserCertByUsage(
+ signOptions->options->certHandle,
+ signOptions->nickname,
+ certUsageEmailRecipient,
+ PR_FALSE,
+ &pwdata)) == NULL) {
+ SECU_PrintError(progName,
+ "the corresponding cert for key \"%s\" does not exist",
+ signOptions->encryptionKeyPreferenceNick);
+ goto loser;
+ }
+ if (NSS_CMSSignerInfo_AddSMIMEEncKeyPrefs(signerinfo, ekpcert,
+ signOptions->options->certHandle) !=
+ SECSuccess) {
+ fprintf(stderr,
+ "ERROR: cannot add SMIMEEncKeyPrefs attribute.\n");
+ goto loser;
+ }
+ if (NSS_CMSSignerInfo_AddMSSMIMEEncKeyPrefs(signerinfo, ekpcert,
+ signOptions->options->certHandle) !=
+ SECSuccess) {
+ fprintf(stderr,
+ "ERROR: cannot add MS SMIMEEncKeyPrefs attribute.\n");
+ goto loser;
+ }
+ if (NSS_CMSSignedData_AddCertificate(sigd, ekpcert) != SECSuccess) {
+ fprintf(stderr, "ERROR: cannot add encryption certificate.\n");
+ goto loser;
+ }
+ }
+ } else if (PL_strcmp(signOptions->encryptionKeyPreferenceNick, "NONE") == 0) {
+ /* No action */
+ } else {
+ /* get the cert, add it to the message */
+ if ((ekpcert = CERT_FindUserCertByUsage(
+ signOptions->options->certHandle,
+ signOptions->encryptionKeyPreferenceNick,
+ certUsageEmailRecipient, PR_FALSE, &pwdata)) ==
+ NULL) {
+ SECU_PrintError(progName,
+ "the corresponding cert for key \"%s\" does not exist",
+ signOptions->encryptionKeyPreferenceNick);
+ goto loser;
+ }
+ if (NSS_CMSSignerInfo_AddSMIMEEncKeyPrefs(signerinfo, ekpcert,
+ signOptions->options->certHandle) !=
+ SECSuccess) {
+ fprintf(stderr, "ERROR: cannot add SMIMEEncKeyPrefs attribute.\n");
+ goto loser;
+ }
+ if (NSS_CMSSignerInfo_AddMSSMIMEEncKeyPrefs(signerinfo, ekpcert,
+ signOptions->options->certHandle) !=
+ SECSuccess) {
+ fprintf(stderr, "ERROR: cannot add MS SMIMEEncKeyPrefs attribute.\n");
+ goto loser;
+ }
+ if (NSS_CMSSignedData_AddCertificate(sigd, ekpcert) != SECSuccess) {
+ fprintf(stderr, "ERROR: cannot add encryption certificate.\n");
+ goto loser;
+ }
+ }
+
+ if (NSS_CMSSignedData_AddSignerInfo(sigd, signerinfo) != SECSuccess) {
+ fprintf(stderr, "ERROR: cannot add CMS signerInfo object.\n");
+ goto loser;
+ }
+ if (cms_verbose) {
+ fprintf(stderr, "created signed-data message\n");
+ }
+ if (ekpcert) {
+ CERT_DestroyCertificate(ekpcert);
+ }
+ if (cert) {
+ CERT_DestroyCertificate(cert);
+ }
+ return cmsg;
+loser:
+ if (ekpcert) {
+ CERT_DestroyCertificate(ekpcert);
+ }
+ if (cert) {
+ CERT_DestroyCertificate(cert);
+ }
+ NSS_CMSMessage_Destroy(cmsg);
+ return NULL;
+}
+
+static NSSCMSMessage *
+enveloped_data(struct envelopeOptionsStr *envelopeOptions)
+{
+ NSSCMSMessage *cmsg = NULL;
+ NSSCMSContentInfo *cinfo;
+ NSSCMSEnvelopedData *envd;
+ NSSCMSRecipientInfo *recipientinfo;
+ CERTCertificate **recipientcerts = NULL;
+ CERTCertDBHandle *dbhandle;
+ PLArenaPool *tmppoolp = NULL;
+ SECOidTag bulkalgtag;
+ int keysize, i = 0;
+ int cnt;
+ dbhandle = envelopeOptions->options->certHandle;
+ /* count the recipients */
+ if ((cnt = nss_CMSArray_Count((void **)envelopeOptions->recipients)) == 0) {
+ fprintf(stderr, "ERROR: please name at least one recipient.\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 **)PORT_ArenaZAlloc(tmppoolp,
+ (cnt + 1) * sizeof(CERTCertificate *))) ==
+ NULL) {
+ fprintf(stderr, "ERROR: out of memory.\n");
+ goto loser;
+ }
+ for (i = 0; envelopeOptions->recipients[i] != NULL; i++) {
+ if ((recipientcerts[i] =
+ CERT_FindCertByNicknameOrEmailAddr(dbhandle,
+ envelopeOptions->recipients[i])) ==
+ NULL) {
+ SECU_PrintError(progName, "cannot find certificate for \"%s\"",
+ envelopeOptions->recipients[i]);
+ i = 0;
+ goto loser;
+ }
+ }
+ recipientcerts[i] = NULL;
+ i = 0;
+ /* 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;
+ }
+ CERT_DestroyCertificate(recipientcerts[i]);
+ }
+ if (tmppoolp)
+ PORT_FreeArena(tmppoolp, PR_FALSE);
+ return cmsg;
+loser:
+ if (recipientcerts) {
+ for (; recipientcerts[i] != NULL; i++) {
+ CERT_DestroyCertificate(recipientcerts[i]);
+ }
+ }
+ if (cmsg)
+ NSS_CMSMessage_Destroy(cmsg);
+ if (tmppoolp)
+ PORT_FreeArena(tmppoolp, PR_FALSE);
+ return NULL;
+}
+
+PK11SymKey *
+dkcb(void *arg, SECAlgorithmID *algid)
+{
+ return (PK11SymKey *)arg;
+}
+
+static SECStatus
+get_enc_params(struct encryptOptionsStr *encryptOptions)
+{
+ struct envelopeOptionsStr envelopeOptions;
+ SECStatus rv = SECFailure;
+ NSSCMSMessage *env_cmsg;
+ NSSCMSContentInfo *cinfo;
+ int i, nlevels;
+ /*
+ * construct an enveloped data message to obtain bulk keys
+ */
+ if (encryptOptions->envmsg) {
+ env_cmsg = encryptOptions->envmsg; /* get it from an old message */
+ } else {
+ SECItem dummyOut = { 0, 0, 0 };
+ SECItem dummyIn = { 0, 0, 0 };
+ char str[] = "Hello!";
+ PLArenaPool *tmparena = PORT_NewArena(1024);
+ dummyIn.data = (unsigned char *)str;
+ dummyIn.len = strlen(str);
+ envelopeOptions.options = encryptOptions->options;
+ envelopeOptions.recipients = encryptOptions->recipients;
+ env_cmsg = enveloped_data(&envelopeOptions);
+ NSS_CMSDEREncode(env_cmsg, &dummyIn, &dummyOut, tmparena);
+ PR_Write(encryptOptions->envFile, dummyOut.data, dummyOut.len);
+ PORT_FreeArena(tmparena, PR_FALSE);
+ }
+ /*
+ * get the content info for the enveloped data
+ */
+ nlevels = NSS_CMSMessage_ContentLevelCount(env_cmsg);
+ for (i = 0; i < nlevels; i++) {
+ SECOidTag typetag;
+ cinfo = NSS_CMSMessage_ContentLevel(env_cmsg, i);
+ typetag = NSS_CMSContentInfo_GetContentTypeTag(cinfo);
+ if (typetag == SEC_OID_PKCS7_DATA) {
+ /*
+ * get the symmetric key
+ */
+ encryptOptions->bulkalgtag = NSS_CMSContentInfo_GetContentEncAlgTag(cinfo);
+ encryptOptions->keysize = NSS_CMSContentInfo_GetBulkKeySize(cinfo);
+ encryptOptions->bulkkey = NSS_CMSContentInfo_GetBulkKey(cinfo);
+ rv = SECSuccess;
+ break;
+ }
+ }
+ if (i == nlevels) {
+ fprintf(stderr, "%s: could not retrieve enveloped data.", progName);
+ }
+ if (env_cmsg)
+ NSS_CMSMessage_Destroy(env_cmsg);
+ return rv;
+}
+
+static NSSCMSMessage *
+encrypted_data(struct encryptOptionsStr *encryptOptions)
+{
+ SECStatus rv = SECFailure;
+ NSSCMSMessage *cmsg = NULL;
+ NSSCMSContentInfo *cinfo;
+ NSSCMSEncryptedData *encd;
+ NSSCMSEncoderContext *ecx = NULL;
+ PLArenaPool *tmppoolp = NULL;
+ SECItem derOut = { 0, 0, 0 };
+ /* arena for output */
+ tmppoolp = PORT_NewArena(1024);
+ if (!tmppoolp) {
+ fprintf(stderr, "%s: out of memory.\n", progName);
+ return NULL;
+ }
+ /*
+ * create the message object
+ */
+ cmsg = NSS_CMSMessage_Create(NULL);
+ if (cmsg == NULL) {
+ fprintf(stderr, "ERROR: cannot create CMS message.\n");
+ goto loser;
+ }
+ /*
+ * build chain of objects: message->encryptedData->data
+ */
+ if ((encd = NSS_CMSEncryptedData_Create(cmsg, encryptOptions->bulkalgtag,
+ encryptOptions->keysize)) ==
+ NULL) {
+ fprintf(stderr, "ERROR: cannot create CMS encryptedData object.\n");
+ goto loser;
+ }
+ cinfo = NSS_CMSMessage_GetContentInfo(cmsg);
+ if (NSS_CMSContentInfo_SetContent_EncryptedData(cmsg, cinfo, encd) !=
+ SECSuccess) {
+ fprintf(stderr, "ERROR: cannot attach CMS encryptedData object.\n");
+ goto loser;
+ }
+ cinfo = NSS_CMSEncryptedData_GetContentInfo(encd);
+ /* 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;
+ }
+ ecx = NSS_CMSEncoder_Start(cmsg, NULL, NULL, &derOut, tmppoolp, NULL, NULL,
+ dkcb, encryptOptions->bulkkey, NULL, NULL);
+ if (!ecx) {
+ fprintf(stderr, "%s: cannot create encoder context.\n", progName);
+ goto loser;
+ }
+ rv = NSS_CMSEncoder_Update(ecx, (char *)encryptOptions->input->data,
+ encryptOptions->input->len);
+ if (rv) {
+ fprintf(stderr, "%s: failed to add data to encoder.\n", progName);
+ goto loser;
+ }
+ rv = NSS_CMSEncoder_Finish(ecx);
+ if (rv) {
+ fprintf(stderr, "%s: failed to encrypt data.\n", progName);
+ goto loser;
+ }
+ fwrite(derOut.data, derOut.len, 1, encryptOptions->outfile);
+ /*
+ if (bulkkey)
+ PK11_FreeSymKey(bulkkey);
+ */
+ if (tmppoolp)
+ PORT_FreeArena(tmppoolp, PR_FALSE);
+ return cmsg;
+loser:
+ /*
+ if (bulkkey)
+ PK11_FreeSymKey(bulkkey);
+ */
+ if (tmppoolp)
+ PORT_FreeArena(tmppoolp, PR_FALSE);
+ if (cmsg)
+ NSS_CMSMessage_Destroy(cmsg);
+ return NULL;
+}
+
+static NSSCMSMessage *
+signed_data_certsonly(struct certsonlyOptionsStr *certsonlyOptions)
+{
+ NSSCMSMessage *cmsg = NULL;
+ NSSCMSContentInfo *cinfo;
+ NSSCMSSignedData *sigd;
+ CERTCertificate **certs = NULL;
+ CERTCertDBHandle *dbhandle;
+ PLArenaPool *tmppoolp = NULL;
+ int i = 0, cnt;
+ dbhandle = certsonlyOptions->options->certHandle;
+ if ((cnt = nss_CMSArray_Count((void **)certsonlyOptions->recipients)) == 0) {
+ fprintf(stderr,
+ "ERROR: please indicate the nickname of a certificate to sign with.\n");
+ goto loser;
+ }
+ if (!(tmppoolp = PORT_NewArena(1024))) {
+ fprintf(stderr, "ERROR: out of memory.\n");
+ goto loser;
+ }
+ if (!(certs = PORT_ArenaZNewArray(tmppoolp, CERTCertificate *, cnt + 1))) {
+ fprintf(stderr, "ERROR: out of memory.\n");
+ goto loser;
+ }
+ for (i = 0; certsonlyOptions->recipients[i] != NULL; i++) {
+ if ((certs[i] =
+ CERT_FindCertByNicknameOrEmailAddr(dbhandle,
+ certsonlyOptions->recipients[i])) ==
+ NULL) {
+ SECU_PrintError(progName, "cannot find certificate for \"%s\"",
+ certsonlyOptions->recipients[i]);
+ i = 0;
+ goto loser;
+ }
+ }
+ certs[i] = NULL;
+ i = 0;
+ /*
+ * create the message object
+ */
+ cmsg = NSS_CMSMessage_Create(NULL);
+ if (cmsg == NULL) {
+ fprintf(stderr, "ERROR: cannot create CMS message.\n");
+ goto loser;
+ }
+ /*
+ * build chain of objects: message->signedData->data
+ */
+ if ((sigd = NSS_CMSSignedData_CreateCertsOnly(cmsg, certs[0], PR_TRUE)) ==
+ NULL) {
+ fprintf(stderr, "ERROR: cannot create CMS signedData object.\n");
+ goto loser;
+ }
+ CERT_DestroyCertificate(certs[0]);
+ for (i = 1; i < cnt; i++) {
+ if (NSS_CMSSignedData_AddCertChain(sigd, certs[i])) {
+ fprintf(stderr, "ERROR: cannot add cert chain for \"%s\".\n",
+ certsonlyOptions->recipients[i]);
+ goto loser;
+ }
+ CERT_DestroyCertificate(certs[i]);
+ }
+ cinfo = NSS_CMSMessage_GetContentInfo(cmsg);
+ if (NSS_CMSContentInfo_SetContent_SignedData(cmsg, cinfo, sigd) !=
+ SECSuccess) {
+ fprintf(stderr, "ERROR: cannot attach CMS signedData object.\n");
+ goto loser;
+ }
+ cinfo = NSS_CMSSignedData_GetContentInfo(sigd);
+ if (NSS_CMSContentInfo_SetContent_Data(cmsg, cinfo, NULL, PR_FALSE) !=
+ SECSuccess) {
+ fprintf(stderr, "ERROR: cannot attach CMS data object.\n");
+ goto loser;
+ }
+ if (tmppoolp)
+ PORT_FreeArena(tmppoolp, PR_FALSE);
+ return cmsg;
+loser:
+ if (certs) {
+ for (; i < cnt; i++) {
+ CERT_DestroyCertificate(certs[i]);
+ }
+ }
+ if (cmsg)
+ NSS_CMSMessage_Destroy(cmsg);
+ if (tmppoolp)
+ PORT_FreeArena(tmppoolp, PR_FALSE);
+ return NULL;
+}
+
+static char *
+pl_fgets(char *buf, int size, PRFileDesc *fd)
+{
+ char *bp = buf;
+ int nb = 0;
+ ;
+
+ while (size > 1) {
+ nb = PR_Read(fd, bp, 1);
+ if (nb < 0) {
+ /* deal with error */
+ return NULL;
+ } else if (nb == 0) {
+ /* deal with EOF */
+ return NULL;
+ } else if (*bp == '\n') {
+ /* deal with EOL */
+ ++bp; /* keep EOL character */
+ break;
+ } else {
+ /* ordinary character */
+ ++bp;
+ --size;
+ }
+ }
+ *bp = '\0';
+ return buf;
+}
+
+typedef enum { UNKNOWN,
+ DECODE,
+ SIGN,
+ ENCRYPT,
+ ENVELOPE,
+ CERTSONLY } Mode;
+
+static int
+doBatchDecode(FILE *outFile, PRFileDesc *batchFile,
+ const struct decodeOptionsStr *decodeOptions)
+{
+ char *str;
+ int exitStatus = 0;
+ char batchLine[512];
+
+ while (NULL != (str = pl_fgets(batchLine, sizeof batchLine, batchFile))) {
+ NSSCMSMessage *cmsg = NULL;
+ PRFileDesc *inFile;
+ int len = strlen(str);
+ SECStatus rv;
+ SECItem input = { 0, 0, 0 };
+ char cc;
+
+ while (len > 0 &&
+ ((cc = str[len - 1]) == '\n' || cc == '\r')) {
+ str[--len] = '\0';
+ }
+ if (!len) /* skip empty line */
+ continue;
+ if (str[0] == '#')
+ continue; /* skip comment line */
+ fprintf(outFile, "========== %s ==========\n", str);
+ inFile = PR_Open(str, PR_RDONLY, 00660);
+ if (inFile == NULL) {
+ fprintf(outFile, "%s: unable to open \"%s\" for reading\n",
+ progName, str);
+ exitStatus = 1;
+ continue;
+ }
+ rv = SECU_FileToItem(&input, inFile);
+ PR_Close(inFile);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "unable to read infile");
+ exitStatus = 1;
+ continue;
+ }
+ cmsg = decode(outFile, &input, decodeOptions);
+ SECITEM_FreeItem(&input, PR_FALSE);
+ if (cmsg)
+ NSS_CMSMessage_Destroy(cmsg);
+ else {
+ SECU_PrintError(progName, "problem decoding");
+ exitStatus = 1;
+ }
+ }
+ return exitStatus;
+}
+
+int
+main(int argc, char **argv)
+{
+ FILE *outFile;
+ NSSCMSMessage *cmsg = NULL;
+ PRFileDesc *inFile;
+ PLOptState *optstate;
+ PLOptStatus status;
+ Mode mode = UNKNOWN;
+ struct decodeOptionsStr decodeOptions = { 0 };
+ struct signOptionsStr signOptions = { 0 };
+ struct envelopeOptionsStr envelopeOptions = { 0 };
+ struct certsonlyOptionsStr certsonlyOptions = { 0 };
+ struct encryptOptionsStr encryptOptions = { 0 };
+ struct optionsStr options = { 0 };
+ int exitstatus;
+ static char *ptrarray[128] = { 0 };
+ int nrecipients = 0;
+ char *str, *tok;
+ char *envFileName;
+ SECItem input = { 0, 0, 0 };
+ SECItem envmsg = { 0, 0, 0 };
+ SECStatus rv;
+ PRFileDesc *contentFile = NULL;
+ PRBool batch = PR_FALSE;
+
+#ifdef NISCC_TEST
+ const char *ev = PR_GetEnvSecure("NSS_DISABLE_ARENA_FREE_LIST");
+ PORT_Assert(ev);
+ ev = PR_GetEnvSecure("NSS_STRICT_SHUTDOWN");
+ PORT_Assert(ev);
+#endif
+
+ progName = strrchr(argv[0], '/');
+ if (!progName)
+ progName = strrchr(argv[0], '\\');
+ progName = progName ? progName + 1 : argv[0];
+
+ inFile = PR_STDIN;
+ outFile = stdout;
+ envFileName = NULL;
+ mode = UNKNOWN;
+ decodeOptions.content.data = NULL;
+ decodeOptions.content.len = 0;
+ decodeOptions.suppressContent = PR_FALSE;
+ decodeOptions.headerLevel = -1;
+ decodeOptions.keepCerts = PR_FALSE;
+ options.certUsage = certUsageEmailSigner;
+ options.password = NULL;
+ options.pwfile = NULL;
+ signOptions.nickname = NULL;
+ signOptions.detached = PR_FALSE;
+ signOptions.signingTime = PR_FALSE;
+ signOptions.smimeProfile = PR_FALSE;
+ signOptions.encryptionKeyPreferenceNick = NULL;
+ signOptions.hashAlgTag = SEC_OID_SHA256;
+ envelopeOptions.recipients = NULL;
+ encryptOptions.recipients = NULL;
+ encryptOptions.envmsg = NULL;
+ encryptOptions.envFile = NULL;
+ encryptOptions.bulkalgtag = SEC_OID_UNKNOWN;
+ encryptOptions.bulkkey = NULL;
+ encryptOptions.keysize = -1;
+
+ /*
+ * Parse command line arguments
+ */
+ optstate = PL_CreateOptState(argc, argv,
+ "CDEGH:N:OPSTY:bc:d:e:f:h:i:kno:p:r:s:u:v");
+ while ((status = PL_GetNextOpt(optstate)) == PL_OPT_OK) {
+ switch (optstate->option) {
+ case 'C':
+ mode = ENCRYPT;
+ break;
+ case 'D':
+ mode = DECODE;
+ break;
+ case 'E':
+ mode = ENVELOPE;
+ break;
+ case 'G':
+ if (mode != SIGN) {
+ fprintf(stderr,
+ "%s: option -G only supported with option -S.\n",
+ progName);
+ Usage();
+ exit(1);
+ }
+ signOptions.signingTime = PR_TRUE;
+ break;
+ case 'H':
+ if (mode != SIGN) {
+ fprintf(stderr,
+ "%s: option -H only supported with option -S.\n",
+ progName);
+ Usage();
+ exit(1);
+ }
+ decodeOptions.suppressContent = PR_TRUE;
+ if (!strcmp(optstate->value, "MD2"))
+ signOptions.hashAlgTag = SEC_OID_MD2;
+ else if (!strcmp(optstate->value, "MD4"))
+ signOptions.hashAlgTag = SEC_OID_MD4;
+ else if (!strcmp(optstate->value, "MD5"))
+ signOptions.hashAlgTag = SEC_OID_MD5;
+ else if (!strcmp(optstate->value, "SHA1"))
+ signOptions.hashAlgTag = SEC_OID_SHA1;
+ else if (!strcmp(optstate->value, "SHA256"))
+ signOptions.hashAlgTag = SEC_OID_SHA256;
+ else if (!strcmp(optstate->value, "SHA384"))
+ signOptions.hashAlgTag = SEC_OID_SHA384;
+ else if (!strcmp(optstate->value, "SHA512"))
+ signOptions.hashAlgTag = SEC_OID_SHA512;
+ else {
+ fprintf(stderr,
+ "%s: -H requires one of MD2,MD4,MD5,SHA1,SHA256,SHA384,SHA512\n",
+ progName);
+ exit(1);
+ }
+ break;
+ case 'N':
+ if (mode != SIGN) {
+ fprintf(stderr,
+ "%s: option -N only supported with option -S.\n",
+ progName);
+ Usage();
+ exit(1);
+ }
+ signOptions.nickname = PORT_Strdup(optstate->value);
+ break;
+ case 'O':
+ mode = CERTSONLY;
+ break;
+ case 'P':
+ if (mode != SIGN) {
+ fprintf(stderr,
+ "%s: option -P only supported with option -S.\n",
+ progName);
+ Usage();
+ exit(1);
+ }
+ signOptions.smimeProfile = PR_TRUE;
+ break;
+ case 'S':
+ mode = SIGN;
+ break;
+ case 'T':
+ if (mode != SIGN) {
+ fprintf(stderr,
+ "%s: option -T only supported with option -S.\n",
+ progName);
+ Usage();
+ exit(1);
+ }
+ signOptions.detached = PR_TRUE;
+ break;
+ case 'Y':
+ if (mode != SIGN) {
+ fprintf(stderr,
+ "%s: option -Y only supported with option -S.\n",
+ progName);
+ Usage();
+ exit(1);
+ }
+ signOptions.encryptionKeyPreferenceNick = strdup(optstate->value);
+ break;
+
+ case 'b':
+ if (mode != DECODE) {
+ fprintf(stderr,
+ "%s: option -b only supported with option -D.\n",
+ progName);
+ Usage();
+ exit(1);
+ }
+ batch = PR_TRUE;
+ break;
+
+ case 'c':
+ if (mode != DECODE) {
+ fprintf(stderr,
+ "%s: option -c only supported with option -D.\n",
+ progName);
+ Usage();
+ exit(1);
+ }
+ contentFile = PR_Open(optstate->value, PR_RDONLY, 006600);
+ if (contentFile == NULL) {
+ fprintf(stderr, "%s: unable to open \"%s\" for reading.\n",
+ progName, optstate->value);
+ exit(1);
+ }
+
+ rv = SECU_FileToItem(&decodeOptions.content, contentFile);
+ PR_Close(contentFile);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "problem reading content file");
+ exit(1);
+ }
+ if (!decodeOptions.content.data) {
+ /* file was zero length */
+ decodeOptions.content.data = (unsigned char *)PORT_Strdup("");
+ decodeOptions.content.len = 0;
+ }
+
+ break;
+ case 'd':
+ SECU_ConfigDirectory(optstate->value);
+ break;
+ case 'e':
+ envFileName = PORT_Strdup(optstate->value);
+ encryptOptions.envFile = PR_Open(envFileName, PR_RDONLY, 00660);
+ break;
+
+ case 'h':
+ if (mode != DECODE) {
+ fprintf(stderr,
+ "%s: option -h only supported with option -D.\n",
+ progName);
+ Usage();
+ 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 'i':
+ if (!optstate->value) {
+ fprintf(stderr, "-i option requires filename argument\n");
+ exit(1);
+ }
+ inFile = PR_Open(optstate->value, PR_RDONLY, 00660);
+ if (inFile == NULL) {
+ fprintf(stderr, "%s: unable to open \"%s\" for reading\n",
+ progName, optstate->value);
+ exit(1);
+ }
+ break;
+
+ case 'k':
+ if (mode != DECODE) {
+ fprintf(stderr,
+ "%s: option -k only supported with option -D.\n",
+ progName);
+ Usage();
+ exit(1);
+ }
+ decodeOptions.keepCerts = PR_TRUE;
+ break;
+
+ case 'n':
+ if (mode != DECODE) {
+ fprintf(stderr,
+ "%s: option -n only supported with option -D.\n",
+ progName);
+ Usage();
+ exit(1);
+ }
+ decodeOptions.suppressContent = PR_TRUE;
+ break;
+ case 'o':
+ outFile = fopen(optstate->value, "wb");
+ if (outFile == NULL) {
+ fprintf(stderr, "%s: unable to open \"%s\" for writing\n",
+ progName, optstate->value);
+ exit(1);
+ }
+ break;
+ case 'p':
+ if (!optstate->value) {
+ fprintf(stderr, "%s: option -p must have a value.\n", progName);
+ Usage();
+ exit(1);
+ }
+
+ options.password = strdup(optstate->value);
+ break;
+
+ case 'f':
+ if (!optstate->value) {
+ fprintf(stderr, "%s: option -f must have a value.\n", progName);
+ Usage();
+ exit(1);
+ }
+
+ options.pwfile = strdup(optstate->value);
+ break;
+
+ case 'r':
+ if (!optstate->value) {
+ fprintf(stderr, "%s: option -r must have a value.\n", progName);
+ Usage();
+ exit(1);
+ }
+ envelopeOptions.recipients = ptrarray;
+ str = (char *)optstate->value;
+ do {
+ tok = strchr(str, ',');
+ if (tok)
+ *tok = '\0';
+ envelopeOptions.recipients[nrecipients++] = strdup(str);
+ if (tok)
+ str = tok + 1;
+ } while (tok);
+ envelopeOptions.recipients[nrecipients] = NULL;
+ encryptOptions.recipients = envelopeOptions.recipients;
+ certsonlyOptions.recipients = envelopeOptions.recipients;
+ break;
+
+ case 'u': {
+ int usageType;
+
+ usageType = atoi(strdup(optstate->value));
+ if (usageType < certUsageSSLClient || usageType > certUsageAnyCA)
+ return -1;
+ options.certUsage = (SECCertUsage)usageType;
+ break;
+ }
+ case 'v':
+ cms_verbose = 1;
+ break;
+ }
+ }
+ if (status == PL_OPT_BAD)
+ Usage();
+ PL_DestroyOptState(optstate);
+
+ if (mode == UNKNOWN)
+ Usage();
+
+ if (mode != CERTSONLY && !batch) {
+ rv = SECU_FileToItem(&input, inFile);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "unable to read infile");
+ exit(1);
+ }
+ }
+ if (cms_verbose) {
+ fprintf(stderr, "received commands\n");
+ }
+
+ /* Call the NSS initialization routines */
+ PR_Init(PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1);
+ rv = NSS_InitReadWrite(SECU_ConfigDirectory(NULL));
+ if (SECSuccess != rv) {
+ SECU_PrintError(progName, "NSS_Init failed");
+ exit(1);
+ }
+ if (cms_verbose) {
+ fprintf(stderr, "NSS has been initialized.\n");
+ }
+ options.certHandle = CERT_GetDefaultCertDB();
+ if (!options.certHandle) {
+ SECU_PrintError(progName, "No default cert DB");
+ exit(1);
+ }
+ if (cms_verbose) {
+ fprintf(stderr, "Got default certdb\n");
+ }
+ if (options.password) {
+ pwdata.source = PW_PLAINTEXT;
+ pwdata.data = options.password;
+ }
+ if (options.pwfile) {
+ pwdata.source = PW_FROMFILE;
+ pwdata.data = options.pwfile;
+ }
+ pwcb = SECU_GetModulePassword;
+ pwcb_arg = (void *)&pwdata;
+
+ PK11_SetPasswordFunc(&SECU_GetModulePassword);
+
+#if defined(_WIN32)
+ if (outFile == stdout) {
+ /* If we're going to write binary data to stdout, we must put stdout
+ ** into O_BINARY mode or else outgoing \n's will become \r\n's.
+ */
+ int smrv = _setmode(_fileno(stdout), _O_BINARY);
+ if (smrv == -1) {
+ fprintf(stderr,
+ "%s: Cannot change stdout to binary mode. Use -o option instead.\n",
+ progName);
+ return smrv;
+ }
+ }
+#endif
+
+ exitstatus = 0;
+ switch (mode) {
+ case DECODE: /* -D */
+ decodeOptions.options = &options;
+ if (encryptOptions.envFile) {
+ /* Decoding encrypted-data, so get the bulkkey from an
+ * enveloped-data message.
+ */
+ SECU_FileToItem(&envmsg, encryptOptions.envFile);
+ decodeOptions.options = &options;
+ encryptOptions.envmsg = decode(NULL, &envmsg, &decodeOptions);
+ if (!encryptOptions.envmsg) {
+ SECU_PrintError(progName, "problem decoding env msg");
+ exitstatus = 1;
+ break;
+ }
+ rv = get_enc_params(&encryptOptions);
+ decodeOptions.dkcb = dkcb;
+ decodeOptions.bulkkey = encryptOptions.bulkkey;
+ }
+ if (!batch) {
+ cmsg = decode(outFile, &input, &decodeOptions);
+ if (!cmsg) {
+ SECU_PrintError(progName, "problem decoding");
+ exitstatus = 1;
+ }
+ } else {
+ exitstatus = doBatchDecode(outFile, inFile, &decodeOptions);
+ }
+ break;
+ case SIGN: /* -S */
+ signOptions.options = &options;
+ cmsg = signed_data(&signOptions);
+ if (!cmsg) {
+ SECU_PrintError(progName, "problem signing");
+ exitstatus = 1;
+ }
+ break;
+ case ENCRYPT: /* -C */
+ if (!envFileName) {
+ fprintf(stderr, "%s: you must specify an envelope file with -e.\n",
+ progName);
+ exit(1);
+ }
+ encryptOptions.options = &options;
+ encryptOptions.input = &input;
+ encryptOptions.outfile = outFile;
+ /* decode an enveloped-data message to get the bulkkey (create
+ * a new one if neccessary)
+ */
+ if (!encryptOptions.envFile) {
+ encryptOptions.envFile = PR_Open(envFileName,
+ PR_WRONLY | PR_CREATE_FILE, 00660);
+ if (!encryptOptions.envFile) {
+ fprintf(stderr, "%s: failed to create file %s.\n", progName,
+ envFileName);
+ exit(1);
+ }
+ } else {
+ SECU_FileToItem(&envmsg, encryptOptions.envFile);
+ decodeOptions.options = &options;
+ encryptOptions.envmsg = decode(NULL, &envmsg, &decodeOptions);
+ if (encryptOptions.envmsg == NULL) {
+ SECU_PrintError(progName, "problem decrypting env msg");
+ exitstatus = 1;
+ break;
+ }
+ }
+ rv = get_enc_params(&encryptOptions);
+ /* create the encrypted-data message */
+ cmsg = encrypted_data(&encryptOptions);
+ if (!cmsg) {
+ SECU_PrintError(progName, "problem encrypting");
+ exitstatus = 1;
+ }
+ if (encryptOptions.bulkkey) {
+ PK11_FreeSymKey(encryptOptions.bulkkey);
+ encryptOptions.bulkkey = NULL;
+ }
+ break;
+ case ENVELOPE: /* -E */
+ envelopeOptions.options = &options;
+ cmsg = enveloped_data(&envelopeOptions);
+ if (!cmsg) {
+ SECU_PrintError(progName, "problem enveloping");
+ exitstatus = 1;
+ }
+ break;
+ case CERTSONLY: /* -O */
+ certsonlyOptions.options = &options;
+ cmsg = signed_data_certsonly(&certsonlyOptions);
+ if (!cmsg) {
+ SECU_PrintError(progName, "problem with certs-only");
+ exitstatus = 1;
+ }
+ break;
+ default:
+ fprintf(stderr, "One of options -D, -S or -E must be set.\n");
+ Usage();
+ exitstatus = 1;
+ }
+
+ if (signOptions.nickname) {
+ PORT_Free(signOptions.nickname);
+ }
+
+ if ((mode == SIGN || mode == ENVELOPE || mode == CERTSONLY) &&
+ (!exitstatus)) {
+ PLArenaPool *arena = PORT_NewArena(1024);
+ NSSCMSEncoderContext *ecx;
+ SECItem output = { 0, 0, 0 };
+
+ if (!arena) {
+ fprintf(stderr, "%s: out of memory.\n", progName);
+ exit(1);
+ }
+
+ if (cms_verbose) {
+ fprintf(stderr, "cmsg [%p]\n", cmsg);
+ fprintf(stderr, "arena [%p]\n", arena);
+ if (pwcb_arg && (PW_PLAINTEXT == ((secuPWData *)pwcb_arg)->source))
+ fprintf(stderr, "password [%s]\n",
+ ((secuPWData *)pwcb_arg)->data);
+ else
+ fprintf(stderr, "password [NULL]\n");
+ }
+ ecx = NSS_CMSEncoder_Start(cmsg,
+ NULL, NULL, /* DER output callback */
+ &output, arena, /* destination storage */
+ pwcb, pwcb_arg, /* password callback */
+ NULL, NULL, /* decrypt key callback */
+ NULL, NULL); /* detached digests */
+ if (!ecx) {
+ fprintf(stderr, "%s: cannot create encoder context.\n", progName);
+ exit(1);
+ }
+ if (cms_verbose) {
+ fprintf(stderr, "input len [%d]\n", input.len);
+ {
+ unsigned int j;
+ for (j = 0; j < input.len; j++)
+ fprintf(stderr, "%2x%c", input.data[j], (j > 0 && j % 35 == 0) ? '\n' : ' ');
+ }
+ }
+ if (input.len > 0) { /* skip if certs-only (or other zero content) */
+ rv = NSS_CMSEncoder_Update(ecx, (char *)input.data, input.len);
+ if (rv) {
+ fprintf(stderr,
+ "%s: failed to add data to encoder.\n", progName);
+ exit(1);
+ }
+ }
+ rv = NSS_CMSEncoder_Finish(ecx);
+ if (rv) {
+ SECU_PrintError(progName, "failed to encode data");
+ exit(1);
+ }
+
+ if (cms_verbose) {
+ fprintf(stderr, "encoding passed\n");
+ }
+ fwrite(output.data, output.len, 1, outFile);
+ if (cms_verbose) {
+ fprintf(stderr, "wrote to file\n");
+ }
+ PORT_FreeArena(arena, PR_FALSE);
+ }
+ if (cmsg)
+ NSS_CMSMessage_Destroy(cmsg);
+ if (outFile != stdout)
+ fclose(outFile);
+
+ if (inFile != PR_STDIN) {
+ PR_Close(inFile);
+ }
+ if (envFileName) {
+ PORT_Free(envFileName);
+ }
+ if (encryptOptions.envFile) {
+ PR_Close(encryptOptions.envFile);
+ }
+
+ SECITEM_FreeItem(&decodeOptions.content, PR_FALSE);
+ SECITEM_FreeItem(&envmsg, PR_FALSE);
+ SECITEM_FreeItem(&input, PR_FALSE);
+ if (NSS_Shutdown() != SECSuccess) {
+ SECU_PrintError(progName, "NSS_Shutdown failed");
+ exitstatus = 1;
+ }
+ PR_Cleanup();
+ return exitstatus;
+}
diff --git a/security/nss/cmd/smimetools/manifest.mn b/security/nss/cmd/smimetools/manifest.mn
new file mode 100644
index 0000000000..0e20ed77cf
--- /dev/null
+++ b/security/nss/cmd/smimetools/manifest.mn
@@ -0,0 +1,17 @@
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+CORE_DEPTH = ../..
+
+MODULE = nss
+
+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 0000000000..b4ee091ef4
--- /dev/null
+++ b/security/nss/cmd/smimetools/rules.mk
@@ -0,0 +1,7 @@
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+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 0000000000..e67f6bedcb
--- /dev/null
+++ b/security/nss/cmd/smimetools/smime
@@ -0,0 +1,547 @@
+#!/usr/local/bin/perl
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#
+# smime.pl - frontend for S/MIME message generation and parsing
+#
+
+use Getopt::Std;
+
+@boundarychars = ( "0" .. "9", "A" .. "F" );
+
+# path to cmsutil
+$cmsutilpath = "cmsutil";
+
+#
+# Thanks to Gisle Aas <gisle@aas.no> for the base64 functions
+# 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;
+}
+
+sub decode_base64($)
+{
+ local($^W) = 0; # unpack("u",...) gives bogus warning in 5.00[123]
+
+ my $str = shift;
+ my $res = "";
+
+ $str =~ tr|A-Za-z0-9+=/||cd; # remove non-base64 chars
+ if (length($str) % 4) {
+ require Carp;
+ Carp::carp("Length of base64 data not a multiple of 4")
+ }
+ $str =~ s/=+$//; # remove padding
+ $str =~ tr|A-Za-z0-9+/| -_|; # convert to uuencoded format
+ while ($str =~ /(.{1,60})/gs) {
+ my $len = chr(32 + length($1)*3/4); # compute length byte
+ $res .= unpack("u", $len . $1 ); # uudecode
+ }
+ $res;
+}
+
+#
+# parse headers into a hash
+#
+# %headers = parseheaders($headertext);
+#
+sub parseheaders($)
+{
+ my ($headerdata) = @_;
+ my $hdr;
+ my %hdrhash;
+ my $hdrname;
+ my $hdrvalue;
+ my @hdrvalues;
+ my $subhdrname;
+ my $subhdrvalue;
+
+ # the expression in split() correctly handles continuation lines
+ foreach $hdr (split(/\n(?=\S)/, $headerdata)) {
+ $hdr =~ s/\r*\n\s+/ /g; # collapse continuation lines
+ ($hdrname, $hdrvalue) = $hdr =~ m/^(\S+):\s+(.*)$/;
+
+ # ignore non-headers (or should we die horribly?)
+ next unless (defined($hdrname));
+ $hdrname =~ tr/A-Z/a-z/; # lowercase the header name
+ @hdrvalues = split(/\s*;\s*/, $hdrvalue); # split header values (XXXX quoting)
+
+ # there is guaranteed to be at least one value
+ $hdrvalue = shift @hdrvalues;
+ if ($hdrvalue =~ /^\s*\"(.*)\"\s*$/) { # strip quotes if there
+ $hdrvalue = $1;
+ }
+
+ $hdrhash{$hdrname}{MAIN} = $hdrvalue;
+ # print "XXX $hdrname = $hdrvalue\n";
+
+ # deal with additional name-value pairs
+ foreach $hdrvalue (@hdrvalues) {
+ ($subhdrname, $subhdrvalue) = $hdrvalue =~ m/^(\S+)\s*=\s*(.*)$/;
+ # ignore non-name-value pairs (or should we die?)
+ next unless (defined($subhdrname));
+ $subhdrname =~ tr/A-Z/a-z/;
+ if ($subhdrvalue =~ /^\s*\"(.*)\"\s*$/) { # strip quotes if there
+ $subhdrvalue = $1;
+ }
+ $hdrhash{$hdrname}{$subhdrname} = $subhdrvalue;
+ }
+
+ }
+ return %hdrhash;
+}
+
+#
+# encryptentity($entity, $options) - encrypt an S/MIME entity,
+# creating a new application/pkcs7-smime entity
+#
+# entity - string containing entire S/MIME entity to encrypt
+# options - options for cmsutil
+#
+# this will generate and return a new application/pkcs7-smime entity containing
+# the enveloped input entity.
+#
+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 enveloped CMS message using our data
+ #
+ 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;
+ }
+
+ $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
+
+ open (ENC, $tmpencfile) or die "ERROR: cannot find newly generated encrypted content";
+ local($/) = undef; # slurp whole file
+ $out .= encode_base64(<ENC>), "\n"; # entity body is base64-encoded CMS message
+ 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 SHA256 digest)
+ $out .= "Content-Type: multipart/signed; protocol=\"application/pkcs7-signature\"; micalg=sha256; boundary=\"${boundary}\"\n";
+ $out .= "\n"; # end of entity header
+ $out .= "This is a cryptographically signed message in MIME format.\n"; # explanatory comment
+ $out .= "\n--${boundary}\n";
+ $out .= $entity;
+ $out .= "\n--${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>); # append base64-encoded signature
+ $out .= "\n--${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 " -D decode a S/MIME message\n";
+ print STDERR " -p passwd use \"passwd\" as security module password\n";
+ print STDERR " (required for decrypting only)\n";
+ print STDERR " -C pathname set pathname of \"cmsutil\"\n";
+ print STDERR " -d directory set directory containing certificate db\n";
+ print STDERR " (default: ~/.netscape)\n";
+ print STDERR "\nWith -S or -E, smime will take a regular RFC822 message or MIME entity\n";
+ print STDERR "on stdin and generate a signed or encrypted S/MIME message with the same\n";
+ print STDERR "headers and content from it. The output can be used as input to a MTA.\n";
+ print STDERR "-D causes smime to strip off all S/MIME layers if possible and output\n";
+ print STDERR "the \"inner\" message.\n";
+}
+
+#
+# start of main procedures
+#
+
+#
+# process command line options
+#
+unless (getopts('S:E:p:d:C:D')) {
+ usage();
+ exit 1;
+}
+
+unless (defined($opt_S) or defined($opt_E) or defined($opt_D)) {
+ print STDERR "ERROR: -S and/or -E, or -D must be specified.\n";
+ usage();
+ exit 1;
+}
+
+$signopts = "";
+$encryptopts = "";
+$decodeopts = "";
+
+# pass -d option along
+if (defined($opt_d)) {
+ $signopts .= "-d \"$opt_d\" ";
+ $encryptopts .= "-d \"$opt_d\" ";
+ $decodeopts .= "-d \"$opt_d\" ";
+}
+
+if (defined($opt_S)) {
+ $signopts .= "-N \"$opt_S\" ";
+}
+
+if (defined($opt_p)) {
+ $signopts .= "-p \"$opt_p\" ";
+ $decodeopts .= "-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 = "";
+$mimeheaders = "";
+$mimebody = "";
+$skippedheaders = "";
+while (<STDIN>) {
+ last if (/^$/);
+ if (/^content-\S+: /i) {
+ $lastref = \$mimeheaders;
+ } elsif (/^mime-version: /i) {
+ $lastref = \$skippedheaders; # skip it
+ } elsif (/^\s/) {
+ ;
+ } else {
+ $lastref = \$rfc822headers;
+ }
+ $$lastref .= $_;
+}
+
+#
+# if there are no MIME entity headers, generate some default ones
+#
+if ($mimeheaders eq "") {
+ $mimeheaders .= "Content-Type: text/plain; charset=us-ascii\n";
+ $mimeheaders .= "Content-Transfer-Encoding: 7bit\n";
+}
+
+#
+# slurp in the entity body
+#
+$saveRS = $/;
+$/ = undef;
+$mimebody = <STDIN>;
+$/ = $saveRS;
+chomp($mimebody);
+
+if (defined $opt_D) {
+ #
+ # decode
+ #
+ # possible options would be:
+ # - strip off only one layer
+ # - strip off outer signature (if present)
+ # - just print information about the structure of the message
+ # - strip n layers, then dump DER of CMS message
+
+ $layercounter = 1;
+
+ while (1) {
+ %hdrhash = parseheaders($mimeheaders);
+ unless (exists($hdrhash{"content-type"}{MAIN})) {
+ print STDERR "ERROR: no content type header found in MIME entity\n";
+ last; # no content-type - we're done
+ }
+
+ $contenttype = $hdrhash{"content-type"}{MAIN};
+ if ($contenttype eq "application/pkcs7-mime") {
+ #
+ # opaque-signed or enveloped message
+ #
+ unless (exists($hdrhash{"content-type"}{"smime-type"})) {
+ print STDERR "ERROR: no smime-type attribute in application/pkcs7-smime entity.\n";
+ last;
+ }
+ $smimetype = $hdrhash{"content-type"}{"smime-type"};
+ if ($smimetype eq "signed-data" or $smimetype eq "enveloped-data") {
+ # it's verification or decryption time!
+
+ # can handle only base64 encoding for now
+ # all other encodings are treated as binary (8bit)
+ if ($hdrhash{"content-transfer-encoding"}{MAIN} eq "base64") {
+ $mimebody = decode_base64($mimebody);
+ }
+
+ # if we need to dump the DER, we would do it right here
+
+ # now write the DER
+ $tmpderfile = "/tmp/der.$$";
+ open(TMP, ">$tmpderfile") or die "ERROR: cannot write signature data to temporary file";
+ print TMP $mimebody;
+ unless (close(TMP)) {
+ print STDERR "ERROR: writing signature data to temporary file.\n";
+ unlink($tmpderfile);
+ exit 1;
+ }
+
+ $mimeheaders = "";
+ open(TMP, "$cmsutilpath -D $decodeopts -h $layercounter -i $tmpderfile |") or die "ERROR: cannot open pipe to cmsutil";
+ $layercounter++;
+ while (<TMP>) {
+ last if (/^\r?$/); # empty lines mark end of header
+ if (/^SMIME: /) { # add all SMIME info to the rfc822 hdrs
+ $lastref = \$rfc822headers;
+ } elsif (/^\s/) {
+ ; # continuation lines go to the last dest
+ } else {
+ $lastref = \$mimeheaders; # all other headers are mime headers
+ }
+ $$lastref .= $_;
+ }
+ # slurp in rest of the data to $mimebody
+ $saveRS = $/; $/ = undef; $mimebody = <TMP>; $/ = $saveRS;
+ close(TMP);
+
+ unlink($tmpderfile);
+
+ } else {
+ print STDERR "ERROR: unknown smime-type \"$smimetype\" in application/pkcs7-smime entity.\n";
+ last;
+ }
+ } elsif ($contenttype eq "multipart/signed") {
+ #
+ # clear signed message
+ #
+ unless (exists($hdrhash{"content-type"}{"protocol"})) {
+ print STDERR "ERROR: content type has no protocol attribute in multipart/signed entity.\n";
+ last;
+ }
+ if ($hdrhash{"content-type"}{"protocol"} ne "application/pkcs7-signature") {
+ # we cannot handle this guy
+ print STDERR "ERROR: unknown protocol \"", $hdrhash{"content-type"}{"protocol"},
+ "\" in multipart/signed entity.\n";
+ last;
+ }
+ unless (exists($hdrhash{"content-type"}{"boundary"})) {
+ print STDERR "ERROR: no boundary attribute in multipart/signed entity.\n";
+ last;
+ }
+ $boundary = $hdrhash{"content-type"}{"boundary"};
+
+ # split $mimebody along \n--$boundary\n - gets you four parts
+ # first (0), any comments the sending agent might have put in
+ # second (1), the message itself
+ # third (2), the signature as a mime entity
+ # fourth (3), trailing data (there shouldn't be any)
+
+ @multiparts = split(/\r?\n--$boundary(?:--)?\r?\n/, $mimebody);
+
+ #
+ # parse the signature headers
+ ($submimeheaders, $submimebody) = split(/^$/m, $multiparts[2]);
+ %sighdrhash = parseheaders($submimeheaders);
+ unless (exists($sighdrhash{"content-type"}{MAIN})) {
+ print STDERR "ERROR: signature entity has no content type.\n";
+ last;
+ }
+ if ($sighdrhash{"content-type"}{MAIN} ne "application/pkcs7-signature") {
+ # we cannot handle this guy
+ print STDERR "ERROR: unknown content type \"", $sighdrhash{"content-type"}{MAIN},
+ "\" in signature entity.\n";
+ last;
+ }
+ if ($sighdrhash{"content-transfer-encoding"}{MAIN} eq "base64") {
+ $submimebody = decode_base64($submimebody);
+ }
+
+ # we would dump the DER at this point
+
+ $tmpsigfile = "/tmp/sig.$$";
+ open(TMP, ">$tmpsigfile") or die "ERROR: cannot write signature data to temporary file";
+ print TMP $submimebody;
+ unless (close(TMP)) {
+ print STDERR "ERROR: writing signature data to temporary file.\n";
+ unlink($tmpsigfile);
+ exit 1;
+ }
+
+ $tmpmsgfile = "/tmp/msg.$$";
+ open(TMP, ">$tmpmsgfile") or die "ERROR: cannot write message data to temporary file";
+ print TMP $multiparts[1];
+ unless (close(TMP)) {
+ print STDERR "ERROR: writing message data to temporary file.\n";
+ unlink($tmpsigfile);
+ unlink($tmpmsgfile);
+ exit 1;
+ }
+
+ $mimeheaders = "";
+ open(TMP, "$cmsutilpath -D $decodeopts -h $layercounter -c $tmpmsgfile -i $tmpsigfile |") or die "ERROR: cannot open pipe to cmsutil";
+ $layercounter++;
+ while (<TMP>) {
+ last if (/^\r?$/);
+ if (/^SMIME: /) {
+ $lastref = \$rfc822headers;
+ } elsif (/^\s/) {
+ ;
+ } else {
+ $lastref = \$mimeheaders;
+ }
+ $$lastref .= $_;
+ }
+ $saveRS = $/; $/ = undef; $mimebody = <TMP>; $/ = $saveRS;
+ close(TMP);
+ unlink($tmpsigfile);
+ unlink($tmpmsgfile);
+
+ } else {
+
+ # not a content type we know - we're done
+ last;
+
+ }
+ }
+
+ # so now we have the S/MIME parsing information in rfc822headers
+ # and the first mime entity we could not handle in mimeheaders and mimebody.
+ # dump 'em out and we're done.
+ print $rfc822headers;
+ print $mimeheaders . "\n" . $mimebody;
+
+} else {
+
+ #
+ # encode (which is much easier than decode)
+ #
+
+ $mimeentity = $mimeheaders . "\n" . $mimebody;
+
+ #
+ # 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/\r*\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/cmd/smimetools/smimetools.gyp b/security/nss/cmd/smimetools/smimetools.gyp
new file mode 100644
index 0000000000..13d3679f3b
--- /dev/null
+++ b/security/nss/cmd/smimetools/smimetools.gyp
@@ -0,0 +1,25 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+{
+ 'includes': [
+ '../../coreconf/config.gypi',
+ '../../cmd/platlibs.gypi'
+ ],
+ 'targets': [
+ {
+ 'target_name': 'cmsutil',
+ 'type': 'executable',
+ 'sources': [
+ 'cmsutil.c'
+ ],
+ 'dependencies': [
+ '<(DEPTH)/exports.gyp:dbm_exports',
+ '<(DEPTH)/exports.gyp:nss_exports'
+ ]
+ }
+ ],
+ 'variables': {
+ 'module': 'nss'
+ }
+} \ No newline at end of file