TT Lab
Get started
Learn Learning paths Courses

Air-Gapped Sites — Defence and Government

Signature OK, Binding FAILED — When an Attestation Points Elsewhere

Continue in TT Lab

Goal

Without the internet, check whether the three kinds of attestation (CycloneDX SBOM · in-toto Statement · SLSA Provenance) that arrived with an artifact in an air-gapped network really point to that artifact, and leave an import review report that separates what you confirmed from what you could not confirm.

Why it matters

In an import review, "signature verification passed" is a much narrower fact than it sounds. All that command confirmed is that the attestation document has not changed since it was signed, and the artifact does not even appear in the command. What connects an attestation to an artifact is the subject's digest. So in a situation where whoever can edit the document can also re-attach a signature, the one line that recomputes the fingerprint and compares it becomes the procedure's only defense. This lab has you build what happens when that one line is missing. You create an attestation whose signature stays valid while only the subject points to an old version that was already approved, and record that the signature layer and the binding layer say different things. The report in the last step is not about filling in a format. If you do not separate what you confirmed from what there is no way to confirm on this network, the reader takes everything as confirmed, and that misunderstanding follows it all the way to the approval signature.

Steps

  1. Run the generation script as is to create the artifact under /root/supply/drop/, five attestation files under /root/supply/evidence/, and two signatures.
  2. Recompute the artifact fingerprint, compare it against the Statement's subject, and write it in /root/supply/check/subject.txt.
  3. Compare the SBOM's component list against the actual tree in both directions and write it in /root/supply/check/sbom-diff.json.
  4. Extract the facts from the Provenance and separate the six questions in /root/supply/QUESTIONS.txt in /root/supply/check/provenance.json.
  5. Verify the attestation signatures with openssl dgst -sha256 -verify and write it in /root/supply/check/signature.txt.
  6. In /root/supply/swap/statement-swapped.json, create an attestation whose signature is valid but whose subject points to an old version, and write the result in /root/supply/check/swap.txt as two layers.
  7. Look at the predicates of the three attestations and separate accept · hold · reject in /root/supply/check/predicates.json.
  8. In /root/supply/check/attestation-report.json, write an attestation check report for the import review.

Notes

Reproduce the artifact and attestation bundle inside the network

Run the generation script as is to create /root/supply/drop/collector-2.4.1.tar.gz, the seven files of /root/supply/drop/payload/, /root/supply/archive/collector-2.4.0.tar.gz, and under /root/supply/evidence/, sbom.cdx.json · statement.json · qa-signoff.json · vendor-release.pub and two signatures. /root/supply/QUESTIONS.txt and /root/supply/REPORT-KEYS.txt are created along with them.

The script fixes the seed and the bundle time. That way the same fingerprint comes out no matter how many times you run it, and the comparison in the next steps does not differ from person to person. Do not modify the script; use it as is. If the signing key already exists, it is not created again.

Recompute the received file's fingerprint and match it against the subject

In /root/supply/check/subject.txt, write the artifact fingerprint, the name and fingerprint of the Statement's subject, whether the two fingerprints match, _type, and predicateType.

The subject is an array. Each element has a name and a digest, and the specification stipulates that the digest must be present. The name is only a distinguishing label, so matching it confirms nothing. Write whether they match as one word, YES or NO.

Compare the SBOM with the actual tree in both directions

In /root/supply/check/sbom-diff.json, write the number of SBOM components, the number of files in the tree, the number of exact matches, the list of those only in the document, the list of those only in the tree, and the list of those whose fingerprints differ.

There are three kinds of mismatch. Those only in the document, those only in the tree, and those whose name is on both sides but whose fingerprint differs. A comparison that only matches up the lists misses the third entirely. Write paths as paths relative to the payload, and count only components whose type is file.

Separate the questions Provenance can answer from those it cannot

In /root/supply/check/provenance.json, write buildType, builder.id, invocationId, the start and end times, the list of keys in externalParameters, and the number of resolvedDependencies, and separate the six questions in /root/supply/QUESTIONS.txt into answerable and not-answerable.

There is one criterion — does the predicate actually have a place to write that fact? The specification states separately that externalParameters must be verified downstream, and that internalParameters need not be verified because the platform is trusted. Do not mix the two in the key list.

Check what the signature covers and what it does not

Verify the signatures of the two documents with openssl dgst -sha256 -verify, and in /root/supply/check/signature.txt write the public key fingerprint, the two verification results, the number of signed documents and the number of unsigned documents, and whether the signature covers the artifact itself.

Look at which files you gave the verification command as input. The artifact does not appear in that command. Compute the public key fingerprint after converting to DER, not PEM. PEM has headers and line breaks mixed in, so the same key can get a different hash from file to file.

Create an attestation whose signature is valid but whose subject points to something else

In /root/supply/swap/statement-swapped.json, create an attestation that leaves the predicate as it is and changes only the subject to point to /root/supply/archive/collector-2.4.0.tar.gz, sign it again with the same key, and write the result in /root/supply/check/swap.txt as two layers.

Do not touch the original attestation; make a separate copy. Only if you re-sign after editing the document does the counterexample "even though the signature is valid" hold. If you change the predicate too, it gets mixed up with the "unknown predicate" problem of the next step and the point is blurred. Do not copy the fingerprint from the Statement; recompute it from that file to check.

Put unknown predicates in a place that is neither pass nor reject

In /root/supply/check/predicates.json, write the list of predicates we know how to read, and for the three attestations (evidence/statement.json · evidence/qa-signoff.json · swap/statement-swapped.json), the predicate, whether it is a known predicate, whether the subject matches, and the verdict.

The verdict follows from two values. A predicate you cannot read cannot be a basis for passing, but neither is it in itself a basis for rejecting. The parsing rules of in-toto recommend writing the policy not as "block if there is bad evidence" but as "pass only if there is good evidence." The verdict values are only three: accept · hold · reject.

Write the attestation check report to attach to the import review

In /root/supply/check/attestation-report.json, write the artifact and its fingerprint, the fingerprints of the six files under /root/supply/evidence/, what you confirmed and what you could not confirm, the three kinds of mismatch counted in step 3, the attestation put on hold in step 7, and the verdict.

In verified and not_verified, divide the eight words in /root/supply/REPORT-KEYS.txt without omitting any and without overlap. There is one criterion — did I compute or verify it myself inside this Pod? A claim someone else wrote in a document is not confirmation. The meanings of the three verdict values are also written in the same file.