#include "debug_auth.h" #include #include "sha256.h" namespace roro::debug { namespace { const char kAlphabet[] = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; } std::string makeToken(const uint8_t random[kTokenRandom]) { std::string token; uint32_t bits = 0; int have = 0; size_t next = 0; while (token.size() < kTokenChars) { if (have < 5) { bits = (bits << 8) | random[next++]; have += 8; } token += kAlphabet[(bits >> (have - 5)) & 31]; have -= 5; } return token; } std::string tidyToken(const std::string& typed) { std::string out; for (char c : typed) { if (c == '-' || c == ' ' || c == '\t' || c == '\r' || c == '\n') continue; if (c >= 'a' && c <= 'z') c = static_cast(c - 'a' + 'A'); // Crockford's rule for the letters his alphabet leaves out: read as the digit they look like. if (c == 'O') c = '0'; if (c == 'I' || c == 'L') c = '1'; out += c; } return out; } bool validToken(const std::string& tidied) { if (tidied.size() < kMinTokenChars || tidied.size() > kMaxTokenChars) return false; for (char c : tidied) if (c <= ' ' || c > '~' || c == '-' || (c >= 'a' && c <= 'z') || c == 'O' || c == 'I' || c == 'L') return false; return true; } std::string groupToken(const std::string& token) { std::string out; for (size_t i = 0; i < token.size(); i++) { if (i && i % 4 == 0) out += '-'; out += token[i]; } return out; } void hmacSha256(const uint8_t* key, size_t keyLen, const uint8_t* message, size_t messageLen, uint8_t out[32]) { uint8_t block[64] = {}; if (keyLen > sizeof block) Sha256::hash(key, keyLen, block); // a long key is hashed first else memcpy(block, key, keyLen); uint8_t pad[64]; for (size_t i = 0; i < sizeof pad; i++) pad[i] = block[i] ^ 0x36; uint8_t inner[32]; Sha256 in; in.update(pad, sizeof pad); in.update(message, messageLen); in.finish(inner); for (size_t i = 0; i < sizeof pad; i++) pad[i] = block[i] ^ 0x5c; Sha256 outer; outer.update(pad, sizeof pad); outer.update(inner, sizeof inner); outer.finish(out); } std::string toHex(const uint8_t* data, size_t len) { static const char digits[] = "0123456789abcdef"; std::string out; out.reserve(len * 2); for (size_t i = 0; i < len; i++) { out += digits[data[i] >> 4]; out += digits[data[i] & 15]; } return out; } std::string answerFor(const std::string& token, const uint8_t nonce[kNonceBytes]) { uint8_t mac[32]; hmacSha256(reinterpret_cast(token.data()), token.size(), nonce, kNonceBytes, mac); return toHex(mac, sizeof mac); } bool sameText(const std::string& a, const std::string& b) { uint8_t diff = a.size() != b.size(); for (size_t i = 0; i < b.size(); i++) diff |= static_cast((i < a.size() ? a[i] : 0) ^ b[i]); return diff == 0; } bool AuthGate::locked(uint32_t nowMs) { if (locked_ && nowMs - lockedAtMs_ >= kLockMs) { // unsigned: right across the 49-day wrap too locked_ = false; failures_ = 0; } return locked_; } bool AuthGate::failed(uint32_t nowMs) { if (locked(nowMs)) return false; if (++failures_ < kMaxFailures) return false; locked_ = true; lockedAtMs_ = nowMs; return true; } } // namespace roro::debug