Skip to main content

identity_kit/keys/
signer.rs

1//! `KeySigner`: a key of a `KeyStore` as a `jose::Signer`.
2
3use core::fmt;
4
5use super::{KeyId, KeyStore, KeyStoreError};
6use crate::jose::{Jwk, JwsAlgorithm, SignError, Signer};
7
8/// A [`Signer`] over one key of a [`KeyStore`].
9///
10/// `S` is the handle the signer holds the store by: a reference (`&InMemoryKeyStore`,
11/// `&dyn KeyStore`) for a signer that lives next to the store, or an `Arc<dyn KeyStore>` for one
12/// that outlives the scope it was made in, moved into a spawned task or kept in shared
13/// application state. Every such handle implements [`KeyStore`] itself.
14///
15/// The signer looks the key up once, when it is created, and keeps the public [`Jwk`] so that a
16/// caller building a DID document or a JWS header never round-trips to the store for it
17/// ([`KeySigner::public_jwk`]). A caller that already has the public key, for instance stored
18/// next to the DID document it published, skips that lookup with [`KeySigner::from_public_jwk`].
19/// Signing goes to the store on every call.
20///
21/// [`Signer::kid`] is the key id, unless [`KeySigner::with_kid`] set another value, such as the
22/// DID URL of the verification method the key backs.
23///
24/// # Examples
25///
26/// ```
27/// # #[cfg(feature = "in-memory")]
28/// # tokio::runtime::Builder::new_current_thread().build()?.block_on(async {
29/// use identity_kit::jose::{CompactJws, DefaultVerifier, JwsAlgorithm, JwsHeader, KeyType, encode_compact};
30/// use identity_kit::keys::{InMemoryKeyStore, KeySigner, KeySpec, KeyStore};
31///
32/// let store = InMemoryKeyStore::new();
33/// let key = store.generate(KeySpec::new(KeyType::Ed25519, JwsAlgorithm::EdDSA)).await?;
34/// let signer = KeySigner::new(&store, key, JwsAlgorithm::EdDSA).await?;
35///
36/// let header = JwsHeader::new(JwsAlgorithm::EdDSA).with_kid(signer.key_id().as_str());
37/// let token = encode_compact(&header, b"{}", &signer).await?;
38/// let jws = CompactJws::decode(&token)?;
39/// jws.verify(&DefaultVerifier, signer.public_jwk())?;
40/// # Ok::<(), Box<dyn std::error::Error>>(())
41/// # })?;
42/// # Ok::<(), Box<dyn std::error::Error>>(())
43/// ```
44///
45/// A signer over an `Arc<dyn KeyStore>` is `'static` and moves into a spawned task:
46///
47/// ```
48/// # #[cfg(feature = "in-memory")]
49/// # tokio::runtime::Builder::new_current_thread().build()?.block_on(async {
50/// use std::sync::Arc;
51///
52/// use identity_kit::jose::{JwsAlgorithm, JwsHeader, KeyType, encode_compact};
53/// use identity_kit::keys::{InMemoryKeyStore, KeySigner, KeySpec, KeyStore};
54///
55/// let store: Arc<dyn KeyStore> = Arc::new(InMemoryKeyStore::new());
56/// let key = store.generate(KeySpec::new(KeyType::P256, JwsAlgorithm::ES256)).await?;
57/// let signer = KeySigner::new(Arc::clone(&store), key, JwsAlgorithm::ES256).await?;
58///
59/// let token = tokio::spawn(async move {
60///     encode_compact(&JwsHeader::new(JwsAlgorithm::ES256), b"{}", &signer).await
61/// })
62/// .await??;
63/// assert_eq!(token.split('.').count(), 3);
64/// # Ok::<(), Box<dyn std::error::Error>>(())
65/// # })?;
66/// # Ok::<(), Box<dyn std::error::Error>>(())
67/// ```
68#[derive(Clone)]
69pub struct KeySigner<S> {
70    store: S,
71    key: KeyId,
72    alg: JwsAlgorithm,
73    public: Jwk,
74    kid: Option<String>,
75}
76
77impl<S: KeyStore> KeySigner<S> {
78    /// A signer over `key` of `store`, signing with `alg`.
79    ///
80    /// # Errors
81    ///
82    /// [`KeyStoreError::UnsupportedAlgorithm`] when the store does not advertise `alg`,
83    /// [`KeyStoreError::NotFound`] when the key does not exist,
84    /// [`KeyStoreError::AlgorithmMismatch`] when the key's public JWK names another algorithm,
85    /// and whatever else the store reports.
86    pub async fn new(store: S, key: KeyId, alg: JwsAlgorithm) -> Result<Self, KeyStoreError> {
87        if !store.capabilities().supports(alg) {
88            return Err(KeyStoreError::UnsupportedAlgorithm(alg));
89        }
90        let public = store.public_jwk(&key).await?;
91        check_alg(&key, &public, alg)?;
92        Ok(Self {
93            store,
94            key,
95            alg,
96            public,
97            kid: None,
98        })
99    }
100
101    /// A signer over `key` of `store`, signing with `alg`, whose public key the caller already
102    /// has. The store is not asked for anything until the first signature.
103    ///
104    /// The caller vouches that `public` is the public key of `key`: the signer cannot tell, and
105    /// with a wrong one it makes signatures that verify against no key the caller publishes.
106    ///
107    /// # Errors
108    ///
109    /// [`KeyStoreError::UnsupportedAlgorithm`] when the store does not advertise `alg`,
110    /// [`KeyStoreError::AlgorithmMismatch`] when `public` names another algorithm, and
111    /// [`KeyStoreError::InvalidKey`] when `public` carries private parameters or is not a key
112    /// for `alg` (its `kty` and `crv`).
113    ///
114    /// # Examples
115    ///
116    /// ```
117    /// # #[cfg(feature = "in-memory")]
118    /// # tokio::runtime::Builder::new_current_thread().build()?.block_on(async {
119    /// use identity_kit::jose::{JwsAlgorithm, KeyType};
120    /// use identity_kit::keys::{InMemoryKeyStore, KeySigner, KeySpec, KeyStore};
121    ///
122    /// let store = InMemoryKeyStore::new();
123    /// let key = store.generate(KeySpec::new(KeyType::Ed25519, JwsAlgorithm::EdDSA)).await?;
124    /// // Fetched once and kept, for instance next to the DID document.
125    /// let public = store.public_jwk(&key).await?;
126    ///
127    /// let signer = KeySigner::from_public_jwk(&store, key, JwsAlgorithm::EdDSA, public)?;
128    /// # Ok::<(), Box<dyn std::error::Error>>(())
129    /// # })?;
130    /// # Ok::<(), Box<dyn std::error::Error>>(())
131    /// ```
132    pub fn from_public_jwk(
133        store: S,
134        key: KeyId,
135        alg: JwsAlgorithm,
136        public: Jwk,
137    ) -> Result<Self, KeyStoreError> {
138        if !store.capabilities().supports(alg) {
139            return Err(KeyStoreError::UnsupportedAlgorithm(alg));
140        }
141        if public.is_private() {
142            return Err(KeyStoreError::invalid_key(
143                key,
144                "the given public JWK carries private parameters",
145            ));
146        }
147        if !fits(&public, alg) {
148            return Err(KeyStoreError::invalid_key(
149                key,
150                format!(
151                    "the given public JWK (`kty` {}, `crv` {}) is not a key for {alg}",
152                    public.kty(),
153                    public.crv().unwrap_or("absent"),
154                ),
155            ));
156        }
157        check_alg(&key, &public, alg)?;
158        Ok(Self {
159            store,
160            key,
161            alg,
162            public,
163            kid: None,
164        })
165    }
166
167    /// This signer with [`Signer::kid`] set to `kid` instead of the key id.
168    #[must_use]
169    pub fn with_kid(mut self, kid: impl Into<String>) -> Self {
170        self.kid = Some(kid.into());
171        self
172    }
173
174    /// The store the key lives in.
175    pub fn store(&self) -> &S {
176        &self.store
177    }
178
179    /// The key this signer signs with.
180    pub fn key_id(&self) -> &KeyId {
181        &self.key
182    }
183
184    /// The public key, as the store reported it when the signer was created, or as given to
185    /// [`KeySigner::from_public_jwk`].
186    pub fn public_jwk(&self) -> &Jwk {
187        &self.public
188    }
189}
190
191/// [`KeyStoreError::AlgorithmMismatch`] unless `public` names no algorithm or names `alg`.
192fn check_alg(key: &KeyId, public: &Jwk, alg: JwsAlgorithm) -> Result<(), KeyStoreError> {
193    let Some(key_alg) = public.alg() else {
194        return Ok(());
195    };
196    let key_alg: JwsAlgorithm = key_alg.parse().map_err(|_| {
197        KeyStoreError::invalid_key(
198            key.clone(),
199            format!("unknown algorithm `{key_alg}` in its public JWK"),
200        )
201    })?;
202    if key_alg == alg {
203        Ok(())
204    } else {
205        Err(KeyStoreError::AlgorithmMismatch {
206            key: key.clone(),
207            key_alg,
208            requested: alg,
209        })
210    }
211}
212
213/// Whether `public` is a key `alg` signs with, by its `kty` and `crv` (RFC 7518 §6, RFC 8037 §2,
214/// RFC 8812 §3.1).
215fn fits(public: &Jwk, alg: JwsAlgorithm) -> bool {
216    match alg {
217        JwsAlgorithm::EdDSA => public.kty() == "OKP" && public.crv() == Some("Ed25519"),
218        JwsAlgorithm::ES256 => public.kty() == "EC" && public.crv() == Some("P-256"),
219        JwsAlgorithm::ES256K => public.kty() == "EC" && public.crv() == Some("secp256k1"),
220        JwsAlgorithm::ES384 => public.kty() == "EC" && public.crv() == Some("P-384"),
221        JwsAlgorithm::RS256 | JwsAlgorithm::PS256 => public.kty() == "RSA",
222    }
223}
224
225impl<S> fmt::Debug for KeySigner<S> {
226    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227        f.debug_struct("KeySigner")
228            .field("key", &self.key)
229            .field("alg", &self.alg)
230            .field("public", &self.public)
231            .field("kid", &self.kid)
232            .finish_non_exhaustive()
233    }
234}
235
236#[async_trait::async_trait]
237impl<S: KeyStore> Signer for KeySigner<S> {
238    fn alg(&self) -> JwsAlgorithm {
239        self.alg
240    }
241
242    fn kid(&self) -> Option<&str> {
243        Some(self.kid.as_deref().unwrap_or(self.key.as_str()))
244    }
245
246    async fn sign(&self, signing_input: &[u8]) -> Result<Vec<u8>, SignError> {
247        self.store
248            .sign(&self.key, self.alg, signing_input)
249            .await
250            .map_err(SignError::new)
251    }
252}