fedimovies/src/http_signatures/verify.rs

251 lines
8.7 KiB
Rust
Raw Normal View History

use std::collections::HashMap;
2023-04-24 15:35:32 +00:00
use actix_web::http::{header::HeaderMap, Method, Uri};
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
2023-04-25 13:49:35 +00:00
use fedimovies_utils::crypto_rsa::verify_rsa_sha256_signature;
2021-04-09 00:22:17 +00:00
const SIGNATURE_PARAMETER_RE: &str = r#"^(?P<key>[a-zA-Z]+)="(?P<value>.+)"$"#;
const SIGNATURE_EXPIRES_IN: i64 = 12; // 12 hours
2021-04-09 00:22:17 +00:00
#[derive(thiserror::Error, Debug)]
pub enum HttpSignatureVerificationError {
2022-12-07 22:10:58 +00:00
#[error("missing signature header")]
NoSignature,
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,
2023-04-24 15:35:32 +00:00
pub message: String, // reconstructed message
2021-04-09 00:22:17 +00:00
pub signature: String, // base64-encoded signature
pub expires_at: DateTime<Utc>,
2021-04-09 00:22:17 +00:00
}
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> {
2023-04-24 15:35:32 +00:00
let signature_header = request_headers
.get("signature")
2022-12-07 22:10:58 +00:00
.ok_or(VerificationError::NoSignature)?
2021-04-09 00:22:17 +00:00
.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(',') {
2023-04-24 15:35:32 +00:00
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);
2023-04-24 15:35:32 +00:00
}
2023-04-24 15:35:32 +00:00
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();
2023-04-24 15:35:32 +00:00
let headers_parameter = signature_parameters
.get("headers")
.ok_or(VerificationError::ParseError(
"headers parameter is missing",
))?
2021-04-09 00:22:17 +00:00
.to_owned();
2023-04-24 15:35:32 +00:00
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") {
2023-04-24 15:35:32 +00:00
let create_at_timestamp = created_at
.parse()
.map_err(|_| VerificationError::ParseError("invalid timestamp"))?;
2023-04-24 15:35:32 +00:00
Utc.timestamp_opt(create_at_timestamp, 0)
.single()
.ok_or(VerificationError::ParseError("invalid timestamp"))?
} else {
2023-04-24 15:35:32 +00:00
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)
};
let expires_at = if let Some(expires_at) = signature_parameters.get("expires") {
2023-04-24 15:35:32 +00:00
let expires_at_timestamp = expires_at
.parse()
.map_err(|_| VerificationError::ParseError("invalid timestamp"))?;
2023-04-24 15:35:32 +00:00
Utc.timestamp_opt(expires_at_timestamp, 0)
.single()
.ok_or(VerificationError::ParseError("invalid timestamp"))?
} else {
created_at + Duration::hours(SIGNATURE_EXPIRES_IN)
};
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)" {
2023-04-24 15:35:32 +00:00
let created =
signature_parameters
.get("created")
.ok_or(VerificationError::ParseError(
"created parameter is missing",
))?;
format!("(created): {}", created)
} else if header == "(expires)" {
2023-04-24 15:35:32 +00:00
let expires =
signature_parameters
.get("expires")
.ok_or(VerificationError::ParseError(
"expires parameter is missing",
))?;
format!("(expires): {}", expires)
} else {
2023-04-24 15:35:32 +00:00
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);
2023-04-24 15:35:32 +00:00
}
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,
expires_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> {
if signature_data.expires_at < Utc::now() {
log::warn!("signature has expired");
2022-10-23 16:04:02 +00:00
};
let signature = base64::decode(&signature_data.signature)?;
2023-04-24 15:35:32 +00:00
let is_valid_signature =
verify_rsa_sha256_signature(signer_key, &signature_data.message, &signature);
2022-10-23 16:04:02 +00:00
if !is_valid_signature {
return Err(VerificationError::InvalidSignature);
};
Ok(())
}
2021-04-09 00:22:17 +00:00
#[cfg(test)]
mod tests {
2023-04-24 15:35:32 +00:00
use super::*;
use crate::http_signatures::create::create_http_signature;
2022-04-08 18:52:13 +00:00
use actix_web::http::{
header,
header::{HeaderMap, HeaderName, HeaderValue},
Uri,
};
2023-04-25 13:49:35 +00:00
use fedimovies_utils::crypto_rsa::generate_weak_rsa_key;
2021-04-09 00:22:17 +00:00
#[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();
2023-04-24 15:35:32 +00:00
request_headers.insert(header::HOST, HeaderValue::from_static("example.com"));
request_headers.insert(
HeaderName::from_static("date"),
2023-04-27 22:01:24 +00:00
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),
);
2023-04-24 15:35:32 +00:00
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.expires_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,
2023-04-24 15:35:32 +00:00
)
.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(),
);
2023-04-24 15:35:32 +00:00
let signature_data =
parse_http_signature(&request_method, &request_url, &request_headers).unwrap();
let signer_public_key = RsaPublicKey::from(signer_key);
2023-04-24 15:35:32 +00:00
let result = verify_http_signature(&signature_data, &signer_public_key);
2023-04-27 22:01:24 +00:00
assert!(result.is_ok());
}
2021-04-09 00:22:17 +00:00
}