From: Alexandra Ellwood Date: Thu, 26 Jun 2008 18:07:47 +0000 (+0000) Subject: Apple PKINIT LKDC support X-Git-Tag: krb5-1.7-alpha1~633 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=493428532cb113f28ca8f1a5def7f2a6cee78f5e;p=krb5.git Apple PKINIT LKDC support ticket: 5968 status: open git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@20476 dc483132-0cff-0310-8789-dd5450dbe970 --- diff --git a/src/include/pkinit_cert_store.h b/src/include/pkinit_cert_store.h index 1e848d7b5..6811d5a72 100644 --- a/src/include/pkinit_cert_store.h +++ b/src/include/pkinit_cert_store.h @@ -44,6 +44,12 @@ extern "C" { */ typedef void *krb5_pkinit_signing_cert_t; +/* + * Opaque reference to a machine-dependent representation of a certificate. + * On Mac OS X this is actually a SecCertificateRef. + */ +typedef void *krb5_pkinit_cert_t; + /* * Opaque reference to a database in which PKINIT-related certificates are stored. */ @@ -72,9 +78,12 @@ krb5_boolean krb5_pkinit_have_client_cert( * in NULL for the client_cert has the effect of deleting the relevant entry * in the cert storage. */ -krb5_error_code krb5_pkinit_set_client_cert( +krb5_error_code krb5_pkinit_set_client_cert_from_signing_cert( const char *principal, /* full principal string */ krb5_pkinit_signing_cert_t client_cert); +krb5_error_code krb5_pkinit_set_client_cert( + const char *principal, /* full principal string */ + krb5_pkinit_cert_t client_cert); /* * Obtain a reference to the client's cert database. Specify either principal diff --git a/src/lib/krb5/krb/pkinit_apple_cert_store.c b/src/lib/krb5/krb/pkinit_apple_cert_store.c index 06ac37d06..0b5420b0c 100644 --- a/src/lib/krb5/krb/pkinit_apple_cert_store.c +++ b/src/lib/krb5/krb/pkinit_apple_cert_store.c @@ -335,36 +335,64 @@ krb5_boolean krb5_pkinit_have_client_cert( /* * Store the specified certificate (or, more likely, some platform-dependent - * reference to it) as the specified principal's signing cert. Passing + * reference to it) as the specified principal's signing certificate. Passing * in NULL for the client_cert has the effect of deleting the relevant entry * in the cert storage. */ -krb5_error_code krb5_pkinit_set_client_cert( +krb5_error_code krb5_pkinit_set_client_cert_from_signing_cert( const char *principal, /* full principal string */ 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; + } + /* Get the cert */ + 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; +} + + +/* + * Store the specified certificate (or, more likely, some platform-dependent + * reference to it) as the specified principal's certificate. Passing + * in NULL for the client_cert has the effect of deleting the relevant entry + * in the cert storage. + */ +krb5_error_code krb5_pkinit_set_client_cert( + const char *principal, /* full principal string */ + 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; - SecCertificateRef certRef = NULL; CFStringRef keyStr = NULL; krb5_error_code ourRtn = 0; - if(idRef != NULL) { - if(CFGetTypeID(idRef) != SecIdentityGetTypeID()) { + if(certRef != NULL) { + if(CFGetTypeID(certRef) != SecCertificateGetTypeID()) { return KRB5KRB_ERR_GENERIC; } - /* Get the cert */ - ortn = SecIdentityCopyCertificate(idRef, &certRef); - if(ortn) { - pkiCssmErr("SecIdentityCopyCertificate", ortn); - return KRB5KRB_ERR_GENERIC; - } - /* Cook up DER-encoded issuer/serial number */ ortn = pkinit_get_cert_issuer_sn(certRef, &issuerSerial); if(ortn) { @@ -383,7 +411,7 @@ krb5_error_code krb5_pkinit_set_client_cert( newDict = CFDictionaryCreateMutableCopy(NULL, 0, existDict); } else { - if(idRef == NULL) { + if(certRef == NULL) { /* no existing entry, nothing to delete, we're done */ return 0; } @@ -397,7 +425,7 @@ krb5_error_code krb5_pkinit_set_client_cert( /* issuer / serial number ==> that dictionary */ keyStr = CFStringCreateWithCString(NULL, principal, kCFStringEncodingASCII); - if(idRef == NULL) { + if(certRef == NULL) { CFDictionaryRemoveValue(newDict, keyStr); } else { @@ -416,9 +444,6 @@ krb5_error_code krb5_pkinit_set_client_cert( ourRtn = EACCES; /* any better ideas? */ } errOut: - if(certRef) { - CFRelease(certRef); - } if(cfIssuerSerial) { CFRelease(cfIssuerSerial); } diff --git a/src/lib/krb5/krb/preauth2.c b/src/lib/krb5/krb/preauth2.c index 96df2db04..32beb47a7 100644 --- a/src/lib/krb5/krb/preauth2.c +++ b/src/lib/krb5/krb/preauth2.c @@ -1146,6 +1146,40 @@ cleanup: } +/* If and only if the realm is that of a Local KDC, accept + * the KDC certificate as valid if its hash matches the + * realm. + */ +static krb5_boolean local_kdc_cert_match( + krb5_context context, + krb5_data *signer_cert, + krb5_principal client) +{ + static const char lkdcprefix[] = "LKDC:SHA1."; + krb5_boolean match = FALSE; + size_t cert_hash_len; + char *cert_hash; + const char *realm_hash; + size_t realm_hash_len; + + if (client->realm.length <= sizeof(lkdcprefix) || + 0 != memcmp(lkdcprefix, client->realm.data, sizeof(lkdcprefix)-1)) + return match; + realm_hash = &client->realm.data[sizeof(lkdcprefix)-1]; + realm_hash_len = client->realm.length - sizeof(lkdcprefix) + 1; + kdcPkinitDebug("checking realm versus certificate hash\n"); + if (NULL != (cert_hash = krb5_pkinit_cert_hash_str(signer_cert))) { + kdcPkinitDebug("hash = %s\n", cert_hash); + cert_hash_len = strlen(cert_hash); + if (cert_hash_len == realm_hash_len && + 0 == memcmp(cert_hash, realm_hash, cert_hash_len)) + match = TRUE; + free(cert_hash); + } + kdcPkinitDebug("result: %s\n", match ? "matches" : "does not match"); + return match; +} + static krb5_error_code pa_pkinit_parse_rep( krb5_context context, krb5_kdc_req *request, @@ -1169,7 +1203,8 @@ static krb5_error_code pa_pkinit_parse_rep( krb5_checksum as_req_checksum_rcd = {0}; /* received checksum */ krb5_checksum as_req_checksum_gen = {0}; /* calculated checksum */ krb5_data *encoded_as_req = NULL; - + krb5_data signer_cert = {0}; + *out_padata = NULL; kdcPkinitDebug("pa_pkinit_parse_rep\n"); if((in_padata == NULL) || (in_padata->length== 0)) { @@ -1198,8 +1233,7 @@ static krb5_error_code pa_pkinit_parse_rep( asRep.length = in_padata->length; krtn = krb5int_pkinit_as_rep_parse(context, &asRep, client_cert, &local_key, &as_req_checksum_rcd, &sig_status, - /* don't care about returned certs - do we? */ - NULL, NULL, NULL); + &signer_cert, NULL, NULL); if(krtn) { kdcPkinitDebug("pkinit_as_rep_parse returned %d\n", (int)krtn); return krtn; @@ -1207,6 +1241,10 @@ static krb5_error_code pa_pkinit_parse_rep( switch(sig_status) { case pki_cs_good: break; + case pki_cs_unknown_root: + if (local_kdc_cert_match(context, &signer_cert, request->client)) + break; + /* FALLTHROUGH */ default: kdcPkinitDebug("pa_pkinit_parse_rep: bad cert/sig status %d\n", (int)sig_status); @@ -1252,6 +1290,9 @@ static krb5_error_code pa_pkinit_parse_rep( krtn = 0; error_out: + if (signer_cert.data) { + free(signer_cert.data); + } if(as_req_checksum_rcd.contents) { free(as_req_checksum_rcd.contents); }