libpurple/ciphers/aes.c

Wed, 21 Aug 2013 14:59:29 +0200

author
Tomasz Wasilczyk <tomkiewicz@cpw.pidgin.im>
date
Wed, 21 Aug 2013 14:59:29 +0200
changeset 34304
faf0414a8b51
parent 34275
a6ddc53d1c84
permissions
-rw-r--r--

Fix most of libpurple warnings about -Wsign-compare

/*
 * purple
 *
 * Purple is the legal property of its developers, whose names are too numerous
 * to list here.  Please refer to the COPYRIGHT file distributed with this
 * source distribution.
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
 *
 * Written by Tomek Wasilczyk <tomkiewicz@cpw.pidgin.im>
 */

#include "internal.h"
#include "cipher.h"
#include "ciphers.h"
#include "debug.h"

#if defined(HAVE_GNUTLS)
#  define PURPLE_AES_USE_GNUTLS 1
#  include <gnutls/gnutls.h>
#  include <gnutls/crypto.h>
#elif defined(HAVE_NSS)
#  define PURPLE_AES_USE_NSS 1
#  include <nss.h>
#  include <pk11pub.h>
#  include <prerror.h>
#else
#  error "No GnuTLS or NSS support"
#endif

/* 128bit */
#define PURPLE_AES_BLOCK_SIZE 16

typedef struct
{
	guchar iv[PURPLE_AES_BLOCK_SIZE];
	guchar key[32];
	guint key_size;
	gboolean failure;
} AESContext;

typedef gboolean (*purple_aes_crypt_func)(
	const guchar *input, guchar *output, size_t len,
	guchar iv[PURPLE_AES_BLOCK_SIZE], guchar key[32], guint key_size);

static void
purple_aes_init(PurpleCipherContext *context, void *extra)
{
	AESContext *ctx_data;

	ctx_data = g_new0(AESContext, 1);
	purple_cipher_context_set_data(context, ctx_data);

	purple_cipher_context_reset(context, extra);
}

static void
purple_aes_uninit(PurpleCipherContext *context)
{
	AESContext *ctx_data;

	purple_cipher_context_reset(context, NULL);

	ctx_data = purple_cipher_context_get_data(context);
	g_free(ctx_data);
	purple_cipher_context_set_data(context, NULL);
}

static void
purple_aes_reset(PurpleCipherContext *context, void *extra)
{
	AESContext *ctx_data = purple_cipher_context_get_data(context);

	g_return_if_fail(ctx_data != NULL);

	memset(ctx_data->iv, 0, sizeof(ctx_data->iv));
	memset(ctx_data->key, 0, sizeof(ctx_data->key));
	ctx_data->key_size = 32; /* 256bit */
	ctx_data->failure = FALSE;
}

static void
purple_aes_set_option(PurpleCipherContext *context, const gchar *name,
	void *value)
{
	AESContext *ctx_data = purple_cipher_context_get_data(context);

	purple_debug_error("cipher-aes", "set_option not supported\n");
	ctx_data->failure = TRUE;
}

static void
purple_aes_set_iv(PurpleCipherContext *context, guchar *iv, size_t len)
{
	AESContext *ctx_data = purple_cipher_context_get_data(context);

	if ((len > 0 && iv == NULL) ||
		(len != 0 && len != sizeof(ctx_data->iv))) {
		purple_debug_error("cipher-aes", "invalid IV length\n");
		ctx_data->failure = TRUE;
		return;
	}

	if (len == 0)
		memset(ctx_data->iv, 0, sizeof(ctx_data->iv));
	else
		memcpy(ctx_data->iv, iv, len);
}

static void
purple_aes_set_key(PurpleCipherContext *context, const guchar *key, size_t len)
{
	AESContext *ctx_data = purple_cipher_context_get_data(context);

	if ((len > 0 && key == NULL) ||
		(len != 0 && len != 16 && len != 24 && len != 32)) {
		purple_debug_error("cipher-aes", "invalid key length\n");
		ctx_data->failure = TRUE;
		return;
	}

	ctx_data->key_size = len;
	memset(ctx_data->key, 0, sizeof(ctx_data->key));
	if (len > 0)
		memcpy(ctx_data->key, key, len);
}

static guchar *
purple_aes_pad_pkcs7(const guchar input[], size_t in_len, size_t *out_len)
{
	int padding_len, total_len;
	guchar *padded;

	g_return_val_if_fail(input != NULL, NULL);
	g_return_val_if_fail(out_len != NULL, NULL);

	padding_len = PURPLE_AES_BLOCK_SIZE - (in_len % PURPLE_AES_BLOCK_SIZE);
	total_len = in_len + padding_len;
	g_assert((total_len % PURPLE_AES_BLOCK_SIZE) == 0);

	padded = g_new(guchar, total_len);
	*out_len = total_len;

	memcpy(padded, input, in_len);
	memset(padded + in_len, padding_len, padding_len);

	return padded;
}

