Use RC4 cipher for encrypting the password data.
Use vectors whenever handling keys or possibly-encrypted data
This commit is contained in:
parent
c9191c9812
commit
894ee43643
1 changed files with 86 additions and 52 deletions
|
@ -20,7 +20,8 @@ See the COPYING file for more details.
|
|||
#include "serialization/string_utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <memory>
|
||||
#include <openssl/rc4.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <boost/range/iterator_range.hpp>
|
||||
|
@ -32,22 +33,30 @@ static lg::log_domain log_config("config");
|
|||
|
||||
struct login_info
|
||||
{
|
||||
std::string username, server, key;
|
||||
login_info(const std::string& username, const std::string& server, const std::string& key)
|
||||
std::string username, server;
|
||||
std::vector<unsigned char> key;
|
||||
login_info(const std::string& username, const std::string& server, const std::vector<unsigned char>& key)
|
||||
: username(username), server(server), key(key)
|
||||
{}
|
||||
login_info(const std::string& username, const std::string& server)
|
||||
: username(username), server(server), key()
|
||||
{}
|
||||
size_t size() const
|
||||
{
|
||||
return 3 + username.size() + server.size() + key.size();
|
||||
}
|
||||
};
|
||||
|
||||
static std::vector<login_info> credentials;
|
||||
|
||||
// Separate password entries with formfeed
|
||||
static const char CREDENTIAL_SEPARATOR = '\f';
|
||||
static const unsigned char CREDENTIAL_SEPARATOR = '\f';
|
||||
|
||||
static std::string encrypt(const std::string& text, const std::string& key);
|
||||
static std::string decrypt(const std::string& text, const std::string& key);
|
||||
static std::string build_key(const std::string& server, const std::string& login);
|
||||
static std::string escape(const std::string& text);
|
||||
static std::string unescape(const std::string& text);
|
||||
static std::vector<unsigned char> encrypt(const std::vector<unsigned char>& text, const std::vector<unsigned char>& key);
|
||||
static std::vector<unsigned char> decrypt(const std::vector<unsigned char>& text, const std::vector<unsigned char>& key);
|
||||
static std::vector<unsigned char> build_key(const std::string& server, const std::string& login);
|
||||
static std::vector<unsigned char> escape(const std::vector<unsigned char>& text);
|
||||
static std::vector<unsigned char> unescape(const std::vector<unsigned char>& text);
|
||||
|
||||
static std::string get_system_username()
|
||||
{
|
||||
|
@ -125,7 +134,8 @@ namespace preferences
|
|||
{
|
||||
if(!remember_password()) {
|
||||
if(!credentials.empty() && credentials[0].username == login && credentials[0].server == server) {
|
||||
return decrypt(credentials[0].key, build_key(server, login));
|
||||
auto temp = decrypt(credentials[0].key, build_key(server, login));
|
||||
return std::string(temp.begin(), temp.end());
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
|
@ -136,14 +146,17 @@ namespace preferences
|
|||
if(cred == credentials.end()) {
|
||||
return "";
|
||||
}
|
||||
return decrypt(unescape(cred->key), build_key(server, login));
|
||||
auto temp = decrypt(cred->key, build_key(server, login));
|
||||
return std::string(temp.begin(), temp.end());
|
||||
}
|
||||
|
||||
void set_password(const std::string& server, const std::string& login, const std::string& key)
|
||||
{
|
||||
std::vector<unsigned char> temp(key.begin(), key.end());
|
||||
if(!remember_password()) {
|
||||
clear_credentials();
|
||||
credentials.emplace_back(login, server, encrypt(key, build_key(server, login)));
|
||||
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) {
|
||||
|
@ -151,9 +164,10 @@ namespace preferences
|
|||
});
|
||||
if(cred == credentials.end()) {
|
||||
// This is equivalent to emplace_back, but also returns the iterator to the new element
|
||||
cred = credentials.emplace(credentials.end(), login, server, "");
|
||||
cred = credentials.emplace(credentials.end(), login, server);
|
||||
}
|
||||
cred->key = escape(encrypt(key, build_key(server, login)));
|
||||
cred->key = encrypt(temp, build_key(server, login));
|
||||
std::fill(temp.begin(), temp.end(), '\0');
|
||||
}
|
||||
|
||||
void load_credentials()
|
||||
|
@ -168,18 +182,19 @@ namespace preferences
|
|||
}
|
||||
filesystem::scoped_istream stream = filesystem::istream_file(cred_file, false);
|
||||
// Credentials file is a binary blob, so use streambuf iterator
|
||||
std::string data((std::istreambuf_iterator<char>(*stream)), (std::istreambuf_iterator<char>()));
|
||||
std::vector<unsigned char> data((std::istreambuf_iterator<char>(*stream)), (std::istreambuf_iterator<char>()));
|
||||
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(data, CREDENTIAL_SEPARATOR, utils::REMOVE_EMPTY)) {
|
||||
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) {
|
||||
credentials.emplace_back(elem.substr(0, at), elem.substr(at + 1, eq - at - 1), elem.substr(eq + 1));
|
||||
std::vector<unsigned char> 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');
|
||||
|
@ -191,61 +206,77 @@ namespace preferences
|
|||
filesystem::delete_file(filesystem::get_credentials_file());
|
||||
return;
|
||||
}
|
||||
std::ostringstream credentials_data;
|
||||
std::vector<unsigned char> credentials_data(1, CREDENTIAL_SEPARATOR);
|
||||
size_t offset = 1;
|
||||
for(const auto& cred : credentials) {
|
||||
credentials_data.put(CREDENTIAL_SEPARATOR);
|
||||
std::copy(cred.username.begin(), cred.username.end(), std::ostreambuf_iterator<char>(credentials_data));
|
||||
credentials_data.put('@');
|
||||
std::copy(cred.server.begin(), cred.server.end(), std::ostreambuf_iterator<char>(credentials_data));
|
||||
credentials_data.put('=');
|
||||
std::copy(cred.key.begin(), cred.key.end(), std::ostreambuf_iterator<char>(credentials_data));
|
||||
credentials_data.resize(credentials_data.size() + cred.size(), CREDENTIAL_SEPARATOR);
|
||||
std::copy(cred.username.begin(), cred.username.end(), credentials_data.begin() + offset);
|
||||
offset += cred.username.size();
|
||||
credentials_data[offset++] = '@';
|
||||
std::copy(cred.server.begin(), cred.server.end(), credentials_data.begin() + offset);
|
||||
offset += cred.server.size();
|
||||
credentials_data[offset++] = '=';
|
||||
std::vector<unsigned char> 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);
|
||||
offset += key_escaped.size() + 1;
|
||||
}
|
||||
credentials_data.put(CREDENTIAL_SEPARATOR);
|
||||
try {
|
||||
filesystem::scoped_ostream credentials_file = filesystem::ostream_file(filesystem::get_credentials_file());
|
||||
std::string encrypted = encrypt(credentials_data.str(), build_key("global", get_system_username()));
|
||||
credentials_file->write(encrypted.c_str(), encrypted.size());
|
||||
std::vector<unsigned char> encrypted = encrypt(credentials_data, build_key("global", get_system_username()));
|
||||
credentials_file->write(reinterpret_cast<const char*>(encrypted.data()), encrypted.size());
|
||||
} catch(filesystem::io_exception&) {
|
||||
ERR_CFG << "error writing to credentials file '" << filesystem::get_credentials_file() << "'" << std::endl;
|
||||
}
|
||||
size_t n = credentials_data.tellp();
|
||||
credentials_data.seekp(0, std::ios::beg);
|
||||
std::fill_n(std::ostreambuf_iterator<char>(credentials_data), n, '\0');
|
||||
std::fill(credentials_data.begin(), credentials_data.end(), '\0');
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Key-stretching (bcrypt was recommended)
|
||||
std::string build_key(const std::string& server, const std::string& login)
|
||||
std::vector<unsigned char> build_key(const std::string& server, const std::string& login)
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << login << get_system_username() << server;
|
||||
return out.str();
|
||||
std::string sysname = get_system_username();
|
||||
std::vector<unsigned char> result(std::max<size_t>(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());
|
||||
std::copy(sysname.begin(), sysname.end(), result.begin() + login.size());
|
||||
std::copy(server.begin(), server.end(), result.begin() + login.size() + sysname.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
// FIXME: XOR encryption is a really terrible choice - swap it out for something better!
|
||||
// TODO: Maybe use cryptopp or something for AES encryption?
|
||||
static std::string xor_crypt(std::string text, const std::string& key)
|
||||
static std::vector<unsigned char> rc4_crypt(const std::vector<unsigned char>& text, const std::vector<unsigned char>& key)
|
||||
{
|
||||
const size_t m = key.size();
|
||||
for(size_t i = 0; i < text.size(); i++) {
|
||||
text[i] ^= key[i % m];
|
||||
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<unsigned char> 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);
|
||||
}
|
||||
return text;
|
||||
if(extra) {
|
||||
size_t i = blocks * block_size;
|
||||
RC4(&cipher_key, extra, text.data() + i, result.data() + i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string encrypt(const std::string& text, const std::string& key)
|
||||
std::vector<unsigned char> encrypt(const std::vector<unsigned char>& text, const std::vector<unsigned char>& key)
|
||||
{
|
||||
return xor_crypt(text, key);
|
||||
return rc4_crypt(text, key);
|
||||
}
|
||||
|
||||
std::string decrypt(const std::string& text, const std::string& key)
|
||||
std::vector<unsigned char> decrypt(const std::vector<unsigned char>& text, const std::vector<unsigned char>& key)
|
||||
{
|
||||
return xor_crypt(text, key);
|
||||
return rc4_crypt(text, key);
|
||||
}
|
||||
|
||||
std::string unescape(const std::string& text)
|
||||
std::vector<unsigned char> unescape(const std::vector<unsigned char>& text)
|
||||
{
|
||||
std::string unescaped;
|
||||
std::vector<unsigned char> unescaped;
|
||||
unescaped.reserve(text.size());
|
||||
bool escaping = false;
|
||||
for(char c : text) {
|
||||
|
@ -268,17 +299,20 @@ std::string unescape(const std::string& text)
|
|||
return unescaped;
|
||||
}
|
||||
|
||||
std::string escape(const std::string& text)
|
||||
std::vector<unsigned char> escape(const std::vector<unsigned char>& text)
|
||||
{
|
||||
std::string escaped;
|
||||
std::vector<unsigned char> escaped;
|
||||
escaped.reserve(text.size());
|
||||
for(char c : text) {
|
||||
if(c == '\x1') {
|
||||
escaped += "\x1\x1";
|
||||
escaped.push_back('\x1');
|
||||
escaped.push_back('\x1');
|
||||
} else if(c == '\xc') {
|
||||
escaped += "\x1\xa";
|
||||
escaped.push_back('\x1');
|
||||
escaped.push_back('\xa');
|
||||
} else if(c == '@') {
|
||||
escaped += "\x1.";
|
||||
escaped.push_back('\x1');
|
||||
escaped.push_back('.');
|
||||
} else {
|
||||
escaped.push_back(c);
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue