Offline FoundriesFactory TUF Keys

FoundriesFactory uses TUF’s multi-level key management strategy to secure software updates. Part of this strategy utilizes roles to separate software update responsibilities. By restricting each role’s responsibilities or actions it’s trusted to perform, the impact of a compromised role’s key is minimized.

Even so, if a key were compromised, TUF provides a mechanism for reliably revoking keys: the root role. The root role exists to delegate trust to all other top-level roles used in the system. TUF allows rotation of the root key in case it gets compromised. Since key rotation is crucial to Factory security, it’s important that the root key be highly secure as well.

To increase a root key’s security further, it is encouraged that the Factory owner rotates it. Rotation will convert the root role’s online-key, generated during the bootstrap of a Factory, to an offline key.

Rotation

Key rotation updates the keys that a FoundriesFactory fleet will trust. Every new Factory should have its keys rotated for offline storage. Key rotation is also necessary in the event of a key compromise. fioctl includes commands for managing TUF keys.

Establishing a root key

The TUF root key is the most important key in TUF. The owner of this key can sign the TUF root.json that defines what keys and roles devices can trust. When a Factory is created, Foundries.io will create the first root key automatically.

A new root key can be defined by doing a “key rotation”. A key rotation sets the new root key for the factory, but it also signs the new root.json with the previous root key to demonstrate proper ownership. The first time one does this it will download the relevant content from a remote server into a file specified. This only needs to be done once to gather the root keys offline. It will gather the keys offline and make a singular, first, rotation at the same time.

This can be done in fioctl with:

fioctl keys rotate-root --initial /absolute/path/to/root.keys.tgz

Note

At this point, the contents of the tarball are as described below.

At this point the only copy of the Factory’s root private key is in this file. This file cannot be lost or it will be impossible to make future key updates to the Factory.

Any further root key rotations can be done with the following command:

fioctl keys rotate-root /absolute/path/to/root.keys.tgz

Note

At this point, the tarball should be backed up as described below.

Establishing an offline target key

TUF has the notion of a targets.json file which specifies what updates(Targets) are available to a device. This file must be signed with a target signing key and pushed to Foundries.io. Normal CI builds sign the targets with a Foundries.io owned target signing key trusted by the Factory.

In order to ensure customer control of updates, production devices require their targets.json file to be signed by two parties:

  • The Foundries.io online target signing key
  • The customer’s offline target signing key

A Factory can create its target signing key in fioctl with:

fioctl keys rotate-targets /absolute/path/to/root.keys.tgz

This will send 2 versions of root.json to Foundries.io. Both versions will be identical except the one for production devices will specify a targets signing threshold of 2 rather than 1.

The Factory’s TUF metadata can be viewed with:

# The normal "CI" root:
fioctl get https://api.foundries.io/ota/repo/<FACTORY>/api/v1/user_repo/root.json

# The production root. Note the target key role has:
#   "threshold" : 2
fioctl get https://api.foundries.io/ota/repo/<FACTORY>/api/v1/user_repo/root.json?production=1

Given the importance of the offline credentials file, it is recommended to create a second file that can sign production targets for Waves but lacks the root keys required to alter Factory root metadata:

fioctl keys copy-targets /absolute/path/to/root.keys.tgz /path/to/target.only.key.tgz

How to backup offline keys

There are 3 recommend types of backups:

  • The actual tarball - Basically cp <tarball> <path to backup storage media>
  • A plain text file of the Factory’s active root private key
  • A print out of the Factory’s active root private key

2-3 copies of these backups should be placed in safes in different geographical locations. Finding the root private key requires understanding the offline keys file format. The initial contents of the offline key file, /absolute/path/to/root.keys.tgz, will look like:

# Most of the files aren't critical. They are used in the Factory's initial
# CI run to setup credentials. They are kept around to help with debug.
tufrepo
`-- keys
    |-- first-root.pub     # Public root shown in root.json
    |-- first-root.sec     # The Factory's first root private
    |-- fioctl-root-<keyid>.sec  # Your offline key(s)
    `-- fioctl-root-<keyid>.pub

The critical file to keep from this tarball is first-root.sec. After the first root key rotation the offline keys will include 2 new files similar to:

tufrepo
`-- keys
    |-- fioctl-root-5d7397a7a9d62d4f89a39b77903831af12172abb8b9f483e7ad9638bacbc93b1.pub
    `-- fioctl-root-5d7397a7a9d62d4f89a39b77903831af12172abb8b9f483e7ad9638bacbc93b1.sec

The new root private key is named with the pattern fioctl-root-<keyid>.sec. The key ID can be verified with:

$ fioctl get https://api.foundries.io/ota/repo/<FACTORY>/api/v1/user_repo/root.json \
  | jq '.signed.roles["root"]["keyids"][0]'
"5d7397a7a9d62d4f89a39b77903831af12172abb8b9f483e7ad9638bacbc93b1"

Every root key rotation will generate a new .sec file and must be backed up.

It is recommended to back up the Factory offline target signing key. However, losing this file isn’t catastrophic - it’s just inconvenient. After doing a target key rotation the offline keys file will have two new files like:

tufrepo
`-- keys
    |-- fioctl-targets-cb58f6b83e1e16276c64b19aef7fb07afe3227818f8511ac3ceb288965afdb65.pub
    `-- fioctl-targets-cb58f6b83e1e16276c64b19aef7fb07afe3227818f8511ac3ceb288965afdb65.sec

The new target signing key is named similar to the root key as: fioctl-targets-<keyid>.sec. The key ID can be verified with:

$ fioctl get https://api.foundries.io/ota/repo/<FACTORY>/api/v1/user_repo/root.json \
  | jq '.signed.roles["targets"]["keyids"][1]'
"cb58f6b83e1e16276c64b19aef7fb07afe3227818f8511ac3ceb288965afdb65"