Security

Cryptographic Key Usage

This section covers the generation and usage of cryptographic keys in the Canton nodes. It assumes that the configuration sets auto-init = true which leads to the generation of the default keys on a node’s startup.

The scope of cryptographic keys covers all Canton-protocol specific keys, private keys for TLS, as well as additional keys required for the domain integrations, e.g., with Besu.

Supported Cryptographic Schemes in Canton

Within Canton we use the cryptographic primitives of signing, symmetric and asymmetric encryption, and MAC with the following supported schemes:

Signing:

  • Ed25519 (default)
  • ECDSA with P-256 and P-384
  • SM2 (experimental)

Symmetric Encryption:

  • AES128-GCM (default)

Asymmetric Encryption:

  • ECIES on P-256 with HMAC-256 and AES128-GCM (default)

MAC:

  • HMAC with SHA-256

Key Generation and storage

Keys are generated in the node and stored in the node’s primary storage. We currently support an early access version of Canton Enterprise that can use an AWS key management system (KMS) to protect Canton’s private keys.

See Secure Cryptographic Private Key Storage for more details on how it is implemented and Key Management Service Setup on how to enable AWS KMS for Canton.

Public Key Distribution using Topology Management

The public keys of the corresponding key pairs that are used for signing and asymmetric encryption within Canton are distributed using Canton’s Topology Management. Specifically, signing and asymmetric encryption public keys are distributed using OwnerToKeyMapping transactions, which associate a node with a public key for either signing or encryption, and NamespaceDelegation for namespace signing public keys.

See Topology Transactions for details on the specific topology transactions in use.

Common Node Keys

Each node provides an Admin API for administrative purposes, which is secured using TLS.

The node reads the private key for the TLS server certificate from a file at startup.

Participant Node Keys

Participant Namespace Signing Key

A Canton participant node spans its own identity namespace, for instance for its own id and the Daml parties allocated on the participant node. The namespace is the hash of the public key of the participant namespace signing key.

The private key is used to sign and thereby authorize all topology transactions for this namespace and this participant, including the following transactions:

  • Root NamespaceDelegation for the new identity namespace of the participant
  • OwnerToKeyMapping for all the public keys that the participant will generate and use (these keys will be explained in the follow-up sections)
  • PartyToParticipant for the parties allocated on this participant
  • VettedPackages for the packages that have been vetted by this participant

Signing Key

In addition to the topology signing key, a participant node will generate another signing key pair that is used for the Canton transaction protocol in the following cases:

  • Sequencer Authentication: Signing the nonce generated by the sequencer as part of its challenge-response authentication protocol. The sequencer verifies the signature with the public key registered for the member in the topology state.
  • Transaction Protocol - The Merkle tree root hash of confirmation requests is signed for a top-level view. - The confirmation responses sent to the mediator are signed as a whole. - The Merkle tree root hash of transfer-in and transfer-out messages is signed.
  • Pruning: Signing of ACS commitments.

Participant Encryption Key

In addition to a signing key pair, a participant node also generates a key pair for encryption based on an asymmetric encryption scheme. A transaction payload is encrypted for a recipient based on the recipient’s public encryption key that is part of the topology state.

See the next section on how a transaction is encrypted using an ephemeral symmetric key.

View Encryption Key

A transaction is composed of multiple views due to sub-transaction privacy. Instead of duplicating each view by directly encrypting the view for each recipient using their participant encryption public key, Canton derives a symmetric key for each view to encrypt that view. The key is derived using a HKDF from a secure seed that is only stored encrypted under the public encryption key of a participants. Thereby, only the encrypted seed is duplicated but not a view.

Ledger API TLS Key

The private key for the TLS server certificate is provided as a file, which can optionally be encrypted and the symmetric decryption key is fetched from a given URL.

Domain Topology Manager Keys

Domain Namespace Signing Key

The domain topology manager governs the namespace of the domain and has a signing key pair for the namespace. The hash of the public key forms the namespace and all entities in the domain (mediator, sequencer, the topology manager itself) may have identities under the domain namespace.

The domain topology manager signs and thereby authorizes the following topology transactions:

  • NamespaceDelegation to register the namespace public key for the new namespace
  • OwnerToKeyMapping to register both its own signing public key (see next section) and the signing public keys of the other domain entities as part of the domain onboarding
  • ParticipantState to enable a new participant on the domain
  • MediatorDomainState to enable a new mediator on the domain

