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:
| Client type | Endpoint | What it returns |
|---|---|---|
ProductionPC |
GET /distributions/products/{id}/secrets |
Product secrets (SRK hash, SRK table, product-specific IV, PKI hierarchy in PKCS#7) |
SecurityEngineer |
GET /distributions/products/{id}/wrappedkeys |
Every product key marked extractable, individually wrapped to the client's EC public key |
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.
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¶
For this step, you'll need to prepare a client private and public key to be used. 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 -c -t $TOKEN \
-a https://app.laavat.io/<CustomerName>/api/v1 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 -c -t $APPTOKEN \
-a https://app.laavat.io/<CustomerName>/api/v1 client \
approve -I 25af75d8-e504-44c0-ac8f-ecae5c1a69d9
Client approved
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 -c -t $TOKEN \
-a https://app.laavat.io/<CustomerName>/api/v1/ 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 extractable keys with a SecurityEngineer client¶
A SecurityEngineer client is used to wrap product keys that have been marked as extractable out of the platform so that
they can be used outside of the HSM (for example a device decryption key bound to a piece of customer firmware). Each key is
wrapped individually so that only the holder of the registered client EC private key can recover the plaintext.
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.
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 -c -t $TOKEN \
-a https://app.laavat.io/<CustomerName>/api/v1 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 -c -t $APPTOKEN \
-a https://app.laavat.io/<CustomerName>/api/v1 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 -c -t $TOKEN \
-a https://app.laavat.io/<CustomerName>/api/v1/ 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 extractable keys are returned
Keys created without the extractable flag are protected by the HSM and never leave it. The wrappedkeys endpoint silently skips them. Mark a 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.