Skip to content

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:

  1. 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_PAD instead — RFC 3394 AES Key Wrap with PKCS#5 padding stripped on unwrap.
    • The result is a wrappedKeysContainer containing the platform's ephemeralPublicKey (uncompressed P-256 point, base64) and a wrappedKeys array. Each entry carries keyName, keyType, tokenID, and a base64 cipherText. After unwrapping, asymmetric keys appear as PKCS#8 DER and symmetric keys as raw bytes.
  2. 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.