From 47be9cbaf0cd726613acefb82645045e13ee82a6 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Mon, 13 Nov 2023 02:24:27 -0600 Subject: [PATCH] Switch to using rustcrypto for JWK verification DSA This also makes more clear when we are working with any JWK vs. when we expect an EC JWK. --- Cargo.toml | 5 +- src/error.rs | 32 ++-- src/jose.rs | 393 +++++++++++++++++++++++++++----------- src/jose_tests.rs | 4 +- src/key_exchange.rs | 177 +++++++---------- src/key_exchange_tests.rs | 15 +- src/tang_interface.rs | 9 +- tests/backend.rs | 3 +- 8 files changed, 384 insertions(+), 254 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5411c60..2e64f4e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,9 +18,8 @@ path = "src/main.rs" [dependencies] clap = { version = "4.4.7", features = ["derive"] } env_logger = "0.10.0" -josekit = "0.8.4" log = "0.4.20" -serde = "1.0.190" +serde = { version = "1.0.190", features = ["derive"] } sha2 = "0.10.8" serde_json = { version = "1.0.108", features = ["preserve_order"] } ureq = { version = "2.8.0", features = ["json"] } @@ -35,6 +34,8 @@ elliptic-curve = { version = "0.13.6", features = ["jwk"] } primeorder = "0.13.3" aead = "0.5.2" concat-kdf = "0.1.0" +ecdsa = "0.16.8" +zeroize = { version = "1.6.0", features = ["serde"] } # vsss-rs = "2.7.1" diff --git a/src/error.rs b/src/error.rs index a6d40d8..bec2e61 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,13 +1,13 @@ use std::{fmt, io, str::Utf8Error}; -use josekit::jwk::Jwk; +use crate::jose::Jwk; pub type Result = core::result::Result; #[derive(Debug)] pub enum Error { Server(Box), - Algorithm(Box, &'static str), + Algorithm(Box), IoError(io::Error), MissingKeyOp(Box), JsonMissingKey(Box), @@ -15,20 +15,22 @@ pub enum Error { Utf8(Utf8Error), Base64(base64ct::Error), Json(serde_json::Error), - Jose(josekit::JoseError), + // Jose(josekit::JoseError), KeyType(Box), VerifyKey, - InvalidPublicKey(Jwk), - EllipitcCurve(elliptic_curve::Error), + InvalidPublicKey(Box), + EllipitcCurve, MissingPublicKey, IdentityPointCreated, + EcDsa, + FailedVerification, } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Server(e) => write!(f, "server error: {e}"), - Self::Algorithm(v, c) => write!(f, "invalid algorithm {v} for {c}"), + Self::Algorithm(v) => write!(f, "unsupported algorithm {v}"), Error::IoError(e) => write!(f, "io error {e}"), Error::MissingKeyOp(e) => write!(f, "no key operation {e}"), Error::Json(e) => write!(f, "json serde error: {e}"), @@ -38,11 +40,13 @@ impl fmt::Display for Error { Error::Base64(e) => write!(f, "base64 error {e}"), Self::VerifyKey => write!(f, "missing a key marked 'verify'"), Self::KeyType(v) => write!(f, "unsupported key type {v}"), - Error::Jose(e) => write!(f, "jose error {e}"), + // Error::Jose(e) => write!(f, "jose error {e}"), Error::InvalidPublicKey(key) => write!(f, "invalid public key {key}"), - Error::EllipitcCurve(_) => write!(f, "elliptic curve cryptography"), + Error::EllipitcCurve => write!(f, "elliptic curve cryptography"), Error::MissingPublicKey => write!(f, "could not locate a key with the correct key ID"), Error::IdentityPointCreated => write!(f, "math resulted an an identity key"), + Error::EcDsa => write!(f, "error with verification"), + Error::FailedVerification => write!(f, "key verification failed"), } } } @@ -79,14 +83,14 @@ impl From for Error { } } -impl From for Error { - fn from(value: josekit::JoseError) -> Self { - Self::Jose(value) +impl From for Error { + fn from(_value: elliptic_curve::Error) -> Self { + Self::EllipitcCurve } } -impl From for Error { - fn from(value: elliptic_curve::Error) -> Self { - Self::EllipitcCurve(value) +impl From for Error { + fn from(_value: ecdsa::Error) -> Self { + Self::EcDsa } } diff --git a/src/jose.rs b/src/jose.rs index 8bba501..067d556 100644 --- a/src/jose.rs +++ b/src/jose.rs @@ -1,15 +1,23 @@ use crate::key_exchange::{create_enc_key, recover_enc_key}; use crate::util::{b64_to_bytes, b64_to_str}; use crate::{EncryptionKey, Error, Result}; -use base64ct::{Base64UrlUnpadded, Encoding}; -use josekit::jwk::Jwk; -use josekit::jws::{self, JwsVerifier}; +use base64ct::{Base64Url, Base64UrlUnpadded, Encoding}; +use elliptic_curve::sec1::{EncodedPoint, FromEncodedPoint, ModulusSize, ToEncodedPoint}; +use elliptic_curve::zeroize::Zeroizing; +use elliptic_curve::{ + AffinePoint, Curve, CurveArithmetic, FieldBytes, FieldBytesSize, JwkParameters, PublicKey, +}; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use sha2::Digest; use sha2::Sha256; use std::fmt; +#[cfg(test)] +use elliptic_curve::SecretKey; +#[cfg(test)] +use zeroize::Zeroize; + /// Representation of a tang advertisment response which is a JWS of available keys. /// /// This is what is produced when you GET `tang_url/adv`. @@ -28,18 +36,14 @@ impl Advertisment { /// /// If a thumbprint is specified, only use a verify key with that thumbprint. Otherwise, /// use any verify key. + fn validate(&self, jwks: &JwkSet, thumbprint: Option<&str>) -> Result> { let (verify_jwk, thp) = if let Some(thp) = thumbprint { (jwks.get_key_by_id(thp)?, Box::from(thp)) } else { let verify_jwk = jwks.get_key_by_op("verify")?; - ( - verify_jwk, - make_thumbprint(verify_jwk, ThpHashAlg::Sha256)?.into(), - ) + (verify_jwk, verify_jwk.make_thumbprint(ThpHashAlg::Sha256)) }; - // jwks.get_key_by_id() - let verifier = get_verifier(verify_jwk)?; // B64 is 4/3 data length, plus a `.` let payload_b64_len = Base64UrlUnpadded::encoded_len(self.payload.as_bytes()); @@ -58,7 +62,7 @@ impl Advertisment { ) .unwrap(); - verifier.verify(&to_verify, &self.signature)?; + verify_jwk.verify(&to_verify, &self.signature)?; Ok(thp) } @@ -90,6 +94,246 @@ impl fmt::Debug for Advertisment { } } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Jwk { + #[serde(flatten)] + pub inner: JwkInner, + #[serde(skip_serializing_if = "Option::is_none")] + pub key_ops: Option>>, + #[serde(skip_serializing_if = "Option::is_none")] + pub alg: Option>, + // #[serde(flatten)] + // pub extra: HashMap, serde_json::Value>, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(tag = "kty", rename_all = "UPPERCASE")] +pub enum JwkInner { + Ec(EcJwk), + Rsa(RsaJwk), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct EcJwk { + pub crv: Box, + pub x: Box, + /// Only required for the `P-` curves + #[serde(skip_serializing_if = "Option::is_none")] + pub y: Option>, + /// Private key part + #[serde(skip_serializing_if = "Option::is_none")] + pub d: Option>>, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RsaJwk { + pub e: Box, + pub n: Box, +} + +impl Jwk { + /// Jwk thumbprint as described in RFC7638 section 3.1. + fn make_thumbprint(&self, alg: ThpHashAlg) -> Box { + match &self.inner { + JwkInner::Ec(ec_key) => ec_key.make_thumbprint(alg), + JwkInner::Rsa(rsa_key) => rsa_key.make_thumbprint(alg), + } + } + + /// Return the EC JWK if that is the correct type, an algorithm error otherwise + pub(crate) fn as_ec(&self) -> Result<&EcJwk> { + match &self.inner { + JwkInner::Ec(key) => Ok(key), + JwkInner::Rsa(_) => Err(Error::Algorithm("RSA".into())), + } + } + + pub(crate) fn verify(&self, message: &[u8], signature: &[u8]) -> Result<()> { + match &self.inner { + JwkInner::Ec(v) => v.verify(message, signature), + JwkInner::Rsa(_) => Err(Error::Algorithm("RSA".into())), + } + } +} + +impl fmt::Display for Jwk { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut to_fmt = self.clone(); + + // Hide the secret key + if let JwkInner::Ec(EcJwk { + d: Some(ref mut val), + .. + }) = to_fmt.inner + { + *val = Zeroizing::new("****".into()); + }; + + f.write_str(&serde_json::to_string(&to_fmt).unwrap()) + } +} + +fn encode_base64url_fe(field: &FieldBytes) -> Box { + Base64Url::encode_string(field).into() +} + +fn decode_base64url_fe(s: &str) -> Result> { + let mut result = FieldBytes::::default(); + Base64Url::decode(s, &mut result).map_err(|_| Error::EllipitcCurve)?; + Ok(result) +} + +impl EcJwk { + /// Convert to a usable `PublicKey` + pub(crate) fn to_pub(&self) -> Result> + where + C: CurveArithmetic + JwkParameters, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, + { + assert_eq!(self.crv.as_ref(), C::CRV); + + let Some(ref y) = self.y else { + return Err(Error::InvalidPublicKey(Jwk::from(self.clone()).into())); + }; + + let x = decode_base64url_fe::(&self.x)?; + let y = decode_base64url_fe::(y)?; + let affine = EncodedPoint::::from_affine_coordinates(&x, &y, false); + + PublicKey::from_sec1_bytes(affine.as_bytes()).map_err(Into::into) + } + + pub(crate) fn from_pub(key: &PublicKey) -> Self + where + C: CurveArithmetic + JwkParameters, + AffinePoint: ToEncodedPoint, + FieldBytesSize: ModulusSize, + { + let point = key.as_affine().to_encoded_point(false); + let x = encode_base64url_fe::(point.x().expect("unexpected identity point")); + let y = encode_base64url_fe::(point.y().expect("unexpected identity point")); + Self { + crv: C::CRV.into(), + x, + y: Some(y), + d: None, + } + } + + #[cfg(test)] + pub(crate) fn to_priv(&self) -> Result> + where + C: CurveArithmetic, + { + let Some(d) = &self.d else { + panic!("expected private key but got public") + }; + + let mut d_bytes = decode_base64url_fe::(d.as_ref())?; + let result = SecretKey::::from_slice(&d_bytes)?; + d_bytes.zeroize(); + + Ok(result) + } + + pub(crate) fn get_curve(&self) -> Result { + match self.crv.as_ref() { + "P-256" => Ok(JwkCurve::P256), + "P-284" => Ok(JwkCurve::P384), + "P-521" => Ok(JwkCurve::P521), + other => Err(Error::Algorithm(other.into())), + } + } + + pub(crate) fn verify(&self, message: &[u8], signature: &[u8]) -> Result<()> { + match self.get_curve()? { + JwkCurve::P256 => self.verify_p256(message, signature), + JwkCurve::P384 => self.verify_p384(message, signature), + JwkCurve::P521 => self.verify_p521(message, signature), + } + } + + pub(crate) fn make_thumbprint(&self, alg: ThpHashAlg) -> Box { + let to_enc = json! {{ + "crv": &self.crv, + "kty": "EC", + "x": &self.x, + "y": &self.y + }}; + alg.hash_data_to_string(to_enc.to_string().as_bytes()) + } + + // FIXME: switch these to use generics once p521 uses the `ecdsa` crate traits + + fn verify_p256(&self, msg: &[u8], sig: &[u8]) -> Result<()> { + use p256::ecdsa::{signature::Verifier, Signature, VerifyingKey}; + let pubkey = self.to_pub::()?; + let verify_key = VerifyingKey::from_affine(*pubkey.as_affine())?; + let signature = Signature::from_slice(sig)?; + verify_key + .verify(msg, &signature) + .map_err(|_| Error::FailedVerification) + } + + fn verify_p384(&self, msg: &[u8], sig: &[u8]) -> Result<()> { + use p384::ecdsa::{signature::Verifier, Signature, VerifyingKey}; + let pubkey = self.to_pub::()?; + let verify_key = VerifyingKey::from_affine(*pubkey.as_affine())?; + let signature = Signature::from_slice(sig)?; + verify_key + .verify(msg, &signature) + .map_err(|_| Error::FailedVerification) + } + + fn verify_p521(&self, msg: &[u8], sig: &[u8]) -> Result<()> { + use p521::ecdsa::{signature::Verifier, Signature, VerifyingKey}; + let pubkey = self.to_pub::()?; + let verify_key = VerifyingKey::from_affine(*pubkey.as_affine())?; + let signature = Signature::from_slice(sig)?; + verify_key + .verify(msg, &signature) + .map_err(|_| Error::FailedVerification) + } +} + +impl From for Jwk { + fn from(value: EcJwk) -> Self { + Jwk { + inner: JwkInner::Ec(value), + key_ops: None, + alg: None, + // extra: HashMap::new(), + } + } +} + +impl TryFrom for EcJwk { + type Error = Error; + + fn try_from(value: Jwk) -> std::result::Result { + match value.inner { + JwkInner::Ec(ec_key) => Ok(ec_key), + JwkInner::Rsa(_) => Err(Error::Algorithm("RSA".into())), + } + } +} + +/// A verification algorithm +#[derive(Clone, Copy, Debug)] +pub(crate) enum JwkCurve { + P256, + P384, + P521, +} + +impl RsaJwk { + fn make_thumbprint(&self, alg: ThpHashAlg) -> Box { + let to_enc = json! {{ "e": &self.e, "kty": "RSA", "n": &self.n }}; + alg.hash_data_to_string(to_enc.to_string().as_bytes()) + } +} + /// The response from a Tang server, containing a list of possible keys to use for derivation #[derive(Clone, Debug, Serialize, Deserialize)] pub struct JwkSet { @@ -102,8 +346,8 @@ impl JwkSet { self.keys .iter() .find(|key| { - key.key_operations().map_or(false, |key_ops| { - key_ops.iter().any(|op| op.eq_ignore_ascii_case(op_name)) + key.key_ops.as_ref().map_or(false, |ops| { + ops.iter().any(|op| op.eq_ignore_ascii_case(op_name)) }) }) .ok_or(Error::MissingKeyOp(op_name.into())) @@ -111,12 +355,12 @@ impl JwkSet { pub(crate) fn get_key_by_id(&self, kid: &str) -> Result<&Jwk> { for key in &self.keys { - let thp_sha256 = make_thumbprint(key, ThpHashAlg::Sha256)?; - if thp_sha256 == kid { + let thp_sha256 = key.make_thumbprint(ThpHashAlg::Sha256); + if thp_sha256.as_ref() == kid { return Ok(key); } - let thp_sha1 = make_thumbprint(key, ThpHashAlg::Sha1)?; - if thp_sha1 == kid { + let thp_sha1 = key.make_thumbprint(ThpHashAlg::Sha1); + if thp_sha1.as_ref() == kid { return Ok(key); } } @@ -129,8 +373,9 @@ impl JwkSet { signing_thumbprint: Box, ) -> Result> { let derive_jwk = self.get_key_by_op("deriveKey")?.clone(); + let derive_jwk = derive_jwk.as_ec()?; - let (epk, encryption_key) = create_enc_key(&derive_jwk)?; + let (epk, encryption_key) = create_enc_key(derive_jwk)?; let clevis = ClevisParams { pin: "tang".into(), tang: TangParams { @@ -142,8 +387,8 @@ impl JwkSet { alg: "ECDH-ES".into(), clevis, enc: None, - epk, - kid: make_thumbprint(&derive_jwk, ThpHashAlg::Sha256)?.into(), + epk: epk.into(), + kid: derive_jwk.make_thumbprint(ThpHashAlg::Sha256), }; Ok(GeneratedKey { @@ -156,104 +401,29 @@ impl JwkSet { /// The key types we support #[derive(Clone, Copy, Debug, PartialEq)] -enum KeyType { - Ec, - Rsa, -} - -/// The key types we support -#[derive(Clone, Copy, Debug, PartialEq)] -enum ThpHashAlg { +pub enum ThpHashAlg { Sha1, Sha256, } -/// Extract the key type of a jwk -fn key_type(jwk: &Jwk) -> Result { - match jwk.key_type() { - "EC" => Ok(KeyType::Ec), - "RSA" => Ok(KeyType::Rsa), - _ => Err(Error::KeyType(jwk.key_type().into())), - } -} - -/// Get a verifier from a JWK -fn get_verifier(jwk: &Jwk) -> Result> { - let kty = key_type(jwk)?; - if kty == KeyType::Ec { - jws::ES512 - .verifier_from_jwk(jwk) - .or_else(|_| jws::ES256.verifier_from_jwk(jwk)) - .or_else(|_| jws::ES256K.verifier_from_jwk(jwk)) - .or_else(|_| jws::ES384.verifier_from_jwk(jwk)) - .map(|v| Box::new(v) as Box) - } else if kty == KeyType::Rsa { - jws::RS256 - .verifier_from_jwk(jwk) - .or_else(|_| jws::RS384.verifier_from_jwk(jwk)) - .or_else(|_| jws::RS512.verifier_from_jwk(jwk)) - .map(|v| Box::new(v) as Box) - } else { - unreachable!() - } - .map_err(Into::into) -} - -/// Jwk thumbprint as described in RFC7638 section 3.1. -fn make_thumbprint(jwk: &Jwk, alg: ThpHashAlg) -> Result { - let kty = key_type(jwk)?; - let to_enc = if kty == KeyType::Ec { - let crv = get_jwk_param(jwk, "crv")?; - let crv = crv - .as_str() - .ok_or_else(|| Error::JsonKeyType(crv.to_string().into()))?; - - // Only specific curves require `y` - let y_param = if ["P-256", "P-384", "P-521"].contains(&crv) { - Some(get_jwk_param(jwk, "y")?) - } else { - None - }; - - json! {{ - "crv": get_jwk_param(jwk, "crv")?, - "kty": jwk.key_type(), - "x": get_jwk_param(jwk, "x")?, - "y": y_param - }} - } else if kty == KeyType::Rsa { - json! {{ - "e": get_jwk_param(jwk, "e")?, - "kty": jwk.key_type(), - "n": get_jwk_param(jwk, "n")?, - }} - } else { - // symmetric keys need "k" and "kty" - unreachable!() - }; - - let to_hash = to_enc.to_string(); - - match alg { - ThpHashAlg::Sha1 => { - let mut hasher = sha1::Sha1::new(); - hasher.update(to_hash.as_bytes()); - Ok(Base64UrlUnpadded::encode_string(&hasher.finalize())) - } - ThpHashAlg::Sha256 => { - let mut hasher = Sha256::new(); - hasher.update(to_hash.as_bytes()); - Ok(Base64UrlUnpadded::encode_string(&hasher.finalize())) +impl ThpHashAlg { + fn hash_data_to_string(self, data: &[u8]) -> Box { + match self { + ThpHashAlg::Sha1 => { + let mut hasher = sha1::Sha1::new(); + hasher.update(data); + Base64UrlUnpadded::encode_string(&hasher.finalize()) + } + ThpHashAlg::Sha256 => { + let mut hasher = Sha256::new(); + hasher.update(data); + Base64UrlUnpadded::encode_string(&hasher.finalize()) + } } + .into_boxed_str() } } -/// Get a parameter from the JWK -fn get_jwk_param<'a>(jwk: &'a Jwk, key: &str) -> Result<&'a Value> { - jwk.parameter(key) - .ok_or_else(|| Error::JsonMissingKey(key.into())) -} - pub struct GeneratedKey { /// Use this key to encrypt data pub encryption_key: EncryptionKey, @@ -317,9 +487,10 @@ impl KeyMeta { &self, server_key_exchange: impl FnOnce(&str, &Jwk) -> Result, ) -> Result> { - let c_pub_jwk = &self.epk; + let c_pub_jwk = &self.epk.as_ec()?; let kid = &self.kid; - let s_pub_jwk = self.clevis.tang.adv.get_key_by_id(kid)?; + let s_pub_jwk = self.clevis.tang.adv.get_key_by_id(kid)?.as_ec()?; + recover_enc_key(c_pub_jwk, s_pub_jwk, |x_pub_jwk| { server_key_exchange(kid, x_pub_jwk) }) diff --git a/src/jose_tests.rs b/src/jose_tests.rs index 573b665..c4f017a 100644 --- a/src/jose_tests.rs +++ b/src/jose_tests.rs @@ -57,12 +57,12 @@ fn test_verify() { fn test_thumbprint() { let jwk: Jwk = serde_json::from_str(SAMPLE_JWK_DERIVE).unwrap(); assert_eq!( - make_thumbprint(&jwk, ThpHashAlg::Sha256).unwrap(), + jwk.make_thumbprint(ThpHashAlg::Sha256).as_ref(), SAMPLE_JWK_DERIVE_THP ); let jwk: Jwk = serde_json::from_str(SAMPLE_JWK_VERIFY).unwrap(); assert_eq!( - make_thumbprint(&jwk, ThpHashAlg::Sha256).unwrap(), + jwk.make_thumbprint(ThpHashAlg::Sha256).as_ref(), SAMPLE_JWK_VERIFY_THP ); } diff --git a/src/key_exchange.rs b/src/key_exchange.rs index ec78289..31088fe 100644 --- a/src/key_exchange.rs +++ b/src/key_exchange.rs @@ -1,3 +1,4 @@ +use crate::jose::{EcJwk, Jwk, JwkCurve}; use crate::{Error, Result}; use elliptic_curve::ecdh; use elliptic_curve::ecdh::SharedSecret; @@ -7,7 +8,6 @@ use elliptic_curve::rand_core::OsRng; use elliptic_curve::sec1::FromEncodedPoint; use elliptic_curve::sec1::ModulusSize; use elliptic_curve::sec1::ToEncodedPoint; -use elliptic_curve::sec1::ValidatePublicKey; use elliptic_curve::zeroize::Zeroizing; use elliptic_curve::AffinePoint; use elliptic_curve::Curve; @@ -17,7 +17,6 @@ use elliptic_curve::JwkParameters; use elliptic_curve::ProjectivePoint; use elliptic_curve::PublicKey; use elliptic_curve::SecretKey; -use josekit::jwk::{alg::ec::EcCurve, Jwk}; /// A zeroizing wrapper around a generated encryption key #[derive(Clone, Debug, PartialEq)] @@ -31,23 +30,6 @@ impl EncryptionKey { } } -fn eccurve_from_jwk(jwk: &Jwk) -> Result { - let crv = match jwk.curve() { - Some("P-521") => EcCurve::P521, - Some("P-384") => EcCurve::P384, - Some("P-256") => EcCurve::P256, - arg => { - return Err(Error::Algorithm( - serde_json::to_string(&arg) - .unwrap_or("none".to_owned()) - .into(), - "key exchange crv", - )) - } - }; - Ok(crv) -} - /// Perform key generation in accordance with tang protocol /// /// Rough description, capitals are public: @@ -56,41 +38,36 @@ fn eccurve_from_jwk(jwk: &Jwk) -> Result { /// - Create a client keypair `c` and `C` /// - Perform half of ECDH with client private key and server public key to get `K = [c]S`. `K` is the /// key used to encrypt data, and also satisfies `K = [cs]G` -pub fn create_enc_key(s_pub_jwk: &Jwk) -> Result<(Jwk, EncryptionKey)> { - match s_pub_jwk.algorithm() { - Some("ECMR" | "ECDH-ES") => (), - alg => { - return Err(Error::Algorithm( - alg.unwrap_or("none").into(), - "key exchange algorithm", - )) - } - } - - match eccurve_from_jwk(s_pub_jwk)? { - EcCurve::P256 => create_enc_key_inner::(s_pub_jwk), - EcCurve::P384 => create_enc_key_inner::(s_pub_jwk), - EcCurve::P521 => create_enc_key_inner::(s_pub_jwk), - EcCurve::Secp256k1 => todo!(), +pub fn create_enc_key(s_pub_jwk: &EcJwk) -> Result<(EcJwk, EncryptionKey)> { + match s_pub_jwk.get_curve()? { + JwkCurve::P256 => create_enc_key_inner::(s_pub_jwk), + JwkCurve::P384 => create_enc_key_inner::(s_pub_jwk), + JwkCurve::P521 => create_enc_key_inner::(s_pub_jwk), } } pub fn recover_enc_key( - c_pub_jwk: &Jwk, - s_pub_jwk: &Jwk, + c_pub_jwk: &EcJwk, + s_pub_jwk: &EcJwk, server_key_exchange: impl FnOnce(&Jwk) -> Result, ) -> Result> { - match eccurve_from_jwk(c_pub_jwk)? { - EcCurve::P256 => { - recover_enc_key_inner::(c_pub_jwk, s_pub_jwk, server_key_exchange) + // Wrapper to turn our EcJwks into Jwks. I think we need to set the `alg` parameter + let key_exchange = |ec_jwk: &EcJwk| -> Result { + let mut jwk: Jwk = ec_jwk.clone().into(); + jwk.alg = Some("ECMR".into()); + server_key_exchange(&jwk).and_then(EcJwk::try_from) + }; + + match c_pub_jwk.get_curve()? { + JwkCurve::P256 => { + recover_enc_key_inner::(c_pub_jwk, s_pub_jwk, key_exchange) } - EcCurve::P384 => { - recover_enc_key_inner::(c_pub_jwk, s_pub_jwk, server_key_exchange) + JwkCurve::P384 => { + recover_enc_key_inner::(c_pub_jwk, s_pub_jwk, key_exchange) } - EcCurve::P521 => { - recover_enc_key_inner::(c_pub_jwk, s_pub_jwk, server_key_exchange) + JwkCurve::P521 => { + recover_enc_key_inner::(c_pub_jwk, s_pub_jwk, key_exchange) } - EcCurve::Secp256k1 => todo!(), } } @@ -99,17 +76,17 @@ pub fn recover_enc_key( /// - Generate an ephemeral key /// - Extract the key using HKDF fn create_enc_key_inner( - remote_jwk: &Jwk, -) -> Result<(Jwk, EncryptionKey)> + remote_jwk: &EcJwk, +) -> Result<(EcJwk, EncryptionKey)> where C: CurveArithmetic + JwkParameters, AffinePoint: FromEncodedPoint + ToEncodedPoint, FieldBytesSize: ModulusSize, { - let serv_kpub = jwk_to_pub(remote_jwk)?; + let serv_kpub = remote_jwk.to_pub()?; let cli_kpriv = ecdh::EphemeralSecret::::random(&mut OsRng); - let jwk = jwk_from_pub(&cli_kpriv.public_key()); + let jwk = EcJwk::from_pub(&cli_kpriv.public_key()); let shared = cli_kpriv.diffie_hellman(&serv_kpub); Ok((jwk, secret_to_key(shared))) @@ -139,17 +116,17 @@ where /// K = y - z # dJWK /// ``` fn recover_enc_key_inner( - c_pub_jwk: &Jwk, - s_pub_jwk: &Jwk, - server_key_exchange: impl FnOnce(&Jwk) -> Result, + c_pub_jwk: &EcJwk, + s_pub_jwk: &EcJwk, + server_key_exchange: impl FnOnce(&EcJwk) -> Result, ) -> Result> where C: CurveArithmetic + JwkParameters, AffinePoint: FromEncodedPoint + ToEncodedPoint, FieldBytesSize: ModulusSize, { - let c_pub = jwk_to_pub::(c_pub_jwk)?; - let s_pub = jwk_to_pub::(s_pub_jwk)?; + let c_pub = c_pub_jwk.to_pub::()?; + let s_pub = s_pub_jwk.to_pub::()?; // e = g * E let e_priv = SecretKey::::random(&mut OsRng); @@ -157,11 +134,11 @@ where // x = c + e let x_pub = ecmr_add(&c_pub, &e_pub)?; - let x_pub_jwk = jwk_from_pub(&x_pub); + let x_pub_jwk = EcJwk::from_pub(&x_pub); // y = x * S, server operation let y_pub_jwk = server_key_exchange(&x_pub_jwk)?; - let y_pub = jwk_to_pub::(&y_pub_jwk)?; + let y_pub = y_pub_jwk.to_pub::()?; // z = s * E let z_pub = diffie_hellman(&e_priv, &s_pub)?; @@ -228,59 +205,41 @@ fn secret_to_key( enc_key } -fn jwk_to_pub(jwk: &Jwk) -> Result> -where - C: CurveArithmetic + JwkParameters, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - FieldBytesSize: ModulusSize, -{ - let errfn = || Error::InvalidPublicKey(jwk.clone()); - // Need to serialize only the fields that RC's JWK expect. Lots of copying, we can make it - // faster after it works. - let json = serde_json::json! {{ - "crv": jwk.parameter("crv").ok_or_else(errfn)?, - "kty": jwk.parameter("kty").ok_or_else(errfn)?, - "x": jwk.parameter("x").ok_or_else(errfn)?, - "y": jwk.parameter("y").ok_or_else(errfn)?, - }}; - PublicKey::from_jwk_str(&json.to_string()).map_err(|_| Error::InvalidPublicKey(jwk.clone())) -} - -#[allow(unused)] -fn jwk_to_priv(jwk: &Jwk) -> Result> -where - C: JwkParameters + ValidatePublicKey, - FieldBytesSize: ModulusSize, -{ - let errfn = || Error::InvalidPublicKey(jwk.clone()); - let json = serde_json::json! {{ - "crv": jwk.parameter("crv").ok_or_else(errfn)?, - "kty": jwk.parameter("kty").ok_or_else(errfn)?, - "d": jwk.parameter("d").ok_or_else(errfn)?, - "x": jwk.parameter("x").ok_or_else(errfn)?, - "y": jwk.parameter("y").ok_or_else(errfn)?, - }}; - SecretKey::from_jwk_str(&json.to_string()).map_err(|_| Error::InvalidPublicKey(jwk.clone())) -} - -fn jwk_from_pub(key: &PublicKey) -> Jwk -where - C: CurveArithmetic + JwkParameters, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - FieldBytesSize: ModulusSize, -{ - serde_json::from_str(key.to_jwk_string().as_ref()).expect("dependency generated an invalid JWK") -} - -#[allow(unused)] -fn jwk_from_priv(key: &SecretKey) -> Jwk -where - C: CurveArithmetic + JwkParameters, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - FieldBytesSize: ModulusSize, -{ - serde_json::from_str(key.to_jwk_string().as_ref()).expect("dependency generated an invalid JWK") -} +// #[allow(unused)] +// fn jwk_to_priv(jwk: &Jwk) -> Result> +// where +// C: JwkParameters + ValidatePublicKey, +// FieldBytesSize: ModulusSize, +// { +// let errfn = || Error::InvalidPublicKey(jwk.clone()); +// let json = serde_json::json! {{ +// "crv": jwk.parameter("crv").ok_or_else(errfn)?, +// "kty": jwk.parameter("kty").ok_or_else(errfn)?, +// "d": jwk.parameter("d").ok_or_else(errfn)?, +// "x": jwk.parameter("x").ok_or_else(errfn)?, +// "y": jwk.parameter("y").ok_or_else(errfn)?, +// }}; +// SecretKey::from_jwk_str(&json.to_string()).map_err(|_| Error::InvalidPublicKey(jwk.clone())) +// } + +// fn jwk_from_pub(key: &PublicKey) -> Jwk +// where +// C: CurveArithmetic + JwkParameters, +// AffinePoint: FromEncodedPoint + ToEncodedPoint, +// FieldBytesSize: ModulusSize, +// { +// serde_json::from_str(key.to_jwk_string().as_ref()).expect("dependency generated an invalid JWK") +// } + +// #[allow(unused)] +// fn jwk_from_priv(key: &SecretKey) -> Jwk +// where +// C: CurveArithmetic + JwkParameters, +// AffinePoint: FromEncodedPoint + ToEncodedPoint, +// FieldBytesSize: ModulusSize, +// { +// serde_json::from_str(key.to_jwk_string().as_ref()).expect("dependency generated an invalid JWK") +// } #[cfg(test)] #[path = "key_exchange_tests.rs"] diff --git a/src/key_exchange_tests.rs b/src/key_exchange_tests.rs index aa74f67..667e27b 100644 --- a/src/key_exchange_tests.rs +++ b/src/key_exchange_tests.rs @@ -13,6 +13,8 @@ //! //! We provide each mode with a separate function and test against the jose output +use crate::jose::Jwk; + use super::*; use serde_json::Value; @@ -71,15 +73,15 @@ const ADDITION: &str = r#"{ // }"#; fn pub1_jwk() -> PublicKey { - jwk_to_pub(&serde_json::from_str(PUB1).unwrap()).unwrap() + EcJwk::to_pub(&serde_json::from_str(PUB1).unwrap()).unwrap() } fn pub2_jwk() -> PublicKey { - jwk_to_pub(&serde_json::from_str(PUB2).unwrap()).unwrap() + EcJwk::to_pub(&serde_json::from_str(PUB2).unwrap()).unwrap() } fn priv_jwk() -> SecretKey { - jwk_to_priv(&serde_json::from_str(PRIV).unwrap()).unwrap() + EcJwk::to_priv(&serde_json::from_str(PRIV).unwrap()).unwrap() } fn assert_json_eq(left: &str, right: &str) { @@ -190,15 +192,14 @@ fn test_roundtrip_jwk() { // Pretend to be the server let server_key_exchange = |x_pub_jwk: &Jwk| -> Result { - let x_pub = jwk_to_pub(x_pub_jwk).unwrap(); + let x_pub = x_pub_jwk.as_ec().unwrap().to_pub().unwrap(); let y_pub = diffie_hellman(&s_priv, &x_pub).unwrap(); - Ok(jwk_from_pub(&y_pub)) + Ok(EcJwk::from_pub(&y_pub).into()) }; let s_pub = s_priv.public_key(); - let mut s_pub_jwk = jwk_from_pub(&s_pub); - s_pub_jwk.set_algorithm("ECMR"); + let s_pub_jwk = EcJwk::from_pub(&s_pub); dbg!(&s_pub_jwk); let (c_pub_jwk, k1) = create_enc_key::<10>(&s_pub_jwk).unwrap(); diff --git a/src/tang_interface.rs b/src/tang_interface.rs index 4b2b20c..6a1117d 100644 --- a/src/tang_interface.rs +++ b/src/tang_interface.rs @@ -1,8 +1,7 @@ use std::time::Duration; -use crate::jose::{Advertisment, GeneratedKey, JwkSet, KeyMeta}; +use crate::jose::{Advertisment, GeneratedKey, Jwk, JwkSet, KeyMeta}; use crate::{EncryptionKey, Result}; -use josekit::jwk::Jwk; // const DEFAULT_URL: &str = "http://tang.local"; const DEFAULT_TIMEOUT: Duration = Duration::from_secs(120); @@ -70,10 +69,4 @@ impl TangClient { .into_json() .map_err(Into::into) } - - // /// Fetch a public key with a key ID - // pub fn fetch_public_key(url: &str, key_id: String) {} - - // /// Perform recovery - // pub fn recover_key(url: &str, key_id: String) {} } diff --git a/tests/backend.rs b/tests/backend.rs index 012bc2f..4ce6385 100644 --- a/tests/backend.rs +++ b/tests/backend.rs @@ -26,11 +26,12 @@ fn test_roundtrip() { } = client.create_secure_key::<10>().unwrap(); let meta_str = meta.to_json(); + dbg!(&meta); println!("{}", json_pretty(&meta_str)); // --- recovery --- - let new_meta = KeyMeta::from_json(&meta_str).unwrap(); + let new_meta = KeyMeta::from_json(&meta_str).unwrap_or_else(|e| panic!("{e}:\n {meta_str}")); let newkey = client.recover_secure_key::<10>(&new_meta).unwrap(); assert_eq!(encryption_key.as_bytes(), newkey.as_bytes());