Files
AzerothAI/deploy/classic/check-auth.py
T
2026-09-25 18:19:50 -07:00

44 lines
2.7 KiB
Python

#!/usr/bin/env python3
"""Verify Classic SRP login and realm listing without entering the game world."""
import argparse,hashlib,json,pathlib,secrets,socket,struct
def verify(host,port,credentials):
username=credentials['username'].upper().encode();password=credentials['password'].upper().encode()
def h(data):return hashlib.sha1(data).digest()
with socket.create_connection((host,port),timeout=8) as sock:
def read(n):
data=b''
while len(data)<n:
block=sock.recv(n-len(data))
if not block:raise RuntimeError('Authentication connection closed')
data+=block
return data
payload=b'WoW\0'+bytes([1,12,1])+struct.pack('<H',5875)+b'68x\0'+b'niW\0'+b'SUne'+struct.pack('<I',0)+socket.inet_aton('127.0.0.1')+bytes([len(username)])+username
sock.sendall(bytes([0,0])+struct.pack('<H',len(payload))+payload)
header=read(3)
if header!=b'\0\0\0':raise RuntimeError('Classic challenge rejected: '+header.hex())
Braw=read(32);graw=read(read(1)[0]);Nraw=read(read(1)[0]);salt=read(32);read(16)
flags=read(1)[0]
if flags:raise RuntimeError('Unexpected extra authentication factor')
N=int.from_bytes(Nraw,'little');g=int.from_bytes(graw,'little');B=int.from_bytes(Braw,'little')
a=secrets.randbits(256);A=pow(g,a,N).to_bytes(32,'little')
x=int.from_bytes(h(salt+h(username+b':'+password)),'little')
u=int.from_bytes(h(A+Braw),'little')
secret=pow((B-3*pow(g,x,N))%N,a+u*x,N).to_bytes(32,'little')
even=h(secret[::2]);odd=h(secret[1::2]);key=bytes(v for pair in zip(even,odd) for v in pair)
proof=h(bytes(x^y for x,y in zip(h(Nraw),h(graw)))+h(username)+salt+A+Braw+key)
sock.sendall(b'\x01'+A+proof+bytes(20)+b'\0\0')
result=read(2)
if result!=b'\x01\0':raise RuntimeError('Classic password proof rejected: '+result.hex())
if read(20)!=h(A+proof+key):raise RuntimeError('Server password proof did not match')
read(4)
sock.sendall(b'\x10'+bytes(4))
if read(1)!=b'\x10':raise RuntimeError('Unexpected realm-list response')
realms=read(struct.unpack('<H',read(2))[0])
if b'AzerothAI Classic\0' not in realms:raise RuntimeError('Expected Classic realm missing')
print('Classic password proof, server proof and AzerothAI Classic realm listing verified.')
if __name__=='__main__':
p=argparse.ArgumentParser();p.add_argument('--root',type=pathlib.Path,required=True);p.add_argument('--host',default='127.0.0.1');p.add_argument('--port',type=int,default=3725);a=p.parse_args()
verify(a.host,a.port,json.loads((a.root/'classic-gm-credentials.json').read_text()))