src/ail_ref/passport.py

Read-only source from the unchanged 0.3.4 kit. Existing license terms apply.

from __future__ import annotations

import base64
from dataclasses import dataclass

from .ast import Atom, Document, Record, String, Unknown
from .canonical import canonical_value


@dataclass(frozen=True)
class PassportResult:
    principal: str | None
    claimed_role: str | None
    claimed_grant: str | None
    status: str
    reason: str


def _text(value):
    if isinstance(value, Atom):
        return value.value
    if isinstance(value, String):
        return value.value
    return None


def _passport_record(document: Document) -> Record:
    if len(document.units) != 1:
        raise ValueError("a passport document must contain exactly one unit")
    value = document.units[0].value
    if not isinstance(value, Record) or value.name != "PASSPORT":
        raise ValueError("document does not contain a PASSPORT record")
    return value


def canonical_signing_payload(record: Record) -> bytes:
    fields = tuple((key, value) for key, value in record.fields if key != "signature")
    return canonical_value(Record(record.name, fields)).encode("utf-8")


def inspect_passport(document: Document) -> PassportResult:
    record = _passport_record(document)
    fields = dict(record.fields)
    principal = _text(fields.get("principal"))
    role = _text(fields.get("role"))
    grant = _text(fields.get("grant"))
    declared = _text(fields.get("verification"))
    signature = fields.get("signature")

    if isinstance(signature, Unknown) or signature is None:
        if declared == "ail:cryptographically_verified":
            return PassportResult(principal, role, grant, "ail:rejected", "passport claims cryptographic verification but has no signature")
        return PassportResult(principal, role, grant, "ail:declared", "owner claim is present without independent authentication")

    if not isinstance(signature, String):
        return PassportResult(principal, role, grant, "ail:rejected", "signature must be a base64 string or unknown")

    algorithm = _text(fields.get("algorithm"))
    public_key = fields.get("public_key")
    if algorithm != "Ed25519" or not isinstance(public_key, String):
        return PassportResult(principal, role, grant, "ail:rejected", "signed passport requires Ed25519 and a base64 public key")

    try:
        from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
    except ImportError:
        return PassportResult(principal, role, grant, "ail:declared", "signature is present but the optional cryptography package is unavailable")

    try:
        key_bytes = base64.b64decode(public_key.value, validate=True)
        signature_bytes = base64.b64decode(signature.value, validate=True)
        Ed25519PublicKey.from_public_bytes(key_bytes).verify(signature_bytes, canonical_signing_payload(record))
    except Exception as exc:
        return PassportResult(principal, role, grant, "ail:rejected", f"signature verification failed: {type(exc).__name__}")
    return PassportResult(principal, role, grant, "ail:cryptographically_verified", "Ed25519 signature verified")