Signing Key

The domain topology manager is not part of the Canton transaction protocol, but it receives topology transactions via the sequencer. Therefore, in addition to the domain namespace, the domain topology manager has a signing key pair, which is registered in the topology state for the topology manager. This signing key is used to perform the challenge-response protocol of the sequencer.

Sequencer Node Keys

Signing Key

The sequencer has a signing key pair that is used to sign all events the sequencer sends to a subscriber.

Ethereum Sequencer

The Ethereum-based sequencer is a client of a Besu node and additional keys are used in this deployment:

  • TLS client certificate and private key to authenticate towards a Besu node if mutual authentication is configured.
  • A Wallet (in BIP-39 or UTC / JSON format), which contains or will result in a signing key pair for Ethereum transactions.

Fabric Sequencer

The Fabric-based sequencer is a Fabric application connecting to an organization’s peer node and the following additional keys are required:

  • TLS client certificate and private key to authenticate towards a Fabric peer node if mutual authentication is required.
  • The client identity’s certificate and private key.

Public API TLS Key

The private key for the TLS server certificate is provided as a file.

Mediator Node Keys

Signing Key

The mediator node is part of the Canton transaction protocol and uses a signing key pair for the following:

  • Sequencer Authentication: Signing of the challenge as part of the sequencer challenge-response protocol.
  • Signing of transaction results, transfer results, and rejections of malformed mediator requests.

Domain Node Keys

The domain node embeds a sequencer, mediator, and domain topology manager. The set of keys remains the same as for the individual nodes.

Canton Console Keys

When the Canton console runs separate from the node and mutual authentication is configured on the Admin API, then the console requires a TLS client certificate and corresponding private key as a file.

Cryptographic Key Management

Rotating Canton Node Keys

Canton supports rotating of node keys (signing and encryption) during live operation through its topology management. In order to ensure continuous operation, the new key is added first and then the previous key is removed.

For participant nodes, domain nodes, and domain topology managers, the nodes can rotate their keys directly using their own identity manager. For sequencer and mediator nodes that are part of a domain, the domain topology manager authorizes the key rotation.

The key rotation requires the following inputs:

  • A console reference to the node owning the key
  • A console reference to the identity manager, which is the same as the node for participants, domain nodes, and domain managers
  • The name of the new key that is being generated
  • The purpose of the key that is rotated

and is done with the following set of commands:

// Get the key owner id from the node id
val owner = node.id match {
  case domainId: DomainId => DomainTopologyManagerId(domainId)
  case owner: KeyOwner => owner
  case unknown =>
    fail(s"Unknown key owner: $unknown")
}

// Find the current key in the identity manager's store
val currentKey = identityManager.topology.owner_to_key_mappings
  .list(filterStore = AuthorizedStore.filterName, filterKeyOwnerUid = owner.filterString)
  .find(x => x.item.owner == owner && x.item.key.purpose == purpose)
  .map(_.item.key)
  .getOrElse(sys.error(s"No key found for owner $owner of purpose $purpose"))

// Generate a new key on the node
val newKey = purpose match {
  case KeyPurpose.Signing => node.keys.secret.generate_signing_key(newKeyName)
  case KeyPurpose.Encryption => node.keys.secret.generate_encryption_key(newKeyName)
}

// Import the generated public key into the identity manager if node and identity manager are separate nodes
if (identityManager != node) {
  identityManager.keys.public
    .upload(newKey.toByteString(testedProtocolVersion), Some(newKeyName))
}

// Rotate the key for the node through the identity manager
identityManager.topology.owner_to_key_mappings.rotate_key(
  owner,
  currentKey,
  newKey,
)

Namespace Intermediate Key Management

Relying on the namespace root key to authorize topology transactions for the namespace is problematic because we cannot rotate the root key without losing the namespace. Instead we can create intermediate keys for the namespace, similar to an intermediate certificate authority, in the following way:

// create a new namespace intermediate key
val intermediateKey = identityManager.keys.secret.generate_signing_key()

// Create a namespace delegation for the intermediate key with the namespace root key
identityManager.topology.namespace_delegations.authorize(
  TopologyChangeOp.Add,
  rootKey.fingerprint,
  intermediateKey.fingerprint,
)

