Skip to content

Commit

Permalink
feat: implement hyper set signature to request
Browse files Browse the repository at this point in the history
  • Loading branch information
junkurihara committed Feb 5, 2024
1 parent e1d6f1c commit 348f43f
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 35 deletions.
89 changes: 69 additions & 20 deletions ext-hyper/src/hyper_http.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
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::{
message_component::{
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

Expand All @@ -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<T>(
&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]
Expand All @@ -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<T>(
&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::<Vec<_>>();

// 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::<Vec<_>>();
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(())
}
Expand Down Expand Up @@ -160,9 +173,22 @@ fn extract_http_message_component_from_request<B>(
/* --------------------------------------- */
#[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<Request<Full<bytes::Bytes>>> {
let body = Full::new(&b"{\"hello\": \"world\"}"[..]);
Expand All @@ -177,6 +203,15 @@ mod tests {
req.set_content_digest(&ContentDigestType::Sha256).await
}

fn build_covered_components() -> Vec<HttpMessageComponentId> {
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();
Expand Down Expand Up @@ -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);
}
}
28 changes: 14 additions & 14 deletions lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand All @@ -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-----
Expand Down Expand Up @@ -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);
// }
}
2 changes: 1 addition & 1 deletion lib/src/signature_params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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-----
"##;
Expand Down

0 comments on commit 348f43f

Please sign in to comment.