|
| 1 | +# Optional signed-rule bundle verification |
| 2 | + |
| 3 | +This development tool verifies an **existing local TUF bundle** with maintained |
| 4 | +[python-tuf 7.0.1](https://theupdateframework.readthedocs.io/en/latest/api/tuf.ngclient.updater.html). |
| 5 | +It performs no network requests, executes no rules, and never changes the |
| 6 | +checker's embedded snapshot. It produces an authenticated JSON artifact for |
| 7 | +subsequent source review and a release build. Authentication does not establish |
| 8 | +advisory accuracy, provenance of a reported runtime, or local exploitability. |
| 9 | +The checker remains one file with no third-party dependencies. |
| 10 | + |
| 11 | +## Trust recommendation and provisioning |
| 12 | + |
| 13 | +CompleteTech should provision an offline Ed25519 root with a 2-of-3 threshold, |
| 14 | +held by independent custodians, and separate targets, snapshot and timestamp |
| 15 | +roles. Keep routine release keys separate from root custody. Production private |
| 16 | +keys must never enter this repository, CI artifacts, reports, or support logs. |
| 17 | +Ship the **public** root through an independently authenticated release channel |
| 18 | +and record its SHA-256 in the release receipt. Do not trust a root merely because |
| 19 | +it arrived beside an update. No production root, private key or automatic feed |
| 20 | +is configured here. Tests generate ephemeral synthetic keys in memory. |
| 21 | + |
| 22 | +Root replacement must satisfy both old and new root thresholds; rotate release |
| 23 | +keys through authorized root metadata. A root compromise requires an independent |
| 24 | +trust recovery procedure, rather than accepting a replacement supplied by the |
| 25 | +untrusted bundle. The [TUF specification](https://theupdateframework.github.io/specification/latest/) |
| 26 | +defines expiration, signature thresholds and rollback checks. |
| 27 | + |
| 28 | +## Explicit offline use |
| 29 | + |
| 30 | +Use an isolated environment; these optional crypto dependencies are justified |
| 31 | +because implementing signature/trust logic ourselves would be unsafe. |
| 32 | + |
| 33 | +```sh |
| 34 | +python -m venv .venv-tuf |
| 35 | +.venv-tuf/bin/python -m pip install -r tools/signed_rules/requirements.txt |
| 36 | +.venv-tuf/bin/python tools/signed_rules/verify_bundle.py --help |
| 37 | +.venv-tuf/bin/python tools/signed_rules/verify_bundle.py --bundle /existing/bundle --trusted-root /trusted/public-root.json --state /private/client-state --target node2shell-rules.json --output /review/new-rules.json |
| 38 | +.venv-tuf/bin/python -m unittest discover -s tools/signed_rules -v |
| 39 | +``` |
| 40 | + |
| 41 | +On Windows use `.venv-tuf\Scripts\python.exe`. The output parent and bundle must |
| 42 | +already exist. A bundle has flat `metadata/` and `targets/` directories containing |
| 43 | +standard TUF versioned metadata and hash-prefixed target files. Delegations are |
| 44 | +not supported. The public root is a separate, explicitly pinned input. |
| 45 | +Target names must be flat JSON filenames; target bytes must be UTF-8 without a |
| 46 | +BOM and decode to JSON objects with unique keys and finite numeric values. The |
| 47 | +public trust root must be located separately from the supplied bundle. |
| 48 | +No claims about a universal advisory schema are made: the subsequent reviewer |
| 49 | +must validate the target against its particular checker's schema and primary |
| 50 | +sources. A signed arbitrary JSON object is not an accepted detection rule. |
| 51 | + |
| 52 | +All files read are limited to four MiB, new root rotations to 32 per invocation (in addition to authenticated cached |
| 53 | +history replay), and delegation |
| 54 | +traversal to zero. Bundle fetches use a local-only custom FetcherInterface; the |
| 55 | +normal python-tuf HTTP fetcher, proxy behavior and retries are never used. |
| 56 | +Symbolic links and path traversal in selected bundle paths are rejected. The |
| 57 | +cache permits only python-tuf's own relative `root.json` convenience link into |
| 58 | +its non-symlink `root_history/`; other cache links are rejected. Keep |
| 59 | +bundle, root, state and output under directories controlled by the invoking user; |
| 60 | +this is not a hostile concurrent filesystem sandbox. |
| 61 | + |
| 62 | +## Failure, rollback and release procedure |
| 63 | + |
| 64 | +Keep the state directory durable, private and dedicated to one original public |
| 65 | +root. A create-exclusive lock prevents cooperating concurrent updates. Do not |
| 66 | +delete the state directory or its metadata to bypass expiration or rollback |
| 67 | +rejection. The bootstrap hash binds it to the exact originally provisioned root; |
| 68 | +a differently serialized or replaced bootstrap is rejected. If a process is |
| 69 | +killed without cleanup, verify that no updater still owns `update.lock` before |
| 70 | +manually removing that lock only. Restore lost state from a trusted backup or |
| 71 | +re-provision under the publisher's recovery procedure; this tool cannot detect |
| 72 | +a hostile administrator deleting or rolling back its local state. |
| 73 | + |
| 74 | +python-tuf persists verified metadata as verification advances, including when |
| 75 | +a later step fails. Keeping this newer metadata prevents replay of older releases. |
| 76 | +Failed signature/hash/length, expired metadata, missing targets, malformed JSON, |
| 77 | +unknown publishers or rollback never replace an existing output. The verifier |
| 78 | +exits 2 for errors (130 for interruption), writes only a generic failure message, |
| 79 | +and retains embedded checker rules. Existing output requires explicit `--force` |
| 80 | +and is replaced atomically only after verification. Rollback protection concerns |
| 81 | +signed metadata versions; a trusted publisher can still intentionally sign older |
| 82 | +content under a new version. Review changelog and advisory provenance before |
| 83 | +releasing any rule change. Never use force or cache deletion as a rollback policy. |
| 84 | + |
| 85 | +Release procedure: verify a local bundle; review the authenticated artifact's |
| 86 | +schema, source links, uncertainty and thresholds; add boundary tests and dated |
| 87 | +methodology; regenerate the embedded snapshot; obtain Copilot review and passing |
| 88 | +CI for the exact commit; publish the normal checker release with a changelog. |
| 89 | +Emergency rollback is a new reviewed, signed release with increasing metadata |
| 90 | +versions. Failed updates leave the last released embedded rules available offline. |
| 91 | + |
| 92 | +Tests cover real Ed25519 signatures, tampering, expiry, rollback, threshold-bound |
| 93 | +root rotation, untrusted root/release keys, protected outputs and lock ownership. |
| 94 | +They use synthetic data and prohibit socket creation. Production trust provisioning |
| 95 | +and an authenticated hosted feed are operational work, not claimed delivered by |
| 96 | +these fixtures. |
| 97 | + |
| 98 | +Cached roots are replayed cryptographically from the original bootstrap on every |
| 99 | +invocation. Their count is added to the new-rotation budget so routine root |
| 100 | +rotations do not exhaust a lifetime limit. History is bounded to 4,096 regular |
| 101 | +root files and 64 MiB; reaching that operational cap requires a publisher-planned |
| 102 | +checkpoint/reprovisioning release, never bypassing verification or deleting cache. |
| 103 | + |
| 104 | +Targets must carry a signed SHA-256 digest as well as their signed length; hashless targets are rejected. No-force publication requires a filesystem supporting hard links and fails closed otherwise. Only direct optional dependencies are version-pinned; transitive dependencies resolve through pip, so release environments should separately lock and audit the complete resolved environment. |
0 commit comments