gecko-dev/security/nss/cmd/smimetools/cmsutil.c
nelsonb%netscape.com aa085e7956 Fix bugzilla bug 225301. r=jpierre. This patch does the following:
1. Fixes the Usage message to document the command line options.
2. Changes the "decode" function to
   a) report an error on bad signatures, only when decoding the input file,
      not when decoding an ancillary "enveloped file".
   b) only output the contents of the "detached content" file (-c option)
      when that file's content was actually used in the computation.
3. Sundry other cleanup and added comments.
2003-11-13 23:03:12 +00:00

1455 lines
44 KiB
C

/*
* 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: cmsutil.c,v 1.44 2003/11/13 23:03:12 nelsonb%netscape.com Exp $
*/
#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;
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 [-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"
" -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"
" -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:SHA1)\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"
" -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", " ");
exit(-1);
}
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;
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 *output, SECItem *input,
const struct decodeOptionsStr *decodeOptions)
{
NSSCMSDecoderContext *dcx;
NSSCMSMessage *cmsg;
NSSCMSContentInfo *cinfo;
NSSCMSSignedData *sigd = NULL;
NSSCMSEnvelopedData *envd;
NSSCMSEncryptedData *encd;
int nlevels, i, nsigners, j;
char *signercn;
NSSCMSSignerInfo *si;
SECOidTag typetag;
SECItem **digests;
SECItem sitem = { 0, 0, 0 };
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)) {
PLArenaPool *poolp;
SECAlgorithmID **digestalgs;
/* detached content: grab content file */
if (!sitem.data) {
SECU_FileToItem(&sitem, decodeOptions->contentFile);
}
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");
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++) {
SECStatus bad;
NSSCMSVerificationStatus vs;
const char * svs;
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);
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:
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) {
SECItem *item = (sitem.data)
? &sitem
: 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,
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");
}
/* 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;
}
typedef enum { UNKNOWN, DECODE, SIGN, ENCRYPT, ENVELOPE, CERTSONLY } Mode;
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 output = { 0, 0, 0};
SECItem dummy = { 0, 0, 0 };
SECItem envmsg = { 0, 0, 0 };
SECStatus rv;
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.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;
signOptions.hashAlgTag = SEC_OID_SHA1;
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:c:d:e:h:i:no: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(progName);
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(progName);
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(progName);
exit(1);
}
signOptions.nickname = 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(progName);
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(progName);
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(progName);
exit(1);
}
signOptions.encryptionKeyPreferenceNick = strdup(optstate->value);
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 'd':
SECU_ConfigDirectory(optstate->value);
break;
case 'e':
envFileName = 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(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 '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 '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 '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(progName);
exit(1);
}
options.password = strdup(optstate->value);
break;
case 'r':
if (!optstate->value) {
fprintf(stderr, "%s: option -r must have a value.\n", progName);
Usage(progName);
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(progName);
PL_DestroyOptState(optstate);
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 (options.password)
{
pwdata.source = PW_PLAINTEXT;
pwdata.data = options.password;
}
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, &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: /* -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, &dummy, &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(progName);
exitstatus = 1;
}
if ( (mode == SIGN || mode == ENVELOPE || mode == CERTSONLY)
&& (!exitstatus) ) {
PLArenaPool *arena = PORT_NewArena(1024);
NSSCMSEncoderContext *ecx;
SECItem output = { 0, 0, 0 };
secuPWData pwdata = { PW_NONE, 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)
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);
{ 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");
}
/*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);
if (NSS_Shutdown() != SECSuccess) {
exit(1);
}
exit(exitstatus);
}