- Overview
- Tutorials
- How Tos
- Download
- Install
- Configure
- Secure
- TLS API Configuration
- Configure API Authentication and Authorization with JWT
- Configure API Limits
- Set Resource Limits
- Crypto key management
- Restrict key usage
- Namespace Key Management
- Key management service (KMS) configuration
- Optimize
- Observe
- Operate
- Initializing node identity manually
- Canton Console
- Synchronizer connections
- High Availability Usage
- Manage Daml packages and archives
- Participant Node pruning
- Party Management
- Party Replication
- Decentralized party overview
- Setup an External Party
- Ledger API User Management
- Node Traffic Management
- Identity Management
- Upgrade
- Decommission
- Recover
- Troubleshoot
- Explanations
- Reference
Note
This page is a work in progress. It may contain incomplete or incorrect information.
Namespace Key Management¶
Online Root Namespace Key¶
By default, the Canton node creates a root namespace key during the initialization of the node. Operators can manually create an intermediate key on the node to authorize topology transactions. Only the intermediate key can be changed. Therefore, it is important to keep the root key secure.
If a Canton node is configured to use a KMS system, then the root key can be administratively isolated, using the KMS Administration controls to remove node access to the root key. As the root key is only required when authorizing a new intermediate key or revoking an existing authorization, it is sufficient to just selectively allow the node to access the key only whenever such operations are performed.
Only the root key can be isolated. All the other keys are essential for Canton to operate correctly. Optionally, the intermediate key can also be isolated but will have to be turned on and off as needed during administrative operations such as uploading DARs or adding parties.
Offline Root Namespace Key¶
A more secure way to manage the root namespace key is to use an offline root namespace key. In this case, the root namespace key is stored in a secure, possibly air gapped location, inaccessible from the Canton node.
Where the root key is stored depends on your organization’s security requirements. For the remainder of this guide, assume that there are two sites, the online site of the node with its intermediate key and the offline site of the root key. The offline root key is used to sign a delegation to the node’s intermediate key, which is then used to authorize topology transactions.
The offline root key procedure is supported by a set of utility scripts, which can be found in the Canton scripts
directory (scripts/offline-root-key
).
Channel for Key Exchange¶
There must be a channel or method which allows to exchange the public intermediate key and the signed intermediate namespace delegations between the online and offline sites. The channel must be trusted in terms of authenticity, but not confidentiality, as no secret information is exchanged. This means you need to ensure that the data is not tampered with during transport, but the data itself does not need to be encrypted.
This can be done using multiple methods, depending on whether the sites are air-gapped or connected through a network. Possible examples are: secure file transfer, QR codes, physical storage medium.
Assuming that such a trusted channel exists, the following steps are required to set up the offline root namespace key:
1. Configure Node To Expect External Root Key¶
Before the first start-up, the Canton node must be configured not to initialize automatically. This is done by setting
Manual init config¶
participant1.init.identity.type = manual
The node can then be started with this configuration. It starts the Admin API, but halts the startup process and wait for the initialization of the node identity together with the necessary topology transactions.
2. Export Public Key of Node¶
Assuming you have access to the remote console of the node, create a new signing key to use as the intermediate key:
@ val key = participant1.keys.secret.generate_signing_key(name = "NamespaceDelegation", usage = com.digitalasset.canton.crypto.SigningKeyUsage.NamespaceOnly)
key : SigningPublicKey = SigningPublicKey(
id = 122001e9b2c9...,
format = DER-encoded X.509 SubjectPublicKeyInfo,
keySpec = EC-Curve25519,
usage = namespace
)
@ val intermediateKeyPath = better.files.File.newTemporaryFile(prefix = "intermediate-key.pub").pathAsString
intermediateKeyPath : String = "/tmp/intermediate-key.pub6141875231608017386"
@ participant1.keys.public.download_to(key.id, intermediateKeyPath)
This creates a file with the public key of the intermediate key.
The supported key specifications are listed in the follow protobuf definition:
Signing key specifications¶
enum SigningKeySpec {
SIGNING_KEY_SPEC_UNSPECIFIED = 0;
// Elliptic Curve Key from Curve25519
// as defined in http://ed25519.cr.yp.to/
SIGNING_KEY_SPEC_EC_CURVE25519 = 1;
// Elliptic Curve Key from the NIST P-256 curve (aka secp256r1)
// as defined in https://doi.org/10.6028/NIST.FIPS.186-4
SIGNING_KEY_SPEC_EC_P256 = 2;
// Elliptic Curve Key from the NIST P-384 curve (aka secp384r1)
// as defined in https://doi.org/10.6028/NIST.FIPS.186-4
SIGNING_KEY_SPEC_EC_P384 = 3;
// Elliptic Curve Key from SECG P256k1 curve (aka secp256k1)
// commonly used in bitcoin and ethereum
// as defined in https://www.secg.org/sec2-v2.pdf
SIGNING_KEY_SPEC_EC_SECP256K1 = 4;
}
The synchronizer the participant node intends to connect to might restrict further the list of supported key specifications. To obtain this information from the synchronizer directly, run the following command on the synchronizer Public API.
grpcurl -d '{}' <sequencer_endpoint> com.digitalasset.canton.sequencer.api.v30.SequencerConnectService/GetSynchronizerParameters
If a console is not accessible, you can use either a bootstrap script or grpccurl
against the Admin API to
invoke the commands.
4. Generate Root Key and The Root Certificate¶
Using OpenSSL¶
Ensure that the necessary scripts are available on the secure site. These scripts are included in the Canton release
packages at scripts/offline-root-key
.
An example demonstrating usage of those scripts using openssl
to generate keys and sign certificates is available at examples/10-offline-root-namespace-init
.
Run the next set of commands from the examples/10-offline-root-namespace-init
directory.
Start by initializing variables used in the snippets below
Script variables¶
# Points to the location of the offline root key scripts under the scripts/offline-root-key directory in the release artifact
SCRIPTS_ROOT="$(dirname "$0")/../../scripts/offline-root-key"
PRIVATE_KEY="$OUTPUT_DIR/root_private_key.der"
PUBLIC_KEY="$OUTPUT_DIR/root_public_key.der"
ROOT_NAMESPACE_PREFIX="$OUTPUT_DIR/root_namespace"
INTERMEDIATE_NAMESPACE_PREFIX="$OUTPUT_DIR/intermediate_namespace"
mkdir -p tmp/certs
OUTPUT_DIR="tmp/certs"
CANTON_NAMESPACE_DELEGATION_PUB_KEY=<Path to the intermediate key downloaded from Canton. ``intermediateKeyPath`` in this example>
As well as setting the path to the protobuf image containing the required protobuf definitions to generate certificates.
Protobuf paths¶
CURRENT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" > /dev/null && pwd)
export BUF_PROTO_IMAGE="$CURRENT_DIR/../../scripts/offline-root-key/root_namespace_buf_image.bin"
source "$CURRENT_DIR/../../scripts/offline-root-key/utils.sh"
Then, generate the root key in the secure environment and extract the public key:
Generate key pair with openssl¶
openssl ecparam -name prime256v1 -genkey -noout -outform DER -out "$PRIVATE_KEY"
openssl ec -inform der -in "$PRIVATE_KEY" -pubout -outform der -out "$PUBLIC_KEY" 2> /dev/null
Then, create the self-signed root namespace delegation, which is effectively a self-signed certificate used as the trust anchor of the given namespace:
Prepare root cert¶
"$SCRIPTS_ROOT/prepare-certs.sh" --root-delegation --root-pub-key "$PUBLIC_KEY" --target-pub-key "$PUBLIC_KEY" --output "$ROOT_NAMESPACE_PREFIX"
Note that the root public key must be in the x509 SPKI DER
format.
This generates two files, root-delegation.prep
and root-delegation.hash
.
.prep
files contain unsigned topology transactions serialized to bytes.
If you really want to be sure what you are signing, inspect the prepare-certs.sh
script to see how it generates the topology transaction and how it computes
the hash. Next, the hash needs to be signed.
If you are using openssl, the following command can be used to sign the hash:
Sign root cert¶
openssl pkeyutl -rawin -inkey "$PRIVATE_KEY" -keyform DER -sign < "$ROOT_NAMESPACE_PREFIX.hash" > "$ROOT_NAMESPACE_PREFIX.signature"
Finally, assemble the signature and the prepared transaction:
Assemble root cert¶
"$SCRIPTS_ROOT/assemble-cert.sh" --prepared-transaction "$ROOT_NAMESPACE_PREFIX.prep" --signature "$ROOT_NAMESPACE_PREFIX.signature" --signature-algorithm ecdsa256 --output "$ROOT_NAMESPACE_PREFIX.cert"
This creates a so-called signed topology transaction.
Using GCP KMS¶
If you are using GCP KMS, you can use KMS CLI (https://cloud.google.com/kms/docs/create-validate-signatures) with the following commands to generate the key:
gcloud kms keyrings create key-ring --location location (APPROXIMATE)
And the following command can be used to generate the signature:
gcloud kms asymmetric-sign \
--version key-version \
--key <root-key> \
--keyring key-ring \
--location location \
--digest-algorithm digest-algorithm \
--input-file root-delegation.prep \
--signature-file root-delegation.signature
5. Create the Intermediate Certificate¶
If the root key and the self-signed root delegation are available, you can create the intermediate certificate. The
steps are very similar to the root certificate, but the target is the public key of the intermediate key,
and the --intermediate-delegation
flag is used instead of --root-delegation
.
Prepare delegation cert¶
"$SCRIPTS_ROOT/prepare-certs.sh" --intermediate-delegation --root-pub-key "$PUBLIC_KEY" --canton-target-pub-key "$CANTON_NAMESPACE_DELEGATION_PUB_KEY" --output "$INTERMEDIATE_NAMESPACE_PREFIX"
Verify that the generated topology transaction (printed to stdout) is correct and refers to the correct keys. Once verified, the generated hash needs to be signed:
Sign delegation cert¶
openssl pkeyutl -rawin -inkey "$PRIVATE_KEY" -keyform DER -sign < "$INTERMEDIATE_NAMESPACE_PREFIX.hash" > "$INTERMEDIATE_NAMESPACE_PREFIX.signature"
Again, the signature and the prepared transaction can be assembled:
Assemble delegation cert¶
"$SCRIPTS_ROOT/assemble-cert.sh" --prepared-transaction "$INTERMEDIATE_NAMESPACE_PREFIX.prep" --signature "$INTERMEDIATE_NAMESPACE_PREFIX.signature" --signature-algorithm ecdsa256 --output "$INTERMEDIATE_NAMESPACE_PREFIX.cert"
7. Copy the Certificates to the Online Site¶
The generated certificates (never the root private key) need to be transferred to the online site. The public keys are included in the certificates and don’t need to be transported separately. You need to transfer both certificates, the root delegation and the intermediate delegation, to the online site.
8. Import the Certificates to the Node¶
On the target site, import the certificates into the waiting node using the console command
@ participant1.topology.init_id(identifier = "participant1", delegationFiles = Seq("tmp/certs/root_namespace.cert", "tmp/certs/intermediate_namespace.cert"))
@ participant1.health.status
res5: NodeStatus[ParticipantStatus] = Participant id: PAR::participant1::12203ea6d3bd5305e1f96503d2f5e8f0f77d104298443fff6b5752b8205644532de9
Uptime: 0.069676s
Ports:
ledger: 30022
admin: 30023
Connected synchronizers: None
Unhealthy synchronizers: None
Active: true
Components:
memory_storage : Ok()
connected-synchronizer : Not Initialized
sync-ephemeral-state : Not Initialized
sequencer-client : Not Initialized
acs-commitment-processor : Not Initialized
Version: 3.4.0-SNAPSHOT
Supported protocol version(s): 34, dev
Alternatively, the Admin API can be used directly via grpccurl
to initialize the node.
Pre-Generated Certificates¶
The certificates can also be provided directly via the node’s configuration file if they’ve been generated beforehand.
In this scenario, instead of generating the intermediate key via the node’s generate_signing_key
command as described above,
they key must be generated on a KMS and its public key material downloaded. The same scripts can then be used to generate the certificate,
with the exception that the intermediate public key will not be in the Canton format but in a DER format and should therefore be set with --target-pub-key
.
Once the certificates are available, you must register the intermediate KMS key by running:
val intermediateKey = node.keys.secret
.register_kms_signing_key(
intermediateNsKmsKeyId,
SigningKeyUsage.NamespaceOnly,
name = s"${node.name}-${SigningKeyUsage.Namespace.identifier}",
)
// user-manual-entry-begin: ManualRegisterKmsNamespaceKey
node.crypto.cryptoPrivateStore
.existsPrivateKey(intermediateKey.id, Signing)
.valueOrFail("intermediate key not registered")
.futureValueUS
// user-manual-entry-begin: ManualRegisterKmsKeys
// Register the KMS signing key used to define the node identity.
val namespaceKey = node.keys.secret
.register_kms_signing_key(
namespaceKmsKeyId,
SigningKeyUsage.NamespaceOnly,
name = s"${node.name}-${SigningKeyUsage.Namespace.identifier}",
)
// Register the KMS signing key used to authenticate the node toward the Sequencer.
val sequencerAuthKey = node.keys.secret
.register_kms_signing_key(
sequencerAuthKmsKeyId,
SigningKeyUsage.SequencerAuthenticationOnly,
name = s"${node.name}-${SigningKeyUsage.SequencerAuthentication.identifier}",
)
// Register the signing key used to sign protocol messages.
val signingKey = node.keys.secret
.register_kms_signing_key(
signingKmsKeyId,
SigningKeyUsage.ProtocolOnly,
name = s"${node.name}-${SigningKeyUsage.Protocol.identifier}",
)
// Register the encryption key.
val encryptionKey = node.keys.secret
.register_kms_encryption_key(encryptionKmsKeyId, name = node.name + "-encryption")
// user-manual-entry-end: ManualRegisterKmsKeys
(namespaceKey, sequencerAuthKey, signingKey, encryptionKey)
} else {
// architecture-handbook-entry-begin: ManualInitKeys
// Create a signing key used to define the node identity.
val namespaceKey =
node.keys.secret
.generate_signing_key(
name = node.name + s"-${SigningKeyUsage.Namespace.identifier}",
SigningKeyUsage.NamespaceOnly,
)
// Create a signing key used to authenticate the node toward the Sequencer.
val sequencerAuthKey =
node.keys.secret.generate_signing_key(
name = node.name + s"-${SigningKeyUsage.SequencerAuthentication.identifier}",
SigningKeyUsage.SequencerAuthenticationOnly,
)
// Create a signing key used to sign protocol messages.
val signingKey =
node.keys.secret
.generate_signing_key(
name = node.name + s"-${SigningKeyUsage.Protocol.identifier}",
SigningKeyUsage.ProtocolOnly,
)
// Create the encryption key.
val encryptionKey =
node.keys.secret.generate_encryption_key(name = node.name + "-encryption")
// architecture-handbook-entry-end: ManualInitKeys
(namespaceKey, sequencerAuthKey, signingKey, encryptionKey)
}
// architecture-handbook-entry-begin: ManualInitNode
// Use the fingerprint of this key for the node identity.
val namespace = Namespace(namespaceKey.id)
node.topology.init_id_from_uid(
UniqueIdentifier.tryCreate("manual-" + node.name, namespace)
)
// Wait until the node is ready to receive the node identity.
node.health.wait_for_ready_for_node_topology()
// Create the self-signed root certificate.
node.topology.namespace_delegations.propose_delegation(
namespace,
namespaceKey,
CanSignAllMappings,
)
// Assign the new keys to this node.
node.topology.owner_to_key_mappings.propose(
OwnerToKeyMapping(
node.id.member,
NonEmpty(Seq, sequencerAuthKey, signingKey, encryptionKey),
),
signedBy = Seq(namespaceKey.fingerprint, sequencerAuthKey.fingerprint, signingKey.fingerprint),
)
// architecture-handbook-entry-end: ManualInitNode
}
}
and then import the certificates to the node.
This configuration directive has no effect once the node is initialized and can subsequently be removed.
Delegation Restrictions¶
You can further restrict the kind of topology transactions a delegation can authorize.
The prepare-certs
script exposes a --delegation-restrictions
flag for that purpose.
Prepare delegation with signing restrictions¶
"$SCRIPTS_ROOT/prepare-certs.sh" --delegation-restrictions PARTY_TO_PARTICIPANT,PARTY_TO_KEY_MAPPING --root-pub-key "$PUBLIC_KEY" --canton-target-pub-key "$CANTON_RESTRICTED_PUB_KEY" --output "$RESTRICTED_KEY_NAMESPACE_PREFIX"
The delegation can then be signed and assembled as before. Once the signed certificate is available, load it onto the node:
Load restricted key certificate onto node¶
participant1.topology.transactions.load_single_from_file(s"$opensslKeysDirectory/restricted_key_namespace.cert", TopologyStoreId.Authorized)
Rotate the Intermediate Key¶
Create new Intermediate Key¶
In order to create another intermediate key, we follow the same steps as before. Create the key on the online site and export it.
Follow the same steps to create a new intermediate delegation for the new intermediate key:
copy to secure site
generate the intermediate delegation (skip self-signed root delegation as it has already been generated)
copy the certificate to the node site
The new intermediate delegation can then be imported into the node as shown here.
Once the new delegation has been imported, the old intermediate key can be revoked.
Revoking the Intermediate Key¶
To revoke the intermediate key, the root key needs to be used to sign a revocation transaction. The revocation transaction is prepared in the same way as the intermediate delegation:
Prepare revocation certificate¶
"$SCRIPTS_ROOT/prepare-certs.sh" --revoke-delegation "$CANTON_NAMESPACE_DELEGATION_TO_REVOKE" --output "$REVOKED_DELEGATION_PREFIX"
The generated hash needs to be signed and then subsequently assembled into a certificate:
Assemble revocation certificate¶
openssl pkeyutl -rawin -inkey "$PRIVATE_KEY" -keyform DER -sign < "$REVOKED_DELEGATION_PREFIX.hash" > "$REVOKED_DELEGATION_PREFIX.signature"
On the node site, the revocation certificate can be imported using:
Load revocation certificate onto node¶
participant1.topology.transactions.load_single_from_file(s"$opensslKeysDirectory/revoked_delegation.cert", TopologyStoreId.Authorized)
Rotating the Root Namespace Key¶
You cannot rotate the root namespace key. If you need to discontinue the usage of the namespace, you need to create a new namespace, new parties and participants in that new namespace, and transfer the contracts to the new parties.