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:
Nicolas Williams
2020-08-24 16:16:59 -05:00
parent ef06b94132
commit 5447b81fb1
50 changed files with 4017 additions and 530 deletions

2
.gitignore vendored
View File

@@ -241,7 +241,7 @@ tags
/lib/hdb/hdb-private.h
/lib/hdb/test_dbinfo
/lib/hdb/test_hdbkeys
/lib/hdb/test_hdbplugin
/lib/hdb/test_namespace
/lib/hdb/test_mkey
/lib/hx509/PKITS_data/
/lib/hx509/cert-ca.der

View File

@@ -34,6 +34,9 @@
#include "kadmin_locl.h"
#include "kadmin-commands.h"
/* No useful password policies for namespaces */
#define NSPOLICY "default"
/*
* fetch the default principal corresponding to `princ'
*/
@@ -308,3 +311,209 @@ add_new_key(struct add_options *opt, int argc, char **argv)
free(kstuple);
return ret != 0;
}
static krb5_error_code
kstuple2etypes(kadm5_principal_ent_rec *rec,
int *maskp,
size_t nkstuple,
krb5_key_salt_tuple *kstuple)
{
krb5_error_code ret;
HDB_EncTypeList etypes;
krb5_data buf;
size_t len, i;
etypes.len = 0;
if ((etypes.val = calloc(nkstuple, sizeof(etypes.val[0]))) == NULL)
return krb5_enomem(context);
for (i = 0; i < nkstuple; i++)
etypes.val[i] = kstuple[i].ks_enctype;
ASN1_MALLOC_ENCODE(HDB_EncTypeList, buf.data, buf.length,
&etypes, &len, ret);
if (ret == 0)
add_tl(rec, KRB5_TL_ETYPES, &buf);
free(etypes.val);
if (ret == 0)
(*maskp) |= KADM5_TL_DATA;
return ret;
}
/*
* Add the namespace `name' to the database.
* Prompt for all data not given by the input parameters.
*/
static krb5_error_code
add_one_namespace(const char *name,
size_t nkstuple,
krb5_key_salt_tuple *kstuple,
const char *max_ticket_life,
const char *max_renewable_life,
const char *key_rotation_epoch,
const char *key_rotation_period,
const char *attributes)
{
krb5_error_code ret;
kadm5_principal_ent_rec princ;
kadm5_principal_ent_rec *default_ent = NULL;
krb5_principal princ_ent = NULL;
int mask = 0;
int default_mask = 0;
HDB_extension ext;
krb5_data buf;
const char *comp0;
const char *comp1;
time_t kre;
char pwbuf[1024];
krb5_deltat krp;
if (!key_rotation_epoch) {
krb5_warnx(context, "key rotation epoch defaulted to \"now\"");
key_rotation_epoch = "now";
}
if (!key_rotation_period) {
krb5_warnx(context, "key rotation period defaulted to \"5d\"");
key_rotation_period = "5d";
}
if ((ret = str2time_t(key_rotation_epoch, &kre)) != 0) {
krb5_warn(context, ret, "invalid rotation epoch: %s",
key_rotation_epoch);
return ret;
}
if (ret == 0 && (ret = str2deltat(key_rotation_period, &krp)) != 0) {
krb5_warn(context, ret, "invalid rotation period: %s",
key_rotation_period);
return ret;
}
if (ret == 0) {
memset(&princ, 0, sizeof(princ));
princ.kvno = 1;
ret = krb5_parse_name(context, name, &princ_ent);
if (ret)
krb5_warn(context, ret, "krb5_parse_name");
}
if (ret != 0)
return ret;
/*
* Check that namespace has exactly one component, and prepend
* WELLKNOWN/HOSTBASED-NAMESPACE
*/
if (krb5_principal_get_num_comp(context, princ_ent) != 2
|| (comp0 = krb5_principal_get_comp_string(context, princ_ent, 0)) == 0
|| (comp1 = krb5_principal_get_comp_string(context, princ_ent, 1)) == 0
|| *comp0 == 0 || *comp1 == 0
|| strcmp(comp0, "krbtgt") == 0)
krb5_warn(context, ret = EINVAL,
"namespaces must have exactly two non-empty components "
"like host-base principal names");
if (ret == 0)
ret = krb5_principal_set_comp_string(context, princ_ent, 2, comp0);
if (ret == 0)
ret = krb5_principal_set_comp_string(context, princ_ent, 3, comp1);
if (ret == 0)
ret = krb5_principal_set_comp_string(context, princ_ent, 0,
"WELLKNOWN");
if (ret == 0)
ret = krb5_principal_set_comp_string(context, princ_ent, 1,
HDB_WK_NAMESPACE);
/* Set up initial key rotation extension */
if (ret == 0) {
KeyRotation kr;
size_t size;
/* Setup key rotation metadata in a convenient way */
kr.flags = int2KeyRotationFlags(0);
kr.base_key_kvno = 1;
/*
* Avoid kvnos 0/1/2 which don't normally appear in fully created
* principals.
*/
kr.base_kvno = 3;
/* XXX: Sanity check */
kr.epoch = kre;
kr.period = krp;
memset(&ext, 0, sizeof(ext));
ext.mandatory = FALSE;
ext.data.element = choice_HDB_extension_data_key_rotation;
ext.data.u.key_rotation.len = 1;
ext.data.u.key_rotation.val = &kr;
ASN1_MALLOC_ENCODE(HDB_extension, buf.data, buf.length,
&ext, &size, ret);
add_tl(&princ, KRB5_TL_EXTENSION, &buf);
mask |= KADM5_TL_DATA;
}
if (ret == 0) {
princ.principal = princ_ent;
mask |= KADM5_PRINCIPAL | KADM5_KVNO;
ret = set_entry(context, &princ, &mask,
max_ticket_life, max_renewable_life,
"never", "never", attributes, NSPOLICY);
}
if (ret == 0)
ret = edit_entry(&princ, &mask, default_ent, default_mask);
if (ret == 0)
ret = kstuple2etypes(&princ, &mask, nkstuple, kstuple);
/* XXX Shouldn't need a password for this */
random_password(pwbuf, sizeof(pwbuf));
ret = kadm5_create_principal_3(kadm_handle, &princ, mask,
nkstuple, kstuple, pwbuf);
if (ret)
krb5_warn(context, ret, "kadm5_create_principal_3");
kadm5_free_principal_ent(kadm_handle, &princ); /* frees princ_ent */
if (default_ent)
kadm5_free_principal_ent(kadm_handle, default_ent);
memset(pwbuf, 0, sizeof(pwbuf));
return ret;
}
int
add_new_namespace(struct add_namespace_options *opt, int argc, char **argv)
{
krb5_error_code ret = 0;
krb5_key_salt_tuple *kstuple = NULL;
const char *enctypes;
size_t i, nkstuple;
if (argc < 1) {
fprintf(stderr, "at least one namespace name required\n");
return 1;
}
enctypes = opt->enctypes_string;
if (enctypes == NULL || enctypes[0] == '\0')
enctypes = krb5_config_get_string(context, NULL, "libdefaults",
"supported_enctypes", NULL);
if (enctypes == NULL || enctypes[0] == '\0')
enctypes = "aes128-cts-hmac-sha1-96";
ret = krb5_string_to_keysalts2(context, enctypes, &nkstuple, &kstuple);
if (ret) {
fprintf(stderr, "enctype(s) unknown\n");
return ret;
}
for (i = 0; i < argc; i++) {
ret = add_one_namespace(argv[i], nkstuple, kstuple,
opt->max_ticket_life_string,
opt->max_renewable_life_string,
opt->key_rotation_epoch_string,
opt->key_rotation_period_string,
opt->attributes_string);
if (ret) {
krb5_warn(context, ret, "adding namespace %s", argv[i]);
break;
}
}
free(kstuple);
return ret != 0;
}

View File

@@ -53,3 +53,47 @@ del_entry(void *opt, int argc, char **argv)
}
return ret != 0;
}
static int
do_del_ns_entry(krb5_principal nsp, void *data)
{
krb5_error_code ret;
krb5_principal p = NULL;
const char *comp0 = krb5_principal_get_comp_string(context, nsp, 0);
const char *comp1 = krb5_principal_get_comp_string(context, nsp, 1);
char *unsp = NULL;
if (krb5_principal_get_num_comp(context, nsp) != 2) {
(void) krb5_unparse_name(context, nsp, &unsp);
krb5_warn(context, ret = EINVAL, "Not a valid namespace name %s",
unsp ? unsp : "<Out of memory>");
return EINVAL;
}
ret = krb5_make_principal(context, &p,
krb5_principal_get_realm(context, nsp),
"WELLKNOWN", HDB_WK_NAMESPACE, NULL);
if (ret == 0)
ret = krb5_principal_set_comp_string(context, p, 2, comp0);
if (ret == 0)
ret = krb5_principal_set_comp_string(context, p, 3, comp1);
if (ret == 0)
ret = kadm5_delete_principal(kadm_handle, p);
krb5_free_principal(context, p);
free(unsp);
return ret;
}
int
del_namespace(void *opt, int argc, char **argv)
{
int i;
krb5_error_code ret = 0;
for(i = 0; i < argc; i++) {
ret = foreach_principal(argv[i], do_del_ns_entry, "del_ns", NULL);
if (ret)
break;
}
return ret != 0;
}

View File

