pkinit_apple_cert_store.c [plain text]
#include "pkinit_cert_store.h"
#include "pkinit_asn1.h"
#include "pkinit_apple_utils.h"
#include <CoreFoundation/CFString.h>
#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>
#include <assert.h>
#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
#include <CommonCrypto/CommonDigest.h>
#include <sys/errno.h>
#define kPkinitClientCertKey "KRBClientCert"
#define kPkinitClientCertApp "edu.mit.Kerberos.pkinit"
#define KDC_KEYCHAIN "/var/db/krb5kdc/kdc.keychain"
static OSStatus pkinit_get_cert_issuer_sn(
SecCertificateRef certRef,
CSSM_DATA *issuerSerial)
{
OSStatus ortn;
CSSM_DATA certData;
krb5_data INIT_KDATA(issuerSerialKrb);
krb5_data certDataKrb;
krb5_error_code krtn;
assert(certRef != NULL);
assert(issuerSerial != NULL);
ortn = SecCertificateGetData(certRef, &certData);
if(ortn) {
pkiCssmErr("SecCertificateGetData", ortn);
return ortn;
}
PKI_CSSM_TO_KRB_DATA(&certData, &certDataKrb);
krtn = krb5int_pkinit_get_issuer_serial(&certDataKrb, &issuerSerialKrb);
if(krtn) {
return CSSMERR_CL_INVALID_DATA;
}
PKI_KRB_TO_CSSM_DATA(&issuerSerialKrb, issuerSerial);
return noErr;
}
static int pkinit_issuer_sn_match(
SecIdentityRef idRef,
const CSSM_DATA *matchIssuerSerial)
{
OSStatus ortn;
SecCertificateRef certRef = NULL;
CSSM_DATA INIT_CDATA(certIssuerSerial);
int ourRtn = 0;
assert(idRef != NULL);
assert(matchIssuerSerial != NULL);
ortn = SecIdentityCopyCertificate(idRef, &certRef);
if(ortn) {
pkiCssmErr("SecIdentityCopyCertificate", ortn);
return 0;
}
ortn = pkinit_get_cert_issuer_sn(certRef, &certIssuerSerial);
if(ortn) {
pkiCssmErr("SecIdentityCopyCertificate", ortn);
goto errOut;
}
ourRtn = pkiCompareCssmData(matchIssuerSerial, &certIssuerSerial) ? 1 : 0;
errOut:
if(certRef != NULL) {
CFRelease(certRef);
}
if(certIssuerSerial.Data != NULL) {
free(certIssuerSerial.Data);
}
return ourRtn;
}
static OSStatus pkinit_search_ident(
CFTypeRef keychainOrArray,
CSSM_KEYUSE keyUsage,
const CSSM_DATA *issuerSerial,
SecIdentityRef *foundId)
{
OSStatus ortn;
SecIdentityRef idRef = NULL;
SecIdentitySearchRef srchRef = NULL;
ortn = SecIdentitySearchCreate(keychainOrArray, keyUsage, &srchRef);
if(ortn) {
pkiCssmErr("SecIdentitySearchCreate", ortn);
return ortn;
}
do {
ortn = SecIdentitySearchCopyNext(srchRef, &idRef);
if(ortn != noErr) {
break;
}
if(issuerSerial == NULL) {
break;
}
else if(pkinit_issuer_sn_match(idRef, issuerSerial)) {
break;
}
CFRelease(idRef);
idRef = NULL;
} while(ortn == noErr);
CFRelease(srchRef);
if(idRef == NULL) {
return errSecItemNotFound;
}
else {
*foundId = idRef;
return noErr;
}
}
static krb5_error_code pkinit_cert_to_db(
krb5_pkinit_signing_cert_t idRef,
krb5_pkinit_cert_db_t *dbRef)
{
SecKeychainRef kcRef = NULL;
SecKeyRef keyRef = NULL;
OSStatus ortn;
ortn = SecIdentityCopyPrivateKey((SecIdentityRef)idRef, &keyRef);
if(ortn) {
pkiCssmErr("SecIdentityCopyPrivateKey", ortn);
return ortn;
}
ortn = SecKeychainItemCopyKeychain((SecKeychainItemRef)keyRef, &kcRef);
if(ortn) {
pkiCssmErr("SecKeychainItemCopyKeychain", ortn);
}
else {
*dbRef = (krb5_pkinit_cert_db_t)kcRef;
}
CFRelease(keyRef);
return ortn;
}
static OSStatus pkinit_get_pref_dict(
CFDictionaryRef *dict)
{
CFDictionaryRef theDict;
theDict = (CFDictionaryRef)CFPreferencesCopyValue(CFSTR(kPkinitClientCertKey),
CFSTR(kPkinitClientCertApp), kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
if(theDict == NULL) {
pkiDebug("pkinit_get_pref_dict: no kPkinitClientCertKey\n");
return errSecItemNotFound;
}
if(CFGetTypeID(theDict) != CFDictionaryGetTypeID()) {
pkiDebug("pkinit_get_pref_dict: bad kPkinitClientCertKey pref\n");
CFRelease(theDict);
return errSecItemNotFound;
}
*dict = theDict;
return noErr;
}
#pragma mark --- Public client side functions ---
krb5_error_code krb5_pkinit_get_client_cert(
const char *principal,
krb5_pkinit_signing_cert_t *client_cert)
{
CFDataRef issuerSerial = NULL;
CSSM_DATA issuerSerialData;
SecIdentityRef idRef = NULL;
OSStatus ortn;
CFDictionaryRef theDict = NULL;
krb5_error_code ourRtn = 0;
if(principal == NULL) {
return KRB5_PRINC_NOMATCH;
}
ortn = pkinit_get_pref_dict(&theDict);
if(ortn) {
return KRB5_PRINC_NOMATCH;
}
CFStringRef cfPrinc = CFStringCreateWithCString(NULL, principal,
kCFStringEncodingASCII);
issuerSerial = (CFDataRef)CFDictionaryGetValue(theDict, cfPrinc);
CFRelease(cfPrinc);
if(issuerSerial == NULL) {
pkiDebug("krb5_pkinit_get_client_cert: no identity found\n");
ourRtn = KRB5_PRINC_NOMATCH;
goto errOut;
}
if(CFGetTypeID(issuerSerial) != CFDataGetTypeID()) {
pkiDebug("krb5_pkinit_get_client_cert: bad kPkinitClientCertKey value\n");
ourRtn = KRB5_PRINC_NOMATCH;
goto errOut;
}
issuerSerialData.Data = (uint8 *)CFDataGetBytePtr(issuerSerial);
issuerSerialData.Length = CFDataGetLength(issuerSerial);
ortn = pkinit_search_ident(NULL, CSSM_KEYUSE_SIGN | CSSM_KEYUSE_ENCRYPT,
&issuerSerialData, &idRef);
if(ortn) {
pkiDebug("krb5_pkinit_get_client_cert: no identity found!\n");
pkiCssmErr("pkinit_search_ident", ortn);
ourRtn = KRB5_PRINC_NOMATCH;
}
else {
*client_cert = (krb5_pkinit_signing_cert_t)idRef;
}
errOut:
if(theDict) {
CFRelease(theDict);
}
return ourRtn;
}
krb5_boolean krb5_pkinit_have_client_cert(
const char *principal)
{
krb5_pkinit_signing_cert_t signing_cert = NULL;
krb5_error_code krtn;
krtn = krb5_pkinit_get_client_cert(principal, &signing_cert);
if(krtn) {
return FALSE;
}
if(signing_cert != NULL) {
krb5_pkinit_release_cert(signing_cert);
return TRUE;
}
else {
return FALSE;
}
}
krb5_error_code krb5_pkinit_set_client_cert_from_signing_cert(
const char *principal,
krb5_pkinit_signing_cert_t client_cert)
{
SecIdentityRef idRef = (SecIdentityRef)client_cert;
SecCertificateRef certRef = NULL;
OSStatus ortn;
krb5_error_code ourRtn = 0;
if (NULL != idRef) {
if (CFGetTypeID(idRef) != SecIdentityGetTypeID()) {
ourRtn = KRB5KRB_ERR_GENERIC;
goto fin;
}
ortn = SecIdentityCopyCertificate(idRef, &certRef);
if (ortn) {
pkiCssmErr("SecIdentityCopyCertificate", ortn);
ourRtn = KRB5KRB_ERR_GENERIC;
goto fin;
}
}
ourRtn = krb5_pkinit_set_client_cert(principal, (krb5_pkinit_cert_t)certRef);
fin:
if (certRef)
CFRelease(certRef);
return ourRtn;
}
krb5_error_code krb5_pkinit_set_client_cert(
const char *principal,
krb5_pkinit_cert_t client_cert)
{
SecCertificateRef certRef = (SecCertificateRef)client_cert;
OSStatus ortn;
CSSM_DATA issuerSerial = {0, NULL};
CFDataRef cfIssuerSerial = NULL;
CFDictionaryRef existDict = NULL;
CFMutableDictionaryRef newDict = NULL;
CFStringRef keyStr = NULL;
krb5_error_code ourRtn = 0;
if(certRef != NULL) {
if(CFGetTypeID(certRef) != SecCertificateGetTypeID()) {
return KRB5KRB_ERR_GENERIC;
}
ortn = pkinit_get_cert_issuer_sn(certRef, &issuerSerial);
if(ortn) {
ourRtn = KRB5KRB_ERR_GENERIC;
goto errOut;
}
}
ortn = pkinit_get_pref_dict(&existDict);
if(ortn == noErr) {
newDict = CFDictionaryCreateMutableCopy(NULL, 0, existDict);
}
else {
if(certRef == NULL) {
return 0;
}
newDict = CFDictionaryCreateMutable(NULL, 0,
&kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
}
if(newDict == NULL) {
ourRtn = ENOMEM;
goto errOut;
}
keyStr = CFStringCreateWithCString(NULL, principal, kCFStringEncodingASCII);
if(certRef == NULL) {
CFDictionaryRemoveValue(newDict, keyStr);
}
else {
cfIssuerSerial = CFDataCreate(NULL, issuerSerial.Data, issuerSerial.Length);
CFDictionarySetValue(newDict, keyStr, cfIssuerSerial);
}
CFPreferencesSetValue(CFSTR(kPkinitClientCertKey), newDict,
CFSTR(kPkinitClientCertApp), kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
if(CFPreferencesSynchronize(CFSTR(kPkinitClientCertApp), kCFPreferencesCurrentUser,
kCFPreferencesAnyHost)) {
ourRtn = 0;
}
else {
ourRtn = EACCES;
}
errOut:
if(cfIssuerSerial) {
CFRelease(cfIssuerSerial);
}
if(issuerSerial.Data) {
free(issuerSerial.Data);
}
if(existDict) {
CFRelease(existDict);
}
if(newDict) {
CFRelease(newDict);
}
if(keyStr) {
CFRelease(keyStr);
}
return ourRtn;
}
krb5_error_code krb5_pkinit_get_client_cert_db(
const char *principal,
krb5_pkinit_signing_cert_t client_cert,
krb5_pkinit_cert_db_t *client_cert_db)
{
krb5_error_code krtn;
krb5_pkinit_signing_cert_t local_cert;
assert((client_cert != NULL) || (principal != NULL));
if(client_cert == NULL) {
krtn = krb5_pkinit_get_client_cert(principal, &local_cert);
if(krtn) {
return krtn;
}
}
else {
local_cert = client_cert;
}
krtn = pkinit_cert_to_db(local_cert, client_cert_db);
if(client_cert == NULL) {
krb5_pkinit_release_cert(local_cert);
}
return krtn;
}
#pragma mark --- Public server side functions ---
krb5_error_code krb5_pkinit_get_kdc_cert(
krb5_ui_4 num_trusted_CAs,
krb5_data *trusted_CAs,
krb5_data *client_spec,
krb5_pkinit_signing_cert_t *kdc_cert)
{
SecIdentityRef idRef = NULL;
OSStatus ortn;
krb5_error_code ourRtn = 0;
ortn = SecIdentityCopySystemIdentity(kSecIdentityDomainKerberosKDC,
&idRef, NULL);
if(ortn) {
pkiCssmErr("SecIdentityCopySystemIdentity", ortn);
return KRB5_PRINC_NOMATCH;
}
*kdc_cert = (krb5_pkinit_signing_cert_t)idRef;
return ourRtn;
}
krb5_error_code krb5_pkinit_get_kdc_cert_db(
krb5_pkinit_cert_db_t *kdc_cert_db)
{
krb5_pkinit_signing_cert_t kdcCert = NULL;
krb5_error_code krtn;
krtn = krb5_pkinit_get_kdc_cert(0, NULL, NULL, &kdcCert);
if(krtn) {
return krtn;
}
krtn = pkinit_cert_to_db(kdcCert, kdc_cert_db);
krb5_pkinit_release_cert(kdcCert);
return krtn;
}
void krb5_pkinit_release_cert(
krb5_pkinit_signing_cert_t cert)
{
if(cert == NULL) {
return;
}
CFRelease((CFTypeRef)cert);
}
extern void krb5_pkinit_release_cert_db(
krb5_pkinit_cert_db_t cert_db)
{
if(cert_db == NULL) {
return;
}
CFRelease((CFTypeRef)cert_db);
}
char *krb5_pkinit_cert_hash_str(
const krb5_data *cert)
{
CC_SHA1_CTX ctx;
char *outstr;
char *cpOut;
unsigned char digest[CC_SHA1_DIGEST_LENGTH];
unsigned dex;
assert(cert != NULL);
CC_SHA1_Init(&ctx);
CC_SHA1_Update(&ctx, cert->data, cert->length);
CC_SHA1_Final(digest, &ctx);
outstr = (char *)malloc((2 * CC_SHA1_DIGEST_LENGTH) + 1);
if(outstr == NULL) {
return NULL;
}
cpOut = outstr;
for(dex=0; dex<CC_SHA1_DIGEST_LENGTH; dex++) {
sprintf(cpOut, "%02X", (unsigned)(digest[dex]));
cpOut += 2;
}
*cpOut = '\0';
return outstr;
}
krb5_error_code krb5_pkinit_get_server_certs(
const char *client_principal,
const char *server_principal,
krb5_data **trusted_CAs,
krb5_ui_4 *num_trusted_CAs,
krb5_data *kdc_cert)
{
*trusted_CAs = NULL;
*num_trusted_CAs = 0;
kdc_cert->data = NULL;
kdc_cert->length = 0;
return 0;
}