Using Member Keys Stored in HSM

This page explains how members’ identity certificates and encryption keys stored in an HSM can be used with CCF. The following guide describes the usage of Azure Key Vault


It is assumed that CCF members already have access to an existing Azure Key Vault. See these instructions for more details on how to create one. Using the Azure CLI, it is possible to check the list of available Key Vault instances:

$ az keyvault list
# Outputs list of available vaults, including name
$ export VAULT_NAME="<vault_name>"

Certificate and Key Generation

Members’ identity certificates should be generated on the secp384r1 elliptic curve, using the az keyvault certificate create command, with the following identity_cert_policy_example.json policy:

  "issuerParameters": {
    "certificateTransparency": null,
    "name": "Self"
  "keyProperties": {
    "curve": "P-384",
    "exportable": true,
    "keyType": "EC",
    "reuseKey": true
  "lifetimeActions": [
      "action": {
        "actionType": "AutoRenew"
      "trigger": {
        "daysBeforeExpiry": 90
  "secretProperties": {
    "contentType": "application/x-pkcs12"
  "x509CertificateProperties": {
    "keyUsage": ["digitalSignature"],
    "subject": "CN=Member",
    "validityInMonths": 12
$ export IDENTITY_CERT_NAME="<identity-cert-name>"
$ az keyvault certificate create --vault-name $VAULT_NAME -n $IDENTITY_CERT_NAME -p @identity_cert_policy_example.json
# Outputs certificate details

# Corresponding private key is accessible at the same URL (substituting /certificate/ with /key/)
$ az keyvault key show --vault-name $VAULT_NAME --name $IDENTITY_CERT_NAME
# Outputs key information, including kid url

Members’ encryption keys should be RSA 2048 keys, generated with the az keyvault key create command:

$ export ENCRYPTION_KEY_NAME="<encryption-key-name>"
$ az keyvault key create --vault-name $VAULT_NAME --name $ENCRYPTION_KEY_NAME --kty RSA --ops decrypt
# Outputs key details, including kid url

The identity certificate and public encryption key can be downloaded to a PEM file and be passed on to members to be registered in a CCF service as a trusted member identity (see Registering a New Member). Alternatively, if the service has not yet been started, the public member identity can be passed on to operators and registered via the command.start.members configuration entry (see Starting the First Node):

$ az keyvault certificate download --file $IDENTITY_CERT_NAME.pem --vault-name $VAULT_NAME --name $IDENTITY_CERT_NAME
# Downloads PEM identity certificate

$ az keyvault key download --file $ENCRYPTION_KEY_NAME.pem --vault-name $VAULT_NAME --name $ENCRYPTION_KEY_NAME
# Downloads PEM encryption public key

Signing Governance Requests

As the Azure CLI (az keyvault ...) does not currently support signing/verifying, it is required to use the corresponding REST API instead. To do so, it is necessary to create a service principal that will be used for authentication:

$ export SP_NAME="<sp-name>"
$ az ad sp create-for-rbac --name $SP_NAME
# Returns client id (appId), client secret (password)


To retrieve the service principal credentials after its creation, the credentials should be refreshed:

$ az ad sp credential reset --name <app_id>
# Returns client id (appId), updated client secret (password)

Once created, the service principal should be given access to Key Vault in Azure. This can be done through the Azure Portal, under the “Access policies” setting of the vault. The service principal should be given access to the vault with “Sign” key permission. See here for more details.

Then, the following command should be run to retrieve an access token, replacing the values for <appid>, <password> and <tenant> with the service principal credentials:

export AZ_TOKEN=$(curl -X POST -d "grant_type=client_credentials&client_id=<appid>&client_secret=<password>&resource="<tenant>/oauth2/token | jq -r .access_token)

The member’s identity key is now ready to be used for signing governance requests.

COSE Signing


The ccf_cose_sign1* scripts are distributed in the ccf Python package, available on PyPI. It can be installed with pip install ccf.

As an alternative to the ccf_cose_sign1 script when signing offline, CCF provides the ccf_cose_sign1_prepare and ccf_cose_sign1_finish scripts.

ccf_cose_sign1_prepare takes the same arguments as ccf_cose_sign1, minus the signing key, to produce a payload that can be sent to AKV:

# Retrieve the digest to be signed
$ export CREATED_AT=`date -uIs`
$ ccf_cose_sign1_prepare --ccf-gov-msg-type proposal --ccf-gov-msg-created_at $CREATED_AT --content proposal.json --signing-cert $IDENTITY_CERT_NAME.pem > tbs
$ cat tbs
{"alg": "ES384", "value": "dUDKb1pqdi22R3gojLDiK4chPG5it3IaHxNbsuO3APIhlvo7pa16BX7miGPzx7Sy"} # To be signed by AKV

# Retrieve the kid url for the identity key
$ export IDENTITY_AKV_KID=$(az keyvault key show --vault-name $VAULT_NAME --name $IDENTITY_CERT_NAME --query key.kid --output tsv)

# Send the digest to the key management service for signing
$ curl -s -X POST $IDENTITY_AKV_KID/sign?api-version=7.1 --data @tbs -H "Authorization: Bearer ${AZ_TOKEN}" -H "Content-Type: application/json" > signature

Finally, COSE Sign1 payload can be assembled with ccf_cose_sign1_finish:

$ ccf_cose_sign1_finish \
  --ccf-gov-msg-type proposal \
  --ccf-gov-msg-created_at $CREATED_AT \
  --content proposal.json \
  --signing-cert $IDENTITY_CERT_NAME.pem \
  --signature signature > cose_sign1

Like ccf_cose_sign1, the output can be sent directly to the service via curl:

$ ccf_cose_sign1_finish \
  --ccf-gov-msg-type proposal \
  --ccf-gov-msg-created_at $CREATED_AT \
  --content proposal.json \
  --signing-cert $IDENTITY_CERT_NAME.pem \
  --signature signature \
| curl https://<ccf-node-address>/gov/members/proposals:create?api-version=2023-06-01-preview \
  --cacert service_cert.pem \
  --data-binary @- \
  -H "content-type: application/cose"
    "ballotCount": 0,
    "proposalId": "1b7cae1585077104e99e1860ad740efe28ebd498dbf9988e0e7b299e720c5377",
    "proposerId": "d5d7d5fed6f839028456641ad5c3df18ce963bd329bd8a21df16ccdbdbba1eb1",
    "proposalState": "Open"

Recovery Share Decryption

A member can fetch their encrypted recovery share through GET /gov/recovery/encrypted-shares/{memberId} (see Submitting Recovery Shares).

The retrieved encrypted recovery share can be decrypted with the encryption key stored in Key Vault:

$ az keyvault key decrypt --vault-name $VAULT_NAME --name $ENCRYPTION_KEY_NAME --algorithm RSA-OAEP-256 --value <base64_encrypted_share>
# Outputs base64 decrypted share

The decrypted recovery share can then be submitted to the CCF recovered service (see Submitting Recovery Shares).


HTTP request signing could be used in previous versions of CCF, but has been removed as of 4.0, in favour of COSE Sign1.