mirror of
https://github.com/MyLuxy/overdrive.git
synced 2026-10-27 07:37:09 +01:00
72 lines
3.0 KiB
Python
72 lines
3.0 KiB
Python
# Copyright (C) 2026 half144 and PS5 React contributors
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
# Additional attribution term: see LICENSE-ATTRIBUTION.
|
|
"""Mozilla's root certificates as curl publishes them (MPL-2.0), pinned in the lock file.
|
|
|
|
Old firmwares' own certificate store lacks roots that sites use today; networking apps ship this
|
|
bundle and the console merges it with the system store at start (native/ps5/network_platform.cpp).
|
|
`python3 tools/ca_bundle.py` pins the newest dated copy from curl.se, after the same checks a build runs.
|
|
"""
|
|
import base64
|
|
import binascii
|
|
import json
|
|
import re
|
|
import ssl
|
|
import sys
|
|
import urllib.request
|
|
from common import DEPS, LOCK, ROOT, digest, fetch
|
|
|
|
PAGE = "https://curl.se/docs/caextract.html"
|
|
BLOCK = re.compile(rb"-----BEGIN CERTIFICATE-----\r?\n(.+?)-----END CERTIFICATE-----", re.S)
|
|
|
|
|
|
def validate(path):
|
|
"""Every block decodes, OpenSSL loads every one, and the count looks like Mozilla's store."""
|
|
data = path.read_bytes()
|
|
if len(data) > 1024 * 1024 or b"\0" in data:
|
|
raise RuntimeError(f"{path.name}: not a PEM bundle")
|
|
blocks = BLOCK.findall(data)
|
|
if data.count(b"-----BEGIN") != len(blocks) or not 100 <= len(blocks) <= 400:
|
|
raise RuntimeError(f"{path.name}: {len(blocks)} certificates, expected 100-400")
|
|
try:
|
|
for block in blocks: base64.b64decode(block, validate=False)
|
|
except binascii.Error as error:
|
|
raise RuntimeError(f"{path.name}: undecodable certificate: {error}")
|
|
loaded = ssl.create_default_context(cafile=str(path)).cert_store_stats()["x509_ca"]
|
|
if loaded != len(blocks):
|
|
raise RuntimeError(f"{path.name}: OpenSSL loaded {loaded} of {len(blocks)} certificates")
|
|
return len(blocks)
|
|
|
|
|
|
def ca_bundle():
|
|
item = LOCK["caBundle"]
|
|
path = fetch(item["url"], DEPS / "ca-bundle" / f"cacert-{item['version']}.pem", item["sha256"])
|
|
validate(path)
|
|
return path
|
|
|
|
|
|
def update():
|
|
with urllib.request.urlopen(PAGE, timeout=60) as page:
|
|
dates = sorted(set(re.findall(r"cacert-(\d{4}-\d{2}-\d{2})\.pem", page.read().decode())))
|
|
if not dates: raise RuntimeError(f"No dated bundle listed on {PAGE}")
|
|
version = dates[-1]
|
|
url = f"https://curl.se/ca/cacert-{version}.pem"
|
|
path = DEPS / "ca-bundle" / f"cacert-{version}.pem"
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
with urllib.request.urlopen(url, timeout=120) as source:
|
|
path.write_bytes(source.read())
|
|
count = validate(path)
|
|
lock_path = ROOT / "dependencies.lock.json"
|
|
lock = json.loads(lock_path.read_text())
|
|
previous = lock.get("caBundle", {}).get("version")
|
|
lock["caBundle"] = {"version": version, "url": url, "sha256": digest(path)}
|
|
lock_path.write_bytes((json.dumps(lock, indent=2) + "\n").encode())
|
|
print(f"caBundle {previous} -> {version}: {count} certificates")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if sys.argv[1:] not in ([], ["--check"]):
|
|
sys.exit("usage: ca_bundle.py [--check]")
|
|
if sys.argv[1:] == ["--check"]: print(f"{ca_bundle().name}: {validate(ca_bundle())} certificates")
|
|
else: update()
|