/*
 	symcrypt.c
 
 	Symmetric encryption functions of 'sks' project
 	
	Copyright (C) 2004-2007  Manuel Pancorbo Castro

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

	Manuel Pancorbo Castro <mpancorbo@gmail.com>
 
*/

#include <unistd.h>
#include <sys/stat.h>
#include <zlib.h>
#include "mycrypt.h"
#include "binasc.h"
#include "sks.h"

extern char err_data_corrupted[];
extern char err_bad_mac[];

/** The password salt is partially reused to split the key
	into HMAC and encrypt keys **/
extern unsigned char salt[];

/**
 ** AES_Cipher functions. 'aes_process' works in CTR mode. MAC is included
 **/

static byte IV[BLOCK_SIZE], IV2[BLOCK_SIZE];
static symmetric_key aes_key;
static int aes_init_flag = 0;
static hash_state hmac;
static int block_index = 0;

int aes_close(byte *hmacout, byte *hmac_key, int tlen)
{
	assert(aes_init_flag);
	byte opad[2*HASH_SIZE];
	hashout c;
	
	memset(opad, 0x5c, HASH_SIZE);
	int i = HASH_SIZE;
	while(i) {
		opad[i] ^= hmac_key[i];
		--i;
	}
	
	tiger_done(&hmac, &opad[HASH_SIZE]);
	hash_binary(c, opad, 2*HASH_SIZE);
	tlen = ((tlen > HASH_SIZE) || (tlen < 0 ))? HASH_SIZE: tlen;
	memcpy(hmacout, c, tlen);
	
	zeromem(IV, BLOCK_SIZE);
 	zeromem(IV2, BLOCK_SIZE);
	zeromem(&aes_key, sizeof(aes_key));
	zeromem(hmac_key, KEY_SIZE);
	/*zeromem(&hmac, sizeof(hash_state));*/
	zeromem(opad, 2*HASH_SIZE);
	
	aes_init_flag = 0;
	block_index = 0;
	return tlen;
}

ERROR aes_init(byte *hmac_key, byte *crypt_key, unsigned int ksize, byte *iv)
{
	assert(HASH_SIZE == KEY_SIZE);
	int bsize = BLOCK_SIZE;
	byte ipad[HASH_SIZE];
	
	assert ((hmac_key != NULL) && (crypt_key != NULL) && (iv != NULL) && !aes_init_flag); /* Already initialized! **/
	
	/** Check input iv **/
	zeromem(IV, bsize);
	if(!memcmp(IV, iv, bsize)){ /** If null, then build it **/
		if(!(bsize = get_entropy(iv, bsize))) return -1;
	}
	memcpy(IV, iv, bsize); /** copy in local iv **/
	
	/** HMAC setup **/
	tiger_init(&hmac);
	memset(ipad, 0x36, HASH_SIZE);
	int i = HASH_SIZE;
	while(i) {
		ipad[i] ^= hmac_key[i];
		--i;
	}
	if((tiger_process(&hmac, ipad, HASH_SIZE)) != CRYPT_OK) return -1;
	zeromem(ipad, HASH_SIZE);
	
	/** Key setup **/
	if((aes_setup(crypt_key, ksize, 0, &aes_key)) != CRYPT_OK){
		zeromem(crypt_key, ksize);
		return -1;
	}
	
	aes_init_flag = 1;
	return CRYPT_OK;
}

ERROR aes_process(byte *dest, register int n, int mode) /** 'dest' must contain data **/
{
	assert((dest != NULL) && aes_init_flag);
	int j, len = n;
	/*static int k = 0;*/
	static byte *bp = NULL; 
	byte  *pt = dest;
	
	/** HMAC **/
	if(!mode) /** mode == 0 means encryption **/
		if((tiger_process(&hmac, dest, len)) != CRYPT_OK) return -1;
	
	/** Encrypt **/
	while(n){
		if(!block_index){
			aes_ecb_encrypt(IV, IV2, &aes_key);
			bp = IV2;
			block_index = BLOCK_SIZE;
			
			/** Increase counter for next time **/
			for(j = 0; j < BLOCK_SIZE; ++j)
				if (++IV[j]) break;
		}

		for(; (block_index) && (n); --block_index, --n)
			*pt++ ^= *bp++;
	}
	
	/** HMAC **/
	if(mode) /** mode != 0 means decryption **/
		if((tiger_process(&hmac, dest, len)) != CRYPT_OK) return -1;
		
	return CRYPT_OK;
}

