Skip to content

FAQ: FPGA AES Encryption Key (eFuse BBRAM)

bunnie edited this page Jul 20, 2021 · 18 revisions

Can the FPGA encryption key foil an attacker who has unlimited physical access to my device?

No. The Starbleed exploit means any attacker that can access the JTAG pins (available via the debug cable) can effectively decrypt and re-encrypt arbitrary bitstreams. Therefore, the security of a Precursor device is precisely equal to the difficulty of accessing the JTAG pins. This is why the device ships with a potting epoxy to seal the JTAG pins.

Once potted, users can raise the stakes against physical read-out by also using the battery-backed key. Attempts to mill through the epoxy would have to do so without removing power to the device, and without nicking the battery-backed key power supply trace, which zig-zags behind regions of interest to attackers.

Can the FPGA encryption key be accessed by a remote attacker?

No, but with caveats. The system is not configured such that the Starbleed exploit can be effectively pulled off by a remote attacker.

However, by default, a copy of the encryption key is stored in the design to allow for user-authorized updates. This key is protected with a password of your choosing. A remote attacker who can acquire your password and the encrypted key can then effectively access the FPGA encryption key, and all the secrets within the OS.

For ultimate protection against remote read-out, users can delete the stored copy, but this means that remote updates are no longer possible (you would have to use the Starbleed exploit to update your FPGA assuming access to JTAG pins).

What is the FPGA encryption key?

The Spartan-7 FPGA chip has a vendor-specific, dedicated hardware encryption scheme that was designed to protect the confidentiality and integrity of the FPGA bitstream. It is a 256-bit AES key that comes in two varieties:

eFuse

  • Permanently burned into the FPGA
  • Can only be burned once
  • Will survive through power-loss
  • System can be configured to exclusively boot from the eFuse key
  • If the above bit is not set, it will opportunistically use the eFuse key only if the bitstream indicates its use
  • Nominally can be read back to check programming, but with an additional fuse can be locked against "normal" read-out

BBRAM

  • Stored in battery-backed RAM
  • Can be re-burned as many times as desired
  • Will be cleared on power loss
  • Cannot be booted from in exclusion of all other keys

On Precursor, as long as the battery is charged and connected, the FPGA will retain the key (even when the system is put into suspend). Thus the key can be cleared by:

  1. "Ship mode" which electrically disconnects the battery from the system for long-term storage and shipment
  2. "self destruct" which actively powers down the key backup voltage and zero-izes the key.
  3. external JTAG commands which instruct such a clearing

Is FPGA Encryption Useless Due to the Starbleed Exploit?

If you subscribe to "defense in depth", FPGA encryption creates another barrier among many to improve the confidentiality of your device. Developers would not turn it on because it can be a bit of a hassle, but it would generally be recommended for end users.

However, if your primary threat model is someone physically confiscating your device and actively attacking it with your full knowledge, then no, the FPGA encryption won't help you. To be honest, in that threat scenario, few technologies, if any, can hold up; it's just the degree of effort and time.

What are the Defaults?

