Skip to main content

Key Management - Signing Keys and Certificates

The Organisation Wallet Suite signs everything that your organisation sends to wallets with your own keys: credentials, status lists, verification requests and signed issuer metadata. This guide explains where the keys can be kept, how to give a key a certificate that wallets trust, and which certificate each credential needs. For every field and response, see the Key Management API reference.

Signing Keys, Key Stores and Certificates​

  • Signing key: the key that signs on behalf of your organisation.
  • Key store: where the private key is kept. The private key never leaves the key store, and the API only returns public keys.
  • Certificate: an X.509 certificate from a certificate authority (CA) that ties the key to your organisation. A key with a certificate chain signs with the x509 format. Each signature carries the chain (x5c), and wallets check it against a trust list. If the list has your certificate or its CA, the wallet shows your organisation as trusted.

A key without a certificate signs with did:key, which is for tests only. mdoc credentials (mso_mdoc) always need x509.

Certificate Presets​

When you request a certificate, you choose a preset: a template for the certificate signing request (CSR) that matches what the key will sign.

What the key signsPreset
PID, EAA, QEAA or PuB-EAA credentialsThe preset of the credential category: pid-provider, eaa-provider, qeaa-provider or pub-eaa-provider
mdoc credentials without a category, such as an mDLmdoc-document-signer
SD-JWT VC or W3C VC credentials, and signed issuer metadata, from an issuer outside the EU trusted listscredential-issuer
Status listsstatus-list-signer, or mdoc-status-list-signer for mdoc credentials that a Document Signer signs
Verification requests in the EUwrpac-legal, or wrpac-natural for a verifier who is a natural person
Signed issuer metadata in the EUwrpac-legal
Verification requests outside the EUrequest-signer-san-dns for the client identifier prefix x509_san_dns, or mdoc-reader for ISO/IEC 18013-7 Annex C
Wallet instance attestations (WIA) and key attestations (KA)wallet-provider, and wallet-status-list-signer for their status lists
Anything elsecustom

A status list without a key of its own is signed with the credential signing key. Use a separate key for each purpose: the API warns you (purpose-reuse) when you request a certificate for a different purpose than the key already has. The List Certificate Presets API gives the fixed values, the required fields and the standards of each preset. The certificate that a credential needs depends on its category: see Credential Categories and Key Usage Types.

Key Store Types​

Every organisation starts with the local vault. Add another key store when your keys must stay in your own vault, cloud KMS or HSM, or with a qualified trust service provider.

Key storeWhat it isUse it when
Local vault (local)The key store of the installation. Every organisation has it, and new keys go there by default.You are happy for the platform to hold your keys.
External vault (vault)Your OpenBao or HashiCorp Vault, with the Transit secrets engine.Your keys must stay in your own vault.
Google Cloud KMS (gcp-kms)A key ring in your Google Cloud project.Your keys must stay in Google Cloud.
AWS KMS (aws-kms)AWS KMS in one region of your AWS account.Your keys must stay in AWS.
Azure Key Vault (azure-key-vault)Your Azure key vault or managed HSM.Your keys must stay in Azure.
Thales Luna Cloud HSM (dpod)A partition of your Thales Luna Cloud HSM service (Thales Data Protection on Demand, DPoD).You want your keys in a dedicated HSM partition.
Remote signing (csc)A remote signing service with the Cloud Signature Consortium (CSC) API: a qualified trust service provider (QTSP) such as Intesi Group, or another CSC-compatible provider. The key and its certificate stay with the provider.You sign with QTSP keys, for example for qualified electronic seals.

You can add one key store for each provider. Your installation may not offer every type: the dashboard and the List Key Store Types API show the ones that are available to you. For on-premise installations, see Organisation key stores.

Qualified keys: for keys held by a QTSP, the wallet checks the certificate against the EU trusted lists when you add the key and at each connection test. If the certificate is qualified and the key is in a qualified signature or seal creation device (QSCD), the dashboard shows the badge Qualified. Other CSC providers never get this badge.

Step 1: Get the API Key​

