#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"
#include <Security/SecPolicyPriv.h>
#include <Security/SecCertificatePriv.h>
#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)
#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;
}
}
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;
}
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)
{
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
#ifdef HAVE_DODUMPSTATES
extern int doDumpStates;
#endif
OSStatus SECU_FileToItem(CSSM_DATA *dst, FILE *src);
extern void SEC_Init(void);
static int cms_verbose = 0;
static int cms_update_single_byte = 0;
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) SECU_FileToItem(&sitem, decodeOptions->contentFile);
SEC_CHECK(SecCmsDecoderCreate(NULL,
NULL, NULL,
pwcb, pwcb_arg,
decodeOptions->dkcb,
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: ");
}
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 (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);
SEC_CHECK(SecCmsSignedDataImportCerts(sigd,decodeOptions->options->certDBHandle,
decodeOptions->options->certUsage, true ),
"cert import failed");
nsigners = SecCmsSignedDataSignerInfoCount(sigd);
if (decodeOptions->headerLevel >= 0)
fprintf(out, "nsigners=%d; ", nsigners);
if (nsigners == 0)
{
OSStatus rv;
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)));
}
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);
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;
}
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", "***" );
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)
{
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
{
sec_error("no signing identity was specified");
return NULL;
}
SEC_CHECK(SecIdentityCopyCertificate(identity, &cert),
"SecIdentityCopyCertificate");
SEC_CHECK0(cmsg = SecCmsMessageCreate(NULL), "cannot create CMS message");
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");
SEC_CHECK(SecCmsContentInfoSetContentData(cmsg, cinfo, NULL, signOptions->detached),
"cannot attach CMS data object");
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");
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)
{
OSStatus FitForEncrypt = CERT_CheckCertUsage(cert, certUsageEmailRecipient);
if (noErr == FitForEncrypt)
{
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
{
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)
{
}
else
{
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;
SEC_CHECK0(cnt = nss_CMSArray_Count((void **)envelopeOptions->recipients),
"please name at least one recipient");
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;
SEC_CHECK(SecSMIMEFindBulkAlgForRecipients(recipientcerts, &bulkalgtag, &keysize),
"cannot find common bulk algorithm");
SEC_CHECK0(cmsg = SecCmsMessageCreate(NULL), "cannot create CMS message");
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");
SEC_CHECK(SecCmsContentInfoSetContentData(cmsg, cinfo, NULL, false),
"cannot attach CMS data object");
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;
if (encryptOptions->envmsg)
env_cmsg = encryptOptions->envmsg; 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);
}
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)
{
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, };
SEC_CHECK(SecArenaPoolCreate(1024, &tmppoolp), "failed to create arenapool");
SEC_CHECK0(cmsg = SecCmsMessageCreate(NULL), "cannot create CMS message");
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");
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);
if (tmppoolp)
SecArenaPoolFree(tmppoolp, false);
return cmsg;
loser:
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;
SEC_CHECK0(cmsg = SecCmsMessageCreate(NULL), "cannot create CMS message");
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;
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;
goto loser;
}
signOptions.signingTime = true;
break;
case 'H':
if (mode != SIGN) {
sec_error("option -n only supported with option -D");
result = 2;
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;
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;
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;
goto loser;
}
signOptions.detached = true;
break;
case 'Y':
if (mode != SIGN) {
sec_error("option -Y only supported with option -S");
result = 2;
goto loser;
}
signOptions.encryptionKeyPreferenceNick = strdup(optarg);
break;
case 'c':
if (mode != DECODE)
{
sec_error("option -c only supported with option -D");
result = 2;
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
case 'e':
envFileName = strdup(optarg);
encryptOptions.envFile = fopen(envFileName, "rb"); break;
case 'h':
if (mode != DECODE) {
sec_error("option -h only supported with option -D");
result = 2;
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"); 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;
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;
goto loser;
}
options.password = (PK11PasswordFunc)ownpw; break;
case 'r':
if (!optarg)
{
sec_error("option -r must have a value");
result = 2;
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;
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;
goto loser;
}
}
argc -= optind;
argv += optind;
if (argc != 0 || mode == UNKNOWN)
{
result = 2;
goto loser;
}
result = 0;
if (mode != CERTSONLY)
SECU_FileToItem(&input, inFile);
if (inFile != stdin)
fclose(inFile);
if (cms_verbose)
fprintf(stderr, "received commands\n");
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)
{
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"); 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;
}
}
rv = get_enc_params(&encryptOptions);
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:
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,
&output, arena,
pwcb, pwcb_arg,
NULL, NULL,
NULL, NULL,
&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) {
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");
}
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 ===================
OSStatus
SECU_FileToItem(CSSM_DATA *dst, FILE *src)
{
const int kReadSize = 4096;
size_t bytesRead, totalRead = 0;
do
{
dst->Length += kReadSize;
dst->Data = realloc(dst->Data, dst->Length);
if (!dst->Data)
return 1 ;
bytesRead = fread (&dst->Data[totalRead], 1, kReadSize, src);
totalRead += bytesRead;
} while (bytesRead == kReadSize);
if (!feof (src))
{
if (dst->Data) {
free(dst->Data);
dst->Data = NULL;
dst->Length = 0;
}
return 1 ;
}
dst->Length = totalRead;
dst->Data = realloc(dst->Data, totalRead);
if (!dst->Data)
return 1 ;
return noErr;
}