aboutsummaryrefslogtreecommitdiff
path: root/src/plugins/kdb/hdb/kdb_windc.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/kdb/hdb/kdb_windc.c')
-rw-r--r--src/plugins/kdb/hdb/kdb_windc.c615
1 files changed, 615 insertions, 0 deletions
diff --git a/src/plugins/kdb/hdb/kdb_windc.c b/src/plugins/kdb/hdb/kdb_windc.c
new file mode 100644
index 0000000..a419d29
--- /dev/null
+++ b/src/plugins/kdb/hdb/kdb_windc.c
@@ -0,0 +1,615 @@
+/* -*- mode: c; indent-tabs-mode: nil -*- */
+/*
+ * plugins/kdb/hdb/kdb_windc.c
+ *
+ * Copyright 2009 by the Massachusetts Institute of Technology.
+ * All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ *
+ */
+
+#include "k5-int.h"
+
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <db.h>
+#include <stdio.h>
+#include <errno.h>
+#include <utime.h>
+#include "kdb5.h"
+#include "kdb_hdb.h"
+
+/*
+ * WinDC helpers
+ */
+
+static krb5_error_code
+kh_windc_pac_generate(krb5_context context,
+ kh_db_context *kh,
+ struct hdb_entry_ex *hentry,
+ heim_pac *pac)
+{
+ if (kh->windc == NULL || kh->windc->pac_generate == NULL)
+ return KRB5_KDB_DBTYPE_NOSUP;
+
+ return kh_map_error((*kh->windc->pac_generate)(kh->windc_ctx,
+ kh->hcontext,
+ hentry,
+ pac));
+}
+
+static krb5_error_code
+kh_windc_pac_verify(krb5_context context,
+ kh_db_context *kh,
+ const Principal *principal,
+ struct hdb_entry_ex *client,
+ struct hdb_entry_ex *server,
+ heim_pac *pac)
+{
+ if (kh->windc == NULL || kh->windc->pac_verify == NULL)
+ return KRB5_KDB_DBTYPE_NOSUP;
+
+ return kh_map_error((*kh->windc->pac_verify)(kh->windc_ctx,
+ kh->hcontext,
+ principal,
+ client,
+ server,
+ pac));
+}
+
+static krb5_error_code
+kh_windc_client_access(krb5_context context,
+ kh_db_context *kh,
+ struct hdb_entry_ex *client,
+ KDC_REQ *req,
+ heim_octet_string *e_data)
+{
+ if (kh->windc == NULL || kh->windc->client_access == NULL)
+ return KRB5_KDB_DBTYPE_NOSUP;
+
+ return kh_map_error((*kh->windc->client_access)(kh->windc_ctx,
+ kh->hcontext,
+ client,
+ req,
+ e_data));
+}
+
+static void
+kh_pac_free(krb5_context context, heim_pac pac)
+{
+ kh_db_context *kh = KH_DB_CONTEXT(context);
+
+ assert(kh->heim_pac_free != NULL);
+
+ if (pac != NULL)
+ (*kh->heim_pac_free)(kh->hcontext, pac);
+}
+
+static krb5_error_code
+kh_pac_parse(krb5_context context,
+ const void *data,
+ size_t len,
+ heim_pac *pac)
+{
+ kh_db_context *kh = KH_DB_CONTEXT(context);
+
+ return kh_map_error((*kh->heim_pac_parse)(kh->hcontext,
+ data,
+ len,
+ pac));
+}
+
+static krb5_error_code
+kh_pac_verify(krb5_context context,
+ const heim_pac pac,
+ time_t authtime,
+ const Principal *princ,
+ const EncryptionKey *server,
+ const EncryptionKey *krbtgt)
+{
+ kh_db_context *kh = KH_DB_CONTEXT(context);
+
+ assert(kh->heim_pac_verify != NULL);
+
+ return kh_map_error((*kh->heim_pac_verify)(kh->hcontext,
+ pac,
+ authtime,
+ princ,
+ server,
+ krbtgt));
+}
+
+static krb5_error_code
+kh_pac_sign(krb5_context context,
+ heim_pac pac,
+ time_t authtime,
+ Principal *princ,
+ const EncryptionKey *server,
+ const EncryptionKey *krbtgt,
+ heim_octet_string *data)
+{
+ kh_db_context *kh = KH_DB_CONTEXT(context);
+
+ assert(kh->heim_pac_sign != NULL);
+
+ return kh_map_error((*kh->heim_pac_sign)(kh->hcontext,
+ pac,
+ authtime,
+ princ,
+ server,
+ krbtgt,
+ data));
+}
+
+/*
+ * Get local TGS key for the realm of the supplied principal.
+ */
+static krb5_error_code
+kh_get_tgs_key(krb5_context context,
+ kh_db_context *kh,
+ const krb5_principal princ,
+ krb5_keyblock *krbtgt_keyblock)
+{
+ krb5_error_code code;
+ krb5_principal tgsname = NULL;
+ krb5_key_data *krbtgt_key = NULL;
+ krb5_db_entry krbtgt;
+
+ memset(&krbtgt, 0, sizeof(krbtgt));
+ krbtgt_keyblock->contents = NULL;
+
+ code = krb5_build_principal_ext(context,
+ &tgsname,
+ princ->realm.length,
+ princ->realm.data,
+ KRB5_TGS_NAME_SIZE,
+ KRB5_TGS_NAME,
+ princ->realm.length,
+ princ->realm.data,
+ 0);
+ if (code != 0)
+ goto cleanup;
+
+ code = kh_get_principal(context, kh, tgsname, HDB_F_GET_KRBTGT, &krbtgt);
+ if (code != 0)
+ goto cleanup;
+
+ code = krb5_dbe_find_enctype(context,
+ &krbtgt,
+ -1, /* ignore enctype */
+ -1, /* ignore salttype */
+ 0, /* highest kvno */
+ &krbtgt_key);
+ if (code != 0)
+ goto cleanup;
+ else if (krbtgt_key == NULL) {
+ code = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
+ goto cleanup;
+ }
+
+ code = kh_decrypt_key(context,
+ KH_DB_CONTEXT(context),
+ krbtgt_key,
+ krbtgt_keyblock,
+ NULL);
+ if (code != 0)
+ goto cleanup;
+
+cleanup:
+ kh_kdb_free_entry(context, KH_DB_CONTEXT(context), &krbtgt);
+ krb5_free_principal(context, tgsname);
+
+ return code;
+}
+
+krb5_error_code
+kh_db_sign_auth_data(krb5_context context,
+ unsigned int method,
+ const krb5_data *req_data,
+ krb5_data *rep_data)
+{
+ kh_db_context *kh = KH_DB_CONTEXT(context);
+ kdb_sign_auth_data_req *req = (kdb_sign_auth_data_req *)req_data->data;
+ kdb_sign_auth_data_rep *rep = (kdb_sign_auth_data_rep *)rep_data->data;
+ heim_pac hpac = NULL;
+ heim_octet_string pac_data;
+ krb5_boolean is_as_req;
+ krb5_error_code code;
+ krb5_authdata **authdata = NULL;
+ Principal *client_hprinc = NULL;
+ EncryptionKey server_hkey;
+ EncryptionKey krbtgt_hkey;
+ krb5_keyblock krbtgt_kkey;
+
+ if (kh->windc == NULL)
+ return KRB5_KDB_DBTYPE_NOSUP; /* short circuit */
+
+ memset(rep, 0, sizeof(*rep));
+ memset(&krbtgt_kkey, 0, sizeof(krbtgt_kkey));
+ pac_data.data = NULL;
+
+ is_as_req = ((req->flags & KRB5_KDB_FLAG_CLIENT_REFERRALS_ONLY) != 0);
+
+ /* Prefer canonicalised name from client entry */
+ if (req->client != NULL) {
+ client_hprinc = KH_DB_ENTRY(req->client)->entry.principal;
+ } else {
+ code = kh_marshal_Principal(context, req->client_princ, &client_hprinc);
+ if (code != 0)
+ goto cleanup;
+ }
+
+ KH_MARSHAL_KEY(req->server_key, &server_hkey);
+ KH_MARSHAL_KEY(req->krbtgt_key, &krbtgt_hkey);
+
+ if (!is_as_req) {
+ /* find the existing PAC, if present */
+ code = krb5int_find_authdata(context,
+ req->auth_data,
+ NULL,
+ KRB5_AUTHDATA_WIN2K_PAC,
+ &authdata);
+ if (code != 0)
+ goto cleanup;
+ }
+
+ if ((is_as_req && (req->flags & KRB5_KDB_FLAG_INCLUDE_PAC)) ||
+ (authdata == NULL && req->client != NULL)) {
+ code = kh_windc_pac_generate(context, kh,
+ KH_DB_ENTRY(req->client), &hpac);
+ if (code != 0)
+ goto cleanup;
+ } else if (authdata != NULL) {
+ assert(authdata[0] != NULL);
+
+ if (authdata[1] != NULL) {
+ code = KRB5KDC_ERR_BADOPTION; /* XXX */
+ goto cleanup;
+ }
+
+ pac_data.data = authdata[0]->contents;
+ pac_data.length = authdata[0]->length;
+
+ code = kh_pac_parse(context, pac_data.data, pac_data.length, &hpac);
+ if (code != 0)
+ goto cleanup;
+
+ /*
+ * In the constrained delegation case, the PAC is from a service
+ * ticket rather than a TGT; we must verify the server and KDC
+ * signatures to assert that the server did not forge the PAC.
+ */
+ if (req->flags & KRB5_KDB_FLAG_CONSTRAINED_DELEGATION) {
+ code = kh_pac_verify(context, hpac, req->authtime,
+ client_hprinc, &server_hkey, &krbtgt_hkey);
+ } else {
+ code = kh_pac_verify(context, hpac, req->authtime,
+ client_hprinc, &krbtgt_hkey, NULL);
+ }
+ if (code != 0)
+ goto cleanup;
+
+ code = kh_windc_pac_verify(context, kh, client_hprinc,
+ req->client ?
+ KH_DB_ENTRY(req->client) : NULL,
+ KH_DB_ENTRY(req->server),
+ &hpac);
+ if (code != 0)
+ goto cleanup;
+ } else {
+ code = KRB5_KDB_DBTYPE_NOSUP;
+ goto cleanup;
+ }
+
+ /*
+ * In the cross-realm case, krbtgt_hkey refers to the cross-realm
+ * TGS key, so we need to explicitly lookup our TGS key.
+ */
+ if (req->flags & KRB5_KDB_FLAG_CROSS_REALM) {
+ assert(!is_as_req);
+
+ code = kh_get_tgs_key(context, kh, req->server->princ, &krbtgt_kkey);
+ if (code != 0)
+ goto cleanup;
+
+ KH_MARSHAL_KEY(&krbtgt_kkey, &krbtgt_hkey);
+ }
+
+ code = kh_pac_sign(context, hpac, req->authtime, client_hprinc,
+ &server_hkey, &krbtgt_hkey, &pac_data);
+ if (code != 0)
+ goto cleanup;
+
+ if (authdata == NULL) {
+ authdata = k5alloc(2 * sizeof(krb5_authdata *), &code);
+ if (code != 0)
+ goto cleanup;
+
+ authdata[0] = k5alloc(sizeof(krb5_authdata), &code);
+ if (code != 0)
+ goto cleanup;
+
+ authdata[1] = NULL;
+ } else {
+ free(authdata[0]->contents);
+ authdata[0]->contents = NULL;
+ authdata[0]->length = 0;
+ }
+
+ /* take ownership of pac_data */
+ authdata[0]->magic = KV5M_AUTHDATA;
+ authdata[0]->ad_type = KRB5_AUTHDATA_WIN2K_PAC;
+ authdata[0]->contents = pac_data.data;
+ authdata[0]->length = pac_data.length;
+
+ pac_data.data = NULL;
+
+ code = krb5_encode_authdata_container(context,
+ KRB5_AUTHDATA_IF_RELEVANT,
+ authdata,
+ &rep->auth_data);
+ if (code != 0)
+ goto cleanup;
+
+cleanup:
+ if (req->client == NULL)
+ kh_free_Principal(context, client_hprinc);
+ kh_pac_free(context, hpac);
+ if (pac_data.data != NULL)
+ free(pac_data.data);
+ krb5_free_authdata(context, authdata);
+ krb5_free_keyblock_contents(context, &krbtgt_kkey);
+
+ return code;
+}
+
+static krb5_error_code
+kh_marshal_KDCOptions(krb5_context context,
+ krb5_flags koptions,
+ KDCOptions *hoptions)
+{
+ memset(hoptions, 0, sizeof(*hoptions));
+
+ if (koptions & KDC_OPT_FORWARDABLE)
+ hoptions->forwardable = 1;
+ if (koptions & KDC_OPT_FORWARDED)
+ hoptions->forwarded = 1;
+ if (koptions & KDC_OPT_PROXIABLE)
+ hoptions->proxiable = 1;
+ if (koptions & KDC_OPT_PROXY)
+ hoptions->proxy = 1;
+ if (koptions & KDC_OPT_ALLOW_POSTDATE)
+ hoptions->allow_postdate = 1;
+ if (koptions & KDC_OPT_POSTDATED)
+ hoptions->postdated = 1;
+ if (koptions & KDC_OPT_RENEWABLE)
+ hoptions->renewable = 1;
+ if (koptions & KDC_OPT_REQUEST_ANONYMOUS)
+ hoptions->request_anonymous = 1;
+ if (koptions & KDC_OPT_CANONICALIZE)
+ hoptions->canonicalize = 1;
+ if (koptions & KDC_OPT_DISABLE_TRANSITED_CHECK)
+ hoptions->disable_transited_check = 1;
+ if (koptions & KDC_OPT_RENEWABLE_OK)
+ hoptions->renewable_ok = 1;
+ if (koptions & KDC_OPT_ENC_TKT_IN_SKEY)
+ hoptions->enc_tkt_in_skey = 1;
+ if (koptions & KDC_OPT_RENEW)
+ hoptions->renew = 1;
+ if (koptions & KDC_OPT_VALIDATE)
+ hoptions->validate = 1;
+
+ return 0;
+}
+
+static krb5_error_code
+kh_marshall_HostAddress(krb5_context context,
+ krb5_address *kaddress,
+ HostAddress *haddress)
+{
+ haddress->addr_type = kaddress->addrtype;
+ haddress->address.data = malloc(kaddress->length);
+ if (haddress->address.data == NULL)
+ return ENOMEM;
+
+ memcpy(haddress->address.data, kaddress->contents, kaddress->length);
+ haddress->address.length = kaddress->length;
+
+ return 0;
+}
+
+static krb5_error_code
+kh_marshall_HostAddresses(krb5_context context,
+ krb5_address **kaddresses,
+ HostAddresses **phaddresses)
+{
+ krb5_error_code code;
+ HostAddresses *haddresses;
+ int i;
+
+ *phaddresses = NULL;
+
+ if (kaddresses == NULL)
+ return 0;
+
+ for (i = 0; kaddresses[i] != NULL; i++)
+ ;
+
+ haddresses = k5alloc(sizeof(*haddresses), &code);
+ if (code != 0)
+ return code;
+
+ haddresses->len = 0;
+ haddresses->val = k5alloc(i * sizeof(HostAddress), &code);
+ if (code != 0)
+ return code;
+
+ for (i = 0; kaddresses[i] != NULL; i++) {
+ code = kh_marshall_HostAddress(context,
+ kaddresses[i],
+ &haddresses->val[i]);
+ if (code != 0)
+ break;
+
+ haddresses->len++;
+ }
+
+ if (code != 0) {
+ free(haddresses->val);
+ free(haddresses);
+ } else {
+ *phaddresses = haddresses;
+ }
+
+ return code;
+}
+
+krb5_error_code
+kh_db_check_policy_as(krb5_context context,
+ unsigned int method,
+ const krb5_data *req_data,
+ krb5_data *rep_data)
+{
+ kh_db_context *kh = KH_DB_CONTEXT(context);
+ kdb_check_policy_as_req *req = (kdb_check_policy_as_req *)req_data->data;
+ kdb_check_policy_as_rep *rep = (kdb_check_policy_as_rep *)rep_data->data;
+ krb5_error_code code;
+ heim_octet_string e_data;
+ krb5_kdc_req *kkdcreq = req->request;
+ KDC_REQ hkdcreq;
+ Principal *hclient = NULL;
+ Principal *hserver = NULL;
+ time_t from, till, rtime;
+
+ if (kh->windc == NULL)
+ return KRB5_KDB_DBTYPE_NOSUP; /* short circuit */
+
+ memset(&hkdcreq, 0, sizeof(hkdcreq));
+
+ hkdcreq.pvno = KRB5_PVNO;
+ hkdcreq.msg_type = kkdcreq->msg_type;
+ hkdcreq.padata = NULL; /* FIXME */
+ code = kh_marshal_KDCOptions(context,
+ kkdcreq->kdc_options,
+ &hkdcreq.req_body.kdc_options);
+ if (code != 0)
+ goto cleanup;
+
+ code = kh_marshal_Principal(context, kkdcreq->client, &hclient);
+ if (code != 0)
+ goto cleanup;
+
+ code = kh_marshal_Principal(context, kkdcreq->server, &hserver);
+ if (code != 0)
+ goto cleanup;
+
+ hkdcreq.req_body.cname = &hclient->name;
+ hkdcreq.req_body.realm = hserver->realm;
+ hkdcreq.req_body.sname = &hserver->name;
+
+ from = kkdcreq->from; hkdcreq.req_body.from = &from;
+ till = kkdcreq->till; hkdcreq.req_body.till = &till;
+ rtime = kkdcreq->rtime; hkdcreq.req_body.rtime = &rtime;
+
+ hkdcreq.req_body.nonce = kkdcreq->nonce;
+ hkdcreq.req_body.etype.len = kkdcreq->nktypes;
+ hkdcreq.req_body.etype.val = kkdcreq->ktype;
+
+ code = kh_marshall_HostAddresses(context,
+ kkdcreq->addresses,
+ &hkdcreq.req_body.addresses);
+ if (code != 0)
+ goto cleanup;
+
+ /* FIXME hkdcreq.req_body.enc_authorization_data */
+ /* FIXME hkdcreq.req_body.additional_tickets */
+
+ code = kh_windc_client_access(context, kh,
+ KH_DB_ENTRY(req->client),
+ &hkdcreq, &e_data);
+
+ rep->e_data.data = e_data.data;
+ rep->e_data.length = e_data.length;
+
+cleanup:
+ kh_free_HostAddresses(context, hkdcreq.req_body.addresses);
+ kh_free_Principal(context, hclient);
+ kh_free_Principal(context, hserver);
+
+ return code;
+}
+
+krb5_error_code
+kh_hdb_windc_init(krb5_context context,
+ const char *libdir,
+ kh_db_context *kh)
+{
+ krb5_error_code code;
+ const char *objdirs[2];
+ void **tables = NULL;
+ int i;
+
+ memset(&kh->windc_plugins, 0, sizeof(kh->windc_plugins));
+
+ code = PLUGIN_DIR_OPEN(&kh->windc_plugins);
+ if (code != 0)
+ return code;
+
+ objdirs[0] = libdir;
+ objdirs[1] = NULL;
+
+ code = krb5int_open_plugin_dirs(objdirs, NULL,
+ &kh->windc_plugins,
+ &context->err);
+ if (code != 0)
+ return code;
+
+ code = krb5int_get_plugin_dir_data(&kh->windc_plugins,
+ "windc",
+ &tables,
+ &context->err);
+ if (code != 0)
+ return code;
+
+ code = KRB5_KDB_DBTYPE_NOSUP;
+
+ for (i = 0; tables[i] != NULL; i++) {
+ krb5plugin_windc_ftable *windc = tables[i];
+
+ if (windc->minor_version < KRB5_WINDC_PLUGIN_MINOR)
+ continue;
+
+ code = kh_map_error((*windc->init)(kh->hcontext, &kh->windc_ctx));
+ if (code != 0)
+ continue;
+
+ kh->windc = windc;
+ break;
+ }
+
+ if (tables != NULL)
+ krb5int_free_plugin_dir_data(tables);
+
+ return code;
+}
+