/*
 * Copyright (c) 1997, 1998 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. All advertising materials mentioning features or use of this software 
 *    must display the following acknowledgement: 
 *      This product includes software developed by Kungliga Tekniska 
 *      Högskolan and its contributors. 
 *
 * 4. 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 <krb5_locl.h>
#include "crc.h"

RCSID("$Id: encrypt.c,v 1.31 1998/01/31 16:37:37 joda Exp $");

struct encryption_type {
    krb5_enctype type;
    size_t blocksize;
    size_t confoundersize;
    void (*encrypt)(void *, size_t, const krb5_keyblock *, int);
    krb5_keytype keytype;
    krb5_cksumtype cksumtype;
    const char *name;
};

static void
NULL_encrypt(void *p, size_t len, const krb5_keyblock *keyblock, int encrypt)
{
}

static void
DES_encrypt_null_ivec(void *p, size_t len, 
		      const krb5_keyblock *keyblock, int encrypt)
{
    des_cblock key;
    des_cblock ivec;
    des_key_schedule schedule;
    memcpy(&key, keyblock->keyvalue.data, sizeof(key));
    des_set_key(&key, schedule);
    memset (&ivec, 0, sizeof(ivec));
    des_cbc_encrypt(p, p, len, schedule, &ivec, encrypt);
}

static void
DES_encrypt_key_ivec(void *p, size_t len, 
		     const krb5_keyblock *keyblock, int encrypt)
{
    des_cblock key;
    des_key_schedule schedule;
    memcpy(&key, keyblock->keyvalue.data, sizeof(key));
    des_set_key(&key, schedule);
    des_cbc_encrypt(p, p, len, schedule, &key, encrypt);
    memset(&key, 0, sizeof(key));
    memset(&schedule, 0, sizeof(schedule));
}

static void
DES3_encrypt_null_ivec(void *p, size_t len, 
		       const krb5_keyblock *keyblock, int encrypt)
{
    des_cblock key;
    des_key_schedule schedule[3];
    int i;
    for(i = 0; i < 3; i++){
	memcpy(&key, (char*)keyblock->keyvalue.data + 8*i, 8);
	des_set_key(&key, schedule[i]);
    }
    memset(&key, 0, sizeof(key));
    des_ede3_cbc_encrypt(p, p, len, schedule[0], schedule[1], schedule[2],
			 &key, encrypt);
    memset(&schedule, 0, sizeof(schedule));
}

static struct encryption_type em [] = {
    { ETYPE_DES_CBC_CRC, 8, 8, DES_encrypt_key_ivec,
      KEYTYPE_DES,  CKSUMTYPE_CRC32, "des-cbc-crc" },
    { ETYPE_DES_CBC_MD4, 8, 8, DES_encrypt_null_ivec,
      KEYTYPE_DES,  CKSUMTYPE_RSA_MD4, "des-cbc-md4" },
    { ETYPE_DES_CBC_MD5, 8, 8, DES_encrypt_null_ivec,
      KEYTYPE_DES,  CKSUMTYPE_RSA_MD5, "des-cbc-md5" },
    { ETYPE_DES3_CBC_MD5, 8, 8, DES3_encrypt_null_ivec, 
      KEYTYPE_DES3, CKSUMTYPE_RSA_MD5, "des3-cbc-md5" },
    { ETYPE_DES3_CBC_SHA1, 8, 8, DES3_encrypt_null_ivec, 
      KEYTYPE_DES3, CKSUMTYPE_SHA1, "des3-cbc-sha1" },
    { ETYPE_NULL, 1, 0, NULL_encrypt, KEYTYPE_NULL, CKSUMTYPE_NONE, "null" },
};

static int num_etypes = sizeof(em) / sizeof(em[0]);

static struct encryption_type *
find_encryption_type(int etype)
{
    struct encryption_type *e;
    for(e = em; e < em + num_etypes; e++)
	if(etype == e->type)
	    return e;
    return NULL;
}

krb5_boolean
krb5_etype_valid(krb5_context context,
		 krb5_enctype etype)
{
    struct encryption_type *e;

    e = find_encryption_type(etype);
    return e != NULL && etype != ETYPE_NULL;
}

krb5_error_code
krb5_etype_to_string(krb5_context context,
		     krb5_enctype etype,
		     char **string)
{
    struct encryption_type *e;
    e = find_encryption_type(etype);
    if(e == NULL)
	return KRB5_PROG_ETYPE_NOSUPP;
    *string = strdup(e->name);
    return 0;
}

krb5_error_code
krb5_string_to_etype(krb5_context context,
		     const char *string,
		     krb5_enctype *etype)
{
    int i;
    for(i = 0; i < num_etypes; i++)
	if(strcasecmp(em[i].name, string) == 0){
	    *etype = em[i].type;
	    return 0;
	}
    return KRB5_PROG_ETYPE_NOSUPP;
}

krb5_error_code
krb5_keytype_to_etypes(krb5_context context,
		       krb5_keytype keytype,
		       krb5_enctype **etypes)
{
    krb5_enctype *tmp;
    struct encryption_type *e;
    int i;

    *etypes = malloc((num_etypes + 1) * sizeof(*etypes));
    if (*etypes == NULL)
	return ENOMEM;
    i = 0;
    for (e = em; e < em + num_etypes; ++e)
	if (e->keytype == keytype)
	    (*etypes)[i++] = e->type;
    tmp = realloc (*etypes, i * sizeof(*etypes));
    if (tmp == NULL) {
	free (*etypes);
	return ENOMEM;
    }
    *etypes = tmp;
    return 0;
}

krb5_error_code
krb5_etype_to_keytype(krb5_context context,
		      krb5_enctype etype,
		      krb5_keytype *keytype)
{
    struct encryption_type *e;
    e = find_encryption_type(etype);
    if(e == NULL)
	return KRB5_PROG_ETYPE_NOSUPP;
    *keytype = e->keytype;
    return 0;
}

krb5_error_code
krb5_decode_keytype(krb5_context context,
		    krb5_keytype *keytype,
		    int decode)
{
    if(context->ktype_is_etype){
	krb5_error_code ret;
	if(decode) {
	    krb5_keytype kt;
	    ret = krb5_etype_to_keytype(context, 
					(krb5_enctype)*keytype, 
					&kt);
	    if(ret)
		return ret;
	    *keytype = kt;
	}else{
	    krb5_enctype et;
	    ret = krb5_keytype_to_etype(context, 
					*keytype, 
					&et);
	    if(ret)
		return ret;
	    *keytype = (krb5_keytype)et;
	}
    }
    return 0;
}

krb5_error_code
krb5_decode_keyblock(krb5_context context,
		     krb5_keyblock *key,
		     int decode)
{
    return krb5_decode_keytype(context, &key->keytype, decode);
}

void
krb5_generate_random_block(void *buf, size_t len)
{
    des_cblock tmp;
    unsigned char *p = buf;
    size_t l;
    while(len){
	des_new_random_key(&tmp);
	l = len > 8 ? 8 : len;
	memcpy(p, tmp, l);
	p += l;
	len -= l;
    }
}

static krb5_error_code
krb5_do_encrypt(krb5_context context,
		void *ptr, 
		size_t len,
		struct encryption_type *et,
		const krb5_keyblock *keyblock,
		krb5_data *result)
{
    size_t sz;
    size_t checksumsize;
    unsigned char *p;
    krb5_error_code ret;
    Checksum cksum;
    
    ret = krb5_cksumsize(context, et->cksumtype, &checksumsize);
    if(ret)
	return ret;
    sz = len + et->confoundersize + checksumsize;
    sz = (sz + et->blocksize - 1) & ~ (et->blocksize - 1);
    p = calloc(1, sz);
    if (p == NULL)
	return ENOMEM;
    krb5_generate_random_block(p, et->confoundersize); /* XXX */
    memcpy(p + et->confoundersize + checksumsize, ptr, len);

    krb5_create_checksum(context, et->cksumtype, p, sz, NULL, &cksum);
    memcpy(p + et->confoundersize, cksum.checksum.data, checksumsize);
    free_Checksum(&cksum);
    (*et->encrypt)(p, sz, keyblock, 1);
    result->data = p;
    result->length = sz;
    return 0;
}

