TT Lab
Get started
Learn Learning paths Courses

Air-Gapped Sites — Defence and Government

Build a Transfer Bundle and Find Where Signature Verification Breaks

Continue in TT Lab

Goal

Build an import bundle for the air-gapped network from scratch, sign it, verify it as the receiving side, and confirm directly, with two kinds of tampering, where verification breaks.

Why it matters

This Pod has no gpg and you cannot install it. Real air-gapped networks are usually like this too — software not on the authorized list needs its own import review first. So you sign and verify with openssl, which is available everywhere. And integrity is not one layer but two. The file hash protects "is this the file the list talked about," and the signature protects "was that list made by someone we know." If you look at only one layer, one of them is certain to get through, and you do not really feel that until you build both cases yourself.

Steps

  1. With python3, create six files under /root/bundle/payload/. Two of them are binaries.
  2. In /root/bundle/stage/MANIFEST.sha256, record the SHA-256 of each file using relative paths.
  3. In /root/bundle/keys/, create an RSA 2048 key pair, set the private key permission to 600, and leave the public key fingerprint in release.pub.sha256.
  4. Create /root/bundle/stage/MANIFEST.sig with openssl dgst -sha256 -sign.
  5. Package it as /root/bundle/out/intake-2609-01.tar.gz and compute the .sha256 of the same name. Do not include the key files.
  6. Unpack it in /root/bundle/recv/, verify in the order bundle hash → signature → file hashes, and write the result in receipt.txt.
  7. Apply two kinds of tampering in /root/bundle/tamper-a/ and /root/bundle/tamper-b/, and write the result in /root/bundle/tamper.txt. For A, change only one byte of payload/deps/libpq.so.5.bin; for B, in addition, also rewrite that line of the manifest.
  8. In /root/bundle/intake.md, write a seven-section import approval request.

Notes

Create the tree to import

With python3, create six files under /root/bundle/payload/. Two of them are binaries.

Create six files with python3. Two of them are binaries, so you cannot check them by eye and can check them only by fingerprint. Do not modify the generation script; use it as is.

Compute the per-file hash manifest

In /root/bundle/stage/MANIFEST.sha256, record the SHA-256 of each file using relative paths.

Compute it with relative paths from inside the payload directory. The -printf '%P\n' option of find strips the leading ./ for you. The receiving side unpacks into a different directory, so absolute paths are useless on the spot.

Create the signing key pair and record the fingerprint

In /root/bundle/keys/, create an RSA 2048 key pair, set the private key permission to 600, and leave the public key fingerprint in release.pub.sha256.

Create a 2048-bit private key with openssl genrsa and set its permission to 600. Extract the public key with -pubout, and for the fingerprint convert it to DER and then run sha256sum.

Sign the manifest

Create /root/bundle/stage/MANIFEST.sig with openssl dgst -sha256 -sign.

The signing target is not the whole payload but the single manifest. Be sure to add -out to openssl dgst -sha256 -sign — if you redirect the screen output, the binary gets corrupted.

Package it as a bundle (do not include the keys)

Package it as /root/bundle/out/intake-2609-01.tar.gz and compute the .sha256 of the same name. Do not include the key files.

Bundle the payload, manifest, and signature into one. Do not include the public key — if it arrives by the same route as the bundle, whoever holds that route can swap in all four. After packaging, check with your own eyes what went in using tar -tzf.

Follow the receiving side's procedure in order

Unpack it in /root/bundle/recv/, verify in the order bundle hash → signature → file hashes, and write the result in receipt.txt.

The order is the key. Whole-bundle hash → signature → per-file hashes. If you look at the file hashes first, you end up comparing against a list whose authenticity has not yet been confirmed.

Confirm where verification breaks, in two cases

Apply two kinds of tampering in /root/bundle/tamper-a/ and /root/bundle/tamper-b/, and write the result in /root/bundle/tamper.txt. For A, change only one byte of payload/deps/libpq.so.5.bin; for B, in addition, also rewrite that line of the manifest.

Make two copies. In A, change just one byte of the binary; in B, make the same change and also rewrite that line of the manifest with the new hash. Run signature verification and file hash comparison on both.

Write the import approval request

In /root/bundle/intake.md, write a seven-section import approval request.

You need seven sections. The reviewer has to be able to redo the same verification without our screen, so write the values and commands, not "it passed."