certanix-verify
Verify a Certanix evidence pack offline. The standard library plus two pinned
dependencies, no network, no cosign binary, no transparency-log lookup, and nothing
from the Certanix platform.
The two dependencies, and why (from 0.6.0):
cryptographychecks signatures (ECDSA, RSA) and X.509 certificates.asn1crypto, pure Python with no compiled code, parses ASN.1: the RFC 3161 timestamp tokens and RFC 4998 evidence records of the Certanix Vault, whichcryptographycannot read. Hand-written DER parsing was rejected as security-sensitive code with no second reviewer, and a separate package was rejected because a person checking a disclosure in 2036 should need to install one tool.
Quickstart
pip install certanix-verify
certanix-verify verify evidence-pack.json --key cosign.pub
echo $? # 0 = verified; any other value is the failure class (see below)
What it checks, and what it does not
It checks that the pack's canonical bytes — recomputed from the document exactly as you supply it — match the detached signature made by the key you pass in, that the pack satisfies the evidence-pack schema, and that it does not claim a capability its own recorded capability gate denies.
It does not tell you whose key that is. A signature verifies against whatever key you hand it, so confirm the key's fingerprint against an out-of-band source (a Certanix deck appendix, printed collateral, a channel you already trust) before trusting what a green result means. A verifier that implied otherwise would be lying by omission.
Exit codes
The exit code is the failure class, so a script can branch on why verification failed without parsing text:
| Code | Class | Meaning |
|---|---|---|
| 0 | OK |
Signature valid over the canonical bytes; schema valid; no gate violation. |
| 1 | USAGE |
Could not begin: unreadable pack, missing key, bad invocation. Says nothing about the pack. |
| 2 | MALFORMED_INPUT |
Not parseable as a JSON object (e.g. truncated). |
| 3 | SCHEMA_INVALID |
Parses, but violates the schema: missing required field, unknown field, value outside a closed vocabulary. |
| 4 | UNSUPPORTED_SCHEMA_VERSION |
A schema version this verifier does not know. Refused rather than guessed at. |
| 5 | SIGNATURE_MISSING |
The pack carries no signature — an unsigned pack, not a failed check. |
| 6 | SIGNATURE_MALFORMED |
A signature is present but its container is not decodable. |
| 7 | SIGNATURE_MISMATCH |
The cryptographic check failed: these are not the bytes that were signed. The tamper class. |
| 8 | KEY_MISMATCH |
The signature names a different signer than the key supplied. |
| 9 | GATE_VIOLATION |
Signature and schema are valid, but the pack claims a capability its recorded gate state denies. |
| 10 | EVIDENCE_MISMATCH |
vault only: the evidence record does not cover these bytes. They changed after they were timestamped. |
| 11 | TIMESTAMP_INVALID |
vault only: the RFC 3161 token is not valid (modified, or its signature fails). |
| 12 | TSA_UNTRUSTED |
vault only: the token's signer does not chain to a trust anchor you supplied, is not a time-stamping certificate, or was not valid when the token was issued. |
| 13 | TSA_EXPIRED |
vault only: the time-stamping certificate has expired at the verification time and nothing in the record renews it. |
Vault evidence records (0.6.0)
certanix-verify vault evidence-record-0001.ers \
--data artefact --data manifest.json --tsa-root tsa-root.pem [--at 2036-01-01T00:00:00Z]
It shows that the data objects are unchanged since the time the TSA states, as attested
by the trust anchor you supply. From 0.7.0 it reads renewed records (RFC 4998 section 5):
a later timestamp over an earlier one, or a new chain under a newer hash algorithm. Each
timestamp must have been covered by the next before its certificate expired, and the
newest must be valid at the verification time; a renewal that came too late is
TSA_EXPIRED, and a broken link is EVIDENCE_MISMATCH. From 0.8.0 records are read
exactly as Bouncy Castle's independent RFC 4998 implementation reads them
(org.bouncycastle.tsp.ers 1.86, cross-checked 2026-10-08), and each record names the
hash-tree convention it was built with. It checks no revocation (it is offline), and it says
nothing about whether the content is true.
Note what code 3 does not mean: the verifier stops at the first failed check, so
SCHEMA_INVALID means it never got as far as the signature. It is not a statement that
the signature was fine.
Two failure modes are deliberately not distinguished. A wrong key and a mutated byte are cryptographically identical from the outside; code 8 is reported only when the signature bundle positively names a different signer, and otherwise the honest answer is code 7. This verifier does not upgrade an ambiguous failure into a more specific accusation.
Machine-readable output
certanix-verify verify evidence-pack.json --key cosign.pub --json
{
"verified": false,
"status": "SIGNATURE_MISMATCH",
"exit_code": 7,
"schema_version": "1.7",
"detail": "recomputed canonical bytes do not match the signed digest",
"verifier_version": "certanix-verify 0.3.0"
}
exit_code always equals the process exit code. verified is carried explicitly so a
consumer never has to know that zero means success.
Two modes, deliberately different
Pack mode (the default) takes the pack JSON and recomputes its canonical form. A pack that has been re-serialised somewhere in your toolchain — different indentation, different key order, same content — still verifies. The signature binds the content, not the transport encoding, and reporting a benign JSON round-trip as tampering would be a false accusation.
Byte-exact mode takes a canonical-bytes file and its detached bundle:
certanix-verify verify evidence-pack.canonical.json --bundle evidence-pack.bundle --key cosign.pub
Here nothing is recomputed, so any byte change fails — including a re-serialisation that pack mode accepts. That is the correct behaviour for an archived artifact whose exact bytes are the claim. The two modes disagreeing on that one input is by design.
Standalone mode takes a signed artifact that is not a pack and names its own type in
artifact_type: a delta report or delta refusal, self-evidence, or an EU Control and
Origin Attestation (attestation):
certanix-verify verify attestation.json --standalone --key cosign.pub
The checks are the same as for a pack (structure, then the signature over the canonical
bytes recomputed from the document as presented), against that type's bundled schema. An
unknown or missing artifact_type is refused as an unsupported schema, never guessed.
Provenance
VENDORED.json records where this verifier came from: the platform source file and its
SHA-256, the platform commit, and the schema version the bundled descriptors were taken
from. The verification logic here is generated verbatim from the platform's single
source — it is a copy, never a reimplementation — and the platform's CI asserts that
this copy and the platform agree on every case in the verification conformance corpus.
Licence
BSD-3-Clause-Clear: see LICENSE. Who holds the copyright, and the assignment to come: see NOTICE. Patents: see PATENTS-NOTICE.md; the licence itself grants no patent rights.
Metadata
Release files for certanix-verify 0.14.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| certanix_verify-0.14.0.tar.gz | 43.4 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| certanix_verify-0.14.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 87.6 kB
Release files / certanix_verify-0.14.0.tar.gz
| Download URL | certanix_verify-0.14.0.tar.gz |
|---|---|
| Size | 43.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
77f8940f35df8a746e6c783456998267c1d3116b8788aeb9d23ac0d1f9a8531b
|
|
BLAKE2b-256 checksum How to use checksums |
e9063886d79f7d0545c7f08c1bc6e0e3bae9c2a181614716b86711bf327e2b9f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency logRelease files / certanix_verify-0.14.0-py3-none-any.whl
| Download URL | certanix_verify-0.14.0-py3-none-any.whl |
|---|---|
| Size | 44.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2aabf5b4a8a8dc5265b5ed85bb46387e2fee48c8419ed5f77288268de4e7797f
|
|
BLAKE2b-256 checksum How to use checksums |
b8baaaef3527afea85fca2c3993fedf62ad886f24ea213070cb24470e8834352
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency logSecurity for certanix-verify 0.14.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for certanix_verify-0.14.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.