diff --git a/Cargo.toml b/Cargo.toml index 51c9e5c..417dd19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "compact_jwt" -version = "0.5.1-dev" +version = "0.5.2-dev" edition = "2021" authors = ["William Brown "] description = "Minimal implementation of JWT for OIDC and other applications" @@ -29,14 +29,13 @@ base64 = "^0.21.5" base64urlsafedata = "^0.5.1" crypto-glue = "^0.1.7" -kanidm-hsm-crypto = "0.3.1" +kanidm-hsm-crypto = "0.3.2" url = { version = "^2.2.2", features = ["serde"] } uuid = { version = "^1.0.0", features = ["serde"] } tracing = "^0.1.34" hex = "0.4" - [dev-dependencies] tracing-subscriber = "^0.3.11" diff --git a/src/crypto/a256gcm.rs b/src/crypto/a256gcm.rs index 50ecf45..5de9bc2 100644 --- a/src/crypto/a256gcm.rs +++ b/src/crypto/a256gcm.rs @@ -15,7 +15,7 @@ pub struct JweA256GCMEncipher { aes_key: Aes256Key, } -#[cfg(all(test, feature = "msextensions"))] +#[cfg(test)] impl JweA256GCMEncipher { pub(crate) fn raw_key(&self) -> Aes256Key { self.aes_key.clone() diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 9616604..df6981f 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -16,6 +16,7 @@ mod es256; mod hs256; mod rs256; mod tpm_es256; +mod tpm_rs256; mod x509; // JWE types @@ -26,7 +27,7 @@ mod a256kw; mod ecdhes_a256kw; mod rsaes_oaep; -#[cfg(feature = "msextensions")] +#[cfg(any(feature = "msextensions", test))] mod ms_oapxbc; pub use es256::{JwsEs256Signer, JwsEs256Verifier}; @@ -41,10 +42,11 @@ pub use a256kw::JweA256KWEncipher; pub use ecdhes_a256kw::{JweEcdhEsA256KWDecipher, JweEcdhEsA256KWEncipher}; pub use rsaes_oaep::{JweRSAOAEPDecipher, JweRSAOAEPEncipher}; -#[cfg(feature = "msextensions")] +#[cfg(any(feature = "msextensions", test))] pub use ms_oapxbc::MsOapxbcSessionKey; pub use tpm_es256::JwsTpmEs256Signer; +pub use tpm_rs256::JwsTpmRs256Signer; #[cfg(test)] impl JwsCompact { diff --git a/src/crypto/ms_oapxbc.rs b/src/crypto/ms_oapxbc.rs index 1aec8f8..4e4c27e 100644 --- a/src/crypto/ms_oapxbc.rs +++ b/src/crypto/ms_oapxbc.rs @@ -50,7 +50,7 @@ impl MsOapxbcSessionKey { jwec: &JweCompact, ) -> Result where - T: TpmMsExtensions, + T: TpmMsExtensions + ?Sized, { let expected_wrap_key_buffer_len = aes256::key_size(); @@ -84,7 +84,7 @@ impl MsOapxbcSessionKey { jwec: &JweCompact, ) -> Result where - T: TpmMsExtensions, + T: TpmMsExtensions + ?Sized, { let ctx_bytes = if let Some(ctx) = &jwec.header.ctx { general_purpose::STANDARD @@ -139,7 +139,7 @@ impl MsOapxbcSessionKey { jwec: &JweCompact, ) -> Result where - T: TpmMsExtensions, + T: TpmMsExtensions + ?Sized, { // Alg must be direct. if jwec.header.alg != JweAlg::DIRECT { @@ -183,7 +183,7 @@ impl MsOapxbcSessionKey { jwe: &Jwe, ) -> Result where - T: TpmMsExtensions, + T: TpmMsExtensions + ?Sized, { let outer = JweDirect::default(); @@ -216,7 +216,7 @@ impl MsOapxbcSessionKey { jws: &V, ) -> Result where - T: TpmMsExtensions, + T: TpmMsExtensions + ?Sized, { let hmac_key = match self { MsOapxbcSessionKey::A256GCM { sealed_session_key } => { @@ -242,7 +242,7 @@ impl MsOapxbcSessionKey { jws: &V, ) -> Result where - T: TpmMsExtensions, + T: TpmMsExtensions + ?Sized, { let mut nonce = [0; CTX_NONCE_LEN]; let mut rng = rand::thread_rng(); @@ -277,7 +277,7 @@ impl MsOapxbcSessionKey { jwsc: &V, ) -> Result where - T: TpmMsExtensions, + T: TpmMsExtensions + ?Sized, { let hmac_key = if let Some(ctx) = &jwsc.data().header.ctx { let ctx_bytes = general_purpose::STANDARD diff --git a/src/crypto/rs256.rs b/src/crypto/rs256.rs index c732e83..6c9784a 100644 --- a/src/crypto/rs256.rs +++ b/src/crypto/rs256.rs @@ -224,6 +224,15 @@ pub struct JwsRs256Verifier { pkey: RS256PublicKey, } +impl TryFrom for JwsRs256Verifier { + type Error = JwtError; + + fn try_from(pkey: RS256PublicKey) -> Result { + let kid = kid_from_public(&pkey)?; + Ok(JwsRs256Verifier { kid, pkey }) + } +} + impl JwsRs256Verifier { /// Create an RSA-SHA256 verifier from a public key in SPKI DER format. pub fn from_rs256_der(der: &[u8]) -> Result { diff --git a/src/crypto/tpm_es256.rs b/src/crypto/tpm_es256.rs index 7aa54f4..318ff57 100644 --- a/src/crypto/tpm_es256.rs +++ b/src/crypto/tpm_es256.rs @@ -97,15 +97,15 @@ where #[cfg(test)] mod tests { use super::JwsTpmEs256Signer; + use crate::crypto::es256::JwsEs256Verifier; + use crate::jws::JwsBuilder; + use crate::traits::*; use kanidm_hsm_crypto::{ + provider::BoxedDynTpm, provider::SoftTpm, provider::{Tpm, TpmES256}, AuthValue, }; - // use crate::compact::{Jwk, JwsCompact}; - use crate::crypto::es256::JwsEs256Verifier; - use crate::jws::JwsBuilder; - use crate::traits::*; #[test] fn tpm_key_generate_cycle() { @@ -154,33 +154,37 @@ mod tests { assert!(released.payload() == &[0, 1, 2, 3, 4]); } - /* #[test] fn tpm_dyn_trait_object_cycle() { let _ = tracing_subscriber::fmt::try_init(); // Setup the tpm - let mut softtpm: BoxedDynTpm = BoxedDynTpm::new(SoftTpm::new()); - // let mut softtpm: &mut BoxedDynTpm = &mut box_softtpm; + let mut soft_tpm: BoxedDynTpm = SoftTpm::default().into(); let auth_value = AuthValue::ephemeral().unwrap(); - let loadable_machine_key = softtpm.machine_key_create(&auth_value).unwrap(); + let loadable_storage_key = soft_tpm + .root_storage_key_create(&auth_value) + .expect("Unable to create new storage key"); - let machine_key = softtpm - .machine_key_load(&auth_value, &loadable_machine_key) - .unwrap(); + let root_storage_key = soft_tpm + .root_storage_key_load(&auth_value, &loadable_storage_key) + .expect("Unable to load storage key"); - let loadable_id_key = softtpm - .identity_key_create(&machine_key, KeyAlgorithm::Ecdsa256) - .unwrap(); + let loadable_es256_key = soft_tpm + .es256_create(&root_storage_key) + .expect("Unable to create es256 key"); - let id_key = softtpm - .identity_key_load(&machine_key, &loadable_id_key) - .unwrap(); + let es256_key = soft_tpm + .es256_load(&root_storage_key, &loadable_es256_key) + .expect("Unable to load es256 key"); + + let es256_pub_key = soft_tpm + .es256_public(&es256_key) + .expect("Unable to access es256 public key"); let mut jws_tpm_signer = - JwsTpmSigner::new(&mut softtpm, &id_key).expect("failed to construct signer."); + JwsTpmEs256Signer::new(&mut soft_tpm, &es256_key).expect("failed to construct signer."); // This time we'll add the jwk pubkey and show it being used with the validator. let jws = JwsBuilder::from(vec![0, 1, 2, 3, 4]) @@ -193,10 +197,10 @@ mod tests { let jwsc = jws_tpm_signer.sign(&jws).expect("Failed to sign"); - let released = jws_tpm_signer - .verify(&jwsc) - .expect("Unable to validate jws"); + let verifier = JwsEs256Verifier::from(es256_pub_key); + + let released = verifier.verify(&jwsc).expect("Unable to validate jws"); + assert!(released.payload() == &[0, 1, 2, 3, 4]); } - */ } diff --git a/src/crypto/tpm_rs256.rs b/src/crypto/tpm_rs256.rs new file mode 100644 index 0000000..0fef2da --- /dev/null +++ b/src/crypto/tpm_rs256.rs @@ -0,0 +1,207 @@ +use crate::compact::{JwaAlg, JwsCompact, ProtectedHeader}; +use crate::error::JwtError; +use crate::traits::*; +use base64::{engine::general_purpose, Engine as _}; +use crypto_glue::traits::SignatureEncoding; +use kanidm_hsm_crypto::{ + provider::{Tpm, TpmRS256}, + structures::RS256Key, +}; + +/// A JWS signer that uses a TPM protected key for signing operations. +/// +/// Due to the construction of TPM's, this struct is intended to be "short lived" +/// relying on references to the TPM rather than taking ownership of it. This means +/// unlike other Signer types, you will need to build this struct each time you want +/// to perform a signing operation in most cases. +pub struct JwsTpmRs256Signer<'a, T: Tpm + TpmRS256 + ?Sized> { + kid: String, + tpm: &'a mut T, + id_key: &'a RS256Key, +} + +impl<'a, T> JwsTpmRs256Signer<'a, T> +where + T: Tpm + TpmRS256, +{ + /// Create a new JwsTpmSigner that will use the provided Identity Key for signing + /// operations. + pub fn new(tpm: &'a mut T, id_key: &'a RS256Key) -> Result { + let kid = tpm + .rs256_fingerprint(id_key) + .map(hex::encode) + .map_err(|_err| JwtError::TpmError)?; + + Ok(Self { kid, tpm, id_key }) + } +} + +impl JwsMutSigner for JwsTpmRs256Signer<'_, T> +where + T: Tpm + TpmRS256, +{ + fn get_kid(&mut self) -> &str { + self.kid.as_str() + } + + fn update_header(&mut self, header: &mut ProtectedHeader) -> Result<(), JwtError> { + header.alg = JwaAlg::RS256; + + // Only set the kid if it's not already set + if header.kid.is_none() { + header.kid = Some(self.kid.clone()); + } + + /* + if self.sign_option_embed_jwk { + header.jwk = self.public_key_as_jwk().map(Some)?; + } + */ + + Ok(()) + } + + fn sign(&mut self, jws: &V) -> Result { + let mut sign_data = jws.data()?; + + // Let the signer update the header as required. + self.update_header(&mut sign_data.header)?; + + let hdr_b64 = serde_json::to_vec(&sign_data.header) + .map_err(|e| { + debug!(?e); + JwtError::InvalidHeaderFormat + }) + .map(|bytes| general_purpose::URL_SAFE_NO_PAD.encode(bytes))?; + + let mut hash_data = Vec::with_capacity(hdr_b64.len() + 1 + sign_data.payload_b64.len()); + hash_data.extend_from_slice(hdr_b64.as_bytes()); + hash_data.extend_from_slice(".".as_bytes()); + hash_data.extend_from_slice(sign_data.payload_b64.as_bytes()); + + let signature = self + .tpm + .rs256_sign(self.id_key, &hash_data) + .map_err(|_err| JwtError::TpmError)?; + + let jwsc = JwsCompact { + header: sign_data.header, + hdr_b64, + payload_b64: sign_data.payload_b64, + signature: signature.to_vec(), + }; + + jws.post_process(jwsc) + } +} + +#[cfg(test)] +mod tests { + use super::JwsTpmRs256Signer; + use crate::crypto::rs256::JwsRs256Verifier; + use crate::jws::JwsBuilder; + use crate::traits::*; + use kanidm_hsm_crypto::{ + provider::BoxedDynTpm, + provider::SoftTpm, + provider::{Tpm, TpmRS256}, + AuthValue, + }; + + #[test] + fn tpm_key_generate_cycle() { + let _ = tracing_subscriber::fmt::try_init(); + + // Setup the tpm + let mut soft_tpm = SoftTpm::default(); + let auth_value = AuthValue::ephemeral().expect("Failed to generate new random secret"); + + let loadable_storage_key = soft_tpm + .root_storage_key_create(&auth_value) + .expect("Unable to create new storage key"); + + let root_storage_key = soft_tpm + .root_storage_key_load(&auth_value, &loadable_storage_key) + .expect("Unable to load storage key"); + + let loadable_rs256_key = soft_tpm + .rs256_create(&root_storage_key) + .expect("Unable to create rs256 key"); + + let rs256_key = soft_tpm + .rs256_load(&root_storage_key, &loadable_rs256_key) + .expect("Unable to load rs256 key"); + + let rs256_pub_key = soft_tpm + .rs256_public(&rs256_key) + .expect("Unable to access rs256 public key"); + + let mut jws_tpm_signer = + JwsTpmRs256Signer::new(&mut soft_tpm, &rs256_key).expect("failed to construct signer."); + + // This time we'll add the jwk pubkey and show it being used with the validator. + let jws = JwsBuilder::from(vec![0, 1, 2, 3, 4]) + .set_kid(Some("abcd")) + .set_typ(Some("abcd")) + .set_cty(Some("abcd")) + .build(); + + let jwsc = jws_tpm_signer.sign(&jws).expect("Failed to sign"); + + let verifier = JwsRs256Verifier::try_from(rs256_pub_key).unwrap(); + + let released = verifier.verify(&jwsc).expect("Unable to validate jws"); + + assert!(released.payload() == &[0, 1, 2, 3, 4]); + } + + #[test] + fn tpm_dyn_trait_object_cycle() { + let _ = tracing_subscriber::fmt::try_init(); + + // Setup the tpm + let mut soft_tpm: BoxedDynTpm = SoftTpm::default().into(); + + let auth_value = AuthValue::ephemeral().unwrap(); + + let loadable_storage_key = soft_tpm + .root_storage_key_create(&auth_value) + .expect("Unable to create new storage key"); + + let root_storage_key = soft_tpm + .root_storage_key_load(&auth_value, &loadable_storage_key) + .expect("Unable to load storage key"); + + let loadable_rs256_key = soft_tpm + .rs256_create(&root_storage_key) + .expect("Unable to create rs256 key"); + + let rs256_key = soft_tpm + .rs256_load(&root_storage_key, &loadable_rs256_key) + .expect("Unable to load rs256 key"); + + let rs256_pub_key = soft_tpm + .rs256_public(&rs256_key) + .expect("Unable to access rs256 public key"); + + let mut jws_tpm_signer = + JwsTpmRs256Signer::new(&mut soft_tpm, &rs256_key).expect("failed to construct signer."); + + // This time we'll add the jwk pubkey and show it being used with the validator. + let jws = JwsBuilder::from(vec![0, 1, 2, 3, 4]) + .set_kid(Some("abcd")) + .set_typ(Some("abcd")) + .set_cty(Some("abcd")) + .build(); + + // jws_tpm_signer.set_sign_option_embed_jwk(true); + + let jwsc = jws_tpm_signer.sign(&jws).expect("Failed to sign"); + + let verifier = JwsRs256Verifier::try_from(rs256_pub_key).unwrap(); + + let released = verifier.verify(&jwsc).expect("Unable to validate jws"); + + assert!(released.payload() == &[0, 1, 2, 3, 4]); + } +}