From: Sarah Day Date: Tue, 26 Jan 2016 17:22:41 +0000 (-0500) Subject: Implement password history in LDAP KDB module X-Git-Tag: krb5-1.15-beta1~277 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b46cce2ea8c0841f7f93db73eefcd180c87a3eae;p=thirdparty%2Fkrb5.git Implement password history in LDAP KDB module The password history is stored in the kerberos LDAP schema attribute 'krbPwdHistory', with one history entry per attribute. When the history is decoded, the history entries are sorted by kvno with the next replacement key set to the end of the list. Based on a patch from Tomas Kuthan. ticket: 5889 --- diff --git a/src/lib/kadm5/admin.h b/src/lib/kadm5/admin.h index 8f377f804f..73f281129a 100644 --- a/src/lib/kadm5/admin.h +++ b/src/lib/kadm5/admin.h @@ -110,7 +110,7 @@ typedef long kadm5_ret_t; #define KADM5_RANDKEY_USED 0x100000 #endif #define KADM5_LOAD 0x200000 -#define KADM5_NOKEY 0x400000 +#define KADM5_KEY_HIST 0x400000 /* all but KEY_DATA, TL_DATA, LOAD */ #define KADM5_PRINCIPAL_NORMAL_MASK 0x41ffff diff --git a/src/lib/kadm5/srv/svr_principal.c b/src/lib/kadm5/srv/svr_principal.c index 9b1efbbcce..f3791c6d14 100644 --- a/src/lib/kadm5/srv/svr_principal.c +++ b/src/lib/kadm5/srv/svr_principal.c @@ -1612,6 +1612,9 @@ kadm5_chpass_principal_3(void *server_handle, KADM5_FAIL_AUTH_COUNT; /* | KADM5_CPW_FUNCTION */ + if (hist_added) + kdb->mask |= KADM5_KEY_HIST; + ret = k5_kadm5_hook_chpass(handle->context, handle->hook_handles, KADM5_HOOK_STAGE_PRECOMMIT, principal, keepold, new_n_ks_tuple, new_ks_tuple, password); diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.c b/src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.c index aca8f31b05..96565c8bd4 100644 --- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.c +++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.c @@ -40,6 +40,7 @@ #include "ldap_pwd_policy.h" #include #include +#include #ifdef NEED_STRPTIME_PROTO extern char *strptime(const char *, const char *, struct tm *); @@ -1313,6 +1314,22 @@ remove_overlapping_subtrees(char **list, int *subtcount, int sscope) *subtcount = count; } +static void +free_princ_ent_contents(osa_princ_ent_t princ_ent) +{ + unsigned int i; + + for (i = 0; i < princ_ent->old_key_len; i++) { + k5_free_key_data(princ_ent->old_keys[i].n_key_data, + princ_ent->old_keys[i].key_data); + princ_ent->old_keys[i].n_key_data = 0; + princ_ent->old_keys[i].key_data = NULL; + } + free(princ_ent->old_keys); + princ_ent->old_keys = NULL; + princ_ent->old_key_len = 0; +} + /* * Fill out a krb5_db_entry princ entry struct given a LDAP message containing * the results of a principal search of the directory. @@ -1333,6 +1350,7 @@ populate_krb5_db_entry(krb5_context context, krb5_ldap_context *ldap_context, char **pnvalues = NULL, **ocvalues = NULL, **a2d2 = NULL; struct berval **ber_key_data = NULL, **ber_tl_data = NULL; krb5_tl_data userinfo_tl_data = { NULL }, **endp, *tl; + osa_princ_ent_rec princ_ent; ret = krb5_copy_principal(context, princ, &entry->princ); if (ret) @@ -1440,6 +1458,8 @@ populate_krb5_db_entry(krb5_context context, krb5_ldap_context *ldap_context, goto cleanup; } + memset(&princ_ent, 0, sizeof(osa_princ_ent_rec)); + ret = krb5_ldap_get_string(ld, ent, "krbpwdpolicyreference", &pwdpolicydn, &attr_present); if (ret) @@ -1451,8 +1471,21 @@ populate_krb5_db_entry(krb5_context context, krb5_ldap_context *ldap_context, ret = krb5_ldap_policydn_to_name(context, pwdpolicydn, &polname); if (ret) goto cleanup; + princ_ent.policy = polname; + princ_ent.aux_attributes |= KADM5_POLICY; + } + + ber_key_data = ldap_get_values_len(ld, ent, "krbpwdhistory"); + if (ber_key_data != NULL) { + mask |= KDB_PWD_HISTORY_ATTR; + ret = krb5_decode_histkey(context, ber_key_data, &princ_ent); + if (ret) + goto cleanup; + ldap_value_free_len(ber_key_data); + } - ret = krb5_update_tl_kadm_data(context, entry, polname); + if (princ_ent.aux_attributes) { + ret = krb5_update_tl_kadm_data(context, entry, &princ_ent); if (ret) goto cleanup; } @@ -1460,8 +1493,7 @@ populate_krb5_db_entry(krb5_context context, krb5_ldap_context *ldap_context, ber_key_data = ldap_get_values_len(ld, ent, "krbprincipalkey"); if (ber_key_data != NULL) { mask |= KDB_SECRET_KEY_ATTR; - ret = krb5_decode_krbsecretkey(context, entry, ber_key_data, - &userinfo_tl_data, &mkvno); + ret = krb5_decode_krbsecretkey(context, entry, ber_key_data, &mkvno); if (ret) goto cleanup; if (mkvno != 0) { @@ -1567,6 +1599,7 @@ cleanup: free(tktpolname); free(policydn); krb5_free_unparsed_name(context, user); + free_princ_ent_contents(&princ_ent); return ret; } diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c index 6a06f558c8..2beb1d0e1f 100644 --- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c +++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c @@ -59,6 +59,7 @@ char *principal_attributes[] = { "krbprincipalname", "krbExtraData", "krbObjectReferences", "krbAllowedToDelegateTo", + "krbPwdHistory", NULL }; /* Must match KDB_*_ATTR macros in ldap_principal.h. */ @@ -77,14 +78,38 @@ static char *attributes_set[] = { "krbmaxticketlife", "krbLastFailedAuth", "krbLoginFailedCount", "krbLastAdminUnlock", + "krbPwdHistory", NULL }; + +static void +k5_free_key_data_contents(krb5_key_data *key) +{ + int16_t i; + + for (i = 0; i < key->key_data_ver; i++) { + zapfree(key->key_data_contents[i], key->key_data_length[i]); + key->key_data_contents[i] = NULL; + } +} + +void +k5_free_key_data(krb5_int16 n_key_data, krb5_key_data *key_data) +{ + int16_t i; + + if (key_data == NULL) + return; + for (i = 0; i < n_key_data; i++) + k5_free_key_data_contents(&key_data[i]); + free(key_data); +} + void krb5_dbe_free_contents(krb5_context context, krb5_db_entry *entry) { krb5_tl_data *tl_data_next=NULL; krb5_tl_data *tl_data=NULL; - int i, j; if (entry->e_data) free(entry->e_data); @@ -96,24 +121,7 @@ krb5_dbe_free_contents(krb5_context context, krb5_db_entry *entry) free(tl_data->tl_data_contents); free(tl_data); } - if (entry->key_data) { - for (i = 0; i < entry->n_key_data; i++) { - for (j = 0; j < entry->key_data[i].key_data_ver; j++) { - if (entry->key_data[i].key_data_length[j]) { - if (entry->key_data[i].key_data_contents[j]) { - memset(entry->key_data[i].key_data_contents[j], - 0, - (unsigned) entry->key_data[i].key_data_length[j]); - free (entry->key_data[i].key_data_contents[j]); - } - } - entry->key_data[i].key_data_contents[j] = NULL; - entry->key_data[i].key_data_length[j] = 0; - entry->key_data[i].key_data_type[j] = 0; - } - } - free(entry->key_data); - } + k5_free_key_data(entry->n_key_data, entry->key_data); memset(entry, 0, sizeof(*entry)); return; } diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h index 4c51e79e7e..78229b98d4 100644 --- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h +++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h @@ -32,6 +32,7 @@ #define _LDAP_PRINCIPAL_H 1 #include "ldap_tkt_policy.h" +#include "princ_xdr.h" #define KEYHEADER 12 @@ -82,6 +83,7 @@ #define KDB_LAST_FAILED_ATTR 0x001000 #define KDB_FAIL_AUTH_COUNT_ATTR 0x002000 #define KDB_LAST_ADMIN_UNLOCK_ATTR 0x004000 +#define KDB_PWD_HISTORY_ATTR 0x008000 /* * This is a private contract between krb5_ldap_lockout_audit() @@ -111,6 +113,12 @@ krb5_ldap_iterate(krb5_context, char *, krb5_error_code (*)(krb5_pointer, krb5_db_entry *), krb5_pointer, krb5_flags); +void +k5_free_key_data(krb5_int16 n_key_data, krb5_key_data *key_data); + +void +krb5_dbe_free_contents(krb5_context context, krb5_db_entry *entry); + void krb5_dbe_free_contents(krb5_context, krb5_db_entry *); @@ -120,9 +128,12 @@ krb5_ldap_unparse_principal_name(char *); krb5_error_code krb5_ldap_parse_principal_name(char *, char **); +krb5_error_code +krb5_decode_histkey(krb5_context, struct berval **, osa_princ_ent_rec *); + krb5_error_code krb5_decode_krbsecretkey(krb5_context, krb5_db_entry *, struct berval **, - krb5_tl_data *, krb5_kvno *); + krb5_kvno *); krb5_error_code berval2tl_data(struct berval *in, krb5_tl_data **out); diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c index 5def4b77cb..503acc8fa9 100644 --- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c +++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c @@ -1,5 +1,34 @@ /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c */ +/* + * Copyright (C) 2016 by the Massachusetts Institute of Technology. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 + * COPYRIGHT HOLDER 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. + */ /* * Copyright (c) 2004-2005, Novell, Inc. * All rights reserved. @@ -361,13 +390,14 @@ asn1_encode_sequence_of_keys(krb5_key_data *key_data, krb5_int16 n_key_data, } static krb5_error_code -asn1_decode_sequence_of_keys(krb5_data *in, krb5_key_data **out, - krb5_int16 *n_key_data, krb5_kvno *mkvno) +asn1_decode_sequence_of_keys(krb5_data *in, ldap_seqof_key_data *out) { krb5_error_code err; ldap_seqof_key_data *p; int i; + memset(out, 0, sizeof(*out)); + /* * This should be pushed back into other library initialization * code. @@ -389,9 +419,7 @@ asn1_decode_sequence_of_keys(krb5_data *in, krb5_key_data **out, p->key_data[i].key_data_ver = 2; } - *out = p->key_data; - *n_key_data = p->n_key_data; - *mkvno = p->mkvno; + *out = *p; free(p); return 0; } @@ -415,19 +443,24 @@ free_berdata(struct berval **array) } } -/* Decoding ASN.1 encoded key */ -static struct berval ** -krb5_encode_krbsecretkey(krb5_key_data *key_data_in, int n_key_data, - krb5_kvno mkvno) { - struct berval **ret = NULL; - int currkvno; - int num_versions = 1; - int i, j, last; +/* + * Encode krb5_key_data into a berval struct for insertion into LDAP. + */ +static krb5_error_code +encode_keys(krb5_key_data *key_data_in, int n_key_data, krb5_kvno mkvno, + struct berval **bval_out) +{ krb5_error_code err = 0; + int i; krb5_key_data *key_data = NULL; + struct berval *bval = NULL; + krb5_data *code; - if (n_key_data < 0) - return NULL; + *bval_out = NULL; + if (n_key_data <= 0) { + err = EINVAL; + goto cleanup; + } /* Make a shallow copy of the key data so we can alter it. */ key_data = k5calloc(n_key_data, sizeof(*key_data), &err); @@ -446,31 +479,68 @@ krb5_encode_krbsecretkey(krb5_key_data *key_data_in, int n_key_data, } } + bval = k5alloc(sizeof(struct berval), &err); + if (bval == NULL) + goto cleanup; + + err = asn1_encode_sequence_of_keys(key_data, n_key_data, mkvno, &code); + if (err) + goto cleanup; + + /* Steal the data pointer from code for bval and discard code. */ + bval->bv_len = code->length; + bval->bv_val = code->data; + free(code); + + *bval_out = bval; + bval = NULL; + +cleanup: + free(key_data); + free(bval); + return err; +} + +/* Decoding ASN.1 encoded key */ +static struct berval ** +krb5_encode_krbsecretkey(krb5_key_data *key_data, int n_key_data, + krb5_kvno mkvno) +{ + struct berval **ret = NULL; + int currkvno; + int num_versions = 0; + int i, j, last; + krb5_error_code err = 0; + + if (n_key_data < 0) + return NULL; + /* Find the number of key versions */ - for (i = 0; i < n_key_data - 1; i++) - if (key_data[i].key_data_kvno != key_data[i + 1].key_data_kvno) - num_versions++; + if (n_key_data > 0) { + for (i = 0, num_versions = 1; i < n_key_data - 1; i++) { + if (key_data[i].key_data_kvno != key_data[i + 1].key_data_kvno) + num_versions++; + } + } - ret = (struct berval **) calloc (num_versions + 1, sizeof (struct berval *)); + ret = calloc(num_versions + 1, sizeof(struct berval *)); if (ret == NULL) { err = ENOMEM; goto cleanup; } - for (i = 0, last = 0, j = 0, currkvno = key_data[0].key_data_kvno; i < n_key_data; i++) { - krb5_data *code; + ret[num_versions] = NULL; + + /* n_key_data may be 0 if a principal is created without a key. */ + if (n_key_data == 0) + goto cleanup; + + currkvno = key_data[0].key_data_kvno; + for (i = 0, last = 0, j = 0; i < n_key_data; i++) { if (i == n_key_data - 1 || key_data[i + 1].key_data_kvno != currkvno) { - ret[j] = k5alloc(sizeof(struct berval), &err); - if (ret[j] == NULL) - goto cleanup; - err = asn1_encode_sequence_of_keys(key_data + last, - (krb5_int16)i - last + 1, - mkvno, &code); + err = encode_keys(key_data + last, (krb5_int16)i - last + 1, mkvno, + &ret[j]); if (err) goto cleanup; - /*CHECK_NULL(ret[j]); */ - ret[j]->bv_len = code->length; - ret[j]->bv_val = code->data; - free(code); j++; last = i + 1; @@ -478,11 +548,48 @@ krb5_encode_krbsecretkey(krb5_key_data *key_data_in, int n_key_data, currkvno = key_data[i + 1].key_data_kvno; } } - ret[num_versions] = NULL; cleanup: + if (err != 0) { + free_berdata(ret); + ret = NULL; + } - free(key_data); + return ret; +} + +/* + * Encode a principal's key history for insertion into ldap. + */ +static struct berval ** +krb5_encode_histkey(osa_princ_ent_rec *princ_ent) +{ + unsigned int i; + krb5_error_code err = 0; + struct berval **ret = NULL; + + if (princ_ent->old_key_len <= 0) + return NULL; + + ret = k5calloc(princ_ent->old_key_len + 1, sizeof(struct berval *), &err); + if (ret == NULL) + goto cleanup; + + for (i = 0; i < princ_ent->old_key_len; i++) { + if (princ_ent->old_keys[i].n_key_data <= 0) { + err = EINVAL; + goto cleanup; + } + err = encode_keys(princ_ent->old_keys[i].key_data, + princ_ent->old_keys[i].n_key_data, + princ_ent->admin_history_kvno, &ret[i]); + if (err) + goto cleanup; + } + + ret[princ_ent->old_key_len] = NULL; + +cleanup: if (err != 0) { free_berdata(ret); ret = NULL; @@ -1003,7 +1110,7 @@ krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry, free (strval[0]); } - if (entry->mask & KADM5_POLICY) { + if (entry->mask & KADM5_POLICY || entry->mask & KADM5_KEY_HIST) { memset(&princ_ent, 0, sizeof(princ_ent)); for (tl_data=entry->tl_data; tl_data; tl_data=tl_data->tl_data_next) { if (tl_data->tl_data_type == KRB5_TL_KADM_DATA) { @@ -1013,7 +1120,9 @@ krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry, break; } } + } + if (entry->mask & KADM5_POLICY) { if (princ_ent.aux_attributes & KADM5_POLICY) { memset(strval, 0, sizeof(strval)); if ((st = krb5_ldap_name_to_policydn (context, princ_ent.policy, &polname)) != 0) @@ -1041,6 +1150,22 @@ krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry, goto cleanup; } + if (entry->mask & KADM5_KEY_HIST) { + bersecretkey = krb5_encode_histkey(&princ_ent); + if (bersecretkey == NULL) { + st = ENOMEM; + goto cleanup; + } + + st = krb5_add_ber_mem_ldap_mod(&mods, "krbpwdhistory", + LDAP_MOD_REPLACE | LDAP_MOD_BVALUES, + bersecretkey); + if (st != 0) + goto cleanup; + free_berdata(bersecretkey); + bersecretkey = NULL; + } + if (entry->mask & KADM5_KEY_DATA || entry->mask & KADM5_KVNO) { krb5_kvno mkvno; @@ -1375,22 +1500,62 @@ cleanup: return st; } -krb5_error_code -krb5_decode_krbsecretkey(krb5_context context, krb5_db_entry *entries, - struct berval **bvalues, - krb5_tl_data *userinfo_tl_data, krb5_kvno *mkvno) +static void +free_ldap_seqof_key_data(ldap_seqof_key_data *keysets, krb5_int16 n_keysets) { - char *user=NULL; - int i=0, j=0, noofkeys=0; - krb5_key_data *key_data=NULL, *tmp; - krb5_error_code st=0; + int i; - if ((st=krb5_unparse_name(context, entries->princ, &user)) != 0) + if (keysets == NULL) + return; + + for (i = 0; i < n_keysets; i++) + k5_free_key_data(keysets[i].n_key_data, keysets[i].key_data); + free(keysets); +} + +/* + * Decode keys from ldap search results. + * + * Arguments: + * - bvalues + * The ldap search results containing the key data. + * - mkvno + * The master kvno that the keys were encrypted with. + * - keysets_out + * The decoded keys in a ldap_seqof_key_data struct. Must be freed using + * free_ldap_seqof_key_data. + * - n_keysets_out + * The number of entries in keys_out. + * - total_keys_out + * An optional argument that if given will be set to the total number of + * keys found throughout all the entries: sum(keys_out.n_key_data) + * May be NULL. + */ +static krb5_error_code +decode_keys(struct berval **bvalues, ldap_seqof_key_data **keysets_out, + krb5_int16 *n_keysets_out, krb5_int16 *total_keys_out) +{ + krb5_error_code err = 0; + krb5_int16 n_keys, i, ki, total_keys; + ldap_seqof_key_data *keysets = NULL; + + *keysets_out = NULL; + *n_keysets_out = 0; + if (total_keys_out) + *total_keys_out = 0; + + /* Precount the number of keys. */ + for (n_keys = 0, i = 0; bvalues[i] != NULL; i++) { + if (bvalues[i]->bv_len > 0) + n_keys++; + } + + keysets = k5calloc(n_keys, sizeof(ldap_seqof_key_data), &err); + if (keysets == NULL) goto cleanup; + memset(keysets, 0, n_keys * sizeof(ldap_seqof_key_data)); - for (i=0; bvalues[i] != NULL; ++i) { - krb5_int16 n_kd; - krb5_key_data *kd; + for (i = 0, ki = 0, total_keys = 0; bvalues[i] != NULL; i++) { krb5_data in; if (bvalues[i]->bv_len == 0) @@ -1398,39 +1563,131 @@ krb5_decode_krbsecretkey(krb5_context context, krb5_db_entry *entries, in.length = bvalues[i]->bv_len; in.data = bvalues[i]->bv_val; - st = asn1_decode_sequence_of_keys (&in, - &kd, - &n_kd, - mkvno); - - if (st != 0) { - const char *msg = error_message(st); - st = -1; /* Something more appropriate ? */ - k5_setmsg(context, st, - _("unable to decode stored principal key data (%s)"), - msg); + err = asn1_decode_sequence_of_keys(&in, &keysets[ki]); + if (err) goto cleanup; - } - noofkeys += n_kd; - tmp = key_data; - /* Allocate an extra key data to avoid allocating zero bytes. */ - key_data = realloc(key_data, (noofkeys + 1) * sizeof (krb5_key_data)); - if (key_data == NULL) { - key_data = tmp; - st = ENOMEM; - goto cleanup; - } - for (j = 0; j < n_kd; j++) - key_data[noofkeys - n_kd + j] = kd[j]; - free (kd); + + if (total_keys_out) + total_keys += keysets[ki].n_key_data; + ki++; + } + + if (total_keys_out) + *total_keys_out = total_keys; + + *n_keysets_out = n_keys; + *keysets_out = keysets; + keysets = NULL; + n_keys = 0; + +cleanup: + free_ldap_seqof_key_data(keysets, n_keys); + return err; +} + +krb5_error_code +krb5_decode_krbsecretkey(krb5_context context, krb5_db_entry *entries, + struct berval **bvalues, krb5_kvno *mkvno) +{ + krb5_key_data *key_data = NULL, *tmp; + krb5_error_code err = 0; + ldap_seqof_key_data *keysets = NULL; + krb5_int16 i, n_keysets = 0, total_keys = 0; + + err = decode_keys(bvalues, &keysets, &n_keysets, &total_keys); + if (err != 0) { + k5_prependmsg(context, err, + _("unable to decode stored principal key data")); + goto cleanup; } - entries->n_key_data = noofkeys; + key_data = k5calloc(total_keys, sizeof(krb5_key_data), &err); + if (key_data == NULL) + goto cleanup; + memset(key_data, 0, total_keys * sizeof(krb5_key_data)); + + if (n_keysets > 0) + *mkvno = keysets[0].mkvno; + + /* Transfer key data values from keysets to a flat list in entries. */ + tmp = key_data; + for (i = 0; i < n_keysets; i++) { + memcpy(tmp, keysets[i].key_data, + sizeof(krb5_key_data) * keysets[i].n_key_data); + tmp += keysets[i].n_key_data; + keysets[i].n_key_data = 0; + } + entries->n_key_data = total_keys; entries->key_data = key_data; + key_data = NULL; cleanup: - free (user); - return st; + free_ldap_seqof_key_data(keysets, n_keysets); + k5_free_key_data(total_keys, key_data); + return err; +} + +static int +compare_osa_pw_hist_ent(const void *left_in, const void *right_in) +{ + int kvno_left, kvno_right; + osa_pw_hist_ent *left = (osa_pw_hist_ent *)left_in; + osa_pw_hist_ent *right = (osa_pw_hist_ent *)right_in; + + kvno_left = left->n_key_data ? left->key_data[0].key_data_kvno : 0; + kvno_right = right->n_key_data ? right->key_data[0].key_data_kvno : 0; + return kvno_left - kvno_right; +} + +/* + * Decode the key history entries from an LDAP search. + * + * NOTE: the caller must free princ_ent->old_keys even on error. + */ +krb5_error_code +krb5_decode_histkey(krb5_context context, struct berval **bvalues, + osa_princ_ent_rec *princ_ent) +{ + krb5_error_code err = 0; + krb5_int16 i, n_keysets = 0; + ldap_seqof_key_data *keysets = NULL; + + err = decode_keys(bvalues, &keysets, &n_keysets, NULL); + if (err != 0) { + k5_prependmsg(context, err, + _("unable to decode stored principal pw history")); + goto cleanup; + } + + princ_ent->old_keys = k5calloc(n_keysets, sizeof(osa_pw_hist_ent), &err); + if (princ_ent->old_keys == NULL) + goto cleanup; + princ_ent->old_key_len = n_keysets; + + if (n_keysets > 0) + princ_ent->admin_history_kvno = keysets[0].mkvno; + + /* Transfer key data pointers from keysets to princ_ent. */ + for (i = 0; i < n_keysets; i++) { + princ_ent->old_keys[i].n_key_data = keysets[i].n_key_data; + princ_ent->old_keys[i].key_data = keysets[i].key_data; + keysets[i].n_key_data = 0; + keysets[i].key_data = NULL; + } + + /* Sort the principal entries by kvno in ascending order. */ + qsort(princ_ent->old_keys, princ_ent->old_key_len, sizeof(osa_pw_hist_ent), + &compare_osa_pw_hist_ent); + + princ_ent->aux_attributes |= KADM5_KEY_HIST; + + /* Set the next key to the end of the list. The queue will be lengthened + * if it isn't full yet; the first entry will be replaced if it is full. */ + princ_ent->old_key_next = princ_ent->old_key_len; + +cleanup: + free_ldap_seqof_key_data(keysets, n_keysets); + return err; } static char * diff --git a/src/plugins/kdb/ldap/libkdb_ldap/princ_xdr.c b/src/plugins/kdb/ldap/libkdb_ldap/princ_xdr.c index cf1201d607..74f0ce1a39 100644 --- a/src/plugins/kdb/ldap/libkdb_ldap/princ_xdr.c +++ b/src/plugins/kdb/ldap/libkdb_ldap/princ_xdr.c @@ -200,20 +200,14 @@ krb5_lookup_tl_kadm_data(krb5_tl_data *tl_data, osa_princ_ent_rec *princ_entry) krb5_error_code krb5_update_tl_kadm_data(krb5_context context, krb5_db_entry *entry, - char *policy_dn) + osa_princ_ent_rec *princ_entry) { XDR xdrs; - osa_princ_ent_rec princ_entry; krb5_tl_data tl_data; krb5_error_code retval; - memset(&princ_entry, 0, sizeof(osa_princ_ent_rec)); - princ_entry.admin_history_kvno = 2; - princ_entry.aux_attributes = KADM5_POLICY; - princ_entry.policy = policy_dn; - xdralloc_create(&xdrs, XDR_ENCODE); - if (! ldap_xdr_osa_princ_ent_rec(&xdrs, &princ_entry)) { + if (! ldap_xdr_osa_princ_ent_rec(&xdrs, princ_entry)) { xdr_destroy(&xdrs); return KADM5_XDR_FAILURE; } diff --git a/src/plugins/kdb/ldap/libkdb_ldap/princ_xdr.h b/src/plugins/kdb/ldap/libkdb_ldap/princ_xdr.h index 78ce2d0994..b4732c50e3 100644 --- a/src/plugins/kdb/ldap/libkdb_ldap/princ_xdr.h +++ b/src/plugins/kdb/ldap/libkdb_ldap/princ_xdr.h @@ -57,6 +57,6 @@ krb5_lookup_tl_kadm_data(krb5_tl_data *tl_data, osa_princ_ent_rec *princ_entry); krb5_error_code krb5_update_tl_kadm_data(krb5_context context, krb5_db_entry *entry, - char *policy_dn); + osa_princ_ent_rec *princ_entry); #endif diff --git a/src/tests/kdbtest.c b/src/tests/kdbtest.c index 7c1d5158d9..3f63cfb5d1 100644 --- a/src/tests/kdbtest.c +++ b/src/tests/kdbtest.c @@ -97,7 +97,7 @@ static krb5_tl_data tl3 = { &tl4, KRB5_TL_KADM_DATA, 32, U("\x12\x34\x5C\x01\x00\x00\x00\x08" "\x3C\x74\x65\x73\x74\x2A\x3E\x00" "\x00\x00\x08\x00\x00\x00\x00\x00" - "\x00\x00\x00\x02\x00\x00\x00\x00") }; + "\x00\x00\x00\x00\x00\x00\x00\x00") }; static krb5_tl_data tl2 = { &tl3, KRB5_TL_MOD_PRINC, 8, U("\5\6\7\0x@Y\0") }; static krb5_tl_data tl1 = { &tl2, KRB5_TL_LAST_PWD_CHANGE, 4, U("\1\2\3\4") }; diff --git a/src/tests/t_kdb.py b/src/tests/t_kdb.py index 27d0e3fe2e..132869d039 100755 --- a/src/tests/t_kdb.py +++ b/src/tests/t_kdb.py @@ -336,6 +336,31 @@ realm.run([kadminl, 'modprinc', '+requires_preauth', 'canon']) realm.kinit('canon', password('canon')) realm.kinit('alias', password('canon'), ['-C']) +# Test password history. +def test_pwhist(nhist): + def cpw(n, **kwargs): + realm.run([kadminl, 'cpw', '-pw', str(n), princ], **kwargs) + def cpw_fail(n): + cpw(n, expected_code=1) + output('*** Testing password history of size %d\n' % nhist) + princ = 'pwhistprinc' + str(nhist) + pol = 'pwhistpol' + str(nhist) + realm.run([kadminl, 'addpol', '-history', str(nhist), pol]) + realm.run([kadminl, 'addprinc', '-policy', pol, '-nokey', princ]) + for i in range(nhist): + # Set a password, then check that all previous passwords fail. + cpw(i) + for j in range(i + 1): + cpw_fail(j) + # Set one more new password, and make sure the oldest key is + # rotated out. + cpw(nhist) + cpw_fail(1) + cpw(0) + +for n in (1, 2, 3, 4, 5): + test_pwhist(n) + # Regression test for #7980 (fencepost when dividing keys up by kvno). realm.run([kadminl, 'addprinc', '-randkey', '-e', 'aes256-cts,aes128-cts', 'kvnoprinc'])