start redoing demo

This commit is contained in:
JJTech0130 2023-08-19 11:27:44 -04:00
parent 524e4b1a35
commit 12cd7ac2cd
No known key found for this signature in database
GPG key ID: 23C92EBCCF8F93D6
4 changed files with 43 additions and 31 deletions

11
apns.py
View file

@ -109,6 +109,8 @@ class APNSConnection:
await self._queue_park.wait() # Wait for a new payload to be added to the queue await self._queue_park.wait() # Wait for a new payload to be added to the queue
logger.debug(f"Woken by event, checking for {id}") logger.debug(f"Woken by event, checking for {id}")
# Check if the new payload matches the id # Check if the new payload matches the id
if len(self._incoming_queue) == 0:
continue # all payloads have been removed by someone else
if self._incoming_queue[-1].id != id: if self._incoming_queue[-1].id != id:
continue continue
if filter is not None: if filter is not None:
@ -294,11 +296,16 @@ class APNSConnection:
# TODO: Check ACK code # TODO: Check ACK code
async def expect_notification(self, topic: str, filter: Callable | None = None): async def expect_notification(self, topics: str | list[str], filter: Callable | None = None):
"""Waits for a notification to be received, and acks it""" """Waits for a notification to be received, and acks it"""
if isinstance(topics, list):
topic_hashes = [sha1(topic.encode()).digest() for topic in topics]
else:
topic_hashes = [sha1(topics.encode()).digest()]
def f(payload: APNSPayload): def f(payload: APNSPayload):
if payload.fields_with_id(2)[0].value != sha1(topic.encode()).digest(): if payload.fields_with_id(2)[0].value not in topic_hashes:
return False return False
if filter is not None: if filter is not None:
return filter(payload) return filter(payload)

27
demo.py
View file

@ -13,6 +13,8 @@ import apns
import ids import ids
import imessage import imessage
import trio
logging.basicConfig( logging.basicConfig(
level=logging.NOTSET, format="%(message)s", datefmt="[%X]", handlers=[RichHandler()] level=logging.NOTSET, format="%(message)s", datefmt="[%X]", handlers=[RichHandler()]
) )
@ -58,13 +60,6 @@ def safe_b64decode(s):
return None return None
async def main(): async def main():
# Try and load config.json
try:
with open("config.json", "r") as f:
CONFIG = json.load(f)
except FileNotFoundError:
CONFIG = {}
token = CONFIG.get("push", {}).get("token") token = CONFIG.get("push", {}).get("token")
if token is not None: if token is not None:
token = b64decode(token) token = b64decode(token)
@ -138,9 +133,21 @@ async def main():
im = imessage.iMessageUser(conn, user) im = imessage.iMessageUser(conn, user)
# Send a message to myself # Send a message to myself
await im.send(imessage.iMessage.create(im, "Hello, world!", [user.current_handle])) async with trio.open_nursery() as nursery:
print(await im.receive()) nursery.start_soon(input_task, im)
nursery.start_soon(output_task, im)
async def input_task(im: imessage.iMessageUser):
while True:
cmd = await trio.to_thread.run_sync(input, "> ", cancellable=True)
if cmd != "":
await im.send(imessage.iMessage.create(im, cmd, [im.user.current_handle]))
async def output_task(im: imessage.iMessageUser):
while True:
msg = await im.receive()
print(str(msg))
if __name__ == "__main__": if __name__ == "__main__":
import trio
trio.run(main) trio.run(main)

View file

@ -21,7 +21,7 @@ logging.basicConfig(
) )
async def main(): async def main():
apns.COURIER_HOST = "windows.courier.push.apple.com" apns.COURIER_HOST = "windows.courier.push.apple.com" # Use windows courier so that /etc/hosts override doesn't affect it
context = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH) context = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH)
context.set_alpn_protocols(["apns-security-v3"]) context.set_alpn_protocols(["apns-security-v3"])
@ -72,8 +72,8 @@ class APNSProxy:
logging.info(f"<- {payload}") logging.info(f"<- {payload}")
def tamper(self, payload: apns.APNSPayload, to_server) -> apns.APNSPayload: def tamper(self, payload: apns.APNSPayload, to_server) -> apns.APNSPayload:
if not to_server: #if not to_server:
payload = self.tamper_lookup_keys(payload) # payload = self.tamper_lookup_keys(payload)
return payload return payload

