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;
CSSM_CL_HANDLE clHand;
CSSM_HANDLE resultHand;
uint32 numFields;
CSSM_DATA_PTR issuer = NULL;
CSSM_DATA_PTR serial = NULL;
krb5_data INIT_KDATA(issuerKrb);
krb5_data INIT_KDATA(serialKrb);
krb5_data INIT_KDATA(issuerSerialKrb);
assert(certRef != NULL);
assert(issuerSerial != NULL);
ortn = SecCertificateGetData(certRef, &certData);
if(ortn) {
pkiCssmErr("SecCertificateGetData", ortn);
return ortn;
}
ortn = SecCertificateGetCLHandle(certRef, &clHand);
if(ortn) {
pkiCssmErr("SecCertificateGetData", ortn);
return ortn;
}
ortn = CSSM_CL_CertGetFirstFieldValue(clHand, &certData,
&CSSMOID_X509V1IssuerNameStd, &resultHand, &numFields, &issuer);
if(ortn) {
pkiCssmErr("CSSM_CL_CertGetFirstFieldValue(issuer)", ortn);
return ortn;
}
CSSM_CL_CertAbortQuery(clHand, resultHand);
ortn = CSSM_CL_CertGetFirstFieldValue(clHand, &certData, &CSSMOID_X509V1SerialNumber,
&resultHand, &numFields, &serial);
if(ortn) {
pkiCssmErr("CSSM_CL_CertGetFirstFieldValue(serial)", ortn);
goto errOut;
}
CSSM_CL_CertAbortQuery(clHand, resultHand);
PKI_CSSM_TO_KRB_DATA(issuer, &issuerKrb);
PKI_CSSM_TO_KRB_DATA(serial, &serialKrb);
ortn = pkinit_issuer_serial_encode(&issuerKrb, &serialKrb, &issuerSerialKrb);
if(ortn) {
pkiCssmErr("pkinit_issuer_serial_encode", ortn);
goto errOut;
}
PKI_KRB_TO_CSSM_DATA(&issuerSerialKrb, issuerSerial);
errOut:
if(issuer) {
CSSM_CL_FreeFieldValue(clHand, &CSSMOID_X509V1IssuerNameStd, issuer);
}
if(serial) {
CSSM_CL_FreeFieldValue(clHand, &CSSMOID_X509V1SerialNumber, serial);
}
return ortn;
}
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(
pkinit_signing_cert_t idRef,
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 = (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 pkinit_get_client_cert(
const char *principal, pkinit_signing_cert_t *client_cert)
{
CFDataRef issuerSerial = NULL;
CSSM_DATA issuerSerialData;
SecIdentityRef idRef = NULL;
OSStatus ortn;
CFDictionaryRef theDict = NULL;
if(principal == NULL) {
return errSecItemNotFound;
}
ortn = pkinit_get_pref_dict(&theDict);
if(ortn) {
return ortn;
}
CFStringRef cfPrinc = CFStringCreateWithCString(NULL, principal,
kCFStringEncodingASCII);
issuerSerial = (CFDataRef)CFDictionaryGetValue(theDict, cfPrinc);
CFRelease(cfPrinc);
if(issuerSerial == NULL) {
pkiDebug("pkinit_get_client_cert: no identity found\n");
ortn = errSecItemNotFound;
goto errOut;
}
if(CFGetTypeID(issuerSerial) != CFDataGetTypeID()) {
pkiDebug("pkinit_get_client_cert: bad kPkinitClientCertKey value\n");
ortn = errSecItemNotFound;
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("pkinit_get_client_cert: no identity found!\n");
pkiCssmErr("pkinit_search_ident", ortn);
}
else {
*client_cert = (pkinit_signing_cert_t)idRef;
}
errOut:
if(theDict) {
CFRelease(theDict);
}
return ortn;
}
krb5_error_code pkinit_set_client_cert(
const char *principal, pkinit_signing_cert_t client_cert)
{
SecIdentityRef idRef = (SecIdentityRef)client_cert;
OSStatus ortn;
CSSM_DATA issuerSerial = {0, NULL};
CFDataRef cfIssuerSerial = NULL;
CFDictionaryRef existDict = NULL;
CFMutableDictionaryRef newDict = NULL;
SecCertificateRef certRef = NULL;
CFStringRef keyStr = NULL;
if(idRef != NULL) {
if(CFGetTypeID(idRef) != SecIdentityGetTypeID()) {
return paramErr;
}
ortn = SecIdentityCopyCertificate(idRef, &certRef);
if(ortn) {
pkiCssmErr("SecIdentityCopyCertificate", ortn);
return ortn;
}
ortn = pkinit_get_cert_issuer_sn(certRef, &issuerSerial);
if(ortn) {
goto errOut;
}
}
ortn = pkinit_get_pref_dict(&existDict);
if(ortn == noErr) {
newDict = CFDictionaryCreateMutableCopy(NULL, 0, existDict);
}
else {
if(idRef == NULL) {
return noErr;
}
newDict = CFDictionaryCreateMutable(NULL, 0,
&kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
}
if(newDict == NULL) {
ortn = ENOMEM;
goto errOut;
}
keyStr = CFStringCreateWithCString(NULL, principal, kCFStringEncodingASCII);
if(idRef == 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)) {
ortn = noErr;
}
else {
ortn = wrPermErr;
}
errOut:
if(certRef) {
CFRelease(certRef);
}
if(cfIssuerSerial) {
CFRelease(cfIssuerSerial);
}
if(issuerSerial.Data) {
free(issuerSerial.Data);
}
if(existDict) {
CFRelease(existDict);
}
if(newDict) {
CFRelease(newDict);
}
if(keyStr) {
CFRelease(keyStr);
}
return ortn;
}
krb5_error_code pkinit_get_client_cert_db(
const char *principal, pkinit_signing_cert_t client_cert, pkinit_cert_db_t *client_cert_db) {
krb5_error_code krtn;
pkinit_signing_cert_t local_cert;
assert((client_cert != NULL) || (principal != NULL));
if(client_cert == NULL) {
krtn = 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) {
pkinit_release_cert(local_cert);
}
return krtn;
}
#pragma mark --- Public server side functions ---
#define KDC_KEYCHAIN_MANUAL_UNLOCK 0
#if KDC_KEYCHAIN_MANUAL_UNLOCK
#define KDC_KC_PWD "password"
#endif
krb5_error_code pkinit_get_kdc_cert(
pkinit_signing_cert_t *kdc_cert)
{
SecIdentityRef idRef = NULL;
SecKeychainRef kcRef = NULL;
OSStatus ortn;
ortn = SecKeychainOpen(KDC_KEYCHAIN, &kcRef);
if(ortn) {
pkiCssmErr("SecKeychainOpen", ortn);
return ortn;
}
#if KDC_KEYCHAIN_MANUAL_UNLOCK
SecKeychainUnlock(kcRef, strlen(KDC_KC_PWD), KDC_KC_PWD, TRUE);
#endif
ortn = pkinit_search_ident(kcRef, CSSM_KEYUSE_SIGN, NULL, &idRef);
if(ortn) {
pkiDebug("pkinit_get_kdc_cert: no identity found!\n");
pkiCssmErr("pkinit_search_ident", ortn);
}
else {
*kdc_cert = (pkinit_signing_cert_t)idRef;
}
CFRelease(kcRef);
return ortn;
}
krb5_error_code pkinit_get_kdc_cert_db(
pkinit_cert_db_t *kdc_cert_db)
{
pkinit_signing_cert_t kdcCert = NULL;
krb5_error_code krtn;
krtn = pkinit_get_kdc_cert(&kdcCert);
if(krtn) {
return krtn;
}
krtn = pkinit_cert_to_db(kdcCert, kdc_cert_db);
pkinit_release_cert(kdcCert);
return krtn;
}
void pkinit_release_cert(
pkinit_signing_cert_t cert)
{
if(cert == NULL) {
return;
}
CFRelease((CFTypeRef)cert);
}
extern void pkinit_release_cert_db(
pkinit_cert_db_t cert_db)
{
if(cert_db == NULL) {
return;
}
CFRelease((CFTypeRef)cert_db);
}
char *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;
}