diff options
Diffstat (limited to 'security/nss/cmd/smimetools/cmsutil.c')
-rw-r--r-- | security/nss/cmd/smimetools/cmsutil.c | 1430 |
1 files changed, 1430 insertions, 0 deletions
diff --git a/security/nss/cmd/smimetools/cmsutil.c b/security/nss/cmd/smimetools/cmsutil.c new file mode 100644 index 000000000..ec2fe56e8 --- /dev/null +++ b/security/nss/cmd/smimetools/cmsutil.c @@ -0,0 +1,1430 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * cmsutil -- A command to work with CMS data + * + * $Id$ + */ + +#include "nspr.h" +#include "secutil.h" +#include "plgetopt.h" +#include "secpkcs7.h" +#include "cert.h" +#include "certdb.h" +#include "secoid.h" +#include "cms.h" +#include "nss.h" +#include "smime.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> + +extern void SEC_Init(void); /* XXX */ +char *progName = NULL; +static int cms_verbose = 0; + +/* 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; + + digcx = NSS_CMSDigestContext_StartMultiple(algids); + if (digcx == NULL) + return SECFailure; + + NSS_CMSDigestContext_Update(digcx, input->data, input->len); + + return NSS_CMSDigestContext_FinishMultiple(digcx, poolp, digests); +} + + +static void +Usage(char *progName) +{ + fprintf(stderr, "Usage: %s [-D|-S|-E] [<options>] [-d dbdir] [-u certusage]\n", progName); + fprintf(stderr, " -i infile use infile as source of data (default: stdin)\n"); + fprintf(stderr, " -o outfile use outfile as destination of data (default: stdout)\n"); + fprintf(stderr, " -d dbdir key/cert database directory (default: ~/.netscape)\n"); + fprintf(stderr, " -p password use password as key db password (default: prompt)\n"); + fprintf(stderr, " -u certusage set type of certificate usage (default: certUsageEmailSigner)\n"); + fprintf(stderr, "\n"); + fprintf(stderr, " -D decode a CMS message\n"); + fprintf(stderr, " -c content use this detached content\n"); + fprintf(stderr, " -n suppress output of content\n"); + fprintf(stderr, " -h num generate email headers with info about CMS message\n"); + fprintf(stderr, " -S create a CMS signed message\n"); + fprintf(stderr, " -N nick use certificate named \"nick\" for signing\n"); + fprintf(stderr, " -T do not include content in CMS message\n"); + fprintf(stderr, " -G include a signing time attribute\n"); + fprintf(stderr, " -P include a SMIMECapabilities attribute\n"); + fprintf(stderr, " -Y nick include a EncryptionKeyPreference attribute with cert\n"); + fprintf(stderr, " -E create a CMS enveloped message (NYI)\n"); + fprintf(stderr, " -r id,... create envelope for these recipients,\n"); + fprintf(stderr, " where id can be a certificate nickname or email address\n"); + fprintf(stderr, " -v print debugging information\n"); + fprintf(stderr, "\nCert usage codes:\n"); + fprintf(stderr, "%-25s 0 - certUsageSSLClient\n", " "); + fprintf(stderr, "%-25s 1 - certUsageSSLServer\n", " "); + fprintf(stderr, "%-25s 2 - certUsageSSLServerWithStepUp\n", " "); + fprintf(stderr, "%-25s 3 - certUsageSSLCA\n", " "); + fprintf(stderr, "%-25s 4 - certUsageEmailSigner\n", " "); + fprintf(stderr, "%-25s 5 - certUsageEmailRecipient\n", " "); + fprintf(stderr, "%-25s 6 - certUsageObjectSigner\n", " "); + fprintf(stderr, "%-25s 7 - certUsageUserCertImport\n", " "); + fprintf(stderr, "%-25s 8 - certUsageVerifyCA\n", " "); + fprintf(stderr, "%-25s 9 - certUsageProtectedObjectSigner\n", " "); + fprintf(stderr, "%-25s 10 - certUsageStatusResponder\n", " "); + fprintf(stderr, "%-25s 11 - certUsageAnyCA\n", " "); + + exit(-1); +} + +char * +ownpw(PK11SlotInfo *info, PRBool retry, void *arg) +{ + char * passwd = NULL; + + if ( (!retry) && arg ) { + passwd = PL_strdup((char *)arg); + } + + return passwd; +} + +struct optionsStr { + char *password; + SECCertUsage certUsage; + CERTCertDBHandle *certHandle; +}; + +struct decodeOptionsStr { + struct optionsStr *options; + PRFileDesc *contentFile; + int headerLevel; + PRBool suppressContent; + NSSCMSGetDecryptKeyCallback dkcb; + PK11SymKey *bulkkey; +}; + +struct signOptionsStr { + struct optionsStr *options; + char *nickname; + char *encryptionKeyPreferenceNick; + PRBool signingTime; + PRBool smimeProfile; + PRBool detached; +}; + +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 *output, SECItem *input, + struct decodeOptionsStr *decodeOptions) +{ + NSSCMSDecoderContext *dcx; + NSSCMSMessage *cmsg; + NSSCMSContentInfo *cinfo; + NSSCMSSignedData *sigd = NULL; + NSSCMSEnvelopedData *envd; + NSSCMSEncryptedData *encd; + SECAlgorithmID **digestalgs; + int nlevels, i, nsigners, j; + char *signercn; + NSSCMSSignerInfo *si; + SECOidTag typetag; + SECItem **digests; + PLArenaPool *poolp; + PK11PasswordFunc pwcb; + void *pwcb_arg; + SECItem *item, sitem = { 0, 0, 0 }; + + pwcb = (decodeOptions->options->password != NULL) ? ownpw : NULL; + pwcb_arg = (decodeOptions->options->password != NULL) ? + (void *)decodeOptions->options->password : NULL; + + if (decodeOptions->contentFile) { + /* detached content: grab content file */ + SECU_FileToItem(&sitem, decodeOptions->contentFile); + item = &sitem; + } + + dcx = NSS_CMSDecoder_Start(NULL, + NULL, NULL, /* content callback */ + pwcb, pwcb_arg, /* password callback */ + decodeOptions->dkcb, /* decrypt key callback */ + decodeOptions->bulkkey); + (void)NSS_CMSDecoder_Update(dcx, (char *)input->data, input->len); + 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++) { + cinfo = NSS_CMSMessage_ContentLevel(cmsg, i); + typetag = NSS_CMSContentInfo_GetContentTypeTag(cinfo); + + if (decodeOptions->headerLevel >= 0) + fprintf(out, "\tlevel=%d.%d; ", decodeOptions->headerLevel, nlevels - i); + + switch (typetag) { + case SEC_OID_PKCS7_SIGNED_DATA: + if (decodeOptions->headerLevel >= 0) + fprintf(out, "type=signedData; "); + sigd = (NSSCMSSignedData *)NSS_CMSContentInfo_GetContent(cinfo); + if (sigd == NULL) { + SECU_PrintError(progName, + "problem finding signedData component"); + goto loser; + } + + /* if we have a content file, but no digests for this signedData */ + if (decodeOptions->contentFile != NULL && !NSS_CMSSignedData_HasDigests(sigd)) { + if ((poolp = PORT_NewArena(1024)) == NULL) { + fprintf(stderr, "cmsutil: Out of memory.\n"); + goto loser; + } + digestalgs = NSS_CMSSignedData_GetDigestAlgs(sigd); + if (DigestFile (poolp, &digests, item, digestalgs) + != SECSuccess) { + SECU_PrintError(progName, + "problem computing message digest"); + goto loser; + } + if (NSS_CMSSignedData_SetDigests(sigd, digestalgs, digests) != SECSuccess) { + + SECU_PrintError(progName, + "problem setting message digests"); + goto loser; + } + PORT_FreeArena(poolp, PR_FALSE); + } + + /* import the certificates */ + if (NSS_CMSSignedData_ImportCerts(sigd, + decodeOptions->options->certHandle, + decodeOptions->options->certUsage, + PR_FALSE) + != 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) { + /* must be a cert transport message */ + SECStatus rv; + /* XXX workaround for bug #54014 */ + NSS_CMSSignedData_ImportCerts(sigd, + decodeOptions->options->certHandle, + decodeOptions->options->certUsage, + PR_TRUE); + 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++) { + si = NSS_CMSSignedData_GetSignerInfo(sigd, j); + signercn = NSS_CMSSignerInfo_GetSignerCommonName(si); + if (signercn == NULL) + signercn = ""; + if (decodeOptions->headerLevel >= 0) + fprintf(out, "\n\t\tsigner%d.id=\"%s\"; ", j, signercn); + (void)NSS_CMSSignedData_VerifySignerInfo(sigd, j, + decodeOptions->options->certHandle, + decodeOptions->options->certUsage); + if (decodeOptions->headerLevel >= 0) + fprintf(out, "signer%d.status=%s; ", j, + NSS_CMSUtil_VerificationStatusToString( + NSS_CMSSignerInfo_GetVerificationStatus(si))); + /* XXX what do we do if we don't print headers? */ + } + break; + case SEC_OID_PKCS7_ENVELOPED_DATA: + if (decodeOptions->headerLevel >= 0) + fprintf(out, "type=envelopedData; "); + envd = (NSSCMSEnvelopedData *)NSS_CMSContentInfo_GetContent(cinfo); + break; + case SEC_OID_PKCS7_ENCRYPTED_DATA: + if (decodeOptions->headerLevel >= 0) + fprintf(out, "type=encryptedData; "); + encd = (NSSCMSEncryptedData *)NSS_CMSContentInfo_GetContent(cinfo); + break; + case SEC_OID_PKCS7_DATA: + if (decodeOptions->headerLevel >= 0) + fprintf(out, "type=data; "); + break; + default: + break; + } + if (decodeOptions->headerLevel >= 0) + fprintf(out, "\n"); + } + + if (!decodeOptions->suppressContent) { + if (!decodeOptions->contentFile) + item = NSS_CMSMessage_GetContent(cmsg); + SECITEM_CopyItem(NULL, output, item); + } + + 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 + 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, + certUsageEmailSigner, + PR_FALSE, + NULL)) == 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 + */ + if ((signerinfo = NSS_CMSSignerInfo_Create(cmsg, cert, SEC_OID_SHA1)) + == 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"); + } + /* 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) { + /* get the cert, add it to the message */ + if ((ekpcert = CERT_FindUserCertByUsage( + signOptions->options->certHandle, + signOptions->encryptionKeyPreferenceNick, + certUsageEmailRecipient, PR_FALSE, NULL)) + == 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_CMSSignedData_AddCertificate(sigd, ekpcert) != SECSuccess) { + fprintf(stderr, "ERROR: cannot add encryption certificate.\n"); + goto loser; + } + } else { + /* 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; + } + } 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, + NULL)) == 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_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-date 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; + PK11SymKey *bulkkey = NULL; + SECOidTag bulkalgtag; + int keysize; + 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 + */ + bulkalgtag = NSS_CMSContentInfo_GetContentEncAlgTag(cinfo); + keysize = NSS_CMSContentInfo_GetBulkKeySize(cinfo); + bulkkey = NSS_CMSContentInfo_GetBulkKey(cinfo); + break; + } + } + if (i == nlevels) { + fprintf(stderr, "%s: could not retrieve enveloped data.", progName); + goto loser; + } + encryptOptions->bulkalgtag = bulkalgtag; + encryptOptions->bulkkey = bulkkey; + encryptOptions->keysize = keysize; + rv = SECSuccess; +loser: + 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)) == NULL) { + fprintf(stderr, "ERROR: out of memory.\n"); + goto loser; + } + if ((certs = + (CERTCertificate **)PORT_ArenaZAlloc(tmppoolp, + (cnt+1)*sizeof(CERTCertificate*))) + == NULL) { + 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; +} + +typedef enum { UNKNOWN, DECODE, SIGN, ENCRYPT, ENVELOPE, CERTSONLY } Mode; + +#if 0 +void +parse_message_for_recipients(PRFileDesc *inFile, + struct envelopeOptionsStr *envelopeOptions) +{ + SECItem filedata; + SECStatus rv; + rv = SECU_FileToItem(&filedata, inFile); +} +#endif + +int +main(int argc, char **argv) +{ + FILE *outFile; + NSSCMSMessage *cmsg = NULL; + PRFileDesc *inFile; + PLOptState *optstate; + PLOptStatus status; + Mode mode = UNKNOWN; + PK11PasswordFunc pwcb; + void *pwcb_arg; + 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 output = { 0, 0, 0}; + SECItem dummy = { 0, 0, 0 }; + SECItem envmsg = { 0, 0, 0 }; + SECStatus rv; + + progName = strrchr(argv[0], '/'); + progName = progName ? progName+1 : argv[0]; + + inFile = PR_STDIN; + outFile = stdout; + envFileName = NULL; + mode = UNKNOWN; + decodeOptions.contentFile = NULL; + decodeOptions.suppressContent = PR_FALSE; + decodeOptions.headerLevel = -1; + options.certUsage = certUsageEmailSigner; + options.password = NULL; + signOptions.nickname = NULL; + signOptions.detached = PR_FALSE; + signOptions.signingTime = PR_FALSE; + signOptions.smimeProfile = PR_FALSE; + signOptions.encryptionKeyPreferenceNick = NULL; + envelopeOptions.recipients = NULL; + 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, + "CDSEOnN:TGPYv:h:p:i:c:d:e:o:s:u:r:"); + while ((status = PL_GetNextOpt(optstate)) == PL_OPT_OK) { + switch (optstate->option) { + case '?': + Usage(progName); + break; + + case 'C': + mode = ENCRYPT; + break; + case 'D': + mode = DECODE; + break; + case 'S': + mode = SIGN; + break; + case 'E': + mode = ENVELOPE; + break; + case 'O': + mode = CERTSONLY; + break; + case 'v': + cms_verbose = 1; + break; + + case 'n': + if (mode != DECODE) { + fprintf(stderr, + "%s: option -n only supported with option -D.\n", + progName); + Usage(progName); + exit(1); + } + decodeOptions.suppressContent = PR_TRUE; + break; + + case 'N': + if (mode != SIGN) { + fprintf(stderr, + "%s: option -N only supported with option -S.\n", + progName); + Usage(progName); + exit(1); + } + signOptions.nickname = strdup(optstate->value); + break; + + case 'Y': + if (mode != SIGN) { + fprintf(stderr, + "%s: option -Y only supported with option -S.\n", + progName); + Usage(progName); + exit(1); + } + signOptions.encryptionKeyPreferenceNick = strdup(optstate->value); + break; + + case 'T': + if (mode != SIGN) { + fprintf(stderr, + "%s: option -T only supported with option -S.\n", + progName); + Usage(progName); + exit(1); + } + signOptions.detached = PR_TRUE; + break; + + case 'G': + if (mode != SIGN) { + fprintf(stderr, + "%s: option -G only supported with option -S.\n", + progName); + Usage(progName); + exit(1); + } + signOptions.signingTime = PR_TRUE; + break; + + case 'P': + if (mode != SIGN) { + fprintf(stderr, + "%s: option -P only supported with option -S.\n", + progName); + Usage(progName); + exit(1); + } + signOptions.smimeProfile = PR_TRUE; + break; + + case 'h': + if (mode != DECODE) { + fprintf(stderr, + "%s: option -h only supported with option -D.\n", + progName); + Usage(progName); + exit(1); + } + decodeOptions.headerLevel = atoi(optstate->value); + if (decodeOptions.headerLevel < 0) { + fprintf(stderr, "option -h cannot have a negative value.\n"); + exit(1); + } + break; + + case 'p': + if (!optstate->value) { + fprintf(stderr, "%s: option -p must have a value.\n", progName); + Usage(progName); + exit(1); + } + + options.password = strdup(optstate->value); + break; + + case 'i': + 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 'c': + if (mode != DECODE) { + fprintf(stderr, + "%s: option -c only supported with option -D.\n", + progName); + Usage(progName); + exit(1); + } + if ((decodeOptions.contentFile = + PR_Open(optstate->value, PR_RDONLY, 006600)) == NULL) { + fprintf(stderr, "%s: unable to open \"%s\" for reading.\n", + progName, optstate->value); + exit(1); + } + break; + + case 'o': +#if 0 + if (mode == DECODE) { + outFile = fopen(optstate->value, "w"); + } else { + outFile = fopen(optstate->value, "wb"); + } +#endif + 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 'r': + if (!optstate->value) { + fprintf(stderr, "%s: option -r must have a value.\n", progName); + Usage(progName); + exit(1); + } +#if 0 + fprintf(stderr, "recipient = %s\n", optstate->value); +#endif + envelopeOptions.recipients = ptrarray; + 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 'd': + SECU_ConfigDirectory(optstate->value); + break; + + case 'e': + envFileName = strdup(optstate->value); + encryptOptions.envFile = PR_Open(envFileName, PR_RDONLY, 00660); + break; + + case 'u': { + int usageType; + + usageType = atoi (strdup(optstate->value)); + if (usageType < certUsageSSLClient || usageType > certUsageAnyCA) + return -1; + options.certUsage = (SECCertUsage)usageType; + break; + } + + } + } + + if (mode == UNKNOWN) + Usage(progName); + + if (mode != CERTSONLY) + SECU_FileToItem(&input, inFile); + if (inFile != PR_STDIN) + PR_Close(inFile); + if (cms_verbose) { + fprintf(stderr, "received commands\n"); + } + + /* Call the libsec 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 defined(_WIN32) + /*if (outFile == stdout && mode != DECODE) {*/ + 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: + 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, &dummy, &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; + } + cmsg = decode(outFile, &output, &input, &decodeOptions); + if (!cmsg) { + SECU_PrintError(progName, "problem decoding"); + exitstatus = 1; + } + fwrite(output.data, output.len, 1, outFile); + break; + case SIGN: + signOptions.options = &options; + cmsg = signed_data(&signOptions); + if (!cmsg) { + SECU_PrintError(progName, "problem signing"); + exitstatus = 1; + } + break; + case ENCRYPT: + 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; + 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, &dummy, &envmsg, + &decodeOptions); + if (encryptOptions.envmsg == NULL) { + SECU_PrintError(progName, "problem decrypting env msg"); + exitstatus = 1; + break; + } + } + /* decode an enveloped-data message to get the bulkkey (create + * a new one if neccessary) + */ + 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: + envelopeOptions.options = &options; +#if 0 + if (!envelopeOptions.recipients) + parse_message_for_recipients(myIn, &envelopeOptions); +#endif + cmsg = enveloped_data(&envelopeOptions); + if (!cmsg) { + SECU_PrintError(progName, "problem enveloping"); + exitstatus = 1; + } + break; + case CERTSONLY: + 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(progName); + exitstatus = 1; + } + 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); + } + pwcb = (options.password != NULL) ? ownpw : NULL; + pwcb_arg = (options.password != NULL) ? (void *)options.password : NULL; + if (cms_verbose) { + fprintf(stderr, "cmsg [%p]\n", cmsg); + fprintf(stderr, "arena [%p]\n", arena); + if (pwcb_arg) + fprintf(stderr, "password [%s]\n", (char *)pwcb_arg); + 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); + { 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"); + } + /*PR_Write(output.data, output.len);*/ + 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 (decodeOptions.contentFile) + PR_Close(decodeOptions.contentFile); + NSS_Shutdown(); + exit(exitstatus); +} |