Apple PKINIT LKDC support
authorAlexandra Ellwood <lxs@mit.edu>
Thu, 26 Jun 2008 18:07:47 +0000 (18:07 +0000)
committerAlexandra Ellwood <lxs@mit.edu>
Thu, 26 Jun 2008 18:07:47 +0000 (18:07 +0000)
ticket: 5968
status: open

git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@20476 dc483132-0cff-0310-8789-dd5450dbe970

src/include/pkinit_cert_store.h
src/lib/krb5/krb/pkinit_apple_cert_store.c
src/lib/krb5/krb/preauth2.c

index 1e848d7b504059ba0b6bc751299b5f23de7e0ef2..6811d5a7226ea33a2dc992f39e23da0ca8b48878 100644 (file)
@@ -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
index 06ac37d062c2d69d0e8b6c5e1148125b73e20c21..0b5420b0ce139afbbfb58c13e38b08060fdfaeda 100644 (file)
@@ -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);
     }
index 96df2db0421a9f593091d6b4f0263c3ddcf6cd6e..32beb47a7f7df08822503fd569d7389c5ef72c01 100644 (file)
@@ -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);
     }