diff --git a/src/credentials.cpp b/src/credentials.cpp index 29d05fe949ba..f31bd543dc5e 100644 --- a/src/credentials.cpp +++ b/src/credentials.cpp @@ -31,11 +31,23 @@ See the COPYING file for more details. static lg::log_domain log_config("config"); #define ERR_CFG LOG_STREAM(err , log_config) +class secure_data : public std::vector +{ +public: + template secure_data(T&&... args) + : vector(std::forward(args)...) + {} + ~secure_data() + { + std::fill(begin(), end(), '\0'); + } +}; + struct login_info { std::string username, server; - std::vector key; - login_info(const std::string& username, const std::string& server, const std::vector& key) + secure_data key; + login_info(const std::string& username, const std::string& server, const secure_data& key) : username(username), server(server), key(key) {} login_info(const std::string& username, const std::string& server) @@ -52,11 +64,11 @@ static std::vector credentials; // Separate password entries with formfeed static const unsigned char CREDENTIAL_SEPARATOR = '\f'; -static std::vector encrypt(const std::vector& text, const std::vector& key); -static std::vector decrypt(const std::vector& text, const std::vector& key); -static std::vector build_key(const std::string& server, const std::string& login); -static std::vector escape(const std::vector& text); -static std::vector unescape(const std::vector& text); +static secure_data encrypt(const secure_data& text, const secure_data& key); +static secure_data decrypt(const secure_data& text, const secure_data& key); +static secure_data build_key(const std::string& server, const std::string& login); +static secure_data escape(const secure_data& text); +static secure_data unescape(const secure_data& text); static std::string get_system_username() { @@ -84,7 +96,6 @@ static void clear_credentials() for(auto& cred : credentials) { std::fill(cred.username.begin(), cred.username.end(), '\0'); std::fill(cred.server.begin(), cred.server.end(), '\0'); - std::fill(cred.key.begin(), cred.key.end(), '\0'); } credentials.clear(); } @@ -152,11 +163,10 @@ namespace preferences void set_password(const std::string& server, const std::string& login, const std::string& key) { - std::vector temp(key.begin(), key.end()); + secure_data temp(key.begin(), key.end()); if(!remember_password()) { clear_credentials(); credentials.emplace_back(login, server, encrypt(temp, build_key(server, login))); - std::fill(temp.begin(), temp.end(), '\0'); return; } auto cred = std::find_if(credentials.begin(), credentials.end(), [&](const login_info& cred) { @@ -167,7 +177,6 @@ namespace preferences cred = credentials.emplace(credentials.end(), login, server); } cred->key = encrypt(temp, build_key(server, login)); - std::fill(temp.begin(), temp.end(), '\0'); } void load_credentials() @@ -182,22 +191,20 @@ namespace preferences } filesystem::scoped_istream stream = filesystem::istream_file(cred_file, false); // Credentials file is a binary blob, so use streambuf iterator - std::vector data((std::istreambuf_iterator(*stream)), (std::istreambuf_iterator())); + secure_data data((std::istreambuf_iterator(*stream)), (std::istreambuf_iterator())); data = decrypt(data, build_key("global", get_system_username())); if(data.empty() || data[0] != CREDENTIAL_SEPARATOR) { ERR_CFG << "Invalid data in credentials file\n"; - std::fill(data.begin(), data.end(), '\0'); return; } for(const std::string& elem : utils::split(std::string(data.begin(), data.end()), CREDENTIAL_SEPARATOR, utils::REMOVE_EMPTY)) { size_t at = elem.find_last_of('@'); size_t eq = elem.find_first_of('=', at + 1); if(at != std::string::npos && eq != std::string::npos) { - std::vector key(elem.begin() + eq + 1, elem.end()); + secure_data key(elem.begin() + eq + 1, elem.end()); credentials.emplace_back(elem.substr(0, at), elem.substr(at + 1, eq - at - 1), unescape(key)); } } - std::fill(data.begin(), data.end(), '\0'); } void save_credentials() @@ -206,7 +213,7 @@ namespace preferences filesystem::delete_file(filesystem::get_credentials_file()); return; } - std::vector credentials_data(1, CREDENTIAL_SEPARATOR); + secure_data credentials_data(1, CREDENTIAL_SEPARATOR); size_t offset = 1; for(const auto& cred : credentials) { credentials_data.resize(credentials_data.size() + cred.size(), CREDENTIAL_SEPARATOR); @@ -216,7 +223,7 @@ namespace preferences std::copy(cred.server.begin(), cred.server.end(), credentials_data.begin() + offset); offset += cred.server.size(); credentials_data[offset++] = '='; - std::vector key_escaped = escape(cred.key); + secure_data key_escaped = escape(cred.key); // Escaping may increase the length, so resize again if so credentials_data.resize(credentials_data.size() + key_escaped.size() - cred.key.size()); std::copy(key_escaped.begin(), key_escaped.end(), credentials_data.begin() + offset); @@ -224,20 +231,19 @@ namespace preferences } try { filesystem::scoped_ostream credentials_file = filesystem::ostream_file(filesystem::get_credentials_file()); - std::vector encrypted = encrypt(credentials_data, build_key("global", get_system_username())); + secure_data encrypted = encrypt(credentials_data, build_key("global", get_system_username())); credentials_file->write(reinterpret_cast(encrypted.data()), encrypted.size()); } catch(filesystem::io_exception&) { ERR_CFG << "error writing to credentials file '" << filesystem::get_credentials_file() << "'" << std::endl; } - std::fill(credentials_data.begin(), credentials_data.end(), '\0'); } } // TODO: Key-stretching (bcrypt was recommended) -std::vector build_key(const std::string& server, const std::string& login) +secure_data build_key(const std::string& server, const std::string& login) { std::string sysname = get_system_username(); - std::vector result(std::max(server.size() + login.size() + sysname.size(), 32)); + secure_data result(std::max(server.size() + login.size() + sysname.size(), 32)); size_t i = 0; std::generate(result.begin(), result.end(), [&i]() {return 'x' ^ i++;}); std::copy(login.begin(), login.end(), result.begin()); @@ -246,14 +252,14 @@ std::vector build_key(const std::string& server, const std::strin return result; } -static std::vector rc4_crypt(const std::vector& text, const std::vector& key) +static secure_data rc4_crypt(const secure_data& text, const secure_data& key) { RC4_KEY cipher_key; RC4_set_key(&cipher_key, key.size(), key.data()); const size_t block_size = key.size(); const size_t blocks = text.size() / block_size; const size_t extra = text.size() % block_size; - std::vector result(text.size(), '\0'); + secure_data result(text.size(), '\0'); for(size_t i = 0; i < blocks * block_size; i += block_size) { RC4(&cipher_key, block_size, text.data() + i, result.data() + i); } @@ -264,19 +270,19 @@ static std::vector rc4_crypt(const std::vector& te return result; } -std::vector encrypt(const std::vector& text, const std::vector& key) +secure_data encrypt(const secure_data& text, const secure_data& key) { return rc4_crypt(text, key); } -std::vector decrypt(const std::vector& text, const std::vector& key) +secure_data decrypt(const secure_data& text, const secure_data& key) { return rc4_crypt(text, key); } -std::vector unescape(const std::vector& text) +secure_data unescape(const secure_data& text) { - std::vector unescaped; + secure_data unescaped; unescaped.reserve(text.size()); bool escaping = false; for(char c : text) { @@ -299,9 +305,9 @@ std::vector unescape(const std::vector& text) return unescaped; } -std::vector escape(const std::vector& text) +secure_data escape(const secure_data& text) { - std::vector escaped; + secure_data escaped; escaped.reserve(text.size()); for(char c : text) { if(c == '\x1') {