summaryrefslogtreecommitdiffstats
path: root/vendor/elliptic-curve/src/jwk.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:47:55 +0000
commit2aadc03ef15cb5ca5cc2af8a7c08e070742f0ac4 (patch)
tree033cc839730fda84ff08db877037977be94e5e3a /vendor/elliptic-curve/src/jwk.rs
parentInitial commit. (diff)
downloadcargo-2aadc03ef15cb5ca5cc2af8a7c08e070742f0ac4.tar.xz
cargo-2aadc03ef15cb5ca5cc2af8a7c08e070742f0ac4.zip
Adding upstream version 0.70.1+ds1.upstream/0.70.1+ds1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/elliptic-curve/src/jwk.rs')
-rw-r--r--vendor/elliptic-curve/src/jwk.rs674
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);
+ }
+}