diff --git a/lib/hdb/Makefile.am b/lib/hdb/Makefile.am index ba28235b5..3c79219fd 100644 --- a/lib/hdb/Makefile.am +++ b/lib/hdb/Makefile.am @@ -29,6 +29,7 @@ gen_files_hdb = \ 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_entry.x \ diff --git a/lib/hdb/common.c b/lib/hdb/common.c index 2715adf63..90e600521 100644 --- a/lib/hdb/common.c +++ b/lib/hdb/common.c @@ -105,7 +105,6 @@ _hdb_fetch_kvno(krb5_context context, HDB *db, krb5_const_principal principal, krb5_principal enterprise_principal = NULL; krb5_data key, value; krb5_error_code ret; - int code; if (principal->name.name_type == KRB5_NT_ENTERPRISE_PRINCIPAL) { if (principal->name.name_string.len != 1) { @@ -125,43 +124,72 @@ _hdb_fetch_kvno(krb5_context context, HDB *db, krb5_const_principal principal, hdb_principal2key(context, principal, &key); if (enterprise_principal) krb5_free_principal(context, enterprise_principal); - code = db->hdb__get(context, db, key, &value); + ret = db->hdb__get(context, db, key, &value); krb5_data_free(&key); - if(code) - return code; - code = hdb_value2entry(context, &value, &entry->entry); - if (code == ASN1_BAD_ID && (flags & HDB_F_CANON) == 0) { + if(ret) + return ret; + ret = hdb_value2entry(context, &value, &entry->entry); + if (ret == ASN1_BAD_ID && (flags & HDB_F_CANON) == 0) { krb5_data_free(&value); return HDB_ERR_NOENTRY; - } else if (code == ASN1_BAD_ID) { + } else if (ret == ASN1_BAD_ID) { hdb_entry_alias alias; - code = hdb_value2entry_alias(context, &value, &alias); - if (code) { + ret = hdb_value2entry_alias(context, &value, &alias); + if (ret) { krb5_data_free(&value); - return code; + return ret; } hdb_principal2key(context, alias.principal, &key); krb5_data_free(&value); free_hdb_entry_alias(&alias); - code = db->hdb__get(context, db, key, &value); + ret = db->hdb__get(context, db, key, &value); krb5_data_free(&key); - if (code) - return code; - code = hdb_value2entry(context, &value, &entry->entry); - if (code) { + if (ret) + return ret; + ret = hdb_value2entry(context, &value, &entry->entry); + if (ret) { krb5_data_free(&value); - return code; + return ret; } } krb5_data_free(&value); if (db->hdb_master_key_set && (flags & HDB_F_DECRYPT)) { - code = hdb_unseal_keys (context, db, &entry->entry); - if (code) + if ((flags & HDB_F_KVNO_SPECIFIED) == 0 && + (flags & HDB_F_CURRENT_KVNO) == 0) { + + /* + * Decrypt all the old keys too, since we don't know which + * the caller will need. + */ + ret = hdb_unseal_keys_kvno(context, db, 0, &entry->entry); + if (ret) { + hdb_free_entry(context, entry); + return ret; + } + } else if ((flags & HDB_F_KVNO_SPECIFIED) != 0 && + kvno != entry->entry.kvno && + kvno < entry->entry.kvno && + kvno > 0) { + + /* Decrypt the keys we were asked for, if not the current ones */ + ret = hdb_unseal_keys_kvno(context, db, kvno, &entry->entry); + if (ret) { + hdb_free_entry(context, entry); + return ret; + } + } + + /* Always decrypt the current keys too */ + ret = hdb_unseal_keys(context, db, &entry->entry); + if (ret) { hdb_free_entry(context, entry); + return ret; + } } - return code; + + return ret; } static krb5_error_code diff --git a/lib/hdb/hdb.asn1 b/lib/hdb/hdb.asn1 index a72851c9f..d24737fbf 100644 --- a/lib/hdb/hdb.asn1 +++ b/lib/hdb/hdb.asn1 @@ -87,6 +87,14 @@ HDB-Ext-Aliases ::= SEQUENCE { aliases[1] SEQUENCE OF Principal -- all names, inc primary } +hdb_keyset ::= SEQUENCE { + kvno[0] INTEGER (0..4294967295), + replace-time[1] KerberosTime, -- time this key was replaced + keys[2] SEQUENCE OF Key +} + +HDB-Ext-KeySet ::= SEQUENCE OF hdb_keyset + HDB-extension ::= SEQUENCE { mandatory[0] BOOLEAN, -- kdc MUST understand this extension, @@ -102,6 +110,7 @@ HDB-extension ::= SEQUENCE { aliases[6] HDB-Ext-Aliases, last-pw-change[7] KerberosTime, pkinit-cert[8] HDB-Ext-PKINIT-cert, + hist-keys[9] HDB-Ext-KeySet, ... }, ... @@ -109,11 +118,6 @@ HDB-extension ::= SEQUENCE { HDB-extensions ::= SEQUENCE OF HDB-extension -hdb_keyset ::= SEQUENCE { - kvno[1] INTEGER (0..4294967295), - keys[0] SEQUENCE OF Key -} - hdb_entry ::= SEQUENCE { principal[0] Principal OPTIONAL, -- this is optional only -- for compatibility with libkrb5 diff --git a/lib/hdb/hdb.c b/lib/hdb/hdb.c index ca05cc4a1..aff576a30 100644 --- a/lib/hdb/hdb.c +++ b/lib/hdb/hdb.c @@ -168,13 +168,14 @@ hdb_unlock(int fd) void hdb_free_entry(krb5_context context, hdb_entry_ex *ent) { - size_t i; + Key *k; + int i; if (ent->free_entry) (*ent->free_entry)(context, ent); - for(i = 0; i < ent->entry.keys.len; ++i) { - Key *k = &ent->entry.keys.val[i]; + for(i = 0; i < ent->entry.keys.len; i++) { + k = &ent->entry.keys.val[i]; memset (k->key.keyvalue.data, 0, k->key.keyvalue.length); } diff --git a/lib/hdb/hdb.h b/lib/hdb/hdb.h index 469ec82ec..e82c5c51e 100644 --- a/lib/hdb/hdb.h +++ b/lib/hdb/hdb.h @@ -57,6 +57,7 @@ enum hdb_lockop{ HDB_RLOCK, HDB_WLOCK }; #define HDB_F_CANON 32 /* want canonicalition */ #define HDB_F_ADMIN_DATA 64 /* want data that kdc don't use */ #define HDB_F_KVNO_SPECIFIED 128 /* we want a particular KVNO */ +#define HDB_F_CURRENT_KVNO 256 /* we want the current KVNO */ /* hdb_capability_flags */ #define HDB_CAP_F_HANDLE_ENTERPRISE_PRINCIPAL 1 diff --git a/lib/hdb/keys.c b/lib/hdb/keys.c index 3d0b9d7c1..9c0af5c47 100644 --- a/lib/hdb/keys.c +++ b/lib/hdb/keys.c @@ -39,7 +39,7 @@ */ void -hdb_free_keys (krb5_context context, int len, Key *keys) +hdb_free_keys(krb5_context context, int len, Key *keys) { int i; @@ -56,6 +56,19 @@ hdb_free_keys (krb5_context context, int len, Key *keys) free (keys); } +void +hdb_free_keysets(krb5_context context, int len, hdb_keyset *keysets) +{ + int i; + + for (i = 0; i < len; i++) { + hdb_free_keys(context, keysets[i].keys.len, keysets[i].keys.val); + keysets[i].keys.val = NULL; + keysets[i].keys.len = 0; + } + free (keysets); +} + /* * for each entry in `default_keys' try to parse it as a sequence * of etype:salttype:salt, syntax of this if something like: @@ -196,6 +209,60 @@ parse_key_set(krb5_context context, const char *key, return 0; } + +krb5_error_code +hdb_add_current_keys_to_history(krb5_context context, hdb_entry *entry) +{ + krb5_error_code ret; + HDB_extension *ext; + HDB_Ext_KeySet *hist_keys; + hdb_keyset *tmp_keysets; + int add = 0; + + ext = hdb_find_extension(entry, choice_HDB_extension_data_hist_keys); + if (ext != NULL) { + hist_keys = &ext->data.u.hist_keys; + tmp_keysets = realloc(hist_keys->val, + sizeof (*hist_keys->val) * (hist_keys->len + 1)); + if (tmp_keysets == NULL) + return ENOMEM; + hist_keys->val = tmp_keysets; + memmove(&hist_keys->val[1], hist_keys->val, + sizeof (*hist_keys->val) * hist_keys->len++); + } else { + add = 1; + ext = calloc(1, sizeof (*ext)); + if (ext == NULL) + return ENOMEM; + ext->data.element = choice_HDB_extension_data_hist_keys; + hist_keys = &ext->data.u.hist_keys; + hist_keys->val = calloc(1, sizeof (*hist_keys->val)); + if (hist_keys->val == NULL) { + free(hist_keys); + return ENOMEM; + } + hist_keys->len = 1; + } + + hist_keys->val[0].keys.val = entry->keys.val; + hist_keys->val[0].keys.len = entry->keys.len; + hist_keys->val[0].kvno = entry->kvno; + hist_keys->val[0].replace_time = time(NULL); + + if (add) { + ret = hdb_replace_extension(context, entry, ext); + if (ret) { + free_HDB_extension(ext); + return ret; + } + } + + /* hdb_replace_extension() copies ext, so we have to free it */ + free_HDB_extension(ext); + return 0; +} + + static krb5_error_code add_enctype_to_key_set(Key **key_set, size_t *nkeyset, krb5_enctype enctype, krb5_salt *salt) diff --git a/lib/hdb/mkey.c b/lib/hdb/mkey.c index 9a13d55a5..4ea7394f3 100644 --- a/lib/hdb/mkey.c +++ b/lib/hdb/mkey.c @@ -32,6 +32,7 @@ */ #include "hdb_locl.h" +#include #ifndef O_BINARY #define O_BINARY 0 #endif @@ -479,6 +480,85 @@ hdb_unseal_keys(krb5_context context, HDB *db, hdb_entry *ent) return hdb_unseal_keys_mkey(context, ent, db->hdb_master_key); } +krb5_error_code +hdb_unseal_keys_kvno(krb5_context context, HDB *db, krb5_kvno kvno, + hdb_entry *ent) +{ + krb5_error_code ret = KRB5KRB_AP_ERR_NOKEY; /* XXX need a better code? */ + HDB_extension *tmp; + HDB_Ext_KeySet *hist_keys; + hdb_keyset *tmp_keys; + Key *tmp_val; + unsigned int tmp_len; + krb5_kvno tmp_kvno; + int i, k; + + assert(kvno == 0 || kvno < ent->kvno); + + tmp = hdb_find_extension(ent, choice_HDB_extension_data_hist_keys); + if (tmp == NULL) + return ret; + + tmp_len = ent->keys.len; + tmp_val = ent->keys.val; + tmp_kvno = ent->kvno; + + hist_keys = &tmp->data.u.hist_keys; + + for (i = hist_keys->len - 1; i >= 0; i++) { + if (kvno != 0 && hist_keys->val[i].kvno != kvno) + continue; + for (k = 0; k < hist_keys->val[i].keys.len; k++) { + ret = hdb_unseal_key_mkey(context, + &hist_keys->val[i].keys.val[k], + db->hdb_master_key); + if (ret) + return (ret); + } + + if (kvno == 0) + continue; + + /* + * NOTE: What follows is a bit of an ugly hack. + * + * This is the keyset we're being asked for, so we add the + * current keyset to the history, leave the one we were asked + * for in the history, and pretend the one we were asked for is + * also the current keyset. + * + * This is a bit of a defensive hack in case an entry fetched + * this way ever gets modified then stored: if the keyset is not + * changed we can detect this and put things back, else we won't + * drop any keysets from history by accident. + * + * Note too that we only ever get called with a non-zero kvno + * either in the KDC or in cases where we aren't changing the + * HDB entry anyways, which is why this is just a defensive + * hack. We also don't fetch specific kvnos in the dump case, + * so there's no danger that we'll dump this entry and load it + * again, repeatedly causing the history to grow boundelessly. + */ + tmp_keys = realloc(hist_keys->val, + sizeof (*hist_keys->val) * (hist_keys->len + 1)); + if (tmp_keys == NULL) + return ENOMEM; + + memmove(&tmp_keys[1], tmp_keys, + sizeof (*hist_keys->val) * hist_keys->len++); + tmp_keys[0].keys.len = ent->keys.len; + tmp_keys[0].keys.val = ent->keys.val; + tmp_keys[0].kvno = ent->kvno; + tmp_keys[0].replace_time = time(NULL); + i++; + ent->keys.len = hist_keys->val[i].keys.len; + ent->keys.val = hist_keys->val[i].keys.val; + ent->kvno = kvno; + } + + return (ret); +} + krb5_error_code hdb_unseal_key(krb5_context context, HDB *db, Key *k) { diff --git a/lib/hdb/print.c b/lib/hdb/print.c index 697d32d29..0ff767ec6 100644 --- a/lib/hdb/print.c +++ b/lib/hdb/print.c @@ -62,11 +62,12 @@ append_string(krb5_context context, krb5_storage *sp, const char *fmt, ...) { krb5_error_code ret; char *s; + int rc; va_list ap; va_start(ap, fmt); - vasprintf(&s, fmt, ap); + rc = vasprintf(&s, fmt, ap); va_end(ap); - if(s == NULL) { + if(rc < 0) { krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); return ENOMEM; } @@ -127,7 +128,7 @@ static krb5_error_code entry2string_int (krb5_context context, krb5_storage *sp, hdb_entry *ent) { char *p; - size_t i; + int i; krb5_error_code ret; /* --- principal */ @@ -234,7 +235,6 @@ entry2string_int (krb5_context context, krb5_storage *sp, hdb_entry *ent) } else append_string(context, sp, "-"); - return 0; } diff --git a/lib/hdb/test_hdbkeys.c b/lib/hdb/test_hdbkeys.c index 1c3216d14..fd5dfcf81 100644 --- a/lib/hdb/test_hdbkeys.c +++ b/lib/hdb/test_hdbkeys.c @@ -88,6 +88,7 @@ main(int argc, char **argv) memset(&keyset, 0, sizeof(keyset)); keyset.kvno = kvno_integer; + keyset.replace_time = time(NULL); ret = hdb_generate_key_set_password(context, principal, password_str, &keyset.keys.val, &len); diff --git a/lib/hdb/version-script.map b/lib/hdb/version-script.map index 50a36cec0..bf0f35c81 100644 --- a/lib/hdb/version-script.map +++ b/lib/hdb/version-script.map @@ -4,6 +4,7 @@ HEIMDAL_HDB_1.0 { global: encode_hdb_keyset; hdb_add_master_key; + hdb_add_current_keys_to_history; hdb_check_db_format; hdb_clear_extension; hdb_clear_master_key; diff --git a/lib/kadm5/chpass_s.c b/lib/kadm5/chpass_s.c index d5407d154..8ffdefa47 100644 --- a/lib/kadm5/chpass_s.c +++ b/lib/kadm5/chpass_s.c @@ -58,6 +58,10 @@ change(void *server_handle, if(ret) goto out; + ret = hdb_add_current_keys_to_history(context->context, &ent.entry); + if (ret) + goto out; + if (context->db->hdb_capability_flags & HDB_CAP_F_HANDLE_PASSWORDS) { ret = context->db->hdb_password(context->context, context->db, &ent, password, cond); @@ -170,6 +174,9 @@ kadm5_s_chpass_principal_with_key(void *server_handle, HDB_F_GET_ANY|HDB_F_ADMIN_DATA, &ent); if(ret == HDB_ERR_NOENTRY) goto out; + ret = hdb_add_current_keys_to_history(context->context, &ent.entry); + if (ret) + goto out2; ret = _kadm5_set_keys2(context, &ent.entry, n_key_data, key_data); if(ret) goto out2; diff --git a/lib/kadm5/randkey_s.c b/lib/kadm5/randkey_s.c index dcb179aac..adb3564be 100644 --- a/lib/kadm5/randkey_s.c +++ b/lib/kadm5/randkey_s.c @@ -59,6 +59,10 @@ kadm5_s_randkey_principal(void *server_handle, if(ret) goto out; + ret = hdb_add_current_keys_to_history(context->context, &ent.entry); + if (ret) + goto out2; + ret = _kadm5_set_keys_randomly (context, &ent.entry, new_keys,