From cce6e39079c5ba6a0c54263b43948e94ade60191 Mon Sep 17 00:00:00 2001 From: JJTech0130 Date: Sat, 23 Sep 2023 14:22:05 -0400 Subject: [PATCH] start implementing --- ids/_helpers.py | 50 +++++++++++++++++++++++++++++++++++++++++++++++ ids/encryption.py | 38 +++++++++++++++++++++++++++++++++++ ids/identity.py | 49 +++++++++++++++++++++++++++++++++++++++++++++- ids/ids.proto | 18 +++++++++++++++++ ids/ids_pb2.py | 28 ++++++++++++++++++++++++++ imessage.py | 1 + test.py | 8 ++++++++ 7 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 ids/encryption.py create mode 100644 ids/ids.proto create mode 100644 ids/ids_pb2.py create mode 100644 test.py diff --git a/ids/_helpers.py b/ids/_helpers.py index 20996a1..d737136 100644 --- a/ids/_helpers.py +++ b/ids/_helpers.py @@ -1,4 +1,5 @@ from collections import namedtuple +import base64 USER_AGENT = "com.apple.madrid-lookup [macOS,13.2.1,22D68,MacBookPro18,3]" PROTOCOL_VERSION = "1640" @@ -36,3 +37,52 @@ def serialize_key(key) -> str: encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo, ).decode("utf-8").strip() + +def parse_encoded_compact_key(key: str): + # Add = padding + key = key + "=" * (4 - len(key) % 4) + # Urlsafe base64 decode + key = base64.urlsafe_b64decode(key) + return parse_compact_key(key) + +def parse_compact_key(key: bytes): + # Parse as a P256 key + from cryptography.hazmat.primitives.asymmetric import ec + from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat + + # Parse compressed key... need to add 0x02 prefix + k = ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256R1(), b"\x02" + key) + # Serialize as PEM + return serialize_key(k) + +def create_compact_key(): + """ + Create a P256 keypair and return the public key as a URL-safe base64 string + and the private key as a PEM string. + """ + + # Generate a P256 keypair + from cryptography.hazmat.primitives.asymmetric import ec + from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat + + # Generate keys until we get one that is even + key = None + pub = None + + while True: + key = ec.generate_private_key(ec.SECP256R1()) + pub = key.public_key().public_bytes(Encoding.X962, PublicFormat.CompressedPoint) + if pub[0] == 0x02: + pub = pub[1:] + break + + return pub, serialize_key(key) + +def create_encoded_compact_key() -> tuple[str, str]: + pub, key = create_compact_key() + # URL-safe base64 encode + pub = base64.urlsafe_b64encode(pub).decode() + # Remove padding + pub = pub.replace("=", "") + + return pub, key \ No newline at end of file diff --git a/ids/encryption.py b/ids/encryption.py new file mode 100644 index 0000000..8c2b4ba --- /dev/null +++ b/ids/encryption.py @@ -0,0 +1,38 @@ +from . import ids_pb2, _helpers + +import logging +logger = logging.getLogger("ids") + +def parse_loggable_data(data: bytes): + # Parse as a LoggableData + loggable_data = ids_pb2.KeyTransparencyLoggableData() + loggable_data.ParseFromString(data) + #print(loggable_data) + + logger.debug(f"LoggableData: {loggable_data}") + + identity = ids_pb2.NgmPublicIdentity() + identity.ParseFromString(loggable_data.ngmPublicIdentity) + + key = _helpers.parse_compact_key(identity.publicKey) + + return key + +def create_loggable_data(): + """ + This function must create the key so we know it fits in the compact format + """ + + pub, key = _helpers.create_compact_key() + + identity = ids_pb2.NgmPublicIdentity() + identity.publicKey = pub + + loggable_data = ids_pb2.KeyTransparencyLoggableData() + loggable_data.ngmPublicIdentity = identity.SerializeToString() + + + + return loggable_data.SerializeToString(), key + + \ No newline at end of file diff --git a/ids/identity.py b/ids/identity.py index b6c3ed4..07088d8 100644 --- a/ids/identity.py +++ b/ids/identity.py @@ -151,7 +151,54 @@ def register( "user-id": user_id, } ], - } + }, + { + "client-data": { + "supports-ack-v1": True, + "public-message-identity-key": identity.encode(), + "supports-update-attachments-v1": True, + "supports-keep-receipts": True, + "supports-people-request-messages-v2": True, + "supports-people-request-messages-v3": True, + "supports-impact-v1": True, + "supports-rem": True, + "nicknames-version": 1.0, + "ec-version": 1.0, + "supports-animoji-v2": True, + "supports-ii-v1": True, + "optionally-receive-typing-indicators": True, + "supports-inline-attachments": True, + "supports-people-request-messages": True, + "supports-cross-platform-sharing": True, + "public-message-ngm-device-prekey-data-key": b"\n \xb4\\\x15\x8e\xa4\xc8\xe5\x07\x98\tp\xd0\xa4^\x84k\x05#Ep\xa9*\xcd\xadt\xf5\xb0\xfb\xa6_ho\x12@\xe3\xf5\xcaOwh\xfd\xb9\xecD\t\x0e\x9e\xb8\xb0\xa1\x1c=\x92\x9dD/lmL\xde.\\o\xeb\x15>\x9f\xae\xd9\xf9\xd1\x9c*\x8dU\xe0\xd2\xdeo\xb2\xcb\xd8\xf8i\xd4\xd0a^\t!\x0fa\xb2\xddI\xfc_*\x19\xb2\xf0#\x12\xe0@\xd9A", + "supports-original-timestamp-v1": True, + "supports-sa-v1": True, + "supports-photos-extension-v2": True, + "supports-photos-extension-v1": True, + "prefers-sdr": False, + "supports-fsm-v1": True, + "supports-fsm-v3": True, + "supports-fsm-v2": True, + "supports-shared-exp": True, + "supports-location-sharing": True, + "device-key-signature": b'0c\x04\x14\x1d\xb02~\xefk&\xf8\r;R\xa4\x95c~\x8a\x90H\x85\xb0\x02\x01\x01\x04H0F\x02!\x00\xa7\x08\xf5"z.3/\xbe\xea\x8c\xce\x8dD\xb6\xf0v\xd0\x030\xac\xd1\xde\x88\x89q\x9ej\x1bJR\xce\x02!\x00\xb8^\xd9\x97`\x19|\xa8\x1d\\\xf9E\x1a`<0\x00\xab\x94\x0bs\xed\x8b\xc4h\xcb\r\x91\xdb\xb0W\xdc', + "supports-st-v1": True, + "supports-ca-v1": True, + "supports-protobuf-payload-data-v2": True, + "supports-hdr": True, + "supports-media-v2": True, + "supports-be-v1": True, + "public-message-identity-version": 2.0, + "supports-heif": True, + "supports-certified-delivery-v1": True, + "supports-autoloopvideo-v1": True, + "supports-dq-nr": True, + "public-message-identity-ngm-version": 12.0, + "supports-audio-messaging-v2": True, + }, + "kt-loggable-data": b'\n"\n \rl\xbe\xca\xf7\xe8\xb2\x89k\x18\x1e\xb9,d\xf8\xe2\n\xbf\x8d\xe1E\xd6\xf3T\xcb\xd9\x99d\xd1mk\xeb\x10\x0c\x18\x05"E\x08\x01\x12A\x04\xe3\xe7Y.zW\x0f\x9d\x95\xc2\xc5\xd9\x9eC\x05\xa0\x95.\xa9DW\xa9\xfb\xa9\xaa\x1a\x868\x0e\xee\xe6\x8f%\x12\xa3\r\x01\xe1\xbb\x97M\x9a\x19\x16\x94D\xcdv\xeb\xefem?\xb3\xefzM#~a\x92\x0fO\xab', + "kt-mismatch-account-flag": True, + }, ], "validation-data": b64decode(validation_data), } diff --git a/ids/ids.proto b/ids/ids.proto new file mode 100644 index 0000000..e4100a7 --- /dev/null +++ b/ids/ids.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +// message InnerMessage { +// required bytes message = 1; +// uint32 counter = 2; +// bytes ktGossipData = 3; +// bytes debugInfo = 99; +// } + +message KeyTransparencyLoggableData { + bytes ngmPublicIdentity = 1; + uint32 ngmVersion = 2; + uint32 ktVersion = 3; +} + +message NgmPublicIdentity { + bytes publicKey = 1; +} \ No newline at end of file diff --git a/ids/ids_pb2.py b/ids/ids_pb2.py new file mode 100644 index 0000000..9477dfd --- /dev/null +++ b/ids/ids_pb2.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: ids/ids.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\rids/ids.proto\"_\n\x1bKeyTransparencyLoggableData\x12\x19\n\x11ngmPublicIdentity\x18\x01 \x01(\x0c\x12\x12\n\nngmVersion\x18\x02 \x01(\r\x12\x11\n\tktVersion\x18\x03 \x01(\r\"&\n\x11NgmPublicIdentity\x12\x11\n\tpublicKey\x18\x01 \x01(\x0c\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'ids.ids_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + + DESCRIPTOR._options = None + _globals['_KEYTRANSPARENCYLOGGABLEDATA']._serialized_start=17 + _globals['_KEYTRANSPARENCYLOGGABLEDATA']._serialized_end=112 + _globals['_NGMPUBLICIDENTITY']._serialized_start=114 + _globals['_NGMPUBLICIDENTITY']._serialized_end=152 +# @@protoc_insertion_point(module_scope) diff --git a/imessage.py b/imessage.py index e9f292a..e9c1a6c 100644 --- a/imessage.py +++ b/imessage.py @@ -549,6 +549,7 @@ class iMessageUser: # Look up the public keys for the participants, and cache a token : public key mapping lookup = await self.user.lookup(participants, topic=topic) + print(lookup) logger.debug(f"Lookup response : {lookup}") for key, participant in lookup.items(): diff --git a/test.py b/test.py new file mode 100644 index 0000000..4bdc3c8 --- /dev/null +++ b/test.py @@ -0,0 +1,8 @@ +import ids +from ids import encryption + +test = "0a220a200d6cbecaf7e8b2896b181eb92c64f8e20abf8de145d6f354cbd99964d16d6beb100c180522450801124104e3e7592e7a570f9d95c2c5d99e4305a0952ea94457a9fba9aa1a86380eeee68f2512a30d01e1bb974d9a19169444cd76ebef656d3fb3ef7a4d237e61920f4fab" +test = bytes.fromhex(test) +encryption.parse_loggable_data(test) + +print(encryption.create_loggable_data()) \ No newline at end of file