xref: /freebsd/crypto/krb5/src/tests/kdbtest.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* tests/kdbtest.c - test program to exercise KDB modules */
3 /*
4  * Copyright (C) 2012 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 test program uses libkdb5 APIs to exercise as much of the LDAP and DB2
35  * back ends.
36  */
37 
38 #include <krb5.h>
39 #include <kadm5/admin.h>
40 #include <string.h>
41 
42 static krb5_context ctx;
43 
44 #define CHECK(code) check(code, __LINE__)
45 #define CHECK_COND(val) check_cond(val, __LINE__)
46 
47 static void
check(krb5_error_code code,int lineno)48 check(krb5_error_code code, int lineno)
49 {
50     const char *errmsg;
51 
52     if (code) {
53         errmsg = krb5_get_error_message(ctx, code);
54         fprintf(stderr, "Unexpected error at line %d: %s\n", lineno, errmsg);
55         krb5_free_error_message(ctx, errmsg);
56         exit(1);
57     }
58 }
59 
60 static void
check_cond(int value,int lineno)61 check_cond(int value, int lineno)
62 {
63     if (!value) {
64         fprintf(stderr, "Unexpected result at line %d\n", lineno);
65         exit(1);
66     }
67 }
68 
69 static krb5_data princ_data[2] = {
70     { KV5M_DATA, 6, "xy*(z)" },
71     { KV5M_DATA, 12, "+<> *()\\#\",;" }
72 };
73 
74 static krb5_principal_data sample_princ = {
75     KV5M_PRINCIPAL,
76     { KV5M_DATA, 11, "KRBTEST.COM" },
77     princ_data, 2, KRB5_NT_UNKNOWN
78 };
79 
80 static krb5_principal_data xrealm_princ = {
81     KV5M_PRINCIPAL,
82     { KV5M_DATA, 12, "KRBTEST2.COM" },
83     princ_data, 2, KRB5_NT_UNKNOWN
84 };
85 
86 #define U(x) (unsigned char *)x
87 
88 /*
89  * tl1 through tl4 are normalized to attributes in the LDAP back end.  tl5 is
90  * stored as untranslated tl-data.  tl3 contains an encoded osa_princ_ent with
91  * a policy reference to "<test*>".
92  */
93 static krb5_tl_data tl5 = { NULL, KRB5_TL_MKVNO, 2, U("\0\1") };
94 static krb5_tl_data tl4 = { &tl5, KRB5_TL_LAST_ADMIN_UNLOCK, 4,
95                             U("\6\0\0\0") };
96 static krb5_tl_data tl3 = { &tl4, KRB5_TL_KADM_DATA, 32,
97                             U("\x12\x34\x5C\x01\x00\x00\x00\x08"
98                               "\x3C\x74\x65\x73\x74\x2A\x3E\x00"
99                               "\x00\x00\x08\x00\x00\x00\x00\x00"
100                               "\x00\x00\x00\x00\x00\x00\x00\x00") };
101 static krb5_tl_data tl2 = { &tl3, KRB5_TL_MOD_PRINC, 8, U("\5\6\7\0x@Y\0") };
102 static krb5_tl_data tl1 = { &tl2, KRB5_TL_LAST_PWD_CHANGE, 4, U("\1\2\3\4") };
103 
104 /* An encoded osa_print_enc with no policy reference. */
105 static krb5_tl_data tl_no_policy = { NULL, KRB5_TL_KADM_DATA, 24,
106                                      U("\x12\x34\x5C\x01\x00\x00\x00\x00"
107                                        "\x00\x00\x00\x00\x00\x00\x00\x00"
108                                        "\x00\x00\x00\x02\x00\x00\x00\x00") };
109 
110 static krb5_key_data keys[] = {
111     {
112         2,                          /* key_data_ver */
113         2,                          /* key_data_kvno */
114         { ENCTYPE_AES256_CTS_HMAC_SHA1_96, KRB5_KDB_SALTTYPE_SPECIAL },
115         { 32, 7 },
116         { U("\x17\xF2\x75\xF2\x95\x4F\x2E\xD1"
117             "\xF9\x0C\x37\x7B\xA7\xF4\xD6\xA3"
118             "\x69\xAA\x01\x36\xE0\xBF\x0C\x92"
119             "\x7A\xD6\x13\x3C\x69\x37\x59\xA9"),
120           U("expsalt") }
121     },
122     {
123         2,                          /* key_data_ver */
124         2,                          /* key_data_kvno */
125         { ENCTYPE_AES128_CTS_HMAC_SHA1_96, 0 },
126         { 16, 0 },
127         { U("\xDC\xEE\xB7\x0B\x3D\xE7\x65\x62"
128             "\xE6\x89\x22\x6C\x76\x42\x91\x48"),
129           NULL }
130     }
131 };
132 #undef U
133 
134 static char polname[] = "<test*>";
135 
136 static krb5_db_entry sample_entry = {
137     0,
138     KRB5_KDB_V1_BASE_LENGTH,
139     /* mask */
140     KADM5_PRINCIPAL | KADM5_PRINC_EXPIRE_TIME | KADM5_PW_EXPIRATION |
141     KADM5_ATTRIBUTES | KADM5_MAX_LIFE | KADM5_POLICY | KADM5_MAX_RLIFE |
142     KADM5_LAST_SUCCESS | KADM5_LAST_FAILED | KADM5_FAIL_AUTH_COUNT |
143     KADM5_KEY_DATA | KADM5_TL_DATA,
144     /* attributes */
145     KRB5_KDB_REQUIRES_PRE_AUTH | KRB5_KDB_REQUIRES_HW_AUTH |
146     KRB5_KDB_DISALLOW_SVR,
147     1234,                       /* max_life */
148     5678,                       /* max_renewable_life */
149     9012,                       /* expiration */
150     3456,                       /* pw_expiration */
151     1,                          /* last_success */
152     5,                          /* last_failed */
153     2,                          /* fail_auth_count */
154     5,                          /* n_tl_data */
155     2,                          /* n_key_data */
156     0, NULL,                    /* e_length, e_data */
157     &sample_princ,
158     &tl1,
159     keys
160 };
161 
162 static osa_policy_ent_rec sample_policy = {
163     0,                          /* version */
164     polname,                    /* name */
165     1357,                       /* pw_min_life */
166     100,                        /* pw_max_life */
167     6,                          /* pw_min_length */
168     2,                          /* pw_min_classes */
169     3,                          /* pw_history_num */
170     0,                          /* policy_refcnt */
171     2,                          /* pw_max_fail */
172     60,                         /* pw_failcnt_interval */
173     120,                        /* pw_lockout_duration */
174     0,                          /* attributes */
175     2468,                       /* max_life */
176     3579,                       /* max_renewable_life */
177     "aes",                      /* allowed_keysalts */
178     0, NULL                     /* n_tl_data, tl_data */
179 };
180 
181 /* Compare pol against sample_policy. */
182 static void
check_policy(osa_policy_ent_t pol)183 check_policy(osa_policy_ent_t pol)
184 {
185     CHECK_COND(strcmp(pol->name, sample_policy.name) == 0);
186     CHECK_COND(pol->pw_min_life == sample_policy.pw_min_life);
187     CHECK_COND(pol->pw_max_life == sample_policy.pw_max_life);
188     CHECK_COND(pol->pw_min_length == sample_policy.pw_min_length);
189     CHECK_COND(pol->pw_min_classes == sample_policy.pw_min_classes);
190     CHECK_COND(pol->pw_history_num == sample_policy.pw_history_num);
191     CHECK_COND(pol->pw_max_life == sample_policy.pw_max_life);
192     CHECK_COND(pol->pw_failcnt_interval == sample_policy.pw_failcnt_interval);
193     CHECK_COND(pol->pw_lockout_duration == sample_policy.pw_lockout_duration);
194     CHECK_COND(pol->attributes == sample_policy.attributes);
195     CHECK_COND(pol->max_life == sample_policy.max_life);
196     CHECK_COND(pol->max_renewable_life == sample_policy.max_renewable_life);
197     CHECK_COND(strcmp(pol->allowed_keysalts,
198                       sample_policy.allowed_keysalts) == 0);
199 }
200 
201 /* Compare ent against sample_entry. */
202 static void
check_entry(krb5_db_entry * ent)203 check_entry(krb5_db_entry *ent)
204 {
205     krb5_int16 i, j;
206     krb5_key_data *k1, *k2;
207     krb5_tl_data *tl, etl;
208 
209     CHECK_COND(ent->attributes == sample_entry.attributes);
210     CHECK_COND(ent->max_life == sample_entry.max_life);
211     CHECK_COND(ent->max_renewable_life == sample_entry.max_renewable_life);
212     CHECK_COND(ent->expiration == sample_entry.expiration);
213     CHECK_COND(ent->pw_expiration == sample_entry.pw_expiration);
214     CHECK_COND(ent->last_success == sample_entry.last_success);
215     CHECK_COND(ent->last_failed == sample_entry.last_failed);
216     CHECK_COND(ent->fail_auth_count == sample_entry.fail_auth_count);
217     CHECK_COND(krb5_principal_compare(ctx, ent->princ, sample_entry.princ));
218     CHECK_COND(ent->n_key_data == sample_entry.n_key_data);
219     for (i = 0; i < ent->n_key_data; i++) {
220         k1 = &ent->key_data[i];
221         k2 = &sample_entry.key_data[i];
222         CHECK_COND(k1->key_data_ver == k2->key_data_ver);
223         CHECK_COND(k1->key_data_kvno == k2->key_data_kvno);
224         for (j = 0; j < k1->key_data_ver; j++) {
225             CHECK_COND(k1->key_data_type[j] == k2->key_data_type[j]);
226             CHECK_COND(k1->key_data_length[j] == k2->key_data_length[j]);
227             CHECK_COND(memcmp(k1->key_data_contents[j],
228                               k2->key_data_contents[j],
229                               k1->key_data_length[j]) == 0);
230         }
231     }
232     for (tl = sample_entry.tl_data; tl != NULL; tl = tl->tl_data_next) {
233         etl.tl_data_type = tl->tl_data_type;
234         CHECK(krb5_dbe_lookup_tl_data(ctx, ent, &etl));
235         CHECK_COND(tl->tl_data_length == etl.tl_data_length);
236         CHECK_COND(memcmp(tl->tl_data_contents, etl.tl_data_contents,
237                           tl->tl_data_length) == 0);
238     }
239 }
240 
241 /* Audit a successful or failed preauth attempt for *entp.  Then reload *entp
242  * (by fetching sample_princ) so we can see the effect. */
243 static void
sim_preauth(krb5_timestamp authtime,krb5_boolean ok,krb5_db_entry ** entp)244 sim_preauth(krb5_timestamp authtime, krb5_boolean ok, krb5_db_entry **entp)
245 {
246     /* Both back ends ignore the request, local_addr, and remote_addr
247      * parameters for now. */
248     krb5_db_audit_as_req(ctx, NULL, NULL, NULL, *entp, *entp, authtime,
249                          ok ? 0 : KRB5KDC_ERR_PREAUTH_FAILED);
250     krb5_db_free_principal(ctx, *entp);
251     CHECK(krb5_db_get_principal(ctx, &sample_princ, 0, entp));
252 }
253 
254 static krb5_error_code
iter_princ_handler(void * data,krb5_db_entry * ent)255 iter_princ_handler(void *data, krb5_db_entry *ent)
256 {
257     int *count = data;
258 
259     CHECK_COND(krb5_principal_compare(ctx, ent->princ, sample_entry.princ));
260     (*count)++;
261     return 0;
262 }
263 
264 static void
iter_pol_handler(void * data,osa_policy_ent_t pol)265 iter_pol_handler(void *data, osa_policy_ent_t pol)
266 {
267     int *count = data;
268 
269     CHECK_COND(strcmp(pol->name, sample_policy.name) == 0);
270     (*count)++;
271 }
272 
273 int
main()274 main()
275 {
276     krb5_db_entry *ent;
277     osa_policy_ent_t pol;
278     krb5_pa_data **e_data;
279     const char *status;
280     int count;
281 
282     CHECK(krb5_init_context_profile(NULL, KRB5_INIT_CONTEXT_KDC, &ctx));
283 
284     /* If we can, revert to requiring all entries match sample_princ in
285      * iter_princ_handler */
286     CHECK_COND(krb5_db_inited(ctx) != 0);
287     CHECK(krb5_db_create(ctx, NULL));
288     CHECK(krb5_db_inited(ctx));
289     CHECK(krb5_db_fini(ctx));
290     CHECK_COND(krb5_db_inited(ctx) != 0);
291 
292     CHECK_COND(krb5_db_inited(ctx) != 0);
293     CHECK(krb5_db_open(ctx, NULL, KRB5_KDB_OPEN_RW | KRB5_KDB_SRV_TYPE_ADMIN));
294     CHECK(krb5_db_inited(ctx));
295 
296     /* Manipulate a policy, leaving it in place at the end. */
297     CHECK_COND(krb5_db_put_policy(ctx, &sample_policy) != 0);
298     CHECK_COND(krb5_db_delete_policy(ctx, polname) != 0);
299     CHECK_COND(krb5_db_get_policy(ctx, polname, &pol) == KRB5_KDB_NOENTRY);
300     CHECK(krb5_db_create_policy(ctx, &sample_policy));
301     CHECK_COND(krb5_db_create_policy(ctx, &sample_policy) != 0);
302     CHECK(krb5_db_get_policy(ctx, polname, &pol));
303     check_policy(pol);
304     pol->pw_min_length--;
305     CHECK(krb5_db_put_policy(ctx, pol));
306     krb5_db_free_policy(ctx, pol);
307     CHECK(krb5_db_get_policy(ctx, polname, &pol));
308     CHECK_COND(pol->pw_min_length == sample_policy.pw_min_length - 1);
309     krb5_db_free_policy(ctx, pol);
310     CHECK(krb5_db_delete_policy(ctx, polname));
311     CHECK_COND(krb5_db_put_policy(ctx, &sample_policy) != 0);
312     CHECK_COND(krb5_db_delete_policy(ctx, polname) != 0);
313     CHECK_COND(krb5_db_get_policy(ctx, polname, &pol) == KRB5_KDB_NOENTRY);
314     CHECK(krb5_db_create_policy(ctx, &sample_policy));
315     count = 0;
316     CHECK(krb5_db_iter_policy(ctx, NULL, iter_pol_handler, &count));
317     CHECK_COND(count == 1);
318 
319     /* Create a principal. */
320     CHECK_COND(krb5_db_delete_principal(ctx, &sample_princ) ==
321                KRB5_KDB_NOENTRY);
322     CHECK_COND(krb5_db_get_principal(ctx, &xrealm_princ, 0, &ent) ==
323                KRB5_KDB_NOENTRY);
324     CHECK(krb5_db_put_principal(ctx, &sample_entry));
325     /* Putting again will fail with LDAP (due to KADM5_PRINCIPAL in mask)
326      * but succeed with DB2, so don't check the result. */
327     (void)krb5_db_put_principal(ctx, &sample_entry);
328     /* But it should succeed in both back ends with KADM5_LOAD in mask. */
329     sample_entry.mask |= KADM5_LOAD;
330     CHECK(krb5_db_put_principal(ctx, &sample_entry));
331     sample_entry.mask &= ~KADM5_LOAD;
332     /* Fetch and compare the added principal. */
333     CHECK(krb5_db_get_principal(ctx, &sample_princ, 0, &ent));
334     check_entry(ent);
335 
336     /* We can't set up a successful allowed-to-delegate check through existing
337      * APIs yet, but we can make a failed check. */
338     CHECK_COND(krb5_db_check_allowed_to_delegate(ctx, &sample_princ, ent,
339                                                  &sample_princ) != 0);
340 
341     /* Exercise lockout code. */
342     /* Policy params: max_fail 2, failcnt_interval 60, lockout_duration 120 */
343     /* Initial state: last_success 1, last_failed 5, fail_auth_count 2,
344      * last admin unlock 6 */
345     /* Check succeeds due to last admin unlock. */
346     CHECK(krb5_db_check_policy_as(ctx, NULL, ent, ent, 7, &status, &e_data));
347     /* Failure count resets to 1 due to last admin unlock. */
348     sim_preauth(8, FALSE, &ent);
349     CHECK_COND(ent->fail_auth_count == 1 && ent->last_failed == 8);
350     /* Failure count resets to 1 due to failcnt_interval */
351     sim_preauth(70, FALSE, &ent);
352     CHECK_COND(ent->fail_auth_count == 1 && ent->last_failed == 70);
353     /* Failure count resets to 0 due to successful preauth. */
354     sim_preauth(75, TRUE, &ent);
355     CHECK_COND(ent->fail_auth_count == 0 && ent->last_success == 75);
356     /* Failure count increments to 2 and stops incrementing. */
357     sim_preauth(80, FALSE, &ent);
358     CHECK_COND(ent->fail_auth_count == 1 && ent->last_failed == 80);
359     sim_preauth(100, FALSE, &ent);
360     CHECK_COND(ent->fail_auth_count == 2 && ent->last_failed == 100);
361     sim_preauth(110, FALSE, &ent);
362     CHECK_COND(ent->fail_auth_count == 2 && ent->last_failed == 100);
363     /* Check fails due to reaching maximum failure count. */
364     CHECK_COND(krb5_db_check_policy_as(ctx, NULL, ent, ent, 170, &status,
365                                        &e_data) == KRB5KDC_ERR_CLIENT_REVOKED);
366     /* Check succeeds after lockout_duration has passed. */
367     CHECK(krb5_db_check_policy_as(ctx, NULL, ent, ent, 230, &status, &e_data));
368     /* Failure count resets to 1 on next failure. */
369     sim_preauth(240, FALSE, &ent);
370     CHECK_COND(ent->fail_auth_count == 1 && ent->last_failed == 240);
371 
372     /* Exercise LDAP code to clear a policy reference and to set the key
373      * data on an existing principal. */
374     CHECK(krb5_dbe_update_tl_data(ctx, ent, &tl_no_policy));
375     ent->mask = KADM5_POLICY_CLR | KADM5_KEY_DATA;
376     CHECK(krb5_db_put_principal(ctx, ent));
377     CHECK(krb5_db_delete_policy(ctx, polname));
378 
379     /* Put the modified entry again (with KDB_TL_USER_INFO tl-data for LDAP) as
380      * from a load operation. */
381     ent->mask = (sample_entry.mask & ~KADM5_POLICY) | KADM5_LOAD;
382     CHECK(krb5_db_put_principal(ctx, ent));
383 
384     /* Exercise LDAP code to create a new principal at a DN from
385      * KDB_TL_USER_INFO tl-data. */
386     CHECK(krb5_db_delete_principal(ctx, &sample_princ));
387     CHECK(krb5_db_put_principal(ctx, ent));
388     krb5_db_free_principal(ctx, ent);
389 
390     /* Exercise principal iteration code. */
391     count = 0;
392     CHECK(krb5_db_iterate(ctx, "xy*", iter_princ_handler, &count, 0));
393     CHECK_COND(count == 1);
394 
395     CHECK(krb5_db_fini(ctx));
396     CHECK_COND(krb5_db_inited(ctx) != 0);
397 
398     /* It might be nice to exercise krb5_db_destroy here, but the LDAP module
399      * doesn't support it. */
400 
401     krb5_free_context(ctx);
402     return 0;
403 }
404