--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/ciphers/aescipher.c Sun Jun 16 03:46:10 2013 +0530 @@ -0,0 +1,658 @@ +/* + * 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 "aescipher.h" +#include "debug.h" + +#include <string.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 + +/****************************************************************************** + * Structs + *****************************************************************************/ +#define PURPLE_AES_CIPHER_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_AES_CIPHER, PurpleAESCipherPrivate)) + +typedef struct { + guchar iv[PURPLE_AES_BLOCK_SIZE]; + guchar key[32]; + guint key_size; + gboolean failure; +} PurpleAESCipherPrivate; + +/****************************************************************************** + * Enums + *****************************************************************************/ +enum { + PROP_NONE, + PROP_BATCH_MODE, + PROP_IV, + PROP_KEY, + PROP_LAST, +}; + +/****************************************************************************** + * Cipher Stuff + *****************************************************************************/ + +typedef gboolean (*purple_aes_cipher_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_cipher_reset(PurpleCipher *cipher) +{ + PurpleAESCipherPrivate *priv = PURPLE_AES_CIPHER_GET_PRIVATE(cipher); + + g_return_if_fail(priv != NULL); + + memset(priv->iv, 0, sizeof(priv->iv)); + memset(priv->key, 0, sizeof(priv->key)); + priv->key_size = 32; /* 256bit */ + priv->failure = FALSE; +} + +static void +purple_aes_cipher_set_iv(PurpleCipher *cipher, guchar *iv, size_t len) +{ + PurpleAESCipherPrivate *priv = PURPLE_AES_CIPHER_GET_PRIVATE(cipher); + + if ((len > 0 && iv == NULL) || + (len != 0 && len != sizeof(priv->iv))) { + purple_debug_error("cipher-aes", "invalid IV length\n"); + priv->failure = TRUE; + return; + } + + if (len == 0) + memset(priv->iv, 0, sizeof(priv->iv)); + else + memcpy(priv->iv, iv, len); + + g_object_notify(G_OBJECT(cipher), "iv"); +} + +static void +purple_aes_cipher_set_key(PurpleCipher *cipher, const guchar *key, size_t len) +{ + PurpleAESCipherPrivate *priv = PURPLE_AES_CIPHER_GET_PRIVATE(cipher); + + if ((len > 0 && key == NULL) || + (len != 0 && len != 16 && len != 24 && len != 32)) { + purple_debug_error("cipher-aes", "invalid key length\n"); + priv->failure = TRUE; + return; + } + + priv->key_size = len; + memset(priv->key, 0, sizeof(priv->key)); + if (len > 0) + memcpy(priv->key, key, len); + + g_object_notify(G_OBJECT(cipher), "key"); +} + +static guchar * +purple_aes_cipher_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_cipher_unpad_pkcs7(guchar input[], size_t in_len) +{ + int 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 %lu) - " + "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_cipher_gnutls_crypt_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_cipher_gnutls_encrypt(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_cipher_gnutls_crypt_init(iv, key, key_size); + if (handle == NULL) + return FALSE; + + ret = gnutls_cipher_encrypt2(handle, (guchar *) 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_cipher_gnutls_decrypt(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_cipher_gnutls_crypt_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; +} + +#endif /* PURPLE_AES_USE_GNUTLS */ + +#ifdef PURPLE_AES_USE_NSS + +typedef struct { + PK11SlotInfo *slot; + PK11SymKey *sym_key; + SECItem *sec_param; + PK11Context *enc_context; +} PurpleAESCipherNSSContext; + +static void +purple_aes_cipher_nss_cleanup(PurpleAESCipherNSSContext *context) +{ + g_return_if_fail(context != NULL); + + if (context->enc_context != NULL) + PK11_DestroyContext(context->enc_context, TRUE); + if (context->sec_param != NULL) + SECITEM_FreeItem(context->sec_param, TRUE); + if (context->sym_key != NULL) + PK11_FreeSymKey(context->sym_key); + if (context->slot != NULL) + PK11_FreeSlot(context->slot); + + memset(context, 0, sizeof(PurpleAESCipherNSSContext)); +} + +static gboolean +purple_aes_cipher_nss_crypt(const guchar *input, guchar *output, size_t len, + guchar iv[PURPLE_AES_BLOCK_SIZE], guchar key[32], guint key_size, + CK_ATTRIBUTE_TYPE operation) +{ + PurpleAESCipherNSSContext context; + CK_MECHANISM_TYPE cipher_mech = CKM_AES_CBC; + SECItem key_item, iv_item; + SECStatus ret; + int outlen = 0; + unsigned int outlen_tmp = 0; + + memset(&context, 0, sizeof(PurpleAESCipherNSSContext)); + + if (NSS_NoDB_Init(NULL) != SECSuccess) { + purple_debug_error("cipher-aes", + "NSS_NoDB_Init failed: %d\n", PR_GetError()); + return FALSE; + } + + context.slot = PK11_GetBestSlot(cipher_mech, NULL); + if (context.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; + context.sym_key = PK11_ImportSymKey(context.slot, cipher_mech, + PK11_OriginUnwrap, CKA_ENCRYPT, &key_item, NULL); + if (context.sym_key == NULL) { + purple_debug_error("cipher-aes", + "PK11_ImportSymKey failed: %d\n", PR_GetError()); + purple_aes_cipher_nss_cleanup(&context); + return FALSE; + } + + iv_item.type = siBuffer; + iv_item.data = iv; + iv_item.len = PURPLE_AES_BLOCK_SIZE; + context.sec_param = PK11_ParamFromIV(cipher_mech, &iv_item); + if (context.sec_param == NULL) { + purple_debug_error("cipher-aes", + "PK11_ParamFromIV failed: %d\n", PR_GetError()); + purple_aes_cipher_nss_cleanup(&context); + return FALSE; + } + + context.enc_context = PK11_CreateContextBySymKey(cipher_mech, operation, + context.sym_key, context.sec_param); + if (context.enc_context == NULL) { + purple_debug_error("cipher-aes", + "PK11_CreateContextBySymKey failed: %d\n", + PR_GetError()); + purple_aes_cipher_nss_cleanup(&context); + return FALSE; + } + + ret = PK11_CipherOp(context.enc_context, output, &outlen, len, input, len); + if (ret != SECSuccess) { + purple_debug_error("cipher-aes", + "PK11_CipherOp failed: %d\n", PR_GetError()); + purple_aes_cipher_nss_cleanup(&context); + return FALSE; + } + + ret = PK11_DigestFinal(context.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_cipher_nss_cleanup(&context); + return FALSE; + } + + purple_aes_cipher_nss_cleanup(&context); + + outlen += outlen_tmp; + if (outlen != len) { + purple_debug_error("cipher-aes", + "resulting length doesn't match: %d (expected: %lu)\n", + outlen, len); + return FALSE; + } + + return TRUE; +} + +static gboolean +purple_aes_cipher_nss_encrypt(const guchar *input, guchar *output, size_t len, + guchar iv[PURPLE_AES_BLOCK_SIZE], guchar key[32], guint key_size) +{ + return purple_aes_cipher_nss_crypt(input, output, len, iv, key, key_size, + CKA_ENCRYPT); +} + +static gboolean +purple_aes_cipher_nss_decrypt(const guchar *input, guchar *output, size_t len, + guchar iv[PURPLE_AES_BLOCK_SIZE], guchar key[32], guint key_size) +{ + return purple_aes_cipher_nss_crypt(input, output, len, iv, key, key_size, + CKA_DECRYPT); +} + +#endif /* PURPLE_AES_USE_NSS */ + +static ssize_t +purple_aes_cipher_encrypt(PurpleCipher *cipher, const guchar input[], + size_t in_len, guchar output[], size_t out_size) +{ + PurpleAESCipherPrivate *priv = PURPLE_AES_CIPHER_GET_PRIVATE(cipher); + purple_aes_cipher_crypt_func encrypt_func; + guchar *input_padded; + size_t out_len = 0; + gboolean succ; + + if (priv->failure) + return -1; + + input_padded = purple_aes_cipher_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_cipher_gnutls_encrypt; +#elif defined(PURPLE_AES_USE_NSS) + encrypt_func = purple_aes_cipher_nss_encrypt; +#else +# error "No matching encrypt_func" +#endif + + succ = encrypt_func(input_padded, output, out_len, priv->iv, + priv->key, priv->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_cipher_decrypt(PurpleCipher *cipher, const guchar input[], + size_t in_len, guchar output[], size_t out_size) +{ + PurpleAESCipherPrivate *priv = PURPLE_AES_CIPHER_GET_PRIVATE(cipher); + purple_aes_cipher_crypt_func decrypt_func; + gboolean succ; + ssize_t out_len; + + if (priv->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_cipher_gnutls_decrypt; +#elif defined(PURPLE_AES_USE_NSS) + decrypt_func = purple_aes_cipher_nss_decrypt; +#else +# error "No matching encrypt_func" +#endif + + succ = decrypt_func(input, output, in_len, priv->iv, priv->key, + priv->key_size); + + if (!succ) { + memset(output, 0, in_len); + return -1; + } + + out_len = purple_aes_cipher_unpad_pkcs7(output, in_len); + if (out_len < 0) { + memset(output, 0, in_len); + return -1; + } + + return out_len; +} + +static size_t +purple_aes_cipher_get_key_size(PurpleCipher *cipher) +{ + PurpleAESCipherPrivate *priv = PURPLE_AES_CIPHER_GET_PRIVATE(cipher); + + return priv->key_size; +} + +static void +purple_aes_cipher_set_batch_mode(PurpleCipher *cipher, + PurpleCipherBatchMode mode) +{ + PurpleAESCipherPrivate *priv = PURPLE_AES_CIPHER_GET_PRIVATE(cipher); + + if (mode == PURPLE_CIPHER_BATCH_MODE_CBC) { + g_object_notify(G_OBJECT(cipher), "batch_mode"); + return; + } + + purple_debug_error("cipher-aes", "unsupported batch mode\n"); + priv->failure = TRUE; +} + +static PurpleCipherBatchMode +purple_aes_cipher_get_batch_mode(PurpleCipher *cipher) +{ + return PURPLE_CIPHER_BATCH_MODE_CBC; +} + +static size_t +purple_aes_cipher_get_block_size(PurpleCipher *cipher) +{ + return PURPLE_AES_BLOCK_SIZE; +} + +static const gchar* +purple_aes_cipher_get_name(PurpleCipher *cipher) +{ + return "aes"; +} + +/****************************************************************************** + * Object Stuff + *****************************************************************************/ +static void +purple_aes_cipher_get_property(GObject *obj, guint param_id, GValue *value, + GParamSpec *pspec) +{ + PurpleCipher *cipher = PURPLE_CIPHER(obj); + + switch(param_id) { + case PROP_BATCH_MODE: + g_value_set_enum(value, + purple_cipher_get_batch_mode(cipher)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec); + break; + } +} + +static void +purple_aes_cipher_set_property(GObject *obj, guint param_id, + const GValue *value, GParamSpec *pspec) +{ + PurpleCipher *cipher = PURPLE_CIPHER(obj); + + switch(param_id) { + case PROP_BATCH_MODE: + purple_cipher_set_batch_mode(cipher, + g_value_get_enum(value)); + break; + case PROP_IV: + { + guchar *iv = (guchar *)g_value_get_string(value); + purple_cipher_set_iv(cipher, iv, strlen((gchar*)iv)); + } + break; + case PROP_KEY: + purple_cipher_set_key(cipher, (guchar *)g_value_get_string(value), + purple_aes_cipher_get_key_size(cipher)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec); + break; + } +} + +static void +purple_aes_cipher_class_init(PurpleAESCipherClass *klass) { + GObjectClass *obj_class = G_OBJECT_CLASS(klass); + PurpleCipherClass *cipher_class = PURPLE_CIPHER_CLASS(klass); + GParamSpec *pspec; + + obj_class->get_property = purple_aes_cipher_get_property; + obj_class->set_property = purple_aes_cipher_set_property; + + cipher_class->reset = purple_aes_cipher_reset; + cipher_class->set_iv = purple_aes_cipher_set_iv; + cipher_class->encrypt = purple_aes_cipher_encrypt; + cipher_class->decrypt = purple_aes_cipher_decrypt; + cipher_class->set_key = purple_aes_cipher_set_key; + cipher_class->get_key_size = purple_aes_cipher_get_key_size; + cipher_class->set_batch_mode = purple_aes_cipher_set_batch_mode; + cipher_class->get_batch_mode = purple_aes_cipher_get_batch_mode; + cipher_class->get_block_size = purple_aes_cipher_get_block_size; + cipher_class->get_name = purple_aes_cipher_get_name; + + pspec = g_param_spec_enum("batch_mode", "batch_mode", "batch_mode", + PURPLE_TYPE_CIPHER_BATCH_MODE, 0, + G_PARAM_READWRITE); + g_object_class_install_property(obj_class, PROP_BATCH_MODE, pspec); + + pspec = g_param_spec_string("iv", "iv", "iv", NULL, + G_PARAM_WRITABLE); + g_object_class_install_property(obj_class, PROP_IV, pspec); + + pspec = g_param_spec_string("key", "key", "key", NULL, + G_PARAM_WRITABLE); + g_object_class_install_property(obj_class, PROP_KEY, pspec); + + g_type_class_add_private(klass, sizeof(PurpleAESCipherPrivate)); +} + +static void +purple_aes_cipher_init(PurpleCipher *cipher) { + purple_cipher_reset(cipher); +} + +/****************************************************************************** + * API + *****************************************************************************/ +GType +purple_aes_cipher_get_gtype(void) { + static GType type = 0; + + if(type == 0) { + static const GTypeInfo info = { + sizeof(PurpleAESCipherClass), + NULL, + NULL, + (GClassInitFunc)purple_aes_cipher_class_init, + NULL, + NULL, + sizeof(PurpleAESCipher), + 0, + (GInstanceInitFunc)purple_aes_cipher_init, + NULL + }; + + type = g_type_register_static(PURPLE_TYPE_CIPHER, + "PurpleAESCipher", + &info, 0); + } + + return type; +} + +PurpleCipher * +purple_aes_cipher_new(void) { + return g_object_new(PURPLE_TYPE_AES_CIPHER, NULL); +}