/* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * lib/kdb/kdb_cpw.c * * Copyright 1995 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 #include static int 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 = 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 = 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; krb5_xfree(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: { #if 0 krb5_data * saltdata; if (retval = krb5_copy_data(context, krb5_princ_realm(context, db_entry->princ), &saltdata)) return(retval); key_salt.data = *saltdata; key_salt.data.length = SALT_TYPE_AFS_LENGTH; /*length actually used below...*/ krb5_xfree(saltdata); #else /* Why do we do this? Well, the afs_mit_string_to_key needs to use strlen, and the realm is not NULL terminated.... */ unsigned int slen = (*krb5_princ_realm(context,db_entry->princ)).length; if(!(key_salt.data.data = (char *) malloc(slen+1))) return ENOMEM; key_salt.data.data[slen] = 0; memcpy((char *)key_salt.data.data, (char *)(*krb5_princ_realm(context,db_entry->princ)).data, slen); key_salt.data.length = SALT_TYPE_AFS_LENGTH; /*length actually used below...*/ #endif } break; default: return(KRB5_KDB_BAD_SALTTYPE); } pwd.data = passwd; pwd.length = strlen(passwd); /* Solaris Kerberos */ memset(&key, 0, sizeof (krb5_keyblock)); /* 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); /* Solaris Kerberos */ krb5_free_keyblock_contents(context, &key); 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 = 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 = 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); }