> Markdown version of https://docs.laavat.io/exampleflows/exportabletoken/ from the LAAVAT PKI and Signing Platform documentation. All pages: https://docs.laavat.io/llms.txt

# 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.

> **Tip: 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](https://docs.laavat.io/solutions/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](https://docs.laavat.io/solutions/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](https://www.etsi.org/deliver/etsi_en/303600_303699/303645/03.01.03_60/en_303645v030103p.pdf)
  (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](https://docs.laavat.io/exampleflows/exportabletoken/#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](https://docs.laavat.io/api/authentication/). Write it
to a file and let `config-init` reference it, so the token is never on the
command line:

```bash
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](https://docs.laavat.io/usage/productguide/product/#exportable-tokens). Create
the product from a template like this one:

```json
{
    "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"
            }
        }
    ]
}
```

```bash
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](https://docs.laavat.io/gui/clients/registration/#client-types). Only that key can
open an export, so keep it in the host's credential store.

```bash
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](https://docs.laavat.io/usage/clientguide/clients/#exporting-aes-keys-with-a-securityengineer-client):

```bash
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](https://docs.laavat.io/appendixes/audit-events/). 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:

```bash
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:

```bash
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:

```text
# 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:

```bash
# 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:

```bash
# 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](https://docs.laavat.io/usage/productguide/product/#exportable-tokens) — the
  `ExportableToken` operation and how it behaves
- [Key custody and export](https://docs.laavat.io/security/#online-encryption-keys-and-exportable-tokens)
  — why exportable tokens have an online export path and signing keys do not
- [Clients](https://docs.laavat.io/usage/clientguide/clients/#exporting-aes-keys-with-a-securityengineer-client)
  — the wrapping scheme behind `secrets getkeys`
- [Trusted device identities](https://docs.laavat.io/solutions/trusted-device-identities/) — a
  key generated on each device instead, with a certificate per device
