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}