/* * wee-secure-config.c - secured data configuration options (file sec.conf) * * Copyright (C) 2013-2020 Sébastien Helleu * * 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 . */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "weechat.h" #include "wee-config-file.h" #include "wee-hashtable.h" #include "wee-secure.h" #include "wee-secure-config.h" #include "wee-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_option *secure_config_crypt_cipher = NULL; struct t_config_option *secure_config_crypt_hash_algo = NULL; struct t_config_option *secure_config_crypt_passphrase_file = NULL; struct t_config_option *secure_config_crypt_salt = NULL; /* * 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 file. * * Returns passphrase read in file (only the first line with max length of * 1024 chars), or NULL if error. * * Note: result must be freed after use. */ char * secure_config_get_passphrase_from_file (const char *filename) { FILE *file; char *passphrase, *filename2, buffer[1024+1], *pos; size_t num_read; passphrase = NULL; filename2 = string_expand_home (filename); if (!filename2) return NULL; file = fopen (filename2, "r"); if (file) { 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); } fclose (file); } free (filename2); return passphrase; } /* * Checks option "sec.crypt.passphrase_file". */ int secure_config_check_crypt_passphrase_file (const void *pointer, void *data, struct t_config_option *option, const char *value) { char *passphrase; /* make C compiler happy */ (void) pointer; (void) data; (void) option; /* empty value is OK in option (no file used for passphrase) */ if (!value || !value[0]) return 1; passphrase = secure_config_get_passphrase_from_file (value); if (passphrase) free (passphrase); else { gui_chat_printf (NULL, _("%sWarning: unable to read passphrase from file " "\"%s\""), gui_chat_prefix[GUI_CHAT_PREFIX_ERROR], value); } return 1; } /* * 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, _("%sError: not possible 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); } /* * 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; /* 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 file is set, use it */ if (CONFIG_STRING(secure_config_crypt_passphrase_file)[0]) secure_passphrase = secure_config_get_passphrase_from_file ( CONFIG_STRING(secure_config_crypt_passphrase_file)); /* 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; } /* 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, secure_hash_algo[CONFIG_INTEGER(secure_config_crypt_hash_algo)], secure_cipher[CONFIG_INTEGER(secure_config_crypt_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; /* make C compiler happy */ (void) hashtable; config_file = (struct t_config_file *)data; buffer = NULL; length_buffer = 0; if (secure_passphrase) { /* encrypt password using passphrase */ rc = secure_encrypt_data ( value, strlen (value) + 1, secure_hash_algo[CONFIG_INTEGER(secure_config_crypt_hash_algo)], secure_cipher[CONFIG_INTEGER(secure_config_crypt_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, _("%sError encrypting 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 () { struct t_config_section *ptr_section; secure_config_file = config_file_new (NULL, SECURE_CONFIG_NAME, &secure_config_reload_cb, NULL, NULL); if (!secure_config_file) return 0; /* crypt */ ptr_section = 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 (!ptr_section) { config_file_free (secure_config_file); secure_config_file = NULL; return 0; } secure_config_crypt_cipher = config_file_new_option ( secure_config_file, ptr_section, "cipher", "integer", 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, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); secure_config_crypt_hash_algo = config_file_new_option ( secure_config_file, ptr_section, "hash_algo", "integer", N_("hash algorithm used to check the decrypted data"), "sha224|sha256|sha384|sha512", 0, 0, "sha256", NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); secure_config_crypt_passphrase_file = config_file_new_option ( secure_config_file, ptr_section, "passphrase_file", "string", N_("path to a file containing the passphrase to encrypt/decrypt secured " "data; this option is used only when reading file sec.conf; only " "first line of file is used; this file is used only if the " "environment variable \"WEECHAT_PASSPHRASE\" is not set (the " "environment variable has higher priority); security note: it is " "recommended to keep this file readable only by you and store it " "outside WeeChat home (for example in your home); example: " "\"~/.weechat-passphrase\""), NULL, 0, 0, "", NULL, 0, &secure_config_check_crypt_passphrase_file, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); secure_config_crypt_salt = config_file_new_option ( secure_config_file, ptr_section, "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, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); /* data */ ptr_section = 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); if (!ptr_section) { config_file_free (secure_config_file); secure_config_file = NULL; return 0; } 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; rc = config_file_read (secure_config_file); 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; }