WIP: make it possible for users to sign activities
This commit is contained in:
parent
b844257e34
commit
6b372861d6
3
migrations/2018-05-03-163427_user_add_keys/down.sql
Normal file
3
migrations/2018-05-03-163427_user_add_keys/down.sql
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
-- This file should undo anything in `up.sql`
|
||||||
|
ALTER TABLE users DROP COLUMN private_key;
|
||||||
|
ALTER TABLE users DROP COLUMN public_key;
|
3
migrations/2018-05-03-163427_user_add_keys/up.sql
Normal file
3
migrations/2018-05-03-163427_user_add_keys/up.sql
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
-- Your SQL goes here
|
||||||
|
ALTER TABLE users ADD COLUMN private_key TEXT;
|
||||||
|
ALTER TABLE users ADD COLUMN public_key TEXT NOT NULL DEFAULT '';
|
@ -1,20 +1,19 @@
|
|||||||
use base64;
|
use base64;
|
||||||
|
use diesel::PgConnection;
|
||||||
use hex;
|
use hex;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use openssl::sha::sha256;
|
use openssl::sha::sha256;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
|
||||||
// (Comments are from the Mastodon source code, to remember what to do.)
|
|
||||||
|
|
||||||
pub trait Signer {
|
pub trait Signer {
|
||||||
fn get_key_id(&self) -> String;
|
fn get_key_id(&self, conn: &PgConnection) -> String;
|
||||||
|
|
||||||
/// Sign some data with the signer keypair
|
/// Sign some data with the signer keypair
|
||||||
fn sign(&self, to_sign: String) -> String; // Base64.strict_encode64(creator.keypair.sign(OpenSSL::Digest::SHA256.new, to_be_signed))
|
fn sign(&self, to_sign: String) -> Vec<u8>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Signable {
|
pub trait Signable {
|
||||||
fn sign<T>(&mut self, creator: T) -> &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();
|
||||||
@ -23,15 +22,18 @@ pub trait Signable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Signable for serde_json::Value {
|
impl Signable for serde_json::Value {
|
||||||
fn sign<T>(&mut self, creator: T) -> &mut serde_json::Value where T: Signer {
|
fn sign<T: Signer>(&mut self, creator: T, conn: &PgConnection) -> &mut serde_json::Value {
|
||||||
|
let creation_date = Utc::now().to_rfc3339();
|
||||||
let mut options = json!({
|
let mut options = json!({
|
||||||
"type": "RsaSignature2017",
|
"type": "RsaSignature2017",
|
||||||
"creator": creator.get_key_id(), // [ActivityPub::TagManager.instance.uri_for(creator), '#main-key'].join,
|
"creator": creator.get_key_id(conn),
|
||||||
"created": Utc::now().to_rfc3339()
|
"created": creation_date
|
||||||
});
|
});
|
||||||
|
|
||||||
//options_hash = hash(options.without('type', 'id', 'signatureValue').merge('@context' => CONTEXT))
|
let options_hash = Self::hash(json!({
|
||||||
let options_hash = Self::hash(String::from(""));
|
"@context": "https://w3id.org/identity/v1",
|
||||||
|
"created": creation_date
|
||||||
|
}).to_string());
|
||||||
let document_hash = Self::hash(self.to_string());
|
let document_hash = Self::hash(self.to_string());
|
||||||
let to_be_signed = options_hash + &document_hash;
|
let to_be_signed = options_hash + &document_hash;
|
||||||
|
|
||||||
|
@ -2,6 +2,10 @@ use bcrypt;
|
|||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use diesel::{self, QueryDsl, RunQueryDsl, ExpressionMethods, BelongingToDsl, PgConnection};
|
use diesel::{self, QueryDsl, RunQueryDsl, ExpressionMethods, BelongingToDsl, PgConnection};
|
||||||
use diesel::dsl::any;
|
use diesel::dsl::any;
|
||||||
|
use openssl::hash::MessageDigest;
|
||||||
|
use openssl::pkey::{PKey, Private};
|
||||||
|
use openssl::rsa::Rsa;
|
||||||
|
use openssl::sign::Signer;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use reqwest::header::{Accept, qitem};
|
use reqwest::header::{Accept, qitem};
|
||||||
use reqwest::mime::Mime;
|
use reqwest::mime::Mime;
|
||||||
@ -16,6 +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::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;
|
||||||
@ -39,7 +44,9 @@ pub struct User {
|
|||||||
pub hashed_password: Option<String>,
|
pub hashed_password: Option<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)]
|
||||||
@ -54,7 +61,9 @@ pub struct NewUser {
|
|||||||
pub email: Option<String>,
|
pub email: Option<String>,
|
||||||
pub hashed_password: Option<String>,
|
pub hashed_password: Option<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 User {
|
impl User {
|
||||||
@ -153,7 +162,9 @@ impl User {
|
|||||||
email: None,
|
email: None,
|
||||||
hashed_password: None,
|
hashed_password: None,
|
||||||
instance_id: instance.id,
|
instance_id: instance.id,
|
||||||
ap_url: acct["id"].as_str().unwrap().to_string()
|
ap_url: acct["id"].as_str().unwrap().to_string(),
|
||||||
|
public_key: acct["publicKey"]["publicKeyPem"].as_str().unwrap().to_string(),
|
||||||
|
private_key: None
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,6 +219,10 @@ impl User {
|
|||||||
let follows = follows::table.filter(follows::follower_id.eq(self.id)).select(follows::following_id);
|
let follows = follows::table.filter(follows::follower_id.eq(self.id)).select(follows::following_id);
|
||||||
users::table.filter(users::id.eq(any(follows))).load::<User>(conn).unwrap()
|
users::table.filter(users::id.eq(any(follows))).load::<User>(conn).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<'a, 'r> FromRequest<'a, 'r> for User {
|
impl<'a, 'r> FromRequest<'a, 'r> for User {
|
||||||
@ -303,6 +318,19 @@ impl Webfinger for User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl sign::Signer for User {
|
||||||
|
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 NewUser {
|
impl NewUser {
|
||||||
/// Creates a new local user
|
/// Creates a new local user
|
||||||
pub fn new_local(
|
pub fn new_local(
|
||||||
@ -314,6 +342,7 @@ impl NewUser {
|
|||||||
password: String,
|
password: String,
|
||||||
instance_id: i32
|
instance_id: i32
|
||||||
) -> NewUser {
|
) -> NewUser {
|
||||||
|
let (pub_key, priv_key) = NewUser::gen_keypair();
|
||||||
NewUser {
|
NewUser {
|
||||||
username: username,
|
username: username,
|
||||||
display_name: display_name,
|
display_name: display_name,
|
||||||
@ -324,7 +353,16 @@ impl NewUser {
|
|||||||
email: Some(email),
|
email: Some(email),
|
||||||
hashed_password: Some(password),
|
hashed_password: Some(password),
|
||||||
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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,6 +75,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