fedimovies/src/http_signatures/verify.rs

230 lines
8 KiB
Rust
Raw Normal View History

use std::collections::HashMap;
use actix_web::http::{Method, Uri, header::HeaderMap};
use chrono::{DateTime, Duration, TimeZone, Utc};
2021-04-09 00:22:17 +00:00
use regex::Regex;
2022-10-23 16:04:02 +00:00
use rsa::RsaPublicKey;
2021-04-09 00:22:17 +00:00
use crate::utils::crypto_rsa::verify_rsa_signature;
2021-04-09 00:22:17 +00:00
#[derive(thiserror::Error, Debug)]
pub enum HttpSignatureVerificationError {
2021-04-09 00:22:17 +00:00
#[error("{0}")]
HeaderError(&'static str),
#[error("{0}")]
ParseError(&'static str),
#[error("invalid encoding")]
InvalidEncoding(#[from] base64::DecodeError),
2021-04-09 00:22:17 +00:00
#[error("invalid signature")]
InvalidSignature,
}
type VerificationError = HttpSignatureVerificationError;
pub struct HttpSignatureData {
pub key_id: String,
2021-04-09 00:22:17 +00:00
pub message: String, // reconstructed message
pub signature: String, // base64-encoded signature
pub created_at: DateTime<Utc>,
2021-04-09 00:22:17 +00:00
}
const SIGNATURE_PARAMETER_RE: &str = r#"^(?P<key>[a-zA-Z]+)="(?P<value>.+)"$"#;
pub fn parse_http_signature(
2021-04-09 00:22:17 +00:00
request_method: &Method,
request_uri: &Uri,
request_headers: &HeaderMap,
2022-10-23 16:04:02 +00:00
) -> Result<HttpSignatureData, VerificationError> {
2021-04-09 00:22:17 +00:00
let signature_header = request_headers.get("signature")
.ok_or(VerificationError::HeaderError("missing signature header"))?
.to_str()
.map_err(|_| VerificationError::HeaderError("invalid signature header"))?;
let signature_parameter_re = Regex::new(SIGNATURE_PARAMETER_RE).unwrap();
let mut signature_parameters = HashMap::new();
for item in signature_header.split(',') {
let caps = signature_parameter_re.captures(item)
.ok_or(VerificationError::HeaderError("invalid signature header"))?;
let key = caps["key"].to_string();
let value = caps["value"].to_string();
signature_parameters.insert(key, value);
};
let key_id = signature_parameters.get("keyId")
2021-04-09 00:22:17 +00:00
.ok_or(VerificationError::ParseError("keyId parameter is missing"))?
.to_owned();
let headers_parameter = signature_parameters.get("headers")
2021-04-09 00:22:17 +00:00
.ok_or(VerificationError::ParseError("headers parameter is missing"))?
.to_owned();
let signature = signature_parameters.get("signature")
2021-04-09 00:22:17 +00:00
.ok_or(VerificationError::ParseError("signature is missing"))?
.to_owned();
let created_at = if let Some(created_at) = signature_parameters.get("created") {
let create_at_timestamp = created_at.parse()
.map_err(|_| VerificationError::ParseError("invalid timestamp"))?;
Utc.timestamp(create_at_timestamp, 0)
} else {
let date_str = request_headers.get("date")
.ok_or(VerificationError::ParseError("missing date"))?
.to_str()
.map_err(|_| VerificationError::ParseError("invalid date header"))?;
let date = DateTime::parse_from_rfc2822(date_str)
.map_err(|_| VerificationError::ParseError("invalid date"))?;
date.with_timezone(&Utc)
};
2021-04-09 00:22:17 +00:00
let mut message_parts = vec![];
2021-11-13 17:37:31 +00:00
for header in headers_parameter.split(' ') {
let message_part = if header == "(request-target)" {
format!(
"(request-target): {} {}",
request_method.as_str().to_lowercase(),
request_uri.path(),
)
} else if header == "(created)" {
let created = signature_parameters.get("created")
.ok_or(VerificationError::ParseError("created parameter is missing"))?;
format!("(created): {}", created)
} else if header == "(expires)" {
let expires = signature_parameters.get("expires")
.ok_or(VerificationError::ParseError("expires parameter is missing"))?;
format!("(expires): {}", expires)
} else {
let header_value = request_headers.get(header)
.ok_or(VerificationError::HeaderError("missing header"))?
.to_str()
.map_err(|_| VerificationError::HeaderError("invalid header value"))?;
format!("{}: {}", header, header_value)
};
message_parts.push(message_part);
};
let message = message_parts.join("\n");
2021-04-09 00:22:17 +00:00
2022-10-23 16:04:02 +00:00
let signature_data = HttpSignatureData {
key_id,
2021-04-09 00:22:17 +00:00
message,
signature,
created_at,
2021-04-09 00:22:17 +00:00
};
Ok(signature_data)
}
pub fn verify_http_signature(
2022-10-23 16:04:02 +00:00
signature_data: &HttpSignatureData,
signer_key: &RsaPublicKey,
) -> Result<(), VerificationError> {
let expires_at = signature_data.created_at + Duration::hours(12);
if expires_at < Utc::now() {
log::warn!("signature has expired");
2022-10-23 16:04:02 +00:00
};
let is_valid_signature = verify_rsa_signature(
2022-10-23 16:04:02 +00:00
signer_key,
&signature_data.message,
&signature_data.signature,
)?;
if !is_valid_signature {
return Err(VerificationError::InvalidSignature);
};
Ok(())
}
2021-04-09 00:22:17 +00:00
#[cfg(test)]
mod tests {
2022-04-08 18:52:13 +00:00
use actix_web::http::{
header,
header::{HeaderMap, HeaderName, HeaderValue},
Uri,
};
use crate::http_signatures::create::create_http_signature;
use crate::utils::crypto_rsa::generate_weak_rsa_key;
2021-04-09 00:22:17 +00:00
use super::*;
#[test]
fn test_parse_signature() {
let request_method = Method::POST;
2021-04-09 00:22:17 +00:00
let request_uri = "/user/123/inbox".parse::<Uri>().unwrap();
let date = "20 Oct 2022 20:00:00 GMT";
2021-04-09 00:22:17 +00:00
let mut request_headers = HeaderMap::new();
request_headers.insert(
header::HOST,
HeaderValue::from_static("example.com"),
);
request_headers.insert(
HeaderName::from_static("date"),
HeaderValue::from_str(&date).unwrap(),
);
2021-04-09 00:22:17 +00:00
let signature_header = concat!(
r#"keyId="https://myserver.org/actor#main-key","#,
r#"headers="(request-target) host date","#,
2021-04-09 00:22:17 +00:00
r#"signature="test""#,
);
request_headers.insert(
HeaderName::from_static("signature"),
HeaderValue::from_static(signature_header),
);
let signature_data = parse_http_signature(
&request_method,
&request_uri,
&request_headers,
).unwrap();
assert_eq!(signature_data.key_id, "https://myserver.org/actor#main-key");
2021-04-09 00:22:17 +00:00
assert_eq!(
signature_data.message,
"(request-target): post /user/123/inbox\nhost: example.com\ndate: 20 Oct 2022 20:00:00 GMT",
2021-04-09 00:22:17 +00:00
);
assert_eq!(signature_data.signature, "test");
assert!(signature_data.created_at < Utc::now());
2021-04-09 00:22:17 +00:00
}
#[test]
fn test_create_and_verify_signature() {
let request_method = Method::POST;
let request_url = "https://example.org/inbox";
let request_body = "{}";
let signer_key = generate_weak_rsa_key().unwrap();
let signer_key_id = "https://myserver.org/actor#main-key";
let signed_headers = create_http_signature(
request_method.clone(),
request_url,
request_body,
&signer_key,
signer_key_id,
).unwrap();
let request_url = request_url.parse::<Uri>().unwrap();
let mut request_headers = HeaderMap::new();
request_headers.append(
HeaderName::from_static("host"),
HeaderValue::from_str(&signed_headers.host).unwrap(),
);
request_headers.append(
HeaderName::from_static("signature"),
HeaderValue::from_str(&signed_headers.signature).unwrap(),
);
request_headers.append(
HeaderName::from_static("date"),
HeaderValue::from_str(&signed_headers.date).unwrap(),
);
request_headers.append(
HeaderName::from_static("digest"),
HeaderValue::from_str(&signed_headers.digest.unwrap()).unwrap(),
);
let signature_data = parse_http_signature(
&request_method,
&request_url,
&request_headers,
).unwrap();
let signer_public_key = RsaPublicKey::from(signer_key);
let result = verify_http_signature(
&signature_data,
&signer_public_key,
);
assert_eq!(result.is_ok(), true);
}
2021-04-09 00:22:17 +00:00
}