summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock57
-rw-r--r--contrib/bruno/users/follow.bru29
-rw-r--r--crates/sellershut/Cargo.toml6
-rw-r--r--crates/sellershut/src/server/activities/follow.rs5
-rw-r--r--crates/sellershut/src/server/middleware/sign_request.rs49
-rw-r--r--crates/sellershut/src/server/middleware/sign_request/signature.rs59
6 files changed, 190 insertions, 15 deletions
diff --git a/Cargo.lock b/Cargo.lock
index f2e4973..5f2b51d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -739,6 +739,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
name = "form_urlencoded"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1546,6 +1561,44 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
[[package]]
+name = "openssl"
+version = "0.10.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "foreign-types",
+ "libc",
+ "once_cell",
+ "openssl-macros",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.104",
+]
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2059,17 +2112,19 @@ dependencies = [
"anyhow",
"async-trait",
"axum",
+ "base64",
"clap",
"config",
"enum_delegate",
"futures-util",
- "hmac",
"nanoid",
+ "openssl",
"serde",
"serde_json",
"sha2",
"sqlx",
"stack-up",
+ "time",
"tokio",
"tower",
"tower-http",
diff --git a/contrib/bruno/users/follow.bru b/contrib/bruno/users/follow.bru
new file mode 100644
index 0000000..89cf4e1
--- /dev/null
+++ b/contrib/bruno/users/follow.bru
@@ -0,0 +1,29 @@
+meta {
+ name: follow
+ type: http
+ seq: 3
+}
+
+post {
+ url: {{HUT_HOSTNAME}}/users/sellershut/inbox
+ body: none
+ auth: inherit
+}
+
+headers {
+ Content-Type: application/activity+json
+}
+
+script:pre-request {
+ const { nanoid } = require("nanoid")
+
+ const hostname = bru.getEnvVar("HUT_HOSTNAME");
+
+ req.setBody({
+ "@context": "https://www.w3.org/ns/activitystreams",
+ "id": `${hostname}/activity/follow/${nanoid()}`,
+ "type": "Follow",
+ "actor": `http://localhost/users/sellershut`,
+ "object": `http://localhost/users/sellershut`
+ })
+}
diff --git a/crates/sellershut/Cargo.toml b/crates/sellershut/Cargo.toml
index 43948f8..7501da5 100644
--- a/crates/sellershut/Cargo.toml
+++ b/crates/sellershut/Cargo.toml
@@ -12,19 +12,21 @@ activitypub_federation = { version = "0.7.0-beta.5", default-features = false, f
anyhow.workspace = true
async-trait.workspace = true
axum = { workspace = true, features = ["macros"] }
+base64 = "0.22.1"
clap = { workspace = true, features = ["derive"] }
config = { workspace = true, features = ["toml"] }
enum_delegate = "0.2.0"
futures-util.workspace = true
-hmac = "0.12.1"
nanoid.workspace = true
+openssl = "0.10.73"
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
sha2 = "0.10.9"
sqlx = { workspace = true, features = ["macros", "migrate", "runtime-tokio", "time", "tls-rustls", "uuid"] }
+time = { version = "0.3.41", default-features = false, features = ["parsing"] }
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "signal"] }
tower = { workspace = true, features = ["util"] }
-tower-http = { workspace = true, features = ["trace"] }
+tower-http = { workspace = true, features = ["map-request-body", "trace", "util"] }
tracing.workspace = true
url.workspace = true
uuid = { workspace = true, features = ["v7"] }
diff --git a/crates/sellershut/src/server/activities/follow.rs b/crates/sellershut/src/server/activities/follow.rs
index a004c6b..f0df0d9 100644
--- a/crates/sellershut/src/server/activities/follow.rs
+++ b/crates/sellershut/src/server/activities/follow.rs
@@ -119,8 +119,8 @@ mod tests {
config.application.port
);
let actor = format!(
- "http://localhost:{}/users/{}",
- config.application.port, hut_config.instance_name
+ "http://localhost/users/{}",
+ hut_config.instance_name
);
let app = server::router(state);
@@ -138,6 +138,7 @@ mod tests {
.oneshot(
Request::builder()
.method("POST")
+ .header("Content-Type", "application/activity+json")
.uri("/users/sellershut/inbox")
.body(Body::from(body))
.unwrap(),
diff --git a/crates/sellershut/src/server/middleware/sign_request.rs b/crates/sellershut/src/server/middleware/sign_request.rs
index a8f2f3a..889984f 100644
--- a/crates/sellershut/src/server/middleware/sign_request.rs
+++ b/crates/sellershut/src/server/middleware/sign_request.rs
@@ -1,14 +1,20 @@
+mod signature;
+
use activitypub_federation::config::FederationConfig;
-use axum::{body::Body, extract::Request, response::Response};
+use axum::{
+ body::Body,
+ extract::Request,
+ http::HeaderValue,
+ response::Response,
+};
use futures_util::future::BoxFuture;
-use hmac::{Hmac, Mac};
-use sha2::{Sha256, digest::KeyInit};
-use std::task::{Context, Poll};
+use std::{
+ task::{Context, Poll},
+};
use tower::{Layer, Service};
-use crate::state::AppHandle;
+use crate::{server::middleware::sign_request::signature::Signature, state::AppHandle};
-type HmacSha256 = Hmac<Sha256>;
#[derive(Clone)]
pub struct SignRequestLayer {
@@ -42,7 +48,7 @@ pub struct SignRequestMiddleware<S> {
impl<S> Service<Request> for SignRequestMiddleware<S>
where
- S: Service<Request, Response = Response> + Send + 'static,
+ S: Service<Request, Response = Response> + Clone + Send + 'static,
S::Future: Send + 'static,
{
type Response = S::Response;
@@ -55,10 +61,33 @@ where
}
fn call(&mut self, request: Request) -> Self::Future {
- let future = self.inner.call(request);
+ let mut inner = self.inner.clone();
+ let (parts, body) = request.into_parts();
+
Box::pin(async move {
- let response: Response = future.await?;
- Ok(response)
+ let bytes = axum::body::to_bytes(body, usize::MAX).await.unwrap();
+
+ let signature = Signature::create(
+ ""
+ .as_bytes(),
+ bytes,
+ )
+ .unwrap();
+
+ let mut new_request = Request::from_parts(parts, Body::from(signature.body));
+
+ let head = new_request.headers_mut();
+ let header = format!(
+ "keyId=\"http://localhost/users/sellershut#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest\",signature=\"{}\"",
+ signature.signature,
+ );
+ println!("{header}");
+ head.insert("Host", HeaderValue::from_str(&signature.host).unwrap());
+ head.insert("Date", HeaderValue::from_str(&signature.date).unwrap());
+ head.insert("Digest", HeaderValue::from_str(&signature.digest).unwrap());
+ head.insert("Signature", HeaderValue::from_str(&header).unwrap());
+
+ inner.call(new_request).await
})
}
}
diff --git a/crates/sellershut/src/server/middleware/sign_request/signature.rs b/crates/sellershut/src/server/middleware/sign_request/signature.rs
new file mode 100644
index 0000000..18e62c0
--- /dev/null
+++ b/crates/sellershut/src/server/middleware/sign_request/signature.rs
@@ -0,0 +1,59 @@
+use axum::body::Bytes;
+use base64::{Engine, engine::general_purpose};
+use openssl::{pkey::PKey, rsa::Rsa, sign::Signer};
+use sha2::{Digest, Sha256};
+use sqlx::types::time::OffsetDateTime;
+use time::format_description::well_known::Rfc2822;
+
+use crate::error::AppError;
+
+/// Signature state for verifying a request
+pub struct Signature {
+ pub host: String,
+ pub date: String,
+ pub digest: String,
+ pub signature: String,
+ pub body: Bytes
+}
+
+impl Signature {
+ pub fn create(key: &[u8], body: Bytes) -> Result<Self, AppError> {
+ let mut hasher = Sha256::new();
+ hasher.update(&body);
+
+ let result = hasher.finalize();
+
+ let digest_hash = general_purpose::STANDARD.encode(result);
+ let digest = format!("SHA-256={digest_hash}");
+
+ let inbox_path = "/users/sellershut/inbox";
+ let now = OffsetDateTime::now_utc().format(&Rfc2822)?;
+
+ let signing_string = [
+ format!("(request-target): post {inbox_path}"),
+ "host: localhost:2210".to_owned(),
+ format!("date: {now}"),
+ format!("digest: {digest}"),
+ ]
+ .join("\n");
+
+ let rsa = Rsa::private_key_from_pem(key)?;
+ let pkey = PKey::from_rsa(rsa)?;
+
+ // Create signer with SHA-256
+ let mut signer = Signer::new(openssl::hash::MessageDigest::sha256(), &pkey)?;
+ signer.update(signing_string.as_bytes())?;
+
+ // Sign and encode as base64
+ let signature = signer.sign_to_vec()?;
+ let result = general_purpose::STANDARD.encode(signature);
+
+ Ok(Self{
+ host: "localhost:2210".to_string(),
+ date: now,
+ digest,
+ signature: result,
+ body,
+ })
+ }
+}