FiSHLiM: Support for CBC mode + more commands (#2347)

This commit is contained in:
Bakasura 2020-07-13 18:27:27 -05:00 committed by GitHub
parent 2f376953f3
commit c5a798beec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 1307 additions and 238 deletions

View File

@ -1,6 +1,7 @@
/* /*
Copyright (c) 2010 Samuel Lidén Borell <samuel@kodafritt.se> Copyright (c) 2010 Samuel Lidén Borell <samuel@kodafritt.se>
Copyright (c) 2019 <bakasura@protonmail.ch>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@ -27,15 +28,19 @@
#define DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER #define DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER
#endif #endif
#include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <openssl/evp.h>
#include <openssl/blowfish.h> #include <openssl/blowfish.h>
#include <openssl/rand.h>
#include "keystore.h" #include "keystore.h"
#include "fish.h" #include "fish.h"
#define IB 64 #define IB 64
static const char fish_base64[64] = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; static const char fish_base64[] = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
static const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
static const signed char fish_unbase64[256] = { static const signed char fish_unbase64[256] = {
IB,IB,IB,IB,IB,IB,IB,IB, IB,IB,IB,IB,IB,IB,IB,IB, IB,IB,IB,IB,IB,IB,IB,IB, IB,IB,IB,IB,IB,IB,IB,IB,
IB,IB,IB,IB,IB,IB,IB,IB, IB,IB,IB,IB,IB,IB,IB,IB, IB,IB,IB,IB,IB,IB,IB,IB, IB,IB,IB,IB,IB,IB,IB,IB,
@ -53,6 +58,12 @@ static const signed char fish_unbase64[256] = {
27,28,29,30,31,32,33,34, 35,36,37,IB,IB,IB,IB,IB, 27,28,29,30,31,32,33,34, 35,36,37,IB,IB,IB,IB,IB,
}; };
/**
* Convert Int to 4 Bytes (Big-endian)
*
* @param int source
* @param char* dest
*/
#define GET_BYTES(dest, source) do { \ #define GET_BYTES(dest, source) do { \
*((dest)++) = ((source) >> 24) & 0xFF; \ *((dest)++) = ((source) >> 24) & 0xFF; \
*((dest)++) = ((source) >> 16) & 0xFF; \ *((dest)++) = ((source) >> 16) & 0xFF; \
@ -60,135 +71,387 @@ static const signed char fish_unbase64[256] = {
*((dest)++) = (source) & 0xFF; \ *((dest)++) = (source) & 0xFF; \
} while (0); } while (0);
/**
* Convert 4 Bytes to Int (Big-endian)
*
* @param char* source
* @param int dest
*/
#define GET_LONG(dest, source) do { \
dest = ((uint8_t)*((source)++) << 24); \
dest |= ((uint8_t)*((source)++) << 16); \
dest |= ((uint8_t)*((source)++) << 8); \
dest |= (uint8_t)*((source)++); \
} while (0);
char *fish_encrypt(const char *key, size_t keylen, const char *message) {
BF_KEY bfkey; /**
size_t messagelen; * Encode ECB FiSH Base64
size_t i; *
int j; * @param [in] message Bytes to encode
char *encrypted; * @param [in] message_len Size of bytes to encode
char *end; * @return Array of char with encoded string
unsigned char bit; */
unsigned char word; char *fish_base64_encode(const char *message, size_t message_len) {
unsigned char d; BF_LONG left = 0, right = 0;
BF_set_key(&bfkey, keylen, (const unsigned char*)key); int i, j;
char *encoded = NULL;
messagelen = strlen(message); char *end = NULL;
if (messagelen == 0) return NULL; char *msg = NULL;
encrypted = g_malloc(((messagelen - 1) / 8) * 12 + 12 + 1); /* each 8-byte block becomes 12 bytes */
end = encrypted; if (message_len == 0)
return NULL;
while (*message) {
/* Read 8 bytes (a Blowfish block) */ /* Each 8-byte block becomes 12 bytes (fish base64 format) and add 1 byte for \0 */
BF_LONG binary[2] = { 0, 0 }; encoded = g_malloc(((message_len - 1) / 8) * 12 + 12 + 1);
unsigned char c; end = encoded;
for (i = 0; i < 8; i++) {
c = message[i]; /* Iterate over each 8-byte block (Blowfish block size) */
binary[i >> 2] |= c << 8*(3 - (i&3)); for (j = 0; j < message_len; j += 8) {
if (c == '\0') break; msg = (char *) message;
/* Set left and right longs */
GET_LONG(left, msg);
GET_LONG(right, msg);
for (i = 0; i < 6; ++i) {
*end++ = fish_base64[right & 0x3fu];
right = (right >> 6u);
} }
for (i = 0; i < 6; ++i) {
*end++ = fish_base64[left & 0x3fu];
left = (left >> 6u);
}
/* The previous for'd ensure fill all bytes of encoded, we don't need will with zeros */
message += 8; message += 8;
/* Encrypt block */
BF_encrypt(binary, &bfkey);
/* Emit FiSH-BASE64 */
bit = 0;
word = 1;
for (j = 0; j < 12; j++) {
d = fish_base64[(binary[word] >> bit) & 63];
*(end++) = d;
bit += 6;
if (j == 5) {
bit = 0;
word = 0;
}
}
/* Stop if a null terminator was found */
if (c == '\0') break;
} }
*end = '\0'; *end = '\0';
return encrypted; return encoded;
} }
/**
* Decode ECB FiSH Base64
*
* @param [in] message Base64 encoded string
* @param [out] final_len Real length of message
* @return Array of char with decoded message
*/
char *fish_base64_decode(const char *message, size_t *final_len) {
BF_LONG left, right;
int i;
char *bytes = NULL;
char *msg = NULL;
char *byt = NULL;
size_t message_len;
char *fish_decrypt(const char *key, size_t keylen, const char *data) { message_len = strlen(message);
BF_KEY bfkey;
size_t i; /* Ensure blocks of 12 bytes each one and valid characters */
char *decrypted; if (message_len == 0 || message_len % 12 != 0 || strspn(message, fish_base64) != message_len)
char *end; return NULL;
unsigned char bit;
unsigned char word; /* Each 12 bytes becomes 8-byte block and add 1 byte for \0 */
unsigned char d; *final_len = ((message_len - 1) / 12) * 8 + 8 + 1;
BF_set_key(&bfkey, keylen, (const unsigned char*)key); (*final_len)--; /* We support binary data */
bytes = (char *) g_malloc0(*final_len);
decrypted = g_malloc(strlen(data) + 1); byt = bytes;
end = decrypted;
msg = (char *) message;
while (*data) {
/* Convert from FiSH-BASE64 */ while (*msg) {
BF_LONG binary[2] = { 0, 0 }; right = 0;
bit = 0; left = 0;
word = 1; for (i = 0; i < 6; i++) right |= (uint8_t) fish_unbase64[*msg++] << (i * 6u);
for (i = 0; i < 12; i++) { for (i = 0; i < 6; i++) left |= (uint8_t) fish_unbase64[*msg++] << (i * 6u);
d = fish_unbase64[(const unsigned char)*(data++)]; GET_BYTES(byt, left);
if (d == IB) goto decrypt_end; GET_BYTES(byt, right);
binary[word] |= (unsigned long)d << bit;
bit += 6;
if (i == 5) {
bit = 0;
word = 0;
}
}
/* Decrypt block */
BF_decrypt(binary, &bfkey);
/* Copy to buffer */
GET_BYTES(end, binary[0]);
GET_BYTES(end, binary[1]);
} }
decrypt_end: return bytes;
*end = '\0'; }
return decrypted;
/**
* Encrypt or decrypt data with Blowfish cipher, support binary data.
*
* Good documentation for EVP:
*
* - https://wiki.openssl.org/index.php/EVP_Symmetric_Encryption_and_Decryption
*
* - https://stackoverflow.com/questions/5727646/what-is-the-length-parameter-of-aes-evp-decrypt
*
* - https://stackoverflow.com/questions/26345175/correct-way-to-free-allocate-the-context-in-the-openssl
*
* - https://stackoverflow.com/questions/29874150/working-with-evp-and-openssl-coding-in-c
*
* @param [in] plaintext Bytes to encrypt or decrypt
* @param [in] plaintext_len Size of plaintext
* @param [in] key Bytes of key
* @param [in] keylen Size of key
* @param [in] encode 1 or encrypt 0 for decrypt
* @param [in] mode EVP_CIPH_ECB_MODE or EVP_CIPH_CBC_MODE
* @param [out] ciphertext_len The bytes written
* @return Array of char with data encrypted or decrypted
*/
char *fish_cipher(const char *plaintext, size_t plaintext_len, const char *key, size_t keylen, int encode, int mode, size_t *ciphertext_len) {
EVP_CIPHER_CTX *ctx;
EVP_CIPHER *cipher = NULL;
int bytes_written = 0;
unsigned char *ciphertext = NULL;
unsigned char *iv_ciphertext = NULL;
unsigned char *iv = NULL;
size_t block_size = 0;
*ciphertext_len = 0;
if (plaintext_len == 0 || keylen == 0 || encode < 0 || encode > 1)
return NULL;
block_size = plaintext_len;
if (mode == EVP_CIPH_CBC_MODE) {
if (encode == 1) {
iv = (unsigned char *) g_malloc0(8);
RAND_bytes(iv, 8);
} else {
if (plaintext_len <= 8) /* IV + DATA */
return NULL;
iv = (unsigned char *) plaintext;
block_size -= 8;
plaintext += 8;
plaintext_len -= 8;
}
cipher = (EVP_CIPHER *) EVP_bf_cbc();
} else if (mode == EVP_CIPH_ECB_MODE) {
cipher = (EVP_CIPHER *) EVP_bf_ecb();
}
/* Zero Padding */
if (block_size % 8 != 0) {
block_size = block_size + 8 - (block_size % 8);
}
ciphertext = (unsigned char *) g_malloc0(block_size);
memcpy(ciphertext, plaintext, plaintext_len);
/* Create and initialise the context */
if (!(ctx = EVP_CIPHER_CTX_new()))
return NULL;
/* Initialise the cipher operation only with mode */
if (!EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, encode))
return NULL;
/* Set custom key length */
if (!EVP_CIPHER_CTX_set_key_length(ctx, keylen))
return NULL;
/* Finish the initiation the cipher operation */
if (1 != EVP_CipherInit_ex(ctx, NULL, NULL, (const unsigned char *) key, iv, encode))
return NULL;
/* We will manage this */
EVP_CIPHER_CTX_set_padding(ctx, 0);
/* Do cipher operation */
if (1 != EVP_CipherUpdate(ctx, ciphertext, &bytes_written, ciphertext, block_size))
return NULL;
*ciphertext_len = bytes_written;
/* Finalise the cipher. Further ciphertext bytes may be written at this stage */
if (1 != EVP_CipherFinal_ex(ctx, ciphertext + bytes_written, &bytes_written))
return NULL;
*ciphertext_len += bytes_written;
/* Clean up */
EVP_CIPHER_CTX_free(ctx);
if (mode == EVP_CIPH_CBC_MODE && encode == 1) {
/* Join IV + DATA */
iv_ciphertext = g_malloc0(8 + *ciphertext_len);
memcpy(iv_ciphertext, iv, 8);
memcpy(&iv_ciphertext[8], ciphertext, *ciphertext_len);
*ciphertext_len += 8;
g_free(ciphertext);
g_free(iv);
return (char *) iv_ciphertext;
} else {
return (char *) ciphertext;
}
}
/**
* Return a fish or standard Base64 encoded string with data encrypted
* is binary safe
*
* @param [in] key Bytes of key
* @param [in] keylen Size of key
* @param [in] message Bytes to encrypt
* @param [in] message_len Size of message
* @param [in] mode Chiper mode
* @return Array of char with data encrypted
*/
char *fish_encrypt(const char *key, size_t keylen, const char *message, size_t message_len, enum fish_mode mode) {
size_t ciphertext_len = 0;
char *ciphertext = NULL;
char *b64 = NULL;
if (keylen == 0 || message_len == 0)
return NULL;
ciphertext = fish_cipher(message, message_len, key, keylen, 1, mode, &ciphertext_len);
if (ciphertext == NULL || ciphertext_len == 0)
return NULL;
switch (mode) {
case FISH_CBC_MODE:
b64 = g_base64_encode((const unsigned char *) ciphertext, ciphertext_len);
break;
case FISH_ECB_MODE:
b64 = fish_base64_encode((const char *) ciphertext, ciphertext_len);
}
g_free(ciphertext);
if (b64 == NULL)
return NULL;
return b64;
}
/**
* Return an array of bytes with data decrypted
* is binary safe
*
* @param [in] key Bytes of key
* @param [in] keylen Size of key
* @param [in] data Fish or standard Base64 encoded string
* @param [in] mode Chiper mode
* @param [out] final_len Length of returned array
* @return Array of char with data decrypted
*/
char *fish_decrypt(const char *key, size_t keylen, const char *data, enum fish_mode mode, size_t *final_len) {
size_t ciphertext_len = 0;
char *ciphertext = NULL;
char *plaintext = NULL;
*final_len = 0;
if (keylen == 0 || strlen(data) == 0)
return NULL;
switch (mode) {
case FISH_CBC_MODE:
if (strspn(data, base64_chars) != strlen(data))
return NULL;
ciphertext = (char *) g_base64_decode(data, &ciphertext_len);
break;
case FISH_ECB_MODE:
ciphertext = fish_base64_decode(data, &ciphertext_len);
}
if (ciphertext == NULL || ciphertext_len == 0)
return NULL;
plaintext = fish_cipher(ciphertext, ciphertext_len, key, keylen, 0, mode, final_len);
g_free(ciphertext);
if (*final_len == 0)
return NULL;
return plaintext;
}
/**
* Similar to fish_decrypt, but pad with zeros any after the first zero in the decrypted data
*
* @param [in] key Bytes of key
* @param [in] keylen Size of key
* @param [in] data Fish or standard Base64 encoded string
* @param [in] mode Chiper mode
* @return Array of char with data decrypted
*/
char *fish_decrypt_str(const char *key, size_t keylen, const char *data, enum fish_mode mode) {
char *decrypted = NULL;
char *plaintext_str = NULL;
size_t decrypted_len = 0;
decrypted = fish_decrypt(key, strlen(key), data, mode, &decrypted_len);
if (decrypted == NULL || decrypted_len == 0)
return NULL;
plaintext_str = g_strndup(decrypted, decrypted_len);
g_free(decrypted);
return plaintext_str;
} }
/** /**
* Encrypts a message (see fish_decrypt). The key is searched for in the * Encrypts a message (see fish_decrypt). The key is searched for in the
* key store. * key store.
*/ */
char *fish_encrypt_for_nick(const char *nick, const char *data) { char *fish_encrypt_for_nick(const char *nick, const char *data, enum fish_mode *omode) {
char *key; char *key;
char *encrypted; char *encrypted, *encrypted_cbc = NULL;
enum fish_mode mode;
/* Look for key */ /* Look for key */
key = keystore_get_key(nick); key = keystore_get_key(nick, &mode);
if (!key) return NULL; if (!key) return NULL;
*omode = mode;
/* Encrypt */ /* Encrypt */
encrypted = fish_encrypt(key, strlen(key), data); encrypted = fish_encrypt(key, strlen(key), data, strlen(data), mode);
g_free(key); g_free(key);
return encrypted;
if (encrypted == NULL || mode == FISH_ECB_MODE)
return encrypted;
/* Add '*' for CBC */
encrypted_cbc = g_strdup_printf("*%s",encrypted);
g_free(encrypted);
return encrypted_cbc;
} }
/** /**
* Decrypts a message (see fish_decrypt). The key is searched for in the * Decrypts a message (see fish_decrypt). The key is searched for in the
* key store. * key store.
*/ */
char *fish_decrypt_from_nick(const char *nick, const char *data) { char *fish_decrypt_from_nick(const char *nick, const char *data, enum fish_mode *omode) {
char *key; char *key;
char *decrypted; char *decrypted;
enum fish_mode mode;
/* Look for key */ /* Look for key */
key = keystore_get_key(nick); key = keystore_get_key(nick, &mode);
if (!key) return NULL; if (!key) return NULL;
*omode = mode;
if (mode == FISH_CBC_MODE)
++data;
/* Decrypt */ /* Decrypt */
decrypted = fish_decrypt(key, strlen(key), data); decrypted = fish_decrypt_str(key, strlen(key), data, mode);
g_free(key); g_free(key);
return decrypted; return decrypted;
} }

