2 * Copyright (c) 2020 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * 3. Neither the name of the Institute nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35 * This program implements an ephemeral, memory-based HDB backend, stores into
36 * it just one HDB entry -one for a namespace- then checks that virtual
37 * principals are returned below that namespace by hdb_fetch_kvno(), and that
38 * the logic for automatic key rotation of virtual principals is correct.
44 static KeyRotation krs
[2];
45 static const char *base_pw
[2] = { "Testing123...", "Tested123..." };
48 HDB hdb
; /* generic members */
50 * Make this dict a global, add a mutex lock around it, and a .finit and/or
51 * atexit() handler to free it, and we'd have a first-class MEMORY HDB.
53 * What would a first-class MEMORY HDB be good for though, besides testing?
55 * However, we could move this dict into `HDB' and then have _hdb_store()
56 * and friends support it as a cache for frequently-used & seldom-changing
57 * entries, such as: K/M, namespaces, and krbtgt principals. That would
58 * speed up lookups, especially for backends with poor reader-writer
59 * concurrency (DB, LMDB) and LDAP. Such entries could be cached for a
60 * minute or three at a time.
71 static krb5_error_code
72 TDB_close(krb5_context context
, HDB
*db
)
77 static krb5_error_code
78 TDB_destroy(krb5_context context
, HDB
*db
)
80 TEST_HDB
*tdb
= (void *)db
;
82 heim_release(tdb
->dict
);
83 free(tdb
->hdb
.hdb_name
);
88 static krb5_error_code
89 TDB_set_sync(krb5_context context
, HDB
*db
, int on
)
94 static krb5_error_code
95 TDB_lock(krb5_context context
, HDB
*db
, int operation
)
101 static krb5_error_code
102 TDB_unlock(krb5_context context
, HDB
*db
)
108 static krb5_error_code
109 TDB_firstkey(krb5_context context
, HDB
*db
, unsigned flags
, hdb_entry
*entry
)
112 /* Tricky thing: heim_dict_iterate_f() is inconvenient here */
113 /* We need this to check that virtual principals aren't created */
117 static krb5_error_code
118 TDB_nextkey(krb5_context context
, HDB
*db
, unsigned flags
, hdb_entry
*entry
)
121 /* Tricky thing: heim_dict_iterate_f() is inconvenient here */
122 /* We need this to check that virtual principals aren't created */
126 static krb5_error_code
127 TDB_rename(krb5_context context
, HDB
*db
, const char *new_name
)
132 static krb5_error_code
133 TDB__get(krb5_context context
, HDB
*db
, krb5_data key
, krb5_data
*reply
)
135 krb5_error_code ret
= 0;
136 TEST_HDB
*tdb
= (void *)db
;
137 heim_object_t k
, v
= NULL
;
139 if ((k
= heim_data_create(key
.data
, key
.length
)) == NULL
)
140 ret
= krb5_enomem(context
);
141 if (ret
== 0 && (v
= heim_dict_get_value(tdb
->dict
, k
)) == NULL
)
142 ret
= HDB_ERR_NOENTRY
;
144 ret
= krb5_data_copy(reply
, heim_data_get_ptr(v
), heim_data_get_length(v
));
149 static krb5_error_code
150 TDB__put(krb5_context context
, HDB
*db
, int rplc
, krb5_data kd
, krb5_data vd
)
152 krb5_error_code ret
= 0;
153 TEST_HDB
*tdb
= (void *)db
;
154 heim_object_t k
= NULL
;
155 heim_object_t v
= NULL
;
157 if ((k
= heim_data_create(kd
.data
, kd
.length
)) == NULL
||
158 (v
= heim_data_create(vd
.data
, vd
.length
)) == NULL
)
159 ret
= krb5_enomem(context
);
160 if (ret
== 0 && !rplc
&& heim_dict_get_value(tdb
->dict
, k
) != NULL
)
161 ret
= HDB_ERR_EXISTS
;
162 if (ret
== 0 && heim_dict_set_value(tdb
->dict
, k
, v
))
163 ret
= krb5_enomem(context
);
169 static krb5_error_code
170 TDB__del(krb5_context context
, HDB
*db
, krb5_data key
)
172 krb5_error_code ret
= 0;
173 TEST_HDB
*tdb
= (void *)db
;
176 if ((k
= heim_data_create(key
.data
, key
.length
)) == NULL
)
177 ret
= krb5_enomem(context
);
178 if (ret
== 0 && heim_dict_get_value(tdb
->dict
, k
) == NULL
)
179 ret
= HDB_ERR_NOENTRY
;
181 heim_dict_delete_key(tdb
->dict
, k
);
186 static krb5_error_code
187 TDB_open(krb5_context context
, HDB
*db
, int flags
, mode_t mode
)
192 static krb5_error_code
193 hdb_test_create(krb5_context context
, struct HDB
**db
, const char *arg
)
197 if ((tdb
= calloc(1, sizeof(tdb
[0]))) == NULL
||
198 (tdb
->hdb
.hdb_name
= strdup(arg
)) == NULL
||
199 (tdb
->dict
= heim_dict_create(10)) == NULL
) {
201 free(tdb
->hdb
.hdb_name
);
203 return krb5_enomem(context
);
206 tdb
->hdb
.hdb_db
= NULL
;
207 tdb
->hdb
.hdb_master_key_set
= 0;
208 tdb
->hdb
.hdb_openp
= 0;
209 tdb
->hdb
.hdb_capability_flags
= HDB_CAP_F_HANDLE_ENTERPRISE_PRINCIPAL
;
210 tdb
->hdb
.hdb_open
= TDB_open
;
211 tdb
->hdb
.hdb_close
= TDB_close
;
212 tdb
->hdb
.hdb_fetch_kvno
= _hdb_fetch_kvno
;
213 tdb
->hdb
.hdb_store
= _hdb_store
;
214 tdb
->hdb
.hdb_remove
= _hdb_remove
;
215 tdb
->hdb
.hdb_firstkey
= TDB_firstkey
;
216 tdb
->hdb
.hdb_nextkey
= TDB_nextkey
;
217 tdb
->hdb
.hdb_lock
= TDB_lock
;
218 tdb
->hdb
.hdb_unlock
= TDB_unlock
;
219 tdb
->hdb
.hdb_rename
= TDB_rename
;
220 tdb
->hdb
.hdb__get
= TDB__get
;
221 tdb
->hdb
.hdb__put
= TDB__put
;
222 tdb
->hdb
.hdb__del
= TDB__del
;
223 tdb
->hdb
.hdb_destroy
= TDB_destroy
;
224 tdb
->hdb
.hdb_set_sync
= TDB_set_sync
;
230 static krb5_error_code
231 hdb_test_init(krb5_context context
, void **ctx
)
237 static void hdb_test_fini(void *ctx
)
241 struct hdb_method hdb_test
=
245 HDB_INTERFACE_VERSION
,
248 1 /*is_file_based*/, 1 /*can_taste*/,
252 .minor_version
= HDB_INTERFACE_VERSION
,
253 .init
= hdb_test_init
,
254 .fini
= hdb_test_fini
,
258 .create
= hdb_test_create
262 static krb5_error_code
263 make_base_key(krb5_context context
,
264 krb5_const_principal p
,
268 return krb5_string_to_key(context
, KRB5_ENCTYPE_AES128_CTS_HMAC_SHA256_128
,
272 static krb5_error_code
273 tderive_key(krb5_context context
,
283 krb5_error_code ret
= 0;
284 krb5_crypto crypto
= NULL
;
285 EncryptionKey intermediate
;
290 n
= toffset
/ kr
->period
;
291 *set_time
= kr
->epoch
+ kr
->period
* n
;
292 *kvno
= kr
->base_kvno
+ n
;
297 /* Derive intermediate key */
298 pad
.data
= (void *)(uintptr_t)p
;
299 pad
.length
= strlen(p
);
300 ret
= krb5_enctype_keysize(context
, base
->keytype
, &len
);
302 ret
= krb5_crypto_init(context
, base
, 0, &crypto
);
304 ret
= krb5_crypto_prfplus(context
, crypto
, &pad
, len
, &out
);
306 krb5_crypto_destroy(context
, crypto
);
309 ret
= krb5_random_to_key(context
, etype
, out
.data
, out
.length
,
311 krb5_data_free(&out
);
313 /* Derive final key */
315 pad
.length
= sizeof(*kvno
);
317 ret
= krb5_enctype_keysize(context
, etype
, &len
);
319 ret
= krb5_crypto_init(context
, &intermediate
, 0, &crypto
);
321 *kvno
= htonl(*kvno
);
322 ret
= krb5_crypto_prfplus(context
, crypto
, &pad
, len
, &out
);
323 *kvno
= ntohl(*kvno
);
326 krb5_crypto_destroy(context
, crypto
);
328 ret
= krb5_random_to_key(context
, etype
, out
.data
, out
.length
, k
);
329 krb5_data_free(&out
);
331 free_EncryptionKey(&intermediate
);
335 /* Create a namespace principal */
337 make_namespace(krb5_context context
, HDB
*db
, const char *name
)
339 krb5_error_code ret
= 0;
343 memset(&k
, 0, sizeof(k
));
347 /* Setup the HDB entry */
348 memset(&e
, 0, sizeof(e
));
349 e
.created_by
.time
= krs
[0].epoch
;
350 e
.valid_start
= e
.valid_end
= e
.pw_end
= 0;
352 e
.flags
= int2HDBFlags(0);
353 e
.flags
.server
= e
.flags
.client
= 1;
358 (e
.etypes
= malloc(sizeof(*e
.etypes
))) == NULL
)
359 ret
= krb5_enomem(context
);
363 (e
.etypes
->val
= calloc(e
.etypes
->len
,
364 sizeof(e
.etypes
->val
[0]))) == NULL
)
365 ret
= krb5_enomem(context
);
367 e
.etypes
->val
[0] = KRB5_ENCTYPE_AES128_CTS_HMAC_SHA256_128
;
368 e
.etypes
->val
[1] = KRB5_ENCTYPE_AES256_CTS_HMAC_SHA384_192
;
369 e
.etypes
->val
[2] = KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1_96
;
372 /* Setup max_life and max_renew */
374 (e
.max_life
= malloc(sizeof(*e
.max_life
))) == NULL
)
375 ret
= krb5_enomem(context
);
377 (e
.max_renew
= malloc(sizeof(*e
.max_renew
))) == NULL
)
378 ret
= krb5_enomem(context
);
380 /* Make it long, so we see the clamped max */
381 *e
.max_renew
= 2 * ((*e
.max_life
= 15 * 24 * 3600));
383 /* Setup principal name and created_by */
385 ret
= krb5_parse_name(context
, name
, &e
.principal
);
387 ret
= krb5_parse_name(context
, "admin@BAR.EXAMPLE",
388 &e
.created_by
.principal
);
390 /* Make base keys for first epoch */
392 ret
= make_base_key(context
, e
.principal
, base_pw
[0], &k
.key
);
394 add_Keys(&e
.keys
, &k
);
396 ret
= hdb_entry_set_pw_change_time(context
, &e
, krs
[0].epoch
);
398 e
.kvno
= krs
[0].base_key_kvno
;
400 /* Move them to history */
402 ret
= hdb_add_current_keys_to_history(context
, &e
);
405 /* Make base keys for second epoch */
407 ret
= make_base_key(context
, e
.principal
, base_pw
[1], &k
.key
);
409 add_Keys(&e
.keys
, &k
);
410 e
.kvno
= krs
[1].base_key_kvno
;
412 ret
= hdb_entry_set_pw_change_time(context
, &e
, krs
[1].epoch
);
414 /* Add the key rotation metadata */
416 ret
= hdb_entry_add_key_rotation(context
, &e
, 0, &krs
[0]);
418 ret
= hdb_entry_add_key_rotation(context
, &e
, 0, &krs
[1]);
421 ret
= db
->hdb_store(context
, db
, 0, &e
);
423 krb5_err(context
, 1, ret
, "failed to setup a namespace principal");
425 hdb_free_entry(context
, db
, &e
);
428 #define WK_PREFIX "WELLKNOWN/" HDB_WK_NAMESPACE "/"
430 static const char *expected
[] = {
431 WK_PREFIX
"_/bar.example@BAR.EXAMPLE",
432 "HTTP/bar.example@BAR.EXAMPLE",
433 "HTTP/foo.bar.example@BAR.EXAMPLE",
434 "host/foo.bar.example@BAR.EXAMPLE",
435 "HTTP/blah.foo.bar.example@BAR.EXAMPLE",
437 static const char *unexpected
[] = {
438 WK_PREFIX
"_/no.example@BAZ.EXAMPLE",
439 "HTTP/no.example@BAR.EXAMPLE",
440 "HTTP/foo.no.example@BAR.EXAMPLE",
441 "HTTP/blah.foo.no.example@BAR.EXAMPLE",
445 * We'll fetch as many entries as we have principal names in `expected[]', for
446 * as many KeyRotation periods as we have (between 1 and 3), and for up to 5
447 * different time offsets in each period.
449 #define NUM_OFFSETS 5
451 (sizeof(expected
) / sizeof(expected
[0])) *
452 (sizeof(krs
) / sizeof(krs
[0])) *
457 hist_key_compar(const void *va
, const void *vb
)
459 const hdb_keyset
*a
= va
;
460 const hdb_keyset
*b
= vb
;
462 return a
->kvno
- b
->kvno
;
466 * Fetch keys for some decent time in the given kr.
468 * `kr' is an index into the global `krs[]'.
469 * `t' is a number 0..4 inclusive that identifies a time period relative to the
470 * epoch of `krs[kr]' (see code below).
473 fetch_entries(krb5_context context
,
479 krb5_error_code ret
= 0;
480 krb5_principal p
= NULL
;
481 krb5_keyblock base_key
, dk
;
487 memset(&base_key
, 0, sizeof(base_key
));
489 /* Work out offset of first entry in `e[]' */
490 assert(kr
< sizeof(krs
) / sizeof(krs
[0]));
491 assert(t
< NUM_OFFSETS
);
492 b
= (kr
* NUM_OFFSETS
+ t
) * (sizeof(expected
) / sizeof(expected
[0]));
493 assert(b
< sizeof(e
) / sizeof(e
[0]));
494 assert(sizeof(e
) / sizeof(e
[0]) - b
>=
495 (sizeof(expected
) / sizeof(expected
[0])));
498 case 0: toffset
= 1; break; /* epoch + 1s */
499 case 1: toffset
= 1 + (krs
[kr
].period
>> 1); break; /* epoch + period/2 */
500 case 2: toffset
= 1 + (krs
[kr
].period
>> 2); break; /* epoch + period/4 */
501 case 3: toffset
= 1 + (krs
[kr
].period
>> 3); break; /* epoch + period/8 */
502 case 4: toffset
= 1 - (krs
[kr
].period
>> 3); break; /* epoch - period/8 */
505 for (i
= 0; ret
== 0 && i
< sizeof(expected
) / sizeof(expected
[0]); i
++) {
507 memset(ep
, 0, sizeof(*ep
));
509 ret
= krb5_parse_name(context
, expected
[i
], &p
);
510 if (ret
== 0 && i
== 0) {
511 if (toffset
< 0 && kr
)
512 ret
= make_base_key(context
, p
, base_pw
[kr
- 1], &base_key
);
514 ret
= make_base_key(context
, p
, base_pw
[kr
], &base_key
);
517 ret
= hdb_fetch_kvno(context
, db
, p
,
518 HDB_F_DECRYPT
| HDB_F_ALL_KVNOS
,
519 krs
[kr
].epoch
+ toffset
, 0, 0, ep
);
520 if (i
&& must_fail
&& ret
== 0)
521 krb5_errx(context
, 1,
522 "virtual principal that shouldn't exist does");
523 if (kr
== 0 && toffset
< 0 && ret
== HDB_ERR_NOENTRY
)
525 if (kr
== 0 && toffset
< 0) {
527 * Virtual principals don't exist before their earliest key
528 * rotation epoch's start time.
532 krb5_errx(context
, 1,
533 "namespace principal does not exist before its time");
536 krb5_errx(context
, 1,
537 "virtual principal exists before its time");
538 if (ret
!= HDB_ERR_NOENTRY
)
539 krb5_errx(context
, 1, "wrong error code");
544 !krb5_principal_compare(context
, p
, ep
->principal
))
545 krb5_errx(context
, 1, "wrong principal in fetched entry");
549 HDB_Ext_KeySet
*hist_keys
;
551 ext
= hdb_find_extension(ep
,
552 choice_HDB_extension_data_hist_keys
);
554 /* Sort key history by kvno, why not */
555 hist_keys
= &ext
->data
.u
.hist_keys
;
556 qsort(hist_keys
->val
, hist_keys
->len
,
557 sizeof(hist_keys
->val
[0]), hist_key_compar
);
561 krb5_free_principal(context
, p
);
563 if (ret
&& must_fail
) {
564 free_EncryptionKey(&base_key
);
568 krb5_err(context
, 1, ret
, "virtual principal test failed");
570 for (i
= 0; i
< sizeof(unexpected
) / sizeof(unexpected
[0]); i
++) {
571 memset(&no
, 0, sizeof(no
));
573 ret
= krb5_parse_name(context
, unexpected
[i
], &p
);
575 ret
= hdb_fetch_kvno(context
, db
, p
, HDB_F_DECRYPT
,
576 krs
[kr
].epoch
+ toffset
, 0, 0, &no
);
578 krb5_errx(context
, 1, "bogus principal exists, wat");
579 krb5_free_principal(context
, p
);
583 if (kr
== 0 && toffset
< 0)
589 * Add check that derived keys are a) different, b) as expected, using a
590 * set of test vectors or else by computing the expected keys here with
591 * code that's not shared with lib/hdb/common.c.
593 * Add check that we get expected past and/or future keys, not just current
596 for (i
= 1; ret
== 0 && i
< sizeof(expected
) / sizeof(expected
[0]); i
++) {
598 time_t set_time
, chg_time
;
602 ret
= tderive_key(context
, expected
[i
], &krs
[kr
], toffset
,
603 &base_key
, base_key
.keytype
, &dk
, &kvno
, &set_time
);
607 ret
= tderive_key(context
, expected
[i
], &krs
[kr
- 1],
608 krs
[kr
].epoch
- krs
[kr
- 1].epoch
+ toffset
,
609 &base_key
, base_key
.keytype
, &dk
, &kvno
, &set_time
);
612 krb5_err(context
, 1, ret
, "deriving keys for comparison");
614 if (kvno
!= ep
->kvno
)
615 krb5_errx(context
, 1, "kvno mismatch (%u != %u)", kvno
, ep
->kvno
);
616 (void) hdb_entry_get_pw_change_time(ep
, &chg_time
);
617 if (set_time
!= chg_time
)
618 krb5_errx(context
, 1, "key change time mismatch");
619 if (ep
->keys
.len
== 0)
620 krb5_errx(context
, 1, "no keys!");
621 if (ep
->keys
.val
[0].key
.keytype
!= dk
.keytype
)
622 krb5_errx(context
, 1, "enctype mismatch!");
623 if (ep
->keys
.val
[0].key
.keyvalue
.length
!=
625 krb5_errx(context
, 1, "key length mismatch!");
626 if (memcmp(ep
->keys
.val
[0].key
.keyvalue
.data
,
627 dk
.keyvalue
.data
, dk
.keyvalue
.length
) != 0)
628 krb5_errx(context
, 1, "key mismatch!");
629 if (memcmp(ep
->keys
.val
[0].key
.keyvalue
.data
,
630 e
[b
+ i
- 1].keys
.val
[0].key
.keyvalue
.data
,
631 dk
.keyvalue
.length
) == 0)
632 krb5_errx(context
, 1, "different virtual principals have the same keys!");
633 /* XXX Add check that we have the expected number of history keys */
634 free_EncryptionKey(&dk
);
636 free_EncryptionKey(&base_key
);
640 check_kvnos(krb5_context context
)
642 HDB_Ext_KeySet keysets
;
643 size_t i
, k
, m
, p
; /* iterator indices */
648 /* For every principal name */
649 for (i
= 0; i
< sizeof(expected
)/sizeof(expected
[0]); i
++) {
650 free_HDB_Ext_KeySet(&keysets
);
652 /* For every entry we've fetched for it */
653 for (k
= 0; k
< sizeof(e
)/sizeof(e
[0]); k
++) {
654 HDB_Ext_KeySet
*hist_keys
;
659 if ((k
% NUM_OFFSETS
) != i
)
663 if (ep
->principal
== NULL
)
664 continue; /* Didn't fetch this one */
667 * Check that the current keys for it match what we've seen already
668 * or else add them to `keysets'.
670 for (m
= 0; m
< keysets
.len
; m
++) {
671 if (ep
->kvno
== keysets
.val
[m
].kvno
) {
672 /* Check the key is the same */
673 if (ep
->keys
.val
[0].key
.keytype
!=
674 keysets
.val
[m
].keys
.val
[0].key
.keytype
||
675 ep
->keys
.val
[0].key
.keyvalue
.length
!=
676 keysets
.val
[m
].keys
.val
[0].key
.keyvalue
.length
||
677 memcmp(ep
->keys
.val
[0].key
.keyvalue
.data
,
678 keysets
.val
[m
].keys
.val
[0].key
.keyvalue
.data
,
679 ep
->keys
.val
[0].key
.keyvalue
.length
) != 0)
680 krb5_errx(context
, 1,
681 "key mismatch for same princ & kvno");
685 if (m
== keysets
.len
) {
691 if (add_HDB_Ext_KeySet(&keysets
, &ks
))
692 krb5_err(context
, 1, ENOMEM
, "out of memory");
698 /* For all non-current keysets, repeat the above */
699 ext
= hdb_find_extension(ep
,
700 choice_HDB_extension_data_hist_keys
);
703 hist_keys
= &ext
->data
.u
.hist_keys
;
704 for (p
= 0; p
< hist_keys
->len
; p
++) {
705 for (m
= 0; m
< keysets
.len
; m
++) {
706 if (keysets
.val
[m
].kvno
== hist_keys
->val
[p
].kvno
)
707 if (ep
->keys
.val
[0].key
.keytype
!=
708 keysets
.val
[m
].keys
.val
[0].key
.keytype
||
709 ep
->keys
.val
[0].key
.keyvalue
.length
!=
710 keysets
.val
[m
].keys
.val
[0].key
.keyvalue
.length
||
711 memcmp(ep
->keys
.val
[0].key
.keyvalue
.data
,
712 keysets
.val
[m
].keys
.val
[0].key
.keyvalue
.data
,
713 ep
->keys
.val
[0].key
.keyvalue
.length
) != 0)
714 krb5_errx(context
, 1,
715 "key mismatch for same princ & kvno");
717 if (m
== keysets
.len
) {
722 if (add_HDB_Ext_KeySet(&keysets
, &ks
))
723 krb5_err(context
, 1, ENOMEM
, "out of memory");
728 free_HDB_Ext_KeySet(&keysets
);
732 print_em(krb5_context context
)
734 HDB_Ext_KeySet
*hist_keys
;
738 for (i
= 0; i
< sizeof(e
)/sizeof(e
[0]); i
++) {
739 const char *name
= expected
[i
% (sizeof(expected
)/sizeof(expected
[0]))];
742 if (0 == i
% (sizeof(expected
)/sizeof(expected
[0])))
744 if (e
[i
].principal
== NULL
)
746 hex_encode(e
[i
].keys
.val
[0].key
.keyvalue
.data
,
747 e
[i
].keys
.val
[0].key
.keyvalue
.length
, &x
);
748 printf("%s %u %s\n", x
, e
[i
].kvno
, name
);
751 ext
= hdb_find_extension(&e
[i
], choice_HDB_extension_data_hist_keys
);
754 hist_keys
= &ext
->data
.u
.hist_keys
;
755 for (p
= 0; p
< hist_keys
->len
; p
++) {
756 hex_encode(hist_keys
->val
[p
].keys
.val
[0].key
.keyvalue
.data
,
757 hist_keys
->val
[p
].keys
.val
[0].key
.keyvalue
.length
, &x
);
758 printf("%s %u %s\n", x
, hist_keys
->val
[p
].kvno
, name
);
766 check_expected_kvnos(krb5_context context
)
768 HDB_Ext_KeySet
*hist_keys
;
772 for (i
= 0; i
< sizeof(expected
)/sizeof(expected
[0]); i
++) {
773 for (k
= 0; k
< sizeof(krs
)/sizeof(krs
[0]); k
++) {
774 hdb_entry
*ep
= &e
[k
* sizeof(expected
)/sizeof(expected
[0]) + i
];
776 if (ep
->principal
== NULL
)
778 for (m
= 0; m
< NUM_OFFSETS
; m
++) {
779 ext
= hdb_find_extension(ep
,
780 choice_HDB_extension_data_hist_keys
);
783 hist_keys
= &ext
->data
.u
.hist_keys
;
784 for (p
= 0; p
< hist_keys
->len
; p
++) {
785 fprintf(stderr
, "%s at %lu, %lu: history kvno %u\n",
786 expected
[i
], k
, m
, hist_keys
->val
[p
].kvno
);
789 fprintf(stderr
, "%s at %lu: kvno %u\n", expected
[i
], k
,
796 #define SOME_TIME 1596318329
797 #define SOME_BASE_KVNO 150
798 #define SOME_EPOCH (SOME_TIME - (7 * 24 * 3600) - (SOME_TIME % (7 * 24 * 3600)))
799 #define SOME_PERIOD 3600
803 "\tenable_virtual_hostbased_princs = true\n" \
804 "\tvirtual_hostbased_princ_mindots = 1\n" \
805 "\tvirtual_hostbased_princ_maxdots = 3\n" \
808 main(int argc
, char **argv
)
811 krb5_context context
;
815 setprogname(argv
[0]);
816 memset(e
, 0, sizeof(e
));
817 ret
= krb5_init_context(&context
);
819 ret
= krb5_set_config(context
, CONF
);
821 ret
= krb5_plugin_register(context
, PLUGIN_TYPE_DATA
, "hdb_test_interface",
824 ret
= hdb_create(context
, &db
, "test:mem");
826 krb5_err(context
, 1, ret
, "failed to setup HDB driver and test");
828 assert(db
->enable_virtual_hostbased_princs
);
829 assert(db
->virtual_hostbased_princ_ndots
== 1);
830 assert(db
->virtual_hostbased_princ_maxdots
== 3);
832 /* Setup key rotation metadata in a convenient way */
834 * FIXME Reorder these two KRs to match how we store them to avoid
835 * confusion. #0 should be future-most, #1 should past-post.
837 krs
[0].flags
= krs
[1].flags
= int2KeyRotationFlags(0);
838 krs
[0].epoch
= SOME_EPOCH
- 20 * 24 * 3600;
839 krs
[0].period
= SOME_PERIOD
>> 1;
840 krs
[0].base_kvno
= 150;
841 krs
[0].base_key_kvno
= 1;
842 krs
[1].epoch
= SOME_TIME
;
843 krs
[1].period
= SOME_PERIOD
;
844 krs
[1].base_kvno
= krs
[0].base_kvno
+ 1 + (krs
[1].epoch
+ (krs
[0].period
- 1) - krs
[0].epoch
) / krs
[0].period
;
845 krs
[1].base_key_kvno
= 2;
848 HDB_Ext_KeyRotation existing_krs
, new_krs
;
849 KeyRotation ordered_krs
[2];
851 ordered_krs
[0] = krs
[1];
852 ordered_krs
[1] = krs
[0];
853 existing_krs
.len
= 0;
854 existing_krs
.val
= 0;
856 new_krs
.val
= &ordered_krs
[1];
857 if ((ret
= hdb_validate_key_rotations(context
, NULL
, &new_krs
)) ||
858 (ret
= hdb_validate_key_rotations(context
, &existing_krs
,
860 krb5_err(context
, 1, ret
, "Valid KeyRotation thought invalid");
862 new_krs
.val
= &ordered_krs
[0];
863 if ((ret
= hdb_validate_key_rotations(context
, NULL
, &new_krs
)) ||
864 (ret
= hdb_validate_key_rotations(context
, &existing_krs
,
866 krb5_err(context
, 1, ret
, "Valid KeyRotation thought invalid");
868 new_krs
.val
= &ordered_krs
[0];
869 if ((ret
= hdb_validate_key_rotations(context
, NULL
, &new_krs
)) ||
870 (ret
= hdb_validate_key_rotations(context
, &existing_krs
,
872 krb5_err(context
, 1, ret
, "Valid KeyRotation thought invalid");
873 existing_krs
.len
= 1;
874 existing_krs
.val
= &ordered_krs
[1];
875 if ((ret
= hdb_validate_key_rotations(context
, &existing_krs
,
877 krb5_err(context
, 1, ret
, "Valid KeyRotation thought invalid");
878 existing_krs
.len
= 2;
879 existing_krs
.val
= &ordered_krs
[0];
880 if ((ret
= hdb_validate_key_rotations(context
, &existing_krs
,
882 krb5_err(context
, 1, ret
, "Valid KeyRotation thought invalid");
885 new_krs
.val
= &krs
[0];
886 if ((ret
= hdb_validate_key_rotations(context
, &existing_krs
,
888 krb5_errx(context
, 1, "Invalid KeyRotation thought valid");
891 make_namespace(context
, db
, WK_PREFIX
"_/bar.example@BAR.EXAMPLE");
893 fetch_entries(context
, db
, 1, 0, 0);
894 fetch_entries(context
, db
, 1, 1, 0);
895 fetch_entries(context
, db
, 1, 2, 0);
896 fetch_entries(context
, db
, 1, 3, 0);
897 fetch_entries(context
, db
, 1, 4, 0); /* Just before newest KR */
899 fetch_entries(context
, db
, 0, 0, 0);
900 fetch_entries(context
, db
, 0, 1, 0);
901 fetch_entries(context
, db
, 0, 2, 0);
902 fetch_entries(context
, db
, 0, 3, 0);
903 fetch_entries(context
, db
, 0, 4, 1); /* Must fail: just before 1st KR */
906 * Check that for every virtual principal in `expected[]', all the keysets
907 * with the same kvno, in all the entries fetched for different times,
910 check_kvnos(context
);
914 * Check that for every virtual principal in `expected[]' we have the
915 * expected key history.
917 check_expected_kvnos(context
);
921 * XXX Add various tests here, checking `e[]':
923 * - Extract all {principal, kvno, key} for all keys, current and
924 * otherwise, then sort by {key, kvno, principal}, then check that the
925 * only time we have matching keys is when the kvno and principal also
932 * XXX Test adding a third KR, a 4th KR, dropping KRs...
936 for (i
= 0; ret
== 0 && i
< sizeof(e
) / sizeof(e
[0]); i
++)
937 hdb_free_entry(context
, db
, &e
[i
]);
938 db
->hdb_destroy(context
, db
);
939 krb5_free_context(context
);