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}