Key Storage
Introduction
The JwkDocumentExt
API allows you to modify a DID document, for example, adding new verification methods.
It enables storing the secrets that verification methods represent securely.
It does so using the two storage interfaces, the JwkStorage
and KeyIdStorage
.
We refer to both of these as the key storage.
The main idea behind the key storage is strongly inspired by the architecture of key management systems (KMS) or secure enclaves: once private keys are entered into the system, they can never be retrieved again. Instead, all operations using the key will have to go through that system.
This approach allows the key storage to be architected more securely than simply storing and loading private keys from a regular database. Of course, security is directly dependent on the concrete implementation, which is why we provide Stronghold, a best-effort in-software enclave, by default.
However, there are cases where one cannot use Stronghold
,
or may want to integrate key management of identities into their own KMS or similar,
which is why the key storage is an abstraction over such systems.
Any implementation of a key storage can be used by the JwkDocumentExt
API.
The two interfaces making up the key storage have two respective responsibilities.
Even though there are two separate interfaces, you can implement them using the same backing storage.
Function Overview
A brief overview of those functions:
JwkStorage
: CRUD and signing operations on JSON Web Keys.generate
: Generate a new key represented as a JSON Web Key.insert
: Insert an existing JSON Web Key into the storage.sign
: Signs the provided data using the stored private key.delete
: Permanently deletes a key.exists
: Returns whether a key exists.
KeyIdStorage
: Stores the mappings from verification methods to their corresponding key identifier in theJwkStorage
.insert_key_id
: Inserts a mapping from a verification method identifier to a key identifier.get_key_id
: Returns the key identifier for a given verification method identifier.delete_key_id
: Deletes a mapping.
Key Identifier
A JwkStorage
stores and operates on keys, so they must be identified.
In general, Key Management Systems use some form of an identifier for their keys.
To abstract over those, the JwkStorage
interface has a general-purpose KeyId
type,
which is effectively a wrapper around a string.
A KeyIdStorage
is needed to store the key id that represents the private key for a given verification method.
To that end, a verification method itself must be identified.
While within a document, each fragment must be unique, the same is not true given multiple documents,
so we cannot rely only on fragments if we don't want to partition the KeyIdStorage
by DID.
The solution to this is using a MethodDigest
, a hash over a verification method.
When following best security practices, each verification method has its own associated key and, thus, a unique public key.
That, plus the fragment of a method, ensures the MethodDigest
is unique.
So, in essence, a JwkStorage
stores a KeyId -> JWK
mapping while a KeyIdStorage
stores a MethodDigest -> KeyId
mapping.
Given the construction and limitations of the method digest, no two documents should contain a method that shares both the same fragment and public key. This should not happen under typical circumstances, but it is good to keep it in mind.
Key Types
To express what key types a given JwkStorage
implementation supports, you should use the KeyType
,
which is another simple wrapper around a string.
The reason for this design might seem odd in Rust, given the existence of associated types. This more simplistic design is necessary to accommodate implementing the interface via the bindings to the library.
Implementations are expected to export constants of the key types they support,
so users have an easy way to discover the supported types.
In general, storage implementations are free to support any JSON Web Algorithm-compatible key.
However, the recommended default used by IOTA Identity is the EdDSA
algorithm with curve Ed25519
.
Implementation
The IOTA Identity library ships two implementations of key storage.
The JwkMemStore
and KeyIdMemstore
are insecure in-memory implementations
intended as example implementations and for testing.
The default key storage implementation is Stronghold
,
which is an example of a storage that implements both storage interfaces simultaneously.
Stronghold
may be interesting for implementers to look at,
as it needs to deal with some challenges the in-memory version does not have to deal with. Note that the Stronghold
implementation is only available in Rust.
Examples
This section shows the Rust and TypeScript Memstore
implementations.
JwkMemStore
- Typescript (Node.js)
- Rust
loading...
loading...
KeyIdMemstore
- Typescript (Node.js)
- Rust
loading...
loading...