ERROR sym_encrypt(FILE *f_inp, FILE *f_out, PubKey key_space, int mode)
{
	unsigned int i, binmode, ksize, bsize = BLOCK_SIZE, 
		cipher_mode, comp_level = 0;
	unsigned int n, insize, outsize;
	unsigned char IV[BLOCK_SIZE], *inBuf = NULL, *outBuf = NULL,
			hmac_key[HASH_SIZE], crypt_key[KEY_SIZE],
			buf[64];
	ERROR err = 0;

	ksize = KEY_SIZE;
	
	/** Set cipher and compress **/
	cipher_mode = mode & 0x7f;
	if(mode & 0x80)	comp_level = DEFAULT_COMP_LEVEL;
	
	zeromem(hmac_key, HASH_SIZE);
	zeromem(crypt_key, ksize);
	if(cipher_mode == AES128_CTR){	/** Backward compatibility HMAC-key == crypt-key **/
		memcpy(hmac_key, key_space, ksize);
		memcpy(crypt_key, key_space, ksize);
	}
	else if(cipher_mode == AES128_CTR2){ /** HMAC and encrypt keys are different **/
		/** process encrypt key = hash(key | PI(0_128) **/
		zeromem(buf, 64);
		memcpy(buf, key_space, ksize);
		memcpy(buf + ksize, salt, 32);	/** Added first 32*8 bits of PI **/
		hash_binary(crypt_key, buf, 32 + ksize);
		/** process hmac key = hask(key | PI(128_256)**/
		zeromem(buf, 64);
		memcpy(buf, key_space, ksize);
		memcpy(buf + ksize, salt + 32, 32);	/** Added next 32*8 bits of PI **/
		hash_binary(hmac_key, buf, 32 + ksize);
		zeromem(buf, 64);
	}
	else return -1;
	
	zeromem(key_space, ksize);
	zeromem(IV, bsize);
	if((aes_init(hmac_key, crypt_key, ksize, IV)) != CRYPT_OK) return -1;
	zeromem(crypt_key, ksize);

	/** Set buffer size and binmode **/
	binmode = (mode & BINARY_FLAG);
	if(f_inp == stdin){
		/*binmode = 0;*/
		insize = LARGE_BUF;
		
	}
	else{
		struct stat f;
		/*binmode = 1;*/
		fstat(fileno(f_inp), &f);
		insize = ((comp_level) && (f.st_size < LARGE_BUF/*MAX_ALLOC_SIZE*/))? 
			f.st_size + 1: LARGE_BUF; 
		while((f.st_size % insize) == 0L) ++insize;
	}
	outsize = insize + insize/1000 + 13;
	if((inBuf = malloc(insize)) == NULL) {err = -1; goto SYM_ENCRYPT_BYE_3;}
	
	/** Write cipher identifier **/
	fputcPlus(cipher_mode, f_out, binmode);
	/** Compression info **/
	fputcPlus(comp_level, f_out, binmode);
	/* Put IV */
	fwritePlus(IV, 1, bsize, f_out, binmode);
	
	/* Put input size */
	write_longint(f_out, insize, binmode);
	if(comp_level){	
		if((outBuf = malloc(outsize)) == NULL) {err = -1; goto SYM_ENCRYPT_BYE_2;}
	}
	else outBuf = inBuf; /** If no compress, perform op. inline **/

	/** Starts the play **/
	while(!feof(f_inp)){
		n = fread(inBuf, 1, insize, f_inp);
		
		/** Compression **/
		if(comp_level && n){
			uLongf len = outsize;
			if((i = compress2(outBuf, &len, inBuf, n, comp_level))){
				break;
			}
			n = len;
		}
		write_longint(f_out, n, binmode);
		
		/** Encryption **/
		if(aes_process(outBuf, n, 0) != CRYPT_OK) {err = -1; goto SYM_ENCRYPT_BYE_1;}
		fwritePlus(outBuf, 1, n, f_out, binmode);
	}
	n = aes_close(outBuf, hmac_key, HMAC_SIZE);
	write_longint(f_out, n + 1, binmode); /** The trick!: put false length n+1 **/
	fwritePlus(outBuf, 1, n, f_out, binmode);
		
	if(!binmode) flushArmour(f_out);
	
SYM_ENCRYPT_BYE_1:
	if (comp_level)
		zeromem(outBuf, outsize), free(outBuf);
SYM_ENCRYPT_BYE_2:
	zeromem(inBuf, insize), free(inBuf);
SYM_ENCRYPT_BYE_3:	
	zerokey(hmac_key);
	burn_stack(200);
	burnBinasc();
	return err;
}


