parent
6b372861d6
commit
22cb286f86
3
migrations/2018-05-03-182555_blogs_add_keys/down.sql
Normal file
3
migrations/2018-05-03-182555_blogs_add_keys/down.sql
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
-- This file should undo anything in `up.sql`
|
||||||
|
ALTER TABLE blogs DROP COLUMN private_key;
|
||||||
|
ALTER TABLE blogs DROP COLUMN public_key;
|
3
migrations/2018-05-03-182555_blogs_add_keys/up.sql
Normal file
3
migrations/2018-05-03-182555_blogs_add_keys/up.sql
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
-- Your SQL goes here
|
||||||
|
ALTER TABLE blogs ADD COLUMN private_key TEXT;
|
||||||
|
ALTER TABLE blogs ADD COLUMN public_key TEXT NOT NULL DEFAULT '';
|
@ -1,9 +1,11 @@
|
|||||||
use diesel::PgConnection;
|
use diesel::PgConnection;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
|
use serde_json;
|
||||||
|
|
||||||
use BASE_URL;
|
use BASE_URL;
|
||||||
use activity_pub::{activity_pub, ActivityPub, context, ap_url};
|
use activity_pub::{activity_pub, ActivityPub, context, ap_url};
|
||||||
use activity_pub::activity::Activity;
|
use activity_pub::activity::Activity;
|
||||||
|
use activity_pub::sign::*;
|
||||||
use models::instance::Instance;
|
use models::instance::Instance;
|
||||||
|
|
||||||
pub enum ActorType {
|
pub enum ActorType {
|
||||||
@ -33,8 +35,12 @@ pub trait Actor: Sized {
|
|||||||
|
|
||||||
fn get_actor_type() -> ActorType;
|
fn get_actor_type() -> ActorType;
|
||||||
|
|
||||||
|
fn custom_props(&self, _conn: &PgConnection) -> serde_json::Map<String, serde_json::Value> {
|
||||||
|
serde_json::Map::new()
|
||||||
|
}
|
||||||
|
|
||||||
fn as_activity_pub (&self, conn: &PgConnection) -> ActivityPub {
|
fn as_activity_pub (&self, conn: &PgConnection) -> ActivityPub {
|
||||||
activity_pub(json!({
|
let mut repr = json!({
|
||||||
"@context": context(),
|
"@context": context(),
|
||||||
"id": self.compute_id(conn),
|
"id": self.compute_id(conn),
|
||||||
"type": Self::get_actor_type().to_string(),
|
"type": Self::get_actor_type().to_string(),
|
||||||
@ -47,7 +53,11 @@ pub trait Actor: Sized {
|
|||||||
"endpoints": {
|
"endpoints": {
|
||||||
"sharedInbox": ap_url(format!("{}/inbox", BASE_URL.as_str()))
|
"sharedInbox": ap_url(format!("{}/inbox", BASE_URL.as_str()))
|
||||||
}
|
}
|
||||||
}))
|
});
|
||||||
|
|
||||||
|
self.custom_props(conn).iter().for_each(|p| repr[p.0] = p.1.clone());
|
||||||
|
|
||||||
|
activity_pub(repr)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_outbox(&self, conn: &PgConnection) -> String {
|
fn compute_outbox(&self, conn: &PgConnection) -> String {
|
||||||
@ -71,10 +81,12 @@ pub trait Actor: Sized {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_to_inbox<A: Activity>(&self, conn: &PgConnection, act: A) {
|
fn send_to_inbox<A: Activity, S: Actor + Signer>(&self, conn: &PgConnection, sender: &S, act: A) {
|
||||||
|
let mut act = act.serialize();
|
||||||
|
let signed = act.sign(sender, conn);
|
||||||
let res = Client::new()
|
let res = Client::new()
|
||||||
.post(&self.compute_inbox(conn)[..])
|
.post(&self.compute_inbox(conn)[..])
|
||||||
.body(act.serialize().to_string())
|
.body(signed.to_string())
|
||||||
.send();
|
.send();
|
||||||
match res {
|
match res {
|
||||||
Ok(_) => println!("Successfully sent activity to inbox"),
|
Ok(_) => println!("Successfully sent activity to inbox"),
|
||||||
|
@ -3,6 +3,7 @@ use serde_json;
|
|||||||
|
|
||||||
use activity_pub::activity;
|
use activity_pub::activity;
|
||||||
use activity_pub::actor::Actor;
|
use activity_pub::actor::Actor;
|
||||||
|
use activity_pub::sign::*;
|
||||||
use models::blogs::Blog;
|
use models::blogs::Blog;
|
||||||
use models::follows::{Follow, NewFollow};
|
use models::follows::{Follow, NewFollow};
|
||||||
use models::posts::{Post, NewPost};
|
use models::posts::{Post, NewPost};
|
||||||
@ -45,13 +46,21 @@ pub trait Inbox: Actor + Sized {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn accept_follow<A: Actor, B: Actor, T: activity::Activity>(&self, conn: &PgConnection, from: &A, target: &B, follow: &T, from_id: i32, target_id: i32) {
|
fn accept_follow<A: Actor, B: Actor + Signer, T: activity::Activity>(
|
||||||
|
&self,
|
||||||
|
conn: &PgConnection,
|
||||||
|
from: &A,
|
||||||
|
target: &B,
|
||||||
|
follow: &T,
|
||||||
|
from_id: i32,
|
||||||
|
target_id: i32
|
||||||
|
) {
|
||||||
Follow::insert(conn, NewFollow {
|
Follow::insert(conn, NewFollow {
|
||||||
follower_id: from_id,
|
follower_id: from_id,
|
||||||
following_id: target_id
|
following_id: target_id
|
||||||
});
|
});
|
||||||
|
|
||||||
let accept = activity::Accept::new(target, follow, conn);
|
let accept = activity::Accept::new(target, follow, conn);
|
||||||
from.send_to_inbox(conn, accept)
|
from.send_to_inbox(conn, target, accept)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ use std::sync::Arc;
|
|||||||
use activity_pub::{activity_pub, ActivityPub, context};
|
use activity_pub::{activity_pub, ActivityPub, context};
|
||||||
use activity_pub::activity::Activity;
|
use activity_pub::activity::Activity;
|
||||||
use activity_pub::actor::Actor;
|
use activity_pub::actor::Actor;
|
||||||
|
use activity_pub::sign::Signer;
|
||||||
use models::users::User;
|
use models::users::User;
|
||||||
|
|
||||||
pub struct Outbox {
|
pub struct Outbox {
|
||||||
@ -41,8 +42,8 @@ impl<'r> Responder<'r> for Outbox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn broadcast<A: Activity + Clone>(conn: &PgConnection, act: A, to: Vec<User>) {
|
pub fn broadcast<A: Activity + Clone, S: Actor + Signer>(conn: &PgConnection, sender: &S, act: A, to: Vec<User>) {
|
||||||
for user in to {
|
for user in to {
|
||||||
user.send_to_inbox(conn, act.clone()); // TODO: run it in Sidekiq or something like that
|
user.send_to_inbox(conn, sender, act.clone()); // TODO: run it in Sidekiq or something like that
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,18 @@ use base64;
|
|||||||
use diesel::PgConnection;
|
use diesel::PgConnection;
|
||||||
use hex;
|
use hex;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
|
use openssl::pkey::PKey;
|
||||||
|
use openssl::rsa::Rsa;
|
||||||
use openssl::sha::sha256;
|
use openssl::sha::sha256;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
|
||||||
|
/// Returns (public key, private key)
|
||||||
|
pub fn gen_keypair() -> (Vec<u8>, Vec<u8>) {
|
||||||
|
let keypair = Rsa::generate(2048).unwrap();
|
||||||
|
let keypair = PKey::from_rsa(keypair).unwrap();
|
||||||
|
(keypair.public_key_to_pem().unwrap(), keypair.private_key_to_pem_pkcs8().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
pub trait Signer {
|
pub trait Signer {
|
||||||
fn get_key_id(&self, conn: &PgConnection) -> String;
|
fn get_key_id(&self, conn: &PgConnection) -> String;
|
||||||
|
|
||||||
@ -13,7 +22,7 @@ pub trait Signer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait Signable {
|
pub trait Signable {
|
||||||
fn sign<T>(&mut self, creator: T, conn: &PgConnection) -> &mut Self where T: Signer;
|
fn sign<T>(&mut self, creator: &T, conn: &PgConnection) -> &mut Self where T: Signer;
|
||||||
|
|
||||||
fn hash(data: String) -> String {
|
fn hash(data: String) -> String {
|
||||||
let bytes = data.into_bytes();
|
let bytes = data.into_bytes();
|
||||||
@ -22,7 +31,7 @@ pub trait Signable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Signable for serde_json::Value {
|
impl Signable for serde_json::Value {
|
||||||
fn sign<T: Signer>(&mut self, creator: T, conn: &PgConnection) -> &mut serde_json::Value {
|
fn sign<T: Signer>(&mut self, creator: &T, conn: &PgConnection) -> &mut serde_json::Value {
|
||||||
let creation_date = Utc::now().to_rfc3339();
|
let creation_date = Utc::now().to_rfc3339();
|
||||||
let mut options = json!({
|
let mut options = json!({
|
||||||
"type": "RsaSignature2017",
|
"type": "RsaSignature2017",
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use diesel::{self, QueryDsl, RunQueryDsl, ExpressionMethods, PgConnection};
|
use diesel::{self, QueryDsl, RunQueryDsl, ExpressionMethods, PgConnection};
|
||||||
|
use openssl::hash::MessageDigest;
|
||||||
|
use openssl::pkey::{PKey, Private};
|
||||||
|
use openssl::rsa::Rsa;
|
||||||
|
use openssl::sign::Signer;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use activity_pub::activity::Activity;
|
use activity_pub::activity::Activity;
|
||||||
use activity_pub::actor::{Actor, ActorType};
|
use activity_pub::actor::{Actor, ActorType};
|
||||||
use activity_pub::outbox::Outbox;
|
use activity_pub::outbox::Outbox;
|
||||||
|
use activity_pub::sign;
|
||||||
use activity_pub::webfinger::*;
|
use activity_pub::webfinger::*;
|
||||||
use models::instance::Instance;
|
use models::instance::Instance;
|
||||||
use schema::blogs;
|
use schema::blogs;
|
||||||
@ -20,7 +25,9 @@ pub struct Blog {
|
|||||||
pub inbox_url: String,
|
pub inbox_url: String,
|
||||||
pub instance_id: i32,
|
pub instance_id: i32,
|
||||||
pub creation_date: NaiveDateTime,
|
pub creation_date: NaiveDateTime,
|
||||||
pub ap_url: String
|
pub ap_url: String,
|
||||||
|
pub private_key: Option<String>,
|
||||||
|
pub public_key: String
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Insertable)]
|
#[derive(Insertable)]
|
||||||
@ -32,7 +39,9 @@ pub struct NewBlog {
|
|||||||
pub outbox_url: String,
|
pub outbox_url: String,
|
||||||
pub inbox_url: String,
|
pub inbox_url: String,
|
||||||
pub instance_id: i32,
|
pub instance_id: i32,
|
||||||
pub ap_url: String
|
pub ap_url: String,
|
||||||
|
pub private_key: Option<String>,
|
||||||
|
pub public_key: String
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Blog {
|
impl Blog {
|
||||||
@ -86,6 +95,10 @@ impl Blog {
|
|||||||
fn get_activities(&self, _conn: &PgConnection) -> Vec<Arc<Activity>> {
|
fn get_activities(&self, _conn: &PgConnection) -> Vec<Arc<Activity>> {
|
||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_keypair(&self) -> PKey<Private> {
|
||||||
|
PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.clone().unwrap().as_ref()).unwrap()).unwrap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Actor for Blog {
|
impl Actor for Blog {
|
||||||
@ -149,6 +162,19 @@ impl Webfinger for Blog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl sign::Signer for Blog {
|
||||||
|
fn get_key_id(&self, conn: &PgConnection) -> String {
|
||||||
|
format!("{}#main-key", self.compute_id(conn))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sign(&self, to_sign: String) -> Vec<u8> {
|
||||||
|
let key = self.get_keypair();
|
||||||
|
let mut signer = Signer::new(MessageDigest::sha256(), &key).unwrap();
|
||||||
|
signer.update(to_sign.as_bytes()).unwrap();
|
||||||
|
signer.sign_to_vec().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl NewBlog {
|
impl NewBlog {
|
||||||
pub fn new_local(
|
pub fn new_local(
|
||||||
actor_id: String,
|
actor_id: String,
|
||||||
@ -156,6 +182,7 @@ impl NewBlog {
|
|||||||
summary: String,
|
summary: String,
|
||||||
instance_id: i32
|
instance_id: i32
|
||||||
) -> NewBlog {
|
) -> NewBlog {
|
||||||
|
let (pub_key, priv_key) = sign::gen_keypair();
|
||||||
NewBlog {
|
NewBlog {
|
||||||
actor_id: actor_id,
|
actor_id: actor_id,
|
||||||
title: title,
|
title: title,
|
||||||
@ -163,7 +190,9 @@ impl NewBlog {
|
|||||||
outbox_url: String::from(""),
|
outbox_url: String::from(""),
|
||||||
inbox_url: String::from(""),
|
inbox_url: String::from(""),
|
||||||
instance_id: instance_id,
|
instance_id: instance_id,
|
||||||
ap_url: String::from("")
|
ap_url: String::from(""),
|
||||||
|
public_key: String::from_utf8(pub_key).unwrap(),
|
||||||
|
private_key: Some(String::from_utf8(priv_key).unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ use diesel::dsl::any;
|
|||||||
use openssl::hash::MessageDigest;
|
use openssl::hash::MessageDigest;
|
||||||
use openssl::pkey::{PKey, Private};
|
use openssl::pkey::{PKey, Private};
|
||||||
use openssl::rsa::Rsa;
|
use openssl::rsa::Rsa;
|
||||||
use openssl::sign::Signer;
|
use openssl::sign;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use reqwest::header::{Accept, qitem};
|
use reqwest::header::{Accept, qitem};
|
||||||
use reqwest::mime::Mime;
|
use reqwest::mime::Mime;
|
||||||
@ -20,7 +20,7 @@ use activity_pub::activity::{Create, Activity};
|
|||||||
use activity_pub::actor::{ActorType, Actor};
|
use activity_pub::actor::{ActorType, Actor};
|
||||||
use activity_pub::inbox::Inbox;
|
use activity_pub::inbox::Inbox;
|
||||||
use activity_pub::outbox::Outbox;
|
use activity_pub::outbox::Outbox;
|
||||||
use activity_pub::sign;
|
use activity_pub::sign::{Signer, gen_keypair};
|
||||||
use activity_pub::webfinger::{Webfinger, resolve};
|
use activity_pub::webfinger::{Webfinger, resolve};
|
||||||
use db_conn::DbConn;
|
use db_conn::DbConn;
|
||||||
use models::follows::Follow;
|
use models::follows::Follow;
|
||||||
@ -263,6 +263,16 @@ impl Actor for User {
|
|||||||
ActorType::Person
|
ActorType::Person
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn custom_props(&self, conn: &PgConnection) -> serde_json::Map<String, serde_json::Value> {
|
||||||
|
let mut res = serde_json::Map::new();
|
||||||
|
res.insert("publicKey".to_string(), json!({
|
||||||
|
"id": self.get_key_id(conn),
|
||||||
|
"owner": self.compute_id(conn),
|
||||||
|
"publicKeyPem": self.public_key
|
||||||
|
}));
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
fn from_url(conn: &PgConnection, url: String) -> Option<User> {
|
fn from_url(conn: &PgConnection, url: String) -> Option<User> {
|
||||||
let in_db = users::table.filter(users::ap_url.eq(url.clone()))
|
let in_db = users::table.filter(users::ap_url.eq(url.clone()))
|
||||||
.limit(1)
|
.limit(1)
|
||||||
@ -318,14 +328,14 @@ impl Webfinger for User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl sign::Signer for User {
|
impl Signer for User {
|
||||||
fn get_key_id(&self, conn: &PgConnection) -> String {
|
fn get_key_id(&self, conn: &PgConnection) -> String {
|
||||||
format!("{}#main-key", self.compute_id(conn))
|
format!("{}#main-key", self.compute_id(conn))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sign(&self, to_sign: String) -> Vec<u8> {
|
fn sign(&self, to_sign: String) -> Vec<u8> {
|
||||||
let key = self.get_keypair();
|
let key = self.get_keypair();
|
||||||
let mut signer = Signer::new(MessageDigest::sha256(), &key).unwrap();
|
let mut signer = sign::Signer::new(MessageDigest::sha256(), &key).unwrap();
|
||||||
signer.update(to_sign.as_bytes()).unwrap();
|
signer.update(to_sign.as_bytes()).unwrap();
|
||||||
signer.sign_to_vec().unwrap()
|
signer.sign_to_vec().unwrap()
|
||||||
}
|
}
|
||||||
@ -342,7 +352,7 @@ impl NewUser {
|
|||||||
password: String,
|
password: String,
|
||||||
instance_id: i32
|
instance_id: i32
|
||||||
) -> NewUser {
|
) -> NewUser {
|
||||||
let (pub_key, priv_key) = NewUser::gen_keypair();
|
let (pub_key, priv_key) = gen_keypair();
|
||||||
NewUser {
|
NewUser {
|
||||||
username: username,
|
username: username,
|
||||||
display_name: display_name,
|
display_name: display_name,
|
||||||
@ -358,11 +368,4 @@ impl NewUser {
|
|||||||
private_key: Some(String::from_utf8(priv_key).unwrap())
|
private_key: Some(String::from_utf8(priv_key).unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns (public key, private key)
|
|
||||||
fn gen_keypair() -> (Vec<u8>, Vec<u8>) {
|
|
||||||
let keypair = Rsa::generate(2048).unwrap();
|
|
||||||
let keypair = PKey::from_rsa(keypair).unwrap();
|
|
||||||
(keypair.public_key_to_pem().unwrap(), keypair.private_key_to_pem_pkcs8().unwrap())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ fn create(blog_name: String, data: Form<NewPostForm>, user: User, conn: DbConn)
|
|||||||
});
|
});
|
||||||
|
|
||||||
let act = Create::new(&user, &post, &*conn);
|
let act = Create::new(&user, &post, &*conn);
|
||||||
broadcast(&*conn, act, user.get_followers(&*conn));
|
broadcast(&*conn, &user, act, user.get_followers(&*conn));
|
||||||
|
|
||||||
Redirect::to(format!("/~/{}/{}", blog_name, slug).as_str())
|
Redirect::to(format!("/~/{}/{}", blog_name, slug).as_str())
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ fn follow(name: String, conn: DbConn, user: User) -> Redirect {
|
|||||||
follower_id: user.id,
|
follower_id: user.id,
|
||||||
following_id: target.id
|
following_id: target.id
|
||||||
});
|
});
|
||||||
target.send_to_inbox(&*conn, activity::Follow::new(&user, &target, &*conn));
|
target.send_to_inbox(&*conn, &user, activity::Follow::new(&user, &target, &*conn));
|
||||||
Redirect::to(format!("/@/{}", name).as_ref())
|
Redirect::to(format!("/@/{}", name).as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,8 @@ table! {
|
|||||||
instance_id -> Int4,
|
instance_id -> Int4,
|
||||||
creation_date -> Timestamp,
|
creation_date -> Timestamp,
|
||||||
ap_url -> Text,
|
ap_url -> Text,
|
||||||
|
private_key -> Nullable<Text>,
|
||||||
|
public_key -> Text,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user