#!/usr/bin/env python3 """ btest EC-SRP5 authentication test client. Connects to a MikroTik btest server (RouterOS >= 6.43) and performs the EC-SRP5 handshake to authenticate. Usage: python3 btest_ecsrp5_client.py -a 172.16.81.1 -u admin -p password python3 btest_ecsrp5_client.py -a 172.16.81.1 -u admin -p password --receive """ import socket import secrets import hashlib import argparse import struct import sys import time import elliptic_curves BTEST_PORT = 2000 def sha256(data: bytes) -> bytes: return hashlib.sha256(data).digest() def hexdump(label: str, data: bytes): print(f" {label} ({len(data)} bytes): {data.hex()}") class BtestECSRP5Client: def __init__(self, host: str, port: int = BTEST_PORT): self.host = host self.port = port self.w = elliptic_curves.WCurve() self.sock = None def connect(self): self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.settimeout(10) self.sock.connect((self.host, self.port)) self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) print(f"Connected to {self.host}:{self.port}") def recv_exact(self, n: int) -> bytes: buf = b"" while len(buf) < n: chunk = self.sock.recv(n - len(buf)) if not chunk: raise ConnectionError("Connection closed") buf += chunk return buf def do_hello_and_command(self, proto=1, direction=2, conn_count=0, tx_size=0x8000): """Send command, return auth response type.""" # Receive HELLO hello = self.recv_exact(4) hexdump("HELLO", hello) assert hello == b"\x01\x00\x00\x00", f"Bad HELLO: {hello.hex()}" # Send command cmd = struct.pack("= 6.43)") return self.do_ecsrp5_auth(username, password) else: print(f"Unknown auth response: {resp.hex()}") return False def main(): parser = argparse.ArgumentParser(description="btest EC-SRP5 auth test client") parser.add_argument("-a", "--address", required=True, help="MikroTik IP address") parser.add_argument("-u", "--username", default="admin", help="Username") parser.add_argument("-p", "--password", default="", help="Password") parser.add_argument("-P", "--port", type=int, default=BTEST_PORT, help="Port") parser.add_argument("--receive", action="store_true", help="Test receive direction") args = parser.parse_args() direction = "receive" if args.receive else "receive" client = BtestECSRP5Client(args.address, args.port) try: success = client.run_test(args.username, args.password, direction) if success: print("\nAuth passed! Reading some data...") try: data = client.sock.recv(4096) hexdump("First data received", data[:64]) print(f" (total {len(data)} bytes)") # Read for a few seconds to confirm data flows client.sock.settimeout(2) total = len(data) for _ in range(5): try: data = client.sock.recv(65536) total += len(data) except socket.timeout: break print(f"\nTotal received: {total} bytes ({total * 8 / 1_000_000:.2f} Mbps over ~2s)") except Exception as e: print(f"Data read error: {e}") sys.exit(0 if success else 1) except Exception as e: print(f"Error: {e}") import traceback traceback.print_exc() sys.exit(1) if __name__ == "__main__": main()