129 lines
5.5 KiB
Python
129 lines
5.5 KiB
Python
#!/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")
|