--- a/libpurple/ciphers/aescipher.c Thu Jun 15 10:48:26 2017 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,725 +0,0 @@ -/* - * 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 <twasilczyk@pidgin.im> - */ - -#include "internal.h" -#include "glibcompat.h" - -#include "aescipher.h" -#include "debug.h" -#include "enums.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 -# warning "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; - PurpleCipherBatchMode batch_mode; -} PurpleAESCipherPrivate; - -/****************************************************************************** - * Enums - *****************************************************************************/ -enum { - PROP_NONE, - PROP_BATCH_MODE, - PROP_IV, - PROP_KEY, - PROP_LAST, -}; - -/******************************************************************************* - * Globals - ******************************************************************************/ -static GParamSpec *properties[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, - PurpleCipherBatchMode batch_mode); - -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_by_pspec(G_OBJECT(cipher), properties[PROP_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_by_pspec(G_OBJECT(cipher), properties[PROP_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) -{ - 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_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, - PurpleCipherBatchMode batch_mode) -{ - gnutls_cipher_hd_t handle; - int ret; - - /* We have to simulate ECB mode, which is not supported by GnuTLS. */ - if (batch_mode == PURPLE_CIPHER_BATCH_MODE_ECB) { - size_t i; - for (i = 0; i < len / PURPLE_AES_BLOCK_SIZE; i++) { - int offset = i * PURPLE_AES_BLOCK_SIZE; - guchar iv_local[PURPLE_AES_BLOCK_SIZE]; - gboolean succ; - - memcpy(iv_local, iv, sizeof(iv_local)); - succ = purple_aes_cipher_gnutls_encrypt( - input + offset, output + offset, - PURPLE_AES_BLOCK_SIZE, - iv_local, key, key_size, - PURPLE_CIPHER_BATCH_MODE_CBC); - if (!succ) - return FALSE; - } - return TRUE; - } - - 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, - PurpleCipherBatchMode batch_mode) -{ - gnutls_cipher_hd_t handle; - int ret; - - /* We have to simulate ECB mode, which is not supported by GnuTLS. */ - if (batch_mode == PURPLE_CIPHER_BATCH_MODE_ECB) { - size_t i; - for (i = 0; i < len / PURPLE_AES_BLOCK_SIZE; i++) { - int offset = i * PURPLE_AES_BLOCK_SIZE; - guchar iv_local[PURPLE_AES_BLOCK_SIZE]; - gboolean succ; - - memcpy(iv_local, iv, sizeof(iv_local)); - succ = purple_aes_cipher_gnutls_decrypt( - input + offset, output + offset, - PURPLE_AES_BLOCK_SIZE, - iv_local, key, key_size, - PURPLE_CIPHER_BATCH_MODE_CBC); - if (!succ) - return FALSE; - } - return TRUE; - } - - 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; -} - -#elif defined(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, CK_MECHANISM_TYPE cipher_mech) -{ - PurpleAESCipherNSSContext context; - 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, - (guchar *)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 != (int)len) { - purple_debug_error("cipher-aes", - "resulting length doesn't match: %d (expected: %" - G_GSIZE_FORMAT ")\n", outlen, len); - return FALSE; - } - - return TRUE; -} - -static CK_MECHANISM_TYPE -purple_aes_cipher_nss_batch_mode(PurpleCipherBatchMode batch_mode) -{ - switch (batch_mode) { - case PURPLE_CIPHER_BATCH_MODE_CBC: - return CKM_AES_CBC; - case PURPLE_CIPHER_BATCH_MODE_ECB: - return CKM_AES_ECB; - } - - return CKM_AES_CBC; -} - -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, - PurpleCipherBatchMode batch_mode) -{ - return purple_aes_cipher_nss_crypt(input, output, len, iv, key, key_size, - CKA_ENCRYPT, purple_aes_cipher_nss_batch_mode(batch_mode)); -} - -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, - PurpleCipherBatchMode batch_mode) -{ - return purple_aes_cipher_nss_crypt(input, output, len, iv, key, key_size, - CKA_DECRYPT, purple_aes_cipher_nss_batch_mode(batch_mode)); -} - -#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 (%" - G_GSIZE_FORMAT " > %" G_GSIZE_FORMAT ")", - out_len, out_size); - 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 - purple_debug_error("cipher-aes", "No matching encrypt_func\n"); - return -1; -#endif - - succ = encrypt_func(input_padded, output, out_len, priv->iv, - priv->key, priv->key_size, priv->batch_mode); - - 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 - purple_debug_error("cipher-aes", "No matching decrypt_func\n"); - return -1; -#endif - - succ = decrypt_func(input, output, in_len, priv->iv, priv->key, - priv->key_size, priv->batch_mode); - - 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 && - mode != PURPLE_CIPHER_BATCH_MODE_ECB) - { - purple_debug_error("cipher-aes", "unsupported batch mode\n"); - priv->failure = TRUE; - } - - priv->batch_mode = mode; - - g_object_notify_by_pspec(G_OBJECT(cipher), properties[PROP_BATCH_MODE]); -} - -static PurpleCipherBatchMode -purple_aes_cipher_get_batch_mode(PurpleCipher *cipher) -{ - PurpleAESCipherPrivate *priv = PURPLE_AES_CIPHER_GET_PRIVATE(cipher); - - return priv->batch_mode; -} - -static size_t -purple_aes_cipher_get_block_size(PurpleCipher *cipher) -{ - return PURPLE_AES_BLOCK_SIZE; -} - -/****************************************************************************** - * 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); - - 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; - - g_type_class_add_private(klass, sizeof(PurpleAESCipherPrivate)); - - properties[PROP_BATCH_MODE] = g_param_spec_enum("batch-mode", - "batch-mode", "batch-mode", PURPLE_TYPE_CIPHER_BATCH_MODE, - PURPLE_CIPHER_BATCH_MODE_CBC, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); - - properties[PROP_IV] = g_param_spec_string("iv", "iv", "iv", NULL, - G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS); - - properties[PROP_KEY] = g_param_spec_string("key", "key", "key", NULL, - G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS); - - g_object_class_install_properties(obj_class, PROP_LAST, properties); -} - -static void -purple_aes_cipher_init(PurpleCipher *cipher) { - purple_cipher_reset(cipher); -} - -/****************************************************************************** - * API - *****************************************************************************/ -GType -purple_aes_cipher_get_type(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); -}