From 7e1a99a6918dd034b9891ee34d2f74bfb9a16e74 Mon Sep 17 00:00:00 2001 From: Vaxry Date: Mon, 14 Apr 2025 13:26:32 +0100 Subject: [PATCH] challenge: make challenges expire after 10 minutes fixes #10 --- html/index.html | 4 +++- html/index.min.html | 2 +- src/core/Challenge.cpp | 17 +++++++++++++---- src/core/Challenge.hpp | 14 +++++++++----- src/core/Crypto.cpp | 4 ++-- src/core/Handler.cpp | 1 + 6 files changed, 29 insertions(+), 13 deletions(-) diff --git a/html/index.html b/html/index.html index fd70009..0777381 100644 --- a/html/index.html +++ b/html/index.html @@ -236,6 +236,7 @@ var it = 0; var start = Date.now(); const challengeNonce = "{{ tl:text challengeNonce }}"; + const challengeTimestamp = "{{ tl:text challengeTimestamp }}"; const difficulty = parseInt("{{ tl:text challengeDifficulty }}"); const challengeSig = "{{ tl:text challengeSignature }}"; const challengeFingerprint = "{{ tl:text challengeFingerprint }}"; @@ -267,7 +268,8 @@ solution: it, difficulty: difficulty, sig: challengeSig, - fingerprint: challengeFingerprint + fingerprint: challengeFingerprint, + timestamp: challengeTimestamp }); fetch("/checkpoint/challenge", diff --git a/html/index.min.html b/html/index.min.html index 1b232ef..8c3c231 100644 --- a/html/index.min.html +++ b/html/index.min.html @@ -1,2 +1,2 @@ STOP! - Checkpoint

🛑

STOP!


Verifying that you are not a bot. This might take a short moment.

You do not need to do anything.

Why am I seeing this?


This website protects itself from AI bots and scrapers by asking you to complete a cryptographic challenge before allowing you entry.

Difficulty {{ tl:text challengeDifficulty }}, elapsed 0ms, 0h, 0h/s

Powered by checkpoint v{{ tl:text checkpointVersion }}
- \ No newline at end of file + \ No newline at end of file diff --git a/src/core/Challenge.cpp b/src/core/Challenge.cpp index 11e9662..20ebe1a 100644 --- a/src/core/Challenge.cpp +++ b/src/core/Challenge.cpp @@ -5,10 +5,11 @@ #include #include -constexpr const uint64_t CHALLENGE_VERSION = 1; +constexpr const uint64_t CHALLENGE_VERSION = 2; +constexpr const uint64_t CHALLENGE_EXPIRE_TIME_S = 600; // 10 minutes CChallenge::CChallenge(const std::string& fingerprint, const std::string& challenge, int difficulty) : - m_fingerprint(fingerprint), m_challenge(challenge), m_difficulty(difficulty) { + m_fingerprint(fingerprint), m_challenge(challenge), m_difficulty(difficulty), m_issued(std::chrono::system_clock::now()) { std::string toSign = getSigString(); m_sig = g_pCrypto->sign(toSign); @@ -28,6 +29,10 @@ CChallenge::CChallenge(const std::string& jsonResponse) { m_fingerprint = s.fingerprint; m_sig = s.sig; + try { + m_issued = std::chrono::system_clock::time_point(std::chrono::seconds(std::stoull(s.timestamp))); + } catch (std::exception& e) { return; } + if (!g_pCrypto->verifySignature(getSigString(), m_sig)) return; @@ -54,9 +59,13 @@ std::string CChallenge::signature() const { } bool CChallenge::valid() const { - return m_valid; + return m_valid && std::chrono::duration_cast(std::chrono::system_clock::now() - m_issued).count() < CHALLENGE_EXPIRE_TIME_S; } std::string CChallenge::getSigString() { - return fmt::format("{}-{},{}", CHALLENGE_VERSION, m_fingerprint, m_challenge); + return fmt::format("{}-{},{},{}", CHALLENGE_VERSION, m_fingerprint, m_challenge, std::chrono::duration_cast(m_issued.time_since_epoch()).count()); +} + +std::string CChallenge::timestampAsString() const { + return std::to_string(std::chrono::duration_cast(m_issued.time_since_epoch()).count()); } diff --git a/src/core/Challenge.hpp b/src/core/Challenge.hpp index 2cbcf6b..938e770 100644 --- a/src/core/Challenge.hpp +++ b/src/core/Challenge.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include class CChallenge { public: @@ -10,17 +11,20 @@ class CChallenge { std::string fingerprint() const; std::string challenge() const; std::string signature() const; + std::string timestampAsString() const; bool valid() const; private: - std::string getSigString(); + std::string getSigString(); - std::string m_sig, m_fingerprint, m_challenge; - bool m_valid = false; - int m_difficulty = 4; + std::string m_sig, m_fingerprint, m_challenge; + bool m_valid = false; + int m_difficulty = 4; + + std::chrono::system_clock::time_point m_issued; struct SChallengeJSON { - std::string fingerprint, challenge, sig; + std::string fingerprint, challenge, sig, timestamp; int difficulty = 4, solution = 0; }; }; \ No newline at end of file diff --git a/src/core/Crypto.cpp b/src/core/Crypto.cpp index 61b40d0..8f55e1d 100644 --- a/src/core/Crypto.cpp +++ b/src/core/Crypto.cpp @@ -98,7 +98,7 @@ std::string CCrypto::sha256(const std::string& in) { } bool CCrypto::genKey() { - EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, nullptr); + EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, nullptr); if (!ctx) return false; @@ -178,7 +178,7 @@ bool CCrypto::verifySignature(const std::string& in, const std::string& sig) { auto sigAsArr = toByteArr(sig); - int ret = EVP_DigestVerify(ctx, sigAsArr.data(), sigAsArr.size(), (const unsigned char*)in.c_str(), in.size()); + int ret = EVP_DigestVerify(ctx, sigAsArr.data(), sigAsArr.size(), (const unsigned char*)in.c_str(), in.size()); if (ret == 1) { // match diff --git a/src/core/Handler.cpp b/src/core/Handler.cpp index a8dc1ed..6937a2f 100644 --- a/src/core/Handler.cpp +++ b/src/core/Handler.cpp @@ -296,6 +296,7 @@ void CServerHandler::serveStop(const Pistache::Http::Request& req, Pistache::Htt page.add("challengeNonce", CTinylatesProp(NONCE)); page.add("challengeSignature", CTinylatesProp(CHALLENGE.signature())); page.add("challengeFingerprint", CTinylatesProp(CHALLENGE.fingerprint())); + page.add("challengeTimestamp", CTinylatesProp(CHALLENGE.timestampAsString())); page.add("checkpointVersion", CTinylatesProp(CHECKPOINT_VERSION)); response.send(Pistache::Http::Code::Ok, page.render().value_or("error")); }