We can rotate an intermediate key by creating a new one and renewing the existing topology transactions that have been authorized with the previous intermediate key. First the new intermediate key has to be created in the same way as the initial intermediate key. To rotate the intermediate key and renew existing topology transactions:

// Renew all active topology transactions that have been authorized by the previous intermediate key with the new intermediate key
identityManager.topology.all.renew(intermediateKey.fingerprint, newIntermediateKey.fingerprint)

// Remove the previous intermediate key
identityManager.topology.namespace_delegations.authorize(
  TopologyChangeOp.Remove,
  rootKey.fingerprint,
  intermediateKey.fingerprint,
)

Moving the Namespace Secret Key to Offline Storage

An identity is ultimately bound to a particular secret key. Owning that secret key gives full authority over the entire namespace. From a security standpoint, it is therefore critical to keep the namespace secret key confidential. This can be achieved by moving the key off the node for offline storage. The identity management system can still be used by creating a new key and an appropriate intermediate certificate. The following steps illustrate how:

// fingerprint of namespace giving key
val participantId = participant1.id
val namespace = participantId.uid.namespace.fingerprint

// create new key
val name = "new-identity-key"
val fingerprint = participant1.keys.secret.generate_signing_key(name = name).fingerprint

// create an intermediate certificate authority through a namespace delegation
// we do this by adding a new namespace delegation for the newly generated key
// and we sign this using the root namespace key
participant1.topology.namespace_delegations.authorize(
  TopologyChangeOp.Add,
  namespace,
  fingerprint,
  signedBy = Some(namespace),
)

