weechat/src/core/wee-secure.c

564 lines
16 KiB
C

/*
* wee-secure.c - secured data
*
* Copyright (C) 2013-2020 Sébastien Helleu <flashcode@flashtux.org>
*
* This file is part of WeeChat, the extensible chat client.
*
* WeeChat is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* WeeChat is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with WeeChat. If not, see <https://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdlib.h>
#include <stdio.h>
#include <gcrypt.h>
#include "weechat.h"
#include "wee-config-file.h"
#include "wee-crypto.h"
#include "wee-hashtable.h"
#include "wee-secure.h"
#include "wee-secure-config.h"
#include "wee-string.h"
#include "../plugins/plugin.h"
/* the passphrase used to encrypt/decrypt data */
char *secure_passphrase = NULL;
/* decrypted data */
struct t_hashtable *secure_hashtable_data = NULL;
/* data still encrypted (if passphrase not set) */
struct t_hashtable *secure_hashtable_data_encrypted = NULL;
/* hash algorithms */
char *secure_hash_algo_string[] = { "sha224", "sha256", "sha384", "sha512",
NULL };
int secure_hash_algo[] = { GCRY_MD_SHA224, GCRY_MD_SHA256, GCRY_MD_SHA384,
GCRY_MD_SHA512 };
/* ciphers */
char *secure_cipher_string[] = { "aes128", "aes192", "aes256", NULL };
int secure_cipher[] = { GCRY_CIPHER_AES128, GCRY_CIPHER_AES192,
GCRY_CIPHER_AES256 };
char *secure_decrypt_error[] = { "memory", "buffer", "key", "cipher", "setkey",
"decrypt", "hash", "hash mismatch" };
/* used only when reading sec.conf: 1 if flag __passphrase__ is enabled */
int secure_data_encrypted = 0;
/*
* Derives a key from salt + passphrase (using a hash).
*
* Returns:
* 1: OK
* 0: error
*/
int
secure_derive_key (const char *salt, const char *passphrase,
unsigned char *key, int length_key)
{
char *buffer, hash[512 / 8];
int length, length_hash;
if (!salt || !passphrase || !key || (length_key < 1))
return 0;
memset (key, 0, length_key);
length = SECURE_SALT_SIZE + strlen (passphrase);
buffer = malloc (length);
if (!buffer)
return 0;
/* build a buffer with salt + passphrase */
memcpy (buffer, salt, SECURE_SALT_SIZE);
memcpy (buffer + SECURE_SALT_SIZE, passphrase, strlen (passphrase));
/* compute hash of buffer */
if (!weecrypto_hash (buffer, length, GCRY_MD_SHA512, hash, &length_hash))
{
free (buffer);
return 0;
}
/* copy beginning of hash (or full hash) in the key */
memcpy (key, hash,
(length_hash > length_key) ? length_key : length_hash);
free (buffer);
return 1;
}
/*
* Encrypts data using a hash algorithm + cipher + passphrase.
*
* Following actions are performed:
* 1. derive a key from the passphrase (with optional salt)
* 2. compute hash of data
* 3. store hash + data in a buffer
* 4. encrypt the buffer (hash + data), using the key
* 5. return salt + encrypted hash/data
*
* Output buffer has following content:
* - salt (8 bytes, used to derive a key from the passphrase)
* - encrypted hash(data) + data
*
* So it looks like:
*
* +----------+------------+------------------------------+
* | salt | hash | data |
* +----------+------------+------------------------------+
* \_ _ _ _ _/\_ _ _ _ _ _ /\_ _ _ _ _ _ _ _ _ _ _ _ _ _ _/
* 8 bytes N bytes variable length
* \_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _/
* encrypted data
*
* Returns:
* 0: OK
* -1: not enough memory
* -2: key derive error
* -3: compute hash error
* -4: cipher open error
* -5: setkey error
* -6: encrypt error
*/
int
secure_encrypt_data (const char *data, int length_data,
int hash_algo, int cipher, const char *passphrase,
char **encrypted, int *length_encrypted)
{
int rc, length_salt, length_hash, length_hash_data, length_key;
int hd_md_opened, hd_cipher_opened;
gcry_md_hd_t *hd_md;
gcry_cipher_hd_t *hd_cipher;
char salt[SECURE_SALT_SIZE];
unsigned char *ptr_hash, *key, *hash_and_data;
rc = -1;
hd_md = NULL;
hd_md_opened = 0;
hd_cipher = NULL;
hd_cipher_opened = 0;
key = NULL;
hash_and_data = NULL;
hd_md = malloc (sizeof (gcry_md_hd_t));
if (!hd_md)
return -1;
hd_cipher = malloc (sizeof (gcry_cipher_hd_t));
if (!hd_cipher)
{
free (hd_md);
return -1;
}
/* derive a key from the passphrase */
length_key = gcry_cipher_get_algo_keylen (cipher);
key = malloc (length_key);
if (!key)
goto encrypt_end;
if (CONFIG_BOOLEAN(secure_config_crypt_salt))
gcry_randomize (salt, SECURE_SALT_SIZE, GCRY_STRONG_RANDOM);
else
{
length_salt = strlen (SECURE_SALT_DEFAULT);
if (length_salt < SECURE_SALT_SIZE)
memset (salt, 0, SECURE_SALT_SIZE);
memcpy (salt, SECURE_SALT_DEFAULT,
(length_salt <= SECURE_SALT_SIZE) ?
length_salt : SECURE_SALT_SIZE);
}
if (!secure_derive_key (salt, passphrase, key, length_key))
{
rc = -2;
goto encrypt_end;
}
/* compute hash of data */
if (gcry_md_open (hd_md, hash_algo, 0) != 0)
{
rc = -3;
goto encrypt_end;
}
hd_md_opened = 1;
length_hash = gcry_md_get_algo_dlen (hash_algo);
gcry_md_write (*hd_md, data, length_data);
ptr_hash = gcry_md_read (*hd_md, hash_algo);
if (!ptr_hash)
{
rc = -3;
goto encrypt_end;
}
/* build a buffer with hash + data */
length_hash_data = length_hash + length_data;
hash_and_data = malloc (length_hash_data);
if (!hash_and_data)
goto encrypt_end;
memcpy (hash_and_data, ptr_hash, length_hash);
memcpy (hash_and_data + length_hash, data, length_data);
/* encrypt hash + data */
if (gcry_cipher_open (hd_cipher, cipher, GCRY_CIPHER_MODE_CFB, 0) != 0)
{
rc = -4;
goto encrypt_end;
}
hd_cipher_opened = 1;
if (gcry_cipher_setkey (*hd_cipher, key, length_key) != 0)
{
rc = -5;
goto encrypt_end;
}
if (gcry_cipher_encrypt (*hd_cipher, hash_and_data, length_hash_data,
NULL, 0) != 0)
{
rc = -6;
goto encrypt_end;
}
/* create buffer and copy salt + encrypted hash/data into this buffer*/
*length_encrypted = SECURE_SALT_SIZE + length_hash_data;
*encrypted = malloc (*length_encrypted);
if (!*encrypted)
goto encrypt_end;
memcpy (*encrypted, salt, SECURE_SALT_SIZE);
memcpy (*encrypted + SECURE_SALT_SIZE, hash_and_data, length_hash_data);
rc = 0;
encrypt_end:
if (hd_md)
{
if (hd_md_opened)
gcry_md_close (*hd_md);
free (hd_md);
}
if (hd_cipher)
{
if (hd_cipher_opened)
gcry_cipher_close (*hd_cipher);
free (hd_cipher);
}
if (key)
free (key);
if (hash_and_data)
free (hash_and_data);
return rc;
}
/*
* Decrypts data using a hash algorithm + cipher + passphrase.
*
* The buffer must contain:
* - salt (8 bytes, used to derive a key from the passphrase)
* - encrypted hash(data) + data
*
* Following actions are performed:
* 1. check length of buffer (it must have at least salt + hash + some data)
* 2. derive a key from the passphrase using salt (at beginning of buffer)
* 3. decrypt hash + data in a buffer
* 4. compute hash of decrypted data
* 5. check that decrypted hash is equal to hash of data
* 6. return decrypted data
*
* Returns:
* 0: OK
* -1: not enough memory
* -2: buffer is not long enough
* -3: key derive error
* -4: cipher open error
* -5: setkey error
* -6: decrypt error
* -7: compute hash error
* -8: hash does not match the decrypted data
*
* Note: when adding a return code, change the array "secure_decrypt_error"
* accordingly.
*/
int
secure_decrypt_data (const char *buffer, int length_buffer,
int hash_algo, int cipher, const char *passphrase,
char **decrypted, int *length_decrypted)
{
int rc, length_hash, length_key, hd_md_opened, hd_cipher_opened;
gcry_md_hd_t *hd_md;
gcry_cipher_hd_t *hd_cipher;
unsigned char *ptr_hash, *key, *decrypted_hash_data;
rc = -1;
/* check length of buffer */
length_hash = gcry_md_get_algo_dlen (hash_algo);
if (length_buffer <= SECURE_SALT_SIZE + length_hash)
return -2;
hd_md = NULL;
hd_md_opened = 0;
hd_cipher = NULL;
hd_cipher_opened = 0;
key = NULL;
decrypted_hash_data = NULL;
hd_md = malloc (sizeof (gcry_md_hd_t));
if (!hd_md)
return rc;
hd_cipher = malloc (sizeof (gcry_cipher_hd_t));
if (!hd_cipher)
{
free (hd_md);
return rc;
}
/* derive a key from the passphrase */
length_key = gcry_cipher_get_algo_keylen (cipher);
key = malloc (length_key);
if (!key)
goto decrypt_end;
if (!secure_derive_key (buffer, passphrase, key, length_key))
{
rc = -3;
goto decrypt_end;
}
/* decrypt hash + data */
decrypted_hash_data = malloc (length_buffer - SECURE_SALT_SIZE);
if (!decrypted_hash_data)
goto decrypt_end;
if (gcry_cipher_open (hd_cipher, cipher, GCRY_CIPHER_MODE_CFB, 0) != 0)
{
rc = -4;
goto decrypt_end;
}
hd_cipher_opened = 1;
if (gcry_cipher_setkey (*hd_cipher, key, length_key) != 0)
{
rc = -5;
goto decrypt_end;
}
if (gcry_cipher_decrypt (*hd_cipher,
decrypted_hash_data,
length_buffer - SECURE_SALT_SIZE,
buffer + SECURE_SALT_SIZE,
length_buffer - SECURE_SALT_SIZE) != 0)
{
rc = -6;
goto decrypt_end;
}
/* check if hash is OK for decrypted data */
if (gcry_md_open (hd_md, hash_algo, 0) != 0)
{
rc = -7;
goto decrypt_end;
}
hd_md_opened = 1;
gcry_md_write (*hd_md, decrypted_hash_data + length_hash,
length_buffer - SECURE_SALT_SIZE - length_hash);
ptr_hash = gcry_md_read (*hd_md, hash_algo);
if (!ptr_hash)
{
rc = -7;
goto decrypt_end;
}
if (memcmp (ptr_hash, decrypted_hash_data, length_hash) != 0)
{
rc = -8;
goto decrypt_end;
}
/* return the decrypted data */
*length_decrypted = length_buffer - SECURE_SALT_SIZE - length_hash;
*decrypted = malloc (*length_decrypted);
if (!*decrypted)
goto decrypt_end;
memcpy (*decrypted, decrypted_hash_data + length_hash, *length_decrypted);
rc = 0;
decrypt_end:
if (hd_md)
{
if (hd_md_opened)
gcry_md_close (*hd_md);
free (hd_md);
}
if (hd_cipher)
{
if (hd_cipher_opened)
gcry_cipher_close (*hd_cipher);
free (hd_cipher);
}
if (key)
free (key);
if (decrypted_hash_data)
free (decrypted_hash_data);
return rc;
}
/*
* Decrypts data still encrypted (data that could not be decrypted when reading
* secured data configuration file (because no passphrase was given).
*
* Returns:
* > 0: number of decrypted data
* 0: error decrypting data
*/
int
secure_decrypt_data_not_decrypted (const char *passphrase)
{
char **keys, *buffer, *decrypted;
const char *value;
int num_ok, num_keys, i, length_buffer, length_decrypted, rc;
/* we need a passphrase to decrypt data! */
if (!passphrase || !passphrase[0])
return 0;
num_ok = 0;
keys = string_split (hashtable_get_string (secure_hashtable_data_encrypted,
"keys"),
",",
NULL,
WEECHAT_STRING_SPLIT_STRIP_LEFT
| WEECHAT_STRING_SPLIT_STRIP_RIGHT
| WEECHAT_STRING_SPLIT_COLLAPSE_SEPS,
0,
&num_keys);
if (keys)
{
for (i = 0; i < num_keys; i++)
{
value = hashtable_get (secure_hashtable_data_encrypted, keys[i]);
if (value && value[0])
{
buffer = malloc (strlen (value) + 1);
if (buffer)
{
length_buffer = string_base16_decode (value, buffer);
decrypted = NULL;
length_decrypted = 0;
rc = secure_decrypt_data (
buffer,
length_buffer,
secure_hash_algo[CONFIG_INTEGER(secure_config_crypt_hash_algo)],
secure_cipher[CONFIG_INTEGER(secure_config_crypt_cipher)],
passphrase,
&decrypted,
&length_decrypted);
if ((rc == 0) && decrypted)
{
hashtable_set (secure_hashtable_data, keys[i],
decrypted);
hashtable_remove (secure_hashtable_data_encrypted,
keys[i]);
num_ok++;
}
if (decrypted)
free (decrypted);
free (buffer);
}
}
}
string_free_split (keys);
}
return num_ok;
}
/*
* Initializes secured data.
*
* Returns:
* 1: OK
* 0: error
*/
int
secure_init ()
{
char *ptr_phrase;
/* try to read passphrase (if not set) from env var "WEECHAT_PASSPHRASE" */
if (!secure_passphrase)
{
ptr_phrase = getenv (SECURE_ENV_PASSPHRASE);
if (ptr_phrase)
{
if (ptr_phrase[0])
secure_passphrase = strdup (ptr_phrase);
unsetenv (SECURE_ENV_PASSPHRASE);
}
}
secure_hashtable_data = hashtable_new (32,
WEECHAT_HASHTABLE_STRING,
WEECHAT_HASHTABLE_STRING,
NULL, NULL);
if (!secure_hashtable_data)
return 0;
secure_hashtable_data_encrypted = hashtable_new (32,
WEECHAT_HASHTABLE_STRING,
WEECHAT_HASHTABLE_STRING,
NULL, NULL);
if (!secure_hashtable_data_encrypted)
{
hashtable_free (secure_hashtable_data);
return 0;
}
return 1;
}
/*
* Frees all allocated data.
*/
void
secure_end ()
{
if (secure_passphrase)
{
free (secure_passphrase);
secure_passphrase = NULL;
}
if (secure_hashtable_data)
{
hashtable_free (secure_hashtable_data);
secure_hashtable_data = NULL;
}
if (secure_hashtable_data_encrypted)
{
hashtable_free (secure_hashtable_data_encrypted);
secure_hashtable_data_encrypted = NULL;
}
}