Clients¶
Clients in LAAVAT PKI and Signing Platform are used to enable the retrieval of sensitive information about the product. Clients are per product and require approval before they can be used. Clients encrypt the response as per JWE with the public key used for registering.
The client type controls which retrieval flow is available and which key the client has to register:
| Client type | Endpoint | What it returns | Key to register |
|---|---|---|---|
ProductionPC |
GET /distributions/products/{id}/secrets |
Product secrets (SRK hash, SRK table, product-specific IV, PKI hierarchy in PKCS#7) | RSA or EC public key |
SecurityEngineer |
GET /distributions/products/{id}/wrappedkeys |
Exportable AES keys for the product, individually wrapped to the client's EC public key | EC P-256 public key only |
The response is a JWE addressed to the registered key. For ProductionPC the key-management algorithm follows the key type,
RSA-OAEP for an RSA key or ECDH-ES+A256KW for an EC key, with A256GCM content encryption; secrets get works with the
same private key file either way. For SecurityEngineer the JWE uses ECDH-ES+A256KW with A256GCM, and each key inside it
is wrapped with a KEK derived by ECDH against the same registered key. The platform refuses a ProductionPC registration
without a public key and a SecurityEngineer registration without an EC P-256 key; the error names the requirement.
It is important to specify the clientUser field correctly since that field is used to check from the AD requesting entity is who he claims to be.
Which identity form to register¶
clientUser carries a prefix that says what kind of identity the value is. When a client calls the platform, the
caller's token is matched against the registered clients in this order: oid: against the token's object id, then
appid: against its application id, then sub:, then upn:.
| Form | Identity it names | Who registers this way |
|---|---|---|
oid:<object-id> |
Entra ID user or group principal, by object id | A person, signing in interactively |
appid:<application-id> |
Entra ID application, by Application (client) ID | An application / service principal: CI pipelines, factory and release scripts |
sub:<id> |
Google user | A person on a Google-backed tenant |
upn:<email> |
Entra ID user, by email | Deprecated, kept for backward compatibility - register oid: instead |
A caller that authenticates without a user - a CI pipeline, a factory PC, a release test - signs in as a service
principal, and the value you have to hand for it is its Application (client) ID, so register it with appid::
(venv) $ signing-tool -n test.ini client \
add -N Manufacturing -D "Read SRK table and SRK hash for fusing" -K client.public \
-U "appid:<application-id>" -T ProductionPC \
-p b508d600-977a-46f4-9070-5cf56646bae1
One active client per identity
For ProductionPC and SecurityEngineer the platform keeps one usable client per combination of clientUser, product
and client type. Approving a second registration for the same combination retires the earlier client to state 256
(NotActive): it stops serving the identity, and the approval response lists it under retiredClients. The key
algorithm plays no part in this, so approving an RSA-keyed client retires an EC-keyed one and vice versa.
Certificate-based client types are matched by certificate instead, so one owner may hold several of them.
Example of decrypted product secrets¶
Gateway Product with support for digest signing¶
The output provides the public key of the generated key. Additionally, the configitem KEK-IVCustomEncryption is generated however is not used in this product.
{
"caInfo": [],
"description": "Imported RnD key for product Gateway Signing",
"id": "f67556fd-d8d3-4d8c-8e1c-3e02a50506ff",
"name": "Gateway",
"productConfigItems": [
],
"tokenInfo": [
{
"description": "Imported RSA key digest signing",
"keyType": "RSA2048",
"name": "Digest sign",
"publicKey": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4H3VwdA3TZ2lJfGRc
4KwiUVWpEdoPiLMtjjwymaa6iba9ilQ9bHQowx2Dj/Bu6AXWi67M5ofszSS6dvQd5SjJTIaoQqI
ldNY0vyqNLH/PukK+3QGxYLU6xZo5lBsgtq/T2gv83AfvCe6WKjDHyl3luVF909AVgk8YV2goL/
cBzjLr86Np8d4+qr2yVJ7wkojQ17ea/wkp8PpvUwsNtzTTA0/
uxXivq0ksYkLwGotl8RRX5YciU14XvJLp35xmQUflCIOw6pVeBRULwX8J3aso/5bUZEXRL1uGH4
ftNMCA89YGYJUccMqr/38Z9xsktQCEoaWGY+nJTRoLtD56mpNzwIDAQAB"
}
]
}
Example of i.MX6 based gateway product. Output is shortened for readability.
{
"caInfo": [
{
"cn": "SRK3 for i.MX6",
"extKeyId": "03d0ae22-ae8a-4b26-85ad-6fb5492e6f24",
"id": "525acdd9-5e94-43f9-a22e-bf051772232c",
"pkcs7chain": "LS0t..."
},
{
"cn": "IMG3",
"extKeyId": "7600980e-b9ce-4a82-aaac-1a1ec8ba2156",
"id": "395ab6be-187c-40bc-a8a3-c224d476496a",
"pkcs7chain": "LS0t..."
},
{
"cn": "CSF3",
"extKeyId": "0db1d33e-e329-4c58-8a5f-9b1749a9e254",
"id": "df85c718-9444-492b-ae36-9ee0f5ef9053",
"pkcs7chain": "LS0t..."
},
{
"cn": "SRK1 for i.MX6",
"extKeyId": "1927a95a-ad7e-4190-b00e-2b51f343ce05",
"id": "67848b56-9f1b-46f1-aa20-0aa9e7b722cd",
"pkcs7chain": "LS0t..."
},
{
"cn": "IMG1",
"extKeyId": "34e6d26f-fbc3-489f-8b03-5e3626dcee06",
"id": "8aec6c30-ffc8-427b-b194-0246380bc406",
"pkcs7chain": "LS0t..."
},
{
"cn": "CSF1",
"extKeyId": "036cb1c2-10d2-4321-8606-61f193e22525",
"id": "dda17fab-480e-472b-ba7c-bf7b1adcbcdf",
"pkcs7chain": "LS0t..."
},
{
"cn": "SRK2 for i.MX6",
"extKeyId": "c1e24fcf-85f3-4650-8177-8af9d936a542",
"id": "944208eb-61f7-432d-9a69-095c262c2464",
"pkcs7chain": "LS0t..."
},
{
"cn": "IMG2",
"extKeyId": "7daa8861-19d1-4a53-bfab-7e6ed6f7c040",
"id": "a01e71ae-9fbf-4d19-8d55-de9fc9a0ffbc",
"pkcs7chain": "LS0t..."
},
{
"cn": "CSF2",
"extKeyId": "d65d701d-3290-444c-ba82-49c68c7892a1",
"id": "fb46c170-9f3e-490e-b8a2-2dd196befe6b",
"pkcs7chain": "LS0t..."
},
{
"cn": "SRK0 for i.MX6",
"extKeyId": "8d3aec9b-621a-4a29-b597-dba7ef07448a",
"id": "bae50147-2a06-4246-a775-5db97e270203",
"pkcs7chain": "LS0t..."
},
{
"cn": "CSF0",
"extKeyId": "72a90543-1ec2-43ba-a04a-0cd5b6e63f76",
"id": "8ca4db26-c90a-41f1-895d-574051ae5981",
"pkcs7chain": "LS0t..."
},
{
"cn": "IMG0",
"extKeyId": "ee674a91-8caf-48fa-82a2-1c826b44b093",
"id": "f8db6bb6-988c-4bc7-bc63-22a8a3d09078",
"pkcs7chain": "LS0t..."
}
],
"description": "Product for new i.MX6 based product",
"id": "f70429ad-ab2f-4e23-b00f-ded05cb04808",
"name": "gateway iot",
"productConfigItems": [
{
"name": "SRKHash",
"value": "Veo0jkU1aP3Y5Uq3KAQtulXIMyP2KWO1X0oEhCxmHdA="
},
{
"name": "SRKTable",
"value": "1whAQ..."
}
],
"tokenInfo": [
{
"description": "Used to encrypt image and the AES key is provided as part of fusemap",
"keyType": "AES128",
"name": "Content encryption operation"
},
{
"description": "Digest signing",
"keyType": "RSA2048",
"name": "Digest sign",
"publicKey": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAwmgs8EL/R+f6DYXMCHRw/zggsdFP/
GAyLJVBfPiFWchLOsBkvF1iqXq0MFdMn4Y37tg3W9eispXudjY
UzAt9gjlNm4B7pDb5Et4YuWVZ9BJ9hS1LOfcndEh33UwHFoYMO
hSUpLlF7SM+FiMXCMIp3G2acnXSPJhROQPz7v++mSd8LHwRFuB
k8g9UnRgVcusdxueWEfsVzqmLrJoUX+T/azABkjD55flGkNLW0
rgNTGXzzDEbERxUcw7XWy4iGQWP8rLpm0
3nt2FDjEKRb1OLI15q99+QbFhzPMG29l1JWucfR9KODNs8Gbpf
BFhZ41+K8TG6R8stK5k/vxZCJGit+QIDAQAB"
}
]
}
Example usage with reference client package: Add a Client for the product¶
Token handling
These commands use -n test.ini — a config file created with config-init that references a token_file and the API URL, so no token appears on the command line. Approver steps use -n approver.ini for a user in the approvers group. See Reference Client › Usage for how to create these configs and for the secure token-input forms (-t @file, -t @-).
For this step, you'll need to prepare a client private and public key to be used. A ProductionPC client may register an
RSA key, as here, or an EC P-256 key generated the way the SecurityEngineer example
shows. You'll also need the object identifier (OID) of the user from Microsoft Entra ID.
# you can convert the client private key to a public one with openssl
(venv) $ openssl genrsa -out client.private
(venv) $ openssl rsa -in client.private -pubout -out client.public
(venv) $ signing-tool -n test.ini client \
add -N TESTCLIENT1 -D TESTCLIENT1 -K client.public \
-U "oid:<your-object-id>" -T ProductionPC \
-p b508d600-977a-46f4-9070-5cf56646bae1
{
"client_type": "ProductionPC",
"description": "TESTCLIENT1",
"id": "25af75d8-e504-44c0-ac8f-ecae5c1a69d9",
"id_product": "b508d600-977a-46f4-9070-5cf56646bae1",
"name": "TESTCLIENT1",
"state": 2
}
Client Add request sent. Request ID: 25af75d8-e504-44c0-ac8f-ecae5c1a69d9 state: ApprovalRequired
Approve the client:
(venv) $ signing-tool -n approver.ini client \
approve -I 25af75d8-e504-44c0-ac8f-ecae5c1a69d9
Client approved
Client states¶
The state field of a client request takes these values:
| Value | Name | Meaning |
|---|---|---|
| 2 | ApprovalRequired | Registered, waiting for a member of the client approver group |
| 4 | Approved | Approved, being activated |
| 16 | Ready | Usable: this is the client the platform serves for its identity, product and type |
| 32 | Rejected | Refused by the approver |
| 256 | NotActive | Retired: a later client with the same clientUser, product and type was approved and replaced it |
| 1024 | ToBeDeleted | Marked for deletion through a client change request |
A decision sent for a request that is not in ApprovalRequired changes nothing; the response reports applied: false and the current state.
Example usage with reference client package: Get product secrets¶
Product secrets for product with id 6f603401-6723-4dec-a4a3-a8749865b46d is fetched in this example.
(venv) $ signing-tool -n test.ini secrets get \
-P 6f603401-6723-4dec-a4a3-a8749865b46d -C client.private -O /tmp/prod.json
SRKHASH written to: /tmp/prod.jsonSRKHASH
SRKTABLE written to: /tmp/prod.jsonSRKTABLE
KEKIV written to: /tmp/prod.jsonKEKIV
Full secret payload written to: /tmp/prod.json
Exporting AES keys with a SecurityEngineer client¶
A SecurityEngineer client is used to wrap a product's exportable AES keys out of the platform so that they can be used
outside of the HSM. Each key is wrapped individually so that only the holder of the registered client EC private key can
recover the plaintext.
Encryption keys are the case where this is necessary rather than optional. Encryption is symmetric: if you ship encrypted firmware, the device must hold the key that decrypts it or it cannot boot, so that key has to leave the platform and be provisioned into the device — typically fused or written into secure storage during manufacturing. Wrapping it to your registered client key is what lets it make that trip without ever being in the clear in transit.
Once you unwrap it, protection is yours: the key is only as safe as the environment you unwrap it into, so use a controlled provisioning host rather than a general-purpose build machine.
Encryption keys only
This path is restricted to encryption keys. extractable is valid only on those — the platform rejects the flag on a
signing key, so a signing key cannot be given an online export path even by a misconfigured template.
Signing keys remain recoverable for business continuity through the offline crypto-officer ceremony described below, which needs no online export path.
Wrapping scheme¶
The response is a single serializedJWE string. Two layers of protection are applied:
- Per-key AES key wrapping with an ephemeral KEK.
- The platform generates a fresh EC P-256 ephemeral keypair for the request.
- The platform then performs ECDH between the generated ephemeral private key and the client's registered EC public key, then derives a 32-byte KEK with HKDF-SHA256(secret, salt=nil, info="jwe-key-wrapping").
- Each extractable key is wrapped with that KEK using AES Key Wrap with Padding (RFC 5649 / AES-KWP). When the deployment is backed by AWS CloudHSM the wrap mechanism is
CKM_CLOUDHSM_AES_KEY_WRAP_PKCS5_PADinstead — RFC 3394 AES Key Wrap with PKCS#5 padding stripped on unwrap. - The result is a
wrappedKeysContainercontaining the platform'sephemeralPublicKey(uncompressed P-256 point, base64) and awrappedKeysarray. Each entry carrieskeyName,keyType,tokenID, and a base64cipherText. After unwrapping, asymmetric keys appear as PKCS#8 DER and symmetric keys as raw bytes.
- Outer JWE addressed to the client. The container is then encrypted into a JWE (
ECDH-ES+A256KW/A256GCM) addressed to the client's registered EC public key.
To recover the keys, the client decrypts the outer JWE with its EC private key, recomputes the shared secret by performing ECDH against the container's ephemeralPublicKey, derives the same KEK with HKDF, and AES-KWP-unwraps each cipherText.
Business continuity: the crypto-officer ceremony¶
Signing keys, and encryption keys not marked extractable, never leave the HSM through any online path. They can still be recovered when there is a genuine business-continuity need, by wrapping them out under an offline crypto-officer ceremony that requires two authorised people. See the crypto-officer ceremony for how it works and when customers typically use it.
Registering a SecurityEngineer client¶
The flow is the same as for a ProductionPC client, but the client public key must be EC P-256 (the platform uses ECDH against this key) and the client type is SecurityEngineer:
# Generate an EC P-256 keypair for the client
(venv) $ openssl ecparam -name prime256v1 -genkey -noout -out client.ec.private
(venv) $ openssl ec -in client.ec.private -pubout -out client.ec.public
(venv) $ signing-tool -n test.ini client \
add -N SECENG1 -D "Security Engineer for product X" -K client.ec.public \
-U "oid:<your-object-id>" -T SecurityEngineer \
-p b508d600-977a-46f4-9070-5cf56646bae1
# Approve the client
(venv) $ signing-tool -n approver.ini client \
approve -I <client-id>
Fetching and unwrapping the keys¶
The signing-tool secrets getkeys subcommand performs the full fetch-and-unwrap flow (outer JWE decrypt, ECDH + HKDF KEK derivation, AES-KWP unwrap, and per-key file output) in a single command.
(venv) $ signing-tool secrets getkeys -h
usage: signing-tool secrets getkeys [-h] -P <PRODUCT_ID> [-O O] [-C C] [--keypass KEYPASS] [--cloudhsm]
options:
-h, --help show this help message and exit
-P <PRODUCT_ID> Product ID to be used
-O O Output filename prefix
-C C Filename for client EC private key
--keypass KEYPASS Password for client private key
--cloudhsm Platform wrapped with CKM_CLOUDHSM_AES_KEY_WRAP_PKCS5_PAD (RFC 3394 + PKCS#5) instead of RFC 5649 KWP
Pass --cloudhsm when the deployment is backed by AWS CloudHSM. The unwrapped keys are written next to the path given by -O: one <prefix><i>.bin file per wrapped key, plus PKCS#8 / SubjectPublicKeyInfo PEM pairs for asymmetric keys.
(venv) $ signing-tool -n test.ini secrets getkeys \
-P 6f603401-6723-4dec-a4a3-a8749865b46d \
-C client.ec.private -O /tmp/prod.keys
[+] inner payload: wrapped_count=2
[+] /tmp/prod.keys0.bin (name='Device decrypt' type=RSA2048 tokenID=...): RSA private key (2048 bits) -> /tmp/prod.keys0.priv.pem, /tmp/prod.keys0.pub.pem
[+] /tmp/prod.keys1.bin (name='Firmware passphrase' type=AES256 tokenID=...): symmetric key (32 bytes)
Only exportable AES keys are returned
Encryption keys created without the extractable flag, and every other class of key, are protected by the HSM and never leave it. The wrappedkeys endpoint silently skips them rather than reporting an error, so check that you received every key you expected. Mark an encryption key extractable in the product template only when you have a concrete need to take it out of the platform.
Once exported, you must implement appropriate safeguards to protect these materials against unauthorized access, loss, or compromise. LAAVAT cannot be held responsible for any security incidents, data breaches, or damages resulting from the handling, storage, or use of exported keys.
We strongly recommend treating exported secrets with the highest level of security and limiting their distribution.
Audit trail
Each successful call to this endpoint writes a GetProductWrappedKeys audit event tagged with the product ID and the requestor. See Audit Events.