diff --git a/docs/api/steam.guard.rst b/docs/api/steam.guard.rst new file mode 100644 index 0000000..989decb --- /dev/null +++ b/docs/api/steam.guard.rst @@ -0,0 +1,5 @@ +guard +===== + +.. automodule:: steam.guard + :members: diff --git a/docs/api/steam.rst b/docs/api/steam.rst index 70479c6..42c4673 100644 --- a/docs/api/steam.rst +++ b/docs/api/steam.rst @@ -6,6 +6,7 @@ steam steam.core steam.enums steam.globalid + steam.guard steam.steamid steam.webapi steam.webauth diff --git a/steam/guard.py b/steam/guard.py new file mode 100644 index 0000000..7d184f2 --- /dev/null +++ b/steam/guard.py @@ -0,0 +1,88 @@ +import struct +from binascii import hexlify +from time import time +from steam import webapi +from steam.core.crypto import hmac_sha1, sha1_hash + +def generate_twofactor_code(shared_secret): + """Generate Steam 2FA code for login with current time + + :param shared_secret: authenticator shared shared_secret + :type shared_secret: bytes + :return: steam two factor code + :rtype: str + """ + return generate_twofactor_code_for_time(shared_secret, time() + get_time_offset()) + +def generate_twofactor_code_for_time(shared_secret, timestamp): + """Generate Steam 2FA code for timestamp + + :param shared_secret: authenticator shared secret + :type shared_secret: bytes + :param timestamp: timestamp to use, if left out uses current time + :type timestamp: int + :return: steam two factor code + :rtype: str + """ + hmac = hmac_sha1(bytes(shared_secret), + struct.pack('>Q', int(timestamp)//30)) # this will NOT stop working in 2038 + + start = ord(hmac[19:20]) & 0xF + codeint = struct.unpack('>I', hmac[start:start+4])[0] & 0x7fffffff + + charset = '23456789BCDFGHJKMNPQRTVWXY' + code = '' + + for _ in range(5): + codeint, i = divmod(codeint, len(charset)) + code += charset[i] + + return code + +def generate_confirmation_key(identity_secret, timestamp, tag=''): + """Generate confirmation key for trades. Can only be used once. + + :param identity_secret: authenticator identity secret + :type identity_secret: bytes + :param timestamp: timestamp to use for generating key + :type timestamp: int + :param tag: tag identifies what the request, see list below + :type tag: str + :return: confirmation key + :rtype: bytes + + Tag choices: + + * ``conf`` to load the confirmations page + * ``details`` to load details about a trade + * ``allow`` to confirm a trade + * ``cancel`` to cancel a trade + + """ + data = struct.pack('>Q', int(timestamp)) + tag.encode('ascii') # this will NOT stop working in 2038 + return hmac_sha1(bytes(identity_secret), data) + +def get_time_offset(): + """Get time offset from steam server time via WebAPI + + :return: time offset + :rtype: int + """ + try: + resp = webapi.post('ITwoFactorService', 'QueryTime', 1, params={'http_timeout': 5}) + except: + return 0 + + ts = int(time()) + return int(resp.get('response', {}).get('server_time', ts)) - ts + +def generate_device_id(steamid): + """Generate Android device id + + :param steamid: Steam ID + :type steamid: :class:`.SteamID`, :class:`int` + :return: android device id + :rtype: str + """ + h = hexlify(sha1(str(steamid).encode('ascii'))).decode('ascii') + return "android:%s-%s-%s-%s-%s" % (h[:8], h[8:12], h[12:16], h[16:20], h[20:32]) diff --git a/tests/test_guard.py b/tests/test_guard.py new file mode 100644 index 0000000..a219259 --- /dev/null +++ b/tests/test_guard.py @@ -0,0 +1,19 @@ +import unittest +import mock + +from steam import guard as g + +class TCguard(unittest.TestCase): + def test_generate_twofactor_code_for_time(self): + code = g.generate_twofactor_code_for_time(b'superdupersecret', timestamp=3000030) + self.assertEqual(code, 'YRGQJ') + + code = g.generate_twofactor_code_for_time(b'superdupersecret', timestamp=3000029) + self.assertEqual(code, '94R9D') + + def test_generate_confirmation_key(self): + key = g.generate_confirmation_key(b'itsmemario', 100000) + self.assertEqual(key, b'\xed\xb5\xe5\xad\x8f\xf1\x99\x01\xc8-w\xd6\xb5 p\xccz\xd7\xd1\x05') + + key = g.generate_confirmation_key(b'itsmemario', 100000, 'allow') + self.assertEqual(key, b"Q'\x06\x80\xe1g\xa8m$\xb2hV\xe6g\x8b'\x8f\xf1L\xb0")