Last active
August 9, 2024 17:00
-
-
Save obfusk/0dc1c4be48ffb0714579aff998585b2f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python3 | |
import json | |
import zipfile | |
from hashlib import sha1, sha256 | |
from asn1crypto import cms # type: ignore[import-untyped] | |
from cryptography.exceptions import InvalidSignature | |
from cryptography.hazmat.primitives import serialization | |
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 | |
from cryptography.hazmat.primitives.hashes import SHA1, SHA256 | |
HASHERS = dict(sha1=(sha1, SHA1), sha256=(sha256, SHA256)) | |
PADDING = dict(rsassa_pkcs1v15=PKCS1v15) | |
def verify_signature(pkpass: str) -> bool: | |
valid = None | |
with zipfile.ZipFile(pkpass) as zf: | |
sig = cms.ContentInfo.load(zf.read("signature")) | |
msg = zf.read("manifest.json") | |
for si in sig["content"]["signer_infos"]: | |
for c in sig["content"]["certificates"]: | |
if c.chosen["tbs_certificate"]["serial_number"] == si["sid"].chosen["serial_number"]: | |
cert = c.chosen | |
for a in si["signed_attrs"]: | |
if a["type"].native == "message_digest": | |
digest = a["values"][0].native | |
data = b"\x31" + si["signed_attrs"].dump()[1:] | |
h0, h1 = HASHERS[si["digest_algorithm"]["algorithm"].native] | |
pad = PADDING[si["signature_algorithm"]["algorithm"].native] | |
pubkey = serialization.load_der_public_key(cert.public_key.dump()) | |
signature = si["signature"].native | |
if h0(msg).digest() == digest: | |
try: | |
pubkey.verify(signature, data, pad(), h1()) # type: ignore | |
except InvalidSignature: | |
print("invalid signature") | |
valid = False | |
else: | |
print("valid signature") | |
if valid is None: | |
valid = True | |
else: | |
print("digest mismatch") | |
valid = False | |
return valid or False | |
def verify_manifest(pkpass: str) -> bool: | |
valid = True | |
with zipfile.ZipFile(pkpass) as zf: | |
files = set(zf.namelist()) - {"manifest.json", "signature"} | |
with zf.open("manifest.json") as fh: | |
manifest = json.load(fh) | |
for file, sha in manifest.items(): | |
files.remove(file) | |
if sha1(zf.read(file)).hexdigest() == sha: | |
print(f"{file!r}: valid sha1") | |
else: | |
print(f"{file!r}: invalid sha1") | |
valid = False | |
missing = [f for f in files if not (f.endswith("/") or f.startswith("__MACOSX/"))] | |
if missing: | |
print(f"files missing sha1: {sorted(missing)}") | |
valid = False | |
return valid | |
if __name__ == "__main__": | |
import sys | |
pkpass, valid = sys.argv[1], True | |
if not verify_signature(pkpass): | |
valid = False | |
if not verify_manifest(pkpass): | |
valid = False | |
if not valid: | |
sys.exit(1) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment