Skip to content

Commit

Permalink
Put bcrypt handling behind an abstraction and improved error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
loonycyborg committed Jul 5, 2017
1 parent c1a5f0b commit 416d63f
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 24 deletions.
28 changes: 13 additions & 15 deletions src/game_initialization/multiplayer.cpp
Expand Up @@ -32,7 +32,6 @@
#include "gui/dialogs/network_transmission.hpp"
#include "gui/widgets/settings.hpp"
#include "hash.hpp"
#include "bcrypt/bcrypt.h"
#include "log.hpp"
#include "multiplayer_error_codes.hpp"
#include "settings.hpp"
Expand All @@ -47,6 +46,7 @@

static lg::log_domain log_mp("mp/main");
#define DBG_MP LOG_STREAM(debug, log_mp)
#define ERR_MP LOG_STREAM(err, log_mp)

/** Opens a new server connection and prompts the client for login credentials, if necessary. */
static wesnothd_connection_ptr open_connection(CVideo& video, const std::string& original_host)
Expand Down Expand Up @@ -226,23 +226,21 @@ static wesnothd_connection_ptr open_connection(CVideo& video, const std::string&
throw wesnothd_error(_("Bad data received from server"));
}

if(salt.compare(0, 3, "$H$") == 0) {
if(utils::md5::is_valid_hash(salt)) {
sp["password"] = utils::md5(utils::md5(password, utils::md5::get_salt(salt),
utils::md5::get_iteration_count(salt)).hex_digest(), salt.substr(12, 8)).hex_digest();
} else if(salt.compare(0, 4, "$2y$") == 0) {
char salt_buf[BCRYPT_HASHSIZE];
char hash_buf[BCRYPT_HASHSIZE];
std::size_t iteration_count_delim_pos = salt.find('$', 4);
if(iteration_count_delim_pos == std::string::npos)
} else if(utils::bcrypt::is_valid_prefix(salt)) {
try {
auto bcrypt_salt { utils::bcrypt::from_salted_salt(salt) };
auto hash { utils::bcrypt::hash_pw(password, bcrypt_salt) };
std::string outer_salt = salt.substr(bcrypt_salt.iteration_count_delim_pos + 23, 8);
if(outer_salt.size() != 8)
throw utils::hash_error("salt too small");
sp["password"] = utils::md5(hash.hex_digest(), outer_salt).hex_digest();
} catch(utils::hash_error& err) {
ERR_MP << "bcrypt hash failed: " << err.what() << std::endl;
throw wesnothd_error(_("Bad data received from server"));
std::string bcrypt_salt = salt.substr(0, iteration_count_delim_pos + 23);
if(bcrypt_salt.size() >= BCRYPT_HASHSIZE)
throw wesnothd_error(_("Bad data received from server"));
strcpy(salt_buf, bcrypt_salt.c_str());
if(bcrypt_hashpw(password.c_str(), salt_buf, hash_buf) != 0)
throw wesnothd_error(_("Bad data received from server"));
std::string outer_salt = salt.substr(iteration_count_delim_pos + 23, 8);
sp["password"] = utils::md5(hash_buf, outer_salt).hex_digest();
}
} else {
throw wesnothd_error(_("Bad data received from server"));
}
Expand Down
61 changes: 61 additions & 0 deletions src/hash.cpp
Expand Up @@ -16,6 +16,8 @@

#include <iostream>
#include <string>
#include <string.h>
#include <assert.h>

#include <openssl/sha.h>
#include <openssl/md5.h>
Expand Down Expand Up @@ -109,4 +111,63 @@ std::string sha1::hex_digest() const
return encode_hash<DIGEST_SIZE>(hash);
}

bcrypt::bcrypt(const std::string& input)
{
assert(is_valid_prefix(input));

iteration_count_delim_pos = input.find('$', 4);
if(iteration_count_delim_pos == std::string::npos)
throw hash_error("hash string malformed");
}

bcrypt bcrypt::from_salted_salt(const std::string& input)
{
bcrypt hash { input };
hash.iteration_count_delim_pos = input.find('$', 4);
if(hash.iteration_count_delim_pos == std::string::npos)
throw hash_error("hash string malformed");
std::string bcrypt_salt = input.substr(0, hash.iteration_count_delim_pos + 23);
if(bcrypt_salt.size() >= BCRYPT_HASHSIZE)
throw hash_error("hash string too large");
strcpy(hash.hash.data(), bcrypt_salt.c_str());

return hash;
}

