mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-28 00:33:06 +08:00
[build] remove license validation from mobile builds
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Collect iOS notices from pinned source archives; validate them offline at build time."""
|
||||
"""Generate iOS notice resources from pinned source archives."""
|
||||
|
||||
import argparse
|
||||
from collections import defaultdict
|
||||
@@ -8,7 +8,6 @@ import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
import urllib.request
|
||||
@@ -82,7 +81,7 @@ def source_files(component, cache):
|
||||
def component_documents(component, names, read):
|
||||
documents = []
|
||||
for item in component["documents"]:
|
||||
data = checked(read(item["path"]), item["sha256"], item["path"])
|
||||
data = read(item["path"])
|
||||
text = data.decode(item.get("encoding", "utf-8"))
|
||||
if "end_before" in item:
|
||||
if item["end_before"] not in text:
|
||||
@@ -134,79 +133,22 @@ def generate(lock, cache):
|
||||
return json_bytes({"schemaVersion": 1, "components": components})
|
||||
|
||||
|
||||
def verify(lock, inputs, target_info, xmake_repository=None, xmake_version=None):
|
||||
checked(OUTPUT.read_bytes(), lock["bundle_sha256"], str(OUTPUT))
|
||||
if xmake_version is not None:
|
||||
minimum = lock["toolchain"]["xmake_min_version"]
|
||||
version = re.search(r"xmake v(\d+)\.(\d+)\.(\d+)(?:\+|[,\s]|$)", xmake_version.read_text())
|
||||
if not version or tuple(map(int, version.groups())) < tuple(map(int, minimum.split("."))):
|
||||
raise ValueError(f"Xmake {minimum} or newer is required; see apps/ios/README.md")
|
||||
if xmake_repository is not None:
|
||||
revision = subprocess.check_output(
|
||||
["git", "-C", str(xmake_repository), "rev-parse", "HEAD"], text=True).strip()
|
||||
if revision != lock["toolchain"]["xmake_repository_revision"]:
|
||||
raise ValueError("Xmake package repository differs from the reviewed iOS source catalog")
|
||||
changes = subprocess.check_output(
|
||||
["git", "-C", str(xmake_repository), "status", "--porcelain", "--untracked-files=normal"],
|
||||
text=True).strip()
|
||||
if changes:
|
||||
raise ValueError("Xmake package repository has local changes; use the unmodified pinned revision")
|
||||
for component in lock["components"]:
|
||||
recipe = component.get("recipe_path", "")
|
||||
if recipe.startswith("deps/"):
|
||||
checked((REPO / recipe).read_bytes(), component["recipe_sha256"], recipe)
|
||||
elif recipe.startswith("xmake-repo/") and xmake_repository is not None:
|
||||
checked((xmake_repository / Path(recipe).relative_to("xmake-repo")).read_bytes(),
|
||||
component["recipe_sha256"], recipe)
|
||||
if component["source"]["kind"] == "repository":
|
||||
for item in component["documents"]:
|
||||
checked((REPO / item["path"]).read_bytes(), item["sha256"], item["path"])
|
||||
if inputs is None:
|
||||
return
|
||||
if target_info is None:
|
||||
raise ValueError("--target-info is required with --inputs (includes header-only dependencies)")
|
||||
known = {c["package"]: c["version"] for c in lock["components"] if "package" in c}
|
||||
seen = {}
|
||||
# Package names/versions come from resolved Xmake link and include paths,
|
||||
# never from an inventory of all packages left in the installation cache.
|
||||
pattern = r"/packages(?:-simulator)?/[^/\s]+/([^/\s]+)/([^/\s]+)/[a-f0-9]+/"
|
||||
for name, version in re.findall(pattern, inputs.read_text() + target_info.read_text()):
|
||||
if name not in known or known[name] != version:
|
||||
raise ValueError(f"Unreviewed iOS dependency: {name} {version}; regenerate licenses from its source")
|
||||
seen[name] = version
|
||||
if set(seen) != set(known):
|
||||
raise ValueError(f"License/build dependency mismatch; missing: {sorted(set(known) - set(seen))}")
|
||||
for line in inputs.read_text().splitlines():
|
||||
sha, path = line.split(maxsplit=1)
|
||||
checked(Path(path).read_bytes(), sha, path)
|
||||
# Repository revisions identify the reviewed notices, not a required build
|
||||
# commit. SourceMetadata records the actual MiniRTC revision and links for
|
||||
# this build; the content and dependency checks above detect stale notices.
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--generate", action="store_true", help="download/check pinned sources and regenerate the bundled JSON")
|
||||
parser.add_argument("--generate", action="store_true", required=True,
|
||||
help="download pinned sources and regenerate the bundled JSON")
|
||||
parser.add_argument("--cache", type=Path, default=Path.home() / ".cache/crossdesk/licenses")
|
||||
parser.add_argument("--inputs", type=Path, help="native archive .inputs.sha256 manifest")
|
||||
parser.add_argument("--target-info", type=Path, help="resolved Xmake minirtc target information")
|
||||
parser.add_argument("--xmake-repository", type=Path, help="verify the active pinned package repository and recipes")
|
||||
parser.add_argument("--xmake-version", type=Path, help="Xmake version output recorded by the native build")
|
||||
args = parser.parse_args()
|
||||
lock = json.loads(LOCK.read_text())
|
||||
if args.generate:
|
||||
data = generate(lock, args.cache)
|
||||
OUTPUT.parent.mkdir(parents=True, exist_ok=True)
|
||||
OUTPUT.write_bytes(data)
|
||||
lock["bundle_sha256"] = digest(data)
|
||||
LOCK.write_bytes(json_bytes(lock))
|
||||
verify(lock, args.inputs, args.target_info, args.xmake_repository, args.xmake_version)
|
||||
print(f"Verified {len(lock['components'])} iOS license components")
|
||||
data = generate(lock, args.cache)
|
||||
OUTPUT.parent.mkdir(parents=True, exist_ok=True)
|
||||
OUTPUT.write_bytes(data)
|
||||
print(f"Generated {len(lock['components'])} iOS license components")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except (OSError, ValueError, KeyError, subprocess.CalledProcessError) as error:
|
||||
except (OSError, ValueError, KeyError) as error:
|
||||
print(f"Third-party notices: {error}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"""Record the source corresponding to an iOS build without changing the checkout."""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
@@ -66,8 +65,6 @@ def source_metadata(require_release=False, tag=None):
|
||||
"miniRTCRevision": mini_revision,
|
||||
"miniRTCSourceURL": f"{MINIRTC_URL}/tree/{mini_revision}",
|
||||
"componentSources": component_sources(lock, mini_revision),
|
||||
"licenseCatalogSHA256": lock["bundle_sha256"],
|
||||
"sourceCatalogSHA256": hashlib.sha256(lock_path.read_bytes()).hexdigest(),
|
||||
"toolchain": lock["toolchain"],
|
||||
}
|
||||
|
||||
|
||||
@@ -38,12 +38,6 @@ def verify(app, require_release=False):
|
||||
for category, codes in required.items():
|
||||
if set(reasons.get(category, [])) != codes:
|
||||
raise ValueError(f"Missing or changed reviewed privacy reasons: {category}")
|
||||
data = (app / "ThirdPartyLicenses.json").read_bytes()
|
||||
catalog = json.loads(data)
|
||||
ids = {c["id"] for c in catalog["components"]}
|
||||
if not {"crossdesk", "minirtc", "openfec"} <= ids or "openh264" in ids:
|
||||
raise ValueError("Unexpected iOS license catalog")
|
||||
openssl = next(c for c in catalog["components"] if c["id"] == "openssl3")
|
||||
framework = app / "Frameworks/OpenSSL.framework"
|
||||
sdk_info = plistlib.loads((framework / "Info.plist").read_bytes())
|
||||
sdk_privacy_data = (framework / "PrivacyInfo.xcprivacy").read_bytes()
|
||||
@@ -57,14 +51,9 @@ def verify(app, require_release=False):
|
||||
if sdk_privacy != expected_privacy:
|
||||
raise ValueError("OpenSSL SDK privacy declaration differs from the reviewed iOS configuration")
|
||||
sdk_build = json.loads((framework / "BuildMetadata.json").read_text())
|
||||
if (sdk_info["CFBundleShortVersionString"] != openssl["version"]
|
||||
or sdk_build["version"] != openssl["version"]
|
||||
or sdk_build["sourceURL"] != openssl["sourceURL"]
|
||||
or sdk_build["buildSourceURL"] != openssl["buildSourceURL"]
|
||||
if (sdk_info["CFBundleShortVersionString"] != sdk_build["version"]
|
||||
or sdk_build["privacySHA256"] != hashlib.sha256(sdk_privacy_data).hexdigest()):
|
||||
raise ValueError("OpenSSL SDK resources do not match the source/license catalog")
|
||||
if (framework / "LICENSE.txt").read_text() != "\n\n".join(d["text"] for d in openssl["documents"]):
|
||||
raise ValueError("OpenSSL SDK license resource differs from its source notices")
|
||||
raise ValueError("OpenSSL SDK resources do not match its build metadata")
|
||||
embedded_binary = framework / sdk_info["CFBundleExecutable"]
|
||||
# Xcode removes the archive and may generate a small codeless-framework
|
||||
# stub. It must not ship a second static archive or dynamic OpenSSL copy.
|
||||
@@ -78,13 +67,11 @@ def verify(app, require_release=False):
|
||||
if "OpenSSL.framework" in linked:
|
||||
raise ValueError("OpenSSL must be statically linked into the application")
|
||||
source = json.loads((app / "SourceMetadata.json").read_text())
|
||||
if source["licenseCatalogSHA256"] != hashlib.sha256(data).hexdigest():
|
||||
raise ValueError("Bundled licenses differ from the recorded source metadata")
|
||||
if require_release and (source["isModified"] or not source["tag"]):
|
||||
raise ValueError("The app was not built from an unmodified version tag")
|
||||
if not re.fullmatch(r"[0-9a-f]{40}", source["revision"]):
|
||||
raise ValueError("Invalid source revision")
|
||||
print("Verified arm64 app, source/license resources, app privacy declarations and OpenSSL SDK privacy packaging")
|
||||
print("Verified arm64 app, source metadata, app privacy declarations and OpenSSL SDK privacy packaging")
|
||||
print("Signing, App Store validation and license authorization require separate review.")
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Verify iOS native build inputs and toolchain without consulting license data."""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
XMAKE_MIN_VERSION = "3.1.1"
|
||||
XMAKE_REPOSITORY_REVISION = "121109352da272cd1a3d4ed52f2dcda34f839955"
|
||||
|
||||
|
||||
def verify(inputs=None, xmake_repository=None, xmake_version=None):
|
||||
if xmake_version is not None:
|
||||
version = re.search(r"xmake v(\d+)\.(\d+)\.(\d+)(?:\+|[,\s]|$)", xmake_version.read_text())
|
||||
if not version or tuple(map(int, version.groups())) < tuple(map(int, XMAKE_MIN_VERSION.split("."))):
|
||||
raise ValueError(f"Xmake {XMAKE_MIN_VERSION} or newer is required; see apps/ios/README.md")
|
||||
if xmake_repository is not None:
|
||||
revision = subprocess.check_output(
|
||||
["git", "-C", str(xmake_repository), "rev-parse", "HEAD"], text=True).strip()
|
||||
if revision != XMAKE_REPOSITORY_REVISION:
|
||||
raise ValueError("Xmake package repository differs from the pinned iOS toolchain")
|
||||
changes = subprocess.check_output(
|
||||
["git", "-C", str(xmake_repository), "status", "--porcelain", "--untracked-files=normal"],
|
||||
text=True).strip()
|
||||
if changes:
|
||||
raise ValueError("Xmake package repository has local changes; use the unmodified pinned revision")
|
||||
if inputs is not None:
|
||||
for line in inputs.read_text().splitlines():
|
||||
sha, name = line.split(maxsplit=1)
|
||||
if hashlib.sha256(Path(name).read_bytes()).hexdigest() != sha:
|
||||
raise ValueError(f"Native input checksum mismatch: {name}")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--inputs", type=Path, help="native archive .inputs.sha256 manifest")
|
||||
parser.add_argument("--xmake-repository", type=Path, help="active pinned Xmake package repository")
|
||||
parser.add_argument("--xmake-version", type=Path, help="Xmake version output recorded by the native build")
|
||||
args = parser.parse_args()
|
||||
if not any((args.inputs, args.xmake_repository, args.xmake_version)):
|
||||
parser.error("provide --inputs, --xmake-repository or --xmake-version")
|
||||
verify(args.inputs, args.xmake_repository, args.xmake_version)
|
||||
print("Verified iOS native build inputs and toolchain")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except (OSError, ValueError, subprocess.CalledProcessError) as error:
|
||||
print(f"iOS native build: {error}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user