The steps below show a request and an example response. Enter your API key to run them against your organisation: each Run replaces the example with the real response. A request with an ID in its path, such as keyStoreId or keyId, has a field for it above the request: the step that returns the ID fills it in, or you can paste one. To get an API key, see Get API Keys.

In the dashboard, key management starts at Issuer & Verifier > Base Configurations > Key Management and Signing > Configure. Sandbox organisations use the key stores and keys of their main organisation, and cannot change them.

Step 2: Add a Key Store (optional)​

You need this step only when your keys must be kept outside the local vault.

  1. Check which providers you can add with List Key Store Types.
  2. Add the key store with Create Key Store: its type, provider, settings and credentials. Select an example, replace the values with your own, and run it. The API reference shows every field for each type.

Request

OpenBao, AppRole

Response

Example
  1. The wallet tests the connection and saves the key store only if the test passes. You can repeat the test at any time with Test Key Store. A failed test still returns 200, with ok set to false and the reason in message.

Request

This request sends no JSON body. Choose Run to send it.

Response

Example

In the dashboard, choose Configure next to the provider. Test Connection runs the test again.

Credentials are write-only and never returned. To change a key store, use Update Key Store: credentials that you leave out are kept, and the provider cannot change. To remove a key store, delete its keys first and then use Delete Key Store. The wallet never deletes keys that your provider holds.

Rights for a cloud KMS

Give the account of the wallet only the rights that it needs. The wallet lists keys, reads public keys and signs. It creates a key only when you ask it to, and it never deletes, disables or rotates a key. A missing right shows up as The connection test failed: Key management service refused the request.

Cloud KMSRights
Google Cloud KMS (service account with a JSON key)On the key ring: roles/cloudkms.viewer and roles/cloudkms.signerVerifier. To create keys, also cloudkms.cryptoKeys.create. Of the predefined roles, only roles/cloudkms.admin has it, and that role can also destroy keys, so use a custom role.
AWS KMS (IAM user with an access key)kms:ListKeys, plus kms:DescribeKey, kms:GetPublicKey and kms:Sign on the keys. To create keys, also kms:CreateKey, and kms:CreateAlias on the aliases alias/ows-*. For a key that you reference, the key policy must allow kms:DescribeKey, kms:GetPublicKey and kms:Sign, or allow the IAM policies of the account.
Azure Key Vault (app registration with a client secret or a client certificate)With Azure RBAC: Key Vault Crypto Officer to create keys, or Key Vault Crypto User to use existing keys only. With access policies: the key permissions Get, List and Sign, plus Create to create keys. For a managed HSM: Managed HSM Crypto User.
Thales Luna Cloud HSM (DPoD)
  1. In DPoD, create the Luna Cloud HSM service and download its service client package (add and configure a client).
  2. Initialise the partition and the Crypto Officer role with lunacm from the package, as described in the DPoD documentation.
  3. Copy AuthTokenConfigURI, AuthTokenClientId and AuthTokenClientSecret from Chrystoki.conf in the package. In the dashboard, Fill In from Chrystoki.conf reads them from the file in your browser, without sending the file.
  4. Add the key store with the partition label, these three values and the Crypto Officer PIN.

New keys are created in the partition as key pairs that cannot be extracted, with the label ows:<keyId>.

PIN: The partition locks after too many wrong PINs. The platform tries each PIN only once: after a wrong PIN, it waits until you send a new one with Update Key Store. Check the PIN before you send it.

Step 3: Create a Key​

Call Create Key. Without keyStoreId, the key goes into the local vault. Add keyStoreId to use a key store that you added, and a name that says what the key is for. The keyId in the response fills in the next steps.

Request

Local vault

Response

Example

New keys are ECDSA P-256 keys (ES256) that cannot be exported. Every key store type except remote signing (csc) can create keys. In the dashboard, choose View Keys on a key store, then Create Signing Key.

Step 4: Reference or Import a Key (instead of Step 3)​

Use a key that your provider already has. Find its providerKeyRef with List Provider Keys, which also shows each public key (jwk), so you can match it against your console. Then call Create Key with keyStoreId and providerKeyRef. A QTSP key comes with its certificate, so you can skip Step 5. In the dashboard, this is Add Existing Key.

