Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auth: Enable 2FA on Classic #583

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 100 additions & 6 deletions src/realmd/AuthSocket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ struct sAuthLogonProof_C
uint8 crc_hash[20];
uint8 number_of_keys;
uint8 securityFlags; // 0x00-0x04
PINData pinData;
insunaa marked this conversation as resolved.
Show resolved Hide resolved
};
/*
typedef struct
Expand Down Expand Up @@ -492,13 +493,20 @@ bool AuthSocket::_HandleLogonChallenge()
if (!self->_token.empty() && self->_build >= 8606) // authenticator was added in 2.4.3
securityFlags = SECURITY_FLAG_AUTHENTICATOR;

if (!self->_token.empty() && self->_build <= 6141)
securityFlags = SECURITY_FLAG_PIN;

*pkt << uint8(securityFlags); // security flags (0x0...0x04)

if (securityFlags & SECURITY_FLAG_PIN) // PIN input
{
*pkt << uint32(0);
*pkt << uint64(0);
*pkt << uint64(0);
uint32 gridSeedPkt = self->m_gridSeed = static_cast<uint32>(0);
EndianConvert(gridSeedPkt);
self->m_serverSecuritySalt.SetRand(16 * 8); // 16 bytes random
self->m_promptPin = true;

*pkt << gridSeedPkt;
pkt->append(self->m_serverSecuritySalt.AsByteArray(16).data(), 16);
}

if (securityFlags & SECURITY_FLAG_UNK) // Matrix input
Expand Down Expand Up @@ -539,7 +547,8 @@ bool AuthSocket::_HandleLogonProof()
DEBUG_LOG("Entering _HandleLogonProof");
///- Read the packet
std::shared_ptr<sAuthLogonProof_C> lp = std::make_shared<sAuthLogonProof_C>();
Read((char*)lp.get(), sizeof(sAuthLogonProof_C), [self = shared_from_this(), lp](const boost::system::error_code& error, std::size_t read)
auto size = m_promptPin ? sizeof(sAuthLogonProof_C) : sizeof(sAuthLogonProof_C) - sizeof(PINData);
insunaa marked this conversation as resolved.
Show resolved Hide resolved
Read((char*)lp.get(), size, [self = shared_from_this(), lp](const boost::system::error_code& error, std::size_t read)
{
if (error)
{
Expand Down Expand Up @@ -575,10 +584,23 @@ bool AuthSocket::_HandleLogonProof()
self->srp.HashSessionKey();
self->srp.CalculateProof(self->_login);

bool pinResult = true;

if (self->m_promptPin && (lp->securityFlags & SECURITY_FLAG_PIN))
pinResult = false;

if (self->m_promptPin && (lp->securityFlags & SECURITY_FLAG_PIN) && !self->_token.empty())
{
auto pin = self->generateToken(self->_token.c_str());

if (pin != uint32(-1))
pinResult = self->VerifyPinData(pin, lp->pinData);
}

///- Check if SRP6 results match (password is correct), else send an error
if (!self->srp.Proof(lp->M1, 20))
if (!self->srp.Proof(lp->M1, 20) && pinResult)
insunaa marked this conversation as resolved.
Show resolved Hide resolved
{
if (lp->securityFlags & SECURITY_FLAG_AUTHENTICATOR || !self->_token.empty())
if (self->_build > 6141 && (lp->securityFlags & SECURITY_FLAG_AUTHENTICATOR || !self->_token.empty()))
{
std::shared_ptr<uint8> pinCount = std::make_shared<uint8>();
self->Read((char*)pinCount.get(), sizeof(uint8), [self, pinCount, lp](const boost::system::error_code& error, std::size_t read)
Expand Down Expand Up @@ -1036,6 +1058,78 @@ bool AuthSocket::_HandleXferAccept()
return true;
}

// Verify PIN entry data
bool AuthSocket::VerifyPinData(uint32 pin, const PINData& clientData)
insunaa marked this conversation as resolved.
Show resolved Hide resolved
{
// remap the grid to match the client's layout
std::vector<uint8> grid { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
std::vector<uint8> remappedGrid(grid.size());

uint8* remappedIndex = remappedGrid.data();
uint32 seed = m_gridSeed;

for (size_t i = grid.size(); i > 0; --i)
{
auto remainder = seed % i;
seed /= i;
*remappedIndex = grid[remainder];

size_t copySize = i;
copySize -= remainder;
--copySize;

uint8* srcPtr = grid.data() + remainder + 1;
uint8* dstPtr = grid.data() + remainder;

std::copy(srcPtr, srcPtr + copySize, dstPtr);
++remappedIndex;
}

// convert the PIN to bytes (for ex. '1234' to {1, 2, 3, 4})
std::vector<uint8> pinBytes;

while (pin != 0)
{
pinBytes.push_back(pin % 10);
pin /= 10;
}

std::reverse(pinBytes.begin(), pinBytes.end());

// validate PIN length
if (pinBytes.size() < 4 || pinBytes.size() > 10)
return false; // PIN outside of expected range

// remap the PIN to calculate the expected client input sequence
for (size_t i = 0; i < pinBytes.size(); ++i)
{
auto index = std::find(remappedGrid.begin(), remappedGrid.end(), pinBytes[i]);
pinBytes[i] = std::distance(remappedGrid.begin(), index);
}

// convert PIN bytes to their ASCII values
for (size_t i = 0; i < pinBytes.size(); ++i)
pinBytes[i] += 0x30;

// validate the PIN, x = H(client_salt | H(server_salt | ascii(pin_bytes)))
Sha1Hash sha;
sha.UpdateData(m_serverSecuritySalt.AsByteArray());
sha.UpdateData(pinBytes.data(), pinBytes.size());
sha.Finalize();

BigNumber hash, clientHash;
hash.SetBinary(sha.GetDigest(), sha.GetLength());
clientHash.SetBinary(clientData.hash, 20);

sha.Initialize();
sha.UpdateData(clientData.salt, sizeof(clientData.salt));
sha.UpdateData(hash.AsByteArray());
sha.Finalize();
hash.SetBinary(sha.GetDigest(), sha.GetLength());

return !memcmp(hash.AsDecStr(), clientHash.AsDecStr(), 20);
}

void AuthSocket::verifyVersionAndFinalizeAuthentication(std::shared_ptr<sAuthLogonProof_C> lp)
{
if (!VerifyVersion(lp->A, sizeof(lp->A), lp->crc_hash, false))
Expand Down
11 changes: 11 additions & 0 deletions src/realmd/AuthSocket.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@

#define HMAC_RES_SIZE 20

struct PINData
{
uint8 salt[16];
uint8 hash[20];
};

struct sAuthLogonProof_C;

class AuthSocket : public MaNGOS::AsyncSocket<AuthSocket>
Expand All @@ -50,6 +56,7 @@ class AuthSocket : public MaNGOS::AsyncSocket<AuthSocket>

void SendProof(Sha1Hash sha);
void LoadRealmlist(ByteBuffer& pkt, uint32 acctid, uint8 accountSecurityLevel = 0);
bool VerifyPinData(uint32 pin, const PINData& clientData);
int32 generateToken(char const* b32key);

uint8 getEligibleRealmCount(uint8 accountSecurityLevel);
Expand Down Expand Up @@ -94,6 +101,10 @@ class AuthSocket : public MaNGOS::AsyncSocket<AuthSocket>
uint16 _build;
AccountTypes _accountSecurityLevel;

BigNumber m_serverSecuritySalt;
uint32 m_gridSeed = 0;
bool m_promptPin = false;

boost::asio::deadline_timer m_timeoutTimer;

virtual bool ProcessIncomingData() override;
Expand Down