@@ -61,6 +61,7 @@ static struct field_name {
{ "fail_auth_count", KADM5_FAIL_AUTH_COUNT, 0, 0, "Fail count", "Failed login count", RTBL_ALIGN_RIGHT },
{ "policy", KADM5_POLICY, 0, 0, "Policy", "Policy", 0 },
{ "keytypes", KADM5_KEY_DATA, 0, KADM5_PRINCIPAL | KADM5_KVNO, "Keytypes", "Keytypes", 0 },
{ "server-keytypes", KADM5_TL_DATA, KRB5_TL_ETYPES, 0, "Server keytypes", "Supported keytypes (servers)", 0 },
{ "password", KADM5_TL_DATA, KRB5_TL_PASSWORD, KADM5_KEY_DATA, "Password", "Password", 0 },
{ "pkinit-acl", KADM5_TL_DATA, KRB5_TL_PKINIT_ACL, 0, "PK-INIT ACL", "PK-INIT ACL", 0 },
{ "aliases", KADM5_TL_DATA, KRB5_TL_ALIASES, 0, "Aliases", "Aliases", 0 },
@@ -81,6 +82,8 @@ struct get_entry_data {
uint32_t mask;
uint32_t extra_mask;
struct field_info *chead, **ctail;
const char *krb5_config_fname;
uint32_t n;
};
static int
@@ -174,9 +177,59 @@ format_keytype(krb5_key_data *k, krb5_salt *def_salt, char *buf, size_t buf_len)
free(s);
}
static int
is_special_file(const char *fname)
{
#ifdef WIN32
if (strcasecmp(fname, "con") == 0 || strcasecmp(fname, "nul") == 0 ||
strcasecmp(fname, "aux") == 0 || strcasecmp(fname, "prn") == 0)
return 1;
if ((strncasecmp(fname, "com", sizeof("com") - 1) == 0 ||
strncasecmp(fname, "lpt", sizeof("lpt") - 1) == 0) &&
fname[sizeof("lpt")] >= '0' && fname[sizeof("lpt")] <= '9' &&
fname[sizeof("lpt") + 1] == '\0')
return 1;
#else
if (strncmp(fname, "/dev/", sizeof("/dev/") - 1) == 0)
return 1;
#endif
return 0;
}
static char *
write_krb5_config(krb5_tl_data *tl,
const char *fn,
uint32_t i)
{
char *s = NULL;
FILE *f = NULL;
if (fn == NULL)
return NULL;
if (i == 0 || is_special_file(fn))
s = strdup(fn);
else if (asprintf(&s, "%s-%u", fn, i) == -1)
s = NULL;
if (s == NULL)
krb5_err(context, 1, errno, "Out of memory");
/* rk_dumpdata() doesn't allow error checking :( */
if ((f = fopen(s, "w")) &&
fwrite(tl->tl_data_contents, tl->tl_data_length, 1, f) != 1)
krb5_warn(context, errno, "Could not write to %s", fn);
if (f && fclose(f))
krb5_warn(context, errno, "Could not write to %s", fn);
return s;
}
static void
format_field(kadm5_principal_ent_t princ, unsigned int field,
unsigned int subfield, char *buf, size_t buf_len, int condensed)
format_field(struct get_entry_data *data,
kadm5_principal_ent_t princ,
unsigned int field,
unsigned int subfield,
char *buf,
size_t buf_len,
int condensed)
{
switch(field) {
case KADM5_PRINCIPAL:
@@ -276,6 +329,31 @@ format_field(kadm5_principal_ent_t princ, unsigned int field,
(int)tl->tl_data_length,
(const char *)tl->tl_data_contents);
break;
case KRB5_TL_ETYPES: {
HDB_EncTypeList etypes;
size_t i, size;
char *str;
int ret;
ret = decode_HDB_EncTypeList(tl->tl_data_contents,
tl->tl_data_length,
&etypes, &size);
if (ret) {
snprintf(buf, buf_len, "failed to decode server etypes");
break;
}
buf[0] = '\0';
for (i = 0; i < etypes.len; i++) {
ret = krb5_enctype_to_string(context, etypes.val[i], &str);
if (ret == 0) {
if (i)
strlcat(buf, ",", buf_len);
strlcat(buf, str, buf_len);
}
}
free_HDB_EncTypeList(&etypes);
break;
}
case KRB5_TL_PKINIT_ACL: {
HDB_Ext_PKINIT_acl acl;
size_t size;
@@ -309,6 +387,16 @@ format_field(kadm5_principal_ent_t princ, unsigned int field,
free_HDB_Ext_PKINIT_acl(&acl);
break;
}
case KRB5_TL_KRB5_CONFIG: {
char *fname;
fname = write_krb5_config(tl, data->krb5_config_fname, data->n);
if (fname) {
strlcat(buf, fname, buf_len);
free(fname);
}
break;
}
case KRB5_TL_ALIASES: {
HDB_Ext_Aliases alias;
size_t size;
@@ -356,7 +444,8 @@ print_entry_short(struct get_entry_data *data, kadm5_principal_ent_t princ)
struct field_info *f;
for(f = data->chead; f != NULL; f = f->next) {
format_field(princ, f->ff->fieldvalue, f->ff->subvalue, buf, sizeof(buf), 1);
format_field(data, princ, f->ff->fieldvalue, f->ff->subvalue, buf,
sizeof(buf), 1);
rtbl_add_column_entry_by_id(data->table, f->ff->fieldvalue, buf);
}
}
@@ -374,7 +463,8 @@ print_entry_long(struct get_entry_data *data, kadm5_principal_ent_t princ)
width = w;
}
for(f = data->chead; f != NULL; f = f->next) {
format_field(princ, f->ff->fieldvalue, f->ff->subvalue, buf, sizeof(buf), 0);
format_field(data, princ, f->ff->fieldvalue, f->ff->subvalue, buf,
sizeof(buf), 0);
printf("%*s: %s\n", width, f->header ? f->header : f->ff->def_longheader, buf);
}
printf("\n");
@@ -391,13 +481,13 @@ do_get_entry(krb5_principal principal, void *data)
ret = kadm5_get_principal(kadm_handle, principal,
&princ,
e->mask | e->extra_mask);
if(ret)
return ret;
else {
(e->format)(e, &princ);
kadm5_free_principal_ent(kadm_handle, &princ);
if (ret == 0) {
(e->format)(e, &princ);
kadm5_free_principal_ent(kadm_handle, &princ);
}
return 0;
e->n++;
return ret;
}
static void
@@ -467,7 +557,7 @@ listit(const char *funcname, int argc, char **argv)
}
#define DEFAULT_COLUMNS_SHORT "principal,princ_expire_time,pw_expiration,last_pwd_change,max_life,max_rlife"
#define DEFAULT_COLUMNS_LONG "principal,princ_expire_time,pw_expiration,last_pwd_change,max_life,max_rlife,kvno,mkvno,last_success,last_failed,fail_auth_count,mod_time,mod_name,attributes,keytypes,pkinit-acl,aliases"
#define DEFAULT_COLUMNS_LONG "principal,princ_expire_time,pw_expiration,last_pwd_change,max_life,max_rlife,kvno,mkvno,last_success,last_failed,fail_auth_count,mod_time,mod_name,attributes,server-keytypes,keytypes,pkinit-acl,aliases"
static int
getit(struct get_options *opt, const char *name, int argc, char **argv)
@@ -493,6 +583,8 @@ getit(struct get_options *opt, const char *name, int argc, char **argv)
data.ctail = &data.chead;
data.mask = 0;
data.extra_mask = 0;
data.krb5_config_fname = opt->krb5_config_file_string;
data.n = 0;
if(opt->short_flag) {
data.table = rtbl_create();

View File

@@ -215,6 +215,128 @@ command = {
min_args = "1"
help = "Adds a principal to the database."
}
command = {
name = "add_namespace"
name = "add_ns"
function = "add_new_namespace"
option = {
long = "enctypes"
short = "e"
type = "string"
help = "encryption type(s)"
}
option = {
long = "max-ticket-life"
type = "string"
argument ="lifetime"
help = "max ticket lifetime"
}
option = {
long = "max-renewable-life"
type = "string"
argument = "lifetime"
help = "max renewable life"
}
option = {
long = "key-rotation-epoch"
type = "string"
argument = "time"
help = "absolute start time (or +timespec for relative to now with default unit of month)"
}
option = {
long = "key-rotation-period"
type = "string"
argument = "time"
help = "automatic key rotation period"
}
option = {
long = "attributes"
type = "string"
argument = "attributes"
help = "principal attributes"
}
argument = "principal..."
min_args = "1"
help = "Adds a namespace of virtual principals with derived keys to the database."
}
command = {
name = "modify_namespace"
name = "mod_ns"
function = "modify_namespace"
option = {
long = "enctypes"
short = "e"
type = "strings"
help = "encryption type(s)"
}
option = {
long = "max-ticket-life"
type = "string"
argument ="lifetime"
help = "max ticket lifetime"
}
option = {
long = "max-renewable-life"
type = "string"
argument = "lifetime"
help = "max renewable life"
}
option = {
long = "attributes"
type = "string"
argument = "attributes"
help = "principal attributes"
}
option = {
long = "krb5-config-file"
short = "C"
type = "string"
help = "filename to save the principal's krb5.confg in"
}
argument = "principal..."
min_args = "1"
help = "Modifies a namespace of virtual principals with derived keys to the database."
}
command = {
name = "modify_namespace_key_rotation"
name = "mod_ns_kr"
function = "modify_ns_kr"
option = {
long = "force"
short = "f"
type = "flag"
help = "change schedule even if it would revoke some extant tickets"
}
option = {
long = "keep-base-key"
short = "k"
type = "flag"
help = "keep current base key for new key rotation schedule"
}
option = {
long = "revoke-old"
short = "r"
type = "string"
argument = "time"
help = "delete base keys older than this to revoke old tickets"
}
option = {
long = "new-key-rotation-epoch"
type = "string"
argument = "time"
help = "new start time relative to now"
}
option = {
long = "new-key-rotation-period"
type = "string"
argument = "time"
help = "new automatic key rotation period"
}
argument = "principal..."
min_args = "1"
max_args = "1"
help = "Adds or changes new key rotation schedule for the given namespace."
}
command = {
name = "passwd"
name = "cpw"
@@ -276,6 +398,14 @@ command = {
min_args = "1"
help = "Deletes all principals matching the expressions."
}
command = {
name = "delete_namespace"
name = "del_ns"
function = "del_namespace"
argument = "principal..."
min_args = "1"
help = "Deletes the given virtual principal namespaces"
}
command = {
name = "del_enctype"
argument = "principal enctype..."
@@ -364,6 +494,12 @@ command = {
type = "string"
help = "columns to print for short output"
}
option = {
long = "krb5-config-file"
short = "C"
type = "string"
help = "filename to save the principal's krb5.confg in"
}
argument = "principal..."
min_args = "1"
help = "Shows information about principals matching the expressions."
@@ -378,6 +514,7 @@ command = {
}
command = {
name = "modify"
name = "mod"
function = "mod_entry"
option = {
long = "max-ticket-life"
@@ -440,6 +577,13 @@ command = {
argument = "policy"
help = "policy name"
}
option = {
long = "service-enctypes"
short = "e"
type = "strings"
argument = "enctype"
help = "set enctypes supported by service"
}
option = {
long = "hist-kvno-diff-clnt"
type = "integer"
@@ -454,6 +598,12 @@ command = {
help = "historic keys allowed for service"
default = "-1"
}
option = {
long = "krb5-config-file"
short = "C"
type = "string"
help = "krb5.conf to save in principal record"
}
argument = "principal"
min_args = "1"
max_args = "1"
@@ -507,6 +657,10 @@ command = {
type = "string"
help = "columns to print for short output"
}
option = {
long = "krb5-config-file"
type = "string"
}
argument = "principal..."
min_args = "1"
help = "Lists principals in a terse format. Equivalent to \"get -t\"."

View File

@@ -140,6 +140,55 @@ The only policy supported by Heimdal servers is
.Ql default .
.Ed
.Pp
.Nm add_namespace
.Ar Fl Fl key-rotation-epoch= Ns Ar time
.Ar Fl Fl key-rotation-period= Ns Ar time
.Op Fl Fl enctypes= Ns Ar string
.Op Fl Fl max-ticket-life= Ns Ar lifetime
.Op Fl Fl max-renewable-life= Ns Ar lifetime
.Op Fl Fl attributes= Ns Ar attributes
.Ar principal...
.Bd -ragged -offset indent
Adds a new namespace of virtual host-based or domain-based
principals to the database, whose keys will be automatically
derived from base keys stored in the namespace record, and which
keys will be rotated automatically.
The namespace names should look like
.Ar hostname@REALM
and these will match all host-based or domain-based service names
where hostname component of such a principal ends in the labels
of the hostname in the namespace name.
.Pp
For example,
.Ar bar.baz.example@BAZ.EXAMPLE
will match
.Ar host/foo.bar.baz.example@BAZ.EXAMPLE
but not
.Ar host/foobar.baz.example@BAZ.EXAMPLE .
.Pp
Note well that services are expected to
.Ar ext_keytab
or otherwise re-fetch their keytabs at least as often as one
quarter of the key rotation period, otherwise they risk not
having keys they need to decrypt tickets with.
.Pp
The epoch must be given as either an absolute time,
.Ar "now",
or as
.Ar "+<N>[<unit>]"
where
.Ar N
is a natural and
.Ar unit
is one "s", "m", "h", "day", "week", "month", defaulting to
"month".
The default key rotation period is
.Ar 7d .
The default enctypes is as for the
.Nm add
command.
.Ed
.Pp
.Nm add_enctype
.Op Fl r | Fl Fl random-key
.Ar principal enctypes...
@@ -213,6 +262,7 @@ behavior is the default if none of these are given.
.Op Fl s | Fl Fl short
.Op Fl t | Fl Fl terse
.Op Fl o Ar string | Fl Fl column-info= Ns Ar string
.Op Fl C Ar path | Fl Fl krb5-config-file= Ns Ar path
.Ar principal...
.Bd -ragged -offset indent
Lists the matching principals, short prints the result as a table,
@@ -229,6 +279,16 @@ The default terse output format is similar to
.Fl s o Ar principal= ,
just printing the names of matched principals.
.Pp
If
.Fl C
or
.Fl Fl krb5-config-file
is given and the principal has krb5 config file contents saved
in its HDB entry, then that will be saved in the given file.
Note that if multiple principals are requested, then the second,
third, and so on will have -1, -2, and so on appended to the
given filename unless the given filename is a device name.
.Pp
Possible column names include:
.Li principal ,
.Li princ_expire_time ,
@@ -260,15 +320,18 @@ and
.Op Fl Fl pw-expiration-time= Ns Ar time
.Op Fl Fl kvno= Ns Ar number
.Op Fl Fl policy= Ns Ar policy-name
.Op Fl C Ar path | Fl Fl krb5-config-file= Ns Ar path
.Ar principal...
.Bd -ragged -offset indent
Modifies certain attributes of a principal. If run without command
line options, you will be prompted. With command line options, it will
only change the ones specified.
.Pp
Only policy supported by Heimdal is
The only policy supported by Heimdal is
.Ql default .
.Pp
If a krb5 config file is given, it will be saved in the entry.
.Pp
Possible attributes are:
.Li new-princ ,
.Li support-desmd5 ,

View File

@@ -156,5 +156,9 @@ kadmind_loop (krb5_context, krb5_keytab, int);
int
handle_mit(krb5_context, void *, size_t, int);
/* mod.c */
void
add_tl(kadm5_principal_ent_rec *, int, krb5_data *);
#endif /* __ADMIN_LOCL_H__ */

View File

@@ -34,7 +34,7 @@
#include "kadmin_locl.h"
#include "kadmin-commands.h"
static void
void
add_tl(kadm5_principal_ent_rec *princ, int type, krb5_data *data)
{
krb5_tl_data *tl, **ptl;
@@ -185,6 +185,43 @@ add_pkinit_acl(krb5_context contextp, kadm5_principal_ent_rec *princ,
add_tl(princ, KRB5_TL_EXTENSION, &buf);
}
static krb5_error_code
add_etypes(krb5_context contextp,
kadm5_principal_ent_rec *princ,
struct getarg_strings *strings)
{
krb5_error_code ret = 0;
HDB_EncTypeList etypes;
krb5_data buf;
size_t i, size;
etypes.len = strings->num_strings;
if ((etypes.val = calloc(strings->num_strings,
sizeof(etypes.val[0]))) == NULL)
krb5_err(contextp, 1, ret, "Out of memory");
for (i = 0; i < strings->num_strings; i++) {
krb5_enctype etype;
ret = krb5_string_to_enctype(contextp, strings->strings[i], &etype);
if (ret) {
krb5_warn(contextp, ret, "Could not parse enctype %s",
strings->strings[i]);
return ret;
}
etypes.val[i] = etype;
}
if (ret == 0) {
ASN1_MALLOC_ENCODE(HDB_EncTypeList, buf.data, buf.length,
&etypes, &size, ret);
}
if (ret || buf.length != size)
abort();
add_tl(princ, KRB5_TL_ETYPES, &buf);
return 0;
}
static void
add_kvno_diff(krb5_context contextp, kadm5_principal_ent_rec *princ,
int is_svc_diff, krb5_kvno kvno_diff)
@@ -216,6 +253,35 @@ add_kvno_diff(krb5_context contextp, kadm5_principal_ent_rec *princ,
add_tl(princ, KRB5_TL_EXTENSION, &buf);
}
static void
add_krb5_config(kadm5_principal_ent_rec *princ, const char *fname)
{
HDB_extension ext;
krb5_data buf;
size_t size;
int ret;
memset(&ext, 0, sizeof(ext));
ext.mandatory = FALSE;
ext.data.element = choice_HDB_extension_data_krb5_config;
if ((ret = rk_undumpdata(fname,
&ext.data.u.krb5_config.data,
&ext.data.u.krb5_config.length))) {
krb5_warn(context, ret, "Could not read %s", fname);
return;
}
ASN1_MALLOC_ENCODE(HDB_extension, buf.data, buf.length,
&ext, &size, ret);
free_HDB_extension(&ext);
if (ret)
abort();
if (buf.length != size)
abort();
add_tl(princ, KRB5_TL_EXTENSION, &buf);
}
static int
do_mod_entry(krb5_principal principal, void *data)
{
@@ -240,9 +306,11 @@ do_mod_entry(krb5_principal principal, void *data)
e->attributes_string ||
e->policy_string ||
e->kvno_integer != -1 ||
e->service_enctypes_strings.num_strings ||
e->constrained_delegation_strings.num_strings ||
e->alias_strings.num_strings ||
e->pkinit_acl_strings.num_strings ||
e->krb5_config_file_string ||
e->hist_kvno_diff_clnt_integer != -1 ||
e->hist_kvno_diff_svc_integer != -1) {
ret = set_entry(context, &princ, &mask,
@@ -269,6 +337,10 @@ do_mod_entry(krb5_principal principal, void *data)
add_pkinit_acl(context, &princ, &e->pkinit_acl_strings);
mask |= KADM5_TL_DATA;
}
if (e->service_enctypes_strings.num_strings) {
ret = add_etypes(context, &princ, &e->service_enctypes_strings);
mask |= KADM5_TL_DATA;
}
if (e->hist_kvno_diff_clnt_integer != -1) {
add_kvno_diff(context, &princ, 0, e->hist_kvno_diff_clnt_integer);
mask |= KADM5_TL_DATA;
@@ -277,6 +349,10 @@ do_mod_entry(krb5_principal principal, void *data)
add_kvno_diff(context, &princ, 1, e->hist_kvno_diff_svc_integer);
mask |= KADM5_TL_DATA;
}
if (e->krb5_config_file_string) {
add_krb5_config(&princ, e->krb5_config_file_string);
mask |= KADM5_TL_DATA;
}
} else
ret = edit_entry(&princ, &mask, NULL, 0);
if(ret == 0) {
@@ -303,3 +379,280 @@ mod_entry(struct modify_options *opt, int argc, char **argv)
return ret != 0;
}
static int
do_mod_ns_entry(krb5_principal principal, void *data)
{
krb5_error_code ret;
kadm5_principal_ent_rec princ;
int mask = 0;
struct modify_namespace_options *e = data;
memset (&princ, 0, sizeof(princ));
ret = kadm5_get_principal(kadm_handle, principal, &princ,
KADM5_PRINCIPAL | KADM5_ATTRIBUTES |
KADM5_MAX_LIFE | KADM5_MAX_RLIFE |
KADM5_PRINC_EXPIRE_TIME |
KADM5_PW_EXPIRATION);
if(ret)
return ret;
if(e->max_ticket_life_string ||
e->max_renewable_life_string ||
e->attributes_string ||
e->enctypes_strings.num_strings ||
e->krb5_config_file_string) {
ret = set_entry(context, &princ, &mask, e->max_ticket_life_string,
e->max_renewable_life_string, NULL, NULL,
e->attributes_string, NULL);
if (e->enctypes_strings.num_strings) {
ret = add_etypes(context, &princ, &e->enctypes_strings);
mask |= KADM5_TL_DATA;
}
if (e->krb5_config_file_string) {
add_krb5_config(&princ, e->krb5_config_file_string);
mask |= KADM5_TL_DATA;
}
} else
ret = edit_entry(&princ, &mask, NULL, 0);
if(ret == 0) {
ret = kadm5_modify_principal(kadm_handle, &princ, mask);
if(ret)
krb5_warn(context, ret, "kadm5_modify_principal");
}
kadm5_free_principal_ent(kadm_handle, &princ);
return ret;
}
int
modify_namespace(struct modify_namespace_options *opt, int argc, char **argv)
{
krb5_error_code ret = 0;
int i;
for(i = 0; i < argc; i++) {
ret = foreach_principal(argv[i], do_mod_ns_entry, "mod_ns", opt);
if (ret)
break;
}
return ret != 0;
}
#if 0
struct modify_namespace_key_rotation_options {
int force_flag;
int keep_base_key_flag;
char* revoke_old_string;
char* new_key_rotation_epoch_string;
char* new_key_rotation_period_string;
};
#endif
static int
princ2kstuple(kadm5_principal_ent_rec *princ,
unsigned int kvno,
krb5_key_salt_tuple **kstuple,
size_t *nkstuple)
{
krb5_error_code ret = 0;
HDB_EncTypeList etypes;
krb5_tl_data *tl;
size_t i;
*kstuple = 0;
*nkstuple = 0;
etypes.len = 0;
etypes.val = 0;
for (tl = princ->tl_data; tl; tl = tl->tl_data_next) {
if (tl->tl_data_type != KRB5_TL_ETYPES || tl->tl_data_length < 0)
continue;
ret = decode_HDB_EncTypeList(tl->tl_data_contents, tl->tl_data_length,
&etypes, NULL);
if (ret)
break;
*nkstuple = etypes.len;
*kstuple = ecalloc(etypes.len, sizeof(kstuple[0][0]));
for (i = 0; i < etypes.len; i++) {
(*kstuple)[i].ks_enctype = etypes.val[i];
(*kstuple)[i].ks_salttype = 0;
}
return 0;
}
if (princ->n_key_data > 0) {
*kstuple = ecalloc(1, sizeof(kstuple[0][0]));
*nkstuple = 1;
for (i = 0; i < princ->n_key_data; i++) {
if (princ->key_data->key_data_kvno == kvno) {
(*kstuple)[0].ks_enctype = princ->key_data->key_data_type[0];
(*kstuple)[0].ks_salttype = princ->key_data->key_data_type[1];
return 0;
}
}
}
krb5_warnx(context, "Could not determine what enctypes to generate "
"keys for; recreate namespace?");
return EINVAL;
}
static int
randkey_kr(kadm5_principal_ent_rec *princ,
unsigned int old_kvno,
unsigned int kvno)
{
krb5_key_salt_tuple *kstuple = 0;
krb5_error_code ret = 0;
size_t nkstuple = 0;
/*
* We might be using kadm5clnt, so we'll use kadm5_randkey_principal_3(),
* which will generate new keys on the server side. This allows a race,
* but it will be detected by the key rotation update checks in lib/kadm5
* and lib/hdb.
*/
ret = princ2kstuple(princ, old_kvno, &kstuple, &nkstuple);
if (ret == 0)
ret = kadm5_randkey_principal_3(kadm_handle, princ->principal, 1,
nkstuple, kstuple, NULL, NULL);
free(kstuple);
return ret;
}
static int
do_mod_ns_kr(krb5_principal principal, void *data)
{
krb5_error_code ret;
kadm5_principal_ent_rec princ;
struct modify_namespace_key_rotation_options *e = data;
HDB_Ext_KeyRotation existing;
HDB_Ext_KeyRotation new_kr;
HDB_extension ext;
KeyRotation new_krs[3];
krb5_tl_data *tl;
krb5_data d;
time_t now = time(NULL);
size_t size;
int freeit = 0;
d.data = 0;
d.length = 0;
new_kr.len = 0;
new_kr.val = new_krs;
ext.mandatory = 0;
ext.data.element = choice_HDB_extension_data_key_rotation;
ext.data.u.key_rotation.len = 0;
ext.data.u.key_rotation.val = 0;
existing.len = 0;
existing.val = 0;
memset(&new_krs, 0, sizeof(new_krs));
memset(&princ, 0, sizeof(princ));
if (e->force_flag || e->revoke_old_string) {
krb5_warnx(context, "--force and --revoke-old not implemented yet");
return ENOTSUP;
}
ret = kadm5_get_principal(kadm_handle, principal, &princ,
KADM5_PRINCIPAL | KADM5_KVNO |
KADM5_KEY_DATA | KADM5_TL_DATA);
if (ret == 0) {
freeit = 1;
for (tl = princ.tl_data; tl; tl = tl->tl_data_next) {
if (tl->tl_data_type != KRB5_TL_KRB5_CONFIG)
continue;
ret = decode_HDB_Ext_KeyRotation(tl->tl_data_contents,
tl->tl_data_length, &existing, NULL);
if (ret) {
krb5_warn(context, ret, "unable to decode existing key "
"rotation schedule");
kadm5_free_principal_ent(kadm_handle, &princ);
return ret;
}
}
if (!existing.len) {
krb5_warnx(context, "no key rotation schedule; "
"re-create namespace?");
kadm5_free_principal_ent(kadm_handle, &princ);
return EINVAL;
}
}
if (ret) {
krb5_warn(context, ret, "No such namespace");
kadm5_free_principal_ent(kadm_handle, &princ);
return ret;
}
if (existing.len > 1)
new_kr.val[1] = existing.val[0];
if (existing.len > 2)
new_kr.val[2] = existing.val[1];
new_kr.val[0].flags = existing.val[0].flags;
new_kr.val[0].base_kvno = princ.kvno + 2; /* XXX Compute better */
new_kr.val[0].base_key_kvno = existing.val[0].base_key_kvno + 1;
if (e->new_key_rotation_epoch_string) {
if ((ret = str2time_t(e->new_key_rotation_epoch_string,
&new_kr.val[0].epoch)))
krb5_warn(context, ret, "Invalid epoch specification: %s",
e->new_key_rotation_epoch_string);
} else {
new_kr.val[0].epoch = existing.val[0].epoch +
existing.val[0].period * (princ.kvno - new_kr.val[0].base_kvno);
}
if (ret == 0 && e->new_key_rotation_period_string) {
time_t t;
if ((ret = str2time_t(e->new_key_rotation_period_string, &t)))
krb5_warn(context, ret, "Invalid period specification: %s",
e->new_key_rotation_period_string);
else
new_kr.val[0].period = t;
} else {
new_kr.val[0].period = existing.val[0].period +
existing.val[0].period * (princ.kvno - new_kr.val[0].base_kvno);
}
if (new_kr.val[0].epoch < now) {
krb5_warnx(context, "New epoch cannot be in the past");
ret = EINVAL;
}
if (new_kr.val[0].epoch < 30) {
krb5_warnx(context, "New period cannot be less than 30s");
ret = EINVAL;
}
if (ret == 0)
ret = randkey_kr(&princ, princ.kvno, new_kr.val[0].base_key_kvno);
ext.data.u.key_rotation = new_kr;
if (ret == 0)
ASN1_MALLOC_ENCODE(HDB_extension, d.data, d.length,
&ext, &size, ret);
if (ret == 0)
add_tl(&princ, KRB5_TL_EXTENSION, &d);
if (ret == 0) {
ret = kadm5_modify_principal(kadm_handle, &princ,
KADM5_PRINCIPAL | KADM5_TL_DATA);
if (ret)
krb5_warn(context, ret, "Could not update namespace");
}
krb5_data_free(&d);
free_HDB_Ext_KeyRotation(&existing);
if (freeit)
kadm5_free_principal_ent(kadm_handle, &princ);
return ret;
}
int
modify_ns_kr(struct modify_namespace_key_rotation_options *opt,
int argc,
char **argv)
{
krb5_error_code ret = 0;
int i;
for(i = 0; i < argc; i++) {
ret = foreach_principal(argv[i], do_mod_ns_kr, "mod_ns", opt);
if (ret)
break;
}
return ret != 0;
return 0;
}

View File

@@ -104,9 +104,6 @@ krb5_kdc_get_config(krb5_context context, krb5_kdc_configuration **config)
c->db = NULL;
c->num_db = 0;
c->logf = NULL;
c->enable_derived_keys = FALSE;
c->derived_keys_ndots = 2;
c->derived_keys_maxdots = -1;
c->num_kdc_processes =
krb5_config_get_int_default(context, NULL, c->num_kdc_processes,
@@ -286,18 +283,6 @@ krb5_kdc_get_config(krb5_context context, krb5_kdc_configuration **config)
0,
"kdc", "pkinit_dh_min_bits", NULL);
c->enable_derived_keys =
krb5_config_get_bool_default(context, NULL, c->enable_derived_keys,
"kdc", "enable_derived_keys", NULL);
c->derived_keys_ndots =
krb5_config_get_int_default(context, NULL, c->derived_keys_ndots,
"kdc", "derived_keys_ndots", NULL);
c->derived_keys_maxdots =
krb5_config_get_int_default(context, NULL, c->derived_keys_maxdots,
"kdc", "derived_keys_maxdots", NULL);
*config = c;
return 0;

View File

@@ -95,10 +95,6 @@ typedef struct krb5_kdc_configuration {
int enable_kx509;
krb5_boolean enable_derived_keys;
int derived_keys_ndots;
int derived_keys_maxdots;
const char *app;
} krb5_kdc_configuration;

View File

@@ -140,6 +140,16 @@ _kdc_is_anon_request(const KDC_REQ *req)
* return the first appropriate key of `princ' in `ret_key'. Look for
* all the etypes in (`etypes', `len'), stopping as soon as we find
* one, but preferring one that has default salt.
*
* XXX This function does way way too much. Split it up!
*
* XXX `etypes' and `len' are always `b->etype.val' and `b->etype.len' -- the
* etype list from the KDC-REQ-BODY, which is available here as
* `r->req->req_body', so we could just stop having it passed in.
*
* XXX Picking an enctype(s) for PA-ETYPE-INFO* is rather different than
* picking an enctype for a ticket's session key. The former is what we do
* here when `(flags & KFE_IS_PREAUTH)', the latter otherwise.
*/
krb5_error_code
@@ -159,7 +169,7 @@ _kdc_find_etype(astgs_request_t r, uint32_t flags,
krb5_enctype enctype = (krb5_enctype)ETYPE_NULL;
const krb5_enctype *p;
Key *key = NULL;
int i, k;
size_t i, k, m;
if (flags & KFE_USE_CLIENT) {
princ = r->client;
@@ -181,8 +191,22 @@ _kdc_find_etype(astgs_request_t r, uint32_t flags,
ret = KRB5KDC_ERR_ETYPE_NOSUPP;
if (use_strongest_session_key) {
/*
* Pick an enctype that is in the intersection of:
*
* - permitted_enctypes (local policy)
* - requested enctypes (KDC-REQ-BODY's etype list)
* - the client's long-term keys' enctypes
* OR
* the server's configured etype list
*
* There are two sub-cases:
*
* - use local enctype preference (local policy)
* - use the client's preference list
*/
if (use_strongest_session_key) {
/*
* Pick the strongest key that the KDC, target service, and
* client all support, using the local cryptosystem enctype
@@ -191,7 +215,7 @@ _kdc_find_etype(astgs_request_t r, uint32_t flags,
* This is not what RFC4120 says to do, but it encourages
* adoption of stronger enctypes. This doesn't play well with
* clients that have multiple Kerberos client implementations
* available with different supported enctype lists.
* with different supported enctype lists sharing the same ccache.
*/
/* drive the search with local supported enctypes list */
@@ -209,20 +233,51 @@ _kdc_find_etype(astgs_request_t r, uint32_t flags,
if (p[i] != etypes[k])
continue;
if (!is_preauth && (flags & KFE_USE_CLIENT)) {
/*
* It suffices that the client says it supports this
* enctype in its KDC-REQ-BODY's etype list, which is what
* `etypes' is here.
*/
ret = 0;
break;
}
/* check target princ support */
key = NULL;
while (hdb_next_enctype2key(context, &princ->entry, NULL,
p[i], &key) == 0) {
if (key->key.keyvalue.length == 0) {
ret = KRB5KDC_ERR_NULL_KEY;
continue;
}
enctype = p[i];
ret = 0;
if (is_preauth && ret_key != NULL &&
!is_default_salt_p(&def_salt, key))
continue;
}
if (!(flags & KFE_USE_CLIENT) && princ->entry.etypes) {
/*
* Use the etypes list from the server's HDB entry instead
* of deriving it from its long-term keys. This allows an
* entry to have just one long-term key but record support
* for multiple enctypes.
*/
for (m = 0; m < princ->entry.etypes->len; m++) {
if (p[i] == princ->entry.etypes->val[m]) {
ret = 0;
break;
}
}
} else {
/*
* Use the entry's long-term keys as the source of its
* supported enctypes, either because we're making
* PA-ETYPE-INFO* or because we're selecting a session key
* enctype.
*/
while (hdb_next_enctype2key(context, &princ->entry, NULL,
p[i], &key) == 0) {
if (key->key.keyvalue.length == 0) {
ret = KRB5KDC_ERR_NULL_KEY;
continue;
}
enctype = p[i];
ret = 0;
if (is_preauth && ret_key != NULL &&
!is_default_salt_p(&def_salt, key))
continue;
}
}
}
}
} else {
@@ -1908,16 +1963,10 @@ _kdc_as_rep(astgs_request_t r)
}
/*
* Select a session enctype from the list of the crypto system
* supported enctypes that is supported by the client and is one of
* the enctype of the enctype of the service (likely krbtgt).
*
* The latter is used as a hint of what enctypes all KDC support,
* to make sure a newer version of KDC won't generate a session
* enctype that an older version of a KDC in the same realm can't
* decrypt.
* Select an enctype for the to-be-issued ticket's session key using the
* intersection of the client's requested enctypes and the server's (like a
* root krbtgt, but not necessarily) etypes from its HDB entry.
*/
ret = _kdc_find_etype(r, (is_tgs ? KFE_IS_TGS:0) | KFE_USE_CLIENT,
b->etype.val, b->etype.len,
&r->sessionetype, NULL, NULL);

View File

@@ -49,184 +49,6 @@ name_type_ok(krb5_context context,
return 0;
}
static void
log_princ(krb5_context context, krb5_kdc_configuration *config, int lvl,
const char *fmt, krb5_const_principal princ)
{
krb5_error_code ret;
char *princstr;
ret = krb5_unparse_name(context, princ, &princstr);
if (ret) {
kdc_log(context, config, 1, "log_princ: ENOMEM");
return;
}
kdc_log(context, config, lvl, fmt, princstr);
free(princstr);
}
static krb5_error_code
_derive_the_keys(krb5_context context, krb5_kdc_configuration *config,
krb5_const_principal princ, krb5uint32 kvno, hdb_entry_ex *h)
{
krb5_error_code ret;
krb5_crypto crypto = NULL;
krb5_data in;
size_t i;
char *princstr = NULL;
const char *errmsg = NULL;
ret = krb5_unparse_name(context, princ, &princstr);
if (ret) {
errmsg = "krb5_unparse_name failed";
goto bail;
}
in.data = princstr;
in.length = strlen(in.data);
for (i = 0; i < h->entry.keys.len; i++) {
krb5_enctype etype = h->entry.keys.val[i].key.keytype;
krb5_keyblock *keyptr = &h->entry.keys.val[i].key;
krb5_data rnd;
size_t len;
kdc_log(context, config, 8, " etype=%d", etype);
errmsg = "Failed to init crypto";
ret = krb5_crypto_init(context, keyptr, 0, &crypto);
if (ret)
goto bail;
errmsg = "Failed to determine keysize";
ret = krb5_enctype_keysize(context, etype, &len);
if (ret)
goto bail;
errmsg = "krb5_crypto_prfplus() failed";
ret = krb5_crypto_prfplus(context, crypto, &in, len, &rnd);
krb5_crypto_destroy(context, crypto);
crypto = NULL;
if (ret)
goto bail;
errmsg = "krb5_random_to_key() failed";
krb5_free_keyblock_contents(context, keyptr);
ret = krb5_random_to_key(context, etype, rnd.data, rnd.length, keyptr);
krb5_data_free(&rnd);
if (ret)
goto bail;
}
bail:
if (ret) {
const char *msg = krb5_get_error_message(context, ret);
kdc_log(context, config, 1, "%s: %s", errmsg, msg);
krb5_free_error_message(context, msg);
}
if (crypto)
krb5_crypto_destroy(context, crypto);
free(princstr);
return 0;
}
static krb5_error_code
_fetch_it(krb5_context context, krb5_kdc_configuration *config, HDB *db,
krb5_const_principal princ, unsigned flags, krb5uint32 kvno,
hdb_entry_ex *ent)
{
krb5_principal tmpprinc;
krb5_error_code ret;
char *host = NULL;
char *tmp;
const char *realm = NULL;
int is_derived_key = 0;
size_t hdots;
size_t ndots = 0;
size_t maxdots = -1;
flags |= HDB_F_DECRYPT;
if (config->enable_derived_keys) {
if (krb5_principal_get_num_comp(context, princ) == 2) {
realm = krb5_principal_get_realm(context, princ);
host = strdup(krb5_principal_get_comp_string(context, princ, 1));
if (!host)
return krb5_enomem(context);
/* Strip the :port */
tmp = strchr(host, ':');
if (tmp) {
*tmp++ = '\0';
if (strchr(tmp, ':')) {
kdc_log(context, config, 7, "Strange host instance, "
"port %s contains a colon (``:'')", tmp);
free(host);
host = NULL;
}
}
ndots = config->derived_keys_ndots;
maxdots = config->derived_keys_maxdots;
for (hdots = 0, tmp = host; tmp && *tmp; tmp++)
if (*tmp == '.')
hdots++;
}
}
/*
* XXXrcd: should we exclude certain principals from this
* muckery? E.g. host? krbtgt?
*/
krb5_copy_principal(context, princ, &tmpprinc);
tmp = host;
for (;;) {
log_princ(context, config, 7, "Looking up %s", tmpprinc);
ret = db->hdb_fetch_kvno(context, db, tmpprinc, flags, kvno, ent);
if (ret != HDB_ERR_NOENTRY)
break;
if (!tmp || !*tmp || hdots < ndots)
break;
while (maxdots > 0 && hdots > maxdots) {
tmp = strchr(tmp, '.');
/* tmp != NULL because maxdots > 0 */
tmp++;
hdots--;
}
is_derived_key = 1;
krb5_free_principal(context, tmpprinc);
krb5_build_principal(context, &tmpprinc, strlen(realm), realm,
"WELLKNOWN", "DERIVED-KEY", "KRB5-CRYPTO-PRFPLUS", tmp, NULL);
tmp = strchr(tmp, '.');
if (!tmp)
break;
tmp++;
hdots--;
}
if (ret == 0 && is_derived_key) {
kdc_log(context, config, 7, "Deriving keys:");
log_princ(context, config, 7, " for %s", princ);
log_princ(context, config, 7, " from %s", tmpprinc);
_derive_the_keys(context, config, princ, kvno, ent);
/* the next function frees the target */
copy_Principal(princ, ent->entry.principal);
}
free(host);
krb5_free_principal(context, tmpprinc);
return ret;
}
struct timeval _kdc_now;
krb5_error_code
@@ -250,6 +72,7 @@ _kdc_db_fetch(krb5_context context,
if (!name_type_ok(context, config, principal))
goto out2;
flags |= HDB_F_DECRYPT;
if (kvno_ptr != NULL && *kvno_ptr != 0) {
kvno = *kvno_ptr;
flags |= HDB_F_KVNO_SPECIFIED;
@@ -291,7 +114,7 @@ _kdc_db_fetch(krb5_context context,
if (!(curdb->hdb_capability_flags & HDB_CAP_F_HANDLE_ENTERPRISE_PRINCIPAL) && enterprise_principal)
princ = enterprise_principal;
ret = _fetch_it(context, config, curdb, princ, flags, kvno, ent);
ret = hdb_fetch_kvno(context, curdb, princ, flags, 0, 0, kvno, ent);
curdb->hdb_close(context, curdb);
switch (ret) {

View File

@@ -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

View File

@@ -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

View File

@@ -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, &current);
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;
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);

View File

@@ -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
}

View File

@@ -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

View File

@@ -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.
*

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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
View 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;
}

View File

@@ -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:
*;

View File

@@ -124,13 +124,29 @@ fetch_acl (kadm5_server_context *context,
if (princ != NULL) {
krb5_principal pattern_princ;
krb5_boolean match;
const char *c0 = krb5_principal_get_comp_string(context->context,
princ, 0);
const char *pat_c0;
ret = krb5_parse_name (context->context, p, &pattern_princ);
ret = krb5_parse_name(context->context, p, &pattern_princ);
if (ret)
break;
match = krb5_principal_match (context->context,
princ, pattern_princ);
krb5_free_principal (context->context, pattern_princ);
pat_c0 = krb5_principal_get_comp_string(context->context,
pattern_princ, 0);
match = krb5_principal_match(context->context,
princ, pattern_princ);
/*
* If `princ' is a WELLKNOWN name, then require the WELLKNOWN label
* be matched exactly.
*
* FIXME: We could do something similar for krbtgt and kadmin other
* principal types.
*/
if (match && c0 && strcmp(c0, "WELLKNOWN") == 0 &&
(!pat_c0 || strcmp(pat_c0, "WELLKNOWN") != 0))
match = FALSE;
krb5_free_principal(context->context, pattern_princ);
if (match) {
*ret_flags = flags;
break;

View File

@@ -73,6 +73,9 @@
#define KRB5_KDB_TRUSTED_FOR_DELEGATION 0x00020000
#define KRB5_KDB_ALLOW_KERBEROS4 0x00040000
#define KRB5_KDB_ALLOW_DIGEST 0x00080000
#define KRB5_KDB_MATERIALIZE 0x00100000
#define KRB5_KDB_VIRTUAL_KEYS 0x00200000
#define KRB5_KDB_VIRTUAL 0x00400000
#define KADM5_PRINCIPAL 0x000001U
#define KADM5_PRINC_EXPIRE_TIME 0x000002U
@@ -141,6 +144,9 @@ typedef struct _krb5_tl_data {
#define KRB5_TL_ALIASES 0x000a
#define KRB5_TL_HIST_KVNO_DIFF_CLNT 0x000b
#define KRB5_TL_HIST_KVNO_DIFF_SVC 0x000c
#define KRB5_TL_ETYPES 0x000d
#define KRB5_TL_KEY_ROTATION 0x000e
#define KRB5_TL_KRB5_CONFIG 0x000f
typedef struct _kadm5_principal_ent_t {
krb5_principal principal;

View File

@@ -147,7 +147,8 @@ change(void *server_handle,
goto out;
ret = context->db->hdb_fetch_kvno(context->context, context->db, princ,
HDB_F_DECRYPT|HDB_F_GET_ANY|HDB_F_ADMIN_DATA, 0, &ent);
HDB_F_DECRYPT|HDB_F_GET_ANY|HDB_F_ADMIN_DATA,
0, &ent);
if (ret)
goto out2;
@@ -381,8 +382,8 @@ kadm5_s_chpass_principal_with_key(void *server_handle,
if (ret)
goto out;
ret = context->db->hdb_fetch_kvno(context->context, context->db, princ, 0,
HDB_F_GET_ANY|HDB_F_ADMIN_DATA, &ent);
ret = context->db->hdb_fetch_kvno(context->context, context->db, princ,
HDB_F_GET_ANY|HDB_F_ADMIN_DATA, 0, &ent);
if (ret == HDB_ERR_NOENTRY)
goto out2;

View File

@@ -54,6 +54,12 @@ kadm5_c_create_principal(void *server_handle,
* We should get around to implementing this... At the moment, the
* the server side API is implemented but the wire protocol has not
* been updated.
*
* Well, we have the etypes extension, which the kadmin ank command now
* adds, but that doesn't include salt types. We could, perhaps, make it
* so if the password is "" or NULL, we send the etypes but not the salt
* type, and then have the server side create random keys of just the
* etypes.
*/
if (n_ks_tuple > 0)
return KADM5_KS_TUPLE_NOSUPP;

View File

@@ -207,7 +207,12 @@ kadm5_s_create_principal_with_key(void *server_handle,
if (ret)
goto out2;
/* This logs the change for iprop and writes to the HDB */
/*
* This logs the change for iprop and writes to the HDB.
*
* Creation of would-be virtual principals w/o the materialize flag will be
* rejected in kadm5_log_create().
*/
ret = kadm5_log_create(context, &ent.entry);
(void) create_principal_hook(context, KADM5_HOOK_STAGE_POSTCOMMIT,
@@ -238,8 +243,31 @@ kadm5_s_create_principal(void *server_handle,
kadm5_ret_t ret;
hdb_entry_ex ent;
kadm5_server_context *context = server_handle;
int use_pw = 1;
if (_kadm5_enforce_pwqual_on_admin_set_p(context)) {
if ((mask & KADM5_ATTRIBUTES) &&
(princ->attributes & (KRB5_KDB_VIRTUAL_KEYS | KRB5_KDB_VIRTUAL)) &&
!(princ->attributes & KRB5_KDB_MATERIALIZE)) {
ret = KADM5_DUP; /* XXX */
goto out;
}
if ((mask & KADM5_ATTRIBUTES) &&
(princ->attributes & KRB5_KDB_VIRTUAL_KEYS) &&
(princ->attributes & KRB5_KDB_VIRTUAL)) {
ret = KADM5_DUP; /* XXX */
goto out;
}
if ((mask & KADM5_ATTRIBUTES) &&
(princ->attributes & KRB5_KDB_VIRTUAL) &&
(princ->attributes & KRB5_KDB_MATERIALIZE))
princ->attributes &= ~(KRB5_KDB_MATERIALIZE | KRB5_KDB_VIRTUAL);
if (password[0] == '\0' && (mask & KADM5_KEY_DATA) && princ->n_key_data &&
!kadm5_all_keys_are_bogus(princ->n_key_data, princ->key_data))
use_pw = 0;
if (use_pw && _kadm5_enforce_pwqual_on_admin_set_p(context)) {
krb5_data pwd_data;
const char *pwd_reason;
@@ -265,13 +293,22 @@ kadm5_s_create_principal(void *server_handle,
if (ret)
return ret;
ret = create_principal(context, princ, mask, &ent,
KADM5_PRINCIPAL,
KADM5_LAST_PWD_CHANGE | KADM5_MOD_TIME
| KADM5_MOD_NAME | KADM5_MKVNO
| KADM5_AUX_ATTRIBUTES | KADM5_KEY_DATA
| KADM5_POLICY_CLR | KADM5_LAST_SUCCESS
| KADM5_LAST_FAILED | KADM5_FAIL_AUTH_COUNT);
if (use_pw)
ret = create_principal(context, princ, mask, &ent,
KADM5_PRINCIPAL,
KADM5_LAST_PWD_CHANGE | KADM5_MOD_TIME
| KADM5_MOD_NAME | KADM5_MKVNO
| KADM5_AUX_ATTRIBUTES | KADM5_KEY_DATA
| KADM5_POLICY_CLR | KADM5_LAST_SUCCESS
| KADM5_LAST_FAILED | KADM5_FAIL_AUTH_COUNT);
else
ret = create_principal(context, princ, mask, &ent,
KADM5_PRINCIPAL | KADM5_KEY_DATA,
KADM5_LAST_PWD_CHANGE | KADM5_MOD_TIME
| KADM5_MOD_NAME | KADM5_MKVNO
| KADM5_AUX_ATTRIBUTES
| KADM5_POLICY_CLR | KADM5_LAST_SUCCESS
| KADM5_LAST_FAILED | KADM5_FAIL_AUTH_COUNT);
if (ret)
return ret;
@@ -289,9 +326,11 @@ kadm5_s_create_principal(void *server_handle,
free_Keys(&ent.entry.keys);
ret = _kadm5_set_keys(context, &ent.entry, n_ks_tuple, ks_tuple, password);
if (ret)
goto out2;
if (use_pw) {
ret = _kadm5_set_keys(context, &ent.entry, n_ks_tuple, ks_tuple, password);
if (ret)
goto out2;
}
ret = hdb_seal_keys(context->context, context->db, &ent.entry);
if (ret)

View File

@@ -108,7 +108,8 @@ kadm5_s_delete_principal(void *server_handle, krb5_principal princ)
goto out;
ret = context->db->hdb_fetch_kvno(context->context, context->db, princ,
HDB_F_DECRYPT|HDB_F_GET_ANY|HDB_F_ADMIN_DATA, 0, &ent);
HDB_F_DECRYPT|HDB_F_GET_ANY|HDB_F_ADMIN_DATA,
0, &ent);
if (ret == HDB_ERR_NOENTRY)
goto out2;
if (ent.entry.flags.immutable) {

View File

@@ -60,6 +60,9 @@ attr_to_flags(unsigned attr, HDBFlags *flags)
flags->trusted_for_delegation = !!(attr & KRB5_KDB_TRUSTED_FOR_DELEGATION);
flags->allow_kerberos4 = !!(attr & KRB5_KDB_ALLOW_KERBEROS4);
flags->allow_digest = !!(attr & KRB5_KDB_ALLOW_DIGEST);
flags->materialize = !!(attr & KRB5_KDB_MATERIALIZE);
flags->virtual_keys = !!(attr & KRB5_KDB_VIRTUAL_KEYS);
flags->virtual = !!(attr & KRB5_KDB_VIRTUAL);
}
/*
@@ -95,6 +98,25 @@ perform_tl_data(krb5_context context,
ret = hdb_entry_set_pw_change_time(context, &ent->entry, t);
} else if (tl_data->tl_data_type == KRB5_TL_KEY_ROTATION) {
HDB_Ext_KeyRotation *prev_kr = 0;
HDB_extension *prev_ext;
HDB_extension ext;
ext.mandatory = 0;
ext.data.element = choice_HDB_extension_data_key_rotation;
prev_ext = hdb_find_extension(&ent->entry, ext.data.element);
if (prev_ext)
prev_kr = &prev_ext->data.u.key_rotation;
ret = decode_HDB_Ext_KeyRotation(tl_data->tl_data_contents,
tl_data->tl_data_length,
&ext.data.u.key_rotation, NULL);
if (ret == 0)
ret = hdb_validate_key_rotations(context, prev_kr,
&ext.data.u.key_rotation);
if (ret == 0)
ret = hdb_replace_extension(context, &ent->entry, &ext);
free_HDB_extension(&ext);
} else if (tl_data->tl_data_type == KRB5_TL_EXTENSION) {
HDB_extension ext;
@@ -105,8 +127,34 @@ perform_tl_data(krb5_context context,
if (ret)
return KADM5_BAD_TL_TYPE;
ret = hdb_replace_extension(context, &ent->entry, &ext);
if (ext.data.element == choice_HDB_extension_data_key_rotation) {
HDB_extension *prev_ext = hdb_find_extension(&ent->entry,
ext.data.element);
HDB_Ext_KeyRotation *prev_kr = 0;
if (prev_ext)
prev_kr = &prev_ext->data.u.key_rotation;
ret = hdb_validate_key_rotations(context, prev_kr,
&ext.data.u.key_rotation);
}
if (ret)
ret = KADM5_BAD_TL_TYPE; /* XXX Need new error code */
if (ret == 0)
ret = hdb_replace_extension(context, &ent->entry, &ext);
free_HDB_extension(&ext);
} else if (tl_data->tl_data_type == KRB5_TL_ETYPES) {
if (!ent->entry.etypes &&
(ent->entry.etypes = calloc(1,
sizeof(ent->entry.etypes[0]))) == NULL)
ret = krb5_enomem(context);
if (ent->entry.etypes)
free_HDB_EncTypeList(ent->entry.etypes);
if (ret == 0)
ret = decode_HDB_EncTypeList(tl_data->tl_data_contents,
tl_data->tl_data_length,
ent->entry.etypes, NULL);
if (ret)
return KADM5_BAD_TL_TYPE;
} else {
return KADM5_BAD_TL_TYPE;
}

View File

@@ -123,6 +123,10 @@ kadm5_s_get_principal(void *server_handle,
kadm5_server_context *context = server_handle;
kadm5_ret_t ret;
hdb_entry_ex ent;
unsigned int flags = HDB_F_GET_ANY | HDB_F_ADMIN_DATA;
if ((mask & KADM5_KEY_DATA) || (mask & KADM5_KVNO))
flags |= HDB_F_ALL_KVNOS | HDB_F_DECRYPT;
memset(&ent, 0, sizeof(ent));
memset(out, 0, sizeof(*out));
@@ -144,9 +148,9 @@ kadm5_s_get_principal(void *server_handle,
* For now we won't attempt to recover the log.
*/
ret = context->db->hdb_fetch_kvno(context->context, context->db, princ,
HDB_F_DECRYPT|HDB_F_ALL_KVNOS|
HDB_F_GET_ANY|HDB_F_ADMIN_DATA, 0, &ent);
ret = hdb_fetch_kvno(context->context, context->db, princ,
HDB_F_DECRYPT|HDB_F_ALL_KVNOS|
HDB_F_GET_ANY|HDB_F_ADMIN_DATA, 0, 0, 0, &ent);
if (!context->keep_open)
context->db->hdb_close(context->context, context->db);
@@ -179,6 +183,8 @@ kadm5_s_get_principal(void *server_handle,
out->attributes |= ent.entry.flags.trusted_for_delegation ? KRB5_KDB_TRUSTED_FOR_DELEGATION : 0;
out->attributes |= ent.entry.flags.allow_kerberos4 ? KRB5_KDB_ALLOW_KERBEROS4 : 0;
out->attributes |= ent.entry.flags.allow_digest ? KRB5_KDB_ALLOW_DIGEST : 0;
out->attributes |= ent.entry.flags.virtual_keys ? KRB5_KDB_VIRTUAL_KEYS : 0;
out->attributes |= ent.entry.flags.virtual ? KRB5_KDB_VIRTUAL : 0;
}
if(mask & KADM5_MAX_LIFE) {
if(ent.entry.max_life)
@@ -295,6 +301,22 @@ kadm5_s_get_principal(void *server_handle,
time_t last_pw_expire;
const HDB_Ext_PKINIT_acl *acl;
const HDB_Ext_Aliases *aliases;
const HDB_Ext_KeyRotation *kr;
heim_octet_string krb5_config;
if (ent.entry.etypes) {
krb5_data buf;
size_t len;
ASN1_MALLOC_ENCODE(HDB_EncTypeList, buf.data, buf.length,
ent.entry.etypes, &len, ret);
if (ret == 0) {
ret = add_tl_data(out, KRB5_TL_ETYPES, buf.data, buf.length);
free(buf.data);
}
if (ret)
goto out;
}
ret = hdb_entry_get_pw_change_time(&ent.entry, &last_pw_expire);
if (ret == 0 && last_pw_expire) {
@@ -302,6 +324,12 @@ kadm5_s_get_principal(void *server_handle,
_krb5_put_int(buf, last_pw_expire, sizeof(buf));
ret = add_tl_data(out, KRB5_TL_LAST_PWD_CHANGE, buf, sizeof(buf));
}
if (ret == 0)
ret = hdb_entry_get_krb5_config(&ent.entry, &krb5_config);
if (ret == 0 && krb5_config.length) {
ret = add_tl_data(out, KRB5_TL_KRB5_CONFIG, krb5_config.data,
krb5_config.length);
}
if (ret)
goto out;
/*
@@ -311,6 +339,7 @@ kadm5_s_get_principal(void *server_handle,
if (mask & KADM5_KEY_DATA) {
heim_utf8_string pw;
/* XXX But not if the client doesn't have ext-keys */
ret = hdb_entry_get_password(context->context,
context->db, &ent.entry, &pw);
if (ret == 0) {
@@ -359,6 +388,26 @@ kadm5_s_get_principal(void *server_handle,
}
if (ret)
goto out;
ret = hdb_entry_get_key_rotation(context->context, &ent.entry, &kr);
if (ret == 0 && kr) {
krb5_data buf;
size_t len;
ASN1_MALLOC_ENCODE(HDB_Ext_KeyRotation, buf.data, buf.length,
kr, &len, ret);
if (ret)
goto out;
if (len != buf.length)
krb5_abortx(context->context,
"internal ASN.1 encoder error");
ret = add_tl_data(out, KRB5_TL_KEY_ROTATION, buf.data, buf.length);
free(buf.data);
if (ret)
goto out;
}
if (ret)
goto out;
}
out:

View File

@@ -940,13 +940,31 @@ kadm5_log_create(kadm5_server_context *context, hdb_entry *entry)
krb5_ssize_t bytes;
kadm5_ret_t ret;
krb5_data value;
hdb_entry_ex ent;
hdb_entry_ex ent, existing;
kadm5_log_context *log_context = &context->log_context;
memset(&ent, 0, sizeof(ent));
ent.ctx = 0;
ent.free_entry = 0;
ent.entry = *entry;
existing = ent;
/*
* Do not allow creation of concrete entries within namespaces unless
* explicitly requested.
*/
ret = hdb_fetch_kvno(context->context, context->db, entry->principal, 0,
0, 0, 0, &existing);
if (ret != 0 && ret != HDB_ERR_NOENTRY)
return ret;
if (ret == 0 && !ent.entry.flags.materialize &&
(existing.entry.flags.virtual || existing.entry.flags.virtual_keys)) {
hdb_free_entry(context->context, &existing);
return HDB_ERR_EXISTS;
}
if (ret == 0)
hdb_free_entry(context->context, &existing);
ent.entry.flags.materialize = 0; /* Clear in stored entry */
/*
* If we're not logging then we can't recover-to-perform, so just
@@ -1406,6 +1424,7 @@ kadm5_log_replay_modify(kadm5_server_context *context,
return ret;
memset(&ent, 0, sizeof(ent));
/* NOTE: We do not use hdb_fetch_kvno() here */
ret = context->db->hdb_fetch_kvno(context->context, context->db,
log_ent.entry.principal,
HDB_F_DECRYPT|HDB_F_ALL_KVNOS|
@@ -1547,25 +1566,43 @@ kadm5_log_replay_modify(kadm5_server_context *context,
}
}
}
if ((mask & KADM5_TL_DATA) && log_ent.entry.etypes) {
if (ent.entry.etypes)
free_HDB_EncTypeList(ent.entry.etypes);
free(ent.entry.etypes);
ent.entry.etypes = calloc(1, sizeof(*ent.entry.etypes));
if (ent.entry.etypes == NULL)
ret = ENOMEM;
if (ret == 0)
ret = copy_HDB_EncTypeList(log_ent.entry.etypes, ent.entry.etypes);
if (ret) {
ret = krb5_enomem(context->context);
free(ent.entry.etypes);
ent.entry.etypes = NULL;
goto out;
}
}
if ((mask & KADM5_TL_DATA) && log_ent.entry.extensions) {
HDB_extensions *es = ent.entry.extensions;
if (ent.entry.extensions) {
free_HDB_extensions(ent.entry.extensions);
free(ent.entry.extensions);
ent.entry.extensions = NULL;
}
ent.entry.extensions = calloc(1, sizeof(*ent.entry.extensions));
if (ent.entry.extensions == NULL)
goto out;
ret = ENOMEM;
ret = copy_HDB_extensions(log_ent.entry.extensions,
ent.entry.extensions);
if (ret == 0)
ret = copy_HDB_extensions(log_ent.entry.extensions,
ent.entry.extensions);
if (ret) {
krb5_set_error_message(context->context, ret, "out of memory");
ret = krb5_enomem(context->context);
free(ent.entry.extensions);
ent.entry.extensions = es;
ent.entry.extensions = NULL;
goto out;
}
if (es) {
free_HDB_extensions(es);
free(es);
}
}
ret = context->db->hdb_store(context->context, context->db,
HDB_F_REPLACE, &ent);

View File

@@ -117,8 +117,14 @@ modify_principal(void *server_handle,
if (ret)
goto out;
/*
* NOTE: We do not use hdb_fetch_kvno() here, which means we'll
* automatically reject modifications of would-be virtual principals.
*/
ret = context->db->hdb_fetch_kvno(context->context, context->db,
princ->principal, HDB_F_GET_ANY|HDB_F_ADMIN_DATA, 0, &ent);
princ->principal,
HDB_F_DECRYPT|HDB_F_GET_ANY|HDB_F_ADMIN_DATA,
0, &ent);
if (ret)
goto out2;
@@ -126,6 +132,10 @@ modify_principal(void *server_handle,
0, princ, mask);
if (ret)
goto out3;
/*
* XXX Make sure that _kadm5_setup_entry() checks that the time of last
* change in `ent' matches the one in `princ'.
*/
ret = _kadm5_setup_entry(context, &ent, mask, princ, mask, NULL, 0);
if (ret)
goto out3;

View File

@@ -109,8 +109,10 @@ kadm5_s_prune_principal(void *server_handle,
if (ret)
goto out;
/* NOTE: We do not use hdb_fetch_kvno() here */
ret = context->db->hdb_fetch_kvno(context->context, context->db, princ,
HDB_F_GET_ANY|HDB_F_ADMIN_DATA, 0, &ent);
HDB_F_DECRYPT|HDB_F_GET_ANY|HDB_F_ADMIN_DATA,
0, &ent);
if (ret)
goto out2;

View File

@@ -117,8 +117,10 @@ kadm5_s_randkey_principal(void *server_handle,
if (ret)
goto out;
/* NOTE: We do not use hdb_fetch_kvno() here (maybe we should) */
ret = context->db->hdb_fetch_kvno(context->context, context->db, princ,
HDB_F_GET_ANY|HDB_F_ADMIN_DATA, 0, &ent);
HDB_F_DECRYPT|HDB_F_GET_ANY|HDB_F_ADMIN_DATA,
0, &ent);
if(ret)
goto out2;

View File

@@ -114,8 +114,11 @@ kadm5_s_rename_principal(void *server_handle,
if (ret)
goto out;
/* NOTE: We do not use hdb_fetch_kvno() here */
ret = context->db->hdb_fetch_kvno(context->context, context->db,
source, HDB_F_GET_ANY|HDB_F_ADMIN_DATA, 0, &ent);
source,
HDB_F_DECRYPT|HDB_F_GET_ANY|HDB_F_ADMIN_DATA,
0, &ent);
if (ret)
goto out2;
oldname = ent.entry.principal;

View File

@@ -132,8 +132,10 @@ kadm5_s_setkey_principal_3(void *server_handle,
return ret;
}
/* NOTE: We do not use hdb_fetch_kvno() here (maybe we should?) */
ret = context->db->hdb_fetch_kvno(context->context, context->db, princ,
HDB_F_GET_ANY|HDB_F_ADMIN_DATA, 0, &ent);
HDB_F_DECRYPT|HDB_F_GET_ANY|HDB_F_ADMIN_DATA,
0, &ent);
if (ret) {
(void) kadm5_log_end(context);
if (!context->keep_open)

View File

@@ -901,28 +901,76 @@ SANs are configured, then no certificate will be issued.
Otherwise if a template and subject name are not specified, then
subject of the certificate will be empty.
.El
.It Li enable_derived_keys = Va boolean
Enable the use of derived key namespaces.
When enabled, principals of the form
.El
.Pp
.Ar WELLKNOWN/DERIVED-KEY/<alg>/<namespace>@REALM
.Pp
match any request of the form:
.Ar */*.<namespace>@REALM .
The keys are derived from the keys in the database and
the name of the requested principal via the algorithm
specified by
.Ar <alg> .
Currently, only
.Ar KRB5-CRYPTO-PRFPLUS
which is implemented by the function
.Fn krb5_crypto_prfplus .
.It Li derived_keys_ndots = Va Integer
The minimum number of dots in a name matched via
derived key namespaces.
.It Li derived_keys_maxdots = Va Integer
The maximim number of dots in a name matched via
derived key namespaces.
.It Li [hdb]
.Bl -tag -width "xxx" -offset indent
.It Li db-dir = Va path
This parameter defines a directory that can contain:
.Bl -tag -width "xxx" -offset indent
.It Va kdc.conf
A configuration file with the same format as krb5.conf that will
be included.
.It Va m-key
The master key file.
.It Va kdc.log
The default logfile for the KDC when a logfile is not specified in
.Li [logging]
.It Va kadm5.acl
The access controls for
.Nm kadmind .
.It Va log
The (binary) log of transactions used for
.Nm HDB
replication via the
.Nm iprop
protocol.
See
.Nm iprop-log(1)
for more detail.
.It Va pki-mapping
The default PKINIT mapping file if one is not specified in
.Va [kdc] pkinit_mappings_file .
.El
and other files related to
.Nm iprop
operation.
.It Li new_service_key_delay = Va time
Sets a bias such that new keys are not taken into service until
after the given time has passed since they were set.
This is useful for key rotation on concrete principals shared by
multiple instances of an application: set this time to twice or
more the keytab fetch period used by applications.
.It Li enable_virtual_hostbased_princs = Va boolean
Heimdal supports a notion of virtual host-based service
principals whose keys are derived from those of a base namespace
principal of the form
.Nm WELLKNOWN/HOSTBASED-NAMESPACE/svc/hostname .
The service name can be wild-carded as
.Va _ .
Non-wildcarded services have to be listed in the
.Li virtual_hostbased_princ_svcs
parameter (see below).
This parameter enables this feature, which is disabled by
default.
.It Li virtual_hostbased_princ_ndots = Va Integer
Minimum number of label-separating periods in virtual host-based
service principals' hostname component.
.It Li virtual_hostbased_princ_maxdots = Va Integer
Maximum number of label-separating periods in namespaces'
hostname component.
.It Li virtual_hostbased_princ_svcs = Va service-name
This multi-valued parameter lists service names not to wildcard
when searching for a namespace for a virtual host-based service
principal.
Other service names will have keys derived from a matching
namespace with a wild-carded service name.
This allows one to have different attributes for different
services.
For example, the
.Nm "host"
service can be configured to have the ok-as-delegate flag while
all others do not.
.El
.Pp
.It Li [kadmin]

View File

@@ -76,9 +76,13 @@ ${kadmin} \
--realm-max-renewable-life=1month \
${R} || exit 1
${kadmin} add -p p1 --use-defaults host/n1.test.h5l.se@${R} || exit 1
${kadmin} add -p p2 --use-defaults host/n2.test.h5l.se@${R} || exit 1
${kadmin} add -p p3 --use-defaults host/n3.test.h5l.se@${R} || exit 1
# Test virtual principals, why not
${kadmin} add_ns --key-rotation-epoch=now \
--key-rotation-period=15m \
--max-ticket-life=10d \
--max-renewable-life=20d \
--attributes= \
"_/test.h5l.se@${R}" || exit 1
${kadmin} ext -k ${keytab} host/n1.test.h5l.se@${R} || exit 1
${kadmin} ext -k ${keytab} host/n2.test.h5l.se@${R} || exit 1
${kadmin} ext -k ${keytab} host/n3.test.h5l.se@${R} || exit 1

View File

@@ -27,6 +27,9 @@ include @srcdirabs@/include-krb5.conf
[hdb]
db-dir = @objdir@
enable_virtual_hostbased_princs = true
virtual_hostbased_princ_mindots = 1
virtual_hostbased_princ_maxdots = 3
[logging]
kdc = 0-/FILE:@objdir@/messages.log