From 767fc266da2185cb87be2c094aa444571bb3fff5 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 18 Aug 2025 13:22:05 -0600 Subject: [PATCH] elliptic-curve: remove JWK support Migrated to the `jose-jwk` crate in RustCrypto/JOSE#85 --- .github/workflows/elliptic-curve.yml | 7 +- Cargo.lock | 32 -- elliptic-curve/Cargo.toml | 5 +- elliptic-curve/src/dev.rs | 8 - elliptic-curve/src/jwk.rs | 676 --------------------------- elliptic-curve/src/lib.rs | 7 - elliptic-curve/src/public_key.rs | 63 +-- elliptic-curve/src/secret_key.rs | 50 +- 8 files changed, 12 insertions(+), 836 deletions(-) delete mode 100644 elliptic-curve/src/jwk.rs diff --git a/.github/workflows/elliptic-curve.yml b/.github/workflows/elliptic-curve.yml index 6ebeb9a05..b35b68ff0 100644 --- a/.github/workflows/elliptic-curve.yml +++ b/.github/workflows/elliptic-curve.yml @@ -17,7 +17,7 @@ env: CARGO_INCREMENTAL: 0 RUSTFLAGS: "-Dwarnings" RUSTDOCFLAGS: "-Dwarnings" - + # Cancels CI jobs when new commits are pushed to a PR branch concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} @@ -48,7 +48,6 @@ jobs: - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features dev - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features digest - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features ecdh - - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features jwk - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features pem - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features pkcs8 - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features sec1 @@ -57,10 +56,10 @@ jobs: - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features alloc,arithmetic,pkcs8 - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features alloc,serde - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features arithmetic,serde - - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features alloc,digest,ecdh,jwk,pem,pkcs8,sec1,serde + - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features alloc,digest,ecdh,pem,pkcs8,sec1,serde minimal-versions: - # Temporarily disabled until elliptic-curve 0.13.0-pre.0 is published + # Temporarily disabled until elliptic-curve 0.14.0 is published if: false runs-on: ubuntu-latest steps: diff --git a/Cargo.lock b/Cargo.lock index 91d3a73b3..e9e05b63e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -197,7 +197,6 @@ name = "elliptic-curve" version = "0.14.0-rc.12" dependencies = [ "base16ct", - "base64ct", "crypto-bigint", "digest", "ff", @@ -209,7 +208,6 @@ dependencies = [ "pkcs8", "rand_core", "sec1", - "serde_json", "serdect", "sha2", "sha3", @@ -336,12 +334,6 @@ dependencies = [ "hybrid-array", ] -[[package]] -name = "itoa" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - [[package]] name = "keccak" version = "0.2.0-pre.0" @@ -365,12 +357,6 @@ version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" -[[package]] -name = "memchr" -version = "2.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" - [[package]] name = "num-bigint" version = "0.3.3" @@ -467,12 +453,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - [[package]] name = "sec1" version = "0.8.0-rc.8" @@ -507,18 +487,6 @@ dependencies = [ "syn 2.0.104", ] -[[package]] -name = "serde_json" -version = "1.0.142" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - [[package]] name = "serdect" version = "0.3.0" diff --git a/elliptic-curve/Cargo.toml b/elliptic-curve/Cargo.toml index b5503969b..d8184004b 100644 --- a/elliptic-curve/Cargo.toml +++ b/elliptic-curve/Cargo.toml @@ -25,7 +25,6 @@ subtle = { version = "2.6", default-features = false } zeroize = { version = "1.7", default-features = false } # optional dependencies -base64ct = { version = "1", optional = true, default-features = false, features = ["alloc"] } digest = { version = "0.11.0-rc.0", optional = true } ff = { version = "=0.14.0-pre.0", optional = true, default-features = false } group = { version = "=0.14.0-pre.0", optional = true, default-features = false } @@ -35,7 +34,6 @@ pem-rfc7468 = { version = "1.0.0-rc.2", optional = true, features = ["alloc"] } pkcs8 = { version = "0.11.0-rc.6", optional = true, default-features = false } sec1 = { version = "0.8.0-rc.8", optional = true, features = ["subtle", "zeroize"] } serdect = { version = "0.3", optional = true, default-features = false, features = ["alloc"] } -serde_json = { version = "1.0.121", optional = true, default-features = false, features = ["alloc"] } [dev-dependencies] hex-literal = "1" @@ -64,10 +62,9 @@ bits = ["arithmetic", "ff/bits"] dev = ["arithmetic", "dep:hex-literal", "pem", "pkcs8"] ecdh = ["arithmetic", "digest", "dep:hkdf"] group = ["dep:group", "ff"] -jwk = ["dep:base64ct", "dep:serde_json", "alloc", "serde", "zeroize/alloc"] pkcs8 = ["dep:pkcs8", "sec1"] pem = ["dep:pem-rfc7468", "alloc", "arithmetic", "pkcs8/pem", "sec1/pem"] serde = ["dep:serdect", "alloc", "pkcs8", "sec1/serde"] [package.metadata.docs.rs] -features = ["bits", "ecdh", "jwk", "pem", "std"] +features = ["bits", "ecdh", "pem", "std"] diff --git a/elliptic-curve/src/dev.rs b/elliptic-curve/src/dev.rs index a7af4ecd6..4e7c334f0 100644 --- a/elliptic-curve/src/dev.rs +++ b/elliptic-curve/src/dev.rs @@ -31,9 +31,6 @@ use alloc::vec::Vec; #[cfg(feature = "bits")] use ff::PrimeFieldBits; -#[cfg(feature = "jwk")] -use crate::JwkParameters; - /// Pseudo-coordinate for fixed-based scalar mult output pub const PSEUDO_COORDINATE_FIXED_BASE_MUL: [u8; 32] = hex!("deadbeef00000000000000000000000000000000000000000000000000000001"); @@ -90,11 +87,6 @@ impl AssociatedOid for MockCurve { const OID: pkcs8::ObjectIdentifier = pkcs8::ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7"); } -#[cfg(feature = "jwk")] -impl JwkParameters for MockCurve { - const CRV: &'static str = "P-256"; -} - /// Example scalar type #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, PartialOrd, Ord)] pub struct Scalar(ScalarPrimitive); diff --git a/elliptic-curve/src/jwk.rs b/elliptic-curve/src/jwk.rs deleted file mode 100644 index b85ac90e1..000000000 --- a/elliptic-curve/src/jwk.rs +++ /dev/null @@ -1,676 +0,0 @@ -//! JSON Web Key (JWK) Support. -//! -//! Specified in RFC 7518 Section 6: Cryptographic Algorithms for Keys: -//! - -use crate::{ - Curve, Error, FieldBytes, FieldBytesSize, Result, - sec1::{Coordinates, EncodedPoint, ModulusSize, ValidatePublicKey}, - secret_key::SecretKey, -}; -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::{Deserialize, Serialize, de, ser}; -use zeroize::{Zeroize, ZeroizeOnDrop}; - -#[cfg(feature = "arithmetic")] -use crate::{ - AffinePoint, CurveArithmetic, - public_key::PublicKey, - sec1::{FromEncodedPoint, ToEncodedPoint}, -}; - -/// 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: - /// - /// - /// Curve values are registered in the IANA "JSON Web Key Elliptic Curve" - /// registry defined in RFC 7518 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: - /// - 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: - /// - 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: - /// - y: String, - - /// The `d` ECC private key parameter as described in RFC 7518 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: - /// - d: Option, -} - -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(&self) -> Result> - where - C: CurveArithmetic + JwkParameters, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - FieldBytesSize: ModulusSize, - { - PublicKey::from_sec1_bytes(self.to_encoded_point::()?.as_bytes()) - } - - /// Create a JWK from a SEC1 [`EncodedPoint`]. - pub fn from_encoded_point(point: &EncodedPoint) -> Option - where - C: Curve + JwkParameters, - FieldBytesSize: 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(&self) -> Result> - where - C: Curve + JwkParameters, - FieldBytesSize: ModulusSize, - { - if self.crv != C::CRV { - return Err(Error); - } - - let x = decode_base64url_fe::(&self.x)?; - let y = decode_base64url_fe::(&self.y)?; - Ok(EncodedPoint::::from_affine_coordinates(&x, &y, false)) - } - - /// Decode a JWK into a [`SecretKey`]. - #[cfg(feature = "arithmetic")] - pub fn to_secret_key(&self) -> Result> - where - C: Curve + JwkParameters + ValidatePublicKey, - FieldBytesSize: ModulusSize, - { - self.try_into() - } -} - -impl FromStr for JwkEcKey { - type Err = Error; - - fn from_str(s: &str) -> Result { - serde_json::from_str(s).map_err(|_| Error) - } -} - -#[allow(clippy::to_string_trait_impl)] -impl ToString for JwkEcKey { - fn to_string(&self) -> String { - serde_json::to_string(self).expect("JWK encoding error") - } -} - -impl TryFrom for SecretKey -where - C: Curve + JwkParameters + ValidatePublicKey, - FieldBytesSize: ModulusSize, -{ - type Error = Error; - - fn try_from(jwk: JwkEcKey) -> Result> { - (&jwk).try_into() - } -} - -impl TryFrom<&JwkEcKey> for SecretKey -where - C: Curve + JwkParameters + ValidatePublicKey, - FieldBytesSize: ModulusSize, -{ - type Error = Error; - - fn try_from(jwk: &JwkEcKey) -> Result> { - if let Some(d_base64) = &jwk.d { - let pk = jwk.to_encoded_point::()?; - let mut d_bytes = decode_base64url_fe::(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 From> for JwkEcKey -where - C: CurveArithmetic + JwkParameters, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - FieldBytesSize: ModulusSize, -{ - fn from(sk: SecretKey) -> JwkEcKey { - (&sk).into() - } -} - -#[cfg(feature = "arithmetic")] -impl From<&SecretKey> for JwkEcKey -where - C: CurveArithmetic + JwkParameters, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - FieldBytesSize: ModulusSize, -{ - fn from(sk: &SecretKey) -> 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 TryFrom for PublicKey -where - C: CurveArithmetic + JwkParameters, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - FieldBytesSize: ModulusSize, -{ - type Error = Error; - - fn try_from(jwk: JwkEcKey) -> Result> { - (&jwk).try_into() - } -} - -#[cfg(feature = "arithmetic")] -impl TryFrom<&JwkEcKey> for PublicKey -where - C: CurveArithmetic + JwkParameters, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - FieldBytesSize: ModulusSize, -{ - type Error = Error; - - fn try_from(jwk: &JwkEcKey) -> Result> { - PublicKey::from_sec1_bytes(jwk.to_encoded_point::()?.as_bytes()) - } -} - -#[cfg(feature = "arithmetic")] -impl From> for JwkEcKey -where - C: CurveArithmetic + JwkParameters, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - FieldBytesSize: ModulusSize, -{ - fn from(pk: PublicKey) -> JwkEcKey { - (&pk).into() - } -} - -#[cfg(feature = "arithmetic")] -impl From<&PublicKey> for JwkEcKey -where - C: CurveArithmetic + JwkParameters, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - FieldBytesSize: ModulusSize, -{ - fn from(pk: &PublicKey) -> JwkEcKey { - Self::from_encoded_point::(&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(deserializer: D) -> core::result::Result - where - D: de::Deserializer<'de>, - { - /// Field positions - enum Field { - Kty, - Crv, - X, - Y, - D, - } - - /// Field visitor - struct FieldVisitor; - - impl de::Visitor<'_> for FieldVisitor { - type Value = Field; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Formatter::write_str(formatter, "field identifier") - } - - fn visit_u64(self, value: u64) -> core::result::Result - 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(self, value: &str) -> core::result::Result - where - E: de::Error, - { - self.visit_bytes(value.as_bytes()) - } - - fn visit_bytes(self, value: &[u8]) -> core::result::Result - 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(__deserializer: D) -> core::result::Result - where - D: de::Deserializer<'de>, - { - de::Deserializer::deserialize_identifier(__deserializer, FieldVisitor) - } - } - - struct Visitor<'de> { - marker: PhantomData, - 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(self, mut seq: A) -> core::result::Result - where - A: de::SeqAccess<'de>, - { - let kty = de::SeqAccess::next_element::(&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::(&mut seq)? - .ok_or_else(|| de::Error::invalid_length(1, &DE_ERROR_MSG))?; - - let x = de::SeqAccess::next_element::(&mut seq)? - .ok_or_else(|| de::Error::invalid_length(2, &DE_ERROR_MSG))?; - - let y = de::SeqAccess::next_element::(&mut seq)? - .ok_or_else(|| de::Error::invalid_length(3, &DE_ERROR_MSG))?; - - let d = de::SeqAccess::next_element::>(&mut seq)? - .ok_or_else(|| de::Error::invalid_length(4, &DE_ERROR_MSG))?; - - Ok(JwkEcKey { crv, x, y, d }) - } - - #[inline] - fn visit_map(self, mut map: A) -> core::result::Result - where - A: de::MapAccess<'de>, - { - let mut kty: Option = None; - let mut crv: Option = None; - let mut x: Option = None; - let mut y: Option = None; - let mut d: Option = None; - - while let Some(key) = de::MapAccess::next_key::(&mut map)? { - match key { - Field::Kty => { - if kty.is_none() { - kty = Some(de::MapAccess::next_value::(&mut map)?); - } else { - return Err(de::Error::duplicate_field(FIELDS[0])); - } - } - Field::Crv => { - if crv.is_none() { - crv = Some(de::MapAccess::next_value::(&mut map)?); - } else { - return Err(de::Error::duplicate_field(FIELDS[1])); - } - } - Field::X => { - if x.is_none() { - x = Some(de::MapAccess::next_value::(&mut map)?); - } else { - return Err(de::Error::duplicate_field(FIELDS[2])); - } - } - Field::Y => { - if y.is_none() { - y = Some(de::MapAccess::next_value::(&mut map)?); - } else { - return Err(de::Error::duplicate_field(FIELDS[3])); - } - } - Field::D => { - if d.is_none() { - d = de::MapAccess::next_value::>(&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::, - lifetime: PhantomData, - }, - ) - } -} - -impl Serialize for JwkEcKey { - fn serialize(&self, serializer: S) -> core::result::Result - 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)?; - } - - SerializeStruct::end(state) - } -} - -/// Decode a Base64url-encoded field element -fn decode_base64url_fe(s: &str) -> Result> { - let mut result = FieldBytes::::default(); - Base64Url::decode(s, &mut result).map_err(|_| Error)?; - Ok(result) -} - -#[cfg(test)] -mod tests { - #![allow(clippy::unwrap_used, clippy::panic)] - use super::*; - - #[cfg(feature = "dev")] - use crate::dev::MockCurve; - - /// Example private key. From RFC 7518 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::().unwrap(); - let (x, y) = match point.coordinates() { - Coordinates::Uncompressed { x, y } => (x, y), - other => panic!("unexpected coordinates: {other:?}"), - }; - - assert_eq!(&decode_base64url_fe::(&jwk.x).unwrap(), x); - assert_eq!(&decode_base64url_fe::(&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::().unwrap(); - let jwk2 = JwkEcKey::from_encoded_point::(&point).unwrap(); - assert_eq!(jwk, jwk2); - } -} diff --git a/elliptic-curve/src/lib.rs b/elliptic-curve/src/lib.rs index bd880be88..4c64d8a4f 100644 --- a/elliptic-curve/src/lib.rs +++ b/elliptic-curve/src/lib.rs @@ -61,7 +61,6 @@ //! When the `serde` feature of this crate is enabled, `Serialize` and //! `Deserialize` impls are provided for the following types: //! -//! - [`JwkEcKey`] //! - [`PublicKey`] //! - [`ScalarPrimitive`] //! @@ -108,9 +107,6 @@ mod arithmetic; #[cfg(feature = "arithmetic")] mod public_key; -#[cfg(feature = "jwk")] -mod jwk; - pub use crate::{ error::{Error, Result}, field::{FieldBytes, FieldBytesEncoding, FieldBytesSize}, @@ -136,9 +132,6 @@ pub use { group::{self, Curve as CurveGroup, Group}, }; -#[cfg(feature = "jwk")] -pub use crate::jwk::{JwkEcKey, JwkParameters}; - #[cfg(feature = "pkcs8")] pub use pkcs8; diff --git a/elliptic-curve/src/public_key.rs b/elliptic-curve/src/public_key.rs index 6995b2568..f0e4a1637 100644 --- a/elliptic-curve/src/public_key.rs +++ b/elliptic-curve/src/public_key.rs @@ -7,14 +7,14 @@ use crate::{ use core::fmt::Debug; use group::Group; -#[cfg(feature = "jwk")] -use crate::{JwkEcKey, JwkParameters}; - #[cfg(feature = "pkcs8")] use pkcs8::spki::{AlgorithmIdentifier, AssociatedAlgorithmIdentifier, ObjectIdentifier}; #[cfg(feature = "pem")] -use core::str::FromStr; +use { + alloc::string::{String, ToString}, + core::str::FromStr, +}; #[cfg(feature = "sec1")] use { @@ -27,18 +27,15 @@ use { subtle::{Choice, CtOption}, }; +#[cfg(feature = "serde")] +use serdect::serde::{Deserialize, Serialize, de, ser}; + #[cfg(all(feature = "alloc", feature = "pkcs8"))] use pkcs8::EncodePublicKey; #[cfg(all(feature = "alloc", feature = "sec1"))] use alloc::boxed::Box; -#[cfg(any(feature = "jwk", feature = "pem"))] -use alloc::string::{String, ToString}; - -#[cfg(feature = "serde")] -use serdect::serde::{Deserialize, Serialize, de, ser}; - #[cfg(any(feature = "pem", feature = "serde"))] use pkcs8::DecodePublicKey; @@ -82,8 +79,6 @@ use { /// /// The serialization is binary-oriented and supports ASN.1 DER /// Subject Public Key Info (SPKI) as the encoding format. -/// -/// For a more text-friendly encoding of public keys, use [`JwkEcKey`] instead. #[derive(Clone, Debug, Eq, PartialEq)] pub struct PublicKey where @@ -162,50 +157,6 @@ where pub fn to_nonidentity(&self) -> NonIdentity> { NonIdentity::new_unchecked(self.point) } - - /// Parse a [`JwkEcKey`] JSON Web Key (JWK) into a [`PublicKey`]. - #[cfg(feature = "jwk")] - pub fn from_jwk(jwk: &JwkEcKey) -> Result - where - C: JwkParameters, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - FieldBytesSize: ModulusSize, - { - jwk.to_public_key::() - } - - /// Parse a string containing a JSON Web Key (JWK) into a [`PublicKey`]. - #[cfg(feature = "jwk")] - pub fn from_jwk_str(jwk: &str) -> Result - where - C: JwkParameters, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - FieldBytesSize: ModulusSize, - { - jwk.parse::().and_then(|jwk| Self::from_jwk(&jwk)) - } - - /// Serialize this public key as [`JwkEcKey`] JSON Web Key (JWK). - #[cfg(feature = "jwk")] - pub fn to_jwk(&self) -> JwkEcKey - where - C: JwkParameters, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - FieldBytesSize: ModulusSize, - { - self.into() - } - - /// Serialize this public key as JSON Web Key (JWK) string. - #[cfg(feature = "jwk")] - pub fn to_jwk_string(&self) -> String - where - C: JwkParameters, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - FieldBytesSize: ModulusSize, - { - self.to_jwk().to_string() - } } impl AsRef> for PublicKey diff --git a/elliptic-curve/src/secret_key.rs b/elliptic-curve/src/secret_key.rs index 1559f62bc..dbc435605 100644 --- a/elliptic-curve/src/secret_key.rs +++ b/elliptic-curve/src/secret_key.rs @@ -20,9 +20,6 @@ use crate::{ rand_core::{CryptoRng, TryCryptoRng}, }; -#[cfg(feature = "jwk")] -use crate::jwk::{JwkEcKey, JwkParameters}; - #[cfg(feature = "pem")] use pem_rfc7468::{self as pem, PemLabel}; @@ -45,12 +42,9 @@ use { sec1::der::Encode, }; -#[cfg(all(feature = "arithmetic", any(feature = "jwk", feature = "pem")))] +#[cfg(all(feature = "arithmetic", feature = "pem"))] use alloc::string::String; -#[cfg(all(feature = "arithmetic", feature = "jwk"))] -use alloc::string::ToString; - #[cfg(all(doc, feature = "pkcs8"))] use {crate::pkcs8::DecodePrivateKey, core::str::FromStr}; @@ -278,48 +272,6 @@ where .map(Zeroizing::new) .ok_or(Error) } - - /// Parse a [`JwkEcKey`] JSON Web Key (JWK) into a [`SecretKey`]. - #[cfg(feature = "jwk")] - pub fn from_jwk(jwk: &JwkEcKey) -> Result - where - C: JwkParameters + ValidatePublicKey, - FieldBytesSize: ModulusSize, - { - Self::try_from(jwk) - } - - /// Parse a string containing a JSON Web Key (JWK) into a [`SecretKey`]. - #[cfg(feature = "jwk")] - pub fn from_jwk_str(jwk: &str) -> Result - where - C: JwkParameters + ValidatePublicKey, - FieldBytesSize: ModulusSize, - { - jwk.parse::().and_then(|jwk| Self::from_jwk(&jwk)) - } - - /// Serialize this secret key as [`JwkEcKey`] JSON Web Key (JWK). - #[cfg(all(feature = "arithmetic", feature = "jwk"))] - pub fn to_jwk(&self) -> JwkEcKey - where - C: CurveArithmetic + JwkParameters, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - FieldBytesSize: ModulusSize, - { - self.into() - } - - /// Serialize this secret key as JSON Web Key (JWK) string. - #[cfg(all(feature = "arithmetic", feature = "jwk"))] - pub fn to_jwk_string(&self) -> Zeroizing - where - C: CurveArithmetic + JwkParameters, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - FieldBytesSize: ModulusSize, - { - Zeroizing::new(self.to_jwk().to_string()) - } } impl ConstantTimeEq for SecretKey