static krb5_error_code
krb5_do_decrypt(krb5_context context,
		void *ptr,
		size_t len,
		struct encryption_type *et,
		const krb5_keyblock *keyblock,
		krb5_data *result)
{
    unsigned char *p = ptr;
    size_t outlen;
    Checksum cksum;
    krb5_error_code ret;

    cksum.cksumtype = et->cksumtype;
    ret = krb5_cksumsize(context, cksum.cksumtype, &cksum.checksum.length);
    if(ret)
	return ret;
    outlen = len - et->confoundersize - cksum.checksum.length;
    cksum.checksum.data = malloc(cksum.checksum.length);
    if(cksum.checksum.data == NULL)
	return ENOMEM;
    (*et->encrypt)(ptr, len, keyblock, 0);

    memcpy(cksum.checksum.data, p + et->confoundersize, cksum.checksum.length);
    memset(p + et->confoundersize, 0, cksum.checksum.length);
    
    ret = krb5_verify_checksum (context,
				ptr, 
				len,
				keyblock,
				&cksum);
    free_Checksum(&cksum);
    if(ret)
	return ret;
    
    result->data = malloc(outlen);
    if(result->data == NULL)
	return ENOMEM;
    result->length = outlen;
    memcpy(result->data, p + (len - outlen), outlen);
    return 0;
}

krb5_error_code
krb5_encrypt (krb5_context context,
	      void *ptr,
	      size_t len,
	      int etype,
	      const krb5_keyblock *keyblock,
	      krb5_data *result)
{
    struct encryption_type *e;
    if((e = find_encryption_type(etype)))
	return krb5_do_encrypt(context, ptr, len, e, keyblock, result);
    return KRB5_PROG_ETYPE_NOSUPP;
}