// export namespace key to file for offline storage, in this example, it's a temporary file
better.files.File.usingTemporaryFile("namespace", ".key") { privateKeyFile =>
  participant1.keys.secret.download_to(namespace, privateKeyFile.toString)

  // delete namespace key (very dangerous ...)
  participant1.keys.secret.delete(namespace, force = true)

When the root namespace key is required, it can be imported again on the original node or on another, using the following steps:

// import it back wherever needed
other.keys.secret.upload(privateKeyFile.toString, Some("newly-imported-identity-key"))

Identifier Delegation Key Management

Identifier delegations work similar to namespace delegations, however a key is only allowed to operate on a specific identity and not an entire namespace (cf. Topology Transactions).

Therefore the key management for identifier delegations also works the same way as for namespace delegations, where all the topology transactions authorized by the previous identifier delegation key have to be renewed.

Key Management Service Setup

Important

This feature is only available in Canton Enterprise

Canton supports using a Key Management Service (KMS) to increase security of stored private keys. This approach is called ‘envelop encryption’ where: (1) Canton’s private keys are stored in a node’s database in an encrypted form and then (2) upon startup the KMS decrypts these keys for use by Canton. The unencrypted keys are stored in memory so this approach increases security without impacting performance. This is a common approach used by KMS vendors; using a symmetric encryption key, called the KMS wrapper key, to encrypt and decrypt the stored, private keys.

The KMS integration is currently only enabled for Amazon Web Services (AWS) KMS in Canton Enterprise. Other KMS integration options (e.g., Google Cloud Provider (GCP) KMS or other on-premise solutions) will be supported in the future.

KMS support can be enabled for a new installations (i.e., during the node bootstrap) or for an existing deployment that is transparently updated to use KMS. When the KMS is enabled after a node has been running, the keys are transparently encrypted and stored in this encrypted form in the Canton node’s database.

Note: AWS KMS keys need to live as long as the Canton database backups that store them, so care must be taken when deleting database backup files or KMS keys. Otherwise, a Canton node restored from a database backup may try to decrypt a Canton key from the KMS and be unable to do so because that KMS key was deleted.

Canton Configuration for Encrypted Private Key Storage

Like other Canton capabilities, KMS integration is enabled within a Canton node’s configuration file. In the example below, the encrypted private key integration is enabled for a participant node (called participant1).

The most important setting that enables encrypted private key storage using a KMS is ‘’type = kms’’. This is shown below. If this is not specified, Canton stores the keys using its default approach, which is in unencrypted form.

canton.participants.participant1.crypto.private-key-store.encryption.type = kms

There are two ways to choose the KMS wrapper key: (1) use an already existing KMS key or; (2) let Canton generate one. To use an already existing KMS key, you must specify its identifier. For example, for AWS KMS this can be one of the following:

  • Key id: “1234abcd-12ab-34cd-56ef-1234567890ab”
  • Key ARN (Amazon Resource Name): “arn:aws:kms:us-east-1:1234abcd-12ab-34cd-56ef-1234567890ab”
  • Key alias: “alias/test-key”

Please be aware that an AWS KMS key needs to be configured with the following settings:

If no wrapper-key-id is specified Canton creates a symmetric key in the KMS. After subsequent restarts the operator does not need to specify the identifier for the newly created key; Canton stores the generated wrapper key id in the database.

An example with a pre-defined KMS key is shown below:

canton.participants.participant1.crypto.private-key-store.encryption.wrapper-key-id = alias/canton-kms-test-key

The KMS configuration has additional parameters that can be specified:

canton.participants.participant1.crypto.kms {
    type = aws
    region = us-east-1
    multi-region-key = false # optional, default is false
}
  • type specifies which KMS to use: currently only aws is supported.

Specific to AWS:

  • region specifies which region the AWS KMS is bound to.
  • multi-region-key flag enables the replication of keys for the KMS wrapper keys that Canton automatically creates. With replication turned on, the operator can replicate a key from one region to another (Note: replication of a key is not done automatically by Canton) and change the region configured in Canton at a later point in time without any other key rotation required. The standard single-region approach is applicable for most scenarios.

An example configuration that puts it all together is below:

canton.participants.participant1.crypto.private-key-store.encryption.type = kms
canton.participants.participant1.crypto.private-key-store.encryption.wrapper-key-id = alias/canton-kms-test-key
canton.participants.participant1.crypto.kms {
    type = aws
    region = us-east-1
    multi-region-key = false
}

Configure AWS Credentials and Permissions

The AWS KMS needs to be configured with the following list of authorized actions (i.e. IAM permissions):

  • “kms:CreateKey”
  • “kms:ScheduleKeyDeletion”
  • “kms:Encrypt”
  • “kms:Decrypt”
  • “kms:ListKeys”
  • “kms:ListAliases”
  • “kms:DescribeKey”

Canton uses the standard AWS credential access to be able to make the API calls to the AWS KMS. For example, the standard environment variables of AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY can be used. Alternatively, you can specify an AWS profile file (e.g. use a temporary access profile credentials - sts). The protection and rotation of the credentials for accessing AWS is a responsibility of the node operator.

Revert Encrypted Private Key Storage

If ever you wish to change the encrypted crypto private key store and revert back to using an unencrypted store, you must restart the nodes with an updated configuration that includes

canton.participants.participant1.crypto.private-key-store.encryption.reverted = true # default is false

Warning

We strongly advise against this as it will force Canton to decrypt its private keys and store them in clear.

For subsequent restarts we recommend deleting all encrypted crypto private key store configurations including the KMS one.

Manual wrapper key rotation

Currently AWS KMS offers a yearly automatic KMS key rotation. Canton extends this by enabling node administrators to manually rotate the AWS KMS wrapper key using the following command:

participant1.keys.secret.rotate_wrapper_key(newWrapperKeyId)

You can optionally pass a wrapper key id to change to or let Canton generate a new key based on the current KMS configuration. If you wish to change the key specification (e.g. enable multi region) you are required to update the configuration before rotating the wrapper key.

Auditability

AWS provides tools to monitor KMS keys. To set automatic external logging, refer to the official documentation. This includes instructions on how to set AWS Cloud Trail or Cloud Watch Alarms to keep track of usage of KMS keys. Errors resulting from the use of the wrapper key (i.e., during encryption and decryption) are logged in Canton.

Ledger-API Authorization

The Ledger Api provides authorization support using JWT tokens. While the JWT token authorization allows third party applications to be authorized properly, it poses some issues for Canton internal services such as the PingService or the DarService, which are used to manage domain wide concerns. Therefore Canton generates a new admin bearer token (64 bytes, randomly generated, hex-encoded) on each startup, which is communicated to these services internally and used by these services to authorize themselves on the Ledger Api. The admin token allows to act as any party registered on that participant node.

The admin token is only used within the same process. Therefore, in order to obtain this token, an attacker needs to be able to either dump the memory or capture the network traffic, which typically only a privileged user can do.

It is important to enable TLS together with JWT support in general, as otherwise tokens can be leaked to an attacker that has the ability to inspect network traffic.