int ks_tuple_count,
char * passwd,
int new_kvno,
- krb5_boolean keepold,
+ unsigned int keepold,
krb5_db_entry * db_entry);
krb5_keyblock * master_key,
krb5_key_salt_tuple * ks_tuple,
int ks_tuple_count,
- krb5_boolean keepold,
+ unsigned int keepold,
krb5_db_entry * db_entry);
krb5_error_code
int ks_tuple_count,
char * passwd,
int new_kvno,
- krb5_boolean keepold,
+ unsigned int keepold,
krb5_db_entry * db_entry);
krb5_error_code
krb5_keyblock *master_key,
krb5_key_salt_tuple *ks_tuple,
int ks_tuple_count, char *passwd,
- int new_kvno, krb5_boolean keepold,
+ int new_kvno, unsigned int keepold,
krb5_db_entry *db_entry);
/*
#include "misc.h"
#include "auth.h"
+/* A principal changing its own keys may retain at most this many key
+ * versions. */
+#define MAX_SELF_KEEPOLD 5
+
extern gss_name_t gss_changepw_name;
extern gss_name_t gss_oldchangepw_name;
extern void * global_server_handle;
return check_min_life(handle, princ, NULL, 0);
}
+/*
+ * Return the appropriate libkadm5 keepold value for a key change request,
+ * given the boolean keepold input from the client. 0 means discard all old
+ * keys, 1 means retain all old keys, and a greater value means to retain that
+ * many key versions including the new one. A principal modifying its own keys
+ * is allowed to retain only a limited number of key versions.
+ */
+static unsigned int
+clamp_self_keepold(kadm5_server_handle_t handle, krb5_principal princ,
+ krb5_boolean keepold)
+{
+ if (!keepold)
+ return 0;
+ if (krb5_principal_compare(handle->context, handle->current_caller, princ))
+ return MAX_SELF_KEEPOLD;
+ return 1;
+}
+
static int
log_unauth(
char *op,
kadm5_principal_ent_rec rec = { 0 };
gss_buffer_desc client_name = GSS_C_EMPTY_BUFFER;
gss_buffer_desc service_name = GSS_C_EMPTY_BUFFER;
+ unsigned int keepold;
kadm5_server_handle_t handle;
const char *errmsg = NULL;
} else {
ret->code = check_self_keychange(handle, rqstp, rec.principal);
if (!ret->code) {
+ keepold = clamp_self_keepold(handle, rec.principal, arg->keepold);
ret->code = kadm5_chpass_principal_3(handle, rec.principal,
- arg->keepold, arg->n_ks_tuple,
+ keepold, arg->n_ks_tuple,
arg->ks_tuple, arg->pass);
}
}
kadm5_principal_ent_rec rec = { 0 };
gss_buffer_desc client_name = GSS_C_EMPTY_BUFFER;
gss_buffer_desc service_name = GSS_C_EMPTY_BUFFER;
+ unsigned int keepold;
kadm5_server_handle_t handle;
const char *errmsg = NULL;
}
} else if (!(CHANGEPW_SERVICE(rqstp)) &&
stub_auth(handle, OP_SETKEY, rec.principal, NULL, NULL, NULL)) {
- ret->code = kadm5_setkey_principal_3(handle, rec.principal,
- arg->keepold, arg->n_ks_tuple,
- arg->ks_tuple, arg->keyblocks,
- arg->n_keys);
+ keepold = clamp_self_keepold(handle, rec.principal, arg->keepold);
+ ret->code = kadm5_setkey_principal_3(handle, rec.principal, keepold,
+ arg->n_ks_tuple, arg->ks_tuple,
+ arg->keyblocks, arg->n_keys);
} else {
log_unauth("kadm5_setkey_principal", prime_arg,
&client_name, &service_name, rqstp);
kadm5_principal_ent_rec rec = { 0 };
gss_buffer_desc client_name = GSS_C_EMPTY_BUFFER;
gss_buffer_desc service_name = GSS_C_EMPTY_BUFFER;
+ unsigned int keepold;
kadm5_server_handle_t handle;
const char *errmsg = NULL;
}
} else if (!(CHANGEPW_SERVICE(rqstp)) &&
stub_auth(handle, OP_SETKEY, rec.principal, NULL, NULL, NULL)) {
- ret->code = kadm5_setkey_principal_4(handle, rec.principal,
- arg->keepold, arg->key_data,
- arg->n_key_data);
+ keepold = clamp_self_keepold(handle, rec.principal, arg->keepold);
+ ret->code = kadm5_setkey_principal_4(handle, rec.principal, keepold,
+ arg->key_data, arg->n_key_data);
} else {
log_unauth("kadm5_setkey_principal", prime_arg, &client_name,
&service_name, rqstp);
gss_buffer_desc service_name = GSS_C_EMPTY_BUFFER;
krb5_keyblock *k;
int nkeys;
+ unsigned int keepold;
kadm5_server_handle_t handle;
const char *errmsg = NULL;
} else {
ret->code = check_self_keychange(handle, rqstp, rec.principal);
if (!ret->code) {
+ keepold = clamp_self_keepold(handle, rec.principal, arg->keepold);
ret->code = kadm5_randkey_principal_3(handle, rec.principal,
- arg->keepold,
- arg->n_ks_tuple,
+ keepold, arg->n_ks_tuple,
arg->ks_tuple, &k, &nkeys);
}
}
char *pass);
kadm5_ret_t kadm5_chpass_principal_3(void *server_handle,
krb5_principal principal,
- krb5_boolean keepold,
+ unsigned int keepold,
int n_ks_tuple,
krb5_key_salt_tuple *ks_tuple,
char *pass);
int *n_keys);
kadm5_ret_t kadm5_randkey_principal_3(void *server_handle,
krb5_principal principal,
- krb5_boolean keepold,
+ unsigned int keepold,
int n_ks_tuple,
krb5_key_salt_tuple *ks_tuple,
krb5_keyblock **keyblocks,
kadm5_ret_t kadm5_setkey_principal_3(void *server_handle,
krb5_principal principal,
- krb5_boolean keepold,
+ unsigned int keepold,
int n_ks_tuple,
krb5_key_salt_tuple *ks_tuple,
krb5_keyblock *keyblocks,
kadm5_ret_t kadm5_setkey_principal_4(void *server_handle,
krb5_principal principal,
- krb5_boolean keepold,
+ unsigned int keepold,
kadm5_key_data *key_data,
int n_key_data);
kadm5_ret_t
kadm5_chpass_principal_3(void *server_handle,
- krb5_principal princ, krb5_boolean keepold,
+ krb5_principal princ, unsigned int keepold,
int n_ks_tuple, krb5_key_salt_tuple *ks_tuple,
char *password)
{
kadm5_ret_t
kadm5_setkey_principal_3(void *server_handle,
krb5_principal princ,
- krb5_boolean keepold, int n_ks_tuple,
+ unsigned int keepold, int n_ks_tuple,
krb5_key_salt_tuple *ks_tuple,
krb5_keyblock *keyblocks,
int n_keys)
kadm5_ret_t
kadm5_setkey_principal_4(void *server_handle,
krb5_principal princ,
- krb5_boolean keepold,
+ unsigned int keepold,
kadm5_key_data *key_data,
int n_key_data)
{
kadm5_ret_t
kadm5_randkey_principal_3(void *server_handle,
krb5_principal princ,
- krb5_boolean keepold, int n_ks_tuple,
+ unsigned int keepold, int n_ks_tuple,
krb5_key_salt_tuple *ks_tuple,
krb5_keyblock **key, int *n_keys)
{
kadm5_ret_t
kadm5_chpass_principal_3(void *server_handle,
- krb5_principal principal, krb5_boolean keepold,
+ krb5_principal principal, unsigned int keepold,
int n_ks_tuple, krb5_key_salt_tuple *ks_tuple,
char *password)
{
kadm5_ret_t
kadm5_randkey_principal_3(void *server_handle,
krb5_principal principal,
- krb5_boolean keepold,
+ unsigned int keepold,
int n_ks_tuple, krb5_key_salt_tuple *ks_tuple,
krb5_keyblock **keyblocks,
int *n_keys)
kadm5_ret_t
kadm5_setkey_principal_3(void *server_handle,
krb5_principal principal,
- krb5_boolean keepold,
+ unsigned int keepold,
int n_ks_tuple, krb5_key_salt_tuple *ks_tuple,
krb5_keyblock *keyblocks,
int n_keys)
kadm5_ret_t
kadm5_setkey_principal_4(void *server_handle, krb5_principal principal,
- krb5_boolean keepold, kadm5_key_data *key_data,
+ unsigned int keepold, kadm5_key_data *key_data,
int n_key_data)
{
krb5_db_entry *kdb;
#include <stdio.h>
#include <errno.h>
-enum save { DISCARD_ALL, KEEP_LAST_KVNO, KEEP_ALL };
-
int
krb5_db_get_key_data_kvno(krb5_context context, int count, krb5_key_data *data)
{
/*
* Add key_data to dbent, making sure that each entry is encrypted in mkey. If
- * kvno is non-zero, preserve only keys of that kvno. May steal some elements
- * from key_data and zero them out.
+ * keepold is greater than 1, preserve only the first (keepold-1) key versions,
+ * so that the total number of key versions including the new key set is
+ * keepold. May steal some elements from key_data and zero them out.
*/
static krb5_error_code
preserve_old_keys(krb5_context context, krb5_keyblock *mkey,
- krb5_db_entry *dbent, int kvno, int n_key_data,
+ krb5_db_entry *dbent, unsigned int keepold, int n_key_data,
krb5_key_data *key_data)
{
krb5_error_code ret;
+ krb5_kvno last_kvno = 0, kvno_changes = 0;
int i;
for (i = 0; i < n_key_data; i++) {
- if (kvno != 0 && key_data[i].key_data_kvno != kvno)
- continue;
+ if (keepold > 1) {
+ if (i > 0 && key_data[i].key_data_kvno != last_kvno)
+ kvno_changes++;
+ if (kvno_changes >= keepold - 1)
+ break;
+ last_kvno = key_data[i].key_data_kvno;
+ }
+
ret = krb5_dbe_create_key_data(context, dbent);
if (ret)
return ret;
static krb5_error_code
rekey(krb5_context context, krb5_keyblock *mkey, krb5_key_salt_tuple *ks_tuple,
int ks_tuple_count, const char *password, int new_kvno,
- enum save savekeys, krb5_db_entry *db_entry)
+ unsigned int keepold, krb5_db_entry *db_entry)
{
krb5_error_code ret;
krb5_key_data *key_data;
- int n_key_data, old_kvno, save_kvno;
+ int n_key_data, old_kvno;
/* Save aside the old key data. */
n_key_data = db_entry->n_key_data;
/* Possibly add some or all of the old keys to the back of the list. May
* steal from and zero out some of the old key data entries. */
- if (savekeys != DISCARD_ALL) {
- save_kvno = (savekeys == KEEP_LAST_KVNO) ? old_kvno : 0;
- ret = preserve_old_keys(context, mkey, db_entry, save_kvno, n_key_data,
+ if (keepold > 0) {
+ ret = preserve_old_keys(context, mkey, db_entry, keepold, n_key_data,
key_data);
}
krb5_error_code
krb5_dbe_crk(krb5_context context, krb5_keyblock *mkey,
krb5_key_salt_tuple *ks_tuple, int ks_tuple_count,
- krb5_boolean keepold, krb5_db_entry *dbent)
+ unsigned int keepold, krb5_db_entry *dbent)
{
- return rekey(context, mkey, ks_tuple, ks_tuple_count, NULL, 0,
- keepold ? KEEP_ALL : DISCARD_ALL, dbent);
+ return rekey(context, mkey, ks_tuple, ks_tuple_count, NULL, 0, keepold,
+ dbent);
}
/*
krb5_key_salt_tuple *ks_tuple, int ks_tuple_count,
krb5_db_entry *dbent)
{
- return rekey(context, mkey, ks_tuple, ks_tuple_count, NULL, 0,
- KEEP_LAST_KVNO, dbent);
+ return rekey(context, mkey, ks_tuple, ks_tuple_count, NULL, 0, 2, dbent);
}
/*
krb5_error_code
krb5_dbe_def_cpw(krb5_context context, krb5_keyblock *mkey,
krb5_key_salt_tuple *ks_tuple, int ks_tuple_count,
- char *password, int new_kvno, krb5_boolean keepold,
+ char *password, int new_kvno, unsigned int keepold,
krb5_db_entry *dbent)
{
return rekey(context, mkey, ks_tuple, ks_tuple_count, password, new_kvno,
- keepold ? KEEP_ALL : DISCARD_ALL, dbent);
+ keepold, dbent);
}
/*
krb5_key_salt_tuple *ks_tuple, int ks_tuple_count, char *password,
krb5_db_entry *dbent)
{
- return rekey(context, mkey, ks_tuple, ks_tuple_count, password, 0,
- KEEP_LAST_KVNO, dbent);
+ return rekey(context, mkey, ks_tuple, ks_tuple_count, password, 0, 2,
+ dbent);
}
from k5test import *
+import re
rollover_krb5_conf = {'libdefaults': {'allow_weak_crypto': 'true'}}
# Now present the DES3 ticket to the KDC and make sure it's rejected.
realm.run([kvno, realm.host_princ], expected_code=1)
+# Test -keepold limit for self-service requests through kadmind.
+def count_kvnos(princ, expected_count):
+ out = realm.run_kadmin(['getprinc', princ])
+ vnos = re.findall(r' vno \d+,', out)
+ if len(set(vnos)) != expected_count:
+ fail('expected %d key versions' % expected_count)
+realm.start_kadmind()
+realm.prep_kadmin(realm.user_princ, password('user'))
+realm.run_kadmin(['cpw', '-randkey', '-keepold', realm.user_princ])
+realm.run_kadmin(['cpw', '-randkey', '-keepold', realm.user_princ])
+realm.run_kadmin(['cpw', '-randkey', '-keepold', realm.user_princ])
+realm.run_kadmin(['cpw', '-randkey', '-keepold', realm.user_princ])
+count_kvnos(realm.user_princ, 5)
+realm.run_kadmin(['cpw', '-randkey', '-keepold', realm.user_princ])
+count_kvnos(realm.user_princ, 5)
+realm.run_kadmin(['cpw', '-pw', 'pw', '-keepold', realm.user_princ])
+count_kvnos(realm.user_princ, 5)
+# Test that the limit doesn't apply when modifying another principal.
+realm.prep_kadmin()
+realm.run_kadmin(['cpw', '-randkey', '-keepold', realm.user_princ])
+count_kvnos(realm.user_princ, 6)
+realm.run_kadmin(['cpw', '-pw', 'pw', '-keepold', realm.user_princ])
+count_kvnos(realm.user_princ, 7)
+
realm.stop()
# Test a cross-realm TGT key rollover scenario where realm 1 mimics