summaryrefslogtreecommitdiff
path: root/src/core/core-secure-config.c
diff options
context:
space:
mode:
authorSébastien Helleu <flashcode@flashtux.org>2024-03-12 21:09:42 +0100
committerSébastien Helleu <flashcode@flashtux.org>2024-03-12 21:27:37 +0100
commit24c4029c96fa04b3cb4f90fbb36dc5248dd39810 (patch)
treeca51727f97207117f335f0309c063ffd2f168f2d /src/core/core-secure-config.c
parentbb346f8c6c62655a6ef8fe4bc848d179258ce008 (diff)
downloadweechat-24c4029c96fa04b3cb4f90fbb36dc5248dd39810.zip
core: remove "wee-" prefix from source files in src/core and src/core/hook
Diffstat (limited to 'src/core/core-secure-config.c')
-rw-r--r--src/core/core-secure-config.c702
1 files changed, 702 insertions, 0 deletions
diff --git a/src/core/core-secure-config.c b/src/core/core-secure-config.c
new file mode 100644
index 000000000..908628edf
--- /dev/null
+++ b/src/core/core-secure-config.c
@@ -0,0 +1,702 @@
+/*
+ * core-secure-config.c - secured data configuration options (file sec.conf)
+ *
+ * Copyright (C) 2013-2024 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 <string.h>
+#include <gcrypt.h>
+
+#include "weechat.h"
+#include "core-config-file.h"
+#include "core-crypto.h"
+#include "core-hashtable.h"
+#include "core-secure.h"
+#include "core-secure-config.h"
+#include "core-string.h"
+#include "../gui/gui-chat.h"
+#include "../gui/gui-main.h"
+#include "../gui/gui-window.h"
+#include "../plugins/plugin.h"
+
+struct t_config_file *secure_config_file = NULL;
+
+struct t_config_section *secure_config_section_crypt = NULL;
+struct t_config_section *secure_config_section_data = NULL;
+
+struct t_config_option *secure_config_crypt_cipher = NULL;
+struct t_config_option *secure_config_crypt_hash_algo = NULL;
+struct t_config_option *secure_config_crypt_passphrase_command = NULL;
+struct t_config_option *secure_config_crypt_salt = NULL;
+
+int secure_config_loading = 0;
+
+
+/*
+ * Gets passphrase from user and puts it in variable "secure_passphrase".
+ */
+
+void
+secure_config_get_passphrase_from_user (const char *error)
+{
+ const char *prompt[5];
+ char passphrase[SECURE_PASSPHRASE_MAX_LENGTH + 1];
+
+ prompt[0] = _("Please enter your passphrase to decrypt the data secured "
+ "by WeeChat:");
+ prompt[1] = _("(enter just one space to skip the passphrase, but this "
+ "will DISABLE all secured data!)");
+ prompt[2] = _("(press ctrl-c to exit WeeChat now)");
+ prompt[3] = error;
+ prompt[4] = NULL;
+
+ while (1)
+ {
+ gui_main_get_password (prompt, passphrase, sizeof (passphrase));
+ if (secure_passphrase)
+ {
+ free (secure_passphrase);
+ secure_passphrase = NULL;
+ }
+ if (passphrase[0])
+ {
+ /* the special value " " (one space) disables passphrase */
+ if (strcmp (passphrase, " ") == 0)
+ {
+ gui_chat_printf (NULL,
+ _("To recover your secured data, you can "
+ "use /secure decrypt (see /help secure)"));
+ }
+ else if (strcmp (passphrase, "\x03") == 0)
+ {
+ /* ctrl-c pressed, just exit now */
+ exit (1);
+ }
+ else
+ secure_passphrase = strdup (passphrase);
+ return;
+ }
+ }
+}
+
+/*
+ * Gets passphrase from a command.
+ *
+ * Returns passphrase from command output (only the first line with max length
+ * of SECURE_PASSPHRASE_MAX_LENGTH chars), or NULL if error.
+ *
+ * Note: result must be freed after use.
+ */
+
+char *
+secure_config_get_passphrase_from_command (const char *command)
+{
+ FILE *file;
+ char *passphrase, *pos, buffer[SECURE_PASSPHRASE_MAX_LENGTH + 1];
+ size_t num_read;
+
+ passphrase = NULL;
+
+ file = popen (command, "r");
+ if (!file)
+ return NULL;
+
+ num_read = fread (buffer, 1, sizeof (buffer) - 1, file);
+ if (num_read > 0)
+ {
+ buffer[num_read] = '\0';
+ pos = strchr (buffer, '\r');
+ if (pos)
+ pos[0] = '\0';
+ pos = strchr (buffer, '\n');
+ if (pos)
+ pos[0] = '\0';
+ if (buffer[0])
+ passphrase = strdup (buffer);
+ }
+
+ pclose (file);
+
+ return passphrase;
+}
+
+/*
+ * Reloads secured data configuration file.
+ *
+ * Returns:
+ * WEECHAT_CONFIG_READ_OK: OK
+ * WEECHAT_CONFIG_READ_MEMORY_ERROR: not enough memory
+ * WEECHAT_CONFIG_READ_FILE_NOT_FOUND: file not found
+ */
+
+int
+secure_config_reload_cb (const void *pointer, void *data,
+ struct t_config_file *config_file)
+{
+ /* make C compiler happy */
+ (void) pointer;
+ (void) data;
+
+ if (secure_hashtable_data_encrypted->items_count > 0)
+ {
+ gui_chat_printf (NULL,
+ _("%sUnable to reload file sec.conf because there is "
+ "still encrypted data (use /secure decrypt, see "
+ "/help secure)"),
+ gui_chat_prefix[GUI_CHAT_PREFIX_ERROR]);
+ return WEECHAT_CONFIG_READ_FILE_NOT_FOUND;
+ }
+
+ secure_data_encrypted = 0;
+
+ /* remove all secured data */
+ hashtable_remove_all (secure_hashtable_data);
+
+ return config_file_reload (config_file);
+}
+
+/*
+ * Callback for changes on some options "weechat.crypt.*" (that can not be
+ * changed if there are encrypted data.
+ */
+
+int
+secure_config_check_crypt_option_cb (const void *pointer, void *data,
+ struct t_config_option *option,
+ const char *value)
+{
+ /* make C compiler happy */
+ (void) pointer;
+ (void) data;
+ (void) value;
+
+ /* any value allowed while reading config */
+ if (secure_config_loading)
+ return 1;
+
+ /* no encrypted data => changes allowed */
+ if (secure_hashtable_data_encrypted->items_count == 0)
+ return 1;
+
+ gui_chat_printf (NULL,
+ _("%sOption %s.%s.%s can not be changed because there "
+ "are still encrypted data"),
+ gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
+ option->config_file->name,
+ option->section->name,
+ option->name);
+ return 0;
+}
+
+/*
+ * Reads a data option in secured data configuration file.
+ */
+
+int
+secure_config_data_read_cb (const void *pointer, void *data,
+ struct t_config_file *config_file,
+ struct t_config_section *section,
+ const char *option_name, const char *value)
+{
+ char *buffer, *decrypted, str_error[1024];
+ int length_buffer, length_decrypted, rc, hash_algo, cipher;
+
+ /* make C compiler happy */
+ (void) pointer;
+ (void) data;
+ (void) config_file;
+ (void) section;
+
+ if (!option_name || !value || !value[0])
+ {
+ return WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE;
+ }
+
+ /* special line indicating if a passphrase must be used to decrypt data */
+ if (strcmp (option_name, SECURE_DATA_PASSPHRASE_FLAG) == 0)
+ {
+ secure_data_encrypted = config_file_string_to_boolean (value);
+ if (secure_data_encrypted && !secure_passphrase && !gui_init_ok)
+ {
+ /*
+ * if a passphrase command is set, read passphrase from the output
+ * of the command
+ */
+ if (CONFIG_STRING(secure_config_crypt_passphrase_command)[0])
+ {
+ secure_passphrase = secure_config_get_passphrase_from_command (
+ CONFIG_STRING(secure_config_crypt_passphrase_command));
+ }
+
+ /* ask passphrase to the user (if no file, or file not found) */
+ if (!secure_passphrase)
+ secure_config_get_passphrase_from_user ("");
+ }
+ return WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE;
+ }
+
+ if (!secure_data_encrypted)
+ {
+ /* clear data: just store value in hashtable */
+ hashtable_set (secure_hashtable_data, option_name, value);
+ return WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE;
+ }
+
+ /* check that passphrase is set */
+ if (!secure_passphrase)
+ {
+ gui_chat_printf (NULL,
+ _("%sPassphrase is not set, unable to decrypt data \"%s\""),
+ gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
+ option_name);
+ hashtable_set (secure_hashtable_data_encrypted, option_name, value);
+ return WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE;
+ }
+
+ /* get hash algorithm */
+ hash_algo = weecrypto_get_hash_algo (
+ config_file_option_string (secure_config_crypt_hash_algo));
+ if (hash_algo == GCRY_MD_NONE)
+ {
+ gui_chat_printf (
+ NULL,
+ _("%sFailed to decrypt data \"%s\": hash algorithm \"%s\" is not "
+ "available (ligbcrypt version is too old?)"),
+ gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
+ option_name,
+ config_file_option_string (secure_config_crypt_hash_algo));
+ hashtable_set (secure_hashtable_data_encrypted, option_name, value);
+ return WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE;
+ }
+
+ /* get cipher */
+ cipher = weecrypto_get_cipher (
+ config_file_option_string (secure_config_crypt_cipher));
+ if (cipher == GCRY_CIPHER_NONE)
+ {
+ gui_chat_printf (
+ NULL,
+ _("%sFailed to decrypt data \"%s\": cipher \"%s\" is not "
+ "available (ligbcrypt version is too old?)"),
+ gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
+ option_name,
+ config_file_option_string (secure_config_crypt_cipher));
+ hashtable_set (secure_hashtable_data_encrypted, option_name, value);
+ return WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE;
+ }
+
+ /* decrypt data */
+ buffer = malloc (strlen (value) + 1);
+ if (!buffer)
+ return WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE;
+
+ length_buffer = string_base16_decode (value, buffer);
+ while (1)
+ {
+ decrypted = NULL;
+ length_decrypted = 0;
+ rc = secure_decrypt_data (
+ buffer,
+ length_buffer,
+ hash_algo,
+ cipher,
+ secure_passphrase,
+ &decrypted,
+ &length_decrypted);
+ if (rc == 0)
+ {
+ if (decrypted)
+ {
+ hashtable_set (secure_hashtable_data, option_name,
+ decrypted);
+ free (decrypted);
+ break;
+ }
+ }
+ else
+ {
+ if (decrypted)
+ free (decrypted);
+ if (gui_init_ok)
+ {
+ gui_chat_printf (NULL,
+ _("%sWrong passphrase, unable to decrypt data "
+ "\"%s\""),
+ gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
+ option_name);
+ break;
+ }
+ snprintf (str_error, sizeof (str_error),
+ _("*** Wrong passphrase (decrypt error: %s) ***"),
+ secure_decrypt_error[(rc * -1) - 1]);
+ secure_config_get_passphrase_from_user (str_error);
+ if (!secure_passphrase)
+ {
+ gui_chat_printf (NULL,
+ _("%sPassphrase is not set, unable to decrypt "
+ "data \"%s\""),
+ gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
+ option_name);
+ hashtable_set (secure_hashtable_data_encrypted, option_name,
+ value);
+ break;
+ }
+ }
+ }
+ free (buffer);
+
+ return WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE;
+}
+
+/*
+ * Encrypts data and writes it in secured data configuration file.
+ */
+
+void
+secure_config_data_write_map_cb (void *data,
+ struct t_hashtable *hashtable,
+ const void *key, const void *value)
+{
+ struct t_config_file *config_file;
+ char *buffer, *buffer_base16;
+ int length_buffer, rc, hash_algo, cipher;
+
+ /* make C compiler happy */
+ (void) hashtable;
+
+ config_file = (struct t_config_file *)data;
+
+ hash_algo = weecrypto_get_hash_algo (
+ config_file_option_string (secure_config_crypt_hash_algo));
+ if (hash_algo == GCRY_MD_NONE)
+ {
+ gui_chat_printf (
+ NULL,
+ _("%sFailed to encrypt data \"%s\": hash algorithm \"%s\" is not "
+ "available (ligbcrypt version is too old?)"),
+ gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
+ key,
+ config_file_option_string (secure_config_crypt_hash_algo));
+ return;
+ }
+
+ cipher = weecrypto_get_cipher (
+ config_file_option_string (secure_config_crypt_cipher));
+ if (cipher == GCRY_CIPHER_NONE)
+ {
+ gui_chat_printf (
+ NULL,
+ _("%sFailed to encrypt data \"%s\": cipher \"%s\" is not "
+ "available (ligbcrypt version is too old?)"),
+ gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
+ key,
+ config_file_option_string (secure_config_crypt_cipher));
+ return;
+ }
+
+ buffer = NULL;
+ length_buffer = 0;
+
+ if (secure_passphrase)
+ {
+ /* encrypt password using passphrase */
+ rc = secure_encrypt_data (
+ value, strlen (value) + 1,
+ hash_algo,
+ cipher,
+ secure_passphrase,
+ &buffer,
+ &length_buffer);
+ if (rc == 0)
+ {
+ if (buffer)
+ {
+ buffer_base16 = malloc ((length_buffer * 2) + 1);
+ if (buffer_base16)
+ {
+ if (string_base16_encode (buffer, length_buffer,
+ buffer_base16) >= 0)
+ {
+ config_file_write_line (config_file, key,
+ "\"%s\"", buffer_base16);
+ }
+ free (buffer_base16);
+ }
+ free (buffer);
+ }
+ }
+ else
+ {
+ gui_chat_printf (NULL,
+ _("%sFailed to encrypt data \"%s\" (%d)"),
+ gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
+ key, rc);
+ }
+ }
+ else
+ {
+ /* store password as plain text */
+ config_file_write_line (config_file, key, "\"%s\"", value);
+ }
+}
+
+/*
+ * Writes already encrypted data in secured data configuration file.
+ */
+
+void
+secure_config_data_write_map_encrypted_cb (void *data,
+ struct t_hashtable *hashtable,
+ const void *key, const void *value)
+{
+ struct t_config_file *config_file;
+
+ /* make C compiler happy */
+ (void) hashtable;
+
+ config_file = (struct t_config_file *)data;
+
+ /* store data as-is (it is already encrypted) */
+ config_file_write_line (config_file, key, "\"%s\"", value);
+}
+
+/*
+ * Writes section "data" in secured data configuration file.
+ */
+
+int
+secure_config_data_write_cb (const void *pointer, void *data,
+ struct t_config_file *config_file,
+ const char *section_name)
+{
+ /* make C compiler happy */
+ (void) pointer;
+ (void) data;
+
+ /* write name of section */
+ if (!config_file_write_line (config_file, section_name, NULL))
+ return WEECHAT_CONFIG_WRITE_ERROR;
+
+ if (secure_hashtable_data->items_count > 0)
+ {
+ /*
+ * write a special line indicating if a passphrase must be used to
+ * decrypt data (if not, then data is stored as plain text)
+ */
+ if (!config_file_write_line (config_file,
+ SECURE_DATA_PASSPHRASE_FLAG,
+ (secure_passphrase) ? "on" : "off"))
+ {
+ return WEECHAT_CONFIG_WRITE_ERROR;
+ }
+ /* encrypt and write secured data */
+ hashtable_map (secure_hashtable_data,
+ &secure_config_data_write_map_cb, config_file);
+ }
+ else if (secure_hashtable_data_encrypted->items_count > 0)
+ {
+ /*
+ * if there is encrypted data, that means passphrase was not set and
+ * we were unable to decrypt => just save the encrypted content
+ * as-is (so that content of sec.conf is not lost)
+ */
+ if (!config_file_write_line (config_file,
+ SECURE_DATA_PASSPHRASE_FLAG, "on"))
+ {
+ return WEECHAT_CONFIG_WRITE_ERROR;
+ }
+ hashtable_map (secure_hashtable_data_encrypted,
+ &secure_config_data_write_map_encrypted_cb, config_file);
+ }
+
+ return WEECHAT_CONFIG_WRITE_OK;
+}
+
+/*
+ * Creates options in secured data configuration.
+ *
+ * Returns:
+ * 1: OK
+ * 0: error
+ */
+
+int
+secure_config_init_options ()
+{
+ secure_config_file = config_file_new (NULL, SECURE_CONFIG_PRIO_NAME,
+ &secure_config_reload_cb, NULL, NULL);
+ if (!secure_config_file)
+ return 0;
+
+ /* crypt */
+ secure_config_section_crypt = config_file_new_section (
+ secure_config_file, "crypt",
+ 0, 0,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL);
+ if (secure_config_section_crypt)
+ {
+ secure_config_crypt_cipher = config_file_new_option (
+ secure_config_file, secure_config_section_crypt,
+ "cipher", "enum",
+ N_("cipher used to crypt data (the number after algorithm is the "
+ "size of the key in bits)"),
+ "aes128|aes192|aes256", 0, 0, "aes256", NULL, 0,
+ &secure_config_check_crypt_option_cb, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL);
+ secure_config_crypt_hash_algo = config_file_new_option (
+ secure_config_file, secure_config_section_crypt,
+ "hash_algo", "enum",
+ N_("hash algorithm used to check the decrypted data; "
+ "some of them require a specific libgcrypt version: "
+ "sha3-*: libgcrypt >= 1.7.0, "
+ "blake2*: libgcrypt >= 1.8.0, "
+ "sha512-*: libgcrypt >= 1.9.4"),
+ "sha224|sha256|sha384|sha512"
+ "|sha512-224|sha512-256"
+ "|sha3-224|sha3-256|sha3-384|sha3-512"
+ "|blake2b-160|blake2b-256|blake2b-384|blake2b-512"
+ "|blake2s-128|blake2s-160|blake2s-224|blake2s-256",
+ 0, 0, "sha256", NULL, 0,
+ &secure_config_check_crypt_option_cb, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL);
+ secure_config_crypt_passphrase_command = config_file_new_option (
+ secure_config_file, secure_config_section_crypt,
+ "passphrase_command", "string",
+ N_("read the passphrase from the output of this system command "
+ "(only the first line is used and it must not contain any extra "
+ "character); this option is used only when reading file "
+ "sec.conf and if the environment variable \"WEECHAT_PASSPHRASE\" "
+ "is not set (the environment variable has higher priority); "
+ "example with password-store: "
+ "\"/usr/bin/pass show weechat/passphrase\""),
+ NULL, 0, 0, "", NULL, 0,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL);
+ secure_config_crypt_salt = config_file_new_option (
+ secure_config_file, secure_config_section_crypt,
+ "salt", "boolean",
+ N_("use salt when generating key used in encryption (recommended "
+ "for maximum security); when enabled, the content of crypted "
+ "data in file sec.conf will be different on each write of the "
+ "file; if you put the file sec.conf in a version control system, "
+ "then you can turn off this option to have always same content "
+ "in file"),
+ NULL, 0, 0, "on", NULL, 0,
+ &secure_config_check_crypt_option_cb, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL);
+ }
+
+ /* data */
+ secure_config_section_data = config_file_new_section (
+ secure_config_file, "data",
+ 0, 0,
+ &secure_config_data_read_cb, NULL, NULL,
+ &secure_config_data_write_cb, NULL, NULL,
+ &secure_config_data_write_cb, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL);
+
+ return 1;
+}
+
+/*
+ * Reads secured data configuration file.
+ *
+ * Returns:
+ * WEECHAT_CONFIG_READ_OK: OK
+ * WEECHAT_CONFIG_READ_MEMORY_ERROR: not enough memory
+ * WEECHAT_CONFIG_READ_FILE_NOT_FOUND: file not found
+ */
+
+int
+secure_config_read ()
+{
+ int rc;
+
+ secure_data_encrypted = 0;
+
+ secure_config_loading = 1;
+ rc = config_file_read (secure_config_file);
+ secure_config_loading = 0;
+
+ return rc;
+}
+
+/*
+ * Writes secured data configuration file.
+ *
+ * Returns:
+ * WEECHAT_CONFIG_WRITE_OK: OK
+ * WEECHAT_CONFIG_WRITE_ERROR: error
+ * WEECHAT_CONFIG_WRITE_MEMORY_ERROR: not enough memory
+ */
+
+int
+secure_config_write ()
+{
+ return config_file_write (secure_config_file);
+}
+
+/*
+ * Initializes secured data configuration.
+ *
+ * Returns:
+ * 1: OK
+ * 0: error
+ */
+
+int
+secure_config_init ()
+{
+ int rc;
+
+ rc = secure_config_init_options ();
+
+ if (!rc)
+ {
+ gui_chat_printf (NULL,
+ _("FATAL: error initializing configuration options"));
+ }
+
+ return rc;
+}
+
+/*
+ * Frees secured data file and variables.
+ */
+
+void
+secure_config_free ()
+{
+ config_file_free (secure_config_file);
+ secure_config_file = NULL;
+}