2021-11-17 21:35:17 +00:00
|
|
|
use actix_web::http::Method;
|
2021-04-09 00:22:17 +00:00
|
|
|
use chrono::Utc;
|
|
|
|
use rsa::RsaPrivateKey;
|
|
|
|
|
|
|
|
use crate::utils::crypto::{sign_message, get_message_digest};
|
|
|
|
|
2022-09-12 21:02:33 +00:00
|
|
|
pub const SIGNATURE_ALGORITHM: &str = "rsa-sha256";
|
|
|
|
|
2021-04-09 00:22:17 +00:00
|
|
|
pub struct SignatureHeaders {
|
|
|
|
pub host: String,
|
|
|
|
pub date: String,
|
2021-11-17 21:35:17 +00:00
|
|
|
pub digest: Option<String>,
|
2021-04-09 00:22:17 +00:00
|
|
|
pub signature: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(thiserror::Error, Debug)]
|
|
|
|
pub enum SignatureError {
|
|
|
|
#[error("invalid request url")]
|
|
|
|
UrlError,
|
|
|
|
|
|
|
|
#[error("signature error")]
|
|
|
|
SignatureError(#[from] rsa::errors::Error),
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Creates HTTP signature according to the old HTTP Signatures Spec:
|
|
|
|
/// https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures.
|
|
|
|
pub fn create_http_signature(
|
2021-11-17 21:35:17 +00:00
|
|
|
request_method: Method,
|
2021-04-09 00:22:17 +00:00
|
|
|
request_url: &str,
|
|
|
|
request_body: &str,
|
2021-10-30 22:35:18 +00:00
|
|
|
actor_key: &RsaPrivateKey,
|
|
|
|
actor_key_id: &str,
|
2021-04-09 00:22:17 +00:00
|
|
|
) -> Result<SignatureHeaders, SignatureError> {
|
|
|
|
let request_url_object = url::Url::parse(request_url)
|
|
|
|
.map_err(|_| SignatureError::UrlError)?;
|
2021-11-17 21:35:17 +00:00
|
|
|
let request_target = format!(
|
|
|
|
"{} {}",
|
|
|
|
request_method.as_str().to_lowercase(),
|
2021-04-09 00:22:17 +00:00
|
|
|
request_url_object.path(),
|
|
|
|
);
|
2021-11-17 21:35:17 +00:00
|
|
|
let host = request_url_object.host_str()
|
|
|
|
.ok_or(SignatureError::UrlError)?
|
|
|
|
.to_string();
|
|
|
|
let date = Utc::now().format("%a, %d %b %Y %T GMT").to_string();
|
|
|
|
let digest = if request_body.is_empty() {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some(get_message_digest(request_body))
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut headers = vec![
|
|
|
|
("(request-target)", &request_target),
|
|
|
|
("host", &host),
|
|
|
|
("date", &date),
|
|
|
|
];
|
|
|
|
if let Some(ref digest) = digest {
|
|
|
|
headers.push(("digest", digest));
|
|
|
|
};
|
|
|
|
|
|
|
|
let message = headers.iter()
|
|
|
|
.map(|(name, value)| format!("{}: {}", name, value))
|
|
|
|
.collect::<Vec<String>>().join("\n");
|
|
|
|
let headers_parameter = headers.iter()
|
|
|
|
.map(|(name, _)| name.to_string())
|
|
|
|
.collect::<Vec<String>>().join(" ");
|
2021-10-30 22:35:18 +00:00
|
|
|
let signature_parameter = sign_message(actor_key, &message)?;
|
2021-04-09 00:22:17 +00:00
|
|
|
let signature_header = format!(
|
2022-09-12 21:02:33 +00:00
|
|
|
r#"keyId="{}",algorithm="{}",headers="{}",signature="{}""#,
|
2021-04-09 00:22:17 +00:00
|
|
|
actor_key_id,
|
2022-09-12 21:02:33 +00:00
|
|
|
SIGNATURE_ALGORITHM,
|
2021-11-17 21:35:17 +00:00
|
|
|
headers_parameter,
|
2021-04-09 00:22:17 +00:00
|
|
|
signature_parameter,
|
|
|
|
);
|
|
|
|
let headers = SignatureHeaders {
|
2021-11-17 21:35:17 +00:00
|
|
|
host,
|
2021-04-09 00:22:17 +00:00
|
|
|
date,
|
|
|
|
digest,
|
|
|
|
signature: signature_header,
|
|
|
|
};
|
|
|
|
Ok(headers)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2022-04-26 22:35:39 +00:00
|
|
|
use crate::utils::crypto::generate_weak_private_key;
|
2021-04-09 00:22:17 +00:00
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
2021-11-17 21:35:17 +00:00
|
|
|
fn test_create_signature_get() {
|
|
|
|
let request_url = "https://example.org/inbox";
|
2022-04-26 22:35:39 +00:00
|
|
|
let actor_key = generate_weak_private_key().unwrap();
|
2021-11-17 21:35:17 +00:00
|
|
|
let actor_key_id = "https://myserver.org/actor#main-key";
|
|
|
|
|
|
|
|
let headers = create_http_signature(
|
|
|
|
Method::GET,
|
|
|
|
request_url,
|
|
|
|
"",
|
|
|
|
&actor_key,
|
|
|
|
actor_key_id,
|
|
|
|
).unwrap();
|
|
|
|
|
|
|
|
assert_eq!(headers.host, "example.org");
|
|
|
|
assert_eq!(headers.digest, None);
|
|
|
|
let expected_signature_header = concat!(
|
|
|
|
r#"keyId="https://myserver.org/actor#main-key","#,
|
2021-12-27 15:28:05 +00:00
|
|
|
r#"algorithm="rsa-sha256","#,
|
2021-11-17 21:35:17 +00:00
|
|
|
r#"headers="(request-target) host date","#,
|
|
|
|
r#"signature=""#,
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
headers.signature.starts_with(expected_signature_header),
|
|
|
|
true,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_create_signature_post() {
|
2021-04-09 00:22:17 +00:00
|
|
|
let request_url = "https://example.org/inbox";
|
|
|
|
let request_body = "{}";
|
2022-04-26 22:35:39 +00:00
|
|
|
let actor_key = generate_weak_private_key().unwrap();
|
2021-04-09 00:22:17 +00:00
|
|
|
let actor_key_id = "https://myserver.org/actor#main-key";
|
|
|
|
|
|
|
|
let result = create_http_signature(
|
2021-11-17 21:35:17 +00:00
|
|
|
Method::POST,
|
2021-04-09 00:22:17 +00:00
|
|
|
request_url,
|
|
|
|
request_body,
|
2021-10-30 22:35:18 +00:00
|
|
|
&actor_key,
|
|
|
|
actor_key_id,
|
2021-04-09 00:22:17 +00:00
|
|
|
);
|
|
|
|
assert_eq!(result.is_ok(), true);
|
|
|
|
|
|
|
|
let headers = result.unwrap();
|
|
|
|
assert_eq!(headers.host, "example.org");
|
|
|
|
assert_eq!(
|
2021-11-17 21:35:17 +00:00
|
|
|
headers.digest.unwrap(),
|
2021-04-09 00:22:17 +00:00
|
|
|
"SHA-256=RBNvo1WzZ4oRRq0W9+hknpT7T8If536DEMBg9hyq/4o=",
|
|
|
|
);
|
|
|
|
let expected_signature_header = concat!(
|
|
|
|
r#"keyId="https://myserver.org/actor#main-key","#,
|
2021-12-27 15:28:05 +00:00
|
|
|
r#"algorithm="rsa-sha256","#,
|
2021-04-09 00:22:17 +00:00
|
|
|
r#"headers="(request-target) host date digest","#,
|
|
|
|
r#"signature=""#,
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
headers.signature.starts_with(expected_signature_header),
|
|
|
|
true,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|