/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
* lib/kdb/kdb_cpw.c
*
* Copyright 1995, 2009 by the Massachusetts Institute of Technology.
* All Rights Reserved.
*
* Export of this software from the United States of America may
* require a specific license from the United States Government.
* It is the responsibility of any person or organization contemplating
* export to obtain such a license before exporting.
*
* WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
* distribute this software and its documentation for any purpose and
* without fee is hereby granted, provided that the above copyright
* notice appear in all copies and that both that copyright notice and
* this permission notice appear in supporting documentation, and that
* the name of M.I.T. not be used in advertising or publicity pertaining
* to distribution of the software without specific, written prior
* permission. Furthermore if you modify this software you must label
* your software as modified software and not distribute it in such a
* fashion that it might be confused with the original M.I.T. software.
* M.I.T. makes no representations about the suitability of
* this software for any purpose. It is provided "as is" without express
* or implied warranty.
*
*/
/*
* Copyright (C) 1998 by the FundsXpress, INC.
*
* All rights reserved.
*
* Export of this software from the United States of America may require
* a specific license from the United States Government. It is the
* responsibility of any person or organization contemplating export to
* obtain such a license before exporting.
*
* WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
* distribute this software and its documentation for any purpose and
* without fee is hereby granted, provided that the above copyright
* notice appear in all copies and that both that copyright notice and
* this permission notice appear in supporting documentation, and that
* the name of FundsXpress. not be used in advertising or publicity pertaining
* to distribution of the software without specific, written prior
* permission. FundsXpress makes no representations about the suitability of
* this software for any purpose. It is provided "as is" without express
* or implied warranty.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#include "k5-int.h"
#include "kdb.h"
#include <stdio.h>
#include <errno.h>
int
krb5_db_get_key_data_kvno(context, count, data)
krb5_context context;
int count;
krb5_key_data * data;
{
int i, kvno;
/* Find last key version number */
for (kvno = i = 0; i < count; i++) {
if (kvno < data[i].key_data_kvno) {
kvno = data[i].key_data_kvno;
}
}
return(kvno);
}
static void
cleanup_key_data(context, count, data)
krb5_context context;
int count;
krb5_key_data * data;
{
int i, j;
/* If data is NULL, count is always 0 */
if (data == NULL) return;
for (i = 0; i < count; i++) {
for (j = 0; j < data[i].key_data_ver; j++) {
if (data[i].key_data_length[j]) {
krb5_db_free(context, data[i].key_data_contents[j]);
}
}
}
krb5_db_free(context, data);
}
static krb5_error_code
add_key_rnd(context, master_key, ks_tuple, ks_tuple_count, db_entry, kvno)
krb5_context context;
krb5_keyblock * master_key;
krb5_key_salt_tuple * ks_tuple;
int ks_tuple_count;
krb5_db_entry * db_entry;
int kvno;
{
krb5_principal krbtgt_princ;
krb5_keyblock key;
krb5_db_entry krbtgt_entry;
krb5_boolean more;
int max_kvno, one, i, j, k;
krb5_error_code retval;
krb5_key_data tmp_key_data;
krb5_key_data *tptr;
memset( &tmp_key_data, 0, sizeof(tmp_key_data));
retval = krb5_build_principal_ext(context, &krbtgt_princ,
db_entry->princ->realm.length,
db_entry->princ->realm.data,
KRB5_TGS_NAME_SIZE,
KRB5_TGS_NAME,
db_entry->princ->realm.length,
db_entry->princ->realm.data,
0);
if (retval)
return retval;
/* Get tgt from database */
retval = krb5_db_get_principal(context, krbtgt_princ, &krbtgt_entry,
&one, &more);
krb5_free_principal(context, krbtgt_princ); /* don't need it anymore */
if (retval)
return(retval);
if ((one > 1) || (more)) {
krb5_db_free_principal(context, &krbtgt_entry, one);
return KRB5KDC_ERR_PRINCIPAL_NOT_UNIQUE;
}
if (!one)
return KRB5_KDB_NOENTRY;
/* Get max kvno */
for (max_kvno = j = 0; j < krbtgt_entry.n_key_data; j++) {
if (max_kvno < krbtgt_entry.key_data[j].key_data_kvno) {
max_kvno = krbtgt_entry.key_data[j].key_data_kvno;
}
}
for (i = 0; i < ks_tuple_count; i++) {
krb5_boolean similar;
similar = 0;
/*
* We could use krb5_keysalt_iterate to replace this loop, or use
* krb5_keysalt_is_present for the loop below, but we want to avoid
* circular library dependencies.
*/
for (j = 0; j < i; j++) {
if ((retval = krb5_c_enctype_compare(context,
ks_tuple[i].ks_enctype,
ks_tuple[j].ks_enctype,
&similar)))
return(retval);
if (similar)
break;
}
if (similar)
continue;
if ((retval = krb5_dbe_create_key_data(context, db_entry)))
goto add_key_rnd_err;
/* there used to be code here to extract the old key, and derive
a new key from it. Now that there's a unified prng, that isn't
necessary. */
/* make new key */
if ((retval = krb5_c_make_random_key(context, ks_tuple[i].ks_enctype,
&key)))
goto add_key_rnd_err;
/* db library will free this. Since, its a so, it could actually be using different memory management
function. So, its better if the memory is allocated by the db's malloc. So, a temporary memory is used
here which will later be copied to the db_entry */
retval = krb5_dbekd_encrypt_key_data(context, master_key,
&key, NULL, kvno,
&tmp_key_data);
krb5_free_keyblock_contents(context, &key);
if( retval )
goto add_key_rnd_err;
tptr = &db_entry->key_data[db_entry->n_key_data-1];
tptr->key_data_ver = tmp_key_data.key_data_ver;
tptr->key_data_kvno = tmp_key_data.key_data_kvno;
for( k = 0; k < tmp_key_data.key_data_ver; k++ )
{
tptr->key_data_type[k] = tmp_key_data.key_data_type[k];
tptr->key_data_length[k] = tmp_key_data.key_data_length[k];
if( tmp_key_data.key_data_contents[k] )
{
tptr->key_data_contents[k] = krb5_db_alloc(context, NULL, tmp_key_data.key_data_length[k]);
if( tptr->key_data_contents[k] == NULL )
{
cleanup_key_data(context, db_entry->n_key_data, db_entry->key_data);
db_entry->key_data = NULL;
db_entry->n_key_data = 0;
retval = ENOMEM;
goto add_key_rnd_err;
}
memcpy( tptr->key_data_contents[k], tmp_key_data.key_data_contents[k], tmp_key_data.key_data_length[k]);
memset( tmp_key_data.key_data_contents[k], 0, tmp_key_data.key_data_length[k]);
free( tmp_key_data.key_data_contents[k] );
tmp_key_data.key_data_contents[k] = NULL;
}
}
}
add_key_rnd_err:
krb5_db_free_principal(context, &krbtgt_entry, one);
for( i = 0; i < tmp_key_data.key_data_ver; i++ )
{
if( tmp_key_data.key_data_contents[i] )
{
memset( tmp_key_data.key_data_contents[i], 0, tmp_key_data.key_data_length[i]);
free( tmp_key_data.key_data_contents[i] );
}
}
return(retval);
}
/*
* Change random key for a krb5_db_entry
* Assumes the max kvno
*
* As a side effect all old keys are nuked if keepold is false.
*/
krb5_error_code
krb5_dbe_crk(context, master_key, ks_tuple, ks_tuple_count, keepold, db_entry)
krb5_context context;
krb5_keyblock * master_key;
krb5_key_salt_tuple * ks_tuple;
int ks_tuple_count;
krb5_boolean keepold;
krb5_db_entry * db_entry;
{
int key_data_count;
int n_new_key_data;
krb5_key_data * key_data;
krb5_error_code retval;
int kvno;
int i;
/* First save the old keydata */
kvno = krb5_db_get_key_data_kvno(context, db_entry->n_key_data,
db_entry->key_data);
key_data_count = db_entry->n_key_data;
key_data = db_entry->key_data;
db_entry->key_data = NULL;
db_entry->n_key_data = 0;
/* increment the kvno */
kvno++;
retval = add_key_rnd(context, master_key, ks_tuple,
ks_tuple_count, db_entry, kvno);
if (retval) {
cleanup_key_data(context, db_entry->n_key_data, db_entry->key_data);
db_entry->n_key_data = key_data_count;
db_entry->key_data = key_data;
} else if (keepold) {
n_new_key_data = db_entry->n_key_data;
for (i = 0; i < key_data_count; i++) {
retval = krb5_dbe_create_key_data(context, db_entry);
if (retval) {
cleanup_key_data(context, db_entry->n_key_data,
db_entry->key_data);
break;
}
db_entry->key_data[i+n_new_key_data] = key_data[i];
memset(&key_data[i], 0, sizeof(krb5_key_data));
}
krb5_db_free(context, key_data); /* we moved the cotents to new memory. But, the original block which contained the data */
} else {
cleanup_key_data(context, key_data_count, key_data);
}
return(retval);
}
/*
* Add random key for a krb5_db_entry
* Assumes the max kvno
*
* As a side effect all old keys older than the max kvno are nuked.
*/
krb5_error_code
krb5_dbe_ark(context, master_key, ks_tuple, ks_tuple_count, db_entry)
krb5_context context;
krb5_keyblock * master_key;
krb5_key_salt_tuple * ks_tuple;
int ks_tuple_count;
krb5_db_entry * db_entry;
{
int key_data_count;
krb5_key_data * key_data;
krb5_error_code retval;
int kvno;
int i;
/* First save the old keydata */
kvno = krb5_db_get_key_data_kvno(context, db_entry->n_key_data,
db_entry->key_data);
key_data_count = db_entry->n_key_data;
key_data = db_entry->key_data;
db_entry->key_data = NULL;
db_entry->n_key_data = 0;
/* increment the kvno */
kvno++;
if ((retval = add_key_rnd(context, master_key, ks_tuple,
ks_tuple_count, db_entry, kvno))) {
cleanup_key_data(context, db_entry->n_key_data, db_entry->key_data);
db_entry->n_key_data = key_data_count;
db_entry->key_data = key_data;
} else {
/* Copy keys with key_data_kvno == kvno - 1 ( = old kvno ) */
for (i = 0; i < key_data_count; i++) {
if (key_data[i].key_data_kvno == (kvno - 1)) {
if ((retval = krb5_dbe_create_key_data(context, db_entry))) {
cleanup_key_data(context, db_entry->n_key_data,
db_entry->key_data);
break;
}
/* We should decrypt/re-encrypt the data to use the same mkvno*/
db_entry->key_data[db_entry->n_key_data - 1] = key_data[i];
memset(&key_data[i], 0, sizeof(krb5_key_data));
}
}
cleanup_key_data(context, key_data_count, key_data);
}
return(retval);
}
/*
* Add key_data for a krb5_db_entry
* If passwd is NULL the assumes that the caller wants a random password.
*/
static krb5_error_code
add_key_pwd(context, master_key, ks_tuple, ks_tuple_count, passwd,
db_entry, kvno)
krb5_context context;
krb5_keyblock * master_key;
krb5_key_salt_tuple * ks_tuple;
int ks_tuple_count;
char * passwd;
krb5_db_entry * db_entry;
int kvno;
{
krb5_error_code retval;
krb5_keysalt key_salt;
krb5_keyblock key;
krb5_data pwd;
int i, j, k;
krb5_key_data tmp_key_data;
krb5_key_data *tptr;
memset( &tmp_key_data, 0, sizeof(tmp_key_data));
retval = 0;
for (i = 0; i < ks_tuple_count; i++) {
krb5_boolean similar;
similar = 0;
/*
* We could use krb5_keysalt_iterate to replace this loop, or use
* krb5_keysalt_is_present for the loop below, but we want to avoid
* circular library dependencies.
*/
for (j = 0; j < i; j++) {
if ((retval = krb5_c_enctype_compare(context,
ks_tuple[i].ks_enctype,
ks_tuple[j].ks_enctype,
&similar)))
return(retval);
if (similar &&
(ks_tuple[j].ks_salttype == ks_tuple[i].ks_salttype))
break;
}
if (j < i)
continue;
if ((retval = krb5_dbe_create_key_data(context, db_entry)))
return(retval);
/* Convert password string to key using appropriate salt */
switch (key_salt.type = ks_tuple[i].ks_salttype) {
case KRB5_KDB_SALTTYPE_ONLYREALM: {
krb5_data * saltdata;
if ((retval = krb5_copy_data(context, krb5_princ_realm(context,
db_entry->princ), &saltdata)))
return(retval);
key_salt.data = *saltdata;
free(saltdata);
}
break;
case KRB5_KDB_SALTTYPE_NOREALM:
if ((retval=krb5_principal2salt_norealm(context, db_entry->princ,
&key_salt.data)))
return(retval);
break;
case KRB5_KDB_SALTTYPE_NORMAL:
if ((retval = krb5_principal2salt(context, db_entry->princ,
&key_salt.data)))
return(retval);
break;
case KRB5_KDB_SALTTYPE_V4:
key_salt.data.length = 0;
key_salt.data.data = 0;
break;
case KRB5_KDB_SALTTYPE_AFS3:
/* The afs_mit_string_to_key needs to use strlen, and the
realm field is not (necessarily) NULL terminated. */
retval = krb5int_copy_data_contents_add0(context,
krb5_princ_realm(context,
db_entry->princ),
&key_salt.data);
if (retval)
return retval;
key_salt.data.length = SALT_TYPE_AFS_LENGTH; /*length actually used below...*/
break;
default:
return(KRB5_KDB_BAD_SALTTYPE);
}
pwd.data = passwd;
pwd.length = strlen(passwd);
/* AFS string to key will happen here */
if ((retval = krb5_c_string_to_key(context, ks_tuple[i].ks_enctype,
&pwd, &key_salt.data, &key))) {
if (key_salt.data.data)
free(key_salt.data.data);
return(retval);
}
if (key_salt.data.length == SALT_TYPE_AFS_LENGTH)
key_salt.data.length =
krb5_princ_realm(context, db_entry->princ)->length;
/* memory allocation to be done by db. So, use temporary block and later copy
it to the memory allocated by db */
retval = krb5_dbekd_encrypt_key_data(context, master_key, &key,
(const krb5_keysalt *)&key_salt,
kvno, &tmp_key_data);
if (key_salt.data.data)
free(key_salt.data.data);
free(key.contents);
if( retval )
return retval;
tptr = &db_entry->key_data[db_entry->n_key_data-1];
tptr->key_data_ver = tmp_key_data.key_data_ver;
tptr->key_data_kvno = tmp_key_data.key_data_kvno;
for( k = 0; k < tmp_key_data.key_data_ver; k++ )
{
tptr->key_data_type[k] = tmp_key_data.key_data_type[k];
tptr->key_data_length[k] = tmp_key_data.key_data_length[k];
if( tmp_key_data.key_data_contents[k] )
{
tptr->key_data_contents[k] = krb5_db_alloc(context, NULL, tmp_key_data.key_data_length[k]);
if( tptr->key_data_contents[k] == NULL )
{
cleanup_key_data(context, db_entry->n_key_data, db_entry->key_data);
db_entry->key_data = NULL;
db_entry->n_key_data = 0;
retval = ENOMEM;
goto add_key_pwd_err;
}
memcpy( tptr->key_data_contents[k], tmp_key_data.key_data_contents[k], tmp_key_data.key_data_length[k]);
memset( tmp_key_data.key_data_contents[k], 0, tmp_key_data.key_data_length[k]);
free( tmp_key_data.key_data_contents[k] );
tmp_key_data.key_data_contents[k] = NULL;
}
}
}
add_key_pwd_err:
for( i = 0; i < tmp_key_data.key_data_ver; i++ )
{
if( tmp_key_data.key_data_contents[i] )
{
memset( tmp_key_data.key_data_contents[i], 0, tmp_key_data.key_data_length[i]);
free( tmp_key_data.key_data_contents[i] );
}
}
return(retval);
}
/*
* Change password for a krb5_db_entry
* Assumes the max kvno
*
* As a side effect all old keys are nuked if keepold is false.
*/
krb5_error_code
krb5_dbe_def_cpw(context, master_key, ks_tuple, ks_tuple_count, passwd,
new_kvno, keepold, db_entry)
krb5_context context;
krb5_keyblock * master_key;
krb5_key_salt_tuple * ks_tuple;
int ks_tuple_count;
char * passwd;
int new_kvno;
krb5_boolean keepold;
krb5_db_entry * db_entry;
{
int key_data_count;
int n_new_key_data;
krb5_key_data * key_data;
krb5_error_code retval;
int old_kvno;
int i;
/* First save the old keydata */
old_kvno = krb5_db_get_key_data_kvno(context, db_entry->n_key_data,
db_entry->key_data);
key_data_count = db_entry->n_key_data;
key_data = db_entry->key_data;
db_entry->key_data = NULL;
db_entry->n_key_data = 0;
/* increment the kvno. if the requested kvno is too small,
increment the old kvno */
if (new_kvno < old_kvno+1)
new_kvno = old_kvno+1;
retval = add_key_pwd(context, master_key, ks_tuple, ks_tuple_count,
passwd, db_entry, new_kvno);
if (retval) {
cleanup_key_data(context, db_entry->n_key_data, db_entry->key_data);
db_entry->n_key_data = key_data_count;
db_entry->key_data = key_data;
} else if (keepold) {
n_new_key_data = db_entry->n_key_data;
for (i = 0; i < key_data_count; i++) {
retval = krb5_dbe_create_key_data(context, db_entry);
if (retval) {
cleanup_key_data(context, db_entry->n_key_data,
db_entry->key_data);
break;
}
db_entry->key_data[i+n_new_key_data] = key_data[i];
memset(&key_data[i], 0, sizeof(krb5_key_data));
}
krb5_db_free( context, key_data );
} else {
cleanup_key_data(context, key_data_count, key_data);
}
return(retval);
}
/*
* Add password for a krb5_db_entry
* Assumes the max kvno
*
* As a side effect all old keys older than the max kvno are nuked.
*/
krb5_error_code
krb5_dbe_apw(context, master_key, ks_tuple, ks_tuple_count, passwd, db_entry)
krb5_context context;
krb5_keyblock * master_key;
krb5_key_salt_tuple * ks_tuple;
int ks_tuple_count;
char * passwd;
krb5_db_entry * db_entry;
{
int key_data_count;
krb5_key_data * key_data;
krb5_error_code retval;
int old_kvno, new_kvno;
int i;
/* First save the old keydata */
old_kvno = krb5_db_get_key_data_kvno(context, db_entry->n_key_data,
db_entry->key_data);
key_data_count = db_entry->n_key_data;
key_data = db_entry->key_data;
db_entry->key_data = NULL;
db_entry->n_key_data = 0;
/* increment the kvno */
new_kvno = old_kvno+1;
if ((retval = add_key_pwd(context, master_key, ks_tuple, ks_tuple_count,
passwd, db_entry, new_kvno))) {
cleanup_key_data(context, db_entry->n_key_data, db_entry->key_data);
db_entry->n_key_data = key_data_count;
db_entry->key_data = key_data;
} else {
/* Copy keys with key_data_kvno == old_kvno */
for (i = 0; i < key_data_count; i++) {
if (key_data[i].key_data_kvno == old_kvno) {
if ((retval = krb5_dbe_create_key_data(context, db_entry))) {
cleanup_key_data(context, db_entry->n_key_data,
db_entry->key_data);
break;
}
/* We should decrypt/re-encrypt the data to use the same mkvno*/
db_entry->key_data[db_entry->n_key_data - 1] = key_data[i];
memset(&key_data[i], 0, sizeof(krb5_key_data));
}
}
cleanup_key_data(context, key_data_count, key_data);
}
return(retval);
}