bcrypt bcrypt::from_hash_string(const std::string& input)
{
bcrypt hash { input };
if(input.size() >= BCRYPT_HASHSIZE)
throw hash_error("hash string too large");
strcpy(hash.hash.data(), input.c_str());

return hash;
}

bcrypt bcrypt::hash_pw(const std::string& password, bcrypt& salt)
{
bcrypt hash;
if(bcrypt_hashpw(password.c_str(), salt.hash.data(), hash.hash.data()) != 0)
throw hash_error("failed to hash password");

return hash;
}

bool bcrypt::is_valid_prefix(const std::string& hash) {
return hash.compare(0, 4, "$2y$") == 0;
}

std::string bcrypt::get_salt() const
{
std::size_t salt_pos = iteration_count_delim_pos + 23;
if(salt_pos >= BCRYPT_HASHSIZE)
throw hash_error("malformed hash");
return std::string(hash.data(), salt_pos);
}

std::string bcrypt::hex_digest() const
{
return std::string(hash.data());
}

} // namespace utils
30 changes: 27 additions & 3 deletions src/hash.hpp
Expand Up @@ -19,24 +19,31 @@
#include <string>

#include "global.hpp"
#include "exceptions.hpp"
#include "bcrypt/bcrypt.h"

namespace utils {

struct hash_error : public game::error
{
hash_error(const std::string& message) : game::error(message) {}
};

class hash_base
{
public:
virtual std::string hex_digest() const = 0;
virtual ~hash_base() {}
};

template<size_t sz>
template<size_t sz, typename T = uint8_t>
class hash_digest : public hash_base
{
protected:
std::array<uint8_t, sz> hash;
std::array<T, sz> hash;
public:
static const int DIGEST_SIZE = sz;
std::array<uint8_t, sz> raw_digest() const {return hash;}
std::array<T, sz> raw_digest() const {return hash;}
};

class md5 : public hash_digest<16>
Expand All @@ -57,4 +64,21 @@ class sha1 : public hash_digest<20>
virtual std::string hex_digest() const override;
};

class bcrypt : public hash_digest<BCRYPT_HASHSIZE, char>
{
bcrypt() {}
bcrypt(const std::string& input);

public:
static bcrypt from_salted_salt(const std::string& input);
static bcrypt from_hash_string(const std::string& input);
static bcrypt hash_pw(const std::string& password, bcrypt& salt);

std::size_t iteration_count_delim_pos;

static bool is_valid_prefix(const std::string& hash);
std::string get_salt() const;
virtual std::string hex_digest() const override;
};

} // namespace utils
13 changes: 7 additions & 6 deletions src/server/forum_user_handler.cpp
Expand Up @@ -78,7 +78,7 @@ bool fuh::login(const std::string& name, const std::string& password, const std:

if(utils::md5::is_valid_hash(hash)) { // md5 hash
valid_hash = utils::md5(hash.substr(12,34), seed).hex_digest();
} else if(hash.compare(0, 4, "$2y$") == 0) { // bcrypt hash
} else if(utils::bcrypt::is_valid_prefix(hash)) { // bcrypt hash
valid_hash = utils::md5(hash, seed).hex_digest();
} else {
ERR_UH << "Invalid hash for user '" << name << "'" << std::endl;
Expand Down Expand Up @@ -109,12 +109,13 @@ std::string fuh::create_pepper(const std::string& name) {
if(utils::md5::is_valid_hash(hash))
return hash.substr(0,12);

if(hash.size() > 59 && hash.compare(0, 4, "$2y$") == 0) {
std::size_t iteration_count_delim_pos = hash.find('$', 4);
if(iteration_count_delim_pos == std::string::npos)
if(utils::bcrypt::is_valid_prefix(hash)) {
try {
return utils::bcrypt::from_hash_string(hash).get_salt();
} catch(utils::hash_error& err) {
ERR_UH << "Error getting salt from hash of user '" << name << "': " << err.what() << std::endl;
return "";
std::size_t seed_pos = iteration_count_delim_pos + 23;
return hash.substr(0, seed_pos);
}
}

return "";
Expand Down

0 comments on commit 416d63f

Please sign in to comment.