Signing & verification#
Signing and verification of fragment envelopes.
Signed envelopes let a server, edge or client that relays fragments check
that they came from the origin unchanged, for the slot they claim, and
recently. Signing is off by default; turn it on with
SignOptions:
from anza import Setup, SignOptions
Setup(root="./templates", signing=SignOptions(mode="hmac", secret=secret))
Setup(root="./templates", signing=SignOptions(mode="ed25519", private_key=seed_hex))
Setup(root="./templates") # mode="none": envelopes carry no signature
Keys
Keys are checked once, in Setup.run(), which builds
a Signer (engine.signer). A bad key raises
AnzaError with code CRYPTO, so a running engine never
signs with a missing, short or mismatched key.
hmacneeds asecret(str or bytes) of at least 32 bytes. Both sides share it; derive per-purpose keys from one master secret withanza.hkdf.derive_key().ed25519needs aprivate_keyas a 32-byte seed, 64-char lowercase hex, or acryptographyEd25519PrivateKey; an optionalpublic_keymust match it. Verifiers only need the public key (engine.signer.public_key). Generate a pair withanza.ed25519.keypair().
repr of SignOptions, Signer,
VerifyKey and Engine never prints key material.
Ed25519 and constant-time comparisons come from the cryptography package
(cryptography>=42, a dependency of anza).
Signature v2
The signature covers the bytes from signature_message() (also
Envelope.message()):
message = "anza-sig-v2\n" field(ts) field(slot) field(html) field(css)
field(x) = <decimal UTF-8 byte length of x> ":" x "\n"
Signatures are lowercase hex: 64 characters for HMAC-SHA256, 128 for Ed25519. Uppercase, odd-length or otherwise malformed hex is rejected, so a signature has exactly one accepted spelling.
Verification
Check a received envelope with verify_envelope() (or
Engine.verify_envelope() with the
engine’s own key):
import json
from anza import VerifyKey, verify_envelope
envelope = json.loads(body)
verify_envelope(VerifyKey.hmac(secret), envelope, max_age_secs=60, slot="feed")
verify_envelope(VerifyKey.ed25519(public_key), envelope, max_age_secs=60, slot="feed")
engine.verify_envelope(envelope, max_age_secs=60, slot="feed")
Each call returns None or raises AnzaError with code
CRYPTO whose message names the failed check: unsupported envelope
version, malformed envelope, envelope is not signed, malformed
signature, envelope timestamp is in the future, envelope expired,
slot mismatch or bad signature. Always pass the slot you expect
and a max_age_secs that fits your use: a valid signature alone does not
stop a replay of an old envelope or one meant for another slot.
The wire format and the checks are specified in the Engine Contract.