Restructured user message handling.

This commit is contained in:
Ravin Perera
2019-10-31 11:57:53 +05:30
parent 93d4abfd2a
commit 2e96ca009f
8 changed files with 164 additions and 144 deletions

View File

@@ -13,6 +13,7 @@ add_executable(hpcore
src/hplog.cpp
src/fbschema/common_helpers.cpp
src/fbschema/p2pmsg_helpers.cpp
src/jsonschema/usrmsg_helpers.cpp
src/sock/socket_client.cpp
src/sock/socket_server.cpp
src/sock/socket_session.cpp

View File

@@ -99,10 +99,10 @@ int create_contract()
cfg.peerport = 22860;
cfg.roundtime = 1000;
cfg.pubport = 8080;
cfg.pubmaxsize = 65536;
cfg.pubmaxcpm = 1000;
cfg.peermaxsize = 65536;
cfg.peermaxcpm = 1000;
cfg.pubmaxsize = 0;
cfg.pubmaxcpm = 0;
cfg.peermaxsize = 0;
cfg.peermaxcpm = 0;
#ifndef NDEBUG
cfg.loglevel = "debug";
@@ -403,8 +403,7 @@ int validate_config()
// Other required fields.
if (cfg.binary.empty() || cfg.listenip.empty() ||
cfg.peerport == 0 || cfg.roundtime == 0 || cfg.pubport == 0 || cfg.pubmaxsize == 0 || cfg.pubmaxcpm == 0 || cfg.peermaxsize == 0 ||
cfg.peermaxcpm == 0 || cfg.loglevel.empty() || cfg.loggers.empty())
cfg.peerport == 0 || cfg.roundtime == 0 || cfg.pubport == 0 || cfg.loglevel.empty() || cfg.loggers.empty())
{
std::cout << "Required configuration fields missing at " << ctx.configFile << std::endl;
return -1;

View File

@@ -0,0 +1,139 @@
#include <rapidjson/document.h>
#include <sodium.h>
#include "../util.hpp"
#include "../crypto.hpp"
#include "../hplog.hpp"
#include "usrmsg_helpers.hpp"
namespace jsonschema::usrmsg
{
static const char *SCHEMA_VERSION = "0.1";
// These fields are used on json messages response validation.
static const char *FLD_TYPE = "type";
static const char *FLD_CHALLENGE = "challenge";
static const char *FLD_SIG = "sig";
static const char *FLD_PUBKEY = "pubkey";
// Message types
static const char *MSGTYPE_CHALLENGE = "public_challenge";
static const char *MSGTYPE_CHALLENGE_RESP = "challenge_response";
// Length of user random challenge bytes.
static const size_t CHALLENGE_LEN = 16;
/**
* Constructs user challenge message json and the challenge string required for
* initial user challenge handshake. This gets called when a user gets establishes
* a web sockets connection to HP.
*
* @param msg String reference to copy the generated json message string into.
* Message format:
* {
* "version": "<HP version>",
* "type": "public_challenge",
* "challenge": "<hex challenge string>"
* }
* @param challenge String reference to copy the generated hex challenge string into.
*/
void create_user_challenge(std::string &msg, std::string &challengehex)
{
// Use libsodium to generate the random challenge bytes.
unsigned char challenge_bytes[CHALLENGE_LEN];
randombytes_buf(challenge_bytes, CHALLENGE_LEN);
// We pass the hex challenge string separately to the caller even though
// we also include it in the challenge msg as well.
util::bin2hex(challengehex, challenge_bytes, CHALLENGE_LEN);
// Construct the challenge msg json.
// We do not use RapidJson here in favour of performance because this is a simple json message.
// Since we know the rough size of the challenge massage we reserve adequate amount for the holder.
// Only Hot Pocket version number is variable length. Therefore message size is roughly 95 bytes
// so allocating 128bits for heap padding.
msg.reserve(128);
msg.append("{\"version\":\"")
.append(SCHEMA_VERSION)
.append("\",\"").append(FLD_TYPE).append("\":\"").append(MSGTYPE_CHALLENGE)
.append("\",\"").append(FLD_CHALLENGE).append("\":\"").append(challengehex)
.append("\"}");
}
/**
* Verifies the user challenge response with the original challenge issued to the user
* and the user public key contained in the response.
*
* @param extracted_pubkeyhex The hex public key extracted from the response.
* @param response The response bytes to verify. This will be parsed as json.
* Accepted response format:
* {
* "type": "challenge_response",
* "challenge": "<original hex challenge the user received>",
* "sig": "<hex signature of the challenge>",
* "pubkey": "<hex public key of the user>"
* }
* @param original_challenge The original hex challenge string issued to the user.
* @return 0 if challenge response is verified. -1 if challenge not met or an error occurs.
*/
int verify_user_challenge_response(std::string &extracted_pubkeyhex, std::string_view response, std::string_view original_challenge)
{
// We load response raw bytes into json document.
rapidjson::Document d;
// Because we project the response message directly from the binary socket buffer in a zero-copy manner, the response
// string is not null terminated. 'kParseStopWhenDoneFlag' avoids rapidjson error in this case.
d.Parse<rapidjson::kParseStopWhenDoneFlag>(response.data());
if (d.HasParseError())
{
LOG_INFO << "Challenge response json parsing failed.";
return -1;
}
// Validate msg type.
if (!d.HasMember(FLD_TYPE) || d[FLD_TYPE] != MSGTYPE_CHALLENGE_RESP)
{
LOG_INFO << "User challenge response type invalid. 'challenge_response' expected.";
return -1;
}
// Compare the response challenge string with the original issued challenge.
if (!d.HasMember(FLD_CHALLENGE) || d[FLD_CHALLENGE] != original_challenge.data())
{
LOG_INFO << "User challenge response challenge invalid.";
return -1;
}
// Check for the 'sig' field existence.
if (!d.HasMember(FLD_SIG) || !d[FLD_SIG].IsString())
{
LOG_INFO << "User challenge response signature invalid.";
return -1;
}
// Check for the 'pubkey' field existence.
if (!d.HasMember(FLD_PUBKEY) || !d[FLD_PUBKEY].IsString())
{
LOG_INFO << "User challenge response public key invalid.";
return -1;
}
// Verify the challenge signature. We do this last due to signature verification cost.
std::string_view pubkeysv = util::getsv(d[FLD_PUBKEY]);
if (crypto::verify_hex(
original_challenge,
util::getsv(d[FLD_SIG]),
pubkeysv) != 0)
{
LOG_INFO << "User challenge response signature verification failed.";
return -1;
}
extracted_pubkeyhex = pubkeysv;
return 0;
}
} // namespace jsonschema::usrmsg

View File

@@ -0,0 +1,13 @@
#ifndef _HP_JSONSCHEMA_USRMSG_HELPERS_H_
#define _HP_JSONSCHEMA_USRMSG_HELPERS_H_
#include <string>
namespace jsonschema::usrmsg
{
void create_user_challenge(std::string &msg, std::string &challengehex);
int verify_user_challenge_response(std::string &extracted_pubkeyhex, std::string_view response, std::string_view original_challenge);
}
#endif

View File

@@ -215,6 +215,8 @@ void socket_session<T>::increment(util::SESSION_THRESHOLDS threshold_type, uint6
t.timestamp = 0;
t.counter_value = 0;
LOG_INFO << "Session " << this->uniqueid << " threshold exceeded. (type:" << threshold_type << " limit:" << t.threshold_limit << ")";
// Invoke the threshold monitor so any actions will be performed.
threshold_monitor(threshold_type, t.threshold_limit, this);
}

View File

@@ -7,14 +7,13 @@
#include "../sock/socket_session.hpp"
#include "../proc.hpp"
#include "../hplog.hpp"
#include "../jsonschema/usrmsg_helpers.hpp"
#include "usr.hpp"
#include "user_session_handler.hpp"
namespace net = boost::asio;
namespace beast = boost::beast;
using tcp = net::ip::tcp;
using error = boost::system::error_code;
namespace jusrmsg = jsonschema::usrmsg;
namespace usr
{
@@ -42,7 +41,7 @@ void user_session_handler::on_connect(sock::socket_session<user_outbound_message
std::string msgstr;
std::string challengehex;
usr::create_user_challenge(msgstr, challengehex);
jusrmsg::create_user_challenge(msgstr, challengehex);
// We init the session unique id to associate with the challenge.
session->init_uniqueid();
@@ -74,7 +73,7 @@ void user_session_handler::on_message(
{
std::string userpubkeyhex;
std::string_view original_challenge = itr->second;
if (usr::verify_user_challenge_response(userpubkeyhex, message, original_challenge) == 0)
if (jusrmsg::verify_user_challenge_response(userpubkeyhex, message, original_challenge) == 0)
{
// Challenge singature verification successful.

View File

@@ -1,8 +1,6 @@
#include <cstdio>
#include <iostream>
#include <unistd.h>
#include <rapidjson/document.h>
#include <sodium.h>
#include <boost/thread/thread.hpp>
#include "usr.hpp"
#include "user_session_handler.hpp"
@@ -59,20 +57,6 @@ std::thread listener_thread;
*/
sock::session_options sess_opts;
// Challenge response fields.
// These fields are used on challenge response validation.
static const char *CHALLENGE_RESP_TYPE = "type";
static const char *CHALLENGE_RESP_CHALLENGE = "challenge";
static const char *CHALLENGE_RESP_SIG = "sig";
static const char *CHALLENGE_RESP_PUBKEY = "pubkey";
// Message type for the user challenge.
static const char *CHALLENGE_MSGTYPE = "public_challenge";
// Message type for the user challenge response.
static const char *CHALLENGE_RESP_MSGTYPE = "challenge_response";
// Length of user random challenge bytes.
static const size_t CHALLENGE_LEN = 16;
/**
* Initializes the usr subsystem. Must be called once during application startup.
* @return 0 for successful initialization. -1 for failure.
@@ -93,119 +77,6 @@ void deinit()
stop_listening();
}
/**
* Constructs user challenge message json and the challenge string required for
* initial user challenge handshake. This gets called when a user gets establishes
* a web sockets connection to HP.
*
* @param msg String reference to copy the generated json message string into.
* Message format:
* {
* "version": "<HP version>",
* "type": "public_challenge",
* "challenge": "<hex challenge string>"
* }
* @param challenge String reference to copy the generated hex challenge string into.
*/
void create_user_challenge(std::string &msg, std::string &challengehex)
{
//Use libsodium to generate the random challenge bytes.
unsigned char challenge_bytes[CHALLENGE_LEN];
randombytes_buf(challenge_bytes, CHALLENGE_LEN);
//We pass the hex challenge string separately to the caller even though
//we also include it in the challenge msg as well.
util::bin2hex(challengehex, challenge_bytes, CHALLENGE_LEN);
//Construct the challenge msg json.
// We do not use RapidJson here in favour of performance because this is a simple json message.
// Since we know the rough size of the challenge massage we reserve adequate amount for the holder.
// Only Hot Pocket version number is variable length. Therefore message size is roughly 95 bytes
// so allocating 128bits for heap padding.
msg.reserve(128);
msg.append("{\"version\":\"")
.append(util::HP_VERSION)
.append("\",\"type\":\"public_challenge\",\"challenge\":\"")
.append(challengehex)
.append("\"}");
}
/**
* Verifies the user challenge response with the original challenge issued to the user
* and the user public key contained in the response.
*
* @param extracted_pubkeyhex The hex public key extracted from the response.
* @param response The response bytes to verify. This will be parsed as json.
* Accepted response format:
* {
* "type": "challenge_response",
* "challenge": "<original hex challenge the user received>",
* "sig": "<hex signature of the challenge>",
* "pubkey": "<hex public key of the user>"
* }
* @param original_challenge The original hex challenge string issued to the user.
* @return 0 if challenge response is verified. -1 if challenge not met or an error occurs.
*/
int verify_user_challenge_response(std::string &extracted_pubkeyhex, std::string_view response, std::string_view original_challenge)
{
// We load response raw bytes into json document.
rapidjson::Document d;
// Because we project the response message directly from the binary socket buffer in a zero-copy manner, the response
// string is not null terminated. 'kParseStopWhenDoneFlag' avoids rapidjson error in this case.
d.Parse<rapidjson::kParseStopWhenDoneFlag>(response.data());
if (d.HasParseError())
{
LOG_INFO << "Challenge response json parsing failed.";
return -1;
}
// Validate msg type.
if (!d.HasMember(CHALLENGE_RESP_TYPE) || d[CHALLENGE_RESP_TYPE] != CHALLENGE_RESP_MSGTYPE)
{
LOG_INFO << "User challenge response type invalid. 'challenge_response' expected.";
return -1;
}
// Compare the response challenge string with the original issued challenge.
if (!d.HasMember(CHALLENGE_RESP_CHALLENGE) || d[CHALLENGE_RESP_CHALLENGE] != original_challenge.data())
{
LOG_INFO << "User challenge response challenge invalid.";
return -1;
}
// Check for the 'sig' field existence.
if (!d.HasMember(CHALLENGE_RESP_SIG) || !d[CHALLENGE_RESP_SIG].IsString())
{
LOG_INFO << "User challenge response signature invalid.";
return -1;
}
// Check for the 'pubkey' field existence.
if (!d.HasMember(CHALLENGE_RESP_PUBKEY) || !d[CHALLENGE_RESP_PUBKEY].IsString())
{
LOG_INFO << "User challenge response public key invalid.";
return -1;
}
// Verify the challenge signature. We do this last due to signature verification cost.
std::string_view pubkeysv = util::getsv(d[CHALLENGE_RESP_PUBKEY]);
if (crypto::verify_hex(
original_challenge,
util::getsv(d[CHALLENGE_RESP_SIG]),
pubkeysv) != 0)
{
LOG_INFO << "User challenge response signature verification failed.";
return -1;
}
extracted_pubkeyhex = pubkeysv;
return 0;
}
/**
* Adds the user denoted by specified session id and public key to the global authed user list.
* This should get called after the challenge handshake is verified.

View File

@@ -64,10 +64,6 @@ int init();
void deinit();
void create_user_challenge(std::string &msg, std::string &challengehex);
int verify_user_challenge_response(std::string &extracted_pubkeyhex, std::string_view response, std::string_view original_challenge);
int add_user(sock::socket_session<user_outbound_message> *session, const std::string &pubkey);
int remove_user(const std::string &sessionid);