Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
231 changes: 224 additions & 7 deletions docs-main/global-synchronizer/reference/kms-driver-guide.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,20 @@ trait DriverFactory extends api.DriverFactory {
`v1.KmsDriverFactory` is a specialization of the generic DriverFactory which defines the driver type as a KmsDriver and the API version as 1.

```scala
// Copyright (c) 2025 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package com.digitalasset.canton.crypto.kms.driver.api
package com.digitalasset.canton.crypto.kms.driver.api.v1

import com.digitalasset.canton.crypto.kms.driver.api
import com.digitalasset.canton.driver.api.v1

trait KmsDriverFactory extends api.KmsDriverFactory with v1.DriverFactory {

import com.digitalasset.canton.driver.api.DriverFactory
override val version: Int = 1

trait KmsDriverFactory extends DriverFactory {
override type Driver <: KmsDriver

}
```

Expand Down Expand Up @@ -143,12 +148,224 @@ The API is designed as an asynchronous API using Futures. An OpenTelemetry trace
Concretely the Scala interface is defined as the following:

```scala
// Copyright (c) 2025 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package com.digitalasset.canton.crypto.kms.driver.api
package com.digitalasset.canton.crypto.kms.driver.api.v1

import com.digitalasset.canton.crypto.kms.driver.api
import io.opentelemetry.context.Context

import scala.concurrent.Future

/** The interface for a pluggable KMS implementation, that is, a KMS Driver.
*
* Cryptographic operations are asynchronous, i.e., they return a Future. In case of failure, the
* Future must fail with a [[KmsDriverException]]. Transient failures should still fail the Future,
* but the exception's `retryable` flag should be set to true. An exception should only be thrown
* for driver operations (e.g., sign), and not, for example, for health checks.
*
* Each KMS operation takes an OpenTelemetry [[io.opentelemetry.context.Context]] as a trace
* context that can optionally be propagated to the external KMS.
*/
trait KmsDriver extends api.KmsDriver with AutoCloseable {

require(
supportedSigningKeySpecs.nonEmpty,
"Supported signing key specifications must not be empty.",
)
require(
supportedSigningAlgoSpecs.nonEmpty,
"Supported signing algorithm specifications must not be empty.",
)
require(
supportedEncryptionKeySpecs.nonEmpty,
"Supported encryption key specifications must not be empty.",
)
require(
supportedEncryptionAlgoSpecs.nonEmpty,
"Supported encryption algorithm specifications must not be empty.",
)

/** Returns the current health of the driver. The driver should not throw an exception; instead,
* it should return a [[com.digitalasset.canton.crypto.kms.driver.api.v1.KmsDriverHealth]] value.
*
* @return
* A future that completes with the driver's health.
*/
def health: Future[KmsDriverHealth]

/** The supported signing key specifications by the driver. This must not be empty. */
def supportedSigningKeySpecs: Set[SigningKeySpec]

/** The supported signing algorithm specifications by the driver. This must not be empty. */
def supportedSigningAlgoSpecs: Set[SigningAlgoSpec]

/** The supported encryption key specifications by the driver. This must not be empty. */
def supportedEncryptionKeySpecs: Set[EncryptionKeySpec]

/** The supported encryption algorithm specifications by the driver. This must not be empty. */
def supportedEncryptionAlgoSpecs: Set[EncryptionAlgoSpec]

/** Generate a new signing key pair.
*
* @param signingKeySpec
* The key specification for the new signing key pair. The caller ensures it is a
* [[supportedSigningKeySpecs]].
* @param keyName
* An optional descriptive name for the key pair, max 300 characters long.
*
* @return
* A future that completes with the unique KMS key identifier, max 300 characters long.
*/
def generateSigningKeyPair(
signingKeySpec: SigningKeySpec,
keyName: Option[String],
)(traceContext: Context): Future[String]

/** Generate a new asymmetric encryption key pair.
*
* @param encryptionKeySpec
* The key specification of the new encryption key pair. The caller ensures it is a
* [[supportedEncryptionKeySpecs]].
* @param keyName
* An optional descriptive name for the key pair, max 300 characters long.
*
* @return
* A future that completes with the unique KMS key identifier, max 300 characters long.
*/
def generateEncryptionKeyPair(
encryptionKeySpec: EncryptionKeySpec,
keyName: Option[String],
)(traceContext: Context): Future[String]

/** Generate a new symmetric encryption key. The default symmetric key specification of the KMS is
* used.
*
* @param keyName
* An optional descriptive name for the symmetric key, max 300 characters long.
*
* @return
* A future that completes with the unique KMS key identifier, max 300 characters long.
*/
def generateSymmetricKey(keyName: Option[String])(traceContext: Context): Future[String]

/** Sign the given data using the private key identified by the keyId with the given signing
* algorithm specification. If the `algoSpec` is not compatible with the key spec of `keyId` then
* this method must fail with a non-retryable exception.
*
* @param data
* The data to be signed with the specified signature algorithm. The upper bound of the data
* size is 4kb.
* @param keyId
* The identifier of the private signing key.
* @param algoSpec
* The signature algorithm specification. The caller ensures it is a
* [[supportedSigningAlgoSpecs]].
*
* @return
* A future that completes with the signature.
*/
def sign(data: Array[Byte], keyId: String, algoSpec: SigningAlgoSpec)(
traceContext: Context
): Future[Array[Byte]]

/** Asymmetrically decrypt the given ciphertext using the private key identified by the keyId with
* the given asymmetric encryption algorithm specification. If the `algoSpec` is not compatible
* with the key spec of `keyId` then this method must fail with a non-retryable exception.
*
* @param ciphertext
* The asymmetrically encrypted ciphertext that needs to be decrypted. The length of the
* ciphertext depends on the parameters of the asymmetric encryption algorithm. Implementations
* may assume that the length of the ciphertext is at most 6144 bytes in any case.
* @param keyId
* The identifier of the private encryption key to perform the asymmetric decryption with.
* @param algoSpec
* The asymmetric encryption algorithm specification. The caller ensures it is a
* [[supportedEncryptionAlgoSpecs]].
*
* @return
* A future that completes with the plaintext.
*/
def decryptAsymmetric(
ciphertext: Array[Byte],
keyId: String,
algoSpec: EncryptionAlgoSpec,
)(traceContext: Context): Future[Array[Byte]]

/** Symmetrically encrypt the given plaintext using the symmetric encryption key identified by the
* keyId. The same/default symmetric encryption algorithm of the KMS must be used for both
* symmetric encryption and decryption.
*
* @param data
* The plaintext to symmetrically encrypt. The upper bound of the data size is 4kb.
* @param keyId
* The identifier of the symmetric encryption key.
*
* @return
* A future that completes with the ciphertext.
*/
def encryptSymmetric(data: Array[Byte], keyId: String)(traceContext: Context): Future[Array[Byte]]

/** Symmetrically decrypt the given ciphertext using the symmetric encryption key identified by
* the keyId. The same/default symmetric encryption algorithm of the KMS must be used for both
* symmetric encryption and decryption.
*
* @param ciphertext
* The ciphertext to symmetrically decrypt. The upper bound of the ciphertext size is 6144
* bytes.
* @param keyId
* The identifier of the symmetric encryption key.
*
* @return
* A future that completes with the plaintext.
*/
def decryptSymmetric(ciphertext: Array[Byte], keyId: String)(
traceContext: Context
): Future[Array[Byte]]

/** Exports a public key from the KMS for the given key pair identified by keyId.
*
* @param keyId
* The identifier of the key pair.
*
* @return
* A future that completes with the exported [[PublicKey]]
*/
def getPublicKey(keyId: String)(traceContext: Context): Future[PublicKey]

/** Asserts that the key given by its identifier exists and is active.
*
* @param keyId
* The identifier of the key to be checked.
*
* @return
* A future that completes successfully if the key exists and is active. Otherwise, the future
* must have been failed.
*/
def keyExistsAndIsActive(keyId: String)(traceContext: Context): Future[Unit]

trait KmsDriver
/** Deletes a key given by its identifier from the KMS.
*
* @param keyId
* The identifier of the key to be deleted.
*
* @return
* A future that completes when the key has been deleted or the deletion of the key has been
* scheduled.
*/
def deleteKey(keyId: String)(traceContext: Context): Future[Unit]

}

/** A public key exported from the KMS.
*
* @param key
* The DER-encoded X.509 public key (SubjectPublicKeyInfo). EC keys must be uncompressed.
* @param spec
* The key specification of the key pair
*/
final case class PublicKey(key: Array[Byte], spec: KeySpec)
```

### Error Handling and Health
Expand Down