View File

@ -1,6 +1,7 @@
/* /*
Copyright (c) 2010 Samuel Lidén Borell <samuel@kodafritt.se> Copyright (c) 2010 Samuel Lidén Borell <samuel@kodafritt.se>
Copyright (c) 2019 <bakasura@protonmail.ch>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@ -29,10 +30,18 @@
#include <glib.h> #include <glib.h>
char *fish_encrypt(const char *key, size_t keylen, const char *message); enum fish_mode {
char *fish_decrypt(const char *key, size_t keylen, const char *data); FISH_ECB_MODE = 0x1,
char *fish_encrypt_for_nick(const char *nick, const char *data); FISH_CBC_MODE = 0x2
char *fish_decrypt_from_nick(const char *nick, const char *data); };
char *fish_base64_encode(const char *message, size_t message_len);
char *fish_base64_decode(const char *message, size_t *final_len);
char *fish_encrypt(const char *key, size_t keylen, const char *message, size_t message_len, enum fish_mode mode);
char *fish_decrypt(const char *key, size_t keylen, const char *data, enum fish_mode mode, size_t *final_len);
char *fish_decrypt_str(const char *key, size_t keylen, const char *data, enum fish_mode mode);
char *fish_encrypt_for_nick(const char *nick, const char *data, enum fish_mode *omode);
char *fish_decrypt_from_nick(const char *nick, const char *data, enum fish_mode *omode);
#endif #endif

View File

@ -23,6 +23,9 @@
<ClInclude Include="bool.h"> <ClInclude Include="bool.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="dh1080.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="fish.h"> <ClInclude Include="fish.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
@ -37,6 +40,9 @@
</ClInclude> </ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="dh1080.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="fish.c"> <ClCompile Include="fish.c">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>

View File

@ -103,27 +103,55 @@ static gchar *get_nick_value(GKeyFile *keyfile, const char *nick, const char *it
/** /**
* Extracts a key from the key store file. * Extracts a key from the key store file.
*/ */
char *keystore_get_key(const char *nick) { char *keystore_get_key(const char *nick, enum fish_mode *mode) {
GKeyFile *keyfile;
char *escaped_nick;
gchar *value, *key_mode;
int encrypted_mode;
char *password;
char *encrypted;
char *decrypted;
/* Get the key */ /* Get the key */
GKeyFile *keyfile = getConfigFile(); keyfile = getConfigFile();
char *escaped_nick = escape_nickname(nick); escaped_nick = escape_nickname(nick);
gchar *value = get_nick_value(keyfile, escaped_nick, "key"); value = get_nick_value(keyfile, escaped_nick, "key");
key_mode = get_nick_value(keyfile, escaped_nick, "mode");
g_key_file_free(keyfile); g_key_file_free(keyfile);
g_free(escaped_nick); g_free(escaped_nick);
/* Determine cipher mode */
*mode = FISH_ECB_MODE;
if (key_mode) {
if (*key_mode == '1')
*mode = FISH_ECB_MODE;
else if (*key_mode == '2')
*mode = FISH_CBC_MODE;
g_free(key_mode);
}
if (!value) if (!value)
return NULL; return NULL;
if (strncmp(value, "+OK ", 4) != 0) { if (strncmp(value, "+OK ", 4) == 0) {
/* Key is stored in plaintext */
return value;
} else {
/* Key is encrypted */ /* Key is encrypted */
const char *encrypted = value+4; encrypted = (char *) value;
const char *password = get_keystore_password(); encrypted += 4;
char *decrypted = fish_decrypt(password, strlen(password), encrypted);
encrypted_mode = FISH_ECB_MODE;
if (*encrypted == '*') {
++encrypted;
encrypted_mode = FISH_CBC_MODE;
}
password = (char *) get_keystore_password();
decrypted = fish_decrypt_str((const char *) password, strlen(password), (const char *) encrypted, encrypted_mode);
g_free(value); g_free(value);
return decrypted; return decrypted;
} else {
/* Key is stored in plaintext */
return value;
} }
} }
@ -189,7 +217,7 @@ G_GNUC_END_IGNORE_DEPRECATIONS
/** /**
* Sets a key in the key store file. * Sets a key in the key store file.
*/ */
gboolean keystore_store_key(const char *nick, const char *key) { gboolean keystore_store_key(const char *nick, const char *key, enum fish_mode mode) {
const char *password; const char *password;
char *encrypted; char *encrypted;
char *wrapped; char *wrapped;
@ -204,11 +232,11 @@ gboolean keystore_store_key(const char *nick, const char *key) {
password = get_keystore_password(); password = get_keystore_password();
if (password) { if (password) {
/* Encrypt the password */ /* Encrypt the password */
encrypted = fish_encrypt(password, strlen(password), key); encrypted = fish_encrypt(password, strlen(password), key, strlen(key), FISH_CBC_MODE);
if (!encrypted) goto end; if (!encrypted) goto end;
/* Prepend "+OK " */ /* Prepend "+OK " */
wrapped = g_strconcat("+OK ", encrypted, NULL); wrapped = g_strconcat("+OK *", encrypted, NULL);
g_free(encrypted); g_free(encrypted);
/* Store encrypted in file */ /* Store encrypted in file */
@ -218,6 +246,9 @@ gboolean keystore_store_key(const char *nick, const char *key) {
/* Store unencrypted in file */ /* Store unencrypted in file */
g_key_file_set_string(keyfile, escaped_nick, "key", key); g_key_file_set_string(keyfile, escaped_nick, "key", key);
} }
/* Store cipher mode */
g_key_file_set_integer(keyfile, escaped_nick, "mode", mode);
/* Save key store file */ /* Save key store file */
ok = save_keystore(keyfile); ok = save_keystore(keyfile);

View File

@ -28,9 +28,10 @@
#include <stddef.h> #include <stddef.h>
#include <glib.h> #include <glib.h>
#include "fish.h"
char *keystore_get_key(const char *nick); char *keystore_get_key(const char *nick, enum fish_mode *mode);
gboolean keystore_store_key(const char *nick, const char *key); gboolean keystore_store_key(const char *nick, const char *key, enum fish_mode mode);
gboolean keystore_delete_nick(const char *nick); gboolean keystore_delete_nick(const char *nick);
#endif #endif

View File

@ -2,6 +2,9 @@ if not libssl_dep.found()
error('fish plugin requires openssl') error('fish plugin requires openssl')
endif endif
# Run tests
subdir('tests')
fishlim_sources = [ fishlim_sources = [
'dh1080.c', 'dh1080.c',
'fish.c', 'fish.c',

View File

@ -2,6 +2,7 @@
Copyright (c) 2010-2011 Samuel Lidén Borell <samuel@kodafritt.se> Copyright (c) 2010-2011 Samuel Lidén Borell <samuel@kodafritt.se>
Copyright (c) 2015 <the.cypher@gmail.com> Copyright (c) 2015 <the.cypher@gmail.com>
Copyright (c) 2019 <bakasura@protonmail.ch>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@ -30,19 +31,20 @@
#include <string.h> #include <string.h>
#include "hexchat-plugin.h" #include "hexchat-plugin.h"
#define HEXCHAT_MAX_WORDS 32
#include "fish.h" #include "fish.h"
#include "dh1080.h" #include "dh1080.h"
#include "keystore.h" #include "keystore.h"
#include "irc.h" #include "irc.h"
static const char *fish_modes[] = {"", "ECB", "CBC", NULL};
static const char plugin_name[] = "FiSHLiM"; static const char plugin_name[] = "FiSHLiM";
static const char plugin_desc[] = "Encryption plugin for the FiSH protocol. Less is More!"; static const char plugin_desc[] = "Encryption plugin for the FiSH protocol. Less is More!";
static const char plugin_version[] = "0.1.0"; static const char plugin_version[] = "1.0.0";
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_delkey[] = "Usage: DELKEY <nick or #channel>, deletes the key for a channel or nick"; static const char usage_delkey[] = "Usage: DELKEY [<nick or #channel>], deletes the key for a channel or nick";
static const char usage_keyx[] = "Usage: KEYX [<nick>], performs DH1080 key-exchange with <nick>"; static const char usage_keyx[] = "Usage: KEYX [<nick>], performs DH1080 key-exchange with <nick>";
static const char usage_topic[] = "Usage: TOPIC+ <topic>, sets a new encrypted topic for the current channel"; static const char usage_topic[] = "Usage: TOPIC+ <topic>, sets a new encrypted topic for the current channel";
static const char usage_notice[] = "Usage: NOTICE+ <nick or #channel> <notice>"; static const char usage_notice[] = "Usage: NOTICE+ <nick or #channel> <notice>";
@ -53,6 +55,13 @@ static hexchat_plugin *ph;
static GHashTable *pending_exchanges; static GHashTable *pending_exchanges;
/**
* Compare two nicks using the current plugin
*/
int irc_nick_cmp(const char *a, const char *b) {
return hexchat_nickcmp (ph, a, b);
}
/** /**
* Returns the path to the key store file. * Returns the path to the key store file.
*/ */
@ -88,7 +97,7 @@ static hexchat_context *find_context_on_network (const char *name) {
int chan_id = hexchat_list_int(ph, channels, "id"); int chan_id = hexchat_list_int(ph, channels, "id");
const char *chan_name = hexchat_list_str(ph, channels, "channel"); const char *chan_name = hexchat_list_str(ph, channels, "channel");
if (chan_id == id && chan_name && hexchat_nickcmp (ph, chan_name, name) == 0) { if (chan_id == id && chan_name && irc_nick_cmp (chan_name, name) == 0) {
ret = (hexchat_context*)hexchat_list_str(ph, channels, "context"); ret = (hexchat_context*)hexchat_list_str(ph, channels, "context");
break; break;
} }
@ -98,8 +107,109 @@ static hexchat_context *find_context_on_network (const char *name) {
return ret; return ret;
} }
int irc_nick_cmp(const char *a, const char *b) { /**
return hexchat_nickcmp (ph, a, b); * Retrive the prefix character for own nick in current context
* @return @ or + or NULL
*/
char *get_my_own_prefix(void) {
char *result = NULL;
const char *own_nick;
hexchat_list *list;
/* Display message */
own_nick = hexchat_get_info(ph, "nick");
if (!own_nick)
return NULL;
/* Get prefix for own nick if any */
list = hexchat_list_get (ph, "users");
if (list) {
while (hexchat_list_next(ph, list)) {
if (irc_nick_cmp(own_nick, hexchat_list_str(ph, list, "nick")) == 0)
result = g_strdup(hexchat_list_str(ph, list, "prefix"));
}
hexchat_list_free(ph, list);
}
return result;
}
/**
* Try to decrypt the first occurrence of fish message
*
* @param message Message to decrypt
* @param key Key of message
* @return Array of char with decrypted message or NULL. The returned string
* should be freed with g_free() when no longer needed.
*/
char *decrypt_raw_message(const char *message, const char *key) {
const char *prefixes[] = {"+OK ", "mcps ", NULL};
char *start = NULL, *end = NULL;
char *left = NULL, *right = NULL;
char *encrypted = NULL, *decrypted = NULL;
int length = 0;
int index_prefix;
enum fish_mode mode;
GString *message_decrypted;
char *result = NULL;
if (message == NULL || key == NULL)
return NULL;
for (index_prefix = 0; index_prefix < 2; index_prefix++) {
start = g_strstr_len(message, strlen(message), prefixes[index_prefix]);
if (start) {
/* Length ALWAYS will be less that original message
* add '[CBC] ' length */
message_decrypted = g_string_sized_new(strlen(message) + 6);
/* Left part of message */
left = g_strndup(message, start - message);
g_string_append(message_decrypted, left);
g_free(left);
/* Encrypted part */
start += strlen(prefixes[index_prefix]);
end = g_strstr_len(start, strlen(message), " ");
if (end) {
length = end - start;
right = end;
}
if (length > 0) {
encrypted = g_strndup(start, length);
} else {
encrypted = g_strdup(start);
}
decrypted = fish_decrypt_from_nick(key, encrypted, &mode);
g_free(encrypted);
if (decrypted == NULL) {
g_string_free(message_decrypted, TRUE);
return NULL;
}
/* Add encrypted flag */
g_string_append(message_decrypted, "[");
g_string_append(message_decrypted, fish_modes[mode]);
g_string_append(message_decrypted, "] ");
/* Decrypted message */
g_string_append(message_decrypted, decrypted);
g_free(decrypted);
/* Right part of message */
if (right) {
g_string_append(message_decrypted, right);
}
result = message_decrypted->str;
g_string_free(message_decrypted, FALSE);
return result;
}
}
return NULL;
} }
/*static int handle_debug(char *word[], char *word_eol[], void *userdata) { /*static int handle_debug(char *word[], char *word_eol[], void *userdata) {
@ -115,15 +225,24 @@ int irc_nick_cmp(const char *a, const char *b) {
* Called when a message is to be sent. * Called when a message is to be sent.
*/ */
static int handle_outgoing(char *word[], char *word_eol[], void *userdata) { static int handle_outgoing(char *word[], char *word_eol[], void *userdata) {
const char *own_nick; char *prefix;
enum fish_mode mode;
char *message;
/* Encrypt the message if possible */ /* Encrypt the message if possible */
const char *channel = hexchat_get_info(ph, "channel"); const char *channel = hexchat_get_info(ph, "channel");
char *encrypted = fish_encrypt_for_nick(channel, word_eol[1]); char *encrypted = fish_encrypt_for_nick(channel, word_eol[1], &mode);
if (!encrypted) return HEXCHAT_EAT_NONE; if (!encrypted) return HEXCHAT_EAT_NONE;
/* Get prefix for own nick if any */
prefix = get_my_own_prefix();
/* Add encrypted flag */
message = g_strconcat("[", fish_modes[mode], "] ", word_eol[1], NULL);
/* Display message */ /* Display message */
own_nick = hexchat_get_info(ph, "nick"); hexchat_emit_print(ph, "Your Message", hexchat_get_info(ph, "nick"), message, prefix, NULL);
hexchat_emit_print(ph, "Your Message", own_nick, word_eol[1], NULL); g_free(prefix);
g_free(message);
/* Send message */ /* Send message */
hexchat_commandf(ph, "PRIVMSG %s :+OK %s", channel, encrypted); hexchat_commandf(ph, "PRIVMSG %s :+OK %s", channel, encrypted);
@ -139,96 +258,59 @@ static int handle_incoming(char *word[], char *word_eol[], hexchat_event_attrs *
const char *prefix; const char *prefix;
const char *command; const char *command;
const char *recipient; const char *recipient;
const char *encrypted; const char *raw_message = word_eol[1];
const char *peice;
char *sender_nick; char *sender_nick;
char *decrypted; char *decrypted;
size_t w; size_t parameters_offset;
size_t ew;
size_t uw;
char prefix_char = 0;
GString *message; GString *message;
if (!irc_parse_message((const char **)word, &prefix, &command, &w)) if (!irc_parse_message((const char **)word, &prefix, &command, &parameters_offset))
return HEXCHAT_EAT_NONE; return HEXCHAT_EAT_NONE;
/* Topic (command 332) has an extra parameter */ /* Topic (command 332) has an extra parameter */
if (!strcmp(command, "332")) w++; if (!strcmp(command, "332"))
parameters_offset++;
/* Look for encrypted data */
for (ew = w+1; ew < HEXCHAT_MAX_WORDS-1; ew++) { /* Extract sender nick and recipient nick/channel and try to decrypt */
const char *s = (ew == w+1 ? word[ew]+1 : word[ew]); recipient = word[parameters_offset];
if (*s && (s[1] == '+' || s[1] == 'm')) { prefix_char = *(s++); } decrypted = decrypt_raw_message(raw_message, recipient);
else { prefix_char = 0; } if (decrypted == NULL) {
if (strcmp(s, "+OK") == 0 || strcmp(s, "mcps") == 0) goto has_encrypted_data; sender_nick = irc_prefix_get_nick(prefix);
decrypted = decrypt_raw_message(raw_message, sender_nick);
g_free(sender_nick);
} }
return HEXCHAT_EAT_NONE;
has_encrypted_data: ; /* Nothing to decrypt */
/* Extract sender nick and recipient nick/channel */ if (decrypted == NULL)
sender_nick = irc_prefix_get_nick(prefix); return HEXCHAT_EAT_NONE;
recipient = word[w];
/* Build decrypted message */
/* Try to decrypt with these (the keys are searched for in the key store) */
encrypted = word[ew+1]; /* decrypted + 'RECV ' + '@time=YYYY-MM-DDTHH:MM:SS.fffffZ ' */
decrypted = fish_decrypt_from_nick(recipient, encrypted); message = g_string_sized_new (strlen(decrypted) + 5 + 33);
if (!decrypted) decrypted = fish_decrypt_from_nick(sender_nick, encrypted); g_string_append (message, "RECV ");
/* Check for error */
if (!decrypted) goto decrypt_error;
/* Build unecrypted message */
message = g_string_sized_new (100); /* TODO: more accurate estimation of size */
g_string_append (message, "RECV");
if (attrs->server_time_utc) if (attrs->server_time_utc)
{ {
GTimeVal tv = { (glong)attrs->server_time_utc, 0 }; GTimeVal tv = { (glong)attrs->server_time_utc, 0 };
char *timestamp = g_time_val_to_iso8601 (&tv); char *timestamp = g_time_val_to_iso8601 (&tv);
g_string_append (message, " @time="); g_string_append (message, "@time=");
g_string_append (message, timestamp); g_string_append (message, timestamp);
g_string_append (message, " ");
g_free (timestamp); g_free (timestamp);
} }
for (uw = 1; uw < HEXCHAT_MAX_WORDS; uw++) { g_string_append (message, decrypted);
if (word[uw][0] != '\0')
g_string_append_c (message, ' ');
if (uw == ew) {
/* Add the encrypted data */
peice = decrypted;
uw++; /* Skip "OK+" */
if (ew == w+1) {
/* Prefix with colon, which gets stripped out otherwise */
g_string_append_c (message, ':');
}
if (prefix_char) {
g_string_append_c (message, prefix_char);
}
} else {
/* Add unencrypted data (for example, a prefix from a bouncer or bot) */
peice = word[uw];
}
g_string_append (message, peice);
}
g_free(decrypted); g_free(decrypted);
/* Simulate unencrypted message */ /* Fake server message
/* hexchat_printf(ph, "simulating: %s\n", message->str); */ * RECV command will throw this function again, if message have multiple
* encrypted data, we will decrypt all */
hexchat_command(ph, message->str); hexchat_command(ph, message->str);
g_string_free (message, TRUE); g_string_free (message, TRUE);
g_free(sender_nick);
return HEXCHAT_EAT_HEXCHAT;
decrypt_error: return HEXCHAT_EAT_HEXCHAT;
g_free(decrypted);
g_free(sender_nick);
return HEXCHAT_EAT_NONE;
} }
static int handle_keyx_notice(char *word[], char *word_eol[], void *userdata) { static int handle_keyx_notice(char *word[], char *word_eol[], void *userdata) {
@ -236,8 +318,8 @@ static int handle_keyx_notice(char *word[], char *word_eol[], void *userdata) {
const char *dh_pubkey = word[5]; const char *dh_pubkey = word[5];
hexchat_context *query_ctx; hexchat_context *query_ctx;
const char *prefix; const char *prefix;
gboolean cbc;
char *sender, *secret_key, *priv_key = NULL; char *sender, *secret_key, *priv_key = NULL;
enum fish_mode mode = FISH_ECB_MODE;
if (!*dh_message || !*dh_pubkey || strlen(dh_pubkey) != 181) if (!*dh_message || !*dh_pubkey || strlen(dh_pubkey) != 181)
return HEXCHAT_EAT_NONE; return HEXCHAT_EAT_NONE;
@ -248,25 +330,21 @@ static int handle_keyx_notice(char *word[], char *word_eol[], void *userdata) {
sender = irc_prefix_get_nick(prefix); sender = irc_prefix_get_nick(prefix);
query_ctx = find_context_on_network(sender); query_ctx = find_context_on_network(sender);
if (query_ctx) if (query_ctx)
hexchat_set_context(ph, query_ctx); g_assert(hexchat_set_context(ph, query_ctx) == 1);
dh_message++; /* : prefix */ dh_message++; /* : prefix */
if (*dh_message == '+' || *dh_message == '-') if (*dh_message == '+' || *dh_message == '-')
dh_message++; /* identify-msg */ dh_message++; /* identify-msg */
cbc = g_strcmp0 (word[6], "CBC") == 0; if (g_strcmp0 (word[6], "CBC") == 0)
mode = FISH_CBC_MODE;
if (!strcmp(dh_message, "DH1080_INIT")) { if (!strcmp(dh_message, "DH1080_INIT")) {
char *pub_key; char *pub_key;
if (cbc) { hexchat_printf(ph, "Received public key from %s (%s), sending mine...", sender, fish_modes[mode]);
hexchat_print(ph, "Received key exchange for CBC mode which is not supported.");
goto cleanup;
}
hexchat_printf(ph, "Received public key from %s, sending mine...", sender);
if (dh1080_generate_key(&priv_key, &pub_key)) { if (dh1080_generate_key(&priv_key, &pub_key)) {
hexchat_commandf(ph, "quote NOTICE %s :DH1080_FINISH %s", sender, pub_key); hexchat_commandf(ph, "quote NOTICE %s :DH1080_FINISH %s%s", sender, pub_key, (mode == FISH_CBC_MODE) ? " CBC" : "");
g_free(pub_key); g_free(pub_key);
} else { } else {
hexchat_print(ph, "Failed to generate keys"); hexchat_print(ph, "Failed to generate keys");
@ -279,11 +357,6 @@ static int handle_keyx_notice(char *word[], char *word_eol[], void *userdata) {
g_hash_table_steal(pending_exchanges, sender_lower); g_hash_table_steal(pending_exchanges, sender_lower);
g_free(sender_lower); g_free(sender_lower);
if (cbc) {
hexchat_print(ph, "Received key exchange for CBC mode which is not supported.");
goto cleanup;
}
if (!priv_key) { if (!priv_key) {
hexchat_printf(ph, "Received a key exchange response for unknown user: %s", sender); hexchat_printf(ph, "Received a key exchange response for unknown user: %s", sender);
goto cleanup; goto cleanup;
@ -295,8 +368,8 @@ static int handle_keyx_notice(char *word[], char *word_eol[], void *userdata) {
} }
if (dh1080_compute_key(priv_key, dh_pubkey, &secret_key)) { if (dh1080_compute_key(priv_key, dh_pubkey, &secret_key)) {
keystore_store_key(sender, secret_key); keystore_store_key(sender, secret_key, mode);
hexchat_printf(ph, "Stored new key for %s", sender); hexchat_printf(ph, "Stored new key for %s (%s)", sender, fish_modes[mode]);
g_free(secret_key); g_free(secret_key);
} else { } else {
hexchat_print(ph, "Failed to create secret key!"); hexchat_print(ph, "Failed to create secret key!");
@ -314,6 +387,7 @@ cleanup:
static int handle_setkey(char *word[], char *word_eol[], void *userdata) { static int handle_setkey(char *word[], char *word_eol[], void *userdata) {
const char *nick; const char *nick;
const char *key; const char *key;
enum fish_mode mode;
/* Check syntax */ /* Check syntax */
if (*word[2] == '\0') { if (*word[2] == '\0') {
@ -331,9 +405,17 @@ static int handle_setkey(char *word[], char *word_eol[], void *userdata) {
key = word_eol[3]; key = word_eol[3];
} }
mode = FISH_ECB_MODE;
if (g_ascii_strncasecmp("cbc:", key, 4) == 0) {
key = key+4;
mode = FISH_CBC_MODE;
} else if (g_ascii_strncasecmp("ecb:", key, 4) == 0) {
key = key+4;
}
/* Set password */ /* Set password */
if (keystore_store_key(nick, key)) { if (keystore_store_key(nick, key, mode)) {
hexchat_printf(ph, "Stored key for %s\n", nick); hexchat_printf(ph, "Stored key for %s (%s)\n", nick, fish_modes[mode]);
} else { } else {
hexchat_printf(ph, "\00305Failed to store key in addon_fishlim.conf\n"); hexchat_printf(ph, "\00305Failed to store key in addon_fishlim.conf\n");
} }
@ -345,22 +427,30 @@ static int handle_setkey(char *word[], char *word_eol[], void *userdata) {
* Command handler for /delkey * Command handler for /delkey
*/ */
static int handle_delkey(char *word[], char *word_eol[], void *userdata) { static int handle_delkey(char *word[], char *word_eol[], void *userdata) {
const char *nick; char *nick = NULL;
int ctx_type = 0;
/* Check syntax */ /* Delete key from input */
if (*word[2] == '\0' || *word[3] != '\0') { if (*word[2] != '\0') {
hexchat_printf(ph, "%s\n", usage_delkey); nick = g_strstrip(g_strdup(word_eol[2]));
return HEXCHAT_EAT_HEXCHAT; } else { /* Delete key from current context */
nick = g_strdup(hexchat_get_info(ph, "channel"));
ctx_type = hexchat_list_int(ph, NULL, "type");
/* Only allow channel or dialog */
if (ctx_type < 2 || ctx_type > 3) {
hexchat_printf(ph, "%s\n", usage_delkey);
return HEXCHAT_EAT_HEXCHAT;
}
} }
nick = g_strstrip (word_eol[2]);
/* Delete the given nick from the key store */ /* Delete the given nick from the key store */
if (keystore_delete_nick(nick)) { if (keystore_delete_nick(nick)) {
hexchat_printf(ph, "Deleted key for %s\n", nick); hexchat_printf(ph, "Deleted key for %s\n", nick);
} else { } else {
hexchat_printf(ph, "\00305Failed to delete key in addon_fishlim.conf!\n"); hexchat_printf(ph, "\00305Failed to delete key in addon_fishlim.conf!\n");
} }
g_free(nick);
return HEXCHAT_EAT_HEXCHAT; return HEXCHAT_EAT_HEXCHAT;
} }
@ -379,7 +469,7 @@ static int handle_keyx(char *word[], char *word_eol[], void *userdata) {
} }
if (query_ctx) { if (query_ctx) {
hexchat_set_context(ph, query_ctx); g_assert(hexchat_set_context(ph, query_ctx) == 1);
ctx_type = hexchat_list_int(ph, NULL, "type"); ctx_type = hexchat_list_int(ph, NULL, "type");
} }
@ -391,8 +481,8 @@ static int handle_keyx(char *word[], char *word_eol[], void *userdata) {
if (dh1080_generate_key(&priv_key, &pub_key)) { if (dh1080_generate_key(&priv_key, &pub_key)) {
g_hash_table_replace (pending_exchanges, g_ascii_strdown(target, -1), priv_key); g_hash_table_replace (pending_exchanges, g_ascii_strdown(target, -1), priv_key);
hexchat_commandf(ph, "quote NOTICE %s :DH1080_INIT %s", target, pub_key); hexchat_commandf(ph, "quote NOTICE %s :DH1080_INIT %s CBC", target, pub_key);
hexchat_printf(ph, "Sent public key to %s, waiting for reply...", target); hexchat_printf(ph, "Sent public key to %s (CBC), waiting for reply...", target);
g_free(pub_key); g_free(pub_key);
} else { } else {
@ -409,6 +499,7 @@ static int handle_crypt_topic(char *word[], char *word_eol[], void *userdata) {
const char *target; const char *target;
const char *topic = word_eol[2]; const char *topic = word_eol[2];
char *buf; char *buf;
enum fish_mode mode;
if (!*topic) { if (!*topic) {
hexchat_print(ph, usage_topic); hexchat_print(ph, usage_topic);
@ -421,7 +512,7 @@ static int handle_crypt_topic(char *word[], char *word_eol[], void *userdata) {
} }
target = hexchat_get_info(ph, "channel"); target = hexchat_get_info(ph, "channel");
buf = fish_encrypt_for_nick(target, topic); buf = fish_encrypt_for_nick(target, topic, &mode);
if (buf == NULL) { if (buf == NULL) {
hexchat_printf(ph, "/topic+ error, no key found for %s", target); hexchat_printf(ph, "/topic+ error, no key found for %s", target);
return HEXCHAT_EAT_ALL; return HEXCHAT_EAT_ALL;
@ -439,21 +530,25 @@ static int handle_crypt_notice(char *word[], char *word_eol[], void *userdata)
{ {
const char *target = word[2]; const char *target = word[2];
const char *notice = word_eol[3]; const char *notice = word_eol[3];
char *notice_flag;
char *buf; char *buf;
enum fish_mode mode;
if (!*target || !*notice) { if (!*target || !*notice) {
hexchat_print(ph, usage_notice); hexchat_print(ph, usage_notice);
return HEXCHAT_EAT_ALL; return HEXCHAT_EAT_ALL;
} }
buf = fish_encrypt_for_nick(target, notice); buf = fish_encrypt_for_nick(target, notice, &mode);
if (buf == NULL) { if (buf == NULL) {
hexchat_printf(ph, "/notice+ error, no key found for %s.", target); hexchat_printf(ph, "/notice+ error, no key found for %s.", target);
return HEXCHAT_EAT_ALL; return HEXCHAT_EAT_ALL;
} }
hexchat_commandf(ph, "quote NOTICE %s :+OK %s", target, buf); hexchat_commandf(ph, "quote NOTICE %s :+OK %s", target, buf);
hexchat_emit_print(ph, "Notice Sent", target, notice); notice_flag = g_strconcat("[", fish_modes[mode], "] ", notice, NULL);
hexchat_emit_print(ph, "Notice Send", target, notice_flag);
g_free(notice_flag);
g_free(buf); g_free(buf);
return HEXCHAT_EAT_ALL; return HEXCHAT_EAT_ALL;
@ -462,19 +557,21 @@ static int handle_crypt_notice(char *word[], char *word_eol[], void *userdata)
/** /**
* Command handler for /msg+ * Command handler for /msg+
*/ */
static int handle_crypt_msg(char *word[], char *word_eol[], void *userdata) static int handle_crypt_msg(char *word[], char *word_eol[], void *userdata) {
{
const char *target = word[2]; const char *target = word[2];
const char *message = word_eol[3]; const char *message = word_eol[3];
char *message_flag;
char *prefix;
hexchat_context *query_ctx; hexchat_context *query_ctx;
char *buf; char *buf;
enum fish_mode mode;
if (!*target || !*message) { if (!*target || !*message) {
hexchat_print(ph, usage_msg); hexchat_print(ph, usage_msg);
return HEXCHAT_EAT_ALL; return HEXCHAT_EAT_ALL;
} }
buf = fish_encrypt_for_nick(target, message); buf = fish_encrypt_for_nick(target, message, &mode);
if (buf == NULL) { if (buf == NULL) {
hexchat_printf(ph, "/msg+ error, no key found for %s", target); hexchat_printf(ph, "/msg+ error, no key found for %s", target);
return HEXCHAT_EAT_ALL; return HEXCHAT_EAT_ALL;
@ -484,13 +581,17 @@ static int handle_crypt_msg(char *word[], char *word_eol[], void *userdata)
query_ctx = find_context_on_network(target); query_ctx = find_context_on_network(target);
if (query_ctx) { if (query_ctx) {
hexchat_set_context(ph, query_ctx); g_assert(hexchat_set_context(ph, query_ctx) == 1);
/* FIXME: Mode char */ prefix = get_my_own_prefix();
/* Add encrypted flag */
message_flag = g_strconcat("[", fish_modes[mode], "] ", message, NULL);
hexchat_emit_print(ph, "Your Message", hexchat_get_info(ph, "nick"), hexchat_emit_print(ph, "Your Message", hexchat_get_info(ph, "nick"),
message, "", ""); message_flag, prefix, NULL);
} g_free(prefix);
else { g_free(message_flag);
} else {
hexchat_emit_print(ph, "Message Send", target, message); hexchat_emit_print(ph, "Message Send", target, message);
} }
@ -501,8 +602,9 @@ static int handle_crypt_msg(char *word[], char *word_eol[], void *userdata)
static int handle_crypt_me(char *word[], char *word_eol[], void *userdata) { static int handle_crypt_me(char *word[], char *word_eol[], void *userdata) {
const char *channel = hexchat_get_info(ph, "channel"); const char *channel = hexchat_get_info(ph, "channel");
char *buf; char *buf;
enum fish_mode mode;
buf = fish_encrypt_for_nick(channel, word_eol[2]); buf = fish_encrypt_for_nick(channel, word_eol[2], &mode);
if (!buf) if (!buf)
return HEXCHAT_EAT_NONE; return HEXCHAT_EAT_NONE;

View File

@ -0,0 +1,47 @@
/*
Copyright (c) 2010 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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include "../../fish.h"
/**
* Extracts a key from the key store file.
*/
char *keystore_get_key(const char *nick, enum fish_mode *mode) {
return NULL;
}
/**
* Sets a key in the key store file.
*/
gboolean keystore_store_key(const char *nick, const char *key, enum fish_mode mode) {
return NULL;
}
/**
* Deletes a nick from the key store.
*/
gboolean keystore_delete_nick(const char *nick) {
return NULL;
}

View File

@ -0,0 +1,17 @@
subdir('old_version')
fishlim_test_sources = [
'tests.c',
'fake/keystore.c',
'../fish.c',
'../utils.c',
]
fishlim_tests = executable('fishlim_tests', fishlim_test_sources,
dependencies: [libgio_dep, libssl_dep],
link_with : fishlim_old_lib
)
test('Fishlim Tests', fishlim_tests,
timeout: 90
)

View File

@ -0,0 +1,194 @@
/*
Copyright (c) 2010 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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#ifdef __APPLE__
#define __AVAILABILITYMACROS__
#define DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER
#endif
#include <stdlib.h>
#include <string.h>
#include <openssl/blowfish.h>
#include "fish.h"
#define IB 64
static const char fish_base64[64] = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
static const signed char fish_unbase64[256] = {
IB,IB,IB,IB,IB,IB,IB,IB, IB,IB,IB,IB,IB,IB,IB,IB,
IB,IB,IB,IB,IB,IB,IB,IB, IB,IB,IB,IB,IB,IB,IB,IB,
/* ! " # $ % & ' ( ) * + , - . / */
IB,IB,IB,IB,IB,IB,IB,IB, IB,IB,IB,IB,IB,IB, 0, 1,
/* 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */
2, 3, 4, 5, 6, 7, 8, 9, 10,11,IB,IB,IB,IB,IB,IB,
/* @ A B C D E F G H I J K L M N O */
IB,38,39,40,41,42,43,44, 45,46,47,48,49,50,51,52,
/* P Q R S T U V W X Y Z [ \ ] ^ _*/
53,54,55,56,57,58,59,60, 61,62,63,IB,IB,IB,IB,IB,
/* ` a b c d e f g h i j k l m n o */
IB,12,13,14,15,16,17,18, 19,20,21,22,23,24,25,26,
/* p q r s t u v w x y z { | } ~ <del> */
27,28,29,30,31,32,33,34, 35,36,37,IB,IB,IB,IB,IB,
};
#define GET_BYTES(dest, source) do { \
*((dest)++) = ((source) >> 24) & 0xFF; \
*((dest)++) = ((source) >> 16) & 0xFF; \
*((dest)++) = ((source) >> 8) & 0xFF; \
*((dest)++) = (source) & 0xFF; \
} while (0);
char *__old_fish_encrypt(const char *key, size_t keylen, const char *message) {
BF_KEY bfkey;
size_t messagelen;
size_t i;
int j;
char *encrypted;
char *end;
unsigned char bit;
unsigned char word;
unsigned char d;
BF_set_key(&bfkey, keylen, (const unsigned char*)key);
messagelen = strlen(message);
if (messagelen == 0) return NULL;
encrypted = g_malloc(((messagelen - 1) / 8) * 12 + 12 + 1); /* each 8-byte block becomes 12 bytes */
end = encrypted;
while (*message) {
/* Read 8 bytes (a Blowfish block) */
BF_LONG binary[2] = { 0, 0 };
unsigned char c;
for (i = 0; i < 8; i++) {
c = message[i];
binary[i >> 2] |= c << 8*(3 - (i&3));
if (c == '\0') break;
}
message += 8;
/* Encrypt block */
BF_encrypt(binary, &bfkey);
/* Emit FiSH-BASE64 */
bit = 0;
word = 1;
for (j = 0; j < 12; j++) {
d = fish_base64[(binary[word] >> bit) & 63];
*(end++) = d;
bit += 6;
if (j == 5) {
bit = 0;
word = 0;
}
}
/* Stop if a null terminator was found */
if (c == '\0') break;
}
*end = '\0';
return encrypted;
}
char *__old_fish_decrypt(const char *key, size_t keylen, const char *data) {
BF_KEY bfkey;
size_t i;
char *decrypted;
char *end;
unsigned char bit;
unsigned char word;
unsigned char d;
BF_set_key(&bfkey, keylen, (const unsigned char*)key);
decrypted = g_malloc(strlen(data) + 1);
end = decrypted;
while (*data) {
/* Convert from FiSH-BASE64 */
BF_LONG binary[2] = { 0, 0 };
bit = 0;
word = 1;
for (i = 0; i < 12; i++) {
d = fish_unbase64[(const unsigned char)*(data++)];
if (d == IB) goto decrypt_end;
binary[word] |= (unsigned long)d << bit;
bit += 6;
if (i == 5) {
bit = 0;
word = 0;
}
}
/* Decrypt block */
BF_decrypt(binary, &bfkey);
/* Copy to buffer */
GET_BYTES(end, binary[0]);
GET_BYTES(end, binary[1]);
}
decrypt_end:
*end = '\0';
return decrypted;
}
/**
* Encrypts a message (see __old_fish_decrypt). The key is searched for in the
* key store.
*/
char *__old_fish_encrypt_for_nick(const char *nick, const char *data) {
char *key;
char *encrypted;
/* Look for key */
/*key = keystore_get_key(nick);*/
if (!key) return NULL;
/* Encrypt */
encrypted = __old_fish_encrypt(key, strlen(key), data);
g_free(key);
return encrypted;
}
/**
* Decrypts a message (see __old_fish_decrypt). The key is searched for in the
* key store.
*/
char *__old_fish_decrypt_from_nick(const char *nick, const char *data) {
char *key;
char *decrypted;
/* Look for key */
/*key = keystore_get_key(nick);*/
if (!key) return NULL;
/* Decrypt */
decrypted = __old_fish_decrypt(key, strlen(key), data);
g_free(key);
return decrypted;
}

View File

@ -0,0 +1,39 @@
/*
Copyright (c) 2010 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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#ifndef FISH_OLD_H
#define FISH_OLD_H
#include <stddef.h>
#include <glib.h>
char *__old_fish_encrypt(const char *key, size_t keylen, const char *message);
char *__old_fish_decrypt(const char *key, size_t keylen, const char *data);
char *__old_fish_encrypt_for_nick(const char *nick, const char *data);
char *__old_fish_decrypt_from_nick(const char *nick, const char *data);
#endif

View File

@ -0,0 +1,4 @@
fishlim_old_lib = shared_library('fishlim_old_lib',
['fish.c'],
dependencies: [libgio_dep, libssl_dep],
)

View File

@ -0,0 +1,248 @@
/*
Copyright (c) 2020 <bakasura@protonmail.ch>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#ifndef PLUGIN_HEXCHAT_FISHLIM_TEST_H
#define PLUGIN_HEXCHAT_FISHLIM_TEST_H
// Libs
#include <stdio.h>
#include <glib.h>
// Project Libs
#include "../fish.h"
#include "../utils.h"
#include "old_version/fish.h"
/**
* Auxiliary function: Generate a random string
* @param out Preallocated string to fill
* @param len Size of bytes to fill
*/
void random_string(char *out, size_t len) {
GRand *rand = NULL;
int i = 0;
rand = g_rand_new();
for (i = 0; i < len; ++i) {
out[i] = g_rand_int_range(rand, 1, 256);
}
out[len] = 0;
g_rand_free(rand);
}
/**
* Check encrypt and decrypt in ECB mode and compare with old implementation
*/
void __ecb() {
char *bo64 = NULL, *b64 = NULL;
char *deo = NULL, *de = NULL;
int key_len, message_len = 0;
char key[57];
char message[1000];
/* Generate key 32448 bits (Yes, I start with 8 bits) */
for (key_len = 1; key_len < 57; ++key_len) {
random_string(key, key_len);
for (message_len = 1; message_len < 1000; ++message_len) {
random_string(message, message_len);
/* Encrypt */
bo64 = __old_fish_encrypt(key, key_len, message);
g_assert_nonnull(bo64);
b64 = fish_encrypt(key, key_len, message, message_len, FISH_ECB_MODE);
g_assert_nonnull(b64);
g_assert_cmpuint(g_strcmp0(b64, bo64), == , 0);
/* Decrypt */
/* Linear */
deo = __old_fish_decrypt(key, key_len, bo64);
de = fish_decrypt_str(key, key_len, b64, FISH_ECB_MODE);
g_assert_nonnull(deo);
g_assert_nonnull(de);
g_assert_cmpuint(g_strcmp0(de, message), == , 0);
g_assert_cmpuint(g_strcmp0(deo, message), == , 0);
g_assert_cmpuint(g_strcmp0(de, deo), == , 0);
g_free(deo);
g_free(de);
/* Mixed */
deo = __old_fish_decrypt(key, key_len, b64);
de = fish_decrypt_str(key, key_len, bo64, FISH_ECB_MODE);
g_assert_nonnull(deo);
g_assert_nonnull(de);
g_assert_cmpuint(g_strcmp0(de, message), == , 0);
g_assert_cmpuint(g_strcmp0(deo, message), == , 0);
g_assert_cmpuint(g_strcmp0(de, deo), == , 0);
g_free(deo);
g_free(de);
/* Free */
g_free(bo64);
g_free(b64);
}
}
}
/**
* Check encrypt and decrypt in CBC mode
*/
void __cbc() {
char *b64 = NULL;
char *de = NULL;
int key_len, message_len = 0;
char key[57];
char message[1000];
/* Generate key 32448 bits (Yes, I start with 8 bits) */
for (key_len = 1; key_len < 57; ++key_len) {
random_string(key, key_len);
for (message_len = 1; message_len < 1000; ++message_len) {
random_string(message, message_len);
/* Encrypt */
b64 = fish_encrypt(key, key_len, message, message_len, FISH_CBC_MODE);
g_assert_nonnull(b64);
/* Decrypt */
/* Linear */
de = fish_decrypt_str(key, key_len, b64, FISH_CBC_MODE);
g_assert_nonnull(de);
g_assert_cmpuint(g_strcmp0(de, message), == , 0);
g_free(de);
/* Free */
g_free(b64);
}
}
}
/**
* Check the calculation of final length from an encoded string in Base64
*/
void __base64_len() {
char *b64 = NULL;
int i, message_len = 0;
char message[1000];
for (i = 0; i < 10; ++i) {
for (message_len = 1; message_len < 1000; ++message_len) {
random_string(message, message_len);
b64 = g_base64_encode((const unsigned char *) message, message_len);
g_assert_nonnull(b64);
g_assert_cmpuint(strlen(b64), == , base64_len(message_len));
g_free(b64);
}
}
}
/**
* Check the calculation of final length from an encoded string in BlowcryptBase64
*/
void __base64_fish_len() {
char *b64 = NULL;
int i, message_len = 0;
char message[1000];
for (i = 0; i < 10; ++i) {
for (message_len = 1; message_len < 1000; ++message_len) {
random_string(message, message_len);
b64 = fish_base64_encode(message, message_len);
g_assert_nonnull(b64);
g_assert_cmpuint(strlen(b64), == , base64_fish_len(message_len));
g_free(b64);
}
}
}
/**
* Check the calculation of final length from an encrypted string in ECB mode
*/
void __base64_ecb_len() {
char *b64 = NULL;
int key_len, message_len = 0;
char key[57];
char message[1000];
/* Generate key 32448 bits (Yes, I start with 8 bits) */
for (key_len = 1; key_len < 57; ++key_len) {
random_string(key, key_len);
for (message_len = 1; message_len < 1000; ++message_len) {
random_string(message, message_len);
b64 = fish_encrypt(key, key_len, message, message_len, FISH_ECB_MODE);
g_assert_nonnull(b64);
g_assert_cmpuint(strlen(b64), == , ecb_len(message_len));
g_free(b64);
}
}
}
/**
* Check the calculation of final length from an encrypted string in CBC mode
*/
void __base64_cbc_len() {
char *b64 = NULL;
int key_len, message_len = 0;
char key[57];
char message[1000];
/* Generate key 32448 bits (Yes, I start with 8 bits) */
for (key_len = 1; key_len < 57; ++key_len) {
random_string(key, key_len);
for (message_len = 1; message_len < 1000; ++message_len) {
random_string(message, message_len);
b64 = fish_encrypt(key, key_len, message, message_len, FISH_CBC_MODE);
g_assert_nonnull(b64);
g_assert_cmpuint(strlen(b64), == , cbc_len(message_len));
g_free(b64);
}
}
}
int main(int argc, char *argv[]) {
g_test_init(&argc, &argv, NULL);
g_test_add_func("/fishlim/__ecb", __ecb);
g_test_add_func("/fishlim/__cbc", __ecb);
g_test_add_func("/fishlim/__base64_len", __base64_len);
g_test_add_func("/fishlim/__base64_fish_len", __base64_fish_len);
g_test_add_func("/fishlim/__base64_ecb_len", __base64_ecb_len);
g_test_add_func("/fishlim/__base64_cbc_len", __base64_cbc_len);
return g_test_run();
}
#endif //PLUGIN_HEXCHAT_FISHLIM_TEST_H

70
plugins/fishlim/utils.c Normal file
View File

@ -0,0 +1,70 @@
/*
Copyright (c) 2020 <bakasura@protonmail.ch>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include "utils.h"
/**
* Calculate the length of Base64-encoded string
*
* @param plaintext_len Size of clear text to encode
* @return Size of encoded string
*/
unsigned long base64_len(size_t plaintext_len) {
int length_unpadded = (4 * plaintext_len) / 3;
/* Add padding */
return length_unpadded % 4 != 0 ? length_unpadded + (4 - length_unpadded % 4) : length_unpadded;
}
/**
* Calculate the length of BlowcryptBase64-encoded string
*
* @param plaintext_len Size of clear text to encode
* @return Size of encoded string
*/
unsigned long base64_fish_len(size_t plaintext_len) {
int length_unpadded = (12 * plaintext_len) / 8;
/* Add padding */
return length_unpadded % 12 != 0 ? length_unpadded + (12 - length_unpadded % 12) : length_unpadded;
}
/**
* Calculate the length of fish-encrypted string in CBC mode
*
* @param plaintext_len Size of clear text to encode
* @return Size of encoded string
*/
unsigned long cbc_len(size_t plaintext_len) {
/*IV + DATA + Zero Padding */
return base64_len(8 + (plaintext_len % 8 != 0 ? plaintext_len + 8 - (plaintext_len % 8) : plaintext_len));
}
/**
* Calculate the length of fish-encrypted string in ECB mode
*
* @param plaintext_len Size of clear text to encode
* @return Size of encoded string
*/
unsigned long ecb_len(size_t plaintext_len) {
return base64_fish_len(plaintext_len);
}

35
plugins/fishlim/utils.h Normal file
View File

@ -0,0 +1,35 @@
/*
Copyright (c) 2020 <bakasura@protonmail.ch>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#ifndef PLUGIN_HEXCHAT_FISHLIM_UTILS_H
#define PLUGIN_HEXCHAT_FISHLIM_UTILS_H
#include <stddef.h>
unsigned long base64_len(size_t plaintext_len);
unsigned long base64_fish_len(size_t plaintext_len);
unsigned long cbc_len(size_t plaintext_len);
unsigned long ecb_len(size_t plaintext_len);
#endif