diff --git a/.gitignore b/.gitignore index b1d0c9e1e..66f290fb4 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/kadmin/ank.c b/kadmin/ank.c index 08e42d4d1..1e5cd6117 100644 --- a/kadmin/ank.c +++ b/kadmin/ank.c @@ -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; +} diff --git a/kadmin/del.c b/kadmin/del.c index a4cec7acb..b2ed73500 100644 --- a/kadmin/del.c +++ b/kadmin/del.c @@ -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 : ""); + 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; +} diff --git a/kadmin/get.c b/kadmin/get.c index 9d9678043..35766aa1b 100644 --- a/kadmin/get.c +++ b/kadmin/get.c @@ -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(); diff --git a/kadmin/kadmin-commands.in b/kadmin/kadmin-commands.in index 1faec5007..505aac14b 100644 --- a/kadmin/kadmin-commands.in +++ b/kadmin/kadmin-commands.in @@ -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\"." diff --git a/kadmin/kadmin.1 b/kadmin/kadmin.1 index c50224520..221c34844 100644 --- a/kadmin/kadmin.1 +++ b/kadmin/kadmin.1 @@ -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 "+[]" +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 , diff --git a/kadmin/kadmin_locl.h b/kadmin/kadmin_locl.h index 924af78dc..368901807 100644 --- a/kadmin/kadmin_locl.h +++ b/kadmin/kadmin_locl.h @@ -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__ */ diff --git a/kadmin/mod.c b/kadmin/mod.c index 4a88a85a4..005cc277f 100644 --- a/kadmin/mod.c +++ b/kadmin/mod.c @@ -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; +} diff --git a/kdc/default_config.c b/kdc/default_config.c index b168175f1..ed4d03b05 100644 --- a/kdc/default_config.c +++ b/kdc/default_config.c @@ -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; diff --git a/kdc/kdc.h b/kdc/kdc.h index ef6ba4440..e8c27cfed 100644 --- a/kdc/kdc.h +++ b/kdc/kdc.h @@ -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; diff --git a/kdc/kerberos5.c b/kdc/kerberos5.c index d4c1a5861..e03c6a69b 100644 --- a/kdc/kerberos5.c +++ b/kdc/kerberos5.c @@ -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); diff --git a/kdc/misc.c b/kdc/misc.c index e13faba97..02c612231 100644 --- a/kdc/misc.c +++ b/kdc/misc.c @@ -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) { diff --git a/lib/hdb/Makefile.am b/lib/hdb/Makefile.am index 448b0242f..692242323 100644 --- a/lib/hdb/Makefile.am +++ b/lib/hdb/Makefile.am @@ -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 diff --git a/lib/hdb/NTMakefile b/lib/hdb/NTMakefile index 01671b078..5ad9d9c57 100644 --- a/lib/hdb/NTMakefile +++ b/lib/hdb/NTMakefile @@ -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 diff --git a/lib/hdb/common.c b/lib/hdb/common.c index 741956bea..666eab1d7 100644 --- a/lib/hdb/common.c +++ b/lib/hdb/common.c @@ -380,6 +380,71 @@ hdb_check_aliases(krb5_context context, HDB *db, hdb_entry_ex *entry) return 0; } +/* + * Many HDB entries don't have `etypes' setup. Historically we use the + * enctypes of the selected keyset as the entry's supported enctypes, but that + * is problematic. By doing this at store time and, if need be, at fetch time, + * we can make sure to stop deriving supported etypes from keys in the long + * run. We also need kadm5/kadmin support for etypes. We'll use this function + * there to derive etypes when using a kadm5_principal_ent_t that lacks the new + * TL data for etypes. + */ +krb5_error_code +hdb_derive_etypes(krb5_context context, hdb_entry *e, HDB_Ext_KeySet *base_keys) +{ + krb5_error_code ret = 0; + size_t i, k, netypes; + HDB_extension *ext; + + if (!base_keys && + (ext = hdb_find_extension(e, choice_HDB_extension_data_hist_keys))) + base_keys = &ext->data.u.hist_keys; + + netypes = e->keys.len; + if (netypes == 0 && base_keys) { + /* There's no way that base_keys->val[i].keys.len == 0, but hey */ + for (i = 0; netypes == 0 && i < base_keys->len; i++) + netypes = base_keys->val[i].keys.len; + } + + if (netypes == 0) + return 0; + + if (e->etypes != NULL) { + free(e->etypes->val); + e->etypes->len = 0; + e->etypes->val = 0; + } + + if (e->etypes == NULL && + (e->etypes = malloc(sizeof(e->etypes[0]))) == NULL) + ret = krb5_enomem(context); + if (ret == 0) { + e->etypes->len = 0; + e->etypes->val = 0; + } + if (ret == 0 && + (e->etypes->val = calloc(netypes, sizeof(e->etypes->val[0]))) == NULL) + ret = krb5_enomem(context); + if (ret) { + free(e->etypes); + e->etypes = 0; + return ret; + } + e->etypes->len = netypes; + for (i = 0; i < e->keys.len && i < netypes; i++) + e->etypes->val[i] = e->keys.val[i].key.keytype; + if (!base_keys || i) + return 0; + for (k = 0; i == 0 && k < base_keys->len; k++) { + if (!base_keys->val[k].keys.len) + continue; + for (; i < base_keys->val[k].keys.len; i++) + e->etypes->val[i] = base_keys->val[k].keys.val[i].key.keytype; + } + return 0; +} + krb5_error_code _hdb_store(krb5_context context, HDB *db, unsigned flags, hdb_entry_ex *entry) { @@ -410,7 +475,11 @@ _hdb_store(krb5_context context, HDB *db, unsigned flags, hdb_entry_ex *entry) return code ? code : HDB_ERR_EXISTS; } - if(entry->entry.generation == NULL) { + if ((entry->entry.etypes == NULL || entry->entry.etypes->len == 0) && + (code = hdb_derive_etypes(context, &entry->entry, NULL))) + return code; + + if (entry->entry.generation == NULL) { struct timeval t; entry->entry.generation = malloc(sizeof(*entry->entry.generation)); if(entry->entry.generation == NULL) { @@ -485,3 +554,901 @@ _hdb_remove(krb5_context context, HDB *db, return code; } +/* PRF+(K_base, pad, keylen(etype)) */ +static krb5_error_code +derive_Key1(krb5_context context, + krb5_data *pad, + EncryptionKey *base, + krb5int32 etype, + EncryptionKey *nk) +{ + krb5_error_code ret; + krb5_crypto crypto = NULL; + krb5_data out; + size_t len; + + out.data = 0; + out.length = 0; + + ret = krb5_enctype_keysize(context, base->keytype, &len); + if (ret == 0) + ret = krb5_crypto_init(context, base, 0, &crypto); + if (ret == 0) + ret = krb5_crypto_prfplus(context, crypto, pad, len, &out); + if (crypto) + krb5_crypto_destroy(context, crypto); + if (ret == 0) + ret = krb5_random_to_key(context, etype, out.data, out.length, nk); + krb5_data_free(&out); + return ret; +} + +/* PRF+(PRF+(K_base, princ, keylen(etype)), kvno, keylen(etype)) */ +/* XXX Make it PRF+(PRF+(K_base, princ, keylen(K_base.etype)), and lift it, kvno, keylen(etype)) */ +static krb5_error_code +derive_Key(krb5_context context, + const char *princ, + krb5uint32 kvno, + EncryptionKey *base, + krb5int32 etype, + Key *nk) +{ + krb5_error_code ret = 0; + EncryptionKey intermediate; + krb5_data pad; + + nk->salt = NULL; + nk->mkvno = NULL; + nk->key.keytype = 0; + nk->key.keyvalue.data = 0; + nk->key.keyvalue.length = 0; + + intermediate.keytype = 0; + intermediate.keyvalue.data = 0; + intermediate.keyvalue.length = 0; + if (princ) { + /* Derive intermediate key for the given principal */ + /* XXX Lift to optimize? */ + pad.data = (void *)(uintptr_t)princ; + pad.length = strlen(princ); + ret = derive_Key1(context, &pad, base, etype, &intermediate); + if (ret == 0) + base = &intermediate; + } /* else `base' is already an intermediate key for the desired princ */ + + /* Derive final key for `kvno' from intermediate key */ + kvno = htonl(kvno); + pad.data = &kvno; + pad.length = sizeof(kvno); + if (ret == 0) + ret = derive_Key1(context, &pad, base, etype, &nk->key); + free_EncryptionKey(&intermediate); + return ret; +} + +/* + * PRF+(PRF+(K_base, princ, keylen(etype)), kvno, keylen(etype)) for one + * enctype. + */ +static krb5_error_code +derive_Keys(krb5_context context, + const char *princ, + krb5uint32 kvno, + krb5int32 etype, + const Keys *base, + Keys *dk) + +{ + krb5_error_code ret = 0; + size_t i; + Key nk; + + dk->len = 0; + dk->val = 0; + + /* + * The enctypes of the base keys is the list of enctypes to derive keys + * for. Still, we derive all keys from the first base key. + */ + for (i = 0; ret == 0 && i < base->len; i++) { + if (etype != KRB5_ENCTYPE_NULL && etype != base->val[i].key.keytype) + continue; + ret = derive_Key(context, princ, kvno, &base->val[0].key, + base->val[i].key.keytype, &nk); + if (ret) + break; + ret = add_Keys(dk, &nk); + free_Key(&nk); + /* + * FIXME We need to finish kdc/kadm5/kadmin support for the `etypes' so + * we can reduce the number of keys in keytabs to just those in current + * use and only of *one* enctype. + * + * What we could do is derive *one* key and for the others output a + * one-byte key of the intended enctype (which will never work). + * + * We'll never need any keys but the first one... + */ + } + + if (ret) + free_Keys(dk); + return ret; +} + +/* Helper for derive_keys_for_kr() */ +static krb5_error_code +derive_keyset(krb5_context context, + const Keys *base_keys, + const char *princ, + krb5int32 etype, + krb5uint32 kvno, + KerberosTime set_time, /* "now" */ + hdb_keyset *dks) +{ + dks->kvno = kvno; + dks->keys.val = 0; + dks->set_time = malloc(sizeof(dks->set_time)); + if (dks->set_time == NULL) + return krb5_enomem(context); + *dks->set_time = set_time; + return derive_Keys(context, princ, kvno, etype, base_keys, &dks->keys); +} + +/* Possibly derive and install in `h' a keyset identified by `t' */ +static krb5_error_code +derive_keys_for_kr(krb5_context context, + hdb_entry_ex *h, + HDB_Ext_KeySet *base_keys, + int is_current_keyset, + int rotation_period_offset, + const char *princ, + krb5int32 etype, + krb5uint32 kvno_wanted, + KerberosTime t, + struct KeyRotation *krp) +{ + krb5_error_code ret; + hdb_keyset dks; + KerberosTime set_time, n; + krb5uint32 kvno; + size_t i; + + if (rotation_period_offset < -1 || rotation_period_offset > 1) + return EINVAL; /* wat */ + + /* + * Compute `kvno' and `set_time' given `t' and `krp'. + * + * There be signed 32-bit time_t dragons here. + * + * (t - krp->epoch < 0) is better than (krp->epoch < t), making us more + * tolerant of signed 32-bit time_t here near 2038. Of course, we have + * signed 32-bit time_t dragons elsewhere. + */ + if (t - krp->epoch < 0) + return 0; /* This KR is not relevant yet */ + n = (t - krp->epoch) / krp->period; + n += rotation_period_offset; + set_time = krp->epoch + krp->period * n; + kvno = krp->base_kvno + n; + + + /* + * Do not waste cycles computing keys not wanted or needed. + * A past kvno is too old if its set_time + rotation period is in the past + * by more than half a rotation period, since then no service ticket + * encrypted with keys of that kvno can still be extant. + * + * A future kvno is not coming up soon enough if we're more than a quarter + * of the rotation period away from it. + * + * Recall: the assumption for virtually-keyed principals is that services + * fetch their future keys frequently enough that they'll never miss having + * the keys they need. + */ + if (!is_current_keyset || rotation_period_offset != 0) { + if ((kvno_wanted && kvno != kvno_wanted) || + t - (set_time + krp->period + (krp->period >> 1)) > 0 || + (set_time - t > 0 && (set_time - t) > (krp->period >> 2))) + return 0; + } + + for (i = 0; i < base_keys->len; i++) { + if (base_keys->val[i].kvno == krp->base_key_kvno) + break; + } + if (i == base_keys->len) { + /* Base key not found! */ + if (kvno_wanted || is_current_keyset) { + krb5_set_error_message(context, ret = HDB_ERR_KVNO_NOT_FOUND, + "Base key version %u not found for %s", + krp->base_key_kvno, princ); + return ret; + } + return 0; + } + + ret = derive_keyset(context, &base_keys->val[i].keys, princ, etype, kvno, + set_time, &dks); + if (ret == 0) + ret = hdb_install_keyset(context, &h->entry, is_current_keyset, &dks); + + free_hdb_keyset(&dks); + return ret; +} + +/* Derive and install current keys, and possibly preceding or next keys */ +static krb5_error_code +derive_keys_for_current_kr(krb5_context context, + hdb_entry_ex *h, + HDB_Ext_KeySet *base_keys, + const char *princ, + unsigned int flags, + krb5int32 etype, + krb5uint32 kvno_wanted, + KerberosTime t, + struct KeyRotation *krp, + KerberosTime future_epoch) +{ + krb5_error_code ret; + + /* derive_keys_for_kr() for current kvno and install as the current keys */ + ret = derive_keys_for_kr(context, h, base_keys, 1, 0, princ, etype, + kvno_wanted, t, krp); + if (!(flags & HDB_F_ALL_KVNOS)) + return ret; + + /* */ + + + /* + * derive_keys_for_kr() for prev kvno if still needed -- it can only be + * needed if the prev kvno's start time is within this KR's epoch. + * + * Note that derive_keys_for_kr() can return without doing anything if this + * is isn't the current keyset. So these conditions need not be + * sufficiently narrow. + */ + if (ret == 0 && t - krp->epoch >= krp->period) + ret = derive_keys_for_kr(context, h, base_keys, 0, -1, princ, etype, + kvno_wanted, t, krp); + /* + * derive_keys_for_kr() for next kvno if near enough, but only if it + * doesn't start after the next KR's epoch. + */ + if (future_epoch && + t - krp->epoch >= 0 /* We know! Hint to the compiler */) { + KerberosTime next_kvno_start, n; + + n = (t - krp->epoch) / krp->period; + next_kvno_start = krp->epoch + krp->period * (n + 1); + if (future_epoch - next_kvno_start <= 0) + return ret; + } + if (ret == 0) + ret = derive_keys_for_kr(context, h, base_keys, 0, 1, princ, etype, + kvno_wanted, t, krp); + return ret; +} + +/* + * Derive and install all keysets in `h' that `princ' needs at time `now'. + * + * This mutates the entry `h' to + * + * a) not have base keys, + * b) have keys derived from the base keys according to + * c) the key rotation periods for the base principal (possibly the same + * principal if it's a concrete principal with virtual keys), and the + * requested time, enctype, and kvno (all of which are optional, with zero + * implying some default). + * + * Arguments: + * + * - `flags' is the flags passed to `hdb_fetch_kvno()' + * - `princ' is the name of the principal we'll end up with in `h->entry' + * - `h_is_namespace' indicates whether `h' is for a namespace or a concrete + * principal (that might nonetheless have virtual/derived keys) + * - `t' is the time such that the derived keys are for kvnos needed at `t' + * - `etype' indicates what enctype to derive keys for (0 for all enctypes in + * `h->entry.etypes') + * - `kvno' requests a particular kvno, or all if zero + * + * The caller doesn't know if the principal needs key derivation -- we make + * that determination in this function. + * + * Note that this function is fully deterministic for any given set of + * arguments and HDB contents. + * + * Definitions: + * + * - A keyset is a set of keys for a single kvno. + * - A keyset is relevant IFF: + * - it is the keyset for a time period identified by `t' in a + * corresponding KR + * - it is a keyset for a past time period for which there may be extant, + * not-yet-expired tickets that a service may need to decrypt + * - it is a keyset for an upcoming time period that a service will need to + * fetch before that time period becomes current, that way the service + * can have keytab entries for those keys in time for when the KDC starts + * encrypting service tickets to those keys + * + * This function derives the keyset(s) for the current KR first. The idea is + * to optimize the order of resulting keytabs so that the most likely keys to + * be used come first. + * + * Invariants: + * + * - KR metadata is sane because sanity is checked for when storing HDB + * entries + * - KRs are sorted by epoch in descending order; KR #0's epoch is the most + * recent + * - KR periods are non-zero (we divide by period) + * - kvnos are numerically ordered and correspond to time periods + * - within each KR, the kvnos for larger times are larger than (or equal + * to) the kvnos of earlier times + * - at KR boundaries, the first kvno of the newer boundary is larger than + * the kvno of the last time period of the previous KR + * - the time `t' must fall into exactly one KR period + * - the time `t' must fall into exactly one period within a KR period + * - at most two kvnos will be relevant from the KR that `t' falls into + * (the current kvno for `t', and possibly either the preceding, or the + * next) + * - at most one kvno from non-current KRs will be derived: possibly one for a + * preceding KR, and possibly one from an upcoming KR + * + * There can be: + * + * - no KR extension (not a namespace principal, and no virtual keys) + * - 1, 2, or 3 KRs (see above) + * - the newest KR may have the `deleted' flag, meaning "does not exist after + * this epoch" + * + * Note that the last time period in any older KR can be partial. + * + * Timeline diagram: + * + * .......|--+--+...+--|---+---+---+...+--|----+... + * T20 T10 T11 RT12 T1n T01 + * ^ ^ ^ ^ ^ ^ ^ T00 + * | | | T22 T2n | | ^ + * ^ | T21 | | | + * princ | | epoch of | epoch of + * did | | middle KR | newest epoch + * not | | | + * exist! | start of Note that T1n + * | second kvno is shown as shorter + * | in 1st epoch than preceding periods + * | + * ^ + * first KR's + * epoch, and start + * of its first kvno + * + * Tmn == the start of the Mth KR's Nth time period. + * (higher M -> older KR; lower M -> newer KR) + * (N is the reverse: lower N -> older time period in KR) + * T20 == start of oldest KR -- no keys before this time will be derived. + * T2n == last time period in oldest KR + * T10 == start of middle KR + * T1n == last time period in middle KR + * T00 == start of newest KR + * T0n == current time period in newest KR for wall clock time + */ +static krb5_error_code +derive_keys(krb5_context context, + unsigned flags, + krb5_const_principal princ, + int h_is_namespace, + krb5_timestamp t, + krb5int32 etype, + krb5uint32 kvno, + hdb_entry_ex *h) +{ + HDB_Ext_KeyRotation kr; + HDB_Ext_KeySet base_keys; + krb5_error_code ret = 0; + size_t current_kr, future_kr, past_kr, i; + char *p = NULL; + int valid = 1; + + if (!h_is_namespace && !h->entry.flags.virtual_keys) + return 0; + h->entry.flags.virtual = 1; + if (h_is_namespace) { + /* Set the entry's principal name */ + free_Principal(h->entry.principal); + ret = copy_Principal(princ, h->entry.principal); + } + + kr.len = 0; + kr.val = 0; + if (ret == 0) { + const HDB_Ext_KeyRotation *ckr; + + /* Installing keys invalidates `ckr', so we copy it */ + ret = hdb_entry_get_key_rotation(context, &h->entry, &ckr); + if (ret == 0) + ret = copy_HDB_Ext_KeyRotation(ckr, &kr); + } + + /* Get the base keys from the entry, and remove them */ + base_keys.val = 0; + base_keys.len = 0; + if (ret == 0) + ret = hdb_remove_base_keys(context, &h->entry, &base_keys); + + /* Make sure we have h->entry.etypes */ + if (ret == 0 && !h->entry.etypes) + ret = hdb_derive_etypes(context, &h->entry, &base_keys); + + /* Keys not desired? Don't derive them! */ + if (ret || !(flags & HDB_F_DECRYPT)) { + free_HDB_Ext_KeyRotation(&kr); + free_HDB_Ext_KeySet(&base_keys); + return ret; + } + + /* The principal name will be used in key derivation and error messages */ + if (ret == 0 && h_is_namespace) + ret = krb5_unparse_name(context, princ, &p); + + /* Sanity check key rotations, determine current & last kr */ + if (ret == 0 && kr.len < 1) + krb5_set_error_message(context, ret = HDB_ERR_NOENTRY, + "no key rotation periods for %s", p); + if (ret == 0) + current_kr = future_kr = past_kr = kr.len; + else + current_kr = future_kr = past_kr = 1; + + /* + * Identify a current, next, and previous KRs if there are any. + * + * There can be up to three KRs, ordered by epoch, descending, making up a + * timeline like: + * + * ...|---------|--------|------> + * ^ | | | + * | | | | + * | | | Newest KR (kr.val[0]) + * | | Middle KR (kr.val[1]) + * | Oldest (last) KR (kr.val[2]) + * | + * Before the begging of time for this namespace + * + * We step through these from future towards past looking for the best + * future, current, and past KRs. The best current KR is one that has its + * epoch nearest to `t' but in the past of `t'. + * + * We validate KRs before storing HDB entries with the KR extension, so we + * can assume they are valid here. However, we do some validity checking, + * and if they're not valid, we pick the best current KR and ignore the + * others. + * + * In principle there cannot be two future KRs, but this function is + * deterministic and takes a time value, so it should not enforce this just + * so we can test. Enforcement of such rules should be done at store time. + */ + for (i = 0; ret == 0 && i < kr.len; i++) { + /* Minimal validation: order and period */ + if (i && kr.val[i - 1].epoch - kr.val[i].epoch <= 0) { + future_kr = past_kr = kr.len; + valid = 0; + } + if (!kr.val[i].period) { + future_kr = past_kr = kr.len; + valid = 0; + continue; + } + if (t - kr.val[i].epoch >= 0) { + /* + * `t' is in the future of this KR's epoch, so it's a candidate for + * either current or past KR. + */ + if (current_kr == kr.len) + current_kr = i; /* First curr KR candidate; should be best */ + else if (kr.val[current_kr].epoch - kr.val[i].epoch < 0) + current_kr = i; /* Invalid KRs, but better curr KR cand. */ + else if (valid && past_kr == kr.len) + past_kr = i; + } else if (valid) { + /* This KR is in the future of `t', a candidate for next KR */ + future_kr = i; + } + } + if (ret == 0 && current_kr == kr.len) + /* No current KR -> too soon */ + krb5_set_error_message(context, ret = HDB_ERR_NOENTRY, + "Too soon for virtual principal to exist"); + + /* Check that the principal has not been marked deleted */ + if (ret == 0 && current_kr < kr.len && kr.val[current_kr].flags.deleted) + krb5_set_error_message(context, ret = HDB_ERR_NOENTRY, + "virtual principal %s does not exist " + "because last key rotation period " + "marks deletion", p); + + /* + * Derive and set in `h' its current kvno and current keys. + * + * This will set h->entry.kvno as well. + * + * This may set up to TWO keysets for the current key rotation period: + * - current keys (h->entry.keys and h->entry.kvno) + * - possibly one future + * OR + * possibly one past keyset in hist_keys for the current_kr + */ + if (ret == 0 && current_kr < kr.len) + ret = derive_keys_for_current_kr(context, h, &base_keys, p, flags, + etype, kvno, t, &kr.val[current_kr], + current_kr ? kr.val[0].epoch : 0); + + /* + * Derive and set in `h' its future keys for next KR if it is soon to be + * current. + * + * We want to derive keys for the first kvno of the next (future) KR if + * it's sufficiently close to `t', meaning within 1 period of the current + * KR, but we want these keys to be available sooner, so 1.5 of the current + * period. + */ + if (ret == 0 && future_kr < kr.len && (flags & HDB_F_ALL_KVNOS)) + ret = derive_keys_for_kr(context, h, &base_keys, 0, 0, p, etype, kvno, + kr.val[future_kr].epoch, &kr.val[future_kr]); + + /* + * Derive and set in `h' its past keys for the previous KR if its last time + * period could still have extant, unexpired service tickets encrypted in + * its keys. + */ + if (ret == 0 && past_kr < kr.len && (flags & HDB_F_ALL_KVNOS)) + ret = derive_keys_for_kr(context, h, &base_keys, 0, 0, p, etype, kvno, + kr.val[current_kr].epoch - 1, &kr.val[past_kr]); + + /* + * Impose a bound on h->entry.max_life so that [when the KDC is the caller] + * the KDC won't issue tickets longer lived than this. + */ + if (ret == 0 && !h->entry.max_life && + (h->entry.max_life = malloc(sizeof(h->entry.max_life[0]))) == NULL) + ret = krb5_enomem(context); + if (ret == 0 && *h->entry.max_life > kr.val[current_kr].period >> 1) + *h->entry.max_life = kr.val[current_kr].period >> 1; + + free_HDB_Ext_KeyRotation(&kr); + free_HDB_Ext_KeySet(&base_keys); + free(p); + return ret; +} + +/* + * In order for disparate keytab provisioning systems such as OSKT and our own + * kadmin ext_keytab and httpkadmind's get-keys to coexist, we need to be able + * to force keys set by the former to not become current keys until users of + * the latter have had a chance to fetch those keys into their keytabs. To do + * this we have to search the list of keys in the entry looking for the newest + * keys older than `now - db->new_service_key_delay'. + * + * The context is that OSKT's krb5_keytab is very happy to change keys in a way + * that requires all members of a cluster to rekey together. If one also + * wishes to have cluster members that opt out of this and just fetch current, + * past, and future keys periodically, then the keys set by OSKT need to not + * come into effect until all the opt-out members have had a chance to fetch + * the new keys. + * + * The assumption is that services will fetch new keys periodically, say, every + * four hours. Then one can set `[hdb] new_service_key_delay = 8h' in the + * configuration and new keys set by OSKT will not be used until 8h after they + * are set. + * + * Naturally, this applies only to concrete principals with concrete keys. + */ +static krb5_error_code +fix_keys(krb5_context context, + HDB *db, + unsigned flags, + krb5_timestamp now, + krb5uint32 kvno, + hdb_entry_ex *h) +{ + HDB_extension *ext; + HDB_Ext_KeySet keys; + time_t current = 0; + time_t best; + size_t i; + + /* + * If we want a specific kvno, or if we're not decrypting the keys, or if + * there's no new-key delay, then we're out. + */ + if (!(flags & HDB_F_DECRYPT) || kvno || h->entry.flags.virtual || + h->entry.flags.virtual_keys || db->new_service_key_delay <= 0) + return 0; + + /* No history -> current keyset is the only one and therefore the best */ + ext = hdb_find_extension(&h->entry, choice_HDB_extension_data_hist_keys); + if (!ext) + return 0; + + /* Assume the current keyset is the best to start with */ + (void) hdb_entry_get_pw_change_time(&h->entry, ¤t); + if (current == 0 && h->entry.modified_by) + current = h->entry.modified_by->time; + if (current == 0) + current = h->entry.created_by.time; + + /* Current keyset starts out as best */ + best = current; + kvno = h->entry.kvno; + + /* Look for a better keyset in the history */ + keys = ext->data.u.hist_keys; + for (i = 0; i < keys.len; i++) { + /* No set_time? Ignore. Too new? Ignore */ + if (!keys.val[i].set_time || + keys.val[i].set_time[0] + db->new_service_key_delay > now) + continue; + + /* + * Ignore the keyset with kvno 1 when the entry is at 2 because + * kadmin's `ank -r' command immediately changes the keys. + */ + if (h->entry.kvno == 2 && keys.val[i].kvno == 1 && + keys.val[i].set_time[0] - best < 30) + continue; + + /* + * This keyset's set_time older than the previous best? Ignore. + * However, if the current best is the entry's current and that one + * is too new, then don't ignore this one. + */ + if (keys.val[i].set_time[0] < best && + (best != current || current + db->new_service_key_delay < now)) + continue; + + /* + * If two good enough keysets have the same set_time, take the keyset + * with the highest kvno. + */ + if (keys.val[i].set_time[0] == best && keys.val[i].kvno <= kvno) + continue; + + /* + * This keyset is clearly more current than the previous best keyset + * but still old enough to use for encrypting tickets with. + */ + best = keys.val[i].set_time[0]; + kvno = keys.val[i].kvno; + } + return hdb_change_kvno(context, kvno, &h->entry); +} + +/* + * Make a WELLKNOWN/HOSTBASED-NAMESPACE/${svc}/${hostname} or + * WELLKNOWN/HOSTBASED-NAMESPACE/${svc}/${hostname}/${domainname} principal + * object, with the service and hostname components take from `wanted', but if + * the service name is not in the list `db->virtual_hostbased_princ_svcs[]' + * then use "_" (wildcard) instead. This way we can have different attributes + * for different services in the same namespaces. + * + * For example, virtual hostbased service names for the "host" service might + * have ok-as-delegate set, but ones for the "HTTP" service might not. + */ +static krb5_error_code +make_namespace_princ(krb5_context context, + HDB *db, + krb5_const_principal wanted, + krb5_principal *namespace) +{ + krb5_error_code ret = 0; + const char *realm = krb5_principal_get_realm(context, wanted); + const char *comp0 = krb5_principal_get_comp_string(context, wanted, 0); + const char *comp1 = krb5_principal_get_comp_string(context, wanted, 1); + const char *comp2 = krb5_principal_get_comp_string(context, wanted, 2); + char * const *svcs = db->virtual_hostbased_princ_svcs; + size_t i; + + *namespace = NULL; + if (comp0 == NULL || comp1 == NULL) + return EINVAL; + if (strcmp(comp0, "krbtgt") == 0) + return 0; + + for (i = 0; svcs && svcs[i]; i++) { + if (strcmp(comp0, svcs[i]) == 0) { + comp0 = svcs[i]; + break; + } + } + if (!svcs || !svcs[i]) + comp0 = "_"; + + /* First go around, need a namespace princ. Make it! */ + ret = krb5_build_principal(context, namespace, strlen(realm), + realm, "WELLKNOWN", + HDB_WK_NAMESPACE, comp0, NULL); + if (ret == 0) + ret = krb5_principal_set_comp_string(context, *namespace, 3, comp1); + if (ret == 0 && comp2) + /* Support domain-based names */ + ret = krb5_principal_set_comp_string(context, *namespace, 4, comp2); + /* Caller frees `*namespace' on error */ + return ret; +} + +/* Wrapper around db->hdb_fetch_kvno() that implements virtual princs/keys */ +static krb5_error_code +fetch_it(krb5_context context, + HDB *db, + krb5_const_principal princ, + unsigned flags, + krb5_timestamp t, + krb5int32 etype, + krb5uint32 kvno, + hdb_entry_ex *ent) +{ + krb5_const_principal tmpprinc = princ; + krb5_principal baseprinc = NULL; + krb5_error_code ret = 0; + const char *comp0 = krb5_principal_get_comp_string(context, princ, 0); + const char *comp1 = krb5_principal_get_comp_string(context, princ, 1); + const char *tmp; + size_t mindots = db->virtual_hostbased_princ_ndots; + size_t maxdots = db->virtual_hostbased_princ_maxdots; + size_t hdots = 0; + char *host = NULL; + int do_search = 0; + + if (db->enable_virtual_hostbased_princs && comp1 && + strcmp("krbtgt", comp0) != 0 && strcmp("WELLKNOWN", comp0) != 0) { + char *htmp; + + if ((host = strdup(comp1)) == NULL) + return krb5_enomem(context); + + /* Strip out any :port */ + htmp = strchr(host, ':'); + if (htmp) { + if (strchr(htmp + 1, ':')) { + /* Extra ':'s? No virtualization for you! */ + free(host); + host = NULL; + htmp = NULL; + } else { + *htmp = '\0'; + } + } + /* Count dots in `host' */ + for (hdots = 0, htmp = host; htmp && *htmp; htmp++) + if (*htmp == '.') + hdots++; + + do_search = 1; + } + + tmp = host ? host : comp1; + for (ret = HDB_ERR_NOENTRY; ret == HDB_ERR_NOENTRY; tmpprinc = baseprinc) { + krb5_error_code ret2 = 0; + + /* + * We break out of this loop with ret == 0 only if we found the HDB + * entry we were looking for or the HDB entry for a matching namespace. + * + * Otherwise we break out with ret != 0, typically HDB_ERR_NOENTRY. + * + * First time through we lookup the principal as given. + * + * Next we lookup a namespace principal, stripping off hostname labels + * from the left until we find one or get tired of looking or run out + * of labels. + */ + ret = db->hdb_fetch_kvno(context, db, tmpprinc, flags, kvno, ent); + if (ret != HDB_ERR_NOENTRY || hdots == 0 || hdots < mindots || !tmp || + !do_search) + break; + + /* + * Breadcrumb: + * + * - if we found a concrete principal, but it's been marked + * as now-virtual, then we must keep going + * + * But this will be coded in the future. + * + * Maybe we can take attributes from the concrete principal... + */ + + /* + * The namespace's hostname will not have more labels than maxdots + 1. + * Thus we truncate immediately down to maxdots + 1 if we haven't yet. + * + * Example: with maxdots == 3, + * foo.bar.baz.app.blah.example -> baz.app.blah.example + */ + while (maxdots && hdots > maxdots && tmp) { + tmp = strchr(tmp, '.'); + /* tmp != NULL because maxdots > 0 */ + tmp++; + hdots--; + } + + if (baseprinc == NULL) + /* First go around, need a namespace princ. Make it! */ + ret2 = make_namespace_princ(context, db, tmpprinc, &baseprinc); + /* Update the hostname component */ + if (ret2 == 0) + ret2 = krb5_principal_set_comp_string(context, baseprinc, 3, tmp); + if (ret2) + ret = ret2; + + if (tmp) { + /* Strip off left-most label for the next go-around */ + if ((tmp = strchr(tmp, '.'))) + tmp++; + hdots--; + } /* else we'll break out after the next db->hdb_fetch_kvno() call */ + } + + /* + * If unencrypted keys were requested, derive them. There may not be any + * key derivation to do, but that's decided in derive_keys(). + */ + if (ret == 0) { + ret = derive_keys(context, flags, princ, !!baseprinc, t, etype, kvno, + ent); + if (ret == 0) + ret = fix_keys(context, db, flags, t, kvno, ent); + if (ret) + hdb_free_entry(context, ent); + } + krb5_free_principal(context, baseprinc); + free(host); + return ret; +} + +/** + * Fetch a principal's HDB entry, possibly generating virtual keys from base + * keys according to strict key rotation schedules. If a time is given, other + * than HDB I/O, this function is pure, thus usable for testing. + * + * HDB writers should use `db->hdb_fetch_kvno()' to avoid materializing virtual + * principals. + * + * HDB readers should use this function rather than `db->hdb_fetch_kvno()' + * unless they only want to see concrete principals and not bother generating + * any virtual keys. + * + * @param context Context + * @param db HDB + * @param principal Principal name + * @param flags Fetch flags + * @param t For virtual keys, use this as the point in time (use zero to mean "now") + * @param etype Key enctype (use KRB5_ENCTYPE_NULL to mean "preferred") + * @param kvno Key version number (use zero to mean "current") + * @param h Output HDB entry + * + * @return Zero on success, an error code otherwise. + */ +krb5_error_code +hdb_fetch_kvno(krb5_context context, + HDB *db, + krb5_const_principal principal, + unsigned int flags, + krb5_timestamp t, + krb5int32 etype, + krb5uint32 kvno, + hdb_entry_ex *h) +{ + krb5_error_code ret = HDB_ERR_NOENTRY; + + flags |= kvno ? HDB_F_KVNO_SPECIFIED : 0; /* XXX is this needed */ + if (t == 0) + krb5_timeofday(context, &t); + ret = fetch_it(context, db, principal, flags, t, etype, kvno, h); + if (ret == HDB_ERR_NOENTRY) + krb5_set_error_message(context, ret, "no such entry found in hdb"); + return ret; +} diff --git a/lib/hdb/db.c b/lib/hdb/db.c index 4cee8d009..6e415b95f 100644 --- a/lib/hdb/db.c +++ b/lib/hdb/db.c @@ -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; diff --git a/lib/hdb/db3.c b/lib/hdb/db3.c index 0d41369d7..0daa25bbe 100644 --- a/lib/hdb/db3.c +++ b/lib/hdb/db3.c @@ -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; diff --git a/lib/hdb/ext.c b/lib/hdb/ext.c index ecefe931b..531ffb4d2 100644 --- a/lib/hdb/ext.c +++ b/lib/hdb/ext.c @@ -86,7 +86,6 @@ hdb_replace_extension(krb5_context context, const HDB_extension *ext) { HDB_extension *ext2; - HDB_extension *es; int ret; ext2 = NULL; @@ -157,22 +156,7 @@ hdb_replace_extension(krb5_context context, return ret; } - es = realloc(entry->extensions->val, - (entry->extensions->len+1)*sizeof(entry->extensions->val[0])); - if (es == NULL) { - krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); - return ENOMEM; - } - entry->extensions->val = es; - - ret = copy_HDB_extension(ext, - &entry->extensions->val[entry->extensions->len]); - if (ret == 0) - entry->extensions->len++; - else - krb5_set_error_message(context, ret, "hdb: failed to copy new extension"); - - return ret; + return add_HDB_extensions(entry->extensions, ext); } krb5_error_code @@ -186,13 +170,8 @@ hdb_clear_extension(krb5_context context, return 0; for (i = 0; i < entry->extensions->len; i++) { - if (entry->extensions->val[i].data.element == (unsigned)type) { - free_HDB_extension(&entry->extensions->val[i]); - memmove(&entry->extensions->val[i], - &entry->extensions->val[i + 1], - sizeof(entry->extensions->val[i]) * (entry->extensions->len - i - 1)); - entry->extensions->len--; - } + if (entry->extensions->val[i].data.element == (unsigned)type) + (void) remove_HDB_extensions(entry->extensions, i); } if (entry->extensions->len == 0) { free(entry->extensions->val); @@ -246,6 +225,33 @@ hdb_entry_get_pkinit_cert(const hdb_entry *entry, const HDB_Ext_PKINIT_cert **a) return 0; } +krb5_error_code +hdb_entry_get_krb5_config(const hdb_entry *entry, heim_octet_string *c) +{ + const HDB_extension *ext; + + c->data = NULL; + c->length = 0; + ext = hdb_find_extension(entry, choice_HDB_extension_data_krb5_config); + if (ext) + *c = ext->data.u.krb5_config; + return 0; +} + +krb5_error_code +hdb_entry_set_krb5_config(krb5_context context, + hdb_entry *entry, + heim_octet_string *s) +{ + HDB_extension ext; + + ext.mandatory = FALSE; + ext.data.element = choice_HDB_extension_data_last_pw_change; + /* hdb_replace_extension() copies this, so no need to copy it here */ + ext.data.u.krb5_config = *s; + return hdb_replace_extension(context, entry, &ext); +} + krb5_error_code hdb_entry_get_pw_change_time(const hdb_entry *entry, time_t *t) { @@ -530,3 +536,251 @@ hdb_set_last_modified_by(krb5_context context, hdb_entry *entry, return 0; } +krb5_error_code +hdb_entry_get_key_rotation(krb5_context context, + const hdb_entry *entry, + const HDB_Ext_KeyRotation **kr) +{ + HDB_extension *ext = + hdb_find_extension(entry, choice_HDB_extension_data_key_rotation); + + *kr = ext ? &ext->data.u.key_rotation : NULL; + return 0; +} + +krb5_error_code +hdb_validate_key_rotation(krb5_context context, + const KeyRotation *past_kr, + const KeyRotation *new_kr) +{ + unsigned int last_kvno; + + if (new_kr->period < 1) { + krb5_set_error_message(context, EINVAL, + "Key rotation periods must be non-zero " + "and positive"); + return EINVAL; + } + if (new_kr->base_key_kvno < 1 || new_kr->base_kvno < 1) { + krb5_set_error_message(context, EINVAL, + "Key version number zero not allowed " + "for key rotation"); + return EINVAL; + } + if (!past_kr) + return 0; + + if (past_kr->base_key_kvno == new_kr->base_key_kvno) { + /* + * The new base keys can be the same as the old, but must have + * different kvnos. (Well, not must must. It's a convention for now.) + */ + krb5_set_error_message(context, EINVAL, + "Base key version numbers for KRs must differ"); + return EINVAL; + } + if (new_kr->epoch - past_kr->epoch <= 0) { + krb5_set_error_message(context, EINVAL, + "New key rotation periods must start later " + "than existing ones"); + return EINVAL; + } + + last_kvno = 1 + ((new_kr->epoch - past_kr->epoch) / past_kr->period); + if (new_kr->base_kvno <= last_kvno) { + krb5_set_error_message(context, EINVAL, + "New key rotation base kvno must be larger " + "the last kvno for the current key " + "rotation (%u)", last_kvno); + return EINVAL; + } + return 0; +} + +static int +kr_eq(const KeyRotation *a, const KeyRotation *b) +{ + return !!( + a->epoch == b->epoch && + a->period == b->period && + a->base_kvno == b->base_kvno && + a->base_key_kvno == b->base_key_kvno && + KeyRotationFlags2int(a->flags) == KeyRotationFlags2int(b->flags) + ); +} + +krb5_error_code +hdb_validate_key_rotations(krb5_context context, + const HDB_Ext_KeyRotation *existing, + const HDB_Ext_KeyRotation *krs) +{ + krb5_error_code ret = 0; + size_t added = 0; + size_t i; + + if ((!existing || !existing->len) && (!krs || !krs->len)) + return 0; /* Nothing to do; weird */ + + /* + * HDB_Ext_KeyRotation has to have 1..3 elements, and this is enforced by + * the ASN.1 compiler and the code it generates. Nonetheless we'll check + * that there's not zero elements. + */ + if ((!krs || !krs->len)) { + /* + * NOTE: We can clear this on concrete principals with virtual keys + * though. The caller can check for that case. + */ + krb5_set_error_message(context, EINVAL, + "Cannot clear key rotation metadata on " + "virtual principal namespaces"); + ret = EINVAL; + } + + /* Validate the new KRs by themselves */ + for (i = 0; ret == 0 && i < krs->len; i++) { + ret = hdb_validate_key_rotation(context, + i+1 < krs->len ? &krs->val[i+1] : 0, + &krs->val[i]); + } + if (ret || !existing || !existing->len) + return ret; + + if (existing->len == krs->len) { + /* Check for no change */ + for (i = 0; i < krs->len; i++) + if (!kr_eq(&existing->val[i], &krs->val[i])) + break; + if (i == krs->len) + return 0; /* No change */ + } + + /* + * Check that new KRs make sense in the context of the previous KRs. + * + * Permitted changes: + * + * - add one new KR in front + * - drop old KRs + * + * Start by checking if we're adding a KR, then go on to check for dropped + * KRs and/or last KR alteration. + */ + if (existing->val[0].epoch == krs->val[0].epoch || + existing->val[0].base_kvno == krs->val[0].base_kvno) { + if (!kr_eq(&existing->val[0], &krs->val[0])) { + krb5_set_error_message(context, EINVAL, + "Key rotation change not sensible"); + ret = EINVAL; + } + /* Key rotation *not* added */ + } else { + /* Key rotation added; check it first */ + ret = hdb_validate_key_rotation(context, + &existing->val[0], + &krs->val[0]); + added = 1; + } + for (i = 0; ret == 0 && i < existing->len && i + added < krs->len; i++) + if (!kr_eq(&existing->val[i], &krs->val[i + added])) + krb5_set_error_message(context, ret = EINVAL, + "Only last key rotation may be truncated"); + return ret; +} + +/* XXX We need a function to "revoke" the past */ + +/** + * This function adds a KeyRotation value to an entry, validating the + * change. One of `entry' and `krs' must be NULL, and the other non-NULL, and + * whichever is given will be altered. + * + * @param context Context + * @param entry An HDB entry + * @param krs A key rotation extension for hdb_entry + * @param kr A new KeyRotation value + * + * @return Zero on success, an error otherwise. + */ +krb5_error_code +hdb_entry_add_key_rotation(krb5_context context, + hdb_entry *entry, + HDB_Ext_KeyRotation *krs, + const KeyRotation *kr) +{ + krb5_error_code ret; + HDB_extension new_ext; + HDB_extension *ext = 0; + KeyRotation tmp; + size_t i, sz; + + if (kr->period < 1) { + krb5_set_error_message(context, EINVAL, + "Key rotation period cannot be zero"); + return EINVAL; + } + + new_ext.mandatory = TRUE; + new_ext.data.element = choice_HDB_extension_data_key_rotation; + new_ext.data.u.key_rotation.len = 0; + new_ext.data.u.key_rotation.val = 0; + + if (entry && krs) + return EINVAL; + + if (entry) { + ext = hdb_find_extension(entry, choice_HDB_extension_data_key_rotation); + if (!ext) + ext = &new_ext; + else + krs = &ext->data.u.key_rotation; + } else { + const KeyRotation *prev_kr = &krs->val[0]; + unsigned int last_kvno = 0; + + if (kr->epoch - prev_kr->epoch <= 0) { + krb5_set_error_message(context, EINVAL, + "New key rotation periods must start later " + "than existing ones"); + return EINVAL; + } + + if (kr->base_kvno <= prev_kr->base_kvno || + kr->base_kvno - prev_kr->base_kvno <= + (last_kvno = 1 + + ((kr->epoch - prev_kr->epoch) / prev_kr->period))) { + krb5_set_error_message(context, EINVAL, + "New key rotation base kvno must be larger " + "the last kvno for the current key " + "rotation (%u)", last_kvno); + return EINVAL; + } + } + + /* First, append */ + ret = add_HDB_Ext_KeyRotation(&ext->data.u.key_rotation, kr); + if (ret) + return ret; + + /* Rotate new to front */ + tmp = ext->data.u.key_rotation.val[ext->data.u.key_rotation.len - 1]; + sz = sizeof(ext->data.u.key_rotation.val[0]); + memmove(&ext->data.u.key_rotation.val[1], &ext->data.u.key_rotation.val[0], + (ext->data.u.key_rotation.len - 1) * sz); + ext->data.u.key_rotation.val[0] = tmp; + + /* Drop too old entries */ + for (i = 3; i < ext->data.u.key_rotation.len; i++) + free_KeyRotation(&ext->data.u.key_rotation.val[i]); + ext->data.u.key_rotation.len = + ext->data.u.key_rotation.len > 3 ? 3 : ext->data.u.key_rotation.len; + + if (ext != &new_ext) + return 0; + + /* Install new extension */ + if (ret == 0 && entry) + ret = hdb_replace_extension(context, entry, ext); + free_HDB_extension(&new_ext); + return ret; +} diff --git a/lib/hdb/hdb-keytab.c b/lib/hdb/hdb-keytab.c index ab2afb5d7..b1cfa0e66 100644 --- a/lib/hdb/hdb-keytab.c +++ b/lib/hdb/hdb-keytab.c @@ -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); diff --git a/lib/hdb/hdb-ldap.c b/lib/hdb/hdb-ldap.c index f204bfde7..16f7a8f0b 100644 --- a/lib/hdb/hdb-ldap.c +++ b/lib/hdb/hdb-ldap.c @@ -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); diff --git a/lib/hdb/hdb-mdb.c b/lib/hdb/hdb-mdb.c index 52d9aed7a..0450d2742 100644 --- a/lib/hdb/hdb-mdb.c +++ b/lib/hdb/hdb-mdb.c @@ -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); diff --git a/lib/hdb/hdb-mitdb.c b/lib/hdb/hdb-mitdb.c index 9a68f96df..5bb439111 100644 --- a/lib/hdb/hdb-mitdb.c +++ b/lib/hdb/hdb-mitdb.c @@ -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; diff --git a/lib/hdb/hdb-sqlite.c b/lib/hdb/hdb-sqlite.c index d5eb3f184..d985d8c47 100644 --- a/lib/hdb/hdb-sqlite.c +++ b/lib/hdb/hdb-sqlite.c @@ -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); diff --git a/lib/hdb/hdb.asn1 b/lib/hdb/hdb.asn1 index c0024d4b4..0fec2e2c6 100644 --- a/lib/hdb/hdb.asn1 +++ b/lib/hdb/hdb.asn1 @@ -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 } diff --git a/lib/hdb/hdb.c b/lib/hdb/hdb.c index e55968568..40fdc4573 100644 --- a/lib/hdb/hdb.c +++ b/lib/hdb/hdb.c @@ -119,6 +119,16 @@ static struct hdb_method default_dbmethod = { HDB_INTERFACE_VERSION, NULL, NULL, "", hdb_ndbm_create }; #endif +/** + * Returns the Keys of `e' for `kvno', or NULL if not found. The Keys will + * remain valid provided that the entry is not mutated. + * + * @param context Context + * @param e The HDB entry + * @param kvno The kvno + * + * @return A pointer to the Keys for the requested kvno. + */ const Keys * hdb_kvno2keys(krb5_context context, const hdb_entry *e, @@ -128,7 +138,7 @@ hdb_kvno2keys(krb5_context context, HDB_extension *extp; size_t i; - if (kvno == 0) + if (kvno == 0 || e->kvno == kvno) return &e->keys; extp = hdb_find_extension(e, choice_HDB_extension_data_hist_keys); @@ -144,6 +154,188 @@ hdb_kvno2keys(krb5_context context, return NULL; } +/* Based on remove_HDB_Ext_KeySet(), generated by the ASN.1 compiler */ +static int +dequeue_HDB_Ext_KeySet(HDB_Ext_KeySet *data, unsigned int element, hdb_keyset *ks) +{ + if (element >= data->len) { + ks->kvno = 0; + ks->keys.len = 0; + ks->keys.val = 0; + ks->set_time = 0; + return ASN1_OVERRUN; + } + *ks = data->val[element]; + data->len--; + /* Swap instead of memmove()... changes the order of elements */ + if (element < data->len) + data->val[element] = data->val[data->len]; + if (data->len == 0) { + free(data->val); + data->val = 0; + } + return 0; +} + + +/** + * Removes from `e' and optionally outputs the keyset for the requested `kvno'. + * + * @param context Context + * @param e The HDB entry + * @param kvno The key version number + * @param ks A pointer to a variable of type hdb_keyset (may be NULL) + * + * @return Zero on success, an error code otherwise. + */ +krb5_error_code +hdb_remove_keys(krb5_context context, + hdb_entry *e, + krb5_kvno kvno, + hdb_keyset *ks) +{ + HDB_Ext_KeySet *hist_keys; + HDB_extension *extp; + size_t i; + + if (kvno == 0 || e->kvno == kvno) { + if (ks) { + KerberosTime t; + + (void) hdb_entry_get_pw_change_time(e, &t); + if (t) { + if ((ks->set_time = malloc(sizeof(*ks->set_time))) == NULL) + return krb5_enomem(context); + *ks->set_time = t; + } + ks->kvno = e->kvno; + ks->keys = e->keys; + e->keys.len = 0; + e->keys.val = NULL; + e->kvno = 0; + } else { + free_Keys(&e->keys); + } + return 0; + } + + if (ks) { + ks->kvno = 0; + ks->keys.len = 0; + ks->keys.val = 0; + ks->set_time = 0; + } + + extp = hdb_find_extension(e, choice_HDB_extension_data_hist_keys); + if (extp == NULL) + return 0; + + hist_keys = &extp->data.u.hist_keys; + for (i = 0; i < hist_keys->len; i++) { + if (hist_keys->val[i].kvno != kvno) + continue; + if (ks) + return dequeue_HDB_Ext_KeySet(hist_keys, i, ks); + return remove_HDB_Ext_KeySet(hist_keys, i); + } + return HDB_ERR_NOENTRY; +} + +/** + * Removes from `e' and outputs all the base keys for virtual principal and/or + * key derivation. + * + * @param context Context + * @param e The HDB entry + * @param ks A pointer to a variable of type HDB_Ext_KeySet + * + * @return Zero on success, an error code otherwise. + */ +krb5_error_code +hdb_remove_base_keys(krb5_context context, + hdb_entry *e, + HDB_Ext_KeySet *base_keys) +{ + krb5_error_code ret; + const HDB_Ext_KeyRotation *ckr; + HDB_Ext_KeyRotation kr; + size_t i, k; + + ret = hdb_entry_get_key_rotation(context, e, &ckr); + if (ret == 0) { + /* + * Changing the entry's extensions invalidates extensions obtained + * before the change. + */ + ret = copy_HDB_Ext_KeyRotation(ckr, &kr); + ckr = NULL; + } + base_keys->len = 0; + if (ret == 0 && + (base_keys->val = calloc(kr.len, sizeof(base_keys->val[0]))) == NULL) + ret = krb5_enomem(context); + + for (k = i = 0; ret == 0 && i < kr.len; i++) { + const KeyRotation *krp = &kr.val[i]; + + /* + * WARNING: O(N * M) where M is number of keysets and N is the number + * of base keysets. + * + * In practice N will never be > 3 because the ASN.1 module imposes + * that as a constraint, and M will generally be the same as N, so this + * will be O(1) after all. + */ + ret = hdb_remove_keys(context, e, krp->base_key_kvno, + &base_keys->val[k]); + if (ret == 0) + k++; + else if (ret == HDB_ERR_NOENTRY) + ret = 0; + } + if (ret == 0) + base_keys->len = k; + else + free_HDB_Ext_KeySet(base_keys); + free_HDB_Ext_KeyRotation(&kr); + return 0; +} + +/** + * Removes from `e' and outputs all the base keys for virtual principal and/or + * key derivation. + * + * @param context Context + * @param e The HDB entry + * @param is_current_keyset Whether to make the keys the current keys for `e' + * @param ks A pointer to an hdb_keyset containing the keys to set + * + * @return Zero on success, an error code otherwise. + */ +krb5_error_code +hdb_install_keyset(krb5_context context, + hdb_entry *e, + int is_current_keyset, + const hdb_keyset *ks) +{ + krb5_error_code ret = 0; + + if (is_current_keyset) { + if (e->keys.len && + (ret = hdb_add_current_keys_to_history(context, e))) + return ret; + free_Keys(&e->keys); + if (ret == 0) + ret = copy_Keys(&ks->keys, &e->keys); + e->kvno = ks->kvno; + if (ks->set_time) + return hdb_entry_set_pw_change_time(context, e, *ks->set_time); + return 0; + } + return hdb_add_history_keyset(context, e, ks); +} + + krb5_error_code hdb_next_enctype2key(krb5_context context, const hdb_entry *e, @@ -481,8 +673,10 @@ _hdb_keytab2hdb_entry(krb5_context context, krb5_error_code hdb_create(krb5_context context, HDB **db, const char *filename) { + krb5_error_code ret; struct cb_s cb_ctx; + *db = NULL; if (filename == NULL) filename = HDB_DEFAULT_DB; cb_ctx.h = find_method (filename, &cb_ctx.residual); @@ -504,9 +698,43 @@ hdb_create(krb5_context context, HDB **db, const char *filename) free(rk_UNCONST(hdb_plugin_data.name)); } + /* XXX krb5_errx()?! */ if (cb_ctx.h == NULL) krb5_errx(context, 1, "No database support for %s", cb_ctx.filename); - return (*cb_ctx.h->create)(context, db, cb_ctx.residual); + ret = (*cb_ctx.h->create)(context, db, cb_ctx.residual); + if (ret == 0 && *db) { + (*db)->enable_virtual_hostbased_princs = + krb5_config_get_bool_default(context, NULL, FALSE, "hdb", + "enable_virtual_hostbased_princs", + NULL); + (*db)->virtual_hostbased_princ_ndots = + krb5_config_get_int_default(context, NULL, 1, "hdb", + "virtual_hostbased_princ_mindots", + NULL); + (*db)->virtual_hostbased_princ_maxdots = + krb5_config_get_int_default(context, NULL, 0, "hdb", + "virtual_hostbased_princ_maxdots", + NULL); + (*db)->new_service_key_delay = + krb5_config_get_time_default(context, NULL, 0, "hdb", + "new_service_key_delay", NULL); + /* + * XXX Needs freeing in the HDB backends because we don't have a + * first-class hdb_close() :( + */ + (*db)->virtual_hostbased_princ_svcs = + krb5_config_get_strings(context, NULL, "hdb", + "virtual_hostbased_princ_svcs", NULL); + /* Check for ENOMEM */ + if ((*db)->virtual_hostbased_princ_svcs == NULL + && krb5_config_get_string(context, NULL, "hdb", + "virtual_hostbased_princ_svcs", NULL)) { + (*db)->hdb_destroy(context, *db); + *db = NULL; + ret = krb5_enomem(context); + } + } + return ret; } uintptr_t KRB5_CALLCONV diff --git a/lib/hdb/hdb.h b/lib/hdb/hdb.h index 7c6dfcf9d..e21a4d720 100644 --- a/lib/hdb/hdb.h +++ b/lib/hdb/hdb.h @@ -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. * diff --git a/lib/hdb/keys.c b/lib/hdb/keys.c index 47e072b32..d5d814dae 100644 --- a/lib/hdb/keys.c +++ b/lib/hdb/keys.c @@ -217,6 +217,15 @@ hdb_prune_keys_kvno(krb5_context context, hdb_entry *entry, int kvno) size_t nelem; size_t i; + /* + * XXX Pruning old keys for namespace principals may not be desirable, but! + * as long as the `set_time's of the base keys for a namespace principal + * match the `epoch's of the corresponding KeyRotation periods, it will be + * perfectly acceptable to prune old [base] keys for namespace principals + * just as for any other principal. Therefore, we may not need to make any + * changes here w.r.t. namespace principals. + */ + ext = hdb_find_extension(entry, choice_HDB_extension_data_hist_keys); if (ext == NULL) return 0; @@ -279,6 +288,53 @@ hdb_prune_keys(krb5_context context, hdb_entry *entry) return hdb_prune_keys_kvno(context, entry, 0); } +/** + * This function adds a keyset to an HDB entry's key history. + * + * @param context Context + * @param entry HDB entry + * @param kvno Key version number of the key to add to the history + * @param key The Key to add + */ +krb5_error_code +hdb_add_history_keyset(krb5_context context, + hdb_entry *entry, + const hdb_keyset *ks) +{ + size_t i; + HDB_Ext_KeySet *hist_keys; + HDB_extension ext; + HDB_extension *extp; + krb5_error_code ret; + + memset(&ext, 0, sizeof (ext)); + + extp = hdb_find_extension(entry, choice_HDB_extension_data_hist_keys); + if (extp == NULL) { + ext.mandatory = FALSE; + ext.data.element = choice_HDB_extension_data_hist_keys; + ext.data.u.hist_keys.len = 0; + ext.data.u.hist_keys.val = 0; + extp = &ext; + } + hist_keys = &extp->data.u.hist_keys; + + for (i = 0; i < hist_keys->len; i++) { + if (hist_keys->val[i].kvno == ks->kvno) { + /* Replace existing */ + free_hdb_keyset(&hist_keys->val[i]); + ret = copy_hdb_keyset(ks, &hist_keys->val[i]); + break; + } + } + if (i >= hist_keys->len) + ret = add_HDB_Ext_KeySet(hist_keys, ks); /* Append new */ + if (ret == 0 && extp == &ext) + ret = hdb_replace_extension(context, entry, &ext); + free_HDB_extension(&ext); + return ret; +} + /** * This function adds an HDB entry's current keyset to the entry's key * history. The current keyset is left alone; the caller is responsible @@ -286,65 +342,30 @@ hdb_prune_keys(krb5_context context, hdb_entry *entry) * * @param context Context * @param entry HDB entry + * + * @return Zero on success, or an error code otherwise. */ krb5_error_code hdb_add_current_keys_to_history(krb5_context context, hdb_entry *entry) { - krb5_boolean replace = FALSE; krb5_error_code ret; - HDB_extension *ext; - HDB_Ext_KeySet *keys; - hdb_keyset newkeyset; + hdb_keyset ks; time_t newtime; if (entry->keys.len == 0) return 0; /* nothing to do */ - ext = hdb_find_extension(entry, choice_HDB_extension_data_hist_keys); - if (ext == NULL) { - replace = TRUE; - ext = calloc(1, sizeof (*ext)); - if (ext == NULL) - return krb5_enomem(context); - - ext->data.element = choice_HDB_extension_data_hist_keys; - } - keys = &ext->data.u.hist_keys; - - ext->mandatory = FALSE; - - /* - * Copy in newest old keyset - */ ret = hdb_entry_get_pw_change_time(entry, &newtime); if (ret) - goto out; + return ret; - memset(&newkeyset, 0, sizeof(newkeyset)); - newkeyset.keys = entry->keys; - newkeyset.kvno = entry->kvno; - newkeyset.set_time = &newtime; + ks.keys = entry->keys; + ks.kvno = entry->kvno; + ks.set_time = &newtime; - ret = add_HDB_Ext_KeySet(keys, &newkeyset); - if (ret) - goto out; - - if (replace) { - /* hdb_replace_extension() deep-copies ext; what a waste */ - ret = hdb_replace_extension(context, entry, ext); - if (ret) - goto out; - } - - ret = hdb_prune_keys(context, entry); - if (ret) - goto out; - - out: - if (replace && ext) { - free_HDB_extension(ext); - free(ext); - } + ret = hdb_add_history_keyset(context, entry, &ks); + if (ret == 0) + ret = hdb_prune_keys(context, entry); return ret; } @@ -355,6 +376,8 @@ hdb_add_current_keys_to_history(krb5_context context, hdb_entry *entry) * @param entry HDB entry * @param kvno Key version number of the key to add to the history * @param key The Key to add + * + * @return Zero on success, or an error code otherwise. */ krb5_error_code hdb_add_history_key(krb5_context context, hdb_entry *entry, krb5_kvno kvno, Key *key) @@ -404,7 +427,6 @@ out: return ret; } - /** * This function changes an hdb_entry's kvno, swapping the current key * set with a historical keyset. If no historical keys are found then diff --git a/lib/hdb/keytab.c b/lib/hdb/keytab.c index 966c12f89..97c923f08 100644 --- a/lib/hdb/keytab.c +++ b/lib/hdb/keytab.c @@ -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; diff --git a/lib/hdb/libhdb-exports.def b/lib/hdb/libhdb-exports.def index bb1d7ad16..52737fe99 100644 --- a/lib/hdb/libhdb-exports.def +++ b/lib/hdb/libhdb-exports.def @@ -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 diff --git a/lib/hdb/ndbm.c b/lib/hdb/ndbm.c index c5a862116..4e3a340fe 100644 --- a/lib/hdb/ndbm.c +++ b/lib/hdb/ndbm.c @@ -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; diff --git a/lib/hdb/test_hdbplugin.c b/lib/hdb/test_hdbplugin.c deleted file mode 100644 index 4ebceb8da..000000000 --- a/lib/hdb/test_hdbplugin.c +++ /dev/null @@ -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; -} diff --git a/lib/hdb/test_namespace.c b/lib/hdb/test_namespace.c new file mode 100644 index 000000000..1436cb9cc --- /dev/null +++ b/lib/hdb/test_namespace.c @@ -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 + +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; +} diff --git a/lib/hdb/version-script.map b/lib/hdb/version-script.map index c421010c9..87c082b66 100644 --- a/lib/hdb/version-script.map +++ b/lib/hdb/version-script.map @@ -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: *; diff --git a/lib/kadm5/acl.c b/lib/kadm5/acl.c index 5f0b3290d..f71ed99f5 100644 --- a/lib/kadm5/acl.c +++ b/lib/kadm5/acl.c @@ -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; diff --git a/lib/kadm5/admin.h b/lib/kadm5/admin.h index 3c735edfc..e881ea83b 100644 --- a/lib/kadm5/admin.h +++ b/lib/kadm5/admin.h @@ -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; diff --git a/lib/kadm5/chpass_s.c b/lib/kadm5/chpass_s.c index 150b82a3e..ed9632e85 100644 --- a/lib/kadm5/chpass_s.c +++ b/lib/kadm5/chpass_s.c @@ -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; diff --git a/lib/kadm5/create_c.c b/lib/kadm5/create_c.c index 519b490b8..f3e757e9e 100644 --- a/lib/kadm5/create_c.c +++ b/lib/kadm5/create_c.c @@ -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; diff --git a/lib/kadm5/create_s.c b/lib/kadm5/create_s.c index f8cc064ce..42125e28a 100644 --- a/lib/kadm5/create_s.c +++ b/lib/kadm5/create_s.c @@ -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) diff --git a/lib/kadm5/delete_s.c b/lib/kadm5/delete_s.c index 5fa0df855..6942148db 100644 --- a/lib/kadm5/delete_s.c +++ b/lib/kadm5/delete_s.c @@ -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) { diff --git a/lib/kadm5/ent_setup.c b/lib/kadm5/ent_setup.c index 1266f204b..6fafe07e9 100644 --- a/lib/kadm5/ent_setup.c +++ b/lib/kadm5/ent_setup.c @@ -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; } diff --git a/lib/kadm5/get_s.c b/lib/kadm5/get_s.c index 6c8655740..562286523 100644 --- a/lib/kadm5/get_s.c +++ b/lib/kadm5/get_s.c @@ -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: diff --git a/lib/kadm5/log.c b/lib/kadm5/log.c index d1801e92b..05ace30ae 100644 --- a/lib/kadm5/log.c +++ b/lib/kadm5/log.c @@ -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); diff --git a/lib/kadm5/modify_s.c b/lib/kadm5/modify_s.c index e0ce5d976..7947ebef4 100644 --- a/lib/kadm5/modify_s.c +++ b/lib/kadm5/modify_s.c @@ -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; diff --git a/lib/kadm5/prune_s.c b/lib/kadm5/prune_s.c index 0bad91102..e5d77f6cf 100644 --- a/lib/kadm5/prune_s.c +++ b/lib/kadm5/prune_s.c @@ -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; diff --git a/lib/kadm5/randkey_s.c b/lib/kadm5/randkey_s.c index 94082040b..9bb83cd14 100644 --- a/lib/kadm5/randkey_s.c +++ b/lib/kadm5/randkey_s.c @@ -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; diff --git a/lib/kadm5/rename_s.c b/lib/kadm5/rename_s.c index 58a608e50..1052042af 100644 --- a/lib/kadm5/rename_s.c +++ b/lib/kadm5/rename_s.c @@ -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; diff --git a/lib/kadm5/setkey3_s.c b/lib/kadm5/setkey3_s.c index 713affcd6..2f8eda54c 100644 --- a/lib/kadm5/setkey3_s.c +++ b/lib/kadm5/setkey3_s.c @@ -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) diff --git a/lib/krb5/krb5.conf.5 b/lib/krb5/krb5.conf.5 index 4dc0986da..1e1cc5f2f 100644 --- a/lib/krb5/krb5.conf.5 +++ b/lib/krb5/krb5.conf.5 @@ -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//@REALM -.Pp -match any request of the form: -.Ar */*.@REALM . -The keys are derived from the keys in the database and -the name of the requested principal via the algorithm -specified by -.Ar . -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] diff --git a/tests/gss/check-gssmask.in b/tests/gss/check-gssmask.in index e4dd1dbec..44769eff5 100644 --- a/tests/gss/check-gssmask.in +++ b/tests/gss/check-gssmask.in @@ -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 diff --git a/tests/gss/krb5.conf.in b/tests/gss/krb5.conf.in index 160346cb3..b8e04b651 100644 --- a/tests/gss/krb5.conf.in +++ b/tests/gss/krb5.conf.in @@ -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