]> git.ipfire.org Git - thirdparty/krb5.git/commitdiff
Implement password history in LDAP KDB module
authorSarah Day <sarahday@mit.edu>
Tue, 26 Jan 2016 17:22:41 +0000 (12:22 -0500)
committerGreg Hudson <ghudson@mit.edu>
Wed, 3 Feb 2016 18:02:48 +0000 (13:02 -0500)
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
src/lib/kadm5/srv/svr_principal.c
src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.c
src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c
src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h
src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c
src/plugins/kdb/ldap/libkdb_ldap/princ_xdr.c
src/plugins/kdb/ldap/libkdb_ldap/princ_xdr.h
src/tests/kdbtest.c
src/tests/t_kdb.py

index 8f377f804fc810c558994c1f524e5e38b5cb2a2a..73f281129a269fcde5e016de3d90c9440f3cd392 100644 (file)
@@ -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
index 9b1efbbcceccfcc4bd122ce49c3ac6b2f0efd857..f3791c6d14abbcd93719615f42700b8aab1b94d6 100644 (file)
@@ -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);
index aca8f31b05b18f8d1c11897303a05e72424e5679..96565c8bd43cb3a051c73d7d69fc331aa8003610 100644 (file)
@@ -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;
 }
 
index 6a06f558c820b172efefa643336c628101d1daa8..2beb1d0e1fb765885d0d7c20facdedea2c77a6a7 100644 (file)
@@ -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;
 }
index 4c51e79e7e4b3c8a149b33ddf7880043e4b51651..78229b98d4a7d2ecdbabbf2495e14530d9da5d58 100644 (file)
@@ -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);
index 5def4b77cb2ad6225a5128cb7010d0d9505abdf8..503acc8fa985b1a2702e16de11aabba1e06cfebe 100644 (file)
@@ -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 *
index cf1201d607e5e8e086373fa3afc9bace92fd4280..74f0ce1a391de9d6317e750fd06029a4fca399a0 100644 (file)
@@ -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;
     }
index 78ce2d09943844d26f0c683ea240ed1ddcf91b7e..b4732c50e37b391c7097a5085904981ecfd8cd67 100644 (file)
@@ -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
index 7c1d5158d9fe617066df496850484ed63252f92b..3f63cfb5d102b244ad41f398143b52318a6dd0cd 100644 (file)
@@ -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") };
 
index 27d0e3fe2ece5e13afbc3f2e2c76fb9bd90757c5..132869d039dd6ab4c499e964465823fd9e896141 100755 (executable)
@@ -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'])