pkinit_apple_cms.c [plain text]
#include "pkinit_cms.h"
#include "pkinit_asn1.h"
#include "pkinit_apple_utils.h"
#include <CoreFoundation/CoreFoundation.h>
#include <Security/CMSEncoder.h>
#include <Security/CMSDecoder.h>
#include <Security/Security.h>
#include <assert.h>
#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacTypes.h>
#define OID_PKINIT 0x2B, 6, 1, 5, 2, 3
#define OID_PKINIT_LEN 6
static const uint8 OID_PKINIT_AUTH_DATA[] = {OID_PKINIT, 1};
static const uint8 OID_PKINIT_RKEY_DATA[] = {OID_PKINIT, 3};
static const CSSM_OID _CSSMOID_PKINIT_AUTH_DATA =
{OID_PKINIT_LEN+1, (uint8 *)OID_PKINIT_AUTH_DATA};
static const CSSM_OID _CSSMOID_PKINIT_RKEY_DATA =
{OID_PKINIT_LEN+1, (uint8 *)OID_PKINIT_RKEY_DATA};
#pragma mark ----- CMS utilities ----
#define CFRELEASE(cf) if(cf) { CFRelease(cf); }
static krb5int_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 krb5int_cert_sig_status pkiInferSigStatus(
CMSSignerStatus cms_status,
OSStatus tp_status)
{
switch(cms_status) {
case kCMSSignerUnsigned:
return pki_not_signed;
case kCMSSignerValid:
return pki_cs_good;
case kCMSSignerNeedsDetachedContent:
return pki_bad_cms;
case kCMSSignerInvalidSignature:
return pki_cs_sig_verify_fail;
case kCMSSignerInvalidCert:
break;
default:
return pki_cs_other_err;
}
return pkiCertSigStatus(tp_status);
}
static OSStatus pkiKrb5DataToSecCert(
const krb5_data *rawCert,
SecCertificateRef *secCert)
{
CSSM_DATA certData;
OSStatus ortn;
assert((rawCert != NULL) && (secCert != NULL));
certData.Data = (uint8 *)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 krb5_error_code pkiCertArrayToKrb5Data(
CFArrayRef cf_certs,
unsigned *num_all_certs,
krb5_data **all_certs)
{
CFIndex num_certs;
krb5_data *allCerts = NULL;
krb5_error_code krtn = 0;
unsigned dex;
if(cf_certs == NULL) {
*all_certs = NULL;
return 0;
}
num_certs = CFArrayGetCount(cf_certs);
*num_all_certs = (unsigned)num_certs;
if(num_certs == 0) {
*all_certs = NULL;
return 0;
}
allCerts = (krb5_data *)malloc(sizeof(krb5_data) * num_certs);
if(allCerts == NULL) {
return ENOMEM;
}
for(dex=0; dex<num_certs; dex++) {
CSSM_DATA cert_data;
OSStatus ortn;
SecCertificateRef sec_cert;
sec_cert = (SecCertificateRef)CFArrayGetValueAtIndex(cf_certs, dex);
ortn = SecCertificateGetData(sec_cert, &cert_data);
if(ortn) {
pkiCssmErr("SecCertificateGetData", ortn);
krtn = KRB5_PARSE_MALFORMED;
break;
}
krtn = pkiCssmDataToKrb5Data(&cert_data, &allCerts[dex]);
if(krtn) {
break;
}
}
if(krtn) {
if(allCerts) {
free(allCerts);
}
}
else {
*all_certs = allCerts;
}
return krtn;
}
#pragma mark ----- Create CMS message -----
krb5_error_code krb5int_pkinit_create_cms_msg(
const krb5_data *content,
krb5_pkinit_signing_cert_t signing_cert,
const krb5_data *recip_cert,
krb5int_cms_content_type content_type,
krb5_ui_4 num_cms_types,
const krb5int_algorithm_id *cms_types,
krb5_data *content_info)
{
krb5_error_code krtn;
OSStatus ortn;
SecCertificateRef sec_recip = NULL;
CFDataRef cf_content = NULL;
const CSSM_OID *eContentOid = NULL;
if((signing_cert == NULL) && (recip_cert == NULL)) {
pkiDebug("krb5int_pkinit_create_cms_msg: no signer or recipient\n");
return KRB5_CRYPTO_INTERNAL;
}
if(recip_cert) {
if(pkiKrb5DataToSecCert(recip_cert, &sec_recip)) {
krtn = ASN1_BAD_FORMAT;
goto errOut;
}
}
if(signing_cert) {
switch(content_type) {
case ECT_PkAuthData:
eContentOid = &_CSSMOID_PKINIT_AUTH_DATA;
break;
case ECT_PkReplyKeyKata:
eContentOid = &_CSSMOID_PKINIT_RKEY_DATA;
break;
case ECT_Data:
break;
default:
pkiDebug("krb5int_pkinit_create_cms_msg: bad contentType\n");
krtn = KRB5_CRYPTO_INTERNAL;
goto errOut;
}
}
ortn = CMSEncode((SecIdentityRef)signing_cert, sec_recip,
eContentOid,
FALSE,
kCMSAttrNone,
content->data, content->length,
&cf_content);
if(ortn) {
pkiCssmErr("CMSEncode", ortn);
krtn = KRB5_CRYPTO_INTERNAL;
goto errOut;
}
krtn = pkiCfDataToKrb5Data(cf_content, content_info);
errOut:
CFRELEASE(sec_recip);
CFRELEASE(cf_content);
return krtn;
}
#pragma mark ----- Generalized parse ContentInfo ----
krb5_error_code krb5int_pkinit_parse_cms_msg(
const krb5_data *content_info,
krb5_pkinit_cert_db_t cert_db,
krb5_boolean is_client_msg,
krb5_boolean *is_signed,
krb5_boolean *is_encrypted,
krb5_data *raw_data,
krb5int_cms_content_type *inner_content_type,
krb5_data *signer_cert,
krb5int_cert_sig_status *signer_cert_status,
unsigned *num_all_certs,
krb5_data **all_certs)
{
SecPolicySearchRef policy_search = NULL;
SecPolicyRef policy = NULL;
OSStatus ortn;
krb5_error_code krtn = 0;
CMSDecoderRef decoder = NULL;
size_t num_signers;
CMSSignerStatus signer_status;
OSStatus cert_verify_status;
CFArrayRef cf_all_certs = NULL;
int msg_is_signed = 0;
if(content_info == NULL) {
pkiDebug("krb5int_pkinit_parse_cms_msg: no ContentInfo\n");
return KRB5_CRYPTO_INTERNAL;
}
ortn = CMSDecoderCreate(&decoder);
if(ortn) {
return ENOMEM;
}
ortn = CMSDecoderUpdateMessage(decoder, content_info->data, content_info->length);
if(ortn) {
krtn = KRB5_PARSE_MALFORMED;
goto errOut;
}
ortn = CMSDecoderFinalizeMessage(decoder);
if(ortn) {
pkiCssmErr("CMSDecoderFinalizeMessage", ortn);
krtn = KRB5_PARSE_MALFORMED;
goto errOut;
}
ortn = CMSDecoderGetNumSigners(decoder, &num_signers);
switch(num_signers) {
case 0:
msg_is_signed = 0;
break;
case 1:
msg_is_signed = 1;
break;
default:
krtn = KRB5_PARSE_MALFORMED;
goto errOut;
}
ortn = SecPolicySearchCreate(CSSM_CERT_X_509v3,
is_client_msg ? &CSSMOID_APPLE_TP_PKINIT_CLIENT : &CSSMOID_APPLE_TP_PKINIT_SERVER,
NULL, &policy_search);
if(ortn) {
pkiCssmErr("SecPolicySearchCreate", ortn);
krtn = KRB5_CRYPTO_INTERNAL;
goto errOut;
}
ortn = SecPolicySearchCopyNext(policy_search, &policy);
if(ortn) {
pkiCssmErr("SecPolicySearchCopyNext", ortn);
krtn = KRB5_CRYPTO_INTERNAL;
goto errOut;
}
if(msg_is_signed) {
if(is_signed) {
*is_signed = TRUE;
}
if(inner_content_type) {
CSSM_OID ec_oid = {0, NULL};
CFDataRef ec_data = NULL;
krb5int_cms_content_type ctype;
ortn = CMSDecoderCopyEncapsulatedContentType(decoder, &ec_data);
if(ortn || (ec_data == NULL)) {
pkiCssmErr("CMSDecoderCopyEncapsulatedContentType", ortn);
krtn = KRB5_CRYPTO_INTERNAL;
goto errOut;
}
ec_oid.Data = (uint8 *)CFDataGetBytePtr(ec_data);
ec_oid.Length = CFDataGetLength(ec_data);
if(pkiCompareCssmData(&ec_oid, &CSSMOID_PKCS7_Data)) {
ctype = ECT_Data;
}
else if(pkiCompareCssmData(&ec_oid, &CSSMOID_PKCS7_SignedData)) {
ctype = ECT_SignedData;
}
else if(pkiCompareCssmData(&ec_oid, &CSSMOID_PKCS7_EnvelopedData)) {
ctype = ECT_EnvelopedData;
}
else if(pkiCompareCssmData(&ec_oid, &CSSMOID_PKCS7_EncryptedData)) {
ctype = ECT_EncryptedData;
}
else if(pkiCompareCssmData(&ec_oid, &_CSSMOID_PKINIT_AUTH_DATA)) {
ctype = ECT_PkAuthData;
}
else if(pkiCompareCssmData(&ec_oid, &_CSSMOID_PKINIT_RKEY_DATA)) {
ctype = ECT_PkReplyKeyKata;
}
else {
ctype = ECT_Other;
}
*inner_content_type = ctype;
CFRelease(ec_data);
}
if(all_certs) {
ortn = CMSDecoderCopyAllCerts(decoder, &cf_all_certs);
if(ortn) {
pkiCssmErr("CMSDecoderCopyAllCerts", ortn);
krtn = KRB5_CRYPTO_INTERNAL;
goto errOut;
}
krtn = pkiCertArrayToKrb5Data(cf_all_certs, num_all_certs, all_certs);
if(krtn) {
goto errOut;
}
}
if(signer_cert) {
SecCertificateRef sec_signer_cert = NULL;
CSSM_DATA cert_data;
ortn = CMSDecoderCopySignerCert(decoder, 0, &sec_signer_cert);
if(ortn) {
pkiCssmErr("CMSDecoderCopySignerStatus", ortn);
krtn = KRB5_CRYPTO_INTERNAL;
goto errOut;
}
ortn = SecCertificateGetData(sec_signer_cert, &cert_data);
if(ortn) {
pkiCssmErr("SecCertificateGetData", ortn);
CFRelease(sec_signer_cert);
krtn = KRB5_CRYPTO_INTERNAL;
goto errOut;
}
krtn = pkiDataToKrb5Data(cert_data.Data, cert_data.Length, signer_cert);
CFRelease(sec_signer_cert);
if(krtn) {
goto errOut;
}
}
}
else {
if(is_signed) {
*is_signed = FALSE;
}
if(inner_content_type) {
*inner_content_type = ECT_Other;
}
if(signer_cert) {
signer_cert->data = NULL;
signer_cert->length = 0;
}
if(signer_cert_status) {
*signer_cert_status = pki_not_signed;
}
if(num_all_certs) {
*num_all_certs = 0;
}
if(all_certs) {
*all_certs = NULL;
}
}
if(is_encrypted) {
Boolean bencr;
ortn = CMSDecoderIsContentEncrypted(decoder, &bencr);
if(ortn) {
pkiCssmErr("CMSDecoderCopySignerStatus", ortn);
krtn = KRB5_CRYPTO_INTERNAL;
goto errOut;
}
*is_encrypted = bencr ? TRUE : FALSE;
}
if((signer_cert_status != NULL) || (signer_cert != NULL)) {
ortn = CMSDecoderCopySignerStatus(decoder,
0,
policy,
signer_cert_status ? TRUE : FALSE,
&signer_status,
NULL,
&cert_verify_status);
if(ortn) {
pkiCssmErr("CMSDecoderCopySignerStatus", ortn);
krtn = KRB5_PARSE_MALFORMED;
goto errOut;
}
}
if(signer_cert_status) {
*signer_cert_status = pkiInferSigStatus(signer_status, cert_verify_status);
}
if(raw_data) {
CFDataRef cf_content = NULL;
ortn = CMSDecoderCopyContent(decoder, &cf_content);
if(ortn) {
pkiCssmErr("CMSDecoderCopyContent", ortn);
krtn = KRB5_PARSE_MALFORMED;
goto errOut;
}
krtn = pkiCfDataToKrb5Data(cf_content, raw_data);
CFRELEASE(cf_content);
}
errOut:
CFRELEASE(policy_search);
CFRELEASE(policy);
CFRELEASE(cf_all_certs);
CFRELEASE(decoder);
return krtn;
}
krb5_error_code krb5int_pkinit_get_cms_types(
krb5int_algorithm_id **supported_cms_types,
krb5_ui_4 *num_supported_cms_types)
{
*supported_cms_types = NULL;
*num_supported_cms_types = 0;
return 0;
}
krb5_error_code krb5int_pkinit_free_cms_types(
krb5int_algorithm_id *supported_cms_types,
krb5_ui_4 num_supported_cms_types)
{
return 0;
}