static ssize_t
purple_aes_unpad_pkcs7(guchar input[], size_t in_len)
{
	guchar padding_len, i;
	size_t out_len;

	g_return_val_if_fail(input != NULL, -1);
	g_return_val_if_fail(in_len > 0, -1);

	padding_len = input[in_len - 1];
	if (padding_len == 0 || padding_len > PURPLE_AES_BLOCK_SIZE ||
		padding_len > in_len) {
		purple_debug_warning("cipher-aes",
			"Invalid padding length: %d (total %" G_GSIZE_FORMAT ") - "
			"most probably, the key was invalid\n",
			padding_len, in_len);
		return -1;
	}

	out_len = in_len - padding_len;
	for (i = 0; i < padding_len; i++) {
		if (input[out_len + i] != padding_len) {
			purple_debug_warning("cipher-aes",
				"Padding doesn't match at pos %d (found %02x, "
				"expected %02x) - "
				"most probably, the key was invalid\n",
				i, input[out_len + i], padding_len);
			return -1;
		}
	}

	memset(input + out_len, 0, padding_len);
	return out_len;
}

#ifdef PURPLE_AES_USE_GNUTLS

static gnutls_cipher_hd_t
purple_aes_crypt_gnutls_init(guchar iv[PURPLE_AES_BLOCK_SIZE], guchar key[32],
	guint key_size)
{
	gnutls_cipher_hd_t handle;
	gnutls_cipher_algorithm_t algorithm;
	gnutls_datum_t key_info, iv_info;
	int ret;

	if (key_size == 16)
		algorithm = GNUTLS_CIPHER_AES_128_CBC;
	else if (key_size == 24)
		algorithm = GNUTLS_CIPHER_AES_192_CBC;
	else if (key_size == 32)
		algorithm = GNUTLS_CIPHER_AES_256_CBC;
	else
		g_return_val_if_reached(NULL);

	key_info.data = key;
	key_info.size = key_size;

	iv_info.data = iv;
	iv_info.size = PURPLE_AES_BLOCK_SIZE;

	ret = gnutls_cipher_init(&handle, algorithm, &key_info, &iv_info);
	if (ret != 0) {
		purple_debug_error("cipher-aes",
			"gnutls_cipher_init failed: %d\n", ret);
		return NULL;
	}

	return handle;
}

static gboolean
purple_aes_encrypt_gnutls(const guchar *input, guchar *output, size_t len,
	guchar iv[PURPLE_AES_BLOCK_SIZE], guchar key[32], guint key_size)
{
	gnutls_cipher_hd_t handle;
	int ret;

	handle = purple_aes_crypt_gnutls_init(iv, key, key_size);
	if (handle == NULL)
		return FALSE;

	ret = gnutls_cipher_encrypt2(handle, (void *)input, len, output, len);
	gnutls_cipher_deinit(handle);

	if (ret != 0) {
		purple_debug_error("cipher-aes",
			"gnutls_cipher_encrypt2 failed: %d\n", ret);
		return FALSE;
	}

	return TRUE;
}

static gboolean
purple_aes_decrypt_gnutls(const guchar *input, guchar *output, size_t len,
	guchar iv[PURPLE_AES_BLOCK_SIZE], guchar key[32], guint key_size)
{
	gnutls_cipher_hd_t handle;
	int ret;

	handle = purple_aes_crypt_gnutls_init(iv, key, key_size);
	if (handle == NULL)
		return FALSE;

	ret = gnutls_cipher_decrypt2(handle, input, len, output, len);
	gnutls_cipher_deinit(handle);

	if (ret != 0) {
		purple_debug_error("cipher-aes",
			"gnutls_cipher_decrypt2 failed: %d\n", ret);
		return FALSE;
	}

	return TRUE;
}

#elif defined(PURPLE_AES_USE_NSS)

typedef struct
{
	PK11SlotInfo *slot;
	PK11SymKey *sym_key;
	SECItem *sec_param;
	PK11Context *enc_context;
} purple_aes_encrypt_nss_context;

