Retain verified recovery proof independently and bind the protected point
Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a17f16-d99c-7ea3-95f0-8212897fba6b
This commit is contained in:
parent
e1918b3f88
commit
09f4fd33cb
7 changed files with 257 additions and 11 deletions
|
|
@ -20,7 +20,7 @@ from application_recovery_identities import (
|
|||
private_directory,
|
||||
private_json,
|
||||
)
|
||||
from application_recovery_kit import PROJECT, download
|
||||
from application_recovery_kit import PROJECT, download, protect_receipt
|
||||
from application_recovery_resources import API, s3, status
|
||||
|
||||
|
||||
|
|
@ -133,6 +133,7 @@ class Drill:
|
|||
"--output", str(self.root / "application-receipt.json")]))
|
||||
app = json.loads(app)
|
||||
baseline = json.loads((contents / "application-recovery.json").read_text())
|
||||
point = json.loads((contents / "primary-receipt.json").read_text())
|
||||
for key in ("actual_pages_rendered_through_view", "same_tenant_ungranted_page_denials", "content_files_unchanged"):
|
||||
if app.get(key) != baseline.get(key):
|
||||
raise Refused("kit_application_baseline_differs")
|
||||
|
|
@ -143,11 +144,16 @@ class Drill:
|
|||
"recovery_seconds": round(time.monotonic() - began, 3), "protected_kit": self.reference,
|
||||
"kit_members_verified": len(manifest["files"]) + 1, "application": "coulomb-social",
|
||||
"captured_at": manifest["captured_at"], "application_restore_verified": True,
|
||||
"all_data_images_and_tools_from_independent_provider_kit": True,
|
||||
"application_data_images_and_recovery_tools_from_provider_kit": True,
|
||||
"bootstrap_api_client_preinstalled": True,
|
||||
"age_identity_from_separate_provider_escrow": True, "original_node_or_plaintext_used": False,
|
||||
"personal_session_used": False, "registry_pulls_performed": False, "fresh_docker_daemon": False,
|
||||
"operator_account_recovery_time_included": False, "real_browser_journey_verified": False,
|
||||
"unpack": unpacked, "restore": restored, "application_checks": app, "timers_activated": False}
|
||||
result.update({k: point[k] for k in ("object_key", "version_id", "ciphertext_sha256", "ciphertext_bytes",
|
||||
"image", "age_recipient", "database_sha256", "content_sha256")})
|
||||
result["protected_recovery_proof"] = protect_receipt(
|
||||
s3(private_json(self.directory / "custodian-pair.json"), PROJECT), reader, result)
|
||||
checkpoint(self.root / "recovery-receipt.json", result)
|
||||
return result
|
||||
|
||||
|
|
|
|||
|
|
@ -49,8 +49,7 @@ def download(client, ref, output):
|
|||
return dict(ref, independently_retrieved_hash_verified=True, automatic_expiry=False)
|
||||
|
||||
|
||||
def publish(client, source):
|
||||
archive.regular_file(source)
|
||||
def require_protected_bucket(client):
|
||||
if client.get_bucket_versioning(Bucket=BUCKET).get("Status") != "Enabled":
|
||||
raise Refused("protected_bucket_versioning_required")
|
||||
try:
|
||||
|
|
@ -61,6 +60,40 @@ def publish(client, source):
|
|||
lifecycle = []
|
||||
if lifecycle:
|
||||
raise Refused("protected_bucket_must_have_no_lifecycle_expiry")
|
||||
|
||||
|
||||
def protect_receipt(writer, reader, receipt):
|
||||
"""Retain the recovery proof independently, without a self-hash cycle."""
|
||||
require_protected_bucket(writer)
|
||||
payload = json.dumps(receipt, sort_keys=True, separators=(",", ":")).encode()
|
||||
if len(payload) > 1024**2 or receipt.get("status") != "application_restored_verified":
|
||||
raise Refused("bounded_successful_recovery_proof_required")
|
||||
expected = hashlib.sha256(payload).hexdigest()
|
||||
key = "kits/receipts/" + expected + "/recovery-proof.json"
|
||||
try:
|
||||
head = writer.head_object(Bucket=BUCKET, Key=key)
|
||||
except Exception as error:
|
||||
if status(error) != 404:
|
||||
raise
|
||||
response = writer.put_object(Bucket=BUCKET, Key=key, Body=payload,
|
||||
IfNoneMatch="*", Metadata={"sha256": expected}, ContentType="application/json")
|
||||
version = archive.require_version(response.get("VersionId"))
|
||||
else:
|
||||
if head["ContentLength"] != len(payload) or head.get("Metadata", {}).get("sha256") != expected:
|
||||
raise Refused("existing_proof_binding_differs")
|
||||
version = archive.require_version(head.get("VersionId"))
|
||||
response = reader.get_object(Bucket=BUCKET, Key=key, VersionId=version)
|
||||
with response["Body"] as stream:
|
||||
if response.get("VersionId") != version or stream.read(len(payload) + 1) != payload:
|
||||
raise Refused("independent_proof_readback_differs")
|
||||
return {"project_id": PROJECT, "bucket": BUCKET, "object_key": key, "version_id": version,
|
||||
"sha256": expected, "bytes": len(payload), "independent_readback_verified": True,
|
||||
"automatic_expiry": False}
|
||||
|
||||
|
||||
def publish(client, source):
|
||||
archive.regular_file(source)
|
||||
require_protected_bucket(client)
|
||||
size = source.stat().st_size
|
||||
if not 0 < size <= MAX_BYTES:
|
||||
raise Refused("bounded_kit_required")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue