text: /*
   * 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
  
  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_message(context, ENOMEM, "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));
  	if(p == NULL) {
  	    krb5_kt_end_seq_get(context, id, &cursor);
  	    ret = ENOMEM;
  	    goto out;
  	}
  	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_message(context, save_errno, "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);
      /* could possibly use ret_keyblock here, but do it with more
         checks for now */
      {
  	ret = krb5_ret_int16(sp, &enctype);
  	if (ret)
  	    goto out;
  	if((htons(enctype) & 0xff00) == 0x3000) {
  	    ret = HEIM_ERR_BAD_MKEY;
  	    krb5_set_error_message(context, ret, "unknown keytype in %s: "
  				   "%#x, expected %#x",
  				   filename, htons(enctype), 0x3000);
  	    goto out;
  	}
  	key.keytype = enctype;
  	ret = krb5_ret_data(sp, &key.keyvalue);
  	if(ret)
  	    goto out;
      }
      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_message(context, save_errno, "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_message(context, save_errno, "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_message(context, save_errno, "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_message(context, save_errno, "error reading %s: %s",
  			       filename, strerror(save_errno));
  	return save_errno;
      }
      if(len != 8) {
  	krb5_set_error_message(context, HEIM_ERR_EOF,
  			       "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_message(context, save_errno, "failed to open %s: %s",
  			       filename, strerror(save_errno));
  	return save_errno;
      }
  
      if(fread(buf, 1, 2, f) != 2) {
  	fclose(f);
  	krb5_set_error_message(context, HEIM_ERR_EOF, "end of file reading %s", filename);
  	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(uint32_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;
  
      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_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_message(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;
  }
  