Request

External vault (OpenBao or HashiCorp Vault): Transit key name

Response

Example

Import a key that you already have. Use Import Key with a password-protected P-256 private key: a PKCS#12 file (.p12 or .pfx) or an encrypted PKCS#8 key in PEM (BEGIN ENCRYPTED PRIVATE KEY). If the file also holds the certificate chain, the key can sign with x509 straight away. You can import into an external vault, and into the local vault when it is backed by OpenBao. Cloud KMS and HSM key stores do not accept imported keys. In the dashboard, this is Import Key.

The import is a file upload (multipart/form-data), so the request is shown with curl:

read -rs KEY_PASSWORD   # the password of the file; the terminal does not show it
curl -X POST "https://demo-api.igrant.io/v2/config/digital-wallet/openid/key-management/keys/import" \
-H "Authorization: ApiKey $API_KEY" \
-F "[email protected]" \
--form-string "password=$KEY_PASSWORD" \
-F "name=PID issuance 2026"

Example response (201):

{
"keyId": "3f2a91c47b8d4e6fa0c5d7e18b924f30",
"name": "PID issuance 2026",
"keyStore": { "id": "local-openbao", "type": "local", "name": "Local vault" },
"origin": "imported",
"algorithm": "ES256",
"isDefault": false,
"jwk": {
"kty": "EC",
"crv": "P-256",
"x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
"y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0",
"kid": "3f2a91c47b8d4e6fa0c5d7e18b924f30",
"alg": "ES256",
"x5c": ["MIICLzCCAdWgAwIBAgIUX2b7...", "MIIB9TCCAZugAwIBAgIUa1c4..."],
"x5c_expiry": "2027-10-22T09:30:00Z"
},
"hasCertificateChain": true,
"certificateSource": "imported",
"createdAt": "2026-10-22T09:30:00Z"
}
Partition full (507): If the HSM partition behind a key store has no room for another key, the request fails with 507. The response has no Retry-After, and a retry fails in the same way until there is room. For your own DPoD partition, remove unused key pairs or add capacity, then try again. If the local vault of your installation uses an HSM, contact the operator of your installation. Any request that creates a key can return this response, including test certificates, test CAs, the deployment of the organisation wallet, the creation of a sandbox organisation and the OpenID Conformance extension.

Step 5: Get a Certificate for a Key​

For production:

  1. Generate a CSR with Generate CSR, with the preset for the purpose of the key. Each example shows every field of the request, filled with the value that the wallet uses when you leave the field out: the fixed values of the preset and the details of your organisation (here Example AB). Change any value, or leave a field out to use the value of the wallet. A value that differs from a fixed value of the preset gives the warning preset-override.

Request

PID provider signing certificate
Signs the PID credentials (SD-JWT VC and mdoc). Preset: pid-provider.

Response

Example
  1. Send the CSR to your CA or QTSP, and ask for the certificate profile of that preset. The CA decides the final content of the certificate.
  2. Upload the chain with [Upload Certificate Chain](/docs/openid4vc-api/config-digital-wallet-open-id-upload-certificate-chain/): a PEM file with the leaf certificate first, then any intermediate certificates, then the root. The upload is a file upload (`multipart/form-data`):
curl -X POST "https://demo-api.igrant.io/v2/config/digital-wallet/openid/key-management/keys/$KEY_ID/certificate-chain" \
-H "Authorization: ApiKey $API_KEY" \
-F "[email protected]"

Example response (200):

{
"key_id": "3f2a91c47b8d4e6fa0c5d7e18b924f30",
"certificates_count": 2,
"x5t": "Pc9qa0x1tWfGqFOAPfUzjgKCOyk",
"x5t_s256": "3T1jzz8BK8b6vrTVo2C5sm8Ehb0cDcnfjVEHg9ZJ7ZM",
"csrId": "5e1c2f7a-8b3d-4c6e-9f0a-1b2c3d4e5f60",
"preset": "credential-issuer",
"warnings": [],
"affectedCredentialConfigurations": []
}

