diff options
author | Tomasz Wasilczyk <tomkiewicz@cpw.pidgin.im> | 2013-05-07 18:11:11 +0200 |
---|---|---|
committer | Tomasz Wasilczyk <tomkiewicz@cpw.pidgin.im> | 2013-05-07 18:11:11 +0200 |
commit | c525f9f4c5b8cc004d06d4cfd299ee86fc9239b0 (patch) | |
tree | 684af9767ae82a054e35766e4f406e579118aadc | |
parent | 60372ec98697473b607859a6d0d1d1bd335d660e (diff) | |
download | pidgin-c525f9f4c5b8cc004d06d4cfd299ee86fc9239b0.tar.gz |
PBKDF2 support
-rw-r--r-- | libpurple/cipher.c | 1 | ||||
-rw-r--r-- | libpurple/ciphers/Makefile.am | 2 | ||||
-rw-r--r-- | libpurple/ciphers/ciphers.h | 3 | ||||
-rw-r--r-- | libpurple/ciphers/pbkdf2.c | 323 | ||||
-rw-r--r-- | libpurple/plugins/Makefile.am | 6 | ||||
-rw-r--r-- | libpurple/plugins/ciphertest.c | 202 |
6 files changed, 536 insertions, 1 deletions
diff --git a/libpurple/cipher.c b/libpurple/cipher.c index 256050e8b0..ec1f20f47b 100644 --- a/libpurple/cipher.c +++ b/libpurple/cipher.c @@ -255,6 +255,7 @@ purple_ciphers_init() { purple_ciphers_register_cipher("hmac", purple_hmac_cipher_get_ops()); purple_ciphers_register_cipher("des", purple_des_cipher_get_ops()); purple_ciphers_register_cipher("des3", purple_des3_cipher_get_ops()); + purple_ciphers_register_cipher("pbkdf2", purple_pbkdf2_cipher_get_ops()); purple_ciphers_register_cipher("rc4", purple_rc4_cipher_get_ops()); } diff --git a/libpurple/ciphers/Makefile.am b/libpurple/ciphers/Makefile.am index 67d887694e..e43abdbcd7 100644 --- a/libpurple/ciphers/Makefile.am +++ b/libpurple/ciphers/Makefile.am @@ -1,10 +1,12 @@ noinst_LTLIBRARIES=libpurple-ciphers.la +# XXX: cipher.lo won't be updated after a change in cipher files libpurple_ciphers_la_SOURCES=\ des.c \ gchecksum.c \ hmac.c \ md4.c \ + pbkdf2.c \ rc4.c noinst_HEADERS =\ diff --git a/libpurple/ciphers/ciphers.h b/libpurple/ciphers/ciphers.h index 333beb2c05..34c6b9aa7b 100644 --- a/libpurple/ciphers/ciphers.h +++ b/libpurple/ciphers/ciphers.h @@ -34,5 +34,8 @@ PurpleCipherOps * purple_hmac_cipher_get_ops(void); /* md4.c */ PurpleCipherOps * purple_md4_cipher_get_ops(void); +/* pbkdf2.c */ +PurpleCipherOps * purple_pbkdf2_cipher_get_ops(void); + /* rc4.c */ PurpleCipherOps * purple_rc4_cipher_get_ops(void); diff --git a/libpurple/ciphers/pbkdf2.c b/libpurple/ciphers/pbkdf2.c new file mode 100644 index 0000000000..339ef5168b --- /dev/null +++ b/libpurple/ciphers/pbkdf2.c @@ -0,0 +1,323 @@ +/* + * 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" + +/* 1024bit */ +#define PBKDF2_HASH_MAX_LEN 128 + +typedef struct +{ + gchar *hash_func; + guint iter_count; + size_t out_len; + + guchar *salt; + size_t salt_len; + guchar *passphrase; + size_t passphrase_len; +} Pbkdf2Context; + +static void +purple_pbkdf2_init(PurpleCipherContext *context, void *extra) +{ + Pbkdf2Context *ctx_data; + + ctx_data = g_new0(Pbkdf2Context, 1); + purple_cipher_context_set_data(context, ctx_data); + + purple_cipher_context_reset(context, extra); +} + +static void +purple_pbkdf2_uninit(PurpleCipherContext *context) +{ + Pbkdf2Context *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_pbkdf2_reset(PurpleCipherContext *context, void *extra) +{ + Pbkdf2Context *ctx_data = purple_cipher_context_get_data(context); + + g_return_if_fail(ctx_data != NULL); + + g_free(ctx_data->hash_func); + ctx_data->hash_func = NULL; + ctx_data->iter_count = 1; + ctx_data->out_len = 256; + + purple_cipher_context_reset_state(context, extra); +} + +static void +purple_pbkdf2_reset_state(PurpleCipherContext *context, void *extra) +{ + Pbkdf2Context *ctx_data = purple_cipher_context_get_data(context); + + g_return_if_fail(ctx_data != NULL); + + purple_cipher_context_set_salt(context, NULL, 0); + purple_cipher_context_set_key(context, NULL, 0); +} + +static void +purple_pbkdf2_set_option(PurpleCipherContext *context, const gchar *name, + void *value) +{ + Pbkdf2Context *ctx_data = purple_cipher_context_get_data(context); + + g_return_if_fail(ctx_data != NULL); + + if (g_strcmp0(name, "hash") == 0) { + g_free(ctx_data->hash_func); + ctx_data->hash_func = g_strdup(value); + return; + } + + if (g_strcmp0(name, "iter_count") == 0) { + ctx_data->iter_count = GPOINTER_TO_UINT(value); + return; + } + + if (g_strcmp0(name, "out_len") == 0) { + ctx_data->out_len = GPOINTER_TO_UINT(value); + return; + } + + purple_debug_warning("pbkdf2", "Unknown option: %s\n", + name ? name : "(null)"); +} + +static void * +purple_pbkdf2_get_option(PurpleCipherContext *context, const gchar *name) +{ + Pbkdf2Context *ctx_data = purple_cipher_context_get_data(context); + + g_return_val_if_fail(ctx_data != NULL, NULL); + + if (g_strcmp0(name, "hash") == 0) + return ctx_data->hash_func; + + if (g_strcmp0(name, "iter_count") == 0) + return GUINT_TO_POINTER(ctx_data->iter_count); + + if (g_strcmp0(name, "out_len") == 0) + return GUINT_TO_POINTER(ctx_data->out_len); + + purple_debug_warning("pbkdf2", "Unknown option: %s\n", + name ? name : "(null)"); + return NULL; +} + +static size_t +purple_pbkdf2_get_digest_size(PurpleCipherContext *context) +{ + Pbkdf2Context *ctx_data = purple_cipher_context_get_data(context); + + g_return_val_if_fail(ctx_data != NULL, 0); + + return ctx_data->out_len; +} + +static void +purple_pbkdf2_set_salt(PurpleCipherContext *context, const guchar *salt, size_t len) +{ + Pbkdf2Context *ctx_data = purple_cipher_context_get_data(context); + + g_return_if_fail(ctx_data != NULL); + + g_free(ctx_data->salt); + ctx_data->salt = NULL; + ctx_data->salt_len = 0; + + if (len == 0) + return; + g_return_if_fail(salt != NULL); + + ctx_data->salt = g_memdup(salt, len); + ctx_data->salt_len = len; +} + +static void +purple_pbkdf2_set_key(PurpleCipherContext *context, const guchar *key, + size_t len) +{ + Pbkdf2Context *ctx_data = purple_cipher_context_get_data(context); + + g_return_if_fail(ctx_data != NULL); + + if (ctx_data->passphrase != NULL) { + memset(ctx_data->passphrase, 0, ctx_data->passphrase_len); + g_free(ctx_data->passphrase); + ctx_data->passphrase = NULL; + } + ctx_data->passphrase_len = 0; + + if (len == 0) + return; + g_return_if_fail(key != NULL); + + ctx_data->passphrase = g_memdup(key, len); + ctx_data->passphrase_len = len; +} + +/* inspired by gnutls 3.1.10, pbkdf2-sha1.c */ +static gboolean +purple_pbkdf2_digest(PurpleCipherContext *context, guchar digest[], size_t len) +{ + Pbkdf2Context *ctx_data = purple_cipher_context_get_data(context); + 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; + PurpleCipherContext *hash; + + g_return_val_if_fail(ctx_data != NULL, FALSE); + g_return_val_if_fail(digest != NULL, FALSE); + g_return_val_if_fail(len >= ctx_data->out_len, FALSE); + + g_return_val_if_fail(ctx_data->hash_func != NULL, FALSE); + g_return_val_if_fail(ctx_data->iter_count > 0, FALSE); + g_return_val_if_fail(ctx_data->passphrase != NULL || + ctx_data->passphrase_len == 0, FALSE); + g_return_val_if_fail(ctx_data->salt != NULL || ctx_data->salt_len == 0, + FALSE); + g_return_val_if_fail(ctx_data->out_len > 0, FALSE); + g_return_val_if_fail(ctx_data->out_len < 0xFFFFFFFFU, FALSE); + + salt_ext_len = ctx_data->salt_len + 4; + + hash = purple_cipher_context_new_by_name("hmac", NULL); + if (hash == NULL) { + purple_debug_error("pbkdf2", "Couldn't create new hmac " + "context\n"); + return FALSE; + } + purple_cipher_context_set_option(hash, "hash", + (void*)ctx_data->hash_func); + purple_cipher_context_set_key(hash, (const guchar*)ctx_data->passphrase, + ctx_data->passphrase_len); + + halfkey_len = purple_cipher_context_get_digest_size(hash); + if (halfkey_len <= 0 || halfkey_len > PBKDF2_HASH_MAX_LEN) { + purple_debug_error("pbkdf2", "Unsupported hash function: %s " + "(digest size: %d)\n", + ctx_data->hash_func ? ctx_data->hash_func : "(null)", + halfkey_len); + return FALSE; + } + + halfkey_count = ((ctx_data->out_len - 1) / halfkey_len) + 1; + halfkey_pad = ctx_data->out_len - (halfkey_count - 1) * halfkey_len; + + salt_ext = g_new(guchar, salt_ext_len); + memcpy(salt_ext, ctx_data->salt, ctx_data->salt_len); + + for (halfkey_no = 1; halfkey_no <= halfkey_count; halfkey_no++) { + memset(halfkey, 0, halfkey_len); + + for (iter_no = 1; iter_no <= ctx_data->iter_count; iter_no++) { + int i; + + purple_cipher_context_reset_state(hash, NULL); + + 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_context_append(hash, salt_ext, + salt_ext_len); + } + else + purple_cipher_context_append(hash, halfkey_hash, + halfkey_len); + + if (!purple_cipher_context_digest(hash, halfkey_hash, + halfkey_len)) { + purple_debug_error("pbkdf2", + "Couldn't retrieve a digest\n"); + g_free(salt_ext); + purple_cipher_context_destroy(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); + purple_cipher_context_destroy(hash); + + return TRUE; +} + +static PurpleCipherOps PBKDF2Ops = { + purple_pbkdf2_set_option, /* set_option */ + purple_pbkdf2_get_option, /* get_option */ + purple_pbkdf2_init, /* init */ + purple_pbkdf2_reset, /* reset */ + purple_pbkdf2_reset_state, /* reset_state */ + purple_pbkdf2_uninit, /* uninit */ + NULL, /* set_iv */ + NULL, /* append */ + purple_pbkdf2_digest, /* digest */ + purple_pbkdf2_get_digest_size, /* get_digest_size */ + NULL, /* encrypt */ + NULL, /* decrypt */ + purple_pbkdf2_set_salt, /* set_salt */ + NULL, /* get_salt_size */ + purple_pbkdf2_set_key, /* set_key */ + NULL, /* get_key_size */ + NULL, /* set_batch_mode */ + NULL, /* get_batch_mode */ + NULL, /* get_block_size */ + NULL, NULL, NULL, NULL /* reserved */ +}; + +PurpleCipherOps * +purple_pbkdf2_cipher_get_ops(void) { + return &PBKDF2Ops; +} diff --git a/libpurple/plugins/Makefile.am b/libpurple/plugins/Makefile.am index 08b146424a..cc3eae6a31 100644 --- a/libpurple/plugins/Makefile.am +++ b/libpurple/plugins/Makefile.am @@ -147,7 +147,11 @@ AM_CPPFLAGS = \ $(DEBUG_CFLAGS) \ $(GLIB_CFLAGS) \ $(PLUGIN_CFLAGS) \ - $(DBUS_CFLAGS) + $(DBUS_CFLAGS) \ + $(NSS_CFLAGS) + +PLUGIN_LIBS = \ + $(NSS_LIBS) # # This part allows people to build their own plugins in here. diff --git a/libpurple/plugins/ciphertest.c b/libpurple/plugins/ciphertest.c index dd16e023b7..4b1e278f5a 100644 --- a/libpurple/plugins/ciphertest.c +++ b/libpurple/plugins/ciphertest.c @@ -231,6 +231,207 @@ cipher_test_digest(void) } /************************************************************************** + * PBKDF2 stuff + **************************************************************************/ + +#include <nss.h> +#include <secmod.h> +#include <pk11func.h> +#include <prerror.h> +#include <secerr.h> + +typedef struct { + const gchar *hash; + const guint iter_count; + const gchar *passphrase; + const gchar *salt; + const guint out_len; + const gchar *answer; +} pbkdf2_test; + +pbkdf2_test pbkdf2_tests[] = { + { "sha256", 1, "password", "salt", 32, "120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b"}, + { "sha1", 1, "password", "salt", 32, "0c60c80f961f0e71f3a9b524af6012062fe037a6e0f0eb94fe8fc46bdc637164"}, + { "sha1", 1000, "ala ma kota", "", 16, "924dba137b5bcf6d0de84998f3d8e1f9"}, + { "sha1", 1, "", "", 32, "1e437a1c79d75be61e91141dae20affc4892cc99abcc3fe753887bccc8920176"}, + { "sha256", 100, "some password", "and salt", 1, "c7"}, + { "sha1", 10000, "pretty long password W Szczebrzeszynie chrzaszcz brzmi w trzcinie i Szczebrzeszyn z tego slynie", "Grzegorz Brzeczyszczykiewicz", 32, "8cb0cb164f2554733ae02f5751b0e84a88fb385446e85a3991bdcdf1ea11795c"}, + { NULL, 0, NULL, NULL, 0, NULL} +}; + +static gchar* +cipher_pbkdf2_nss_sha1(const gchar *passphrase, const gchar *salt, + guint iter_count, guint out_len) +{ + PK11SlotInfo *slot; + SECAlgorithmID *algorithm = NULL; + PK11SymKey *symkey = NULL; + const SECItem *symkey_data = NULL; + SECItem salt_item, passphrase_item; + guchar *passphrase_buff, *salt_buff; + gchar *ret; + + g_return_val_if_fail(passphrase != NULL, NULL); + g_return_val_if_fail(iter_count > 0, NULL); + g_return_val_if_fail(out_len > 0, NULL); + + NSS_NoDB_Init(NULL); + + slot = PK11_GetBestSlot(PK11_AlgtagToMechanism(SEC_OID_PKCS5_PBKDF2), + NULL); + if (slot == NULL) { + purple_debug_error("cipher-test", "NSS: couldn't get slot: " + "%d\n", PR_GetError()); + return NULL; + } + + salt_buff = (guchar*)g_strdup(salt ? salt : ""); + salt_item.type = siBuffer; + salt_item.data = salt_buff; + salt_item.len = salt ? strlen(salt) : 0; + + algorithm = PK11_CreatePBEV2AlgorithmID(SEC_OID_PKCS5_PBKDF2, + SEC_OID_AES_256_CBC, SEC_OID_HMAC_SHA1, out_len, iter_count, + &salt_item); + if (algorithm == NULL) { + purple_debug_error("cipher-test", "NSS: couldn't create " + "algorithm ID: %d\n", PR_GetError()); + PK11_FreeSlot(slot); + g_free(salt_buff); + return NULL; + } + + passphrase_buff = (guchar*)g_strdup(passphrase); + passphrase_item.type = siBuffer; + passphrase_item.data = passphrase_buff; + passphrase_item.len = strlen(passphrase); + + symkey = PK11_PBEKeyGen(slot, algorithm, &passphrase_item, PR_FALSE, + NULL); + if (symkey == NULL) { + purple_debug_error("cipher-test", "NSS: Couldn't generate key: " + "%d\n", PR_GetError()); + SECOID_DestroyAlgorithmID(algorithm, PR_TRUE); + PK11_FreeSlot(slot); + g_free(passphrase_buff); + g_free(salt_buff); + return NULL; + } + + if (PK11_ExtractKeyValue(symkey) == SECSuccess) + symkey_data = PK11_GetKeyData(symkey); + + if (symkey_data == NULL || symkey_data->data == NULL) { + purple_debug_error("cipher-test", "NSS: Couldn't extract key " + "value: %d\n", PR_GetError()); + PK11_FreeSymKey(symkey); + SECOID_DestroyAlgorithmID(algorithm, PR_TRUE); + PK11_FreeSlot(slot); + g_free(passphrase_buff); + g_free(salt_buff); + return NULL; + } + + if (symkey_data->len != out_len) { + purple_debug_error("cipher-test", "NSS: Invalid key length: %d " + "(should be %d)\n", symkey_data->len, out_len); + PK11_FreeSymKey(symkey); + SECOID_DestroyAlgorithmID(algorithm, PR_TRUE); + PK11_FreeSlot(slot); + g_free(passphrase_buff); + g_free(salt_buff); + return NULL; + } + + ret = purple_base16_encode(symkey_data->data, symkey_data->len); + + PK11_FreeSymKey(symkey); + SECOID_DestroyAlgorithmID(algorithm, PR_TRUE); + PK11_FreeSlot(slot); + g_free(passphrase_buff); + g_free(salt_buff); + return ret; +} + +static void +cipher_test_pbkdf2(void) +{ + PurpleCipherContext *context; + int i = 0; + gboolean fail = FALSE; + + purple_debug_info("cipher-test", "Running PBKDF2 tests\n"); + + context = purple_cipher_context_new_by_name("pbkdf2", NULL); + + while (pbkdf2_tests[i].answer) { + pbkdf2_test *test = &pbkdf2_tests[i]; + gchar digest[2 * 32 + 1 + 10]; + gchar *digest_nss = NULL; + gboolean ret, skip_nss = FALSE; + + i++; + + purple_debug_info("cipher-test", "Test %02d:\n", i); + purple_debug_info("cipher-test", + "\tTesting '%s' with salt:'%s' hash:%s iter_count:%d \n", + test->passphrase, test->salt, test->hash, + test->iter_count); + + purple_cipher_context_set_option(context, "hash", (gpointer)test->hash); + purple_cipher_context_set_option(context, "iter_count", GUINT_TO_POINTER(test->iter_count)); + purple_cipher_context_set_option(context, "out_len", GUINT_TO_POINTER(test->out_len)); + purple_cipher_context_set_salt(context, (const guchar*)test->salt, test->salt ? strlen(test->salt): 0); + purple_cipher_context_set_key(context, (const guchar*)test->passphrase, strlen(test->passphrase)); + + ret = purple_cipher_context_digest_to_str(context, digest, sizeof(digest)); + purple_cipher_context_reset(context, NULL); + + if (!ret) { + purple_debug_info("cipher-test", "\tfailed\n"); + fail = TRUE; + continue; + } + + if (g_strcmp0(test->hash, "sha1") != 0) + skip_nss = TRUE; + if (test->out_len != 16 && test->out_len != 32) + skip_nss = TRUE; + + if (!skip_nss) { + digest_nss = cipher_pbkdf2_nss_sha1(test->passphrase, + test->salt, test->iter_count, test->out_len); + } + + if (!ret) { + purple_debug_info("cipher-test", "\tnss test failed\n"); + fail = TRUE; + } + + if (g_strcmp0(digest, test->answer) == 0 && + (skip_nss || g_strcmp0(digest, digest_nss) == 0)) { + purple_debug_info("cipher-test", "\tTest OK\n"); + } + else { + purple_debug_info("cipher-test", "\twrong answer\n"); + fail = TRUE; + } + + purple_debug_info("cipher-test", "\tGot: %s\n", digest); + if (digest_nss) + purple_debug_info("cipher-test", "\tGot from NSS: %s\n", digest_nss); + purple_debug_info("cipher-test", "\tWanted: %s\n", test->answer); + } + + purple_cipher_context_destroy(context); + + if (fail) + purple_debug_info("cipher-test", "PBKDF2 tests FAILED\n\n"); + else + purple_debug_info("cipher-test", "PBKDF2 tests completed successfully\n\n"); +} + +/************************************************************************** * Plugin stuff **************************************************************************/ static gboolean @@ -238,6 +439,7 @@ plugin_load(PurplePlugin *plugin) { cipher_test_md5(); cipher_test_sha1(); cipher_test_digest(); + cipher_test_pbkdf2(); return TRUE; } |