Update sig lib, forward more error context, better blind key rotation support

This commit is contained in:
asonix 2020-04-22 17:41:01 -05:00
parent ee5c259c2e
commit a528046a68
6 changed files with 114 additions and 56 deletions

28
Cargo.lock generated
View file

@ -653,9 +653,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.0.50"
version = "1.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd"
checksum = "c3d87b23d6a92cd03af510a5ade527033f6aa6fa92161e2d5863a907d4c5e31d"
[[package]]
name = "cfg-if"
@ -1192,9 +1192,9 @@ dependencies = [
[[package]]
name = "http-signature-normalization"
version = "0.4.1"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "257835255b5d40c6de712d90e56dc874ca5da2816121e7b9f3cfc7b3a55a5714"
checksum = "fde6b0321d465ea2cdc18b5d5ec73eee2ff20177ecee126cd8dd997f6c49e088"
dependencies = [
"chrono",
"thiserror",
@ -1202,9 +1202,9 @@ dependencies = [
[[package]]
name = "http-signature-normalization-actix"
version = "0.3.0-alpha.9"
version = "0.3.0-alpha.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "195de65fba66cf11426765b07fde96553f02c9a982f760d9e736330cc2e7f07f"
checksum = "31cbaa36c8b370b423bc69bdbe146bfc52d32ad3e38a91281f1a4ac0b1762274"
dependencies = [
"actix-http",
"actix-web",
@ -1765,9 +1765,9 @@ checksum = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae"
[[package]]
name = "pin-utils"
version = "0.1.0-alpha.4"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "postgres-protocol"
@ -2092,9 +2092,9 @@ dependencies = [
[[package]]
name = "ructe"
version = "0.11.0"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bfdbe059afc8a41171d0c9f362c2bd67e03131f2d71939feef0a26b0a765b0"
checksum = "15be061bce86758995098b361a5d3ed2b4203f658dffd522a1ee680b457418b2"
dependencies = [
"base64 0.12.0",
"bytecount",
@ -2438,9 +2438,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "structopt"
version = "0.3.13"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff6da2e8d107dfd7b74df5ef4d205c6aebee0706c647f6bc6a2d5789905c00fb"
checksum = "863246aaf5ddd0d6928dfeb1a9ca65f505599e4e1b399935ef7e75107516b4ef"
dependencies = [
"clap",
"lazy_static",
@ -2449,9 +2449,9 @@ dependencies = [
[[package]]
name = "structopt-derive"
version = "0.4.6"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a489c87c08fbaf12e386665109dd13470dcc9c4583ea3e10dd2b4523e5ebd9ac"
checksum = "d239ca4b13aee7a2142e6795cbd69e457665ff8037aed33b3effdc430d2f927a"
dependencies = [
"heck",
"proc-macro-error",

View file

@ -30,7 +30,7 @@ deadpool-postgres = "0.5.5"
dotenv = "0.15.0"
env_logger = "0.7.1"
futures = "0.3.4"
http-signature-normalization-actix = { version = "0.3.0-alpha.9", default-features = false, features = ["sha-2"] }
http-signature-normalization-actix = { version = "0.3.0-alpha.10", default-features = false, features = ["sha-2"] }
log = "0.4"
lru = "0.4.3"
mime = "0.3.16"

View file

@ -8,6 +8,27 @@ use uuid::Uuid;
const REFETCH_DURATION: Duration = Duration::from_secs(60 * 30);
#[derive(Debug)]
pub enum MaybeCached<T> {
Cached(T),
Fetched(T),
}
impl<T> MaybeCached<T> {
pub fn is_cached(&self) -> bool {
match self {
MaybeCached::Cached(_) => true,
_ => false,
}
}
pub fn into_inner(self) -> T {
match self {
MaybeCached::Cached(t) | MaybeCached::Fetched(t) => t,
}
}
}
#[derive(Clone)]
pub struct ActorCache {
db: Db,
@ -32,19 +53,11 @@ impl ActorCache {
self.following.read().await.contains(id)
}
pub async fn get(&self, id: &XsdAnyUri, requests: &Requests) -> Result<Actor, MyError> {
if let Some(actor) = self.cache.read().await.get(id) {
return Ok(actor.clone());
}
if let Some(actor) = self.lookup(id).await? {
self.cache
.write()
.await
.insert(id.clone(), actor.clone(), REFETCH_DURATION);
return Ok(actor);
}
pub async fn get_no_cache(
&self,
id: &XsdAnyUri,
requests: &Requests,
) -> Result<Actor, MyError> {
let accepted_actor = requests.fetch::<AcceptedActors>(id.as_str()).await?;
let input_host = id.as_url().host();
@ -85,6 +98,28 @@ impl ActorCache {
Ok(actor)
}
pub async fn get(
&self,
id: &XsdAnyUri,
requests: &Requests,
) -> Result<MaybeCached<Actor>, MyError> {
if let Some(actor) = self.cache.read().await.get(id) {
return Ok(MaybeCached::Cached(actor.clone()));
}
if let Some(actor) = self.lookup(id).await? {
self.cache
.write()
.await
.insert(id.clone(), actor.clone(), REFETCH_DURATION);
return Ok(MaybeCached::Cached(actor));
}
self.get_no_cache(id, requests)
.await
.map(MaybeCached::Fetched)
}
pub async fn follower(&self, actor: &Actor) -> Result<(), MyError> {
self.save(actor.clone()).await
}

View file

@ -80,8 +80,8 @@ pub enum MyError {
#[error("Timed out while waiting on db pool, {0:?}")]
DbTimeout(TimeoutType),
#[error("Invalid algorithm provided to verifier")]
Algorithm,
#[error("Invalid algorithm provided to verifier, {0}")]
Algorithm(String),
#[error("Object has already been relayed")]
Duplicate,

View file

@ -2,7 +2,7 @@ use crate::{data::ActorCache, error::MyError, requests::Requests};
use activitystreams::primitives::XsdAnyUri;
use actix_web::web;
use http_signature_normalization_actix::{prelude::*, verify::DeprecatedAlgorithm};
use log::{error, warn};
use log::error;
use rsa::{hash::Hashes, padding::PaddingScheme, PublicKey, RSAPublicKey};
use rsa_pem::KeyExt;
use sha2::{Digest, Sha256};
@ -22,37 +22,64 @@ impl MyVerify {
let mut uri: XsdAnyUri = key_id.parse()?;
uri.as_url_mut().set_fragment(None);
let actor = self.1.get(&uri, &self.0).await?;
let public_key = RSAPublicKey::from_pem_pkcs8(&actor.public_key)?;
let was_cached = actor.is_cached();
let actor = actor.into_inner();
match algorithm {
Some(Algorithm::Hs2019) => (),
Some(Algorithm::Deprecated(DeprecatedAlgorithm::RsaSha256)) => (),
other => {
warn!("Invalid algorithm supplied for signature, {:?}", other);
return Err(MyError::Algorithm);
Some(other) => {
return Err(MyError::Algorithm(other.to_string()));
}
None => (),
};
web::block(move || {
let decoded = base64::decode(signature)?;
let hashed = Sha256::digest(signing_string.as_bytes());
let res = do_verify(&actor.public_key, signature.clone(), signing_string.clone()).await;
public_key.verify(
PaddingScheme::PKCS1v15,
Some(&Hashes::SHA2_256),
&hashed,
&decoded,
)?;
if let Err(e) = res {
if !was_cached {
return Err(e);
}
} else {
return Ok(true);
}
Ok(()) as Result<(), MyError>
})
.await?;
// Previously we verified the sig from an actor's local cache
//
// Now we make sure we fetch an updated actor
let actor = self.1.get_no_cache(&uri, &self.0).await?;
do_verify(&actor.public_key, signature, signing_string).await?;
Ok(true)
}
}
async fn do_verify(
public_key: &str,
signature: String,
signing_string: String,
) -> Result<(), MyError> {
let public_key = RSAPublicKey::from_pem_pkcs8(public_key)?;
web::block(move || {
let decoded = base64::decode(signature)?;
let hashed = Sha256::digest(signing_string.as_bytes());
public_key.verify(
PaddingScheme::PKCS1v15,
Some(&Hashes::SHA2_256),
&hashed,
&decoded,
)?;
Ok(()) as Result<(), MyError>
})
.await?;
Ok(())
}
impl SignatureVerify for MyVerify {
type Error = MyError;
type Future = Pin<Box<dyn Future<Output = Result<bool, Self::Error>>>>;
@ -60,14 +87,10 @@ impl SignatureVerify for MyVerify {
fn signature_verify(
&mut self,
algorithm: Option<Algorithm>,
key_id: &str,
signature: &str,
signing_string: &str,
key_id: String,
signature: String,
signing_string: String,
) -> Self::Future {
let key_id = key_id.to_owned();
let signature = signature.to_owned();
let signing_string = signing_string.to_owned();
let this = self.clone();
Box::pin(async move {

View file

@ -26,7 +26,7 @@ pub async fn route(
) -> Result<HttpResponse, MyError> {
let input = input.into_inner();
let actor = actors.get(&input.actor, &client).await?;
let actor = actors.get(&input.actor, &client).await?.into_inner();
let (is_blocked, is_whitelisted, is_listener) = join!(
state.is_blocked(&actor.id),