diff --git a/zerostoragecaptchacrypto.cpp b/zerostoragecaptchacrypto.cpp index 5a3a502..5d6b76b 100644 --- a/zerostoragecaptchacrypto.cpp +++ b/zerostoragecaptchacrypto.cpp @@ -2,20 +2,25 @@ // Zero Storage Captcha #include "zerostoragecaptchacrypto.h" -#include "timetoken.h" +#include #include -#include #include +#include #include constexpr int TIME_TOKEN_SIZE = 10; +constexpr int DEFAULT_SIZE_OF_USED_TOKENS_CACHE = 100000; +constexpr int TIMER_TO_CHANGE_TOKEN_MSECS = 90000; // 1,5 min namespace ZeroStorageCaptchaCrypto { QTimer* TimeToken::m_updater = nullptr; QString TimeToken::m_current; QString TimeToken::m_prev; +size_t KeyHolder::m_maximalSizeOfUsedMap = DEFAULT_SIZE_OF_USED_TOKENS_CACHE; +QMutex KeyHolder::m_usedTokensMtx; +std::unordered_multimap KeyHolder::m_usedTokens; bool KeyHolder::m_caseSensitive = false; uint8_t KeyHolder::m_key[KEYSIZE] {0}; @@ -25,8 +30,9 @@ void TimeToken::init() m_updater = new QTimer; m_current = ZeroStorageCaptchaCrypto::random(TIME_TOKEN_SIZE); - m_updater->setInterval(2000); // 2 minutes + m_updater->setInterval(TIMER_TO_CHANGE_TOKEN_MSECS); QObject::connect(m_updater, &QTimer::timeout, [&]() { + KeyHolder::removeOldToken(m_prev); m_prev = m_current; m_current = ZeroStorageCaptchaCrypto::random(TIME_TOKEN_SIZE); }); @@ -35,6 +41,12 @@ void TimeToken::init() QString KeyHolder::captchaSecretLine(const QString &captchaAnswer, bool prevTimeToken) { + if (m_usedTokens.size() > m_maximalSizeOfUsedMap) + { + warningLog(); + return QString(); + } + if (m_key[0] == 0) { auto noize = ZeroStorageCaptchaCrypto::random(KEYSIZE); @@ -61,8 +73,38 @@ QString KeyHolder::captchaSecretLine(const QString &captchaAnswer, bool prevTime bool KeyHolder::validateCaptchaAnswer(const QString &answer, const QString &secretLine) { - if (captchaSecretLine(answer) == secretLine) return true; - return captchaSecretLine(answer, true) == secretLine; + QString timeKey; + if (captchaSecretLine(answer) == secretLine) + { + timeKey = TimeToken::currentToken(); + } + else if (captchaSecretLine(answer, true) == secretLine) + { + timeKey = TimeToken::prevToken(); + } + + if (not timeKey.isEmpty()) + { + QMutexLocker lock (&m_usedTokensMtx); + if (m_usedTokens.size() > m_maximalSizeOfUsedMap) + { + warningLog(); + return false; + } + if (m_usedTokens.find( timeKey ) == m_usedTokens.end()) + { + m_usedTokens.insert({ timeKey, secretLine }); + return true; + } + } + + return false; +} + +void KeyHolder::removeOldToken(const QString &oldPrevToken) +{ + QMutexLocker lock (&m_usedTokensMtx); + m_usedTokens.erase(oldPrevToken); } QString KeyHolder::compact(const QString &str) @@ -89,16 +131,23 @@ void KeyHolder::sign(const uint8_t *buf, int len, uint8_t *signature, const uint EVP_DigestSignInit (MDCtx, NULL, NULL, NULL, PKey); size_t l = SIGSIZE; - EVP_DigestSign (MDCtx, signature, &l, buf, len); EVP_PKEY_free (PKey); EVP_MD_CTX_destroy (MDCtx); } +void KeyHolder::warningLog() +{ + qInfo().noquote() << + "\n" + " Token cache is full (" + QString::number(m_maximalSizeOfUsedMap) + "). Service temporary unavailable.\n" + " You can increase maximal cache size via ZeroStorageCaptchaCrypto::KeyHolder::setMaxSizeOfUsedTokensCache(size_t)\n" + ""; +} + QString hash(const QString &str) { - // Partially bit-inverted SHA256 QVector in; for(auto c: str) { @@ -114,31 +163,23 @@ QString hash(const QString &str) rawResult.push_back(b); } - short count = 0; - for (auto it = rawResult.begin(); it != rawResult.end(); ++it) - { - if (++count % 2 == 0) - { - *it = ~*it; - } - } - return rawResult.toBase64(QByteArray::Base64Option::Base64UrlEncoding); } -QByteArray random(int length) +QByteArray random(int length, bool onlyNumbers) { - constexpr char randomtable[60] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', - 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', - 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', - 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', - 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X'}; + constexpr char randomtable[60] = + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'k', 'l', 'm', 'n', 'k', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', + 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', + 'h', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X'}; QByteArray random_value; std::random_device rd; - std::uniform_int_distribution dist(0, 59); + std::uniform_int_distribution dist(onlyNumbers ? 0 : 1, onlyNumbers ? 9 : 59); while(random_value.size() < length) { @@ -148,4 +189,4 @@ QByteArray random(int length) return random_value; } -} // namespace +} // namespace