diff --git a/albert.py b/albert.py index bdf25b1..5a3d330 100644 --- a/albert.py +++ b/albert.py @@ -79,7 +79,8 @@ def generate_push_cert() -> tuple[str, str]: resp = requests.post( "https://albert.apple.com/WebObjects/ALUnbrick.woa/wa/deviceActivation?device=Windows", - data={"activation-info": plistlib.dumps(body)}, verify=False, + data={"activation-info": plistlib.dumps(body)}, + verify=False, ) protocol = re.search("(.*)", resp.text).group(1) diff --git a/apns.py b/apns.py index 8f6a99b..5b1f459 100644 --- a/apns.py +++ b/apns.py @@ -29,31 +29,32 @@ def _connect(private_key: str, cert: str) -> tlslite.TLSConnection: return sock + class IncomingQueue: def __init__(self): self.queue = [] self.lock = threading.Lock() - + def append(self, item): with self.lock: self.queue.append(item) - + def pop(self, index): with self.lock: return self.queue.pop(index) - + def __getitem__(self, index): with self.lock: return self.queue[index] - + def __len__(self): with self.lock: return len(self.queue) - + def find(self, finder): with self.lock: return next((i for i in self.queue if finder(i)), None) - + def pop_find(self, finder): with self.lock: found = next((i for i in self.queue if finder(i)), None) @@ -61,7 +62,7 @@ class IncomingQueue: # We have the lock, so we can safely remove it self.queue.remove(found) return found - + def wait_pop_find(self, finder, delay=0.1): found = None while found is None: @@ -70,6 +71,7 @@ class IncomingQueue: time.sleep(delay) return found + class APNSConnection: incoming_queue = IncomingQueue() @@ -87,8 +89,8 @@ class APNSConnection: # print("QUEUE: Got payload?") if payload is not None: - #print("QUEUE: Received payload: " + str(payload)) - #print("QUEUE: Received payload type: " + hex(payload[0])) + # print("QUEUE: Received payload: " + str(payload)) + # print("QUEUE: Received payload type: " + hex(payload[0])) self.incoming_queue.append(payload) # print("QUEUE: Thread ended") @@ -109,10 +111,10 @@ class APNSConnection: # if found is None: # time.sleep(0.1) # return found - - # def find_packet(self, finder) -> - - #def replace_packet(self, payload: tuple[int, list[tuple[int, bytes]]]): + + # def find_packet(self, finder) -> + + # def replace_packet(self, payload: tuple[int, list[tuple[int, bytes]]]): # self.incoming_queue.append(payload) def __init__(self, private_key=None, cert=None): @@ -137,11 +139,16 @@ class APNSConnection: if token is None: payload = _serialize_payload( - 7, [(2, 0x01.to_bytes(1, 'big')), (5, flags.to_bytes(4, 'big'))] + 7, [(2, 0x01.to_bytes(1, "big")), (5, flags.to_bytes(4, "big"))] ) else: payload = _serialize_payload( - 7, [(1, token), (2, 0x01.to_bytes(1, 'big')), (5, flags.to_bytes(4, 'big'))] + 7, + [ + (1, token), + (2, 0x01.to_bytes(1, "big")), + (5, flags.to_bytes(4, "big")), + ], ) self.sock.write(payload) @@ -151,7 +158,7 @@ class APNSConnection: if ( payload == None or payload[0] != 8 - or _get_field(payload[1], 1) != 0x00.to_bytes(1, 'big') + or _get_field(payload[1], 1) != 0x00.to_bytes(1, "big") ): raise Exception("Failed to connect") @@ -194,13 +201,14 @@ class APNSConnection: # Wait for ACK payload = self.incoming_queue.wait_pop_find(lambda i: i[0] == 0x0B) - if payload[1][0][1] != 0x00.to_bytes(1, 'big'): + if payload[1][0][1] != 0x00.to_bytes(1, "big"): raise Exception("Failed to send message") def set_state(self, state: int): self.sock.write( _serialize_payload( - 0x14, [(1, state.to_bytes(1, 'big')), (2, 0x7FFFFFFF.to_bytes(4, 'big'))] + 0x14, + [(1, state.to_bytes(1, "big")), (2, 0x7FFFFFFF.to_bytes(4, "big"))], ) ) @@ -226,7 +234,7 @@ class APNSConnection: def _serialize_field(id: int, value: bytes) -> bytes: - return id.to_bytes(1, 'big') + len(value).to_bytes(2, "big") + value + return id.to_bytes(1, "big") + len(value).to_bytes(2, "big") + value def _serialize_payload(id: int, fields: list[(int, bytes)]) -> bytes: @@ -236,7 +244,7 @@ def _serialize_payload(id: int, fields: list[(int, bytes)]) -> bytes: if fid is not None: payload += _serialize_field(fid, value) - return id.to_bytes(1, 'big') + len(payload).to_bytes(4, "big") + payload + return id.to_bytes(1, "big") + len(payload).to_bytes(4, "big") + payload def _deserialize_field(stream: bytes) -> tuple[int, bytes]: diff --git a/bags.py b/bags.py index 3f362b6..ca27903 100644 --- a/bags.py +++ b/bags.py @@ -45,11 +45,11 @@ if __name__ == "__main__": # config = get_config() # print(config) # print(apns_init_bag_2()) - #print(apns_init_bag_2() == apns_init_bag()) + # print(apns_init_bag_2() == apns_init_bag()) bag = ids_bag() for key in bag: - #print(key) - #print(bag[key]) + # print(key) + # print(bag[key]) if type(bag[key]) == str: - if 'http' in bag[key]: + if "http" in bag[key]: print(key, bag[key]) diff --git a/demo.py b/demo.py index 839cbc5..b3bd117 100644 --- a/demo.py +++ b/demo.py @@ -1,8 +1,9 @@ -from ids import * -import ids import getpass import json +import ids +from ids import * + # Open config try: with open("config.json", "r") as f: @@ -10,6 +11,7 @@ try: except FileNotFoundError: CONFIG = {} + def input_multiline(prompt): print(prompt) lines = [] @@ -20,6 +22,7 @@ def input_multiline(prompt): lines.append(line) return "\n".join(lines) + def refresh_token(): # If no username is set, prompt for it if "username" not in CONFIG: @@ -38,44 +41,48 @@ def refresh_token(): CONFIG["username"], CONFIG["password"], CONFIG["use_gsa"], factor_gen=factor_gen ) + def refresh_cert(): CONFIG["key"], CONFIG["auth_cert"] = ids._get_auth_cert( CONFIG["user_id"], CONFIG["token"] ) -def create_connection(): + +def create_connection(): conn = apns.APNSConnection() token = conn.connect() - #conn.filter(['com.apple.madrid']) - CONFIG['push'] = { - 'token': b64encode(token).decode(), - 'cert': conn.cert, - 'key': conn.private_key + # conn.filter(['com.apple.madrid']) + CONFIG["push"] = { + "token": b64encode(token).decode(), + "cert": conn.cert, + "key": conn.private_key, } return conn + def restore_connection(): - conn = apns.APNSConnection(CONFIG['push']['key'], CONFIG['push']['cert']) - conn.connect(True, b64decode(CONFIG['push']['token'])) - #conn.filter(['com.apple.madrid', 'com.apple.private.alloy.facetime.multi']) + conn = apns.APNSConnection(CONFIG["push"]["key"], CONFIG["push"]["cert"]) + conn.connect(True, b64decode(CONFIG["push"]["token"])) + # conn.filter(['com.apple.madrid', 'com.apple.private.alloy.facetime.multi']) return conn + def refresh_ids_cert(): info = { "uri": "mailto:" + CONFIG["username"], - "user_id": CONFIG['user_id'], + "user_id": CONFIG["user_id"], } resp = None try: if "validation_data" in CONFIG: resp = ids._register_request( - CONFIG['push']['token'], + CONFIG["push"]["token"], info, - CONFIG['auth_cert'], - CONFIG['key'], - CONFIG['push']['cert'], - CONFIG['push']['key'], + CONFIG["auth_cert"], + CONFIG["key"], + CONFIG["push"]["cert"], + CONFIG["push"]["key"], CONFIG["validation_data"], ) except Exception as e: @@ -92,40 +99,36 @@ def refresh_ids_cert(): .replace(" ", "") ) resp = ids._register_request( - CONFIG['push']['token'], + CONFIG["push"]["token"], info, - CONFIG['auth_cert'], - CONFIG['key'], - CONFIG['push']['cert'], - CONFIG['push']['key'], + CONFIG["auth_cert"], + CONFIG["key"], + CONFIG["push"]["cert"], + CONFIG["push"]["key"], validation_data, ) CONFIG["validation_data"] = validation_data - ids_cert = x509.load_der_x509_certificate( - resp["services"][0]["users"][0]["cert"] - ) - ids_cert = ( - ids_cert.public_bytes(serialization.Encoding.PEM).decode("utf-8").strip() - ) + ids_cert = x509.load_der_x509_certificate(resp["services"][0]["users"][0]["cert"]) + ids_cert = ids_cert.public_bytes(serialization.Encoding.PEM).decode("utf-8").strip() CONFIG["ids_cert"] = ids_cert -if not 'push' in CONFIG: +if not "push" in CONFIG: print("No existing APNs credentials, creating new ones...") - #print("No push conn") + # print("No push conn") conn = create_connection() else: print("Restoring APNs credentials...") conn = restore_connection() print("Connected to APNs!") -if not 'ids_cert' in CONFIG: +if not "ids_cert" in CONFIG: print("No existing IDS certificate, creating new one...") - if not 'key' in CONFIG: + if not "key" in CONFIG: print("No existing authentication certificate, creating new one...") - if not 'token' in CONFIG: + if not "token" in CONFIG: print("No existing authentication token, creating new one...") refresh_token() print("Got authentication token!") @@ -134,34 +137,36 @@ if not 'ids_cert' in CONFIG: refresh_ids_cert() print("Got IDS certificate!") -ids_keypair = ids.KeyPair(CONFIG['key'], CONFIG['ids_cert']) +ids_keypair = ids.KeyPair(CONFIG["key"], CONFIG["ids_cert"]) -def lookup(topic:str, users: list[str]): + +def lookup(topic: str, users: list[str]): print(f"Looking up users {users} for topic {topic}...") - resp = ids.lookup(conn, CONFIG['username'], ids_keypair, topic, users) + resp = ids.lookup(conn, CONFIG["username"], ids_keypair, topic, users) - #print(resp) - #r = list(resp['results'].values())[0] - for k, v in resp['results'].items(): + # print(resp) + # r = list(resp['results'].values())[0] + for k, v in resp["results"].items(): print(f"Result for user {k} topic {topic}:") - i = v['identities'] + i = v["identities"] print(f"IDENTITIES: {len(i)}") for iden in i: print("IDENTITY", end=" ") print(f"Push Token: {b64encode(iden['push-token']).decode()}", end=" ") - if 'client-data' in iden: + if "client-data" in iden: print(f"Client Data: {len(iden['client-data'])}") - + else: print("No client data") + # Hack to make sure that the requests and responses match up # This filter MUST contain all the topics you are looking up -#conn.filter(['com.apple.madrid', 'com.apple.private.alloy.facetime.multi', 'com.apple.private.alloy.multiplex1', 'com.apple.private.alloy.screensharing']) -#import time -#print("...waiting for queued messages... (this is a hack)") -#time.sleep(5) # Let the server send us any messages it was holding -#conn.sink() # Dump the messages +# conn.filter(['com.apple.madrid', 'com.apple.private.alloy.facetime.multi', 'com.apple.private.alloy.multiplex1', 'com.apple.private.alloy.screensharing']) +# import time +# print("...waiting for queued messages... (this is a hack)") +# time.sleep(5) # Let the server send us any messages it was holding +# conn.sink() # Dump the messages lookup("com.apple.madrid", ["mailto:jjtech@jjtech.dev"]) lookup("com.apple.private.alloy.facetime.multi", ["mailto:jjtech@jjtech.dev"]) @@ -173,7 +178,7 @@ lookup("com.apple.private.alloy.multiplex1", ["mailto:user_test2@icloud.com"]) lookup("com.apple.private.alloy.screensharing", ["mailto:user_test2@icloud.com"]) -#time.sleep(4) +# time.sleep(4) # Save config with open("config.json", "w") as f: - json.dump(CONFIG, f, indent=4) \ No newline at end of file + json.dump(CONFIG, f, indent=4) diff --git a/development/demo.py b/development/demo.py index 7f25c82..70ff35b 100644 --- a/development/demo.py +++ b/development/demo.py @@ -12,6 +12,6 @@ conn1.connect() conn1.filter(["com.apple.madrid"]) -#print(ids.lookup(conn1, ["mailto:jjtech@jjtech.dev"])) +# print(ids.lookup(conn1, ["mailto:jjtech@jjtech.dev"])) print(ids.register(conn1, "user_test2@icloud.com", "wowSecure1")) diff --git a/development/proxy/proxy.py b/development/proxy/proxy.py index ba2042e..0ca16ac 100644 --- a/development/proxy/proxy.py +++ b/development/proxy/proxy.py @@ -47,9 +47,10 @@ cert: str = None key: str = None -import apns import printer +import apns + outgoing_list = [] incoming_list = [] # last_outgoing = b"" diff --git a/development/test.py b/development/test.py index a8e7b05..41bbc45 100644 --- a/development/test.py +++ b/development/test.py @@ -1,10 +1,11 @@ -import ids import hashlib -from base64 import b64decode -import zlib import plistlib +import zlib +from base64 import b64decode -with open('body.txt', 'r') as f: +import ids + +with open("body.txt", "r") as f: BODY = f.read() CERT = "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" @@ -12,19 +13,21 @@ SIG = "AQEZs/u9Ptb8AmFpCv5XgzUsskvcleZDBxYTe5JOoshFCxpnByTwFA0mxplklHqT2rTEeF+Bu TOKEN = "5V7AY+ikHr4DiSfq1W2UBa71G3FLGkpUSKTrOLg81yk=" NONCE = "AQAAAYeBb0XwKDMBW5PfAPM=" + def extract_hash(sig: bytes, cert: str) -> str: - #sig = b64decode(SIG)[2:] + # sig = b64decode(SIG)[2:] sig = int.from_bytes(sig) # Get the correct hash from cryptography.hazmat.backends import default_backend from cryptography.x509 import load_pem_x509_certificate - #key, cert = load_keys() + + # key, cert = load_keys() cert = "-----BEGIN CERTIFICATE-----\n" + cert + "\n-----END CERTIFICATE-----" cert = load_pem_x509_certificate(cert.encode(), default_backend()) modulus = cert.public_key().public_numbers().n power = cert.public_key().public_numbers().e - #print(hex(pow(sig, power, modulus))) + # print(hex(pow(sig, power, modulus))) return pow(sig, power, modulus) # from cryptography import x509 @@ -45,13 +48,14 @@ def extract_hash(sig: bytes, cert: str) -> str: # return hash -body = plistlib.dumps(plistlib.loads(BODY.encode())) -body = zlib.compress(BODY.encode(), wbits=16+zlib.MAX_WBITS) -p = ids._create_payload('id-register', '', TOKEN, body, b64decode(NONCE))[0] +body = plistlib.dumps(plistlib.loads(BODY.encode())) +body = zlib.compress(BODY.encode(), wbits=16 + zlib.MAX_WBITS) + +p = ids._create_payload("id-register", "", TOKEN, body, b64decode(NONCE))[0] s = hashlib.sha1(p).digest() print(s.hex()) -#extract_hash(SIG, CERT) +# extract_hash(SIG, CERT) # Loop through all POSSIBLE ranges # sig = b64decode(SIG) @@ -71,12 +75,11 @@ print(s.hex()) # #print(hex(extract_hash(SIG, CERT))) -#CERT2 = "MIICnjCCAgegAwIBAgIKBAr40/DyW42YxjANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEVMBMGA1UECxMMQXBwbGUgaVBob25lMR8wHQYDVQQDExZBcHBsZSBpUGhvbmUgRGV2aWNlIENBMB4XDTIzMDQwNzE0MTUwNVoXDTI0MDQwNzE0MjAwNVowLzEtMCsGA1UEAxYkOUNCOTkzMTYtNkJERi00REYzLUFCRjUtNzcxNDU5MjFFQkY1MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCwsLiv8cifPdQZJQZtWvoD0WoTekSGwRj7KhxOi+AC1EUTdByWna8l7DDnixqww01FyA9pCBwottv0Xk9lOsrJrK05RXS+A7IieycejnUMdmRkgS7AsHIXOUSjtlkg2sfz5eYV9cqemTJnhdOvKtbqb9lYVN/8EehXD5JuogN+vwIDAQABo4GVMIGSMB8GA1UdIwQYMBaAFLL+ISNEhpVqedWBJo5zENinTI50MB0GA1UdDgQWBBQCl798/NQ3s5KywbJjoCjfjvWvmDAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIFoDAgBgNVHSUBAf8EFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwEAYKKoZIhvdjZAYKBAQCBQAwDQYJKoZIhvcNAQEFBQADgYEAfBwkujrswCn+wtu0eKCa39Cv58YC3AhK24Aj2iwXbddHaj9B9ye6HDy1BHPG21LKNGqm4X/XEtJQ3ZY/hGr4eenmtYjOI4a/oi127mrSt7uZmoib9x5S6w68eCCKkO+DD2JqDbMr2ATUhVNUxMegrzYdju8LofYqXBKzkoZ0/nk=" -#SIG2 = "AQGOTyyRBWMxoGWqEUl5bZJXssL6bkK4acxIDOCJTUy0MMavNEwtFThZkqVpQFqjB7eXNBM6PxwPtmwHmf/5IWgIkBUthIwhGJV3pLUkhDHTVX5YjbUSF7Z4y+Y39BQ2hhYjfcz1bw2KH40MByt+bnk28Xv2XaKWYuBinH9PVajp3g==" +# CERT2 = "MIICnjCCAgegAwIBAgIKBAr40/DyW42YxjANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEVMBMGA1UECxMMQXBwbGUgaVBob25lMR8wHQYDVQQDExZBcHBsZSBpUGhvbmUgRGV2aWNlIENBMB4XDTIzMDQwNzE0MTUwNVoXDTI0MDQwNzE0MjAwNVowLzEtMCsGA1UEAxYkOUNCOTkzMTYtNkJERi00REYzLUFCRjUtNzcxNDU5MjFFQkY1MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCwsLiv8cifPdQZJQZtWvoD0WoTekSGwRj7KhxOi+AC1EUTdByWna8l7DDnixqww01FyA9pCBwottv0Xk9lOsrJrK05RXS+A7IieycejnUMdmRkgS7AsHIXOUSjtlkg2sfz5eYV9cqemTJnhdOvKtbqb9lYVN/8EehXD5JuogN+vwIDAQABo4GVMIGSMB8GA1UdIwQYMBaAFLL+ISNEhpVqedWBJo5zENinTI50MB0GA1UdDgQWBBQCl798/NQ3s5KywbJjoCjfjvWvmDAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIFoDAgBgNVHSUBAf8EFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwEAYKKoZIhvdjZAYKBAQCBQAwDQYJKoZIhvcNAQEFBQADgYEAfBwkujrswCn+wtu0eKCa39Cv58YC3AhK24Aj2iwXbddHaj9B9ye6HDy1BHPG21LKNGqm4X/XEtJQ3ZY/hGr4eenmtYjOI4a/oi127mrSt7uZmoib9x5S6w68eCCKkO+DD2JqDbMr2ATUhVNUxMegrzYdju8LofYqXBKzkoZ0/nk=" +# SIG2 = "AQGOTyyRBWMxoGWqEUl5bZJXssL6bkK4acxIDOCJTUy0MMavNEwtFThZkqVpQFqjB7eXNBM6PxwPtmwHmf/5IWgIkBUthIwhGJV3pLUkhDHTVX5YjbUSF7Z4y+Y39BQ2hhYjfcz1bw2KH40MByt+bnk28Xv2XaKWYuBinH9PVajp3g==" SIG3 = "AQEZs/u9Ptb8AmFpCv5XgzUsskvcleZDBxYTe5JOoshFCxpnByTwFA0mxplklHqT2rTEeF+Bu0Bo0vEPlh9KslmIoQLo6ej25bbtFN07dnHNwd84xzQzWBa4VHLQE1gNjSpcorppxpAUon/eFRu5yRxmwQVmqo+XmmxSuFCzxUaAZAPFPDna+8tvRwd0q3kuK9b0w/kuT16X1SL166fFNzmsQGcBqob9C9xX0VlYGqSd4K975gWdYsPo/kiY0ni4Q130oc6oAANr8ATN0bEeAO6/AfVM2aqHJTGlYlekBFWf8Tp8AJLUc4cm676346IEBST+l4rYGxYYStV2PEmp9cZ+" CERT3 = "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" print(hex(extract_hash(b64decode(SIG)[2:], CERT))) - diff --git a/gsa.py b/gsa.py index f49949c..c8dbfe4 100644 --- a/gsa.py +++ b/gsa.py @@ -1,24 +1,25 @@ -from base64 import b64encode, b64decode -from datetime import datetime -from random import randbytes -import uuid -import locale -import plistlib as plist -import json +import getpass import hashlib import hmac +import json +import locale +import plistlib as plist +import uuid +from base64 import b64decode, b64encode +from datetime import datetime +from random import randbytes + +import pbkdf2 import requests import srp._pysrp as srp -import pbkdf2 -from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives import padding -import getpass +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes # Constants DEBUG = True # Allows using a proxy for debugging (disables SSL verification) # Server to use for anisette generation -#ANISETTE = "https://sign.rheaa.xyz/" -#ANISETTE = 'http://45.132.246.138:6969/' +# ANISETTE = "https://sign.rheaa.xyz/" +# ANISETTE = 'http://45.132.246.138:6969/' ANISETTE = False # ANISETTE = 'https://sideloadly.io/anisette/irGb3Quww8zrhgqnzmrx' # ANISETTE = "http://jkcoxson.com:2052/" @@ -35,11 +36,13 @@ urllib3.disable_warnings() def generate_anisette() -> dict: import objc - from Foundation import NSBundle, NSClassFromString #type: ignore + from Foundation import NSBundle, NSClassFromString # type: ignore - AOSKitBundle = NSBundle.bundleWithPath_('/System/Library/PrivateFrameworks/AOSKit.framework') - objc.loadBundleFunctions(AOSKitBundle, globals(), [("retrieveOTPHeadersForDSID", b'')]) #type: ignore - util = NSClassFromString('AOSUtilities') + AOSKitBundle = NSBundle.bundleWithPath_( + "/System/Library/PrivateFrameworks/AOSKit.framework" + ) + objc.loadBundleFunctions(AOSKitBundle, globals(), [("retrieveOTPHeadersForDSID", b"")]) # type: ignore + util = NSClassFromString("AOSUtilities") h = util.retrieveOTPHeadersForDSID_("-2") @@ -47,13 +50,13 @@ def generate_anisette() -> dict: "X-Apple-I-MD": str(h["X-Apple-MD"]), "X-Apple-I-MD-M": str(h["X-Apple-MD-M"]), } - #h["X-Apple-I-MD"] = str(h["X-Apple-MD"]) - #h["X-Apple-I-MD-M"] = str(h["X-Apple-MD-M"]) - #print(o) + # h["X-Apple-I-MD"] = str(h["X-Apple-MD"]) + # h["X-Apple-I-MD-M"] = str(h["X-Apple-MD-M"]) + # print(o) return o - #r = requests.get(ANISETTE, verify=False if DEBUG else True, timeout=5) - #r = json.loads(r.text) - #return r + # r = requests.get(ANISETTE, verify=False if DEBUG else True, timeout=5) + # r = json.loads(r.text) + # return r class Anisette: @@ -304,7 +307,7 @@ def authenticated_request(parameters, anisette: Anisette) -> dict: } resp = requests.post( - #"https://17.32.194.2/grandslam/GsService2", + # "https://17.32.194.2/grandslam/GsService2", "https://gsa.apple.com/grandslam/GsService2", headers=headers, data=plist.dumps(body), @@ -460,7 +463,7 @@ def authenticate(username, password, anisette: Anisette): { "A2k": A, "ps": ["s2k", "s2k_fo"], - #"ps": ["s2k"], + # "ps": ["s2k"], "u": username, "o": "init", }, @@ -527,5 +530,5 @@ def authenticate(username, password, anisette: Anisette): print(f"Unknown auth value {r['Status']['au']}") return else: - #print("Assuming 2FA is not required") + # print("Assuming 2FA is not required") return spd diff --git a/ids.py b/ids.py index 4b56951..5a506fb 100644 --- a/ids.py +++ b/ids.py @@ -1,8 +1,9 @@ +import gzip import plistlib import random import uuid -import gzip from base64 import b64decode, b64encode +from collections import namedtuple from datetime import datetime import requests @@ -11,7 +12,6 @@ from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import padding, rsa from cryptography.x509.oid import NameOID -from collections import namedtuple import apns import bags @@ -21,6 +21,7 @@ USER_AGENT = "com.apple.madrid-lookup [macOS,13.2.1,22D68,MacBookPro18,3]" KeyPair = namedtuple("KeyPair", ["key", "cert"]) + # Nonce Format: # 01000001876bd0a2c0e571093967fce3d7 # 01 # version @@ -33,6 +34,7 @@ def generate_nonce() -> bytes: + random.randbytes(8) ) + def _create_payload( bag_key: str, query_string: str, @@ -47,13 +49,13 @@ def _create_payload( return ( nonce - + len(bag_key).to_bytes(4, 'big') + + len(bag_key).to_bytes(4, "big") + bag_key.encode() - + len(query_string).to_bytes(4, 'big') + + len(query_string).to_bytes(4, "big") + query_string.encode() - + len(payload).to_bytes(4, 'big') + + len(payload).to_bytes(4, "big") + payload - + len(push_token).to_bytes(4, 'big') + + len(push_token).to_bytes(4, "big") + push_token, nonce, ) @@ -78,7 +80,15 @@ def sign_payload( # global_key, global_cert = load_keys() -def _send_request(conn: apns.APNSConnection, bag_key: str, topic: str, body: bytes, keypair: KeyPair, username: str) -> bytes: + +def _send_request( + conn: apns.APNSConnection, + bag_key: str, + topic: str, + body: bytes, + keypair: KeyPair, + username: str, +) -> bytes: body = gzip.compress(body, mtime=0) push_token = b64encode(conn.token).decode() @@ -93,12 +103,12 @@ def _send_request(conn: apns.APNSConnection, bag_key: str, topic: str, body: byt "x-id-nonce": b64encode(nonce).decode(), "x-id-sig": signature, "x-push-token": push_token, - "x-id-self-uri": 'mailto:' + username, + "x-id-self-uri": "mailto:" + username, "User-Agent": USER_AGENT, "x-protocol-version": "1630", } - #print(headers) + # print(headers) msg_id = random.randbytes(16) @@ -114,7 +124,7 @@ def _send_request(conn: apns.APNSConnection, bag_key: str, topic: str, body: byt } conn.send_message(topic, plistlib.dumps(req, fmt=plistlib.FMT_BINARY)) - #resp = conn.wait_for_packet(0x0A) + # resp = conn.wait_for_packet(0x0A) def check_response(x): if x[0] != 0x0A: @@ -123,12 +133,12 @@ def _send_request(conn: apns.APNSConnection, bag_key: str, topic: str, body: byt if resp_body is None: return False resp_body = plistlib.loads(resp_body) - return resp_body['U'] == msg_id - + return resp_body["U"] == msg_id + # Lambda to check if the response is the one we want - #conn.incoming_queue.find(check_response) + # conn.incoming_queue.find(check_response) payload = conn.incoming_queue.wait_pop_find(check_response) - #conn._send_ack(apns._get_field(payload[1], 4)) + # conn._send_ack(apns._get_field(payload[1], 4)) resp = apns._get_field(payload[1], 3) return plistlib.loads(resp) @@ -139,12 +149,14 @@ def _send_request(conn: apns.APNSConnection, bag_key: str, topic: str, body: byt # keypair: a KeyPair object containing the user's private key and certificate # topic: the IDS topic to query # query: a list of URIs to query -def lookup(conn: apns.APNSConnection, self: str, keypair: KeyPair, topic: str, query: list[str]) -> any: +def lookup( + conn: apns.APNSConnection, self: str, keypair: KeyPair, topic: str, query: list[str] +) -> any: conn.filter([topic]) query = {"uris": query} resp = _send_request(conn, "id-query", topic, plistlib.dumps(query), keypair, self) - #resp = plistlib.loads(resp) - #print(resp) + # resp = plistlib.loads(resp) + # print(resp) resp = gzip.decompress(resp["b"]) resp = plistlib.loads(resp) return resp