diff --git a/src/encoding.rs b/src/encoding.rs index 3be53524..5cf59b5b 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -18,7 +18,7 @@ use crate::serialization::b64_encode_part; #[derive(Clone)] pub struct EncodingKey { pub(crate) family: AlgorithmFamily, - content: Vec, + pub(crate) content: Vec, } impl EncodingKey { diff --git a/src/jwk.rs b/src/jwk.rs index 9a656bb9..2e53905d 100644 --- a/src/jwk.rs +++ b/src/jwk.rs @@ -5,9 +5,14 @@ //! tweaked to remove the private bits as it's not the goal for this crate currently. use crate::{ + crypto::ecdsa::alg_to_ec_signing, errors::{self, Error, ErrorKind}, serialization::b64_encode, - Algorithm, + Algorithm, EncodingKey, +}; +use ring::{ + rand, + signature::{self, KeyPair}, }; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use std::{fmt, str::FromStr}; @@ -425,6 +430,85 @@ impl Jwk { self.common.key_algorithm.unwrap().to_algorithm().is_ok() } + pub fn from_encoding_key(key: &EncodingKey, alg: Algorithm) -> crate::errors::Result { + Ok(Self { + common: CommonParameters { + key_algorithm: Some(match alg { + Algorithm::HS256 => KeyAlgorithm::HS256, + Algorithm::HS384 => KeyAlgorithm::HS384, + Algorithm::HS512 => KeyAlgorithm::HS512, + Algorithm::ES256 => KeyAlgorithm::ES256, + Algorithm::ES384 => KeyAlgorithm::ES384, + Algorithm::RS256 => KeyAlgorithm::RS256, + Algorithm::RS384 => KeyAlgorithm::RS384, + Algorithm::RS512 => KeyAlgorithm::RS512, + Algorithm::PS256 => KeyAlgorithm::PS256, + Algorithm::PS384 => KeyAlgorithm::PS384, + Algorithm::PS512 => KeyAlgorithm::PS512, + Algorithm::EdDSA => KeyAlgorithm::EdDSA, + }), + ..Default::default() + }, + algorithm: match key.family { + crate::algorithms::AlgorithmFamily::Hmac => { + AlgorithmParameters::OctetKey(OctetKeyParameters { + key_type: OctetKeyType::Octet, + value: b64_encode(&key.content), + }) + } + crate::algorithms::AlgorithmFamily::Rsa => { + let key_pair = signature::RsaKeyPair::from_der(&key.content) + .map_err(|e| ErrorKind::InvalidRsaKey(e.to_string()))?; + let public = key_pair.public(); + let components = + ring::signature::RsaPublicKeyComponents::>::from(public); + AlgorithmParameters::RSA(RSAKeyParameters { + key_type: RSAKeyType::RSA, + n: b64_encode(components.n), + e: b64_encode(components.e), + }) + } + crate::algorithms::AlgorithmFamily::Ec => { + let rng = rand::SystemRandom::new(); + let key_pair = signature::EcdsaKeyPair::from_pkcs8( + alg_to_ec_signing(alg), + &key.content, + &rng, + )?; + // Ring has this as `ring::ec::suite_b::curve::P384.elem_scalar_seed_len` but + // it's private and not exposed via any methods AFAICT. + let pub_elem_bytes; + let curve; + match alg { + Algorithm::ES256 => { + pub_elem_bytes = 32; + curve = EllipticCurve::P256; + } + Algorithm::ES384 => { + pub_elem_bytes = 48; + curve = EllipticCurve::P384; + } + _ => unreachable!(), + }; + let pub_bytes = key_pair.public_key().as_ref(); + if pub_bytes[0] != 4 { + panic!("Compressed coordinates in public key!"); + } + let (x, y) = pub_bytes[1..].split_at(pub_elem_bytes); + AlgorithmParameters::EllipticCurve(EllipticCurveKeyParameters { + key_type: EllipticCurveKeyType::EC, + curve, + x: b64_encode(x), + y: b64_encode(y), + }) + } + crate::algorithms::AlgorithmFamily::Ed => { + unimplemented!(); + } + }, + }) + } + /// Compute the thumbprint of the JWK. /// /// Per (RFC-7638)[https://datatracker.ietf.org/doc/html/rfc7638] diff --git a/tests/ecdsa/mod.rs b/tests/ecdsa/mod.rs index 8c06910f..66ff22c1 100644 --- a/tests/ecdsa/mod.rs +++ b/tests/ecdsa/mod.rs @@ -103,6 +103,29 @@ fn ec_x_y() { assert!(res.is_ok()); } +#[cfg(feature = "use_pem")] +#[test] +#[wasm_bindgen_test] +fn ec_jwk_from_key() { + use jsonwebtoken::jwk::Jwk; + use serde_json::json; + + let privkey = include_str!("private_ecdsa_key.pem"); + let encoding_key = EncodingKey::from_ec_pem(privkey.as_ref()).unwrap(); + let jwk = Jwk::from_encoding_key(&encoding_key, Algorithm::ES256).unwrap(); + assert_eq!( + jwk, + serde_json::from_value(json!({ + "kty": "EC", + "crv": "P-256", + "x": "w7JAoU_gJbZJvV-zCOvU9yFJq0FNC_edCMRM78P8eQQ", + "y": "wQg1EytcsEmGrM70Gb53oluoDbVhCZ3Uq3hHMslHVb4", + "alg": "ES256", + })) + .unwrap() + ); +} + #[cfg(feature = "use_pem")] #[test] #[wasm_bindgen_test] diff --git a/tests/rsa/mod.rs b/tests/rsa/mod.rs index 3297149f..f31969c3 100644 --- a/tests/rsa/mod.rs +++ b/tests/rsa/mod.rs @@ -169,6 +169,24 @@ fn rsa_modulus_exponent() { assert!(res.is_ok()); } +#[cfg(feature = "use_pem")] +#[test] +#[wasm_bindgen_test] +fn rsa_jwk_from_key() { + use jsonwebtoken::jwk::Jwk; + use serde_json::json; + + let privkey = include_str!("private_rsa_key_pkcs8.pem"); + let encoding_key = EncodingKey::from_rsa_pem(privkey.as_ref()).unwrap(); + let jwk = Jwk::from_encoding_key(&encoding_key, Algorithm::RS256).unwrap(); + assert_eq!(jwk, serde_json::from_value(json!({ + "kty": "RSA", + "n": "yRE6rHuNR0QbHO3H3Kt2pOKGVhQqGZXInOduQNxXzuKlvQTLUTv4l4sggh5_CYYi_cvI-SXVT9kPWSKXxJXBXd_4LkvcPuUakBoAkfh-eiFVMh2VrUyWyj3MFl0HTVF9KwRXLAcwkREiS3npThHRyIxuy0ZMeZfxVL5arMhw1SRELB8HoGfG_AtH89BIE9jDBHZ9dLelK9a184zAf8LwoPLxvJb3Il5nncqPcSfKDDodMFBIMc4lQzDKL5gvmiXLXB1AGLm8KBjfE8s3L5xqi-yUod-j8MtvIj812dkS4QMiRVN_by2h3ZY8LYVGrqZXZTcgn2ujn8uKjXLZVD5TdQ", + "e": "AQAB", + "alg": "RS256", + })).unwrap()); +} + #[cfg(feature = "use_pem")] #[test] #[wasm_bindgen_test]