This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Offline Phase-1.0AD activation-record contract.
|
||||
|
||||
This module validates data only. It has no socket, DNS, clock, CLI, file
|
||||
output, transport or device capability.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timezone
|
||||
import ipaddress
|
||||
import re
|
||||
|
||||
|
||||
PHASE = "PHASE_1_0AD_INACTIVE_NUMERIC_TARGET_CONTRACT"
|
||||
PORT = 2323
|
||||
MAX_WINDOW_SECONDS = 300
|
||||
RUN_ID = re.compile(r"^[A-Z0-9][A-Z0-9_-]{7,63}$")
|
||||
SHA256 = re.compile(r"^[0-9a-f]{64}$")
|
||||
|
||||
|
||||
class ActivationContractError(ValueError):
|
||||
"""An activation record is incomplete, ambiguous or unsafe."""
|
||||
|
||||
|
||||
def _utc(value: str) -> datetime:
|
||||
if not isinstance(value, str) or not value.endswith("Z"):
|
||||
raise ActivationContractError("timestamp must be UTC with Z suffix")
|
||||
try:
|
||||
parsed = datetime.fromisoformat(value[:-1] + "+00:00")
|
||||
except ValueError as error:
|
||||
raise ActivationContractError("timestamp is invalid") from error
|
||||
if parsed.tzinfo != timezone.utc:
|
||||
raise ActivationContractError("timestamp is not UTC")
|
||||
return parsed
|
||||
|
||||
|
||||
def validate_numeric_target(value: str) -> str:
|
||||
"""Return canonical private IPv4 text without resolving a name."""
|
||||
if not isinstance(value, str) or not value or value != value.strip():
|
||||
raise ActivationContractError("target must be exact numeric text")
|
||||
if ":" in value or any(character.isalpha() for character in value):
|
||||
raise ActivationContractError("only numeric IPv4 targets are allowed")
|
||||
try:
|
||||
address = ipaddress.IPv4Address(value)
|
||||
except ipaddress.AddressValueError as error:
|
||||
raise ActivationContractError("target is not canonical IPv4") from error
|
||||
if str(address) != value or not address.is_private or address.is_loopback \
|
||||
or address.is_link_local or address.is_multicast \
|
||||
or address.is_unspecified:
|
||||
raise ActivationContractError("target is not canonical private IPv4")
|
||||
return value
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ActivationRecord:
|
||||
phase: str
|
||||
active: bool
|
||||
target_address: str | None
|
||||
target_port: int | None
|
||||
run_id: str | None
|
||||
not_before: str | None
|
||||
expires_at: str | None
|
||||
launcher_sha256: str | None
|
||||
payload_sha256: str | None
|
||||
approval_sha256: str | None
|
||||
one_shot: bool
|
||||
automatic_retry: bool
|
||||
reconnect: bool
|
||||
resume: bool
|
||||
device_write_authorized: bool
|
||||
app_termination_authorized: bool
|
||||
system_remount_authorized: bool
|
||||
|
||||
|
||||
def validate_inactive(record: ActivationRecord) -> None:
|
||||
"""Validate the only tracked Phase-1.0AD state: entirely inactive."""
|
||||
if type(record) is not ActivationRecord or record.phase != PHASE:
|
||||
raise ActivationContractError("activation phase is invalid")
|
||||
if record.active is not False:
|
||||
raise ActivationContractError("tracked activation must be inactive")
|
||||
optional = (
|
||||
record.target_address, record.target_port, record.run_id,
|
||||
record.not_before, record.expires_at, record.launcher_sha256,
|
||||
record.payload_sha256, record.approval_sha256,
|
||||
)
|
||||
if any(value is not None for value in optional):
|
||||
raise ActivationContractError("inactive activation must be target-free")
|
||||
if record.one_shot is not True or record.automatic_retry is not False \
|
||||
or record.reconnect is not False or record.resume is not False:
|
||||
raise ActivationContractError("inactive one-shot policy is invalid")
|
||||
if record.device_write_authorized or record.app_termination_authorized \
|
||||
or record.system_remount_authorized:
|
||||
raise ActivationContractError("inactive device effects must be false")
|
||||
|
||||
|
||||
def validate_candidate(record: ActivationRecord) -> None:
|
||||
"""Validate hypothetical activation data without activating anything."""
|
||||
if type(record) is not ActivationRecord or record.phase != PHASE \
|
||||
or record.active is not True:
|
||||
raise ActivationContractError("candidate is not explicitly active")
|
||||
validate_numeric_target(record.target_address) # type: ignore[arg-type]
|
||||
if record.target_port != PORT:
|
||||
raise ActivationContractError("candidate port is not source-bound")
|
||||
if not isinstance(record.run_id, str) or not RUN_ID.fullmatch(record.run_id):
|
||||
raise ActivationContractError("candidate run id is invalid")
|
||||
if not isinstance(record.launcher_sha256, str) \
|
||||
or not SHA256.fullmatch(record.launcher_sha256):
|
||||
raise ActivationContractError("launcher identity is invalid")
|
||||
if not isinstance(record.payload_sha256, str) \
|
||||
or not SHA256.fullmatch(record.payload_sha256):
|
||||
raise ActivationContractError("payload identity is invalid")
|
||||
if not isinstance(record.approval_sha256, str) \
|
||||
or not SHA256.fullmatch(record.approval_sha256):
|
||||
raise ActivationContractError("approval identity is invalid")
|
||||
start = _utc(record.not_before) # type: ignore[arg-type]
|
||||
end = _utc(record.expires_at) # type: ignore[arg-type]
|
||||
duration = (end - start).total_seconds()
|
||||
if not 0 < duration <= MAX_WINDOW_SECONDS:
|
||||
raise ActivationContractError("candidate window is invalid")
|
||||
if record.one_shot is not True or record.automatic_retry is not False \
|
||||
or record.reconnect is not False or record.resume is not False:
|
||||
raise ActivationContractError("candidate one-shot policy is invalid")
|
||||
if record.device_write_authorized or record.app_termination_authorized \
|
||||
or record.system_remount_authorized:
|
||||
raise ActivationContractError("candidate requests forbidden effects")
|
||||
Reference in New Issue
Block a user