summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSamuel Lidén Borell <samuel@kodafritt.se>2015-06-21 12:13:49 (GMT)
committerSamuel Lidén Borell <samuel@kodafritt.se>2015-06-21 12:13:49 (GMT)
commita0396e8e26799051c457d71c71472d4221f3c06e (patch)
tree79a923ced179c71eb17e402283482c14b374388b
parent0cbb5ec0949033fa7bdd4b2afe72d70c478042dc (diff)
downloadfishlim-a0396e8e26799051c457d71c71472d4221f3c06e.zip
fishlim-a0396e8e26799051c457d71c71472d4221f3c06e.tar.gz
fishlim-a0396e8e26799051c457d71c71472d4221f3c06e.tar.bz2
Implement encryption in CBC mode
-rw-r--r--README15
-rw-r--r--fish.c74
-rw-r--r--fish.h3
-rw-r--r--keystore.c48
-rw-r--r--keystore.h7
-rw-r--r--plugin_xchat.c63
-rw-r--r--test.c61
7 files changed, 246 insertions, 25 deletions
diff --git a/README b/README
index 7ff0b15..f710e0f 100644
--- a/README
+++ b/README
@@ -20,6 +20,7 @@ Working:
* Using unecrypted keys / keys without a password from blow.ini
* Pure protocol-level filtering (works with highlighting, nick coloring etc)
* Partially encrypted messages (i.e. prefixed with nickname by a bouncer)
+ * CBC mode
Not working:
* Key exchange
@@ -32,10 +33,22 @@ Not working:
Commands
--------
-/setkey [nick or #channel] password
+/setkey [nick or #channel] [mode:]password
Sets the encryption key for the nick or channel to password. The keys
are stored in the configuration file in ~/.config/hexchat/blow.ini
+
+ Optionally, the block cipher mode for outgoing messages may be specified.
+ If unspecified it will be ECB for backwards compatibility, but for
+ greater security, please use CBC mode if possible.
+
+ For incoming messages, the block cipher mode is auto-detected, regardless
+ of which mode was configured with this command.
+
+
+/ciphermode [nick or #channel] mode
+
+ Sets the cipher mode to ECB or CBC. See /setkey.
/delkey nick-or-#channel
diff --git a/fish.c b/fish.c
index 74a1f36..a7c3cfc 100644
--- a/fish.c
+++ b/fish.c
@@ -27,6 +27,7 @@
#include <openssl/blowfish.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
+#include <openssl/rand.h>
#include "keystore.h"
#include "fish.h"
@@ -58,9 +59,9 @@ static const signed char fish_unbase64[256] = {
} while (0);
/**
- * Encrypts a message. Currently, only ECB mode is supported.
+ * Encrypts a message in ECB mode.
*/
-char *fish_encrypt(const char *key, size_t keylen, const char *message) {
+char *fish_encrypt_ecb(const char *key, size_t keylen, const char *message) {
BF_KEY bfkey;
size_t messagelen;
size_t i;
@@ -113,9 +114,67 @@ char *fish_encrypt(const char *key, size_t keylen, const char *message) {
}
/**
+ * Encrypts a message in CBC mode.
+ */
+char *fish_encrypt_cbc(const char *key, size_t keylen, const char *message) {
+ BF_KEY bfkey;
+ unsigned char *encrypted = NULL;
+ BIO *b64 = NULL;
+ BF_set_key(&bfkey, keylen, (const unsigned char*)key);
+
+ size_t messagelen = strlen(message);
+ if (messagelen == 0) goto err;
+
+ // Allocate structure for final encrypted data.
+ int cryptlen = 8 + ((messagelen+7)&~7);
+ encrypted = malloc(cryptlen);
+ if (!encrypted) goto err;
+
+ // Generate IV
+ unsigned char iv[8];
+ RAND_pseudo_bytes(iv, 8);
+ memcpy(encrypted, iv, 8);
+
+ // Encrypt in CBC mode. The IV is overwritten
+ BF_cbc_encrypt((const unsigned char*)message, encrypted+8, messagelen, &bfkey, iv, BF_ENCRYPT);
+
+ // Base64 encode
+ b64 = BIO_new(BIO_f_base64());
+ if (!b64) goto err;
+ BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
+ BIO *bmem = BIO_new(BIO_s_mem());
+ if (!bmem) goto err;
+ BIO *bio = BIO_push(b64, bmem);
+ if (cryptlen != 0) {
+ BIO_write(bio, encrypted, cryptlen);
+ }
+ free(encrypted);
+ encrypted = NULL;
+ BIO_flush(bio);
+
+ unsigned char *b64enc;
+ int b64len = (int)BIO_ctrl(bio, BIO_CTRL_INFO, 0, (char *)&b64enc);
+ if (b64len <= 0) goto err;
+
+ // Copy the string
+ char *encoded = malloc(b64len+2);
+ encoded[0] = '*'; // prepended to indicate CBC mode
+ memcpy(encoded+1, b64enc, b64len);
+ encoded[b64len+1] = '\0'; // null terminator
+ BIO_free_all(b64); // data has been copied now
+ return encoded;
+
+ err:
+ free(encrypted);
+ BIO_free_all(b64);
+ return NULL;
+}
+
+
+/**
* Decrypts a message in CBC mode. The leading "*" should be included.
*/
-char *fish_decrypt_cbc(const char *key, size_t keylen, const char *data) {
+static char *fish_decrypt_cbc(const char *key, size_t keylen, const char *data) {
BF_KEY bfkey;
unsigned char *decrypted, *bindata = NULL;
BIO *b64 = NULL;
@@ -224,13 +283,16 @@ char *fish_decrypt(const char *key, size_t keylen, const char *data) {
char *fish_encrypt_for_nick(const char *nick, const char *data) {
char *key;
char *encrypted;
+ bool is_cbc;
// Look for key
- key = keystore_get_key(nick);
+ key = keystore_get_key(nick, &is_cbc);
if (!key) return NULL;
// Encrypt
- encrypted = fish_encrypt(key, strlen(key), data);
+ encrypted = is_cbc ?
+ fish_encrypt_cbc(key, strlen(key), data) :
+ fish_encrypt_ecb(key, strlen(key), data);
free(key);
return encrypted;
@@ -244,7 +306,7 @@ char *fish_decrypt_from_nick(const char *nick, const char *data) {
char *key;
char *decrypted;
// Look for key
- key = keystore_get_key(nick);
+ key = keystore_get_key(nick, NULL);
if (!key) return NULL;
// Decrypt
diff --git a/fish.h b/fish.h
index 5a4e85d..afb7a70 100644
--- a/fish.h
+++ b/fish.h
@@ -32,7 +32,8 @@
#endif
#include <stddef.h>
-char *fish_encrypt(const char *key, size_t keylen, const char *message);
+char *fish_encrypt_ecb(const char *key, size_t keylen, const char *message);
+char *fish_encrypt_cbc(const char *key, size_t keylen, const char *message);
char *fish_decrypt(const char *key, size_t keylen, const char *data);
char *fish_encrypt_for_nick(const char *nick, const char *data);
char *fish_decrypt_from_nick(const char *nick, const char *data);
diff --git a/keystore.c b/keystore.c
index e628c28..44393a0 100644
--- a/keystore.c
+++ b/keystore.c
@@ -1,6 +1,6 @@
/*
- Copyright (c) 2010 Samuel Lidén Borell <samuel@kodafritt.se>
+ Copyright (c) 2010-2015 Samuel Lidén Borell <samuel@kodafritt.se>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -32,6 +32,7 @@
#include "plugin_xchat.h"
+// TODO use CBC in keystore_store_key once this is implemented
static char *keystore_password = NULL;
@@ -86,13 +87,21 @@ static gchar *get_nick_value(GKeyFile *keyfile, const char *nick, const char *it
/**
* Extracts a key from the key store file.
*/
-char *keystore_get_key(const char *nick) {
+char *keystore_get_key(const char *nick, bool *is_cbc) {
// Get the key
GKeyFile *keyfile = getConfigFile();
gchar *value = get_nick_value(keyfile, nick, "key");
+ gchar *cbcval = get_nick_value(keyfile, nick, "cbc");
+ if (is_cbc) {
+ // All values except 0 means "use CBC".
+ // Mircryption parses "N" and "n" as false also.
+ *is_cbc = (cbcval && *cbcval && strcmp(cbcval, "0");
+ }
+ g_free(cbcval);
g_key_file_free(keyfile);
if (!value) return NULL;
+ // TODO if the key value begins with cbc: or CBC: then it's also a CBC key
if (strncmp(value, "+OK ", 4) != 0) {
// Key is stored in plaintext
return import_glib_string(value);
@@ -101,6 +110,7 @@ char *keystore_get_key(const char *nick) {
const char *encrypted = value+4;
const char *password = get_keystore_password();
char *decrypted = fish_decrypt(password, strlen(password), encrypted);
+ secure_erase(value, strlen(value));
g_free(value);
return decrypted;
}
@@ -147,7 +157,7 @@ static bool save_keystore(GKeyFile *keyfile) {
/**
* Sets a key in the key store file.
*/
-bool keystore_store_key(const char *nick, const char *key) {
+bool keystore_store_key(const char *nick, const char *key, bool is_cbc) {
const char *password;
char *encrypted;
char *wrapped;
@@ -161,7 +171,8 @@ bool keystore_store_key(const char *nick, const char *key) {
password = get_keystore_password();
if (password) {
// Encrypt the password
- encrypted = fish_encrypt(password, strlen(password), key);
+ // TODO Should use CBC here once keystore_password is implemented
+ encrypted = fish_encrypt_ecb(password, strlen(password), key);
if (!encrypted) goto end;
// Prepend "+OK "
@@ -176,6 +187,35 @@ bool keystore_store_key(const char *nick, const char *key) {
g_key_file_set_string(keyfile, nick, "key", key);
}
+ // Store the CBC status. Note that all keys for the nick are deleted above
+ if (is_cbc) {
+ g_key_file_set_string(keyfile, nick, "cbc", "1");
+ }
+
+ // Save key store file
+ ok = save_keystore(keyfile);
+
+ end:
+ g_key_file_free(keyfile);
+ return ok;
+}
+
+/**
+ * Sets the block cipher mode for a nick to ECB or CBC.
+ */
+bool keystore_set_mode(const char *nick, bool is_cbc) {
+ bool ok = false;
+ GKeyFile *keyfile = getConfigFile();
+
+ if (!g_key_file_has_group(keyfile, nick)) goto end;
+
+ // Store the CBC status
+ if (is_cbc) {
+ g_key_file_set_string(keyfile, nick, "cbc", "1");
+ } else {
+ g_key_file_remove_key(keyfile, nick, "cbc", NULL);
+ }
+
// Save key store file
ok = save_keystore(keyfile);
diff --git a/keystore.h b/keystore.h
index edf5499..ad1aee8 100644
--- a/keystore.h
+++ b/keystore.h
@@ -1,6 +1,6 @@
/*
- Copyright (c) 2010 Samuel Lidén Borell <samuel@kodafritt.se>
+ Copyright (c) 2010-2015 Samuel Lidén Borell <samuel@kodafritt.se>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -32,8 +32,9 @@
#endif
#include <stddef.h>
-char *keystore_get_key(const char *nick);
-bool keystore_store_key(const char *nick, const char *key);
+char *keystore_get_key(const char *nick, bool *is_cbc);
+bool keystore_store_key(const char *nick, const char *key, bool is_cbc);
+bool keystore_set_mode(const char *nick, bool is_cbc);
bool keystore_delete_nick(const char *nick);
void keystore_secure_free(void *ptr, size_t size);
diff --git a/plugin_xchat.c b/plugin_xchat.c
index 95b9a26..8e6197b 100644
--- a/plugin_xchat.c
+++ b/plugin_xchat.c
@@ -44,9 +44,10 @@
static const char plugin_name[] = "FiSHLiM";
static const char plugin_desc[] = "Encryption plugin for the FiSH protocol. Less is More!";
-static const char plugin_version[] = "0.0.18";
+static const char plugin_version[] = "0.0.19";
-static const char usage_setkey[] = "Usage: SETKEY [<nick or #channel>] <password>, sets the key for a channel or nick";
+static const char usage_setkey[] = "Usage: SETKEY [<nick or #channel>] [<mode>:]<password>, sets the key for a channel or nick. Modes: ECB, CBC";
+static const char usage_ciphermode[] = "Usage: CIPHERMODE [<nick or #channel>] <mode>, sets the cipher mode for a channel or nick to ECB or CBC";
static const char usage_delkey[] = "Usage: DELKEY <nick or #channel>, deletes the key for a channel or nick";
static hexchat_plugin *ph;
@@ -204,6 +205,7 @@ static int handle_incoming(char *word[], char *word_eol[], void *userdata) {
static int handle_setkey(char *word[], char *word_eol[], void *userdata) {
const char *nick;
const char *key;
+ bool is_cbc = false;
// Check syntax
if (*word[2] == '\0') {
@@ -221,11 +223,61 @@ static int handle_setkey(char *word[], char *word_eol[], void *userdata) {
key = word_eol[3];
}
+ if (!strncmp("cbc:", key, 4) || !strncmp("CBC:", key, 4)) {
+ key = key+4;
+ is_cbc = true;
+ } else if (!strncmp("ecb:", key, 4) || !strncmp("ECB:", key, 4)) {
+ key = key+4;
+ }
+
// Set password
- if (keystore_store_key(nick, key)) {
- hexchat_printf(ph, "Stored key for %s\n", nick);
+ if (keystore_store_key(nick, key, is_cbc)) {
+ hexchat_printf(ph, "Stored key for %s (%s)\n", nick, is_cbc ? "CBC mode" : "old insecure ECB mode");
+ } else {
+ hexchat_printf(ph, "\00305Failed to store key in blow.ini\n");
+ }
+
+ return HEXCHAT_EAT_HEXCHAT;
+}
+
+/**
+ * Command handler for /ciphermode
+ */
+static int handle_ciphermode(char *word[], char *word_eol[], void *userdata) {
+ const char *nick;
+ const char *mode;
+ bool is_cbc;
+
+ // Check syntax
+ if (*word[2] == '\0') {
+ hexchat_printf(ph, "%s\n", usage_ciphermode);
+ return HEXCHAT_EAT_HEXCHAT;
+ }
+
+ if (*word[3] == '\0') {
+ // /ciphermode mode
+ nick = hexchat_get_info(ph, "channel");
+ mode = word_eol[2];
+ } else {
+ // /ciphermode #channel mode
+ nick = word[2];
+ mode = word_eol[3];
+ }
+
+ if (!strcmp("cbc", mode) || !strcmp("CBC", mode)) {
+ is_cbc = true;
+ } else if (!strcmp("ecb", mode) || !strcmp("ECB", mode)) {
+ is_cbc = false;
+ } else {
+ hexchat_printf(ph, "Invalid mode: %s. Only ECB and CBC are supported. CBC is recommended.\n");
+ return HEXCHAT_EAT_HEXCHAT;
+ }
+
+ // Set mode
+ if (keystore_set_mode(nick, is_cbc)) {
+ hexchat_printf(ph, "Changed mode for %s to %s\n", nick, is_cbc ? "CBC mode" : "old insecure ECB mode");
} else {
- hexchat_printf(ph, "\00305Failed to store key in blow.ini\n", nick, key);
+ hexchat_printf(ph, "\00305Failed to set mode for nick %s\n", nick);
}
return HEXCHAT_EAT_HEXCHAT;
@@ -282,6 +334,7 @@ int hexchat_plugin_init(hexchat_plugin *plugin_handle,
/* Register commands */
hexchat_hook_command(ph, "SETKEY", HEXCHAT_PRI_NORM, handle_setkey, usage_setkey, NULL);
+ hexchat_hook_command(ph, "CIPHERMODE", HEXCHAT_PRI_NORM, handle_ciphermode, usage_ciphermode, NULL);
hexchat_hook_command(ph, "DELKEY", HEXCHAT_PRI_NORM, handle_delkey, usage_delkey, NULL);
/* Add handlers */
diff --git a/test.c b/test.c
index 05276d1..181628d 100644
--- a/test.c
+++ b/test.c
@@ -30,12 +30,28 @@
#include "keystore.h"
// We can't use the XChat plugin API from here...
+// Note that this function is used from keystore.c
gchar *get_config_filename() {
const gchar *homedir = g_get_home_dir();
return g_build_filename(homedir, ".config", "hexchat", "blow.ini", NULL);
}
+static bool prompt(const char *message, bool def) {
+ while (1) {
+ char resp[100];
+ fprintf(stderr, "%s [%s] ", message, def ? "Y/n" : "y/N");
+ if (!fgets(resp, sizeof(resp), stdin)) { abort(); }
+ if (resp[0] == '\0' || resp[0] == '\n' || resp[0] == '\r') {
+ return def;
+ }
+ if (resp[0] == 'Y' || resp[0] == 'y') return true;
+ if (resp[0] == 'N' || resp[0] == 'n') return false;
+ fprintf(stderr, "Please answer Y or N.");
+ }
+}
+
+
static int decrypt(int nick_count, char *nicks[]) {
char encrypted[8192];
while (fgets(encrypted, sizeof(encrypted), stdin)) {
@@ -93,30 +109,65 @@ static int setkeys(int nick_count, char *nicks[]) {
char *newline = strchr(password, '\n');
if (newline) *newline = '\0';
- if (!keystore_store_key(nicks[i], password)) {
- fprintf(stderr, "Failed to update key file.\n");
+ fprintf(stderr, "Older plugins/clients only support the insecure ECB mode.\n");
+ bool use_cbc = prompt("Enable encryption in the more secure CBC mode by default?", 1);
+
+ if (!keystore_store_key(nicks[i], password, use_cbc)) {
+ fprintf(stderr, "Failed to update key file, for nick %s.\n", nicks[i]);
return 1;
}
}
return 0;
}
+static int setmode(int argc, char *argv[]) {
+ if (argc < 2) {
+ fprintf(stderr, "-m option requires a mode argument (CBC or ECB) and at least one nick name\n");
+ return 2;
+ }
+ const char *mode = argv[0];
+ bool is_cbc;
+ if (!strcmp(mode, "ecb") || !strcmp(mode, "ECB")) {
+ is_cbc = false;
+ } else if (!strcmp(mode, "cbc") || !strcmp(mode, "CBC")) {
+ is_cbc = true;
+ } else {
+ fprintf(stderr, "Invalid mode, must be CBC or ECB\n");
+ return 2;
+ }
+
+ int error = 0;
+ for (int i = 1; i < argc; i++) {
+ if (!keystore_set_mode(argv[i], is_cbc)) {
+ fprintf(stderr, "Failed to set mode for nick %s.\n", argv[i]);
+ error = 1;
+ }
+ }
+ return error;
+}
+
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "usage: %s [-e] nick...\n"
- "usage: %s -k nick...\n"
+ " %s -k nick...\n"
+ " %s -m MODE nick...\n"
"\n"
"Options:\n"
- " -e Encrypt (default is to decrypt\n"
+ " -e Encrypt (default is to decrypt)\n"
" -k Set key (will prompt for password)\n"
+ " -m MODE Set default encryption mode for key.\n"
+ " Supported modes: ecb (default), cbc (recommended)\n"
"\n"
"This tool will read/write keys from %s\n",
- argv[0], argv[0], get_config_filename());
+ argv[0], argv[0], argv[0], get_config_filename());
return 2;
}
+
if (strcmp(argv[1], "-k") == 0) {
return setkeys(argc-2, &argv[2]);
+ } else if (strcmp(argv[1], "-m") == 0) {
+ return setmode(argc-2, &argv[2]);
} else if (strcmp(argv[1], "-e") == 0) {
return encrypt(argc-2, &argv[2]);
} else {