mas_jose/jwk/
public_parameters.rs

1// Copyright 2024, 2025 New Vector Ltd.
2// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
3//
4// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
5// Please see LICENSE files in the repository root for full details.
6
7use mas_iana::jose::{
8    JsonWebKeyEcEllipticCurve, JsonWebKeyOkpEllipticCurve, JsonWebKeyType, JsonWebSignatureAlg,
9};
10use schemars::JsonSchema;
11use serde::{Deserialize, Serialize};
12
13use super::ParametersInfo;
14use crate::{base64::Base64UrlNoPad, jwk::Thumbprint};
15
16#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
17#[serde(tag = "kty")]
18pub enum JsonWebKeyPublicParameters {
19    #[serde(rename = "RSA")]
20    Rsa(RsaPublicParameters),
21
22    #[serde(rename = "EC")]
23    Ec(EcPublicParameters),
24
25    #[serde(rename = "OKP")]
26    Okp(OkpPublicParameters),
27}
28
29impl JsonWebKeyPublicParameters {
30    #[must_use]
31    pub const fn rsa(&self) -> Option<&RsaPublicParameters> {
32        match self {
33            Self::Rsa(params) => Some(params),
34            _ => None,
35        }
36    }
37
38    #[must_use]
39    pub const fn ec(&self) -> Option<&EcPublicParameters> {
40        match self {
41            Self::Ec(params) => Some(params),
42            _ => None,
43        }
44    }
45
46    #[must_use]
47    pub const fn okp(&self) -> Option<&OkpPublicParameters> {
48        match self {
49            Self::Okp(params) => Some(params),
50            _ => None,
51        }
52    }
53}
54
55impl Thumbprint for JsonWebKeyPublicParameters {
56    fn thumbprint_prehashed(&self) -> String {
57        match self {
58            JsonWebKeyPublicParameters::Rsa(RsaPublicParameters { n, e }) => {
59                format!("{{\"e\":\"{e}\",\"kty\":\"RSA\",\"n\":\"{n}\"}}")
60            }
61            JsonWebKeyPublicParameters::Ec(EcPublicParameters { crv, x, y }) => {
62                format!("{{\"crv\":\"{crv}\",\"kty\":\"EC\",\"x\":\"{x}\",\"y\":\"{y}\"}}")
63            }
64            JsonWebKeyPublicParameters::Okp(OkpPublicParameters { crv, x }) => {
65                format!("{{\"crv\":\"{crv}\",\"kty\":\"OKP\",\"x\":\"{x}\"}}")
66            }
67        }
68    }
69}
70
71impl ParametersInfo for JsonWebKeyPublicParameters {
72    fn kty(&self) -> JsonWebKeyType {
73        match self {
74            Self::Rsa(_) => JsonWebKeyType::Rsa,
75            Self::Ec(_) => JsonWebKeyType::Ec,
76            Self::Okp(_) => JsonWebKeyType::Okp,
77        }
78    }
79
80    fn possible_algs(&self) -> &[JsonWebSignatureAlg] {
81        match self {
82            JsonWebKeyPublicParameters::Rsa(p) => p.possible_algs(),
83            JsonWebKeyPublicParameters::Ec(p) => p.possible_algs(),
84            JsonWebKeyPublicParameters::Okp(p) => p.possible_algs(),
85        }
86    }
87}
88
89#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
90pub struct RsaPublicParameters {
91    #[schemars(with = "String")]
92    n: Base64UrlNoPad,
93
94    #[schemars(with = "String")]
95    e: Base64UrlNoPad,
96}
97
98impl ParametersInfo for RsaPublicParameters {
99    fn kty(&self) -> JsonWebKeyType {
100        JsonWebKeyType::Rsa
101    }
102
103    fn possible_algs(&self) -> &[JsonWebSignatureAlg] {
104        &[
105            JsonWebSignatureAlg::Rs256,
106            JsonWebSignatureAlg::Rs384,
107            JsonWebSignatureAlg::Rs512,
108            JsonWebSignatureAlg::Ps256,
109            JsonWebSignatureAlg::Ps384,
110            JsonWebSignatureAlg::Ps512,
111        ]
112    }
113}
114
115impl RsaPublicParameters {
116    pub const fn new(n: Base64UrlNoPad, e: Base64UrlNoPad) -> Self {
117        Self { n, e }
118    }
119}
120
121#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
122pub struct EcPublicParameters {
123    pub(crate) crv: JsonWebKeyEcEllipticCurve,
124
125    #[schemars(with = "String")]
126    x: Base64UrlNoPad,
127
128    #[schemars(with = "String")]
129    y: Base64UrlNoPad,
130}
131
132impl EcPublicParameters {
133    pub const fn new(crv: JsonWebKeyEcEllipticCurve, x: Base64UrlNoPad, y: Base64UrlNoPad) -> Self {
134        Self { crv, x, y }
135    }
136}
137
138impl ParametersInfo for EcPublicParameters {
139    fn kty(&self) -> JsonWebKeyType {
140        JsonWebKeyType::Ec
141    }
142
143    fn possible_algs(&self) -> &[JsonWebSignatureAlg] {
144        match &self.crv {
145            JsonWebKeyEcEllipticCurve::P256 => &[JsonWebSignatureAlg::Es256],
146            JsonWebKeyEcEllipticCurve::P384 => &[JsonWebSignatureAlg::Es384],
147            JsonWebKeyEcEllipticCurve::P521 => &[JsonWebSignatureAlg::Es512],
148            JsonWebKeyEcEllipticCurve::Secp256K1 => &[JsonWebSignatureAlg::Es256K],
149            _ => &[],
150        }
151    }
152}
153
154#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
155pub struct OkpPublicParameters {
156    crv: JsonWebKeyOkpEllipticCurve,
157
158    #[schemars(with = "String")]
159    x: Base64UrlNoPad,
160}
161
162impl ParametersInfo for OkpPublicParameters {
163    fn kty(&self) -> JsonWebKeyType {
164        JsonWebKeyType::Okp
165    }
166
167    fn possible_algs(&self) -> &[JsonWebSignatureAlg] {
168        &[JsonWebSignatureAlg::EdDsa]
169    }
170}
171
172impl OkpPublicParameters {
173    pub const fn new(crv: JsonWebKeyOkpEllipticCurve, x: Base64UrlNoPad) -> Self {
174        Self { crv, x }
175    }
176}
177
178mod rsa_impls {
179    use rsa::{BigUint, RsaPublicKey, traits::PublicKeyParts};
180
181    use super::{JsonWebKeyPublicParameters, RsaPublicParameters};
182    use crate::base64::Base64UrlNoPad;
183
184    impl From<RsaPublicKey> for JsonWebKeyPublicParameters {
185        fn from(key: RsaPublicKey) -> Self {
186            Self::from(&key)
187        }
188    }
189
190    impl From<&RsaPublicKey> for JsonWebKeyPublicParameters {
191        fn from(key: &RsaPublicKey) -> Self {
192            Self::Rsa(key.into())
193        }
194    }
195
196    impl From<RsaPublicKey> for RsaPublicParameters {
197        fn from(key: RsaPublicKey) -> Self {
198            Self::from(&key)
199        }
200    }
201
202    impl From<&RsaPublicKey> for RsaPublicParameters {
203        fn from(key: &RsaPublicKey) -> Self {
204            Self {
205                n: Base64UrlNoPad::new(key.n().to_bytes_be()),
206                e: Base64UrlNoPad::new(key.e().to_bytes_be()),
207            }
208        }
209    }
210
211    impl TryFrom<RsaPublicParameters> for RsaPublicKey {
212        type Error = rsa::errors::Error;
213        fn try_from(value: RsaPublicParameters) -> Result<Self, Self::Error> {
214            (&value).try_into()
215        }
216    }
217
218    impl TryFrom<&RsaPublicParameters> for RsaPublicKey {
219        type Error = rsa::errors::Error;
220        fn try_from(value: &RsaPublicParameters) -> Result<Self, Self::Error> {
221            let n = BigUint::from_bytes_be(value.n.as_bytes());
222            let e = BigUint::from_bytes_be(value.e.as_bytes());
223            let key = RsaPublicKey::new(n, e)?;
224            Ok(key)
225        }
226    }
227}
228
229mod ec_impls {
230    use digest::typenum::Unsigned;
231    use ecdsa::EncodedPoint;
232    use elliptic_curve::{
233        AffinePoint, FieldBytes, PublicKey,
234        sec1::{Coordinates, FromEncodedPoint, ModulusSize, ToEncodedPoint},
235    };
236
237    use super::{super::JwkEcCurve, EcPublicParameters, JsonWebKeyPublicParameters};
238    use crate::base64::Base64UrlNoPad;
239
240    impl<C> TryFrom<&EcPublicParameters> for PublicKey<C>
241    where
242        C: elliptic_curve::CurveArithmetic,
243        AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
244        C::FieldBytesSize: ModulusSize + Unsigned,
245    {
246        type Error = elliptic_curve::Error;
247        fn try_from(value: &EcPublicParameters) -> Result<Self, Self::Error> {
248            let x = value
249                .x
250                .as_bytes()
251                .get(..C::FieldBytesSize::USIZE)
252                .ok_or(elliptic_curve::Error)?;
253            let y = value
254                .y
255                .as_bytes()
256                .get(..C::FieldBytesSize::USIZE)
257                .ok_or(elliptic_curve::Error)?;
258
259            let x = FieldBytes::<C>::from_slice(x);
260            let y = FieldBytes::<C>::from_slice(y);
261            let pubkey = EncodedPoint::<C>::from_affine_coordinates(x, y, false);
262            let pubkey: Option<_> = PublicKey::from_encoded_point(&pubkey).into();
263            pubkey.ok_or(elliptic_curve::Error)
264        }
265    }
266
267    impl<C> From<PublicKey<C>> for JsonWebKeyPublicParameters
268    where
269        C: elliptic_curve::CurveArithmetic + JwkEcCurve,
270        AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
271        C::FieldBytesSize: ModulusSize,
272    {
273        fn from(key: PublicKey<C>) -> Self {
274            (&key).into()
275        }
276    }
277
278    impl<C> From<&PublicKey<C>> for JsonWebKeyPublicParameters
279    where
280        C: elliptic_curve::CurveArithmetic + JwkEcCurve,
281        AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
282        C::FieldBytesSize: ModulusSize,
283    {
284        fn from(key: &PublicKey<C>) -> Self {
285            Self::Ec(key.into())
286        }
287    }
288
289    impl<C> From<PublicKey<C>> for EcPublicParameters
290    where
291        C: elliptic_curve::CurveArithmetic + JwkEcCurve,
292        AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
293        C::FieldBytesSize: ModulusSize,
294    {
295        fn from(key: PublicKey<C>) -> Self {
296            (&key).into()
297        }
298    }
299
300    impl<C> From<&PublicKey<C>> for EcPublicParameters
301    where
302        C: elliptic_curve::CurveArithmetic + JwkEcCurve,
303        AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
304        C::FieldBytesSize: ModulusSize,
305    {
306        fn from(key: &PublicKey<C>) -> Self {
307            let point = key.to_encoded_point(false);
308            let Coordinates::Uncompressed { x, y } = point.coordinates() else {
309                unreachable!()
310            };
311            EcPublicParameters {
312                crv: C::CRV,
313                x: Base64UrlNoPad::new(x.to_vec()),
314                y: Base64UrlNoPad::new(y.to_vec()),
315            }
316        }
317    }
318}
319
320#[cfg(test)]
321mod tests {
322    use super::*;
323
324    #[test]
325    fn test_thumbprint_rfc_example() {
326        // From https://www.rfc-editor.org/rfc/rfc7638.html#section-3.1
327        let n = Base64UrlNoPad::parse(
328            "\
329            0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAt\
330            VT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn6\
331            4tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FD\
332            W2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n9\
333            1CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINH\
334            aQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
335        )
336        .unwrap();
337        let e = Base64UrlNoPad::parse("AQAB").unwrap();
338
339        let jwkpps = JsonWebKeyPublicParameters::Rsa(RsaPublicParameters { n, e });
340
341        assert_eq!(
342            jwkpps.thumbprint_sha256_base64(),
343            "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs"
344        );
345    }
346}