Require HTTP signatures to have creation date
This commit is contained in:
parent
2e7403ef14
commit
3bbf902b28
2 changed files with 29 additions and 17 deletions
|
@ -4,7 +4,8 @@ use rsa::RsaPrivateKey;
|
||||||
|
|
||||||
use crate::utils::crypto::{sign_message, get_message_digest};
|
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 struct HttpSignatureHeaders {
|
||||||
pub host: String,
|
pub host: String,
|
||||||
|
@ -40,7 +41,7 @@ pub fn create_http_signature(
|
||||||
let host = request_url_object.host_str()
|
let host = request_url_object.host_str()
|
||||||
.ok_or(url::ParseError::EmptyHost)?
|
.ok_or(url::ParseError::EmptyHost)?
|
||||||
.to_string();
|
.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() {
|
let maybe_digest = if request_body.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
|
@ -72,7 +73,7 @@ pub fn create_http_signature(
|
||||||
let signature_header = format!(
|
let signature_header = format!(
|
||||||
r#"keyId="{}",algorithm="{}",headers="{}",signature="{}""#,
|
r#"keyId="{}",algorithm="{}",headers="{}",signature="{}""#,
|
||||||
signer_key_id,
|
signer_key_id,
|
||||||
SIGNATURE_ALGORITHM,
|
HTTP_SIGNATURE_ALGORITHM,
|
||||||
headers_parameter,
|
headers_parameter,
|
||||||
signature_parameter,
|
signature_parameter,
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use actix_web::http::{Method, Uri, header::HeaderMap};
|
use actix_web::http::{Method, Uri, header::HeaderMap};
|
||||||
use chrono::{DateTime, TimeZone, Utc};
|
use chrono::{DateTime, Duration, TimeZone, Utc};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use rsa::RsaPublicKey;
|
use rsa::RsaPublicKey;
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ pub struct HttpSignatureData {
|
||||||
pub key_id: String,
|
pub key_id: String,
|
||||||
pub message: String, // reconstructed message
|
pub message: String, // reconstructed message
|
||||||
pub signature: String, // base64-encoded signature
|
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>.+)"$"#;
|
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")
|
let signature = signature_parameters.get("signature")
|
||||||
.ok_or(VerificationError::ParseError("signature is missing"))?
|
.ok_or(VerificationError::ParseError("signature is missing"))?
|
||||||
.to_owned();
|
.to_owned();
|
||||||
let maybe_created_at = if let Some(created_at) = signature_parameters.get("created") {
|
let created_at = if let Some(created_at) = signature_parameters.get("created") {
|
||||||
created_at.parse().ok().map(|ts| Utc.timestamp(ts, 0))
|
let create_at_timestamp = created_at.parse()
|
||||||
|
.map_err(|_| VerificationError::ParseError("invalid timestamp"))?;
|
||||||
|
Utc.timestamp(create_at_timestamp, 0)
|
||||||
} else {
|
} else {
|
||||||
request_headers.get("date")
|
let date_str = request_headers.get("date")
|
||||||
.and_then(|header| header.to_str().ok())
|
.ok_or(VerificationError::ParseError("missing date"))?
|
||||||
.and_then(|date| DateTime::parse_from_rfc2822(date).ok())
|
.to_str()
|
||||||
.map(|datetime| datetime.with_timezone(&Utc))
|
.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![];
|
let mut message_parts = vec![];
|
||||||
|
@ -102,7 +107,7 @@ pub fn parse_http_signature(
|
||||||
key_id,
|
key_id,
|
||||||
message,
|
message,
|
||||||
signature,
|
signature,
|
||||||
created_at: maybe_created_at,
|
created_at,
|
||||||
};
|
};
|
||||||
Ok(signature_data)
|
Ok(signature_data)
|
||||||
}
|
}
|
||||||
|
@ -111,8 +116,9 @@ pub fn verify_http_signature(
|
||||||
signature_data: &HttpSignatureData,
|
signature_data: &HttpSignatureData,
|
||||||
signer_key: &RsaPublicKey,
|
signer_key: &RsaPublicKey,
|
||||||
) -> Result<(), VerificationError> {
|
) -> Result<(), VerificationError> {
|
||||||
if signature_data.created_at.is_none() {
|
let expires_at = signature_data.created_at + Duration::hours(12);
|
||||||
log::warn!("signature creation time is missing");
|
if expires_at < Utc::now() {
|
||||||
|
log::warn!("signature has expired");
|
||||||
};
|
};
|
||||||
let is_valid_signature = verify_signature(
|
let is_valid_signature = verify_signature(
|
||||||
signer_key,
|
signer_key,
|
||||||
|
@ -140,14 +146,19 @@ mod tests {
|
||||||
fn test_parse_signature() {
|
fn test_parse_signature() {
|
||||||
let request_method = Method::POST;
|
let request_method = Method::POST;
|
||||||
let request_uri = "/user/123/inbox".parse::<Uri>().unwrap();
|
let request_uri = "/user/123/inbox".parse::<Uri>().unwrap();
|
||||||
|
let date = "20 Oct 2022 20:00:00 GMT";
|
||||||
let mut request_headers = HeaderMap::new();
|
let mut request_headers = HeaderMap::new();
|
||||||
request_headers.insert(
|
request_headers.insert(
|
||||||
header::HOST,
|
header::HOST,
|
||||||
HeaderValue::from_static("example.com"),
|
HeaderValue::from_static("example.com"),
|
||||||
);
|
);
|
||||||
|
request_headers.insert(
|
||||||
|
HeaderName::from_static("date"),
|
||||||
|
HeaderValue::from_str(&date).unwrap(),
|
||||||
|
);
|
||||||
let signature_header = concat!(
|
let signature_header = concat!(
|
||||||
r#"keyId="https://myserver.org/actor#main-key","#,
|
r#"keyId="https://myserver.org/actor#main-key","#,
|
||||||
r#"headers="(request-target) host","#,
|
r#"headers="(request-target) host date","#,
|
||||||
r#"signature="test""#,
|
r#"signature="test""#,
|
||||||
);
|
);
|
||||||
request_headers.insert(
|
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.key_id, "https://myserver.org/actor#main-key");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
signature_data.message,
|
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.signature, "test");
|
||||||
assert_eq!(signature_data.created_at.is_some(), false);
|
assert!(signature_data.created_at < Utc::now());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Reference in a new issue