This repository contains the tools and instructions for releasing a Thistle OTA update bundle signed with a key managed in a Cloud KMS (key management system). Currently, GCP KMS is supported.
You need
-
MacOS (
x86_64
oraarch64
) or Linux (x86_64
, tested on Ubuntu) -
A Google Cloud project with Cloud Key Management Service (KMS) API enabled, and a principal (user account) with the following IAM roles assigned
- roles/cloudkms.signerVerifier
- This role is a role needed for
sign
, and for querying an existing public key in KMS withkeygen
- It can be assigned to a principal responsible for signing an OTA bundle
- This role is a role needed for
- roles/cloudkms.admin
- This is a role only needed for
keygen
for the first-time key generation. It alone does not allow for signing of an OTA bundle - It can be assigned to a principal responsible for administration of Cloud KMS operations in your Cloud account
- This is a role only needed for
- roles/cloudkms.signerVerifier
This step only needs to be done once per release signing key.
-
First, clone this repository, and activate Hermit
$ git clone https://github.com/thistletech/trh-k.git $ . trh-k/bin/activate-hermit trh-kπ $
-
Create a JSON configuration file, say,
gcp_config.json
(cf. sample_gcp_config.json), containing the following fields that describe the to-be-generated signing key.{ "project": "GCP project name, e.g., my-awsome-gcp-project", "location": "GCP region, e.g., us-west1", "keyring": "Name of keyring, e.g., dummy-keyring-for-trh-k", "keyname": "Name of signing key, e.g., dummy-key-for-trh-k" }
-
Generate a new ECDSA key pair in KMS for OTA bundle signing, as described in
gcp_config.json
. All commands below are executed under an active Hermit environment.# Login to GCP project $ gcloud auth login $ gcloud config set project YOUR_PROJECT_ID # Keygen $ trh-k/thistle-bin/keygen-gcp-k -c gcp_config.json # Example output Please add the following public key to the public_keys array in TRH configuration: ecdsa:BK9k+finvyLNVoqj5p07EvMh9OiOQ2DIK7w4uii1UVNc7yWB4lCI/Wk3I/PRHthBbPKSog3dis5ALzMdsHqSIC4=
Save the public key value for the initialization step later, or one can run
keygen-gcp-k
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 "kms signing".
-
In the newly created project "kms 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, initialize the project as follows.
# Create an empty "release" directory for the tutorial, and change to it
$ mkdir release
$ cd release
# Clone this repo
$ git clone https://github.com/thistletech/trh-k.git
# Activate Hermit, and run subsequent commands in Hermit environment
$ . trh-k/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 shell history
trh-kπ $ export THISTLE_TOKEN=$(cat)
trh-kπ $ trh init --persist /path/to/device-persist-storage \
--public-key <PUBLIC_KEY>
# Example:
# trh init --persist /tmp/persist \
# --public-key "ecdsa:BK9k+finvyLNVoqj5p07EvMh9OiOQ2DIK7w4uii1UVNc7yWB4lCI/Wk3I/PRHthBbPKSog3dis5ALzMdsHqSIC4="
...
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_token", "public_keys": ["ecdsa:BK9k+finvyLNVoqj5p07EvMh9OiOQ2DIK7w4uii1UVNc7yWB4lCI/Wk3I/PRHthBbPKSog3dis5ALzMdsHqSIC4="] }
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": "01GKMZAJ5X229EHVR1WK4T43FX", "ts": "1670372477.117195000", "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 KMS signing.
Let's use the following file update case as an example.
In the release
directory on a Linux or macOS machine, run the follow commands.
Make sure manifest.json
and gcp_config.json
are directly under release/
.
# Activate Hermit, and run subsequent commands in Hermit environment
$ . trh-k/bin/activate-hermit
# Paste the "Access Token" of the project obtained from Thistle portal, and type
# enter + ctrl-d
trh-kπ $ export THISTLE_TOKEN=$(cat)
# Prepare a local OTA bundle
trh-kπ $ mkdir -p rel/tmp
trh-kπ $ echo "This is a test" > rel/tmp/test.txt
trh-kπ $ trh --external-sign "trh-k/thistle-bin/sign-gcp-k -c gcp_config.json" prepare -target=./rel --file-base-path=/
Read manifest at "./manifest.json"
Processed file "./rel/tmp/test.txt"
Executing external signing command
=====
=====
Manifest amended successfully
# Release OTA bundle to Thistle backend
trh-kπ $ trh --external-sign "trh-k/thistle-bin/sign-gcp-k -c gcp_config.json" release
Read manifest at "./manifest.json"
Executing external signing command
=====
=====
Prepared backup release
Uploaded asset test.txt
Executing external signing command
=====
=====
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-kπ $ tuc --log-level=info -c config.json
...
!! setting update status to Pass
.. signature verified with public key #0 type ecdsa
.. reported data to server
~~ next check in 3600s
-
thistle-bin/keygen-gcp-k
Usage: keygen-gcp-k -h Display this help message keygen-gcp-k -c CONFIG_FILE If GCP KMS resources in CONFIG_FILE are already provisioned, prints out public key string. Otherwise, generates a new key pair in GCP KMS as specified in CONFIG_FILE. User needs to login to appropriate GCP account and project before invoking command.
-
thistle-bin/sign-gcp-k
Usage: sign-gcp-k -h Display this help message sign-gcp-k -c CONFIG_FILE SIGNABLE_STRING Signs SIGNABLE_STRING using GCP resources specified in CONFIG_FILE