aboutsummaryrefslogtreecommitdiff
path: root/src/kdc/kdc_preauth.c
diff options
context:
space:
mode:
authorAlexandra Ellwood <lxs@mit.edu>2008-05-30 20:47:03 +0000
committerAlexandra Ellwood <lxs@mit.edu>2008-05-30 20:47:03 +0000
commit7ee1ef1a8a7d8424faa3cf7df88b184b0f911b3a (patch)
tree7cbcc11e0de0af794f9c2f16d03a6b1505e20a1d /src/kdc/kdc_preauth.c
parent8505824cad8ed0b6e8b96a5103cd43373c266996 (diff)
downloadkrb5-7ee1ef1a8a7d8424faa3cf7df88b184b0f911b3a.zip
krb5-7ee1ef1a8a7d8424faa3cf7df88b184b0f911b3a.tar.gz
krb5-7ee1ef1a8a7d8424faa3cf7df88b184b0f911b3a.tar.bz2
Apple PKINIT patch commit
Commit of Apple PKINIT patches under "APPLE_PKINIT" preprocessor symbol. Long term goal is to merge these patches with the pkinit preauth plugin which does not currently have support for Mac OS X crypto libraries or the exported functions used by Back To My Mac. ticket: new status: open git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@20346 dc483132-0cff-0310-8789-dd5450dbe970
Diffstat (limited to 'src/kdc/kdc_preauth.c')
-rw-r--r--src/kdc/kdc_preauth.c502
1 files changed, 502 insertions, 0 deletions
diff --git a/src/kdc/kdc_preauth.c b/src/kdc/kdc_preauth.c
index 67764b2..a250cf4 100644
--- a/src/kdc/kdc_preauth.c
+++ b/src/kdc/kdc_preauth.c
@@ -57,6 +57,11 @@
#include "extern.h"
#include <stdio.h>
#include "adm_proto.h"
+#if APPLE_PKINIT
+#include "pkinit_server.h"
+#include "pkinit_cert_store.h"
+#endif /* APPLE_PKINIT */
+
#include <syslog.h>
#include <assert.h>
@@ -188,7 +193,58 @@ static krb5_error_code return_sam_data
void *pa_module_context,
void **pa_request_context);
+#if APPLE_PKINIT
+/* PKINIT preauth support */
+static krb5_error_code get_pkinit_edata(
+ krb5_context context,
+ krb5_kdc_req *request,
+ krb5_db_entry *client,
+ krb5_db_entry *server,
+ preauth_get_entry_data_proc get_entry_data,
+ void *pa_module_context,
+ krb5_pa_data *pa_data);
+static krb5_error_code verify_pkinit_request(
+ krb5_context context,
+ krb5_db_entry *client,
+ krb5_data *req_pkt,
+ krb5_kdc_req *request,
+ krb5_enc_tkt_part *enc_tkt_reply,
+ krb5_pa_data *data,
+ preauth_get_entry_data_proc get_entry_data,
+ void *pa_module_context,
+ void **pa_request_context,
+ krb5_data **e_data,
+ krb5_authdata ***authz_data);
+static krb5_error_code return_pkinit_response(
+ krb5_context context,
+ krb5_pa_data * padata,
+ krb5_db_entry *client,
+ krb5_data *req_pkt,
+ krb5_kdc_req *request,
+ krb5_kdc_rep *reply,
+ krb5_key_data *client_key,
+ krb5_keyblock *encrypting_key,
+ krb5_pa_data **send_pa,
+ preauth_get_entry_data_proc get_entry_data,
+ void *pa_module_context,
+ void **pa_request_context);
+#endif /* APPLE_PKINIT */
+
static krb5_preauth_systems static_preauth_systems[] = {
+#if APPLE_PKINIT
+ {
+ "pkinit",
+ KRB5_PADATA_PK_AS_REQ,
+ PA_SUFFICIENT,
+ NULL, // pa_sys_context
+ NULL, // init
+ NULL, // fini
+ get_pkinit_edata,
+ verify_pkinit_request,
+ return_pkinit_response,
+ NULL // free_pa_request_context
+ },
+#endif /* APPLE_PKINIT */
{
"timestamp",
KRB5_PADATA_ENC_TIMESTAMP,
@@ -2350,3 +2406,449 @@ verify_sam_response(krb5_context context, krb5_db_entry *client,
return retval;
}
+
+#if APPLE_PKINIT
+/* PKINIT preauth support */
+#define PKINIT_DEBUG 0
+#if PKINIT_DEBUG
+#define kdcPkinitDebug(args...) printf(args)
+#else
+#define kdcPkinitDebug(args...)
+#endif
+
+/*
+ * get_edata() - our only job is to determine whether this KDC is capable of
+ * performing PKINIT. We infer that from the presence or absence of any
+ * KDC signing cert.
+ */
+static krb5_error_code get_pkinit_edata(
+ krb5_context context,
+ krb5_kdc_req *request,
+ krb5_db_entry *client,
+ krb5_db_entry *server,
+ preauth_get_entry_data_proc pkinit_get_entry_data,
+ void *pa_module_context,
+ krb5_pa_data *pa_data)
+{
+ krb5_pkinit_signing_cert_t cert = NULL;
+ krb5_error_code err = krb5_pkinit_get_kdc_cert(0, NULL, NULL, &cert);
+
+ kdcPkinitDebug("get_pkinit_edata: kdc cert %s\n", err ? "NOT FOUND" : "FOUND");
+ if(cert) {
+ krb5_pkinit_release_cert(cert);
+ }
+ return err;
+}
+
+/*
+ * This is 0 only for testing until the KDC DB contains
+ * the hash of the client cert
+ */
+#define REQUIRE_CLIENT_CERT_MATCH 1
+
+static krb5_error_code verify_pkinit_request(
+ krb5_context context,
+ krb5_db_entry *client,
+ krb5_data *req_pkt,
+ krb5_kdc_req *request,
+ krb5_enc_tkt_part *enc_tkt_reply,
+ krb5_pa_data *data,
+ preauth_get_entry_data_proc pkinit_get_entry_data,
+ void *pa_module_context,
+ void **pa_request_context,
+ krb5_data **e_data,
+ krb5_authdata ***authz_data)
+{
+ krb5_error_code krtn;
+ krb5_data pa_data;
+ krb5_data *der_req = NULL;
+ krb5_boolean valid_cksum;
+ char *cert_hash = NULL;
+ unsigned cert_hash_len;
+ unsigned key_dex;
+ unsigned cert_match = 0;
+ krb5_keyblock decrypted_key;
+
+ /* the data we get from the AS-REQ */
+ krb5_timestamp client_ctime = 0;
+ krb5_ui_4 client_cusec = 0;
+ krb5_timestamp kdc_ctime = 0;
+ krb5_int32 kdc_cusec = 0;
+ krb5_ui_4 nonce = 0;
+ krb5_checksum pa_cksum;
+ krb5int_cert_sig_status cert_sig_status;
+ krb5_data client_cert = {0, 0, NULL};
+
+ krb5_kdc_req *tmp_as_req = NULL;
+
+ kdcPkinitDebug("verify_pkinit_request\n");
+
+ decrypted_key.contents = NULL;
+ pa_data.data = (char *)data->contents;
+ pa_data.length = data->length;
+ krtn = krb5int_pkinit_as_req_parse(context, &pa_data,
+ &client_ctime, &client_cusec,
+ &nonce, &pa_cksum,
+ &cert_sig_status,
+ NULL, NULL, /* num_cms_types, cms_types */
+ &client_cert, /* signer_cert */
+ /* remaining fields unused (for now) */
+ NULL, NULL, /* num_all_certs, all_certs */
+ NULL, NULL, /* num_trusted_CAs, trusted_CAs */
+ NULL); /* kdc_cert */
+ if(krtn) {
+ kdcPkinitDebug("pa_pk_as_req_parse returned %d; PKINIT aborting.\n",
+ (int)krtn);
+ return krtn;
+ }
+ #if PKINIT_DEBUG
+ if(cert_sig_status != pki_cs_good) {
+ kdcPkinitDebug("verify_pkinit_request: cert_sig_status %d\n",
+ (int)cert_sig_status);
+ }
+ #endif /* PKINIT_DEBUG */
+
+ /*
+ * Verify signature and cert.
+ * FIXME: The spec calls for an e-data with error-specific type to be
+ * returned on error here. TD_TRUSTED_CERTIFIERS
+ * to be returned to the client here. There is no way for a preauth
+ * module to pass back e-data to process_as_req at this time. We
+ * might want to add such capability via an out param to check_padata
+ * and to its callees.
+ */
+ switch(cert_sig_status) {
+ case pki_cs_good:
+ break;
+ case pki_cs_sig_verify_fail:
+ /* no e-data */
+ krtn = KDC_ERR_INVALID_SIG;
+ goto cleanup;
+ case pki_cs_no_root:
+ case pki_cs_unknown_root:
+ case pki_cs_untrusted:
+ /*
+ * Can't verify to known root.
+ * e-data TD_TRUSTED_CERTIFIERS
+ */
+ kdcPkinitDebug("verify_pkinit_request: KDC_ERR_CANT_VERIFY_CERTIFICATE\n");
+ krtn = KDC_ERR_CANT_VERIFY_CERTIFICATE;
+ goto cleanup;
+ case pki_cs_bad_leaf:
+ case pki_cs_expired:
+ case pki_cs_not_valid_yet:
+ /*
+ * Problems with client cert itself.
+ * e-data type TD_INVALID_CERTIFICATES
+ */
+ krtn = KDC_ERR_INVALID_CERTIFICATE;
+ goto cleanup;
+ case pki_cs_revoked:
+ /* e-data type TD-INVALID-CERTIFICATES */
+ krtn = KDC_ERR_REVOKED_CERTIFICATE;
+ goto cleanup;
+ case pki_bad_key_use:
+ krtn = KDC_ERR_INCONSISTENT_KEY_PURPOSE;
+ /* no e-data */
+ goto cleanup;
+ case pki_bad_digest:
+ /* undefined (explicitly!) e-data */
+ krtn = KDC_ERR_DIGEST_IN_SIGNED_DATA_NOT_ACCEPTED;
+ goto cleanup;
+ case pki_bad_cms:
+ case pki_cs_other_err:
+ default:
+ krtn = KRB5KDC_ERR_PREAUTH_FAILED;
+ goto cleanup;
+ }
+
+ krtn = krb5_us_timeofday(context, &kdc_ctime, &kdc_cusec);
+ if(krtn) {
+ goto cleanup;
+ }
+ if (labs(kdc_ctime - client_ctime) > context->clockskew) {
+ kdcPkinitDebug("verify_pkinit_request: clock skew violation client %d svr %d\n",
+ (int)client_ctime, (int)kdc_ctime);
+ krtn = KRB5KRB_AP_ERR_SKEW;
+ goto cleanup;
+ }
+
+ /*
+ * The KDC may have modified the request after decoding it.
+ * We need to compute the checksum on the data that
+ * came from the client. Therefore, we use the original
+ * packet contents.
+ */
+ krtn = decode_krb5_as_req(req_pkt, &tmp_as_req);
+ if(krtn) {
+ kdcPkinitDebug("decode_krb5_as_req returned %d\n", (int)krtn);
+ goto cleanup;
+ }
+
+ /* calculate and compare checksum */
+ krtn = encode_krb5_kdc_req_body(tmp_as_req, &der_req);
+ if(krtn) {
+ kdcPkinitDebug("encode_krb5_kdc_req_body returned %d\n", (int)krtn);
+ goto cleanup;
+ }
+ krtn = krb5_c_verify_checksum(context, NULL, 0, der_req,
+ &pa_cksum, &valid_cksum);
+ if(krtn) {
+ kdcPkinitDebug("krb5_c_verify_checksum returned %d\n", (int)krtn);
+ goto cleanup;
+ }
+ if(!valid_cksum) {
+ kdcPkinitDebug("verify_pkinit_request: checksum error\n");
+ krtn = KRB5KRB_AP_ERR_BAD_INTEGRITY;
+ goto cleanup;
+ }
+
+ #if REQUIRE_CLIENT_CERT_MATCH
+ /* look up in the KDB to ensure correct client/cert binding */
+ cert_hash = krb5_pkinit_cert_hash_str(&client_cert);
+ if(cert_hash == NULL) {
+ krtn = ENOMEM;
+ goto cleanup;
+ }
+ cert_hash_len = strlen(cert_hash);
+ for(key_dex=0; key_dex<client->n_key_data; key_dex++) {
+ krb5_key_data *key_data = &client->key_data[key_dex];
+ kdcPkinitDebug("--- key %u type[0] %u length[0] %u type[1] %u length[1] %u\n",
+ key_dex,
+ key_data->key_data_type[0], key_data->key_data_length[0],
+ key_data->key_data_type[1], key_data->key_data_length[1]);
+ if(key_data->key_data_type[1] != KRB5_KDB_SALTTYPE_CERTHASH) {
+ continue;
+ }
+
+ /*
+ * Unfortunately this key is stored encrypted even though it's
+ * not sensitive...
+ */
+ krtn = krb5_dbekd_decrypt_key_data(context, &master_keyblock,
+ key_data, &decrypted_key, NULL);
+ if(krtn) {
+ kdcPkinitDebug("verify_pkinit_request: error decrypting cert hash block\n");
+ break;
+ }
+ if((decrypted_key.contents != NULL) &&
+ (cert_hash_len == decrypted_key.length) &&
+ !memcmp(decrypted_key.contents, cert_hash, cert_hash_len)) {
+ cert_match = 1;
+ break;
+ }
+ }
+ if(decrypted_key.contents) {
+ krb5_free_keyblock_contents(context, &decrypted_key);
+ }
+ if(!cert_match) {
+ kdcPkinitDebug("verify_pkinit_request: client cert does not match\n");
+ krtn = KDC_ERR_CLIENT_NOT_TRUSTED;
+ goto cleanup;
+ }
+ #endif /* REQUIRE_CLIENT_CERT_MATCH */
+ krtn = 0;
+ setflag(enc_tkt_reply->flags, TKT_FLG_PRE_AUTH);
+
+cleanup:
+ if(pa_cksum.contents) {
+ free(pa_cksum.contents);
+ }
+ if (tmp_as_req) {
+ krb5_free_kdc_req(context, tmp_as_req);
+ }
+ if (der_req) {
+ krb5_free_data(context, der_req);
+ }
+ if(cert_hash) {
+ free(cert_hash);
+ }
+ if(client_cert.data) {
+ free(client_cert.data);
+ }
+ kdcPkinitDebug("verify_pkinit_request: returning %d\n", (int)krtn);
+ return krtn;
+}
+
+static krb5_error_code return_pkinit_response(
+ krb5_context context,
+ krb5_pa_data * padata,
+ krb5_db_entry *client,
+ krb5_data *req_pkt,
+ krb5_kdc_req *request,
+ krb5_kdc_rep *reply,
+ krb5_key_data *client_key,
+ krb5_keyblock *encrypting_key,
+ krb5_pa_data **send_pa,
+ preauth_get_entry_data_proc pkinit_get_entry_data,
+ void *pa_module_context,
+ void **pa_request_context)
+{
+ krb5_error_code krtn;
+ krb5_data pa_data;
+ krb5_pkinit_signing_cert_t signing_cert = NULL;
+ krb5_checksum as_req_checksum = {0};
+ krb5_data *encoded_as_req = NULL;
+ krb5int_algorithm_id *cms_types = NULL;
+ krb5_ui_4 num_cms_types = 0;
+
+ /* the data we get from the AS-REQ */
+ krb5_ui_4 nonce = 0;
+ krb5_data client_cert = {0};
+
+ /*
+ * Trusted CA list and specific KC cert optionally obtained via
+ * krb5int_pkinit_as_req_parse(). All are DER-encoded
+ * issuerAndSerialNumbers.
+ */
+ krb5_data *trusted_CAs = NULL;
+ krb5_ui_4 num_trusted_CAs;
+ krb5_data kdc_cert = {0};
+
+ if (padata == NULL) {
+ /* Client has to send us something */
+ return 0;
+ }
+
+ kdcPkinitDebug("return_pkinit_response\n");
+ pa_data.data = (char *)padata->contents;
+ pa_data.length = padata->length;
+
+ /*
+ * We've already verified; just obtain the fields we need to create a response
+ */
+ krtn = krb5int_pkinit_as_req_parse(context,
+ &pa_data,
+ NULL, NULL, &nonce, /* ctime, cusec, nonce */
+ NULL, NULL, /* pa_cksum, cert_status */
+ &num_cms_types, &cms_types,
+ &client_cert, /* signer_cert: we encrypt for this */
+ /* remaining fields unused (for now) */
+ NULL, NULL, /* num_all_certs, all_certs */
+ &num_trusted_CAs, &trusted_CAs,
+ &kdc_cert);
+ if(krtn) {
+ kdcPkinitDebug("pa_pk_as_req_parse returned %d; PKINIT aborting.\n", (int)krtn);
+ goto cleanup;
+ }
+ if(client_cert.data == NULL) {
+ kdcPkinitDebug("pa_pk_as_req_parse failed to give a client_cert; aborting.\n");
+ krtn = KRB5KDC_ERR_PREAUTH_FAILED;
+ goto cleanup;
+ }
+
+ if(krb5_pkinit_get_kdc_cert(num_trusted_CAs, trusted_CAs,
+ (kdc_cert.data ? &kdc_cert : NULL),
+ &signing_cert)) {
+ /*
+ * Since get_pkinit_edata was able to obtain *some* KDC cert,
+ * this means that we can't satisfy the client's requirement.
+ * FIXME - particular error status for this?
+ */
+ kdcPkinitDebug("return_pkinit_response: NO appropriate signing cert!\n");
+ krtn = KRB5KDC_ERR_PREAUTH_FAILED;
+ goto cleanup;
+ }
+
+ /*
+ * Cook up keyblock for caller and for outgoing AS-REP.
+ * FIXME how much is known to be valid about encrypting_key?
+ * Will encrypting_key->enctype always be valid here? Seems that
+ * if we allow for clients without a shared secret (i.e. preauth
+ * by PKINIT only) there won't be a valid encrypting_key set up
+ * here for us.
+ */
+ krb5_free_keyblock_contents(context, encrypting_key);
+ krb5_c_make_random_key(context, encrypting_key->enctype, encrypting_key);
+
+ /* calculate checksum of incoming AS-REQ */
+ krtn = encode_krb5_as_req(request, &encoded_as_req);
+ if(krtn) {
+ kdcPkinitDebug("encode_krb5_as_req returned %d; PKINIT aborting.\n", (int)krtn);
+ goto cleanup;
+ }
+ krtn = krb5_c_make_checksum(context, context->kdc_req_sumtype,
+ encrypting_key, KRB5_KEYUSAGE_TGS_REQ_AUTH_CKSUM,
+ encoded_as_req, &as_req_checksum);
+ if(krtn) {
+ goto cleanup;
+ }
+
+ /*
+ * FIXME: here we assume that the client has one cert - the one that
+ * signed the AuthPack in the request (and that we therefore obtained from
+ * krb5int_pkinit_as_req_parse()), and the one we're using to encrypt the
+ * ReplyKeyPack with here. This may need rethinking.
+ */
+ krtn = krb5int_pkinit_as_rep_create(context,
+ encrypting_key, &as_req_checksum, signing_cert, TRUE,
+ &client_cert,
+ num_cms_types, cms_types,
+ num_trusted_CAs, trusted_CAs,
+ (kdc_cert.data ? &kdc_cert : NULL),
+ &pa_data);
+ if(krtn) {
+ kdcPkinitDebug("pa_pk_as_rep_create returned %d; PKINIT aborting.\n", (int)krtn);
+ goto cleanup;
+ }
+
+ *send_pa = (krb5_pa_data *)malloc(sizeof(krb5_pa_data));
+ if(*send_pa == NULL) {
+ krtn = ENOMEM;
+ free(pa_data.data);
+ goto cleanup;
+ }
+ (*send_pa)->magic = KV5M_PA_DATA;
+ (*send_pa)->pa_type = KRB5_PADATA_PK_AS_REP;
+ (*send_pa)->length = pa_data.length;
+ (*send_pa)->contents = (krb5_octet *)pa_data.data;
+ krtn = 0;
+
+ #if PKINIT_DEBUG
+ fprintf(stderr, "return_pkinit_response: SUCCESS\n");
+ fprintf(stderr, "nonce 0x%x enctype %d keydata %02x %02x %02x %02x...\n",
+ (int)nonce, (int)encrypting_key->enctype,
+ encrypting_key->contents[0], encrypting_key->contents[1],
+ encrypting_key->contents[2], encrypting_key->contents[3]);
+ #endif
+
+cleanup:
+ /* all of this was allocd by krb5int_pkinit_as_req_parse() */
+ if(signing_cert) {
+ krb5_pkinit_release_cert(signing_cert);
+ }
+ if(cms_types) {
+ unsigned dex;
+ krb5int_algorithm_id *alg_id;
+
+ for(dex=0; dex<num_cms_types; dex++) {
+ alg_id = &cms_types[dex];
+ if(alg_id->algorithm.data) {
+ free(alg_id->algorithm.data);
+ }
+ if(alg_id->parameters.data) {
+ free(alg_id->parameters.data);
+ }
+ }
+ free(cms_types);
+ }
+ if(trusted_CAs) {
+ unsigned dex;
+ for(dex=0; dex<num_trusted_CAs; dex++) {
+ free(trusted_CAs[dex].data);
+ }
+ free(trusted_CAs);
+ }
+ if(kdc_cert.data) {
+ free(kdc_cert.data);
+ }
+ if(client_cert.data) {
+ free(client_cert.data);
+ }
+ if(encoded_as_req) {
+ krb5_free_data(context, encoded_as_req);
+ }
+ return krtn;
+}
+
+#endif /* APPLE_PKINIT */