diff options
Diffstat (limited to 'vendor/elliptic-curve/src/jwk.rs')
-rw-r--r-- | vendor/elliptic-curve/src/jwk.rs | 674 |
1 files changed, 674 insertions, 0 deletions
diff --git a/vendor/elliptic-curve/src/jwk.rs b/vendor/elliptic-curve/src/jwk.rs new file mode 100644 index 0000000..e0233cc --- /dev/null +++ b/vendor/elliptic-curve/src/jwk.rs @@ -0,0 +1,674 @@ +//! JSON Web Key (JWK) Support. +//! +//! Specified in RFC 7518 Section 6: Cryptographic Algorithms for Keys: +//! <https://tools.ietf.org/html/rfc7518#section-6> + +use crate::{ + sec1::{Coordinates, EncodedPoint, ModulusSize, ValidatePublicKey}, + secret_key::SecretKey, + Curve, Error, FieldBytes, FieldBytesSize, Result, +}; +use alloc::{ + borrow::ToOwned, + format, + string::{String, ToString}, +}; +use base64ct::{Base64UrlUnpadded as Base64Url, Encoding}; +use core::{ + fmt::{self, Debug}, + marker::PhantomData, + str::{self, FromStr}, +}; +use serdect::serde::{de, ser, Deserialize, Serialize}; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +#[cfg(feature = "arithmetic")] +use crate::{ + public_key::PublicKey, + sec1::{FromEncodedPoint, ToEncodedPoint}, + AffinePoint, CurveArithmetic, +}; + +/// Key Type (`kty`) for elliptic curve keys. +pub const EC_KTY: &str = "EC"; + +/// Deserialization error message. +const DE_ERROR_MSG: &str = "struct JwkEcKey with 5 elements"; + +/// Name of the JWK type +const JWK_TYPE_NAME: &str = "JwkEcKey"; + +/// Field names +const FIELDS: &[&str] = &["kty", "crv", "x", "y", "d"]; + +/// Elliptic curve parameters used by JSON Web Keys. +pub trait JwkParameters: Curve { + /// The `crv` parameter which identifies a particular elliptic curve + /// as defined in RFC 7518 Section 6.2.1.1: + /// <https://tools.ietf.org/html/rfc7518#section-6.2.1.1> + /// + /// Curve values are registered in the IANA "JSON Web Key Elliptic Curve" + /// registry defined in RFC 7518 Section 7.6: + /// <https://tools.ietf.org/html/rfc7518#section-7.6> + const CRV: &'static str; +} + +/// JSON Web Key (JWK) with a `kty` of `"EC"` (elliptic curve). +/// +/// Specified in [RFC 7518 Section 6: Cryptographic Algorithms for Keys][1]. +/// +/// This type can represent either a public/private keypair, or just a +/// public key, depending on whether or not the `d` parameter is present. +/// +/// [1]: https://tools.ietf.org/html/rfc7518#section-6 +// TODO(tarcieri): eagerly decode or validate `x`, `y`, and `d` as Base64 +#[derive(Clone)] +pub struct JwkEcKey { + /// The `crv` parameter which identifies a particular elliptic curve + /// as defined in RFC 7518 Section 6.2.1.1: + /// <https://tools.ietf.org/html/rfc7518#section-6.2.1.1> + crv: String, + + /// The x-coordinate of the elliptic curve point which is the public key + /// value associated with this JWK as defined in RFC 7518 6.2.1.2: + /// <https://tools.ietf.org/html/rfc7518#section-6.2.1.2> + x: String, + + /// The y-coordinate of the elliptic curve point which is the public key + /// value associated with this JWK as defined in RFC 7518 6.2.1.3: + /// <https://tools.ietf.org/html/rfc7518#section-6.2.1.3> + y: String, + + /// The `d` ECC private key parameter as described in RFC 7518 6.2.2.1: + /// <https://tools.ietf.org/html/rfc7518#section-6.2.2.1> + /// + /// Value is optional and if omitted, this JWK represents a private key. + /// + /// Inner value is encoded according to the `Integer-to-Octet-String` + /// conversion as defined in SEC1 section 2.3.7: + /// <https://www.secg.org/sec1-v2.pdf> + d: Option<String>, +} + +impl JwkEcKey { + /// Get the `crv` parameter for this JWK. + pub fn crv(&self) -> &str { + &self.crv + } + + /// Is this JWK a keypair that includes a private key? + pub fn is_keypair(&self) -> bool { + self.d.is_some() + } + + /// Does this JWK contain only a public key? + pub fn is_public_key(&self) -> bool { + self.d.is_none() + } + + /// Decode a JWK into a [`PublicKey`]. + #[cfg(feature = "arithmetic")] + pub fn to_public_key<C>(&self) -> Result<PublicKey<C>> + where + C: CurveArithmetic + JwkParameters, + AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>, + FieldBytesSize<C>: ModulusSize, + { + PublicKey::from_sec1_bytes(self.to_encoded_point::<C>()?.as_bytes()) + } + + /// Create a JWK from a SEC1 [`EncodedPoint`]. + pub fn from_encoded_point<C>(point: &EncodedPoint<C>) -> Option<Self> + where + C: Curve + JwkParameters, + FieldBytesSize<C>: ModulusSize, + { + match point.coordinates() { + Coordinates::Uncompressed { x, y } => Some(JwkEcKey { + crv: C::CRV.to_owned(), + x: Base64Url::encode_string(x), + y: Base64Url::encode_string(y), + d: None, + }), + _ => None, + } + } + + /// Get the public key component of this JWK as a SEC1 [`EncodedPoint`]. + pub fn to_encoded_point<C>(&self) -> Result<EncodedPoint<C>> + where + C: Curve + JwkParameters, + FieldBytesSize<C>: ModulusSize, + { + if self.crv != C::CRV { + return Err(Error); + } + + let x = decode_base64url_fe::<C>(&self.x)?; + let y = decode_base64url_fe::<C>(&self.y)?; + Ok(EncodedPoint::<C>::from_affine_coordinates(&x, &y, false)) + } + + /// Decode a JWK into a [`SecretKey`]. + #[cfg(feature = "arithmetic")] + pub fn to_secret_key<C>(&self) -> Result<SecretKey<C>> + where + C: Curve + JwkParameters + ValidatePublicKey, + FieldBytesSize<C>: ModulusSize, + { + self.try_into() + } +} + +impl FromStr for JwkEcKey { + type Err = Error; + + fn from_str(s: &str) -> Result<Self> { + serde_json::from_str(s).map_err(|_| Error) + } +} + +impl ToString for JwkEcKey { + fn to_string(&self) -> String { + serde_json::to_string(self).expect("JWK encoding error") + } +} + +impl<C> TryFrom<JwkEcKey> for SecretKey<C> +where + C: Curve + JwkParameters + ValidatePublicKey, + FieldBytesSize<C>: ModulusSize, +{ + type Error = Error; + + fn try_from(jwk: JwkEcKey) -> Result<SecretKey<C>> { + (&jwk).try_into() + } +} + +impl<C> TryFrom<&JwkEcKey> for SecretKey<C> +where + C: Curve + JwkParameters + ValidatePublicKey, + FieldBytesSize<C>: ModulusSize, +{ + type Error = Error; + + fn try_from(jwk: &JwkEcKey) -> Result<SecretKey<C>> { + if let Some(d_base64) = &jwk.d { + let pk = jwk.to_encoded_point::<C>()?; + let mut d_bytes = decode_base64url_fe::<C>(d_base64)?; + let result = SecretKey::from_slice(&d_bytes); + d_bytes.zeroize(); + + result.and_then(|secret_key| { + C::validate_public_key(&secret_key, &pk)?; + Ok(secret_key) + }) + } else { + Err(Error) + } + } +} + +#[cfg(feature = "arithmetic")] +impl<C> From<SecretKey<C>> for JwkEcKey +where + C: CurveArithmetic + JwkParameters, + AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>, + FieldBytesSize<C>: ModulusSize, +{ + fn from(sk: SecretKey<C>) -> JwkEcKey { + (&sk).into() + } +} + +#[cfg(feature = "arithmetic")] +impl<C> From<&SecretKey<C>> for JwkEcKey +where + C: CurveArithmetic + JwkParameters, + AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>, + FieldBytesSize<C>: ModulusSize, +{ + fn from(sk: &SecretKey<C>) -> JwkEcKey { + let mut jwk = sk.public_key().to_jwk(); + let mut d = sk.to_bytes(); + jwk.d = Some(Base64Url::encode_string(&d)); + d.zeroize(); + jwk + } +} + +#[cfg(feature = "arithmetic")] +impl<C> TryFrom<JwkEcKey> for PublicKey<C> +where + C: CurveArithmetic + JwkParameters, + AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>, + FieldBytesSize<C>: ModulusSize, +{ + type Error = Error; + + fn try_from(jwk: JwkEcKey) -> Result<PublicKey<C>> { + (&jwk).try_into() + } +} + +#[cfg(feature = "arithmetic")] +impl<C> TryFrom<&JwkEcKey> for PublicKey<C> +where + C: CurveArithmetic + JwkParameters, + AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>, + FieldBytesSize<C>: ModulusSize, +{ + type Error = Error; + + fn try_from(jwk: &JwkEcKey) -> Result<PublicKey<C>> { + PublicKey::from_sec1_bytes(jwk.to_encoded_point::<C>()?.as_bytes()) + } +} + +#[cfg(feature = "arithmetic")] +impl<C> From<PublicKey<C>> for JwkEcKey +where + C: CurveArithmetic + JwkParameters, + AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>, + FieldBytesSize<C>: ModulusSize, +{ + fn from(pk: PublicKey<C>) -> JwkEcKey { + (&pk).into() + } +} + +#[cfg(feature = "arithmetic")] +impl<C> From<&PublicKey<C>> for JwkEcKey +where + C: CurveArithmetic + JwkParameters, + AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>, + FieldBytesSize<C>: ModulusSize, +{ + fn from(pk: &PublicKey<C>) -> JwkEcKey { + Self::from_encoded_point::<C>(&pk.to_encoded_point(false)).expect("JWK encoding error") + } +} + +impl Debug for JwkEcKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let d = if self.d.is_some() { + "Some(...)" + } else { + "None" + }; + + // NOTE: this implementation omits the `d` private key parameter + f.debug_struct(JWK_TYPE_NAME) + .field("crv", &self.crv) + .field("x", &self.x) + .field("y", &self.y) + .field("d", &d) + .finish() + } +} + +impl PartialEq for JwkEcKey { + fn eq(&self, other: &Self) -> bool { + use subtle::ConstantTimeEq; + + // Compare private key in constant time + let d_eq = match &self.d { + Some(d1) => match &other.d { + Some(d2) => d1.as_bytes().ct_eq(d2.as_bytes()).into(), + None => other.d.is_none(), + }, + None => other.d.is_none(), + }; + + self.crv == other.crv && self.x == other.x && self.y == other.y && d_eq + } +} + +impl Eq for JwkEcKey {} + +impl ZeroizeOnDrop for JwkEcKey {} + +impl Drop for JwkEcKey { + fn drop(&mut self) { + self.zeroize(); + } +} + +impl Zeroize for JwkEcKey { + fn zeroize(&mut self) { + if let Some(d) = &mut self.d { + d.zeroize(); + } + } +} + +impl<'de> Deserialize<'de> for JwkEcKey { + fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error> + where + D: de::Deserializer<'de>, + { + /// Field positions + enum Field { + Kty, + Crv, + X, + Y, + D, + } + + /// Field visitor + struct FieldVisitor; + + impl<'de> de::Visitor<'de> for FieldVisitor { + type Value = Field; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Formatter::write_str(formatter, "field identifier") + } + + fn visit_u64<E>(self, value: u64) -> core::result::Result<Self::Value, E> + where + E: de::Error, + { + match value { + 0 => Ok(Field::Kty), + 1 => Ok(Field::Crv), + 2 => Ok(Field::X), + 3 => Ok(Field::Y), + 4 => Ok(Field::D), + _ => Err(de::Error::invalid_value( + de::Unexpected::Unsigned(value), + &"field index 0 <= i < 5", + )), + } + } + + fn visit_str<E>(self, value: &str) -> core::result::Result<Self::Value, E> + where + E: de::Error, + { + self.visit_bytes(value.as_bytes()) + } + + fn visit_bytes<E>(self, value: &[u8]) -> core::result::Result<Self::Value, E> + where + E: de::Error, + { + match value { + b"kty" => Ok(Field::Kty), + b"crv" => Ok(Field::Crv), + b"x" => Ok(Field::X), + b"y" => Ok(Field::Y), + b"d" => Ok(Field::D), + _ => Err(de::Error::unknown_field( + &String::from_utf8_lossy(value), + FIELDS, + )), + } + } + } + + impl<'de> Deserialize<'de> for Field { + #[inline] + fn deserialize<D>(__deserializer: D) -> core::result::Result<Self, D::Error> + where + D: de::Deserializer<'de>, + { + de::Deserializer::deserialize_identifier(__deserializer, FieldVisitor) + } + } + + struct Visitor<'de> { + marker: PhantomData<JwkEcKey>, + lifetime: PhantomData<&'de ()>, + } + + impl<'de> de::Visitor<'de> for Visitor<'de> { + type Value = JwkEcKey; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Formatter::write_str(formatter, "struct JwkEcKey") + } + + #[inline] + fn visit_seq<A>(self, mut seq: A) -> core::result::Result<Self::Value, A::Error> + where + A: de::SeqAccess<'de>, + { + let kty = de::SeqAccess::next_element::<String>(&mut seq)? + .ok_or_else(|| de::Error::invalid_length(0, &DE_ERROR_MSG))?; + + if kty != EC_KTY { + return Err(de::Error::custom(format!("unsupported JWK kty: {kty:?}"))); + } + + let crv = de::SeqAccess::next_element::<String>(&mut seq)? + .ok_or_else(|| de::Error::invalid_length(1, &DE_ERROR_MSG))?; + + let x = de::SeqAccess::next_element::<String>(&mut seq)? + .ok_or_else(|| de::Error::invalid_length(2, &DE_ERROR_MSG))?; + + let y = de::SeqAccess::next_element::<String>(&mut seq)? + .ok_or_else(|| de::Error::invalid_length(3, &DE_ERROR_MSG))?; + + let d = de::SeqAccess::next_element::<Option<String>>(&mut seq)? + .ok_or_else(|| de::Error::invalid_length(4, &DE_ERROR_MSG))?; + + Ok(JwkEcKey { crv, x, y, d }) + } + + #[inline] + fn visit_map<A>(self, mut map: A) -> core::result::Result<Self::Value, A::Error> + where + A: de::MapAccess<'de>, + { + let mut kty: Option<String> = None; + let mut crv: Option<String> = None; + let mut x: Option<String> = None; + let mut y: Option<String> = None; + let mut d: Option<String> = None; + + while let Some(key) = de::MapAccess::next_key::<Field>(&mut map)? { + match key { + Field::Kty => { + if kty.is_none() { + kty = Some(de::MapAccess::next_value::<String>(&mut map)?); + } else { + return Err(de::Error::duplicate_field(FIELDS[0])); + } + } + Field::Crv => { + if crv.is_none() { + crv = Some(de::MapAccess::next_value::<String>(&mut map)?); + } else { + return Err(de::Error::duplicate_field(FIELDS[1])); + } + } + Field::X => { + if x.is_none() { + x = Some(de::MapAccess::next_value::<String>(&mut map)?); + } else { + return Err(de::Error::duplicate_field(FIELDS[2])); + } + } + Field::Y => { + if y.is_none() { + y = Some(de::MapAccess::next_value::<String>(&mut map)?); + } else { + return Err(de::Error::duplicate_field(FIELDS[3])); + } + } + Field::D => { + if d.is_none() { + d = de::MapAccess::next_value::<Option<String>>(&mut map)?; + } else { + return Err(de::Error::duplicate_field(FIELDS[4])); + } + } + } + } + + let kty = kty.ok_or_else(|| de::Error::missing_field("kty"))?; + + if kty != EC_KTY { + return Err(de::Error::custom(format!("unsupported JWK kty: {kty}"))); + } + + let crv = crv.ok_or_else(|| de::Error::missing_field("crv"))?; + let x = x.ok_or_else(|| de::Error::missing_field("x"))?; + let y = y.ok_or_else(|| de::Error::missing_field("y"))?; + + Ok(JwkEcKey { crv, x, y, d }) + } + } + + de::Deserializer::deserialize_struct( + deserializer, + JWK_TYPE_NAME, + FIELDS, + Visitor { + marker: PhantomData::<JwkEcKey>, + lifetime: PhantomData, + }, + ) + } +} + +impl Serialize for JwkEcKey { + fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error> + where + S: ser::Serializer, + { + use ser::SerializeStruct; + + let mut state = serializer.serialize_struct(JWK_TYPE_NAME, 5)?; + + for (i, field) in [EC_KTY, &self.crv, &self.x, &self.y].iter().enumerate() { + state.serialize_field(FIELDS[i], field)?; + } + + if let Some(d) = &self.d { + state.serialize_field("d", d)?; + } + + ser::SerializeStruct::end(state) + } +} + +/// Decode a Base64url-encoded field element +fn decode_base64url_fe<C: Curve>(s: &str) -> Result<FieldBytes<C>> { + let mut result = FieldBytes::<C>::default(); + Base64Url::decode(s, &mut result).map_err(|_| Error)?; + Ok(result) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[cfg(feature = "dev")] + use crate::dev::MockCurve; + + /// Example private key. From RFC 7518 Appendix C: + /// <https://tools.ietf.org/html/rfc7518#appendix-C> + const JWK_PRIVATE_KEY: &str = r#" + { + "kty":"EC", + "crv":"P-256", + "x":"gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0", + "y":"SLW_xSffzlPWrHEVI30DHM_4egVwt3NQqeUD7nMFpps", + "d":"0_NxaRPUMQoAJt50Gz8YiTr8gRTwyEaCumd-MToTmIo" + } + "#; + + /// Example public key. + const JWK_PUBLIC_KEY: &str = r#" + { + "kty":"EC", + "crv":"P-256", + "x":"gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0", + "y":"SLW_xSffzlPWrHEVI30DHM_4egVwt3NQqeUD7nMFpps" + } + "#; + + /// Example unsupported JWK (RSA key) + const UNSUPPORTED_JWK: &str = r#" + { + "kty":"RSA", + "kid":"cc34c0a0-bd5a-4a3c-a50d-a2a7db7643df", + "use":"sig", + "n":"pjdss8ZaDfEH6K6U7GeW2nxDqR4IP049fk1fK0lndimbMMVBdPv_hSpm8T8EtBDxrUdi1OHZfMhUixGaut-3nQ4GG9nM249oxhCtxqqNvEXrmQRGqczyLxuh-fKn9Fg--hS9UpazHpfVAFnB5aCfXoNhPuI8oByyFKMKaOVgHNqP5NBEqabiLftZD3W_lsFCPGuzr4Vp0YS7zS2hDYScC2oOMu4rGU1LcMZf39p3153Cq7bS2Xh6Y-vw5pwzFYZdjQxDn8x8BG3fJ6j8TGLXQsbKH1218_HcUJRvMwdpbUQG5nvA2GXVqLqdwp054Lzk9_B_f1lVrmOKuHjTNHq48w", + "e":"AQAB", + "d":"ksDmucdMJXkFGZxiomNHnroOZxe8AmDLDGO1vhs-POa5PZM7mtUPonxwjVmthmpbZzla-kg55OFfO7YcXhg-Hm2OWTKwm73_rLh3JavaHjvBqsVKuorX3V3RYkSro6HyYIzFJ1Ek7sLxbjDRcDOj4ievSX0oN9l-JZhaDYlPlci5uJsoqro_YrE0PRRWVhtGynd-_aWgQv1YzkfZuMD-hJtDi1Im2humOWxA4eZrFs9eG-whXcOvaSwO4sSGbS99ecQZHM2TcdXeAs1PvjVgQ_dKnZlGN3lTWoWfQP55Z7Tgt8Nf1q4ZAKd-NlMe-7iqCFfsnFwXjSiaOa2CRGZn-Q", + "p":"4A5nU4ahEww7B65yuzmGeCUUi8ikWzv1C81pSyUKvKzu8CX41hp9J6oRaLGesKImYiuVQK47FhZ--wwfpRwHvSxtNU9qXb8ewo-BvadyO1eVrIk4tNV543QlSe7pQAoJGkxCia5rfznAE3InKF4JvIlchyqs0RQ8wx7lULqwnn0", + "q":"ven83GM6SfrmO-TBHbjTk6JhP_3CMsIvmSdo4KrbQNvp4vHO3w1_0zJ3URkmkYGhz2tgPlfd7v1l2I6QkIh4Bumdj6FyFZEBpxjE4MpfdNVcNINvVj87cLyTRmIcaGxmfylY7QErP8GFA-k4UoH_eQmGKGK44TRzYj5hZYGWIC8", + "dp":"lmmU_AG5SGxBhJqb8wxfNXDPJjf__i92BgJT2Vp4pskBbr5PGoyV0HbfUQVMnw977RONEurkR6O6gxZUeCclGt4kQlGZ-m0_XSWx13v9t9DIbheAtgVJ2mQyVDvK4m7aRYlEceFh0PsX8vYDS5o1txgPwb3oXkPTtrmbAGMUBpE", + "dq":"mxRTU3QDyR2EnCv0Nl0TCF90oliJGAHR9HJmBe__EjuCBbwHfcT8OG3hWOv8vpzokQPRl5cQt3NckzX3fs6xlJN4Ai2Hh2zduKFVQ2p-AF2p6Yfahscjtq-GY9cB85NxLy2IXCC0PF--Sq9LOrTE9QV988SJy_yUrAjcZ5MmECk", + "qi":"ldHXIrEmMZVaNwGzDF9WG8sHj2mOZmQpw9yrjLK9hAsmsNr5LTyqWAqJIYZSwPTYWhY4nu2O0EY9G9uYiqewXfCKw_UngrJt8Xwfq1Zruz0YY869zPN4GiE9-9rzdZB33RBw8kIOquY3MK74FMwCihYx_LiU2YTHkaoJ3ncvtvg" + } + "#; + + #[test] + fn parse_private_key() { + let jwk = JwkEcKey::from_str(JWK_PRIVATE_KEY).unwrap(); + assert_eq!(jwk.crv, "P-256"); + assert_eq!(jwk.x, "gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0"); + assert_eq!(jwk.y, "SLW_xSffzlPWrHEVI30DHM_4egVwt3NQqeUD7nMFpps"); + assert_eq!( + jwk.d.as_ref().unwrap(), + "0_NxaRPUMQoAJt50Gz8YiTr8gRTwyEaCumd-MToTmIo" + ); + } + + #[test] + fn parse_public_key() { + let jwk = JwkEcKey::from_str(JWK_PUBLIC_KEY).unwrap(); + assert_eq!(jwk.crv, "P-256"); + assert_eq!(jwk.x, "gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0"); + assert_eq!(jwk.y, "SLW_xSffzlPWrHEVI30DHM_4egVwt3NQqeUD7nMFpps"); + assert_eq!(jwk.d, None); + } + + #[test] + fn parse_unsupported() { + assert_eq!(JwkEcKey::from_str(UNSUPPORTED_JWK), Err(Error)); + } + + #[test] + fn serialize_private_key() { + let actual = JwkEcKey::from_str(JWK_PRIVATE_KEY).unwrap().to_string(); + let expected: String = JWK_PRIVATE_KEY.split_whitespace().collect(); + assert_eq!(actual, expected); + } + + #[test] + fn serialize_public_key() { + let actual = JwkEcKey::from_str(JWK_PUBLIC_KEY).unwrap().to_string(); + let expected: String = JWK_PUBLIC_KEY.split_whitespace().collect(); + assert_eq!(actual, expected); + } + + #[cfg(feature = "dev")] + #[test] + fn jwk_into_encoded_point() { + let jwk = JwkEcKey::from_str(JWK_PUBLIC_KEY).unwrap(); + let point = jwk.to_encoded_point::<MockCurve>().unwrap(); + let (x, y) = match point.coordinates() { + Coordinates::Uncompressed { x, y } => (x, y), + other => panic!("unexpected coordinates: {:?}", other), + }; + + assert_eq!(&decode_base64url_fe::<MockCurve>(&jwk.x).unwrap(), x); + assert_eq!(&decode_base64url_fe::<MockCurve>(&jwk.y).unwrap(), y); + } + + #[cfg(feature = "dev")] + #[test] + fn encoded_point_into_jwk() { + let jwk = JwkEcKey::from_str(JWK_PUBLIC_KEY).unwrap(); + let point = jwk.to_encoded_point::<MockCurve>().unwrap(); + let jwk2 = JwkEcKey::from_encoded_point::<MockCurve>(&point).unwrap(); + assert_eq!(jwk, jwk2); + } +} |