krb5_error_code
krb5_encrypt_EncryptedData(krb5_context context,
			   void *ptr,
			   size_t len,
			   int etype,
			   int kvno,
			   const krb5_keyblock *keyblock,
			   EncryptedData *result)
{
    result->etype = etype;
    if(kvno){
	result->kvno = malloc(sizeof(*result->kvno));
	*result->kvno = kvno;
    }else
	result->kvno = NULL;
    return krb5_encrypt(context, ptr, len, etype, keyblock, &result->cipher);
}

krb5_error_code
krb5_decrypt (krb5_context context,
	      void *ptr,
	      size_t len,
	      int etype,
	      const krb5_keyblock *keyblock,
	      krb5_data *result)
{
    struct encryption_type *e;
    if((e = find_encryption_type(etype)))
	return krb5_do_decrypt(context, ptr, len, e, keyblock, result);
    return KRB5_PROG_ETYPE_NOSUPP;
}

krb5_error_code
krb5_decrypt_EncryptedData (krb5_context context,
			    EncryptedData *e,
			    const krb5_keyblock *keyblock,
			    krb5_data *result)
{
    return krb5_decrypt(context, e->cipher.data, e->cipher.length, e->etype, 
			keyblock, result);
}

static krb5_error_code
DES_random_key(krb5_data *key)
{
    unsigned char *p;
    key->length = 8;
    p = malloc(key->length);
    if(p == NULL)
	return ENOMEM;
    des_new_random_key((void*)p);
    key->data = p;
    return 0;
}

static krb5_error_code
DES3_random_key(krb5_data *key)
{
    unsigned char *p;
    key->length = 24;
    p = malloc(key->length);
    if(p == NULL)
	return ENOMEM;
    des_new_random_key((void*)p);
    des_new_random_key((void*)(p + 8));
    des_new_random_key((void*)(p + 16));
    key->data = p;
    return 0;
}

static struct key_type {
    krb5_keytype ktype;
    krb5_error_code (*random_key)(krb5_data *);
    krb5_enctype best_etype;
    krb5_cksumtype best_cksumtype;
    const char *name;
} km [] = {
    { KEYTYPE_NULL,	NULL,			ETYPE_NULL,
      CKSUMTYPE_NONE,		"null" },
    { KEYTYPE_DES,	DES_random_key,		ETYPE_DES_CBC_MD5,
      CKSUMTYPE_RSA_MD5_DES,	"des" },
    { KEYTYPE_DES_AFS3,	DES_random_key,		ETYPE_DES_CBC_MD5,
      CKSUMTYPE_RSA_MD5_DES,	"des" },
    { KEYTYPE_DES3,	DES3_random_key,	ETYPE_DES3_CBC_SHA1,
      CKSUMTYPE_HMAC_SHA1_DES3, "des3" }
};

static struct key_type*
find_key_type(krb5_keytype ktype)
{
    int i;
    for(i = 0; i < sizeof(km) / sizeof(km[0]); i++)
	if(km[i].ktype == ktype)
	    return &km[i];
    return NULL;
}

krb5_error_code
krb5_generate_random_keyblock(krb5_context context,
			      krb5_keytype ktype,
			      krb5_keyblock *key)
{
    krb5_error_code ret;
    struct key_type *k = find_key_type(ktype);
    if(k == NULL)
	return KRB5_PROG_KEYTYPE_NOSUPP;
    ret = (*k->random_key)(&key->keyvalue);
    if(ret)
	return ret;
    key->keytype = ktype;
    return 0;
}


krb5_error_code
krb5_keytype_to_etype(krb5_context context, krb5_keytype ktype, 
		      krb5_enctype *etype)
{
    struct key_type *k = find_key_type(ktype);
    if(k == NULL)
	return KRB5_PROG_KEYTYPE_NOSUPP;
    *etype = k->best_etype;
    return 0;
}

krb5_error_code
krb5_keytype_to_cksumtype(krb5_context context,
			  krb5_keytype ktype,
			  krb5_cksumtype *ctype)
{
    struct key_type *k = find_key_type(ktype);
    if(k == NULL)
	return KRB5_PROG_KEYTYPE_NOSUPP;
    *ctype = k->best_cksumtype;
    return 0;
}

krb5_error_code
krb5_string_to_keytype(krb5_context context, const char *string,
		       krb5_keytype *ktype)
{
    int i;
    for(i = 0; i < sizeof(km) / sizeof(km[0]); i++)
	if(strcasecmp(km[i].name, string) == 0){
	    *ktype = km[i].ktype;
	    return 0;
	}
    return KRB5_PROG_KEYTYPE_NOSUPP;
}

krb5_error_code
krb5_keytype_to_string(krb5_context context, krb5_keytype ktype, char **string)
{
    struct key_type *k = find_key_type(ktype);
    if(k == NULL)
	return KRB5_PROG_KEYTYPE_NOSUPP;
    *string = strdup(k->name);
    if(*string == NULL)
	return ENOMEM;
    return 0;
}
