/* * Copyright (c) 2000 - 2002 Kungliga Tekniska Högskolan * (Royal Institute of Technology, Stockholm, Sweden). * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. 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. * * 3. Neither the name of the Institute nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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. */ #include "hdb_locl.h" #ifndef O_BINARY #define O_BINARY 0 #endif RCSID("$Id: mkey.c,v 1.15 2003/03/28 02:01:33 lha Exp $"); struct hdb_master_key_data { krb5_keytab_entry keytab; krb5_crypto crypto; struct hdb_master_key_data *next; }; void hdb_free_master_key(krb5_context context, hdb_master_key mkey) { struct hdb_master_key_data *ptr; while(mkey) { krb5_kt_free_entry(context, &mkey->keytab); if (mkey->crypto) krb5_crypto_destroy(context, mkey->crypto); ptr = mkey; mkey = mkey->next; free(ptr); } } krb5_error_code hdb_process_master_key(krb5_context context, int kvno, krb5_keyblock *key, krb5_enctype etype, hdb_master_key *mkey) { krb5_error_code ret; *mkey = calloc(1, sizeof(**mkey)); if(*mkey == NULL) { krb5_set_error_string(context, "malloc: out of memory"); return ENOMEM; } (*mkey)->keytab.vno = kvno; ret = krb5_parse_name(context, "K/M", &(*mkey)->keytab.principal); if(ret) goto fail; ret = krb5_copy_keyblock_contents(context, key, &(*mkey)->keytab.keyblock); if(ret) goto fail; if(etype != 0) (*mkey)->keytab.keyblock.keytype = etype; (*mkey)->keytab.timestamp = time(NULL); ret = krb5_crypto_init(context, key, etype, &(*mkey)->crypto); if(ret) goto fail; return 0; fail: hdb_free_master_key(context, *mkey); *mkey = NULL; return ret; } krb5_error_code hdb_add_master_key(krb5_context context, krb5_keyblock *key, hdb_master_key *inout) { int vno = 0; hdb_master_key p; krb5_error_code ret; for(p = *inout; p; p = p->next) vno = max(vno, p->keytab.vno); vno++; ret = hdb_process_master_key(context, vno, key, 0, &p); if(ret) return ret; p->next = *inout; *inout = p; return 0; } static krb5_error_code read_master_keytab(krb5_context context, const char *filename, hdb_master_key *mkey) { krb5_error_code ret; krb5_keytab id; krb5_kt_cursor cursor; krb5_keytab_entry entry; hdb_master_key p; ret = krb5_kt_resolve(context, filename, &id); if(ret) return ret; ret = krb5_kt_start_seq_get(context, id, &cursor); if(ret) goto out; *mkey = NULL; while(krb5_kt_next_entry(context, id, &entry, &cursor) == 0) { p = calloc(1, sizeof(*p)); p->keytab = entry; ret = krb5_crypto_init(context, &p->keytab.keyblock, 0, &p->crypto); p->next = *mkey; *mkey = p; } krb5_kt_end_seq_get(context, id, &cursor); out: krb5_kt_close(context, id); return ret; } /* read a MIT master keyfile */ static krb5_error_code read_master_mit(krb5_context context, const char *filename, hdb_master_key *mkey) { int fd; krb5_error_code ret; krb5_storage *sp; u_int16_t enctype; krb5_keyblock key; fd = open(filename, O_RDONLY | O_BINARY); if(fd < 0) { int save_errno = errno; krb5_set_error_string(context, "failed to open %s: %s", filename, strerror(save_errno)); return save_errno; } sp = krb5_storage_from_fd(fd); if(sp == NULL) { close(fd); return errno; } krb5_storage_set_flags(sp, KRB5_STORAGE_HOST_BYTEORDER); #if 0 /* could possibly use ret_keyblock here, but do it with more checks for now */ ret = krb5_ret_keyblock(sp, &key); #else ret = krb5_ret_int16(sp, &enctype); if((htons(enctype) & 0xff00) == 0x3000) { krb5_set_error_string(context, "unknown keytype in %s: %#x, expected %#x", filename, htons(enctype), 0x3000); ret = HEIM_ERR_BAD_MKEY; goto out; } key.keytype = enctype; ret = krb5_ret_data(sp, &key.keyvalue); if(ret) goto out; #endif ret = hdb_process_master_key(context, 0, &key, 0, mkey); krb5_free_keyblock_contents(context, &key); out: krb5_storage_free(sp); close(fd); return ret; } /* read an old master key file */ static krb5_error_code read_master_encryptionkey(krb5_context context, const char *filename, hdb_master_key *mkey) { int fd; krb5_keyblock key; krb5_error_code ret; unsigned char buf[256]; ssize_t len; size_t ret_len; fd = open(filename, O_RDONLY | O_BINARY); if(fd < 0) { int save_errno = errno; krb5_set_error_string(context, "failed to open %s: %s", filename, strerror(save_errno)); return save_errno; } len = read(fd, buf, sizeof(buf)); close(fd); if(len < 0) { int save_errno = errno; krb5_set_error_string(context, "error reading %s: %s", filename, strerror(save_errno)); return save_errno; } ret = decode_EncryptionKey(buf, len, &key, &ret_len); memset(buf, 0, sizeof(buf)); if(ret) return ret; /* Originally, the keytype was just that, and later it got changed to des-cbc-md5, but we always used des in cfb64 mode. This should cover all cases, but will break if someone has hacked this code to really use des-cbc-md5 -- but then that's not my problem. */ if(key.keytype == KEYTYPE_DES || key.keytype == ETYPE_DES_CBC_MD5) key.keytype = ETYPE_DES_CFB64_NONE; ret = hdb_process_master_key(context, 0, &key, 0, mkey); krb5_free_keyblock_contents(context, &key); return ret; } /* read a krb4 /.k style file */ static krb5_error_code read_master_krb4(krb5_context context, const char *filename, hdb_master_key *mkey) { int fd; krb5_keyblock key; krb5_error_code ret; unsigned char buf[256]; ssize_t len; fd = open(filename, O_RDONLY | O_BINARY); if(fd < 0) { int save_errno = errno; krb5_set_error_string(context, "failed to open %s: %s", filename, strerror(save_errno)); return save_errno; } len = read(fd, buf, sizeof(buf)); close(fd); if(len < 0) { int save_errno = errno; krb5_set_error_string(context, "error reading %s: %s", filename, strerror(save_errno)); return save_errno; } if(len != 8) { krb5_set_error_string(context, "bad contents of %s", filename); return HEIM_ERR_EOF; /* XXX file might be too large */ } memset(&key, 0, sizeof(key)); key.keytype = ETYPE_DES_PCBC_NONE; ret = krb5_data_copy(&key.keyvalue, buf, len); memset(buf, 0, sizeof(buf)); if(ret) return ret; ret = hdb_process_master_key(context, 0, &key, 0, mkey); krb5_free_keyblock_contents(context, &key); return ret; } krb5_error_code hdb_read_master_key(krb5_context context, const char *filename, hdb_master_key *mkey) { FILE *f; unsigned char buf[16]; krb5_error_code ret; off_t len; *mkey = NULL; if(filename == NULL) filename = HDB_DB_DIR "/m-key"; f = fopen(filename, "r"); if(f == NULL) { int save_errno = errno; krb5_set_error_string(context, "failed to open %s: %s", filename, strerror(save_errno)); return save_errno; } if(fread(buf, 1, 2, f) != 2) { krb5_set_error_string(context, "end of file reading %s", filename); fclose(f); return HEIM_ERR_EOF; } fseek(f, 0, SEEK_END); len = ftell(f); if(fclose(f) != 0) return errno; if(len < 0) return errno; if(len == 8) { ret = read_master_krb4(context, filename, mkey); } else if(buf[0] == 0x30 && len <= 127 && buf[1] == len - 2) { ret = read_master_encryptionkey(context, filename, mkey); } else if(buf[0] == 5 && buf[1] >= 1 && buf[1] <= 2) { ret = read_master_keytab(context, filename, mkey); } else { ret = read_master_mit(context, filename, mkey); } return ret; } krb5_error_code hdb_write_master_key(krb5_context context, const char *filename, hdb_master_key mkey) { krb5_error_code ret; hdb_master_key p; krb5_keytab kt; if(filename == NULL) filename = HDB_DB_DIR "/m-key"; ret = krb5_kt_resolve(context, filename, &kt); if(ret) return ret; for(p = mkey; p; p = p->next) { ret = krb5_kt_add_entry(context, kt, &p->keytab); } krb5_kt_close(context, kt); return ret; } static hdb_master_key find_master_key(Key *key, hdb_master_key mkey) { hdb_master_key ret = NULL; while(mkey) { if(ret == NULL && mkey->keytab.vno == 0) ret = mkey; if(key->mkvno == NULL) { if(ret == NULL || mkey->keytab.vno > ret->keytab.vno) ret = mkey; } else if(mkey->keytab.vno == *key->mkvno) return mkey; mkey = mkey->next; } return ret; } krb5_error_code hdb_unseal_keys_mkey(krb5_context context, hdb_entry *ent, hdb_master_key mkey) { int i; krb5_error_code ret; krb5_data res; size_t keysize; Key *k; for(i = 0; i < ent->keys.len; i++){ hdb_master_key key; k = &ent->keys.val[i]; if(k->mkvno == NULL) continue; key = find_master_key(&ent->keys.val[i], mkey); if (key == NULL) return HDB_ERR_NO_MKEY; ret = krb5_decrypt(context, key->crypto, HDB_KU_MKEY, k->key.keyvalue.data, k->key.keyvalue.length, &res); if (ret) return ret; /* fixup keylength if the key got padded when encrypting it */ ret = krb5_enctype_keysize(context, k->key.keytype, &keysize); if (ret) { krb5_data_free(&res); return ret; } if (keysize > res.length) { krb5_data_free(&res); return KRB5_BAD_KEYSIZE; } memset(k->key.keyvalue.data, 0, k->key.keyvalue.length); free(k->key.keyvalue.data); k->key.keyvalue = res; k->key.keyvalue.length = keysize; free(k->mkvno); k->mkvno = NULL; } return 0; } krb5_error_code hdb_unseal_keys(krb5_context context, HDB *db, hdb_entry *ent) { if (db->master_key_set == 0) return 0; return hdb_unseal_keys_mkey(context, ent, db->master_key); } krb5_error_code hdb_seal_keys_mkey(krb5_context context, hdb_entry *ent, hdb_master_key mkey) { int i; krb5_error_code ret; krb5_data res; for(i = 0; i < ent->keys.len; i++){ Key *k = &ent->keys.val[i]; hdb_master_key key; if(k->mkvno != NULL) continue; key = find_master_key(k, mkey); if (key == NULL) return HDB_ERR_NO_MKEY; ret = krb5_encrypt(context, key->crypto, HDB_KU_MKEY, k->key.keyvalue.data, k->key.keyvalue.length, &res); if (ret) return ret; memset(k->key.keyvalue.data, 0, k->key.keyvalue.length); free(k->key.keyvalue.data); k->key.keyvalue = res; k->mkvno = malloc(sizeof(*k->mkvno)); if (k->mkvno == NULL) return ENOMEM; *k->mkvno = key->keytab.vno; } return 0; } krb5_error_code hdb_seal_keys(krb5_context context, HDB *db, hdb_entry *ent) { if (db->master_key_set == 0) return 0; return hdb_seal_keys_mkey(context, ent, db->master_key); } krb5_error_code hdb_set_master_key (krb5_context context, HDB *db, krb5_keyblock *key) { krb5_error_code ret; hdb_master_key mkey; ret = hdb_process_master_key(context, 0, key, 0, &mkey); if (ret) return ret; db->master_key = mkey; #if 0 /* XXX - why? */ des_set_random_generator_seed(key.keyvalue.data); #endif db->master_key_set = 1; return 0; } krb5_error_code hdb_set_master_keyfile (krb5_context context, HDB *db, const char *keyfile) { hdb_master_key key; krb5_error_code ret; ret = hdb_read_master_key(context, keyfile, &key); if (ret) { if (ret != ENOENT) return ret; krb5_clear_error_string(context); return 0; } db->master_key = key; db->master_key_set = 1; return ret; } krb5_error_code hdb_clear_master_key (krb5_context context, HDB *db) { if (db->master_key_set) { hdb_free_master_key(context, db->master_key); db->master_key_set = 0; } return 0; }