By default, all images are encrypted with a "null key" (all 0's), and configured to fetch the null key from the eFuse array. This offers no protection, but it does mean that there is only one code path to test, and you are always using it whether or not you choose to customize your key.

How Do I Configure the eFuse Key?

There are two primary methods to do this.

  1. Self-provisioning. In this method, the key is generated inside the Precursor using its internal TRNG, and recorded within the bitstream for future updates. The eFuse can then be burned by the FPGA without help of external tools. In this flow, the key is never disclosed outside the Precursor device. Note: work in progress as of July 2021; status: all primitives available, but awaiting integration into a single user-friendly command.

  2. External provisioning. In this method, the key is written to a .nky file, and the bitstream is encrypted to match the key in this file. The .nky file is also used to drive an external script to burn the fuses on the FPGA. This method necessarily requires a trusted environment for storing and provisioning the secret material. The rest of this FAQ addresses how to do this.

What is the .nky format?

.nky is the format that Xilinx uses for key management. For Precursor, it's a text file with this format:

Device xc7s50;
Key 0 39cc176c205da0faa48c2c043f053d8aef8a593044bfe3f197fd81f619b7a962;
Key StartCBC 00000000000000000000000000000000;
Key HMAC 0000000000000000000000000000000000000000000000000000000000000000;

Place your secret key as a hex string after the "Key 0" token on the second line.

The StartCBC and HMAC are AES IV and SHA HMAC values. You can fill them in, but in the Precursor flow, they are always replaced with fresh, random values by intermediating commands. They are not fused into the device, and are instead embedded in the bitstream.

How Do I Externally Provision My Device?

External provisioning is a slightly complicated process, and it's only recommended for flows that target the BBRAM (eFuse flows should use self-provisioning, once that is finalized). Therefore, the commands presented here will be for a BBRAM-targeted build.

Make Your Key

In these examples, we assume you have prepared your AES key in a file called my_key.nky. We don't have a convenience script to generate these files automatically. Typically, what I do is open the key file in a text editor, and then in another window I open a Python REPL and use the following commands to generate the key:

Python 3.8.10 (default, Jun  2 2021, 10:49:15)
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import secrets
>>> secrets.token_hex(32)
'39cc176c205da0faa48c2c043f053d8aef8a593044bfe3f197fd81f619b7a962'
>>>

Copy and paste the hex string into my_key.nky.

Use the Key (With Build Environment)

The "simplest way" to embed your key into the SoC is to invoke the FPGA build script with the following arguments:

python ./betrusted_soc.py -e my_key.nky -b

The -e mmy_key.nky instructs the script to use your key to encrypt the final bitstream, and -b instructs the flow to configure the bitstream headers to look for its key in the BBRAM. Dropping -b will result in a bitstream that looks for the key in the eFuse.

"Simplest way" is put in quotes because this requires you to set up the Litex+Vivado toolchain, which consumes many gigabytes of data and requires many privacy-invasive steps.

Once you have finished this step, you can skip to Provision Your Device

Use the Key (without build environment)

The "hard way" is more privacy-friendly, and requires only some local scripts to be run, but involves multiple, manual commands. However, the process has not been automated because I, the developer, almost always build the FPGA from source, and thus I have no personal use for this tool flow. If you prefer this flow, maybe you can turn it into an "easy" script; it's all open source.

Assume that:

  • you have cloned, recursively, the betrusted-soc repo
  • you have prepared my_key.nky, and placed it in the betrusted-soc root.
  • that you are running a recent enough Python 3 (3.5 iirc is the minimum requirement; some packages may be missing depending on your distro)
  • and that you are currently in the root directory of the betrusted-soc repo.

Let's also assume that you're grabbing a pre-built FPGA image, such as those available from the CI server.

Pre-populate the build artifacts

  1. Download and copy encrypted.bin to build/gateware/encrypted.bin (make the directory path if it does not already exist)
  2. Download and copy csr.csv to build/csr.csv
  3. Download and copy rom.db to ./ (the root directory of your betrusted-soc repo)
  • encrypted.bin is the gateware, encrypted to a dummy null key
  • csr.csv is the CSR map in plaintext, necessary for USB updaters to operate
  • rom.db is the map of SLICE primitives in the FPGA to patch to inject your new KEYROM

Populate the Key data

The following commands takes your fuse key, and merges it with various other keys needed for secure boot, into an image that is the KEYROM accessible by Xous. You could skip this step, but then you couldn't do self-updates of the FPGA image, as the BBRAM or eFuse key would not exist in an area that the OS can read.

python ./gen_keyrom.py --efuse-key my_key.nky --dev-pubkey devkey/dev-x509.crt --output keystore.bin
python ./deps/rom-locate/key2bits.py -k keystore.bin -r rom.db > keystore.patch

Re-Encrypt to Your Own Key

The following commands takes the patch file you prepared in the previous command, and combines it with my_key.nky, and re-encrypts the FPGA bitstream to your new key.

python ./deps/encrypt-bitstream-python/encrypt-bitstream.py --bbram -f build/gateware/betrusted_soc.bin -i dummy.nky -k my_key.nky -o my_encrypted -p keystore.patch
  • The --bbram argument should be dropped if you are targeting eFuse
  • betrusted_soc.bin is the output of Vivado, encrypted with a null key
  • dummy.nky is the null key (input key)
  • my_key.nky is your new key
  • my_encrypted is the name of the output encrypted bitstream (a .bin suffix is automatically added)
  • keystore.patch is the patch file from the previous step that allows us to merge in the KEYROM data

The following command takes your encrypted image and appends the plaintext CSR locations, so that a USB updater knows where to poke to do updates (as the exact register locations for peripherals can shift build-to-build):

python ./append_csr.py -b ./my_encrypted.bin -c ./build/csr.csv -o ./soc_csr.bin

Provision to Your Device

The next steps would be run on a Raspberry Pi, which is used to drive the JTAG pins of the Precursor device directly. You need something to drive the JTAG pins, because BBRAM fusing can only be done through JTAG; you can't do it through USB or via the internal JTAG loop-back alone (because BBRAM fusing forces the device into a reset state, while ironically, eFUse fusing does not). We've prepared the debug HAT for the Raspberry Pi and its matching debug cable to make it easy for you, but you could just as well do this with a Xilinx proprietary platform cable. Of course, the commands would be different, and we don't support that flow.

To set up the Raspberry Pi, please refer to the README in betrusted-scripts.

The output file of the final append_csr.py script above, soc_csr.bin, has been given a name that is default-compatible with the provision-xous.sh script from the betrusted-scripts repo. Copy this file to the precursors directory on your Raspberry Pi, and then run provision-xous.sh -l -k (the -l -k arguments suppress burning of the loader and the kernel, correspondingly). This will burn the BBRAM-fuse encrypted image. Upon completion of the burn, the Precursor device should not boot, as it does not yet have the BBRAM key.

Provision the BBRAM Key

The final step is to provision the BBRAM key.

  • copy my_key.nky to your provisioning device (e.g. the Raspberry Pi), placing it in the betrusted_scripts/jtag-tools directory.
  • Inside the jtag-tools directory, run ./bbram_gen.py -k my_key.nky. This will create a file my_key.jtg which is a JTAG GPIO command script that does the BBRAM programming.
  • Run ./jtag_gpio.py -f my_key.jtg. This will execute the JTAG programming script and load the BBRAM key into the Precursor device. You can add the -d command to confirm things are working; note that the bbkey_rbk fields are shifted to the left by 5 bits due to padding on the JTAG readback chain.
  • Change back to the betrusted_scripts repo directory and run reset-soc.sh. This should trigger a reload of the encrypted bitstream, using the now-loaded BBRAM keys. The FPGA should boot at this point.

If that's a lot to take in, it is. Eventually this process will be streamlined but right now, it's a set of disjoint tools that need to be integrated into a final user-friendly tool for burning.