/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* plugins/kdb/test/kdb_test.c - Test KDB module */ /* * Copyright (C) 2015 by the Massachusetts Institute of Technology. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * This is a read-only KDB module intended to help test KDC behavior which * cannot be exercised with the DB2 module. Responses are read from the * dbmodules subsection according to this example: * * [dbmodules] * test = { * alias = { * aliasname = canonname * # For cross-realm aliases, only the realm part will * # matter to the client. * aliasname = @FOREIGN_REALM * enterprise@PRINC = @FOREIGN_REALM * } * princs = { * krbtgt/KRBTEST.COM = { * flags = +preauth +ok-to-auth-as-delegate * maxlife = 1d * maxrenewlife = 7d * expiration = 14d # relative to current time * pwexpiration = 1h * # Initial number is kvno; defaults to 1. * keys = 3 aes256-cts aes128-cts:normal * keys = 2 rc4-hmac * strings = key1:value1 * strings = key2:value2 * } * } * delegation = { * # Traditional constrained delegation; target_service * # must be in the same realm. * intermediate_service = target_service * } * rbcd = { * # Resource-based constrained delegation; * # intermediate_service may be in a different realm. * target_service = intermediate_service * } * } * * Key values are generated using a hash of the kvno, enctype, salt type, * principal name, and lookup realm. This module does not use master key * encryption, so it serves as a partial test of the DAL's ability to avoid * that. * * Inbound cross-realm TGT entries are currently implicit; they will use the * same configuration and key enctypes as the local krbtgt principal, although * they will use different keys (because the lookup realm is hashed in). * Outgoing cross-realm TGT entries must be added explicitly * (krbtgt/OTHER_REALM). */ #include "k5-int.h" #include "kdb5.h" #include "adm_proto.h" #include #define TEST_AD_TYPE -456 #define IS_TGS_PRINC(p) ((p)->length == 2 && \ data_eq_string((p)->data[0], KRB5_TGS_NAME)) typedef struct { void *profile; char *section; const char *names[6]; } *testhandle; static void * ealloc(size_t sz) { void *p = calloc(sz, 1); if (p == NULL) abort(); return p; } static char * estrdup(const char *s) { char *copy = strdup(s); if (copy == NULL) abort(); return copy; } static void check(krb5_error_code code) { if (code != 0) abort(); } /* Set up for a profile query using h->names. Look up s1 -> s2 -> s3 (some of * which may be NULL) within this database's dbmodules section. */ static void set_names(testhandle h, const char *s1, const char *s2, const char *s3) { h->names[0] = KDB_MODULE_SECTION; h->names[1] = h->section; h->names[2] = s1; h->names[3] = s2; h->names[4] = s3; h->names[5] = NULL; } /* Look up a string within this database's dbmodules section. */ static char * get_string(testhandle h, const char *s1, const char *s2, const char *s3) { krb5_error_code ret; char **values, *val; set_names(h, s1, s2, s3); ret = profile_get_values(h->profile, h->names, &values); if (ret == PROF_NO_RELATION) return NULL; if (ret) abort(); val = estrdup(values[0]); profile_free_list(values); return val; } /* Look up a duration within this database's dbmodules section. */ static krb5_deltat get_duration(testhandle h, const char *s1, const char *s2, const char *s3) { char *strval = get_string(h, s1, s2, s3); krb5_deltat val; if (strval == NULL) return 0; check(krb5_string_to_deltat(strval, &val)); free(strval); return val; } /* Look up an absolute time within this database's dbmodules section. The time * is expressed in the profile as an interval relative to the current time. */ static krb5_timestamp get_time(testhandle h, const char *s1, const char *s2, const char *s3) { char *strval = get_string(h, s1, s2, s3); krb5_deltat val; if (strval == NULL) return 0; check(krb5_string_to_deltat(strval, &val)); free(strval); return val + time(NULL); } /* Initialize kb_out with a key of type etype, using a hash of kvno, etype, * salttype, and princstr for the key bytes. */ static void make_keyblock(krb5_kvno kvno, krb5_enctype etype, int32_t salttype, const char *princstr, const krb5_data *realm, krb5_keyblock *kb_out) { size_t keybytes, keylength, pos, n; char *hashstr; krb5_data d, rndin; krb5_checksum cksum; check(krb5_c_keylengths(NULL, etype, &keybytes, &keylength)); alloc_data(&rndin, keybytes); /* Hash the kvno, enctype, salt type, and principal name together. */ if (asprintf(&hashstr, "%d %d %d %s %.*s", (int)kvno, (int)etype, (int)salttype, princstr, (int)realm->length, realm->data) < 0) abort(); d = string2data(hashstr); check(krb5_c_make_checksum(NULL, CKSUMTYPE_SHA1, NULL, 0, &d, &cksum)); /* Make the appropriate number of input bytes from the hash result. */ for (pos = 0; pos < keybytes; pos += n) { n = (cksum.length < keybytes - pos) ? cksum.length : keybytes - pos; memcpy(rndin.data + pos, cksum.contents, n); } kb_out->enctype = etype; kb_out->length = keylength; kb_out->contents = ealloc(keylength); check(krb5_c_random_to_key(NULL, etype, &rndin, kb_out)); free(cksum.contents); free(rndin.data); free(hashstr); } /* Return key data for the given key/salt tuple strings, using hashes of the * enctypes, salts, and princstr for the key contents. */ static void make_keys(char **strings, const char *princstr, const krb5_data *realm, krb5_db_entry *ent) { krb5_key_data *key_data, *kd; krb5_keyblock kb; int32_t *ks_list_sizes, nstrings, nkeys, i, j; krb5_key_salt_tuple **ks_lists, *ks; krb5_kvno *kvnos; char *s; for (nstrings = 0; strings[nstrings] != NULL; nstrings++); ks_lists = ealloc(nstrings * sizeof(*ks_lists)); ks_list_sizes = ealloc(nstrings * sizeof(*ks_list_sizes)); kvnos = ealloc(nstrings * sizeof(*kvnos)); /* Convert each string into a key/salt tuple list and count the total * number of key data structures needed. */ nkeys = 0; for (i = 0; i < nstrings; i++) { s = strings[i]; /* Read a leading kvno if present; otherwise assume kvno 1. */ if (isdigit(*s)) { kvnos[i] = strtol(s, &s, 10); while (isspace(*s)) s++; } else { kvnos[i] = 1; } check(krb5_string_to_keysalts(s, NULL, NULL, FALSE, &ks_lists[i], &ks_list_sizes[i])); nkeys += ks_list_sizes[i]; } /* Turn each key/salt tuple into a key data entry. */ kd = key_data = ealloc(nkeys * sizeof(*kd)); for (i = 0; i < nstrings; i++) { ks = ks_lists[i]; for (j = 0; j < ks_list_sizes[i]; j++) { make_keyblock(kvnos[i], ks[j].ks_enctype, ks[j].ks_salttype, princstr, realm, &kb); kd->key_data_ver = 2; kd->key_data_kvno = kvnos[i]; kd->key_data_type[0] = ks[j].ks_enctype; kd->key_data_length[0] = kb.length; kd->key_data_contents[0] = kb.contents; kd->key_data_type[1] = ks[j].ks_salttype; kd++; } } for (i = 0; i < nstrings; i++) free(ks_lists[i]); free(ks_lists); free(ks_list_sizes); free(kvnos); ent->key_data = key_data; ent->n_key_data = nkeys; } static void make_strings(char **stringattrs, krb5_db_entry *ent) { struct k5buf buf; char **p; const char *str, *sep; krb5_tl_data *tl; k5_buf_init_dynamic(&buf); for (p = stringattrs; *p != NULL; p++) { str = *p; sep = strchr(str, ':'); assert(sep != NULL); k5_buf_add_len(&buf, str, sep - str); k5_buf_add_len(&buf, "\0", 1); k5_buf_add_len(&buf, sep + 1, strlen(sep + 1) + 1); } assert(buf.data != NULL); tl = ealloc(sizeof(*ent->tl_data)); tl->tl_data_next = NULL; tl->tl_data_type = KRB5_TL_STRING_ATTRS; tl->tl_data_length = buf.len; tl->tl_data_contents = buf.data; ent->tl_data = tl; } static krb5_error_code test_init(void) { return 0; } static krb5_error_code test_cleanup(void) { return 0; } static krb5_error_code test_open(krb5_context context, char *conf_section, char **db_args, int mode) { testhandle h; h = ealloc(sizeof(*h)); h->profile = context->profile; h->section = estrdup(conf_section); context->dal_handle->db_context = h; return 0; } static krb5_error_code test_close(krb5_context context) { testhandle h = context->dal_handle->db_context; free(h->section); free(h); return 0; } /* Return the principal name krbtgt/tgs_realm@our_realm. */ static krb5_principal tgtname(krb5_context context, const krb5_data *tgs_realm, const krb5_data *our_realm) { krb5_principal princ; check(krb5_build_principal_ext(context, &princ, our_realm->length, our_realm->data, KRB5_TGS_NAME_SIZE, KRB5_TGS_NAME, tgs_realm->length, tgs_realm->data, 0)); princ->type = KRB5_NT_SRV_INST; return princ; } /* Return true if search_for is within context's default realm or is an * incoming cross-realm TGS name. */ static krb5_boolean request_for_us(krb5_context context, krb5_const_principal search_for) { char *defrealm; krb5_data realm; krb5_boolean for_us; krb5_principal local_tgs; check(krb5_get_default_realm(context, &defrealm)); realm = string2data(defrealm); local_tgs = tgtname(context, &realm, &realm); krb5_free_default_realm(context, defrealm); for_us = krb5_realm_compare(context, local_tgs, search_for) || krb5_principal_compare_any_realm(context, local_tgs, search_for); krb5_free_principal(context, local_tgs); return for_us; } static krb5_error_code test_get_principal(krb5_context context, krb5_const_principal search_for, unsigned int flags, krb5_db_entry **entry) { krb5_error_code ret; krb5_principal princ = NULL, tgtprinc; krb5_principal_data empty_princ = { KV5M_PRINCIPAL }; testhandle h = context->dal_handle->db_context; char *search_name = NULL, *canon = NULL, *flagstr; char **names, **key_strings, **stringattrs; const char *ename; krb5_db_entry *ent; *entry = NULL; if (!request_for_us(context, search_for)) return KRB5_KDB_NOENTRY; check(krb5_unparse_name_flags(context, search_for, KRB5_PRINCIPAL_UNPARSE_NO_REALM, &search_name)); canon = get_string(h, "alias", search_name, NULL); if (canon != NULL) { check(krb5_parse_name(context, canon, &princ)); if (!krb5_realm_compare(context, search_for, princ)) { /* Out of realm */ if ((flags & KRB5_KDB_FLAG_CLIENT) && (flags & KRB5_KDB_FLAG_REFERRAL_OK)) { /* Return a client referral by creating an entry with only the * principal set. */ *entry = ealloc(sizeof(**entry)); (*entry)->princ = princ; princ = NULL; ret = 0; goto cleanup; } else if (flags & KRB5_KDB_FLAG_REFERRAL_OK) { /* Generate a server referral by looking up the TGT for the * canonical name's realm. */ tgtprinc = tgtname(context, &princ->realm, &search_for->realm); krb5_free_principal(context, princ); princ = tgtprinc; krb5_free_unparsed_name(context, search_name); check(krb5_unparse_name_flags(context, princ, KRB5_PRINCIPAL_UNPARSE_NO_REALM, &search_name)); ename = search_name; } else { ret = KRB5_KDB_NOENTRY; goto cleanup; } } else { ename = canon; } } else { check(krb5_copy_principal(context, search_for, &princ)); ename = search_name; } /* Check that the entry exists. */ set_names(h, "princs", ename, NULL); ret = profile_get_relation_names(h->profile, h->names, &names); if (ret == PROF_NO_RELATION) { ret = KRB5_KDB_NOENTRY; goto cleanup; } profile_free_list(names); /* No error exits after this point. */ ent = ealloc(sizeof(*ent)); ent->princ = princ; princ = NULL; flagstr = get_string(h, "princs", ename, "flags"); if (flagstr != NULL) { check(krb5_flagspec_to_mask(flagstr, &ent->attributes, &ent->attributes)); } free(flagstr); ent->max_life = get_duration(h, "princs", ename, "maxlife"); ent->max_renewable_life = get_duration(h, "princs", ename, "maxrenewlife"); ent->expiration = get_time(h, "princs", ename, "expiration"); ent->pw_expiration = get_time(h, "princs", ename, "pwexpiration"); /* Leave last_success, last_failed, fail_auth_count zeroed. */ /* Leave e_data empty. */ set_names(h, "princs", ename, "keys"); ret = profile_get_values(h->profile, h->names, &key_strings); if (ret != PROF_NO_RELATION) { make_keys(key_strings, ename, &search_for->realm, ent); profile_free_list(key_strings); } set_names(h, "princs", ename, "strings"); ret = profile_get_values(h->profile, h->names, &stringattrs); if (ret != PROF_NO_RELATION) { make_strings(stringattrs, ent); profile_free_list(stringattrs); } /* We must include mod-princ data or kadm5_get_principal() won't work and * we can't extract keys with kadmin.local. */ check(krb5_dbe_update_mod_princ_data(context, ent, 0, &empty_princ)); *entry = ent; ret = 0; cleanup: krb5_free_unparsed_name(context, search_name); krb5_free_principal(context, princ); free(canon); return ret; } static void lookup_princ_by_cert(krb5_context context, const krb5_data *client_cert, krb5_principal *princ) { krb5_error_code ret; char *cert_princ_name; /* The test client sends a principal string instead of a cert. */ cert_princ_name = k5memdup0(client_cert->data, client_cert->length, &ret); check(ret); check(krb5_parse_name_flags(context, cert_princ_name, KRB5_PRINCIPAL_PARSE_ENTERPRISE, princ)); free(cert_princ_name); } static krb5_error_code test_get_s4u_x509_principal(krb5_context context, const krb5_data *client_cert, krb5_const_principal princ, unsigned int flags, krb5_db_entry **entry) { krb5_error_code ret; krb5_principal cert_princ, canon_princ; testhandle h = context->dal_handle->db_context; krb5_boolean match; char *canon, *princ_name; lookup_princ_by_cert(context, client_cert, &cert_princ); ret = test_get_principal(context, cert_princ, flags, entry); krb5_free_principal(context, cert_princ); if (ret || (flags & KRB5_KDB_FLAG_REFERRAL_OK)) return ret; if (!krb5_realm_compare(context, princ, (*entry)->princ)) abort(); if (princ->length == 0 || krb5_principal_compare(context, princ, (*entry)->princ)) return 0; match = FALSE; check(krb5_unparse_name_flags(context, princ, KRB5_PRINCIPAL_UNPARSE_NO_REALM, &princ_name)); canon = get_string(h, "alias", princ_name, NULL); krb5_free_unparsed_name(context, princ_name); if (canon != NULL) { check(krb5_parse_name(context, canon, &canon_princ)); match = krb5_principal_compare(context, canon_princ, (*entry)->princ); krb5_free_principal(context, canon_princ); } free(canon); return match ? 0 : KRB5KDC_ERR_CLIENT_NAME_MISMATCH; } static krb5_error_code test_fetch_master_key(krb5_context context, krb5_principal mname, krb5_keyblock *key_out, krb5_kvno *kvno_out, char *db_args) { memset(key_out, 0, sizeof(*key_out)); *kvno_out = 0; return 0; } static krb5_error_code test_fetch_master_key_list(krb5_context context, krb5_principal mname, const krb5_keyblock *key, krb5_keylist_node **mkeys_out) { /* krb5_dbe_get_mkvno() returns an error if we produce NULL, so return an * empty node to make kadm5_get_principal() work. */ *mkeys_out = ealloc(sizeof(**mkeys_out)); return 0; } static krb5_error_code test_decrypt_key_data(krb5_context context, const krb5_keyblock *mkey, const krb5_key_data *kd, krb5_keyblock *key_out, krb5_keysalt *salt_out) { key_out->magic = KV5M_KEYBLOCK; key_out->enctype = kd->key_data_type[0]; key_out->length = kd->key_data_length[0]; key_out->contents = ealloc(key_out->length); memcpy(key_out->contents, kd->key_data_contents[0], key_out->length); if (salt_out != NULL) { salt_out->type = (kd->key_data_ver > 1) ? kd->key_data_type[1] : KRB5_KDB_SALTTYPE_NORMAL; salt_out->data = empty_data(); } return 0; } static krb5_error_code test_encrypt_key_data(krb5_context context, const krb5_keyblock *mkey, const krb5_keyblock *key, const krb5_keysalt *salt, int kvno, krb5_key_data *kd_out) { memset(kd_out, 0, sizeof(*kd_out)); kd_out->key_data_ver = 2; kd_out->key_data_kvno = kvno; kd_out->key_data_type[0] = key->enctype; kd_out->key_data_length[0] = key->length; kd_out->key_data_contents[0] = ealloc(key->length); memcpy(kd_out->key_data_contents[0], key->contents, key->length); kd_out->key_data_type[1] = (salt != NULL) ? salt->type : KRB5_KDB_SALTTYPE_NORMAL; return 0; } static void change_auth_indicators(krb5_context context, krb5_data ***auth_indicators) { krb5_data **inds, d; int i, val; /* If we see an auth indicator "dbincrX", replace the whole indicator list * with "dbincr{X+1}". */ inds = *auth_indicators; for (i = 0; inds != NULL && inds[i] != NULL; i++) { if (inds[i]->length == 7 && memcmp(inds[i]->data, "dbincr", 6) == 0) { val = inds[i]->data[6]; k5_free_data_ptr_list(inds); inds = ealloc(2 * sizeof(*inds)); d = string2data("dbincr0"); check(krb5_copy_data(context, &d, &inds[0])); inds[0]->data[6] = val + 1; inds[1] = NULL; *auth_indicators = inds; break; } } } static krb5_error_code test_issue_pac(krb5_context context, unsigned int flags, krb5_db_entry *client, krb5_keyblock *replaced_reply_key, krb5_db_entry *server, krb5_db_entry *krb5tgt, krb5_timestamp authtime, krb5_pac old_pac, krb5_pac new_pac, krb5_data ***auth_indicators) { krb5_data data = empty_data(); krb5_boolean found_logon_info = FALSE; krb5_ui_4 *types = NULL; size_t num_buffers = 0, i; change_auth_indicators(context, auth_indicators); if (old_pac == NULL || (client != NULL && (flags & KRB5_KDB_FLAG_PROTOCOL_TRANSITION))) { /* Generating an initial PAC. */ assert(client != NULL); data = string2data("fake"); check(krb5_pac_add_buffer(context, new_pac, KRB5_PAC_LOGON_INFO, &data)); if (replaced_reply_key != NULL) { /* Add a fake PAC_CREDENTIALS_INFO buffer so we can test whether * this parameter was set. */ data = string2data("fake credinfo"); check(krb5_pac_add_buffer(context, new_pac, KRB5_PAC_CREDENTIALS_INFO, &data)); } return 0; } else { /* Field copying - my favorite! */ if (old_pac != NULL) check(krb5_pac_get_types(context, old_pac, &num_buffers, &types)); for (i = 0; i < num_buffers; i++) { /* Skip buffer types handled by KDC. */ if (types[i] == KRB5_PAC_SERVER_CHECKSUM || types[i] == KRB5_PAC_PRIVSVR_CHECKSUM || types[i] == KRB5_PAC_TICKET_CHECKSUM || types[i] == KRB5_PAC_CLIENT_INFO || types[i] == KRB5_PAC_DELEGATION_INFO) continue; check(krb5_pac_get_buffer(context, old_pac, types[i], &data)); if (types[i] == KRB5_PAC_LOGON_INFO) { found_logon_info = TRUE; assert(data_eq_string(data, "fake")); } check(krb5_pac_add_buffer(context, new_pac, types[i], &data)); krb5_free_data_contents(context, &data); } if (old_pac != NULL) assert(found_logon_info); free(types); } return 0; } static krb5_boolean match_in_table(krb5_context context, const char *table, const char *sprinc, const char *tprinc) { testhandle h = context->dal_handle->db_context; krb5_error_code ret; char **values, **v; krb5_boolean found = FALSE; set_names(h, table, sprinc, NULL); ret = profile_get_values(h->profile, h->names, &values); assert(ret == 0 || ret == PROF_NO_RELATION); if (ret) return FALSE; for (v = values; *v != NULL; v++) { if (tprinc == NULL || strcmp(*v, tprinc) == 0) { found = TRUE; break; } } profile_free_list(values); return found; } static krb5_error_code test_check_allowed_to_delegate(krb5_context context, krb5_const_principal client, const krb5_db_entry *server, krb5_const_principal proxy) { char *sprinc, *tprinc = NULL; krb5_boolean found = FALSE; check(krb5_unparse_name_flags(context, server->princ, KRB5_PRINCIPAL_UNPARSE_NO_REALM, &sprinc)); if (proxy != NULL) { check(krb5_unparse_name_flags(context, proxy, KRB5_PRINCIPAL_UNPARSE_NO_REALM, &tprinc)); } found = match_in_table(context, "delegation", sprinc, tprinc); krb5_free_unparsed_name(context, sprinc); krb5_free_unparsed_name(context, tprinc); return found ? 0 : KRB5KDC_ERR_BADOPTION; } static krb5_error_code test_allowed_to_delegate_from(krb5_context context, krb5_const_principal client, krb5_const_principal server, krb5_pac server_pac, const krb5_db_entry *proxy) { char *proxy_princ, *server_princ, *pac_client_princ, *client_princ; krb5_boolean found = FALSE; assert(server_pac != NULL); check(krb5_unparse_name(context, proxy->princ, &proxy_princ)); check(krb5_unparse_name(context, server, &server_princ)); check(krb5_unparse_name(context, client, &client_princ)); check(krb5_pac_get_client_info(context, server_pac, NULL, &pac_client_princ)); /* Skip realm portion if not present in PAC. */ assert(strncmp(pac_client_princ, server_princ, strlen(pac_client_princ)) == 0); free(pac_client_princ); found = match_in_table(context, "rbcd", proxy_princ, server_princ); krb5_free_unparsed_name(context, proxy_princ); krb5_free_unparsed_name(context, server_princ); krb5_free_unparsed_name(context, client_princ); return found ? 0 : KRB5KDC_ERR_BADOPTION; } kdb_vftabl PLUGIN_SYMBOL_NAME(krb5_test, kdb_function_table) = { KRB5_KDB_DAL_MAJOR_VERSION, /* major version number */ 0, /* minor version number */ test_init, test_cleanup, test_open, test_close, NULL, /* create */ NULL, /* destroy */ NULL, /* get_age */ NULL, /* lock */ NULL, /* unlock */ test_get_principal, NULL, /* put_principal */ NULL, /* delete_principal */ NULL, /* rename_principal */ NULL, /* iterate */ NULL, /* create_policy */ NULL, /* get_policy */ NULL, /* put_policy */ NULL, /* iter_policy */ NULL, /* delete_policy */ test_fetch_master_key, test_fetch_master_key_list, NULL, /* store_master_key_list */ NULL, /* dbe_search_enctype */ NULL, /* change_pwd */ NULL, /* promote_db */ test_decrypt_key_data, test_encrypt_key_data, NULL, /* check_transited_realms */ NULL, /* check_policy_as */ NULL, /* check_policy_tgs */ NULL, /* audit_as_req */ NULL, /* refresh_config */ test_check_allowed_to_delegate, NULL, /* free_principal_e_data */ test_get_s4u_x509_principal, test_allowed_to_delegate_from, test_issue_pac, };