Custody and export packages
Every product AARCV makes is hashed and recorded in a signed, hash-chained custody manifest. Export packages carry that manifest and the public key, so the receiving side can check them with no access to the source system.
Get the public key#
curl -su "$AARCV_USER:$AARCV_PASS" -o aarcv_custody_public_key.pem "$AARCV_URL/api/security/public-key"
Signatures are Ed25519. The private key is created on first start at data/keys/custody_ed25519.pem (mode 0600), or read from AARCV_CUSTODY_KEY. In production it belongs in an HSM or the enclave's key service.
Export a package#
curl -su "$AARCV_USER:$AARCV_PASS" -o pkg.zip \
"$AARCV_URL/api/security/sorties/$SID/export?network=unclass"
The zip contains only products that pass every release rule for that network and for the caller, plus:
| File | Contents |
|---|---|
export_manifest.json | Sortie, network, package level, each included file with its marking and SHA-256, each withheld product and why, the custody head, and a signature |
custody.json | The sortie's signed custody manifest, including the export event |
custody_public_key.pem | The key that signed both |
README.txt | How to verify |
Verify a package#
aarcv verify-package pkg.zip --public-key aarcv_custody_public_key.pem
Prints a JSON report and exits 0 only when every check passes:
{
"ok": true,
"problems": [],
"sortie_id": "S-1A2B3C4D",
"network": "unclass",
"package_level": "CUI",
"files": ["debrief.mp4", "report.html", "assessment.json"],
"trusted_key": true
}
It checks the export manifest signature, every included file's hash against both the export manifest and the custody record, that no unlisted files were added, the custody signature and chain, and that the custody chain contains this export. Without --public-key the key inside the package is used, which proves integrity but not origin, and trusted_key is false.
Verify in your own code#
Signatures cover a canonical JSON encoding of the manifest with the signature field removed: keys sorted, no insignificant whitespace, non-ASCII characters escaped (Python json.dumps defaults). In Python:
import json
from cryptography.hazmat.primitives.serialization import load_pem_public_key
man = json.load(open("export_manifest.json"))
sig = bytes.fromhex(man.pop("signature")) # hex-encoded Ed25519 signature
msg = json.dumps(man, sort_keys=True, separators=(",", ":")).encode()
load_pem_public_key(open("custody_public_key.pem", "rb").read()).verify(sig, msg)
Check aarcv/security/custody.py for the exact encoding used by your version before relying on a reimplementation; aarcv verify-package is the reference.
Verify on the server#
POST /api/security/sorties/{sid}/verifyre-checks the signature, the chain and every product file's hash.GET /api/audit/verifychecks the audit log's hash chain;GET /api/security/checkpoints/verifychecks it against signed checkpoints, catching truncation.
Release decisions#
GET /api/security/sorties/{sid} returns each product's marking, release state and the decision for the caller (which rule passed or failed). Use it to show users why something is unavailable instead of a bare 403.