View file

@ -261,7 +261,8 @@ class iMessage(Message):
"""Creates a basic outgoing `iMessage` from the given text and participants""" """Creates a basic outgoing `iMessage` from the given text and participants"""
sender = user.user.current_handle sender = user.user.current_handle
participants += [sender] if sender not in participants:
participants += [sender]
return iMessage( return iMessage(
text=text, text=text,
@ -492,18 +493,12 @@ class iMessageUser:
except: except:
return False return False
async def receive(self) -> Message | None: async def receive(self) -> Message:
""" """
Will return the next iMessage in the queue, or None if there are no messages Will return the next iMessage in the queue, or None if there are no messages
""" """
for type, (topic, cls) in MESSAGE_TYPES.items(): body = await self._receive_raw([t for t, _ in MESSAGE_TYPES.items()], [t[0] for _, t in MESSAGE_TYPES.items()])
body = await self._receive_raw(type, topic) t = MESSAGE_TYPES[body["c"]][1]
if body is not None:
t = cls
break
else:
return None
if not await self._verify_payload(body["P"], body["sP"], body["t"]): if not await self._verify_payload(body["P"], body["sP"], body["t"]):
raise Exception("Failed to verify payload") raise Exception("Failed to verify payload")
@ -516,7 +511,7 @@ class iMessageUser:
return t.from_raw(decrypted, body["sP"]) return t.from_raw(decrypted, body["sP"])
except Exception as e: except Exception as e:
logger.error(f"Failed to parse message : {e}") logger.error(f"Failed to parse message : {e}")
return None return Message(text="Failed to parse message", sender="System", participants=[], id=uuid.uuid4(), _raw=body)
KEY_CACHE_HANDLE: str = "" KEY_CACHE_HANDLE: str = ""
KEY_CACHE: dict[bytes, dict[str, tuple[bytes, bytes]]] = {} KEY_CACHE: dict[bytes, dict[str, tuple[bytes, bytes]]] = {}
@ -545,8 +540,7 @@ class iMessageUser:
logger.warning(f"Participant {key} has no identities, this is probably not a real account") logger.warning(f"Participant {key} has no identities, this is probably not a real account")
for key, participant in lookup.items(): for key, participant in lookup.items():
if not key in self.USER_CACHE: self.USER_CACHE[key] = [] # Clear so that we don't keep appending multiple times
self.USER_CACHE[key] = []
for identity in participant["identities"]: for identity in participant["identities"]:
if not "client-data" in identity: if not "client-data" in identity:
@ -626,18 +620,22 @@ class iMessageUser:
await self.connection.send_notification(topic, body, message_id) await self.connection.send_notification(topic, body, message_id)
async def _receive_raw(self, c: int, topic: str) -> dict: async def _receive_raw(self, c: int | list[int], topics: str | list[str]) -> dict:
def check(payload: apns.APNSPayload): def check(payload: apns.APNSPayload):
# Check if the "c" key matches # Check if the "c" key matches
body = payload.fields_with_id(3)[0].value body = payload.fields_with_id(3)[0].value
if body is None: if body is None:
return False return False
body = plistlib.loads(body) body = plistlib.loads(body)
if not "c" in body or body["c"] != c: if not "c" in body:
return False
if isinstance(c, int) and body["c"] != c:
return False
elif isinstance(c, list) and body["c"] not in c:
return False return False
return True return True
payload = await self.connection.expect_notification(topic, check) payload = await self.connection.expect_notification(topics, check)
body = payload.fields_with_id(3)[0].value body = payload.fields_with_id(3)[0].value
body = plistlib.loads(body) body = plistlib.loads(body)