Inspect the agent and audit tokens
A local stack keeps its keys on three SoftHSM tokens, one for each deployable that holds keys, so that a service which opens its own token cannot open another's. This page shows how to list the tokens and the keys on each, to check that the keys you expect are there and that none can leave the token.
The commands are for a local stack only. They read a token's user PIN from the stack's local state directory, which exists only on a development machine. A production deployment has a hardware security module, and its PINs are never in a file on an operator's machine.
Run every command from the repository root.
The three tokens
| Token | Volume | SoftHSM configuration | User PIN file | What it holds |
|---|---|---|---|---|
oktoid | oktoid-local_hsm-tokens | softhsm2.conf | user-pin | The trust module's federation signing keys, one per hosted entity (federation), and the wrapping key of every person's data key (key-wrap) |
oktoid-agent | oktoid-local_hsm-tokens-agent | softhsm2-agent.conf | agent-user-pin | Identifier keys. For each identifier its assertion keys, current and retired, and for a did:webvh identifier the update keys. A spent update key is destroyed once the entry it signed is published |
oktoid-audit | oktoid-local_hsm-tokens-audit | softhsm2-audit.conf | audit-user-pin | The audit trail's checkpoint signing key (audit-checkpoint) |
The configuration files are in infra/environments/shared/. The PIN files are
in infra/compose/.state/local/secrets/oktoid/hsm/. Each token directory inside
a container is /var/lib/softhsm/<directory>, where <directory> is the
volume name without oktoid-local_hsm-: tokens, tokens-agent or
tokens-audit.
The service images carry neither softhsm2-util nor pkcs11-tool. Use the
image of the job that creates the tokens, oktoid/softhsm-init:local, which
has both. Every command below runs it with no network, as the user that owns
the token directories (uid 1000), and mounts the token's volume read-only, so
nothing on this page can change a token.
List the tokens
Listing the slots needs no PIN. For the agent's token:
docker run --rm --network none --user 1000 --entrypoint softhsm2-util \
-v oktoid-local_hsm-tokens-agent:/var/lib/softhsm/tokens-agent:ro \
-v "$PWD/infra/environments/shared/softhsm2-agent.conf:/etc/softhsm2.conf:ro" \
-e SOFTHSM2_CONF=/etc/softhsm2.conf \
oktoid/softhsm-init:local --show-slots
The token appears in a slot whose number SoftHSM derives from the token, so it
is not 0:
Slot 892996715
...
Token info:
...
Initialized: yes
User PIN init.: yes
Label: oktoid-agent
A second slot follows with Initialized: no and no label. That is SoftHSM's
free slot, always offered for a new token, and it is expected.
For the other two tokens, change the volume, its directory and the configuration file as the table above gives them.
List the keys on a token
Listing keys needs the token's user PIN. The PIN must not appear on the
command line, where it would be kept in your shell history and could be read
from the process list. Mount the PIN file into the container instead, and let
the shell inside the container read it into the variable that pkcs11-tool
takes the PIN from. For the agent's token:
docker run --rm --network none --user 1000 --entrypoint sh \
-v oktoid-local_hsm-tokens-agent:/var/lib/softhsm/tokens-agent:ro \
-v "$PWD/infra/environments/shared/softhsm2-agent.conf:/etc/softhsm2.conf:ro" \
-v "$PWD/infra/compose/.state/local/secrets/oktoid/hsm/agent-user-pin:/run/pin:ro" \
-e SOFTHSM2_CONF=/etc/softhsm2.conf \
oktoid/softhsm-init:local \
-c 'PIN="$(cat /run/pin)" pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so --login --pin env:PIN --list-objects --type privkey'
Keep the single quotes around the last line: they stop your own shell from
expanding $(cat /run/pin), so the PIN is read inside the container and is
never on your machine's command line or in its history. --pin env:PIN tells
pkcs11-tool to read the PIN from the variable rather than from an argument.
--type privkey lists private keys only. Use --type secrkey for secret keys
such as the wrapping key, or leave --type out to list every object, public
keys included.
For the oktoid token, mount oktoid-local_hsm-tokens at
/var/lib/softhsm/tokens, softhsm2.conf and user-pin. For the audit token,
oktoid-local_hsm-tokens-audit at /var/lib/softhsm/tokens-audit,
softhsm2-audit.conf and audit-user-pin.
What to look for
Each key is printed with its label, its use and its access attributes. On the audit token:
Private Key Object; EC
label: audit-checkpoint
ID: 594e86c7091680c793cc511870600c97
Usage: sign
Access: sensitive, always sensitive, never extractable, local
Every private key and every secret key must read
sensitive, always sensitive, never extractable, local. never extractable
means the key cannot be exported from the token in any form; local means it
was generated on the token, not imported. A private or secret key without
those attributes is a fault to report. Public keys are listed with
Access: local only, which is expected: a public key is not secret.
What each token should hold depends on how far the stack has gone:
| Token | On a stack whose setup has completed |
|---|---|
oktoid | One key-wrap secret key (AES, 32 bytes), and a federation private key for the anchor and for each organization whose statements were prepared |
oktoid-agent | The anchor's identifier keys, and each further identifier's. After an identifier's key has been rotated, its retired assertion keys are still there, and its spent update keys are not |
oktoid-audit | One audit-checkpoint private key |
On a stack whose setup is still under way, the oktoid token may hold only the
key-wrap key and the agent's token no private key at all: identifier keys are
made when the setup run creates the anchor's identifier.
When a command fails
| Symptom | Cause |
|---|---|
--show-slots lists only a slot with Initialized: no and no label | The volume name is wrong, or the configuration file names another token's directory. Docker creates a named volume that does not exist, empty, so a mistyped name is not an error: remove the stray volume with docker volume rm <name>. docker volume ls lists the stack's real volumes |
C_Login failed: rv = CKR_PIN_INCORRECT | The PIN file belongs to another token. Take the volume, the configuration file and the PIN file from the same row of the table |