564 lines
16 KiB
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;
|
|
}
|
|
}
|