Skip to main content

mas_jose/jwk/
public_parameters.rs

1// Copyright 2025, 2026 Element Creations Ltd.
2// Copyright 2024, 2025 New Vector Ltd.
3// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
4//
5// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
6// Please see LICENSE files in the repository root for full details.
7
8use mas_iana::jose::{
9    JsonWebKeyEcEllipticCurve, JsonWebKeyOkpEllipticCurve, JsonWebKeyType, JsonWebSignatureAlg,
10};
11use schemars::JsonSchema;
12use serde::{Deserialize, Serialize};
13
14use super::ParametersInfo;
15use crate::{base64::Base64UrlNoPad, jwk::Thumbprint};
16
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
18#[serde(tag = "kty")]
19pub enum JsonWebKeyPublicParameters {
20    #[serde(rename = "RSA")]
21    Rsa(RsaPublicParameters),
22
23    #[serde(rename = "EC")]
24    Ec(EcPublicParameters),
25
26    #[serde(rename = "OKP")]
27    Okp(OkpPublicParameters),
28}
29
30impl JsonWebKeyPublicParameters {
31    #[must_use]
32    pub const fn rsa(&self) -> Option<&RsaPublicParameters> {
33        match self {
34            Self::Rsa(params) => Some(params),
35            _ => None,
36        }
37    }
38
39    #[must_use]
40    pub const fn ec(&self) -> Option<&EcPublicParameters> {
41        match self {
42            Self::Ec(params) => Some(params),
43            _ => None,
44        }
45    }
46
47    #[must_use]
48    pub const fn okp(&self) -> Option<&OkpPublicParameters> {
49        match self {
50            Self::Okp(params) => Some(params),
51            _ => None,
52        }
53    }
54}
55
56impl Thumbprint for JsonWebKeyPublicParameters {
57    fn thumbprint_prehashed(&self) -> String {
58        match self {
59            JsonWebKeyPublicParameters::Rsa(RsaPublicParameters { n, e }) => {
60                format!("{{\"e\":\"{e}\",\"kty\":\"RSA\",\"n\":\"{n}\"}}")
61            }
62            JsonWebKeyPublicParameters::Ec(EcPublicParameters { crv, x, y }) => {
63                format!("{{\"crv\":\"{crv}\",\"kty\":\"EC\",\"x\":\"{x}\",\"y\":\"{y}\"}}")
64            }
65            JsonWebKeyPublicParameters::Okp(OkpPublicParameters { crv, x }) => {
66                format!("{{\"crv\":\"{crv}\",\"kty\":\"OKP\",\"x\":\"{x}\"}}")
67            }
68        }
69    }
70}
71
72impl ParametersInfo for JsonWebKeyPublicParameters {
73    fn kty(&self) -> JsonWebKeyType {
74        match self {
75            Self::Rsa(_) => JsonWebKeyType::Rsa,
76            Self::Ec(_) => JsonWebKeyType::Ec,
77            Self::Okp(_) => JsonWebKeyType::Okp,
78        }
79    }
80
81    fn possible_algs(&self) -> &[JsonWebSignatureAlg] {
82        match self {
83            JsonWebKeyPublicParameters::Rsa(p) => p.possible_algs(),
84            JsonWebKeyPublicParameters::Ec(p) => p.possible_algs(),
85            JsonWebKeyPublicParameters::Okp(p) => p.possible_algs(),
86        }
87    }
88}
89
90#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
91pub struct RsaPublicParameters {
92    #[schemars(with = "String")]
93    n: Base64UrlNoPad,
94
95    #[schemars(with = "String")]
96    e: Base64UrlNoPad,
97}
98
99impl ParametersInfo for RsaPublicParameters {
100    fn kty(&self) -> JsonWebKeyType {
101        JsonWebKeyType::Rsa
102    }
103
104    fn possible_algs(&self) -> &[JsonWebSignatureAlg] {
105        &[
106            JsonWebSignatureAlg::Rs256,
107            JsonWebSignatureAlg::Rs384,
108            JsonWebSignatureAlg::Rs512,
109            JsonWebSignatureAlg::Ps256,
110            JsonWebSignatureAlg::Ps384,
111            JsonWebSignatureAlg::Ps512,
112        ]
113    }
114}
115
116impl RsaPublicParameters {
117    pub const fn new(n: Base64UrlNoPad, e: Base64UrlNoPad) -> Self {
118        Self { n, e }
119    }
120}
121
122#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
123pub struct EcPublicParameters {
124    pub(crate) crv: JsonWebKeyEcEllipticCurve,
125
126    #[schemars(with = "String")]
127    x: Base64UrlNoPad,
128
129    #[schemars(with = "String")]
130    y: Base64UrlNoPad,
131}
132
133impl EcPublicParameters {
134    pub const fn new(crv: JsonWebKeyEcEllipticCurve, x: Base64UrlNoPad, y: Base64UrlNoPad) -> Self {
135        Self { crv, x, y }
136    }
137}
138
139impl ParametersInfo for EcPublicParameters {
140    fn kty(&self) -> JsonWebKeyType {
141        JsonWebKeyType::Ec
142    }
143
144    fn possible_algs(&self) -> &[JsonWebSignatureAlg] {
145        match &self.crv {
146            JsonWebKeyEcEllipticCurve::P256 => &[JsonWebSignatureAlg::Es256],
147            JsonWebKeyEcEllipticCurve::P384 => &[JsonWebSignatureAlg::Es384],
148            JsonWebKeyEcEllipticCurve::P521 => &[JsonWebSignatureAlg::Es512],
149            JsonWebKeyEcEllipticCurve::Secp256K1 => &[JsonWebSignatureAlg::Es256K],
150            _ => &[],
151        }
152    }
153}
154
155#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
156pub struct OkpPublicParameters {
157    crv: JsonWebKeyOkpEllipticCurve,
158
159    #[schemars(with = "String")]
160    x: Base64UrlNoPad,
161}
162
163impl ParametersInfo for OkpPublicParameters {
164    fn kty(&self) -> JsonWebKeyType {
165        JsonWebKeyType::Okp
166    }
167
168    fn possible_algs(&self) -> &[JsonWebSignatureAlg] {
169        &[JsonWebSignatureAlg::EdDsa]
170    }
171}
172
173impl OkpPublicParameters {
174    pub const fn new(crv: JsonWebKeyOkpEllipticCurve, x: Base64UrlNoPad) -> Self {
175        Self { crv, x }
176    }
177}
178
179mod rsa_impls {
180    use rsa::{BoxedUint, RsaPublicKey, traits::PublicKeyParts};
181
182    use super::{JsonWebKeyPublicParameters, RsaPublicParameters};
183    use crate::base64::Base64UrlNoPad;
184
185    impl From<RsaPublicKey> for JsonWebKeyPublicParameters {
186        fn from(key: RsaPublicKey) -> Self {
187            Self::from(&key)
188        }
189    }
190
191    impl From<&RsaPublicKey> for JsonWebKeyPublicParameters {
192        fn from(key: &RsaPublicKey) -> Self {
193            Self::Rsa(key.into())
194        }
195    }
196
197    impl From<RsaPublicKey> for RsaPublicParameters {
198        fn from(key: RsaPublicKey) -> Self {
199            Self::from(&key)
200        }
201    }
202
203    impl From<&RsaPublicKey> for RsaPublicParameters {
204        fn from(key: &RsaPublicKey) -> Self {
205            Self {
206                n: Base64UrlNoPad::new(key.n().to_be_bytes_trimmed_vartime().into_vec()),
207                e: Base64UrlNoPad::new(key.e().to_be_bytes_trimmed_vartime().into_vec()),
208            }
209        }
210    }
211
212    impl TryFrom<RsaPublicParameters> for RsaPublicKey {
213        type Error = rsa::errors::Error;
214        fn try_from(value: RsaPublicParameters) -> Result<Self, Self::Error> {
215            (&value).try_into()
216        }
217    }
218
219    impl TryFrom<&RsaPublicParameters> for RsaPublicKey {
220        type Error = rsa::errors::Error;
221        fn try_from(value: &RsaPublicParameters) -> Result<Self, Self::Error> {
222            let n = BoxedUint::from_be_slice_vartime(value.n.as_bytes());
223            let e = BoxedUint::from_be_slice_vartime(value.e.as_bytes());
224            let key = RsaPublicKey::new(n, e)?;
225            Ok(key)
226        }
227    }
228}
229
230mod ec_impls {
231    use digest::typenum::Unsigned;
232    use elliptic_curve::{
233        AffinePoint, FieldBytes, PublicKey,
234        sec1::{Coordinates, FromSec1Point, ModulusSize, Sec1Point, ToSec1Point},
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>: FromSec1Point<C> + ToSec1Point<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>::try_from(x).map_err(|_| elliptic_curve::Error)?;
260            let y = FieldBytes::<C>::try_from(y).map_err(|_| elliptic_curve::Error)?;
261            let pubkey = Sec1Point::<C>::from_affine_coordinates(&x, &y, false);
262            let pubkey: Option<_> = PublicKey::from_sec1_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>: FromSec1Point<C> + ToSec1Point<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>: FromSec1Point<C> + ToSec1Point<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>: FromSec1Point<C> + ToSec1Point<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>: FromSec1Point<C> + ToSec1Point<C>,
304        C::FieldBytesSize: ModulusSize,
305    {
306        fn from(key: &PublicKey<C>) -> Self {
307            let point = key.to_sec1_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}