Require HTTP signatures to have creation date

This commit is contained in:
silverpill 2022-10-28 12:37:10 +00:00
parent 2e7403ef14
commit 3bbf902b28
2 changed files with 29 additions and 17 deletions

View file

@ -4,7 +4,8 @@ use rsa::RsaPrivateKey;
use crate::utils::crypto::{sign_message, get_message_digest};
pub const SIGNATURE_ALGORITHM: &str = "rsa-sha256";
const HTTP_SIGNATURE_ALGORITHM: &str = "rsa-sha256";
const HTTP_SIGNATURE_DATE_FORMAT: &str = "%a, %d %b %Y %T GMT";
pub struct HttpSignatureHeaders {
pub host: String,
@ -40,7 +41,7 @@ pub fn create_http_signature(
let host = request_url_object.host_str()
.ok_or(url::ParseError::EmptyHost)?
.to_string();
let date = Utc::now().format("%a, %d %b %Y %T GMT").to_string();
let date = Utc::now().format(HTTP_SIGNATURE_DATE_FORMAT).to_string();
let maybe_digest = if request_body.is_empty() {
None
} else {
@ -72,7 +73,7 @@ pub fn create_http_signature(
let signature_header = format!(
r#"keyId="{}",algorithm="{}",headers="{}",signature="{}""#,
signer_key_id,
SIGNATURE_ALGORITHM,
HTTP_SIGNATURE_ALGORITHM,
headers_parameter,
signature_parameter,
);

View file

@ -1,7 +1,7 @@
use std::collections::HashMap;
use actix_web::http::{Method, Uri, header::HeaderMap};
use chrono::{DateTime, TimeZone, Utc};
use chrono::{DateTime, Duration, TimeZone, Utc};
use regex::Regex;
use rsa::RsaPublicKey;
@ -28,7 +28,7 @@ pub struct HttpSignatureData {
pub key_id: String,
pub message: String, // reconstructed message
pub signature: String, // base64-encoded signature
pub created_at: Option<DateTime<Utc>>,
pub created_at: DateTime<Utc>,
}
const SIGNATURE_PARAMETER_RE: &str = r#"^(?P<key>[a-zA-Z]+)="(?P<value>.+)"$"#;
@ -62,13 +62,18 @@ pub fn parse_http_signature(
let signature = signature_parameters.get("signature")
.ok_or(VerificationError::ParseError("signature is missing"))?
.to_owned();
let maybe_created_at = if let Some(created_at) = signature_parameters.get("created") {
created_at.parse().ok().map(|ts| Utc.timestamp(ts, 0))
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 {
request_headers.get("date")
.and_then(|header| header.to_str().ok())
.and_then(|date| DateTime::parse_from_rfc2822(date).ok())
.map(|datetime| datetime.with_timezone(&Utc))
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 mut message_parts = vec![];
@ -102,7 +107,7 @@ pub fn parse_http_signature(
key_id,
message,
signature,
created_at: maybe_created_at,
created_at,
};
Ok(signature_data)
}
@ -111,8 +116,9 @@ pub fn verify_http_signature(
signature_data: &HttpSignatureData,
signer_key: &RsaPublicKey,
) -> Result<(), VerificationError> {
if signature_data.created_at.is_none() {
log::warn!("signature creation time is missing");
let expires_at = signature_data.created_at + Duration::hours(12);
if expires_at < Utc::now() {
log::warn!("signature has expired");
};
let is_valid_signature = verify_signature(
signer_key,
@ -140,14 +146,19 @@ mod tests {
fn test_parse_signature() {
let request_method = Method::POST;
let request_uri = "/user/123/inbox".parse::<Uri>().unwrap();
let date = "20 Oct 2022 20:00:00 GMT";
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(),
);
let signature_header = concat!(
r#"keyId="https://myserver.org/actor#main-key","#,
r#"headers="(request-target) host","#,
r#"headers="(request-target) host date","#,
r#"signature="test""#,
);
request_headers.insert(
@ -163,10 +174,10 @@ mod tests {
assert_eq!(signature_data.key_id, "https://myserver.org/actor#main-key");
assert_eq!(
signature_data.message,
"(request-target): post /user/123/inbox\nhost: example.com",
"(request-target): post /user/123/inbox\nhost: example.com\ndate: 20 Oct 2022 20:00:00 GMT",
);
assert_eq!(signature_data.signature, "test");
assert_eq!(signature_data.created_at.is_some(), false);
assert!(signature_data.created_at < Utc::now());
}
#[test]