2021-12-27 15:28:05 +00:00
|
|
|
use std::collections::HashMap;
|
|
|
|
|
2021-04-09 00:22:17 +00:00
|
|
|
use actix_web::{
|
|
|
|
HttpRequest,
|
2022-04-08 18:52:13 +00:00
|
|
|
http::{Method, Uri, header::HeaderMap},
|
2021-04-09 00:22:17 +00:00
|
|
|
};
|
|
|
|
use regex::Regex;
|
2022-02-23 23:07:51 +00:00
|
|
|
use tokio_postgres::GenericClient;
|
2021-04-09 00:22:17 +00:00
|
|
|
|
2021-12-28 18:34:13 +00:00
|
|
|
use crate::activitypub::fetcher::helpers::{
|
|
|
|
get_or_import_profile_by_actor_id,
|
|
|
|
ImportError,
|
|
|
|
};
|
2021-04-09 00:22:17 +00:00
|
|
|
use crate::config::Config;
|
|
|
|
use crate::errors::DatabaseError;
|
2022-05-30 19:51:32 +00:00
|
|
|
use crate::models::profiles::queries::get_profile_by_actor_id;
|
2022-02-23 23:07:51 +00:00
|
|
|
use crate::models::profiles::types::DbActorProfile;
|
2021-04-09 00:22:17 +00:00
|
|
|
use crate::utils::crypto::{deserialize_public_key, verify_signature};
|
|
|
|
|
|
|
|
#[derive(thiserror::Error, Debug)]
|
|
|
|
pub enum VerificationError {
|
|
|
|
#[error("{0}")]
|
|
|
|
HeaderError(&'static str),
|
|
|
|
|
|
|
|
#[error("{0}")]
|
|
|
|
ParseError(&'static str),
|
|
|
|
|
|
|
|
#[error("invalid key ID")]
|
|
|
|
UrlError(#[from] url::ParseError),
|
|
|
|
|
2021-11-22 23:23:50 +00:00
|
|
|
#[error("database error")]
|
|
|
|
DatabaseError(#[from] DatabaseError),
|
|
|
|
|
|
|
|
#[error("{0}")]
|
|
|
|
ActorError(String),
|
2021-04-09 00:22:17 +00:00
|
|
|
|
2022-02-13 13:10:09 +00:00
|
|
|
#[error("invalid public key")]
|
|
|
|
InvalidPublicKey(#[from] rsa::pkcs8::Error),
|
2021-04-09 00:22:17 +00:00
|
|
|
|
2022-02-11 13:26:25 +00:00
|
|
|
#[error("invalid encoding")]
|
|
|
|
InvalidEncoding(#[from] base64::DecodeError),
|
|
|
|
|
2021-04-09 00:22:17 +00:00
|
|
|
#[error("invalid signature")]
|
|
|
|
InvalidSignature,
|
|
|
|
}
|
|
|
|
|
2022-02-23 23:07:51 +00:00
|
|
|
struct SignatureData {
|
2022-05-02 23:29:04 +00:00
|
|
|
pub key_id: String,
|
2021-04-09 00:22:17 +00:00
|
|
|
pub message: String, // reconstructed message
|
|
|
|
pub signature: String, // base64-encoded signature
|
|
|
|
}
|
|
|
|
|
2021-12-27 15:28:05 +00:00
|
|
|
const SIGNATURE_PARAMETER_RE: &str = r#"^(?P<key>[a-zA-Z]+)="(?P<value>.+)"$"#;
|
|
|
|
|
2021-04-09 00:22:17 +00:00
|
|
|
fn parse_http_signature(
|
|
|
|
request_method: &Method,
|
|
|
|
request_uri: &Uri,
|
|
|
|
request_headers: &HeaderMap,
|
|
|
|
) -> Result<SignatureData, VerificationError> {
|
|
|
|
let signature_header = request_headers.get("signature")
|
|
|
|
.ok_or(VerificationError::HeaderError("missing signature header"))?
|
|
|
|
.to_str()
|
|
|
|
.map_err(|_| VerificationError::HeaderError("invalid signature header"))?;
|
2021-12-27 15:28:05 +00:00
|
|
|
|
|
|
|
let signature_parameter_re = Regex::new(SIGNATURE_PARAMETER_RE).unwrap();
|
|
|
|
let mut signature_parameters = HashMap::new();
|
2021-12-28 18:34:13 +00:00
|
|
|
for item in signature_header.split(',') {
|
2021-12-27 15:28:05 +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);
|
|
|
|
};
|
|
|
|
|
|
|
|
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();
|
2021-12-27 15:28:05 +00:00
|
|
|
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();
|
2021-12-27 15:28:05 +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();
|
|
|
|
|
2022-09-12 20:08:13 +00:00
|
|
|
let mut message_parts = vec![];
|
2021-11-13 17:37:31 +00:00
|
|
|
for header in headers_parameter.split(' ') {
|
2022-09-12 20:08:13 +00:00
|
|
|
let message_part = if header == "(request-target)" {
|
|
|
|
format!(
|
|
|
|
"(request-target): {} {}",
|
|
|
|
request_method.as_str().to_lowercase(),
|
|
|
|
request_uri,
|
|
|
|
)
|
|
|
|
} 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
|
|
|
|
|
|
|
let signature_data = SignatureData {
|
2022-05-02 23:29:04 +00:00
|
|
|
key_id,
|
2021-04-09 00:22:17 +00:00
|
|
|
message,
|
|
|
|
signature,
|
|
|
|
};
|
|
|
|
Ok(signature_data)
|
|
|
|
}
|
|
|
|
|
2022-05-02 23:29:04 +00:00
|
|
|
fn key_id_to_actor_id(key_id: &str) -> Result<String, url::ParseError> {
|
|
|
|
let key_url = url::Url::parse(key_id)?;
|
|
|
|
// Strip #main-key (works with most AP servers)
|
|
|
|
let actor_id = &key_url[..url::Position::BeforeQuery];
|
|
|
|
// GoToSocial compat
|
|
|
|
let actor_id = actor_id.trim_end_matches("/main-key");
|
|
|
|
Ok(actor_id.to_string())
|
|
|
|
}
|
|
|
|
|
2022-05-30 19:51:32 +00:00
|
|
|
/// Verifies HTTP signature and returns signer
|
2021-04-09 00:22:17 +00:00
|
|
|
pub async fn verify_http_signature(
|
|
|
|
config: &Config,
|
2022-02-23 23:07:51 +00:00
|
|
|
db_client: &impl GenericClient,
|
2021-04-09 00:22:17 +00:00
|
|
|
request: &HttpRequest,
|
2022-05-30 19:51:32 +00:00
|
|
|
no_fetch: bool,
|
2022-02-23 23:07:51 +00:00
|
|
|
) -> Result<DbActorProfile, VerificationError> {
|
2021-04-09 00:22:17 +00:00
|
|
|
let signature_data = parse_http_signature(
|
|
|
|
request.method(),
|
|
|
|
request.uri(),
|
|
|
|
request.headers(),
|
|
|
|
)?;
|
|
|
|
|
2022-05-02 23:29:04 +00:00
|
|
|
let actor_id = key_id_to_actor_id(&signature_data.key_id)?;
|
2022-05-30 19:51:32 +00:00
|
|
|
let actor_profile = if no_fetch {
|
|
|
|
get_profile_by_actor_id(db_client, &actor_id).await?
|
|
|
|
} else {
|
|
|
|
match get_or_import_profile_by_actor_id(
|
|
|
|
db_client,
|
|
|
|
&config.instance(),
|
|
|
|
&config.media_dir(),
|
|
|
|
&actor_id,
|
|
|
|
).await {
|
|
|
|
Ok(profile) => profile,
|
|
|
|
Err(ImportError::DatabaseError(error)) => return Err(error.into()),
|
|
|
|
Err(other_error) => {
|
|
|
|
return Err(VerificationError::ActorError(other_error.to_string()));
|
|
|
|
},
|
|
|
|
}
|
2021-04-09 00:22:17 +00:00
|
|
|
};
|
2021-12-31 15:29:44 +00:00
|
|
|
let actor = actor_profile.actor_json.as_ref()
|
2021-11-22 23:23:50 +00:00
|
|
|
.ok_or(VerificationError::ActorError("invalid profile".to_string()))?;
|
2021-04-09 00:22:17 +00:00
|
|
|
|
|
|
|
let public_key = deserialize_public_key(&actor.public_key.public_key_pem)?;
|
|
|
|
let is_valid_signature = verify_signature(
|
|
|
|
&public_key,
|
|
|
|
&signature_data.message,
|
|
|
|
&signature_data.signature,
|
2022-02-11 13:26:25 +00:00
|
|
|
)?;
|
2021-04-09 00:22:17 +00:00
|
|
|
if !is_valid_signature {
|
|
|
|
return Err(VerificationError::InvalidSignature);
|
2022-02-11 13:26:25 +00:00
|
|
|
};
|
2022-02-23 23:07:51 +00:00
|
|
|
Ok(actor_profile)
|
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,
|
|
|
|
};
|
2021-04-09 00:22:17 +00:00
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_signature() {
|
2021-11-17 21:35:17 +00:00
|
|
|
let request_method = Method::POST;
|
2021-04-09 00:22:17 +00:00
|
|
|
let request_uri = "/user/123/inbox".parse::<Uri>().unwrap();
|
|
|
|
let mut request_headers = HeaderMap::new();
|
|
|
|
request_headers.insert(
|
|
|
|
header::HOST,
|
|
|
|
HeaderValue::from_static("example.com"),
|
|
|
|
);
|
|
|
|
let signature_header = concat!(
|
|
|
|
r#"keyId="https://myserver.org/actor#main-key","#,
|
|
|
|
r#"headers="(request-target) host","#,
|
|
|
|
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();
|
2022-05-02 23:29:04 +00:00
|
|
|
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",
|
|
|
|
);
|
|
|
|
assert_eq!(signature_data.signature, "test");
|
|
|
|
}
|
2022-05-02 23:29:04 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_key_id_to_actor_id() {
|
|
|
|
let key_id = "https://myserver.org/actor#main-key";
|
|
|
|
let actor_id = key_id_to_actor_id(key_id).unwrap();
|
|
|
|
assert_eq!(actor_id, "https://myserver.org/actor");
|
|
|
|
|
|
|
|
let key_id = "https://myserver.org/actor/main-key";
|
|
|
|
let actor_id = key_id_to_actor_id(key_id).unwrap();
|
|
|
|
assert_eq!(actor_id, "https://myserver.org/actor");
|
|
|
|
}
|
2021-04-09 00:22:17 +00:00
|
|
|
}
|