diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /security/nss/cmd/smimetools | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | security/nss/cmd/smimetools/Makefile | 49 | ||||
-rw-r--r-- | security/nss/cmd/smimetools/cmsutil.c | 1625 | ||||
-rw-r--r-- | security/nss/cmd/smimetools/manifest.mn | 17 | ||||
-rw-r--r-- | security/nss/cmd/smimetools/rules.mk | 7 | ||||
-rwxr-xr-x | security/nss/cmd/smimetools/smime | 547 | ||||
-rw-r--r-- | security/nss/cmd/smimetools/smimetools.gyp | 25 |
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 |