Unverified Commit c5a798be authored by Bakasura's avatar Bakasura Committed by GitHub
Browse files

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

parent 2f376953
/*
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
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
#endif
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/evp.h>
#include <openssl/blowfish.h>
#include <openssl/rand.h>
#include "keystore.h"
#include "fish.h"
#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] = {
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,
};
/**
* Convert Int to 4 Bytes (Big-endian)
*
* @param int source
* @param char* dest
*/
#define GET_BYTES(dest, source) do { \
*((dest)++) = ((source) >> 24) & 0xFF; \
*((dest)++) = ((source) >> 16) & 0xFF; \
......@@ -60,135 +71,387 @@ static const signed char fish_unbase64[256] = {
*((dest)++) = (source) & 0xFF; \
} 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);
/**
* Encode ECB FiSH Base64
*
* @param [in] message Bytes to encode
* @param [in] message_len Size of bytes to encode
* @return Array of char with encoded string
*/
char *fish_base64_encode(const char *message, size_t message_len) {
BF_LONG left = 0, right = 0;
int i, j;
char *encoded = NULL;
char *end = NULL;
char *msg = NULL;
if (message_len == 0)
return NULL;
/* Each 8-byte block becomes 12 bytes (fish base64 format) and add 1 byte for \0 */
encoded = g_malloc(((message_len - 1) / 8) * 12 + 12 + 1);
end = encoded;
/* Iterate over each 8-byte block (Blowfish block size) */
for (j = 0; j < message_len; j += 8) {
msg = (char *) message;
/* Set left and right longs */
GET_LONG(left, msg);
GET_LONG(right, msg);
char *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;
for (i = 0; i < 6; ++i) {
*end++ = fish_base64[right & 0x3fu];
right = (right >> 6u);
}
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;
}
for (i = 0; i < 6; ++i) {
*end++ = fish_base64[left & 0x3fu];
left = (left >> 6u);
}
/* Stop if a null terminator was found */
if (c == '\0') break;
/* The previous for'd ensure fill all bytes of encoded, we don't need will with zeros */
message += 8;
}
*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) {
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;
}
message_len = strlen(message);
/* Ensure blocks of 12 bytes each one and valid characters */
if (message_len == 0 || message_len % 12 != 0 || strspn(message, fish_base64) != message_len)
return NULL;
/* Each 12 bytes becomes 8-byte block and add 1 byte for \0 */
*final_len = ((message_len - 1) / 12) * 8 + 8 + 1;
(*final_len)--; /* We support binary data */
bytes = (char *) g_malloc0(*final_len);
byt = bytes;
msg = (char *) message;
while (*msg) {
right = 0;
left = 0;
for (i = 0; i < 6; i++) right |= (uint8_t) fish_unbase64[*msg++] << (i * 6u);
for (i = 0; i < 6; i++) left |= (uint8_t) fish_unbase64[*msg++] << (i * 6u);
GET_BYTES(byt, left);
GET_BYTES(byt, right);
}
return bytes;
}
/**
* 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;
}
/* Decrypt block */
BF_decrypt(binary, &bfkey);
/* Copy to buffer */
GET_BYTES(end, binary[0]);
GET_BYTES(end, binary[1]);
cipher = (EVP_CIPHER *) EVP_bf_cbc();
} else if (mode == EVP_CIPH_ECB_MODE) {
cipher = (EVP_CIPHER *) EVP_bf_ecb();
}
decrypt_end:
*end = '\0';
return decrypted;
/* 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
* 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 *encrypted;
char *encrypted, *encrypted_cbc = NULL;
enum fish_mode mode;
/* Look for key */
key = keystore_get_key(nick);
key = keystore_get_key(nick, &mode);
if (!key) return NULL;
*omode = mode;
/* Encrypt */
encrypted = fish_encrypt(key, strlen(key), data);
encrypted = fish_encrypt(key, strlen(key), data, strlen(data), mode);
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
* 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 *decrypted;
enum fish_mode mode;
/* Look for key */
key = keystore_get_key(nick);
key = keystore_get_key(nick, &mode);
if (!key) return NULL;
*omode = mode;
if (mode == FISH_CBC_MODE)
++data;
/* Decrypt */
decrypted = fish_decrypt(key, strlen(key), data);
decrypted = fish_decrypt_str(key, strlen(key), data, mode);
g_free(key);
return decrypted;
}
......
/*
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
of this software and associated documentation files (the "Software"), to deal
......@@ -29,10 +30,18 @@
#include <glib.h>
char *fish_encrypt(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);
enum fish_mode {
FISH_ECB_MODE = 0x1,
FISH_CBC_MODE = 0x2
};
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
......
......@@ -23,6 +23,9 @@
<ClInclude Include="bool.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="dh1080.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="fish.h">
<Filter>Header Files</Filter>
</ClInclude>
......@@ -37,6 +40,9 @@
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="dh1080.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="fish.c">
<Filter>Source Files</Filter>
</ClCompile>
......
......@@ -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.
*/
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 */
GKeyFile *keyfile = getConfigFile();
char *escaped_nick = escape_nickname(nick);
gchar *value = get_nick_value(keyfile, escaped_nick, "key");
keyfile = getConfigFile();
escaped_nick = escape_nickname(nick);
value = get_nick_value(keyfile, escaped_nick, "key");
key_mode = get_nick_value(keyfile, escaped_nick, "mode");
g_key_file_free(keyfile);
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)
return NULL;