#
# This patch cherry-picks Password history in LDAP KDB plugin feature from
# MIT krb5 1.15.
#
# It is 1-1 port of the following changesets:
# 44ad57d8d38efc944f64536354435f5b721c0ee0
# d7f91ac2f6655e77bb3658c2c8cc6132f958a340
# b46cce2ea8c0841f7f93db73eefcd180c87a3eae
# 9526953f36b39323ec07448a5f218d27c6f1c76f
#
# Patch source: upstream
#
# When upgrading to MIT krb5 1.15 this patch will be dropped.
#
@@ -1,6 +1,6 @@
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
- * Copyright 1990,1991 by the Massachusetts Institute of Technology.
+ * Copyright 1990, 1991, 2016 by the Massachusetts Institute of Technology.
* All Rights Reserved.
*
* Export of this software from the United States of America may
@@ -209,6 +209,8 @@ typedef struct _krb5_db_entry_new {
krb5_principal princ; /* Length, data */
krb5_tl_data * tl_data; /* Linked list */
+
+ /* key_data must be sorted by kvno in descending order. */
krb5_key_data * key_data; /* Array */
} krb5_db_entry;
@@ -683,6 +685,19 @@ krb5_error_code krb5_db_check_allowed_to
const krb5_db_entry *server,
krb5_const_principal proxy);
+/**
+ * Sort an array of @a krb5_key_data keys in descending order by their kvno.
+ * Key data order within a kvno is preserved.
+ *
+ * @param key_data
+ * The @a krb5_key_data array to sort. This is sorted in place so the
+ * array will be modified.
+ * @param key_data_length
+ * The length of @a key_data.
+ */
+void
+krb5_dbe_sort_key_data(krb5_key_data *key_data, size_t key_data_length);
+
/* default functions. Should not be directly called */
/*
* Default functions prototype
@@ -113,7 +113,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
@@ -1084,6 +1084,16 @@ check_pw_reuse(krb5_context context,
return(0);
}
+static void
+free_history_entry(krb5_context context, osa_pw_hist_ent *hist)
+{
+ int i;
+
+ for (i = 0; i < hist->n_key_data; i++)
+ krb5_free_key_data_contents(context, &hist->key_data[i]);
+ free(hist->key_data);
+}
+
/*
* Function: create_history_entry
*
@@ -1097,7 +1107,7 @@ check_pw_reuse(krb5_context context,
* hist_key (r) history keyblock to encrypt key data with
* n_key_data (r) number of elements in key_data
* key_data (r) keys to add to the history entry
- * hist (w) history entry to fill in
+ * hist_out (w) history entry to fill in
*
* Effects:
*
@@ -1109,45 +1119,62 @@ check_pw_reuse(krb5_context context,
static
int create_history_entry(krb5_context context,
krb5_keyblock *hist_key, int n_key_data,
- krb5_key_data *key_data, osa_pw_hist_ent *hist)
+ krb5_key_data *key_data, osa_pw_hist_ent *hist_out)
{
- krb5_error_code ret;
+ int i;
+ krb5_error_code ret = 0;
krb5_keyblock key;
krb5_keysalt salt;
- int i;
+ krb5_ui_2 kvno;
+ osa_pw_hist_ent hist;
+
+ hist_out->key_data = NULL;
+ hist_out->n_key_data = 0;
+
+ if (n_key_data < 0)
+ return EINVAL;
+
+ memset(&key, 0, sizeof(key));
+ memset(&hist, 0, sizeof(hist));
+
+ if (n_key_data == 0)
+ goto cleanup;
- hist->key_data = k5calloc(n_key_data, sizeof(krb5_key_data), &ret);
- if (hist->key_data == NULL)
- return ret;
+ hist.key_data = k5calloc(n_key_data, sizeof(krb5_key_data), &ret);
+ if (hist.key_data == NULL)
+ goto cleanup;
+
+ /* We only want to store the most recent kvno, and key_data should already
+ * be sorted in descending order by kvno. */
+ kvno = key_data[0].key_data_kvno;
for (i = 0; i < n_key_data; i++) {
- ret = krb5_dbe_decrypt_key_data(context, NULL, &key_data[i], &key,
+ if (key_data[i].key_data_kvno < kvno)
+ break;
+ ret = krb5_dbe_decrypt_key_data(context, NULL,
+ &key_data[i], &key,
&salt);
if (ret)
- return ret;
+ goto cleanup;
ret = krb5_dbe_encrypt_key_data(context, hist_key, &key, &salt,
key_data[i].key_data_kvno,
- &hist->key_data[i]);
+ &hist.key_data[hist.n_key_data]);
if (ret)
- return ret;
-
+ goto cleanup;
+ hist.n_key_data++;
krb5_free_keyblock_contents(context, &key);
/* krb5_free_keysalt(context, &salt); */
}
- hist->n_key_data = n_key_data;
- return 0;
-}
-
-static
-void free_history_entry(krb5_context context, osa_pw_hist_ent *hist)
-{
- int i;
-
- for (i = 0; i < hist->n_key_data; i++)
- krb5_free_key_data_contents(context, &hist->key_data[i]);
- free(hist->key_data);
+ *hist_out = hist;
+ hist.n_key_data = 0;
+ hist.key_data = NULL;
+
+cleanup:
+ krb5_free_keyblock_contents(context, &key);
+ free_history_entry(context, &hist);
+ return ret;
}
/*
@@ -1526,11 +1553,14 @@ kadm5_chpass_principal_3(void *server_ha
goto done;
}
- ret = add_to_history(handle->context, hist_kvno, &adb, &pol,
- &hist);
- if (ret)
- goto done;
- hist_added = 1;
+ /* Don't save empty history. */
+ if (hist.n_key_data > 0) {
+ ret = add_to_history(handle->context, hist_kvno, &adb, &pol,
+ &hist);
+ if (ret)
+ goto done;
+ hist_added = 1;
+ }
}
if (pol.pw_max_life)
@@ -1582,6 +1612,9 @@ kadm5_chpass_principal_3(void *server_ha
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);
@@ -1,6 +1,7 @@
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
- * Copyright 2006, 2009, 2010 by the Massachusetts Institute of Technology.
+ * Copyright 2006, 2009, 2010, 2016 by the Massachusetts Institute of
+ * Technology.
* All Rights Reserved.
*
* Export of this software from the United States of America may
@@ -758,7 +759,15 @@ krb5_db_get_principal(krb5_context kcont
return status;
if (v->get_principal == NULL)
return KRB5_PLUGIN_OP_NOTSUPP;
- return v->get_principal(kcontext, search_for, flags, entry);
+ status = v->get_principal(kcontext, search_for, flags, entry);
+ if (status)
+ return status;
+
+ /* Sort the keys in the db entry as some parts of krb5 expect it to be. */
+ if ((*entry)->key_data != NULL)
+ krb5_dbe_sort_key_data((*entry)->key_data, (*entry)->n_key_data);
+
+ return 0;
}
void
@@ -948,6 +957,26 @@ krb5_db_delete_principal(krb5_context kc
return status;
}
+/*
+ * Use a proxy function for iterate so that we can sort the keys before sending
+ * them to the callback.
+ */
+struct callback_proxy_args {
+ int (*func)(krb5_pointer, krb5_db_entry *);
+ krb5_pointer func_arg;
+};
+
+static int
+sort_entry_callback_proxy(krb5_pointer func_arg, krb5_db_entry *entry)
+{
+ struct callback_proxy_args *args = (struct callback_proxy_args *)func_arg;
+
+ /* Sort the keys in the db entry as some parts of krb5 expect it to be. */
+ if (entry && entry->key_data)
+ krb5_dbe_sort_key_data(entry->key_data, entry->n_key_data);
+ return args->func(args->func_arg, entry);
+}
+
krb5_error_code
krb5_db_iterate(krb5_context kcontext, char *match_entry,
int (*func)(krb5_pointer, krb5_db_entry *),
@@ -955,13 +984,20 @@ krb5_db_iterate(krb5_context kcontext, c
{
krb5_error_code status = 0;
kdb_vftabl *v;
+ struct callback_proxy_args proxy_args;
status = get_vftabl(kcontext, &v);
if (status)
return status;
if (v->iterate == NULL)
return KRB5_PLUGIN_OP_NOTSUPP;
- return v->iterate(kcontext, match_entry, func, func_arg, iterflags);
+
+ /* Use the proxy function to sort key data before passing entries to
+ * callback. */
+ proxy_args.func = func;
+ proxy_args.func_arg = func_arg;
+ return v->iterate(kcontext, match_entry, sort_entry_callback_proxy,
+ &proxy_args, iterflags);
}
/* Return a read only pointer alias to mkey list. Do not free this! */
@@ -2570,3 +2606,22 @@ krb5_db_check_allowed_to_delegate(krb5_c
return KRB5_PLUGIN_OP_NOTSUPP;
return v->check_allowed_to_delegate(kcontext, client, server, proxy);
}
+
+void
+krb5_dbe_sort_key_data(krb5_key_data *key_data, size_t key_data_length)
+{
+ size_t i, j;
+ krb5_key_data tmp;
+
+ /* Use insertion sort as a stable sort. */
+ for (i = 1; i < key_data_length; i++) {
+ j = i;
+ while (j > 0 &&
+ key_data[j - 1].key_data_kvno < key_data[j].key_data_kvno) {
+ tmp = key_data[j];
+ key_data[j] = key_data[j - 1];
+ key_data[j - 1] = tmp;
+ j--;
+ }
+ }
+}
@@ -99,3 +99,4 @@ ulog_get_sno_status
ulog_replay
ulog_set_last
xdr_kdb_incr_update_t
+krb5_dbe_sort_key_data
@@ -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 *);
@@ -1324,6 +1325,22 @@ remove_overlapping_subtrees(char **list,
*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.
@@ -1344,6 +1361,9 @@ populate_krb5_db_entry(krb5_context cont
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;
+
+ memset(&princ_ent, 0, sizeof(princ_ent));
ret = krb5_copy_principal(context, princ, &entry->princ);
if (ret)
@@ -1462,8 +1482,21 @@ populate_krb5_db_entry(krb5_context cont
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;
}
@@ -1471,8 +1504,7 @@ populate_krb5_db_entry(krb5_context cont
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) {
@@ -1578,6 +1610,7 @@ cleanup:
free(tktpolname);
free(policydn);
krb5_free_unparsed_name(context, user);
+ free_princ_ent_contents(&princ_ent);
return ret;
}
@@ -59,6 +59,7 @@ char *principal_attributes[] = { "kr
"krbExtraData",
"krbObjectReferences",
"krbAllowedToDelegateTo",
+ "krbPwdHistory",
NULL };
/* Must match KDB_*_ATTR macros in ldap_principal.h. */
@@ -77,14 +78,38 @@ static char *attributes_set[] = { "krbma
"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 cont
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;
}
@@ -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);
@@ -1,6 +1,35 @@
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
+ * 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.
*
@@ -362,13 +391,14 @@ asn1_encode_sequence_of_keys(krb5_key_da
}
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.
@@ -390,9 +420,7 @@ asn1_decode_sequence_of_keys(krb5_data *
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;
}
@@ -416,19 +444,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);
@@ -447,31 +480,68 @@ krb5_encode_krbsecretkey(krb5_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;
@@ -479,11 +549,48 @@ krb5_encode_krbsecretkey(krb5_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;
@@ -1004,7 +1111,7 @@ krb5_ldap_put_principal(krb5_context con
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) {
@@ -1014,7 +1121,9 @@ krb5_ldap_put_principal(krb5_context con
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)
@@ -1042,6 +1151,22 @@ krb5_ldap_put_principal(krb5_context con
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;
@@ -1376,22 +1501,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)
@@ -1399,39 +1564,131 @@ krb5_decode_krbsecretkey(krb5_context co
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);
- 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;
+ err = asn1_decode_sequence_of_keys(&in, &keysets[ki]);
+ if (err)
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 *
@@ -204,20 +204,14 @@ krb5_lookup_tl_kadm_data(krb5_tl_data *t
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.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;
}
@@ -57,6 +57,6 @@ krb5_lookup_tl_kadm_data(krb5_tl_data *t
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
@@ -97,7 +97,7 @@ static krb5_tl_data tl3 = { &tl4, KRB5_T
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") };
@@ -337,6 +337,31 @@ realm.run([kadminl, 'modprinc', '+requir
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'])
@@ -368,6 +393,13 @@ out = realm.run([kadminl, 'getprinc', 'k
if 'Number of keys: 0' not in out:
fail('After purgekeys -all, keys remain')
+# Test for 8354 (old password history entries when -keepold is used)
+realm.run([kadminl, 'addpol', '-history', '2', 'keepoldpasspol'])
+realm.run([kadminl, 'addprinc', '-policy', 'keepoldpasspol', '-pw', 'aaaa',
+ 'keepoldpassprinc'])
+for p in ('bbbb', 'cccc', 'aaaa'):
+ realm.run([kadminl, 'cpw', '-keepold', '-pw', p, 'keepoldpassprinc'])
+
# Briefly test dump and load.