--- /dev/null
+/*
+ * 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.
+ */
+
+/*
+ * Modifications Copyright (c) 2003-2009,2012,2014 Apple Inc. All Rights Reserved.
+ *
+ * cmsutil -- A command to work with CMS data
+ */
+
+#include "security_tool.h"
+#include "keychain_utilities.h"
+#include "identity_find.h"
+
+#include <Security/SecCmsBase.h>
+#include <Security/SecCmsContentInfo.h>
+#include <Security/SecCmsDecoder.h>
+#include <Security/SecCmsDigestContext.h>
+#include <Security/SecCmsDigestedData.h>
+#include <Security/SecCmsEncoder.h>
+#include <Security/SecCmsEncryptedData.h>
+#include <Security/SecCmsEnvelopedData.h>
+#include <Security/SecCmsMessage.h>
+#include <Security/SecCmsRecipientInfo.h>
+#include <Security/SecCmsSignedData.h>
+#include <Security/SecCmsSignerInfo.h>
+#include <Security/SecSMIME.h>
+#include <Security/tsaSupport.h>
+
+#include <Security/oidsalg.h>
+#include <Security/SecPolicy.h>
+#include <Security/SecKeychain.h>
+#include <Security/SecKeychainSearch.h>
+#include <Security/SecIdentity.h>
+#include <Security/SecIdentitySearch.h>
+#include <CoreFoundation/CFString.h>
+#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
+#include <utilities/SecCFRelease.h>
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "cmsutil.h"
+
+// SecPolicyCopy
+#include <Security/SecPolicyPriv.h>
+// SecKeychainSearchCreateForCertificateByEmail, SecCertificateFindBySubjectKeyID, SecCertificateFindByEmail
+#include <Security/SecCertificatePriv.h>
+// SecIdentitySearchCreateWithPolicy
+#include <Security/SecIdentitySearchPriv.h>
+
+#define SEC_CHECK0(CALL, ERROR) do { if (!(CALL)) { sec_error(ERROR); goto loser; } } while(0)
+#define SEC_CHECK(CALL, ERROR) do { rv = (CALL); if (rv) { sec_perror(ERROR, rv); goto loser; } } while(0)
+#define SEC_CHECK2(CALL, ERROR, ARG) do { rv = CALL; if (rv) \
+ { sec_error(ERROR ": %s", ARG, sec_errstr(rv)); goto loser; } } while(0)
+
+// @@@ Remove this
+#if 1
+
+static CSSM_KEYUSE CERT_KeyUsageForCertUsage(SECCertUsage certUsage)
+{
+ switch (certUsage)
+ {
+ case certUsageSSLClient: return CSSM_KEYUSE_SIGN;
+ case certUsageSSLServer: return CSSM_KEYUSE_SIGN;
+ case certUsageSSLServerWithStepUp: return CSSM_KEYUSE_SIGN;
+ case certUsageSSLCA: return CSSM_KEYUSE_SIGN;
+ case certUsageEmailSigner: return CSSM_KEYUSE_SIGN;
+ case certUsageEmailRecipient: return CSSM_KEYUSE_UNWRAP;
+ case certUsageObjectSigner: return CSSM_KEYUSE_SIGN;
+ case certUsageUserCertImport: return CSSM_KEYUSE_SIGN;
+ case certUsageVerifyCA: return CSSM_KEYUSE_SIGN;
+ case certUsageProtectedObjectSigner: return CSSM_KEYUSE_SIGN;
+ case certUsageStatusResponder: return CSSM_KEYUSE_SIGN;
+ case certUsageAnyCA: return CSSM_KEYUSE_SIGN;
+ default:
+ sec_error("CERT_PolicyForCertUsage %ld: unknown certUsage", certUsage);
+ return CSSM_KEYUSE_SIGN;
+ }
+}
+
+static SecPolicyRef CERT_PolicyForCertUsage(SECCertUsage certUsage, const char *emailAddress)
+{
+ SecPolicyRef policy = NULL;
+ const CSSM_OID *policyOID;
+ OSStatus rv;
+
+ switch (certUsage)
+ {
+ case certUsageSSLClient: policyOID = &CSSMOID_APPLE_TP_SSL; break;
+ case certUsageSSLServer: policyOID = &CSSMOID_APPLE_TP_SSL; break;
+ case certUsageSSLServerWithStepUp: policyOID = &CSSMOID_APPLE_TP_SSL; break;
+ case certUsageSSLCA: policyOID = &CSSMOID_APPLE_TP_SSL; break;
+ case certUsageEmailSigner: policyOID = &CSSMOID_APPLE_TP_SMIME; break;
+ case certUsageEmailRecipient: policyOID = &CSSMOID_APPLE_TP_SMIME; break;
+ case certUsageObjectSigner: policyOID = &CSSMOID_APPLE_TP_CODE_SIGN; break;
+ case certUsageUserCertImport: policyOID = &CSSMOID_APPLE_X509_BASIC; break;
+ case certUsageVerifyCA: policyOID = &CSSMOID_APPLE_X509_BASIC; break;
+ case certUsageProtectedObjectSigner: policyOID = &CSSMOID_APPLE_ISIGN; break;
+ case certUsageStatusResponder: policyOID = &CSSMOID_APPLE_TP_REVOCATION_OCSP; break;
+ case certUsageAnyCA: policyOID = &CSSMOID_APPLE_X509_BASIC; break;
+ default:
+ sec_error("CERT_PolicyForCertUsage %ld: unknown certUsage", certUsage);
+ goto loser;
+ }
+
+ SEC_CHECK(SecPolicyCopy(CSSM_CERT_X_509v3, policyOID, &policy), "SecPolicyCopy");
+ if (certUsage == certUsageEmailSigner || certUsage == certUsageEmailRecipient)
+ {
+ CSSM_APPLE_TP_SMIME_OPTIONS options =
+ {
+ CSSM_APPLE_TP_SMIME_OPTS_VERSION,
+ certUsage == certUsageEmailSigner
+ ? CE_KU_DigitalSignature | CE_KU_NonRepudiation
+ : CE_KU_KeyEncipherment,
+ emailAddress ? sizeof(emailAddress) : 0,
+ emailAddress
+ };
+ CSSM_DATA value = { sizeof(options), (uint8 *)&options };
+ if (SecPolicySetValue(policy, &value)) {
+ goto loser;
+ }
+ }
+
+ // @@@ Need to set values for SSL and other policies.
+ return policy;
+
+loser:
+ if (policy) CFRelease(policy);
+ return NULL;
+}
+
+static SecCertificateRef CERT_FindUserCertByUsage(CFTypeRef keychainOrArray, const char *emailAddress,
+ SECCertUsage certUsage, Boolean validOnly)
+{
+ SecKeychainSearchRef search = NULL;
+ SecCertificateRef cert = NULL;
+ SecPolicyRef policy;
+ OSStatus rv;
+
+ policy = CERT_PolicyForCertUsage(certUsage, emailAddress);
+ if (!policy)
+ goto loser;
+
+ SEC_CHECK2(SecKeychainSearchCreateForCertificateByEmail(keychainOrArray, emailAddress, &search),
+ "create search for certificate with email: \"%s\"", emailAddress);
+ for (;;)
+ {
+ SecKeychainItemRef item;
+ rv = SecKeychainSearchCopyNext(search, &item);
+ if (rv)
+ {
+ if (rv == errSecItemNotFound)
+ break;
+
+ sec_perror("error finding next matching certificate", rv);
+ goto loser;
+ }
+
+ cert = (SecCertificateRef)item;
+ // @@@ Check cert against policy.
+ }
+
+loser:
+ if (policy) CFRelease(policy);
+ if (search) CFRelease(search);
+
+ return cert;
+}
+
+static SecIdentityRef CERT_FindIdentityByUsage(CFTypeRef keychainOrArray,
+ const char *emailAddress,
+ SECCertUsage certUsage,
+ Boolean validOnly)
+{
+ SecIdentitySearchRef search = NULL;
+ SecIdentityRef identity = NULL;
+ CFStringRef idString = CFStringCreateWithCString(NULL, emailAddress, kCFStringEncodingUTF8);
+ SecPolicyRef policy;
+ OSStatus rv;
+
+ policy = CERT_PolicyForCertUsage(certUsage, emailAddress);
+ if (!policy)
+ goto loser;
+
+
+ SEC_CHECK2(SecIdentitySearchCreateWithPolicy(policy, idString,
+ CERT_KeyUsageForCertUsage(certUsage), keychainOrArray, validOnly, &search),
+ "create search for identity with email: \"%s\"", emailAddress);
+ for (;;)
+ {
+ rv = SecIdentitySearchCopyNext(search, &identity);
+ if (rv)
+ {
+ if (rv == errSecItemNotFound)
+ break;
+
+ sec_perror("error finding next matching identity", rv);
+ goto loser;
+ }
+ }
+
+loser:
+ if (policy) CFRelease(policy);
+ if (search) CFRelease(search);
+ if (idString) CFRelease(idString);
+
+ return identity;
+
+
+#if 0
+ SecIdentityRef identity = NULL;
+ SecCertificateRef cert;
+ OSStatus rv;
+
+ cert = CERT_FindUserCertByUsage(keychainOrArray, emailAddress, certUsage, validOnly);
+ if (!cert)
+ goto loser;
+
+ SEC_CHECK2(SecIdentityCreateWithCertificate(keychainOrArray, cert, &identity),
+ "failed to find private key for certificate with email: \"%s\"", emailAddress);
+loser:
+ if (cert) CFRelease(cert);
+
+ return identity;
+#endif
+}
+
+static SecCertificateRef CERT_FindCertByNicknameOrEmailAddr(CFTypeRef keychainOrArray, const char *emailAddress)
+{
+ SecCertificateRef certificate = NULL;
+ OSStatus rv;
+
+ SEC_CHECK2(SecCertificateFindByEmail(keychainOrArray, emailAddress, &certificate),
+ "failed to find certificate with email: \"%s\"", emailAddress);
+
+loser:
+ return certificate;
+}
+
+static SecIdentityRef CERT_FindIdentityBySubjectKeyID(CFTypeRef keychainOrArray,
+ const char *subjectKeyIDString)
+{
+ // ss will be something like "B2ACD31AC8D0DA62E7679432ADDD3398EF66948B"
+
+ SecCertificateRef certificate = NULL;
+ SecIdentityRef identityRef = NULL;
+ OSStatus rv;
+
+ CSSM_SIZE len = strlen(subjectKeyIDString)/2;
+ CSSM_DATA subjectKeyID = {0,};
+ subjectKeyID.Length = len;
+ subjectKeyID.Data = (uint8 *)malloc(subjectKeyID.Length);
+ fromHex(subjectKeyIDString, &subjectKeyID);
+
+ SEC_CHECK2(SecCertificateFindBySubjectKeyID(keychainOrArray, &subjectKeyID, &certificate),
+ "failed to find identity with subject key ID: \"%s\"", subjectKeyIDString);
+
+ SEC_CHECK2(SecIdentityCreateWithCertificate(keychainOrArray, certificate, &identityRef),
+ "failed to find certificate with subject key ID: \"%s\"", subjectKeyIDString);
+loser:
+ return identityRef;
+}
+
+
+static OSStatus CERT_CheckCertUsage (SecCertificateRef cert,unsigned char usage)
+{
+ return 0;
+}
+
+#endif
+
+// @@@ Eleminate usage of this header.
+//#include "cert.h"
+//#include <security_asn1/secerr.h>
+//#include "plgetopt.h"
+//#include "secitem.h"
+
+#ifdef HAVE_DODUMPSTATES
+extern int doDumpStates;
+#endif /* HAVE_DODUMPSTATES */
+
+OSStatus SECU_FileToItem(CSSM_DATA *dst, FILE *src);
+
+
+extern void SEC_Init(void); /* XXX */
+static int cms_verbose = 0;
+static int cms_update_single_byte = 0;
+
+/* XXX stolen from cmsarray.c
+ * nss_CMSArray_Count - count number of elements in array
+ */
+static int nss_CMSArray_Count(void **array)
+{
+ int n = 0;
+ if (array == NULL)
+ return 0;
+ while (*array++ != NULL)
+ n++;
+ return n;
+}
+
+typedef OSStatus(update_func)(void *cx, const char *data, size_t len);
+
+static OSStatus do_update(update_func *update,
+ void *cx, const unsigned char *data, size_t len)
+{
+ OSStatus rv = noErr;
+ if (cms_update_single_byte)
+ {
+ for (;len; --len, ++data)
+ {
+ rv = update(cx, (const char *)data, 1);
+ if (rv)
+ break;
+ }
+ }
+ else
+ rv = update(cx, (const char *)data, len);
+
+ return rv;
+}
+
+
+static OSStatus DigestFile(SecArenaPoolRef poolp, CSSM_DATA ***digests, CSSM_DATA *input, SECAlgorithmID **algids)
+{
+ SecCmsDigestContextRef digcx = SecCmsDigestContextStartMultiple(algids);
+ if (digcx == NULL)
+ return paramErr;
+
+ do_update((update_func *)SecCmsDigestContextUpdate, digcx, input->Data, input->Length);
+
+ return SecCmsDigestContextFinishMultiple(digcx, poolp, digests);
+}
+
+static char *
+ownpw(void *info, Boolean retry, void *arg)
+{
+ char * passwd = NULL;
+
+ if ( (!retry) && arg ) {
+ passwd = strdup((char *)arg);
+ }
+
+ return passwd;
+}
+
+struct optionsStr {
+ PK11PasswordFunc password;
+ SECCertUsage certUsage;
+ SecKeychainRef certDBHandle;
+};
+
+struct decodeOptionsStr {
+ struct optionsStr *options;
+ FILE *contentFile;
+ int headerLevel;
+ Boolean suppressContent;
+ SecCmsGetDecryptKeyCallback dkcb;
+ SecSymmetricKeyRef bulkkey;
+};
+
+struct signOptionsStr {
+ struct optionsStr *options;
+ char *nickname;
+ char *encryptionKeyPreferenceNick;
+ char *subjectKeyID;
+ Boolean signingTime;
+ Boolean smimeProfile;
+ Boolean detached;
+ SECOidTag hashAlgTag;
+ Boolean wantTimestamping;
+ char *timestampingURL;
+};
+
+struct envelopeOptionsStr {
+ struct optionsStr *options;
+ char **recipients;
+};
+
+struct certsonlyOptionsStr {
+ struct optionsStr *options;
+ char **recipients;
+};
+
+struct encryptOptionsStr {
+ struct optionsStr *options;
+ char **recipients;
+ SecCmsMessageRef envmsg;
+ CSSM_DATA *input;
+ FILE *outfile;
+ FILE *envFile;
+ SecSymmetricKeyRef bulkkey;
+ SECOidTag bulkalgtag;
+ int keysize;
+};
+
+static SecCmsMessageRef decode(FILE *out, CSSM_DATA *output, CSSM_DATA *input,
+ const struct decodeOptionsStr *decodeOptions)
+{
+ SecCmsDecoderRef dcx;
+ SecCmsMessageRef cmsg=NULL;
+ SecCmsContentInfoRef cinfo;
+ SecCmsSignedDataRef sigd = NULL;
+ SecCmsEnvelopedDataRef envd;
+ SecCmsEncryptedDataRef encd;
+ SECAlgorithmID **digestalgs;
+ int nlevels, i, nsigners, j;
+ CFStringRef signercn;
+ SecCmsSignerInfoRef si;
+ SECOidTag typetag;
+ CSSM_DATA **digests;
+ SecArenaPoolRef poolp = NULL;
+ PK11PasswordFunc pwcb;
+ void *pwcb_arg;
+ CSSM_DATA *item, sitem = { 0, };
+ CFTypeRef policy = NULL;
+ OSStatus rv;
+
+ pwcb = (PK11PasswordFunc)((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);
+
+ SEC_CHECK(SecCmsDecoderCreate(NULL,
+ NULL, NULL, /* content callback */
+ pwcb, pwcb_arg, /* password callback */
+ decodeOptions->dkcb, /* decrypt key callback */
+ decodeOptions->bulkkey,
+ &dcx),
+ "failed to create to decoder");
+ SEC_CHECK(do_update((update_func *)SecCmsDecoderUpdate, dcx, input->Data, input->Length),
+ "failed to add data to decoder");
+ SEC_CHECK(SecCmsDecoderFinish(dcx, &cmsg),
+ "failed to decode message");
+
+ if (decodeOptions->headerLevel >= 0)
+ {
+ /*fprintf(out, "SMIME: ", decodeOptions->headerLevel, i);*/
+ fprintf(out, "SMIME: ");
+ }
+
+ nlevels = SecCmsMessageContentLevelCount(cmsg);
+ for (i = 0; i < nlevels; i++)
+ {
+ cinfo = SecCmsMessageContentLevel(cmsg, i);
+ typetag = SecCmsContentInfoGetContentTypeTag(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; ");
+
+ SEC_CHECK0(sigd = (SecCmsSignedDataRef )SecCmsContentInfoGetContent(cinfo),
+ "problem finding signedData component");
+ /* if we have a content file, but no digests for this signedData */
+ if (decodeOptions->contentFile != NULL && !SecCmsSignedDataHasDigests(sigd))
+ {
+ SEC_CHECK(SecArenaPoolCreate(1024, &poolp), "failed to create arenapool");
+ digestalgs = SecCmsSignedDataGetDigestAlgs(sigd);
+ SEC_CHECK(DigestFile(poolp, &digests, &sitem, digestalgs),
+ "problem computing message digest");
+ SEC_CHECK(SecCmsSignedDataSetDigests(sigd, digestalgs, digests),
+ "problem setting message digests");
+ SecArenaPoolFree(poolp, false);
+ }
+
+ policy = CERT_PolicyForCertUsage(decodeOptions->options->certUsage, NULL);
+ // import the certificates
+ SEC_CHECK(SecCmsSignedDataImportCerts(sigd,decodeOptions->options->certDBHandle,
+ decodeOptions->options->certUsage, true /* false */),
+ "cert import failed");
+
+ /* find out about signers */
+ nsigners = SecCmsSignedDataSignerInfoCount(sigd);
+ if (decodeOptions->headerLevel >= 0)
+ fprintf(out, "nsigners=%d; ", nsigners);
+ if (nsigners == 0)
+ {
+ /* must be a cert transport message */
+ OSStatus rv;
+ /* XXX workaround for bug #54014 */
+ SecCmsSignedDataImportCerts(sigd,decodeOptions->options->certDBHandle,
+ decodeOptions->options->certUsage,true);
+ SEC_CHECK(SecCmsSignedDataVerifyCertsOnly(sigd,decodeOptions->options->certDBHandle, policy),
+ "verify certs-only failed");
+ return cmsg;
+ }
+
+ SEC_CHECK0(SecCmsSignedDataHasDigests(sigd), "message has no digests");
+ for (j = 0; j < nsigners; j++)
+ {
+ si = SecCmsSignedDataGetSignerInfo(sigd, j);
+ signercn = SecCmsSignerInfoGetSignerCommonName(si);
+ if (decodeOptions->headerLevel >= 0)
+ {
+ const char *px = signercn ? CFStringGetCStringPtr(signercn,kCFStringEncodingMacRoman) : "<NULL>";
+ fprintf(out, "\n\t\tsigner%d.id=\"%s\"; ", j, px);
+ }
+ SecCmsSignedDataVerifySignerInfo(sigd, j, decodeOptions->options->certDBHandle,
+ policy, NULL);
+ if (decodeOptions->headerLevel >= 0)
+ fprintf(out, "signer%d.status=%s; ", j,
+ SecCmsUtilVerificationStatusToString(SecCmsSignerInfoGetVerificationStatus(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 = (SecCmsEnvelopedDataRef )SecCmsContentInfoGetContent(cinfo);
+ break;
+ case SEC_OID_PKCS7_ENCRYPTED_DATA:
+ if (decodeOptions->headerLevel >= 0)
+ fprintf(out, "type=encryptedData; ");
+ encd = (SecCmsEncryptedDataRef )SecCmsContentInfoGetContent(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)
+ {
+ item = decodeOptions->contentFile ? &sitem :
+ SecCmsMessageGetContent(cmsg);
+ /* Copy the data. */
+ output->Length = item->Length;
+ output->Data = malloc(output->Length);
+ memcpy(output->Data, item->Data, output->Length);
+ }
+
+ if (policy) CFRelease(policy);
+
+ return cmsg;
+loser:
+ if (policy) CFRelease(policy);
+ if (cmsg) SecCmsMessageDestroy(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 SecCmsMessageRef signed_data(struct signOptionsStr *signOptions)
+{
+ SecCmsMessageRef cmsg = NULL;
+ SecCmsContentInfoRef cinfo;
+ SecCmsSignedDataRef sigd;
+ SecCmsSignerInfoRef signerinfo;
+ SecIdentityRef identity = NULL;
+ SecCertificateRef cert = NULL, ekpcert = NULL;
+ OSStatus rv;
+
+ 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->certDBHandle)
+ fprintf(stderr, "certdb [%p]\n", signOptions->options->certDBHandle);
+ else
+ fprintf(stderr, "certdb [NULL]\n");
+ if (signOptions->nickname)
+ fprintf(stderr, "nickname [%s]\n", signOptions->nickname);
+ else
+ fprintf(stderr, "nickname [NULL]\n");
+ if (signOptions->subjectKeyID)
+ fprintf(stderr, "subject Key ID [%s]\n", signOptions->subjectKeyID);
+ }
+
+ if (signOptions->subjectKeyID)
+ {
+ if ((identity = CERT_FindIdentityBySubjectKeyID(signOptions->options->certDBHandle,
+ signOptions->subjectKeyID)) == NULL)
+ {
+ sec_error("could not find signing identity for subject key ID: \"%s\"", signOptions->subjectKeyID);
+ return NULL;
+ }
+
+ if (cms_verbose)
+ fprintf(stderr, "Found identity for subject key ID %s\n", signOptions->subjectKeyID);
+ }
+ else if (signOptions->nickname)
+ {
+ if ((identity = CERT_FindIdentityByUsage(signOptions->options->certDBHandle,
+ signOptions->nickname,
+ signOptions->options->certUsage,
+ false)) == NULL)
+ {
+ // look for identity by common name rather than email address
+ if ((identity = CopyMatchingIdentity(signOptions->options->certDBHandle,
+ signOptions->nickname,
+ NULL,
+ signOptions->options->certUsage)) == NULL)
+ {
+ sec_error("could not find signing identity for name: \"%s\"", signOptions->nickname);
+ return NULL;
+ }
+ }
+
+ if (cms_verbose)
+ fprintf(stderr, "Found identity for %s\n", signOptions->nickname);
+ }
+ else
+ {
+ // no identity was specified
+ sec_error("no signing identity was specified");
+ return NULL;
+ }
+
+ // Get the cert from the identity
+ SEC_CHECK(SecIdentityCopyCertificate(identity, &cert),
+ "SecIdentityCopyCertificate");
+ // create the message object on its own pool
+ SEC_CHECK0(cmsg = SecCmsMessageCreate(NULL), "cannot create CMS message");
+ // build chain of objects: message->signedData->data
+ SEC_CHECK0(sigd = SecCmsSignedDataCreate(cmsg),
+ "cannot create CMS signedData object");
+ SEC_CHECK0(cinfo = SecCmsMessageGetContentInfo(cmsg),
+ "message has no content info");
+ SEC_CHECK(SecCmsContentInfoSetContentSignedData(cmsg, cinfo, sigd),
+ "cannot attach CMS signedData object");
+ SEC_CHECK0(cinfo = SecCmsSignedDataGetContentInfo(sigd),
+ "signed data has no content info");
+ /* we're always passing data in and detaching optionally */
+ SEC_CHECK(SecCmsContentInfoSetContentData(cmsg, cinfo, NULL, signOptions->detached),
+ "cannot attach CMS data object");
+ // create & attach signer information
+ SEC_CHECK0(signerinfo = SecCmsSignerInfoCreate(cmsg, identity, signOptions->hashAlgTag),
+ "cannot create CMS signerInfo object");
+ if (cms_verbose)
+ fprintf(stderr,"Created CMS message, added signed data w/ signerinfo\n");
+
+ // we want the cert chain included for this one
+ SEC_CHECK(SecCmsSignerInfoIncludeCerts(signerinfo, SecCmsCMCertChain, signOptions->options->certUsage),
+ "cannot add cert chain");
+
+ if (cms_verbose)
+ fprintf(stderr, "imported certificate\n");
+
+ if (signOptions->signingTime)
+ SEC_CHECK(SecCmsSignerInfoAddSigningTime(signerinfo, CFAbsoluteTimeGetCurrent()),
+ "cannot add signingTime attribute");
+
+ if (signOptions->smimeProfile)
+ SEC_CHECK(SecCmsSignerInfoAddSMIMECaps(signerinfo),
+ "cannot add SMIMECaps attribute");
+
+ if (signOptions->wantTimestamping)
+ {
+ CFErrorRef error = NULL;
+ SecCmsMessageSetTSAContext(cmsg, SecCmsTSAGetDefaultContext(&error));
+ }
+
+ if (!signOptions->encryptionKeyPreferenceNick)
+ {
+ /* check signing cert for fitness as encryption cert */
+ OSStatus FitForEncrypt = CERT_CheckCertUsage(cert, certUsageEmailRecipient);
+
+ if (noErr == FitForEncrypt)
+ {
+ /* if yes, add signing cert as EncryptionKeyPreference */
+ SEC_CHECK(SecCmsSignerInfoAddSMIMEEncKeyPrefs(signerinfo, cert, signOptions->options->certDBHandle),
+ "cannot add default SMIMEEncKeyPrefs attribute");
+ SEC_CHECK(SecCmsSignerInfoAddMSSMIMEEncKeyPrefs(signerinfo, cert, signOptions->options->certDBHandle),
+ "cannot add default MS SMIMEEncKeyPrefs attribute");
+ }
+ 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->certDBHandle,
+ signOptions->nickname,
+ certUsageEmailRecipient,
+ false)) == NULL)
+ {
+ sec_error("can find encryption cert for \"%s\"", signOptions->nickname);
+ goto loser;
+ }
+
+ SEC_CHECK(SecCmsSignerInfoAddSMIMEEncKeyPrefs(signerinfo, ekpcert,signOptions->options->certDBHandle),
+ "cannot add SMIMEEncKeyPrefs attribute");
+ SEC_CHECK(SecCmsSignerInfoAddMSSMIMEEncKeyPrefs(signerinfo, ekpcert,signOptions->options->certDBHandle),
+ "cannot add MS SMIMEEncKeyPrefs attribute");
+ SEC_CHECK(SecCmsSignedDataAddCertificate(sigd, ekpcert),
+ "cannot add encryption certificate");
+ }
+ }
+ else if (strcmp(signOptions->encryptionKeyPreferenceNick, "NONE") == 0)
+ {
+ /* No action */
+ }
+ else
+ {
+ /* specific email address for encryption preferred encryption cert specified.
+ get the cert, add it to the message */
+ if ((ekpcert = CERT_FindUserCertByUsage(
+ signOptions->options->certDBHandle,
+ signOptions->encryptionKeyPreferenceNick,
+ certUsageEmailRecipient, false)) == NULL)
+ {
+ sec_error("can find encryption cert for \"%s\"", signOptions->encryptionKeyPreferenceNick);
+ goto loser;
+ }
+
+ SEC_CHECK(SecCmsSignerInfoAddSMIMEEncKeyPrefs(signerinfo, ekpcert,signOptions->options->certDBHandle),
+ "cannot add SMIMEEncKeyPrefs attribute");
+ SEC_CHECK(SecCmsSignerInfoAddMSSMIMEEncKeyPrefs(signerinfo, ekpcert,signOptions->options->certDBHandle),
+ "cannot add MS SMIMEEncKeyPrefs attribute");
+ SEC_CHECK(SecCmsSignedDataAddCertificate(sigd, ekpcert),
+ "cannot add encryption certificate");
+ }
+
+ SEC_CHECK(SecCmsSignedDataAddSignerInfo(sigd, signerinfo),
+ "cannot add CMS signerInfo object");
+
+ if (cms_verbose)
+ fprintf(stderr, "created signed-data message\n");
+
+ if (ekpcert) CFRelease(ekpcert);
+ if (cert) CFRelease(cert);
+ if (identity) CFRelease(identity);
+ return cmsg;
+
+loser:
+ if (ekpcert) CFRelease(ekpcert);
+ if (cert) CFRelease(cert);
+ if (identity) CFRelease(identity);
+ SecCmsMessageDestroy(cmsg);
+ return NULL;
+}
+
+static SecCmsMessageRef enveloped_data(struct envelopeOptionsStr *envelopeOptions)
+{
+ SecCmsMessageRef cmsg = NULL;
+ SecCmsContentInfoRef cinfo;
+ SecCmsEnvelopedDataRef envd;
+ SecCmsRecipientInfoRef recipientinfo;
+ SecCertificateRef *recipientcerts = NULL;
+ SecKeychainRef dbhandle;
+ SECOidTag bulkalgtag;
+ OSStatus rv;
+ int keysize, i = 0;
+ int cnt;
+
+ dbhandle = envelopeOptions->options->certDBHandle;
+ /* count the recipients */
+ SEC_CHECK0(cnt = nss_CMSArray_Count((void **)envelopeOptions->recipients),
+ "please name at least one recipient");
+
+ // @@@ find the recipient's certs by email address or nickname
+ if ((recipientcerts = (SecCertificateRef *)calloc((cnt+1), sizeof(SecCertificateRef))) == NULL)
+ {
+ sec_error("failed to alloc certs array: %s", strerror(errno));
+ goto loser;
+ }
+
+ for (i = 0; envelopeOptions->recipients[i] != NULL; ++i)
+ {
+ if ((recipientcerts[i] =
+ CERT_FindCertByNicknameOrEmailAddr(dbhandle, envelopeOptions->recipients[i])) == NULL)
+ {
+ i = 0;
+ goto loser;
+ }
+ }
+
+ recipientcerts[i] = NULL;
+ i = 0;
+
+ // find a nice bulk algorithm
+ SEC_CHECK(SecSMIMEFindBulkAlgForRecipients(recipientcerts, &bulkalgtag, &keysize),
+ "cannot find common bulk algorithm");
+ // create the message object on its own pool
+ SEC_CHECK0(cmsg = SecCmsMessageCreate(NULL), "cannot create CMS message");
+ // build chain of objects: message->envelopedData->data
+ SEC_CHECK0(envd = SecCmsEnvelopedDataCreate(cmsg, bulkalgtag, keysize),
+ "cannot create CMS envelopedData object");
+ SEC_CHECK0(cinfo = SecCmsMessageGetContentInfo(cmsg),
+ "message has no content info");
+ SEC_CHECK(SecCmsContentInfoSetContentEnvelopedData(cmsg, cinfo, envd),
+ "cannot attach CMS envelopedData object");
+ SEC_CHECK0(cinfo = SecCmsEnvelopedDataGetContentInfo(envd),
+ "enveloped data has no content info");
+ // We're always passing data in, so the content is NULL
+ SEC_CHECK(SecCmsContentInfoSetContentData(cmsg, cinfo, NULL, false),
+ "cannot attach CMS data object");
+
+ // create & attach recipient information
+ for (i = 0; recipientcerts[i] != NULL; i++)
+ {
+ SEC_CHECK0(recipientinfo = SecCmsRecipientInfoCreate(cmsg, recipientcerts[i]),
+ "cannot create CMS recipientInfo object");
+ SEC_CHECK(SecCmsEnvelopedDataAddRecipient(envd, recipientinfo),
+ "cannot add CMS recipientInfo object");
+ CFRelease(recipientcerts[i]);
+ }
+
+ if (recipientcerts)
+ free(recipientcerts);
+
+ return cmsg;
+loser:
+ if (recipientcerts)
+ {
+ for (; recipientcerts[i] != NULL; i++)
+ CFRelease(recipientcerts[i]);
+ }
+
+ if (cmsg)
+ SecCmsMessageDestroy(cmsg);
+
+ if (recipientcerts)
+ free(recipientcerts);
+
+ return NULL;
+}
+
+static SecSymmetricKeyRef dkcb(void *arg, SECAlgorithmID *algid)
+{
+ return (SecSymmetricKeyRef)arg;
+}
+
+static OSStatus get_enc_params(struct encryptOptionsStr *encryptOptions)
+{
+ struct envelopeOptionsStr envelopeOptions;
+ OSStatus rv = paramErr;
+ SecCmsMessageRef env_cmsg = NULL;
+ SecCmsContentInfoRef 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
+ {
+ CSSM_DATA dummyOut = { 0, };
+ CSSM_DATA dummyIn = { 0, };
+ char str[] = "Hello!";
+ SecArenaPoolRef tmparena = NULL;
+
+ SEC_CHECK(SecArenaPoolCreate(1024, &tmparena), "failed to create arenapool");
+
+ dummyIn.Data = (unsigned char *)str;
+ dummyIn.Length = strlen(str);
+ envelopeOptions.options = encryptOptions->options;
+ envelopeOptions.recipients = encryptOptions->recipients;
+ env_cmsg = enveloped_data(&envelopeOptions);
+ rv = SecCmsMessageEncode(env_cmsg, &dummyIn, tmparena, &dummyOut);
+ if (rv) {
+ goto loser;
+ }
+ fwrite(dummyOut.Data, 1, dummyOut.Length,encryptOptions->envFile);
+
+ SecArenaPoolFree(tmparena, false);
+ }
+
+ // get the content info for the enveloped data
+ nlevels = SecCmsMessageContentLevelCount(env_cmsg);
+ for (i = 0; i < nlevels; i++)
+ {
+ SECOidTag typetag;
+ cinfo = SecCmsMessageContentLevel(env_cmsg, i);
+ typetag = SecCmsContentInfoGetContentTypeTag(cinfo);
+ if (typetag == SEC_OID_PKCS7_DATA)
+ {
+ // get the symmetric key
+ encryptOptions->bulkalgtag = SecCmsContentInfoGetContentEncAlgTag(cinfo);
+ encryptOptions->keysize = SecCmsContentInfoGetBulkKeySize(cinfo);
+ encryptOptions->bulkkey = SecCmsContentInfoGetBulkKey(cinfo);
+ rv = noErr;
+ break;
+ }
+ }
+ if (i == nlevels)
+ sec_error("could not retrieve enveloped data: messsage has: %ld levels", nlevels);
+
+loser:
+ if (env_cmsg)
+ SecCmsMessageDestroy(env_cmsg);
+
+ return rv;
+}
+
+static SecCmsMessageRef encrypted_data(struct encryptOptionsStr *encryptOptions)
+{
+ OSStatus rv = paramErr;
+ SecCmsMessageRef cmsg = NULL;
+ SecCmsContentInfoRef cinfo;
+ SecCmsEncryptedDataRef encd;
+ SecCmsEncoderRef ecx = NULL;
+ SecArenaPoolRef tmppoolp = NULL;
+ CSSM_DATA derOut = { 0, };
+
+ /* arena for output */
+ SEC_CHECK(SecArenaPoolCreate(1024, &tmppoolp), "failed to create arenapool");
+ // create the message object on its own pool
+ SEC_CHECK0(cmsg = SecCmsMessageCreate(NULL), "cannot create CMS message");
+ // build chain of objects: message->encryptedData->data
+ SEC_CHECK0(encd = SecCmsEncryptedDataCreate(cmsg, encryptOptions->bulkalgtag,
+ encryptOptions->keysize),
+ "cannot create CMS encryptedData object");
+ SEC_CHECK0(cinfo = SecCmsMessageGetContentInfo(cmsg),
+ "message has no content info");
+ SEC_CHECK(SecCmsContentInfoSetContentEncryptedData(cmsg, cinfo, encd),
+ "cannot attach CMS encryptedData object");
+ SEC_CHECK0(cinfo = SecCmsEncryptedDataGetContentInfo(encd),
+ "encrypted data has no content info");
+ /* we're always passing data in, so the content is NULL */
+ SEC_CHECK(SecCmsContentInfoSetContentData(cmsg, cinfo, NULL, false),
+ "cannot attach CMS data object");
+ SEC_CHECK(SecCmsEncoderCreate(cmsg, NULL, NULL, &derOut, tmppoolp, NULL, NULL,
+ dkcb, encryptOptions->bulkkey, NULL, NULL, &ecx),
+ "cannot create encoder context");
+ SEC_CHECK(do_update((update_func *)SecCmsEncoderUpdate, ecx, encryptOptions->input->Data,
+ encryptOptions->input->Length),
+ "failed to add data to encoder");
+ SEC_CHECK(SecCmsEncoderFinish(ecx), "failed to encrypt data");
+ fwrite(derOut.Data, derOut.Length, 1, encryptOptions->outfile);
+ /* @@@ Check and report write errors. */
+ /*
+ if (bulkkey)
+ CFRelease(bulkkey);
+ */
+
+ if (tmppoolp)
+ SecArenaPoolFree(tmppoolp, false);
+ return cmsg;
+loser:
+ /*
+ if (bulkkey)
+ CFRelease(bulkkey);
+ */
+ if (tmppoolp)
+ SecArenaPoolFree(tmppoolp, false);
+ if (cmsg)
+ SecCmsMessageDestroy(cmsg);
+
+ return NULL;
+}
+
+static SecCmsMessageRef signed_data_certsonly(struct certsonlyOptionsStr *certsonlyOptions)
+{
+ SecCmsMessageRef cmsg = NULL;
+ SecCmsContentInfoRef cinfo;
+ SecCmsSignedDataRef sigd;
+ SecCertificateRef *certs = NULL;
+ SecKeychainRef dbhandle;
+ OSStatus rv;
+
+ int i = 0, cnt;
+ dbhandle = certsonlyOptions->options->certDBHandle;
+ SEC_CHECK0(cnt = nss_CMSArray_Count((void**)certsonlyOptions->recipients),
+ "please indicate the nickname of a certificate to sign with");
+ if ((certs = (SecCertificateRef *)calloc((cnt+1), sizeof(SecCertificateRef))) == NULL)
+ {
+ sec_error("failed to alloc certs array: %s", strerror(errno));
+ goto loser;
+ }
+ for (i=0; certsonlyOptions->recipients && certsonlyOptions->recipients[i] != NULL; i++)
+ {
+ if ((certs[i] = CERT_FindCertByNicknameOrEmailAddr(dbhandle,certsonlyOptions->recipients[i])) == NULL)
+ {
+ i=0;
+ goto loser;
+ }
+ }
+ certs[i] = NULL;
+ i = 0;
+ // create the message object on its own pool
+ SEC_CHECK0(cmsg = SecCmsMessageCreate(NULL), "cannot create CMS message");
+ // build chain of objects: message->signedData->data
+ SEC_CHECK0(sigd = SecCmsSignedDataCreateCertsOnly(cmsg, certs[0], true),
+ "cannot create certs only CMS signedData object");
+ CFReleaseNull(certs[0]);
+ for (i = 1; i < cnt; ++i)
+ {
+ SEC_CHECK2(SecCmsSignedDataAddCertChain(sigd, certs[i]),
+ "cannot add cert chain for \"%s\"", certsonlyOptions->recipients[i]);
+ CFReleaseNull(certs[i]);
+ }
+
+ SEC_CHECK0(cinfo = SecCmsMessageGetContentInfo(cmsg),
+ "message has no content info");
+ SEC_CHECK(SecCmsContentInfoSetContentSignedData(cmsg, cinfo, sigd),
+ "cannot attach CMS signedData object");
+ SEC_CHECK0(cinfo = SecCmsSignedDataGetContentInfo(sigd),
+ "signed data has no content info");
+ SEC_CHECK(SecCmsContentInfoSetContentData(cmsg, cinfo, NULL, false),
+ "cannot attach CMS data object");
+
+ if (certs)
+ free(certs);
+
+ return cmsg;
+loser:
+ if (certs)
+ {
+ for (; i < cnt; ++i)
+ CFReleaseNull(certs[i]);
+
+ free(certs);
+ }
+ if (cmsg) SecCmsMessageDestroy(cmsg);
+
+ return NULL;
+}
+
+typedef enum { UNKNOWN, DECODE, SIGN, ENCRYPT, ENVELOPE, CERTSONLY } Mode;
+
+int cms_util(int argc, char * const *argv)
+{
+ FILE *outFile;
+ SecCmsMessageRef cmsg = NULL;
+ FILE *inFile;
+ int ch;
+ 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 result = 1;
+ static char *ptrarray[128] = { 0 };
+ int nrecipients = 0;
+ char *str, *tok;
+ char *envFileName;
+ const char *keychainName = NULL;
+ CSSM_DATA input = { 0,};
+ CSSM_DATA output = { 0,};
+ CSSM_DATA dummy = { 0, };
+ CSSM_DATA envmsg = { 0, };
+ OSStatus rv;
+
+ inFile = stdin;
+ outFile = stdout;
+ envFileName = NULL;
+ mode = UNKNOWN;
+ decodeOptions.contentFile = NULL;
+ decodeOptions.suppressContent = false;
+ decodeOptions.headerLevel = -1;
+ options.certUsage = certUsageEmailSigner;
+ options.password = NULL;
+ signOptions.nickname = NULL;
+ signOptions.subjectKeyID = NULL;
+ signOptions.detached = false;
+ signOptions.signingTime = false;
+ signOptions.smimeProfile = 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
+ while ((ch = getopt(argc, argv, "CDEGH:N:OPSTY:Z:c:de:h:i:k:no:p:r:su:vt:")) != -1)
+ {
+ switch (ch)
+ {
+ case 'C':
+ mode = ENCRYPT;
+ break;
+ case 'D':
+ mode = DECODE;
+ break;
+ case 'E':
+ mode = ENVELOPE;
+ break;
+ case 'G':
+ if (mode != SIGN) {
+ sec_error("option -G only supported with option -S");
+ result = 2; /* Trigger usage message. */
+ goto loser;
+ }
+ signOptions.signingTime = true;
+ break;
+ case 'H':
+ if (mode != SIGN) {
+ sec_error("option -n only supported with option -D");
+ result = 2; /* Trigger usage message. */
+ goto loser;
+ }
+ if(!optarg) {
+ result = 2;
+ goto loser;
+ }
+ decodeOptions.suppressContent = true;
+ if (!strcmp(optarg, "MD2"))
+ signOptions.hashAlgTag = SEC_OID_MD2;
+ else if (!strcmp(optarg, "MD4"))
+ signOptions.hashAlgTag = SEC_OID_MD4;
+ else if (!strcmp(optarg, "MD5"))
+ signOptions.hashAlgTag = SEC_OID_MD5;
+ else if (!strcmp(optarg, "SHA1"))
+ signOptions.hashAlgTag = SEC_OID_SHA1;
+ else if (!strcmp(optarg, "SHA256"))
+ signOptions.hashAlgTag = SEC_OID_SHA256;
+ else if (!strcmp(optarg, "SHA384"))
+ signOptions.hashAlgTag = SEC_OID_SHA384;
+ else if (!strcmp(optarg, "SHA512"))
+ signOptions.hashAlgTag = SEC_OID_SHA512;
+ else {
+ sec_error("option -H requires one of MD2,MD4,MD5,SHA1,SHA256,SHA384,SHA512");
+ goto loser;
+ }
+ break;
+ case 'N':
+ if (mode != SIGN) {
+ sec_error("option -N only supported with option -S");
+ result = 2; /* Trigger usage message. */
+ goto loser;
+ }
+ signOptions.nickname = strdup(optarg);
+ break;
+ case 'O':
+ mode = CERTSONLY;
+ break;
+ case 'P':
+ if (mode != SIGN) {
+ sec_error("option -P only supported with option -S");
+ result = 2; /* Trigger usage message. */
+ goto loser;
+ }
+ signOptions.smimeProfile = true;
+ break;
+ case 'S':
+ mode = SIGN;
+ break;
+ case 'T':
+ if (mode != SIGN) {
+ sec_error("option -T only supported with option -S");
+ result = 2; /* Trigger usage message. */
+ goto loser;
+ }
+ signOptions.detached = true;
+ break;
+ case 'Y':
+ if (mode != SIGN) {
+ sec_error("option -Y only supported with option -S");
+ result = 2; /* Trigger usage message. */
+ goto loser;
+ }
+ signOptions.encryptionKeyPreferenceNick = strdup(optarg);
+ break;
+
+ case 'c':
+ if (mode != DECODE)
+ {
+ sec_error("option -c only supported with option -D");
+ result = 2; /* Trigger usage message. */
+ goto loser;
+ }
+ if ((decodeOptions.contentFile = fopen(optarg, "rb")) == NULL)
+ {
+ sec_error("unable to open \"%s\" for reading: %s", optarg, strerror(errno));
+ result = 1;
+ goto loser;
+ }
+ break;
+
+#ifdef HAVE_DODUMPSTATES
+ case 'd':
+ doDumpStates++;
+ break;
+#endif /* HAVE_DODUMPSTATES */
+
+ case 'e':
+ envFileName = strdup(optarg);
+ encryptOptions.envFile = fopen(envFileName, "rb"); // PR_RDONLY, 00660);
+ break;
+
+ case 'h':
+ if (mode != DECODE) {
+ sec_error("option -h only supported with option -D");
+ result = 2; /* Trigger usage message. */
+ goto loser;
+ }
+ decodeOptions.headerLevel = atoi(optarg);
+ if (decodeOptions.headerLevel < 0) {
+ sec_error("option -h cannot have a negative value");
+ goto loser;
+ }
+ break;
+ case 'i':
+ inFile = fopen(optarg,"rb"); // PR_RDONLY, 00660);
+ if (inFile == NULL)
+ {
+ sec_error("unable to open \"%s\" for reading: %s", optarg, strerror(errno));
+ goto loser;
+ }
+ break;
+
+ case 'k':
+ keychainName = optarg;
+ break;
+
+ case 'n':
+ if (mode != DECODE)
+ {
+ sec_error("option -n only supported with option -D");
+ result = 2; /* Trigger usage message. */
+ goto loser;
+ }
+ decodeOptions.suppressContent = true;
+ break;
+ case 'o':
+ outFile = fopen(optarg, "wb");
+ if (outFile == NULL)
+ {
+ sec_error("unable to open \"%s\" for writing: %s", optarg, strerror(errno));
+ goto loser;
+ }
+ break;
+ case 'p':
+ if (!optarg)
+ {
+ sec_error("option -p must have a value");
+ result = 2; /* Trigger usage message. */
+ goto loser;
+ }
+
+ options.password = (PK11PasswordFunc)ownpw;//strdup(optarg);
+ break;
+
+ case 'r':
+ if (!optarg)
+ {
+ sec_error("option -r must have a value");
+ result = 2; /* Trigger usage message. */
+ goto loser;
+ }
+
+ envelopeOptions.recipients = ptrarray;
+ str = (char *)optarg;
+ 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 's':
+ cms_update_single_byte = 1;
+ break;
+
+ case 'Z':
+ if (!optarg)
+ {
+ sec_error("option -Z must have a value");
+ result = 2; /* Trigger usage message. */
+ goto loser;
+ }
+ signOptions.subjectKeyID = strdup(optarg);
+
+ break;
+
+ case 'u':
+ {
+ int usageType = atoi (optarg);
+ if (usageType < certUsageSSLClient || usageType > certUsageAnyCA)
+ {
+ result = 1;
+ goto loser;
+ }
+ options.certUsage = (SECCertUsage)usageType;
+ break;
+ }
+ case 'v':
+ cms_verbose = 1;
+ break;
+ case 't':
+ if (optarg)
+ signOptions.timestampingURL = strdup(optarg);
+ signOptions.wantTimestamping = true;
+ break;
+ default:
+ result = 2; /* Trigger usage message. */
+ goto loser;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 0 || mode == UNKNOWN)
+ {
+ result = 2; /* Trigger usage message. */
+ goto loser;
+ }
+
+ result = 0;
+
+ if (mode != CERTSONLY)
+ SECU_FileToItem(&input, inFile);
+ if (inFile != stdin)
+ fclose(inFile);
+ if (cms_verbose)
+ fprintf(stderr, "received commands\n");
+
+ /* Call the libsec initialization routines */
+ if (keychainName)
+ {
+ check_obsolete_keychain(keychainName);
+ options.certDBHandle = keychain_open(keychainName);
+ if (!options.certDBHandle)
+ {
+ sec_perror("SecKeychainOpen", errSecInvalidKeychain);
+ result = 1;
+ goto loser;
+ }
+ }
+
+ if (cms_verbose)
+ fprintf(stderr, "Got default certdb\n");
+
+ 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)
+ {
+ sec_error("problem decoding env msg");
+ result = 1;
+ break;
+ }
+ rv = get_enc_params(&encryptOptions);
+ decodeOptions.dkcb = dkcb;
+ decodeOptions.bulkkey = encryptOptions.bulkkey;
+ }
+ cmsg = decode(outFile, &output, &input, &decodeOptions);
+ if (!cmsg)
+ {
+ sec_error("problem decoding");
+ result = 1;
+ }
+ fwrite(output.Data, output.Length, 1, outFile);
+ break;
+ case SIGN:
+ signOptions.options = &options;
+ cmsg = signed_data(&signOptions);
+ if (!cmsg)
+ {
+ sec_error("problem signing");
+ result = 1;
+ }
+ break;
+ case ENCRYPT:
+ if (!envFileName)
+ {
+ sec_error("you must specify an envelope file with -e");
+ result = 1;
+ goto loser;
+ }
+ encryptOptions.options = &options;
+ encryptOptions.input = &input;
+ encryptOptions.outfile = outFile;
+ if (!encryptOptions.envFile) {
+ encryptOptions.envFile = fopen(envFileName,"wb"); //PR_WRONLY|PR_CREATE_FILE, 00660);
+ if (!encryptOptions.envFile)
+ {
+ sec_error("failed to create file %s: %s", envFileName, strerror(errno));
+ result = 1;
+ goto loser;
+ }
+ }
+ else
+ {
+ SECU_FileToItem(&envmsg, encryptOptions.envFile);
+ decodeOptions.options = &options;
+ encryptOptions.envmsg = decode(NULL, &dummy, &envmsg,
+ &decodeOptions);
+ if (encryptOptions.envmsg == NULL)
+ {
+ sec_error("problem decrypting env msg");
+ result = 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)
+ {
+ sec_error("problem encrypting");
+ result = 1;
+ }
+
+ if (encryptOptions.bulkkey)
+ {
+ CFRelease(encryptOptions.bulkkey);
+ encryptOptions.bulkkey = NULL;
+ }
+ break;
+ case ENVELOPE:
+ envelopeOptions.options = &options;
+ cmsg = enveloped_data(&envelopeOptions);
+ if (!cmsg)
+ {
+ sec_error("problem enveloping");
+ result = 1;
+ }
+ break;
+ case CERTSONLY:
+ certsonlyOptions.options = &options;
+ cmsg = signed_data_certsonly(&certsonlyOptions);
+ if (!cmsg)
+ {
+ sec_error("problem with certs-only");
+ result = 1;
+ }
+ break;
+ case UNKNOWN:
+ /* Already handled above. */
+ break;
+ }
+
+ if ( (mode == SIGN || mode == ENVELOPE || mode == CERTSONLY)
+ && (!result) )
+ {
+ SecArenaPoolRef arena = NULL;
+ SecCmsEncoderRef ecx;
+ CSSM_DATA output = {};
+
+ SEC_CHECK(SecArenaPoolCreate(1024, &arena), "failed to create arenapool");
+ pwcb = (PK11PasswordFunc)((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");
+ }
+
+ SEC_CHECK(SecCmsEncoderCreate(cmsg,
+ NULL, NULL, /* DER output callback */
+ &output, arena, /* destination storage */
+ pwcb, pwcb_arg, /* password callback */
+ NULL, NULL, /* decrypt key callback */
+ NULL, NULL, /* detached digests */
+ &ecx),
+ "cannot create encoder context");
+ if (cms_verbose)
+ {
+ fprintf(stderr, "input len [%ld]\n", input.Length);
+ {
+ unsigned int j;
+ for (j = 0; j < input.Length; ++j)
+ fprintf(stderr, "%2x%c", input.Data[j], (j>0&&j%35==0)?'\n':' ');
+ }
+ }
+
+ if (input.Length > 0) { /* skip if certs-only (or other zero content) */
+ SEC_CHECK(SecCmsEncoderUpdate(ecx, (char *)input.Data, input.Length),
+ "failed to add data to encoder");
+ }
+
+ SEC_CHECK(SecCmsEncoderFinish(ecx), "failed to encode data");
+
+ if (cms_verbose) {
+ fprintf(stderr, "encoding passed\n");
+ }
+
+ /*PR_Write(output.data, output.len);*/
+ fwrite(output.Data, output.Length, 1, outFile);
+ if (cms_verbose) {
+ fprintf(stderr, "wrote to file\n");
+ }
+ SecArenaPoolFree(arena, false);
+ }
+
+loser:
+ if(signOptions.encryptionKeyPreferenceNick) {
+ free(signOptions.encryptionKeyPreferenceNick);
+ }
+ if(signOptions.nickname) {
+ free(signOptions.nickname);
+ }
+ if(signOptions.subjectKeyID) {
+ free(signOptions.subjectKeyID);
+ }
+ if(signOptions.timestampingURL) {
+ free(signOptions.timestampingURL);
+ }
+ if(envFileName) {
+ free(envFileName);
+ }
+ if (cmsg)
+ SecCmsMessageDestroy(cmsg);
+ if (outFile != stdout)
+ fclose(outFile);
+
+ if (decodeOptions.contentFile)
+ fclose(decodeOptions.contentFile);
+
+ return result;
+}
+
+
+#pragma mark ================ Misc from NSS ===================
+// from /security/nss/cmd/lib/secutil.c
+
+OSStatus
+SECU_FileToItem(CSSM_DATA *dst, FILE *src)
+{
+ const int kReadSize = 4096;
+ size_t bytesRead, totalRead = 0;
+
+ do
+ {
+ /* Make room in dst for the new data. */
+ dst->Length += kReadSize;
+ dst->Data = realloc(dst->Data, dst->Length);
+ if (!dst->Data)
+ return 1 /* @@@ memFullErr */;
+
+ bytesRead = fread (&dst->Data[totalRead], 1, kReadSize, src);
+ totalRead += bytesRead;
+ } while (bytesRead == kReadSize);
+
+ if (!feof (src))
+ {
+ /* We are here, but there's no EOF. This is bad */
+ if (dst->Data) {
+ free(dst->Data);
+ dst->Data = NULL;
+ dst->Length = 0;
+ }
+ return 1 /* @@@ ioErr */;
+ }
+
+ /* Trim down the buffer. */
+ dst->Length = totalRead;
+ dst->Data = realloc(dst->Data, totalRead);
+ if (!dst->Data)
+ return 1 /* @@@ memFullErr */;
+
+ return noErr;
+}