"""Extracted from octo_updater.py. Keep this module focused on its named responsibility.""" import json import hashlib import os import sys import ssl import re import subprocess import urllib.request from urllib.parse import urlsplit import shutil import stat import struct import time import math import threading import queue from functools import cache from pathlib import Path from config import UA # ────────────────────────────────────────────────────────────────────────────── # Secure networking # ────────────────────────────────────────────────────────────────────────────── # Hardened TLS: verify the server certificate against the system trust store, # require the hostname to match, and refuse anything below TLS 1.2. This is # the primary defence against a man-in-the-middle tampering with downloads. SSL_CTX = ssl.create_default_context() SSL_CTX.check_hostname = True SSL_CTX.verify_mode = ssl.CERT_REQUIRED # Trust certifi's curated roots *in addition to* the system store, so a stale # or incomplete Windows root store (Python's ssl uses a static snapshot and # never triggers Windows' on-demand root update) can't break verification. # If certifi isn't bundled, fall back to the system store alone. try: import certifi SSL_CTX.load_verify_locations(certifi.where()) except Exception: pass try: SSL_CTX.minimum_version = ssl.TLSVersion.TLSv1_2 except (AttributeError, ValueError): pass # Binaries may only be fetched from these hosts. TLS already stops a MITM from # impersonating them; this additionally stops a tampered API response from # redirecting a download (e.g. a mod DLL) to an unexpected host. ALLOWED_DOWNLOAD_HOSTS = { "octowow.st", "dl.octowow.st", "github.com", "raw.githubusercontent.com", "objects.githubusercontent.com", "release-assets.githubusercontent.com", "codeberg.org", } def _check_url(url: str, allowed_hosts): """Enforce HTTPS and (optionally) an allowlist on a URL.""" parts = urlsplit(url) if parts.scheme != "https": raise RuntimeError(f"Refusing non-HTTPS URL: {url}") if allowed_hosts is not None: host = (parts.hostname or "").lower() if host not in allowed_hosts: raise RuntimeError(f"Refusing download from unexpected host: {host}") class _HttpsOnlyRedirectHandler(urllib.request.HTTPRedirectHandler): """Require every redirect target to stay HTTPS (blocks an https→http downgrade). The host allowlist is deliberately *not* re-applied on redirects: an allowlisted host controls its own redirects — legitimately to its CDN (e.g. octowow.st→dl.octowow.st, github.com→codeload) — and TLS protects wherever it lands. The allowlist's job is to vet the *initial* URL (against a tampered API response), which secure_urlopen still does.""" def redirect_request(self, req, fp, code, msg, headers, newurl): _check_url(newurl, None) # HTTPS-only, no host check return super().redirect_request(req, fp, code, msg, headers, newurl) # Shared opener with the hardened TLS context and the HTTPS-only redirect # guard, built once. _SECURE_OPENER = urllib.request.build_opener( urllib.request.HTTPSHandler(context=SSL_CTX), _HttpsOnlyRedirectHandler()) def secure_urlopen(req, timeout, allowed_hosts=None): """urlopen wrapper that enforces HTTPS + an optional host allowlist on the initial URL, keeps redirects on HTTPS, and uses the hardened TLS context. `req` may be a URL string or a urllib Request.""" url = req.full_url if isinstance(req, urllib.request.Request) else req _check_url(url, allowed_hosts) return _SECURE_OPENER.open(req, timeout=timeout)