hdb: Move virtual principals into HDB layer
This is a large commit that adds several features: - Revamps and moves virtual host-based service principal functionality from kdc/ to lib/hdb/ so that it may be automatically visible to lib/kadm5/, as well as kadmin(1)/kadmind(8) and ktutil(1). The changes are backwards-incompatible. - Completes support for documenting a service principal's supported enctypes in its HDB entry independently of its long-term keys. This will reduce HDB bloat by not requiring that service principals have more long-term keys than they need just to document the service's supported enctypes. - Adds support for storing krb5.conf content in principals' HDB entries. This may eventually be used for causing Heimdal KDC services to reconfigure primary/secondary roles automatically by discovering the configured primary in an HDB entry for the realm. For now this will be used to help reduce the amount of configuration needed by clients of an upcoming HTTP binding of the kadmin service.
This commit is contained in:
@@ -26,12 +26,16 @@ gen_files_hdb = \
|
||||
asn1_HDB_Ext_PKINIT_cert.x \
|
||||
asn1_HDB_Ext_PKINIT_hash.x \
|
||||
asn1_HDB_Ext_Constrained_delegation_acl.x \
|
||||
asn1_HDB_Ext_KeyRotation.x \
|
||||
asn1_HDB_Ext_Lan_Manager_OWF.x \
|
||||
asn1_HDB_Ext_Password.x \
|
||||
asn1_HDB_Ext_Aliases.x \
|
||||
asn1_HDB_Ext_KeySet.x \
|
||||
asn1_HDB_extension.x \
|
||||
asn1_HDB_extensions.x \
|
||||
asn1_HDB_EncTypeList.x \
|
||||
asn1_KeyRotation.x \
|
||||
asn1_KeyRotationFlags.x \
|
||||
asn1_hdb_entry.x \
|
||||
asn1_hdb_entry_alias.x \
|
||||
asn1_hdb_keyset.x \
|
||||
@@ -72,7 +76,9 @@ if versionscript
|
||||
libhdb_la_LDFLAGS += $(LDFLAGS_VERSION_SCRIPT)$(srcdir)/version-script.map
|
||||
endif
|
||||
|
||||
noinst_PROGRAMS = test_dbinfo test_hdbkeys test_mkey test_hdbplugin
|
||||
# test_hdbkeys and test_mkey are not tests -- they are manual test utils
|
||||
noinst_PROGRAMS = test_dbinfo test_hdbkeys test_mkey test_namespace
|
||||
TESTS = test_dbinfo test_namespace
|
||||
|
||||
dist_libhdb_la_SOURCES = \
|
||||
common.c \
|
||||
@@ -118,10 +124,12 @@ ALL_OBJECTS = $(libhdb_la_OBJECTS)
|
||||
ALL_OBJECTS += $(test_dbinfo_OBJECTS)
|
||||
ALL_OBJECTS += $(test_hdbkeys_OBJECTS)
|
||||
ALL_OBJECTS += $(test_mkey_OBJECTS)
|
||||
ALL_OBJECTS += $(test_hdbplugin_OBJECTS)
|
||||
ALL_OBJECTS += $(test_namespace_OBJECTS)
|
||||
|
||||
$(ALL_OBJECTS): $(HDB_PROTOS) hdb_asn1.h hdb_asn1-priv.h hdb_err.h
|
||||
|
||||
test_namespace_LDADD = $(LDADD) $(test_hdbkeys_LIBS) $(LIB_heimbase)
|
||||
|
||||
$(srcdir)/hdb-protos.h: $(dist_libhdb_la_SOURCES)
|
||||
cd $(srcdir); perl ../../cf/make-proto.pl -q -P comment -o hdb-protos.h $(dist_libhdb_la_SOURCES) || rm -f hdb-protos.h
|
||||
|
||||
@@ -131,13 +139,10 @@ $(srcdir)/hdb-private.h: $(dist_libhdb_la_SOURCES)
|
||||
$(gen_files_hdb) hdb_asn1.hx hdb_asn1-priv.hx: hdb_asn1_files
|
||||
|
||||
hdb_asn1_files: $(ASN1_COMPILE_DEP) $(srcdir)/hdb.asn1
|
||||
$(ASN1_COMPILE) --sequence=HDB-Ext-KeySet --sequence=Keys $(srcdir)/hdb.asn1 hdb_asn1
|
||||
|
||||
test_dbinfo_LIBS = libhdb.la
|
||||
|
||||
test_hdbkeys_LIBS = ../krb5/libkrb5.la libhdb.la
|
||||
test_mkey_LIBS = $(test_hdbkeys_LIBS)
|
||||
test_hdbplugin_LIBS = $(test_hdbkeys_LIBS)
|
||||
$(ASN1_COMPILE) --sequence=HDB-extensions \
|
||||
--sequence=HDB-Ext-KeyRotation \
|
||||
--sequence=HDB-Ext-KeySet \
|
||||
--sequence=Keys $(srcdir)/hdb.asn1 hdb_asn1
|
||||
|
||||
# to help stupid solaris make
|
||||
|
||||
|
@@ -37,7 +37,7 @@ gen_files_hdb = $(OBJ)\asn1_hdb_asn1.x
|
||||
|
||||
$(gen_files_hdb) $(OBJ)\hdb_asn1.hx $(OBJ)\hdb_asn1-priv.hx: $(BINDIR)\asn1_compile.exe hdb.asn1
|
||||
cd $(OBJ)
|
||||
$(BINDIR)\asn1_compile.exe --sequence=HDB-Ext-KeySet --sequence=Keys --one-code-file $(SRCDIR)\hdb.asn1 hdb_asn1
|
||||
$(BINDIR)\asn1_compile.exe --sequence=HDB-extensions --sequence=HDB-Ext-KeyRotation --sequence=HDB-Ext-KeySet --sequence=Keys --one-code-file $(SRCDIR)\hdb.asn1 hdb_asn1
|
||||
cd $(SRCDIR)
|
||||
|
||||
$(gen_files_hdb:.x=.c): $$(@R).x
|
||||
@@ -150,7 +150,7 @@ clean::
|
||||
|
||||
test:: test-binaries test-run
|
||||
|
||||
test-binaries: $(OBJ)\test_dbinfo.exe $(OBJ)\test_hdbkeys.exe $(OBJ)\test_hdbplugin.exe
|
||||
test-binaries: $(OBJ)\test_dbinfo.exe $(OBJ)\test_hdbkeys.exe $(OBJ)\test_namespace.exe
|
||||
|
||||
$(OBJ)\test_dbinfo.exe: $(OBJ)\test_dbinfo.obj $(LIBHDB) $(LIBHEIMDAL) $(LIBROKEN) $(LIBVERS)
|
||||
$(EXECONLINK)
|
||||
@@ -160,7 +160,7 @@ $(OBJ)\test_hdbkeys.exe: $(OBJ)\test_hdbkeys.obj $(LIBHDB) $(LIBHEIMDAL) $(LIBRO
|
||||
$(EXECONLINK)
|
||||
$(EXEPREP_NODIST)
|
||||
|
||||
$(OBJ)\test_hdbplugin.exe: $(OBJ)\test_hdbplugin.obj $(LIBHDB) $(LIBHEIMDAL) $(LIBROKEN) $(LIBVERS)
|
||||
$(OBJ)\test_namespace.exe: $(OBJ)\test_namespace.obj $(LIBHDB) $(LIBHEIMDAL) $(LIBHEIMBASE) $(LIBROKEN) $(LIBVERS)
|
||||
$(EXECONLINK)
|
||||
$(EXEPREP_NODIST)
|
||||
|
||||
@@ -168,7 +168,7 @@ test-run:
|
||||
cd $(OBJ)
|
||||
-test_dbinfo.exe
|
||||
-test_hdbkeys.exe
|
||||
-test_hdbplugin.exe
|
||||
-test_namespace.exe
|
||||
cd $(SRCDIR)
|
||||
|
||||
!ifdef OPENLDAP_INC
|
||||
|
969
lib/hdb/common.c
969
lib/hdb/common.c
@@ -380,6 +380,71 @@ hdb_check_aliases(krb5_context context, HDB *db, hdb_entry_ex *entry)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Many HDB entries don't have `etypes' setup. Historically we use the
|
||||
* enctypes of the selected keyset as the entry's supported enctypes, but that
|
||||
* is problematic. By doing this at store time and, if need be, at fetch time,
|
||||
* we can make sure to stop deriving supported etypes from keys in the long
|
||||
* run. We also need kadm5/kadmin support for etypes. We'll use this function
|
||||
* there to derive etypes when using a kadm5_principal_ent_t that lacks the new
|
||||
* TL data for etypes.
|
||||
*/
|
||||
krb5_error_code
|
||||
hdb_derive_etypes(krb5_context context, hdb_entry *e, HDB_Ext_KeySet *base_keys)
|
||||
{
|
||||
krb5_error_code ret = 0;
|
||||
size_t i, k, netypes;
|
||||
HDB_extension *ext;
|
||||
|
||||
if (!base_keys &&
|
||||
(ext = hdb_find_extension(e, choice_HDB_extension_data_hist_keys)))
|
||||
base_keys = &ext->data.u.hist_keys;
|
||||
|
||||
netypes = e->keys.len;
|
||||
if (netypes == 0 && base_keys) {
|
||||
/* There's no way that base_keys->val[i].keys.len == 0, but hey */
|
||||
for (i = 0; netypes == 0 && i < base_keys->len; i++)
|
||||
netypes = base_keys->val[i].keys.len;
|
||||
}
|
||||
|
||||
if (netypes == 0)
|
||||
return 0;
|
||||
|
||||
if (e->etypes != NULL) {
|
||||
free(e->etypes->val);
|
||||
e->etypes->len = 0;
|
||||
e->etypes->val = 0;
|
||||
}
|
||||
|
||||
if (e->etypes == NULL &&
|
||||
(e->etypes = malloc(sizeof(e->etypes[0]))) == NULL)
|
||||
ret = krb5_enomem(context);
|
||||
if (ret == 0) {
|
||||
e->etypes->len = 0;
|
||||
e->etypes->val = 0;
|
||||
}
|
||||
if (ret == 0 &&
|
||||
(e->etypes->val = calloc(netypes, sizeof(e->etypes->val[0]))) == NULL)
|
||||
ret = krb5_enomem(context);
|
||||
if (ret) {
|
||||
free(e->etypes);
|
||||
e->etypes = 0;
|
||||
return ret;
|
||||
}
|
||||
e->etypes->len = netypes;
|
||||
for (i = 0; i < e->keys.len && i < netypes; i++)
|
||||
e->etypes->val[i] = e->keys.val[i].key.keytype;
|
||||
if (!base_keys || i)
|
||||
return 0;
|
||||
for (k = 0; i == 0 && k < base_keys->len; k++) {
|
||||
if (!base_keys->val[k].keys.len)
|
||||
continue;
|
||||
for (; i < base_keys->val[k].keys.len; i++)
|
||||
e->etypes->val[i] = base_keys->val[k].keys.val[i].key.keytype;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
krb5_error_code
|
||||
_hdb_store(krb5_context context, HDB *db, unsigned flags, hdb_entry_ex *entry)
|
||||
{
|
||||
@@ -410,7 +475,11 @@ _hdb_store(krb5_context context, HDB *db, unsigned flags, hdb_entry_ex *entry)
|
||||
return code ? code : HDB_ERR_EXISTS;
|
||||
}
|
||||
|
||||
if(entry->entry.generation == NULL) {
|
||||
if ((entry->entry.etypes == NULL || entry->entry.etypes->len == 0) &&
|
||||
(code = hdb_derive_etypes(context, &entry->entry, NULL)))
|
||||
return code;
|
||||
|
||||
if (entry->entry.generation == NULL) {
|
||||
struct timeval t;
|
||||
entry->entry.generation = malloc(sizeof(*entry->entry.generation));
|
||||
if(entry->entry.generation == NULL) {
|
||||
@@ -485,3 +554,901 @@ _hdb_remove(krb5_context context, HDB *db,
|
||||
return code;
|
||||
}
|
||||
|
||||
/* PRF+(K_base, pad, keylen(etype)) */
|
||||
static krb5_error_code
|
||||
derive_Key1(krb5_context context,
|
||||
krb5_data *pad,
|
||||
EncryptionKey *base,
|
||||
krb5int32 etype,
|
||||
EncryptionKey *nk)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
krb5_crypto crypto = NULL;
|
||||
krb5_data out;
|
||||
size_t len;
|
||||
|
||||
out.data = 0;
|
||||
out.length = 0;
|
||||
|
||||
ret = krb5_enctype_keysize(context, base->keytype, &len);
|
||||
if (ret == 0)
|
||||
ret = krb5_crypto_init(context, base, 0, &crypto);
|
||||
if (ret == 0)
|
||||
ret = krb5_crypto_prfplus(context, crypto, pad, len, &out);
|
||||
if (crypto)
|
||||
krb5_crypto_destroy(context, crypto);
|
||||
if (ret == 0)
|
||||
ret = krb5_random_to_key(context, etype, out.data, out.length, nk);
|
||||
krb5_data_free(&out);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* PRF+(PRF+(K_base, princ, keylen(etype)), kvno, keylen(etype)) */
|
||||
/* XXX Make it PRF+(PRF+(K_base, princ, keylen(K_base.etype)), and lift it, kvno, keylen(etype)) */
|
||||
static krb5_error_code
|
||||
derive_Key(krb5_context context,
|
||||
const char *princ,
|
||||
krb5uint32 kvno,
|
||||
EncryptionKey *base,
|
||||
krb5int32 etype,
|
||||
Key *nk)
|
||||
{
|
||||
krb5_error_code ret = 0;
|
||||
EncryptionKey intermediate;
|
||||
krb5_data pad;
|
||||
|
||||
nk->salt = NULL;
|
||||
nk->mkvno = NULL;
|
||||
nk->key.keytype = 0;
|
||||
nk->key.keyvalue.data = 0;
|
||||
nk->key.keyvalue.length = 0;
|
||||
|
||||
intermediate.keytype = 0;
|
||||
intermediate.keyvalue.data = 0;
|
||||
intermediate.keyvalue.length = 0;
|
||||
if (princ) {
|
||||
/* Derive intermediate key for the given principal */
|
||||
/* XXX Lift to optimize? */
|
||||
pad.data = (void *)(uintptr_t)princ;
|
||||
pad.length = strlen(princ);
|
||||
ret = derive_Key1(context, &pad, base, etype, &intermediate);
|
||||
if (ret == 0)
|
||||
base = &intermediate;
|
||||
} /* else `base' is already an intermediate key for the desired princ */
|
||||
|
||||
/* Derive final key for `kvno' from intermediate key */
|
||||
kvno = htonl(kvno);
|
||||
pad.data = &kvno;
|
||||
pad.length = sizeof(kvno);
|
||||
if (ret == 0)
|
||||
ret = derive_Key1(context, &pad, base, etype, &nk->key);
|
||||
free_EncryptionKey(&intermediate);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* PRF+(PRF+(K_base, princ, keylen(etype)), kvno, keylen(etype)) for one
|
||||
* enctype.
|
||||
*/
|
||||
static krb5_error_code
|
||||
derive_Keys(krb5_context context,
|
||||
const char *princ,
|
||||
krb5uint32 kvno,
|
||||
krb5int32 etype,
|
||||
const Keys *base,
|
||||
Keys *dk)
|
||||
|
||||
{
|
||||
krb5_error_code ret = 0;
|
||||
size_t i;
|
||||
Key nk;
|
||||
|
||||
dk->len = 0;
|
||||
dk->val = 0;
|
||||
|
||||
/*
|
||||
* The enctypes of the base keys is the list of enctypes to derive keys
|
||||
* for. Still, we derive all keys from the first base key.
|
||||
*/
|
||||
for (i = 0; ret == 0 && i < base->len; i++) {
|
||||
if (etype != KRB5_ENCTYPE_NULL && etype != base->val[i].key.keytype)
|
||||
continue;
|
||||
ret = derive_Key(context, princ, kvno, &base->val[0].key,
|
||||
base->val[i].key.keytype, &nk);
|
||||
if (ret)
|
||||
break;
|
||||
ret = add_Keys(dk, &nk);
|
||||
free_Key(&nk);
|
||||
/*
|
||||
* FIXME We need to finish kdc/kadm5/kadmin support for the `etypes' so
|
||||
* we can reduce the number of keys in keytabs to just those in current
|
||||
* use and only of *one* enctype.
|
||||
*
|
||||
* What we could do is derive *one* key and for the others output a
|
||||
* one-byte key of the intended enctype (which will never work).
|
||||
*
|
||||
* We'll never need any keys but the first one...
|
||||
*/
|
||||
}
|
||||
|
||||
if (ret)
|
||||
free_Keys(dk);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Helper for derive_keys_for_kr() */
|
||||
static krb5_error_code
|
||||
derive_keyset(krb5_context context,
|
||||
const Keys *base_keys,
|
||||
const char *princ,
|
||||
krb5int32 etype,
|
||||
krb5uint32 kvno,
|
||||
KerberosTime set_time, /* "now" */
|
||||
hdb_keyset *dks)
|
||||
{
|
||||
dks->kvno = kvno;
|
||||
dks->keys.val = 0;
|
||||
dks->set_time = malloc(sizeof(dks->set_time));
|
||||
if (dks->set_time == NULL)
|
||||
return krb5_enomem(context);
|
||||
*dks->set_time = set_time;
|
||||
return derive_Keys(context, princ, kvno, etype, base_keys, &dks->keys);
|
||||
}
|
||||
|
||||
/* Possibly derive and install in `h' a keyset identified by `t' */
|
||||
static krb5_error_code
|
||||
derive_keys_for_kr(krb5_context context,
|
||||
hdb_entry_ex *h,
|
||||
HDB_Ext_KeySet *base_keys,
|
||||
int is_current_keyset,
|
||||
int rotation_period_offset,
|
||||
const char *princ,
|
||||
krb5int32 etype,
|
||||
krb5uint32 kvno_wanted,
|
||||
KerberosTime t,
|
||||
struct KeyRotation *krp)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
hdb_keyset dks;
|
||||
KerberosTime set_time, n;
|
||||
krb5uint32 kvno;
|
||||
size_t i;
|
||||
|
||||
if (rotation_period_offset < -1 || rotation_period_offset > 1)
|
||||
return EINVAL; /* wat */
|
||||
|
||||
/*
|
||||
* Compute `kvno' and `set_time' given `t' and `krp'.
|
||||
*
|
||||
* There be signed 32-bit time_t dragons here.
|
||||
*
|
||||
* (t - krp->epoch < 0) is better than (krp->epoch < t), making us more
|
||||
* tolerant of signed 32-bit time_t here near 2038. Of course, we have
|
||||
* signed 32-bit time_t dragons elsewhere.
|
||||
*/
|
||||
if (t - krp->epoch < 0)
|
||||
return 0; /* This KR is not relevant yet */
|
||||
n = (t - krp->epoch) / krp->period;
|
||||
n += rotation_period_offset;
|
||||
set_time = krp->epoch + krp->period * n;
|
||||
kvno = krp->base_kvno + n;
|
||||
|
||||
|
||||
/*
|
||||
* Do not waste cycles computing keys not wanted or needed.
|
||||
* A past kvno is too old if its set_time + rotation period is in the past
|
||||
* by more than half a rotation period, since then no service ticket
|
||||
* encrypted with keys of that kvno can still be extant.
|
||||
*
|
||||
* A future kvno is not coming up soon enough if we're more than a quarter
|
||||
* of the rotation period away from it.
|
||||
*
|
||||
* Recall: the assumption for virtually-keyed principals is that services
|
||||
* fetch their future keys frequently enough that they'll never miss having
|
||||
* the keys they need.
|
||||
*/
|
||||
if (!is_current_keyset || rotation_period_offset != 0) {
|
||||
if ((kvno_wanted && kvno != kvno_wanted) ||
|
||||
t - (set_time + krp->period + (krp->period >> 1)) > 0 ||
|
||||
(set_time - t > 0 && (set_time - t) > (krp->period >> 2)))
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < base_keys->len; i++) {
|
||||
if (base_keys->val[i].kvno == krp->base_key_kvno)
|
||||
break;
|
||||
}
|
||||
if (i == base_keys->len) {
|
||||
/* Base key not found! */
|
||||
if (kvno_wanted || is_current_keyset) {
|
||||
krb5_set_error_message(context, ret = HDB_ERR_KVNO_NOT_FOUND,
|
||||
"Base key version %u not found for %s",
|
||||
krp->base_key_kvno, princ);
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = derive_keyset(context, &base_keys->val[i].keys, princ, etype, kvno,
|
||||
set_time, &dks);
|
||||
if (ret == 0)
|
||||
ret = hdb_install_keyset(context, &h->entry, is_current_keyset, &dks);
|
||||
|
||||
free_hdb_keyset(&dks);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Derive and install current keys, and possibly preceding or next keys */
|
||||
static krb5_error_code
|
||||
derive_keys_for_current_kr(krb5_context context,
|
||||
hdb_entry_ex *h,
|
||||
HDB_Ext_KeySet *base_keys,
|
||||
const char *princ,
|
||||
unsigned int flags,
|
||||
krb5int32 etype,
|
||||
krb5uint32 kvno_wanted,
|
||||
KerberosTime t,
|
||||
struct KeyRotation *krp,
|
||||
KerberosTime future_epoch)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
|
||||
/* derive_keys_for_kr() for current kvno and install as the current keys */
|
||||
ret = derive_keys_for_kr(context, h, base_keys, 1, 0, princ, etype,
|
||||
kvno_wanted, t, krp);
|
||||
if (!(flags & HDB_F_ALL_KVNOS))
|
||||
return ret;
|
||||
|
||||
/* */
|
||||
|
||||
|
||||
/*
|
||||
* derive_keys_for_kr() for prev kvno if still needed -- it can only be
|
||||
* needed if the prev kvno's start time is within this KR's epoch.
|
||||
*
|
||||
* Note that derive_keys_for_kr() can return without doing anything if this
|
||||
* is isn't the current keyset. So these conditions need not be
|
||||
* sufficiently narrow.
|
||||
*/
|
||||
if (ret == 0 && t - krp->epoch >= krp->period)
|
||||
ret = derive_keys_for_kr(context, h, base_keys, 0, -1, princ, etype,
|
||||
kvno_wanted, t, krp);
|
||||
/*
|
||||
* derive_keys_for_kr() for next kvno if near enough, but only if it
|
||||
* doesn't start after the next KR's epoch.
|
||||
*/
|
||||
if (future_epoch &&
|
||||
t - krp->epoch >= 0 /* We know! Hint to the compiler */) {
|
||||
KerberosTime next_kvno_start, n;
|
||||
|
||||
n = (t - krp->epoch) / krp->period;
|
||||
next_kvno_start = krp->epoch + krp->period * (n + 1);
|
||||
if (future_epoch - next_kvno_start <= 0)
|
||||
return ret;
|
||||
}
|
||||
if (ret == 0)
|
||||
ret = derive_keys_for_kr(context, h, base_keys, 0, 1, princ, etype,
|
||||
kvno_wanted, t, krp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Derive and install all keysets in `h' that `princ' needs at time `now'.
|
||||
*
|
||||
* This mutates the entry `h' to
|
||||
*
|
||||
* a) not have base keys,
|
||||
* b) have keys derived from the base keys according to
|
||||
* c) the key rotation periods for the base principal (possibly the same
|
||||
* principal if it's a concrete principal with virtual keys), and the
|
||||
* requested time, enctype, and kvno (all of which are optional, with zero
|
||||
* implying some default).
|
||||
*
|
||||
* Arguments:
|
||||
*
|
||||
* - `flags' is the flags passed to `hdb_fetch_kvno()'
|
||||
* - `princ' is the name of the principal we'll end up with in `h->entry'
|
||||
* - `h_is_namespace' indicates whether `h' is for a namespace or a concrete
|
||||
* principal (that might nonetheless have virtual/derived keys)
|
||||
* - `t' is the time such that the derived keys are for kvnos needed at `t'
|
||||
* - `etype' indicates what enctype to derive keys for (0 for all enctypes in
|
||||
* `h->entry.etypes')
|
||||
* - `kvno' requests a particular kvno, or all if zero
|
||||
*
|
||||
* The caller doesn't know if the principal needs key derivation -- we make
|
||||
* that determination in this function.
|
||||
*
|
||||
* Note that this function is fully deterministic for any given set of
|
||||
* arguments and HDB contents.
|
||||
*
|
||||
* Definitions:
|
||||
*
|
||||
* - A keyset is a set of keys for a single kvno.
|
||||
* - A keyset is relevant IFF:
|
||||
* - it is the keyset for a time period identified by `t' in a
|
||||
* corresponding KR
|
||||
* - it is a keyset for a past time period for which there may be extant,
|
||||
* not-yet-expired tickets that a service may need to decrypt
|
||||
* - it is a keyset for an upcoming time period that a service will need to
|
||||
* fetch before that time period becomes current, that way the service
|
||||
* can have keytab entries for those keys in time for when the KDC starts
|
||||
* encrypting service tickets to those keys
|
||||
*
|
||||
* This function derives the keyset(s) for the current KR first. The idea is
|
||||
* to optimize the order of resulting keytabs so that the most likely keys to
|
||||
* be used come first.
|
||||
*
|
||||
* Invariants:
|
||||
*
|
||||
* - KR metadata is sane because sanity is checked for when storing HDB
|
||||
* entries
|
||||
* - KRs are sorted by epoch in descending order; KR #0's epoch is the most
|
||||
* recent
|
||||
* - KR periods are non-zero (we divide by period)
|
||||
* - kvnos are numerically ordered and correspond to time periods
|
||||
* - within each KR, the kvnos for larger times are larger than (or equal
|
||||
* to) the kvnos of earlier times
|
||||
* - at KR boundaries, the first kvno of the newer boundary is larger than
|
||||
* the kvno of the last time period of the previous KR
|
||||
* - the time `t' must fall into exactly one KR period
|
||||
* - the time `t' must fall into exactly one period within a KR period
|
||||
* - at most two kvnos will be relevant from the KR that `t' falls into
|
||||
* (the current kvno for `t', and possibly either the preceding, or the
|
||||
* next)
|
||||
* - at most one kvno from non-current KRs will be derived: possibly one for a
|
||||
* preceding KR, and possibly one from an upcoming KR
|
||||
*
|
||||
* There can be:
|
||||
*
|
||||
* - no KR extension (not a namespace principal, and no virtual keys)
|
||||
* - 1, 2, or 3 KRs (see above)
|
||||
* - the newest KR may have the `deleted' flag, meaning "does not exist after
|
||||
* this epoch"
|
||||
*
|
||||
* Note that the last time period in any older KR can be partial.
|
||||
*
|
||||
* Timeline diagram:
|
||||
*
|
||||
* .......|--+--+...+--|---+---+---+...+--|----+...
|
||||
* T20 T10 T11 RT12 T1n T01
|
||||
* ^ ^ ^ ^ ^ ^ ^ T00
|
||||
* | | | T22 T2n | | ^
|
||||
* ^ | T21 | | |
|
||||
* princ | | epoch of | epoch of
|
||||
* did | | middle KR | newest epoch
|
||||
* not | | |
|
||||
* exist! | start of Note that T1n
|
||||
* | second kvno is shown as shorter
|
||||
* | in 1st epoch than preceding periods
|
||||
* |
|
||||
* ^
|
||||
* first KR's
|
||||
* epoch, and start
|
||||
* of its first kvno
|
||||
*
|
||||
* Tmn == the start of the Mth KR's Nth time period.
|
||||
* (higher M -> older KR; lower M -> newer KR)
|
||||
* (N is the reverse: lower N -> older time period in KR)
|
||||
* T20 == start of oldest KR -- no keys before this time will be derived.
|
||||
* T2n == last time period in oldest KR
|
||||
* T10 == start of middle KR
|
||||
* T1n == last time period in middle KR
|
||||
* T00 == start of newest KR
|
||||
* T0n == current time period in newest KR for wall clock time
|
||||
*/
|
||||
static krb5_error_code
|
||||
derive_keys(krb5_context context,
|
||||
unsigned flags,
|
||||
krb5_const_principal princ,
|
||||
int h_is_namespace,
|
||||
krb5_timestamp t,
|
||||
krb5int32 etype,
|
||||
krb5uint32 kvno,
|
||||
hdb_entry_ex *h)
|
||||
{
|
||||
HDB_Ext_KeyRotation kr;
|
||||
HDB_Ext_KeySet base_keys;
|
||||
krb5_error_code ret = 0;
|
||||
size_t current_kr, future_kr, past_kr, i;
|
||||
char *p = NULL;
|
||||
int valid = 1;
|
||||
|
||||
if (!h_is_namespace && !h->entry.flags.virtual_keys)
|
||||
return 0;
|
||||
h->entry.flags.virtual = 1;
|
||||
if (h_is_namespace) {
|
||||
/* Set the entry's principal name */
|
||||
free_Principal(h->entry.principal);
|
||||
ret = copy_Principal(princ, h->entry.principal);
|
||||
}
|
||||
|
||||
kr.len = 0;
|
||||
kr.val = 0;
|
||||
if (ret == 0) {
|
||||
const HDB_Ext_KeyRotation *ckr;
|
||||
|
||||
/* Installing keys invalidates `ckr', so we copy it */
|
||||
ret = hdb_entry_get_key_rotation(context, &h->entry, &ckr);
|
||||
if (ret == 0)
|
||||
ret = copy_HDB_Ext_KeyRotation(ckr, &kr);
|
||||
}
|
||||
|
||||
/* Get the base keys from the entry, and remove them */
|
||||
base_keys.val = 0;
|
||||
base_keys.len = 0;
|
||||
if (ret == 0)
|
||||
ret = hdb_remove_base_keys(context, &h->entry, &base_keys);
|
||||
|
||||
/* Make sure we have h->entry.etypes */
|
||||
if (ret == 0 && !h->entry.etypes)
|
||||
ret = hdb_derive_etypes(context, &h->entry, &base_keys);
|
||||
|
||||
/* Keys not desired? Don't derive them! */
|
||||
if (ret || !(flags & HDB_F_DECRYPT)) {
|
||||
free_HDB_Ext_KeyRotation(&kr);
|
||||
free_HDB_Ext_KeySet(&base_keys);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* The principal name will be used in key derivation and error messages */
|
||||
if (ret == 0 && h_is_namespace)
|
||||
ret = krb5_unparse_name(context, princ, &p);
|
||||
|
||||
/* Sanity check key rotations, determine current & last kr */
|
||||
if (ret == 0 && kr.len < 1)
|
||||
krb5_set_error_message(context, ret = HDB_ERR_NOENTRY,
|
||||
"no key rotation periods for %s", p);
|
||||
if (ret == 0)
|
||||
current_kr = future_kr = past_kr = kr.len;
|
||||
else
|
||||
current_kr = future_kr = past_kr = 1;
|
||||
|
||||
/*
|
||||
* Identify a current, next, and previous KRs if there are any.
|
||||
*
|
||||
* There can be up to three KRs, ordered by epoch, descending, making up a
|
||||
* timeline like:
|
||||
*
|
||||
* ...|---------|--------|------>
|
||||
* ^ | | |
|
||||
* | | | |
|
||||
* | | | Newest KR (kr.val[0])
|
||||
* | | Middle KR (kr.val[1])
|
||||
* | Oldest (last) KR (kr.val[2])
|
||||
* |
|
||||
* Before the begging of time for this namespace
|
||||
*
|
||||
* We step through these from future towards past looking for the best
|
||||
* future, current, and past KRs. The best current KR is one that has its
|
||||
* epoch nearest to `t' but in the past of `t'.
|
||||
*
|
||||
* We validate KRs before storing HDB entries with the KR extension, so we
|
||||
* can assume they are valid here. However, we do some validity checking,
|
||||
* and if they're not valid, we pick the best current KR and ignore the
|
||||
* others.
|
||||
*
|
||||
* In principle there cannot be two future KRs, but this function is
|
||||
* deterministic and takes a time value, so it should not enforce this just
|
||||
* so we can test. Enforcement of such rules should be done at store time.
|
||||
*/
|
||||
for (i = 0; ret == 0 && i < kr.len; i++) {
|
||||
/* Minimal validation: order and period */
|
||||
if (i && kr.val[i - 1].epoch - kr.val[i].epoch <= 0) {
|
||||
future_kr = past_kr = kr.len;
|
||||
valid = 0;
|
||||
}
|
||||
if (!kr.val[i].period) {
|
||||
future_kr = past_kr = kr.len;
|
||||
valid = 0;
|
||||
continue;
|
||||
}
|
||||
if (t - kr.val[i].epoch >= 0) {
|
||||
/*
|
||||
* `t' is in the future of this KR's epoch, so it's a candidate for
|
||||
* either current or past KR.
|
||||
*/
|
||||
if (current_kr == kr.len)
|
||||
current_kr = i; /* First curr KR candidate; should be best */
|
||||
else if (kr.val[current_kr].epoch - kr.val[i].epoch < 0)
|
||||
current_kr = i; /* Invalid KRs, but better curr KR cand. */
|
||||
else if (valid && past_kr == kr.len)
|
||||
past_kr = i;
|
||||
} else if (valid) {
|
||||
/* This KR is in the future of `t', a candidate for next KR */
|
||||
future_kr = i;
|
||||
}
|
||||
}
|
||||
if (ret == 0 && current_kr == kr.len)
|
||||
/* No current KR -> too soon */
|
||||
krb5_set_error_message(context, ret = HDB_ERR_NOENTRY,
|
||||
"Too soon for virtual principal to exist");
|
||||
|
||||
/* Check that the principal has not been marked deleted */
|
||||
if (ret == 0 && current_kr < kr.len && kr.val[current_kr].flags.deleted)
|
||||
krb5_set_error_message(context, ret = HDB_ERR_NOENTRY,
|
||||
"virtual principal %s does not exist "
|
||||
"because last key rotation period "
|
||||
"marks deletion", p);
|
||||
|
||||
/*
|
||||
* Derive and set in `h' its current kvno and current keys.
|
||||
*
|
||||
* This will set h->entry.kvno as well.
|
||||
*
|
||||
* This may set up to TWO keysets for the current key rotation period:
|
||||
* - current keys (h->entry.keys and h->entry.kvno)
|
||||
* - possibly one future
|
||||
* OR
|
||||
* possibly one past keyset in hist_keys for the current_kr
|
||||
*/
|
||||
if (ret == 0 && current_kr < kr.len)
|
||||
ret = derive_keys_for_current_kr(context, h, &base_keys, p, flags,
|
||||
etype, kvno, t, &kr.val[current_kr],
|
||||
current_kr ? kr.val[0].epoch : 0);
|
||||
|
||||
/*
|
||||
* Derive and set in `h' its future keys for next KR if it is soon to be
|
||||
* current.
|
||||
*
|
||||
* We want to derive keys for the first kvno of the next (future) KR if
|
||||
* it's sufficiently close to `t', meaning within 1 period of the current
|
||||
* KR, but we want these keys to be available sooner, so 1.5 of the current
|
||||
* period.
|
||||
*/
|
||||
if (ret == 0 && future_kr < kr.len && (flags & HDB_F_ALL_KVNOS))
|
||||
ret = derive_keys_for_kr(context, h, &base_keys, 0, 0, p, etype, kvno,
|
||||
kr.val[future_kr].epoch, &kr.val[future_kr]);
|
||||
|
||||
/*
|
||||
* Derive and set in `h' its past keys for the previous KR if its last time
|
||||
* period could still have extant, unexpired service tickets encrypted in
|
||||
* its keys.
|
||||
*/
|
||||
if (ret == 0 && past_kr < kr.len && (flags & HDB_F_ALL_KVNOS))
|
||||
ret = derive_keys_for_kr(context, h, &base_keys, 0, 0, p, etype, kvno,
|
||||
kr.val[current_kr].epoch - 1, &kr.val[past_kr]);
|
||||
|
||||
/*
|
||||
* Impose a bound on h->entry.max_life so that [when the KDC is the caller]
|
||||
* the KDC won't issue tickets longer lived than this.
|
||||
*/
|
||||
if (ret == 0 && !h->entry.max_life &&
|
||||
(h->entry.max_life = malloc(sizeof(h->entry.max_life[0]))) == NULL)
|
||||
ret = krb5_enomem(context);
|
||||
if (ret == 0 && *h->entry.max_life > kr.val[current_kr].period >> 1)
|
||||
*h->entry.max_life = kr.val[current_kr].period >> 1;
|
||||
|
||||
free_HDB_Ext_KeyRotation(&kr);
|
||||
free_HDB_Ext_KeySet(&base_keys);
|
||||
free(p);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* In order for disparate keytab provisioning systems such as OSKT and our own
|
||||
* kadmin ext_keytab and httpkadmind's get-keys to coexist, we need to be able
|
||||
* to force keys set by the former to not become current keys until users of
|
||||
* the latter have had a chance to fetch those keys into their keytabs. To do
|
||||
* this we have to search the list of keys in the entry looking for the newest
|
||||
* keys older than `now - db->new_service_key_delay'.
|
||||
*
|
||||
* The context is that OSKT's krb5_keytab is very happy to change keys in a way
|
||||
* that requires all members of a cluster to rekey together. If one also
|
||||
* wishes to have cluster members that opt out of this and just fetch current,
|
||||
* past, and future keys periodically, then the keys set by OSKT need to not
|
||||
* come into effect until all the opt-out members have had a chance to fetch
|
||||
* the new keys.
|
||||
*
|
||||
* The assumption is that services will fetch new keys periodically, say, every
|
||||
* four hours. Then one can set `[hdb] new_service_key_delay = 8h' in the
|
||||
* configuration and new keys set by OSKT will not be used until 8h after they
|
||||
* are set.
|
||||
*
|
||||
* Naturally, this applies only to concrete principals with concrete keys.
|
||||
*/
|
||||
static krb5_error_code
|
||||
fix_keys(krb5_context context,
|
||||
HDB *db,
|
||||
unsigned flags,
|
||||
krb5_timestamp now,
|
||||
krb5uint32 kvno,
|
||||
hdb_entry_ex *h)
|
||||
{
|
||||
HDB_extension *ext;
|
||||
HDB_Ext_KeySet keys;
|
||||
time_t current = 0;
|
||||
time_t best;
|
||||
size_t i;
|
||||
|
||||
/*
|
||||
* If we want a specific kvno, or if we're not decrypting the keys, or if
|
||||
* there's no new-key delay, then we're out.
|
||||
*/
|
||||
if (!(flags & HDB_F_DECRYPT) || kvno || h->entry.flags.virtual ||
|
||||
h->entry.flags.virtual_keys || db->new_service_key_delay <= 0)
|
||||
return 0;
|
||||
|
||||
/* No history -> current keyset is the only one and therefore the best */
|
||||
ext = hdb_find_extension(&h->entry, choice_HDB_extension_data_hist_keys);
|
||||
if (!ext)
|
||||
return 0;
|
||||
|
||||
/* Assume the current keyset is the best to start with */
|
||||
(void) hdb_entry_get_pw_change_time(&h->entry, ¤t);
|
||||
if (current == 0 && h->entry.modified_by)
|
||||
current = h->entry.modified_by->time;
|
||||
if (current == 0)
|
||||
current = h->entry.created_by.time;
|
||||
|
||||
/* Current keyset starts out as best */
|
||||
best = current;
|
||||
kvno = h->entry.kvno;
|
||||
|
||||
/* Look for a better keyset in the history */
|
||||
keys = ext->data.u.hist_keys;
|
||||
for (i = 0; i < keys.len; i++) {
|
||||
/* No set_time? Ignore. Too new? Ignore */
|
||||
if (!keys.val[i].set_time ||
|
||||
keys.val[i].set_time[0] + db->new_service_key_delay > now)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* Ignore the keyset with kvno 1 when the entry is at 2 because
|
||||
* kadmin's `ank -r' command immediately changes the keys.
|
||||
*/
|
||||
if (h->entry.kvno == 2 && keys.val[i].kvno == 1 &&
|
||||
keys.val[i].set_time[0] - best < 30)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* This keyset's set_time older than the previous best? Ignore.
|
||||
* However, if the current best is the entry's current and that one
|
||||
* is too new, then don't ignore this one.
|
||||
*/
|
||||
if (keys.val[i].set_time[0] < best &&
|
||||
(best != current || current + db->new_service_key_delay < now))
|
||||
continue;
|
||||
|
||||
/*
|
||||
* If two good enough keysets have the same set_time, take the keyset
|
||||
* with the highest kvno.
|
||||
*/
|
||||
if (keys.val[i].set_time[0] == best && keys.val[i].kvno <= kvno)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* This keyset is clearly more current than the previous best keyset
|
||||
* but still old enough to use for encrypting tickets with.
|
||||
*/
|
||||
best = keys.val[i].set_time[0];
|
||||
kvno = keys.val[i].kvno;
|
||||
}
|
||||
return hdb_change_kvno(context, kvno, &h->entry);
|
||||
}
|
||||
|
||||
/*
|
||||
* Make a WELLKNOWN/HOSTBASED-NAMESPACE/${svc}/${hostname} or
|
||||
* WELLKNOWN/HOSTBASED-NAMESPACE/${svc}/${hostname}/${domainname} principal
|
||||
* object, with the service and hostname components take from `wanted', but if
|
||||
* the service name is not in the list `db->virtual_hostbased_princ_svcs[]'
|
||||
* then use "_" (wildcard) instead. This way we can have different attributes
|
||||
* for different services in the same namespaces.
|
||||
*
|
||||
* For example, virtual hostbased service names for the "host" service might
|
||||
* have ok-as-delegate set, but ones for the "HTTP" service might not.
|
||||
*/
|
||||
static krb5_error_code
|
||||
make_namespace_princ(krb5_context context,
|
||||
HDB *db,
|
||||
krb5_const_principal wanted,
|
||||
krb5_principal *namespace)
|
||||
{
|
||||
krb5_error_code ret = 0;
|
||||
const char *realm = krb5_principal_get_realm(context, wanted);
|
||||
const char *comp0 = krb5_principal_get_comp_string(context, wanted, 0);
|
||||
const char *comp1 = krb5_principal_get_comp_string(context, wanted, 1);
|
||||
const char *comp2 = krb5_principal_get_comp_string(context, wanted, 2);
|
||||
char * const *svcs = db->virtual_hostbased_princ_svcs;
|
||||
size_t i;
|
||||
|
||||
*namespace = NULL;
|
||||
if (comp0 == NULL || comp1 == NULL)
|
||||
return EINVAL;
|
||||
if (strcmp(comp0, "krbtgt") == 0)
|
||||
return 0;
|
||||
|
||||
for (i = 0; svcs && svcs[i]; i++) {
|
||||
if (strcmp(comp0, svcs[i]) == 0) {
|
||||
comp0 = svcs[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!svcs || !svcs[i])
|
||||
comp0 = "_";
|
||||
|
||||
/* First go around, need a namespace princ. Make it! */
|
||||
ret = krb5_build_principal(context, namespace, strlen(realm),
|
||||
realm, "WELLKNOWN",
|
||||
HDB_WK_NAMESPACE, comp0, NULL);
|
||||
if (ret == 0)
|
||||
ret = krb5_principal_set_comp_string(context, *namespace, 3, comp1);
|
||||
if (ret == 0 && comp2)
|
||||
/* Support domain-based names */
|
||||
ret = krb5_principal_set_comp_string(context, *namespace, 4, comp2);
|
||||
/* Caller frees `*namespace' on error */
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Wrapper around db->hdb_fetch_kvno() that implements virtual princs/keys */
|
||||
static krb5_error_code
|
||||
fetch_it(krb5_context context,
|
||||
HDB *db,
|
||||
krb5_const_principal princ,
|
||||
unsigned flags,
|
||||
krb5_timestamp t,
|
||||
krb5int32 etype,
|
||||
krb5uint32 kvno,
|
||||
hdb_entry_ex *ent)
|
||||
{
|
||||
krb5_const_principal tmpprinc = princ;
|
||||
krb5_principal baseprinc = NULL;
|
||||
krb5_error_code ret = 0;
|
||||
const char *comp0 = krb5_principal_get_comp_string(context, princ, 0);
|
||||
const char *comp1 = krb5_principal_get_comp_string(context, princ, 1);
|
||||
const char *tmp;
|
||||
size_t mindots = db->virtual_hostbased_princ_ndots;
|
||||
size_t maxdots = db->virtual_hostbased_princ_maxdots;
|
||||
size_t hdots = 0;
|
||||
char *host = NULL;
|
||||
int do_search = 0;
|
||||
|
||||
if (db->enable_virtual_hostbased_princs && comp1 &&
|
||||
strcmp("krbtgt", comp0) != 0 && strcmp("WELLKNOWN", comp0) != 0) {
|
||||
char *htmp;
|
||||
|
||||
if ((host = strdup(comp1)) == NULL)
|
||||
return krb5_enomem(context);
|
||||
|
||||
/* Strip out any :port */
|
||||
htmp = strchr(host, ':');
|
||||
if (htmp) {
|
||||
if (strchr(htmp + 1, ':')) {
|
||||
/* Extra ':'s? No virtualization for you! */
|
||||
free(host);
|
||||
host = NULL;
|
||||
htmp = NULL;
|
||||
} else {
|
||||
*htmp = '\0';
|
||||
}
|
||||
}
|
||||
/* Count dots in `host' */
|
||||
for (hdots = 0, htmp = host; htmp && *htmp; htmp++)
|
||||
if (*htmp == '.')
|
||||
hdots++;
|
||||
|
||||
do_search = 1;
|
||||
}
|
||||
|
||||
tmp = host ? host : comp1;
|
||||
for (ret = HDB_ERR_NOENTRY; ret == HDB_ERR_NOENTRY; tmpprinc = baseprinc) {
|
||||
krb5_error_code ret2 = 0;
|
||||
|
||||
/*
|
||||
* We break out of this loop with ret == 0 only if we found the HDB
|
||||
* entry we were looking for or the HDB entry for a matching namespace.
|
||||
*
|
||||
* Otherwise we break out with ret != 0, typically HDB_ERR_NOENTRY.
|
||||
*
|
||||
* First time through we lookup the principal as given.
|
||||
*
|
||||
* Next we lookup a namespace principal, stripping off hostname labels
|
||||
* from the left until we find one or get tired of looking or run out
|
||||
* of labels.
|
||||
*/
|
||||
ret = db->hdb_fetch_kvno(context, db, tmpprinc, flags, kvno, ent);
|
||||
if (ret != HDB_ERR_NOENTRY || hdots == 0 || hdots < mindots || !tmp ||
|
||||
!do_search)
|
||||
break;
|
||||
|
||||
/*
|
||||
* Breadcrumb:
|
||||
*
|
||||
* - if we found a concrete principal, but it's been marked
|
||||
* as now-virtual, then we must keep going
|
||||
*
|
||||
* But this will be coded in the future.
|
||||
*
|
||||
* Maybe we can take attributes from the concrete principal...
|
||||
*/
|
||||
|
||||
/*
|
||||
* The namespace's hostname will not have more labels than maxdots + 1.
|
||||
* Thus we truncate immediately down to maxdots + 1 if we haven't yet.
|
||||
*
|
||||
* Example: with maxdots == 3,
|
||||
* foo.bar.baz.app.blah.example -> baz.app.blah.example
|
||||
*/
|
||||
while (maxdots && hdots > maxdots && tmp) {
|
||||
tmp = strchr(tmp, '.');
|
||||
/* tmp != NULL because maxdots > 0 */
|
||||
tmp++;
|
||||
hdots--;
|
||||
}
|
||||
|
||||
if (baseprinc == NULL)
|
||||
/* First go around, need a namespace princ. Make it! */
|
||||
ret2 = make_namespace_princ(context, db, tmpprinc, &baseprinc);
|
||||
/* Update the hostname component */
|
||||
if (ret2 == 0)
|
||||
ret2 = krb5_principal_set_comp_string(context, baseprinc, 3, tmp);
|
||||
if (ret2)
|
||||
ret = ret2;
|
||||
|
||||
if (tmp) {
|
||||
/* Strip off left-most label for the next go-around */
|
||||
if ((tmp = strchr(tmp, '.')))
|
||||
tmp++;
|
||||
hdots--;
|
||||
} /* else we'll break out after the next db->hdb_fetch_kvno() call */
|
||||
}
|
||||
|
||||
/*
|
||||
* If unencrypted keys were requested, derive them. There may not be any
|
||||
* key derivation to do, but that's decided in derive_keys().
|
||||
*/
|
||||
if (ret == 0) {
|
||||
ret = derive_keys(context, flags, princ, !!baseprinc, t, etype, kvno,
|
||||
ent);
|
||||
if (ret == 0)
|
||||
ret = fix_keys(context, db, flags, t, kvno, ent);
|
||||
if (ret)
|
||||
hdb_free_entry(context, ent);
|
||||
}
|
||||
krb5_free_principal(context, baseprinc);
|
||||
free(host);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a principal's HDB entry, possibly generating virtual keys from base
|
||||
* keys according to strict key rotation schedules. If a time is given, other
|
||||
* than HDB I/O, this function is pure, thus usable for testing.
|
||||
*
|
||||
* HDB writers should use `db->hdb_fetch_kvno()' to avoid materializing virtual
|
||||
* principals.
|
||||
*
|
||||
* HDB readers should use this function rather than `db->hdb_fetch_kvno()'
|
||||
* unless they only want to see concrete principals and not bother generating
|
||||
* any virtual keys.
|
||||
*
|
||||
* @param context Context
|
||||
* @param db HDB
|
||||
* @param principal Principal name
|
||||
* @param flags Fetch flags
|
||||
* @param t For virtual keys, use this as the point in time (use zero to mean "now")
|
||||
* @param etype Key enctype (use KRB5_ENCTYPE_NULL to mean "preferred")
|
||||
* @param kvno Key version number (use zero to mean "current")
|
||||
* @param h Output HDB entry
|
||||
*
|
||||
* @return Zero on success, an error code otherwise.
|
||||
*/
|
||||
krb5_error_code
|
||||
hdb_fetch_kvno(krb5_context context,
|
||||
HDB *db,
|
||||
krb5_const_principal principal,
|
||||
unsigned int flags,
|
||||
krb5_timestamp t,
|
||||
krb5int32 etype,
|
||||
krb5uint32 kvno,
|
||||
hdb_entry_ex *h)
|
||||
{
|
||||
krb5_error_code ret = HDB_ERR_NOENTRY;
|
||||
|
||||
flags |= kvno ? HDB_F_KVNO_SPECIFIED : 0; /* XXX is this needed */
|
||||
if (t == 0)
|
||||
krb5_timeofday(context, &t);
|
||||
ret = fetch_it(context, db, principal, flags, t, etype, kvno, h);
|
||||
if (ret == HDB_ERR_NOENTRY)
|
||||
krb5_set_error_message(context, ret, "no such entry found in hdb");
|
||||
return ret;
|
||||
}
|
||||
|
@@ -71,7 +71,8 @@ DB_destroy(krb5_context context, HDB *db)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
|
||||
ret = hdb_clear_master_key (context, db);
|
||||
ret = hdb_clear_master_key(context, db);
|
||||
krb5_config_free_strings(db->virtual_hostbased_princ_svcs);
|
||||
free(db->hdb_name);
|
||||
free(db);
|
||||
return ret;
|
||||
|
@@ -86,7 +86,8 @@ DB_destroy(krb5_context context, HDB *db)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
|
||||
ret = hdb_clear_master_key (context, db);
|
||||
ret = hdb_clear_master_key(context, db);
|
||||
krb5_config_free_strings(db->virtual_hostbased_princ_svcs);
|
||||
free(db->hdb_name);
|
||||
free(db);
|
||||
return ret;
|
||||
|
302
lib/hdb/ext.c
302
lib/hdb/ext.c
@@ -86,7 +86,6 @@ hdb_replace_extension(krb5_context context,
|
||||
const HDB_extension *ext)
|
||||
{
|
||||
HDB_extension *ext2;
|
||||
HDB_extension *es;
|
||||
int ret;
|
||||
|
||||
ext2 = NULL;
|
||||
@@ -157,22 +156,7 @@ hdb_replace_extension(krb5_context context,
|
||||
return ret;
|
||||
}
|
||||
|
||||
es = realloc(entry->extensions->val,
|
||||
(entry->extensions->len+1)*sizeof(entry->extensions->val[0]));
|
||||
if (es == NULL) {
|
||||
krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
|
||||
return ENOMEM;
|
||||
}
|
||||
entry->extensions->val = es;
|
||||
|
||||
ret = copy_HDB_extension(ext,
|
||||
&entry->extensions->val[entry->extensions->len]);
|
||||
if (ret == 0)
|
||||
entry->extensions->len++;
|
||||
else
|
||||
krb5_set_error_message(context, ret, "hdb: failed to copy new extension");
|
||||
|
||||
return ret;
|
||||
return add_HDB_extensions(entry->extensions, ext);
|
||||
}
|
||||
|
||||
krb5_error_code
|
||||
@@ -186,13 +170,8 @@ hdb_clear_extension(krb5_context context,
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < entry->extensions->len; i++) {
|
||||
if (entry->extensions->val[i].data.element == (unsigned)type) {
|
||||
free_HDB_extension(&entry->extensions->val[i]);
|
||||
memmove(&entry->extensions->val[i],
|
||||
&entry->extensions->val[i + 1],
|
||||
sizeof(entry->extensions->val[i]) * (entry->extensions->len - i - 1));
|
||||
entry->extensions->len--;
|
||||
}
|
||||
if (entry->extensions->val[i].data.element == (unsigned)type)
|
||||
(void) remove_HDB_extensions(entry->extensions, i);
|
||||
}
|
||||
if (entry->extensions->len == 0) {
|
||||
free(entry->extensions->val);
|
||||
@@ -246,6 +225,33 @@ hdb_entry_get_pkinit_cert(const hdb_entry *entry, const HDB_Ext_PKINIT_cert **a)
|
||||
return 0;
|
||||
}
|
||||
|
||||
krb5_error_code
|
||||
hdb_entry_get_krb5_config(const hdb_entry *entry, heim_octet_string *c)
|
||||
{
|
||||
const HDB_extension *ext;
|
||||
|
||||
c->data = NULL;
|
||||
c->length = 0;
|
||||
ext = hdb_find_extension(entry, choice_HDB_extension_data_krb5_config);
|
||||
if (ext)
|
||||
*c = ext->data.u.krb5_config;
|
||||
return 0;
|
||||
}
|
||||
|
||||
krb5_error_code
|
||||
hdb_entry_set_krb5_config(krb5_context context,
|
||||
hdb_entry *entry,
|
||||
heim_octet_string *s)
|
||||
{
|
||||
HDB_extension ext;
|
||||
|
||||
ext.mandatory = FALSE;
|
||||
ext.data.element = choice_HDB_extension_data_last_pw_change;
|
||||
/* hdb_replace_extension() copies this, so no need to copy it here */
|
||||
ext.data.u.krb5_config = *s;
|
||||
return hdb_replace_extension(context, entry, &ext);
|
||||
}
|
||||
|
||||
krb5_error_code
|
||||
hdb_entry_get_pw_change_time(const hdb_entry *entry, time_t *t)
|
||||
{
|
||||
@@ -530,3 +536,251 @@ hdb_set_last_modified_by(krb5_context context, hdb_entry *entry,
|
||||
return 0;
|
||||
}
|
||||
|
||||
krb5_error_code
|
||||
hdb_entry_get_key_rotation(krb5_context context,
|
||||
const hdb_entry *entry,
|
||||
const HDB_Ext_KeyRotation **kr)
|
||||
{
|
||||
HDB_extension *ext =
|
||||
hdb_find_extension(entry, choice_HDB_extension_data_key_rotation);
|
||||
|
||||
*kr = ext ? &ext->data.u.key_rotation : NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
krb5_error_code
|
||||
hdb_validate_key_rotation(krb5_context context,
|
||||
const KeyRotation *past_kr,
|
||||
const KeyRotation *new_kr)
|
||||
{
|
||||
unsigned int last_kvno;
|
||||
|
||||
if (new_kr->period < 1) {
|
||||
krb5_set_error_message(context, EINVAL,
|
||||
"Key rotation periods must be non-zero "
|
||||
"and positive");
|
||||
return EINVAL;
|
||||
}
|
||||
if (new_kr->base_key_kvno < 1 || new_kr->base_kvno < 1) {
|
||||
krb5_set_error_message(context, EINVAL,
|
||||
"Key version number zero not allowed "
|
||||
"for key rotation");
|
||||
return EINVAL;
|
||||
}
|
||||
if (!past_kr)
|
||||
return 0;
|
||||
|
||||
if (past_kr->base_key_kvno == new_kr->base_key_kvno) {
|
||||
/*
|
||||
* The new base keys can be the same as the old, but must have
|
||||
* different kvnos. (Well, not must must. It's a convention for now.)
|
||||
*/
|
||||
krb5_set_error_message(context, EINVAL,
|
||||
"Base key version numbers for KRs must differ");
|
||||
return EINVAL;
|
||||
}
|
||||
if (new_kr->epoch - past_kr->epoch <= 0) {
|
||||
krb5_set_error_message(context, EINVAL,
|
||||
"New key rotation periods must start later "
|
||||
"than existing ones");
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
last_kvno = 1 + ((new_kr->epoch - past_kr->epoch) / past_kr->period);
|
||||
if (new_kr->base_kvno <= last_kvno) {
|
||||
krb5_set_error_message(context, EINVAL,
|
||||
"New key rotation base kvno must be larger "
|
||||
"the last kvno for the current key "
|
||||
"rotation (%u)", last_kvno);
|
||||
return EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
kr_eq(const KeyRotation *a, const KeyRotation *b)
|
||||
{
|
||||
return !!(
|
||||
a->epoch == b->epoch &&
|
||||
a->period == b->period &&
|
||||
a->base_kvno == b->base_kvno &&
|
||||
a->base_key_kvno == b->base_key_kvno &&
|
||||
KeyRotationFlags2int(a->flags) == KeyRotationFlags2int(b->flags)
|
||||
);
|
||||
}
|
||||
|
||||
krb5_error_code
|
||||
hdb_validate_key_rotations(krb5_context context,
|
||||
const HDB_Ext_KeyRotation *existing,
|
||||
const HDB_Ext_KeyRotation *krs)
|
||||
{
|
||||
krb5_error_code ret = 0;
|
||||
size_t added = 0;
|
||||
size_t i;
|
||||
|
||||
if ((!existing || !existing->len) && (!krs || !krs->len))
|
||||
return 0; /* Nothing to do; weird */
|
||||
|
||||
/*
|
||||
* HDB_Ext_KeyRotation has to have 1..3 elements, and this is enforced by
|
||||
* the ASN.1 compiler and the code it generates. Nonetheless we'll check
|
||||
* that there's not zero elements.
|
||||
*/
|
||||
if ((!krs || !krs->len)) {
|
||||
/*
|
||||
* NOTE: We can clear this on concrete principals with virtual keys
|
||||
* though. The caller can check for that case.
|
||||
*/
|
||||
krb5_set_error_message(context, EINVAL,
|
||||
"Cannot clear key rotation metadata on "
|
||||
"virtual principal namespaces");
|
||||
ret = EINVAL;
|
||||
}
|
||||
|
||||
/* Validate the new KRs by themselves */
|
||||
for (i = 0; ret == 0 && i < krs->len; i++) {
|
||||
ret = hdb_validate_key_rotation(context,
|
||||
i+1 < krs->len ? &krs->val[i+1] : 0,
|
||||
&krs->val[i]);
|
||||
}
|
||||
if (ret || !existing || !existing->len)
|
||||
return ret;
|
||||
|
||||
if (existing->len == krs->len) {
|
||||
/* Check for no change */
|
||||
for (i = 0; i < krs->len; i++)
|
||||
if (!kr_eq(&existing->val[i], &krs->val[i]))
|
||||
break;
|
||||
if (i == krs->len)
|
||||
return 0; /* No change */
|
||||
}
|
||||
|
||||
/*
|
||||
* Check that new KRs make sense in the context of the previous KRs.
|
||||
*
|
||||
* Permitted changes:
|
||||
*
|
||||
* - add one new KR in front
|
||||
* - drop old KRs
|
||||
*
|
||||
* Start by checking if we're adding a KR, then go on to check for dropped
|
||||
* KRs and/or last KR alteration.
|
||||
*/
|
||||
if (existing->val[0].epoch == krs->val[0].epoch ||
|
||||
existing->val[0].base_kvno == krs->val[0].base_kvno) {
|
||||
if (!kr_eq(&existing->val[0], &krs->val[0])) {
|
||||
krb5_set_error_message(context, EINVAL,
|
||||
"Key rotation change not sensible");
|
||||
ret = EINVAL;
|
||||
}
|
||||
/* Key rotation *not* added */
|
||||
} else {
|
||||
/* Key rotation added; check it first */
|
||||
ret = hdb_validate_key_rotation(context,
|
||||
&existing->val[0],
|
||||
&krs->val[0]);
|
||||
added = 1;
|
||||
}
|
||||
for (i = 0; ret == 0 && i < existing->len && i + added < krs->len; i++)
|
||||
if (!kr_eq(&existing->val[i], &krs->val[i + added]))
|
||||
krb5_set_error_message(context, ret = EINVAL,
|
||||
"Only last key rotation may be truncated");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* XXX We need a function to "revoke" the past */
|
||||
|
||||
/**
|
||||
* This function adds a KeyRotation value to an entry, validating the
|
||||
* change. One of `entry' and `krs' must be NULL, and the other non-NULL, and
|
||||
* whichever is given will be altered.
|
||||
*
|
||||
* @param context Context
|
||||
* @param entry An HDB entry
|
||||
* @param krs A key rotation extension for hdb_entry
|
||||
* @param kr A new KeyRotation value
|
||||
*
|
||||
* @return Zero on success, an error otherwise.
|
||||
*/
|
||||
krb5_error_code
|
||||
hdb_entry_add_key_rotation(krb5_context context,
|
||||
hdb_entry *entry,
|
||||
HDB_Ext_KeyRotation *krs,
|
||||
const KeyRotation *kr)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
HDB_extension new_ext;
|
||||
HDB_extension *ext = 0;
|
||||
KeyRotation tmp;
|
||||
size_t i, sz;
|
||||
|
||||
if (kr->period < 1) {
|
||||
krb5_set_error_message(context, EINVAL,
|
||||
"Key rotation period cannot be zero");
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
new_ext.mandatory = TRUE;
|
||||
new_ext.data.element = choice_HDB_extension_data_key_rotation;
|
||||
new_ext.data.u.key_rotation.len = 0;
|
||||
new_ext.data.u.key_rotation.val = 0;
|
||||
|
||||
if (entry && krs)
|
||||
return EINVAL;
|
||||
|
||||
if (entry) {
|
||||
ext = hdb_find_extension(entry, choice_HDB_extension_data_key_rotation);
|
||||
if (!ext)
|
||||
ext = &new_ext;
|
||||
else
|
||||
krs = &ext->data.u.key_rotation;
|
||||
} else {
|
||||
const KeyRotation *prev_kr = &krs->val[0];
|
||||
unsigned int last_kvno = 0;
|
||||
|
||||
if (kr->epoch - prev_kr->epoch <= 0) {
|
||||
krb5_set_error_message(context, EINVAL,
|
||||
"New key rotation periods must start later "
|
||||
"than existing ones");
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
if (kr->base_kvno <= prev_kr->base_kvno ||
|
||||
kr->base_kvno - prev_kr->base_kvno <=
|
||||
(last_kvno = 1 +
|
||||
((kr->epoch - prev_kr->epoch) / prev_kr->period))) {
|
||||
krb5_set_error_message(context, EINVAL,
|
||||
"New key rotation base kvno must be larger "
|
||||
"the last kvno for the current key "
|
||||
"rotation (%u)", last_kvno);
|
||||
return EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
/* First, append */
|
||||
ret = add_HDB_Ext_KeyRotation(&ext->data.u.key_rotation, kr);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Rotate new to front */
|
||||
tmp = ext->data.u.key_rotation.val[ext->data.u.key_rotation.len - 1];
|
||||
sz = sizeof(ext->data.u.key_rotation.val[0]);
|
||||
memmove(&ext->data.u.key_rotation.val[1], &ext->data.u.key_rotation.val[0],
|
||||
(ext->data.u.key_rotation.len - 1) * sz);
|
||||
ext->data.u.key_rotation.val[0] = tmp;
|
||||
|
||||
/* Drop too old entries */
|
||||
for (i = 3; i < ext->data.u.key_rotation.len; i++)
|
||||
free_KeyRotation(&ext->data.u.key_rotation.val[i]);
|
||||
ext->data.u.key_rotation.len =
|
||||
ext->data.u.key_rotation.len > 3 ? 3 : ext->data.u.key_rotation.len;
|
||||
|
||||
if (ext != &new_ext)
|
||||
return 0;
|
||||
|
||||
/* Install new extension */
|
||||
if (ret == 0 && entry)
|
||||
ret = hdb_replace_extension(context, entry, ext);
|
||||
free_HDB_extension(&new_ext);
|
||||
return ret;
|
||||
}
|
||||
|
@@ -65,7 +65,8 @@ hkt_destroy(krb5_context context, HDB *db)
|
||||
hdb_keytab k = (hdb_keytab)db->hdb_db;
|
||||
krb5_error_code ret;
|
||||
|
||||
ret = hdb_clear_master_key (context, db);
|
||||
ret = hdb_clear_master_key(context, db);
|
||||
krb5_config_free_strings(db->virtual_hostbased_princ_svcs);
|
||||
|
||||
free(k->path);
|
||||
free(k);
|
||||
|
@@ -1882,6 +1882,7 @@ LDAP_destroy(krb5_context context, HDB * db)
|
||||
free(HDB2CREATE(db));
|
||||
if (HDB2URL(db))
|
||||
free(HDB2URL(db));
|
||||
krb5_config_free_strings(db->virtual_hostbased_princ_svcs);
|
||||
if (db->hdb_name)
|
||||
free(db->hdb_name);
|
||||
free(db->hdb_db);
|
||||
|
@@ -68,7 +68,8 @@ DB_destroy(krb5_context context, HDB *db)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
|
||||
ret = hdb_clear_master_key (context, db);
|
||||
ret = hdb_clear_master_key(context, db);
|
||||
krb5_config_free_strings(db->virtual_hostbased_princ_svcs);
|
||||
free(db->hdb_name);
|
||||
free(db->hdb_db);
|
||||
free(db);
|
||||
|
@@ -697,7 +697,8 @@ mdb_destroy(krb5_context context, HDB *db)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
|
||||
ret = hdb_clear_master_key (context, db);
|
||||
ret = hdb_clear_master_key(context, db);
|
||||
krb5_config_free_strings(db->virtual_hostbased_princ_svcs);
|
||||
free(db->hdb_name);
|
||||
free(db);
|
||||
return ret;
|
||||
|
@@ -800,6 +800,7 @@ hdb_sqlite_destroy(krb5_context context, HDB *db)
|
||||
|
||||
hsdb = (hdb_sqlite_db*)(db->hdb_db);
|
||||
|
||||
krb5_config_free_strings(db->virtual_hostbased_princ_svcs);
|
||||
free(hsdb->db_file);
|
||||
free(db->hdb_db);
|
||||
free(db);
|
||||
|
@@ -49,6 +49,11 @@ HDBFlags ::= BIT STRING {
|
||||
locked-out(17), -- Account is locked out,
|
||||
-- authentication will be denied
|
||||
require-pwchange(18), -- require a passwd change
|
||||
|
||||
materialize(19), -- store even if within virtual namespace
|
||||
virtual-keys(20), -- entry stored; keys mostly derived
|
||||
virtual(21), -- entry not stored; keys always derived
|
||||
|
||||
force-canonicalize(30), -- force the KDC to return the canonical
|
||||
-- principal irrespective of the setting
|
||||
-- of the canonicalize KDC option
|
||||
@@ -103,6 +108,84 @@ hdb_keyset ::= SEQUENCE {
|
||||
|
||||
HDB-Ext-KeySet ::= SEQUENCE OF hdb_keyset
|
||||
|
||||
--
|
||||
-- We need a function of current (or given, but it will always be current) time
|
||||
-- and a base hdb_entry or its HDB-Ext-KeyRotation and service ticket lifetime,
|
||||
-- that outputs a sequence of {kvno, set_time, max_life} representing past keys
|
||||
-- (up to one per past and current KeyRotation), current keys (for the current
|
||||
-- KeyRotation), up to one future key for the current KeyRotation, and up to
|
||||
-- one future key for the _next_ (future) KeyRotation if there is one.
|
||||
--
|
||||
-- We have to impose constraints on new KeyRotation elements of
|
||||
-- HDB-Ext-KeyRotation.
|
||||
--
|
||||
-- So virtual keysets (keytabs) will contain:
|
||||
--
|
||||
-- - up to one past keyset for all KeyRotation periods that are "applicable"
|
||||
-- - the current keyset for all KeyRotation periods that are "applicable"
|
||||
-- - up to one future keyset for all KeyRotation periods that are "applicable"
|
||||
--
|
||||
-- An applicable KeyRotation period is:
|
||||
--
|
||||
-- - the KeyRotation whose `epoch` is a) in the past and b) nearest to the
|
||||
-- current time - we call this the current KeyRotation
|
||||
-- - a KeyRotation whose `epoch` is nearest but in the past of the current
|
||||
-- one
|
||||
-- - a KeyRotation whose `epoch` is nearest but in the future of the current
|
||||
-- one
|
||||
--
|
||||
-- A service principal's max ticket life will be bounded by half the current
|
||||
-- key rotation period.
|
||||
--
|
||||
-- Note: There can be more than one applicable past KeyRotation, and more than
|
||||
-- one applicable KeyRotation. We might not want to permit this.
|
||||
-- However, it's probably easier to permit it, though we might not test
|
||||
-- end-to-end.
|
||||
--
|
||||
-- Testing:
|
||||
--
|
||||
-- - We should have standalone unit tests for all these pure functions.
|
||||
--
|
||||
-- - We should have a test that uses kadm5 and GSS to test against a KDC using
|
||||
-- small key rotation periods on the order of seconds, with back-off in case
|
||||
-- of losing a race condition.
|
||||
--
|
||||
KeyRotationFlags ::= BIT STRING {
|
||||
deleted(0), -- if set on a materialized principal, this will mean
|
||||
-- the principal does not exist
|
||||
-- if set on a namespace, this will mean that
|
||||
-- only materialized principal below it exist
|
||||
parent(1) -- if set on a materialized principal, this will mean
|
||||
-- that the keys for kvnos in this KeyRotation spec
|
||||
-- will be derived from the parent's base keys and
|
||||
-- corresponding KeyRotation spec
|
||||
-- if set on a namespace, this flag will be ignored
|
||||
-- (or we could support nested namespaces?)
|
||||
}
|
||||
KeyRotation ::= SEQUENCE {
|
||||
-- base-kvno is always computed at set time and set for the principal,
|
||||
-- and is never subject to admin choice. The base-kvno is that of the
|
||||
-- current kvno at that period's `from` given the previous period.
|
||||
--
|
||||
-- Also, insertion of KeyRotation elements before existing ones (in
|
||||
-- time) is never permitted, and all new KeyRotation elements must be
|
||||
-- in the future relative to existing ones.
|
||||
--
|
||||
-- HDB-Ext-KeyRotation will always be sorted (as stored) by `from`, in
|
||||
-- descending order.
|
||||
--
|
||||
-- Max service ticket lifetime will be constrained to no more than half
|
||||
-- the period of the the applicable KeyRotation elements.
|
||||
--
|
||||
flags[0] KeyRotationFlags,
|
||||
epoch[1] KerberosTime, -- start of this period
|
||||
period[2] INTEGER(0..4294967295), -- key rotation seconds
|
||||
base-kvno[3] INTEGER(0..4294967295), -- starting from this kvno
|
||||
base-key-kvno[4]INTEGER(0..4294967295), -- kvno of base-key
|
||||
...
|
||||
}
|
||||
|
||||
HDB-Ext-KeyRotation ::= SEQUENCE SIZE (1..3) OF KeyRotation
|
||||
|
||||
HDB-extension ::= SEQUENCE {
|
||||
mandatory[0] BOOLEAN, -- kdc MUST understand this extension,
|
||||
@@ -111,7 +194,7 @@ HDB-extension ::= SEQUENCE {
|
||||
data[1] CHOICE {
|
||||
pkinit-acl[0] HDB-Ext-PKINIT-acl,
|
||||
pkinit-cert-hash[1] HDB-Ext-PKINIT-hash,
|
||||
allowed-to-delegate-to[2] HDB-Ext-Constrained-delegation-acl,
|
||||
allowed-to-delegate-to[2] HDB-Ext-Constrained-delegation-acl,
|
||||
-- referral-info[3] HDB-Ext-Referrals,
|
||||
lm-owf[4] HDB-Ext-Lan-Manager-OWF,
|
||||
password[5] HDB-Ext-Password,
|
||||
@@ -123,6 +206,8 @@ HDB-extension ::= SEQUENCE {
|
||||
hist-kvno-diff-svc[11] INTEGER (0..4294967295),
|
||||
policy[12] UTF8String,
|
||||
principal-id[13] INTEGER(-9223372036854775808..9223372036854775807),
|
||||
key-rotation[14] HDB-Ext-KeyRotation,
|
||||
krb5-config[15] OCTET STRING,
|
||||
...
|
||||
},
|
||||
...
|
||||
@@ -130,6 +215,9 @@ HDB-extension ::= SEQUENCE {
|
||||
|
||||
HDB-extensions ::= SEQUENCE OF HDB-extension
|
||||
|
||||
-- Just for convenience, for encoding this as TL data in lib/kadm5
|
||||
HDB-EncTypeList ::= SEQUENCE OF INTEGER (0..4294967295)
|
||||
|
||||
hdb_entry ::= SEQUENCE {
|
||||
principal[0] Principal OPTIONAL, -- this is optional only
|
||||
-- for compatibility with libkrb5
|
||||
@@ -143,7 +231,7 @@ hdb_entry ::= SEQUENCE {
|
||||
max-life[8] INTEGER (0..4294967295) OPTIONAL,
|
||||
max-renew[9] INTEGER (0..4294967295) OPTIONAL,
|
||||
flags[10] HDBFlags,
|
||||
etypes[11] SEQUENCE OF INTEGER (0..4294967295) OPTIONAL,
|
||||
etypes[11] HDB-EncTypeList OPTIONAL,
|
||||
generation[12] GENERATION OPTIONAL,
|
||||
extensions[13] HDB-extensions OPTIONAL
|
||||
}
|
||||
|
232
lib/hdb/hdb.c
232
lib/hdb/hdb.c
@@ -119,6 +119,16 @@ static struct hdb_method default_dbmethod =
|
||||
{ HDB_INTERFACE_VERSION, NULL, NULL, "", hdb_ndbm_create };
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Returns the Keys of `e' for `kvno', or NULL if not found. The Keys will
|
||||
* remain valid provided that the entry is not mutated.
|
||||
*
|
||||
* @param context Context
|
||||
* @param e The HDB entry
|
||||
* @param kvno The kvno
|
||||
*
|
||||
* @return A pointer to the Keys for the requested kvno.
|
||||
*/
|
||||
const Keys *
|
||||
hdb_kvno2keys(krb5_context context,
|
||||
const hdb_entry *e,
|
||||
@@ -128,7 +138,7 @@ hdb_kvno2keys(krb5_context context,
|
||||
HDB_extension *extp;
|
||||
size_t i;
|
||||
|
||||
if (kvno == 0)
|
||||
if (kvno == 0 || e->kvno == kvno)
|
||||
return &e->keys;
|
||||
|
||||
extp = hdb_find_extension(e, choice_HDB_extension_data_hist_keys);
|
||||
@@ -144,6 +154,188 @@ hdb_kvno2keys(krb5_context context,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Based on remove_HDB_Ext_KeySet(), generated by the ASN.1 compiler */
|
||||
static int
|
||||
dequeue_HDB_Ext_KeySet(HDB_Ext_KeySet *data, unsigned int element, hdb_keyset *ks)
|
||||
{
|
||||
if (element >= data->len) {
|
||||
ks->kvno = 0;
|
||||
ks->keys.len = 0;
|
||||
ks->keys.val = 0;
|
||||
ks->set_time = 0;
|
||||
return ASN1_OVERRUN;
|
||||
}
|
||||
*ks = data->val[element];
|
||||
data->len--;
|
||||
/* Swap instead of memmove()... changes the order of elements */
|
||||
if (element < data->len)
|
||||
data->val[element] = data->val[data->len];
|
||||
if (data->len == 0) {
|
||||
free(data->val);
|
||||
data->val = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes from `e' and optionally outputs the keyset for the requested `kvno'.
|
||||
*
|
||||
* @param context Context
|
||||
* @param e The HDB entry
|
||||
* @param kvno The key version number
|
||||
* @param ks A pointer to a variable of type hdb_keyset (may be NULL)
|
||||
*
|
||||
* @return Zero on success, an error code otherwise.
|
||||
*/
|
||||
krb5_error_code
|
||||
hdb_remove_keys(krb5_context context,
|
||||
hdb_entry *e,
|
||||
krb5_kvno kvno,
|
||||
hdb_keyset *ks)
|
||||
{
|
||||
HDB_Ext_KeySet *hist_keys;
|
||||
HDB_extension *extp;
|
||||
size_t i;
|
||||
|
||||
if (kvno == 0 || e->kvno == kvno) {
|
||||
if (ks) {
|
||||
KerberosTime t;
|
||||
|
||||
(void) hdb_entry_get_pw_change_time(e, &t);
|
||||
if (t) {
|
||||
if ((ks->set_time = malloc(sizeof(*ks->set_time))) == NULL)
|
||||
return krb5_enomem(context);
|
||||
*ks->set_time = t;
|
||||
}
|
||||
ks->kvno = e->kvno;
|
||||
ks->keys = e->keys;
|
||||
e->keys.len = 0;
|
||||
e->keys.val = NULL;
|
||||
e->kvno = 0;
|
||||
} else {
|
||||
free_Keys(&e->keys);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ks) {
|
||||
ks->kvno = 0;
|
||||
ks->keys.len = 0;
|
||||
ks->keys.val = 0;
|
||||
ks->set_time = 0;
|
||||
}
|
||||
|
||||
extp = hdb_find_extension(e, choice_HDB_extension_data_hist_keys);
|
||||
if (extp == NULL)
|
||||
return 0;
|
||||
|
||||
hist_keys = &extp->data.u.hist_keys;
|
||||
for (i = 0; i < hist_keys->len; i++) {
|
||||
if (hist_keys->val[i].kvno != kvno)
|
||||
continue;
|
||||
if (ks)
|
||||
return dequeue_HDB_Ext_KeySet(hist_keys, i, ks);
|
||||
return remove_HDB_Ext_KeySet(hist_keys, i);
|
||||
}
|
||||
return HDB_ERR_NOENTRY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes from `e' and outputs all the base keys for virtual principal and/or
|
||||
* key derivation.
|
||||
*
|
||||
* @param context Context
|
||||
* @param e The HDB entry
|
||||
* @param ks A pointer to a variable of type HDB_Ext_KeySet
|
||||
*
|
||||
* @return Zero on success, an error code otherwise.
|
||||
*/
|
||||
krb5_error_code
|
||||
hdb_remove_base_keys(krb5_context context,
|
||||
hdb_entry *e,
|
||||
HDB_Ext_KeySet *base_keys)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
const HDB_Ext_KeyRotation *ckr;
|
||||
HDB_Ext_KeyRotation kr;
|
||||
size_t i, k;
|
||||
|
||||
ret = hdb_entry_get_key_rotation(context, e, &ckr);
|
||||
if (ret == 0) {
|
||||
/*
|
||||
* Changing the entry's extensions invalidates extensions obtained
|
||||
* before the change.
|
||||
*/
|
||||
ret = copy_HDB_Ext_KeyRotation(ckr, &kr);
|
||||
ckr = NULL;
|
||||
}
|
||||
base_keys->len = 0;
|
||||
if (ret == 0 &&
|
||||
(base_keys->val = calloc(kr.len, sizeof(base_keys->val[0]))) == NULL)
|
||||
ret = krb5_enomem(context);
|
||||
|
||||
for (k = i = 0; ret == 0 && i < kr.len; i++) {
|
||||
const KeyRotation *krp = &kr.val[i];
|
||||
|
||||
/*
|
||||
* WARNING: O(N * M) where M is number of keysets and N is the number
|
||||
* of base keysets.
|
||||
*
|
||||
* In practice N will never be > 3 because the ASN.1 module imposes
|
||||
* that as a constraint, and M will generally be the same as N, so this
|
||||
* will be O(1) after all.
|
||||
*/
|
||||
ret = hdb_remove_keys(context, e, krp->base_key_kvno,
|
||||
&base_keys->val[k]);
|
||||
if (ret == 0)
|
||||
k++;
|
||||
else if (ret == HDB_ERR_NOENTRY)
|
||||
ret = 0;
|
||||
}
|
||||
if (ret == 0)
|
||||
base_keys->len = k;
|
||||
else
|
||||
free_HDB_Ext_KeySet(base_keys);
|
||||
free_HDB_Ext_KeyRotation(&kr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes from `e' and outputs all the base keys for virtual principal and/or
|
||||
* key derivation.
|
||||
*
|
||||
* @param context Context
|
||||
* @param e The HDB entry
|
||||
* @param is_current_keyset Whether to make the keys the current keys for `e'
|
||||
* @param ks A pointer to an hdb_keyset containing the keys to set
|
||||
*
|
||||
* @return Zero on success, an error code otherwise.
|
||||
*/
|
||||
krb5_error_code
|
||||
hdb_install_keyset(krb5_context context,
|
||||
hdb_entry *e,
|
||||
int is_current_keyset,
|
||||
const hdb_keyset *ks)
|
||||
{
|
||||
krb5_error_code ret = 0;
|
||||
|
||||
if (is_current_keyset) {
|
||||
if (e->keys.len &&
|
||||
(ret = hdb_add_current_keys_to_history(context, e)))
|
||||
return ret;
|
||||
free_Keys(&e->keys);
|
||||
if (ret == 0)
|
||||
ret = copy_Keys(&ks->keys, &e->keys);
|
||||
e->kvno = ks->kvno;
|
||||
if (ks->set_time)
|
||||
return hdb_entry_set_pw_change_time(context, e, *ks->set_time);
|
||||
return 0;
|
||||
}
|
||||
return hdb_add_history_keyset(context, e, ks);
|
||||
}
|
||||
|
||||
|
||||
krb5_error_code
|
||||
hdb_next_enctype2key(krb5_context context,
|
||||
const hdb_entry *e,
|
||||
@@ -481,8 +673,10 @@ _hdb_keytab2hdb_entry(krb5_context context,
|
||||
krb5_error_code
|
||||
hdb_create(krb5_context context, HDB **db, const char *filename)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
struct cb_s cb_ctx;
|
||||
|
||||
*db = NULL;
|
||||
if (filename == NULL)
|
||||
filename = HDB_DEFAULT_DB;
|
||||
cb_ctx.h = find_method (filename, &cb_ctx.residual);
|
||||
@@ -504,9 +698,43 @@ hdb_create(krb5_context context, HDB **db, const char *filename)
|
||||
|
||||
free(rk_UNCONST(hdb_plugin_data.name));
|
||||
}
|
||||
/* XXX krb5_errx()?! */
|
||||
if (cb_ctx.h == NULL)
|
||||
krb5_errx(context, 1, "No database support for %s", cb_ctx.filename);
|
||||
return (*cb_ctx.h->create)(context, db, cb_ctx.residual);
|
||||
ret = (*cb_ctx.h->create)(context, db, cb_ctx.residual);
|
||||
if (ret == 0 && *db) {
|
||||
(*db)->enable_virtual_hostbased_princs =
|
||||
krb5_config_get_bool_default(context, NULL, FALSE, "hdb",
|
||||
"enable_virtual_hostbased_princs",
|
||||
NULL);
|
||||
(*db)->virtual_hostbased_princ_ndots =
|
||||
krb5_config_get_int_default(context, NULL, 1, "hdb",
|
||||
"virtual_hostbased_princ_mindots",
|
||||
NULL);
|
||||
(*db)->virtual_hostbased_princ_maxdots =
|
||||
krb5_config_get_int_default(context, NULL, 0, "hdb",
|
||||
"virtual_hostbased_princ_maxdots",
|
||||
NULL);
|
||||
(*db)->new_service_key_delay =
|
||||
krb5_config_get_time_default(context, NULL, 0, "hdb",
|
||||
"new_service_key_delay", NULL);
|
||||
/*
|
||||
* XXX Needs freeing in the HDB backends because we don't have a
|
||||
* first-class hdb_close() :(
|
||||
*/
|
||||
(*db)->virtual_hostbased_princ_svcs =
|
||||
krb5_config_get_strings(context, NULL, "hdb",
|
||||
"virtual_hostbased_princ_svcs", NULL);
|
||||
/* Check for ENOMEM */
|
||||
if ((*db)->virtual_hostbased_princ_svcs == NULL
|
||||
&& krb5_config_get_string(context, NULL, "hdb",
|
||||
"virtual_hostbased_princ_svcs", NULL)) {
|
||||
(*db)->hdb_destroy(context, *db);
|
||||
*db = NULL;
|
||||
ret = krb5_enomem(context);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
uintptr_t KRB5_CALLCONV
|
||||
|
@@ -81,6 +81,12 @@ enum hdb_lockop{ HDB_RLOCK, HDB_WLOCK };
|
||||
/* key usage for master key */
|
||||
#define HDB_KU_MKEY 0x484442
|
||||
|
||||
/*
|
||||
* Second component of WELLKNOWN namespace principals, the third component is
|
||||
* the common DNS suffix of the implied virtual hosts.
|
||||
*/
|
||||
#define HDB_WK_NAMESPACE "HOSTBASED-NAMESPACE"
|
||||
|
||||
typedef struct hdb_master_key_data *hdb_master_key;
|
||||
|
||||
/**
|
||||
@@ -114,6 +120,17 @@ typedef struct HDB {
|
||||
int hdb_capability_flags;
|
||||
int lock_count;
|
||||
int lock_type;
|
||||
/*
|
||||
* These fields cache config values.
|
||||
*
|
||||
* XXX Move these into a structure that we point to so that we
|
||||
* don't need to break the ABI every time we add a field.
|
||||
*/
|
||||
int enable_virtual_hostbased_princs;
|
||||
size_t virtual_hostbased_princ_ndots; /* Min. # of .s in hostname */
|
||||
size_t virtual_hostbased_princ_maxdots; /* Max. # of .s in namespace */
|
||||
char **virtual_hostbased_princ_svcs; /* Which svcs are not wildcarded */
|
||||
time_t new_service_key_delay; /* Delay for new keys */
|
||||
/**
|
||||
* Open (or create) the a Kerberos database.
|
||||
*
|
||||
|
114
lib/hdb/keys.c
114
lib/hdb/keys.c
@@ -217,6 +217,15 @@ hdb_prune_keys_kvno(krb5_context context, hdb_entry *entry, int kvno)
|
||||
size_t nelem;
|
||||
size_t i;
|
||||
|
||||
/*
|
||||
* XXX Pruning old keys for namespace principals may not be desirable, but!
|
||||
* as long as the `set_time's of the base keys for a namespace principal
|
||||
* match the `epoch's of the corresponding KeyRotation periods, it will be
|
||||
* perfectly acceptable to prune old [base] keys for namespace principals
|
||||
* just as for any other principal. Therefore, we may not need to make any
|
||||
* changes here w.r.t. namespace principals.
|
||||
*/
|
||||
|
||||
ext = hdb_find_extension(entry, choice_HDB_extension_data_hist_keys);
|
||||
if (ext == NULL)
|
||||
return 0;
|
||||
@@ -279,6 +288,53 @@ hdb_prune_keys(krb5_context context, hdb_entry *entry)
|
||||
return hdb_prune_keys_kvno(context, entry, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function adds a keyset to an HDB entry's key history.
|
||||
*
|
||||
* @param context Context
|
||||
* @param entry HDB entry
|
||||
* @param kvno Key version number of the key to add to the history
|
||||
* @param key The Key to add
|
||||
*/
|
||||
krb5_error_code
|
||||
hdb_add_history_keyset(krb5_context context,
|
||||
hdb_entry *entry,
|
||||
const hdb_keyset *ks)
|
||||
{
|
||||
size_t i;
|
||||
HDB_Ext_KeySet *hist_keys;
|
||||
HDB_extension ext;
|
||||
HDB_extension *extp;
|
||||
krb5_error_code ret;
|
||||
|
||||
memset(&ext, 0, sizeof (ext));
|
||||
|
||||
extp = hdb_find_extension(entry, choice_HDB_extension_data_hist_keys);
|
||||
if (extp == NULL) {
|
||||
ext.mandatory = FALSE;
|
||||
ext.data.element = choice_HDB_extension_data_hist_keys;
|
||||
ext.data.u.hist_keys.len = 0;
|
||||
ext.data.u.hist_keys.val = 0;
|
||||
extp = &ext;
|
||||
}
|
||||
hist_keys = &extp->data.u.hist_keys;
|
||||
|
||||
for (i = 0; i < hist_keys->len; i++) {
|
||||
if (hist_keys->val[i].kvno == ks->kvno) {
|
||||
/* Replace existing */
|
||||
free_hdb_keyset(&hist_keys->val[i]);
|
||||
ret = copy_hdb_keyset(ks, &hist_keys->val[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i >= hist_keys->len)
|
||||
ret = add_HDB_Ext_KeySet(hist_keys, ks); /* Append new */
|
||||
if (ret == 0 && extp == &ext)
|
||||
ret = hdb_replace_extension(context, entry, &ext);
|
||||
free_HDB_extension(&ext);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function adds an HDB entry's current keyset to the entry's key
|
||||
* history. The current keyset is left alone; the caller is responsible
|
||||
@@ -286,65 +342,30 @@ hdb_prune_keys(krb5_context context, hdb_entry *entry)
|
||||
*
|
||||
* @param context Context
|
||||
* @param entry HDB entry
|
||||
*
|
||||
* @return Zero on success, or an error code otherwise.
|
||||
*/
|
||||
krb5_error_code
|
||||
hdb_add_current_keys_to_history(krb5_context context, hdb_entry *entry)
|
||||
{
|
||||
krb5_boolean replace = FALSE;
|
||||
krb5_error_code ret;
|
||||
HDB_extension *ext;
|
||||
HDB_Ext_KeySet *keys;
|
||||
hdb_keyset newkeyset;
|
||||
hdb_keyset ks;
|
||||
time_t newtime;
|
||||
|
||||
if (entry->keys.len == 0)
|
||||
return 0; /* nothing to do */
|
||||
|
||||
ext = hdb_find_extension(entry, choice_HDB_extension_data_hist_keys);
|
||||
if (ext == NULL) {
|
||||
replace = TRUE;
|
||||
ext = calloc(1, sizeof (*ext));
|
||||
if (ext == NULL)
|
||||
return krb5_enomem(context);
|
||||
|
||||
ext->data.element = choice_HDB_extension_data_hist_keys;
|
||||
}
|
||||
keys = &ext->data.u.hist_keys;
|
||||
|
||||
ext->mandatory = FALSE;
|
||||
|
||||
/*
|
||||
* Copy in newest old keyset
|
||||
*/
|
||||
ret = hdb_entry_get_pw_change_time(entry, &newtime);
|
||||
if (ret)
|
||||
goto out;
|
||||
return ret;
|
||||
|
||||
memset(&newkeyset, 0, sizeof(newkeyset));
|
||||
newkeyset.keys = entry->keys;
|
||||
newkeyset.kvno = entry->kvno;
|
||||
newkeyset.set_time = &newtime;
|
||||
ks.keys = entry->keys;
|
||||
ks.kvno = entry->kvno;
|
||||
ks.set_time = &newtime;
|
||||
|
||||
ret = add_HDB_Ext_KeySet(keys, &newkeyset);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
if (replace) {
|
||||
/* hdb_replace_extension() deep-copies ext; what a waste */
|
||||
ret = hdb_replace_extension(context, entry, ext);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = hdb_prune_keys(context, entry);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
out:
|
||||
if (replace && ext) {
|
||||
free_HDB_extension(ext);
|
||||
free(ext);
|
||||
}
|
||||
ret = hdb_add_history_keyset(context, entry, &ks);
|
||||
if (ret == 0)
|
||||
ret = hdb_prune_keys(context, entry);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -355,6 +376,8 @@ hdb_add_current_keys_to_history(krb5_context context, hdb_entry *entry)
|
||||
* @param entry HDB entry
|
||||
* @param kvno Key version number of the key to add to the history
|
||||
* @param key The Key to add
|
||||
*
|
||||
* @return Zero on success, or an error code otherwise.
|
||||
*/
|
||||
krb5_error_code
|
||||
hdb_add_history_key(krb5_context context, hdb_entry *entry, krb5_kvno kvno, Key *key)
|
||||
@@ -404,7 +427,6 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function changes an hdb_entry's kvno, swapping the current key
|
||||
* set with a historical keyset. If no historical keys are found then
|
||||
|
@@ -211,10 +211,10 @@ hdb_get_entry(krb5_context context,
|
||||
goto out2;
|
||||
}
|
||||
|
||||
ret = (*db->hdb_fetch_kvno)(context, db, principal,
|
||||
HDB_F_DECRYPT|HDB_F_KVNO_SPECIFIED|
|
||||
HDB_F_GET_CLIENT|HDB_F_GET_SERVER|HDB_F_GET_KRBTGT,
|
||||
kvno, &ent);
|
||||
ret = hdb_fetch_kvno(context, db, principal,
|
||||
HDB_F_DECRYPT|HDB_F_KVNO_SPECIFIED|
|
||||
HDB_F_GET_CLIENT|HDB_F_GET_SERVER|HDB_F_GET_KRBTGT,
|
||||
0, 0, kvno, &ent);
|
||||
|
||||
if(ret == HDB_ERR_NOENTRY) {
|
||||
ret = KRB5_KT_NOTFOUND;
|
||||
|
@@ -1,7 +1,12 @@
|
||||
EXPORTS
|
||||
encode_hdb_keyset
|
||||
hdb_add_master_key
|
||||
_hdb_fetch_kvno
|
||||
_hdb_remove
|
||||
_hdb_store
|
||||
hdb_add_current_keys_to_history
|
||||
hdb_add_history_key
|
||||
hdb_add_history_keyset
|
||||
hdb_add_master_key
|
||||
hdb_change_kvno
|
||||
hdb_check_db_format
|
||||
hdb_clear_extension
|
||||
@@ -16,22 +21,28 @@ EXPORTS
|
||||
hdb_dbinfo_get_mkey_file
|
||||
hdb_dbinfo_get_next
|
||||
hdb_dbinfo_get_realm
|
||||
hdb_derive_etypes
|
||||
hdb_default_db
|
||||
hdb_enctype2key
|
||||
hdb_entry2string
|
||||
hdb_entry2value
|
||||
hdb_entry_add_key_rotation
|
||||
hdb_entry_alias2value
|
||||
hdb_entry_check_mandatory
|
||||
hdb_entry_clear_password
|
||||
hdb_entry_get_ConstrainedDelegACL
|
||||
hdb_entry_get_aliases
|
||||
hdb_entry_get_key_rotation
|
||||
hdb_entry_get_krb5_config
|
||||
hdb_entry_get_password
|
||||
hdb_entry_get_pkinit_acl
|
||||
hdb_entry_get_pkinit_cert
|
||||
hdb_entry_get_pkinit_hash
|
||||
hdb_entry_get_pw_change_time
|
||||
hdb_entry_set_krb5_config
|
||||
hdb_entry_set_password
|
||||
hdb_entry_set_pw_change_time
|
||||
hdb_fetch_kvno
|
||||
hdb_find_extension
|
||||
hdb_foreach
|
||||
hdb_free_dbinfo
|
||||
@@ -45,6 +56,7 @@ EXPORTS
|
||||
hdb_get_dbinfo
|
||||
hdb_get_instance
|
||||
hdb_init_db
|
||||
hdb_install_keyset
|
||||
hdb_interface_version DATA
|
||||
hdb_key2principal
|
||||
hdb_kvno2keys
|
||||
@@ -57,6 +69,8 @@ EXPORTS
|
||||
hdb_prune_keys
|
||||
hdb_prune_keys_kvno
|
||||
hdb_read_master_key
|
||||
hdb_remove_base_keys
|
||||
hdb_remove_keys
|
||||
hdb_replace_extension
|
||||
hdb_seal_key
|
||||
hdb_seal_key_mkey
|
||||
@@ -69,7 +83,10 @@ EXPORTS
|
||||
hdb_unseal_key
|
||||
hdb_unseal_key_mkey
|
||||
hdb_unseal_keys
|
||||
hdb_unseal_keys_kvno
|
||||
hdb_unseal_keys_mkey
|
||||
hdb_validate_key_rotation
|
||||
hdb_validate_key_rotations
|
||||
hdb_value2entry
|
||||
hdb_value2entry_alias
|
||||
hdb_write_master_key
|
||||
@@ -80,43 +97,58 @@ EXPORTS
|
||||
hdb_get_kt_ops
|
||||
|
||||
; MIT KDB related entries
|
||||
_hdb_mdb_value2entry
|
||||
_hdb_mit_dump2mitdb_entry
|
||||
_hdb_mdb_value2entry
|
||||
_hdb_mit_dump2mitdb_entry
|
||||
|
||||
; some random bits needed for libkadm
|
||||
HDBFlags2int
|
||||
add_HDB_Ext_KeyRotation
|
||||
add_HDB_Ext_KeySet
|
||||
add_Keys
|
||||
asn1_HDBFlags_units
|
||||
copy_Event
|
||||
copy_HDB_EncTypeList
|
||||
copy_HDB_extensions
|
||||
copy_HDB_Ext_KeyRotation
|
||||
copy_Key
|
||||
copy_Keys
|
||||
copy_Keys
|
||||
copy_Salt
|
||||
decode_HDB_EncTypeList
|
||||
decode_HDB_Ext_Aliases
|
||||
decode_HDB_Ext_PKINIT_acl
|
||||
decode_HDB_extension
|
||||
decode_Key
|
||||
decode_Keys
|
||||
decode_HDB_Ext_KeyRotation
|
||||
decode_HDB_Ext_PKINIT_acl
|
||||
decode_Key
|
||||
decode_Keys
|
||||
encode_HDB_EncTypeList
|
||||
encode_HDB_Ext_Aliases
|
||||
encode_HDB_Ext_PKINIT_acl
|
||||
encode_HDB_extension
|
||||
encode_Key
|
||||
encode_Keys
|
||||
encode_HDB_Ext_KeyRotation
|
||||
encode_HDB_Ext_PKINIT_acl
|
||||
encode_Key
|
||||
encode_Keys
|
||||
free_Event
|
||||
free_HDB_EncTypeList
|
||||
free_hdb_entry
|
||||
free_HDB_Ext_Aliases
|
||||
free_HDB_Ext_PKINIT_acl
|
||||
free_HDB_extension
|
||||
free_HDB_extensions
|
||||
free_HDB_Ext_KeyRotation
|
||||
free_HDB_Ext_KeySet
|
||||
free_HDB_Ext_PKINIT_acl
|
||||
free_hdb_keyset
|
||||
free_Key
|
||||
free_Keys
|
||||
free_Salt
|
||||
free_hdb_entry
|
||||
free_hdb_keyset
|
||||
free_Salt
|
||||
HDBFlags2int
|
||||
int2HDBFlags
|
||||
int2KeyRotationFlags
|
||||
KeyRotationFlags2int
|
||||
length_HDB_EncTypeList
|
||||
length_HDB_Ext_Aliases
|
||||
length_HDB_Ext_PKINIT_acl
|
||||
length_HDB_extension
|
||||
length_Key
|
||||
length_Keys
|
||||
add_Keys
|
||||
add_HDB_Ext_KeySet
|
||||
remove_Keys
|
||||
length_HDB_Ext_KeyRotation
|
||||
length_HDB_Ext_PKINIT_acl
|
||||
length_Key
|
||||
length_Keys
|
||||
remove_HDB_Ext_KeyRotation
|
||||
remove_Keys
|
||||
|
@@ -53,7 +53,8 @@ struct ndbm_db {
|
||||
static krb5_error_code
|
||||
NDBM_destroy(krb5_context context, HDB *db)
|
||||
{
|
||||
hdb_clear_master_key (context, db);
|
||||
hdb_clear_master_key(context, db);
|
||||
krb5_config_free_strings(db->virtual_hostbased_princ_svcs);
|
||||
free(db->hdb_name);
|
||||
free(db);
|
||||
return 0;
|
||||
|
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2013 Jeffrey Clark
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. 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.
|
||||
*
|
||||
* 3. Neither the name of the Institute nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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.
|
||||
*/
|
||||
|
||||
#include "hdb_locl.h"
|
||||
|
||||
struct hdb_called {
|
||||
int create;
|
||||
int init;
|
||||
int fini;
|
||||
};
|
||||
struct hdb_called testresult;
|
||||
|
||||
static krb5_error_code
|
||||
hdb_test_create(krb5_context context, struct HDB **db, const char *arg)
|
||||
{
|
||||
testresult.create = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static krb5_error_code
|
||||
hdb_test_init(krb5_context context, void **ctx)
|
||||
{
|
||||
*ctx = NULL;
|
||||
testresult.init = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hdb_test_fini(void *ctx)
|
||||
{
|
||||
testresult.fini = 1;
|
||||
}
|
||||
|
||||
struct hdb_method hdb_test =
|
||||
{
|
||||
#ifdef WIN32
|
||||
/* Not c99 */
|
||||
HDB_INTERFACE_VERSION,
|
||||
hdb_test_init,
|
||||
hdb_test_fini,
|
||||
"test",
|
||||
hdb_test_create
|
||||
#else
|
||||
.version = HDB_INTERFACE_VERSION,
|
||||
.init = hdb_test_init,
|
||||
.fini = hdb_test_fini,
|
||||
.prefix = "test",
|
||||
.create = hdb_test_create
|
||||
#endif
|
||||
};
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
krb5_context context;
|
||||
HDB *db;
|
||||
|
||||
setprogname(argv[0]);
|
||||
|
||||
ret = krb5_init_context(&context);
|
||||
if (ret)
|
||||
errx(1, "krb5_init_contex");
|
||||
|
||||
ret = krb5_plugin_register(context,
|
||||
PLUGIN_TYPE_DATA, "hdb_test_interface",
|
||||
&hdb_test);
|
||||
if(ret) {
|
||||
krb5_err(context, 1, ret, "krb5_plugin_register");
|
||||
}
|
||||
|
||||
ret = hdb_create(context, &db, "test:test&1234");
|
||||
if(ret) {
|
||||
krb5_err(context, 1, ret, "hdb_create");
|
||||
}
|
||||
|
||||
krb5_free_context(context);
|
||||
return 0;
|
||||
}
|
892
lib/hdb/test_namespace.c
Normal file
892
lib/hdb/test_namespace.c
Normal file
@@ -0,0 +1,892 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Kungliga Tekniska Högskolan
|
||||
* (Royal Institute of Technology, Stockholm, Sweden).
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. 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.
|
||||
*
|
||||
* 3. Neither the name of the Institute nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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 program implements an ephemeral, memory-based HDB backend, stores into
|
||||
* it just one HDB entry -one for a namespace- then checks that virtual
|
||||
* principals are returned below that namespace by hdb_fetch_kvno(), and that
|
||||
* the logic for automatic key rotation of virtual principals is correct.
|
||||
*/
|
||||
|
||||
#include "hdb_locl.h"
|
||||
#include <hex.h>
|
||||
|
||||
static KeyRotation krs[2];
|
||||
static const char *base_pw[2] = { "Testing123...", "Tested123..." };
|
||||
|
||||
typedef struct {
|
||||
HDB hdb; /* generic members */
|
||||
/*
|
||||
* Make this dict a global, add a mutex lock around it, and a .finit and/or
|
||||
* atexit() handler to free it, and we'd have a first-class MEMORY HDB.
|
||||
*
|
||||
* What would a first-class MEMORY HDB be good for though, besides testing?
|
||||
*
|
||||
* However, we could move this dict into `HDB' and then have _hdb_store()
|
||||
* and friends support it as a cache for frequently-used & seldom-changing
|
||||
* entries, such as: K/M, namespaces, and krbtgt principals. That would
|
||||
* speed up lookups, especially for backends with poor reader-writer
|
||||
* concurrency (DB, LMDB) and LDAP. Such entries could be cached for a
|
||||
* minute or three at a time.
|
||||
*/
|
||||
heim_dict_t dict;
|
||||
} TEST_HDB;
|
||||
|
||||
struct hdb_called {
|
||||
int create;
|
||||
int init;
|
||||
int fini;
|
||||
};
|
||||
|
||||
static krb5_error_code
|
||||
TDB_close(krb5_context context, HDB *db)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static krb5_error_code
|
||||
TDB_destroy(krb5_context context, HDB *db)
|
||||
{
|
||||
TEST_HDB *tdb = (void *)db;
|
||||
|
||||
heim_release(tdb->dict);
|
||||
free(tdb->hdb.hdb_name);
|
||||
free(tdb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static krb5_error_code
|
||||
TDB_set_sync(krb5_context context, HDB *db, int on)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static krb5_error_code
|
||||
TDB_lock(krb5_context context, HDB *db, int operation)
|
||||
{
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static krb5_error_code
|
||||
TDB_unlock(krb5_context context, HDB *db)
|
||||
{
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static krb5_error_code
|
||||
TDB_firstkey(krb5_context context, HDB *db, unsigned flags, hdb_entry_ex *entry)
|
||||
{
|
||||
/* XXX Implement */
|
||||
/* Tricky thing: heim_dict_iterate_f() is inconvenient here */
|
||||
/* We need this to check that virtual principals aren't created */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static krb5_error_code
|
||||
TDB_nextkey(krb5_context context, HDB *db, unsigned flags, hdb_entry_ex *entry)
|
||||
{
|
||||
/* XXX Implement */
|
||||
/* Tricky thing: heim_dict_iterate_f() is inconvenient here */
|
||||
/* We need this to check that virtual principals aren't created */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static krb5_error_code
|
||||
TDB_rename(krb5_context context, HDB *db, const char *new_name)
|
||||
{
|
||||
return EEXIST;
|
||||
}
|
||||
|
||||
static krb5_error_code
|
||||
TDB__get(krb5_context context, HDB *db, krb5_data key, krb5_data *reply)
|
||||
{
|
||||
krb5_error_code ret = 0;
|
||||
TEST_HDB *tdb = (void *)db;
|
||||
heim_object_t k, v;
|
||||
|
||||
if ((k = heim_data_create(key.data, key.length)) == NULL)
|
||||
ret = krb5_enomem(context);
|
||||
if (ret == 0 && (v = heim_dict_get_value(tdb->dict, k)) == NULL)
|
||||
ret = HDB_ERR_NOENTRY;
|
||||
if (ret == 0)
|
||||
ret = krb5_data_copy(reply, heim_data_get_ptr(v), heim_data_get_length(v));
|
||||
heim_release(k);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static krb5_error_code
|
||||
TDB__put(krb5_context context, HDB *db, int rplc, krb5_data kd, krb5_data vd)
|
||||
{
|
||||
krb5_error_code ret = 0;
|
||||
TEST_HDB *tdb = (void *)db;
|
||||
heim_object_t e = NULL;
|
||||
heim_object_t k = NULL;
|
||||
heim_object_t v = NULL;
|
||||
|
||||
if ((k = heim_data_create(kd.data, kd.length)) == NULL ||
|
||||
(v = heim_data_create(vd.data, vd.length)) == NULL)
|
||||
ret = krb5_enomem(context);
|
||||
if (ret == 0 && !rplc && (e = heim_dict_get_value(tdb->dict, k)) != NULL)
|
||||
ret = HDB_ERR_EXISTS;
|
||||
if (ret == 0 && heim_dict_set_value(tdb->dict, k, v))
|
||||
ret = krb5_enomem(context);
|
||||
heim_release(k);
|
||||
heim_release(v);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static krb5_error_code
|
||||
TDB__del(krb5_context context, HDB *db, krb5_data key)
|
||||
{
|
||||
krb5_error_code ret = 0;
|
||||
TEST_HDB *tdb = (void *)db;
|
||||
heim_object_t k, v;
|
||||
|
||||
if ((k = heim_data_create(key.data, key.length)) == NULL)
|
||||
ret = krb5_enomem(context);
|
||||
if (ret == 0 && (v = heim_dict_get_value(tdb->dict, k)) == NULL)
|
||||
ret = HDB_ERR_NOENTRY;
|
||||
if (ret == 0)
|
||||
heim_dict_delete_key(tdb->dict, k);
|
||||
heim_release(k);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static krb5_error_code
|
||||
TDB_open(krb5_context context, HDB *db, int flags, mode_t mode)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static krb5_error_code
|
||||
hdb_test_create(krb5_context context, struct HDB **db, const char *arg)
|
||||
{
|
||||
TEST_HDB *tdb;
|
||||
|
||||
if ((tdb = calloc(1, sizeof(tdb[0]))) == NULL ||
|
||||
(tdb->hdb.hdb_name = strdup(arg)) == NULL ||
|
||||
(tdb->dict = heim_dict_create(10)) == NULL) {
|
||||
free(tdb->hdb.hdb_name);
|
||||
free(tdb);
|
||||
return krb5_enomem(context);
|
||||
}
|
||||
|
||||
tdb->hdb.hdb_db = NULL;
|
||||
tdb->hdb.hdb_master_key_set = 0;
|
||||
tdb->hdb.hdb_openp = 0;
|
||||
tdb->hdb.hdb_capability_flags = HDB_CAP_F_HANDLE_ENTERPRISE_PRINCIPAL;
|
||||
tdb->hdb.hdb_open = TDB_open;
|
||||
tdb->hdb.hdb_close = TDB_close;
|
||||
tdb->hdb.hdb_fetch_kvno = _hdb_fetch_kvno;
|
||||
tdb->hdb.hdb_store = _hdb_store;
|
||||
tdb->hdb.hdb_remove = _hdb_remove;
|
||||
tdb->hdb.hdb_firstkey = TDB_firstkey;
|
||||
tdb->hdb.hdb_nextkey= TDB_nextkey;
|
||||
tdb->hdb.hdb_lock = TDB_lock;
|
||||
tdb->hdb.hdb_unlock = TDB_unlock;
|
||||
tdb->hdb.hdb_rename = TDB_rename;
|
||||
tdb->hdb.hdb__get = TDB__get;
|
||||
tdb->hdb.hdb__put = TDB__put;
|
||||
tdb->hdb.hdb__del = TDB__del;
|
||||
tdb->hdb.hdb_destroy = TDB_destroy;
|
||||
tdb->hdb.hdb_set_sync = TDB_set_sync;
|
||||
*db = &tdb->hdb;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static krb5_error_code
|
||||
hdb_test_init(krb5_context context, void **ctx)
|
||||
{
|
||||
*ctx = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hdb_test_fini(void *ctx)
|
||||
{
|
||||
}
|
||||
|
||||
struct hdb_method hdb_test =
|
||||
{
|
||||
#ifdef WIN32
|
||||
/* Not c99 */
|
||||
HDB_INTERFACE_VERSION,
|
||||
hdb_test_init,
|
||||
hdb_test_fini,
|
||||
"test",
|
||||
hdb_test_create
|
||||
#else
|
||||
.version = HDB_INTERFACE_VERSION,
|
||||
.init = hdb_test_init,
|
||||
.fini = hdb_test_fini,
|
||||
.prefix = "test",
|
||||
.create = hdb_test_create
|
||||
#endif
|
||||
};
|
||||
|
||||
static krb5_error_code
|
||||
make_base_key(krb5_context context,
|
||||
krb5_const_principal p,
|
||||
const char *pw,
|
||||
krb5_keyblock *k)
|
||||
{
|
||||
return krb5_string_to_key(context, KRB5_ENCTYPE_AES128_CTS_HMAC_SHA256_128,
|
||||
pw, p, k);
|
||||
}
|
||||
|
||||
static krb5_error_code
|
||||
tderive_key(krb5_context context,
|
||||
const char *p,
|
||||
KeyRotation *kr,
|
||||
int toffset,
|
||||
krb5_keyblock *base,
|
||||
krb5int32 etype,
|
||||
krb5_keyblock *k,
|
||||
uint32_t *kvno,
|
||||
time_t *set_time)
|
||||
{
|
||||
krb5_error_code ret = 0;
|
||||
krb5_crypto crypto = NULL;
|
||||
EncryptionKey intermediate;
|
||||
krb5_data pad, out;
|
||||
size_t len;
|
||||
int n;
|
||||
|
||||
n = toffset / kr->period;
|
||||
*set_time = kr->epoch + kr->period * n;
|
||||
*kvno = kr->base_kvno + n;
|
||||
|
||||
out.data = 0;
|
||||
out.length = 0;
|
||||
|
||||
/* Derive intermediate key */
|
||||
pad.data = (void *)(uintptr_t)p;
|
||||
pad.length = strlen(p);
|
||||
ret = krb5_enctype_keysize(context, base->keytype, &len);
|
||||
if (ret == 0)
|
||||
ret = krb5_crypto_init(context, base, 0, &crypto);
|
||||
if (ret == 0)
|
||||
ret = krb5_crypto_prfplus(context, crypto, &pad, len, &out);
|
||||
if (crypto)
|
||||
krb5_crypto_destroy(context, crypto);
|
||||
crypto = NULL;
|
||||
if (ret == 0)
|
||||
ret = krb5_random_to_key(context, etype, out.data, out.length,
|
||||
&intermediate);
|
||||
krb5_data_free(&out);
|
||||
|
||||
/* Derive final key */
|
||||
pad.data = kvno;
|
||||
pad.length = sizeof(*kvno);
|
||||
if (ret == 0)
|
||||
ret = krb5_enctype_keysize(context, etype, &len);
|
||||
if (ret == 0)
|
||||
ret = krb5_crypto_init(context, &intermediate, 0, &crypto);
|
||||
if (ret == 0) {
|
||||
*kvno = htonl(*kvno);
|
||||
ret = krb5_crypto_prfplus(context, crypto, &pad, len, &out);
|
||||
*kvno = ntohl(*kvno);
|
||||
}
|
||||
if (crypto)
|
||||
krb5_crypto_destroy(context, crypto);
|
||||
if (ret == 0)
|
||||
ret = krb5_random_to_key(context, etype, out.data, out.length, k);
|
||||
krb5_data_free(&out);
|
||||
|
||||
free_EncryptionKey(&intermediate);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Create a namespace principal */
|
||||
static void
|
||||
make_namespace(krb5_context context, HDB *db, const char *name)
|
||||
{
|
||||
krb5_error_code ret = 0;
|
||||
hdb_entry_ex e;
|
||||
Key k;
|
||||
|
||||
memset(&k, 0, sizeof(k));
|
||||
k.mkvno = 0;
|
||||
k.salt = 0;
|
||||
|
||||
/* Setup the HDB entry */
|
||||
memset(&e, 0, sizeof(e));
|
||||
e.ctx = 0;
|
||||
e.free_entry = 0;
|
||||
e.entry.created_by.time = krs[0].epoch;
|
||||
e.entry.valid_start = e.entry.valid_end = e.entry.pw_end = 0;
|
||||
e.entry.generation = 0;
|
||||
e.entry.flags = int2HDBFlags(0);
|
||||
e.entry.flags.server = e.entry.flags.client = 1;
|
||||
e.entry.flags.virtual = 1;
|
||||
|
||||
/* Setup etypes */
|
||||
if (ret == 0 &&
|
||||
(e.entry.etypes = malloc(sizeof(*e.entry.etypes))) == NULL)
|
||||
ret = krb5_enomem(context);
|
||||
if (ret == 0)
|
||||
e.entry.etypes->len = 3;
|
||||
if (ret == 0 &&
|
||||
(e.entry.etypes->val = calloc(e.entry.etypes->len,
|
||||
sizeof(e.entry.etypes->val[0]))) == NULL)
|
||||
ret = krb5_enomem(context);
|
||||
if (ret == 0) {
|
||||
e.entry.etypes->val[0] = KRB5_ENCTYPE_AES128_CTS_HMAC_SHA256_128;
|
||||
e.entry.etypes->val[1] = KRB5_ENCTYPE_AES256_CTS_HMAC_SHA384_192;
|
||||
e.entry.etypes->val[2] = KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1_96;
|
||||
}
|
||||
|
||||
/* Setup max_life and max_renew */
|
||||
if (ret == 0 &&
|
||||
(e.entry.max_life = malloc(sizeof(*e.entry.max_life))) == NULL)
|
||||
ret = krb5_enomem(context);
|
||||
if (ret == 0 &&
|
||||
(e.entry.max_renew = malloc(sizeof(*e.entry.max_renew))) == NULL)
|
||||
ret = krb5_enomem(context);
|
||||
if (ret == 0)
|
||||
/* Make it long, so we see the clamped max */
|
||||
*e.entry.max_renew = 2 * ((*e.entry.max_life = 15 * 24 * 3600));
|
||||
|
||||
/* Setup principal name and created_by */
|
||||
if (ret == 0)
|
||||
ret = krb5_parse_name(context, name, &e.entry.principal);
|
||||
if (ret == 0)
|
||||
ret = krb5_parse_name(context, "admin@BAR.EXAMPLE",
|
||||
&e.entry.created_by.principal);
|
||||
|
||||
/* Make base keys for first epoch */
|
||||
if (ret == 0)
|
||||
ret = make_base_key(context, e.entry.principal, base_pw[0], &k.key);
|
||||
if (ret == 0)
|
||||
add_Keys(&e.entry.keys, &k);
|
||||
if (ret == 0)
|
||||
ret = hdb_entry_set_pw_change_time(context, &e.entry, krs[0].epoch);
|
||||
free_Key(&k);
|
||||
e.entry.kvno = krs[0].base_key_kvno;
|
||||
|
||||
/* Move them to history */
|
||||
if (ret == 0)
|
||||
ret = hdb_add_current_keys_to_history(context, &e.entry);
|
||||
free_Keys(&e.entry.keys);
|
||||
|
||||
/* Make base keys for second epoch */
|
||||
if (ret == 0)
|
||||
ret = make_base_key(context, e.entry.principal, base_pw[1], &k.key);
|
||||
if (ret == 0)
|
||||
add_Keys(&e.entry.keys, &k);
|
||||
e.entry.kvno = krs[1].base_key_kvno;
|
||||
if (ret == 0)
|
||||
ret = hdb_entry_set_pw_change_time(context, &e.entry, krs[1].epoch);
|
||||
|
||||
/* Add the key rotation metadata */
|
||||
if (ret == 0)
|
||||
ret = hdb_entry_add_key_rotation(context, &e.entry, 0, &krs[0]);
|
||||
if (ret == 0)
|
||||
ret = hdb_entry_add_key_rotation(context, &e.entry, 0, &krs[1]);
|
||||
|
||||
if (ret == 0)
|
||||
ret = db->hdb_store(context, db, 0, &e);
|
||||
if (ret)
|
||||
krb5_err(context, 1, ret, "failed to setup a namespace principal");
|
||||
free_Key(&k);
|
||||
hdb_free_entry(context, &e);
|
||||
}
|
||||
|
||||
#define WK_PREFIX "WELLKNOWN/" HDB_WK_NAMESPACE "/"
|
||||
|
||||
static const char *expected[] = {
|
||||
WK_PREFIX "_/bar.example@BAR.EXAMPLE",
|
||||
"HTTP/bar.example@BAR.EXAMPLE",
|
||||
"HTTP/foo.bar.example@BAR.EXAMPLE",
|
||||
"host/foo.bar.example@BAR.EXAMPLE",
|
||||
"HTTP/blah.foo.bar.example@BAR.EXAMPLE",
|
||||
};
|
||||
static const char *unexpected[] = {
|
||||
WK_PREFIX "_/no.example@BAZ.EXAMPLE",
|
||||
"HTTP/no.example@BAR.EXAMPLE",
|
||||
"HTTP/foo.no.example@BAR.EXAMPLE",
|
||||
"HTTP/blah.foo.no.example@BAR.EXAMPLE",
|
||||
};
|
||||
|
||||
/*
|
||||
* We'll fetch as many entries as we have principal names in `expected[]', for
|
||||
* as many KeyRotation periods as we have (between 1 and 3), and for up to 5
|
||||
* different time offsets in each period.
|
||||
*/
|
||||
#define NUM_OFFSETS 5
|
||||
static hdb_entry_ex e[
|
||||
(sizeof(expected) / sizeof(expected[0])) *
|
||||
(sizeof(krs) / sizeof(krs[0])) *
|
||||
NUM_OFFSETS
|
||||
];
|
||||
|
||||
static int
|
||||
hist_key_compar(const void *va, const void *vb)
|
||||
{
|
||||
const hdb_keyset *a = va;
|
||||
const hdb_keyset *b = vb;
|
||||
|
||||
return a->kvno - b->kvno;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fetch keys for some decent time in the given kr.
|
||||
*
|
||||
* `kr' is an index into the global `krs[]'.
|
||||
* `t' is a number 0..4 inclusive that identifies a time period relative to the
|
||||
* epoch of `krs[kr]' (see code below).
|
||||
*/
|
||||
static void
|
||||
fetch_entries(krb5_context context,
|
||||
HDB *db,
|
||||
size_t kr,
|
||||
size_t t,
|
||||
int must_fail)
|
||||
{
|
||||
krb5_error_code ret = 0;
|
||||
krb5_principal p;
|
||||
krb5_keyblock base_key, dk;
|
||||
hdb_entry_ex *ep;
|
||||
hdb_entry_ex no;
|
||||
size_t i, b;
|
||||
int toffset;
|
||||
|
||||
/* Work out offset of first entry in `e[]' */
|
||||
assert(kr < sizeof(krs) / sizeof(krs[0]));
|
||||
assert(t < NUM_OFFSETS);
|
||||
b = (kr * NUM_OFFSETS + t) * (sizeof(expected) / sizeof(expected[0]));
|
||||
assert(b < sizeof(e) / sizeof(e[0]));
|
||||
assert(sizeof(e) / sizeof(e[0]) - b >=
|
||||
(sizeof(expected) / sizeof(expected[0])));
|
||||
|
||||
switch (t) {
|
||||
case 0: toffset = 1; break; /* epoch + 1s */
|
||||
case 1: toffset = 1 + (krs[kr].period >> 1); break; /* epoch + period/2 */
|
||||
case 2: toffset = 1 + (krs[kr].period >> 2); break; /* epoch + period/4 */
|
||||
case 3: toffset = 1 + (krs[kr].period >> 3); break; /* epoch + period/8 */
|
||||
case 4: toffset = 1 - (krs[kr].period >> 3); break; /* epoch - period/8 */
|
||||
}
|
||||
|
||||
for (i = 0; ret == 0 && i < sizeof(expected) / sizeof(expected[0]); i++) {
|
||||
ep = &e[b + i];
|
||||
if (ret == 0)
|
||||
ret = krb5_parse_name(context, expected[i], &p);
|
||||
if (ret == 0 && i == 0) {
|
||||
if (toffset < 0 && kr)
|
||||
ret = make_base_key(context, p, base_pw[kr - 1], &base_key);
|
||||
else
|
||||
ret = make_base_key(context, p, base_pw[kr], &base_key);
|
||||
}
|
||||
if (ret == 0)
|
||||
ret = hdb_fetch_kvno(context, db, p,
|
||||
HDB_F_DECRYPT | HDB_F_ALL_KVNOS,
|
||||
krs[kr].epoch + toffset, 0, 0, ep);
|
||||
if (i && must_fail && ret == 0)
|
||||
krb5_errx(context, 1,
|
||||
"virtual principal that shouldn't exist does");
|
||||
if (kr == 0 && toffset < 0 && ret == HDB_ERR_NOENTRY)
|
||||
continue;
|
||||
if (kr == 0 && toffset < 0) {
|
||||
/*
|
||||
* Virtual principals don't exist before their earliest key
|
||||
* rotation epoch's start time.
|
||||
*/
|
||||
if (i == 0) {
|
||||
if (ret)
|
||||
krb5_errx(context, 1,
|
||||
"namespace principal does not exist before its time");
|
||||
} else if (i != 0) {
|
||||
if (ret == 0)
|
||||
krb5_errx(context, 1,
|
||||
"virtual principal exists before its time");
|
||||
if (ret != HDB_ERR_NOENTRY)
|
||||
krb5_errx(context, 1, "wrong error code");
|
||||
ret = 0;
|
||||
}
|
||||
} else {
|
||||
if (ret == 0 &&
|
||||
!krb5_principal_compare(context, p, ep->entry.principal))
|
||||
krb5_errx(context, 1, "wrong principal in fetched entry");
|
||||
}
|
||||
|
||||
{
|
||||
HDB_Ext_KeySet *hist_keys;
|
||||
HDB_extension *ext;
|
||||
ext = hdb_find_extension(&ep->entry,
|
||||
choice_HDB_extension_data_hist_keys);
|
||||
if (ext) {
|
||||
/* Sort key history by kvno, why not */
|
||||
hist_keys = &ext->data.u.hist_keys;
|
||||
qsort(hist_keys->val, hist_keys->len,
|
||||
sizeof(hist_keys->val[0]), hist_key_compar);
|
||||
}
|
||||
}
|
||||
|
||||
krb5_free_principal(context, p);
|
||||
}
|
||||
if (ret && must_fail) {
|
||||
free_EncryptionKey(&base_key);
|
||||
return;
|
||||
}
|
||||
if (ret)
|
||||
krb5_err(context, 1, ret, "virtual principal test failed");
|
||||
|
||||
for (i = 0; i < sizeof(unexpected) / sizeof(unexpected[0]); i++) {
|
||||
if (ret == 0)
|
||||
ret = krb5_parse_name(context, unexpected[i], &p);
|
||||
if (ret == 0)
|
||||
ret = hdb_fetch_kvno(context, db, p, HDB_F_DECRYPT,
|
||||
krs[kr].epoch + toffset, 0, 0, &no);
|
||||
if (ret == 0)
|
||||
krb5_errx(context, 1, "bogus principal exists, wat");
|
||||
krb5_free_principal(context, p);
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
if (kr == 0 && toffset < 0)
|
||||
return;
|
||||
|
||||
/*
|
||||
* XXX
|
||||
*
|
||||
* Add check that derived keys are a) different, b) as expected, using a
|
||||
* set of test vectors or else by computing the expected keys here with
|
||||
* code that's not shared with lib/hdb/common.c.
|
||||
*
|
||||
* Add check that we get expected past and/or future keys, not just current
|
||||
* keys.
|
||||
*/
|
||||
for (i = 1; ret == 0 && i < sizeof(expected) / sizeof(expected[0]); i++) {
|
||||
uint32_t kvno;
|
||||
time_t set_time, chg_time;
|
||||
|
||||
ep = &e[b + i];
|
||||
if (toffset > 0) {
|
||||
ret = tderive_key(context, expected[i], &krs[kr], toffset,
|
||||
&base_key, base_key.keytype, &dk, &kvno, &set_time);
|
||||
} else /* XXX */{
|
||||
/* XXX */
|
||||
assert(kr);
|
||||
ret = tderive_key(context, expected[i], &krs[kr - 1],
|
||||
krs[kr].epoch - krs[kr - 1].epoch + toffset,
|
||||
&base_key, base_key.keytype, &dk, &kvno, &set_time);
|
||||
}
|
||||
if (ret)
|
||||
krb5_err(context, 1, ret, "deriving keys for comparison");
|
||||
|
||||
if (kvno != ep->entry.kvno)
|
||||
krb5_errx(context, 1, "kvno mismatch (%u != %u)", kvno, ep->entry.kvno);
|
||||
(void) hdb_entry_get_pw_change_time(&ep->entry, &chg_time);
|
||||
if (set_time != chg_time)
|
||||
krb5_errx(context, 1, "key change time mismatch");
|
||||
if (ep->entry.keys.len == 0)
|
||||
krb5_errx(context, 1, "no keys!");
|
||||
if (ep->entry.keys.val[0].key.keytype != dk.keytype)
|
||||
krb5_errx(context, 1, "enctype mismatch!");
|
||||
if (ep->entry.keys.val[0].key.keyvalue.length !=
|
||||
dk.keyvalue.length)
|
||||
krb5_errx(context, 1, "key length mismatch!");
|
||||
if (memcmp(ep->entry.keys.val[0].key.keyvalue.data,
|
||||
dk.keyvalue.data, dk.keyvalue.length))
|
||||
krb5_errx(context, 1, "key mismatch!");
|
||||
if (memcmp(ep->entry.keys.val[0].key.keyvalue.data,
|
||||
e[b + i - 1].entry.keys.val[0].key.keyvalue.data,
|
||||
dk.keyvalue.length) == 0)
|
||||
krb5_errx(context, 1, "different virtual principals have the same keys!");
|
||||
/* XXX Add check that we have the expected number of history keys */
|
||||
free_EncryptionKey(&dk);
|
||||
}
|
||||
free_EncryptionKey(&base_key);
|
||||
}
|
||||
|
||||
static void
|
||||
check_kvnos(krb5_context context)
|
||||
{
|
||||
HDB_Ext_KeySet keysets;
|
||||
size_t i, k, m, p; /* iterator indices */
|
||||
|
||||
keysets.len = 0;
|
||||
keysets.val = 0;
|
||||
|
||||
/* For every principal name */
|
||||
for (i = 0; i < sizeof(expected)/sizeof(expected[0]); i++) {
|
||||
free_HDB_Ext_KeySet(&keysets);
|
||||
|
||||
/* For every entry we've fetched for it */
|
||||
for (k = 0; k < sizeof(e)/sizeof(e[0]); k++) {
|
||||
HDB_Ext_KeySet *hist_keys;
|
||||
HDB_extension *ext;
|
||||
hdb_entry_ex *ep;
|
||||
int match = 0;
|
||||
|
||||
if ((k % NUM_OFFSETS) != i)
|
||||
continue;
|
||||
|
||||
ep = &e[k];
|
||||
if (ep->entry.principal == NULL)
|
||||
continue; /* Didn't fetch this one */
|
||||
|
||||
/*
|
||||
* Check that the current keys for it match what we've seen already
|
||||
* or else add them to `keysets'.
|
||||
*/
|
||||
for (m = 0; m < keysets.len; m++) {
|
||||
if (ep->entry.kvno == keysets.val[m].kvno) {
|
||||
/* Check the key is the same */
|
||||
if (ep->entry.keys.val[0].key.keytype !=
|
||||
keysets.val[m].keys.val[0].key.keytype ||
|
||||
ep->entry.keys.val[0].key.keyvalue.length !=
|
||||
keysets.val[m].keys.val[0].key.keyvalue.length ||
|
||||
memcmp(ep->entry.keys.val[0].key.keyvalue.data,
|
||||
keysets.val[m].keys.val[0].key.keyvalue.data,
|
||||
ep->entry.keys.val[0].key.keyvalue.length))
|
||||
krb5_errx(context, 1,
|
||||
"key mismatch for same princ & kvno");
|
||||
match = 1;
|
||||
}
|
||||
}
|
||||
if (m == keysets.len) {
|
||||
hdb_keyset ks;
|
||||
|
||||
ks.kvno = ep->entry.kvno;
|
||||
ks.keys = ep->entry.keys;
|
||||
ks.set_time = 0;
|
||||
if (add_HDB_Ext_KeySet(&keysets, &ks))
|
||||
krb5_err(context, 1, ENOMEM, "out of memory");
|
||||
match = 1;
|
||||
}
|
||||
if (match)
|
||||
continue;
|
||||
|
||||
/* For all non-current keysets, repeat the above */
|
||||
ext = hdb_find_extension(&ep->entry,
|
||||
choice_HDB_extension_data_hist_keys);
|
||||
if (!ext)
|
||||
continue;
|
||||
hist_keys = &ext->data.u.hist_keys;
|
||||
for (p = 0; p < hist_keys->len; p++) {
|
||||
for (m = 0; m < keysets.len; m++) {
|
||||
if (keysets.val[m].kvno == hist_keys->val[p].kvno)
|
||||
if (ep->entry.keys.val[0].key.keytype !=
|
||||
keysets.val[m].keys.val[0].key.keytype ||
|
||||
ep->entry.keys.val[0].key.keyvalue.length !=
|
||||
keysets.val[m].keys.val[0].key.keyvalue.length ||
|
||||
memcmp(ep->entry.keys.val[0].key.keyvalue.data,
|
||||
keysets.val[m].keys.val[0].key.keyvalue.data,
|
||||
ep->entry.keys.val[0].key.keyvalue.length))
|
||||
krb5_errx(context, 1,
|
||||
"key mismatch for same princ & kvno");
|
||||
}
|
||||
if (m == keysets.len) {
|
||||
hdb_keyset ks;
|
||||
ks.kvno = ep->entry.kvno;
|
||||
ks.keys = ep->entry.keys;
|
||||
ks.set_time = 0;
|
||||
if (add_HDB_Ext_KeySet(&keysets, &ks))
|
||||
krb5_err(context, 1, ENOMEM, "out of memory");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
free_HDB_Ext_KeySet(&keysets);
|
||||
}
|
||||
|
||||
static void
|
||||
print_em(krb5_context context)
|
||||
{
|
||||
HDB_Ext_KeySet *hist_keys;
|
||||
HDB_extension *ext;
|
||||
size_t i, p;
|
||||
|
||||
for (i = 0; i < sizeof(e)/sizeof(e[0]); i++) {
|
||||
const char *name = expected[i % (sizeof(expected)/sizeof(expected[0]))];
|
||||
char *x;
|
||||
|
||||
if (0 == i % (sizeof(expected)/sizeof(expected[0])))
|
||||
continue;
|
||||
if (e[i].entry.principal == NULL)
|
||||
continue;
|
||||
hex_encode(e[i].entry.keys.val[0].key.keyvalue.data,
|
||||
e[i].entry.keys.val[0].key.keyvalue.length, &x);
|
||||
printf("%s %u %s\n", x, e[i].entry.kvno, name);
|
||||
free(x);
|
||||
|
||||
ext = hdb_find_extension(&e[i].entry,
|
||||
choice_HDB_extension_data_hist_keys);
|
||||
if (!ext)
|
||||
continue;
|
||||
hist_keys = &ext->data.u.hist_keys;
|
||||
for (p = 0; p < hist_keys->len; p++) {
|
||||
hex_encode(hist_keys->val[p].keys.val[0].key.keyvalue.data,
|
||||
hist_keys->val[p].keys.val[0].key.keyvalue.length, &x);
|
||||
printf("%s %u %s\n", x, hist_keys->val[p].kvno, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
static void
|
||||
check_expected_kvnos(krb5_context context)
|
||||
{
|
||||
HDB_Ext_KeySet *hist_keys;
|
||||
HDB_extension *ext;
|
||||
size_t i, k, m, p;
|
||||
|
||||
for (i = 0; i < sizeof(expected)/sizeof(expected[0]); i++) {
|
||||
for (k = 0; k < sizeof(krs)/sizeof(krs[0]); k++) {
|
||||
hdb_entry_ex *ep = &e[k * sizeof(expected)/sizeof(expected[0]) + i];
|
||||
|
||||
if (ep->entry.principal == NULL)
|
||||
continue;
|
||||
for (m = 0; m < NUM_OFFSETS; m++) {
|
||||
ext = hdb_find_extension(&ep->entry,
|
||||
choice_HDB_extension_data_hist_keys);
|
||||
if (!ext)
|
||||
continue;
|
||||
hist_keys = &ext->data.u.hist_keys;
|
||||
for (p = 0; p < hist_keys->len; p++) {
|
||||
fprintf(stderr, "%s at %lu, %lu: history kvno %u\n",
|
||||
expected[i], k, m, hist_keys->val[p].kvno);
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "%s at %lu: kvno %u\n", expected[i], k,
|
||||
ep->entry.kvno);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#define SOME_TIME 1596318329
|
||||
#define SOME_BASE_KVNO 150
|
||||
#define SOME_EPOCH (SOME_TIME - (7 * 24 * 3600) - (SOME_TIME % (7 * 24 * 3600)))
|
||||
#define SOME_PERIOD 3600
|
||||
|
||||
#define CONF \
|
||||
"[hdb]\n" \
|
||||
"\tenable_virtual_hostbased_princs = true\n" \
|
||||
"\tvirtual_hostbased_princ_mindots = 1\n" \
|
||||
"\tvirtual_hostbased_princ_maxdots = 3\n" \
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
krb5_context context;
|
||||
size_t i;
|
||||
HDB *db;
|
||||
|
||||
setprogname(argv[0]);
|
||||
memset(e, 0, sizeof(e));
|
||||
ret = krb5_init_context(&context);
|
||||
if (ret == 0)
|
||||
ret = krb5_set_config(context, CONF);
|
||||
if (ret == 0)
|
||||
ret = krb5_plugin_register(context, PLUGIN_TYPE_DATA, "hdb_test_interface",
|
||||
&hdb_test);
|
||||
if (ret == 0)
|
||||
ret = hdb_create(context, &db, "test:mem");
|
||||
if (ret)
|
||||
krb5_err(context, 1, ret, "failed to setup HDB driver and test");
|
||||
|
||||
assert(db->enable_virtual_hostbased_princs);
|
||||
assert(db->virtual_hostbased_princ_ndots == 1);
|
||||
assert(db->virtual_hostbased_princ_maxdots == 3);
|
||||
|
||||
/* Setup key rotation metadata in a convenient way */
|
||||
/*
|
||||
* FIXME Reorder these two KRs to match how we store them to avoid
|
||||
* confusion. #0 should be future-most, #1 should past-post.
|
||||
*/
|
||||
krs[0].flags = krs[1].flags = int2KeyRotationFlags(0);
|
||||
krs[0].epoch = SOME_EPOCH - 20 * 24 * 3600;
|
||||
krs[0].period = SOME_PERIOD >> 1;
|
||||
krs[0].base_kvno = 150;
|
||||
krs[0].base_key_kvno = 1;
|
||||
krs[1].epoch = SOME_TIME;
|
||||
krs[1].period = SOME_PERIOD;
|
||||
krs[1].base_kvno = krs[0].base_kvno + 1 + (krs[1].epoch + (krs[0].period - 1) - krs[0].epoch) / krs[0].period;
|
||||
krs[1].base_key_kvno = 2;
|
||||
|
||||
make_namespace(context, db, WK_PREFIX "_/bar.example@BAR.EXAMPLE");
|
||||
|
||||
fetch_entries(context, db, 1, 0, 0);
|
||||
fetch_entries(context, db, 1, 1, 0);
|
||||
fetch_entries(context, db, 1, 2, 0);
|
||||
fetch_entries(context, db, 1, 3, 0);
|
||||
fetch_entries(context, db, 1, 4, 0); /* Just before newest KR */
|
||||
|
||||
fetch_entries(context, db, 0, 0, 0);
|
||||
fetch_entries(context, db, 0, 1, 0);
|
||||
fetch_entries(context, db, 0, 2, 0);
|
||||
fetch_entries(context, db, 0, 3, 0);
|
||||
fetch_entries(context, db, 0, 4, 1); /* Must fail: just before 1st KR */
|
||||
|
||||
/*
|
||||
* Check that for every virtual principal in `expected[]', all the keysets
|
||||
* with the same kvno, in all the entries fetched for different times,
|
||||
* match.
|
||||
*/
|
||||
check_kvnos(context);
|
||||
|
||||
#if 0
|
||||
/*
|
||||
* Check that for every virtual principal in `expected[]' we have the
|
||||
* expected key history.
|
||||
*/
|
||||
check_expected_kvnos(context);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* XXX Add various tests here, checking `e[]':
|
||||
*
|
||||
* - Extract all {principal, kvno, key} for all keys, current and
|
||||
* otherwise, then sort by {key, kvno, principal}, then check that the
|
||||
* only time we have matching keys is when the kvno and principal also
|
||||
* match.
|
||||
*/
|
||||
|
||||
print_em(context);
|
||||
|
||||
/*
|
||||
* XXX Test adding a third KR, a 4th KR, dropping KRs...
|
||||
*/
|
||||
|
||||
/* Cleanup */
|
||||
for (i = 0; ret == 0 && i < sizeof(e) / sizeof(e[0]); i++)
|
||||
hdb_free_entry(context, &e[i]);
|
||||
db->hdb_destroy(context, db);
|
||||
krb5_free_context(context);
|
||||
return 0;
|
||||
}
|
@@ -2,9 +2,13 @@
|
||||
|
||||
HEIMDAL_HDB_1.0 {
|
||||
global:
|
||||
encode_hdb_keyset;
|
||||
hdb_add_master_key;
|
||||
_hdb_fetch_kvno;
|
||||
_hdb_remove;
|
||||
_hdb_store;
|
||||
hdb_add_current_keys_to_history;
|
||||
hdb_add_history_key;
|
||||
hdb_add_history_keyset;
|
||||
hdb_add_master_key;
|
||||
hdb_change_kvno;
|
||||
hdb_check_db_format;
|
||||
hdb_clear_extension;
|
||||
@@ -20,21 +24,27 @@ HEIMDAL_HDB_1.0 {
|
||||
hdb_dbinfo_get_next;
|
||||
hdb_dbinfo_get_realm;
|
||||
hdb_default_db;
|
||||
hdb_derive_etypes;
|
||||
hdb_enctype2key;
|
||||
hdb_entry2string;
|
||||
hdb_entry2value;
|
||||
hdb_entry_add_key_rotation;
|
||||
hdb_entry_alias2value;
|
||||
hdb_entry_check_mandatory;
|
||||
hdb_entry_clear_password;
|
||||
hdb_entry_get_ConstrainedDelegACL;
|
||||
hdb_entry_get_aliases;
|
||||
hdb_entry_get_key_rotation;
|
||||
hdb_entry_get_krb5_config;
|
||||
hdb_entry_get_password;
|
||||
hdb_entry_get_pkinit_acl;
|
||||
hdb_entry_get_pkinit_cert;
|
||||
hdb_entry_get_pkinit_hash;
|
||||
hdb_entry_get_pw_change_time;
|
||||
hdb_entry_set_krb5_config;
|
||||
hdb_entry_set_password;
|
||||
hdb_entry_set_pw_change_time;
|
||||
hdb_fetch_kvno;
|
||||
hdb_find_extension;
|
||||
hdb_foreach;
|
||||
hdb_free_dbinfo;
|
||||
@@ -48,6 +58,7 @@ HEIMDAL_HDB_1.0 {
|
||||
hdb_get_dbinfo;
|
||||
hdb_get_instance;
|
||||
hdb_init_db;
|
||||
hdb_install_keyset;
|
||||
hdb_key2principal;
|
||||
hdb_kvno2keys;
|
||||
hdb_list_builtin;
|
||||
@@ -59,6 +70,8 @@ HEIMDAL_HDB_1.0 {
|
||||
hdb_prune_keys;
|
||||
hdb_prune_keys_kvno;
|
||||
hdb_read_master_key;
|
||||
hdb_remove_base_keys;
|
||||
hdb_remove_keys;
|
||||
hdb_replace_extension;
|
||||
hdb_seal_key;
|
||||
hdb_seal_key_mkey;
|
||||
@@ -71,7 +84,10 @@ HEIMDAL_HDB_1.0 {
|
||||
hdb_unseal_key;
|
||||
hdb_unseal_key_mkey;
|
||||
hdb_unseal_keys;
|
||||
hdb_unseal_keys_kvno;
|
||||
hdb_unseal_keys_mkey;
|
||||
hdb_validate_key_rotation;
|
||||
hdb_validate_key_rotations;
|
||||
hdb_value2entry;
|
||||
hdb_value2entry_alias;
|
||||
hdb_write_master_key;
|
||||
@@ -87,29 +103,42 @@ HEIMDAL_HDB_1.0 {
|
||||
hdb_get_kt_ops;
|
||||
|
||||
# some random bits needed for libkadm
|
||||
add_HDB_Ext_KeyRotation;
|
||||
add_HDB_Ext_KeySet;
|
||||
add_HDB_Ext_KeySet;
|
||||
add_Keys;
|
||||
add_Keys;
|
||||
asn1_HDBFlags_units;
|
||||
copy_Event;
|
||||
copy_HDB_EncTypeList;
|
||||
copy_HDB_extensions;
|
||||
copy_HDB_Ext_KeyRotation;
|
||||
copy_Key;
|
||||
copy_Keys;
|
||||
copy_Salt;
|
||||
decode_HDB_EncTypeList;
|
||||
decode_HDB_Ext_Aliases;
|
||||
decode_HDB_extension;
|
||||
decode_HDB_Ext_KeyRotation;
|
||||
decode_HDB_Ext_PKINIT_acl;
|
||||
decode_Key;
|
||||
decode_Keys;
|
||||
encode_HDB_EncTypeList;
|
||||
encode_HDB_Ext_Aliases;
|
||||
encode_HDB_extension;
|
||||
encode_HDB_Ext_KeyRotation;
|
||||
encode_HDB_Ext_PKINIT_acl;
|
||||
encode_hdb_keyset;
|
||||
encode_Key;
|
||||
encode_Keys;
|
||||
free_Event;
|
||||
free_HDB_EncTypeList;
|
||||
free_hdb_entry;
|
||||
free_HDB_Ext_Aliases;
|
||||
free_HDB_extension;
|
||||
free_HDB_extensions;
|
||||
free_HDB_Ext_KeyRotation;
|
||||
free_HDB_Ext_KeySet;
|
||||
free_HDB_Ext_PKINIT_acl;
|
||||
free_hdb_keyset;
|
||||
free_Key;
|
||||
@@ -117,14 +146,17 @@ HEIMDAL_HDB_1.0 {
|
||||
free_Salt;
|
||||
HDBFlags2int;
|
||||
int2HDBFlags;
|
||||
int2KeyRotationFlags;
|
||||
KeyRotationFlags2int;
|
||||
length_HDB_EncTypeList;
|
||||
length_HDB_Ext_Aliases;
|
||||
length_HDB_extension;
|
||||
length_HDB_Ext_KeyRotation;
|
||||
length_HDB_Ext_PKINIT_acl;
|
||||
length_Key;
|
||||
length_Keys;
|
||||
remove_HDB_Ext_KeyRotation;
|
||||
remove_Keys;
|
||||
add_Keys;
|
||||
add_HDB_Ext_KeySet;
|
||||
|
||||
local:
|
||||
*;
|
||||
|
Reference in New Issue
Block a user