static void
purple_aes_encrypt_nss_context_cleanup(purple_aes_encrypt_nss_context *ctx)
{
	g_return_if_fail(ctx != NULL);

	if (ctx->enc_context != NULL)
		PK11_DestroyContext(ctx->enc_context, TRUE);
	if (ctx->sec_param != NULL)
		SECITEM_FreeItem(ctx->sec_param, TRUE);
	if (ctx->sym_key != NULL)
		PK11_FreeSymKey(ctx->sym_key);
	if (ctx->slot != NULL)
		PK11_FreeSlot(ctx->slot);

	memset(ctx, 0, sizeof(purple_aes_encrypt_nss_context));
}

static gboolean
purple_aes_crypt_nss(const guchar *input, guchar *output, size_t len,
	guchar iv[PURPLE_AES_BLOCK_SIZE], guchar key[32], guint key_size,
	CK_ATTRIBUTE_TYPE operation)
{
	purple_aes_encrypt_nss_context ctx;
	CK_MECHANISM_TYPE cipher_mech = CKM_AES_CBC;
	SECItem key_item, iv_item;
	SECStatus ret;
	int outlen = 0;
	unsigned int outlen_tmp = 0;

	memset(&ctx, 0, sizeof(purple_aes_encrypt_nss_context));

	if (NSS_NoDB_Init(NULL) != SECSuccess) {
		purple_debug_error("cipher-aes",
			"NSS_NoDB_Init failed: %d\n", PR_GetError());
		return FALSE;
	}

	ctx.slot = PK11_GetBestSlot(cipher_mech, NULL);
	if (ctx.slot == NULL) {
		purple_debug_error("cipher-aes",
			"PK11_GetBestSlot failed: %d\n", PR_GetError());
		return FALSE;
	}

	key_item.type = siBuffer;
	key_item.data = key;
	key_item.len = key_size;
	ctx.sym_key = PK11_ImportSymKey(ctx.slot, cipher_mech,
		PK11_OriginUnwrap, CKA_ENCRYPT, &key_item, NULL);
	if (ctx.sym_key == NULL) {
		purple_debug_error("cipher-aes",
			"PK11_ImportSymKey failed: %d\n", PR_GetError());
		purple_aes_encrypt_nss_context_cleanup(&ctx);
		return FALSE;
	}

	iv_item.type = siBuffer;
	iv_item.data = iv;
	iv_item.len = PURPLE_AES_BLOCK_SIZE;
	ctx.sec_param = PK11_ParamFromIV(cipher_mech, &iv_item);
	if (ctx.sec_param == NULL) {
		purple_debug_error("cipher-aes",
			"PK11_ParamFromIV failed: %d\n", PR_GetError());
		purple_aes_encrypt_nss_context_cleanup(&ctx);
		return FALSE;
	}

	ctx.enc_context = PK11_CreateContextBySymKey(cipher_mech, operation,
		ctx.sym_key, ctx.sec_param);
	if (ctx.enc_context == NULL) {
		purple_debug_error("cipher-aes",
			"PK11_CreateContextBySymKey failed: %d\n",
				PR_GetError());
		purple_aes_encrypt_nss_context_cleanup(&ctx);
		return FALSE;
	}

	ret = PK11_CipherOp(ctx.enc_context, output, &outlen, len, input, len);
	if (ret != SECSuccess) {
		purple_debug_error("cipher-aes",
			"PK11_CipherOp failed: %d\n", PR_GetError());
		purple_aes_encrypt_nss_context_cleanup(&ctx);
		return FALSE;
	}

	ret = PK11_DigestFinal(ctx.enc_context, output + outlen, &outlen_tmp,
		len - outlen);
	if (ret != SECSuccess) {
		purple_debug_error("cipher-aes",
			"PK11_DigestFinal failed: %d\n", PR_GetError());
		purple_aes_encrypt_nss_context_cleanup(&ctx);
		return FALSE;
	}

	purple_aes_encrypt_nss_context_cleanup(&ctx);

	outlen += outlen_tmp;
	if (outlen != len) {
		purple_debug_error("cipher-aes",
			"resulting length doesn't match: %d (expected: %d)\n",
			outlen, len);
		return FALSE;
	}

	return TRUE;
}

static gboolean
purple_aes_encrypt_nss(const guchar *input, guchar *output, size_t len,
	guchar iv[PURPLE_AES_BLOCK_SIZE], guchar key[32], guint key_size)
{
	return purple_aes_crypt_nss(input, output, len, iv, key, key_size,
		CKA_ENCRYPT);
}

static gboolean
purple_aes_decrypt_nss(const guchar *input, guchar *output, size_t len,
	guchar iv[PURPLE_AES_BLOCK_SIZE], guchar key[32], guint key_size)
{
	return purple_aes_crypt_nss(input, output, len, iv, key, key_size,
		CKA_DECRYPT);
}

#endif /* PURPLE_AES_USE_NSS */

