Skip to content

signed

signed

What a paired device must prove to be heard, whatever it is saying.

Three surfaces are open to devices that hold no API key — the presence beacon, the command poll, and the result acknowledgement — and all three answer the same question first: is this really that device, saying this, now? The answer is one Ed25519 signature and seven checks, and they live here so there is exactly one copy of them to get right.

What each surface signs stays its own business: every caller passes the explicit tuple of fields covered by the signature. Listing them rather than signing "everything but the signature" means adding a field later is a deliberate act with a version bump, not an accident that silently changes what is protected.

Classes

SignedRejected

SignedRejected(code: str, message: str)

Bases: Exception

Refused, with a reason already written for the user in French.

Source code in src/diapason/mesh/signed.py
def __init__(self, code: str, message: str) -> None:
    super().__init__(message)
    self.code = code
    self.message = message

Functions:

signable

signable(
    payload: Mapping[str, Any], fields: Sequence[str]
) -> dict[str, Any]

The exact subset the signature covers, in a stable order.

Source code in src/diapason/mesh/signed.py
def signable(payload: Mapping[str, Any], fields: Sequence[str]) -> dict[str, Any]:
    """The exact subset the signature covers, in a stable order."""
    return {field: payload.get(field) for field in fields}

sign_payload

sign_payload(
    payload: Mapping[str, Any], fields: Sequence[str]
) -> dict[str, Any]

Return payload with this device's signature over fields attached.

Source code in src/diapason/mesh/signed.py
def sign_payload(payload: Mapping[str, Any], fields: Sequence[str]) -> dict[str, Any]:
    """Return *payload* with this device's signature over *fields* attached."""
    from diapason.mesh.identity import sign_envelope

    body = dict(payload)
    body["signature"] = sign_envelope(signable(body, fields))
    return body

verify_payload

verify_payload(
    raw: Mapping[str, Any],
    *,
    fields: Sequence[str],
    version: int,
    registry: Any,
    local_owner_id: str,
    local_device_id: str,
    now_ms: int,
    stamp_field: str = "sentAtMs",
    subject: str = "requête",
) -> str

Run the seven checks every device-signed request must pass.

Order matches verify_command: the cheap structural checks first, so a malformed or misaddressed payload never reaches the cryptography.

subject names the thing being refused, so somebody debugging why their phone will not pair reads « Cette relève est trop ancienne » rather than a generic noun. It must be a FEMININE French noun — the sentences below agree with it (« Cette … n'est pas signée »).

@returns the verified sending device id.

Source code in src/diapason/mesh/signed.py
def verify_payload(
    raw: Mapping[str, Any],
    *,
    fields: Sequence[str],
    version: int,
    registry: Any,
    local_owner_id: str,
    local_device_id: str,
    now_ms: int,
    stamp_field: str = "sentAtMs",
    subject: str = "requête",
) -> str:
    """Run the seven checks every device-signed request must pass.

    Order matches ``verify_command``: the cheap structural checks first, so a
    malformed or misaddressed payload never reaches the cryptography.

    ``subject`` names the thing being refused, so somebody debugging why their
    phone will not pair reads « Cette relève est trop ancienne » rather than a
    generic noun. It must be a FEMININE French noun — the sentences below
    agree with it (« Cette … n'est pas signée »).

    @returns the verified sending device id.
    """
    # Every read below is defensive. These four routes are the only surface a
    # stranger on the network can reach, so anything they send has to come out
    # as a refusal — a TypeError escaping here became a 500 with a stack trace
    # in the log, on demand, from anyone.
    if not isinstance(raw, Mapping):
        raise SignedRejected("DENIED", f"Cette {subject} est illisible.")

    if _as_int(raw.get("version")) != version:
        raise SignedRejected(
            "UNSUPPORTED", f"Cette {subject} utilise une version non prise en charge."
        )

    owner = str(raw.get("ownerId") or "")
    if not owner or owner != local_owner_id:
        raise SignedRejected(
            "DENIED", f"Cette {subject} vient d'un autre ensemble d'appareils."
        )

    device_id = str(raw.get("deviceId") or "")
    if not device_id:
        raise SignedRejected("DENIED", f"Cette {subject} ne dit pas qui l'envoie.")
    if device_id == local_device_id:
        raise SignedRejected("DENIED", "Un appareil ne s'adresse pas à lui-même.")

    # A revoked device holds no key here, so revocation stops it at once —
    # the same single choke point revocation already uses for commands.
    public_key = registry.public_key_of(device_id)
    if public_key is None:
        raise SignedRejected(
            "DENIED", "Cet appareil n'est pas autorisé sur cette machine."
        )

    sent_at = _as_int(raw.get(stamp_field))
    if sent_at - MAX_SIGNED_SKEW_MS > now_ms:
        raise SignedRejected("DENIED", f"Cette {subject} est datée du futur.")
    if sent_at + MAX_SIGNED_SKEW_MS < now_ms:
        raise SignedRejected("EXPIRED", f"Cette {subject} est trop ancienne.")

    from diapason.mesh.identity import verify_envelope

    signature = str(raw.get("signature") or "")
    if not signature:
        raise SignedRejected("DENIED", f"Cette {subject} n'est pas signée.")
    if not verify_envelope(signable(raw, fields), signature, public_key):
        raise SignedRejected("DENIED", f"La signature de cette {subject} est invalide.")

    return device_id