ERROR sym_decrypt(FILE *f_inp, FILE *f_out, PubKey key_space, int binmode)
{
	unsigned int /*binmode,*/ ksize, bsize = BLOCK_SIZE, 
		comp_flag, auth_flag = 0;
	unsigned long int n, insize, outsize;
	unsigned char IV[BLOCK_SIZE], check[HMAC_SIZE],
		*inBuf = NULL, *outBuf = NULL, c[2],
		hmac_key[HASH_SIZE], crypt_key[KEY_SIZE],
		buf[64];
	ERROR err = 0;

	/*binmode = (f_inp == stdin)? 0: 1;*/
	freadPlus(c, 1, 2, f_inp, binmode);

	ksize =  KEY_SIZE;
	
	zeromem(hmac_key, HASH_SIZE);
	zeromem(crypt_key, ksize);
	if(c[0] == AES128_CTR){ /* Only one cipher is considered (at the moment) **/
		memcpy(hmac_key, key_space, ksize);
		memcpy(crypt_key, key_space, ksize);
	}
	else if(c[0] == AES128_CTR2){ /** HMAC and encrypt keys are different **/
		/** process encrypt key **/
		zeromem(buf, 64);
		memcpy(buf, key_space, ksize);
		memcpy(buf + ksize, salt, 32);	/** Added first 32*8 bits of PI **/
		hash_binary(crypt_key, buf, 32 + ksize);
		/** process hmac key **/
		zeromem(buf, 64);
		memcpy(buf, key_space, ksize);
		memcpy(buf + ksize, salt + 32, 32);	/** Added next 32*8 bits of PI **/
		hash_binary(hmac_key, buf, 32 + ksize);
		zeromem(buf, 64);
	}
	else return -1;
	
	zeromem(key_space, ksize);
	freadPlus(IV, 1, bsize, f_inp, binmode);
	aes_init(hmac_key, crypt_key, ksize, IV);
	zeromem(crypt_key, ksize);
	
	/** Look for compression **/
	comp_flag = c[1];
	
	/** Starts the play **/
	read_longint(f_inp, &outsize, binmode);
	if(!comp_flag){
		if((inBuf = malloc(outsize)) == NULL){err = -1; goto SYM_DECRYPT_BYE_2;}
		outBuf = inBuf;
	}
	else if((outBuf = malloc(outsize)) == NULL){err = -1; goto SYM_DECRYPT_BYE_1;}
	
	while(!feof(f_inp)){
		
		if(!read_longint(f_inp, &insize, binmode)) break;
		if(comp_flag){
			inBuf = realloc(inBuf, insize);
		}
		
		n = freadPlus(inBuf, 1, insize, f_inp, binmode);
		if((insize == n + 1 ))	break;
		
		/** Decrypt **/
		aes_process(inBuf, n, 1);
		
		if(comp_flag){
			uLongf len = outsize;
			if(Z_DATA_ERROR == uncompress(outBuf, &len, inBuf, insize)){
				fputs(CHCONV(err_data_corrupted), stderr);
				auth_flag = 0;
				break;
			}
			n = len;
		}
		fwrite(outBuf, 1, n, f_out);
	}
	aes_close(check, hmac_key, HMAC_SIZE);
	
	/** Compare MACs **/
	auth_flag |= !memcmp(check, inBuf, HMAC_SIZE);
	if(!auth_flag) fputs(CHCONV(err_bad_mac), stderr);
	
	zeromem(outBuf, outsize);
	free(outBuf);
SYM_DECRYPT_BYE_1:
	if(comp_flag) free(inBuf);
SYM_DECRYPT_BYE_2:
	zerokey(hmac_key);
	
	burn_stack(200);
	burnBinasc();
	return err;
}


