Merge pull request #256 from Plume-org/verify-signature

Verify activity's signature
This commit is contained in:
Baptiste Gelez
2018-10-10 21:31:11 +01:00
committed by GitHub
9 changed files with 202 additions and 17 deletions
+5 -5
View File
@@ -2,8 +2,7 @@ use activitypub::{Activity, Actor, Object, Link};
use array_tool::vec::Uniq;
use reqwest::Client;
use rocket::{
Outcome,
http::Status,
Outcome, http::Status,
response::{Response, Responder},
request::{FromRequest, Request}
};
@@ -104,11 +103,12 @@ pub fn broadcast<S: sign::Signer, A: Activity, T: inbox::WithInbox + Actor>(send
for inbox in boxes {
// TODO: run it in Sidekiq or something like that
let mut headers = request::headers();
headers.set(request::Digest::digest(signed.to_string()));
let res = Client::new()
.post(&inbox[..])
.headers(request::headers())
.header(request::signature(sender, request::headers()))
.header(request::digest(signed.to_string()))
.headers(headers.clone())
.header(request::signature(sender, headers))
.body(signed.to_string())
.send();
match res {
+43 -7
View File
@@ -4,6 +4,7 @@ use reqwest::{
mime::Mime,
header::{Accept, Date, Headers, UserAgent, qitem}
};
use std::ops::Deref;
use std::time::SystemTime;
use activity_pub::ap_accept_header;
@@ -19,6 +20,48 @@ header! {
(Digest, "Digest") => [String]
}
impl Digest {
pub fn digest(body: String) -> Self {
let mut hasher = Hasher::new(MessageDigest::sha256()).unwrap();
hasher.update(&body.into_bytes()[..]).unwrap();
let res = base64::encode(&hasher.finish().unwrap());
Digest(format!("SHA-256={}", res))
}
pub fn verify(&self, body: String) -> bool {
if self.algorithm()=="SHA-256" {
let mut hasher = Hasher::new(MessageDigest::sha256()).unwrap();
hasher.update(&body.into_bytes()).unwrap();
self.value().deref()==hasher.finish().unwrap().deref()
} else {
false //algorithm not supported
}
}
pub fn algorithm(&self) -> &str {
let pos = self.0.find('=').unwrap();
&self.0[..pos]
}
pub fn value(&self) -> Vec<u8> {
let pos = self.0.find('=').unwrap()+1;
base64::decode(&self.0[pos..]).unwrap()
}
pub fn from_header(dig: &str) -> Result<Self, ()> {
if let Some(pos) = dig.find('=') {
let pos = pos+1;
if let Ok(_) = base64::decode(&dig[pos..]) {
Ok(Digest(dig.to_owned()))
} else {
Err(())
}
} else {
Err(())
}
}
}
pub fn headers() -> Headers {
let mut headers = Headers::new();
headers.set(UserAgent::new(USER_AGENT));
@@ -41,10 +84,3 @@ pub fn signature<S: Signer>(signer: &S, headers: Headers) -> Signature {
signature = sign
))
}
pub fn digest(body: String) -> Digest {
let mut hasher = Hasher::new(MessageDigest::sha256()).unwrap();
hasher.update(&body.into_bytes()[..]).unwrap();
let res = base64::encode(&hasher.finish().unwrap());
Digest(format!("SHA-256={}", res))
}
+87
View File
@@ -6,6 +6,8 @@ use openssl::{
rsa::Rsa,
sha::sha256
};
use super::request;
use rocket::http::HeaderMap;
use serde_json;
/// Returns (public key, private key)
@@ -20,10 +22,13 @@ pub trait Signer {
/// Sign some data with the signer keypair
fn sign(&self, to_sign: String) -> Vec<u8>;
/// Verify if the signature is valid
fn verify(&self, data: String, signature: Vec<u8>) -> bool;
}
pub trait Signable {
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 {
let bytes = data.into_bytes();
@@ -53,4 +58,86 @@ impl Signable for serde_json::Value {
self["signature"] = options;
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{
let sig_header = all_headers.get_one("Signature");
if sig_header.is_none() {
return SignatureValidity::Absent
}
let sig_header = sig_header.unwrap();
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_none() || headers.is_none() {//missing part of the header
return SignatureValidity::Invalid
}
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())) {
return SignatureValidity::Invalid
}
if !headers.contains(&"digest") {// signature is valid, but body content is not verified
return SignatureValidity::ValidNoDigest
}
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) {// signature was valid, but body content does not match its digest
SignatureValidity::Invalid
} else {
SignatureValidity::Valid// all check passed
}
}