Skip to content

Commit

Permalink
Use RAII for all that zero-filling
Browse files Browse the repository at this point in the history
  • Loading branch information
CelticMinstrel committed May 4, 2017
1 parent b93dc47 commit f8d428a
Showing 1 changed file with 35 additions and 29 deletions.
64 changes: 35 additions & 29 deletions src/credentials.cpp
Expand Up @@ -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<unsigned char>
{
public:
template<typename... T> secure_data(T&&... args)
: vector<unsigned char>(std::forward<T>(args)...)
{}
~secure_data()
{
std::fill(begin(), end(), '\0');
}
};

struct login_info
{
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)
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)
Expand All @@ -52,11 +64,11 @@ static std::vector<login_info> credentials;
// Separate password entries with formfeed
static const unsigned char CREDENTIAL_SEPARATOR = '\f';

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 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()
{
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -152,11 +163,10 @@ namespace preferences

void set_password(const std::string& server, const std::string& login, const std::string& key)
{
std::vector<unsigned char> 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) {
Expand All @@ -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()
Expand All @@ -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<unsigned char> data((std::istreambuf_iterator<char>(*stream)), (std::istreambuf_iterator<char>()));
secure_data 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(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<unsigned char> 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()
Expand All @@ -206,7 +213,7 @@ namespace preferences
filesystem::delete_file(filesystem::get_credentials_file());
return;
}
std::vector<unsigned char> 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);
Expand All @@ -216,28 +223,27 @@ namespace preferences
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);
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);
offset += key_escaped.size() + 1;
}
try {
filesystem::scoped_ostream credentials_file = filesystem::ostream_file(filesystem::get_credentials_file());
std::vector<unsigned char> 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<const char*>(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<unsigned char> 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<unsigned char> result(std::max<size_t>(server.size() + login.size() + sysname.size(), 32));
secure_data 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());
Expand All @@ -246,14 +252,14 @@ std::vector<unsigned char> build_key(const std::string& server, const std::strin
return result;
}

static std::vector<unsigned char> rc4_crypt(const std::vector<unsigned char>& text, const std::vector<unsigned char>& 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<unsigned char> 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);
}
Expand All @@ -264,19 +270,19 @@ static std::vector<unsigned char> rc4_crypt(const std::vector<unsigned char>& te
return result;
}

std::vector<unsigned char> encrypt(const std::vector<unsigned char>& text, const std::vector<unsigned char>& key)
secure_data encrypt(const secure_data& text, const secure_data& key)
{
return rc4_crypt(text, key);
}

std::vector<unsigned char> decrypt(const std::vector<unsigned char>& text, const std::vector<unsigned char>& key)
secure_data decrypt(const secure_data& text, const secure_data& key)
{
return rc4_crypt(text, key);
}

std::vector<unsigned char> unescape(const std::vector<unsigned char>& text)
secure_data unescape(const secure_data& text)
{
std::vector<unsigned char> unescaped;
secure_data unescaped;
unescaped.reserve(text.size());
bool escaping = false;
for(char c : text) {
Expand All @@ -299,9 +305,9 @@ std::vector<unsigned char> unescape(const std::vector<unsigned char>& text)
return unescaped;
}

std::vector<unsigned char> escape(const std::vector<unsigned char>& text)
secure_data escape(const secure_data& text)
{
std::vector<unsigned char> escaped;
secure_data escaped;
escaped.reserve(text.size());
for(char c : text) {
if(c == '\x1') {
Expand Down

0 comments on commit f8d428a

Please sign in to comment.