Files
archy/scripts/tests/build_os_ota_crypto_fixture.py

85 lines
4.4 KiB
Python

#!/usr/bin/env python3
"""Compile isolated test-root verifier using exact production crypto source.
Only newly generated disposable key bytes are used. No release seed, signing
ceremony, runtime production override, publication or candidate qualification.
Run as a durable CPU2/8G/no-swap job; heavy lock is acquired inside the job.
"""
import argparse
import fcntl
import hashlib
import json
import os
from pathlib import Path
import secrets
import shutil
import subprocess
import tomllib
def sha(path):
with path.open('rb') as f:
return hashlib.file_digest(f,'sha256').hexdigest()
p=argparse.ArgumentParser(description=__doc__)
p.add_argument('--source',type=Path,required=True)
p.add_argument('--output',type=Path,required=True)
a=p.parse_args()
with open('/tmp/opencode/release-heavy.lock','a') as lock:
fcntl.flock(lock,fcntl.LOCK_EX)
assert shutil.disk_usage(a.output.parent).free>24*1024**3
a.output.mkdir(mode=0o700,exist_ok=False)
crate=a.output/'crate'
trust=crate/'src/trust'
trust.mkdir(parents=True)
sources={}
for name in ('signed_doc.rs','canonical.rs','did.rs'):
path=a.source/'core/archipelago/src/trust'/name
raw=path.read_bytes()
(trust/name).write_bytes(raw)
sources[str(path.relative_to(a.source))]=hashlib.sha256(raw).hexdigest()
main=a.source/'scripts/tests/os_ota_crypto_fixture.rs'
shutil.copyfile(main,crate/'src/main.rs')
seed=secrets.token_bytes(32)
key=a.output/'fixture.seed'
fd=os.open(key,os.O_CREAT|os.O_EXCL|os.O_WRONLY,0o600)
with os.fdopen(fd,'wb') as f:
f.write(seed)
# PKCS#8 wrapper of our freshly generated raw Ed25519 seed. OpenSSL only
# derives its public key; it never reads an operator key or environment seed.
public=subprocess.check_output(['openssl','pkey','-inform','DER','-pubout','-outform','DER'],
input=bytes.fromhex('302e020100300506032b657004220420')+seed)
assert public[:12]==bytes.fromhex('302a300506032b6570032100') and len(public)==44
pin=public[12:].hex()
(trust/'anchor.rs').write_text('''// GENERATED DISPOSABLE TEST ANCHOR; NO RUNTIME OVERRIDE.
pub fn release_root_pubkey() -> Option<ed25519_dalek::VerifyingKey> {
let raw: [u8;32] = hex::decode("'''+pin+'''").unwrap().try_into().unwrap();
Some(ed25519_dalek::VerifyingKey::from_bytes(&raw).unwrap())
}
''')
packages=tomllib.loads((a.source/'core/Cargo.lock').read_text())['package']
dependencies={}
for name in ('anyhow','ed25519-dalek','serde_json','hex','bs58'):
versions=[x['version'] for x in packages if x['name']==name and (name!='ed25519-dalek' or x['version'].startswith('2.'))]
assert len(versions)==1,(name,versions)
dependencies[name]='='+versions[0]
(crate/'Cargo.toml').write_text('[package]\nname="archipelago-os-ota-crypto-fixture"\nversion="0.0.0"\nedition="2021"\n[workspace]\n[dependencies]\n'+
'\n'.join(name+'='+json.dumps(version) for name,version in dependencies.items())+'\n')
env=dict(os.environ,CARGO_BUILD_JOBS='1',CARGO_INCREMENTAL='0',CARGO_TARGET_DIR=str(a.output/'target'))
command=['/home/archipelago/.cargo/bin/cargo','build','--offline','--release','--manifest-path',str(crate/'Cargo.toml')]
with (a.output/'build.log').open('x') as log:
result=subprocess.run(command,env=env,stdout=log,stderr=subprocess.STDOUT)
(a.output/'compile-result.json').write_text(json.dumps(dict(exit=result.returncode,command=command))+'\n')
result.check_returncode()
binary=a.output/'fixture-native'
shutil.copyfile(a.output/'target/release/archipelago-os-ota-crypto-fixture',binary)
proof=dict(schema=1,scope='ISOLATED_COMPILETIME_TEST_ROOT_NOT_CANDIDATE',
production_approved=False,production_sources_unchanged=sources,
production_anchor_sha256=sha(a.source/'core/archipelago/src/trust/anchor.rs'),
fixture_anchor_sha256=sha(trust/'anchor.rs'),fixture_main_sha256=sha(crate/'src/main.rs'),
fixture_public_key=pin,binary_sha256=sha(binary),cargo_lock_sha256=sha(crate/'Cargo.lock'),
trust_difference='compile-time generated test anchor; production canonical/DID/verify_strict code unchanged; no environment override',
dependencies=dependencies)
(a.output/'fixture-proof.json').write_text(json.dumps(proof,indent=2)+'\n')
assert all(sha(a.source/name)==expected for name,expected in sources.items())
print(json.dumps(proof,indent=2),flush=True)