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::InvalidKey {
143 key,
144 reason: "the given public JWK carries private parameters".to_owned(),
145 });
146 }
147 if !fits(&public, alg) {
148 return Err(KeyStoreError::InvalidKey {
149 reason: format!(
150 "the given public JWK (`kty` {}, `crv` {}) is not a key for {alg}",
151 public.kty(),
152 public.crv().unwrap_or("absent"),
153 ),
154 key,
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(|_| KeyStoreError::InvalidKey {
197 key: key.clone(),
198 reason: format!("unknown algorithm `{key_alg}` in its public JWK"),
199 })?;
200 if key_alg == alg {
201 Ok(())
202 } else {
203 Err(KeyStoreError::AlgorithmMismatch {
204 key: key.clone(),
205 key_alg,
206 requested: alg,
207 })
208 }
209}
210
211/// Whether `public` is a key `alg` signs with, by its `kty` and `crv` (RFC 7518 §6, RFC 8037 §2,
212/// RFC 8812 §3.1).
213fn fits(public: &Jwk, alg: JwsAlgorithm) -> bool {
214 match alg {
215 JwsAlgorithm::EdDSA => public.kty() == "OKP" && public.crv() == Some("Ed25519"),
216 JwsAlgorithm::ES256 => public.kty() == "EC" && public.crv() == Some("P-256"),
217 JwsAlgorithm::ES256K => public.kty() == "EC" && public.crv() == Some("secp256k1"),
218 JwsAlgorithm::ES384 => public.kty() == "EC" && public.crv() == Some("P-384"),
219 JwsAlgorithm::RS256 | JwsAlgorithm::PS256 => public.kty() == "RSA",
220 }
221}
222
223impl<S> fmt::Debug for KeySigner<S> {
224 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225 f.debug_struct("KeySigner")
226 .field("key", &self.key)
227 .field("alg", &self.alg)
228 .field("public", &self.public)
229 .field("kid", &self.kid)
230 .finish_non_exhaustive()
231 }
232}
233
234#[async_trait::async_trait]
235impl<S: KeyStore> Signer for KeySigner<S> {
236 fn alg(&self) -> JwsAlgorithm {
237 self.alg
238 }
239
240 fn kid(&self) -> Option<&str> {
241 Some(self.kid.as_deref().unwrap_or(self.key.as_str()))
242 }
243
244 async fn sign(&self, signing_input: &[u8]) -> Result<Vec<u8>, SignError> {
245 self.store
246 .sign(&self.key, self.alg, signing_input)
247 .await
248 .map_err(SignError::new)
249 }
250}