The wallet compares the certificate with the preset of the latest CSR of the key. It warns you about differences (profile-mismatch) but does not reject the upload. A new upload replaces the existing chain.

In the dashboard, choose View Key on the key, then Generate CSR in Key Details. Select the Certificate Preset and choose Generate. When the certificate arrives, choose Upload Certificate Chain.

The built-in test CA (development and testing)​

For development and testing, the built-in test CA issues a certificate and stores the chain on the key in one call: Create Test Certificate. The body is the same as for a CSR, with an optional validityDays (the examples show the default of an installation, 365 days). In the dashboard, switch on Use the built-in test CA in Generate CSR.

Request

PID provider signing certificate
Signs the PID credentials (SD-JWT VC and mdoc). Preset: pid-provider.

Response

Example
  • Each organisation has three test root CAs, one for each trust domain: mdoc-iaca for mdoc, access for access certificates (WRPAC) and request-signer-san-dns, and provider for all other presets. The first test certificate in a domain creates its root, and Read Test CA lists the roots.
  • For mdoc, the Document Signer, the IACA and the mdoc issuing_country must share a country. To choose that country, create the root first with Create Test CA.
  • Verifiers on this platform trust a test certificate only if they add its root as a trust authority (Create Trust Authority), or use a trust list that contains the root.
  • A new test certificate replaces and revokes the previous one. If a key has a chain from another CA, delete that chain first.
  • The dashboard marks these keys with the badge Test certificate. Your installation can switch the test CA off, and the test CA requests then return 403.
Warning: Development and testing only. The built-in test CA is not on any trusted list, and wallets and verifiers outside this platform do not trust it. Do not use it in production.

Step 6: Register the Certificate in a Trust List​

Wallets trust your organisation when a trust list contains your certificate or its CA. To join the NXD trust list, follow Register as a Wallet-Relying Party and send the request to [email protected]. Trust in the Wallet Ecosystem explains how trust lists work.

  • Issuers of PID, EAA, QEAA or PuB-EAA credentials belong in the list for that category. Verifiers belong in the Wallet-Relying Party Access Certificate (WRPAC) list.
  • The EU lists of trusted entities (ETSI TS 119 602) and the EU trusted lists hold the CA certificate, not your leaf certificate. For access certificates (WRPAC), that is the Access CA of the trust service provider that issued them. The NXD trust list registers the WRPAC of a relying party directly.
  • Signatures carry the leaf and intermediate certificates but not the root (OpenID4VC HAIP), so wallets and verifiers build the path to a CA on their own list.
  • In tests, the roots of the built-in test CA take the place of these CAs.

Step 7: Manage Your Keys​

Find keys. List Keys returns each key with its key store, public key, certificate details and usage count. You can filter by key store (keyStoreId), by keys with a certificate (withCertificateChain) or by text (search). Read Key returns a single key.

Request

This request sends no JSON body. Choose Run to send it.

Response

Example

See where a key is used. Read Key Usage lists the credential definitions, presentation definitions and status lists that sign with the key. canDelete tells you whether the key can be deleted.

Request

This request sends no JSON body. Choose Run to send it.

Response

Example

Delete a key. Delete Key needs the body {"confirm": "DELETE"} and returns 204 without a body. The default key and keys in use cannot be deleted. Only the record in the wallet is removed: the key itself stays with your provider.

Request

Response

Example
  • Rename a key: Update Key. For a QTSP key, the same call changes the PIN.
  • Remove a certificate chain: Delete Certificate Chain with {"confirm": "DELETE"}. This is refused while the key still signs with x509, so move those uses to another key first.
  • Renew a certificate: a key with an expired certificate stops signing with x509. jwk.x5c_expiry shows when the certificate expires, so upload a new chain before then. An mdoc is never valid beyond its Document Signer certificate.

In the dashboard, choose View Keys on a key store, then View Key, Edit Key or Delete Key. Key Details shows the certificate chain, where the key is used, and Delete Certificate Chain. Next to the key field of a credential definition or a presentation definition, Trust List Lookup shows whether the trust lists know the selected key. The Look Up a Key in the Trust Lists API returns the same result. In the developer guides on this site, the key label beside each signing key field lets you pick one of your keys.

