#include "k5-int.h"
#include "kdb5.h"
#include "adm_proto.h"
#include <ctype.h>
#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();
}
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;
}
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;
}
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;
}
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);
}
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);
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));
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);
}
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));
nkeys = 0;
for (i = 0; i < nstrings; i++) {
s = strings[i];
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];
}
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;
}
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;
}
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)) {
if ((flags & KRB5_KDB_FLAG_CLIENT) &&
(flags & KRB5_KDB_FLAG_REFERRAL_OK)) {
*entry = ealloc(sizeof(**entry));
(*entry)->princ = princ;
princ = NULL;
ret = 0;
goto cleanup;
} else if (flags & KRB5_KDB_FLAG_REFERRAL_OK) {
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;
}
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);
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");
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);
}
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;
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)
{
*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;
size_t i;
int val;
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))) {
assert(client != NULL);
data = string2data("fake");
check(krb5_pac_add_buffer(context, new_pac, KRB5_PAC_LOGON_INFO,
&data));
if (replaced_reply_key != NULL) {
data = string2data("fake credinfo");
check(krb5_pac_add_buffer(context, new_pac,
KRB5_PAC_CREDENTIALS_INFO, &data));
}
return 0;
} else {
if (old_pac != NULL)
check(krb5_pac_get_types(context, old_pac, &num_buffers, &types));
for (i = 0; i < num_buffers; i++) {
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));
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,
0,
test_init,
test_cleanup,
test_open,
test_close,
NULL,
NULL,
NULL,
NULL,
NULL,
test_get_principal,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
test_fetch_master_key,
test_fetch_master_key_list,
NULL,
NULL,
NULL,
NULL,
test_decrypt_key_data,
test_encrypt_key_data,
NULL,
NULL,
NULL,
NULL,
NULL,
test_check_allowed_to_delegate,
NULL,
test_get_s4u_x509_principal,
test_allowed_to_delegate_from,
test_issue_pac,
};