mirror of
https://github.com/Sneed-Group/pypush-plus-plus
synced 2024-12-23 19:32:29 -06:00
begin refactor
This commit is contained in:
parent
e47230f139
commit
81175e7d65
6 changed files with 156 additions and 141 deletions
18
demo.py
18
demo.py
|
@ -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}")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
@ -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,
|
||||
|
|
108
ids/keydec.py
108
ids/keydec.py
|
@ -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
37
imessage.py
Normal 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
|
Loading…
Reference in a new issue