initial version 🎉

This commit is contained in:
JJTech0130 2023-04-05 19:52:14 -04:00
commit b7df7a3b1e
No known key found for this signature in database
GPG key ID: 23C92EBCCF8F93D6
4 changed files with 414 additions and 0 deletions

164
.gitignore vendored Normal file
View file

@ -0,0 +1,164 @@
# APNS keys
push.crt
push.key
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

102
albert.py Normal file

File diff suppressed because one or more lines are too long

101
apns.py Normal file
View file

@ -0,0 +1,101 @@
from __future__ import annotations
class Fields:
@staticmethod
def from_bytes(data: bytes) -> Fields:
fields = {}
while len(data) > 0:
field = data[0]
length = int.from_bytes(data[1:3], "big")
value = data[3:3 + length]
fields[field] = value
data = data[3 + length:]
return Fields(fields)
def __init__(self, fields: dict[int, bytes]):
self.fields = fields
def to_bytes(self) -> bytes:
buffer = bytearray()
for field, value in self.fields.items():
buffer.append(field)
buffer.extend(len(value).to_bytes(2, "big"))
buffer.extend(value)
return buffer
# Debug formating
def __str__(self) -> str:
return f"{self.fields}"
# Define number to command name mapping
COMMANDS = {
0x7: "Connect",
0x8: "ConnectResponse",
0x9: "PushTopics",
0x0A: "PushNotification",
}
class Payload:
@staticmethod
def from_stream(stream) -> Payload|None:
command = int.from_bytes(stream.read(1), "big")
if command == 0:
return None # We reached the end of the stream
length = int.from_bytes(stream.read(4), "big")
fields = Fields.from_bytes(stream.read(length))
return Payload(command, fields)
@staticmethod
def from_bytes(data: bytes) -> Payload:
# Convert it to bytes for cleaner printing
data = bytes(data)
command = data[0]
length = int.from_bytes(data[1:5], "big")
fields = Fields.from_bytes(data[5:5 + length])
return Payload(command, fields)
def __init__(self, command: int, fields: Fields):
self.command = command
self.fields = fields
def to_bytes(self) -> bytes:
buffer = bytearray()
buffer.append(self.command)
fields = self.fields.to_bytes()
buffer.extend(len(fields).to_bytes(4, "big"))
buffer.extend(fields)
return buffer
# Debug formating
def __str__(self) -> str:
return f"{COMMANDS[self.command]}: {self.fields}"
if __name__ == "__main__":
import courier
sock = courier.connect()
payload = Payload(7, Fields({2: 0x01.to_bytes()}))
sock.write(payload.to_bytes())
print("recieved: ", Payload.from_stream(sock))
print("recieved: ", Payload.from_stream(sock))
sock.close()
# with socket.create_connection((COURIER_HOST, COURIER_PORT)) as sock:
# with context.wrap_socket(sock, server_hostname=COURIER_HOST) as ssock:
# payload = Payload(7, Fields({2: 0x01.to_bytes()}))
# #print(payload)
# #print(payload.to_bytes())
# #print(Payload.from_bytes(payload.to_bytes()))
# ssock.write(payload.to_bytes())
# print("recieved: ", Payload.from_stream(ssock))
# print("recieved: ", Payload.from_stream(ssock))

47
courier.py Normal file
View file

@ -0,0 +1,47 @@
import albert
import tlslite
import socket
COURIER_HOST = "10-courier.push.apple.com"
COURIER_PORT = 5223
#ALPN = [b"apns-security-v2"]
ALPN = None
# Check if we have already generated a push certificate
# If not, generate one
def _setup_push_cert():
try:
with open("push.key", "r") as f:
private_key = f.read()
with open("push.crt", "r") as f:
cert = f.read()
except FileNotFoundError:
private_key, cert = albert.generate_push_cert()
with open("push.key", "w") as f:
f.write(private_key)
with open("push.crt", "w") as f:
f.write(cert)
return private_key, cert
def connect():
private_key, cert = _setup_push_cert()
# Connect to the courier server
sock = socket.create_connection((COURIER_HOST, COURIER_PORT))
# Wrap the socket in TLS
sock = tlslite.TLSConnection(sock)
# Parse the certificate and private key
cert = tlslite.X509CertChain([tlslite.X509().parse(cert)])
private_key = tlslite.parsePEMKey(private_key, private=True)
# Handshake with the server
sock.handshakeClientCert(cert, private_key, alpn=ALPN)
return sock
if __name__ == "__main__":
sock = connect()
sock.write(b"Hello World!")
print(sock.read())
sock.close()