Distributed key management service
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
- Rust
- Motoko
Below is an example of how to instantiate the backend KeyManager
component inside a canister.
loading...
Below is an example of how to instantiate the backend KeyManager
component inside a canister.
loading...
Step 2: Define backend canister APIs for the frontend to communicate with
- Rust
- Motoko
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.
loading...
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.
loading...
Step 3: Implement KeyManagerClient
in the frontend
- Typescript
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.
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:
- Typescript
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);