begin refactor

This commit is contained in:
JJTech0130 2023-07-27 11:04:57 -04:00
parent e47230f139
commit 81175e7d65
No known key found for this signature in database
GPG key ID: 23C92EBCCF8F93D6
6 changed files with 156 additions and 141 deletions

18
demo.py
View file

@ -15,7 +15,6 @@ from rich.logging import RichHandler
import apns
import ids
from ids.keydec import IdentityKeys
logging.basicConfig(
level=logging.NOTSET, format="%(message)s", datefmt="[%X]", handlers=[RichHandler()]
@ -66,13 +65,11 @@ else:
user.authenticate(username, password)
user.ec_key = CONFIG.get("encryption", {}).get("ec_key")
user.rsa_key = CONFIG.get("encryption", {}).get("rsa_key")
user.encryption_identity = ids.identity.IDSIdentity(encryption_key=CONFIG.get("encryption", {}).get("rsa_key"), signing_key=CONFIG.get("encryption", {}).get("ec_key"))
if (
CONFIG.get("id", {}).get("cert") is not None
and user.ec_key is not None
and user.rsa_key is not None
and user.encryption_identity is not None
):
id_keypair = ids._helpers.KeyPair(CONFIG["id"]["key"], CONFIG["id"]["cert"])
user.restore_identity(id_keypair)
@ -100,8 +97,8 @@ threading.Thread(target=keepalive, daemon=True).start()
# Write config.json
CONFIG["encryption"] = {
"ec_key": user.ec_key,
"rsa_key": user.rsa_key,
"rsa_key": user.encryption_identity.encryption_key,
"ec_key": user.encryption_identity.signing_key,
}
CONFIG["id"] = {
"key": user._id_keypair.key,
@ -122,8 +119,7 @@ CONFIG["push"] = {
with open("config.json", "w") as f:
json.dump(CONFIG, f, indent=4)
user_rsa_key = load_pem_private_key(user.rsa_key.encode(), password=None)
user_rsa_key = ids._helpers.parse_key(user.encryption_identity.encryption_key)
NORMAL_NONCE = b"\x00" * 15 + b"\x01"
def decrypt(payload, sender_token, rsa_key: rsa.RSAPrivateKey = user_rsa_key):
@ -187,9 +183,9 @@ def decrypt(payload, sender_token, rsa_key: rsa.RSAPrivateKey = user_rsa_key):
logging.error(f"Failed to find identity for {sender_token}")
identity_keys = sender['client-data']['public-message-identity-key']
identity_keys = IdentityKeys.decode(identity_keys)
identity_keys = ids.identity.IDSIdentity.decode(identity_keys)
sender_ec_key = identity_keys.ecdsa_key
sender_ec_key = ids._helpers.parse_key(identity_keys.signing_public_key)
from cryptography.hazmat.primitives.asymmetric import ec
#logging.debug(f"Verifying signature {signature} with key {sender_ec_key.public_numbers()} and data {body}")

View file

@ -2,7 +2,7 @@ from base64 import b64encode
import apns
from . import _helpers, identity, profile, query, keydec
from . import _helpers, identity, profile, query
class IDSUser:
@ -60,10 +60,9 @@ class IDSUser:
self.ec_key, self.rsa_key will be set to a randomly gnenerated EC and RSA keypair
if they are not already set
"""
if self.ec_key is None or self.rsa_key is None:
self.ec_key, self.rsa_key, published_keys = keydec.generate_keys()
else:
published_keys = keydec.load_keys(self.ec_key, self.rsa_key)
if self.encryption_identity is None:
self.encryption_identity = identity.IDSIdentity()
cert = identity.register(
b64encode(self.push_connection.token),
@ -71,7 +70,7 @@ class IDSUser:
self.user_id,
self._auth_keypair,
self._push_keypair,
published_keys,
self.encryption_identity,
validation_data,
)
self._id_keypair = _helpers.KeyPair(self._auth_keypair.key, cert)

View file

@ -14,3 +14,29 @@ def dearmour(armoured: str) -> str:
return re.sub(r"-----BEGIN .*-----|-----END .*-----", "", armoured).replace(
"\n", ""
)
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.types import (
PrivateKeyTypes,
PublicKeyTypes,
)
def parse_key(key: str):
# Check if it is a public or private key
if "PUBLIC" in key:
return serialization.load_pem_public_key(key.encode())
else:
return serialization.load_pem_private_key(key.encode(), None)
def serialize_key(key: PrivateKeyTypes | PublicKeyTypes):
if isinstance(key, PrivateKeyTypes):
return key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption(),
).decode("utf-8").strip()
else:
return key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
).decode("utf-8").strip()

View file

@ -3,16 +3,93 @@ from base64 import b64decode
import requests
from ._helpers import PROTOCOL_VERSION, USER_AGENT, KeyPair
from ._helpers import PROTOCOL_VERSION, USER_AGENT, KeyPair, parse_key, serialize_key
from .signing import add_auth_signature, armour_cert
from .keydec import IdentityKeys
from io import BytesIO
from cryptography.hazmat.primitives.asymmetric import ec, rsa
import logging
logger = logging.getLogger("ids")
class IDSIdentity:
def __init__(self, signing_key: str | None = None, encryption_key: str | None = None, signing_public_key: str | None = None, encryption_public_key: str | None = None):
if signing_key is not None:
self.signing_key = signing_key
self.signing_public_key = serialize_key(parse_key(signing_key).public_key())
elif signing_public_key is not None:
self.signing_key = None
self.signing_public_key = signing_public_key
else:
# Generate a new key
self.signing_key = serialize_key(ec.generate_private_key(ec.SECP256R1()))
self.signing_public_key = serialize_key(parse_key(self.signing_key).public_key())
if encryption_key is not None:
self.encryption_key = encryption_key
self.encryption_public_key = serialize_key(parse_key(encryption_key).public_key())
elif encryption_public_key is not None:
self.encryption_key = None
self.encryption_public_key = encryption_public_key
else:
self.encryption_key = serialize_key(rsa.generate_private_key(65537, 1280))
self.encryption_public_key = serialize_key(parse_key(self.encryption_key).public_key())
def decode(input: bytes) -> 'IDSIdentity':
input = BytesIO(input)
assert input.read(5) == b'\x30\x81\xF6\x81\x43' # DER header
raw_ecdsa = input.read(67)
assert input.read(3) == b'\x82\x81\xAE' # DER header
raw_rsa = input.read(174)
# Parse the RSA key
raw_rsa = BytesIO(raw_rsa)
assert raw_rsa.read(2) == b'\x00\xAC' # Not sure what this is
assert raw_rsa.read(3) == b'\x30\x81\xA9' # Inner DER header
assert raw_rsa.read(3) == b'\x02\x81\xA1'
rsa_modulus = raw_rsa.read(161)
rsa_modulus = int.from_bytes(rsa_modulus, "big")
assert raw_rsa.read(5) == b'\x02\x03\x01\x00\x01' # Exponent, should always be 65537
# Parse the EC key
assert raw_ecdsa[:3] == b'\x00\x41\x04'
raw_ecdsa = raw_ecdsa[3:]
ec_x = int.from_bytes(raw_ecdsa[:32], "big")
ec_y = int.from_bytes(raw_ecdsa[32:], "big")
ec_key = ec.EllipticCurvePublicNumbers(ec_x, ec_y, ec.SECP256R1())
ec_key = ec_key.public_key()
rsa_key = rsa.RSAPublicNumbers(e=65537, n=rsa_modulus)
rsa_key = rsa_key.public_key()
return IDSIdentity(signing_public_key=serialize_key(ec_key), encryption_public_key=serialize_key(rsa_key))
def encode(self) -> bytes:
output = BytesIO()
raw_rsa = BytesIO()
raw_rsa.write(b'\x00\xAC')
raw_rsa.write(b'\x30\x81\xA9')
raw_rsa.write(b'\x02\x81\xA1')
raw_rsa.write(parse_key(self.encryption_public_key).public_numbers().n.to_bytes(161, "big"))
raw_rsa.write(b'\x02\x03\x01\x00\x01') # Hardcode the exponent
output.write(b'\x30\x81\xF6\x81\x43')
output.write(b'\x00\x41\x04')
output.write(parse_key(self.signing_public_key).public_numbers().x.to_bytes(32, "big"))
output.write(parse_key(self.signing_public_key).public_numbers().y.to_bytes(32, "big"))
output.write(b'\x82\x81\xAE')
output.write(raw_rsa.getvalue())
return output.getvalue()
def register(
push_token, handles, user_id, auth_key: KeyPair, push_key: KeyPair, published_keys: IdentityKeys, validation_data
push_token, handles, user_id, auth_key: KeyPair, push_key: KeyPair, identity: IDSIdentity, validation_data
):
logger.debug(f"Registering IDS identity for {handles}")
uris = [{"uri": handle} for handle in handles]
@ -31,19 +108,7 @@ def register(
"client-data": {
'is-c2k-equipment': True,
'optionally-receive-typing-indicators': True,
'public-message-identity-key': published_keys.encode(),
# 'public-message-identity-key': b64decode("""MIH2gUMAQQSYmvE+hYOWVGotZUCd
# M6zoW/2clK8RIzUtE6JAmWSCwj7d
# B213vxEBNAPHefEtlxkVKlQH6bsw
# ja5qYyl3Fh28goGuAKwwgakCgaEA
# 4lw3MrXOFIWWIi3TTUGksXVCIz92
# R3AG3ghBa1ZBoZ6rIJHeuxhD2vTV
# hicpW7kvZ/+AFgE4vFFef/9TjG6C
# rsBtWUUfPtYHqc7+uaghVW13qfYC
# tdGsW8Apvf6MJqsRmITJjoYZ5kwl
# scp5Xw/1KVQzKMfZrwZeLC/UZ6O1
# 41u4Xvm+u40e+Ky/wMCOwLGBG0Ag
# ZBH91Xrq+S8izgSLmQIDAQAB""".replace("\n", "").replace(" ", "").replace("\t", "")),
'public-message-identity-key': identity.encode(),
'public-message-identity-version':2,
'show-peer-errors': True,
'supports-ack-v1': True,

View file

@ -1,108 +0,0 @@
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
from base64 import b64decode, b64encode
from io import BytesIO
def generate_keys() -> tuple[str, str, 'IdentityKeys']:
"""
ECDSA key, RSA key, IdentityKeys
"""
ecdsa_key = ec.generate_private_key(ec.SECP256R1())
rsa_key = rsa.generate_private_key(65537, 1280)
# Serialize the keys into PEM
ecdsa_key_p = ecdsa_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption(),
).decode("utf-8").strip()
rsa_key_p = rsa_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption(),
).decode("utf-8").strip()
return ecdsa_key_p, rsa_key_p, IdentityKeys(ecdsa_key.public_key(), rsa_key.public_key())
def load_keys(ecdsa_key_p: str, rsa_key_p: str) -> 'IdentityKeys':
ecdsa_key = serialization.load_pem_private_key(ecdsa_key_p.encode(), password=None)
rsa_key = serialization.load_pem_private_key(rsa_key_p.encode(), password=None)
return IdentityKeys(ecdsa_key.public_key(), rsa_key.public_key())
class IdentityKeys():
def __init__(self, ecdsa_key: ec.EllipticCurvePublicKey, rsa_key: rsa.RSAPublicKey):
self.ecdsa_key = ecdsa_key
self.rsa_key = rsa_key
def decode(input: bytes) -> 'IdentityKeys':
input = BytesIO(input)
assert input.read(5) == b'\x30\x81\xF6\x81\x43' # DER header
raw_ecdsa = input.read(67)
assert input.read(3) == b'\x82\x81\xAE' # DER header
raw_rsa = input.read(174)
# Parse the RSA key
raw_rsa = BytesIO(raw_rsa)
assert raw_rsa.read(2) == b'\x00\xAC' # Not sure what this is
assert raw_rsa.read(3) == b'\x30\x81\xA9' # Inner DER header
assert raw_rsa.read(3) == b'\x02\x81\xA1'
rsa_modulus = raw_rsa.read(161)
rsa_modulus = int.from_bytes(rsa_modulus, "big")
assert raw_rsa.read(5) == b'\x02\x03\x01\x00\x01' # Exponent, should always be 65537
# Parse the EC key
assert raw_ecdsa[:3] == b'\x00\x41\x04'
raw_ecdsa = raw_ecdsa[3:]
ec_x = int.from_bytes(raw_ecdsa[:32], "big")
ec_y = int.from_bytes(raw_ecdsa[32:], "big")
ec_key = ec.EllipticCurvePublicNumbers(ec_x, ec_y, ec.SECP256R1())
ec_key = ec_key.public_key()
rsa_key = rsa.RSAPublicNumbers(e=65537, n=rsa_modulus)
rsa_key = rsa_key.public_key()
return IdentityKeys(ec_key, rsa_key)
def encode(self) -> bytes:
output = BytesIO()
raw_rsa = BytesIO()
raw_rsa.write(b'\x00\xAC')
raw_rsa.write(b'\x30\x81\xA9')
raw_rsa.write(b'\x02\x81\xA1')
raw_rsa.write(self.rsa_key.public_numbers().n.to_bytes(161, "big"))
raw_rsa.write(b'\x02\x03\x01\x00\x01') # Hardcode the exponent
output.write(b'\x30\x81\xF6\x81\x43')
output.write(b'\x00\x41\x04')
output.write(self.ecdsa_key.public_numbers().x.to_bytes(32, "big"))
output.write(self.ecdsa_key.public_numbers().y.to_bytes(32, "big"))
output.write(b'\x82\x81\xAE')
output.write(raw_rsa.getvalue())
return output.getvalue()
if __name__ == "__main__":
input_key = """MIH2gUMAQQSYmvE+hYOWVGotZUCd
M6zoW/2clK8RIzUtE6JAmWSCwj7d
B213vxEBNAPHefEtlxkVKlQH6bsw
ja5qYyl3Fh28goGuAKwwgakCgaEA
4lw3MrXOFIWWIi3TTUGksXVCIz92
R3AG3ghBa1ZBoZ6rIJHeuxhD2vTV
hicpW7kvZ/+AFgE4vFFef/9TjG6C
rsBtWUUfPtYHqc7+uaghVW13qfYC
tdGsW8Apvf6MJqsRmITJjoYZ5kwl
scp5Xw/1KVQzKMfZrwZeLC/UZ6O1
41u4Xvm+u40e+Ky/wMCOwLGBG0Ag
ZBH91Xrq+S8izgSLmQIDAQAB""".replace("\n", "").replace(" ", "").replace("\t", "")
keys = IdentityKeys.decode(b64decode(input_key))
print(b64encode(keys.encode()).decode())
print(len(keys.encode()))
print(len(b64decode(input_key)))
print(keys.encode() == b64decode(input_key))
print(keys.rsa_key.key_size)

37
imessage.py Normal file
View file

@ -0,0 +1,37 @@
# LOW LEVEL imessage function, decryption etc
# Don't handle APNS etc, accept it already setup
## HAVE ANOTHER FILE TO SETUP EVERYTHING AUTOMATICALLY, etc
# JSON parsing of keys, don't pass around strs??
import apns
import ids
class iMessageUser:
def __init__(self, apns: apns.APNSConnection, ids: ids.IDSUser, encryption_key: str, signing_key: str):
self.apns = apns
self.ids = ids
self.encryption_key = encryption_key
self.signing_key = signing_key
def _get_raw_messages(self) -> list[dict]:
pass
def _send_raw_message(self, message: dict):
pass
def _decrypt_message(self, message: dict) -> dict:
pass
def _encrypt_message(self, message: dict) -> dict:
pass
def _sign_message(self, message: dict) -> dict:
pass
def _verify_message(self, message: dict) -> dict:
pass
def get_messages(self) -> list[dict]:
pass