mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Support PIN-required auth servers
This commit is contained in:
parent
f247d53309
commit
62a49644a5
9 changed files with 271 additions and 13 deletions
|
|
@ -89,6 +89,7 @@ set(WOWEE_SOURCES
|
||||||
src/auth/auth_handler.cpp
|
src/auth/auth_handler.cpp
|
||||||
src/auth/auth_opcodes.cpp
|
src/auth/auth_opcodes.cpp
|
||||||
src/auth/auth_packets.cpp
|
src/auth/auth_packets.cpp
|
||||||
|
src/auth/pin_auth.cpp
|
||||||
src/auth/srp.cpp
|
src/auth/srp.cpp
|
||||||
src/auth/big_num.cpp
|
src/auth/big_num.cpp
|
||||||
src/auth/crypto.cpp
|
src/auth/crypto.cpp
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
namespace wowee {
|
namespace wowee {
|
||||||
namespace network { class TCPSocket; class Packet; }
|
namespace network { class TCPSocket; class Packet; }
|
||||||
|
|
@ -19,6 +20,7 @@ enum class AuthState {
|
||||||
CONNECTED,
|
CONNECTED,
|
||||||
CHALLENGE_SENT,
|
CHALLENGE_SENT,
|
||||||
CHALLENGE_RECEIVED,
|
CHALLENGE_RECEIVED,
|
||||||
|
PIN_REQUIRED,
|
||||||
PROOF_SENT,
|
PROOF_SENT,
|
||||||
AUTHENTICATED,
|
AUTHENTICATED,
|
||||||
REALM_LIST_REQUESTED,
|
REALM_LIST_REQUESTED,
|
||||||
|
|
@ -44,6 +46,9 @@ public:
|
||||||
// Authentication
|
// Authentication
|
||||||
void authenticate(const std::string& username, const std::string& password);
|
void authenticate(const std::string& username, const std::string& password);
|
||||||
void authenticateWithHash(const std::string& username, const std::vector<uint8_t>& authHash);
|
void authenticateWithHash(const std::string& username, const std::vector<uint8_t>& authHash);
|
||||||
|
// Optional: when the auth server requires a PIN (securityFlags & 0x01), call this to continue.
|
||||||
|
// PIN must be 4-10 digits.
|
||||||
|
void submitPin(const std::string& pin);
|
||||||
|
|
||||||
// Set client version info (call before authenticate)
|
// Set client version info (call before authenticate)
|
||||||
void setClientInfo(const ClientInfo& info) { clientInfo = info; }
|
void setClientInfo(const ClientInfo& info) { clientInfo = info; }
|
||||||
|
|
@ -96,6 +101,12 @@ private:
|
||||||
|
|
||||||
// Receive buffer
|
// Receive buffer
|
||||||
std::vector<uint8_t> receiveBuffer;
|
std::vector<uint8_t> receiveBuffer;
|
||||||
|
|
||||||
|
// Challenge security extension (PIN)
|
||||||
|
uint8_t securityFlags_ = 0;
|
||||||
|
uint32_t pinGridSeed_ = 0;
|
||||||
|
std::array<uint8_t, 16> pinServerSalt_{}; // from LOGON_CHALLENGE response
|
||||||
|
std::string pendingPin_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace auth
|
} // namespace auth
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
namespace wowee {
|
namespace wowee {
|
||||||
namespace auth {
|
namespace auth {
|
||||||
|
|
@ -38,6 +39,10 @@ struct LogonChallengeResponse {
|
||||||
std::vector<uint8_t> salt; // Salt (32 bytes)
|
std::vector<uint8_t> salt; // Salt (32 bytes)
|
||||||
uint8_t securityFlags;
|
uint8_t securityFlags;
|
||||||
|
|
||||||
|
// PIN extension (securityFlags & 0x01)
|
||||||
|
uint32_t pinGridSeed = 0;
|
||||||
|
std::array<uint8_t, 16> pinSalt{};
|
||||||
|
|
||||||
bool isSuccess() const { return result == AuthResult::SUCCESS; }
|
bool isSuccess() const { return result == AuthResult::SUCCESS; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -52,6 +57,11 @@ class LogonProofPacket {
|
||||||
public:
|
public:
|
||||||
static network::Packet build(const std::vector<uint8_t>& A,
|
static network::Packet build(const std::vector<uint8_t>& A,
|
||||||
const std::vector<uint8_t>& M1);
|
const std::vector<uint8_t>& M1);
|
||||||
|
static network::Packet build(const std::vector<uint8_t>& A,
|
||||||
|
const std::vector<uint8_t>& M1,
|
||||||
|
uint8_t securityFlags,
|
||||||
|
const std::array<uint8_t, 16>* pinClientSalt,
|
||||||
|
const std::array<uint8_t, 20>* pinHash);
|
||||||
};
|
};
|
||||||
|
|
||||||
// LOGON_PROOF response data
|
// LOGON_PROOF response data
|
||||||
|
|
|
||||||
28
include/auth/pin_auth.hpp
Normal file
28
include/auth/pin_auth.hpp
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace wowee {
|
||||||
|
namespace auth {
|
||||||
|
|
||||||
|
struct PinProof {
|
||||||
|
std::array<uint8_t, 16> clientSalt{};
|
||||||
|
std::array<uint8_t, 20> hash{};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Implements the "PIN" security extension used in the WoW auth protocol (securityFlags & 0x01).
|
||||||
|
// Algorithm based on documented client behavior:
|
||||||
|
// - Remap digits using pinGridSeed (a permutation of 0..9)
|
||||||
|
// - Convert user-entered PIN digits into randomized indices in that permutation
|
||||||
|
// - Compute: pin_hash = SHA1(client_salt || SHA1(server_salt || randomized_pin_ascii))
|
||||||
|
//
|
||||||
|
// PIN must be 4-10 ASCII digits.
|
||||||
|
PinProof computePinProof(const std::string& pinDigits,
|
||||||
|
uint32_t pinGridSeed,
|
||||||
|
const std::array<uint8_t, 16>& serverSalt);
|
||||||
|
|
||||||
|
} // namespace auth
|
||||||
|
} // namespace wowee
|
||||||
|
|
||||||
|
|
@ -54,6 +54,7 @@ private:
|
||||||
char hostname[256] = "127.0.0.1";
|
char hostname[256] = "127.0.0.1";
|
||||||
char username[256] = "";
|
char username[256] = "";
|
||||||
char password[256] = "";
|
char password[256] = "";
|
||||||
|
char pinCode[32] = "";
|
||||||
int port = 3724;
|
int port = 3724;
|
||||||
int expansionIndex = 0; // Index into expansion registry profiles
|
int expansionIndex = 0; // Index into expansion registry profiles
|
||||||
bool authenticating = false;
|
bool authenticating = false;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
#include "auth/auth_handler.hpp"
|
#include "auth/auth_handler.hpp"
|
||||||
|
#include "auth/pin_auth.hpp"
|
||||||
#include "network/tcp_socket.hpp"
|
#include "network/tcp_socket.hpp"
|
||||||
#include "network/packet.hpp"
|
#include "network/packet.hpp"
|
||||||
#include "core/logger.hpp"
|
#include "core/logger.hpp"
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
|
||||||
namespace wowee {
|
namespace wowee {
|
||||||
namespace auth {
|
namespace auth {
|
||||||
|
|
@ -17,7 +20,17 @@ AuthHandler::~AuthHandler() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AuthHandler::connect(const std::string& host, uint16_t port) {
|
bool AuthHandler::connect(const std::string& host, uint16_t port) {
|
||||||
LOG_INFO("Connecting to auth server: ", host, ":", port);
|
auto trimHost = [](std::string s) {
|
||||||
|
auto isSpace = [](unsigned char c) { return c == ' ' || c == '\t' || c == '\r' || c == '\n'; };
|
||||||
|
size_t b = 0;
|
||||||
|
while (b < s.size() && isSpace(static_cast<unsigned char>(s[b]))) ++b;
|
||||||
|
size_t e = s.size();
|
||||||
|
while (e > b && isSpace(static_cast<unsigned char>(s[e - 1]))) --e;
|
||||||
|
return s.substr(b, e - b);
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::string hostTrimmed = trimHost(host);
|
||||||
|
LOG_INFO("Connecting to auth server: ", hostTrimmed, ":", port);
|
||||||
|
|
||||||
socket = std::make_unique<network::TCPSocket>();
|
socket = std::make_unique<network::TCPSocket>();
|
||||||
|
|
||||||
|
|
@ -28,7 +41,7 @@ bool AuthHandler::connect(const std::string& host, uint16_t port) {
|
||||||
handlePacket(mutablePacket);
|
handlePacket(mutablePacket);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!socket->connect(host, port)) {
|
if (!socket->connect(hostTrimmed, port)) {
|
||||||
LOG_ERROR("Failed to connect to auth server");
|
LOG_ERROR("Failed to connect to auth server");
|
||||||
setState(AuthState::FAILED);
|
setState(AuthState::FAILED);
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -84,6 +97,10 @@ void AuthHandler::authenticate(const std::string& user, const std::string& pass)
|
||||||
|
|
||||||
username = user;
|
username = user;
|
||||||
password = pass;
|
password = pass;
|
||||||
|
pendingPin_.clear();
|
||||||
|
securityFlags_ = 0;
|
||||||
|
pinGridSeed_ = 0;
|
||||||
|
pinServerSalt_ = {};
|
||||||
|
|
||||||
// Initialize SRP
|
// Initialize SRP
|
||||||
srp = std::make_unique<SRP>();
|
srp = std::make_unique<SRP>();
|
||||||
|
|
@ -110,6 +127,10 @@ void AuthHandler::authenticateWithHash(const std::string& user, const std::vecto
|
||||||
|
|
||||||
username = user;
|
username = user;
|
||||||
password.clear();
|
password.clear();
|
||||||
|
pendingPin_.clear();
|
||||||
|
securityFlags_ = 0;
|
||||||
|
pinGridSeed_ = 0;
|
||||||
|
pinServerSalt_ = {};
|
||||||
|
|
||||||
// Initialize SRP with pre-computed hash
|
// Initialize SRP with pre-computed hash
|
||||||
srp = std::make_unique<SRP>();
|
srp = std::make_unique<SRP>();
|
||||||
|
|
@ -144,7 +165,7 @@ void AuthHandler::handleLogonChallengeResponse(network::Packet& packet) {
|
||||||
|
|
||||||
if (response.securityFlags != 0) {
|
if (response.securityFlags != 0) {
|
||||||
LOG_WARNING("Server sent security flags: 0x", std::hex, (int)response.securityFlags, std::dec);
|
LOG_WARNING("Server sent security flags: 0x", std::hex, (int)response.securityFlags, std::dec);
|
||||||
if (response.securityFlags & 0x01) LOG_WARNING(" PIN required (not supported)");
|
if (response.securityFlags & 0x01) LOG_WARNING(" PIN required");
|
||||||
if (response.securityFlags & 0x02) LOG_WARNING(" Matrix card required (not supported)");
|
if (response.securityFlags & 0x02) LOG_WARNING(" Matrix card required (not supported)");
|
||||||
if (response.securityFlags & 0x04) LOG_WARNING(" Authenticator required (not supported)");
|
if (response.securityFlags & 0x04) LOG_WARNING(" Authenticator required (not supported)");
|
||||||
}
|
}
|
||||||
|
|
@ -155,9 +176,20 @@ void AuthHandler::handleLogonChallengeResponse(network::Packet& packet) {
|
||||||
// Feed SRP with server challenge data
|
// Feed SRP with server challenge data
|
||||||
srp->feed(response.B, response.g, response.N, response.salt);
|
srp->feed(response.B, response.g, response.N, response.salt);
|
||||||
|
|
||||||
|
securityFlags_ = response.securityFlags;
|
||||||
|
if (securityFlags_ & 0x01) {
|
||||||
|
pinGridSeed_ = response.pinGridSeed;
|
||||||
|
pinServerSalt_ = response.pinSalt;
|
||||||
|
}
|
||||||
|
|
||||||
setState(AuthState::CHALLENGE_RECEIVED);
|
setState(AuthState::CHALLENGE_RECEIVED);
|
||||||
|
|
||||||
// Send LOGON_PROOF immediately
|
// If PIN is required, wait for user input.
|
||||||
|
if ((securityFlags_ & 0x01) && pendingPin_.empty()) {
|
||||||
|
setState(AuthState::PIN_REQUIRED);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
sendLogonProof();
|
sendLogonProof();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -167,12 +199,38 @@ void AuthHandler::sendLogonProof() {
|
||||||
auto A = srp->getA();
|
auto A = srp->getA();
|
||||||
auto M1 = srp->getM1();
|
auto M1 = srp->getM1();
|
||||||
|
|
||||||
auto packet = LogonProofPacket::build(A, M1);
|
std::array<uint8_t, 16> pinClientSalt{};
|
||||||
|
std::array<uint8_t, 20> pinHash{};
|
||||||
|
const std::array<uint8_t, 16>* pinClientSaltPtr = nullptr;
|
||||||
|
const std::array<uint8_t, 20>* pinHashPtr = nullptr;
|
||||||
|
|
||||||
|
if (securityFlags_ & 0x01) {
|
||||||
|
try {
|
||||||
|
PinProof proof = computePinProof(pendingPin_, pinGridSeed_, pinServerSalt_);
|
||||||
|
pinClientSalt = proof.clientSalt;
|
||||||
|
pinHash = proof.hash;
|
||||||
|
pinClientSaltPtr = &pinClientSalt;
|
||||||
|
pinHashPtr = &pinHash;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
fail(std::string("PIN required but invalid: ") + e.what());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto packet = LogonProofPacket::build(A, M1, securityFlags_, pinClientSaltPtr, pinHashPtr);
|
||||||
socket->send(packet);
|
socket->send(packet);
|
||||||
|
|
||||||
setState(AuthState::PROOF_SENT);
|
setState(AuthState::PROOF_SENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AuthHandler::submitPin(const std::string& pin) {
|
||||||
|
pendingPin_ = pin;
|
||||||
|
// If we're waiting on a PIN, continue immediately.
|
||||||
|
if (state == AuthState::PIN_REQUIRED) {
|
||||||
|
sendLogonProof();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void AuthHandler::handleLogonProofResponse(network::Packet& packet) {
|
void AuthHandler::handleLogonProofResponse(network::Packet& packet) {
|
||||||
LOG_DEBUG("Handling LOGON_PROOF response");
|
LOG_DEBUG("Handling LOGON_PROOF response");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -135,18 +135,38 @@ bool LogonChallengeResponseParser::parse(network::Packet& packet, LogonChallenge
|
||||||
// Security flags
|
// Security flags
|
||||||
response.securityFlags = packet.readUInt8();
|
response.securityFlags = packet.readUInt8();
|
||||||
|
|
||||||
|
// Optional security extensions (protocol v8+)
|
||||||
|
if (response.securityFlags & 0x01) {
|
||||||
|
// PIN required: u32 pin_grid_seed + u8[16] pin_salt
|
||||||
|
response.pinGridSeed = packet.readUInt32();
|
||||||
|
for (size_t i = 0; i < response.pinSalt.size(); ++i) {
|
||||||
|
response.pinSalt[i] = packet.readUInt8();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LOG_DEBUG("Parsed LOGON_CHALLENGE response:");
|
LOG_DEBUG("Parsed LOGON_CHALLENGE response:");
|
||||||
LOG_DEBUG(" B size: ", response.B.size(), " bytes");
|
LOG_DEBUG(" B size: ", response.B.size(), " bytes");
|
||||||
LOG_DEBUG(" g size: ", response.g.size(), " bytes");
|
LOG_DEBUG(" g size: ", response.g.size(), " bytes");
|
||||||
LOG_DEBUG(" N size: ", response.N.size(), " bytes");
|
LOG_DEBUG(" N size: ", response.N.size(), " bytes");
|
||||||
LOG_DEBUG(" salt size: ", response.salt.size(), " bytes");
|
LOG_DEBUG(" salt size: ", response.salt.size(), " bytes");
|
||||||
LOG_DEBUG(" Security flags: ", (int)response.securityFlags);
|
LOG_DEBUG(" Security flags: ", (int)response.securityFlags);
|
||||||
|
if (response.securityFlags & 0x01) {
|
||||||
|
LOG_DEBUG(" PIN grid seed: ", response.pinGridSeed);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
network::Packet LogonProofPacket::build(const std::vector<uint8_t>& A,
|
network::Packet LogonProofPacket::build(const std::vector<uint8_t>& A,
|
||||||
const std::vector<uint8_t>& M1) {
|
const std::vector<uint8_t>& M1) {
|
||||||
|
return build(A, M1, 0, nullptr, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
network::Packet LogonProofPacket::build(const std::vector<uint8_t>& A,
|
||||||
|
const std::vector<uint8_t>& M1,
|
||||||
|
uint8_t securityFlags,
|
||||||
|
const std::array<uint8_t, 16>* pinClientSalt,
|
||||||
|
const std::array<uint8_t, 20>* pinHash) {
|
||||||
if (A.size() != 32) {
|
if (A.size() != 32) {
|
||||||
LOG_ERROR("Invalid A size: ", A.size(), " (expected 32)");
|
LOG_ERROR("Invalid A size: ", A.size(), " (expected 32)");
|
||||||
}
|
}
|
||||||
|
|
@ -171,7 +191,17 @@ network::Packet LogonProofPacket::build(const std::vector<uint8_t>& A,
|
||||||
packet.writeUInt8(0);
|
packet.writeUInt8(0);
|
||||||
|
|
||||||
// Security flags
|
// Security flags
|
||||||
packet.writeUInt8(0);
|
packet.writeUInt8(securityFlags);
|
||||||
|
|
||||||
|
if (securityFlags & 0x01) {
|
||||||
|
if (!pinClientSalt || !pinHash) {
|
||||||
|
LOG_ERROR("LOGON_PROOF: PIN flag set but PIN data missing");
|
||||||
|
} else {
|
||||||
|
// PIN: u8[16] client_salt + u8[20] pin_hash
|
||||||
|
packet.writeBytes(pinClientSalt->data(), pinClientSalt->size());
|
||||||
|
packet.writeBytes(pinHash->data(), pinHash->size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LOG_DEBUG("Built LOGON_PROOF packet:");
|
LOG_DEBUG("Built LOGON_PROOF packet:");
|
||||||
LOG_DEBUG(" A size: ", A.size(), " bytes");
|
LOG_DEBUG(" A size: ", A.size(), " bytes");
|
||||||
|
|
|
||||||
108
src/auth/pin_auth.cpp
Normal file
108
src/auth/pin_auth.cpp
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
#include "auth/pin_auth.hpp"
|
||||||
|
#include "auth/crypto.hpp"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <random>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace wowee {
|
||||||
|
namespace auth {
|
||||||
|
|
||||||
|
static std::array<uint8_t, 16> randomSalt16() {
|
||||||
|
std::array<uint8_t, 16> out{};
|
||||||
|
std::random_device rd;
|
||||||
|
for (auto& b : out) {
|
||||||
|
b = static_cast<uint8_t>(rd() & 0xFFu);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::array<uint8_t, 10> remapPinGrid(uint32_t seed) {
|
||||||
|
// Generates a permutation of digits 0..9 from a seed.
|
||||||
|
// Based on:
|
||||||
|
// https://gtker.com/wow_messages/docs/auth/pin.html
|
||||||
|
uint32_t v = seed;
|
||||||
|
std::array<uint8_t, 10> remapped{};
|
||||||
|
uint8_t used = 0;
|
||||||
|
for (int i = 0; i < 10; ++i) {
|
||||||
|
uint32_t divisor = 10 - i;
|
||||||
|
uint32_t remainder = v % divisor;
|
||||||
|
v /= divisor;
|
||||||
|
|
||||||
|
uint32_t index = 0;
|
||||||
|
for (uint32_t j = 0; j < 10; ++j) {
|
||||||
|
if (used & (1u << j)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (index == remainder) {
|
||||||
|
used = static_cast<uint8_t>(used | (1u << j));
|
||||||
|
remapped[i] = static_cast<uint8_t>(j);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return remapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<uint8_t> randomizePinDigits(const std::string& pinDigits,
|
||||||
|
const std::array<uint8_t, 10>& remapped) {
|
||||||
|
// Transforms each pin digit into an index in the remapped permutation.
|
||||||
|
// Based on:
|
||||||
|
// https://gtker.com/wow_messages/docs/auth/pin.html
|
||||||
|
std::vector<uint8_t> out;
|
||||||
|
out.reserve(pinDigits.size());
|
||||||
|
|
||||||
|
for (char c : pinDigits) {
|
||||||
|
uint8_t d = static_cast<uint8_t>(c - '0');
|
||||||
|
uint8_t idx = 0xFF;
|
||||||
|
for (uint8_t j = 0; j < 10; ++j) {
|
||||||
|
if (remapped[j] == d) { idx = j; break; }
|
||||||
|
}
|
||||||
|
if (idx == 0xFF) {
|
||||||
|
throw std::runtime_error("PIN digit not found in remapped grid");
|
||||||
|
}
|
||||||
|
out.push_back(static_cast<uint8_t>(idx + 0x30)); // ASCII '0'+idx
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
PinProof computePinProof(const std::string& pinDigits,
|
||||||
|
uint32_t pinGridSeed,
|
||||||
|
const std::array<uint8_t, 16>& serverSalt) {
|
||||||
|
if (pinDigits.size() < 4 || pinDigits.size() > 10) {
|
||||||
|
throw std::runtime_error("PIN must be 4-10 digits");
|
||||||
|
}
|
||||||
|
if (!std::all_of(pinDigits.begin(), pinDigits.end(),
|
||||||
|
[](unsigned char c) { return c >= '0' && c <= '9'; })) {
|
||||||
|
throw std::runtime_error("PIN must contain only digits");
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto remapped = remapPinGrid(pinGridSeed);
|
||||||
|
const auto randomizedAsciiDigits = randomizePinDigits(pinDigits, remapped);
|
||||||
|
|
||||||
|
// server_hash = SHA1(server_salt || randomized_pin_ascii)
|
||||||
|
std::vector<uint8_t> serverHashInput;
|
||||||
|
serverHashInput.reserve(serverSalt.size() + randomizedAsciiDigits.size());
|
||||||
|
serverHashInput.insert(serverHashInput.end(), serverSalt.begin(), serverSalt.end());
|
||||||
|
serverHashInput.insert(serverHashInput.end(), randomizedAsciiDigits.begin(), randomizedAsciiDigits.end());
|
||||||
|
const auto serverHash = Crypto::sha1(serverHashInput); // 20 bytes
|
||||||
|
|
||||||
|
PinProof proof;
|
||||||
|
proof.clientSalt = randomSalt16();
|
||||||
|
|
||||||
|
// final_hash = SHA1(client_salt || server_hash)
|
||||||
|
std::vector<uint8_t> finalInput;
|
||||||
|
finalInput.reserve(proof.clientSalt.size() + serverHash.size());
|
||||||
|
finalInput.insert(finalInput.end(), proof.clientSalt.begin(), proof.clientSalt.end());
|
||||||
|
finalInput.insert(finalInput.end(), serverHash.begin(), serverHash.end());
|
||||||
|
const auto finalHash = Crypto::sha1(finalInput);
|
||||||
|
std::copy_n(finalHash.begin(), proof.hash.size(), proof.hash.begin());
|
||||||
|
|
||||||
|
return proof;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace auth
|
||||||
|
} // namespace wowee
|
||||||
|
|
||||||
|
|
@ -354,15 +354,26 @@ void AuthScreen::render(auth::AuthHandler& authHandler) {
|
||||||
|
|
||||||
// Connect button
|
// Connect button
|
||||||
if (authenticating) {
|
if (authenticating) {
|
||||||
authTimer += ImGui::GetIO().DeltaTime;
|
auto state = authHandler.getState();
|
||||||
|
if (state != auth::AuthState::PIN_REQUIRED) {
|
||||||
|
authTimer += ImGui::GetIO().DeltaTime;
|
||||||
|
|
||||||
// Show progress with elapsed time
|
// Show progress with elapsed time
|
||||||
char progressBuf[128];
|
char progressBuf[128];
|
||||||
snprintf(progressBuf, sizeof(progressBuf), "Authenticating... (%.0fs)", authTimer);
|
snprintf(progressBuf, sizeof(progressBuf), "Authenticating... (%.0fs)", authTimer);
|
||||||
ImGui::Text("%s", progressBuf);
|
ImGui::Text("%s", progressBuf);
|
||||||
|
} else {
|
||||||
|
ImGui::TextWrapped("This server requires a PIN. Enter your PIN to continue.");
|
||||||
|
ImGui::InputText("PIN", pinCode, sizeof(pinCode), ImGuiInputTextFlags_Password);
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Submit PIN")) {
|
||||||
|
authHandler.submitPin(pinCode);
|
||||||
|
// Don't keep the PIN around longer than needed.
|
||||||
|
pinCode[0] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check authentication status
|
// Check authentication status
|
||||||
auto state = authHandler.getState();
|
|
||||||
if (state == auth::AuthState::AUTHENTICATED) {
|
if (state == auth::AuthState::AUTHENTICATED) {
|
||||||
setStatus("Authentication successful!", false);
|
setStatus("Authentication successful!", false);
|
||||||
authenticating = false;
|
authenticating = false;
|
||||||
|
|
@ -390,7 +401,7 @@ void AuthScreen::render(auth::AuthHandler& authHandler) {
|
||||||
setStatus("Authentication failed", true);
|
setStatus("Authentication failed", true);
|
||||||
}
|
}
|
||||||
authenticating = false;
|
authenticating = false;
|
||||||
} else if (authTimer >= AUTH_TIMEOUT) {
|
} else if (state != auth::AuthState::PIN_REQUIRED && authTimer >= AUTH_TIMEOUT) {
|
||||||
setStatus("Connection timed out - server did not respond", true);
|
setStatus("Connection timed out - server did not respond", true);
|
||||||
authenticating = false;
|
authenticating = false;
|
||||||
authHandler.disconnect();
|
authHandler.disconnect();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue