elliptic_curve/
jwk.rs

1//! JSON Web Key (JWK) Support.
2//!
3//! Specified in RFC 7518 Section 6: Cryptographic Algorithms for Keys:
4//! <https://tools.ietf.org/html/rfc7518#section-6>
5
6use crate::{
7    sec1::{Coordinates, EncodedPoint, ModulusSize, ValidatePublicKey},
8    secret_key::SecretKey,
9    Curve, Error, FieldBytes, FieldBytesSize, Result,
10};
11use alloc::{
12    borrow::ToOwned,
13    format,
14    string::{String, ToString},
15};
16use base64ct::{Base64UrlUnpadded as Base64Url, Encoding};
17use core::{
18    fmt::{self, Debug},
19    marker::PhantomData,
20    str::{self, FromStr},
21};
22use serdect::serde::{de, ser, Deserialize, Serialize};
23use zeroize::{Zeroize, ZeroizeOnDrop};
24
25#[cfg(feature = "arithmetic")]
26use crate::{
27    public_key::PublicKey,
28    sec1::{FromEncodedPoint, ToEncodedPoint},
29    AffinePoint, CurveArithmetic,
30};
31
32/// Key Type (`kty`) for elliptic curve keys.
33pub const EC_KTY: &str = "EC";
34
35/// Deserialization error message.
36const DE_ERROR_MSG: &str = "struct JwkEcKey with 5 elements";
37
38/// Name of the JWK type
39const JWK_TYPE_NAME: &str = "JwkEcKey";
40
41/// Field names
42const FIELDS: &[&str] = &["kty", "crv", "x", "y", "d"];
43
44/// Elliptic curve parameters used by JSON Web Keys.
45pub trait JwkParameters: Curve {
46    /// The `crv` parameter which identifies a particular elliptic curve
47    /// as defined in RFC 7518 Section 6.2.1.1:
48    /// <https://tools.ietf.org/html/rfc7518#section-6.2.1.1>
49    ///
50    /// Curve values are registered in the IANA "JSON Web Key Elliptic Curve"
51    /// registry defined in RFC 7518 Section 7.6:
52    /// <https://tools.ietf.org/html/rfc7518#section-7.6>
53    const CRV: &'static str;
54}
55
56/// JSON Web Key (JWK) with a `kty` of `"EC"` (elliptic curve).
57///
58/// Specified in [RFC 7518 Section 6: Cryptographic Algorithms for Keys][1].
59///
60/// This type can represent either a public/private keypair, or just a
61/// public key, depending on whether or not the `d` parameter is present.
62///
63/// [1]: https://tools.ietf.org/html/rfc7518#section-6
64// TODO(tarcieri): eagerly decode or validate `x`, `y`, and `d` as Base64
65#[derive(Clone)]
66pub struct JwkEcKey {
67    /// The `crv` parameter which identifies a particular elliptic curve
68    /// as defined in RFC 7518 Section 6.2.1.1:
69    /// <https://tools.ietf.org/html/rfc7518#section-6.2.1.1>
70    crv: String,
71
72    /// The x-coordinate of the elliptic curve point which is the public key
73    /// value associated with this JWK as defined in RFC 7518 6.2.1.2:
74    /// <https://tools.ietf.org/html/rfc7518#section-6.2.1.2>
75    x: String,
76
77    /// The y-coordinate of the elliptic curve point which is the public key
78    /// value associated with this JWK as defined in RFC 7518 6.2.1.3:
79    /// <https://tools.ietf.org/html/rfc7518#section-6.2.1.3>
80    y: String,
81
82    /// The `d` ECC private key parameter as described in RFC 7518 6.2.2.1:
83    /// <https://tools.ietf.org/html/rfc7518#section-6.2.2.1>
84    ///
85    /// Value is optional and if omitted, this JWK represents a private key.
86    ///
87    /// Inner value is encoded according to the `Integer-to-Octet-String`
88    /// conversion as defined in SEC1 section 2.3.7:
89    /// <https://www.secg.org/sec1-v2.pdf>
90    d: Option<String>,
91}
92
93impl JwkEcKey {
94    /// Get the `crv` parameter for this JWK.
95    pub fn crv(&self) -> &str {
96        &self.crv
97    }
98
99    /// Is this JWK a keypair that includes a private key?
100    pub fn is_keypair(&self) -> bool {
101        self.d.is_some()
102    }
103
104    /// Does this JWK contain only a public key?
105    pub fn is_public_key(&self) -> bool {
106        self.d.is_none()
107    }
108
109    /// Decode a JWK into a [`PublicKey`].
110    #[cfg(feature = "arithmetic")]
111    pub fn to_public_key<C>(&self) -> Result<PublicKey<C>>
112    where
113        C: CurveArithmetic + JwkParameters,
114        AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
115        FieldBytesSize<C>: ModulusSize,
116    {
117        PublicKey::from_sec1_bytes(self.to_encoded_point::<C>()?.as_bytes())
118    }
119
120    /// Create a JWK from a SEC1 [`EncodedPoint`].
121    pub fn from_encoded_point<C>(point: &EncodedPoint<C>) -> Option<Self>
122    where
123        C: Curve + JwkParameters,
124        FieldBytesSize<C>: ModulusSize,
125    {
126        match point.coordinates() {
127            Coordinates::Uncompressed { x, y } => Some(JwkEcKey {
128                crv: C::CRV.to_owned(),
129                x: Base64Url::encode_string(x),
130                y: Base64Url::encode_string(y),
131                d: None,
132            }),
133            _ => None,
134        }
135    }
136
137    /// Get the public key component of this JWK as a SEC1 [`EncodedPoint`].
138    pub fn to_encoded_point<C>(&self) -> Result<EncodedPoint<C>>
139    where
140        C: Curve + JwkParameters,
141        FieldBytesSize<C>: ModulusSize,
142    {
143        if self.crv != C::CRV {
144            return Err(Error);
145        }
146
147        let x = decode_base64url_fe::<C>(&self.x)?;
148        let y = decode_base64url_fe::<C>(&self.y)?;
149        Ok(EncodedPoint::<C>::from_affine_coordinates(&x, &y, false))
150    }
151
152    /// Decode a JWK into a [`SecretKey`].
153    #[cfg(feature = "arithmetic")]
154    pub fn to_secret_key<C>(&self) -> Result<SecretKey<C>>
155    where
156        C: Curve + JwkParameters + ValidatePublicKey,
157        FieldBytesSize<C>: ModulusSize,
158    {
159        self.try_into()
160    }
161}
162
163impl FromStr for JwkEcKey {
164    type Err = Error;
165
166    fn from_str(s: &str) -> Result<Self> {
167        serde_json::from_str(s).map_err(|_| Error)
168    }
169}
170
171impl ToString for JwkEcKey {
172    fn to_string(&self) -> String {
173        serde_json::to_string(self).expect("JWK encoding error")
174    }
175}
176
177impl<C> TryFrom<JwkEcKey> for SecretKey<C>
178where
179    C: Curve + JwkParameters + ValidatePublicKey,
180    FieldBytesSize<C>: ModulusSize,
181{
182    type Error = Error;
183
184    fn try_from(jwk: JwkEcKey) -> Result<SecretKey<C>> {
185        (&jwk).try_into()
186    }
187}
188
189impl<C> TryFrom<&JwkEcKey> for SecretKey<C>
190where
191    C: Curve + JwkParameters + ValidatePublicKey,
192    FieldBytesSize<C>: ModulusSize,
193{
194    type Error = Error;
195
196    fn try_from(jwk: &JwkEcKey) -> Result<SecretKey<C>> {
197        if let Some(d_base64) = &jwk.d {
198            let pk = jwk.to_encoded_point::<C>()?;
199            let mut d_bytes = decode_base64url_fe::<C>(d_base64)?;
200            let result = SecretKey::from_slice(&d_bytes);
201            d_bytes.zeroize();
202
203            result.and_then(|secret_key| {
204                C::validate_public_key(&secret_key, &pk)?;
205                Ok(secret_key)
206            })
207        } else {
208            Err(Error)
209        }
210    }
211}
212
213#[cfg(feature = "arithmetic")]
214impl<C> From<SecretKey<C>> for JwkEcKey
215where
216    C: CurveArithmetic + JwkParameters,
217    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
218    FieldBytesSize<C>: ModulusSize,
219{
220    fn from(sk: SecretKey<C>) -> JwkEcKey {
221        (&sk).into()
222    }
223}
224
225#[cfg(feature = "arithmetic")]
226impl<C> From<&SecretKey<C>> for JwkEcKey
227where
228    C: CurveArithmetic + JwkParameters,
229    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
230    FieldBytesSize<C>: ModulusSize,
231{
232    fn from(sk: &SecretKey<C>) -> JwkEcKey {
233        let mut jwk = sk.public_key().to_jwk();
234        let mut d = sk.to_bytes();
235        jwk.d = Some(Base64Url::encode_string(&d));
236        d.zeroize();
237        jwk
238    }
239}
240
241#[cfg(feature = "arithmetic")]
242impl<C> TryFrom<JwkEcKey> for PublicKey<C>
243where
244    C: CurveArithmetic + JwkParameters,
245    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
246    FieldBytesSize<C>: ModulusSize,
247{
248    type Error = Error;
249
250    fn try_from(jwk: JwkEcKey) -> Result<PublicKey<C>> {
251        (&jwk).try_into()
252    }
253}
254
255#[cfg(feature = "arithmetic")]
256impl<C> TryFrom<&JwkEcKey> for PublicKey<C>
257where
258    C: CurveArithmetic + JwkParameters,
259    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
260    FieldBytesSize<C>: ModulusSize,
261{
262    type Error = Error;
263
264    fn try_from(jwk: &JwkEcKey) -> Result<PublicKey<C>> {
265        PublicKey::from_sec1_bytes(jwk.to_encoded_point::<C>()?.as_bytes())
266    }
267}
268
269#[cfg(feature = "arithmetic")]
270impl<C> From<PublicKey<C>> for JwkEcKey
271where
272    C: CurveArithmetic + JwkParameters,
273    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
274    FieldBytesSize<C>: ModulusSize,
275{
276    fn from(pk: PublicKey<C>) -> JwkEcKey {
277        (&pk).into()
278    }
279}
280
281#[cfg(feature = "arithmetic")]
282impl<C> From<&PublicKey<C>> for JwkEcKey
283where
284    C: CurveArithmetic + JwkParameters,
285    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
286    FieldBytesSize<C>: ModulusSize,
287{
288    fn from(pk: &PublicKey<C>) -> JwkEcKey {
289        Self::from_encoded_point::<C>(&pk.to_encoded_point(false)).expect("JWK encoding error")
290    }
291}
292
293impl Debug for JwkEcKey {
294    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295        let d = if self.d.is_some() {
296            "Some(...)"
297        } else {
298            "None"
299        };
300
301        // NOTE: this implementation omits the `d` private key parameter
302        f.debug_struct(JWK_TYPE_NAME)
303            .field("crv", &self.crv)
304            .field("x", &self.x)
305            .field("y", &self.y)
306            .field("d", &d)
307            .finish()
308    }
309}
310
311impl PartialEq for JwkEcKey {
312    fn eq(&self, other: &Self) -> bool {
313        use subtle::ConstantTimeEq;
314
315        // Compare private key in constant time
316        let d_eq = match &self.d {
317            Some(d1) => match &other.d {
318                Some(d2) => d1.as_bytes().ct_eq(d2.as_bytes()).into(),
319                None => other.d.is_none(),
320            },
321            None => other.d.is_none(),
322        };
323
324        self.crv == other.crv && self.x == other.x && self.y == other.y && d_eq
325    }
326}
327
328impl Eq for JwkEcKey {}
329
330impl ZeroizeOnDrop for JwkEcKey {}
331
332impl Drop for JwkEcKey {
333    fn drop(&mut self) {
334        self.zeroize();
335    }
336}
337
338impl Zeroize for JwkEcKey {
339    fn zeroize(&mut self) {
340        if let Some(d) = &mut self.d {
341            d.zeroize();
342        }
343    }
344}
345
346impl<'de> Deserialize<'de> for JwkEcKey {
347    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
348    where
349        D: de::Deserializer<'de>,
350    {
351        /// Field positions
352        enum Field {
353            Kty,
354            Crv,
355            X,
356            Y,
357            D,
358        }
359
360        /// Field visitor
361        struct FieldVisitor;
362
363        impl<'de> de::Visitor<'de> for FieldVisitor {
364            type Value = Field;
365
366            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
367                fmt::Formatter::write_str(formatter, "field identifier")
368            }
369
370            fn visit_u64<E>(self, value: u64) -> core::result::Result<Self::Value, E>
371            where
372                E: de::Error,
373            {
374                match value {
375                    0 => Ok(Field::Kty),
376                    1 => Ok(Field::Crv),
377                    2 => Ok(Field::X),
378                    3 => Ok(Field::Y),
379                    4 => Ok(Field::D),
380                    _ => Err(de::Error::invalid_value(
381                        de::Unexpected::Unsigned(value),
382                        &"field index 0 <= i < 5",
383                    )),
384                }
385            }
386
387            fn visit_str<E>(self, value: &str) -> core::result::Result<Self::Value, E>
388            where
389                E: de::Error,
390            {
391                self.visit_bytes(value.as_bytes())
392            }
393
394            fn visit_bytes<E>(self, value: &[u8]) -> core::result::Result<Self::Value, E>
395            where
396                E: de::Error,
397            {
398                match value {
399                    b"kty" => Ok(Field::Kty),
400                    b"crv" => Ok(Field::Crv),
401                    b"x" => Ok(Field::X),
402                    b"y" => Ok(Field::Y),
403                    b"d" => Ok(Field::D),
404                    _ => Err(de::Error::unknown_field(
405                        &String::from_utf8_lossy(value),
406                        FIELDS,
407                    )),
408                }
409            }
410        }
411
412        impl<'de> Deserialize<'de> for Field {
413            #[inline]
414            fn deserialize<D>(__deserializer: D) -> core::result::Result<Self, D::Error>
415            where
416                D: de::Deserializer<'de>,
417            {
418                de::Deserializer::deserialize_identifier(__deserializer, FieldVisitor)
419            }
420        }
421
422        struct Visitor<'de> {
423            marker: PhantomData<JwkEcKey>,
424            lifetime: PhantomData<&'de ()>,
425        }
426
427        impl<'de> de::Visitor<'de> for Visitor<'de> {
428            type Value = JwkEcKey;
429
430            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
431                fmt::Formatter::write_str(formatter, "struct JwkEcKey")
432            }
433
434            #[inline]
435            fn visit_seq<A>(self, mut seq: A) -> core::result::Result<Self::Value, A::Error>
436            where
437                A: de::SeqAccess<'de>,
438            {
439                let kty = de::SeqAccess::next_element::<String>(&mut seq)?
440                    .ok_or_else(|| de::Error::invalid_length(0, &DE_ERROR_MSG))?;
441
442                if kty != EC_KTY {
443                    return Err(de::Error::custom(format!("unsupported JWK kty: {kty:?}")));
444                }
445
446                let crv = de::SeqAccess::next_element::<String>(&mut seq)?
447                    .ok_or_else(|| de::Error::invalid_length(1, &DE_ERROR_MSG))?;
448
449                let x = de::SeqAccess::next_element::<String>(&mut seq)?
450                    .ok_or_else(|| de::Error::invalid_length(2, &DE_ERROR_MSG))?;
451
452                let y = de::SeqAccess::next_element::<String>(&mut seq)?
453                    .ok_or_else(|| de::Error::invalid_length(3, &DE_ERROR_MSG))?;
454
455                let d = de::SeqAccess::next_element::<Option<String>>(&mut seq)?
456                    .ok_or_else(|| de::Error::invalid_length(4, &DE_ERROR_MSG))?;
457
458                Ok(JwkEcKey { crv, x, y, d })
459            }
460
461            #[inline]
462            fn visit_map<A>(self, mut map: A) -> core::result::Result<Self::Value, A::Error>
463            where
464                A: de::MapAccess<'de>,
465            {
466                let mut kty: Option<String> = None;
467                let mut crv: Option<String> = None;
468                let mut x: Option<String> = None;
469                let mut y: Option<String> = None;
470                let mut d: Option<String> = None;
471
472                while let Some(key) = de::MapAccess::next_key::<Field>(&mut map)? {
473                    match key {
474                        Field::Kty => {
475                            if kty.is_none() {
476                                kty = Some(de::MapAccess::next_value::<String>(&mut map)?);
477                            } else {
478                                return Err(de::Error::duplicate_field(FIELDS[0]));
479                            }
480                        }
481                        Field::Crv => {
482                            if crv.is_none() {
483                                crv = Some(de::MapAccess::next_value::<String>(&mut map)?);
484                            } else {
485                                return Err(de::Error::duplicate_field(FIELDS[1]));
486                            }
487                        }
488                        Field::X => {
489                            if x.is_none() {
490                                x = Some(de::MapAccess::next_value::<String>(&mut map)?);
491                            } else {
492                                return Err(de::Error::duplicate_field(FIELDS[2]));
493                            }
494                        }
495                        Field::Y => {
496                            if y.is_none() {
497                                y = Some(de::MapAccess::next_value::<String>(&mut map)?);
498                            } else {
499                                return Err(de::Error::duplicate_field(FIELDS[3]));
500                            }
501                        }
502                        Field::D => {
503                            if d.is_none() {
504                                d = de::MapAccess::next_value::<Option<String>>(&mut map)?;
505                            } else {
506                                return Err(de::Error::duplicate_field(FIELDS[4]));
507                            }
508                        }
509                    }
510                }
511
512                let kty = kty.ok_or_else(|| de::Error::missing_field("kty"))?;
513
514                if kty != EC_KTY {
515                    return Err(de::Error::custom(format!("unsupported JWK kty: {kty}")));
516                }
517
518                let crv = crv.ok_or_else(|| de::Error::missing_field("crv"))?;
519                let x = x.ok_or_else(|| de::Error::missing_field("x"))?;
520                let y = y.ok_or_else(|| de::Error::missing_field("y"))?;
521
522                Ok(JwkEcKey { crv, x, y, d })
523            }
524        }
525
526        de::Deserializer::deserialize_struct(
527            deserializer,
528            JWK_TYPE_NAME,
529            FIELDS,
530            Visitor {
531                marker: PhantomData::<JwkEcKey>,
532                lifetime: PhantomData,
533            },
534        )
535    }
536}
537
538impl Serialize for JwkEcKey {
539    fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
540    where
541        S: ser::Serializer,
542    {
543        use ser::SerializeStruct;
544
545        let mut state = serializer.serialize_struct(JWK_TYPE_NAME, 5)?;
546
547        for (i, field) in [EC_KTY, &self.crv, &self.x, &self.y].iter().enumerate() {
548            state.serialize_field(FIELDS[i], field)?;
549        }
550
551        if let Some(d) = &self.d {
552            state.serialize_field("d", d)?;
553        }
554
555        ser::SerializeStruct::end(state)
556    }
557}
558
559/// Decode a Base64url-encoded field element
560fn decode_base64url_fe<C: Curve>(s: &str) -> Result<FieldBytes<C>> {
561    let mut result = FieldBytes::<C>::default();
562    Base64Url::decode(s, &mut result).map_err(|_| Error)?;
563    Ok(result)
564}
565
566#[cfg(test)]
567mod tests {
568    #![allow(clippy::unwrap_used, clippy::panic)]
569    use super::*;
570
571    #[cfg(feature = "dev")]
572    use crate::dev::MockCurve;
573
574    /// Example private key. From RFC 7518 Appendix C:
575    /// <https://tools.ietf.org/html/rfc7518#appendix-C>
576    const JWK_PRIVATE_KEY: &str = r#"
577        {
578          "kty":"EC",
579          "crv":"P-256",
580          "x":"gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0",
581          "y":"SLW_xSffzlPWrHEVI30DHM_4egVwt3NQqeUD7nMFpps",
582          "d":"0_NxaRPUMQoAJt50Gz8YiTr8gRTwyEaCumd-MToTmIo"
583        }
584    "#;
585
586    /// Example public key.
587    const JWK_PUBLIC_KEY: &str = r#"
588        {
589          "kty":"EC",
590          "crv":"P-256",
591          "x":"gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0",
592          "y":"SLW_xSffzlPWrHEVI30DHM_4egVwt3NQqeUD7nMFpps"
593        }
594    "#;
595
596    /// Example unsupported JWK (RSA key)
597    const UNSUPPORTED_JWK: &str = r#"
598        {
599          "kty":"RSA",
600          "kid":"cc34c0a0-bd5a-4a3c-a50d-a2a7db7643df",
601          "use":"sig",
602          "n":"pjdss8ZaDfEH6K6U7GeW2nxDqR4IP049fk1fK0lndimbMMVBdPv_hSpm8T8EtBDxrUdi1OHZfMhUixGaut-3nQ4GG9nM249oxhCtxqqNvEXrmQRGqczyLxuh-fKn9Fg--hS9UpazHpfVAFnB5aCfXoNhPuI8oByyFKMKaOVgHNqP5NBEqabiLftZD3W_lsFCPGuzr4Vp0YS7zS2hDYScC2oOMu4rGU1LcMZf39p3153Cq7bS2Xh6Y-vw5pwzFYZdjQxDn8x8BG3fJ6j8TGLXQsbKH1218_HcUJRvMwdpbUQG5nvA2GXVqLqdwp054Lzk9_B_f1lVrmOKuHjTNHq48w",
603          "e":"AQAB",
604          "d":"ksDmucdMJXkFGZxiomNHnroOZxe8AmDLDGO1vhs-POa5PZM7mtUPonxwjVmthmpbZzla-kg55OFfO7YcXhg-Hm2OWTKwm73_rLh3JavaHjvBqsVKuorX3V3RYkSro6HyYIzFJ1Ek7sLxbjDRcDOj4ievSX0oN9l-JZhaDYlPlci5uJsoqro_YrE0PRRWVhtGynd-_aWgQv1YzkfZuMD-hJtDi1Im2humOWxA4eZrFs9eG-whXcOvaSwO4sSGbS99ecQZHM2TcdXeAs1PvjVgQ_dKnZlGN3lTWoWfQP55Z7Tgt8Nf1q4ZAKd-NlMe-7iqCFfsnFwXjSiaOa2CRGZn-Q",
605          "p":"4A5nU4ahEww7B65yuzmGeCUUi8ikWzv1C81pSyUKvKzu8CX41hp9J6oRaLGesKImYiuVQK47FhZ--wwfpRwHvSxtNU9qXb8ewo-BvadyO1eVrIk4tNV543QlSe7pQAoJGkxCia5rfznAE3InKF4JvIlchyqs0RQ8wx7lULqwnn0",
606          "q":"ven83GM6SfrmO-TBHbjTk6JhP_3CMsIvmSdo4KrbQNvp4vHO3w1_0zJ3URkmkYGhz2tgPlfd7v1l2I6QkIh4Bumdj6FyFZEBpxjE4MpfdNVcNINvVj87cLyTRmIcaGxmfylY7QErP8GFA-k4UoH_eQmGKGK44TRzYj5hZYGWIC8",
607          "dp":"lmmU_AG5SGxBhJqb8wxfNXDPJjf__i92BgJT2Vp4pskBbr5PGoyV0HbfUQVMnw977RONEurkR6O6gxZUeCclGt4kQlGZ-m0_XSWx13v9t9DIbheAtgVJ2mQyVDvK4m7aRYlEceFh0PsX8vYDS5o1txgPwb3oXkPTtrmbAGMUBpE",
608          "dq":"mxRTU3QDyR2EnCv0Nl0TCF90oliJGAHR9HJmBe__EjuCBbwHfcT8OG3hWOv8vpzokQPRl5cQt3NckzX3fs6xlJN4Ai2Hh2zduKFVQ2p-AF2p6Yfahscjtq-GY9cB85NxLy2IXCC0PF--Sq9LOrTE9QV988SJy_yUrAjcZ5MmECk",
609          "qi":"ldHXIrEmMZVaNwGzDF9WG8sHj2mOZmQpw9yrjLK9hAsmsNr5LTyqWAqJIYZSwPTYWhY4nu2O0EY9G9uYiqewXfCKw_UngrJt8Xwfq1Zruz0YY869zPN4GiE9-9rzdZB33RBw8kIOquY3MK74FMwCihYx_LiU2YTHkaoJ3ncvtvg"
610        }
611    "#;
612
613    #[test]
614    fn parse_private_key() {
615        let jwk = JwkEcKey::from_str(JWK_PRIVATE_KEY).unwrap();
616        assert_eq!(jwk.crv, "P-256");
617        assert_eq!(jwk.x, "gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0");
618        assert_eq!(jwk.y, "SLW_xSffzlPWrHEVI30DHM_4egVwt3NQqeUD7nMFpps");
619        assert_eq!(
620            jwk.d.as_ref().unwrap(),
621            "0_NxaRPUMQoAJt50Gz8YiTr8gRTwyEaCumd-MToTmIo"
622        );
623    }
624
625    #[test]
626    fn parse_public_key() {
627        let jwk = JwkEcKey::from_str(JWK_PUBLIC_KEY).unwrap();
628        assert_eq!(jwk.crv, "P-256");
629        assert_eq!(jwk.x, "gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0");
630        assert_eq!(jwk.y, "SLW_xSffzlPWrHEVI30DHM_4egVwt3NQqeUD7nMFpps");
631        assert_eq!(jwk.d, None);
632    }
633
634    #[test]
635    fn parse_unsupported() {
636        assert_eq!(JwkEcKey::from_str(UNSUPPORTED_JWK), Err(Error));
637    }
638
639    #[test]
640    fn serialize_private_key() {
641        let actual = JwkEcKey::from_str(JWK_PRIVATE_KEY).unwrap().to_string();
642        let expected: String = JWK_PRIVATE_KEY.split_whitespace().collect();
643        assert_eq!(actual, expected);
644    }
645
646    #[test]
647    fn serialize_public_key() {
648        let actual = JwkEcKey::from_str(JWK_PUBLIC_KEY).unwrap().to_string();
649        let expected: String = JWK_PUBLIC_KEY.split_whitespace().collect();
650        assert_eq!(actual, expected);
651    }
652
653    #[cfg(feature = "dev")]
654    #[test]
655    fn jwk_into_encoded_point() {
656        let jwk = JwkEcKey::from_str(JWK_PUBLIC_KEY).unwrap();
657        let point = jwk.to_encoded_point::<MockCurve>().unwrap();
658        let (x, y) = match point.coordinates() {
659            Coordinates::Uncompressed { x, y } => (x, y),
660            other => panic!("unexpected coordinates: {other:?}"),
661        };
662
663        assert_eq!(&decode_base64url_fe::<MockCurve>(&jwk.x).unwrap(), x);
664        assert_eq!(&decode_base64url_fe::<MockCurve>(&jwk.y).unwrap(), y);
665    }
666
667    #[cfg(feature = "dev")]
668    #[test]
669    fn encoded_point_into_jwk() {
670        let jwk = JwkEcKey::from_str(JWK_PUBLIC_KEY).unwrap();
671        let point = jwk.to_encoded_point::<MockCurve>().unwrap();
672        let jwk2 = JwkEcKey::from_encoded_point::<MockCurve>(&point).unwrap();
673        assert_eq!(jwk, jwk2);
674    }
675}