/*
 * aes.c: interface to AES cipher
 *
 * Written By Matthew Green
 *
 * Copyright (c) 2000-2021 Matthew R. Green.
 * 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. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``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 AUTHORS 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.
 */

/* XXX THIS DOES NOT WORK XXX */

IRCII_RCSID_NAMED("@(#)$eterna: aes.c,v 1.11 2021/06/17 05:47:20 mrg Exp $", aes_rcsid);

#ifdef USE_OPENSSL
#include <openssl/evp.h>
#endif

#define AES_BLOCK_SIZE	16
#define AES_KEY_BITS	256

typedef unsigned char aeskey[AES_KEY_BITS / 8];

static	u_char *aes_decrypt_str(crypt_key *, u_char *, size_t *);
static	u_char *aes_encrypt_str(crypt_key *, u_char *, size_t *);
static	int	aes_setkey(crypt_key *, int);

/*
 * aes_setkey:
 *	- unlike cast one, this just converts string into something
 *	  by spreading bits across 256.
 *	- the method is kinda stupid.  we take 6 bits from each char
 *	  of the input in the range 32->95, and put it in the output
 *        looping over when we run out of real key.
 */
static int
aes_setkey(crypt_key *key, int clear)
{
#ifdef USE_OPENSSL
	aeskey *k;
	const size_t keysize = sizeof(*k);
	size_t len;
	size_t alen, i;

	if (clear)
	{
		if (key->cookie)
			memset(key->cookie, 0, keysize);
		return 0;
	}

	len = my_strlen(key->key);
	key->cookie = k = new_malloc(keysize);

	alen = 0;
	for (i = 0; i < keysize; i++)
	{
		unsigned b1, b2;

		if (alen <= 0)
			alen = len;
#define AESBITS(x) (((x) - 32) & 0x3f)
		b1 = AESBITS(key->key[alen]);
		b2 = AESBITS(key->key[alen - 1]);
#undef AESBITS

		/*
		 * 3 output bytes = 4 input bytes
		 *
		 * output[0] = input[0] + first 2 from input[1]
		 * output[1] = last 4 from input[1] + first 4 from input[2]
		 * output[2] = first 2 from input[2] + input[3]
		 */
		if (i % 1)
		{
			(*k)[i] = ((b1 & 0x3c) << 2) | (b2 & 0xf);
			alen -= 1;
		}
		else if (i % 2)
		{
			(*k)[i] = ((b1 & 0x3) << 6) | b2;
			alen -= 2;
		}
		else
		{
			(*k)[i] = b1 | ((b2 & 0x3) << 6);
			alen -= 1;
		}
	}
	return 0;
#else
	static int warned = 0;

	if (!warned)
	{
		yell("--- AES support not enabled - rebuild with openssl present.");
		warned = 1;
	}
	return 1;
#endif
}

static u_char *
aes_encrypt_str(crypt_key *key, u_char *inbuf, size_t *len)
{
#ifdef USE_OPENSSL
	EVP_CIPHER_CTX *ctx;
	int outlen, outmaxlen, finallen, inlen = *len;
	u_char *outbuf;

	ctx = EVP_CIPHER_CTX_new();
	if (ctx == NULL)
		return NULL;

	outmaxlen = inlen + (AES_BLOCK_SIZE * 3) - 1;
	outbuf = new_malloc(outmaxlen + 1);

	/*
	 * get random IV.  put this at the start of the buffer, to avoid having
	 * to copy around a second pointer.
	 */
	crypt_get_random_data(outbuf, AES_BLOCK_SIZE);

	if (EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key->cookie, outbuf) != 1 ||
	    EVP_EncryptUpdate(ctx, outbuf + AES_BLOCK_SIZE, &outlen, inbuf, *len) != 1 ||
	    EVP_EncryptFinal_ex(ctx, outbuf + AES_BLOCK_SIZE + outlen, &finallen) != 1)
	{
		new_free(&outbuf);
		return NULL;
	}

	*len = AES_BLOCK_SIZE + outlen + finallen;

	EVP_CIPHER_CTX_free(ctx);

	return outbuf;
#else
	return NULL;
#endif
}

static	u_char *
aes_decrypt_str(crypt_key *key, u_char *inbuf, size_t *len)
{
#ifdef USE_OPENSSL
	EVP_CIPHER_CTX *ctx;
	int outlen, finallen, inlen = *len;
	u_char *outbuf;

	ctx = EVP_CIPHER_CTX_new();
	if (ctx == NULL)
		return NULL;

	outbuf = new_malloc(inlen + 1);

	if (EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key->cookie, inbuf) != 1 ||
	    EVP_DecryptUpdate(ctx, outbuf, &outlen, inbuf + AES_BLOCK_SIZE, inlen - AES_BLOCK_SIZE) != 1 ||
	    EVP_DecryptFinal_ex(ctx, outbuf + outlen, &finallen) != 1)
	{
		new_free(&outbuf);
		return NULL;
	}

	*len = outlen + finallen;
	outbuf[*len] = '\0';

	EVP_CIPHER_CTX_free(ctx);

	return outbuf;
#else
	return NULL;
#endif
}
