Add http signature to outgoing apub requests
This commit is contained in:
parent
0199b5f169
commit
8daf72278d
12 changed files with 191 additions and 71 deletions
27
server/Cargo.lock
generated
vendored
27
server/Cargo.lock
generated
vendored
|
@ -74,7 +74,7 @@ dependencies = [
|
||||||
"derive_more 0.99.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"derive_more 0.99.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"trust-dns-proto 0.18.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"trust-dns-proto 0.18.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"trust-dns-resolver 0.18.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"trust-dns-resolver 0.18.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -126,7 +126,7 @@ dependencies = [
|
||||||
"futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"h2 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"h2 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -160,7 +160,7 @@ version = "0.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytestring 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"bytestring 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"regex 1.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex 1.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde 1.0.105 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde 1.0.105 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -1176,7 +1176,7 @@ dependencies = [
|
||||||
"futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -1237,7 +1237,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -1245,6 +1245,15 @@ dependencies = [
|
||||||
"itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http-signature-normalization"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"thiserror 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httparse"
|
name = "httparse"
|
||||||
version = "1.3.4"
|
version = "1.3.4"
|
||||||
|
@ -1314,7 +1323,7 @@ dependencies = [
|
||||||
"futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"futures-io 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"futures-io 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
"mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -1387,6 +1396,7 @@ dependencies = [
|
||||||
"actix-rt 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"actix-rt 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"actix-web 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"actix-web 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"actix-web-actors 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"actix-web-actors 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"base64 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"bcrypt 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"bcrypt 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
"chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"comrak 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"comrak 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -1398,6 +1408,8 @@ dependencies = [
|
||||||
"failure 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
"failure 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"hjson 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"hjson 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"htmlescape 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"htmlescape 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"http-signature-normalization 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"isahc 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"isahc 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"jsonwebtoken 7.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"jsonwebtoken 7.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -3070,7 +3082,8 @@ dependencies = [
|
||||||
"checksum hostname 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "21ceb46a83a85e824ef93669c8b390009623863b5c195d1ba747292c0c72f94e"
|
"checksum hostname 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "21ceb46a83a85e824ef93669c8b390009623863b5c195d1ba747292c0c72f94e"
|
||||||
"checksum hostname 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
|
"checksum hostname 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
|
||||||
"checksum htmlescape 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163"
|
"checksum htmlescape 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163"
|
||||||
"checksum http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b708cc7f06493459026f53b9a61a7a121a5d1ec6238dee58ea4941132b30156b"
|
"checksum http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9"
|
||||||
|
"checksum http-signature-normalization 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "257835255b5d40c6de712d90e56dc874ca5da2816121e7b9f3cfc7b3a55a5714"
|
||||||
"checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
|
"checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
|
||||||
"checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
|
"checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
|
||||||
"checksum ident_case 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
"checksum ident_case 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||||
|
|
3
server/Cargo.toml
vendored
3
server/Cargo.toml
vendored
|
@ -39,3 +39,6 @@ percent-encoding = "2.1.0"
|
||||||
isahc = "0.9"
|
isahc = "0.9"
|
||||||
comrak = "0.7"
|
comrak = "0.7"
|
||||||
openssl = "0.10"
|
openssl = "0.10"
|
||||||
|
http = "0.2.1"
|
||||||
|
http-signature-normalization = "0.4.1"
|
||||||
|
base64 = "0.12.0"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::apub::activities::follow_community;
|
use crate::apub::activities::follow_community;
|
||||||
use crate::apub::{gen_keypair_str, make_apub_endpoint, EndpointType};
|
use crate::apub::signatures::generate_actor_keypair;
|
||||||
|
use crate::apub::{make_apub_endpoint, EndpointType};
|
||||||
use diesel::PgConnection;
|
use diesel::PgConnection;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
@ -200,7 +201,7 @@ impl Perform<CommunityResponse> for Oper<CreateCommunity> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// When you create a community, make sure the user becomes a moderator and a follower
|
// When you create a community, make sure the user becomes a moderator and a follower
|
||||||
let (community_public_key, community_private_key) = gen_keypair_str();
|
let keypair = generate_actor_keypair();
|
||||||
|
|
||||||
let community_form = CommunityForm {
|
let community_form = CommunityForm {
|
||||||
name: data.name.to_owned(),
|
name: data.name.to_owned(),
|
||||||
|
@ -214,8 +215,8 @@ impl Perform<CommunityResponse> for Oper<CreateCommunity> {
|
||||||
updated: None,
|
updated: None,
|
||||||
actor_id: make_apub_endpoint(EndpointType::Community, &data.name).to_string(),
|
actor_id: make_apub_endpoint(EndpointType::Community, &data.name).to_string(),
|
||||||
local: true,
|
local: true,
|
||||||
private_key: Some(community_private_key),
|
private_key: Some(keypair.private_key),
|
||||||
public_key: Some(community_public_key),
|
public_key: Some(keypair.public_key),
|
||||||
last_refreshed_at: None,
|
last_refreshed_at: None,
|
||||||
published: None,
|
published: None,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::apub::{gen_keypair_str, make_apub_endpoint, EndpointType};
|
use crate::apub::signatures::generate_actor_keypair;
|
||||||
|
use crate::apub::{make_apub_endpoint, EndpointType};
|
||||||
use crate::settings::Settings;
|
use crate::settings::Settings;
|
||||||
use crate::{generate_random_string, send_email};
|
use crate::{generate_random_string, send_email};
|
||||||
use bcrypt::verify;
|
use bcrypt::verify;
|
||||||
|
@ -251,7 +252,7 @@ impl Perform<LoginResponse> for Oper<Register> {
|
||||||
return Err(APIError::err("admin_already_created").into());
|
return Err(APIError::err("admin_already_created").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let (user_public_key, user_private_key) = gen_keypair_str();
|
let keypair = generate_actor_keypair();
|
||||||
|
|
||||||
// Register the new user
|
// Register the new user
|
||||||
let user_form = UserForm {
|
let user_form = UserForm {
|
||||||
|
@ -274,8 +275,8 @@ impl Perform<LoginResponse> for Oper<Register> {
|
||||||
actor_id: make_apub_endpoint(EndpointType::User, &data.username).to_string(),
|
actor_id: make_apub_endpoint(EndpointType::User, &data.username).to_string(),
|
||||||
bio: None,
|
bio: None,
|
||||||
local: true,
|
local: true,
|
||||||
private_key: Some(user_private_key),
|
private_key: Some(keypair.private_key),
|
||||||
public_key: Some(user_public_key),
|
public_key: Some(keypair.public_key),
|
||||||
last_refreshed_at: None,
|
last_refreshed_at: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -295,7 +296,7 @@ impl Perform<LoginResponse> for Oper<Register> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let (community_public_key, community_private_key) = gen_keypair_str();
|
let keypair = generate_actor_keypair();
|
||||||
|
|
||||||
// Create the main community if it doesn't exist
|
// Create the main community if it doesn't exist
|
||||||
let main_community: Community = match Community::read(&conn, 2) {
|
let main_community: Community = match Community::read(&conn, 2) {
|
||||||
|
@ -314,8 +315,8 @@ impl Perform<LoginResponse> for Oper<Register> {
|
||||||
updated: None,
|
updated: None,
|
||||||
actor_id: make_apub_endpoint(EndpointType::Community, default_community_name).to_string(),
|
actor_id: make_apub_endpoint(EndpointType::Community, default_community_name).to_string(),
|
||||||
local: true,
|
local: true,
|
||||||
private_key: Some(community_private_key),
|
private_key: Some(keypair.private_key),
|
||||||
public_key: Some(community_public_key),
|
public_key: Some(keypair.public_key),
|
||||||
last_refreshed_at: None,
|
last_refreshed_at: None,
|
||||||
published: None,
|
published: None,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::apub::is_apub_id_valid;
|
use crate::apub::is_apub_id_valid;
|
||||||
|
use crate::apub::signatures::{sign, Keypair};
|
||||||
use crate::db::community::Community;
|
use crate::db::community::Community;
|
||||||
use crate::db::community_view::CommunityFollowerView;
|
use crate::db::community_view::CommunityFollowerView;
|
||||||
use crate::db::post::Post;
|
use crate::db::post::Post;
|
||||||
|
@ -14,6 +15,7 @@ use failure::_core::fmt::Debug;
|
||||||
use isahc::prelude::*;
|
use isahc::prelude::*;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
fn populate_object_props(
|
fn populate_object_props(
|
||||||
props: &mut ObjectProperties,
|
props: &mut ObjectProperties,
|
||||||
|
@ -32,18 +34,27 @@ fn populate_object_props(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send an activity to a list of recipients, using the correct headers etc.
|
/// Send an activity to a list of recipients, using the correct headers etc.
|
||||||
fn send_activity<A>(activity: &A, to: Vec<String>) -> Result<(), Error>
|
fn send_activity<A>(
|
||||||
|
activity: &A,
|
||||||
|
keypair: &Keypair,
|
||||||
|
sender_id: &str,
|
||||||
|
to: Vec<String>,
|
||||||
|
) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
A: Serialize + Debug,
|
A: Serialize + Debug,
|
||||||
{
|
{
|
||||||
let json = serde_json::to_string(&activity)?;
|
let json = serde_json::to_string(&activity)?;
|
||||||
debug!("Sending activitypub activity {} to {:?}", json, to);
|
debug!("Sending activitypub activity {} to {:?}", json, to);
|
||||||
for t in to {
|
for t in to {
|
||||||
if !is_apub_id_valid(&t) {
|
let to_url = Url::parse(&t)?;
|
||||||
|
if !is_apub_id_valid(&to_url) {
|
||||||
debug!("Not sending activity to {} (invalid or blacklisted)", t);
|
debug!("Not sending activity to {} (invalid or blacklisted)", t);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let res = Request::post(t)
|
let request = Request::post(t).header("Host", to_url.domain().unwrap());
|
||||||
|
let signature = sign(&request, keypair, sender_id)?;
|
||||||
|
let res = request
|
||||||
|
.header("Signature", signature)
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
.body(json.to_owned())?
|
.body(json.to_owned())?
|
||||||
.send()?;
|
.send()?;
|
||||||
|
@ -77,7 +88,12 @@ pub fn post_create(post: &Post, creator: &User_, conn: &PgConnection) -> Result<
|
||||||
.create_props
|
.create_props
|
||||||
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
|
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
|
||||||
.set_object_base_box(page)?;
|
.set_object_base_box(page)?;
|
||||||
send_activity(&create, get_follower_inboxes(conn, &community)?)?;
|
send_activity(
|
||||||
|
&create,
|
||||||
|
&creator.get_keypair().unwrap(),
|
||||||
|
&creator.actor_id,
|
||||||
|
get_follower_inboxes(conn, &community)?,
|
||||||
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +111,12 @@ pub fn post_update(post: &Post, creator: &User_, conn: &PgConnection) -> Result<
|
||||||
.update_props
|
.update_props
|
||||||
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
|
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
|
||||||
.set_object_base_box(page)?;
|
.set_object_base_box(page)?;
|
||||||
send_activity(&update, get_follower_inboxes(conn, &community)?)?;
|
send_activity(
|
||||||
|
&update,
|
||||||
|
&creator.get_keypair().unwrap(),
|
||||||
|
&creator.actor_id,
|
||||||
|
get_follower_inboxes(conn, &community)?,
|
||||||
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,12 +137,23 @@ pub fn follow_community(
|
||||||
.set_actor_xsd_any_uri(user.actor_id.clone())?
|
.set_actor_xsd_any_uri(user.actor_id.clone())?
|
||||||
.set_object_xsd_any_uri(community.actor_id.clone())?;
|
.set_object_xsd_any_uri(community.actor_id.clone())?;
|
||||||
let to = format!("{}/inbox", community.actor_id);
|
let to = format!("{}/inbox", community.actor_id);
|
||||||
send_activity(&follow, vec![to])?;
|
send_activity(
|
||||||
|
&follow,
|
||||||
|
&community.get_keypair().unwrap(),
|
||||||
|
&community.actor_id,
|
||||||
|
vec![to],
|
||||||
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// As a local community, accept the follow request from a remote user.
|
/// As a local community, accept the follow request from a remote user.
|
||||||
pub fn accept_follow(follow: &Follow) -> Result<(), Error> {
|
pub fn accept_follow(follow: &Follow, conn: &PgConnection) -> Result<(), Error> {
|
||||||
|
let community_uri = follow
|
||||||
|
.follow_props
|
||||||
|
.get_actor_xsd_any_uri()
|
||||||
|
.unwrap()
|
||||||
|
.to_string();
|
||||||
|
let community = Community::read_from_actor_id(conn, &community_uri)?;
|
||||||
let mut accept = Accept::new();
|
let mut accept = Accept::new();
|
||||||
accept
|
accept
|
||||||
.object_props
|
.object_props
|
||||||
|
@ -137,14 +169,12 @@ pub fn accept_follow(follow: &Follow) -> Result<(), Error> {
|
||||||
accept
|
accept
|
||||||
.accept_props
|
.accept_props
|
||||||
.set_object_base_box(BaseBox::from_concrete(follow.clone())?)?;
|
.set_object_base_box(BaseBox::from_concrete(follow.clone())?)?;
|
||||||
let to = format!(
|
let to = format!("{}/inbox", community_uri);
|
||||||
"{}/inbox",
|
send_activity(
|
||||||
follow
|
&accept,
|
||||||
.follow_props
|
&community.get_keypair().unwrap(),
|
||||||
.get_actor_xsd_any_uri()
|
&community.actor_id,
|
||||||
.unwrap()
|
vec![to],
|
||||||
.to_string()
|
)?;
|
||||||
);
|
|
||||||
send_activity(&accept, vec![to])?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,6 @@ fn handle_follow(follow: &Follow, conn: &PgConnection) -> Result<HttpResponse, E
|
||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
};
|
};
|
||||||
CommunityFollower::follow(&conn, &community_follower_form)?;
|
CommunityFollower::follow(&conn, &community_follower_form)?;
|
||||||
accept_follow(&follow)?;
|
accept_follow(&follow, conn)?;
|
||||||
Ok(HttpResponse::Ok().finish())
|
Ok(HttpResponse::Ok().finish())
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,7 @@ pub fn fetch_remote_object<Response>(url: &Url) -> Result<Response, Error>
|
||||||
where
|
where
|
||||||
Response: for<'de> Deserialize<'de>,
|
Response: for<'de> Deserialize<'de>,
|
||||||
{
|
{
|
||||||
if !is_apub_id_valid(&url.to_string()) {
|
if !is_apub_id_valid(&url) {
|
||||||
return Err(format_err!("Activitypub uri invalid or blocked: {}", url));
|
return Err(format_err!("Activitypub uri invalid or blocked: {}", url));
|
||||||
}
|
}
|
||||||
// TODO: this function should return a future
|
// TODO: this function should return a future
|
||||||
|
|
|
@ -12,7 +12,6 @@ use activitystreams::actor::{properties::ApActorProperties, Group, Person};
|
||||||
use activitystreams::ext::Ext;
|
use activitystreams::ext::Ext;
|
||||||
use actix_web::body::Body;
|
use actix_web::body::Body;
|
||||||
use actix_web::HttpResponse;
|
use actix_web::HttpResponse;
|
||||||
use openssl::{pkey::PKey, rsa::Rsa};
|
|
||||||
use serde::ser::Serialize;
|
use serde::ser::Serialize;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
@ -72,31 +71,9 @@ pub fn get_apub_protocol_string() -> &'static str {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate the asymmetric keypair for ActivityPub HTTP signatures.
|
|
||||||
pub fn gen_keypair_str() -> (String, String) {
|
|
||||||
let rsa = Rsa::generate(2048).expect("sign::gen_keypair: key generation error");
|
|
||||||
let pkey = PKey::from_rsa(rsa).expect("sign::gen_keypair: parsing error");
|
|
||||||
let public_key = pkey
|
|
||||||
.public_key_to_pem()
|
|
||||||
.expect("sign::gen_keypair: public key encoding error");
|
|
||||||
let private_key = pkey
|
|
||||||
.private_key_to_pem_pkcs8()
|
|
||||||
.expect("sign::gen_keypair: private key encoding error");
|
|
||||||
(vec_bytes_to_str(public_key), vec_bytes_to_str(private_key))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn vec_bytes_to_str(bytes: Vec<u8>) -> String {
|
|
||||||
String::from_utf8_lossy(&bytes).into_owned()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks if the ID has a valid format, correct scheme, and is in the whitelist.
|
// Checks if the ID has a valid format, correct scheme, and is in the whitelist.
|
||||||
fn is_apub_id_valid(apub_id: &str) -> bool {
|
fn is_apub_id_valid(apub_id: &Url) -> bool {
|
||||||
let url = match Url::parse(apub_id) {
|
if apub_id.scheme() != get_apub_protocol_string() {
|
||||||
Ok(u) => u,
|
|
||||||
Err(_) => return false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if url.scheme() != get_apub_protocol_string() {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,7 +83,7 @@ fn is_apub_id_valid(apub_id: &str) -> bool {
|
||||||
.split(',')
|
.split(',')
|
||||||
.map(|d| d.to_string())
|
.map(|d| d.to_string())
|
||||||
.collect();
|
.collect();
|
||||||
match url.domain() {
|
match apub_id.domain() {
|
||||||
Some(d) => whitelist.contains(&d.to_owned()),
|
Some(d) => whitelist.contains(&d.to_owned()),
|
||||||
None => false,
|
None => false,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,69 @@
|
||||||
// For this example, we'll use the Extensible trait, the Extension trait, the Actor trait, and
|
|
||||||
// the Person type
|
|
||||||
use activitystreams::{actor::Actor, ext::Extension};
|
use activitystreams::{actor::Actor, ext::Extension};
|
||||||
|
use failure::Error;
|
||||||
|
use http::request::Builder;
|
||||||
|
use http_signature_normalization::Config;
|
||||||
|
use openssl::hash::MessageDigest;
|
||||||
|
use openssl::sign::Signer;
|
||||||
|
use openssl::{pkey::PKey, rsa::Rsa};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
pub struct Keypair {
|
||||||
|
pub private_key: String,
|
||||||
|
pub public_key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate the asymmetric keypair for ActivityPub HTTP signatures.
|
||||||
|
pub fn generate_actor_keypair() -> Keypair {
|
||||||
|
let rsa = Rsa::generate(2048).expect("sign::gen_keypair: key generation error");
|
||||||
|
let pkey = PKey::from_rsa(rsa).expect("sign::gen_keypair: parsing error");
|
||||||
|
let public_key = pkey
|
||||||
|
.public_key_to_pem()
|
||||||
|
.expect("sign::gen_keypair: public key encoding error");
|
||||||
|
let private_key = pkey
|
||||||
|
.private_key_to_pem_pkcs8()
|
||||||
|
.expect("sign::gen_keypair: private key encoding error");
|
||||||
|
Keypair {
|
||||||
|
private_key: String::from_utf8_lossy(&private_key).into_owned(),
|
||||||
|
public_key: String::from_utf8_lossy(&public_key).into_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Signs request headers with the given keypair.
|
||||||
|
pub fn sign(request: &Builder, keypair: &Keypair, sender_id: &str) -> Result<String, Error> {
|
||||||
|
let signing_key_id = format!("{}#main-key", sender_id);
|
||||||
|
let config = Config::new();
|
||||||
|
|
||||||
|
let headers = request
|
||||||
|
.headers_ref()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.map(|h| -> Result<(String, String), Error> {
|
||||||
|
Ok((h.0.as_str().to_owned(), h.1.to_str()?.to_owned()))
|
||||||
|
})
|
||||||
|
.collect::<Result<BTreeMap<String, String>, Error>>()?;
|
||||||
|
|
||||||
|
let signature_header_value = config
|
||||||
|
.begin_sign(
|
||||||
|
request.method_ref().unwrap().as_str(),
|
||||||
|
request
|
||||||
|
.uri_ref()
|
||||||
|
.unwrap()
|
||||||
|
.path_and_query()
|
||||||
|
.unwrap()
|
||||||
|
.as_str(),
|
||||||
|
headers,
|
||||||
|
)
|
||||||
|
.sign(signing_key_id, |signing_string| {
|
||||||
|
let private_key = PKey::private_key_from_pem(keypair.private_key.as_bytes())?;
|
||||||
|
let mut signer = Signer::new(MessageDigest::sha256(), &private_key).unwrap();
|
||||||
|
signer.update(signing_string.as_bytes()).unwrap();
|
||||||
|
Ok(base64::encode(signer.sign_to_vec()?)) as Result<_, Error>
|
||||||
|
})?
|
||||||
|
.signature_header();
|
||||||
|
|
||||||
|
Ok(signature_header_value)
|
||||||
|
}
|
||||||
|
|
||||||
// The following is taken from here:
|
// The following is taken from here:
|
||||||
// https://docs.rs/activitystreams/0.5.0-alpha.17/activitystreams/ext/index.html
|
// https://docs.rs/activitystreams/0.5.0-alpha.17/activitystreams/ext/index.html
|
||||||
|
|
|
@ -4,7 +4,8 @@ use super::community::{Community, CommunityForm};
|
||||||
use super::post::Post;
|
use super::post::Post;
|
||||||
use super::user::{UserForm, User_};
|
use super::user::{UserForm, User_};
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::apub::{gen_keypair_str, make_apub_endpoint, EndpointType};
|
use crate::apub::signatures::generate_actor_keypair;
|
||||||
|
use crate::apub::{make_apub_endpoint, EndpointType};
|
||||||
use crate::naive_now;
|
use crate::naive_now;
|
||||||
use log::info;
|
use log::info;
|
||||||
|
|
||||||
|
@ -29,7 +30,7 @@ fn user_updates_2020_04_02(conn: &PgConnection) -> Result<(), Error> {
|
||||||
.load::<User_>(conn)?;
|
.load::<User_>(conn)?;
|
||||||
|
|
||||||
for cuser in &incorrect_users {
|
for cuser in &incorrect_users {
|
||||||
let (user_public_key, user_private_key) = gen_keypair_str();
|
let keypair = generate_actor_keypair();
|
||||||
|
|
||||||
let form = UserForm {
|
let form = UserForm {
|
||||||
name: cuser.name.to_owned(),
|
name: cuser.name.to_owned(),
|
||||||
|
@ -51,8 +52,8 @@ fn user_updates_2020_04_02(conn: &PgConnection) -> Result<(), Error> {
|
||||||
actor_id: make_apub_endpoint(EndpointType::User, &cuser.name).to_string(),
|
actor_id: make_apub_endpoint(EndpointType::User, &cuser.name).to_string(),
|
||||||
bio: cuser.bio.to_owned(),
|
bio: cuser.bio.to_owned(),
|
||||||
local: cuser.local,
|
local: cuser.local,
|
||||||
private_key: Some(user_private_key),
|
private_key: Some(keypair.private_key),
|
||||||
public_key: Some(user_public_key),
|
public_key: Some(keypair.public_key),
|
||||||
last_refreshed_at: Some(naive_now()),
|
last_refreshed_at: Some(naive_now()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -76,7 +77,7 @@ fn community_updates_2020_04_02(conn: &PgConnection) -> Result<(), Error> {
|
||||||
.load::<Community>(conn)?;
|
.load::<Community>(conn)?;
|
||||||
|
|
||||||
for ccommunity in &incorrect_communities {
|
for ccommunity in &incorrect_communities {
|
||||||
let (community_public_key, community_private_key) = gen_keypair_str();
|
let keypair = generate_actor_keypair();
|
||||||
|
|
||||||
let form = CommunityForm {
|
let form = CommunityForm {
|
||||||
name: ccommunity.name.to_owned(),
|
name: ccommunity.name.to_owned(),
|
||||||
|
@ -90,8 +91,8 @@ fn community_updates_2020_04_02(conn: &PgConnection) -> Result<(), Error> {
|
||||||
updated: None,
|
updated: None,
|
||||||
actor_id: make_apub_endpoint(EndpointType::Community, &ccommunity.name).to_string(),
|
actor_id: make_apub_endpoint(EndpointType::Community, &ccommunity.name).to_string(),
|
||||||
local: ccommunity.local,
|
local: ccommunity.local,
|
||||||
private_key: Some(community_private_key),
|
private_key: Some(keypair.private_key),
|
||||||
public_key: Some(community_public_key),
|
public_key: Some(keypair.public_key),
|
||||||
last_refreshed_at: Some(naive_now()),
|
last_refreshed_at: Some(naive_now()),
|
||||||
published: None,
|
published: None,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::apub::signatures::Keypair;
|
||||||
use crate::schema::{community, community_follower, community_moderator, community_user_ban};
|
use crate::schema::{community, community_follower, community_moderator, community_user_ban};
|
||||||
|
|
||||||
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
|
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
|
||||||
|
@ -95,6 +96,21 @@ impl Community {
|
||||||
pub fn get_url(&self) -> String {
|
pub fn get_url(&self) -> String {
|
||||||
format!("https://{}/c/{}", Settings::get().hostname, self.name)
|
format!("https://{}/c/{}", Settings::get().hostname, self.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_keypair(&self) -> Option<Keypair> {
|
||||||
|
if let Some(private) = self.private_key.to_owned() {
|
||||||
|
if let Some(public) = self.public_key.to_owned() {
|
||||||
|
Some(Keypair {
|
||||||
|
private_key: private,
|
||||||
|
public_key: public,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
|
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::apub::signatures::Keypair;
|
||||||
use crate::schema::user_;
|
use crate::schema::user_;
|
||||||
use crate::schema::user_::dsl::*;
|
use crate::schema::user_::dsl::*;
|
||||||
use crate::{is_email_regex, naive_now, Settings};
|
use crate::{is_email_regex, naive_now, Settings};
|
||||||
|
@ -124,6 +125,21 @@ impl User_ {
|
||||||
use crate::schema::user_::dsl::*;
|
use crate::schema::user_::dsl::*;
|
||||||
user_.filter(actor_id.eq(object_id)).first::<Self>(conn)
|
user_.filter(actor_id.eq(object_id)).first::<Self>(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_keypair(&self) -> Option<Keypair> {
|
||||||
|
if let Some(private) = self.private_key.to_owned() {
|
||||||
|
if let Some(public) = self.public_key.to_owned() {
|
||||||
|
Some(Keypair {
|
||||||
|
private_key: private,
|
||||||
|
public_key: public,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
|
Loading…
Reference in a new issue