krb5 commit: Implement password history in LDAP KDB module
Greg Hudson
ghudson at mit.edu
Wed Feb 3 13:03:13 EST 2016
https://github.com/krb5/krb5/commit/b46cce2ea8c0841f7f93db73eefcd180c87a3eae
commit b46cce2ea8c0841f7f93db73eefcd180c87a3eae
Author: Sarah Day <sarahday at mit.edu>
Date: Tue Jan 26 12:22:41 2016 -0500
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
src/lib/kadm5/admin.h | 2 +-
src/lib/kadm5/srv/svr_principal.c | 3 +
src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.c | 39 ++-
src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c | 46 ++-
src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h | 13 +-
src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c | 403 ++++++++++++++++----
src/plugins/kdb/ldap/libkdb_ldap/princ_xdr.c | 10 +-
src/plugins/kdb/ldap/libkdb_ldap/princ_xdr.h | 2 +-
src/tests/kdbtest.c | 2 +-
src/tests/t_kdb.py | 25 ++
10 files changed, 438 insertions(+), 107 deletions(-)
diff --git a/src/lib/kadm5/admin.h b/src/lib/kadm5/admin.h
index 8f377f8..73f2811 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 9b1efbb..f3791c6 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 aca8f31..96565c8 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 <time.h>
#include <ctype.h>
+#include <kadm5/admin.h>
#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 6a06f55..2beb1d0 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 4c51e79..78229b9 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()
@@ -112,6 +114,12 @@ krb5_ldap_iterate(krb5_context, char *,
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 *);
krb5_error_code
@@ -121,8 +129,11 @@ 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 5def4b7..503acc8 100644
--- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c
+++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c
@@ -1,6 +1,35 @@
/* -*- 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 cf1201d..74f0ce1 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 78ce2d0..b4732c5 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 7c1d515..3f63cfb 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 at 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 27d0e3f..132869d 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'])
More information about the cvs-krb5
mailing list