Credential Categories and Key Usage Types​

Each credential configuration (each entry of credentialDefinitions) has a credential category, credentialCategory. The category is the eIDAS category of the credential, and it decides which certificate the signing key must have (ETSI TS 119 412-6). The wallet checks the key whenever you create or update the credential definition. The key that it checks is the cryptographicKeyIdentifier of the configuration, or the key of the definition if the configuration has none.

The categories, as the List Credential Categories API describes them:

  • None (none): No category and no certificate check. Use it for tests or for credentials outside eIDAS. An mDL still needs an ISO/IEC 18013-5 Document Signer certificate.
  • PID (pid): Person identification data: the identity data of a natural person (for example the name and date of birth) that a Member State, or a body on its behalf, issues to the EUDI Wallet. It needs a PID provider certificate.
  • EAA (non-qualified attestation) (eaa): An electronic attestation of attributes that is not qualified, for example a membership card or a ticket. Any organisation can issue it as a non-qualified trust service. It needs an electronic seal certificate.
  • QEAA (qualified attestation) (qeaa): A qualified electronic attestation of attributes. Only a qualified trust service provider (QTSP) can issue it, and it has the same legal effect as a lawfully issued paper attestation. It needs a qualified seal certificate. The platform adds the ETSI category urn:etsi:esi:eaa:eu:qualified.
  • PuB-EAA (public sector attestation) (pub-eaa): An electronic attestation of attributes that a public sector body responsible for an authentic source issues, or that is issued on its behalf, for example a vehicle registration from the national register. It has the same legal effect as a lawfully issued paper attestation. It needs a qualified seal certificate with the public sector statement (the law and the authentic source). The platform adds the ETSI category urn:etsi:esi:eaa:eu:pub.
CategoryThe certificate of the signing key must havePreset to request
noneNo ruleNone
pidQcType pid (0.4.0.194126.1.1) and key usage Type A, B, C or F (ETSI TS 119 412-6 PID-4.4.1-01)pid-provider
eaaKey usage Type A, B or F (ETSI EN 319 412-3 LEG-4.3.1-3) and organizationIdentifier in the subject (LEG-4.2.1-2)eaa-provider
qeaaQcCompliance, QcSSCD, QcType eseal and key usage Type A, B or Fqeaa-provider
pub-eaaThe QcPSB statement (0.4.0.194126.1.3, with all three values), QcCompliance, QcSSCD and key usage Type A, B or F. The QcType is eseal, or the QcType value of CIR (EU) 2024/2979pub-eaa-provider

The same API also gives the entitlement (ETSI TS 119 475 Annex A.2) and the sources of each category.

Mobile driving licences (mDL): an mDL (mso_mdoc with the doctype org.iso.18013.5.1.mDL) always needs an ISO/IEC 18013-5 Document Signer certificate, whatever its category: key usage digitalSignature and extended key usage 1.0.18013.5.1.2, both critical. With the category none, request it with the preset mdoc-document-signer. With another category, use the preset of that category and add the two extensions to the CSR. Because the Document Signer profile has only digitalSignature, an mDL in the category eaa, qeaa or pub-eaa may also use key usage Type C. This is a known deviation from ETSI EN 319 412-3 LEG-4.3.1-3.

Key usage types: ETSI EN 319 412-2 (clause 4.3.2, table 1) defines six key usage types. A certificate with any other key usage bit, such as keyCertSign, has no type.

TypenonRepudiationdigitalSignaturekeyEncipherment or keyAgreementUse for a credential signing key
AYesA seal over content: EAA, QEAA, PuB-EAA and PID. ETSI recommends Type A. The presets qeaa-provider and pub-eaa-provider use it
BYesYesA seal over content: EAA, QEAA, PuB-EAA and PID. The preset eaa-provider uses it, because validators also check digitalSignature
CYesPID, and the mDL Document Signer (ISO/IEC 18013-5). For an EAA, a QEAA or a PuB-EAA only when it is an mDL
DYesYesNot for a seal over content (EAA, QEAA, PuB-EAA) or a PID
EYesNot for a credential signing key: the key cannot sign
FYesYesYesA seal over content: EAA, QEAA, PuB-EAA and PID

