refactor: update according to PR comment and factorize 'verify_digest'

This commit is contained in:
Paul Delafosse 2022-11-28 19:14:40 +01:00
parent 605d8cc60b
commit d01b55f5d2
12 changed files with 78 additions and 135 deletions

31
Cargo.lock generated
View file

@ -21,7 +21,6 @@ dependencies = [
"env_logger",
"http",
"http-signature-normalization",
"http-signature-normalization-actix",
"http-signature-normalization-reqwest",
"httpdate",
"hyper",
@ -900,26 +899,6 @@ dependencies = [
"httpdate",
]
[[package]]
name = "http-signature-normalization-actix"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86dfd54a1764ad79376b8dbf29e5bf918a463eb5ec66c90cd0388508289af6f0"
dependencies = [
"actix-http",
"actix-rt",
"actix-web",
"base64",
"futures-util",
"http-signature-normalization",
"sha2",
"thiserror",
"tokio",
"tracing",
"tracing-error",
"tracing-futures",
]
[[package]]
name = "http-signature-normalization-reqwest"
version = "0.7.1"
@ -1978,16 +1957,6 @@ dependencies = [
"valuable",
]
[[package]]
name = "tracing-error"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e"
dependencies = [
"tracing",
"tracing-subscriber",
]
[[package]]
name = "tracing-futures"
version = "0.2.5"

View file

@ -34,7 +34,6 @@ http-signature-normalization = "0.6.0"
actix-rt = { version = "2.7.0" }
actix-web = { version = "4.2.1", default-features = false, optional = true }
http-signature-normalization-actix = { version = "0.6.1", default-features = false, features = ["server", "sha-2"], optional = true }
axum = { version = "0.6.0", features = ["json", "headers", "macros", "original-uri"], optional = true }
# Axum
@ -45,7 +44,7 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"], optional = tr
[features]
default = ["actix"]
actix = ["dep:actix-web", "dep:http-signature-normalization-actix"]
actix = ["dep:actix-web"]
axum = [
"dep:axum",
"dep:tower-http",

View file

@ -24,9 +24,7 @@ use activitypub_federation::{
use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer};
use async_trait::async_trait;
use http_signature_normalization_actix::prelude::VerifyDigest;
use reqwest::Client;
use sha2::{Digest, Sha256};
use std::{
ops::Deref,
sync::{Arc, Mutex},
@ -94,9 +92,6 @@ impl Instance {
.route("/objects/{user_name}", web::get().to(http_get_user))
.service(
web::scope("")
// Important: this ensures that the activity json matches the hashsum in signed
// HTTP header
.wrap(VerifyDigest::new(Sha256::new()))
// Just a single, global inbox for simplicity
.route("/inbox", web::post().to(http_post_user_inbox)),
)

View file

@ -6,9 +6,8 @@ use crate::{
LocalInstance,
};
use crate::core::actix::signature::verify_signature;
use actix_web::{dev::Payload, FromRequest, HttpRequest, HttpResponse};
use http_signature_normalization_actix::prelude::DigestVerified;
use crate::core::signatures::verify_signature;
use actix_web::{HttpRequest, HttpResponse};
use serde::de::DeserializeOwned;
use tracing::debug;
@ -26,12 +25,9 @@ where
<Activity as ActivityHandler>::Error: From<anyhow::Error>
+ From<Error>
+ From<<ActorT as ApubObject>::Error>
+ From<serde_json::Error>
+ From<http_signature_normalization_actix::digest::middleware::VerifyError>,
+ From<serde_json::Error>,
<ActorT as ApubObject>::Error: From<Error> + From<anyhow::Error>,
{
// ensure that payload hash was checked against digest header by middleware
DigestVerified::from_request(&request, &mut Payload::None).await?;
local_instance.verify_url_and_domain(&activity).await?;
let request_counter = &mut 0;
@ -39,7 +35,12 @@ where
.dereference(data, local_instance, request_counter)
.await?;
verify_signature(&request, actor.public_key())?;
verify_signature(
request.headers(),
request.method(),
request.uri(),
actor.public_key(),
)?;
debug!("Verifying activity {}", activity.id().to_string());
activity.verify(data, request_counter).await?;

View file

@ -1,2 +1 @@
pub mod inbox;
mod signature;

View file

@ -1,35 +0,0 @@
use actix_web::HttpRequest;
use anyhow::anyhow;
use http_signature_normalization_actix::Config as ConfigActix;
use once_cell::sync::Lazy;
use openssl::{hash::MessageDigest, pkey::PKey, sign::Verifier};
use tracing::debug;
static CONFIG2: Lazy<ConfigActix> = Lazy::new(ConfigActix::new);
/// Verifies the HTTP signature on an incoming inbox request.
pub fn verify_signature(request: &HttpRequest, public_key: &str) -> Result<(), anyhow::Error> {
let verified = CONFIG2
.begin_verify(
request.method(),
request.uri().path_and_query(),
request.headers().clone(),
)?
.verify(|signature, signing_string| -> anyhow::Result<bool> {
debug!(
"Verifying with key {}, message {}",
&public_key, &signing_string
);
let public_key = PKey::public_key_from_pem(public_key.as_bytes())?;
let mut verifier = Verifier::new(MessageDigest::sha256(), &public_key)?;
verifier.update(signing_string.as_bytes())?;
Ok(verifier.verify(&base64::decode(signature)?)?)
})?;
if verified {
debug!("verified signature for {}", &request.uri());
Ok(())
} else {
Err(anyhow!("Invalid signature on request: {}", &request.uri()))
}
}

View file

@ -1,8 +1,5 @@
use crate::{
core::{
axum::{signature::verify_signature, DigestVerified},
object_id::ObjectId,
},
core::{axum::DigestVerified, object_id::ObjectId, signatures::verify_signature},
data::Data,
traits::{ActivityHandler, Actor, ApubObject},
Error,
@ -39,7 +36,7 @@ where
.dereference(data, local_instance, request_counter)
.await?;
verify_signature(&headers, method, uri, actor.public_key())?;
verify_signature(&headers, &method, &uri, actor.public_key())?;
debug!("Verifying activity {}", activity.id().to_string());
activity.verify(data, request_counter).await?;

View file

@ -11,7 +11,6 @@ use digest::{verify_sha256, DigestPart};
mod digest;
pub mod inbox;
pub mod json;
mod signature;
/// A request guard to ensure digest has been verified request has been
/// see [`receive_activity`]

View file

@ -1,44 +0,0 @@
use anyhow::anyhow;
use http::{uri::PathAndQuery, HeaderMap, Method, Uri};
use http_signature_normalization::Config;
use openssl::{hash::MessageDigest, pkey::PKey, sign::Verifier};
use std::collections::BTreeMap;
use tracing::debug;
/// Verifies the HTTP signature on an incoming inbox request.
pub fn verify_signature(
headers: &HeaderMap,
method: Method,
uri: Uri,
public_key: &str,
) -> Result<(), anyhow::Error> {
let config = Config::default();
let mut header_map = BTreeMap::new();
for (name, value) in headers {
if let Ok(value) = value.to_str() {
header_map.insert(name.to_string(), value.to_string());
}
}
let path_and_query = uri.path_and_query().map(PathAndQuery::as_str).unwrap_or("");
let verified = config
.begin_verify(method.as_str(), path_and_query, header_map)?
.verify(|signature, signing_string| -> anyhow::Result<bool> {
debug!(
"Verifying with key {}, message {}",
&public_key, &signing_string
);
let public_key = PKey::public_key_from_pem(public_key.as_bytes())?;
let mut verifier = Verifier::new(MessageDigest::sha256(), &public_key)?;
verifier.update(signing_string.as_bytes())?;
Ok(verifier.verify(&base64::decode(signature)?)?)
})?;
if verified {
debug!("verified signature for {}", uri);
Ok(())
} else {
Err(anyhow!("Invalid signature on request: {}", uri))
}
}

View file

@ -1,11 +1,20 @@
use crate::utils::header_to_map;
use anyhow::anyhow;
use http::{header::HeaderName, uri::PathAndQuery, HeaderValue, Method, Uri};
use http_signature_normalization_reqwest::prelude::{Config, SignExt};
use once_cell::sync::OnceCell;
use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa, sign::Signer};
use once_cell::sync::{Lazy, OnceCell};
use openssl::{
hash::MessageDigest,
pkey::PKey,
rsa::Rsa,
sign::{Signer, Verifier},
};
use reqwest::Request;
use reqwest_middleware::RequestBuilder;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::io::{Error, ErrorKind};
use tracing::debug;
use url::Url;
static HTTP_SIG_CONFIG: OnceCell<Config> = OnceCell::new();
@ -94,3 +103,40 @@ impl PublicKey {
}
}
}
static CONFIG2: Lazy<http_signature_normalization::Config> =
Lazy::new(http_signature_normalization::Config::new);
/// Verifies the HTTP signature on an incoming inbox request.
pub fn verify_signature<'a, H>(
headers: H,
method: &Method,
uri: &Uri,
public_key: &str,
) -> Result<(), anyhow::Error>
where
H: IntoIterator<Item = (&'a HeaderName, &'a HeaderValue)>,
{
let headers = header_to_map(headers);
let path_and_query = uri.path_and_query().map(PathAndQuery::as_str).unwrap_or("");
let verified = CONFIG2
.begin_verify(method.as_str(), path_and_query, headers)?
.verify(|signature, signing_string| -> anyhow::Result<bool> {
debug!(
"Verifying with key {}, message {}",
&public_key, &signing_string
);
let public_key = PKey::public_key_from_pem(public_key.as_bytes())?;
let mut verifier = Verifier::new(MessageDigest::sha256(), &public_key)?;
verifier.update(signing_string.as_bytes())?;
Ok(verifier.verify(&base64::decode(signature)?)?)
})?;
if verified {
debug!("verified signature for {}", uri);
Ok(())
} else {
Err(anyhow!("Invalid signature on request: {}", uri))
}
}

0
src/signature.rs Normal file
View file

View file

@ -1,6 +1,7 @@
use crate::{Error, LocalInstance, APUB_JSON_CONTENT_TYPE};
use http::StatusCode;
use http::{header::HeaderName, HeaderValue, StatusCode};
use serde::de::DeserializeOwned;
use std::collections::BTreeMap;
use tracing::info;
use url::Url;
@ -50,3 +51,19 @@ pub fn verify_urls_match(a: &Url, b: &Url) -> Result<(), Error> {
}
Ok(())
}
/// Utility to converts either actix or axum headermap to a BTreeMap
pub fn header_to_map<'a, H>(headers: H) -> BTreeMap<String, String>
where
H: IntoIterator<Item = (&'a HeaderName, &'a HeaderValue)>,
{
let mut header_map = BTreeMap::new();
for (name, value) in headers {
if let Ok(value) = value.to_str() {
header_map.insert(name.to_string(), value.to_string());
}
}
header_map
}