mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
731c45f2b1
r=rrelyea
1652 lines
49 KiB
C
1652 lines
49 KiB
C
/* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* 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 the Initial Developer are Copyright (C) 1994-2000
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
* the provisions above, a recipient may use your version of this file under
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
/*
|
|
* cmsutil -- A command to work with CMS data
|
|
*
|
|
* $Id: cmsutil.c,v 1.54 2008/08/08 23:48:06 julien.pierre.boogz%sun.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;
|
|
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(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"
|
|
" -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: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"
|
|
" -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", " ");
|
|
|
|
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;
|
|
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.
|
|
*/
|
|
SECStatus rv;
|
|
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");
|
|
}
|
|
/* 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_GetEnv("NSS_DISABLE_ARENA_FREE_LIST");
|
|
PORT_Assert(ev);
|
|
ev = PR_GetEnv("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_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: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(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 'b':
|
|
if (mode != DECODE) {
|
|
fprintf(stderr,
|
|
"%s: option -b only supported with option -D.\n",
|
|
progName);
|
|
Usage(progName);
|
|
exit(1);
|
|
}
|
|
batch = PR_TRUE;
|
|
break;
|
|
|
|
case 'c':
|
|
if (mode != DECODE) {
|
|
fprintf(stderr,
|
|
"%s: option -c only supported with option -D.\n",
|
|
progName);
|
|
Usage(progName);
|
|
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 = 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':
|
|
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(progName);
|
|
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(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 'f':
|
|
if (!optstate->value) {
|
|
fprintf(stderr, "%s: option -f must have a value.\n", progName);
|
|
Usage(progName);
|
|
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(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 && !batch) {
|
|
rv = SECU_FileToItem(&input, inFile);
|
|
if (rv != SECSuccess) {
|
|
SECU_PrintError(progName, "unable to read infile");
|
|
exit(1);
|
|
}
|
|
if (inFile != PR_STDIN) {
|
|
PR_Close(inFile);
|
|
}
|
|
}
|
|
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);
|
|
if (inFile != PR_STDIN) {
|
|
PR_Close(inFile);
|
|
}
|
|
}
|
|
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(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);
|
|
}
|
|
|
|
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);
|
|
|
|
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;
|
|
}
|