core: move away from db towards hash-based auth
This commit is contained in:
@@ -29,7 +29,7 @@ add_compile_definitions(CHECKPOINT_VERSION="${CHECKPOINT_VERSION}")
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
|
||||
pkg_check_modules(deps IMPORTED_TARGET openssl sqlite3)
|
||||
pkg_check_modules(deps IMPORTED_TARGET openssl)
|
||||
|
||||
target_include_directories(checkpoint
|
||||
PRIVATE
|
||||
|
||||
@@ -154,7 +154,8 @@
|
||||
transition: ease-in-out 0.1s;
|
||||
}
|
||||
|
||||
@media (pointer:none), (pointer:coarse) {
|
||||
@media (pointer:none),
|
||||
(pointer:coarse) {
|
||||
.big-icon {
|
||||
margin-top: 10rem;
|
||||
}
|
||||
@@ -184,7 +185,6 @@
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div class="middle-box">
|
||||
@@ -198,9 +198,9 @@
|
||||
<p class="text-description">
|
||||
Verifying that you are not a bot. This might take a short moment.
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
|
||||
You do not need to do anything.
|
||||
</p>
|
||||
|
||||
@@ -219,7 +219,7 @@
|
||||
<div class="bottom-progress">
|
||||
<div class="bottom-progress-light" name="progress-light"></div>
|
||||
</div>
|
||||
|
||||
|
||||
<p class="bottom-hash-text" id="results">
|
||||
Difficulty {{ tl:text challengeDifficulty }}, elapsed 0ms, 0h, 0h/s
|
||||
</p>
|
||||
@@ -237,6 +237,8 @@
|
||||
var start = Date.now();
|
||||
const challengeNonce = "{{ tl:text challengeNonce }}";
|
||||
const difficulty = parseInt("{{ tl:text challengeDifficulty }}");
|
||||
const challengeSig = "{{ tl:text challengeSignature }}";
|
||||
const challengeFingerprint = "{{ tl:text challengeFingerprint }}";
|
||||
|
||||
function valid(sha) {
|
||||
const MIN_ZEROES = difficulty;
|
||||
@@ -273,8 +275,11 @@
|
||||
const data = JSON.stringify({
|
||||
challenge: challengeNonce,
|
||||
solution: it,
|
||||
difficulty: difficulty,
|
||||
sig: challengeSig,
|
||||
fingerprint: challengeFingerprint
|
||||
});
|
||||
|
||||
|
||||
fetch("/checkpoint/challenge",
|
||||
{
|
||||
headers: {
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
<!DOCTYPE html><head><title>STOP! - Checkpoint</title></head><body style="background-color:#0e0e0e;overflow:hidden"><style>@font-face{font-family:"Noto Sans";src:url(/checkpoint/NotoSans.woff)}*{font-family:"Noto Sans"}.middle-box{display:block;position:absolute;width:30rem;height:calc(100% - 2rem);left:calc(50% - 15rem);top:1rem;background-color:#111;border-radius:.5rem;border:1px solid #280d0e}.big-icon{margin:0;padding:0;font-size:8rem;color:#fff;width:100%;text-align:center}.subtext{margin:0;padding:0;font-size:3rem;color:#d9d9d9;width:100%;text-align:center}.text-hr{margin:auto;margin-top:1rem;margin-bottom:1rem;padding:0;background-color:#444;width:50%;box-shadow:none;text-align:center;height:1px;border:none}.text-hr-small{margin:auto;margin-top:1rem;margin-bottom:1rem;padding:0;background-color:#444;width:25%;box-shadow:none;text-align:center;height:1px;border:none}.text-description{margin:0;padding:0;font-size:1rem;color:#d9d9d9;width:85%;margin-left:7.5%;text-align:center}.text-question{margin:0;margin-top:4rem;padding:0;font-size:1.4rem;color:#d9d9d9;width:85%;margin-left:7.5%;text-align:center}.bottom-bar{position:absolute;width:100%;height:auto;bottom:.4rem;left:0}.bottom-hash-text{margin:0;padding:0;font-size:1rem;color:#d9d9d9;width:85%;margin-left:7.5%;text-align:center}.bottom-credit-text{display:block;margin:0;padding:0;font-size:.5rem;color:#4b4b4b;width:98%;text-align:right;transition:ease-in-out .1s;margin-top:.2rem}.bottom-credit-text:hover,.bottom-credit-text:link:hover,.bottom-credit-text:visited:hover{color:#764343;cursor:pointer}.bottom-credit-text:link,.bottom-credit-text:visited{color:#4b4b4b}.bottom-progress{position:relative;height:3px;background-color:#222;width:80%;margin-left:10%;margin-bottom:.8rem}.bottom-progress-light{position:absolute;left:0;top:0;width:0%;height:3px;background-color:#692225;transition:ease-in-out .1s}@media (pointer:none),(pointer:coarse){.big-icon{margin-top:10rem}.middle-box{width:90%;left:5%}.subtext{font-size:6rem}.text-description{font-size:3rem}.text-hr{height:3px}.bottom-hash-text{font-size:2.5rem}.bottom-credit-text{font-size:1rem}}</style><div class="middle-box"><p class="big-icon">🛑</p><p class="subtext" id="subtext">STOP!</p><hr class="text-hr"><p class="text-description">Verifying that you are not a bot. This might take a short moment.<br><br>You do not need to do anything.</p><p class="text-question">Why am I seeing this?</p><hr class="text-hr-small"><p class="text-description">This website protects itself from AI bots and scrapers by asking you to complete a cryptographic challenge before allowing you entry.</p><div class="bottom-bar"><div class="bottom-progress"><div class="bottom-progress-light" name="progress-light"></div></div><p class="bottom-hash-text" id="results">Difficulty {{ tl:text challengeDifficulty }}, elapsed 0ms, 0h, 0h/s</p><a class="bottom-credit-text" href="https://github.com/vaxerski/checkpoint"><i>Powered by checkpoint v{{ tl:text checkpointVersion }}</i></a></div></div>
|
||||
<script type="text/javascript">setTimeout(async function(){var e=0,t=Date.now();let n="{{ tl:text challengeNonce }}",o=parseInt("{{ tl:text challengeDifficulty }}");function $(e){let t=o;for(let n=0;n<t;n+=1)if("0"!=e[n])return!1;return!0}function l(e){return e>1e6?parseInt(e/1e5)/10+"M":parseInt(e/100)/10+"k"}function r(e){return e>1e3?parseInt(e/100)/10+"s":e+"ms"}function i(e){return Math.floor((1-1/(Math.pow(e/Math.pow(2,4*o)*3,2)+1))*100)}function s(e){document.getElementById("results").innerHTML="Success! Completed challenge after "+l(e)+" iterations, in "+r(Math.floor(Date.now()-t))+".",document.getElementsByName("progress-light")[0].style.width="100%";let o=JSON.stringify({challenge:n,solution:e});fetch("/checkpoint/challenge",{headers:{"Content-Type":"application/json"},method:"POST",body:o}).then(e=>{200==e.status?(console.log("Got token."),window.location.reload()):console.log("Server error")})}let c=new TextEncoder;for(;;){let _=c.encode(n+e),a=await window.crypto.subtle.digest("SHA-256",_),u=Array.from(new Uint8Array(a)),f=u.map(e=>e.toString(16).padStart(2,"0")).join("");if($(f)){s(e),console.log("Success: it "+e+": "+f);break}if(++e%11377==0){let g=Math.floor(Date.now()-t),h=e/(g/1e3);document.getElementById("results").innerHTML="Difficulty: "+o+", elapsed "+r(g)+", "+l(e)+"h, "+l(h)+"h/s",document.getElementsByName("progress-light")[0].style.width=i(e)+"%"}}},100);var currentTitle=1;setInterval(()=>{let e=["STOP","HALT","STÓJ","ARRÊT","СТІЙ"];document.getElementById("subtext").innerHTML=e[currentTitle]+"!",++currentTitle>=e.length&&(currentTitle=0)},2e3);</script></body>
|
||||
<script type="text/javascript">setTimeout(async function(){var e=0,t=Date.now();let n="{{ tl:text challengeNonce }}",l=parseInt("{{ tl:text challengeDifficulty }}");function r(e){let t=l;for(let n=0;n<t;n+=1)if("0"!=e[n])return!1;return!0}function $(e){return e>1e6?parseInt(e/1e5)/10+"M":parseInt(e/100)/10+"k"}function i(e){return e>1e3?parseInt(e/100)/10+"s":e+"ms"}function o(e){return Math.floor((1-1/(Math.pow(e/Math.pow(2,4*l)*3,2)+1))*100)}function c(e){document.getElementById("results").innerHTML="Success! Completed challenge after "+$(e)+" iterations, in "+i(Math.floor(Date.now()-t))+".",document.getElementsByName("progress-light")[0].style.width="100%";let r=JSON.stringify({challenge:n,solution:e,difficulty:l,sig:"{{ tl:text challengeSignature }}",fingerprint:"{{ tl:text challengeFingerprint }}"});fetch("/checkpoint/challenge",{headers:{"Content-Type":"application/json"},method:"POST",body:r}).then(e=>{200==e.status?(console.log("Got token."),window.location.reload()):console.log("Server error")})}let s=new TextEncoder;for(;;){let a=s.encode(n+e),g=await window.crypto.subtle.digest("SHA-256",a),u=Array.from(new Uint8Array(g)),_=u.map(e=>e.toString(16).padStart(2,"0")).join("");if(r(_)){c(e),console.log("Success: it "+e+": "+_);break}if(++e%11377==0){let f=Math.floor(Date.now()-t),h=e/(f/1e3);document.getElementById("results").innerHTML="Difficulty: "+l+", elapsed "+i(f)+", "+$(e)+"h, "+$(h)+"h/s",document.getElementsByName("progress-light")[0].style.width=o(e)+"%"}}},100);var currentTitle=1;setInterval(()=>{let e=["STOP","HALT","STÓJ","ARRÊT","СТІЙ"];document.getElementById("subtext").innerHTML=e[currentTitle]+"!",++currentTitle>=e.length&&(currentTitle=0)},2e3);</script></body>
|
||||
62
src/core/Challenge.cpp
Normal file
62
src/core/Challenge.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#include "Challenge.hpp"
|
||||
|
||||
#include "Crypto.hpp"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <glaze/glaze.hpp>
|
||||
|
||||
constexpr const uint64_t CHALLENGE_VERSION = 1;
|
||||
|
||||
CChallenge::CChallenge(const std::string& fingerprint, const std::string& challenge, int difficulty) :
|
||||
m_fingerprint(fingerprint), m_challenge(challenge), m_difficulty(difficulty) {
|
||||
std::string toSign = getSigString();
|
||||
|
||||
m_sig = g_pCrypto->sign(toSign);
|
||||
|
||||
m_valid = true;
|
||||
}
|
||||
|
||||
CChallenge::CChallenge(const std::string& jsonResponse) {
|
||||
auto json = glz::read_json<SChallengeJSON>(jsonResponse);
|
||||
|
||||
if (!json.has_value())
|
||||
return;
|
||||
|
||||
SChallengeJSON s = json.value();
|
||||
|
||||
m_challenge = s.challenge;
|
||||
m_fingerprint = s.fingerprint;
|
||||
m_sig = s.sig;
|
||||
|
||||
if (!g_pCrypto->verifySignature(getSigString(), m_sig))
|
||||
return;
|
||||
|
||||
const auto SHA = g_pCrypto->sha256(m_challenge + std::to_string(s.solution));
|
||||
|
||||
for (size_t i = 0; i < m_difficulty; ++i) {
|
||||
if (SHA.at(i) != '0')
|
||||
return;
|
||||
}
|
||||
|
||||
m_valid = true;
|
||||
}
|
||||
|
||||
std::string CChallenge::fingerprint() const {
|
||||
return m_fingerprint;
|
||||
}
|
||||
|
||||
std::string CChallenge::challenge() const {
|
||||
return m_challenge;
|
||||
}
|
||||
|
||||
std::string CChallenge::signature() const {
|
||||
return m_sig;
|
||||
}
|
||||
|
||||
bool CChallenge::valid() const {
|
||||
return m_valid;
|
||||
}
|
||||
|
||||
std::string CChallenge::getSigString() {
|
||||
return fmt::format("{}-{},{}", CHALLENGE_VERSION, m_fingerprint, m_challenge);
|
||||
}
|
||||
26
src/core/Challenge.hpp
Normal file
26
src/core/Challenge.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
class CChallenge {
|
||||
public:
|
||||
CChallenge(const std::string& fingerprint, const std::string& challenge, int difficulty);
|
||||
CChallenge(const std::string& jsonResponse);
|
||||
|
||||
std::string fingerprint() const;
|
||||
std::string challenge() const;
|
||||
std::string signature() const;
|
||||
bool valid() const;
|
||||
|
||||
private:
|
||||
std::string getSigString();
|
||||
|
||||
std::string m_sig, m_fingerprint, m_challenge;
|
||||
bool m_valid = false;
|
||||
int m_difficulty = 4;
|
||||
|
||||
struct SChallengeJSON {
|
||||
std::string fingerprint, challenge, sig;
|
||||
int difficulty = 4, solution = 0;
|
||||
};
|
||||
};
|
||||
200
src/core/Crypto.cpp
Normal file
200
src/core/Crypto.cpp
Normal file
@@ -0,0 +1,200 @@
|
||||
#include "Crypto.hpp"
|
||||
|
||||
#include "../GlobalState.hpp"
|
||||
#include "../config/Config.hpp"
|
||||
#include "../debug/log.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <string_view>
|
||||
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/err.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
constexpr const char* KEY_FILENAME = "privateKey.key";
|
||||
|
||||
static std::string dataDir() {
|
||||
static const std::string dir = std::filesystem::canonical(g_pGlobalState->cwd + "/" + g_pConfig->m_config.data_dir).string();
|
||||
return dir;
|
||||
}
|
||||
|
||||
static std::string readFileAsText(const std::string& path) {
|
||||
std::ifstream ifs(path);
|
||||
auto res = std::string((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
|
||||
if (res.back() == '\n')
|
||||
res.pop_back();
|
||||
return res;
|
||||
}
|
||||
|
||||
CCrypto::CCrypto() {
|
||||
if (!std::filesystem::exists(dataDir() + "/" + KEY_FILENAME)) {
|
||||
Debug::log(LOG, "No private key, generating one.");
|
||||
if (!genKey()) {
|
||||
Debug::log(CRIT, "Couldn't generate a key.");
|
||||
throw std::runtime_error("Keygen failed");
|
||||
}
|
||||
} else {
|
||||
auto f = fopen((dataDir() + "/" + KEY_FILENAME).c_str(), "r");
|
||||
PEM_read_PrivateKey(f, &m_evpPkey, nullptr, nullptr);
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
if (!m_evpPkey) {
|
||||
Debug::log(CRIT, "Couldn't read the key.");
|
||||
throw std::runtime_error("Key read openssl failed");
|
||||
}
|
||||
|
||||
Debug::log(LOG, "Read private key");
|
||||
}
|
||||
|
||||
CCrypto::~CCrypto() {
|
||||
if (m_evpPkey)
|
||||
EVP_PKEY_free(m_evpPkey);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> CCrypto::toByteArr(const std::string_view& s) {
|
||||
std::vector<uint8_t> inAsHash;
|
||||
inAsHash.reserve(s.size() / 2);
|
||||
for (size_t i = 0; i < s.size(); i += 2) {
|
||||
uint8_t byte = std::stoi(std::string{s.substr(i, 2)}, nullptr, 16);
|
||||
inAsHash.emplace_back(byte);
|
||||
}
|
||||
return inAsHash;
|
||||
}
|
||||
|
||||
std::string CCrypto::sha256(const std::string& in) {
|
||||
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
|
||||
if (!ctx)
|
||||
return "";
|
||||
|
||||
if (!EVP_DigestInit(ctx, EVP_sha256())) {
|
||||
EVP_MD_CTX_free(ctx);
|
||||
return "";
|
||||
}
|
||||
|
||||
if (!EVP_DigestUpdate(ctx, in.c_str(), in.size())) {
|
||||
EVP_MD_CTX_free(ctx);
|
||||
return "";
|
||||
}
|
||||
|
||||
uint8_t buf[32];
|
||||
|
||||
if (!EVP_DigestFinal(ctx, buf, nullptr)) {
|
||||
EVP_MD_CTX_free(ctx);
|
||||
return "";
|
||||
}
|
||||
|
||||
std::stringstream ss;
|
||||
for (size_t i = 0; i < 32; ++i) {
|
||||
ss << fmt::format("{:02x}", buf[i]);
|
||||
}
|
||||
|
||||
EVP_MD_CTX_free(ctx);
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
bool CCrypto::genKey() {
|
||||
EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, nullptr);
|
||||
|
||||
if (!ctx)
|
||||
return false;
|
||||
|
||||
if (EVP_PKEY_keygen_init(ctx) <= 0) {
|
||||
EVP_PKEY_CTX_free(ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (EVP_PKEY_keygen(ctx, &m_evpPkey) <= 0) {
|
||||
EVP_PKEY_CTX_free(ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto f = fopen((dataDir() + "/" + KEY_FILENAME).c_str(), "w");
|
||||
PEM_write_PrivateKey(f, m_evpPkey, nullptr, nullptr, 0, nullptr, nullptr);
|
||||
fclose(f);
|
||||
|
||||
EVP_PKEY_CTX_free(ctx);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string CCrypto::sign(const std::string& in) {
|
||||
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
|
||||
if (!ctx)
|
||||
return "";
|
||||
|
||||
if (!EVP_DigestSignInit(ctx, nullptr, nullptr, nullptr, m_evpPkey)) {
|
||||
Debug::log(ERR, "CCrypto::sign: EVP_DigestSignInit: err {}", ERR_error_string(ERR_get_error(), nullptr));
|
||||
EVP_MD_CTX_free(ctx);
|
||||
return "";
|
||||
}
|
||||
|
||||
size_t len = 0;
|
||||
|
||||
if (!EVP_DigestSign(ctx, nullptr, &len, (const unsigned char*)in.c_str(), in.size())) {
|
||||
Debug::log(ERR, "CCrypto::sign: EVP_DigestSign: err {}", ERR_error_string(ERR_get_error(), nullptr));
|
||||
EVP_MD_CTX_free(ctx);
|
||||
return "";
|
||||
}
|
||||
|
||||
if (len <= 0) {
|
||||
EVP_MD_CTX_free(ctx);
|
||||
return "";
|
||||
}
|
||||
|
||||
std::vector<uint8_t> buf;
|
||||
buf.resize(len);
|
||||
|
||||
if (!EVP_DigestSign(ctx, buf.data(), &len, (const unsigned char*)in.c_str(), in.size())) {
|
||||
Debug::log(ERR, "CCrypto::sign: EVP_DigestSign: err {}", ERR_error_string(ERR_get_error(), nullptr));
|
||||
EVP_MD_CTX_free(ctx);
|
||||
return "";
|
||||
}
|
||||
|
||||
std::stringstream ss;
|
||||
for (size_t i = 0; i < buf.size(); ++i) {
|
||||
ss << fmt::format("{:02x}", buf[i]);
|
||||
}
|
||||
|
||||
EVP_MD_CTX_free(ctx);
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
bool CCrypto::verifySignature(const std::string& in, const std::string& sig) {
|
||||
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
|
||||
if (!ctx)
|
||||
return false;
|
||||
|
||||
if (!EVP_DigestVerifyInit(ctx, nullptr, nullptr, nullptr, m_evpPkey)) {
|
||||
Debug::log(ERR, "CCrypto::verifySignature: EVP_DigestVerifyInit: err {}", ERR_error_string(ERR_get_error(), nullptr));
|
||||
EVP_MD_CTX_free(ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto sigAsArr = toByteArr(sig);
|
||||
|
||||
int ret = EVP_DigestVerify(ctx, sigAsArr.data(), sigAsArr.size(), (const unsigned char*)in.c_str(), in.size());
|
||||
|
||||
if (ret == 1) {
|
||||
// match
|
||||
EVP_MD_CTX_free(ctx);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ret == 0) {
|
||||
// no match
|
||||
EVP_MD_CTX_free(ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
Debug::log(ERR, "CCrypto::verifySignature: EVP_DigestVerify: err {}", ERR_error_string(ERR_get_error(), nullptr));
|
||||
|
||||
// invalid sig??
|
||||
EVP_MD_CTX_free(ctx);
|
||||
return false;
|
||||
}
|
||||
26
src/core/Crypto.hpp
Normal file
26
src/core/Crypto.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
class CCrypto {
|
||||
public:
|
||||
CCrypto();
|
||||
~CCrypto();
|
||||
|
||||
std::string sha256(const std::string& in);
|
||||
std::string sign(const std::string& in);
|
||||
bool verifySignature(const std::string& in, const std::string& sig);
|
||||
|
||||
private:
|
||||
EVP_PKEY* m_evpPkey = nullptr;
|
||||
|
||||
bool genKey();
|
||||
void readKey();
|
||||
std::vector<uint8_t> toByteArr(const std::string_view& s);
|
||||
};
|
||||
|
||||
inline std::unique_ptr<CCrypto> g_pCrypto;
|
||||
276
src/core/Db.cpp
276
src/core/Db.cpp
@@ -1,276 +0,0 @@
|
||||
#include "Db.hpp"
|
||||
|
||||
#include "../GlobalState.hpp"
|
||||
#include "../debug/log.hpp"
|
||||
#include "../config/Config.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <string_view>
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
|
||||
constexpr const char* DB_FILE = "data.db";
|
||||
constexpr const uint64_t DB_TIME_BEFORE_CLEANUP_MS = 1000 * 60 * 10; // 10 mins
|
||||
constexpr const uint64_t DB_TOKEN_LIFE_LENGTH_S = 60 * 60; // 1hr
|
||||
constexpr const uint64_t DB_CHALLENGE_LIFE_LENGTH_S = 60 * 10; // 10 mins
|
||||
constexpr const uint64_t DB_SCHEMA_VERSION = 2;
|
||||
|
||||
//
|
||||
static std::string readFileAsText(const std::string& path) {
|
||||
std::ifstream ifs(path);
|
||||
auto res = std::string((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
|
||||
if (res.back() == '\n')
|
||||
res.pop_back();
|
||||
return res;
|
||||
}
|
||||
|
||||
static std::string dbDir() {
|
||||
static const std::string dir = std::filesystem::canonical(g_pGlobalState->cwd + "/" + g_pConfig->m_config.data_dir).string();
|
||||
return dir;
|
||||
}
|
||||
|
||||
static std::string dbPath() {
|
||||
static const std::string path = dbDir() + "/" + DB_FILE;
|
||||
return path;
|
||||
}
|
||||
|
||||
static bool isHashValid(const std::string_view sv) {
|
||||
return std::all_of(sv.begin(), sv.end(), [](const char& c) { return (c >= 'a' && c <= 'f') || std::isdigit(c); });
|
||||
}
|
||||
|
||||
CDatabase::CDatabase() {
|
||||
if (!std::filesystem::exists(dbDir())) {
|
||||
Debug::log(LOG, "Data dir doesn't exist, creating.");
|
||||
std::filesystem::create_directory(dbDir());
|
||||
}
|
||||
|
||||
if (std::filesystem::exists(dbPath())) {
|
||||
if (std::filesystem::exists(dbDir() + "/schema")) {
|
||||
int schema = std::stoi(readFileAsText(dbDir() + "/schema"));
|
||||
if (schema == DB_SCHEMA_VERSION) {
|
||||
if (sqlite3_open(dbPath().c_str(), &m_db) != SQLITE_OK)
|
||||
throw std::runtime_error("failed to open sqlite3 db");
|
||||
|
||||
cleanupDb();
|
||||
return;
|
||||
} else
|
||||
Debug::log(LOG, "Database outdated, recreating db");
|
||||
} else
|
||||
Debug::log(LOG, "Database schema not present, recreating db");
|
||||
} else
|
||||
Debug::log(LOG, "Database not present, creating one");
|
||||
|
||||
std::filesystem::remove(dbPath());
|
||||
|
||||
if (sqlite3_open(dbPath().c_str(), &m_db) != SQLITE_OK)
|
||||
throw std::runtime_error("failed to open sqlite3 db");
|
||||
|
||||
std::ofstream of(dbDir() + "/schema", std::ios::trunc);
|
||||
of << DB_SCHEMA_VERSION;
|
||||
of.close();
|
||||
|
||||
// create db layout
|
||||
char* errmsg = nullptr;
|
||||
|
||||
const char* CHALLENGE_TABLE = R"#(
|
||||
CREATE TABLE challenges (
|
||||
nonce TEXT NOT NULL,
|
||||
fingerprint TEXT NOT NULL,
|
||||
difficulty INTEGER NOT NULL,
|
||||
epoch INTEGER NOT NULL,
|
||||
CONSTRAINT PK PRIMARY KEY (nonce)
|
||||
);)#";
|
||||
|
||||
sqlite3_exec(m_db, CHALLENGE_TABLE, [](void* data, int len, char** a, char** b) -> int { return 0; }, nullptr, &errmsg);
|
||||
|
||||
const char* TOKENS_TABLE = R"#(
|
||||
CREATE TABLE tokens (
|
||||
token TEXT NOT NULL,
|
||||
fingerprint TEXT NOT NULL,
|
||||
epoch INTEGER NOT NULL,
|
||||
CONSTRAINT PK PRIMARY KEY (token)
|
||||
);)#";
|
||||
|
||||
sqlite3_exec(m_db, TOKENS_TABLE, [](void* data, int len, char** a, char** b) -> int { return 0; }, nullptr, &errmsg);
|
||||
}
|
||||
|
||||
CDatabase::~CDatabase() {
|
||||
if (m_db)
|
||||
sqlite3_close(m_db);
|
||||
}
|
||||
|
||||
void CDatabase::addChallenge(const SDatabaseChallengeEntry& entry) {
|
||||
if (!isHashValid(entry.nonce))
|
||||
return;
|
||||
|
||||
if (!isHashValid(entry.fingerprint))
|
||||
return;
|
||||
|
||||
const std::string CMD = fmt::format(R"#(
|
||||
INSERT INTO challenges VALUES (
|
||||
"{}", "{}", {}, {}
|
||||
);)#",
|
||||
entry.nonce, entry.fingerprint, entry.difficulty, entry.epoch);
|
||||
|
||||
char* errmsg = nullptr;
|
||||
sqlite3_exec(m_db, CMD.c_str(), nullptr, nullptr, &errmsg);
|
||||
|
||||
if (errmsg)
|
||||
Debug::log(ERR, "sqlite3 error: tried to persist:\n{}\nGot: {}", CMD, errmsg);
|
||||
}
|
||||
|
||||
std::optional<SDatabaseChallengeEntry> CDatabase::getChallenge(const std::string& nonce) {
|
||||
if (!isHashValid(nonce))
|
||||
return std::nullopt;
|
||||
|
||||
const std::string CMD = fmt::format(R"#(
|
||||
SELECT * FROM challenges WHERE nonce = "{}";
|
||||
)#",
|
||||
nonce);
|
||||
|
||||
char* errmsg = nullptr;
|
||||
CDatabase::SQueryResult result;
|
||||
|
||||
sqlite3_exec(
|
||||
m_db, CMD.c_str(),
|
||||
[](void* result, int len, char** a, char** b) -> int {
|
||||
auto res = reinterpret_cast<CDatabase::SQueryResult*>(result);
|
||||
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
res->result.push_back(a[i]);
|
||||
res->result2.push_back(b[i]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
},
|
||||
&result, &errmsg);
|
||||
|
||||
if (errmsg || result.result.size() < 4)
|
||||
return std::nullopt;
|
||||
|
||||
return SDatabaseChallengeEntry{.nonce = nonce, .difficulty = std::stoi(result.result.at(2)), .epoch = std::stoull(result.result.at(3)), .fingerprint = result.result.at(1)};
|
||||
}
|
||||
|
||||
void CDatabase::dropChallenge(const std::string& nonce) {
|
||||
if (!isHashValid(nonce))
|
||||
return;
|
||||
|
||||
const std::string CMD = fmt::format(R"#(
|
||||
DELETE FROM tokens WHERE token = "{}"
|
||||
)#",
|
||||
nonce);
|
||||
|
||||
char* errmsg = nullptr;
|
||||
sqlite3_exec(m_db, CMD.c_str(), nullptr, nullptr, &errmsg);
|
||||
|
||||
if (errmsg)
|
||||
Debug::log(ERR, "sqlite3 error: tried to persist:\n{}\nGot: {}", CMD, errmsg);
|
||||
}
|
||||
|
||||
void CDatabase::addToken(const SDatabaseTokenEntry& entry) {
|
||||
if (!isHashValid(entry.token))
|
||||
return;
|
||||
|
||||
if (!isHashValid(entry.fingerprint))
|
||||
return;
|
||||
|
||||
const std::string CMD = fmt::format(R"#(
|
||||
INSERT INTO tokens VALUES (
|
||||
"{}", "{}", {}
|
||||
);)#",
|
||||
entry.token, entry.fingerprint, entry.epoch);
|
||||
|
||||
char* errmsg = nullptr;
|
||||
sqlite3_exec(m_db, CMD.c_str(), nullptr, nullptr, &errmsg);
|
||||
|
||||
if (errmsg)
|
||||
Debug::log(ERR, "sqlite3 error: tried to persist:\n{}\nGot: {}", CMD, errmsg);
|
||||
}
|
||||
|
||||
void CDatabase::dropToken(const std::string& token) {
|
||||
if (!isHashValid(token))
|
||||
return;
|
||||
|
||||
const std::string CMD = fmt::format(R"#(
|
||||
DELETE FROM tokens WHERE token = "{}"
|
||||
)#",
|
||||
token);
|
||||
|
||||
char* errmsg = nullptr;
|
||||
sqlite3_exec(m_db, CMD.c_str(), nullptr, nullptr, &errmsg);
|
||||
|
||||
if (errmsg)
|
||||
Debug::log(ERR, "sqlite3 error: tried to persist:\n{}\nGot: {}", CMD, errmsg);
|
||||
}
|
||||
|
||||
std::optional<SDatabaseTokenEntry> CDatabase::getToken(const std::string& token) {
|
||||
if (!isHashValid(token))
|
||||
return std::nullopt;
|
||||
|
||||
if (shouldCleanupDb())
|
||||
cleanupDb();
|
||||
|
||||
const std::string CMD = fmt::format(R"#(
|
||||
SELECT * FROM tokens WHERE token = "{}";
|
||||
)#",
|
||||
token);
|
||||
|
||||
char* errmsg = nullptr;
|
||||
CDatabase::SQueryResult result;
|
||||
|
||||
sqlite3_exec(
|
||||
m_db, CMD.c_str(),
|
||||
[](void* result, int len, char** a, char** b) -> int {
|
||||
auto res = reinterpret_cast<CDatabase::SQueryResult*>(result);
|
||||
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
res->result.push_back(a[i]);
|
||||
res->result2.push_back(b[i]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
},
|
||||
&result, &errmsg);
|
||||
|
||||
if (errmsg || result.result.size() < 3)
|
||||
return std::nullopt;
|
||||
|
||||
return SDatabaseTokenEntry{.token = token, .epoch = std::stoull(result.result.at(2)), .fingerprint = result.result.at(1)};
|
||||
}
|
||||
|
||||
bool CDatabase::shouldCleanupDb() {
|
||||
const auto TIME = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
|
||||
const auto LAST = std::chrono::duration_cast<std::chrono::milliseconds>(m_lastDbCleanup.time_since_epoch()).count();
|
||||
|
||||
if (TIME - LAST > DB_TIME_BEFORE_CLEANUP_MS)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void CDatabase::cleanupDb() {
|
||||
m_lastDbCleanup = std::chrono::steady_clock::now();
|
||||
|
||||
const auto TIME = std::chrono::milliseconds(std::time(nullptr)).count();
|
||||
|
||||
std::string CMD = fmt::format(R"#(
|
||||
DELETE FROM tokens WHERE epoch < {};
|
||||
)#",
|
||||
TIME - DB_TOKEN_LIFE_LENGTH_S);
|
||||
|
||||
char* errmsg = nullptr;
|
||||
sqlite3_exec(m_db, CMD.c_str(), nullptr, nullptr, &errmsg);
|
||||
|
||||
if (errmsg)
|
||||
Debug::log(ERR, "sqlite3 error: tried to persist:\n{}\nGot: {}", CMD, errmsg);
|
||||
|
||||
CMD = fmt::format(R"#(
|
||||
DELETE FROM challenges WHERE epoch < {};
|
||||
)#",
|
||||
TIME - DB_CHALLENGE_LIFE_LENGTH_S);
|
||||
|
||||
sqlite3_exec(m_db, CMD.c_str(), nullptr, nullptr, &errmsg);
|
||||
|
||||
if (errmsg)
|
||||
Debug::log(ERR, "sqlite3 error: tried to persist:\n{}\nGot: {}", CMD, errmsg);
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <sqlite3.h>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
struct SDatabaseChallengeEntry {
|
||||
std::string nonce = "";
|
||||
int difficulty = 0;
|
||||
unsigned long int epoch = std::chrono::system_clock::now().time_since_epoch() / std::chrono::seconds(1);
|
||||
std::string fingerprint = "";
|
||||
};
|
||||
|
||||
struct SDatabaseTokenEntry {
|
||||
std::string token = "";
|
||||
unsigned long int epoch = std::chrono::system_clock::now().time_since_epoch() / std::chrono::seconds(1);
|
||||
std::string fingerprint = "";
|
||||
};
|
||||
|
||||
class CDatabase {
|
||||
public:
|
||||
CDatabase();
|
||||
~CDatabase();
|
||||
|
||||
void addChallenge(const SDatabaseChallengeEntry& entry);
|
||||
std::optional<SDatabaseChallengeEntry> getChallenge(const std::string& nonce);
|
||||
void dropChallenge(const std::string& nonce);
|
||||
|
||||
void addToken(const SDatabaseTokenEntry& entry);
|
||||
std::optional<SDatabaseTokenEntry> getToken(const std::string& token);
|
||||
void dropToken(const std::string& token);
|
||||
|
||||
private:
|
||||
struct SQueryResult {
|
||||
bool failed = false;
|
||||
std::string error = "";
|
||||
std::vector<std::string> result;
|
||||
std::vector<std::string> result2;
|
||||
};
|
||||
|
||||
sqlite3* m_db = nullptr;
|
||||
std::chrono::steady_clock::time_point m_lastDbCleanup = std::chrono::steady_clock::now();
|
||||
|
||||
void cleanupDb();
|
||||
bool shouldCleanupDb();
|
||||
};
|
||||
|
||||
inline std::unique_ptr<CDatabase> g_pDB;
|
||||
@@ -1,4 +1,7 @@
|
||||
#include "Handler.hpp"
|
||||
#include "Crypto.hpp"
|
||||
#include "Token.hpp"
|
||||
#include "Challenge.hpp"
|
||||
#include "../headers/authorization.hpp"
|
||||
#include "../headers/cfHeader.hpp"
|
||||
#include "../headers/xforwardfor.hpp"
|
||||
@@ -8,7 +11,6 @@
|
||||
#include "../debug/log.hpp"
|
||||
#include "../GlobalState.hpp"
|
||||
#include "../config/Config.hpp"
|
||||
#include "Db.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
@@ -20,7 +22,8 @@
|
||||
#include <glaze/glaze.hpp>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
constexpr const uint64_t TOKEN_MAX_AGE_MS = 1000 * 60 * 60; // 1hr
|
||||
constexpr const uint64_t TOKEN_MAX_AGE_MS = 1000 * 60 * 60; // 1hr
|
||||
constexpr const char* TOKEN_COOKIE_NAME = "checkpoint-token";
|
||||
|
||||
//
|
||||
static std::string readFileAsText(const std::string& path) {
|
||||
@@ -57,36 +60,6 @@ static std::string generateToken() {
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
static std::string sha256(const std::string& string) {
|
||||
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
|
||||
if (!ctx)
|
||||
return "";
|
||||
|
||||
if (!EVP_DigestInit(ctx, EVP_sha256())) {
|
||||
EVP_MD_CTX_free(ctx);
|
||||
return "";
|
||||
}
|
||||
|
||||
if (!EVP_DigestUpdate(ctx, string.c_str(), string.size())) {
|
||||
EVP_MD_CTX_free(ctx);
|
||||
return "";
|
||||
}
|
||||
|
||||
uint8_t buf[32];
|
||||
|
||||
if (!EVP_DigestFinal(ctx, buf, nullptr)) {
|
||||
EVP_MD_CTX_free(ctx);
|
||||
return "";
|
||||
}
|
||||
|
||||
std::stringstream ss;
|
||||
for (size_t i = 0; i < 32; ++i) {
|
||||
ss << fmt::format("{:02x}", buf[i]);
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string CServerHandler::fingerprintForRequest(const Pistache::Http::Request& req) {
|
||||
const auto HEADERS = req.headers();
|
||||
std::shared_ptr<const Pistache::Http::Header::AcceptEncoding> acceptEncodingHeader;
|
||||
@@ -132,7 +105,7 @@ std::string CServerHandler::fingerprintForRequest(const Pistache::Http::Request&
|
||||
|
||||
input += req.address().host();
|
||||
|
||||
return sha256(input);
|
||||
return g_pCrypto->sha256(input);
|
||||
}
|
||||
|
||||
bool CServerHandler::isResourceCheckpoint(const std::string_view& res) {
|
||||
@@ -247,24 +220,24 @@ void CServerHandler::onRequest(const Pistache::Http::Request& req, Pistache::Htt
|
||||
}
|
||||
}
|
||||
|
||||
if (req.cookies().has("CheckpointToken")) {
|
||||
if (req.cookies().has(TOKEN_COOKIE_NAME)) {
|
||||
// check the token
|
||||
const auto TOKEN = g_pDB->getToken(req.cookies().get("CheckpointToken").value);
|
||||
if (TOKEN) {
|
||||
const auto AGE = std::chrono::milliseconds(std::time(nullptr)).count() - TOKEN->epoch;
|
||||
if (AGE <= TOKEN_MAX_AGE_MS && TOKEN->fingerprint == fingerprintForRequest(req)) {
|
||||
const auto TOKEN = CToken(req.cookies().get(TOKEN_COOKIE_NAME).value);
|
||||
if (TOKEN.valid()) {
|
||||
const auto AGE = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count() -
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(TOKEN.issued().time_since_epoch()).count();
|
||||
if (AGE <= TOKEN_MAX_AGE_MS && TOKEN.fingerprint() == fingerprintForRequest(req)) {
|
||||
Debug::log(LOG, " | Action: PASS (token)");
|
||||
proxyPass(req, response);
|
||||
return;
|
||||
} else { // token has been used from a different IP or is expired. Nuke it.
|
||||
g_pDB->dropToken(TOKEN->token);
|
||||
if (AGE > TOKEN_MAX_AGE_MS)
|
||||
Debug::log(LOG, " | Action: CHALLENGE (token expired)");
|
||||
else
|
||||
Debug::log(LOG, " | Action: CHALLENGE (token fingerprint mismatch)");
|
||||
}
|
||||
} else
|
||||
Debug::log(LOG, " | Action: CHALLENGE (token not found in db)");
|
||||
Debug::log(LOG, " | Action: CHALLENGE (token invalid)");
|
||||
} else
|
||||
Debug::log(LOG, " | Action: CHALLENGE (no token)");
|
||||
|
||||
@@ -279,64 +252,20 @@ void CServerHandler::challengeSubmitted(const Pistache::Http::Request& req, Pist
|
||||
const auto JSON = req.body();
|
||||
const auto FINGERPRINT = fingerprintForRequest(req);
|
||||
|
||||
std::shared_ptr<const CFConnectingIPHeader> cfHeader;
|
||||
try {
|
||||
cfHeader = Pistache::Http::Header::header_cast<CFConnectingIPHeader>(req.headers().get("cf-connecting-ip"));
|
||||
} catch (std::exception& e) {
|
||||
; // silent ignore
|
||||
}
|
||||
const auto CHALLENGE = CChallenge(req.body());
|
||||
|
||||
auto json = glz::read_json<SChallengeResponse>(JSON);
|
||||
STokenResponse resp;
|
||||
|
||||
if (!json) {
|
||||
resp.error = "bad input";
|
||||
response.send(Pistache::Http::Code::Bad_Request, glz::write_json(resp).value());
|
||||
if (!CHALLENGE.valid()) {
|
||||
response.send(Pistache::Http::Code::Bad_Request, "Bad request");
|
||||
return;
|
||||
}
|
||||
|
||||
auto val = json.value();
|
||||
|
||||
const auto CHALLENGE = g_pDB->getChallenge(val.challenge);
|
||||
|
||||
if (!CHALLENGE.has_value()) {
|
||||
resp.error = "bad challenge";
|
||||
response.send(Pistache::Http::Code::Bad_Request, glz::write_json(resp).value());
|
||||
return;
|
||||
}
|
||||
|
||||
if (CHALLENGE->fingerprint != FINGERPRINT) {
|
||||
resp.error = "bad challenge";
|
||||
response.send(Pistache::Http::Code::Bad_Request, glz::write_json(resp).value());
|
||||
return;
|
||||
}
|
||||
|
||||
// drop challenge already.
|
||||
g_pDB->dropChallenge(val.challenge);
|
||||
|
||||
// verify challenge
|
||||
const auto SHA = sha256(val.challenge + std::to_string(val.solution));
|
||||
|
||||
for (int i = 0; i < CHALLENGE->difficulty; ++i) {
|
||||
if (SHA.at(i) != '0') {
|
||||
resp.error = "bad solution";
|
||||
response.send(Pistache::Http::Code::Bad_Request, glz::write_json(resp).value());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// correct solution, return a token
|
||||
|
||||
const auto TOKEN = generateToken();
|
||||
const auto TOKEN = CToken(FINGERPRINT, std::chrono::system_clock::now());
|
||||
|
||||
g_pDB->addToken(SDatabaseTokenEntry{.token = TOKEN, .fingerprint = FINGERPRINT});
|
||||
response.headers().add(std::make_shared<SetCookieHeader>(std::string{TOKEN_COOKIE_NAME} + "=" + TOKEN.tokenCookie() + "; HttpOnly; Path=/; Secure; SameSite=Lax"));
|
||||
|
||||
resp.success = true;
|
||||
resp.token = TOKEN;
|
||||
|
||||
response.headers().add(std::make_shared<SetCookieHeader>("CheckpointToken=" + TOKEN + "; HttpOnly; Path=/; Secure; SameSite=Lax"));
|
||||
|
||||
response.send(Pistache::Http::Code::Ok, glz::write_json(resp).value());
|
||||
response.send(Pistache::Http::Code::Ok, "Ok");
|
||||
}
|
||||
|
||||
void CServerHandler::serveStop(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response) {
|
||||
@@ -348,10 +277,12 @@ void CServerHandler::serveStop(const Pistache::Http::Request& req, Pistache::Htt
|
||||
const auto NONCE = generateNonce();
|
||||
const auto DIFFICULTY = 4;
|
||||
|
||||
g_pDB->addChallenge(SDatabaseChallengeEntry{.nonce = NONCE, .difficulty = DIFFICULTY, .fingerprint = fingerprintForRequest(req)});
|
||||
const auto CHALLENGE = CChallenge(fingerprintForRequest(req), NONCE, DIFFICULTY);
|
||||
|
||||
page.add("challengeDifficulty", CTinylatesProp(std::to_string(DIFFICULTY)));
|
||||
page.add("challengeNonce", CTinylatesProp(NONCE));
|
||||
page.add("challengeSignature", CTinylatesProp(CHALLENGE.signature()));
|
||||
page.add("challengeFingerprint", CTinylatesProp(CHALLENGE.fingerprint()));
|
||||
page.add("checkpointVersion", CTinylatesProp(CHECKPOINT_VERSION));
|
||||
response.send(Pistache::Http::Code::Ok, page.render().value_or("error"));
|
||||
}
|
||||
|
||||
69
src/core/Token.cpp
Normal file
69
src/core/Token.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
#include "Token.hpp"
|
||||
|
||||
#include "Crypto.hpp"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
constexpr const uint64_t TOKEN_VERSION = 1;
|
||||
|
||||
CToken::CToken(const std::string& fingerprint, std::chrono::system_clock::time_point issued) : m_fingerprint(fingerprint), m_issued(issued) {
|
||||
std::string toSign = getSigString();
|
||||
|
||||
m_sig = g_pCrypto->sign(toSign);
|
||||
|
||||
m_fullCookie = fmt::format("{},{}", toSign, m_sig);
|
||||
|
||||
m_valid = true;
|
||||
}
|
||||
|
||||
CToken::CToken(const std::string& cookie) : m_fullCookie(cookie) {
|
||||
// try to parse the cookie
|
||||
if (std::count(cookie.begin(), cookie.end(), ',') != 2)
|
||||
return;
|
||||
|
||||
if (!cookie.contains('-'))
|
||||
return;
|
||||
|
||||
auto dash = cookie.find('-');
|
||||
|
||||
try {
|
||||
if (std::stoi(cookie.substr(0, dash)) != TOKEN_VERSION)
|
||||
return;
|
||||
} catch (std::exception& e) { return; }
|
||||
|
||||
std::string_view cookieData = std::string_view{cookie}.substr(dash + 1);
|
||||
auto firstComma = cookieData.find(',');
|
||||
auto lastComma = cookieData.find_last_of(',');
|
||||
|
||||
m_fingerprint = cookieData.substr(0, firstComma);
|
||||
m_sig = cookieData.substr(lastComma + 1);
|
||||
const auto tpStrMs = cookieData.substr(firstComma + 1, lastComma - firstComma - 1);
|
||||
|
||||
try {
|
||||
m_issued = std::chrono::system_clock::time_point(std::chrono::milliseconds(std::stoull(std::string{tpStrMs})));
|
||||
} catch (std::exception& e) { return; }
|
||||
|
||||
std::string toSign = getSigString();
|
||||
|
||||
m_valid = g_pCrypto->verifySignature(toSign, m_sig);
|
||||
}
|
||||
|
||||
std::string CToken::tokenCookie() const {
|
||||
return m_fullCookie;
|
||||
}
|
||||
|
||||
std::string CToken::fingerprint() const {
|
||||
return m_fingerprint;
|
||||
}
|
||||
|
||||
bool CToken::valid() const {
|
||||
return m_valid;
|
||||
}
|
||||
|
||||
std::chrono::system_clock::time_point CToken::issued() const {
|
||||
return m_issued;
|
||||
}
|
||||
|
||||
std::string CToken::getSigString() {
|
||||
return fmt::format("{}-{},{}", TOKEN_VERSION, m_fingerprint, std::chrono::duration_cast<std::chrono::milliseconds>(m_issued.time_since_epoch()).count());
|
||||
}
|
||||
23
src/core/Token.hpp
Normal file
23
src/core/Token.hpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <chrono>
|
||||
|
||||
class CToken {
|
||||
public:
|
||||
CToken(const std::string& fingerprint, std::chrono::system_clock::time_point issued);
|
||||
CToken(const std::string& cookie);
|
||||
|
||||
std::string tokenCookie() const;
|
||||
std::string fingerprint() const;
|
||||
bool valid() const;
|
||||
std::chrono::system_clock::time_point issued() const;
|
||||
|
||||
private:
|
||||
std::string getSigString();
|
||||
|
||||
std::string m_sig, m_fingerprint, m_fullCookie;
|
||||
std::chrono::system_clock::time_point m_issued;
|
||||
bool m_valid = false;
|
||||
};
|
||||
@@ -18,7 +18,7 @@
|
||||
#include "debug/log.hpp"
|
||||
|
||||
#include "core/Handler.hpp"
|
||||
#include "core/Db.hpp"
|
||||
#include "core/Crypto.hpp"
|
||||
|
||||
#include "config/Config.hpp"
|
||||
|
||||
@@ -79,7 +79,7 @@ int main(int argc, char** argv, char** envp) {
|
||||
Pistache::Http::Header::Registry::instance().registerHeader<AcceptLanguageHeader>();
|
||||
Pistache::Http::Header::Registry::instance().registerHeader<SetCookieHeader>();
|
||||
|
||||
g_pDB = std::make_unique<CDatabase>();
|
||||
g_pCrypto = std::make_unique<CCrypto>();
|
||||
|
||||
auto endpoint = std::make_unique<Pistache::Http::Endpoint>(address);
|
||||
auto opts = Pistache::Http::Endpoint::options().threads(threads).flags(Pistache::Tcp::Options::ReuseAddr | Pistache::Tcp::Options::ReusePort);
|
||||
|
||||
Reference in New Issue
Block a user