The ArmoredWitness device is built to help improve the security properties of ecosystems which rely on transparency logs.
It does this by:
- Observing public transparency logs
Verifying that they're operating in an append-only fashion, and counter-signing those checkpoints which it has determined are consistent with all previous checkpoints its seen from the same log. - Making these counter-signed checkpoints available
Sending them to a distributor, which then collates counter-signatures for a given checkpoint from one or more ArmoredWitness devices, and serves them via a public API.
The benefit of this system comes though removing trust from log operators to behave honestly, and placing some of that trust in the witnesses. Splitting the trust across multiple parties in this way means that a larger number of parties must collude to hide malfeasance, and as other witness implementations/networks start to appear, the number of parties required to collude increases correspondingly.
However, we can minimise the amount of trust required to be placed in the ArmoredWitness by having it be as transparent as possible too.
In order to minimise the level of trust required to be placed in the ArmoredWitness, we have designed & implemented it such that:
- All firmware is opensource, written in TamaGo, and is build-reproducible by anyone.
- All firmware is logged to a Firmware Transparency log at build and release time.
- The
provision
tool will only use firmware artefacts discovered in the FT log in order to program devices. - The on-device self-update process requires that updated firmware is hosted in the FT log.
- The boot "chain of trust" requires valid "offline FT proof bundles" to be present
alongside the firmware at boot time:
- The bootloader verifies signatures and FT proofs for the secure monitor ("OS"), and only launches it if they succeed.
- The secure monitor ("OS") verifies signatures and FT proofs for the witness applet, and only launches it if they succeed.
- The
verify
tool can be used by custodians to inspect the device, extract the firmware components from it, and verify that they are present in the FT log. - The
verify_build
command continuously monitors the contents of the FT log, and tests that every logged firmware is indeed reproducibly built.
Every piece of non-ROM firmware for the ArmoredWitness is automatically added to our FirmwareTransparency logs during the build & release process (e.g. applet GCB config ). In conjunction with the controls in the firmware self-update, provision, and user verification tooling mentioned above, this helps to ensure that we, or anybode else, would find it very difficult to covertly target all, or a subset of the devices, with malicious firmware.
Rather than writing compiled blobs directly into the log, we store a manifest, which commits to all necessary inputs to the build, and the corresponding firmware output.
Here is a real manifest from the CI log:
{
"schema_version": 0,
"component": "TRUSTED_APPLET",
"git": {
"tag_name": "0.3.1709910063-incompatible",
"commit_fingerprint": "9651fc25839d9937acc041057cf3906f26fc1ae5"
},
"build": {
"tamago_version": "1.22.0",
"envs": [
"FT_LOG_URL=https://api.transparency.dev/armored-witness-firmware/ci/log/2",
"FT_BIN_URL=https://api.transparency.dev/armored-witness-firmware/ci/artefacts/2",
"LOG_ORIGIN=transparency.dev/armored-witness/firmware_transparency/ci/2",
"LOG_PUBLIC_KEY=transparency.dev-aw-ftlog-ci-2+f77c6276+AZXqiaARpwF4MoNOxx46kuiIRjrML0PDTm+c7BLaAMt6",
"APPLET_PUBLIC_KEY=transparency.dev-aw-applet-ci+3ff32e2c+AV1fgxtByjXuPjPfi0/7qTbEBlPGGCyxqr6ZlppoLOz3",
"OS_PUBLIC_KEY1=transparency.dev-aw-os1-ci+7a0eaef3+AcsqvmrcKIbs21H2Bm2fWb6oFWn/9MmLGNc6NLJty2eQ",
"OS_PUBLIC_KEY2=transparency.dev-aw-os2-ci+af8e4114+AbBJk5MgxRB+68KhGojhUdSt1ts5GAdRIT1Eq9zEkgQh",
"REST_DISTRIBUTOR_BASE_URL=https://api.transparency.dev/ci",
"BEE=1",
"DEBUG=1",
"SRK_HASH=b8ba457320663bf006accd3c57e06720e63b21ce5351cb91b4650690bb08d85a"
]
},
"output": {
"firmware_digest_sha256": "lLPLT5TO2+Ln71cByKhVvNFyAL47IzOOSGoXNKVSCvU="
}
}
— transparency.dev-aw-applet-ci P/MuLOfW8473+PNMa58SZA2/rw1aEaIaLTw/aNfdawSiyFEcDjGksYqCTFMnHHGAhhbfnITkkktL1We6UF3VMuHakwU=
The full description of this structure is avilable in the source
here,
but, broadly, the component
and git
fields tell us that the build is for the APPLET
firmware
type, and was done:
- at
github.com/transparency-dev/armored-witness-applet@9651fc25839d9937acc041057cf3906f26fc1ae5
- using TamaGo
v1.22.0
- with the set of environment variables in
env
declared.
and that the resulting firmware binary has the SHA256 hash in the firmware_digest_sha256
field.
Since we're staking our reputation on this claim being true, the line starting "— transparency.dev-aw-applet-ci P/Mu..." is a signature from our CI build robot committing to it.
The actual binary itself is stored in a content addressable store (CAS) served adjacent to the log itself (in fact,
the URL to the root of that CAS is present in the env
section under the FT_BIN_URL
variable), keyed by the hex
encoded firmware hash (rather than the base64 encoding the JSON stores).
If you wish, you could download that firmware image and verify its hash with the following commands:
$ SHA256HEX=$(echo -n "lLPLT5TO2+Ln71cByKhVvNFyAL47IzOOSGoXNKVSCvU=" | base64 -d | hexdump -v -e '/1 "%02x" ')
$ echo ${SHA256HEX}
94b3cb4f94cedbe2e7ef5701c8a855bcd17200be3b23338e486a1734a5520af5
$ wget https://api.transparency.dev/armored-witness-firmware/ci/artefacts/2/${SHA256HEX}
--2024-03-08 15:53:22-- https://api.transparency.dev/armored-witness-firmware/ci/artefacts/2/94b3cb4f94cedbe2e7ef5701c8a855bcd17200be3b23338e486a1734a5520af5
Resolving api.transparency.dev (api.transparency.dev)... 2600:1901:0:499e::, 34.36.253.177
Connecting to api.transparency.dev (api.transparency.dev)|2600:1901:0:499e::|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 16961249 (16M) [application/octet-stream]
Saving to: ‘94b3cb4f94cedbe2e7ef5701c8a855bcd17200be3b23338e486a1734a5520af5.1’
94b3cb4f94cedbe2e7ef5701c8a855bcd1 100%[==============================================================>] 16.17M 70.1MB/s in 0.2s
2024-03-08 15:53:22 (70.1 MB/s) - ‘94b3cb4f94cedbe2e7ef5701c8a855bcd17200be3b23338e486a1734a5520af5’ saved [16961249/16961249]
$ sha256sum 94b3cb4f94cedbe2e7ef5701c8a855bcd17200be3b23338e486a1734a5520af5
94b3cb4f94cedbe2e7ef5701c8a855bcd17200be3b23338e486a1734a5520af5 94b3cb4f94cedbe2e7ef5701c8a855bcd17200be3b23338e486a1734a5520af5
With the information above, you can check out the applet repo,
at the particular commit, install the correct version of TamaGo
, set up the environment variables, run make imx
and expect it
to create a trusted_applet.imx
file which should have the hash as above.
We've built a tool to do this for you though, it's in this repo and is called verify_build
.
Instructions on running it are in that directory, but in summary it will "tail" the FT log, and for each entry it finds
there it'll attempt the above and report on whether it was able to successfully reproduce the build and get a matching
hash from the output file.
Even with many people running the verify_build
tool above, it's no use if the device installation & update
process allows firmware which is not in the log to be installed on the device.
The provision
tool, which is what we use to first turn blank devices into locked-down
ArmoredWitnesses, has no capability to fetch firmware (including the
recovery
firmware used as part of the installation process)
from anywhere other than the FT log. Before attempting to install any of the firmware, it also checks that
the firmware hash matches the expected value in the manifest.
The device has a
self-update
component, which, similarly to the provision
tool, is built to only use the FT log as the source for updates.
If a newer APPLET
or OS
component than is currently installed is found (note that the updater cannot update
the bootloader
), then the updater builds a
ProofBundle
before submitting it to the OS
via RPC to be installed.
The ProofBundle
contains:
- An FT log
Checkpoint
which commits to the state of the log in which the update was found. - The
Manifest
for the firmware build found. - The
Index
of theManifest
in the log. - The
InclusionProof
of theManifest
under theCheckpoint
. - The
Firmware
binary.
Before flashing the updated firmware onto the device, the OS checks whether the proof bundle is valid:
- Verify the signature on the
Checkpoint
is correct and from the expected log signer, and that theCheckpoint
format is correct. - Verify that the
InclusionProof
for theManifest
atIndex
does reproduce the root hash theCheckpoint
commits to. - Verify that the SHA256 of the
FirmwareImage
matches thefirmware_digest_sha256
value in theManifest
.
If any of these checks fail, the update is rejected.
Both the provision
and self-update
component store the ProofBundle
data on the MMC at the same time as the
firmware is written.
The bootloader
verifiers the ProofBundle
for the OS
before launching it. Similarly, the OS
verifies the
ProofBundle
for the Applet
before launching that.
This, coupled with:
- The self-update being unable to upgrade the bootloader,
- The requirement to manually flip a DIP switch on the bottom of the device to boot the
recovery
image,
means that only firmware present in the FT log can boot on the device without the custodian being aware that something untoward has happened.
To prevent roll-back attacks, the firmware uses the
Replay-Protected Memory Block
(RPMB) on the
eMMC storage to store the highest version of firmware which has successfully booted. It checks its own version
against that on each boot, and will halt if the currently running version is older than the one stored in the RPMB.
Of course, we could have a SeKrEt EvIl HaX0r
version of the provision
tool that we didn't opensource,
in which all these checks are defeated, and which will install corresponding SeKrEt EvIl
firmware versions
that will accept non-FT updates too.
To make sure we'd be caught if we did that, we've provided the verify
tool.
This tool uses the recovery
firmware from the FT log to expose
the device's MMC storage as a USB Mass Storage device, and then extracts each of the 3 types of firmware
and their Manifest
s stored on there, before verifying the firmware hashes against the ones in the Manifest
s
and finally checking with the FT log that all 3 Manifest
s are present there.
TODO(al): redo this