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

  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.

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.