When a key does not fit:

  • If the key of a configuration does not fit its category or the mDL rule, the save fails with 400 and errorType signing_key_does_not_fit. errorMessages explains each rule that the key misses and which preset to request:

    {
    "errorCode": 400,
    "errorDescription": "credentialDefinitions[0].cryptographicKeyIdentifier: A QEAA needs a qualified seal certificate. This key has an EAA certificate (no QcCompliance statement, no QcSSCD statement, no QcType eseal). Request a certificate with the preset QEAA provider (qeaa-provider).",
    "errorType": "signing_key_does_not_fit",
    "errorField": "credentialDefinitions[0].cryptographicKeyIdentifier",
    "errorMessages": [
    "A QEAA needs a qualified seal certificate. This key has an EAA certificate (no QcCompliance statement, no QcSSCD statement, no QcType eseal). Request a certificate with the preset QEAA provider (qeaa-provider)."
    ]
    }
  • A key with a PID certificate signs PID credentials only.

  • none has no certificate rule, and it is the category of every configuration saved without credentialCategory. If a none configuration that does not issue a PID uses a key with a PID certificate, the check adds an information message.

  • A key whose certificate is already in a trusted list can have a trusted-list exemption, which only the platform operator sets. While it applies, the key is accepted and the check result is exempt. A new certificate chain ends the exemption.

  • The check never stops issuance. If the certificate of a key changes after the save, the configuration shows does-not-fit until you choose another key or request a certificate with the preset of the category.

The category in the issued credential: for QEAA and PuB-EAA, the issuer adds the EAA category of ETSI TS 119 472-1 to every credential: urn:etsi:esi:eaa:eu:qualified for qeaa and urn:etsi:esi:eaa:eu:pub for pub-eaa. In SD-JWT VC it is the top-level claim category, always disclosed. In mdoc it is the data element category in the namespace org.etsi.01947201.010101. In W3C VC (jwt_vc_json) it is an extra value in type. Do not add these values yourself: with the category eaa, qeaa or pub-eaa, the save rejects them with 400 and errorType eaa_category_claim_reserved.

API fields:

FieldWhereWhat it tells you
credentialCategoryEach entry of credentialDefinitions (Create Credential Definition)none, pid, eaa, qeaa or pub-eaa. If it is missing or null, the category is none.
signingKeyCheckEach entry, in every responsefits, does-not-fit, exempt or not-checked, with messages. It matches the result that a save would give now.
suggestedCredentialCategoryEach entry, in every responsepid when a none configuration issues a PID and its key fits PID, else null.
certificateAssessmentEach key (List Keys)The key usage type, the categories that the certificate fits (also for an mDL), whether it is an mDL Document Signer, its public sector (QcPSB) data, and the save messages for each category.
affectedCredentialConfigurationsUpload certificate chain, delete certificate chain and create test certificateThe configurations that no longer fit after the change. The change still goes through.

In the dashboard:

  • The field Credential Category is directly below Credential Format in each configuration. For a new PID configuration, the dashboard selects PID only if the selected key fits PID.
  • In the card Credential Signing Key, keys that do not fit are greyed out, with the reason on hover. If the selected key does not fit, a red message below Cryptographic Key Identifier (KID) lists each rule that it misses, and the configuration shows a red status. The dashboard does not show exemptions or information messages here, but the API returns them.
  • For QEAA and PuB-EAA, the configuration shows Added at issuance: category = <urn>. For PuB-EAA, it also shows the law, the country and the authentic source from the certificate of the key.
  • When a none configuration issues a PID and a key that fits PID exists, a hint offers Use the PID category.
  • Key Details shows the key usage type, the categories that the certificate fits and the badge Exempt if the key has an exemption. Once the certificate changes, the keys table shows Exemption does not apply. If a certificate upload breaks a configuration, the dashboard lists the configurations that are affected.