// 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.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]
/** 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)