Certificate Signatures: How to Verify an Assurd Certificate Yourself
A certificate is only worth what it costs to forge. If anyone with a text editor could change a grade or a hotspot delta and still show a page that looks like an Assurd certificate, the measurements behind it would not matter. So every certificate minted since KMS signing went live carries a digital signature. Anyone can check it, without an account and without trusting this website.
This post explains what the signature covers and how it is made (SOP-GPU-001 v1.1, Step 5.2). It shows how to verify one yourself, and is just as explicit about what a valid signature does not prove.
What is signed
At mint, the whole certificate document is signed, except the signed_envelope field that will hold the signature. That covers:
- every pillar of the certificate: physical triage, silicon identity, thermals, performance and the grade;
- the
report_id, theasset_idand the time of issue,generated_at; - the SOP version the card was tested under;
- the legacy fingerprint,
certificate_signature.
Change any single byte of any of these and the signature no longer verifies.
Canonical JSON
A signature covers bytes, but a certificate is a JSON value, and one value can be written as many different byte strings. The backend is written in Python. Python's default JSON writer produces
{"score": 72.0, "grade": "GOLD", "asset_id": "a1"}
A JavaScript program holding the same value would write 72, not 72.0, and might order the keys differently. Same certificate, different bytes, different hash: a signature over one would fail to verify against the other.
The fix is to agree on exactly one byte string per value. Assurd uses the JSON Canonicalization Scheme (JCS, RFC 8785):
- object keys are sorted;
- there is no insignificant whitespace;
- numbers are written in their shortest round-trip form;
- strings use a minimal, fixed escaping.
The value above becomes
{"asset_id":"a1","grade":"GOLD","score":72}
There is one more rule, and it matters more than the scheme: verify the bytes, never re-serialise them. The signed bytes are stored base64-encoded in the envelope. A verifier decodes them and checks the signature over exactly those bytes. The certificate you read is then the parse of those verified bytes. Nothing is ever re-encoded on the way, so no JSON library anywhere can break or bend the check.
The ES256 signature
The signing key is an ECDSA key on the P-256 curve, used with SHA-256: the algorithm called ES256. It lives in Google Cloud KMS, in key ring assurd-signing, key certificate-signing. The private key never leaves KMS: no technician, lab or Assurd service can read it. KMS will only sign a digest it is given.
With the certificate, the private key and the signature:
An ECDSA signature is a pair of 256-bit integers . Assurd stores it in the fixed P1363 form: and as 32 bytes each, concatenated, 64 bytes in all.
Before anything is stored, the backend verifies the new signature against the KMS public key. If KMS is unavailable or the check fails, no certificate is minted. An unsigned certificate is never issued as a fallback.
The signed envelope
| Field | Content |
|---|---|
payload_b64 | Base64 of the exact RFC 8785 bytes that were signed |
signature_b64 | Base64 of the 64-byte P1363 signature |
alg / sig_format | ES256 / P1363 |
kms_key_version | Full KMS resource name of the key version that signed |
signed_at | Equals the certificate's generated_at |
legacy_signature | Copy of certificate_signature |
The certificate stored in the public registry is exactly the decoded payload plus this envelope.
Keys and rotation
The public half of each signing key is published as a JSON Web Key set at
https://api.assurd.in/api/registry/keys
Each key's kid is the full KMS key-version name, so the envelope's kms_key_version says exactly which key to use. The production key versions are also pinned in the website's build, so the certificate page has its own copy and does not depend on the live registry.
Keys are rotated by creating a new key version. Old versions are never disabled or destroyed, because every certificate they signed must stay verifiable for its whole life.
Verification in the browser
Every certificate page runs the check itself, in your browser:
- Select the key whose
kidequals the envelope'skms_key_version: the pinned copy first, then the live registry. - Base64-decode the payload and the signature.
- Verify the ES256 signature over the decoded payload bytes, exactly as decoded.
- Parse the payload and check that its
report_idis the certificate you asked for. A valid signature on a different certificate is not a pass.
The page then shows one of four results:
- Signature verified. The certificate shown is the verified payload. Any field shown elsewhere that differs from it is not certified.
- Signature invalid. The certificate body is hidden.
- Signing key not recognised. Neither the pinned keys nor the registry hold the named key version.
- Legacy · not KMS-signed. The certificate was minted before KMS signing and has no envelope (see The fingerprint is not a signature).
Verify it offline
A check that runs on our website still asks you to trust our website's code. The certificate page's Verify offline dialog removes that. It downloads two files:
- the envelope, as
assurd-<report-id>-envelope.json; - the public key that verified it, as
assurd-signing-key.pem.
It then gives you a command to run in the download folder (bash, jq, xxd, OpenSSL 3). The command is shown here with one step per line:
E='assurd-<report-id>-envelope.json' K='assurd-signing-key.pem';
jq -r .payload_b64 "$E" | base64 -d > payload.json &&
S=$(jq -r .signature_b64 "$E" | base64 -d | xxd -p -c64) &&
printf 'asn1=SEQUENCE:s\n[s]\nr=INTEGER:0x%s\ns=INTEGER:0x%s\n' "${S:0:64}" "${S:64:64}" > sig.cnf &&
openssl asn1parse -genconf sig.cnf -out sig.der -noout &&
openssl dgst -sha256 -verify "$K" -signature sig.der payload.json
Line by line, it does the following:
- It writes the signed bytes to
payload.json, unchanged. That file is the certificate as signed: open it and read it. - It reads the 64-byte signature as hex, then takes the first 32 bytes as and the last 32 as .
- It rebuilds the signature in the DER form OpenSSL expects. DER encodes
SEQUENCE { INTEGER r, INTEGER s }, and an integer whose top bit is set gets a leading zero byte. So a 64-byte P1363 signature becomes 70, 71 or 72 bytes of DER, depending on whether neither, one or both of and need that byte. It is shorter only in the rare case that or starts with zero bytes. - It verifies. OpenSSL prints
Verified OKfor an intact certificate, andVerification failureif a single byte of the payload or signature differs.
Checking the key itself
The offline check proves that payload.json was signed by the key in assurd-signing-key.pem. That the PEM is Assurd's key is a separate question, because the PEM also came from this website. To answer it independently, compare the PEM with the published key set:
openssl ec -pubin -in assurd-signing-key.pem -text -noout
This prints the public point as 04 followed by the 32-byte and 32-byte coordinates, in hex. The registry entry with the same kid gives the same and , base64url-encoded. If they match, the certificate was signed by the key Assurd publishes for that version.
The fingerprint is not a signature
Every certificate also carries certificate_signature. Despite its name, it is a content fingerprint, not a signature:
with the four fields joined by | and the digest written in uppercase hex. It uses no key, so anyone can compute it, including a forger. You can reproduce the sample certificate's fingerprint yourself from four of its own fields:
printf '%s' 'sample|sample-asset|0de3b0a12d41a594b860ef22709934d14f99da1e09e1ee1f4d15e53fe5518312|2026-11-01T06:30:00+00:00' | sha256sum
It prints
b174712ebbc06779d3ffb0ecce5c506c33295c44c1a52187502cfcf6b2bd096c
which is the sample's SHA256:B174712E… in uppercase.
Being able to recompute it is exactly why it carries no authority. Certificates minted before KMS signing have only this fingerprint and no envelope. Their pages say Legacy · not KMS-signed, never "verified".
JCS turns the Python output with key order score, grade, asset_id and the value 72.0 into the bytes with sorted keys and 72. The sample certificate's fingerprint input hashes to B174712E…096C, uppercase.
What a signature proves, and what it does not
A verified signature proves two things:
- Integrity. The certificate you are reading is byte-for-byte the one that was signed. Not one field, digit or flag has changed since mint.
- Origin. It was signed by the Assurd KMS key version it names, which no lab or person can extract.
It does not prove:
- That the measurements are right. The signature fixes what the lab recorded and the Rules Engine computed. It does not re-measure the card. The quality of the measurements rests on the procedure, which the other posts in this series explain.
- That the card in front of you is the certified card. The certificate names an asset and records its identifiers. Matching a physical card to those identifiers is a separate check.
- That the certificate is still current. A re-certification creates a new certificate for the same asset and marks the earlier one expired. The old certificate's signature still verifies, because it is still the record that was issued.
See it on a certificate
A real certificate shows its signature status at the top, with the Verify offline dialog beside it. The sample certificate has nothing to verify: it is labelled Sample · not signed, because its data is illustrative and no key ever signed it.
Notation
| Symbol | Meaning |
|---|---|
The certificate, without its signed_envelope | |
| Its RFC 8785 canonical bytes (the payload) | |
| SHA-256 digest of the payload | |
| The KMS private key (never leaves KMS) | |
| ECDSA P-256 signature; stored as 64-byte | |
| , , , | report_id, asset_id, VBIOS ROM SHA-256, generated_at (fingerprint inputs) |