Skip to main content

identity_kit/keys/
mod.rs

1//! Key stores: private keys behind an opaque id, with signing as a remote operation.
2//!
3//! The module is shaped by remote signers, an HSM or a cloud KMS being the design target:
4//!
5//! - A key is named by an opaque [`KeyId`] (a label, a PKCS#11 object name, a cloud key ARN).
6//! - Private material never leaves the backend. [`KeyStore::sign`] is `async`, and callers keep
7//!   the public [`Jwk`] they get from [`KeyStore::public_jwk`] rather than asking for it on every
8//!   use; [`KeySigner`] does exactly that and implements [`Signer`](crate::jose::Signer).
9//! - Every key has one algorithm. A backend declares what it signs with, and whether it can
10//!   generate and delete keys, in [`KeyStoreCapabilities`]; the declaration must be truthful and
11//!   [`testsuite`] (feature `test-vectors`) checks it.
12//! - Signatures are returned in their JWS form: 64 bytes for EdDSA, raw `r || s` for ECDSA.
13//!
14//! Two backends ship with the crate, for development and tests: [`InMemoryKeyStore`] (feature
15//! `in-memory`) and [`SoftwareKeyStore`] (feature `software`), a private JWK set file. Both
16//! sign EdDSA, ES256 and, with the `secp256k1` feature, ES256K. Neither signs with RSA, which
17//! stays outside the crate per `docs/adr/0001-rsa-verification-only.md`. Vendor-specific
18//! backends live in their own crates and implement [`KeyStore`]; `docs/key-stores.md` is the
19//! contract they follow.
20//!
21//! # Cargo features
22//!
23//! - `in-memory`: [`InMemoryKeyStore`].
24//! - `software` (default): [`SoftwareKeyStore`].
25//! - `test-vectors`: [`testsuite`].
26
27use core::fmt;
28use std::sync::Arc;
29
30use serde::{Deserialize, Serialize};
31
32use crate::jose::{Jwk, JwsAlgorithm};
33
34mod error;
35#[cfg(feature = "in-memory")]
36mod in_memory;
37#[cfg(any(feature = "in-memory", feature = "software"))]
38mod local;
39mod signer;
40#[cfg(feature = "software")]
41mod software;
42#[cfg(feature = "test-vectors")]
43#[cfg_attr(docsrs, doc(cfg(feature = "test-vectors")))]
44pub mod testsuite;
45
46pub use crate::jose::KeyType;
47pub use error::KeyStoreError;
48#[cfg(feature = "in-memory")]
49#[cfg_attr(docsrs, doc(cfg(feature = "in-memory")))]
50pub use in_memory::InMemoryKeyStore;
51pub use signer::KeySigner;
52#[cfg(feature = "software")]
53#[cfg_attr(docsrs, doc(cfg(feature = "software")))]
54pub use software::{SoftwareKeyStore, SoftwareKeyStoreOptions};
55
56/// The opaque id of a key in a [`KeyStore`].
57///
58/// What the string means is up to the backend: a label, a PKCS#11 object name, a cloud key ARN.
59/// The crate never parses it. It serializes as a plain string.
60///
61/// # Examples
62///
63/// ```
64/// use identity_kit::keys::KeyId;
65///
66/// let id = KeyId::new("signing-2026");
67/// assert_eq!(id.as_str(), "signing-2026");
68/// assert_eq!(id.to_string(), "signing-2026");
69/// assert_eq!(serde_json::to_string(&id)?, "\"signing-2026\"");
70/// # Ok::<(), serde_json::Error>(())
71/// ```
72#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
73#[serde(transparent)]
74pub struct KeyId(String);
75
76impl KeyId {
77    /// A key id from any string.
78    pub fn new(id: impl Into<String>) -> Self {
79        Self(id.into())
80    }
81
82    /// The id as a string.
83    pub fn as_str(&self) -> &str {
84        &self.0
85    }
86}
87
88impl From<&str> for KeyId {
89    fn from(id: &str) -> Self {
90        Self::new(id)
91    }
92}
93
94impl From<String> for KeyId {
95    fn from(id: String) -> Self {
96        Self(id)
97    }
98}
99
100impl From<KeyId> for String {
101    fn from(id: KeyId) -> Self {
102        id.0
103    }
104}
105
106impl AsRef<str> for KeyId {
107    fn as_ref(&self) -> &str {
108        &self.0
109    }
110}
111
112impl fmt::Debug for KeyId {
113    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114        fmt::Debug::fmt(&self.0, f)
115    }
116}
117
118impl fmt::Display for KeyId {
119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120        f.write_str(&self.0)
121    }
122}
123
124/// What to generate: a key type, the one algorithm the key will sign with, and an optional
125/// label.
126///
127/// The algorithm must fit the key type ([`KeySpec::validate`]); backends reject a spec that
128/// does not. How a backend uses the label is its own business; the software backends use it as
129/// the key id when set and the RFC 7638 thumbprint of the public key otherwise.
130///
131/// # Examples
132///
133/// ```
134/// use identity_kit::jose::{JwsAlgorithm, KeyType};
135/// use identity_kit::keys::{KeySpec, KeyStoreError};
136///
137/// let spec = KeySpec::new(KeyType::P256, JwsAlgorithm::ES256).with_label("issuer");
138/// assert!(spec.validate().is_ok());
139///
140/// let wrong = KeySpec::new(KeyType::P256, JwsAlgorithm::EdDSA);
141/// assert!(matches!(wrong.validate(), Err(KeyStoreError::InvalidSpec { .. })));
142/// ```
143#[derive(Debug, Clone, PartialEq, Eq)]
144#[non_exhaustive]
145pub struct KeySpec {
146    /// The kind of key.
147    pub key_type: KeyType,
148    /// The algorithm the key signs with.
149    pub alg: JwsAlgorithm,
150    /// A label for the backend: the key id for the software backends, a PKCS#11 label, a
151    /// display name.
152    pub label: Option<String>,
153}
154
155impl KeySpec {
156    /// A spec without a label.
157    pub fn new(key_type: KeyType, alg: JwsAlgorithm) -> Self {
158        Self {
159            key_type,
160            alg,
161            label: None,
162        }
163    }
164
165    /// This spec with a label.
166    #[must_use]
167    pub fn with_label(mut self, label: impl Into<String>) -> Self {
168        self.label = Some(label.into());
169        self
170    }
171
172    /// Checks that the algorithm fits the key type.
173    ///
174    /// # Errors
175    ///
176    /// [`KeyStoreError::InvalidSpec`] when it does not: `EdDSA` needs an Ed25519 key, `ES256` a
177    /// P-256 key, `ES256K` a secp256k1 key and `RS256` or `PS256` an RSA key. `ES384` fits no
178    /// [`KeyType`] this crate has.
179    pub fn validate(&self) -> Result<(), KeyStoreError> {
180        if algorithm_fits(self.key_type, self.alg) {
181            Ok(())
182        } else {
183            Err(KeyStoreError::InvalidSpec {
184                key_type: self.key_type,
185                alg: self.alg,
186            })
187        }
188    }
189}
190
191/// Whether `alg` is an algorithm for keys of type `key_type`.
192fn algorithm_fits(key_type: KeyType, alg: JwsAlgorithm) -> bool {
193    match key_type {
194        KeyType::Ed25519 => alg == JwsAlgorithm::EdDSA,
195        KeyType::P256 => alg == JwsAlgorithm::ES256,
196        KeyType::Secp256k1 => alg == JwsAlgorithm::ES256K,
197        KeyType::Rsa2048 | KeyType::Rsa4096 => {
198            matches!(alg, JwsAlgorithm::RS256 | JwsAlgorithm::PS256)
199        }
200    }
201}
202
203/// What a [`KeyStore`] can do.
204///
205/// A backend's declaration must be truthful: every algorithm listed signs, and `generate` and
206/// `delete` succeed only when advertised. [`testsuite`] checks both directions.
207///
208/// # Examples
209///
210/// ```
211/// use identity_kit::jose::JwsAlgorithm;
212/// use identity_kit::keys::KeyStoreCapabilities;
213///
214/// let hsm = KeyStoreCapabilities::new([JwsAlgorithm::ES256, JwsAlgorithm::RS256]).remote();
215/// assert!(hsm.supports(JwsAlgorithm::ES256));
216/// assert!(!hsm.supports(JwsAlgorithm::EdDSA));
217/// assert!(!hsm.can_generate && !hsm.can_delete && hsm.remote);
218/// ```
219#[derive(Debug, Clone, PartialEq, Eq)]
220#[non_exhaustive]
221pub struct KeyStoreCapabilities {
222    /// Whether [`KeyStore::generate`] is implemented.
223    pub can_generate: bool,
224    /// Whether [`KeyStore::delete`] is implemented.
225    pub can_delete: bool,
226    /// Whether signing leaves the process (an HSM, a network service), so that callers should
227    /// expect latency and transient failures.
228    pub remote: bool,
229    /// The algorithms the store signs with, without duplicates.
230    pub algorithms: Vec<JwsAlgorithm>,
231}
232
233impl KeyStoreCapabilities {
234    /// A local store that signs with `algorithms` and can neither generate nor delete.
235    pub fn new(algorithms: impl IntoIterator<Item = JwsAlgorithm>) -> Self {
236        let mut deduplicated: Vec<JwsAlgorithm> = Vec::new();
237        for alg in algorithms {
238            if !deduplicated.contains(&alg) {
239                deduplicated.push(alg);
240            }
241        }
242        Self {
243            can_generate: false,
244            can_delete: false,
245            remote: false,
246            algorithms: deduplicated,
247        }
248    }
249
250    /// With [`can_generate`](Self::can_generate) set.
251    #[must_use]
252    pub fn with_generate(mut self) -> Self {
253        self.can_generate = true;
254        self
255    }
256
257    /// With [`can_delete`](Self::can_delete) set.
258    #[must_use]
259    pub fn with_delete(mut self) -> Self {
260        self.can_delete = true;
261        self
262    }
263
264    /// With [`remote`](Self::remote) set.
265    #[must_use]
266    pub fn remote(mut self) -> Self {
267        self.remote = true;
268        self
269    }
270
271    /// Whether the store signs with `alg`.
272    pub fn supports(&self, alg: JwsAlgorithm) -> bool {
273        self.algorithms.contains(&alg)
274    }
275}
276
277/// A backend holding private keys.
278///
279/// This is the crate's key extension point: a KMS, an HSM or a software store implements it,
280/// and everything that signs takes a `&dyn KeyStore` (usually through [`KeySigner`]). The
281/// contract, which [`testsuite`] checks, is:
282///
283/// - [`capabilities`](Self::capabilities) is truthful: every listed algorithm signs; `generate`
284///   and `delete` return [`KeyStoreError::Unsupported`] unless advertised.
285/// - [`public_jwk`](Self::public_jwk) returns a key without private parameters, with `alg` set
286///   to the key's algorithm when the backend knows it.
287/// - [`sign`](Self::sign) takes the raw signing input (the backend hashes) and returns the JWS
288///   form of the signature: 64 bytes for EdDSA, raw `r || s` for ECDSA, the modulus-sized octet
289///   string for RSA. A key signs only with its own algorithm; any other `alg` is
290///   [`KeyStoreError::AlgorithmMismatch`].
291/// - An unknown key is [`KeyStoreError::NotFound`] from every operation but
292///   [`exists`](Self::exists), which returns `false`.
293///
294/// `generate` and `delete` have default implementations returning
295/// [`KeyStoreError::Unsupported`], for backends whose keys are provisioned out of band.
296#[async_trait::async_trait]
297pub trait KeyStore: Send + Sync {
298    /// What this store can do.
299    fn capabilities(&self) -> KeyStoreCapabilities;
300
301    /// The public key of `key`, without private parameters.
302    ///
303    /// # Errors
304    ///
305    /// [`KeyStoreError::NotFound`] when there is no such key.
306    async fn public_jwk(&self, key: &KeyId) -> Result<Jwk, KeyStoreError>;
307
308    /// Signs `data` with `key` under `alg`, returning the signature in its JWS form.
309    ///
310    /// # Errors
311    ///
312    /// [`KeyStoreError::NotFound`] when there is no such key and
313    /// [`KeyStoreError::AlgorithmMismatch`] when `alg` is not the key's algorithm.
314    async fn sign(
315        &self,
316        key: &KeyId,
317        alg: JwsAlgorithm,
318        data: &[u8],
319    ) -> Result<Vec<u8>, KeyStoreError>;
320
321    /// Whether `key` exists.
322    ///
323    /// # Errors
324    ///
325    /// Only when the backend cannot answer.
326    async fn exists(&self, key: &KeyId) -> Result<bool, KeyStoreError>;
327
328    /// Generates a key as `spec` describes and returns its id.
329    ///
330    /// # Errors
331    ///
332    /// [`KeyStoreError::Unsupported`] when the store does not generate keys (the default),
333    /// [`KeyStoreError::InvalidSpec`] when the algorithm does not fit the key type,
334    /// [`KeyStoreError::UnsupportedKeyType`] or [`KeyStoreError::UnsupportedAlgorithm`] when the
335    /// store cannot make such a key, and [`KeyStoreError::AlreadyExists`] when the label names
336    /// an existing key.
337    async fn generate(&self, spec: KeySpec) -> Result<KeyId, KeyStoreError> {
338        let _ = spec;
339        Err(KeyStoreError::Unsupported("generate"))
340    }
341
342    /// Deletes `key`.
343    ///
344    /// # Errors
345    ///
346    /// [`KeyStoreError::Unsupported`] when the store does not delete keys (the default) and
347    /// [`KeyStoreError::NotFound`] when there is no such key.
348    async fn delete(&self, key: &KeyId) -> Result<(), KeyStoreError> {
349        let _ = key;
350        Err(KeyStoreError::Unsupported("delete"))
351    }
352}
353
354macro_rules! forward_key_store {
355    ($($ty:ty),* $(,)?) => {
356        $(
357            #[async_trait::async_trait]
358            impl<T: KeyStore + ?Sized> KeyStore for $ty {
359                fn capabilities(&self) -> KeyStoreCapabilities {
360                    (**self).capabilities()
361                }
362
363                async fn public_jwk(&self, key: &KeyId) -> Result<Jwk, KeyStoreError> {
364                    (**self).public_jwk(key).await
365                }
366
367                async fn sign(
368                    &self,
369                    key: &KeyId,
370                    alg: JwsAlgorithm,
371                    data: &[u8],
372                ) -> Result<Vec<u8>, KeyStoreError> {
373                    (**self).sign(key, alg, data).await
374                }
375
376                async fn exists(&self, key: &KeyId) -> Result<bool, KeyStoreError> {
377                    (**self).exists(key).await
378                }
379
380                async fn generate(&self, spec: KeySpec) -> Result<KeyId, KeyStoreError> {
381                    (**self).generate(spec).await
382                }
383
384                async fn delete(&self, key: &KeyId) -> Result<(), KeyStoreError> {
385                    (**self).delete(key).await
386                }
387            }
388        )*
389    };
390}
391
392forward_key_store!(&T, Box<T>, Arc<T>);