Air-Gapped Sites — Defence and Government
Build a Transfer Bundle and Find Where Signature Verification Breaks
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
- With
python3, create six files under/root/bundle/payload/. Two of them are binaries. - In
/root/bundle/stage/MANIFEST.sha256, record the SHA-256 of each file using relative paths. - In
/root/bundle/keys/, create an RSA 2048 key pair, set the private key permission to 600, and leave the public key fingerprint inrelease.pub.sha256. - Create
/root/bundle/stage/MANIFEST.sigwithopenssl dgst -sha256 -sign. - Package it as
/root/bundle/out/intake-2609-01.tar.gzand compute the.sha256of the same name. Do not include the key files. - Unpack it in
/root/bundle/recv/, verify in the order bundle hash → signature → file hashes, and write the result inreceipt.txt. - 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 ofpayload/deps/libpq.so.5.bin; for B, in addition, also rewrite that line of the manifest. - In
/root/bundle/intake.md, write a seven-section import approval request.
Notes
- Do not open binaries with
headorgrep. NUL bytes are mixed in and you cannot trust the result. Use onlyfile,sha256sum, andopenssl. sha256sum -clooks for the paths written in the manifest relative to the current directory. Run it inside the payload directory.- To change just one byte,
printf '\xff' | dd of=파일 bs=1 seek=1024 count=1 conv=notruncis convenient (the placeholder is the file path). - Common mistake 1: leaving out
-outfromopenssl dgst -signand redirecting with>. The binary gets corrupted. - Common mistake 2: repackaging the bundle and not recomputing the
.sha256. Keep packaging and hash computation together in one script.
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."