*/
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.
*/
* 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
/*
* 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) {
newDict = CFDictionaryCreateMutableCopy(NULL, 0, existDict);
}
else {
- if(idRef == NULL) {
+ if(certRef == NULL) {
/* no existing entry, nothing to delete, we're done */
return 0;
}
/* issuer / serial number ==> that dictionary */
keyStr = CFStringCreateWithCString(NULL, principal, kCFStringEncodingASCII);
- if(idRef == NULL) {
+ if(certRef == NULL) {
CFDictionaryRemoveValue(newDict, keyStr);
}
else {
ourRtn = EACCES; /* any better ideas? */
}
errOut:
- if(certRef) {
- CFRelease(certRef);
- }
if(cfIssuerSerial) {
CFRelease(cfIssuerSerial);
}
}
+/* 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,
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)) {
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;
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);
krtn = 0;
error_out:
+ if (signer_cert.data) {
+ free(signer_cert.data);
+ }
if(as_req_checksum_rcd.contents) {
free(as_req_checksum_rcd.contents);
}