diff --git a/ext-hyper/src/hyper_http.rs b/ext-hyper/src/hyper_http.rs index e2b7034..a8e0379 100644 --- a/ext-hyper/src/hyper_http.rs +++ b/ext-hyper/src/hyper_http.rs @@ -1,5 +1,6 @@ use anyhow::{bail, ensure}; use async_trait::async_trait; +use base64::{engine::general_purpose, Engine as _}; use http::Request; use http_body::Body; use httpsig::prelude::{ @@ -7,9 +8,11 @@ use httpsig::prelude::{ build_http_message_component, DerivedComponentName, HttpMessageComponent, HttpMessageComponentId, HttpMessageComponentName, HttpMessageComponentParam, }, - HttpSignatureBase, HttpSignatureParams, + HttpSignatureBase, HttpSignatureParams, SigningKey, }; -// + +/// Default signature name used to indicate signature in http header (`signature` and `signature-input`) +const DEFAULT_SIGNATURE_NAME: &str = "sig"; // hyper's http specific extension to generate and verify http signature @@ -18,9 +21,15 @@ use httpsig::prelude::{ /// A trait to set the http message signature from given http signature params pub trait HyperRequestMessageSignature { type Error; - async fn set_message_signature(&mut self, signature_params: &HttpSignatureParams) -> std::result::Result<(), Self::Error> + async fn set_message_signature( + &mut self, + signature_params: &HttpSignatureParams, + sigining_key: &T, + signature_name: Option<&str>, + ) -> std::result::Result<(), Self::Error> where - Self: Sized; + Self: Sized, + T: SigningKey + Sync; } #[async_trait] @@ -30,24 +39,28 @@ where { type Error = anyhow::Error; - async fn set_message_signature(&mut self, signature_params: &HttpSignatureParams) -> std::result::Result<(), Self::Error> + async fn set_message_signature( + &mut self, + signature_params: &HttpSignatureParams, + sigining_key: &T, + signature_name: Option<&str>, + ) -> std::result::Result<(), Self::Error> where Self: Sized, + T: SigningKey + Sync, { - // let component_lines = signature_params - // .covered_components - // .iter() - // .map(|component_id_str| { - // let component_id = HttpMessageComponentName::from(component_id_str.as_str()); - - // extract_component_from_request(self, &component_id) - // }) - // .collect::>(); - - // anyhow::ensure!(component_lines.iter().all(|c| c.is_ok()), "Failed to extract component lines"); - // let component_lines = component_lines.into_iter().map(|c| c.unwrap()).collect::>(); + let signature_base = build_signature_base_from_request(self, signature_params)?; + let signature_base_bytes = signature_base.as_bytes(); + let signature = sigining_key.sign(&signature_base_bytes)?; + let base64_signature = general_purpose::STANDARD.encode(signature); + let signature_name = signature_name.unwrap_or(DEFAULT_SIGNATURE_NAME); - // let signature_base = SignatureBase::try_new(&component_lines, signature_params); + let signature_input_header_value = format!("{signature_name}={signature_params}"); + let signature_header_value = format!("{signature_name}=:{base64_signature}:"); + self + .headers_mut() + .append("signature-input", signature_input_header_value.parse()?); + self.headers_mut().append("signature", signature_header_value.parse()?); Ok(()) } @@ -160,9 +173,22 @@ fn extract_http_message_component_from_request( /* --------------------------------------- */ #[cfg(test)] mod tests { - use super::super::{hyper_content_digest::HyperRequestContentDigest, ContentDigestType}; - use super::*; + use super::{ + super::{hyper_content_digest::HyperRequestContentDigest, ContentDigestType}, + *, + }; use http_body_util::Full; + use httpsig::prelude::SecretKey; + + const EDDSA_SECRET_KEY: &str = r##"-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIDSHAE++q1BP7T8tk+mJtS+hLf81B0o6CFyWgucDFN/C +-----END PRIVATE KEY----- +"##; + const _EDDSA_PUBLIC_KEY: &str = r##"-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= +-----END PUBLIC KEY----- +"##; + const _EDDSA_KEY_ID: &str = "gjrE7ACMxgzYfFHgabgf4kLTg1eKIdsJ94AiFTFj1is"; async fn build_request() -> anyhow::Result>> { let body = Full::new(&b"{\"hello\": \"world\"}"[..]); @@ -177,6 +203,15 @@ mod tests { req.set_content_digest(&ContentDigestType::Sha256).await } + fn build_covered_components() -> Vec { + vec![ + HttpMessageComponentId::try_from("@method").unwrap(), + HttpMessageComponentId::try_from("date").unwrap(), + HttpMessageComponentId::try_from("content-type").unwrap(), + HttpMessageComponentId::try_from("content-digest").unwrap(), + ] + } + #[tokio::test] async fn test_extract_component_from_request() { let req = build_request().await.unwrap(); @@ -239,4 +274,18 @@ mod tests { "@signature-params": ("@method" "content-type" "date" "content-digest");created=1704972031;alg="ed25519";keyid="gjrE7ACMxgzYfFHgabgf4kLTg1eKIdsJ94AiFTFj1is""## ); } + + #[tokio::test] + async fn test_set_message_signature() { + let mut req = build_request().await.unwrap(); + let secret_key = SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap(); + let mut signature_params = HttpSignatureParams::try_new(&build_covered_components()).unwrap(); + signature_params.set_key_info(&secret_key); + + req.set_message_signature(&signature_params, &secret_key, None).await.unwrap(); + let signature_input = req.headers().get("signature-input").unwrap().to_str().unwrap(); + assert!(signature_input.starts_with(r##"sig=("@method" "date" "content-type" "content-digest")"##)); + let signature = req.headers().get("signature").unwrap().to_str().unwrap(); + println!("{}", signature); + } } diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 34ed5e7..52eb4ec 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -6,11 +6,6 @@ mod signer; mod trace; mod util; -use crate::{ - crypto::{PublicKey, SecretKey, SigningKey, VerifyingKey}, - // signature_params::{HttpSignatureParams, HttpSignatureParamsBuilder}, -}; - pub mod prelude { pub mod message_component { pub use crate::message_component::{ @@ -19,12 +14,17 @@ pub mod prelude { }; } - pub use crate::{signature_base::HttpSignatureBase, signature_params::HttpSignatureParams}; + pub use crate::{ + crypto::{PublicKey, SecretKey, SigningKey, VerifyingKey}, + signature_base::HttpSignatureBase, + signature_params::HttpSignatureParams, + }; } #[cfg(test)] mod tests { - use super::*; + // use super::*; + use crate::crypto::{PublicKey, SecretKey, SigningKey, VerifyingKey}; use base64::{engine::general_purpose, Engine as _}; // params from https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#name-signing-a-request-using-ed2 const EDDSA_SECRET_KEY: &str = r##"-----BEGIN PRIVATE KEY----- @@ -72,11 +72,11 @@ Signature: sig-b26=:wqcAqbmYJ2ji2glfAMaRy4gruYYnx2nEFN2HN6jrnDnQCK1\ assert!(verification_result.is_ok()); } - #[test] - fn test_http_signature_params() { - let signature_params_str = - r##"("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519""##; - // let signature_params = HttpSignatureParams::try_from(signature_params_str).unwrap(); - // assert_eq!(signature_params.to_string(), signature_params_str); - } + // #[test] + // fn test_http_signature_params() { + // let signature_params_str = + // r##"("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519""##; + // // let signature_params = HttpSignatureParams::try_from(signature_params_str).unwrap(); + // // assert_eq!(signature_params.to_string(), signature_params_str); + // } } diff --git a/lib/src/signature_params.rs b/lib/src/signature_params.rs index a39b5c3..4be0ed3 100644 --- a/lib/src/signature_params.rs +++ b/lib/src/signature_params.rs @@ -212,7 +212,7 @@ mod tests { MC4CAQAwBQYDK2VwBCIEIDSHAE++q1BP7T8tk+mJtS+hLf81B0o6CFyWgucDFN/C -----END PRIVATE KEY----- "##; - const EDDSA_PUBLIC_KEY: &str = r##"-----BEGIN PUBLIC KEY----- + const _EDDSA_PUBLIC_KEY: &str = r##"-----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= -----END PUBLIC KEY----- "##;