pkinit_apple_cms.c [plain text]
#define IGNORE_VERIFY_ERRORS 1
#define PKINIT_VARIABLE_CONTENT_TYPE 0
#include "pkinit_cms.h"
#include "pkinit_asn1.h"
#include "pkinit_apple_utils.h"
#include <CoreFoundation/CoreFoundation.h>
#include <Security/SecCmsEncoder.h>
#include <Security/SecCmsDecoder.h>
#include <Security/SecCmsMessage.h>
#include <Security/SecCmsSignedData.h>
#include <Security/SecCmsEnvelopedData.h>
#include <Security/SecCmsSignerInfo.h>
#include <Security/SecCmsContentInfo.h>
#include <Security/SecCmsRecipientInfo.h>
#include <Security/SecSMIME.h>
#include <Security/Security.h>
#include <Security/SecIdentityPriv.h>
#include <Security/SecTrustPriv.h>
#include <assert.h>
#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacTypes.h>
#pragma mark ----- CMS utilities ----
static pki_cert_sig_status pkiCertSigStatus(
OSStatus certStatus)
{
switch(certStatus) {
case CSSM_OK:
return pki_cs_good;
case CSSMERR_CSP_VERIFY_FAILED:
return pki_cs_sig_verify_fail;
case CSSMERR_TP_NOT_TRUSTED:
return pki_cs_no_root;
case CSSMERR_TP_INVALID_ANCHOR_CERT:
return pki_cs_unknown_root;
case CSSMERR_TP_CERT_EXPIRED:
return pki_cs_expired;
case CSSMERR_TP_CERT_NOT_VALID_YET:
return pki_cs_not_valid_yet;
case CSSMERR_TP_CERT_REVOKED:
return pki_cs_revoked;
case KRB5_KDB_UNAUTH:
return pki_cs_untrusted;
case CSSMERR_TP_INVALID_CERTIFICATE:
return pki_cs_bad_leaf;
default:
return pki_cs_other_err;
}
}
static OSStatus pkiKrb5DataToSecCert(
const krb5_data *rawCert,
SecCertificateRef *secCert) {
CSSM_DATA certData;
OSStatus ortn;
assert((rawCert != NULL) && (secCert != NULL));
certData.Data = rawCert->data;
certData.Length = rawCert->length;
ortn = SecCertificateCreateFromData(&certData, CSSM_CERT_X_509v3,
CSSM_CERT_ENCODING_DER, secCert);
if(ortn) {
pkiCssmErr("SecCertificateCreateFromData", ortn);
}
return ortn;
}
static OSStatus pkiEncodeCms(
SecCmsMessageRef cmsMsg,
const unsigned char *inData, unsigned inDataLen,
unsigned char **outData, unsigned *outDataLen) {
SecArenaPoolRef arena = NULL;
SecArenaPoolCreate(1024, &arena);
SecCmsEncoderRef cmsEnc = NULL;
CSSM_DATA output = { 0, NULL };
OSStatus ortn;
ortn = SecCmsEncoderCreate(cmsMsg,
NULL, NULL, &output, arena, NULL, NULL, NULL, NULL, NULL, NULL, &cmsEnc);
if(ortn) {
pkiCssmErr("SecCmsEncoderCreate", ortn);
goto errOut;
}
ortn = SecCmsEncoderUpdate(cmsEnc, (char *)inData, inDataLen);
if(ortn) {
pkiCssmErr("SecCmsEncoderUpdate", ortn);
goto errOut;
}
ortn = SecCmsEncoderFinish(cmsEnc);
if(ortn) {
pkiCssmErr("SecCMsEncoderFinish", ortn);
goto errOut;
}
if(output.Length) {
*outData = (unsigned char *)malloc(output.Length);
memmove(*outData, output.Data, output.Length);
*outDataLen = output.Length;
}
else {
*outData = NULL;
*outDataLen = 0;
}
errOut:
if(arena) {
SecArenaPoolFree(arena, false);
}
return ortn;
}
#pragma mark ----- Create SignedData ----
krb5_error_code pkinit_create_signed_data(
const krb5_data *to_be_signed, pkinit_signing_cert_t signing_cert, krb5_boolean include_cert, PKI_ContentType content_type, krb5_data *content_info) {
OSStatus ortn;
SecIdentityRef idRef = (SecIdentityRef)signing_cert;
SecCmsMessageRef cmsMsg = NULL;
SecCmsContentInfoRef contentInfo = NULL;
SecCmsSignedDataRef signedData = NULL;
SecCertificateRef ourCert = NULL;
SecCmsSignerInfoRef signerInfo;
SecKeychainRef kcRef = NULL;
unsigned char *outData;
unsigned outDataLen;
SecKeyRef keyRef = NULL;
#if PKINIT_VARIABLE_CONTENT_TYPE
SECOidTag whichOid;
#endif
SecCmsCertChainMode certChainMode;
assert((to_be_signed != NULL) &&
(signing_cert != NULL) &&
(content_info != NULL));
#if PKINIT_VARIABLE_CONTENT_TYPE
switch(content_type) {
case ECT_Data:
whichOid = SEC_OID_PKCS7_DATA;
break;
case ECT_PkAuthData:
pkiDebug("**WARNING: content_type ECT_PkAuthData not supported\n");
whichOid = SEC_OID_KERBEROS_PK_AUTH_DATA;
break;
default:
pkiDebug("pkinit_create_signed_data: bad content_type spec\n");
ortn = paramErr;
goto errOut;
}
#endif
ortn = SecIdentityCopyCertificate(idRef, &ourCert);
if(ortn) {
pkiCssmErr("SecIdentityCopyCertificate", ortn);
goto errOut;
}
ortn = SecIdentityCopyPrivateKey(idRef, &keyRef);
if(ortn) {
pkiCssmErr("SecIdentityCopyPrivateKey", ortn);
goto errOut;
}
ortn = SecKeychainItemCopyKeychain((SecKeychainItemRef)keyRef, &kcRef);
CFRelease(keyRef);
if(ortn) {
pkiCssmErr("SecKeychainItemCopyKeychain", ortn);
goto errOut;
}
cmsMsg = SecCmsMessageCreate(NULL);
if(cmsMsg == NULL) {
pkiDebug("***Error creating SecCmsMessageRef\n");
ortn = -1;
goto errOut;
}
signedData = SecCmsSignedDataCreate(cmsMsg);
if(signedData == NULL) {
pkiDebug("***Error creating SecCmsSignedDataRef\n");
ortn = -1;
goto errOut;
}
contentInfo = SecCmsMessageGetContentInfo(cmsMsg);
ortn = SecCmsContentInfoSetContentSignedData(cmsMsg, contentInfo, signedData);
if(ortn) {
pkiCssmErr("SecCmsContentInfoSetContentSignedData", ortn);
goto errOut;
}
contentInfo = SecCmsSignedDataGetContentInfo(signedData);
#if PKINIT_VARIABLE_CONTENT_TYPE
ortn = SecCmsContentInfoSetContentDataAndOid(cmsMsg, contentInfo, NULL ,
whichOid, false);
#else
ortn = SecCmsContentInfoSetContentData(cmsMsg, contentInfo, NULL, false);
#endif
if(ortn) {
pkiCssmErr("SecCmsContentInfoSetContentData", ortn);
goto errOut;
}
signerInfo = SecCmsSignerInfoCreate(cmsMsg, idRef, SEC_OID_SHA1);
if (signerInfo == NULL) {
pkiDebug("***Error on SecCmsSignerInfoCreate\n");
ortn = -1;
goto errOut;
}
if(include_cert) {
certChainMode = SecCmsCMCertChainWithRoot;
}
else {
certChainMode = SecCmsCMNone;
}
ortn = SecCmsSignerInfoIncludeCerts(signerInfo, certChainMode, certUsageEmailSigner);
if(ortn) {
pkiCssmErr("SecCmsSignerInfoIncludeCerts", ortn);
goto errOut;
}
ortn = SecCmsSignedDataAddSignerInfo(signedData, signerInfo);
if(ortn) {
pkiCssmErr("SecCmsSignedDataAddSignerInfo", ortn);
goto errOut;
}
ortn = pkiEncodeCms(cmsMsg, to_be_signed->data, to_be_signed->length,
&outData, &outDataLen);
if(ortn) {
goto errOut;
}
content_info->data = (char *)outData;
content_info->length = outDataLen;
errOut:
if(cmsMsg) {
SecCmsMessageDestroy(cmsMsg);
}
if(ourCert) {
CFRelease(ourCert);
}
if(kcRef) {
CFRelease(kcRef);
}
return ortn;
}
#pragma mark ----- Create EnvelopedData ----
krb5_error_code pkinit_create_envel_data(
const krb5_data *raw_content, const krb5_data *recip_cert, PKI_ContentType content_type, krb5_data *content_info) {
SecCmsMessageRef cmsMsg = NULL;
SecCmsContentInfoRef contentInfo = NULL;
SecCmsEnvelopedDataRef envelopedData = NULL;
SecCmsRecipientInfoRef recipientInfo = NULL;
OSStatus ortn;
SecCertificateRef allCerts[2];
SECOidTag algorithmTag;
int keySize;
unsigned char *outData;
unsigned outDataLen;
#if PKINIT_VARIABLE_CONTENT_TYPE
SECOidTag whichOid;
#endif
assert((raw_content != NULL) && (recip_cert != NULL) && (content_info != NULL));
#if PKINIT_VARIABLE_CONTENT_TYPE
switch(content_type) {
case ECT_Data:
whichOid = SEC_OID_PKCS7_DATA;
break;
case ECT_PkReplyKeyKata:
pkiDebug("**WARNING: content_type ECT_PkReplyKeyKata not supported\n");
goto errOut;
}
#endif
ortn = pkiKrb5DataToSecCert(recip_cert, &allCerts[0]);
if(ortn) {
return ortn;
}
allCerts[1] = NULL;
ortn = SecSMIMEFindBulkAlgForRecipients(allCerts, &algorithmTag, &keySize);
if(ortn) {
pkiCssmErr("SecSMIMEFindBulkAlgForRecipients", ortn);
goto errOut;
}
cmsMsg = SecCmsMessageCreate(NULL);
if(cmsMsg == NULL) {
pkiDebug("***Error creating SecCmsMessageRef\n");
ortn = -1;
goto errOut;
}
envelopedData = SecCmsEnvelopedDataCreate(cmsMsg, algorithmTag, keySize);
if(envelopedData == NULL) {
pkiDebug("***Error creating SecCmsEnvelopedDataRef\n");
ortn = -1;
goto errOut;
}
contentInfo = SecCmsMessageGetContentInfo(cmsMsg);
ortn = SecCmsContentInfoSetContentEnvelopedData(cmsMsg, contentInfo, envelopedData);
if(ortn) {
pkiCssmErr("SecCmsContentInfoSetContentEnvelopedData", ortn);
goto errOut;
}
contentInfo = SecCmsEnvelopedDataGetContentInfo(envelopedData);
#if PKINIT_VARIABLE_CONTENT_TYPE
ortn = SecCmsContentInfoSetContentDataAndOid(cmsMsg, contentInfo, NULL ,
whichOid, false);
#else
ortn = SecCmsContentInfoSetContentData(cmsMsg, contentInfo, NULL , false);
#endif
if(ortn) {
pkiCssmErr("SecCmsContentInfoSetContentData", ortn);
goto errOut;
}
recipientInfo = SecCmsRecipientInfoCreate(cmsMsg, allCerts[0]);
ortn = SecCmsEnvelopedDataAddRecipient(envelopedData, recipientInfo);
if(ortn) {
pkiCssmErr("SecCmsEnvelopedDataAddRecipient", ortn);
goto errOut;
}
ortn = pkiEncodeCms(cmsMsg, raw_content->data, raw_content->length,
&outData, &outDataLen);
if(ortn) {
goto errOut;
}
content_info->data = (char *)outData;
content_info->length = outDataLen;
errOut:
if(cmsMsg) {
SecCmsMessageDestroy(cmsMsg);
}
if(allCerts[0]) {
CFRelease(allCerts[0]);
}
return ortn;
}
#pragma mark ----- Parse SignedData ----
static OSStatus pkiEvalSecTrust(
SecTrustRef secTrust)
{
OSStatus ortn;
SecTrustResultType secTrustResult;
ortn = SecTrustEvaluate(secTrust, &secTrustResult);
if(ortn) {
pkiCssmErr("SecTrustEvaluate", ortn);
return ortn;
}
switch(secTrustResult) {
case kSecTrustResultUnspecified:
case kSecTrustResultProceed:
return noErr;
case kSecTrustResultDeny:
case kSecTrustResultConfirm:
return KRB5_KDB_UNAUTH;
default:
{
OSStatus tpStatus;
ortn = SecTrustGetCssmResultCode(secTrust, &tpStatus);
if(ortn) {
pkiCssmErr("SecTrustGetCssmResultCode", ortn);
return ortn;
}
return tpStatus;
}
}
}
static OSStatus pkiParseSignedData(
SecCmsSignedDataRef signedData,
pkinit_cert_db_t cert_db, OSStatus *certVerifyStatus, SecCertificateRef *signerCert, CSSM_DATA ***allCerts) {
SecTrustRef secTrust = NULL;
OSStatus ortn = noErr;
SecPolicyRef policy = NULL;
SecPolicySearchRef policySearch = NULL;
Boolean b;
SecCmsSignerInfoRef signerInfo = NULL;
if(signerCert) {
*signerCert = NULL;
}
if(certVerifyStatus) {
*certVerifyStatus = -1;
}
if(allCerts) {
*allCerts = NULL;
}
int numSigners = SecCmsSignedDataSignerInfoCount(signedData);
if(numSigners != 1) {
pkiDebug("***pkiParseSignedData: numSigners %d, expected 1\n", numSigners);
return internalComponentErr;
}
if((certVerifyStatus != NULL) ||
(signerCert != NULL) ||
(allCerts != NULL)) {
CSSM_DATA_PTR *certList = SecCmsSignedDataGetCertificateList(signedData);
if(certList == NULL) {
pkiDebug("***pkiParseSignedData: no certList available\n");
return ASN1_BAD_FORMAT;
}
if(allCerts != NULL) {
*allCerts = certList;
}
if((certVerifyStatus != NULL) ||
(signerCert != NULL)) {
unsigned numCerts = pkiNssArraySize((const void **)certList);
unsigned dex;
if(numCerts == 0) {
pkiDebug("***pkiParseSignedData: empty certList\n");
return ASN1_BAD_FORMAT;
}
if(cert_db == NULL) {
pkiDebug("***pkiParseSignedData requires a cert_db to proceed\n");
return internalComponentErr;
}
for(dex=0; dex<numCerts; dex++) {
SecCertificateRef certRef;
OSStatus ortn;
ortn = SecCertificateCreateFromData(certList[dex], CSSM_CERT_X_509v3,
CSSM_CERT_ENCODING_DER, &certRef);
if(ortn) {
pkiCssmErr("pkiParseSignedData:SecCertificateCreateFromData", ortn);
return ortn;
}
ortn = SecCertificateAddToKeychain(certRef, (SecKeychainRef)cert_db);
switch(ortn) {
case noErr:
break;
case errSecDuplicateItem: ortn = noErr;
break;
default:
pkiCssmErr("pkiParseSignedData:SecCertificateAddToKeychain",
ortn);
break;
}
CFRelease(certRef);
if(ortn) {
return ortn;
}
}
}
}
if(certVerifyStatus != NULL) {
ortn = SecPolicySearchCreate(CSSM_CERT_X_509v3,
&CSSMOID_APPLE_X509_BASIC, NULL, &policySearch);
if(ortn) {
pkiCssmErr("SecPolicySearchCreate", ortn);
return ortn;
}
ortn = SecPolicySearchCopyNext(policySearch, &policy);
if(ortn) {
pkiCssmErr("SecPolicySearchCopyNext", ortn);
CFRelease(policySearch);
return ortn;
}
b = SecCmsSignedDataHasDigests(signedData);
if(b) {
ortn = SecCmsSignedDataVerifySignerInfo(signedData, 0, cert_db,
policy, &secTrust);
if(ortn) {
pkiCssmErr(" SecCmsSignedDataVerifySignerInfo", ortn);
#if IGNORE_VERIFY_ERRORS
ortn = noErr;
#endif
}
if(secTrust == NULL) {
pkiDebug("***NO SecTrust available!\n");
#if IGNORE_VERIFY_ERRORS
ortn = noErr;
*certVerifyStatus = noErr;
#else
ortn = internalComponentErr;
#endif
}
else {
*certVerifyStatus = pkiEvalSecTrust(secTrust);
}
}
else {
pkiDebug("pkiParseSignedData: No digest attached to cms message\n");
}
}
if(signerCert != NULL) {
signerInfo = SecCmsSignedDataGetSignerInfo(signedData, 0);
*signerCert = SecCmsSignerInfoGetSigningCertificate(signerInfo, cert_db);
}
if(allCerts != NULL) {
*allCerts = SecCmsSignedDataGetCertificateList(signedData);
}
if(policySearch) {
CFRelease(policySearch);
}
if(policy) {
CFRelease(policy);
}
return ortn;
}
static OSStatus pkiCertArrayToKrb5Data(
CSSM_DATA **cdAllCerts,
unsigned *num_all_certs,
krb5_data **all_certs)
{
krb5_data *allCerts = NULL;
OSStatus ortn = noErr;
unsigned numCerts;
unsigned dex;
assert(num_all_certs != NULL);
assert(all_certs != NULL);
*num_all_certs = 0;
*all_certs = NULL;
if(cdAllCerts == NULL) {
return 0;
}
numCerts = pkiNssArraySize((const void **)cdAllCerts);
if(numCerts == 0) {
return 0;
}
allCerts = (krb5_data *)malloc(sizeof(krb5_data) * numCerts);
if(allCerts == NULL) {
return ENOMEM;
}
for(dex=0; dex<numCerts; dex++) {
if(pkiCssmDataToKrb5Data(cdAllCerts[dex], &allCerts[dex])) {
ortn = ENOMEM;
goto errOut;
}
}
errOut:
if(ortn) {
if(allCerts) {
free(allCerts);
}
}
else {
*all_certs = allCerts;
*num_all_certs = (unsigned)numCerts;
}
return ortn;
}
#pragma mark ----- Generalized parse ContentInfo ----
krb5_error_code pkinit_parse_content_info(
const krb5_data *content_info,
pkinit_cert_db_t cert_db, krb5_boolean *is_signed, krb5_boolean *is_encrypted, krb5_data *raw_data, PKI_ContentType *inner_content_type, krb5_data *signer_cert, pki_cert_sig_status *signer_cert_status, unsigned *num_all_certs, krb5_data **all_certs) {
SecArenaPoolRef arena = NULL;
SecCmsMessageRef cmsMsg = NULL;
SecCmsDecoderRef decoder;
OSStatus ortn;
Boolean b;
int numContentInfos;
CSSM_DATA_PTR odata;
int dex;
OSStatus osCertStatus;
OSStatus *osCertStatusP = NULL;
assert(content_info != NULL);
if(signer_cert) {
signer_cert->data = NULL;
signer_cert->length = 0;
}
if(raw_data) {
raw_data->data = NULL;
raw_data->length = 0;
}
if(all_certs) {
assert(num_all_certs != NULL);
*all_certs = NULL;
*num_all_certs = 0;
}
if(signer_cert_status) {
*signer_cert_status = -1;
osCertStatusP = &osCertStatus;
}
SecArenaPoolCreate(1024, &arena);
ortn = SecCmsDecoderCreate(arena, NULL, NULL, NULL, NULL, NULL, NULL, &decoder);
if(ortn) {
pkiCssmErr("SecCmsDecoderCreate", ortn);
return ortn;
}
ortn = SecCmsDecoderUpdate(decoder, content_info->data, content_info->length);
if(ortn) {
pkiCssmErr("SecCmsDecoderUpdate", ortn);
goto errOut;
}
ortn = SecCmsDecoderFinish(decoder, &cmsMsg);
if(ortn) {
pkiCssmErr("SecCmsDecoderFinish", ortn);
goto errOut;
}
if(is_signed) {
b = SecCmsMessageIsSigned(cmsMsg);
*is_signed = b ? TRUE : FALSE;
}
if(is_encrypted) {
b = SecCmsMessageIsEncrypted(cmsMsg);
*is_encrypted = b ? TRUE : FALSE;
}
numContentInfos = SecCmsMessageContentLevelCount(cmsMsg);
if(numContentInfos == 0) {
pkiDebug("pkinit_parse_content_info: no ContentInfos!\n");
ortn = ASN1_BAD_FORMAT;
goto errOut;
}
if((signer_cert != NULL) || (signer_cert_status != NULL) ||
(num_all_certs != NULL) || (all_certs != NULL)) {
b = TRUE;
}
else {
b = FALSE;
}
if(b) {
bool gotOneSignedData = false;
for(dex=0; dex<numContentInfos; dex++) {
SecCmsContentInfoRef ci = SecCmsMessageContentLevel(cmsMsg, dex);
SECOidTag tag = SecCmsContentInfoGetContentTypeTag(ci);
switch(tag) {
case SEC_OID_PKCS7_SIGNED_DATA:
{
SecCmsSignedDataRef sd =
(SecCmsSignedDataRef) SecCmsContentInfoGetContent(ci);
SecCertificateRef certRef = NULL;
SecCertificateRef *certRefP = NULL;
CSSM_DATA certData;
CSSM_DATA **cdAllCerts = NULL;
CSSM_DATA ***cdAllCertsP = NULL;
if(signer_cert) {
certRefP = &certRef;
}
if(all_certs) {
cdAllCertsP = &cdAllCerts;
}
if(gotOneSignedData) {
pkiDebug("pkinit_parse_content_info: Multiple SignedDatas!\n");
ortn = ASN1_BAD_FORMAT;
goto errOut;
}
ortn = pkiParseSignedData(sd, cert_db, osCertStatusP, certRefP,
cdAllCertsP);
if(ortn) {
goto errOut;
}
if(certRef) {
ortn = SecCertificateGetData(certRef, &certData);
if(ortn) {
pkiCssmErr("SecCertificateGetData", ortn);
goto errOut;
}
pkiDataToKrb5Data(certData.Data, certData.Length, signer_cert);
}
if(cdAllCerts) {
ortn = pkiCertArrayToKrb5Data(cdAllCerts, num_all_certs,
all_certs);
if(ortn) {
goto errOut;
}
}
if(signer_cert_status) {
*signer_cert_status = pkiCertSigStatus(osCertStatus);
}
gotOneSignedData = true;
break;
}
case SEC_OID_PKCS7_DATA:
case SEC_OID_PKCS7_ENVELOPED_DATA:
case SEC_OID_PKCS7_ENCRYPTED_DATA:
default:
break;
}
}
}
if(raw_data != NULL) {
odata = SecCmsMessageGetContent(cmsMsg);
if(odata == NULL) {
pkiDebug("***pkinit_parse_content_info: No inner content available\n");
}
else {
pkiDataToKrb5Data(odata->Data, odata->Length, raw_data);
}
}
errOut:
if(arena) {
SecArenaPoolFree(arena, false);
}
if(cmsMsg) {
SecCmsMessageDestroy(cmsMsg);
}
return ortn;
}