Implement JSON-ld signature verification
Implement JSON-ld signature verification Move signature verification functions to the proper file
This commit is contained in:
parent
62c94ed463
commit
3466e55548
|
@ -1,10 +1,8 @@
|
||||||
use activitypub::{Activity, Actor, Object, Link};
|
use activitypub::{Activity, Actor, Object, Link};
|
||||||
use array_tool::vec::Uniq;
|
use array_tool::vec::Uniq;
|
||||||
use base64;
|
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use rocket::{
|
use rocket::{
|
||||||
Outcome,
|
Outcome, http::Status,
|
||||||
http::{Status,HeaderMap},
|
|
||||||
response::{Response, Responder},
|
response::{Response, Responder},
|
||||||
request::{FromRequest, Request}
|
request::{FromRequest, Request}
|
||||||
};
|
};
|
||||||
|
@ -120,68 +118,6 @@ pub fn broadcast<S: sign::Signer, A: Activity, T: inbox::WithInbox + Actor>(send
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum SignatureValidity {
|
|
||||||
Invalid,
|
|
||||||
ValidNoDigest,
|
|
||||||
Valid,
|
|
||||||
Absent,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SignatureValidity {
|
|
||||||
pub fn is_secure(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
SignatureValidity::Valid => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn verify_http_headers<S: sign::Signer+::std::fmt::Debug>(sender: &S, all_headers: HeaderMap, data: String) -> SignatureValidity{
|
|
||||||
if let Some(sig_header) = all_headers.get_one("Signature") {
|
|
||||||
let mut _key_id = None;
|
|
||||||
let mut _algorithm = None;
|
|
||||||
let mut headers = None;
|
|
||||||
let mut signature = None;
|
|
||||||
for part in sig_header.split(',') {
|
|
||||||
match part {
|
|
||||||
part if part.starts_with("keyId=") => _key_id = Some(&part[7..part.len()-1]),
|
|
||||||
part if part.starts_with("algorithm=") => _algorithm = Some(&part[11..part.len()-1]),
|
|
||||||
part if part.starts_with("headers=") => headers = Some(&part[9..part.len()-1]),
|
|
||||||
part if part.starts_with("signature=") => signature = Some(&part[11..part.len()-1]),
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if signature.is_some() && headers.is_some() {
|
|
||||||
let headers = headers.unwrap().split_whitespace().collect::<Vec<_>>();
|
|
||||||
let signature = signature.unwrap();
|
|
||||||
let h = headers.iter()
|
|
||||||
.map(|header| (header,all_headers.get_one(header)))
|
|
||||||
.map(|(header, value)| format!("{}: {}", header.to_lowercase(), value.unwrap_or("")))
|
|
||||||
.collect::<Vec<_>>().join("\n");
|
|
||||||
if sender.verify(h, base64::decode(signature).unwrap_or(Vec::new())) {
|
|
||||||
if headers.contains(&"digest") {
|
|
||||||
let digest = all_headers.get_one("digest").unwrap_or("");
|
|
||||||
let digest = request::Digest::from_header(digest);
|
|
||||||
if digest.map(|d| d.verify(data)).unwrap_or(false) {
|
|
||||||
SignatureValidity::Valid
|
|
||||||
} else {
|
|
||||||
SignatureValidity::Invalid
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
SignatureValidity::ValidNoDigest
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
SignatureValidity::Invalid
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
SignatureValidity::Invalid
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
SignatureValidity::Absent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
pub struct Id(String);
|
pub struct Id(String);
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,8 @@ use openssl::{
|
||||||
rsa::Rsa,
|
rsa::Rsa,
|
||||||
sha::sha256
|
sha::sha256
|
||||||
};
|
};
|
||||||
|
use super::request;
|
||||||
|
use rocket::http::HeaderMap;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
|
||||||
/// Returns (public key, private key)
|
/// Returns (public key, private key)
|
||||||
|
@ -26,6 +28,7 @@ pub trait Signer {
|
||||||
|
|
||||||
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) -> &mut Self where T: Signer;
|
||||||
|
fn verify<T>(self, creator: &T) -> bool where T: Signer;
|
||||||
|
|
||||||
fn hash(data: String) -> String {
|
fn hash(data: String) -> String {
|
||||||
let bytes = data.into_bytes();
|
let bytes = data.into_bytes();
|
||||||
|
@ -55,4 +58,85 @@ impl Signable for serde_json::Value {
|
||||||
self["signature"] = options;
|
self["signature"] = options;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn verify<T: Signer>(mut self, creator: &T) -> bool {
|
||||||
|
let signature_obj = if let Some(sig) = self.as_object_mut().and_then(|o| o.remove("signature")) {
|
||||||
|
sig
|
||||||
|
} else {
|
||||||
|
//signature not present
|
||||||
|
return false
|
||||||
|
};
|
||||||
|
let signature = if let Ok(sig) = base64::decode(&signature_obj["signatureValue"].as_str().unwrap_or("")) {
|
||||||
|
sig
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
};
|
||||||
|
let creation_date = &signature_obj["created"];
|
||||||
|
let options_hash = Self::hash(json!({
|
||||||
|
"@context": "https://w3id.org/identity/v1",
|
||||||
|
"created": creation_date
|
||||||
|
}).to_string());
|
||||||
|
let document_hash = Self::hash(self.to_string());
|
||||||
|
let to_be_signed = options_hash + &document_hash;
|
||||||
|
creator.verify(to_be_signed, signature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug,Copy,Clone,PartialEq)]
|
||||||
|
pub enum SignatureValidity {
|
||||||
|
Invalid,
|
||||||
|
ValidNoDigest,
|
||||||
|
Valid,
|
||||||
|
Absent,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SignatureValidity {
|
||||||
|
pub fn is_secure(&self) -> bool {
|
||||||
|
self==&SignatureValidity::Valid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_http_headers<S: Signer+::std::fmt::Debug>(sender: &S, all_headers: HeaderMap, data: String) -> SignatureValidity{
|
||||||
|
if let Some(sig_header) = all_headers.get_one("Signature") {
|
||||||
|
let mut _key_id = None;
|
||||||
|
let mut _algorithm = None;
|
||||||
|
let mut headers = None;
|
||||||
|
let mut signature = None;
|
||||||
|
for part in sig_header.split(',') {
|
||||||
|
match part {
|
||||||
|
part if part.starts_with("keyId=") => _key_id = Some(&part[7..part.len()-1]),
|
||||||
|
part if part.starts_with("algorithm=") => _algorithm = Some(&part[11..part.len()-1]),
|
||||||
|
part if part.starts_with("headers=") => headers = Some(&part[9..part.len()-1]),
|
||||||
|
part if part.starts_with("signature=") => signature = Some(&part[11..part.len()-1]),
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if signature.is_some() && headers.is_some() {
|
||||||
|
let headers = headers.unwrap().split_whitespace().collect::<Vec<_>>();
|
||||||
|
let signature = signature.unwrap();
|
||||||
|
let h = headers.iter()
|
||||||
|
.map(|header| (header,all_headers.get_one(header)))
|
||||||
|
.map(|(header, value)| format!("{}: {}", header.to_lowercase(), value.unwrap_or("")))
|
||||||
|
.collect::<Vec<_>>().join("\n");
|
||||||
|
if sender.verify(h, base64::decode(signature).unwrap_or(Vec::new())) {
|
||||||
|
if headers.contains(&"digest") {
|
||||||
|
let digest = all_headers.get_one("digest").unwrap_or("");
|
||||||
|
let digest = request::Digest::from_header(digest);
|
||||||
|
if digest.map(|d| d.verify(data)).unwrap_or(false) {
|
||||||
|
SignatureValidity::Valid
|
||||||
|
} else {
|
||||||
|
SignatureValidity::Invalid
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SignatureValidity::ValidNoDigest
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SignatureValidity::Invalid
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SignatureValidity::Invalid
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SignatureValidity::Absent
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,8 @@ use rocket_contrib::{Json, Template};
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use validator::{Validate};
|
use validator::{Validate};
|
||||||
|
|
||||||
use plume_common::activity_pub::verify_http_headers;
|
use plume_common::activity_pub::sign::{Signable,
|
||||||
|
verify_http_headers};
|
||||||
use plume_models::{
|
use plume_models::{
|
||||||
admin::Admin,
|
admin::Admin,
|
||||||
comments::Comment,
|
comments::Comment,
|
||||||
|
@ -198,9 +199,9 @@ fn shared_inbox(conn: DbConn, data: String, headers: Headers) -> String {
|
||||||
let actor_id = activity["actor"].as_str()
|
let actor_id = activity["actor"].as_str()
|
||||||
.unwrap_or_else(|| activity["actor"]["id"].as_str().expect("No actor ID for incoming activity, blocks by panicking"));
|
.unwrap_or_else(|| activity["actor"]["id"].as_str().expect("No actor ID for incoming activity, blocks by panicking"));
|
||||||
|
|
||||||
let sig = verify_http_headers(&User::from_url(&conn, actor_id.to_owned()).unwrap(), headers.0, data).is_secure();
|
let actor = User::from_url(&conn, actor_id.to_owned()).unwrap();
|
||||||
if !sig {
|
if !verify_http_headers(&actor, headers.0, data).is_secure() &&
|
||||||
// TODO check for valid json-ld signature
|
!act.clone().verify(&actor) {
|
||||||
return "invalid signature".to_owned();
|
return "invalid signature".to_owned();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,8 @@ use workerpool::thunk::*;
|
||||||
|
|
||||||
use plume_common::activity_pub::{
|
use plume_common::activity_pub::{
|
||||||
ActivityStream, broadcast, Id, IntoId, ApRequest,
|
ActivityStream, broadcast, Id, IntoId, ApRequest,
|
||||||
verify_http_headers, inbox::{FromActivity, Notify, Deletable}
|
inbox::{FromActivity, Notify, Deletable},
|
||||||
|
sign::{Signable, verify_http_headers}
|
||||||
};
|
};
|
||||||
use plume_common::utils;
|
use plume_common::utils;
|
||||||
use plume_models::{
|
use plume_models::{
|
||||||
|
@ -304,9 +305,9 @@ fn inbox(name: String, conn: DbConn, data: String, headers: Headers) -> String {
|
||||||
let actor_id = activity["actor"].as_str()
|
let actor_id = activity["actor"].as_str()
|
||||||
.unwrap_or_else(|| activity["actor"]["id"].as_str().expect("User: No actor ID for incoming activity, blocks by panicking"));
|
.unwrap_or_else(|| activity["actor"]["id"].as_str().expect("User: No actor ID for incoming activity, blocks by panicking"));
|
||||||
|
|
||||||
let sig = verify_http_headers(&User::from_url(&conn, actor_id.to_owned()).unwrap(), headers.0, data).is_secure();
|
let actor = User::from_url(&conn, actor_id.to_owned()).unwrap();
|
||||||
if !sig {
|
if !verify_http_headers(&actor, headers.0, data).is_secure() &&
|
||||||
// TODO check for json-ld signature
|
!act.clone().verify(&actor) {
|
||||||
return "invalid signature".to_owned();
|
return "invalid signature".to_owned();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue