bh_jws_utils/traits.rs
1// Copyright (C) 2020-2025 The Blockhouse Technology Limited (TBTL).
2//
3// This program is free software: you can redistribute it and/or modify it
4// under the terms of the GNU Affero General Public License as published by
5// the Free Software Foundation, either version 3 of the License, or (at your
6// option) any later version.
7//
8// This program is distributed in the hope that it will be useful, but
9// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
11// License for more details.
12//
13// You should have received a copy of the GNU Affero General Public License
14// along with this program. If not, see <https://www.gnu.org/licenses/>.
15
16use std::str::FromStr;
17
18use bherror::Error;
19use bhx5chain::X5Chain;
20use serde::{Deserialize, Serialize};
21
22use crate::{error::SignatureError, utils::BoxError, JwkPublic};
23
24/// Signature algorithms approved for use in the context of EUDI.
25///
26/// # Algorithms
27///
28/// This enumeration contains only JOSE asymmetric signature algorithms approved
29/// for use by SOG-IS ACM v1.2, with any parameters (e.g. RSA modulus size)
30/// meeting therein imposed requirements.
31///
32/// For more details see the following references:
33/// - IETF draft [section 5.1.1], [section 10.1];
34/// - [SOG-IS Agreed Cryptographic Mechanisms v1.2];
35/// - [ETSI TS 119 312] sections 6 and 7.
36///
37/// [section 5.1.1]: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-selective-disclosure-jwt-08#section-5.1.1-2
38/// [section 10.1]: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-selective-disclosure-jwt-08#section-10.1-3
39/// [SOG-IS Agreed Cryptographic Mechanisms v1.2]: https://www.sogis.eu/documents/cc/crypto/SOGIS-Agreed-Cryptographic-Mechanisms-1.2.pdf
40/// [ETSI TS 119 312]: https://www.etsi.org/deliver/etsi_ts/119300_119399/119312/01.04.03_60/ts_119312v010403p.pdf
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
42#[serde(rename_all = "UPPERCASE")]
43pub enum SigningAlgorithm {
44 /// ECDSA over P-256 with SHA-256
45 Es256,
46 /// ECDSA over P-384 with SHA-384
47 Es384,
48 /// ECDSA over P-521 with SHA-512
49 Es512,
50 /// RSASSA-PSS with SHA-256 and MGF1 with SHA-256
51 Ps256,
52 /// RSASSA-PSS with SHA-384 and MGF1 with SHA-384
53 Ps384,
54 /// RSASSA-PSS with SHA-512 and MGF1 with SHA-512
55 Ps512,
56}
57
58/// JWS `"alg"` header parameter value for digital signature algorithm
59/// **ECDSA using P-256 and SHA-256**, as specified in [RFC7518].
60///
61/// [RFC7518]: https://datatracker.ietf.org/doc/html/rfc7518#section-3.1
62pub const SIGNING_ALG_ES256: &str = "ES256";
63/// JWS `"alg"` header parameter value for digital signature algorithm
64/// **ECDSA using P-384 and SHA-384**, as specified in [RFC7518].
65///
66/// [RFC7518]: https://datatracker.ietf.org/doc/html/rfc7518#section-3.1
67pub const SIGNING_ALG_ES384: &str = "ES384";
68/// JWS `"alg"` header parameter value for digital signature algorithm
69/// **ECDSA using P-521 and SHA-512**, as specified in [RFC7518].
70///
71/// [RFC7518]: https://datatracker.ietf.org/doc/html/rfc7518#section-3.1
72pub const SIGNING_ALG_ES512: &str = "ES512";
73/// JWS `"alg"` header parameter value for digital signature algorithm
74/// **RSASSA-PSS using SHA-256 and MGF1 with SHA-256**, as specified in [RFC7518].
75///
76/// [RFC7518]: https://datatracker.ietf.org/doc/html/rfc7518#section-3.1
77pub const SIGNING_ALG_PS256: &str = "PS256";
78/// JWS `"alg"` header parameter value for digital signature algorithm
79/// **RSASSA-PSS using SHA-384 and MGF1 with SHA-384**, as specified in [RFC7518].
80///
81/// [RFC7518]: https://datatracker.ietf.org/doc/html/rfc7518#section-3.1
82pub const SIGNING_ALG_PS384: &str = "PS384";
83/// JWS `"alg"` header parameter value for digital signature algorithm
84/// **RSASSA-PSS using SHA-512 and MGF1 with SHA-512**, as specified in [RFC7518].
85///
86/// [RFC7518]: https://datatracker.ietf.org/doc/html/rfc7518#section-3.1
87pub const SIGNING_ALG_PS512: &str = "PS512";
88
89impl FromStr for SigningAlgorithm {
90 type Err = Error<SignatureError>;
91
92 fn from_str(value: &str) -> Result<Self, Self::Err> {
93 match value {
94 SIGNING_ALG_ES256 => Ok(SigningAlgorithm::Es256),
95 SIGNING_ALG_ES384 => Ok(SigningAlgorithm::Es384),
96 SIGNING_ALG_ES512 => Ok(SigningAlgorithm::Es512),
97 SIGNING_ALG_PS256 => Ok(SigningAlgorithm::Ps256),
98 SIGNING_ALG_PS384 => Ok(SigningAlgorithm::Ps384),
99 SIGNING_ALG_PS512 => Ok(SigningAlgorithm::Ps512),
100 _ => Err(Error::root(SignatureError::InvalidSigningAlgorithm(
101 value.to_string(),
102 ))),
103 }
104 }
105}
106
107impl std::fmt::Display for SigningAlgorithm {
108 // This trait requires `fmt` with this exact signature.
109 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
110 let message = match self {
111 Self::Es256 => SIGNING_ALG_ES256,
112 Self::Es384 => SIGNING_ALG_ES384,
113 Self::Es512 => SIGNING_ALG_ES512,
114 Self::Ps256 => SIGNING_ALG_PS256,
115 Self::Ps384 => SIGNING_ALG_PS384,
116 Self::Ps512 => SIGNING_ALG_PS512,
117 };
118 write!(f, "{}", message)
119 }
120}
121
122impl From<SigningAlgorithm> for jwt::AlgorithmType {
123 fn from(value: SigningAlgorithm) -> Self {
124 match value {
125 SigningAlgorithm::Es256 => Self::Es256,
126 SigningAlgorithm::Es384 => Self::Es384,
127 SigningAlgorithm::Es512 => Self::Es512,
128 SigningAlgorithm::Ps256 => Self::Ps256,
129 SigningAlgorithm::Ps384 => Self::Ps384,
130 SigningAlgorithm::Ps512 => Self::Ps512,
131 }
132 }
133}
134
135/// An external signing backend, to be used for computing a JWS signature.
136///
137/// # Algorithms
138///
139/// Implementors of this trait must use only approved JOSE asymmetric signature algorithms,
140/// with any parameters (e.g. RSA modulus size) meeting standards-imposed requirements.
141/// For more details see [`SigningAlgorithm`].
142///
143/// The output of the signer, regardless of the algorithm, must be a valid **JWS signature**.
144/// See step 5 in [section 5.1 of RFC7515](https://www.rfc-editor.org/rfc/rfc7515.html#section-5.1)
145/// for more information.
146pub trait Signer {
147 /// The algorithm this signer uses. Must be a constant function.
148 fn algorithm(&self) -> SigningAlgorithm;
149
150 /// Produce a JWS signature as a byte array, not yet base64url-encoded.
151 ///
152 /// The `message` is guaranteed to be a valid JWS signing input.
153 fn sign(&self, message: &[u8]) -> Result<Vec<u8>, BoxError>;
154
155 /// The public key of the respective [`Signer`] in the JWK format.
156 fn public_jwk(&self) -> Result<JwkPublic, BoxError>;
157}
158
159/// Subtrait for [`Signer`]-s which have an associated JWK `kid` (Key ID) parameter.
160/// This is used to set the `kid` header parameter when signing a JWT.
161pub trait HasJwkKid: Signer {
162 /// Return the `kid` parameter of the associated JWK.
163 ///
164 /// See [section 4.5 of RFC7517](https://datatracker.ietf.org/doc/html/rfc7517#section-4.5)
165 /// for more detials.
166 fn jwk_kid(&self) -> &str;
167}
168
169/// Subtrait for [`Signer`]-s which have an associated `x5chain`.
170pub trait HasX5Chain: Signer {
171 /// Return the `x5c` parameter of the associated JWK.
172 fn x5chain(&self) -> X5Chain;
173}
174
175/// An external backend for signature verification, to be used for verifying
176/// JWS signatures.
177pub trait SignatureVerifier: Sync {
178 /// The algorithm used for the signature verification.
179 fn algorithm(&self) -> SigningAlgorithm;
180
181 /// Verifies the signature of the message, against the provided public key.
182 ///
183 /// The algorithm used to verify the signature must be the one returned by
184 /// [`SignatureVerifier::algorithm`].
185 ///
186 /// # Return
187 /// Method returns `Ok(true)` if the signature if valid for the given
188 /// message, `Ok(false)` if it isn't (but there was no issue with the
189 /// verifier itself), and `Err(_)` when the verifier itself encounters an
190 /// error for any other reason.
191 fn verify(
192 &self,
193 message: &[u8],
194 signature: &[u8],
195 public_key: &JwkPublic,
196 ) -> Result<bool, BoxError>;
197}
198
199/// An external backend capable of signing JWTs.
200///
201/// This is an extension trait over [`Signer`]; prefer depending on this trait
202/// when writing code which handles JWTs. It is however not object safe; depend
203/// on [`Signer`] directly if you need that.
204pub trait JwtSigner: Signer {
205 /// Utility function that delegates to [`jwt::SignWithKey`] while allowing
206 /// proper propagation of errors from both the foreign trait and the [`Signer`].
207 fn sign_jwt<UnsignedJwt, SignedJwt>(
208 &self,
209 unsigned_jwt: UnsignedJwt,
210 ) -> Result<SignedJwt, BoxError>
211 where
212 UnsignedJwt: jwt::SignWithKey<SignedJwt>;
213}
214
215impl<S: Signer + ?Sized> JwtSigner for S {
216 fn sign_jwt<UnsignedJwt, SignedJwt>(
217 &self,
218 unsigned_jwt: UnsignedJwt,
219 ) -> Result<SignedJwt, BoxError>
220 where
221 UnsignedJwt: jwt::SignWithKey<SignedJwt>,
222 {
223 crate::utils::sign_jwt(unsigned_jwt, self)
224 }
225}
226
227/// An external backend capable of verifying the signatures of JWTs.
228///
229/// This is an extension trait over [`SignatureVerifier`]; prefer depending on
230/// this trait when writing code which handles JWTs. It is however not object
231/// safe; depend on [`SignatureVerifier`] directly if you need that.
232pub trait JwtVerifier: SignatureVerifier {
233 /// Utility function that delegates to [`jwt::VerifyWithKey`] while allowing
234 /// proper propagation of errors from both the foreign trait and the
235 /// [`SignatureVerifier`].
236 fn verify_jwt_signature<UnverifiedJwt, VerifiedJwt>(
237 &self,
238 unverified_jwt: UnverifiedJwt,
239 public_key: &JwkPublic,
240 ) -> Result<VerifiedJwt, BoxError>
241 where
242 UnverifiedJwt: jwt::VerifyWithKey<VerifiedJwt>;
243}
244
245impl<V: SignatureVerifier + ?Sized> JwtVerifier for V {
246 fn verify_jwt_signature<UnverifiedJwt, VerifiedJwt>(
247 &self,
248 unverified_jwt: UnverifiedJwt,
249 public_key: &JwkPublic,
250 ) -> Result<VerifiedJwt, BoxError>
251 where
252 UnverifiedJwt: jwt::VerifyWithKey<VerifiedJwt>,
253 {
254 crate::utils::verify_jwt_signature(unverified_jwt, self, public_key)
255 }
256}
257
258#[cfg(test)]
259pub(crate) mod tests {
260 use super::*;
261
262 #[test]
263 fn signing_algorithms_serialize_correctly() {
264 struct TestCase<'a> {
265 alg: SigningAlgorithm,
266 alg_str: &'a str,
267 }
268
269 let test_cases: &[TestCase] = &[
270 TestCase {
271 alg: SigningAlgorithm::Es256,
272 alg_str: SIGNING_ALG_ES256,
273 },
274 TestCase {
275 alg: SigningAlgorithm::Es384,
276 alg_str: SIGNING_ALG_ES384,
277 },
278 TestCase {
279 alg: SigningAlgorithm::Es512,
280 alg_str: SIGNING_ALG_ES512,
281 },
282 TestCase {
283 alg: SigningAlgorithm::Ps256,
284 alg_str: SIGNING_ALG_PS256,
285 },
286 TestCase {
287 alg: SigningAlgorithm::Ps384,
288 alg_str: SIGNING_ALG_PS384,
289 },
290 TestCase {
291 alg: SigningAlgorithm::Ps512,
292 alg_str: SIGNING_ALG_PS512,
293 },
294 ];
295
296 for TestCase { alg, alg_str } in test_cases {
297 let serialized = serde_json::to_string(alg).unwrap();
298 let expected = format!("\"{}\"", alg_str);
299 assert_eq!(expected, serialized);
300
301 let deserialized_serde: SigningAlgorithm = serde_json::from_str(&expected).unwrap();
302 assert_eq!(alg, &deserialized_serde);
303
304 let deserialized_str = SigningAlgorithm::from_str(alg_str).unwrap();
305 assert_eq!(alg, &deserialized_str);
306
307 assert_eq!(*alg, SigningAlgorithm::from_str(&alg.to_string()).unwrap());
308 }
309 }
310}