Skip to content

feat: add signed trust bundles, JVM verification and install policy - #207

Open
brunoro wants to merge 9 commits into
feat/tool-signing-source-stampfrom
feat/tool-signing-policy
Open

brunoro wants to merge 9 commits into
feat/tool-signing-source-stampfrom
feat/tool-signing-policy

Conversation

@brunoro

@brunoro brunoro commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

PR #188 added APK source stamps, establishing provenance from Light’s infrastructure. This PR adds signed JSON trust bundles, JVM code that verifies and stores them, and the policy that turns them into an install decision.

The trust bundle contains:

  • Tool IDs and APK signing-certificate digests.
  • Approved artifact identifiers.
  • Minimum APK versions eligible for approval, enforced by the policy layer in this PR.
  • Block/purge entries and stamp-certificate trust and revocation.

The store reconciles firmware-provided bundles, persisted bundles, and incoming updates supplied by callers. It keeps accepted state in memory; API integration follows later.

Changes

  • Add lightsigner bundle build and bundle verify.
  • Define the v1 bundle format.
  • Add defensive statement and bundle parsing to :sdk:trust, rejecting duplicate keys, malformed fields, excessive nesting, and unsupported schema versions.
  • Verify detached signatures before parsing using BouncyCastle 1.86’s lightweight API, without registering a JCA provider. Validate every configured public-key pin before signature attempts.
  • Add LightTrustStore and a persistence interface. Persist the signed bundle and its version floor as one record before changing in-memory state. Startup selects the highest verified stored/image version, with the image winning ties. Corrupt stored state is rejected rather than silently falling back.
  • Recompute effective stamp trust as (imagePins ∪ trustedStampCerts) − revokedStampCerts. Revocation wins. Omitting an image pin cannot remove it; omitting a previously delegated certificate can remove its trust.
  • Add LightInstallPolicy.decide, a total function over the stamp result, statement, platform-reported signer, APK hash, manifest version code, bundle and filter level. No clock, no filesystem, no Context, so the same function runs on device, in tests, and server-side.
  • Apply rules in a fixed order: the permissive level, then blocks, then provenance, then the statement checks, then the filter level. Approval is read only from the bundle — a forged "approved": true in a statement changes nothing.
  • Add DenyReason as a closed enum, splitting the approval failures into NotApproved, BelowMinVersion and SignerNotApprovedForTool so a tool refused for the wrong reason fails its test.
  • Hash the APK at most once, and only when a block or approved-artifact rule needs it. buildId and identity-matched blocks are tried first, so a rule’s position in the bundle cannot decide whether the hash is paid for.
  • Mirror ClientFilterLevel as LightTrustFilterLevel: a JVM module cannot depend on the Android :sdk:server, and subtask 07 would make the reverse edge a cycle.
  • Add shared Python-generated fixtures and Gradle checks prohibiting Android imports, production apksig dependencies, and BC provider registration.
  • Keep checked-in INSECURE- keys strictly for tests.

Validation

  • uv run --with pytest python -m pytest signer/tests -q
  • ./gradlew :sdk:trust:check — 26 tests, covering the milestone’s unit threat rows, the boundaries the threat table does not name, the APK-hash laziness, and totality across every input combination.
  • ./gradlew check
  • CLI build/verify round trip and byte-identical fixture regeneration verified.

Follow-up

  • Integration with LightOS installation and Toolbox filtering: real checkCert, the tool inbox, and the platform stamp verifier.
  • API integration for delivering signed trust bundles.
  • Decide whether to collapse ClientFilterLevel and LightTrustFilterLevel into one enum in :sdk:shared
  • Figure out which block entry should win when several match the same APK (today it is bundle order, then identity matches over hash matches).

One total function decides what happens to a tool: the permissive level, then
blocks, then provenance, then the statement checks, then the filter level.
Approval is read only from the trust bundle, never from the statement, and the
APK hash is computed at most once and only when a rule actually needs it.

DenyReason is closed and splits the approval failures apart, so a tool denied
for the wrong reason fails the tests rather than passing them.

Table-driven tests cover the milestone's unit threat rows and the boundaries
the threat table does not name, including totality across every input
combination.
@brunoro brunoro changed the title feat: add signed trust bundles and JVM verification feat: add signed trust bundles, JVM verification and install policy Sep 23, 2026
@brunoro
brunoro added this pull request to stack #221 September 23, 2026 08:50
@brunoro
brunoro marked this pull request as ready for review September 23, 2026 08:50
setup-android defaults its packages input to `tools platform-tools`. Google
has removed the obsolete `tools` package, so sdkmanager cannot resolve it and
the step fails before Gradle runs. Ask for platform-tools only.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants