SHA256
build: collect inspected unsigned Android artifacts with checksums
Verify the production manifest, system-only trust, development APK signature and unsigned release outputs before collecting local artifacts. Record source identity and hashes outside Git. Exercised successfully against the built APKs and AAB; collection must follow a clean-source build.
This commit is contained in:
1 parent
9088ee7e6a
commit
f7b9dcd91e
1 file changed
+68
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Collect development/unsigned outputs with source identity and inspection evidence."""
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import zipfile
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
BUILD = Path(os.environ.get("YUVOMI_BUILD_DIR", ROOT / "android"))
|
||||
APP = BUILD / "app" if "YUVOMI_BUILD_DIR" in os.environ else BUILD / "app/build"
|
||||
SDK = Path(os.environ["ANDROID_HOME"])
|
||||
TOOLS = SDK / "build-tools/36.0.0"
|
||||
DESTINATION = ROOT / "artifacts/android"
|
||||
DESTINATION.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def tool(name, *arguments):
|
||||
return subprocess.check_output([str(TOOLS / name), *map(str, arguments)], text=True)
|
||||
|
||||
|
||||
release = APP / "outputs/apk/release/app-release-unsigned.apk"
|
||||
manifest = tool("aapt2", "dump", "xmltree", release, "--file", "AndroidManifest.xml")
|
||||
assert 'package="community.yuvomi.mobile"' in manifest
|
||||
assert re.search(r":allowBackup\([^\n]*=false", manifest)
|
||||
assert re.search(r":usesCleartextTraffic\([^\n]*=false", manifest)
|
||||
assert not re.search(r":debuggable\([^\n]*=true", manifest)
|
||||
resources = tool("aapt2", "dump", "resources", release)
|
||||
network = re.search(r"xml/network_security_config\s+\(\) \(file\) (\S+)", resources)
|
||||
assert network, "Missing production network security configuration"
|
||||
policy = tool("aapt2", "dump", "xmltree", release, "--file", network.group(1))
|
||||
assert "cleartextTrafficPermitted=false" in policy
|
||||
assert policy.count("E: certificates") == 1 and 'src="system"' in policy
|
||||
(DESTINATION / "release-manifest.txt").write_text(manifest)
|
||||
(DESTINATION / "release-network-policy.txt").write_text(policy)
|
||||
|
||||
debug = APP / "outputs/apk/debug/app-debug.apk"
|
||||
(DESTINATION / "debug-signature.txt").write_text(tool("apksigner", "verify", "--verbose", debug))
|
||||
unsigned = subprocess.run([str(TOOLS / "apksigner"), "verify", str(release)], capture_output=True, text=True)
|
||||
assert unsigned.returncode != 0 and "Missing META-INF/MANIFEST.MF" in unsigned.stderr, unsigned.stderr
|
||||
|
||||
entries = []
|
||||
for source in [debug, release, APP / "outputs/bundle/release/app-release.aab"]:
|
||||
with zipfile.ZipFile(source) as archive:
|
||||
for name in archive.namelist():
|
||||
assert "integration_ca" not in name and not name.endswith((".pem", ".jks", ".keystore", ".p12")), name
|
||||
if source.suffix == ".aab":
|
||||
assert not any(name.upper().startswith("META-INF/") and name.upper().endswith((".RSA", ".DSA", ".EC"))
|
||||
for name in archive.namelist()), "Expected unsigned AAB"
|
||||
destination = DESTINATION / source.name
|
||||
shutil.copyfile(source, destination)
|
||||
entries.append({"file": source.name, "bytes": destination.stat().st_size,
|
||||
"sha256": hashlib.sha256(destination.read_bytes()).hexdigest()})
|
||||
|
||||
revision = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=ROOT, text=True).strip()
|
||||
dirty = bool(subprocess.check_output(["git", "status", "--porcelain"], cwd=ROOT, text=True).strip())
|
||||
(DESTINATION / "SHA256SUMS").write_text("".join(f"{entry['sha256']} {entry['file']}\n" for entry in entries))
|
||||
(DESTINATION / "build-info.json").write_text(json.dumps({
|
||||
"source_revision": revision, "worktree_dirty_at_collection": dirty,
|
||||
"upstream_revision": "10bfef7686ed7303b4c74c7fcc55a5eb16356c9d",
|
||||
"status": "Development debug APK; unsigned release APK/AAB. Not production-ready.",
|
||||
"artifacts": entries,
|
||||
}, indent=2) + "\n")
|
||||
print(f"Inspected and collected {len(entries)} artifacts in {DESTINATION}")
|
||||
print("Build source identity assumes the documented build ran immediately before collection.")
|
||||
Reference in new issue
Block a user