presence_verify.py
Verify an ECDSA-P256 presence assertion against a dongle's public key.
Overview
Verify an ECDSA-P256 presence assertion against a dongle's public key.
This is the portable half of the presence primitive. A P-256 assertion is checkable by any holder of the dongle's public point, which is what lets a CI job, a release-tag hook or a second reviewer accept "a human was physically at that machine" without being trusted with a secret. A shared-secret assertion could only be checked by a holder of the key that could equally forge it, which is why that mode was retired rather than kept alongside this one.
Stdlib only. The curve arithmetic is delegated to the openssl binary already present on any machine that can run a CI job, so nothing is vendored and no third-party Python package is required. That also keeps this file honest about what it does NOT do: it never implements P-256 itself.
The wire format is defined by modules/woz_aliro/src/aliro_assert.c, and the verdicts and their ORDER mirror aliro_assert_verify_p256() exactly, so the two implementations cannot disagree about what a frame means. The offsets below are drift-gated against that C source by tests/host/test_presence_verify.py.
A distance is only worth as much as the measurement behind it, so the frame also carries that measurement's integrity evidence and this verifier refuses a frame that does not claim a well-correlated STS. Rejecting is the whole point: an undefended 19 cm and a defended 19 cm arrive as the same integer, so a verifier that ignores the evidence cannot tell a measurement from an assertion.
Usage: presence_verify.py --pubkey <hex|file> --frame <hex|file> --nonce <hex> --cred-id <hex> [--max-cm N] [--min-uptime-ms N] [--min-sts-quality N] [--json]
Exit status is 0 only when presence is confirmed, so it can gate a command directly. Every rejection exits 1 and names its reason.
used by presence_service.py presence_git.py
API
Cclass OpensslMissing(RuntimeError)
Raised when the openssl binary cannot be run.
Deliberately not folded into a verification failure: "we could not check" and "the signature is bad" are different facts, and silently reporting the first as the second would turn a broken install into a security verdict.
verify_signatureFder_uint(v: bytes) -> bytes
DER-encode a big-endian unsigned integer as an ASN.1 INTEGER.
raw_sig_to_derFraw_sig_to_der(sig: bytes) -> bytes
Convert a raw r||s ECDSA signature to the DER form openssl expects.
verify_signature · calls der_uintFpoint_to_pem(point: bytes) -> bytes
Wrap a 65-byte uncompressed P-256 point as a PEM public key.
verify_signatureFverify_signature(point: bytes, msg: bytes, sig: bytes, openssl: str='openssl') -> bool
True when sig is a valid ECDSA-P256-SHA256 signature over msg under point.
Returns False for a key that is structurally unusable -- notably the all-zero point a dongle reports when it has no signing key -- because such a key must never verify anything.
verify · calls OpensslMissing, point_to_pem, raw_sig_to_derFparse_fields(wire: bytes) -> dict
Decode the assertion body. Assumes framing has already been checked.
Parsing before authentication is intentional and matches the C: the fields are returned for logging even on a reject, and the caller is responsible for ignoring them unless the verdict is OK.
verifyFcheck_framing(wire: bytes, want_alg: int=ALG_ECDSA_P256) -> int
Length, magic, version and algorithm checks, in the C's exact order.
Algorithm is tested before length on purpose: a well-formed frame for the other algorithm is a misconfigured peer, not corrupt framing, and the two have entirely different fixes.
verifyFverify(wire: bytes, pubkey: bytes, expected_nonce: bytes, expected_cred_id: bytes, max_cm: int=40, min_uptime_ms: int=0, min_sts_quality: int=0, openssl: str='openssl')
Fully verify a P-256 assertion. Returns (verdict, fields-or-None).
Check order mirrors aliro_assert_verify_p256(): framing, algorithm, signature, nonce echo, enrolled credential, forward progress, presence, distance, then range integrity. Fields are returned only once the signature has passed, so nothing a forger wrote can reach a caller that logs them.
min_sts_quality is the one policy knob the C verifier deliberately does not have. The producer already applied its own compiled-in floor, so 0 changes nothing; raising it lets a verifier demand better correlation than the firmware was built with, which is how a floor gets tightened from bench captures without reflashing every board first. trust_level is reported but not thresholded, because the firmware only ever emits ranges that already reached its consensus K -- a knob with no reachable range is not a knob.
main · calls check_framing, parse_fields, verify_signatureFread_bytes(arg: str, name: str) -> bytes
Accept either a hex string or a path to a file of hex or raw bytes.
mainFmain(argv=None) -> int
Parse command-line arguments and verify an ECDSA-P256 presence assertion. Arguments include the dongle public key, assertion frame, challenge nonce, credential ID, and optional distance/uptime/STS-quality thresholds. Outputs human-readable or JSON verdict. Returns 0 if the assertion verifies, 1 if it is rejected, 2 if openssl is unavailable.
read_bytes, verify