xref: /freebsd/crypto/krb5/src/plugins/kdb/test/kdb_test.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* plugins/kdb/test/kdb_test.c - Test KDB module */
3 /*
4  * Copyright (C) 2015 by the Massachusetts Institute of Technology.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * * Redistributions of source code must retain the above copyright
12  *   notice, this list of conditions and the following disclaimer.
13  *
14  * * Redistributions in binary form must reproduce the above copyright
15  *   notice, this list of conditions and the following disclaimer in
16  *   the documentation and/or other materials provided with the
17  *   distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
24  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
30  * OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 /*
34  * This is a read-only KDB module intended to help test KDC behavior which
35  * cannot be exercised with the DB2 module.  Responses are read from the
36  * dbmodules subsection according to this example:
37  *
38  *     [dbmodules]
39  *         test = {
40  *             alias = {
41  *                 aliasname = canonname
42  *                 # For cross-realm aliases, only the realm part will
43  *                 # matter to the client.
44  *                 aliasname = @FOREIGN_REALM
45  *                 enterprise@PRINC = @FOREIGN_REALM
46  *             }
47  *             princs = {
48  *                 krbtgt/KRBTEST.COM = {
49  *                     flags = +preauth +ok-to-auth-as-delegate
50  *                     maxlife = 1d
51  *                     maxrenewlife = 7d
52  *                     expiration = 14d # relative to current time
53  *                     pwexpiration = 1h
54  *                     # Initial number is kvno; defaults to 1.
55  *                     keys = 3 aes256-cts aes128-cts:normal
56  *                     keys = 2 rc4-hmac
57  *                     strings = key1:value1
58  *                     strings = key2:value2
59  *                 }
60  *             }
61  *             delegation = {
62  *                 # Traditional constrained delegation; target_service
63  *                 # must be in the same realm.
64  *                 intermediate_service = target_service
65  *             }
66  *             rbcd = {
67  *                 # Resource-based constrained delegation;
68  *                 # intermediate_service may be in a different realm.
69  *                 target_service = intermediate_service
70  *             }
71  *         }
72  *
73  * Key values are generated using a hash of the kvno, enctype, salt type,
74  * principal name, and lookup realm.  This module does not use master key
75  * encryption, so it serves as a partial test of the DAL's ability to avoid
76  * that.
77  *
78  * Inbound cross-realm TGT entries are currently implicit; they will use the
79  * same configuration and key enctypes as the local krbtgt principal, although
80  * they will use different keys (because the lookup realm is hashed in).
81  * Outgoing cross-realm TGT entries must be added explicitly
82  * (krbtgt/OTHER_REALM).
83  */
84 
85 #include "k5-int.h"
86 #include "kdb5.h"
87 #include "adm_proto.h"
88 #include <ctype.h>
89 
90 #define TEST_AD_TYPE -456
91 
92 #define IS_TGS_PRINC(p) ((p)->length == 2 &&                            \
93                          data_eq_string((p)->data[0], KRB5_TGS_NAME))
94 
95 typedef struct {
96     void *profile;
97     char *section;
98     const char *names[6];
99 } *testhandle;
100 
101 static void *
ealloc(size_t sz)102 ealloc(size_t sz)
103 {
104     void *p = calloc(sz, 1);
105 
106     if (p == NULL)
107         abort();
108     return p;
109 }
110 
111 static char *
estrdup(const char * s)112 estrdup(const char *s)
113 {
114     char *copy = strdup(s);
115 
116     if (copy == NULL)
117         abort();
118     return copy;
119 }
120 
121 static void
check(krb5_error_code code)122 check(krb5_error_code code)
123 {
124     if (code != 0)
125         abort();
126 }
127 
128 /* Set up for a profile query using h->names.  Look up s1 -> s2 -> s3 (some of
129  * which may be NULL) within this database's dbmodules section. */
130 static void
set_names(testhandle h,const char * s1,const char * s2,const char * s3)131 set_names(testhandle h, const char *s1, const char *s2, const char *s3)
132 {
133     h->names[0] = KDB_MODULE_SECTION;
134     h->names[1] = h->section;
135     h->names[2] = s1;
136     h->names[3] = s2;
137     h->names[4] = s3;
138     h->names[5] = NULL;
139 }
140 
141 /* Look up a string within this database's dbmodules section. */
142 static char *
get_string(testhandle h,const char * s1,const char * s2,const char * s3)143 get_string(testhandle h, const char *s1, const char *s2, const char *s3)
144 {
145     krb5_error_code ret;
146     char **values, *val;
147 
148     set_names(h, s1, s2, s3);
149     ret = profile_get_values(h->profile, h->names, &values);
150     if (ret == PROF_NO_RELATION)
151         return NULL;
152     if (ret)
153         abort();
154     val = estrdup(values[0]);
155     profile_free_list(values);
156     return val;
157 }
158 
159 /* Look up a duration within this database's dbmodules section. */
160 static krb5_deltat
get_duration(testhandle h,const char * s1,const char * s2,const char * s3)161 get_duration(testhandle h, const char *s1, const char *s2, const char *s3)
162 {
163     char *strval = get_string(h, s1, s2, s3);
164     krb5_deltat val;
165 
166     if (strval == NULL)
167         return 0;
168     check(krb5_string_to_deltat(strval, &val));
169     free(strval);
170     return val;
171 }
172 
173 /* Look up an absolute time within this database's dbmodules section.  The time
174  * is expressed in the profile as an interval relative to the current time. */
175 static krb5_timestamp
get_time(testhandle h,const char * s1,const char * s2,const char * s3)176 get_time(testhandle h, const char *s1, const char *s2, const char *s3)
177 {
178     char *strval = get_string(h, s1, s2, s3);
179     krb5_deltat val;
180 
181     if (strval == NULL)
182         return 0;
183     check(krb5_string_to_deltat(strval, &val));
184     free(strval);
185     return val + time(NULL);
186 }
187 
188 /* Initialize kb_out with a key of type etype, using a hash of kvno, etype,
189  * salttype, and princstr for the key bytes. */
190 static void
make_keyblock(krb5_kvno kvno,krb5_enctype etype,int32_t salttype,const char * princstr,const krb5_data * realm,krb5_keyblock * kb_out)191 make_keyblock(krb5_kvno kvno, krb5_enctype etype, int32_t salttype,
192               const char *princstr, const krb5_data *realm,
193               krb5_keyblock *kb_out)
194 {
195     size_t keybytes, keylength, pos, n;
196     char *hashstr;
197     krb5_data d, rndin;
198     krb5_checksum cksum;
199 
200     check(krb5_c_keylengths(NULL, etype, &keybytes, &keylength));
201     alloc_data(&rndin, keybytes);
202 
203     /* Hash the kvno, enctype, salt type, and principal name together. */
204     if (asprintf(&hashstr, "%d %d %d %s %.*s", (int)kvno, (int)etype,
205                  (int)salttype, princstr, (int)realm->length, realm->data) < 0)
206         abort();
207     d = string2data(hashstr);
208     check(krb5_c_make_checksum(NULL, CKSUMTYPE_SHA1, NULL, 0, &d, &cksum));
209 
210     /* Make the appropriate number of input bytes from the hash result. */
211     for (pos = 0; pos < keybytes; pos += n) {
212         n = (cksum.length < keybytes - pos) ? cksum.length : keybytes - pos;
213         memcpy(rndin.data + pos, cksum.contents, n);
214     }
215 
216     kb_out->enctype = etype;
217     kb_out->length = keylength;
218     kb_out->contents = ealloc(keylength);
219     check(krb5_c_random_to_key(NULL, etype, &rndin, kb_out));
220     free(cksum.contents);
221     free(rndin.data);
222     free(hashstr);
223 }
224 
225 /* Return key data for the given key/salt tuple strings, using hashes of the
226  * enctypes, salts, and princstr for the key contents. */
227 static void
make_keys(char ** strings,const char * princstr,const krb5_data * realm,krb5_db_entry * ent)228 make_keys(char **strings, const char *princstr, const krb5_data *realm,
229           krb5_db_entry *ent)
230 {
231     krb5_key_data *key_data, *kd;
232     krb5_keyblock kb;
233     int32_t *ks_list_sizes, nstrings, nkeys, i, j;
234     krb5_key_salt_tuple **ks_lists, *ks;
235     krb5_kvno *kvnos;
236     char *s;
237 
238     for (nstrings = 0; strings[nstrings] != NULL; nstrings++);
239     ks_lists = ealloc(nstrings * sizeof(*ks_lists));
240     ks_list_sizes = ealloc(nstrings * sizeof(*ks_list_sizes));
241     kvnos = ealloc(nstrings * sizeof(*kvnos));
242 
243     /* Convert each string into a key/salt tuple list and count the total
244      * number of key data structures needed. */
245     nkeys = 0;
246     for (i = 0; i < nstrings; i++) {
247         s = strings[i];
248         /* Read a leading kvno if present; otherwise assume kvno 1. */
249         if (isdigit(*s)) {
250             kvnos[i] = strtol(s, &s, 10);
251             while (isspace(*s))
252                 s++;
253         } else {
254             kvnos[i] = 1;
255         }
256         check(krb5_string_to_keysalts(s, NULL, NULL, FALSE, &ks_lists[i],
257                                       &ks_list_sizes[i]));
258         nkeys += ks_list_sizes[i];
259     }
260 
261     /* Turn each key/salt tuple into a key data entry. */
262     kd = key_data = ealloc(nkeys * sizeof(*kd));
263     for (i = 0; i < nstrings; i++) {
264         ks = ks_lists[i];
265         for (j = 0; j < ks_list_sizes[i]; j++) {
266             make_keyblock(kvnos[i], ks[j].ks_enctype, ks[j].ks_salttype,
267                           princstr, realm, &kb);
268             kd->key_data_ver = 2;
269             kd->key_data_kvno = kvnos[i];
270             kd->key_data_type[0] = ks[j].ks_enctype;
271             kd->key_data_length[0] = kb.length;
272             kd->key_data_contents[0] = kb.contents;
273             kd->key_data_type[1] = ks[j].ks_salttype;
274             kd++;
275         }
276     }
277 
278     for (i = 0; i < nstrings; i++)
279         free(ks_lists[i]);
280     free(ks_lists);
281     free(ks_list_sizes);
282     free(kvnos);
283     ent->key_data = key_data;
284     ent->n_key_data = nkeys;
285 }
286 
287 static void
make_strings(char ** stringattrs,krb5_db_entry * ent)288 make_strings(char **stringattrs, krb5_db_entry *ent)
289 {
290     struct k5buf buf;
291     char **p;
292     const char *str, *sep;
293     krb5_tl_data *tl;
294 
295     k5_buf_init_dynamic(&buf);
296     for (p = stringattrs; *p != NULL; p++) {
297         str = *p;
298         sep = strchr(str, ':');
299         assert(sep != NULL);
300         k5_buf_add_len(&buf, str, sep - str);
301         k5_buf_add_len(&buf, "\0", 1);
302         k5_buf_add_len(&buf, sep + 1, strlen(sep + 1) + 1);
303     }
304     assert(buf.data != NULL);
305 
306     tl = ealloc(sizeof(*ent->tl_data));
307     tl->tl_data_next = NULL;
308     tl->tl_data_type = KRB5_TL_STRING_ATTRS;
309     tl->tl_data_length = buf.len;
310     tl->tl_data_contents = buf.data;
311     ent->tl_data = tl;
312 }
313 
314 static krb5_error_code
test_init()315 test_init()
316 {
317     return 0;
318 }
319 
320 static krb5_error_code
test_cleanup()321 test_cleanup()
322 {
323     return 0;
324 }
325 
326 static krb5_error_code
test_open(krb5_context context,char * conf_section,char ** db_args,int mode)327 test_open(krb5_context context, char *conf_section, char **db_args, int mode)
328 {
329     testhandle h;
330 
331     h = ealloc(sizeof(*h));
332     h->profile = context->profile;
333     h->section = estrdup(conf_section);
334     context->dal_handle->db_context = h;
335     return 0;
336 }
337 
338 static krb5_error_code
test_close(krb5_context context)339 test_close(krb5_context context)
340 {
341     testhandle h = context->dal_handle->db_context;
342 
343     free(h->section);
344     free(h);
345     return 0;
346 }
347 
348 /* Return the principal name krbtgt/tgs_realm@our_realm. */
349 static krb5_principal
tgtname(krb5_context context,const krb5_data * tgs_realm,const krb5_data * our_realm)350 tgtname(krb5_context context, const krb5_data *tgs_realm,
351         const krb5_data *our_realm)
352 {
353     krb5_principal princ;
354 
355     check(krb5_build_principal_ext(context, &princ,
356                                    our_realm->length, our_realm->data,
357                                    KRB5_TGS_NAME_SIZE, KRB5_TGS_NAME,
358                                    tgs_realm->length, tgs_realm->data, 0));
359     princ->type = KRB5_NT_SRV_INST;
360     return princ;
361 }
362 
363 /* Return true if search_for is within context's default realm or is an
364  * incoming cross-realm TGS name. */
365 static krb5_boolean
request_for_us(krb5_context context,krb5_const_principal search_for)366 request_for_us(krb5_context context, krb5_const_principal search_for)
367 {
368     char *defrealm;
369     krb5_data realm;
370     krb5_boolean for_us;
371     krb5_principal local_tgs;
372 
373     check(krb5_get_default_realm(context, &defrealm));
374     realm = string2data(defrealm);
375     local_tgs = tgtname(context, &realm, &realm);
376     krb5_free_default_realm(context, defrealm);
377 
378     for_us = krb5_realm_compare(context, local_tgs, search_for) ||
379         krb5_principal_compare_any_realm(context, local_tgs, search_for);
380     krb5_free_principal(context, local_tgs);
381     return for_us;
382 }
383 
384 static krb5_error_code
test_get_principal(krb5_context context,krb5_const_principal search_for,unsigned int flags,krb5_db_entry ** entry)385 test_get_principal(krb5_context context, krb5_const_principal search_for,
386                    unsigned int flags, krb5_db_entry **entry)
387 {
388     krb5_error_code ret;
389     krb5_principal princ = NULL, tgtprinc;
390     krb5_principal_data empty_princ = { KV5M_PRINCIPAL };
391     testhandle h = context->dal_handle->db_context;
392     char *search_name = NULL, *canon = NULL, *flagstr;
393     char **names, **key_strings, **stringattrs;
394     const char *ename;
395     krb5_db_entry *ent;
396 
397     *entry = NULL;
398 
399     if (!request_for_us(context, search_for))
400         return KRB5_KDB_NOENTRY;
401 
402     check(krb5_unparse_name_flags(context, search_for,
403                                   KRB5_PRINCIPAL_UNPARSE_NO_REALM,
404                                   &search_name));
405     canon = get_string(h, "alias", search_name, NULL);
406     if (canon != NULL) {
407         check(krb5_parse_name(context, canon, &princ));
408         if (!krb5_realm_compare(context, search_for, princ)) {
409             /* Out of realm */
410             if ((flags & KRB5_KDB_FLAG_CLIENT) &&
411                 (flags & KRB5_KDB_FLAG_REFERRAL_OK)) {
412                 /* Return a client referral by creating an entry with only the
413                  * principal set. */
414                 *entry = ealloc(sizeof(**entry));
415                 (*entry)->princ = princ;
416                 princ = NULL;
417                 ret = 0;
418                 goto cleanup;
419             } else if (flags & KRB5_KDB_FLAG_REFERRAL_OK) {
420                 /* Generate a server referral by looking up the TGT for the
421                  * canonical name's realm. */
422                 tgtprinc = tgtname(context, &princ->realm, &search_for->realm);
423                 krb5_free_principal(context, princ);
424                 princ = tgtprinc;
425 
426                 krb5_free_unparsed_name(context, search_name);
427                 check(krb5_unparse_name_flags(context, princ,
428                                               KRB5_PRINCIPAL_UNPARSE_NO_REALM,
429                                               &search_name));
430                 ename = search_name;
431             } else {
432                 ret = KRB5_KDB_NOENTRY;
433                 goto cleanup;
434             }
435         } else {
436             ename = canon;
437         }
438     } else {
439         check(krb5_copy_principal(context, search_for, &princ));
440         ename = search_name;
441     }
442 
443     /* Check that the entry exists. */
444     set_names(h, "princs", ename, NULL);
445     ret = profile_get_relation_names(h->profile, h->names, &names);
446     if (ret == PROF_NO_RELATION) {
447         ret = KRB5_KDB_NOENTRY;
448         goto cleanup;
449     }
450     profile_free_list(names);
451 
452     /* No error exits after this point. */
453 
454     ent = ealloc(sizeof(*ent));
455     ent->princ = princ;
456     princ = NULL;
457 
458     flagstr = get_string(h, "princs", ename, "flags");
459     if (flagstr != NULL) {
460         check(krb5_flagspec_to_mask(flagstr, &ent->attributes,
461                                     &ent->attributes));
462     }
463     free(flagstr);
464 
465     ent->max_life = get_duration(h, "princs", ename, "maxlife");
466     ent->max_renewable_life = get_duration(h, "princs", ename, "maxrenewlife");
467     ent->expiration = get_time(h, "princs", ename, "expiration");
468     ent->pw_expiration = get_time(h, "princs", ename, "pwexpiration");
469 
470     /* Leave last_success, last_failed, fail_auth_count zeroed. */
471     /* Leave e_data empty. */
472 
473     set_names(h, "princs", ename, "keys");
474     ret = profile_get_values(h->profile, h->names, &key_strings);
475     if (ret != PROF_NO_RELATION) {
476         make_keys(key_strings, ename, &search_for->realm, ent);
477         profile_free_list(key_strings);
478     }
479 
480     set_names(h, "princs", ename, "strings");
481     ret = profile_get_values(h->profile, h->names, &stringattrs);
482     if (ret != PROF_NO_RELATION) {
483         make_strings(stringattrs, ent);
484         profile_free_list(stringattrs);
485     }
486 
487     /* We must include mod-princ data or kadm5_get_principal() won't work and
488      * we can't extract keys with kadmin.local. */
489     check(krb5_dbe_update_mod_princ_data(context, ent, 0, &empty_princ));
490 
491     *entry = ent;
492     ret = 0;
493 
494 cleanup:
495     krb5_free_unparsed_name(context, search_name);
496     krb5_free_principal(context, princ);
497     free(canon);
498     return ret;
499 }
500 
501 static void
lookup_princ_by_cert(krb5_context context,const krb5_data * client_cert,krb5_principal * princ)502 lookup_princ_by_cert(krb5_context context, const krb5_data *client_cert,
503                      krb5_principal *princ)
504 {
505     krb5_error_code ret;
506     char *cert_princ_name;
507 
508     /* The test client sends a principal string instead of a cert. */
509     cert_princ_name = k5memdup0(client_cert->data, client_cert->length, &ret);
510     check(ret);
511 
512     check(krb5_parse_name_flags(context, cert_princ_name,
513                                 KRB5_PRINCIPAL_PARSE_ENTERPRISE, princ));
514     free(cert_princ_name);
515 }
516 
517 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)518 test_get_s4u_x509_principal(krb5_context context, const krb5_data *client_cert,
519                             krb5_const_principal princ, unsigned int flags,
520                             krb5_db_entry **entry)
521 {
522     krb5_error_code ret;
523     krb5_principal cert_princ, canon_princ;
524     testhandle h = context->dal_handle->db_context;
525     krb5_boolean match;
526     char *canon, *princ_name;
527 
528     lookup_princ_by_cert(context, client_cert, &cert_princ);
529 
530     ret = test_get_principal(context, cert_princ, flags, entry);
531     krb5_free_principal(context, cert_princ);
532     if (ret || (flags & KRB5_KDB_FLAG_REFERRAL_OK))
533         return ret;
534 
535     if (!krb5_realm_compare(context, princ, (*entry)->princ))
536         abort();
537 
538     if (princ->length == 0 ||
539         krb5_principal_compare(context, princ, (*entry)->princ))
540         return 0;
541 
542     match = FALSE;
543     check(krb5_unparse_name_flags(context, princ,
544                                   KRB5_PRINCIPAL_UNPARSE_NO_REALM,
545                                   &princ_name));
546     canon = get_string(h, "alias", princ_name, NULL);
547     krb5_free_unparsed_name(context, princ_name);
548     if (canon != NULL) {
549         check(krb5_parse_name(context, canon, &canon_princ));
550         match = krb5_principal_compare(context, canon_princ, (*entry)->princ);
551         krb5_free_principal(context, canon_princ);
552     }
553 
554     free(canon);
555     return match ? 0 : KRB5KDC_ERR_CLIENT_NAME_MISMATCH;
556 }
557 
558 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)559 test_fetch_master_key(krb5_context context, krb5_principal mname,
560                       krb5_keyblock *key_out, krb5_kvno *kvno_out,
561                       char *db_args)
562 {
563     memset(key_out, 0, sizeof(*key_out));
564     *kvno_out = 0;
565     return 0;
566 }
567 
568 static krb5_error_code
test_fetch_master_key_list(krb5_context context,krb5_principal mname,const krb5_keyblock * key,krb5_keylist_node ** mkeys_out)569 test_fetch_master_key_list(krb5_context context, krb5_principal mname,
570                            const krb5_keyblock *key,
571                            krb5_keylist_node **mkeys_out)
572 {
573     /* krb5_dbe_get_mkvno() returns an error if we produce NULL, so return an
574      * empty node to make kadm5_get_principal() work. */
575     *mkeys_out = ealloc(sizeof(**mkeys_out));
576     return 0;
577 }
578 
579 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)580 test_decrypt_key_data(krb5_context context, const krb5_keyblock *mkey,
581                       const krb5_key_data *kd, krb5_keyblock *key_out,
582                       krb5_keysalt *salt_out)
583 {
584     key_out->magic = KV5M_KEYBLOCK;
585     key_out->enctype = kd->key_data_type[0];
586     key_out->length = kd->key_data_length[0];
587     key_out->contents = ealloc(key_out->length);
588     memcpy(key_out->contents, kd->key_data_contents[0], key_out->length);
589     if (salt_out != NULL) {
590         salt_out->type = (kd->key_data_ver > 1) ? kd->key_data_type[1] :
591             KRB5_KDB_SALTTYPE_NORMAL;
592         salt_out->data = empty_data();
593     }
594     return 0;
595 }
596 
597 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)598 test_encrypt_key_data(krb5_context context, const krb5_keyblock *mkey,
599                       const krb5_keyblock *key, const krb5_keysalt *salt,
600                       int kvno, krb5_key_data *kd_out)
601 {
602     memset(kd_out, 0, sizeof(*kd_out));
603     kd_out->key_data_ver = 2;
604     kd_out->key_data_kvno = kvno;
605     kd_out->key_data_type[0] = key->enctype;
606     kd_out->key_data_length[0] = key->length;
607     kd_out->key_data_contents[0] = ealloc(key->length);
608     memcpy(kd_out->key_data_contents[0], key->contents, key->length);
609     kd_out->key_data_type[1] = (salt != NULL) ? salt->type :
610         KRB5_KDB_SALTTYPE_NORMAL;
611     return 0;
612 }
613 
614 static void
change_auth_indicators(krb5_context context,krb5_data *** auth_indicators)615 change_auth_indicators(krb5_context context, krb5_data ***auth_indicators)
616 {
617     krb5_data **inds, d;
618     int i, val;
619 
620     /* If we see an auth indicator "dbincrX", replace the whole indicator list
621      * with "dbincr{X+1}". */
622     inds = *auth_indicators;
623     for (i = 0; inds != NULL && inds[i] != NULL; i++) {
624         if (inds[i]->length == 7 && memcmp(inds[i]->data, "dbincr", 6) == 0) {
625             val = inds[i]->data[6];
626             k5_free_data_ptr_list(inds);
627             inds = ealloc(2 * sizeof(*inds));
628             d = string2data("dbincr0");
629             check(krb5_copy_data(context, &d, &inds[0]));
630             inds[0]->data[6] = val + 1;
631             inds[1] = NULL;
632             *auth_indicators = inds;
633             break;
634         }
635     }
636 }
637 
638 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)639 test_issue_pac(krb5_context context, unsigned int flags, krb5_db_entry *client,
640                krb5_keyblock *replaced_reply_key, krb5_db_entry *server,
641                krb5_db_entry *krb5tgt, krb5_timestamp authtime,
642                krb5_pac old_pac, krb5_pac new_pac,
643                krb5_data ***auth_indicators)
644 {
645     krb5_data data = empty_data();
646     krb5_boolean found_logon_info = FALSE;
647     krb5_ui_4 *types = NULL;
648     size_t num_buffers = 0, i;
649 
650     change_auth_indicators(context, auth_indicators);
651 
652     if (old_pac == NULL ||
653         (client != NULL && (flags & KRB5_KDB_FLAG_PROTOCOL_TRANSITION))) {
654         /* Generating an initial PAC. */
655         assert(client != NULL);
656         data = string2data("fake");
657         check(krb5_pac_add_buffer(context, new_pac, KRB5_PAC_LOGON_INFO,
658                                   &data));
659 
660         if (replaced_reply_key != NULL) {
661             /* Add a fake PAC_CREDENTIALS_INFO buffer so we can test whether
662              * this parameter was set. */
663             data = string2data("fake credinfo");
664             check(krb5_pac_add_buffer(context, new_pac,
665                                       KRB5_PAC_CREDENTIALS_INFO, &data));
666         }
667         return 0;
668     } else {
669         /* Field copying - my favorite! */
670         if (old_pac != NULL)
671             check(krb5_pac_get_types(context, old_pac, &num_buffers, &types));
672 
673         for (i = 0; i < num_buffers; i++) {
674             /* Skip buffer types handled by KDC. */
675             if (types[i] == KRB5_PAC_SERVER_CHECKSUM ||
676                 types[i] == KRB5_PAC_PRIVSVR_CHECKSUM ||
677                 types[i] == KRB5_PAC_TICKET_CHECKSUM ||
678                 types[i] == KRB5_PAC_CLIENT_INFO ||
679                 types[i] == KRB5_PAC_DELEGATION_INFO)
680                 continue;
681 
682             check(krb5_pac_get_buffer(context, old_pac, types[i], &data));
683 
684             if (types[i] == KRB5_PAC_LOGON_INFO) {
685                 found_logon_info = TRUE;
686                 assert(data_eq_string(data, "fake"));
687             }
688 
689             check(krb5_pac_add_buffer(context, new_pac, types[i], &data));
690             krb5_free_data_contents(context, &data);
691         }
692 
693         if (old_pac != NULL)
694             assert(found_logon_info);
695 
696         free(types);
697     }
698 
699     return 0;
700 }
701 
702 static krb5_boolean
match_in_table(krb5_context context,const char * table,const char * sprinc,const char * tprinc)703 match_in_table(krb5_context context, const char *table, const char *sprinc,
704                const char *tprinc)
705 {
706     testhandle h = context->dal_handle->db_context;
707     krb5_error_code ret;
708     char **values, **v;
709     krb5_boolean found = FALSE;
710 
711     set_names(h, table, sprinc, NULL);
712     ret = profile_get_values(h->profile, h->names, &values);
713     assert(ret == 0 || ret == PROF_NO_RELATION);
714     if (ret)
715         return FALSE;
716     for (v = values; *v != NULL; v++) {
717         if (tprinc == NULL || strcmp(*v, tprinc) == 0) {
718             found = TRUE;
719             break;
720         }
721     }
722     profile_free_list(values);
723     return found;
724 }
725 
726 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)727 test_check_allowed_to_delegate(krb5_context context,
728                                krb5_const_principal client,
729                                const krb5_db_entry *server,
730                                krb5_const_principal proxy)
731 {
732     char *sprinc, *tprinc = NULL;
733     krb5_boolean found = FALSE;
734 
735     check(krb5_unparse_name_flags(context, server->princ,
736                                   KRB5_PRINCIPAL_UNPARSE_NO_REALM, &sprinc));
737     if (proxy != NULL) {
738         check(krb5_unparse_name_flags(context, proxy,
739                                       KRB5_PRINCIPAL_UNPARSE_NO_REALM,
740                                       &tprinc));
741     }
742     found = match_in_table(context, "delegation", sprinc, tprinc);
743     krb5_free_unparsed_name(context, sprinc);
744     krb5_free_unparsed_name(context, tprinc);
745     return found ? 0 : KRB5KDC_ERR_BADOPTION;
746 }
747 
748 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)749 test_allowed_to_delegate_from(krb5_context context,
750                               krb5_const_principal client,
751                               krb5_const_principal server,
752                               krb5_pac server_pac,
753                               const krb5_db_entry *proxy)
754 {
755     char *proxy_princ, *server_princ, *pac_client_princ, *client_princ;
756     krb5_boolean found = FALSE;
757 
758     assert(server_pac != NULL);
759 
760     check(krb5_unparse_name(context, proxy->princ, &proxy_princ));
761     check(krb5_unparse_name(context, server, &server_princ));
762     check(krb5_unparse_name(context, client, &client_princ));
763 
764     check(krb5_pac_get_client_info(context, server_pac, NULL,
765                                    &pac_client_princ));
766 
767     /* Skip realm portion if not present in PAC. */
768     assert(strncmp(pac_client_princ, server_princ,
769                    strlen(pac_client_princ)) == 0);
770 
771     free(pac_client_princ);
772 
773     found = match_in_table(context, "rbcd", proxy_princ, server_princ);
774     krb5_free_unparsed_name(context, proxy_princ);
775     krb5_free_unparsed_name(context, server_princ);
776     krb5_free_unparsed_name(context, client_princ);
777     return found ? 0 : KRB5KDC_ERR_BADOPTION;
778 }
779 
780 kdb_vftabl PLUGIN_SYMBOL_NAME(krb5_test, kdb_function_table) = {
781     KRB5_KDB_DAL_MAJOR_VERSION,             /* major version number */
782     0,                                      /* minor version number */
783     test_init,
784     test_cleanup,
785     test_open,
786     test_close,
787     NULL, /* create */
788     NULL, /* destroy */
789     NULL, /* get_age */
790     NULL, /* lock */
791     NULL, /* unlock */
792     test_get_principal,
793     NULL, /* put_principal */
794     NULL, /* delete_principal */
795     NULL, /* rename_principal */
796     NULL, /* iterate */
797     NULL, /* create_policy */
798     NULL, /* get_policy */
799     NULL, /* put_policy */
800     NULL, /* iter_policy */
801     NULL, /* delete_policy */
802     test_fetch_master_key,
803     test_fetch_master_key_list,
804     NULL, /* store_master_key_list */
805     NULL, /* dbe_search_enctype */
806     NULL, /* change_pwd */
807     NULL, /* promote_db */
808     test_decrypt_key_data,
809     test_encrypt_key_data,
810     NULL, /* check_transited_realms */
811     NULL, /* check_policy_as */
812     NULL, /* check_policy_tgs */
813     NULL, /* audit_as_req */
814     NULL, /* refresh_config */
815     test_check_allowed_to_delegate,
816     NULL, /* free_principal_e_data */
817     test_get_s4u_x509_principal,
818     test_allowed_to_delegate_from,
819     test_issue_pac,
820 };
821