61 lines
3.4 KiB
Python
61 lines
3.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Cold-boot evidence for an interrupted crypto/D09 transaction; never promote it.
|
|
|
|
Observe real installed kernel/package effects and native signatures, but require
|
|
reconciliation and refuse replay/postboot success after the owner's anchor loss.
|
|
"""
|
|
import json
|
|
from pathlib import Path
|
|
import platform
|
|
import subprocess
|
|
import sys
|
|
|
|
assert 'QEMU' in Path('/sys/class/dmi/id/sys_vendor').read_text()
|
|
assert subprocess.check_output(['findmnt','-n','-o','SOURCE','/'],text=True).strip()=='/dev/vda'
|
|
sys.path.insert(0,'/usr/libexec')
|
|
import os_update_executor as executor
|
|
assert executor.ROOT==Path('/var/lib/archipelago-os-update')
|
|
binding=json.loads(Path('/etc/archipelago-d09/binding.json').read_text())
|
|
instance=executor.Executor(binding['os_update'])
|
|
assert instance.status['phase']=='RECONCILIATION_REQUIRED'
|
|
assert platform.release()==instance.plan['boot']['expected_release']
|
|
before=instance.status_path.read_bytes()
|
|
inventory=executor.inventory()
|
|
instance.verify_installed()
|
|
boot=instance.boot_provenance()
|
|
evidence=Path('/var/log/os-ota-stage')
|
|
expected=json.loads((evidence/'canaries-before.json').read_text())
|
|
assert {p.name:executor.file_hash(p) for p in Path('/var/lib/archipelago/ota-synthetic-canaries').iterdir()}==expected
|
|
assert Path('/var/lib/archipelago').stat().st_uid==1000 and executor.ROOT.stat().st_uid==0
|
|
subprocess.run(['runuser','-u','archipelago','--','touch','/var/lib/archipelago/held-postboot-positive-probe'],check=True)
|
|
denials=[]
|
|
for args in (['touch',str(executor.ROOT/'app-write-denied')],
|
|
['python3','-c',"import os;os.rename('/var/lib/archipelago-os-update','/var/lib/app-rename-denied')"],
|
|
['python3','-c',"open('/root/os-ota-crypto-fixture/test.seed','rb').read()"]):
|
|
r=subprocess.run(['runuser','-u','archipelago','--',*args],capture_output=True,text=True)
|
|
assert r.returncode!=0
|
|
denials.append(dict(command=args,exit=r.returncode))
|
|
refusals=[]
|
|
for phase in ('apply','postboot'):
|
|
try:
|
|
executor.stage(binding,'APPLY',phase)
|
|
except (executor.ReconciliationRequired,ValueError) as error:
|
|
refusals.append(dict(operation=phase,error=str(error)))
|
|
else:
|
|
raise AssertionError('uncertain transaction promoted/replayed')
|
|
assert executor.inventory()==inventory and instance.status_path.read_bytes()==before
|
|
public=subprocess.run(['/usr/libexec/archipelago-d09ctl','inspect'],capture_output=True,text=True)
|
|
assert public.returncode==0,public.stderr
|
|
status=json.loads(public.stdout)
|
|
assert status['state']=='UNKNOWN' and not (status.get('os_update') or {}).get('post_boot_verified',False)
|
|
result=dict(passed=True,scope='held transaction coldboot/crypto/custody/refusal proof ONLY',
|
|
full_ota_passed=False,postboot_confirmed=False,production_approved=False,candidate_qualified=False,
|
|
running_kernel=platform.release(),kernel_build=platform.version(),boot_id=executor.boot_id(),
|
|
authenticated_archive_sha256=boot['archive_sha256'],verified_kernel_files=len(boot['files']),
|
|
native_crypto_verified=True,all_planned_packages_configured=True,canaries_preserved=True,
|
|
os_phase=instance.status['phase'],status_unchanged=True,package_replay=False,
|
|
app_data_uid=1000,os_root=str(executor.ROOT),custody_denials=denials,refusals=refusals,
|
|
authenticated_owner_inspect=status)
|
|
(evidence/'crypto-held-postboot-result.json').write_text(json.dumps(result,indent=2)+'\n')
|
|
print(json.dumps(result,indent=2),flush=True)
|