static ssize_t
purple_aes_encrypt(PurpleCipherContext *context, const guchar input[],
	size_t in_len, guchar output[], size_t out_size)
{
	AESContext *ctx_data = purple_cipher_context_get_data(context);
	purple_aes_crypt_func encrypt_func;
	guchar *input_padded;
	size_t out_len = 0;
	gboolean succ;

	if (ctx_data->failure)
		return -1;

	input_padded = purple_aes_pad_pkcs7(input, in_len, &out_len);

	if (out_len > out_size) {
		purple_debug_error("cipher-aes", "Output buffer too small\n");
		memset(input_padded, 0, out_len);
		g_free(input_padded);
		return -1;
	}

#if defined(PURPLE_AES_USE_GNUTLS)
	encrypt_func = purple_aes_encrypt_gnutls;
#elif defined(PURPLE_AES_USE_NSS)
	encrypt_func = purple_aes_encrypt_nss;
#else
#  error "No matching encrypt_func"
#endif

	succ = encrypt_func(input_padded, output, out_len, ctx_data->iv,
		ctx_data->key, ctx_data->key_size);

	memset(input_padded, 0, out_len);
	g_free(input_padded);

	if (!succ) {
		memset(output, 0, out_len);
		return -1;
	}

	return out_len;
}

static ssize_t
purple_aes_decrypt(PurpleCipherContext *context, const guchar input[],
	size_t in_len, guchar output[], size_t out_size)
{
	AESContext *ctx_data = purple_cipher_context_get_data(context);
	purple_aes_crypt_func decrypt_func;
	gboolean succ;
	ssize_t out_len;

	if (ctx_data->failure)
		return -1;

	if (in_len > out_size) {
		purple_debug_error("cipher-aes", "Output buffer too small\n");
		return -1;
	}

	if ((in_len % PURPLE_AES_BLOCK_SIZE) != 0 || in_len == 0) {
		purple_debug_error("cipher-aes", "Malformed data\n");
		return -1;
	}

#if defined(PURPLE_AES_USE_GNUTLS)
	decrypt_func = purple_aes_decrypt_gnutls;
#elif defined(PURPLE_AES_USE_NSS)
	decrypt_func = purple_aes_decrypt_nss;
#else
#  error "No matching encrypt_func"
#endif

	succ = decrypt_func(input, output, in_len, ctx_data->iv, ctx_data->key,
		ctx_data->key_size);

	if (!succ) {
		memset(output, 0, in_len);
		return -1;
	}

	out_len = purple_aes_unpad_pkcs7(output, in_len);
	if (out_len < 0) {
		memset(output, 0, in_len);
		return -1;
	}

	return out_len;
}

static size_t
purple_aes_get_key_size(PurpleCipherContext *context)
{
	AESContext *ctx_data = purple_cipher_context_get_data(context);

	return ctx_data->key_size;
}

static void
purple_aes_set_batch_mode(PurpleCipherContext *context,
	PurpleCipherBatchMode mode)
{
	AESContext *ctx_data = purple_cipher_context_get_data(context);

	if (mode == PURPLE_CIPHER_BATCH_MODE_CBC)
		return;

	purple_debug_error("cipher-aes", "unsupported batch mode\n");
	ctx_data->failure = TRUE;
}

static PurpleCipherBatchMode
purple_aes_get_batch_mode(PurpleCipherContext *context)
{
	return PURPLE_CIPHER_BATCH_MODE_CBC;
}

static size_t
purple_aes_get_block_size(PurpleCipherContext *context)
{
	return PURPLE_AES_BLOCK_SIZE;
}

static PurpleCipherOps AESOps = {
	purple_aes_set_option,     /* set_option */
	NULL,                      /* get_option */
	purple_aes_init,           /* init */
	purple_aes_reset,          /* reset */
	NULL,                      /* reset_state */
	purple_aes_uninit,         /* uninit */
	purple_aes_set_iv,         /* set_iv */
	NULL,                      /* append */
	NULL,                      /* digest */
	NULL,                      /* get_digest_size */
	purple_aes_encrypt,        /* encrypt */
	purple_aes_decrypt,        /* decrypt */
	NULL,                      /* set_salt */
	NULL,                      /* get_salt_size */
	purple_aes_set_key,        /* set_key */
	purple_aes_get_key_size,   /* get_key_size */
	purple_aes_set_batch_mode, /* set_batch_mode */
	purple_aes_get_batch_mode, /* get_batch_mode */
	purple_aes_get_block_size, /* get_block_size */
	NULL, NULL, NULL, NULL     /* reserved */
};

PurpleCipherOps *
purple_aes_cipher_get_ops(void) {
	return &AESOps;
}

mercurial