diff options
author | Andrew Bartlett <abartlet@samba.org> | 2005-08-20 06:00:50 +0000 |
---|---|---|
committer | Gerald (Jerry) Carter <jerry@samba.org> | 2007-10-10 13:33:35 -0500 |
commit | 55f5453bc81d9a3a4fe67ff0a6ba528d8d0f7984 (patch) | |
tree | 19d28e75e07ead51f0096328ddf913726cfbd59e /source4/heimdal/lib/hdb | |
parent | b456bfa0155ea70ca60590a83e19232c63ec1ac1 (diff) | |
download | samba-55f5453bc81d9a3a4fe67ff0a6ba528d8d0f7984.tar.gz samba-55f5453bc81d9a3a4fe67ff0a6ba528d8d0f7984.tar.bz2 samba-55f5453bc81d9a3a4fe67ff0a6ba528d8d0f7984.zip |
r9413: Bring Samba4 back up to date with lorikeet-heimdal.
Delete test_crypto_wrapping.c, previously included but unbuilt.
Andrew Bartlett
(This used to be commit d5fb30fb0cef330e0947969f0c9afc1f58fc4c7d)
Diffstat (limited to 'source4/heimdal/lib/hdb')
-rw-r--r-- | source4/heimdal/lib/hdb/ext.c | 366 | ||||
-rw-r--r-- | source4/heimdal/lib/hdb/hdb-private.h | 28 | ||||
-rw-r--r-- | source4/heimdal/lib/hdb/hdb-protos.h | 59 | ||||
-rw-r--r-- | source4/heimdal/lib/hdb/hdb.asn1 | 50 | ||||
-rw-r--r-- | source4/heimdal/lib/hdb/hdb.h | 17 | ||||
-rw-r--r-- | source4/heimdal/lib/hdb/hdb_err.et | 3 | ||||
-rw-r--r-- | source4/heimdal/lib/hdb/mkey.c | 595 |
7 files changed, 1105 insertions, 13 deletions
diff --git a/source4/heimdal/lib/hdb/ext.c b/source4/heimdal/lib/hdb/ext.c new file mode 100644 index 0000000000..850b23fb04 --- /dev/null +++ b/source4/heimdal/lib/hdb/ext.c @@ -0,0 +1,366 @@ +/* + * Copyright (c) 2004 - 2005 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" +#include <der.h> + +RCSID("$Id: ext.c,v 1.1 2005/08/11 20:49:31 lha Exp $"); + +krb5_error_code +hdb_entry_check_mandatory(krb5_context context, const hdb_entry *ent) +{ + int i; + + if (ent->extensions == NULL) + return 0; + + /* + * check for unknown extensions and if they where tagged mandatory + */ + + for (i = 0; i < ent->extensions->len; i++) { + if (ent->extensions->val[i].data.element != + choice_HDB_extension_data_asn1_ellipsis) + continue; + if (ent->extensions->val[i].mandatory) { + krb5_set_error_string(context, "Principal have unknown " + "mandatory extension"); + return HDB_ERR_MANDATORY_OPTION; + } + } + return 0; +} + +HDB_extension * +hdb_find_extension(const hdb_entry *entry, int type) +{ + int i; + + if (entry->extensions == NULL) + return NULL; + + for (i = 0; i < entry->extensions->len; i++) + if (entry->extensions->val[i].data.element == type) + return &entry->extensions->val[i]; + return NULL; +} + +/* + * Replace the extension `ext' in `entry'. Make a copy of the + * extension, so the caller must still free `ext' on both success and + * failure. Returns 0 or error code. + */ + +krb5_error_code +hdb_replace_extension(krb5_context context, + hdb_entry *entry, + const HDB_extension *ext) +{ + HDB_extension *ext2; + HDB_extension *es; + int ret; + + ext2 = NULL; + + if (entry->extensions == NULL) { + entry->extensions = calloc(1, sizeof(*entry->extensions)); + if (entry->extensions == NULL) { + krb5_set_error_string(context, "malloc: out of memory"); + return ENOMEM; + } + } else if (ext->data.element != choice_HDB_extension_data_asn1_ellipsis) { + ext2 = hdb_find_extension(entry, ext->data.element); + } else { + /* + * This is an unknown extention, and we are asked to replace a + * possible entry in `entry' that is of the same type. This + * might seem impossible, but ASN.1 CHOICE comes to our + * rescue. The first tag in each branch in the CHOICE is + * unique, so just find the element in the list that have the + * same tag was we are putting into the list. + */ + Der_class replace_class, list_class; + Der_type replace_type, list_type; + unsigned int replace_tag, list_tag; + size_t size; + int i; + + ret = der_get_tag(ext->data.u.asn1_ellipsis.data, + ext->data.u.asn1_ellipsis.length, + &replace_class, &replace_type, &replace_tag, + &size); + if (ret) { + krb5_set_error_string(context, "hdb: failed to decode " + "replacement hdb extention"); + return ret; + } + + for (i = 0; i < entry->extensions->len; i++) { + HDB_extension *ext3 = &entry->extensions->val[i]; + + if (ext3->data.element != choice_HDB_extension_data_asn1_ellipsis) + continue; + + ret = der_get_tag(ext3->data.u.asn1_ellipsis.data, + ext3->data.u.asn1_ellipsis.length, + &list_class, &list_type, &list_tag, + &size); + if (ret) { + krb5_set_error_string(context, "hdb: failed to decode " + "present hdb extention"); + return ret; + } + + if (MAKE_TAG(replace_class,replace_type,replace_type) == + MAKE_TAG(list_class,list_type,list_type)) { + ext2 = ext3; + break; + } + } + } + + if (ext2) { + free_HDB_extension(ext2); + ret = copy_HDB_extension(ext, ext2); + if (ret) + krb5_set_error_string(context, "hdb: failed to copy replacement " + "hdb extention"); + return ret; + } + + es = realloc(entry->extensions->val, + (entry->extensions->len+1)*sizeof(entry->extensions->val[0])); + if (es == NULL) { + krb5_set_error_string(context, "malloc: out of memory"); + return ENOMEM; + } + entry->extensions->val = es; + + ret = copy_HDB_extension(ext, + &entry->extensions->val[entry->extensions->len]); + if (ret == 0) { + entry->extensions->len++; + krb5_set_error_string(context, "hdb: failed to copy new extension"); + } + + return ret; +} + +krb5_error_code +hdb_clear_extension(krb5_context context, + hdb_entry *entry, + int type) +{ + int i; + + if (entry->extensions == NULL) + return 0; + + for (i = 0; i < entry->extensions->len; i++) { + if (entry->extensions->val[i].data.element == type) { + free_HDB_extension(&entry->extensions->val[i]); + memmove(&entry->extensions->val[i], + &entry->extensions->val[i + 1], + sizeof(entry->extensions->val[i]) * (entry->extensions->len - i - 1)); + entry->extensions->len--; + } + } + if (entry->extensions->len == 0) { + free(entry->extensions->val); + free(entry->extensions); + entry->extensions = NULL; + } + + return 0; +} + + +krb5_error_code +hdb_entry_get_pkinit_acl(const hdb_entry *entry, const HDB_Ext_PKINIT_acl **a) +{ + const HDB_extension *ext; + + ext = hdb_find_extension(entry, choice_HDB_extension_data_pkinit_acl); + if (ext) + *a = &ext->data.u.pkinit_acl; + else + *a = NULL; + + return 0; +} + +krb5_error_code +hdb_entry_get_pw_change_time(const hdb_entry *entry, time_t *t) +{ + const HDB_extension *ext; + + ext = hdb_find_extension(entry, choice_HDB_extension_data_last_pw_change); + if (ext) + *t = ext->data.u.last_pw_change; + else + *t = 0; + + return 0; +} + +krb5_error_code +hdb_entry_set_pw_change_time(krb5_context context, + hdb_entry *entry, + time_t t) +{ + HDB_extension ext; + + ext.mandatory = FALSE; + ext.data.element = choice_HDB_extension_data_last_pw_change; + if (t == 0) + t = time(NULL); + ext.data.u.last_pw_change = t; + + return hdb_replace_extension(context, entry, &ext); +} + +int +hdb_entry_get_password(krb5_context context, HDB *db, + const hdb_entry *entry, char **p) +{ + HDB_extension *ext; + int ret; + + ext = hdb_find_extension(entry, choice_HDB_extension_data_password); + if (ext) { + heim_utf8_string str; + heim_octet_string pw; + + if (db->hdb_master_key_set && ext->data.u.password.mkvno) { + hdb_master_key key; + + key = _hdb_find_master_key(ext->data.u.password.mkvno, + db->hdb_master_key); + + if (key == NULL) { + krb5_set_error_string(context, "master key %d missing", + *ext->data.u.password.mkvno); + return HDB_ERR_NO_MKEY; + } + + ret = _hdb_mkey_decrypt(context, key, HDB_KU_MKEY, + ext->data.u.password.password.data, + ext->data.u.password.password.length, + &pw); + } else { + ret = copy_octet_string(&ext->data.u.password.password, &pw); + } + if (ret) { + krb5_clear_error_string(context); + return ret; + } + + str = pw.data; + if (str[pw.length - 1] != '\0') { + krb5_set_error_string(context, "password malformated"); + return EINVAL; + } + + *p = strdup(str); + + free_octet_string(&pw); + if (*p == NULL) { + krb5_set_error_string(context, "malloc: out of memory"); + return ENOMEM; + } + return 0; + } + krb5_set_error_string(context, "password attribute not found"); + return ENOENT; +} + +int +hdb_entry_set_password(krb5_context context, HDB *db, + hdb_entry *entry, const char *p) +{ + HDB_extension ext; + hdb_master_key key; + int ret; + + ext.mandatory = FALSE; + ext.data.element = choice_HDB_extension_data_password; + + if (db->hdb_master_key_set) { + + key = _hdb_find_master_key(NULL, db->hdb_master_key); + if (key == NULL) { + krb5_set_error_string(context, "hdb_entry_set_password: " + "failed to find masterkey"); + return HDB_ERR_NO_MKEY; + } + + ret = _hdb_mkey_encrypt(context, key, HDB_KU_MKEY, + p, strlen(p) + 1, + &ext.data.u.password.password); + if (ret) + return ret; + + ext.data.u.password.mkvno = + malloc(sizeof(*ext.data.u.password.mkvno)); + if (ext.data.u.password.mkvno == NULL) { + free_HDB_extension(&ext); + krb5_set_error_string(context, "malloc: out of memory"); + return ENOMEM; + } + *ext.data.u.password.mkvno = _hdb_mkey_version(key); + + } else { + ext.data.u.password.mkvno = NULL; + + ret = krb5_data_copy(&ext.data.u.password.password, + p, strlen(p) + 1); + if (ret) { + krb5_set_error_string(context, "malloc: out of memory"); + free_HDB_extension(&ext); + return ret; + } + } + + ret = hdb_replace_extension(context, entry, &ext); + + free_HDB_extension(&ext); + + return ret; +} + +int +hdb_entry_clear_password(krb5_context context, hdb_entry *entry) +{ + return hdb_clear_extension(context, entry, + choice_HDB_extension_data_password); +} diff --git a/source4/heimdal/lib/hdb/hdb-private.h b/source4/heimdal/lib/hdb/hdb-private.h index a2b96bb047..7baa944053 100644 --- a/source4/heimdal/lib/hdb/hdb-private.h +++ b/source4/heimdal/lib/hdb/hdb-private.h @@ -9,10 +9,36 @@ _hdb_fetch ( krb5_context /*context*/, HDB */*db*/, unsigned /*flags*/, - krb5_principal /*principal*/, + krb5_const_principal /*principal*/, enum hdb_ent_type /*ent_type*/, hdb_entry */*entry*/); +hdb_master_key +_hdb_find_master_key ( + u_int32_t */*mkvno*/, + hdb_master_key /*mkey*/); + +int +_hdb_mkey_decrypt ( + krb5_context /*context*/, + hdb_master_key /*key*/, + krb5_key_usage /*usage*/, + void */*ptr*/, + size_t /*size*/, + krb5_data */*res*/); + +int +_hdb_mkey_encrypt ( + krb5_context /*context*/, + hdb_master_key /*key*/, + krb5_key_usage /*usage*/, + const void */*ptr*/, + size_t /*size*/, + krb5_data */*res*/); + +int +_hdb_mkey_version (hdb_master_key /*mkey*/); + krb5_error_code _hdb_remove ( krb5_context /*context*/, diff --git a/source4/heimdal/lib/hdb/hdb-protos.h b/source4/heimdal/lib/hdb/hdb-protos.h index 886d48e5bd..799f013eba 100644 --- a/source4/heimdal/lib/hdb/hdb-protos.h +++ b/source4/heimdal/lib/hdb/hdb-protos.h @@ -20,6 +20,12 @@ hdb_check_db_format ( HDB */*db*/); krb5_error_code +hdb_clear_extension ( + krb5_context /*context*/, + hdb_entry */*entry*/, + int /*type*/); + +krb5_error_code hdb_clear_master_key ( krb5_context /*context*/, HDB */*db*/); @@ -56,6 +62,51 @@ hdb_entry2value ( krb5_data */*value*/); krb5_error_code +hdb_entry_check_mandatory ( + krb5_context /*context*/, + const hdb_entry */*ent*/); + +int +hdb_entry_clear_password ( + krb5_context /*context*/, + hdb_entry */*entry*/); + +int +hdb_entry_get_password ( + krb5_context /*context*/, + HDB */*db*/, + const hdb_entry */*entry*/, + char **/*p*/); + +krb5_error_code +hdb_entry_get_pkinit_acl ( + const hdb_entry */*entry*/, + const HDB_Ext_PKINIT_acl **/*a*/); + +krb5_error_code +hdb_entry_get_pw_change_time ( + const hdb_entry */*entry*/, + time_t */*t*/); + +int +hdb_entry_set_password ( + krb5_context /*context*/, + HDB */*db*/, + hdb_entry */*entry*/, + const char */*p*/); + +krb5_error_code +hdb_entry_set_pw_change_time ( + krb5_context /*context*/, + hdb_entry */*entry*/, + time_t /*t*/); + +HDB_extension * +hdb_find_extension ( + const hdb_entry */*entry*/, + int /*type*/); + +krb5_error_code hdb_foreach ( krb5_context /*context*/, HDB */*db*/, @@ -141,7 +192,7 @@ hdb_next_enctype2key ( int hdb_principal2key ( krb5_context /*context*/, - krb5_principal /*p*/, + krb5_const_principal /*p*/, krb5_data */*key*/); krb5_error_code @@ -166,6 +217,12 @@ hdb_read_master_key ( hdb_master_key */*mkey*/); krb5_error_code +hdb_replace_extension ( + krb5_context /*context*/, + hdb_entry */*entry*/, + const HDB_extension */*ext*/); + +krb5_error_code hdb_seal_key ( krb5_context /*context*/, HDB */*db*/, diff --git a/source4/heimdal/lib/hdb/hdb.asn1 b/source4/heimdal/lib/hdb/hdb.asn1 index 770acf4dce..c8a1a34b4f 100644 --- a/source4/heimdal/lib/hdb/hdb.asn1 +++ b/source4/heimdal/lib/hdb/hdb.asn1 @@ -1,4 +1,4 @@ --- $Id: hdb.asn1,v 1.12 2004/11/10 18:50:27 lha Exp $ +-- $Id: hdb.asn1,v 1.13 2005/08/11 13:15:44 lha Exp $ HDB DEFINITIONS ::= BEGIN @@ -50,6 +50,51 @@ GENERATION ::= SEQUENCE { gen[2] INTEGER (0..4294967295) -- generation number } +HDB-Ext-PKINIT-acl ::= SEQUENCE OF SEQUENCE { + subject[0] UTF8String, + issuer[1] UTF8String +} + +HDB-Ext-PKINIT-certificate ::= SEQUENCE OF OCTET STRING + +HDB-Ext-Constrained-delegation-acl ::= SEQUENCE OF Principal + +-- hdb-ext-referrals ::= PA-SERVER-REFERRAL-DATA + +HDB-Ext-Lan-Manager-OWF ::= OCTET STRING + +HDB-Ext-Password ::= SEQUENCE { + mkvno[0] INTEGER (0..4294967295) OPTIONAL, -- master key version number + password OCTET STRING +} + +HDB-Ext-Aliases ::= SEQUENCE { + case-insensitive[0] BOOLEAN, -- case insensitive name allowed + aliases[1] SEQUENCE OF Principal -- all names, inc primary +} + + +HDB-extension ::= SEQUENCE { + mandatory[0] BOOLEAN, -- kdc MUST understand this extension, + -- if not the whole entry must + -- be rejected + data[1] CHOICE { + pkinit-acl[0] HDB-Ext-PKINIT-acl, + pkinit-cert[1] HDB-Ext-PKINIT-certificate, + allowed-to-delegate-to[2] HDB-Ext-Constrained-delegation-acl, +-- referral-info[3] HDB-Ext-Referrals, + lm-owf[4] HDB-Ext-Lan-Manager-OWF, + password[5] HDB-Ext-Password, + aliases[6] HDB-Ext-Aliases, + last-pw-change[7] KerberosTime, + ... + }, + ... +} + +HDB-extensions ::= SEQUENCE OF HDB-extension + + hdb_entry ::= SEQUENCE { principal[0] Principal OPTIONAL, -- this is optional only -- for compatibility with libkrb5 @@ -64,7 +109,8 @@ hdb_entry ::= SEQUENCE { max-renew[9] INTEGER (0..4294967295) OPTIONAL, flags[10] HDBFlags, etypes[11] SEQUENCE OF INTEGER (0..4294967295) OPTIONAL, - generation[12] GENERATION OPTIONAL + generation[12] GENERATION OPTIONAL, + extensions[13] HDB-extensions OPTIONAL } END diff --git a/source4/heimdal/lib/hdb/hdb.h b/source4/heimdal/lib/hdb/hdb.h index 481d4ea93d..fe86f0ae72 100644 --- a/source4/heimdal/lib/hdb/hdb.h +++ b/source4/heimdal/lib/hdb/hdb.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997 - 2000 Kungliga Tekniska Högskolan + * Copyright (c) 1997 - 2005 Kungliga Tekniska Högskolan * (Royal Institute of Technology, Stockholm, Sweden). * All rights reserved. * @@ -31,20 +31,21 @@ * SUCH DAMAGE. */ -/* $Id: hdb.h,v 1.33 2003/09/19 00:19:36 lha Exp $ */ +/* $Id: hdb.h,v 1.35 2005/08/11 13:16:44 lha Exp $ */ #ifndef __HDB_H__ #define __HDB_H__ #include <hdb_err.h> +#include <heim_asn1.h> #include <hdb_asn1.h> enum hdb_lockop{ HDB_RLOCK, HDB_WLOCK }; /* flags for various functions */ -#define HDB_F_DECRYPT 1 /* decrypt keys */ -#define HDB_F_REPLACE 2 /* replace entry */ +#define HDB_F_DECRYPT 1 /* decrypt keys */ +#define HDB_F_REPLACE 2 /* replace entry */ /* key usage for master key */ #define HDB_KU_MKEY 0x484442 @@ -68,20 +69,20 @@ typedef struct HDB{ krb5_error_code (*hdb_store)(krb5_context,struct HDB*,unsigned,hdb_entry*); krb5_error_code (*hdb_remove)(krb5_context, struct HDB*, hdb_entry*); krb5_error_code (*hdb_firstkey)(krb5_context, struct HDB*, - unsigned, hdb_entry*); + unsigned, hdb_entry*); krb5_error_code (*hdb_nextkey)(krb5_context, struct HDB*, - unsigned, hdb_entry*); + unsigned, hdb_entry*); krb5_error_code (*hdb_lock)(krb5_context, struct HDB*, int operation); krb5_error_code (*hdb_unlock)(krb5_context, struct HDB*); krb5_error_code (*hdb_rename)(krb5_context, struct HDB*, const char*); krb5_error_code (*hdb__get)(krb5_context,struct HDB*,krb5_data,krb5_data*); krb5_error_code (*hdb__put)(krb5_context, struct HDB*, int, - krb5_data, krb5_data); + krb5_data, krb5_data); krb5_error_code (*hdb__del)(krb5_context, struct HDB*, krb5_data); krb5_error_code (*hdb_destroy)(krb5_context, struct HDB*); }HDB; -#define HDB_INTERFACE_VERSION 1 +#define HDB_INTERFACE_VERSION 2 struct hdb_so_method { int version; diff --git a/source4/heimdal/lib/hdb/hdb_err.et b/source4/heimdal/lib/hdb/hdb_err.et index 9929a56311..f2636b2fea 100644 --- a/source4/heimdal/lib/hdb/hdb_err.et +++ b/source4/heimdal/lib/hdb/hdb_err.et @@ -3,7 +3,7 @@ # # This might look like a com_err file, but is not # -id "$Id: hdb_err.et,v 1.5 2001/01/28 23:05:52 assar Exp $" +id "$Id: hdb_err.et,v 1.6 2005/08/11 13:17:22 lha Exp $" error_table hdb @@ -23,5 +23,6 @@ error_code CANT_LOCK_DB, "Insufficient access to lock database" error_code EXISTS, "Entry already exists in database" error_code BADVERSION, "Wrong database version" error_code NO_MKEY, "No correct master key" +error_code MANDATORY_OPTION, "Entry contains unknown mandatory extension" end diff --git a/source4/heimdal/lib/hdb/mkey.c b/source4/heimdal/lib/hdb/mkey.c new file mode 100644 index 0000000000..9e04dc6d8d --- /dev/null +++ b/source4/heimdal/lib/hdb/mkey.c @@ -0,0 +1,595 @@ +/* + * Copyright (c) 2000 - 2004 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.20 2005/08/10 08:41:03 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; + 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; +} + +hdb_master_key +_hdb_find_master_key(u_int32_t *mkvno, hdb_master_key mkey) +{ + hdb_master_key ret = NULL; + while(mkey) { + if(ret == NULL && mkey->keytab.vno == 0) + ret = mkey; + if(mkvno == NULL) { + if(ret == NULL || mkey->keytab.vno > ret->keytab.vno) + ret = mkey; + } else if(mkey->keytab.vno == *mkvno) + return mkey; + mkey = mkey->next; + } + return ret; +} + +int +_hdb_mkey_version(hdb_master_key mkey) +{ + return mkey->keytab.vno; +} + +int +_hdb_mkey_decrypt(krb5_context context, hdb_master_key key, + krb5_key_usage usage, + void *ptr, size_t size, krb5_data *res) +{ + return krb5_decrypt(context, key->crypto, usage, + ptr, size, res); +} + +int +_hdb_mkey_encrypt(krb5_context context, hdb_master_key key, + krb5_key_usage usage, + const void *ptr, size_t size, krb5_data *res) +{ + return krb5_encrypt(context, key->crypto, usage, + ptr, size, res); +} + +krb5_error_code +hdb_unseal_key_mkey(krb5_context context, Key *k, hdb_master_key mkey) +{ + + krb5_error_code ret; + krb5_data res; + size_t keysize; + + hdb_master_key key; + + if(k->mkvno == NULL) + return 0; + + key = _hdb_find_master_key(k->mkvno, mkey); + + if (key == NULL) + return HDB_ERR_NO_MKEY; + + ret = _hdb_mkey_decrypt(context, key, HDB_KU_MKEY, + k->key.keyvalue.data, + k->key.keyvalue.length, + &res); + if(ret == KRB5KRB_AP_ERR_BAD_INTEGRITY) { + /* try to decrypt with MIT key usage */ + ret = _hdb_mkey_decrypt(context, key, 0, + 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_mkey(krb5_context context, hdb_entry *ent, hdb_master_key mkey) +{ + int i; + + for(i = 0; i < ent->keys.len; i++){ + krb5_error_code ret; + + ret = hdb_unseal_key_mkey(context, &ent->keys.val[i], mkey); + if (ret) + return ret; + } + return 0; +} + +krb5_error_code +hdb_unseal_keys(krb5_context context, HDB *db, hdb_entry *ent) +{ + if (db->hdb_master_key_set == 0) + return 0; + return hdb_unseal_keys_mkey(context, ent, db->hdb_master_key); +} + +krb5_error_code +hdb_unseal_key(krb5_context context, HDB *db, Key *k) +{ + if (db->hdb_master_key_set == 0) + return 0; + return hdb_unseal_key_mkey(context, k, db->hdb_master_key); +} + +krb5_error_code +hdb_seal_key_mkey(krb5_context context, Key *k, hdb_master_key mkey) +{ + krb5_error_code ret; + krb5_data res; + hdb_master_key key; + + key = _hdb_find_master_key(k->mkvno, mkey); + + if (key == NULL) + return HDB_ERR_NO_MKEY; + + ret = _hdb_mkey_encrypt(context, key, 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; + + if (k->mkvno == NULL) { + 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_mkey(krb5_context context, hdb_entry *ent, hdb_master_key mkey) +{ + int i; + for(i = 0; i < ent->keys.len; i++){ + krb5_error_code ret; + + ret = hdb_seal_key_mkey(context, &ent->keys.val[i], mkey); + if (ret) + return ret; + } + return 0; +} + +krb5_error_code +hdb_seal_keys(krb5_context context, HDB *db, hdb_entry *ent) +{ + if (db->hdb_master_key_set == 0) + return 0; + + return hdb_seal_keys_mkey(context, ent, db->hdb_master_key); +} + +krb5_error_code +hdb_seal_key(krb5_context context, HDB *db, Key *k) +{ + if (db->hdb_master_key_set == 0) + return 0; + + return hdb_seal_key_mkey(context, k, db->hdb_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->hdb_master_key = mkey; +#if 0 /* XXX - why? */ + des_set_random_generator_seed(key.keyvalue.data); +#endif + db->hdb_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->hdb_master_key = key; + db->hdb_master_key_set = 1; + return ret; +} + +krb5_error_code +hdb_clear_master_key (krb5_context context, + HDB *db) +{ + if (db->hdb_master_key_set) { + hdb_free_master_key(context, db->hdb_master_key); + db->hdb_master_key_set = 0; + } + return 0; +} |