Skip to main content

Distributed key management service

Advanced
vetKeys
Key manager

vetKeys can be used to facilitate a distributed key management service (DKMS).

vetKeys obtained from ICP are only known to the client that requested them, therefore they can be used by the client for different purposes such as encryption, signing, and further key derivation. These keys can be shared with other users by adding corresponding user permissions in the canister.

Below is an example implementation of DKMS in the KeyManager library, which offers access control and vetKey derivation.

Why use KeyManager?

It is a ready-to-use solution that offers:

  • Simple, cleartext frontend APIs for key derivation. No need to define or manage your own cryptographic primitives.

  • Deterministic key generation. Built on the strong randomness of vetKeys.

  • Built-in access control for keys. No peer-to-peer interaction required. Parties receiving access permissions don’t need to be online during the process.

  • Enhanced security when fetching keys on demand. Avoids persisting sensitive keys locally, reducing the risk of key compromise.

How does KeyManager work?

A KeyManager-enabled canister determines access control for vetKeys. It controls what vetKeys a user owns and what vetKeys are shared with other users, including specific access rights (e.g., whether an added user can further share the vetKey). Furthermore, access control in KeyManager is flexible as well, such that it can be extended to further restrict access to vetKeys based on time.

Every vetKey identity in KeyManager is defined by the vetKey owner's principal and an arbitrary byte string. This separates the domains from which users can derive vetKeys, while still allowing them to derive an arbitrary number of vetKeys.

The KeyManager provides both frontend and backend libraries that are designed to be used together. The backend KeyManager library provides a component that should be added to the backend canister. The frontend KeyManager library provides a way to interact with the backend KeyManager component that lives inside the backend canister. The developer's role in the implementation is to define which canister APIs need to be called and how, enabling the frontend library to communicate effectively with the backend component.

KeyManager handles the following:

  • Retrieve and decrypt vetKeys: Securely fetch encrypted vetKeys and decrypt them locally using a transport secret key. The frontend KeyManager API abstracts away this process, returning just the decrypted vetKey in the frontend.

  • Retrieve vetKey verification key: Obtain the public verification key used to validate encrypted vetKeys.

  • Access shared key information: Query which vetKeys a user has access permissions to.

  • Manage key access: Assign, update, or revoke user access rights for stored vetKeys.

How to use KeyManager?

To set up KeyManager for your dapp, you need to instantiate the backend KeyManager component in your canister and configure how your frontend will communicate with it.

In the backend, you can define logic so that when specific canister APIs are called, the canister delegates those calls to the corresponding methods on a KeyManager instance. For example, when the set_user_rights API of a canister is invoked, the canister internally calls the set_user_rights method on its KeyManager.

For reference, see the default KeyManager canister implementation.

In the frontend, you can define that when the set_user_rights method of the KeyManager is called, it triggers a call to the corresponding canister API set_user_rights. This is done by implementing the KeyManagerClient interface.

For an example implementation, see DefaultKeyManagerClient, which connects the frontend logic to the canister APIs.

Step 1: Instantiate the KeyManager component in a canister

Below is an example of how to instantiate the backend KeyManager component inside a canister.

backend/rs/canisters/ic_vetkeys_manager_canister/src/lib.rs
loading...

Step 2: Define backend canister APIs for the frontend to communicate with

Below is an example of how to create a backend canister API that allows the frontend to interact with the set_user_rights method of the KeyManager component. You can follow a similar pattern to expose additional methods of the KeyManager through canister APIs.

backend/rs/canisters/ic_vetkeys_manager_canister/src/lib.rs
loading...

Step 3: Implement KeyManagerClient in the frontend

Below is an example of how to provide a KeyManagerClient implementation to interact with the backend KeyManager component’s set_user_rights method. Implementations for other methods of KeyManager can be added in a similar fashion.

frontend/ic_vetkeys/src/key_manager/key_manager_canister.ts
loading...

Step 4: Use KeyManager in the frontend

Your application's frontend can communicate with a backend canister that exposes KeyManager API methods using the following TypeScript code:

import { KeyManager } from "ic_vetkeys/key_manager";

// Initialize the KeyManager
const keyManager = new KeyManager(keyManagerClientInstance);

// Retrieve shared keys
const sharedKeys = await keyManager.getAccessibleSharedKeyIds();

// Request and decrypt a vetKey
const keyOwner = Principal.fromText("aaaaa-aa");
const vetkeyName = "my_secure_key";
const vetkey = await keyManager.getVetKey(keyOwner, vetkeyName);

// Manage user access rights
const user = Principal.fromText("bbbbbb-bb");
const accessRights = { Read: null };
const result = await keyManager.setUserRights(keyOwner, vetkeyName, user, accessRights);

Resources