Exportable tokens example¶
An exportable token is a key generated in the HSM that an authorised client can
export. This example uses one as the SSH key a device keeps in its crypto chip:
it creates a product with an exportable token, exports the key with a
SecurityEngineer client, writes it into the chip, and uses it to log in to a
server. The key is generated from the HSM's hardware random number generator,
so it has full entropy and is never created on a laptop or build machine.
Recommended: a key per device
The usual approach — and what ETSI EN 303 645 and EN 18031-1 expect for keys that protect communication with a backend — is a key generated inside each device's chip, with a certificate per device, so that every device can be authenticated, restricted and shut out on its own. See Trusted device identities. This example shows the shared-key alternative, for the cases where a key per device is not possible.
This example uses the reference python client (signing-tool) for every step
that talks to the platform, and the GUI or signing-tool for the approvals.
The example is divided into the following stages:
- Authentication
- Creating the product with an exportable token
- Registering the provisioning host
- Exporting the key
- Writing the key into the chip
- Trusting the key on the server, restricted to one job
- Using the key on the device
- Checking the result
When a shared key is the right choice¶
An exportable token is one key for the whole product: every device carries the same private key, and the server trusts one public key. Choose it only when a key per device is not possible, typically because:
- the chip cannot generate keys itself; or
- the chip is programmed before it is fitted to the board, for example by the chip supplier, so no per-device key can be created and certified on the production line.
Even then, keep the shared key to one narrow purpose, such as a single service the devices log in to for log upload or a maintenance tunnel, and restrict it on the server as shown below.
If each device can have its own key, use that instead: the key is generated on the device, inside the chip, and the platform issues its certificate — see Trusted device identities.
Limitations¶
- One device exposes all of them. A key extracted from one chip lets an attacker log in as any device of the product.
- No expiry. The key stays valid until it is replaced.
- No single device can be shut out. Removing the key from the server locks out the whole fleet.
- The key is unwrapped outside the HSM. It leaves the HSM wrapped, and is open only briefly on the provisioning host, while it is written into the chip or the production tester image.
- Replacing the key means re-provisioning every device. A new token's key has to reach every chip, and the server trusts both keys until the last device has the new one.
- It is not a unique device credential. ETSI EN 303 645 (provision 5.4-4) and EN 18031-1 (CCK-3) require keys that protect communication with a backend to be unique per device. A shared key does not meet that, whether or not it is stored in a chip.
Trust the key on the server limits what the key can do on the server, so that a leaked key can only do what a device does.
Authentication¶
A valid token is needed to use the reference client; how to obtain one is
explained in the authentication chapter. Write it
to a file and let config-init reference it, so the token is never on the
command line:
az account get-access-token --resource api://<resource> \
--query accessToken -o tsv > user.token
config-init -n test.ini -t @user.token -a https://app.laavat.io/<tenant>/api/v1
Create the product¶
The token has no operation behind it and no approval rule; the platform never signs with it — see Exportable tokens. Create the product from a template like this one:
{
"name": "Gateway SSH key",
"description": "SSH key for the device crypto chip",
"productType": "Production",
"enabled": true,
"caInfo": [],
"rndKeys": [],
"productOperations": [
{
"name": "Device SSH key",
"description": "SSH key for the device crypto chip, from the HSM",
"operationType": "ExportableToken",
"token": {
"name": "Device SSH key",
"description": "ECDSA P-256",
"keyType": "ECDSAP256"
}
}
]
}
signing-tool -n test.ini product add -T gateway-ssh-product.json
Then approve the product in the GUI or with signing-tool.
Pick the key type the chip supports: ECDSAP256, ECDSAP384, ECDSAP521,
RSA2048, RSA3072 or RSA4096.
Register the provisioning host¶
The provisioning host is the machine that fetches the key: a factory station
on the production line, or the build server job that makes the production
tester image. It gets an EC P-256 key pair and is registered on the product as
a SecurityEngineer client, bound to the identity it logs in with — see
client types. Only that key can
open an export, so keep it in the host's credential store.
openssl ecparam -name prime256v1 -genkey -noout -out host.key
openssl ec -in host.key -pubout -out host.pub
signing-tool -n test.ini client add -N "ssh-key-provisioning" \
-D "exports the device SSH key" -K host.pub \
-U "appid:<host-identity-app-id>" -T SecurityEngineer -p <product-uuid>
Client Add request sent. Request ID: <request-uuid> state: ApprovalRequired
Then approve the registration in the GUI or with signing-tool.
Export the key¶
On the provisioning host — see exporting with a SecurityEngineer client:
signing-tool -n test.ini secrets getkeys -P <product-uuid> -C host.key -O ssh
ssh0.bin (name='Device SSH key' type=ECDSAP256 tokenID=<uuid>):
EC private key (secp256r1) -> ssh0.priv.pem, ssh0.pub.pem
ssh0.priv.pem is the private key as PKCS#8, readable by the owner only. Every
export returns the same key and is recorded as a GetProductWrappedKeys
audit event. A production tenant backed by
CloudHSM adds --cloudhsm.
Write the key into the chip¶
This happens on the production line. A factory station does it directly. If the build server exported the key, the key reaches the line inside the production tester image instead: the build job encrypts it with a key the tester holds, puts only the encrypted key into the image and deletes the exported files, and the tester decrypts it and runs this step.
Use the chip vendor's provisioning tool, or its PKCS#11 library. Write the public key as well, with the same ID and label: OpenSSH finds a key in the chip through its public key object. For example:
openssl pkey -in ssh0.priv.pem -outform DER -out ssh0.priv.der
openssl pkey -pubin -in ssh0.pub.pem -outform DER -out ssh0.pub.der
pkcs11-tool --module <chip-pkcs11-library> --login --write-object ssh0.priv.der \
--type privkey --id 01 --label device-ssh
pkcs11-tool --module <chip-pkcs11-library> --login --write-object ssh0.pub.der \
--type pubkey --id 01 --label device-ssh
shred -u ssh0.priv.der ssh0.priv.pem ssh0.bin
Trust the key on the server¶
The public key comes from the platform, so the server side needs no access to the factory. On the server, convert it to OpenSSH's format:
signing-tool -n test.ini product getpubkey -P <product-uuid> \
--operid <token-operation-uuid> -O ssh-pub.pem
ssh-keygen -i -m PKCS8 -f ssh-pub.pem
Add it to ~/.ssh/authorized_keys of a dedicated account that only the
devices use, with options that limit the key to the one job the devices do.
For a maintenance tunnel, the key can open one reverse tunnel and nothing else;
for log upload, it can only use SFTP. Each entry is one line, where <key> is
the line ssh-keygen -i printed:
# maintenance tunnel only: no shell, one listening port
restrict,port-forwarding,permitlisten="localhost:2201",command="/bin/false" <key>
# log upload only: SFTP, no shell
restrict,command="internal-sftp" <key>
Use the key on the device¶
OpenSSH reaches the key through the chip's PKCS#11 library:
# maintenance tunnel
ssh -N -R 2201:localhost:22 -o PKCS11Provider=<chip-pkcs11-library> \
device@service.example.com
# log upload
sftp -o PKCS11Provider=<chip-pkcs11-library> device@service.example.com
Check the result¶
The key in the chip and the key the server trusts must be the same:
# on the device: the public key held in the chip
ssh-keygen -D <chip-pkcs11-library>
# the product's public key, as converted on the server
ssh-keygen -i -m PKCS8 -f ssh-pub.pem
Both print the same ecdsa-sha2-nistp256 … line. A first connection with
ssh -v from the device then reports
Authenticated to service.example.com … using "publickey".
Recap¶
| Stage | Actor | Action |
|---|---|---|
| Product creation | Writer | Submitted the template with an ExportableToken operation (ECDSA P-256) |
| Product approval | Approver | Approved the product |
| Client registration | Security engineer | Registered the provisioning host's EC P-256 key as a SecurityEngineer client; an approver approved it |
| Key export | Provisioning host | Ran secrets getkeys; received the key wrapped, unwrapped it; recorded as an audit event |
| Chip provisioning | Factory station or production tester | Wrote the private and public key into the chip, deleted the files |
| Server setup | Backend operator | Added the product's public key to a dedicated account, restricted to a tunnel or to SFTP |
| Use and check | Device | Connected through the chip's PKCS#11 library; the chip's key matched the server's |
Related¶
- Exportable tokens — the
ExportableTokenoperation and how it behaves - Key custody and export — why exportable tokens have an online export path and signing keys do not
- Clients
— the wrapping scheme behind
secrets getkeys - Trusted device identities — a key generated on each device instead, with a certificate per device