Skip to content

Commit

Permalink
Merge pull request #5 from andrewbaxter/jwk-from-encoding-key
Browse files Browse the repository at this point in the history
Add method to generate JWK from EncodingKey
  • Loading branch information
andrewbaxter authored Jan 22, 2024
2 parents 0cfda16 + d61c791 commit 4f57bea
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 2 deletions.
2 changes: 1 addition & 1 deletion src/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::serialization::b64_encode_part;
#[derive(Clone)]
pub struct EncodingKey {
pub(crate) family: AlgorithmFamily,
content: Vec<u8>,
pub(crate) content: Vec<u8>,
}

impl EncodingKey {
Expand Down
86 changes: 85 additions & 1 deletion src/jwk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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<Self> {
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::<Vec<u8>>::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]
Expand Down
23 changes: 23 additions & 0 deletions tests/ecdsa/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
18 changes: 18 additions & 0 deletions tests/rsa/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down

0 comments on commit 4f57bea

Please sign in to comment.