Verifying your downloads
bits binaries run on your hosts with privileged visibility, and bitcollector produces the
evidence you and your auditor rely on. Evidence is only ever as trustworthy as the
instrument that produced it.
So before you install anything, you should be able to prove exactly which bytes you are about to run. This page shows you how, using published tools only, offline.
Do not install the binary. Do not "try the download again and hope". Keep the files, and email [email protected] with the release version and the failing command's output.
What ships with every releaseβ
| File | What it is |
|---|---|
bitcollector-<os>-<arch> | The binary itself |
bitcollector-<os>-<arch>.sbom.json | CycloneDX SBOM β everything inside that binary |
bitcollector-<os>-<arch>.sig | Cosign signature bundle over those exact bytes |
bitcollector-<os>-<arch>.att | Cosign attestation binding the SBOM to those exact bytes |
SHA256SUMS | Checksums of every file above |
SHA256SUMS.sig | Cosign signature over SHA256SUMS |
release-provenance.json | Commit, Go toolchain and platforms the release was built from |
bitscanner, bitenforcer and bitmapper releases have the same layout, with their own name
in place of bitcollector. All four products are signed with the same release key, so one
key check covers the whole family β and a key that changed between two of them would be a
reason to stop, not to update your copy.
Step 0 β get the toolsβ
You need cosign v3 or newer, and
sha256sum (part of coreutils; on macOS, shasum -a 256 or brew install coreutils).
cosign version
Step 1 β get the public key, and check it is the right keyβ
Download the release signing key beside the release directory, not into it:
# from the directory that CONTAINS your release directory
# DNS route β works from a CLI and inside CI. The HTTPS copy of this key is
# currently behind a bot challenge, so `curl` cannot fetch it; see the note below.
dig +short TXT _cosign-key.cert-ix.com | tr -d '"' | sed 's/.*key=//' \
| base64 -d | openssl pkey -pubin -inform DER -out cosign.pub
verify-release.sh treats every file inside a release directory as something that
should be signed. Dropping cosign.pub in there makes it report an unaccounted-for
file β which reads exactly like a tampered release, but is not one. Keep the key one
level up. For the same reason, pass it by absolute path: the script changes into
the release directory, so a relative --key cosign.pub stops resolving and the
failure it prints is indistinguishable from a bad signature.
β cosign.pub
An attacker who can replace a binary on our website can replace the public key sitting next to it. Verification would then pass β against their key. You would run their binary and get a green tick.
A signature check is only worth the trust anchor behind it. Confirm the key fingerprint from a second, independent source before you trust it. That is the whole point of the next few lines, and it is the step most verification instructions leave out.
The fingerprintβ
Compute the fingerprint of the key you downloaded:
# The robust form: SHA-256 of the DER-encoded public key.
# Independent of PEM whitespace, so copy-paste through a browser cannot change it.
openssl pkey -pubin -in cosign.pub -outform DER | sha256sum
It must be exactly:
552839f6b1b1eb0934557e1886326c7270584fa602fbf56f84754a98be3de822
If you would rather hash the file as-is:
sha256sum cosign.pub
# 150eacccc1bc26a83c746a1e441b0b0bdc8104c3ef07efd75c62deb5b069f914
That second value covers the exact bytes of the published file, newline included, so it only matches if you downloaded the file rather than pasting its contents.
The key itself is short enough to read. This is the whole of it:
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPA6gi66NIBXbSwYS3CL16Ien3sho
wCNOR8dRivdd07SQWto107QMDelUKMeebJQHSlOchNO4PJSWjCcrsUYb5w==
-----END PUBLIC KEY-----
It is an ECDSA P-256 (prime256v1) public key. You can confirm that yourself:
openssl pkey -pubin -in cosign.pub -text -noout | head -2
# Public-Key: (256 bit)
Second sources for the fingerprintβ
Do not take the fingerprint from the same page that served you the key, if you can avoid it. Cross-check it against at least one of:
- A DNS record, served by different infrastructure from this website. The same
fingerprint is published as a TXT record at
_cosign.cert-ix.com, and one command checks it:dig +short TXT _cosign.cert-ix.com. This is the cross-check worth doing, because an attacker who has taken over this web server would have to take over our DNS as well to make the two agree β different systems, different credentials. If they disagree, stop: do not run the binaries, and write to [email protected]. - Your Cert-IX contact, out of band. Ask your account or security contact to read the fingerprint back to you over a channel that is not this website β a phone call, a signed email from [email protected], or your existing support channel.
- A key you already have. If you verified a previous bits release, compare against the key you kept. A release key that changes without an announcement is a reason to stop and ask, not a reason to update your copy.
Once you have confirmed the fingerprint, keep your copy of the key and reuse it. The value of a trust anchor comes from it not changing.
Step 2 β verify the checksum manifest firstβ
Order matters. Verify the signature over SHA256SUMS before you trust any checksum
inside it β an unsigned checksum file proves nothing, because whoever can replace a binary
can replace a plain checksum list just as easily.
cosign verify-blob --key cosign.pub --bundle SHA256SUMS.sig \
--insecure-ignore-tlog=true SHA256SUMS
Expected output:
WARNING: Skipping tlog verification is an insecure practice that lacks transparency and
auditability verification for the blob.
Verified OK
Cert-IX is a sovereign EU platform and operates no public Sigstore transparency log.
Signatures are key-based. Without --insecure-ignore-tlog=true, cosign v3 looks for a
transparency-log entry that intentionally does not exist and reports "no signatures found".
The flag does not weaken the signature check itself. What it does mean is that you get no independent, append-only record that this signature ever existed β so the fingerprint cross-check in Step 1 is doing work that a transparency log would otherwise do for you. That is the trade, stated plainly.
Step 3 β verify every file matches its checksumβ
sha256sum -c SHA256SUMS
Step 4 β verify the binary you are about to installβ
cosign verify-blob --key cosign.pub --bundle bitcollector-linux-amd64.sig \
--insecure-ignore-tlog=true bitcollector-linux-amd64
Step 5 β verify the SBOM really describes these bytesβ
cosign verify-blob-attestation --key cosign.pub --bundle bitcollector-linux-amd64.att \
--type cyclonedx --insecure-ignore-tlog=true bitcollector-linux-amd64
The SBOM is how you know what is inside the collector that will produce your evidence. A binary whose attestation does not match its bytes is a binary whose contents you cannot enumerate.
The whole thing in one commandβ
Cert-IX ships the same verification script it runs against its own releases before
publishing, at ci/verify-release.sh in the repository:
./verify-release.sh "$PWD/bitcollector-release" "$PWD/cosign.pub"
[verify-release] verifying the signature over SHA256SUMS
[verify-release] SHA256SUMS signature OK
[verify-release] verifying checksums of every artefact
[verify-release] all checksums OK
[verify-release] bitcollector-darwin-amd64 signature OK
[verify-release] bitcollector-darwin-amd64 SBOM attestation OK
[verify-release] bitcollector-darwin-arm64 signature OK
[verify-release] bitcollector-darwin-arm64 SBOM attestation OK
[verify-release] bitcollector-linux-amd64 signature OK
[verify-release] bitcollector-linux-amd64 SBOM attestation OK
[verify-release] bitcollector-linux-arm64 signature OK
[verify-release] bitcollector-linux-arm64 SBOM attestation OK
[verify-release] bitcollector-windows-amd64.exe signature OK
[verify-release] bitcollector-windows-amd64.exe SBOM attestation OK
[verify-release] VERIFIED: 5 binary/binaries, checksums, and signatures all valid
[verify-release] built from commit 8819759 with go1.25.12
OK: 5 verified
Exit code 0 and a line reading OK: <n> verified means every binary is authentic and
intact. Anything else means do not install.
The script changes into the release directory before it starts work, so a relative path to the public key stops resolving and every signature check fails with:
[verify-release] REFUSED: the signature over SHA256SUMS is NOT valid for this key.
That refusal reads exactly like a tampered release. If you see it, re-run with absolute
paths ("$PWD/cosign.pub") before you conclude anything β and if it still fails with an
absolute path, then treat it as real and contact
[email protected].
Two details worth noticing, because they are the difference between a check and a ritual:
- It reports a count. A verification that finds nothing to check must not report success, so the script refuses when the directory holds no binaries, and it prints how many it actually verified.
- It refuses on a missing signature. An unsigned binary sitting in a signed release is exactly what a supply-chain attack looks like, so the script fails rather than skipping it.
What the signature proves todayβ
Be precise about this, because "signed" is often heard as more than it is.
What it proves: these exact bytes were produced by the Cert-IX release pipeline and
signed by the holder of the key whose fingerprint you checked in Step 1, and they have not
changed since. Combined with SHA256SUMS and the SBOM attestation, you can enumerate what
is inside the binary and be sure the enumeration matches the bytes.
What it does not prove, stated honestly:
- The signing key currently lives on the build host, protected as a file with a passphrase held outside version control. It is not in a hardware security module. So the signature proves "this is the binary that build produced" β it does not prove that the build host itself was uncompromised at signing time.
- There is no transparency log. You cannot consult an independent append-only record to see whether a signature for some other binary was ever issued under this key.
Both of those are why the reproducible build below matters, and why the fingerprint cross-check in Step 1 is not optional.
Rebuilding the binary yourselfβ
You do not have to take our word for what is in the binary β you can rebuild it and compare. bits releases are reproducible: the same commit, built with the same Go toolchain, produces byte-identical output.
Start from release-provenance.json, which records exactly what the release was built from:
{
"service": "bitcollector",
"version": "0.1.0-ga",
"commit": "8819759",
"go_toolchain": "go1.25.12",
"source_date_epoch": 1786277293,
"build_time": "2026-08-09T12:08:13Z",
"platforms": ["linux/amd64", "linux/arm64", "darwin/amd64", "darwin/arm64", "windows/amd64"],
"reproducible": true,
"signed": true
}
With the source and the toolchain named there:
git checkout <commit from release-provenance.json>
SOURCE_DATE_EPOCH=<source_date_epoch from release-provenance.json>
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath \
-ldflags="-s -w -buildid= \
-X main.version=<version> \
-X main.buildTime=$(date -u -d @$SOURCE_DATE_EPOCH +%Y-%m-%dT%H:%M:%SZ) \
-X main.gitCommit=<commit>" \
-o bitcollector-linux-amd64 ./cmd/bitcollector
sha256sum bitcollector-linux-amd64 # must equal the value in SHA256SUMS
A matching hash means the published binary contains nothing that is not in the source you just read.
Current releasesβ
All four bits are published, and all four are signed with the same key β the one whose fingerprint you checked in Step 1. One key check covers the family.
| Product | Version | Commit | Toolchain | Platforms |
|---|---|---|---|---|
bitcollector | 0.1.0-ga | 8819759 | go1.25.12 | linux amd64/arm64, macOS amd64/arm64, Windows amd64 |
bitscanner | 0.1.3-ga | 7db3d4c | go1.25.12 | linux amd64/arm64 |
bitenforcer | 0.1.0-ga | 4c6ce93 | go1.25.12 | linux amd64/arm64 |
bitmapper | 0.1.0-ga | 64ebc64 | go1.25.12 | linux amd64 |
The platform columns are not all the same, and the differences are deliberate. Where a platform is missing it is because the agent would not do its job there, not because the build was forgotten. Each product page gives the reason:
| Product | What is withheld, and why |
|---|---|
bitcollector | Nothing. It ships on all five β platforms |
bitscanner | No macOS/Windows: its neighbour and route readers are Linux implementations, and an empty neighbour table would read as a quiet segment β platforms |
bitenforcer | No macOS/Windows: its whole surface is iptables/sysctl/systemd/SELinux, so a build there would report every module NOT EXAMINED β platforms |
bitmapper | Linux amd64 only: capture needs a compiled-in libpcap, so it cannot be cross-compiled. Windows builds but has never been run there, so it is withheld as a testing gap β platforms |
The commits above are what the current releases were built from. release-provenance.json
inside your release directory is the authoritative record for that directory β check it
against the table rather than assuming, and the binary itself prints the same values from the
bytes you are about to run. If the three disagree, stop and write to
[email protected].
The command is not the same on all four binaries. Use the one for the binary you have:
| Binary | Command that prints its version |
|---|---|
bitcollector | bitcollector --version |
bitscanner | bitscanner version |
bitenforcer | bitenforcer version |
bitmapper | bitmapper version |
Please use exactly these spellings, because the wrong one does not simply print an error:
- On
bitcollectorup to and including0.1.0-ga,bitcollector versionis not a version command. The argument is ignored and the binary starts the agent β it creates its identity key under the data directory and begins collecting. If you ran it, stop the process and delete the data directory (/var/lib/bitcollectorunless you changed it) before enrolling the host for real.bitcollector --versionprints and exits, and always has. - On
bitscanner,bitenforcerandbitmapper,--versionis not a flag they have; the command fails withunknown flag: --version. That one is harmless, just unhelpful.
The binaries and their signing key are on the downloads page. The steps above work offline against whatever release directory you have, so you can verify on a machine that never touches our website.
Next stepsβ
- bitcollector β what it collects, and how its evidence chain is verified (a different key, and a separate check).
- bitenforcer β what v1 does and does not do.
- bits overview β start here if you arrived at this page first.
Was this page helpful?