Tarsana docsDashboard

Verify a signature yourself

Every image Tarsana builds comes with a signed statement, an attestation, that names the image, its software bill of materials (SBOM) and its package set by their SHA-256 digests. In this quickstart you check that signature and those digests with openssl, jq and sha256sum, without trusting Tarsana's own check.

Before you start, finish Build your first image. You need TENANT, ACCESS_TOKEN and the SUBMISSION of a build that has succeeded. You also need OpenSSL 3.0 or later (openssl version).

1. Get the attestation

attestation returns the signed statement and the public key in one answer:

curl "https://api.tarsana.io/v1/tenants/$TENANT/submissions/$SUBMISSION/attestation" \
  -H "X-Tarsana-Access-Token: $ACCESS_TOKEN" \
  -H "X-Tarsana-Tenant: $TENANT"

Save the answer as attestation.json, for example by adding > attestation.json to the command. It has this shape:

{
  "attestation": {
    "schema": "tarsana.submission-attestation/v1",
    "key": {
      "keyid": "52f4abfb...",
      "public_key": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----\n",
      "registered": true,
      "claim": "A sentence that says what a signature by this key covers."
    },
    "signature": {
      "algorithm": "ed25519",
      "format": "in-toto-dsse",
      "keyid": "52f4abfb...",
      "envelope": {
        "payloadType": "application/vnd.in-toto+json",
        "payload": "eyJfdHlwZSI6...",
        "signatures": [
          {
            "algorithm": "ed25519",
            "keyid": "52f4abfb...",
            "sig": "MEUCIQ..."
          }
        ]
      }
    },
    "subject": {
      "image": {
        "digest": "sha256:0f1e2d3c...",
        "attested": true
      },
      "release": {
        "plain_sha256": "9487214a...",
        "plain_size": 2147483648,
        "version": "20261005-030112"
      },
      "sbom": {
        "digest": "sha256:fb917071...",
        "format": "CycloneDX"
      }
    },
    "verification": {
      "verdict": "verified",
      "checks": [
        {
          "id": "signature_verifies",
          "result": "pass",
          "detail": "A sentence about this check."
        }
      ]
    },
    "verify_it_yourself": {
      "why": "A sentence.",
      "steps": []
    }
  }
}

attestation.verification is Tarsana's own check. The steps below repeat it with your own tools, so you do not have to trust it.

2. Check the key

Write the public key to a file:

jq -r '.attestation.key.public_key' attestation.json > tarsana-signing.pub

A key ID is the SHA-256 of the public key in DER form. Compute it yourself and compare it with the key ID the signature names:

openssl pkey -pubin -in tarsana-signing.pub -outform DER | sha256sum
jq -r '.attestation.signature.envelope.signatures[0].keyid' attestation.json

The two must be the same. A key you got from the same answer as the signature only proves that the answer is consistent. Keep a copy of the key, compare it with the copy you get next time, and treat a new key ID as something to look into. attestation.key.claim says what a signature by this key covers.

3. Check the signature

The signature is a DSSE signature: it covers the statement together with its type. Rebuild the exact bytes that were signed, then check them with the key:

jq -r '.attestation.signature.envelope.payload' attestation.json | base64 -d > statement.json
jq -r '.attestation.signature.envelope.signatures[0].sig' attestation.json | base64 -d > signature.bin
TYPE=$(jq -r '.attestation.signature.envelope.payloadType' attestation.json)
printf 'DSSEv1 %d %s %d ' "${#TYPE}" "$TYPE" "$(wc -c < statement.json)" > signed.bin
cat statement.json >> signed.bin
openssl pkeyutl -verify -pubin -inkey tarsana-signing.pub -rawin -in signed.bin -sigfile signature.bin

If it worked, you see:

Signature Verified Successfully

If a single byte of the statement or the signature was changed, you see Signature Verification Failure instead.

4. Check what the statement covers

The statement lists its subjects: the disk image (node.raw), the SBOM and the package set, each with its SHA-256:

jq -r '.subject[] | "\(.digest.sha256)  \(.name)"' statement.json

To check the image itself, download it, decompress it and compare. fetch with artifact set to image gives you a download link that expires:

curl "https://api.tarsana.io/v1/tenants/$TENANT/submissions/$SUBMISSION/image" \
  -H "X-Tarsana-Access-Token: $ACCESS_TOKEN" \
  -H "X-Tarsana-Tenant: $TENANT"
curl -fL -o node.raw.zst "$(jq -r '.content.image.url' image.json)"
zstd -d node.raw.zst -o node.raw
sha256sum node.raw

Here image.json is the saved answer of the fetch call. The digest of node.raw must match the node.raw subject of the statement. The signature covers the decompressed image, so compare node.raw, not the .zst file you downloaded.

What a pass means

A pass means: this statement was signed by the key you checked, and it names exactly these digests. It does not tell you that the software in the image is free of faults, or that you should trust the key. Read the SBOM with fetch (artifact set to sbom) to see what is in the image.

Next steps

View this page as Markdown