From c5a798beec0f24f6828cd3c43bbefd1e18a9d33a Mon Sep 17 00:00:00 2001 From: Bakasura Date: Mon, 13 Jul 2020 18:27:27 -0500 Subject: FiSHLiM: Support for CBC mode + more commands (#2347) --- plugins/fishlim/fish.c | 455 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 359 insertions(+), 96 deletions(-) (limited to 'plugins/fishlim/fish.c') diff --git a/plugins/fishlim/fish.c b/plugins/fishlim/fish.c index 00ecfbfa..9301d5d5 100644 --- a/plugins/fishlim/fish.c +++ b/plugins/fishlim/fish.c @@ -1,6 +1,7 @@ /* Copyright (c) 2010 Samuel Lidén Borell + Copyright (c) 2019 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 #include #include +#include #include +#include #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; } -- cgit 1.4.1