From bd1caaf5daf68aaca65df90fd7c75265a1991348 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 21 Jan 2021 02:45:29 +0900 Subject: [PATCH 1/6] Include (request-target) and Host header to HTTP Signature --- plume-common/src/activity_pub/mod.rs | 18 ++++++++++-- plume-common/src/activity_pub/request.rs | 37 +++++++++++++++++++----- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/plume-common/src/activity_pub/mod.rs b/plume-common/src/activity_pub/mod.rs index bf83530b..dd739634 100644 --- a/plume-common/src/activity_pub/mod.rs +++ b/plume-common/src/activity_pub/mod.rs @@ -1,6 +1,6 @@ use activitypub::{Activity, Link, Object}; use array_tool::vec::Uniq; -use reqwest::r#async::ClientBuilder; +use reqwest::{header::HeaderValue, r#async::ClientBuilder, Url}; use rocket::{ http::Status, request::{FromRequest, Request}, @@ -143,6 +143,20 @@ where for inbox in boxes { let body = signed.to_string(); let mut headers = request::headers(); + let url = Url::parse(&inbox); + if url.is_err() { + warn!("Inbox is invalid URL: {:?}", &inbox); + continue; + } + let url = url.unwrap(); + if !url.has_host() { + warn!("Inbox doesn't have host: {:?}", &inbox); + continue; + }; + headers.insert( + "Host", + HeaderValue::from_str(&url.host_str().unwrap()).unwrap(), + ); headers.insert("Digest", request::Digest::digest(&body)); rt.spawn( client @@ -150,7 +164,7 @@ where .headers(headers.clone()) .header( "Signature", - request::signature(sender, &headers) + request::signature(sender, &headers, "post", url.path(), url.query()) .expect("activity_pub::broadcast: request signature error"), ) .body(body) diff --git a/plume-common/src/activity_pub/request.rs b/plume-common/src/activity_pub/request.rs index 35bc0059..4ffe7754 100644 --- a/plume-common/src/activity_pub/request.rs +++ b/plume-common/src/activity_pub/request.rs @@ -112,25 +112,46 @@ pub fn headers() -> HeaderMap { headers } -pub fn signature(signer: &S, headers: &HeaderMap) -> Result { - let signed_string = headers +type Method<'a> = &'a str; +type Path<'a> = &'a str; +type Query<'a> = &'a str; + +pub fn signature( + signer: &S, + headers: &HeaderMap, + method: Method, + path: Path, + query: Option, +) -> Result { + let origin_form = if let Some(query) = query { + format!("{}?{}", path, query) + } else { + path.to_string() + }; + + let mut headers = headers .iter() .map(|(h, v)| { - format!( - "{}: {}", + ( h.as_str().to_lowercase(), v.to_str() - .expect("request::signature: invalid header error") + .expect("request::signature: invalid header error"), ) }) + .collect::>(); + let request_target = format!("{} {}", method.to_lowercase(), origin_form); + headers.push(("(request-target)".to_string(), &request_target)); + + let signed_string = headers + .iter() + .map(|(h, v)| format!("{}: {}", h, v)) .collect::>() .join("\n"); let signed_headers = headers .iter() - .map(|(h, _)| h.as_str()) + .map(|(h, _)| h.as_ref()) .collect::>() - .join(" ") - .to_lowercase(); + .join(" "); let data = signer.sign(&signed_string).map_err(|_| Error())?; let sign = base64::encode(&data); From 191239584b394718f05d1cb4061ed21b6cc3aed1 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Mon, 25 Jan 2021 00:13:53 +0900 Subject: [PATCH 2/6] Define RequestTarget --- plume-common/src/activity_pub/mod.rs | 2 +- plume-common/src/activity_pub/request.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plume-common/src/activity_pub/mod.rs b/plume-common/src/activity_pub/mod.rs index dd739634..539af143 100644 --- a/plume-common/src/activity_pub/mod.rs +++ b/plume-common/src/activity_pub/mod.rs @@ -164,7 +164,7 @@ where .headers(headers.clone()) .header( "Signature", - request::signature(sender, &headers, "post", url.path(), url.query()) + request::signature(sender, &headers, ("post", url.path(), url.query())) .expect("activity_pub::broadcast: request signature error"), ) .body(body) diff --git a/plume-common/src/activity_pub/request.rs b/plume-common/src/activity_pub/request.rs index 4ffe7754..6768f2b0 100644 --- a/plume-common/src/activity_pub/request.rs +++ b/plume-common/src/activity_pub/request.rs @@ -115,14 +115,14 @@ pub fn headers() -> HeaderMap { type Method<'a> = &'a str; type Path<'a> = &'a str; type Query<'a> = &'a str; +type RequestTarget<'a> = (Method<'a>, Path<'a>, Option>); pub fn signature( signer: &S, headers: &HeaderMap, - method: Method, - path: Path, - query: Option, + request_target: RequestTarget, ) -> Result { + let (method, path, query) = request_target; let origin_form = if let Some(query) = query { format!("{}?{}", path, query) } else { From 348259a14bae2ad8cd88ef71733b8915d066b368 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 4 Feb 2021 15:31:57 +0900 Subject: [PATCH 3/6] Care about error case --- plume-common/src/activity_pub/mod.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plume-common/src/activity_pub/mod.rs b/plume-common/src/activity_pub/mod.rs index 539af143..179f9864 100644 --- a/plume-common/src/activity_pub/mod.rs +++ b/plume-common/src/activity_pub/mod.rs @@ -153,10 +153,12 @@ where warn!("Inbox doesn't have host: {:?}", &inbox); continue; }; - headers.insert( - "Host", - HeaderValue::from_str(&url.host_str().unwrap()).unwrap(), - ); + let host_header_value = HeaderValue::from_str(&url.host_str().expect("Unreachable")); + if host_header_value.is_err() { + warn!("Header valid is invalid: {:?}", url.host_str()); + continue; + } + headers.insert("Host", host_header_value.unwrap()); headers.insert("Digest", request::Digest::digest(&body)); rt.spawn( client From d08d2c69581a29bfd15dc8099d24b8e9165b1987 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 4 Feb 2021 15:44:06 +0900 Subject: [PATCH 4/6] Care the case of invalid header value --- plume-common/src/activity_pub/mod.rs | 2 +- plume-common/src/activity_pub/request.rs | 26 ++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/plume-common/src/activity_pub/mod.rs b/plume-common/src/activity_pub/mod.rs index 179f9864..bf643d89 100644 --- a/plume-common/src/activity_pub/mod.rs +++ b/plume-common/src/activity_pub/mod.rs @@ -155,7 +155,7 @@ where }; let host_header_value = HeaderValue::from_str(&url.host_str().expect("Unreachable")); if host_header_value.is_err() { - warn!("Header valid is invalid: {:?}", url.host_str()); + warn!("Header value is invalid: {:?}", url.host_str()); continue; } headers.insert("Host", host_header_value.unwrap()); diff --git a/plume-common/src/activity_pub/request.rs b/plume-common/src/activity_pub/request.rs index 6768f2b0..04a07c83 100644 --- a/plume-common/src/activity_pub/request.rs +++ b/plume-common/src/activity_pub/request.rs @@ -3,6 +3,7 @@ use openssl::hash::{Hasher, MessageDigest}; use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE, DATE, USER_AGENT}; use std::ops::Deref; use std::time::SystemTime; +use tracing::warn; use crate::activity_pub::sign::Signer; use crate::activity_pub::{ap_accept_header, AP_CONTENT_TYPE}; @@ -129,25 +130,24 @@ pub fn signature( path.to_string() }; - let mut headers = headers - .iter() - .map(|(h, v)| { - ( - h.as_str().to_lowercase(), - v.to_str() - .expect("request::signature: invalid header error"), - ) - }) - .collect::>(); + let mut headers_vec = Vec::with_capacity(headers.len()); + for (h, v) in headers.iter() { + let v = v.to_str(); + if v.is_err() { + warn!("invalid header error: {:?}", v.unwrap_err()); + return Err(Error()); + } + headers_vec.push((h.as_str().to_lowercase(), v.expect("Unreachable"))); + } let request_target = format!("{} {}", method.to_lowercase(), origin_form); - headers.push(("(request-target)".to_string(), &request_target)); + headers_vec.push(("(request-target)".to_string(), &request_target)); - let signed_string = headers + let signed_string = headers_vec .iter() .map(|(h, v)| format!("{}: {}", h, v)) .collect::>() .join("\n"); - let signed_headers = headers + let signed_headers = headers_vec .iter() .map(|(h, _)| h.as_ref()) .collect::>() From 540b9af87389cbff13d4701e0c0564b8c25dfa88 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 4 Feb 2021 22:14:25 +0900 Subject: [PATCH 5/6] Add changelog about (request-target) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c220aa80..74de3970 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - More translation languages (#862) - Proxy support (#829) - Riker a actor system library (#870) +- (request-target) and Host header in HTTP Signature (#872) ### Changed From 1b9aeae53c82bc300d20eb2ebc7d8995630d7dc4 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 5 Feb 2021 10:23:54 +0900 Subject: [PATCH 6/6] Add test for signagure() --- plume-common/src/activity_pub/request.rs | 58 ++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/plume-common/src/activity_pub/request.rs b/plume-common/src/activity_pub/request.rs index 04a07c83..73e6d6ee 100644 --- a/plume-common/src/activity_pub/request.rs +++ b/plume-common/src/activity_pub/request.rs @@ -163,3 +163,61 @@ pub fn signature( signature = sign )).map_err(|_| Error()) } + +#[cfg(test)] +mod tests { + use super::{signature, Error}; + use crate::activity_pub::sign::{gen_keypair, Signer}; + use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa}; + use reqwest::header::HeaderMap; + + struct MySigner { + public_key: String, + private_key: String, + } + + impl MySigner { + fn new() -> Self { + let (pub_key, priv_key) = gen_keypair(); + Self { + public_key: String::from_utf8(pub_key).unwrap(), + private_key: String::from_utf8(priv_key).unwrap(), + } + } + } + + impl Signer for MySigner { + type Error = Error; + + fn get_key_id(&self) -> String { + "mysigner".into() + } + + fn sign(&self, to_sign: &str) -> Result, Self::Error> { + let key = PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.as_ref()).unwrap()) + .unwrap(); + let mut signer = openssl::sign::Signer::new(MessageDigest::sha256(), &key).unwrap(); + signer.update(to_sign.as_bytes()).unwrap(); + signer.sign_to_vec().map_err(|_| Error()) + } + + fn verify(&self, data: &str, signature: &[u8]) -> Result { + let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref()).unwrap()) + .unwrap(); + let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap(); + verifier.update(data.as_bytes()).unwrap(); + verifier.verify(&signature).map_err(|_| Error()) + } + } + + #[test] + fn test_signature_request_target() { + let signer = MySigner::new(); + let headers = HeaderMap::new(); + let result = signature(&signer, &headers, ("post", "/inbox", None)).unwrap(); + let fields: Vec<&str> = result.to_str().unwrap().split(",").collect(); + assert_eq!(r#"headers="(request-target)""#, fields[2]); + let sign = &fields[3][11..(fields[3].len() - 1)]; + assert!(signer.verify("post /inbox", sign.as_bytes()).is_ok()); + } +}