This repository contains the tools and instructions for releasing a Thistle OTA update bundle signed with a key protected by a YubiKey.
-
Q: What types of YubiKeys will work?
A: Any device in the YubiKey 5 series should work. Earlier versions of YubiKeys may work, but are not thoroughly tested.
-
Q: Can the private signing key be exported outside of a provisioned YubiKey so I can back it up?
A: No. The private key cannot be exported from the YubiKey hardware.
-
Q: What if a provisioned YubiKey is lost, stolen or broken?
A: Although the private signing key cannot be exported/leaked from the YubiKey, losing a production signing key can be catastrophic. To mitigate the risk, one may provision multiple YubiKeys, and add the associated public keys to
tuc
's configuration JSON file for redundancy. The YubiKeys used in production signing should be kept safe. Specifically, an organization should consider establishing a key update process for fielded devices, and designing and performing regular disaster recover exercises to mitigate the risk of a lost key. Production keys should always be taken seriously! -
Q: We cannot easily update public signing keys in our fielded devices. Is there an alternative?
A: If your devices cannot update the OTA bundle public signing keys easily after they leave the manufacturing facility (e.g., keys are fused in hardware), you may consider managing your signing keys using a Could Key Management System (KMS), for which hardware failures are practically not a concern (you do need to have a Cloud account). Thistle provides an external signing tool trh-k to help you do it with ease.
You need
- MacOS (
x86_64
oraarch64
) or Linux (x86_64
, tested on Ubuntu) - A YubiKey (tested on YubiKey 5)
This step only needs to be done once per release signing key.
-
First, clone this repository, including its submodule.
# use --recursive instead of --recurse-submodules for older Git versions git clone --recurse-submodules https://github.com/thistletech/trh-y.git
Or (if you did not use the
--recursive
option when cloning the repository)git clone https://github.com/thistletech/trh-y.git cd trh-y git submodule update --init --recursive
-
Follow these instructions to install dependencies of yubisign.
-
Generate a new
pivit
key pair in the YubiKey for OTA bundle signingUnder the directory root of
trh-y
$ thistle-bin/keygen-y # Example output Please add the following public key to the public_keys array in TRH configuration: pivit:MIICcjCCAVqgAwIBAgIQAcSbbBRftUAwYtLVH3ewmTANBgkqhkiG9w0BAQsFADAhMR8wHQYDVQQDDBZZdWJpY28gUElWIEF0dGVzdGF0aW9uMCAXDTE2MDMxNDAwMDAwMFoYDzIwNTIwNDE3MDAwMDAwWjAlMSMwIQYDVQQDDBpZdWJpS2V5IFBJViBBdHRlc3RhdGlvbiA5ZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABNlQN+0/nPF4OhrUXESFcjfYKKXIcsRRZQ5N76b9Jz6KcsKZl79LiAjIhk6pAC4hE3D13CrXxC/KQgwxxV+3qINmcIpxIvwvi4QdOwm0PMPyBTdDo1vkDDKi239rfQb/jqNOMEwwEQYKKwYBBAGCxAoDAwQDBQIHMBQGCisGAQQBgsQKAwcEBgIEANsSlzAQBgorBgEEAYLECgMIBAIBAjAPBgorBgEEAYLECgMJBAEBMA0GCSqGSIb3DQEBCwUAA4IBAQCKWWgcGKkSKE5TR7e5EIjs2OPk+vTQFfydPur8iJoR7vxuUvNRtd5mIlRWLxzc4Q/dG10asO5x0ehtYWi998izHqdW0t3zhPSdhdsksYTcFEjkeu3JZpahRH2ZdQUjUYobCzxbqEE3upBjfdiZI8BbkMtn66UMxNlVBISvrQnuFKFH97qkVoK8mWpIuXvlQBI55VHg1vTJgAe/diJtDLcgF+Ug2urPBfMrFp0O/A7pX4fKE5SRfI/vwiujnu35xvRy2oD8xSWSkfDEnD2DxeIrYMworUW8W0Vfpd3+2d+GPcX34BxKjBbMlR6XNgk+OavcCUl9/jpLLgXgFxRkYuDS
Save the public key value for the initialization step later, or one can run
keygen-y
again to print the public key value to the console.
-
Login to the Thistle Web app, and create a new project where your fleet of devices associated with the OTA bundle are managed. In the example below, we name the project "yubikey signing".
-
In the newly created project "yubikey signing", visit the "Settings" section of a project to obtain the API token to be used as
THISTLE_TOKEN
in the initialization step below.
In the project initialization step, we generate a release manifest file
(manifest.json
) and a device configuration file (config.json
) using the
Thistle Release Helper (trh
).
-
The manifest file will be used by a release operator, with the help of
trh
, to describe an OTA bundle for devices in the project. -
The device configuration file will be provisioned into a device where the Thistle Update Client (TUC) runs, and can be used as a boilerplate template for subsequent devices added to the project.
On a Linux or macOS machine where the provisioned YubiKey is inserted, initialize the project as follows.
# Create an empty "release" directory for the tutorial, and change to it
$ mkdir release
$ cd release
# Clone this repository
$ git clone --recursive https://github.com/thistletech/trh-y.git
# Activate Hermit, and run subsequent commands in Hermit environment
$ . trh-y/bin/activate-hermit
# Paste the "Access Token" of the project obtained from Thistle portal, and type
# `enter + ctrl-d`. Use this command to prevent the sensitive $THISTLE_TOKEN
# from being logged in bash history
trh-yπ $ export THISTLE_TOKEN=$(cat)
trh-yπ $ trh init --persist /path/to/device-persist-storage \
--public-key <PUBLIC_KEY>
# Example:
# trh init --persist /tmp/persist \
# --public-key pivit:MIICcjCCAVqgAwIBAgIQAcSbbBRftUAwYtLVH3ewmTANBgkqhkiG9w0BAQsFADAhMR8wHQYDVQQDDBZZdWJpY28gUElWIEF0dGVzdGF0aW9uMCAXDTE2MDMxNDAwMDAwMFoYDzIwNTIwNDE3MDAwMDAwWjAlMSMwIQYDVQQDDBpZdWJpS2V5IFBJViBBdHRlc3RhdGlvbiA5ZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABNlQN+0/nPF4OhrUXESFcjfYKKXIcsRRZQ5N76b9Jz6KcsKZl79LiAjIhk6pAC4hE3D13CrXxC/KQgwxxV+3qINmcIpxIvwvi4QdOwm0PMPyBTdDo1vkDDKi239rfQb/jqNOMEwwEQYKKwYBBAGCxAoDAwQDBQIHMBQGCisGAQQBgsQKAwcEBgIEANsSlzAQBgorBgEEAYLECgMIBAIBAjAPBgorBgEEAYLECgMJBAEBMA0GCSqGSIb3DQEBCwUAA4IBAQCKWWgcGKkSKE5TR7e5EIjs2OPk+vTQFfydPur8iJoR7vxuUvNRtd5mIlRWLxzc4Q/dG10asO5x0ehtYWi998izHqdW0t3zhPSdhdsksYTcFEjkeu3JZpahRH2ZdQUjUYobCzxbqEE3upBjfdiZI8BbkMtn66UMxNlVBISvrQnuFKFH97qkVoK8mWpIuXvlQBI55VHg1vTJgAe/diJtDLcgF+Ug2urPBfMrFp0O/A7pX4fKE5SRfI/vwiujnu35xvRy2oD8xSWSkfDEnD2DxeIrYMworUW8W0Vfpd3+2d+GPcX34BxKjBbMlR6XNgk+OavcCUl9/jpLLgXgFxRkYuDS
...
Manifest generated at: "./manifest.json"
Configuration generated at path "./config.json"
Here, <PUBLIC_KEY>
is the public key value shown in the YubiKey provisioning
step.
-
An example of
config.json
created in this step.{ "name": "mydevice", "persistent_directory": "/tmp/persist", "device_enrollment_token": "redacted_device_enrollment_token", "public_keys": ["pivit:MIICcjCCAVqgAwIBAgIQAcSbbBRftUAwYtLVH3ewmTANBgkqhkiG9w0BAQsFADAhMR8wHQYDVQQDDBZZdWJpY28gUElWIEF0dGVzdGF0aW9uMCAXDTE2MDMxNDAwMDAwMFoYDzIwNTIwNDE3MDAwMDAwWjAlMSMwIQYDVQQDDBpZdWJpS2V5IFBJViBBdHRlc3RhdGlvbiA5ZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABNlQN+0/nPF4OhrUXESFcjfYKKXIcsRRZQ5N76b9Jz6KcsKZl79LiAjIhk6pAC4hE3D13CrXxC/KQgwxxV+3qINmcIpxIvwvi4QdOwm0PMPyBTdDo1vkDDKi239rfQb/jqNOMEwwEQYKKwYBBAGCxAoDAwQDBQIHMBQGCisGAQQBgsQKAwcEBgIEANsSlzAQBgorBgEEAYLECgMIBAIBAjAPBgorBgEEAYLECgMJBAEBMA0GCSqGSIb3DQEBCwUAA4IBAQCKWWgcGKkSKE5TR7e5EIjs2OPk+vTQFfydPur8iJoR7vxuUvNRtd5mIlRWLxzc4Q/dG10asO5x0ehtYWi998izHqdW0t3zhPSdhdsksYTcFEjkeu3JZpahRH2ZdQUjUYobCzxbqEE3upBjfdiZI8BbkMtn66UMxNlVBISvrQnuFKFH97qkVoK8mWpIuXvlQBI55VHg1vTJgAe/diJtDLcgF+Ug2urPBfMrFp0O/A7pX4fKE5SRfI/vwiujnu35xvRy2oD8xSWSkfDEnD2DxeIrYMworUW8W0Vfpd3+2d+GPcX34BxKjBbMlR6XNgk+OavcCUl9/jpLLgXgFxRkYuDS"] }
This configuration file can be used as a template for other devices in the same project. While
persistent_directory
,device_enrollment_token
andpublic_keys
values are shared by all devices in the project, the fieldsname
anddevice_id
can be device unique, and customizable according to a customer's device management setting.Note that Thistle Update Client (TUC) and Thistle's backend by default assume a "self-provisioning (aka self-enrollment)" model: Suppose
device_id
is missing fromconfig.json
, anddevice_enrollment_token
is valid. During the first HTTP request from TUC to the backenddevice_id
is not present. In this case, this request will be viewed as a "device provisioning" request (authorized bydevice_enrollment_token
): a new, uniquedevice_id
will be automatically created on the backend, and returned to the client to persist. Subsequent client-initiated requests will have thisdevice_id
value included for device identification.Thistle's OTA update system can also be customized to support a device key provisioning mechanism with pre-generated, pre-configured
device_name
anddevice_id
, if it's better suited for a customer's device manufacturing needs. -
An example of
manifest.json
created in this step.{ "comment": null, "id": "01GK5TNHH687GMXA18ZSS43JC4", "ts": "1669864277.542302000", "version": 1, "name": "default", "signature": "", "pre_install": null, "post_install": null, "post_reboot": null, "rootfs": null, "files": [], "status": null, "path": null }
As with File Update
and Full System
Update, we use
trh
's subcommands prepare
and release
to prepare, sign and release OTA
bundles, but also supply the --external-sign
argument for YubiKey signing.
Let's use the following file update case as an example.
In the release
directory on a Linux or macOS machine where the provisioned
YubiKey is inserted, run the follow commands. Make sure manifest.json
is
directly under release/
.
# Activate Hermit, and run subsequent commands in Hermit environment
$ . trh-y/bin/activate-hermit
# Paste the "Access Token" of the project obtained from Thistle portal, and type
# enter + ctrl-d
trh-yπ $ export THISTLE_TOKEN=$(cat)
# Prepare a local OTA bundle
trh-yπ $ mkdir -p rel/tmp
trh-yπ $ echo "This is a test" > rel/tmp/test.txt
trh-yπ $ trh --external-sign "trh-y/thistle-bin/sign-y" prepare --target=./rel --file-base-path=/
Read manifest at "./manifest.json"
Processed file "./rel/tmp/test.txt"
Executing external signing command
=====
Signing /var/folders/5k/pbgrbj194573msk705r200sc0000gn/T/tmp.gALetxzM with YubiKey. Touch your YubiKey if it blinks.
=====
Manifest amended successfully
# Release OTA bundle to Thistle backend
trh-yπ $ trh --external-sign "trh-y/thistle-bin/sign-y" release
Read manifest at "./manifest.json"
Executing external signing command
=====
Signing /var/folders/5k/pbgrbj194573msk705r200sc0000gn/T/tmp.SSNRixzn with YubiKey. Touch your YubiKey if it blinks.
=====
Prepared backup release
Uploaded asset test.txt
Executing external signing command
=====
Signing /var/folders/5k/pbgrbj194573msk705r200sc0000gn/T/tmp.Km3MGeJT with YubiKey. Touch your YubiKey if it blinks.
=====
Backup manifest uploaded successfully
Manifest uploaded successfully
Local compressed artifacts removed
To update an existing release, re-run trh prepare
and trh release
commands
with the --external-sign
argument exactly as above, and as described
here,
to get the manifest updated and signed, and OTA bundle uploaded to Thistle
backend.
On a device running tuc
, run the following command to test the released OTA
bundle, using config.json
. This is similar to what is described
here.
trh-yπ $ tuc --log-level=info -c config.json
...
!! setting update status to Pass
.. signature verified with public key #0 type pivit
.. reported data to server
~~ next check in 3600s
-
thistle-bin/keygen-y
Usage: keygen-y -h Display this help message keygen-y [-f] If YubiKey is already provisioned, prints out certificate string. Otherwise, generates a new key pair in the YubiKey. When the -f flag is present, force generates a new key pair.
-
thistle-bin/sign-y
Usage: sign-y -h Display this help message thistle-bin/sign-y SIGNABLE_STRING Signs SIGNABLE_STRING using yubisign