Skip to content

Verify a Release

Verifying a release happens at two layers, and you should do both before you trust a deployment:

  1. The release artifacts — the checksums, manifests, attestation policies, and measurements published with a GitHub release. Verify them with the repo’s scripts: SHA-256 checksums plus cosign (Sigstore) signatures that pin who built them.
  2. A running instance — a live KMS or merod node. Verify its attestation with the public web verifier: it appraises a quote through Intel Trust Authority and compares its measurements against the release policy.

The artifact scripts prove the release you downloaded is authentic and untampered; the web verifier proves a specific live instance is running that release. See Verification for the meaning of each check.

The combined entry point takes a version and verifies both the KMS and the node-image asset sets for it:

Terminal window
scripts/release/verify-release-assets.sh <version>

<version> may be a bare X.Y.Z or a full tag; the script normalizes it and derives the two release tags it needs:

Terminal window
# All of these verify the 2.3.51 KMS and node-image asset sets:
scripts/release/verify-release-assets.sh 2.3.51
scripts/release/verify-release-assets.sh mero-kms-v2.3.51
scripts/release/verify-release-assets.sh mero-tee-v2.3.51

Internally it calls the two per-artifact verifiers, which you can also run directly against a single asset set:

Terminal window
scripts/release/verify-kms-release-assets.sh mero-kms-v<version>
scripts/release/verify-node-image-gcp-release-assets.sh mero-tee-v<version>
  • Downloads the signed assets for the tag — checksums file, release manifest, attestation policies (including the per-profile kms-attestation-policy.<profile>.json files), and SBOMs.
  • Verifies SHA-256 checksums — runs sha256sum -c over the release’s checksums file, and cross-checks it against the release manifest so the two must agree.
  • Checks the KMS policy — every kms-attestation-policy[.<profile>].json must carry well-formed, non-empty kms_allowed_mrtd / kms_allowed_rtmr0..3 lists, the measurements merod pins the KMS image by.
  • Verifies cosign signatures — for every signed asset it runs cosign verify-blob against the asset’s signature, pinning the signer identity and OIDC issuer:
    • identity (default): the release workflow, e.g. ^https://github.com/calimero-network/mero-tee/.github/workflows/release-kms.yaml@refs/heads/master$ (the node-image script pins release-node-image-gcp.yaml).
    • OIDC issuer (default): https://token.actions.githubusercontent.com.

A non-zero exit means the release is incomplete or tampered — do not deploy it.

The KMS verifier needs jq, cosign, sha256sum, awk, basename, curl, and git on PATH. gh (GitHub CLI) is optional — the script uses it when present and falls back to curl otherwise. For higher API rate limits or private repos, export GH_TOKEN (or GITHUB_TOKEN).

Layer 2 — verify a live instance with the web verifier

Section titled “Layer 2 — verify a live instance with the web verifier”

The public attestation verifier is a small web app backed by a stateless /api/verify function. It has two routes:

Route Verifies Reachability
/kms a mero-kms replica, from a pasted /attest response none: KMS replicas listen only inside their VPC, so you call /attest from there and paste the result
/mero-tee a merod node, by URL node must serve POST /admin-api/tee/attest

On /mero-tee you enter the node URL and, optionally, a release tag; the backend generates a fresh 32-byte nonce and fetches the attestation itself. On /kms you paste the KMS’s /attest response and, optionally, the nonce you sent, a release tag and a profile. Either way the backend appraises the quote through Intel Trust Authority, verifies the returned JWT against Intel’s JWKS, and the page compares MRTD and RTMR0–3 against the published release policy (kms_allowed_* of kms-attestation-policy.<profile>.json for a KMS, published-mrtds.json for a node). The mechanics of each check are covered in Verification.

Both routes read their inputs from query parameters, so you can share a pre-filled verification link. On /kms the release and profile are pre-filled (the attestation itself is always pasted):

/kms?release_tag=mero-kms-v2.3.75&profile=locked-read-only
  • release_tag — release to compare against, e.g. mero-kms-v2.3.75 (optional).
  • profile — debug, debug-read-only, or locked-read-only (optional).

A node deep link on /mero-tee:

/mero-tee?node_url=http://34.65.123.45:80&release_tag=mero-tee-v2.3.51

node_url (alias nodeUrl) and release_tag (alias releaseTag) are both accepted. When a URL parameter is present the page verifies automatically on load; otherwise fill the form and submit.

This is how a KMS is verified. From a machine inside the KMS’s VPC, call /attest with a nonce you chose:

Terminal window
NONCE=$(openssl rand -base64 32)
curl -s -X POST http://<kms-address>:8080/attest \
-H 'content-type: application/json' \
-d "{\"nonceB64\":\"$NONCE\"}"

Then paste the response into /kms, or send it to POST /api/verify as an attestation object, with nonce_b64 so the freshness check runs:

Terminal window
curl -s -X POST https://<verifier-host>/api/verify \
-H 'content-type: application/json' \
-d '{
"attestation": {
"quoteB64": "<base64 raw TDX quote>"
},
"nonce_b64": "<the nonce you sent to /attest>"
}'

The function extracts the quote (top-level quoteB64 / quote_b64 for a KMS attestation, or data.quoteB64 for a merod node response), sends it to Intel Trust Authority, and returns the appraisal — ita_token, ita_token_verified, and ita_claims. With nonce_b64 the report_data inside the quote must start with that nonce; without it the verifier cannot vouch that the quote is fresh. The authenticity and measurement checks run either way.