# Reimplementation of the Steam Mobile Authentication protocol, as demonstrated
# by https://github.com/geel9/SteamAuth (MIT-licensed, as is this code).

import base64
import hashlib
import hmac
import time

_time_offset = None # TODO: Align clocks with Valve

def get_default_user():
	print("TODO: check if there's exactly one user, and if so,")
	print("default to that user. Not yet implemented.")
	raise SystemExit

def timecheck():
	url = "https://api.steampowered.com/ITwoFactorService/QueryTime/v0001"
	import requests # ImportError? Install 'requests' using pip or similar.
	data = requests.post(url, "steamid=0").json()
	return int(data["response"]["server_time"]) - int(time.time())

def do_timecheck(user):
	"""Check your clock against Valve's"""
	offset = timecheck()
	if offset == 0:
		print("Your clock is in sync with Valve's (+/- 0.5 sec)")
	elif -3 < offset < 3:
		# A tiny offset is unlikely to cause issues. TODO: Figure out
		# how far out the clocks can be and still not be a problem.
		print("Your clock is in sync with Valve's (%+d sec)" % offset)
	else:
		print("CAUTION: Your clock is %+d sec out of sync" % offset)
		print("Consider adjusting your clock, or use --time-sync")

def generate_code(secret, timestamp=None):
	"""Generate a SteamGuard code for a given shared secret

	If timestamp is None, will generate one for the current
	time; otherwise it should be a Unix time.

	>>> generate_code("HvveV1y11i/lGOqBWdo3a1fU/290", 1516187198)
	"2K3V8"
	"""
	secret = base64.b64decode(secret)
	if timestamp is None:
		timestamp = int(time.time())
	timestamp //= 30 # The code regenerates every half minute
	timestamp = timestamp.to_bytes(8, "big")
	hash = hmac.new(secret, timestamp, hashlib.sha1).digest()
	# The low four bits of the last byte tell us where to grab four bytes from.
	b = hash[-1] & 15
	quad = hash[b:b+4]
	# The code seems to be masked off to 31-bit (??)
	code = int.from_bytes(quad, "big") & 0x7FFFFFFF
	alphabet = "23456789BCDFGHJKMNPQRTVWXY"
	ret = ""
	for _ in range(5):
		ret += alphabet[code % 26]
		code //= 26
	return ret

def do_code(user):
	"""Generate an auth code for logins"""
	# TODO: Retrieve the saved shared-secret, decode it if necessary,
	# and call generate_code on that secret.
	if not user: user = get_default_user()
	print("Stub, unimplemented")

def do_trade(user):
	"""Accept all pending trades/markets"""
	if not user: user = get_default_user()
	print("Stub, unimplemented")

def do_setup(user):
	"""Set up a new user"""
	print("Stub, unimplemented")

def usage():
	print("USAGE: python3 steamguard.py [command] [user]")
	print("command is one of:")
	for func in sorted(globals()):
		if func.startswith("do_"):
			doc = globals()[func].__doc__.split("\n")[0]
			print("%-10s %s" % (func[3:], doc))
	print("user is the name of the Steam account to use - optional if")
	print("only one account is registered with this app.")
	return 1

def main(args):
	func = user = None
	for arg in args:
		f = globals().get("do_" + arg)
		if f:
			if func: return usage()
			func = f
		else:
			if user: return usage()
			user = arg
	if not func: func = do_code
	func(user)

if __name__ == "__main__":
	import sys
	sys.exit(main(sys.argv[1:]))

@@ -206,16 +206,96 @@
 rt time%0D
+%0A# import requests # Not done globally as it's big and not all calls require it%0D
 %0A%0D%0A_time
@@ -2641,36 +2641,683 @@
 %22%0D%0A%09
-print(%22Stub, unimplemented%22)
+if not user:%0D%0A%09%09user = input(%22User name: %22)%0D%0A%09%09if not user: return%0D%0A%09import getpass%0D%0A%09password = getpass.getpass()%0D%0A%09import requests%0D%0A%09data = requests.post(%22https://steamcommunity.com/login/getrsakey%22, %7B%22username%22: user%7D).json()%0D%0A%09pkey = int(data%5B%22publickey_mod%22%5D, 16)%0D%0A%09rsa_timestamp = data%5B%22timestamp%22%5D%0D%0A%09# TODO: Encrypt the password (encoded ASCII or maybe UTF-8) using the%0D%0A%09# public key given.%0D%0A%09password = base64.b64encode(b%22...%22)%0D%0A%09data = requests.post(%22https://steamcommunity.com/login/dologin%22, %7B%0D%0A%09%09%22username%22: user, %22password%22: password,%0D%0A%09%09%22rsatimestamp%22: rsa_timestamp,%0D%0A%09%7D).json()%0D%0A%09print(data)%0D%0A%09# Next step: see if email or 2FA is needed, and prompt for that.
 %0D%0A%0D%0A

Implement most of logins