pidgin/pidgin

Better fix for this

2016-12-12, Gary Kramlich
ee5f6e1f76bb
Better fix for this
/*
* 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 "pbkdf2cipher.h"
#include "hmaccipher.h"
#include "debug.h"
/* 1024bit */
#define PBKDF2_HASH_MAX_LEN 128
/******************************************************************************
* Structs
*****************************************************************************/
#define PURPLE_PBKDF2_CIPHER_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_PBKDF2_CIPHER, PurplePBKDF2CipherPrivate))
typedef struct {
PurpleHash *hash;
guint iter_count;
size_t out_len;
guchar *salt;
size_t salt_len;
guchar *passphrase;
size_t passphrase_len;
} PurplePBKDF2CipherPrivate;
/******************************************************************************
* Enums
*****************************************************************************/
enum {
PROP_NONE,
PROP_HASH,
PROP_ITER_COUNT,
PROP_OUT_LEN,
PROP_LAST,
};
/*******************************************************************************
* Globals
******************************************************************************/
static GObjectClass *parent_class = NULL;
static GParamSpec *properties[PROP_LAST];
/*******************************************************************************
* Helpers
******************************************************************************/
static void
purple_pbkdf2_cipher_set_hash(PurpleCipher *cipher,
PurpleHash *hash)
{
PurplePBKDF2CipherPrivate *priv = PURPLE_PBKDF2_CIPHER_GET_PRIVATE(cipher);
priv->hash = g_object_ref(G_OBJECT(hash));
g_object_notify_by_pspec(G_OBJECT(cipher), properties[PROP_HASH]);
}
/******************************************************************************
* Cipher Stuff
*****************************************************************************/
static void
purple_pbkdf2_cipher_reset(PurpleCipher *cipher)
{
PurplePBKDF2CipherPrivate *priv = PURPLE_PBKDF2_CIPHER_GET_PRIVATE(cipher);
g_return_if_fail(priv != NULL);
if(PURPLE_IS_HASH(priv->hash))
purple_hash_reset(priv->hash);
priv->iter_count = 1;
priv->out_len = 256;
purple_cipher_reset_state(cipher);
}
static void
purple_pbkdf2_cipher_reset_state(PurpleCipher *cipher)
{
PurplePBKDF2CipherPrivate *priv = PURPLE_PBKDF2_CIPHER_GET_PRIVATE(cipher);
g_return_if_fail(priv != NULL);
purple_cipher_set_salt(cipher, NULL, 0);
purple_cipher_set_key(cipher, NULL, 0);
}
static size_t
purple_pbkdf2_cipher_get_digest_size(PurpleCipher *cipher)
{
PurplePBKDF2CipherPrivate *priv = PURPLE_PBKDF2_CIPHER_GET_PRIVATE(cipher);
g_return_val_if_fail(priv != NULL, 0);
return priv->out_len;
}
static void
purple_pbkdf2_cipher_set_salt(PurpleCipher *cipher, const guchar *salt, size_t len)
{
PurplePBKDF2CipherPrivate *priv = PURPLE_PBKDF2_CIPHER_GET_PRIVATE(cipher);
g_return_if_fail(priv != NULL);
g_free(priv->salt);
priv->salt = NULL;
priv->salt_len = 0;
if (len == 0)
return;
g_return_if_fail(salt != NULL);
priv->salt = g_memdup(salt, len);
priv->salt_len = len;
}
static void
purple_pbkdf2_cipher_set_key(PurpleCipher *cipher, const guchar *key,
size_t len)
{
PurplePBKDF2CipherPrivate *priv = PURPLE_PBKDF2_CIPHER_GET_PRIVATE(cipher);
g_return_if_fail(priv != NULL);
if (priv->passphrase != NULL) {
memset(priv->passphrase, 0, priv->passphrase_len);
g_free(priv->passphrase);
priv->passphrase = NULL;
}
priv->passphrase_len = 0;
if (len == 0)
return;
g_return_if_fail(key != NULL);
priv->passphrase = g_memdup(key, len);
priv->passphrase_len = len;
}
/* inspired by gnutls 3.1.10, pbkdf2-sha1.c */
static gboolean
purple_pbkdf2_cipher_digest(PurpleCipher *cipher, guchar digest[], size_t len)
{
PurplePBKDF2CipherPrivate *priv = PURPLE_PBKDF2_CIPHER_GET_PRIVATE(cipher);
guchar halfkey[PBKDF2_HASH_MAX_LEN], halfkey_hash[PBKDF2_HASH_MAX_LEN];
guint halfkey_len, halfkey_count, halfkey_pad, halfkey_no;
guchar *salt_ext;
size_t salt_ext_len;
guint iter_no;
PurpleCipher *hash;
g_return_val_if_fail(priv != NULL, FALSE);
g_return_val_if_fail(digest != NULL, FALSE);
g_return_val_if_fail(len >= priv->out_len, FALSE);
g_return_val_if_fail(priv->hash != NULL, FALSE);
g_return_val_if_fail(priv->iter_count > 0, FALSE);
g_return_val_if_fail(priv->passphrase != NULL ||
priv->passphrase_len == 0, FALSE);
g_return_val_if_fail(priv->salt != NULL || priv->salt_len == 0,
FALSE);
g_return_val_if_fail(priv->out_len > 0, FALSE);
g_return_val_if_fail(priv->out_len < 0xFFFFFFFFU, FALSE);
salt_ext_len = priv->salt_len + 4;
hash = purple_hmac_cipher_new(priv->hash);
if (hash == NULL) {
purple_debug_error("pbkdf2", "Couldn't create new hmac "
"cipher\n");
return FALSE;
}
purple_cipher_set_key(hash, (const guchar*)priv->passphrase,
priv->passphrase_len);
halfkey_len = purple_cipher_get_digest_size(hash);
if (halfkey_len <= 0 || halfkey_len > PBKDF2_HASH_MAX_LEN) {
purple_debug_error("pbkdf2", "Unsupported hash function. "
"(digest size: %d)\n", halfkey_len);
return FALSE;
}
halfkey_count = ((priv->out_len - 1) / halfkey_len) + 1;
halfkey_pad = priv->out_len - (halfkey_count - 1) * halfkey_len;
salt_ext = g_new(guchar, salt_ext_len);
if (priv->salt_len > 0)
memcpy(salt_ext, priv->salt, priv->salt_len);
for (halfkey_no = 1; halfkey_no <= halfkey_count; halfkey_no++) {
memset(halfkey, 0, halfkey_len);
for (iter_no = 1; iter_no <= priv->iter_count; iter_no++) {
guint i;
purple_cipher_reset_state(hash);
if (iter_no == 1) {
salt_ext[salt_ext_len - 4] =
(halfkey_no & 0xff000000) >> 24;
salt_ext[salt_ext_len - 3] =
(halfkey_no & 0x00ff0000) >> 16;
salt_ext[salt_ext_len - 2] =
(halfkey_no & 0x0000ff00) >> 8;
salt_ext[salt_ext_len - 1] =
(halfkey_no & 0x000000ff) >> 0;
purple_cipher_append(hash, salt_ext,
salt_ext_len);
}
else
purple_cipher_append(hash, halfkey_hash,
halfkey_len);
if (!purple_cipher_digest(hash, halfkey_hash,
halfkey_len)) {
purple_debug_error("pbkdf2",
"Couldn't retrieve a digest\n");
g_free(salt_ext);
g_object_unref(hash);
return FALSE;
}
for (i = 0; i < halfkey_len; i++)
halfkey[i] ^= halfkey_hash[i];
}
memcpy(digest + (halfkey_no - 1) * halfkey_len, halfkey,
(halfkey_no == halfkey_count) ? halfkey_pad :
halfkey_len);
}
g_free(salt_ext);
g_object_unref(hash);
return TRUE;
}
/******************************************************************************
* Object Stuff
*****************************************************************************/
static void
purple_pbkdf2_cipher_get_property(GObject *obj, guint param_id, GValue *value,
GParamSpec *pspec)
{
PurplePBKDF2Cipher *cipher = PURPLE_PBKDF2_CIPHER(obj);
PurplePBKDF2CipherPrivate *priv = PURPLE_PBKDF2_CIPHER_GET_PRIVATE(cipher);
switch(param_id) {
case PROP_HASH:
g_value_set_object(value, purple_pbkdf2_cipher_get_hash(cipher));
break;
case PROP_ITER_COUNT:
g_value_set_uint(value, priv->iter_count);
break;
case PROP_OUT_LEN:
g_value_set_uint(value, priv->out_len);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
break;
}
}
static void
purple_pbkdf2_cipher_set_property(GObject *obj, guint param_id,
const GValue *value, GParamSpec *pspec)
{
PurpleCipher *cipher = PURPLE_CIPHER(obj);
PurplePBKDF2CipherPrivate *priv = PURPLE_PBKDF2_CIPHER_GET_PRIVATE(cipher);
switch(param_id) {
case PROP_HASH:
purple_pbkdf2_cipher_set_hash(cipher, g_value_get_object(value));
break;
case PROP_ITER_COUNT:
priv->iter_count = g_value_get_uint(value);
break;
case PROP_OUT_LEN:
priv->out_len = g_value_get_uint(value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
break;
}
}
static void
purple_pbkdf2_cipher_finalize(GObject *obj)
{
PurpleCipher *cipher = PURPLE_CIPHER(obj);
PurplePBKDF2CipherPrivate *priv = PURPLE_PBKDF2_CIPHER_GET_PRIVATE(cipher);
purple_pbkdf2_cipher_reset(cipher);
if (priv->hash != NULL)
g_object_unref(priv->hash);
parent_class->finalize(obj);
}
static void
purple_pbkdf2_cipher_class_init(PurplePBKDF2CipherClass *klass) {
GObjectClass *obj_class = G_OBJECT_CLASS(klass);
PurpleCipherClass *cipher_class = PURPLE_CIPHER_CLASS(klass);
parent_class = g_type_class_peek_parent(klass);
obj_class->finalize = purple_pbkdf2_cipher_finalize;
obj_class->get_property = purple_pbkdf2_cipher_get_property;
obj_class->set_property = purple_pbkdf2_cipher_set_property;
cipher_class->reset = purple_pbkdf2_cipher_reset;
cipher_class->reset_state = purple_pbkdf2_cipher_reset_state;
cipher_class->digest = purple_pbkdf2_cipher_digest;
cipher_class->get_digest_size = purple_pbkdf2_cipher_get_digest_size;
cipher_class->set_salt = purple_pbkdf2_cipher_set_salt;
cipher_class->set_key = purple_pbkdf2_cipher_set_key;
g_type_class_add_private(klass, sizeof(PurplePBKDF2CipherPrivate));
properties[PROP_HASH] = g_param_spec_object("hash", "hash", "hash",
PURPLE_TYPE_HASH,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS);
properties[PROP_ITER_COUNT] = g_param_spec_uint("iter-count", "iter-count",
"iter-count", 0,
G_MAXUINT, 0, G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS);
properties[PROP_OUT_LEN] = g_param_spec_uint("out-len", "out-len",
"out-len", 0,
G_MAXUINT, 0, G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS);
g_object_class_install_properties(obj_class, PROP_LAST, properties);
}
static void
purple_pbkdf2_cipher_init(PurpleCipher *cipher)
{
purple_cipher_reset(cipher);
}
/******************************************************************************
* API
*****************************************************************************/
GType
purple_pbkdf2_cipher_get_type(void) {
static GType type = 0;
if(type == 0) {
static const GTypeInfo info = {
sizeof(PurplePBKDF2CipherClass),
NULL,
NULL,
(GClassInitFunc)purple_pbkdf2_cipher_class_init,
NULL,
NULL,
sizeof(PurplePBKDF2Cipher),
0,
(GInstanceInitFunc)purple_pbkdf2_cipher_init,
NULL
};
type = g_type_register_static(PURPLE_TYPE_CIPHER,
"PurplePBKDF2Cipher",
&info, 0);
}
return type;
}
PurpleCipher *
purple_pbkdf2_cipher_new(PurpleHash *hash) {
g_return_val_if_fail(PURPLE_IS_HASH(hash), NULL);
return g_object_new(PURPLE_TYPE_PBKDF2_CIPHER,
"hash", hash,
NULL);
}
PurpleHash *
purple_pbkdf2_cipher_get_hash(const PurplePBKDF2Cipher *cipher) {
PurplePBKDF2CipherPrivate *priv = NULL;
g_return_val_if_fail(PURPLE_IS_PBKDF2_CIPHER(cipher), NULL);
priv = PURPLE_PBKDF2_CIPHER_GET_PRIVATE(cipher);
if(priv && priv->hash)
return priv->hash;
return NULL;
}