Move password hashing to server_base.
This is in preparation for adding the `forum_auth` option when uploading an add-on.
This commit is contained in:
parent
ac39ffdcd3
commit
08bfe41b9e
10 changed files with 159 additions and 166 deletions
|
@ -1,6 +1,5 @@
|
|||
addon/validation.cpp
|
||||
server/common/dbconn.cpp
|
||||
server/common/user_handler.cpp
|
||||
server/common/forum_user_handler.cpp
|
||||
server/common/server_base.cpp
|
||||
server/common/simple_wml.cpp
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
server/common/dbconn.cpp
|
||||
server/common/user_handler.cpp
|
||||
server/common/forum_user_handler.cpp
|
||||
server/common/resultsets/ban_check.cpp
|
||||
server/common/resultsets/tournaments.cpp
|
||||
|
|
|
@ -49,12 +49,12 @@ fuh::fuh(const config& c)
|
|||
}
|
||||
}
|
||||
|
||||
bool fuh::login(const std::string& name, const std::string& password, const std::string& seed) {
|
||||
bool fuh::login(const std::string& name, const std::string& password, const std::string& nonce) {
|
||||
// Retrieve users' password as hash
|
||||
std::string hash;
|
||||
|
||||
try {
|
||||
hash = get_hash(name);
|
||||
hash = get_hashed_password_from_db(name);
|
||||
} catch (const error& e) {
|
||||
ERR_UH << "Could not retrieve hash for user '" << name << "' :" << e.message << std::endl;
|
||||
return false;
|
||||
|
@ -63,9 +63,9 @@ bool fuh::login(const std::string& name, const std::string& password, const std:
|
|||
std::string valid_hash;
|
||||
|
||||
if(utils::md5::is_valid_hash(hash)) { // md5 hash
|
||||
valid_hash = utils::md5(hash.substr(12,34), seed).base64_digest();
|
||||
valid_hash = utils::md5(hash.substr(12,34), nonce).base64_digest();
|
||||
} else if(utils::bcrypt::is_valid_prefix(hash)) { // bcrypt hash
|
||||
valid_hash = utils::md5(hash, seed).base64_digest();
|
||||
valid_hash = utils::md5(hash, nonce).base64_digest();
|
||||
} else {
|
||||
ERR_UH << "Invalid hash for user '" << name << "'" << std::endl;
|
||||
return false;
|
||||
|
@ -86,7 +86,7 @@ std::string fuh::extract_salt(const std::string& name) {
|
|||
std::string hash;
|
||||
|
||||
try {
|
||||
hash = get_hash(name);
|
||||
hash = get_hashed_password_from_db(name);
|
||||
} catch (const error& e) {
|
||||
ERR_UH << "Could not retrieve hash for user '" << name << "' :" << e.message << std::endl;
|
||||
return "";
|
||||
|
@ -188,7 +188,7 @@ std::string fuh::user_info(const std::string& name) {
|
|||
return info.str();
|
||||
}
|
||||
|
||||
std::string fuh::get_hash(const std::string& user) {
|
||||
std::string fuh::get_hashed_password_from_db(const std::string& user) {
|
||||
return conn_.get_user_string(db_users_table_, "user_password", user);
|
||||
}
|
||||
|
||||
|
|
|
@ -37,11 +37,11 @@ public:
|
|||
*
|
||||
* @param name The username used to login.
|
||||
* @param password The hashed password sent by the client.
|
||||
* @param seed The nonce created for this login attempt.
|
||||
* @param nonce The nonce created for this login attempt.
|
||||
* @see server::send_password_request().
|
||||
* @return Whether the hashed password sent by the client matches the hash retrieved from the phpbb database.
|
||||
*/
|
||||
bool login(const std::string& name, const std::string& password, const std::string& seed);
|
||||
bool login(const std::string& name, const std::string& password, const std::string& nonce);
|
||||
|
||||
/**
|
||||
* Needed because the hashing algorithm used by phpbb requires some info
|
||||
|
@ -270,7 +270,7 @@ private:
|
|||
* @param user The player's username.
|
||||
* @return The player's hashed password from the phpbb forum database.
|
||||
*/
|
||||
std::string get_hash(const std::string& user);
|
||||
std::string get_hashed_password_from_db(const std::string& user);
|
||||
|
||||
/**
|
||||
* @param user The player's username.
|
||||
|
|
|
@ -14,9 +14,13 @@
|
|||
|
||||
#include "server/common/server_base.hpp"
|
||||
|
||||
#include "config.hpp"
|
||||
#include "hash.hpp"
|
||||
#include "log.hpp"
|
||||
#include "serialization/parser.hpp"
|
||||
#include "serialization/base64.hpp"
|
||||
#include "filesystem.hpp"
|
||||
#include "random.hpp"
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
|
@ -38,8 +42,19 @@
|
|||
#endif
|
||||
#include <boost/asio/write.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <ctime>
|
||||
#include <functional>
|
||||
#include <queue>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#ifndef __APPLE__
|
||||
#include <openssl/rand.h>
|
||||
#else
|
||||
#include <cstdlib>
|
||||
#endif
|
||||
|
||||
|
||||
static lg::log_domain log_server("server");
|
||||
#define ERR_SERVER LOG_STREAM(err, log_server)
|
||||
|
@ -571,6 +586,106 @@ void server_base::load_tls_config(const config& cfg)
|
|||
if(!cfg["tls_dh"].str().empty()) tls_context_.use_tmp_dh_file(cfg["tls_dh"].str());
|
||||
}
|
||||
|
||||
std::string server_base::create_unsecure_nonce(int length)
|
||||
{
|
||||
srand(static_cast<unsigned>(std::time(nullptr)));
|
||||
|
||||
std::stringstream ss;
|
||||
|
||||
for(int i = 0; i < length; i++) {
|
||||
ss << randomness::rng::default_instance().get_random_int(0, 9);
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
#ifndef __APPLE__
|
||||
namespace
|
||||
{
|
||||
class RAND_bytes_exception : public std::exception
|
||||
{
|
||||
};
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
std::string server_base::create_secure_nonce()
|
||||
{
|
||||
// Must be full base64 encodings (3 bytes = 4 chars) else we skew the PRNG results
|
||||
std::array<unsigned char, (3 * 32) / 4> buf;
|
||||
|
||||
#ifndef __APPLE__
|
||||
if(!RAND_bytes(buf.data(), buf.size())) {
|
||||
throw RAND_bytes_exception();
|
||||
}
|
||||
#else
|
||||
arc4random_buf(buf.data(), buf.size());
|
||||
#endif
|
||||
|
||||
return base64::encode({buf.data(), buf.size()});
|
||||
}
|
||||
|
||||
std::pair<std::string, std::string> server_base::hash_password(const std::string& pw, const std::string& plain_salt, const std::string& username)
|
||||
{
|
||||
std::string password = pw;
|
||||
|
||||
// if using crypt_blowfish hashing, create a properly secure nonce
|
||||
// otherwise if using MD5 hashing, create an insecure nonce
|
||||
std::string nonce{(plain_salt.length() > 1 && plain_salt[1] == '2')
|
||||
? create_secure_nonce()
|
||||
: create_unsecure_nonce()};
|
||||
|
||||
std::string salt = plain_salt + nonce;
|
||||
|
||||
// Apparently HTML key-characters are passed to the hashing functions of phpbb in this escaped form.
|
||||
// I will do closer investigations on this, for now let's just hope these are all of them.
|
||||
|
||||
// Note: we must obviously replace '&' first, I wasted some time before I figured that out... :)
|
||||
for(std::string::size_type pos = 0; (pos = password.find('&', pos)) != std::string::npos; ++pos) {
|
||||
password.replace(pos, 1, "&");
|
||||
}
|
||||
for(std::string::size_type pos = 0; (pos = password.find('\"', pos)) != std::string::npos; ++pos) {
|
||||
password.replace(pos, 1, """);
|
||||
}
|
||||
for(std::string::size_type pos = 0; (pos = password.find('<', pos)) != std::string::npos; ++pos) {
|
||||
password.replace(pos, 1, "<");
|
||||
}
|
||||
for(std::string::size_type pos = 0; (pos = password.find('>', pos)) != std::string::npos; ++pos) {
|
||||
password.replace(pos, 1, ">");
|
||||
}
|
||||
|
||||
if(salt.length() < 12) {
|
||||
ERR_SERVER << "Bad salt found for user: " << username << std::endl;
|
||||
return std::make_pair("", "");
|
||||
}
|
||||
|
||||
if(utils::md5::is_valid_prefix(salt)) {
|
||||
std::string md5_1 = utils::md5(password, utils::md5::get_salt(salt), utils::md5::get_iteration_count(salt)).base64_digest();
|
||||
std::string md5_2 = utils::md5(md5_1, salt.substr(12, 8)).base64_digest();
|
||||
return std::make_pair(md5_2, nonce);
|
||||
} 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);
|
||||
|
||||
const std::string outer_salt = salt.substr(bcrypt_salt.iteration_count_delim_pos + 23);
|
||||
if(outer_salt.size() != 32) {
|
||||
throw utils::hash_error("salt wrong size");
|
||||
}
|
||||
|
||||
return std::make_pair(
|
||||
utils::md5(hash.base64_digest(), outer_salt).base64_digest(),
|
||||
nonce
|
||||
);
|
||||
} catch(const utils::hash_error& err) {
|
||||
ERR_SERVER << "bcrypt hash failed for user " << username << ": " << err.what() << std::endl;
|
||||
return std::make_pair("", "");
|
||||
}
|
||||
} else {
|
||||
ERR_SERVER << "Unable to determine how to hash the password for user: " << username << std::endl;
|
||||
return std::make_pair("", "");
|
||||
}
|
||||
}
|
||||
|
||||
// This is just here to get it to build without the deprecation_message function
|
||||
#include "game_version.hpp"
|
||||
#include "deprecation.hpp"
|
||||
|
|
|
@ -117,6 +117,31 @@ public:
|
|||
template<class SocketPtr> void async_send_error(SocketPtr socket, const std::string& msg, const char* error_code = "", const info_table& info = {});
|
||||
template<class SocketPtr> void async_send_warning(SocketPtr socket, const std::string& msg, const char* warning_code = "", const info_table& info = {});
|
||||
|
||||
/**
|
||||
* Create the poor security nonce for use with passwords still hashed with MD5.
|
||||
* Uses 8 random integer digits, 29.8 bits entropy.
|
||||
*
|
||||
* @param length How many random numbers to generate.
|
||||
* @return The nonce to use.
|
||||
*/
|
||||
std::string create_unsecure_nonce(int length = 8);
|
||||
/**
|
||||
* Create a good security nonce for use with bcrypt/crypt_blowfish hashing.
|
||||
* Uses 32 random Base64 characters, cryptographic-strength, 192 bits entropy
|
||||
*
|
||||
* @return The nonce to use.
|
||||
*/
|
||||
std::string create_secure_nonce();
|
||||
/**
|
||||
* Handles hashing the password provided by the player before comparing it to the hashed password in the forum database.
|
||||
*
|
||||
* @param pw The plaintext password.
|
||||
* @param plain_salt The salt as retrieved from the forum database.
|
||||
* @param username The player attempting to log in.
|
||||
* @return The hashed password, or empty if the password couldn't be hashed.
|
||||
*/
|
||||
std::pair<std::string, std::string> hash_password(const std::string& pw, const std::string& plain_salt, const std::string& username);
|
||||
|
||||
protected:
|
||||
unsigned short port_;
|
||||
bool keep_alive_;
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
Copyright (C) 2008 - 2018 by Thomas Baumhauer <thomas.baumhauer@NOSPAMgmail.com>
|
||||
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
|
||||
|
||||
This program 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 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#include "server/common/user_handler.hpp"
|
||||
#include "config.hpp"
|
||||
#include "random.hpp"
|
||||
#include "serialization/base64.hpp"
|
||||
|
||||
#ifndef __APPLE__
|
||||
#include <openssl/rand.h>
|
||||
#else
|
||||
#include <cstdlib>
|
||||
#endif
|
||||
|
||||
#include <array>
|
||||
#include <ctime>
|
||||
#include <sstream>
|
||||
|
||||
std::string user_handler::create_unsecure_nonce(int length)
|
||||
{
|
||||
srand(static_cast<unsigned>(std::time(nullptr)));
|
||||
|
||||
std::stringstream ss;
|
||||
|
||||
for(int i = 0; i < length; i++) {
|
||||
ss << randomness::rng::default_instance().get_random_int(0, 9);
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
#ifndef __APPLE__
|
||||
namespace
|
||||
{
|
||||
class RAND_bytes_exception : public std::exception
|
||||
{
|
||||
};
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
std::string user_handler::create_secure_nonce()
|
||||
{
|
||||
// Must be full base64 encodings (3 bytes = 4 chars) else we skew the PRNG results
|
||||
std::array<unsigned char, (3 * 32) / 4> buf;
|
||||
|
||||
#ifndef __APPLE__
|
||||
if(!RAND_bytes(buf.data(), buf.size())) {
|
||||
throw RAND_bytes_exception();
|
||||
}
|
||||
#else
|
||||
arc4random_buf(buf.data(), buf.size());
|
||||
#endif
|
||||
|
||||
return base64::encode({buf.data(), buf.size()});
|
||||
}
|
|
@ -128,14 +128,8 @@ public:
|
|||
error(const std::string& message) : game::error(message) {}
|
||||
};
|
||||
|
||||
/** Create a random string of digits for password encryption. */
|
||||
std::string create_unsecure_nonce(int length = 8);
|
||||
std::string create_secure_nonce();
|
||||
|
||||
/**
|
||||
* Create custom salt.
|
||||
*
|
||||
* If not needed let it return and empty string or whatever you feel like.
|
||||
*/
|
||||
virtual std::string extract_salt(const std::string& username) = 0;
|
||||
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
#include "config.hpp"
|
||||
#include "filesystem.hpp"
|
||||
#include "game_config.hpp"
|
||||
#include "hash.hpp"
|
||||
#include "log.hpp"
|
||||
#include "multiplayer_error_codes.hpp"
|
||||
#include "serialization/parser.hpp"
|
||||
|
@ -60,7 +59,6 @@
|
|||
#include <queue>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
static lg::log_domain log_server("server");
|
||||
|
@ -889,7 +887,16 @@ template<class SocketPtr> bool server::authenticate(
|
|||
registered = false;
|
||||
|
||||
if(user_handler_) {
|
||||
const auto [hashed_password, nonce] = hash_password(password, username, socket);
|
||||
const std::string salt = user_handler_->extract_salt(username);
|
||||
if(salt.empty()) {
|
||||
async_send_error(socket,
|
||||
"Even though your nickname is registered on this server you "
|
||||
"cannot log in due to an error in the hashing algorithm. "
|
||||
"Logging into your forum account on https://forums.wesnoth.org "
|
||||
"may fix this problem.");
|
||||
return false;
|
||||
}
|
||||
const auto [hashed_password, nonce] = hash_password(password, salt, username);
|
||||
const bool exists = user_handler_->user_exists(username);
|
||||
|
||||
// This name is registered but the account is not active
|
||||
|
@ -980,77 +987,6 @@ template<class SocketPtr> bool server::authenticate(
|
|||
return true;
|
||||
}
|
||||
|
||||
template<class SocketPtr> std::pair<std::string, std::string> server::hash_password(const std::string& pw, const std::string& user, SocketPtr socket)
|
||||
{
|
||||
std::string plain_salt = user_handler_->extract_salt(user);
|
||||
std::string password = pw;
|
||||
|
||||
// If using crypt_blowfish, use 32 random Base64 characters, cryptographic-strength, 192 bits entropy
|
||||
// else (phppass, MD5, $H$), use 8 random integer digits, not secure, do not use, this is crap, 29.8 bits entropy
|
||||
std::string nonce{(plain_salt[1] == '2')
|
||||
? user_handler_->create_secure_nonce()
|
||||
: user_handler_->create_unsecure_nonce()};
|
||||
|
||||
std::string salt = plain_salt + nonce;
|
||||
if(salt.empty()) {
|
||||
async_send_error(socket,
|
||||
"Even though your nickname is registered on this server you "
|
||||
"cannot log in due to an error in the hashing algorithm. "
|
||||
"Logging into your forum account on https://forums.wesnoth.org "
|
||||
"may fix this problem.");
|
||||
return std::make_pair("", "");
|
||||
}
|
||||
|
||||
// Apparently HTML key-characters are passed to the hashing functions of phpbb in this escaped form.
|
||||
// I will do closer investigations on this, for now let's just hope these are all of them.
|
||||
|
||||
// Note: we must obviously replace '&' first, I wasted some time before I figured that out... :)
|
||||
for(std::string::size_type pos = 0; (pos = password.find('&', pos)) != std::string::npos; ++pos) {
|
||||
password.replace(pos, 1, "&");
|
||||
}
|
||||
for(std::string::size_type pos = 0; (pos = password.find('\"', pos)) != std::string::npos; ++pos) {
|
||||
password.replace(pos, 1, """);
|
||||
}
|
||||
for(std::string::size_type pos = 0; (pos = password.find('<', pos)) != std::string::npos; ++pos) {
|
||||
password.replace(pos, 1, "<");
|
||||
}
|
||||
for(std::string::size_type pos = 0; (pos = password.find('>', pos)) != std::string::npos; ++pos) {
|
||||
password.replace(pos, 1, ">");
|
||||
}
|
||||
|
||||
if(salt.length() < 12) {
|
||||
ERR_SERVER << "Bad salt found for user: " << user << std::endl;
|
||||
return std::make_pair("", "");
|
||||
}
|
||||
|
||||
if(utils::md5::is_valid_prefix(salt)) {
|
||||
std::string md5_1 = utils::md5(password, utils::md5::get_salt(salt), utils::md5::get_iteration_count(salt)).base64_digest();
|
||||
std::string md5_2 = utils::md5(md5_1, salt.substr(12, 8)).base64_digest();
|
||||
return std::make_pair(md5_2, nonce);
|
||||
} 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);
|
||||
|
||||
const std::string outer_salt = salt.substr(bcrypt_salt.iteration_count_delim_pos + 23);
|
||||
if(outer_salt.size() != 32) {
|
||||
throw utils::hash_error("salt wrong size");
|
||||
}
|
||||
|
||||
return std::make_pair(
|
||||
utils::md5(hash.base64_digest(), outer_salt).base64_digest(),
|
||||
nonce
|
||||
);
|
||||
} catch(const utils::hash_error& err) {
|
||||
ERR_SERVER << "bcrypt hash failed: " << err.what() << std::endl;
|
||||
return std::make_pair("", "");
|
||||
}
|
||||
} else {
|
||||
ERR_SERVER << "Unable to determine how to hash the password for user: " << user << std::endl;
|
||||
return std::make_pair("", "");
|
||||
}
|
||||
}
|
||||
|
||||
template<class SocketPtr> void server::send_password_request(SocketPtr socket,
|
||||
const std::string& msg,
|
||||
const char* error_code,
|
||||
|
|
|
@ -58,15 +58,6 @@ private:
|
|||
void handle_join_game(player_iterator player, simple_wml::node& join);
|
||||
void disconnect_player(player_iterator player);
|
||||
void remove_player(player_iterator player);
|
||||
/**
|
||||
* Handles hashing the password provided by the player before comparing it to the hashed password in the forum database.
|
||||
*
|
||||
* @param pw The plaintext password.
|
||||
* @param user The player attempting to log in.
|
||||
* @param socket The socket the player is connected with.
|
||||
* @return The hashed password, or empty if the password couldn't be hashed.
|
||||
*/
|
||||
template<class SocketPtr> std::pair<std::string, std::string> hash_password(const std::string& pw, const std::string& user, SocketPtr socket);
|
||||
|
||||
public:
|
||||
template<class SocketPtr> void send_server_message(SocketPtr socket, const std::string& message, const std::string& type);
|
||||
|
|
Loading…
Add table
Reference in a new issue