FLEX-WP-0024: CCR-2026-0050 Access review, signer public receipt and T07 founder gate
Approve CCR-2026-0050 for Access scope (sent to railiance-platform).
Add the exact consumer pin derived at candidate 84c78e0 and an offline
projection from the Platform custody receipt to the secrets-engine
pdp-trust.v1 bytes plus a public receipt. No key, rollout or admission.
T07 waits on founder decision 4fab263d.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 809600@bnt-lap001
Assistant-Session: 40ba228f-c8a0-4ec9-bb29-3129951afdc4
This commit is contained in:
parent
c130b7d739
commit
c1e83c16bf
6 changed files with 236 additions and 1 deletions
116
tools/prepare_signer_public_receipt.py
Normal file
116
tools/prepare_signer_public_receipt.py
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Project a Platform custody receipt into the Secrets trust file plus a public receipt.
|
||||
|
||||
FLEX-WP-0024-T06. Offline only: no network, token, key generation or effect.
|
||||
Reads only the allowlisted public fields of `railiance-platform.signer-cas-receipt.v1`
|
||||
(kid, public_key, public_fingerprint, kv_version, path, status) and the committed
|
||||
Access pin (`docs/native-signer-public-pin.json`). Emits:
|
||||
|
||||
* the exact `secrets-engine.pdp-trust.v1` bytes the founder signs
|
||||
(`ssh-keygen -Y sign -n secrets-engine-pdp-trust pdp-trust.json`), and
|
||||
* `access-engine.signer-public-receipt.v1`: kid, public key, pin and provenance
|
||||
hashes only. Owner authentication is the Access owner's commit of that receipt
|
||||
under `docs/evidence/`; the receipt never admits trust on its own.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import argparse
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
CUSTODY_SCHEMA = 'railiance-platform.signer-cas-receipt.v1'
|
||||
CUSTODY_PATH = 'platform/data/workloads/flex-auth/envelope-signing'
|
||||
FINAL = {'custody-verified-pending-independent-public-admission',
|
||||
'custody-reconciled-pending-independent-public-admission'}
|
||||
PIN_FIELDS = ('evaluator', 'mode', 'policy_package', 'policy_version', 'policy_package_digest')
|
||||
FORBIDDEN = re.compile(r'private|seed|secret_value|token', re.I)
|
||||
|
||||
|
||||
def sha(data: bytes) -> str:
|
||||
return 'sha256:' + hashlib.sha256(data).hexdigest()
|
||||
|
||||
|
||||
def read(path: str, limit: int = 65536) -> bytes:
|
||||
target = Path(path)
|
||||
if not target.is_absolute() or target.is_symlink() or not target.is_file():
|
||||
raise ValueError(f'absolute regular input required: {path}')
|
||||
data = target.read_bytes()
|
||||
if len(data) > limit:
|
||||
raise ValueError(f'input too large: {path}')
|
||||
return data
|
||||
|
||||
|
||||
def check_public_key(key: str, fingerprint: str) -> None:
|
||||
if not isinstance(key, str) or not re.fullmatch(r'[A-Za-z0-9_-]{43}', key):
|
||||
raise ValueError('public key must be unpadded base64url, 43 characters')
|
||||
if len(base64.urlsafe_b64decode(key + '=')) != 32:
|
||||
raise ValueError('public key must decode to 32 bytes')
|
||||
if fingerprint != sha(key.encode()):
|
||||
raise ValueError('public fingerprint does not match public key')
|
||||
|
||||
|
||||
def build(custody_raw: bytes, pin_raw: bytes, ccr_raw: bytes, plan_raw: bytes) -> tuple[bytes, dict]:
|
||||
custody, pin_doc = json.loads(custody_raw), json.loads(pin_raw)
|
||||
if any(FORBIDDEN.search(k) for k in custody):
|
||||
raise ValueError('custody receipt carries a non-public field; refuse without echoing it')
|
||||
if custody.get('schema') != CUSTODY_SCHEMA or custody.get('path') != CUSTODY_PATH:
|
||||
raise ValueError('not the Platform envelope-signing custody receipt')
|
||||
if custody.get('status') not in FINAL or custody.get('kv_version') != 1:
|
||||
raise ValueError('custody not verified at exact KV version 1')
|
||||
if custody.get('kid') != pin_doc['expected_kid']:
|
||||
raise ValueError('custody kid differs from the reviewed rollout kid')
|
||||
check_public_key(custody.get('public_key'), custody.get('public_fingerprint'))
|
||||
if custody.get('source_ccr_sha256') not in (None, sha(ccr_raw)):
|
||||
raise ValueError('custody receipt names a different CCR revision')
|
||||
pin = pin_doc['pin']
|
||||
if set(pin) != set(PIN_FIELDS) or any(not isinstance(pin[k], str) or not pin[k] for k in PIN_FIELDS):
|
||||
raise ValueError('incomplete issuer/policy pin')
|
||||
trust = {'schema': 'secrets-engine.pdp-trust.v1', **{k: pin[k] for k in PIN_FIELDS},
|
||||
'keys': {custody['kid']: custody['public_key']}}
|
||||
trust_bytes = (json.dumps(trust, indent=2, sort_keys=True) + '\n').encode()
|
||||
receipt = {
|
||||
'schema': 'access-engine.signer-public-receipt.v1',
|
||||
'kid': custody['kid'],
|
||||
'public_key': custody['public_key'],
|
||||
'public_fingerprint': custody['public_fingerprint'],
|
||||
'pin': {k: pin[k] for k in PIN_FIELDS},
|
||||
'trust_file_sha256': sha(trust_bytes),
|
||||
'provenance': {
|
||||
'custody_receipt_sha256': sha(custody_raw),
|
||||
'custody_status': custody['status'],
|
||||
'custody_kv_path': CUSTODY_PATH,
|
||||
'custody_kv_version': 1,
|
||||
'ccr_sha256': sha(ccr_raw),
|
||||
'native_plan_sha256': sha(plan_raw),
|
||||
'pin_file_sha256': sha(pin_raw),
|
||||
'source_revision': pin_doc['derivation']['source_revision'],
|
||||
'image': pin_doc['derivation']['image'],
|
||||
'rollout_manifest': pin_doc['derivation']['rollout_manifest'],
|
||||
},
|
||||
'admission': 'none: founder signs the trust bytes; Secrets owner installs them (SECRETS-WP-0014-T04)',
|
||||
}
|
||||
return trust_bytes, receipt
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
||||
for name in ('custody-receipt', 'pin', 'ccr', 'native-plan', 'trust-out', 'receipt-out'):
|
||||
parser.add_argument('--' + name, required=True)
|
||||
args = parser.parse_args()
|
||||
trust_bytes, receipt = build(read(args.custody_receipt), read(args.pin), read(args.ccr),
|
||||
read(args.native_plan, 262144))
|
||||
for out, data in ((args.trust_out, trust_bytes),
|
||||
(args.receipt_out, (json.dumps(receipt, indent=2, sort_keys=True) + '\n').encode())):
|
||||
target = Path(out)
|
||||
if not target.is_absolute() or target.exists():
|
||||
raise SystemExit(f'refusing existing or relative output: {out}')
|
||||
target.write_bytes(data)
|
||||
print(json.dumps({'kid': receipt['kid'], 'public_fingerprint': receipt['public_fingerprint'],
|
||||
'trust_file_sha256': receipt['trust_file_sha256']}))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue