Verify signed activities

This commit is contained in:
silverpill 2022-10-23 23:46:51 +00:00
parent 077d942573
commit 367e3f37c7
5 changed files with 187 additions and 14 deletions

View file

@ -1,4 +1,5 @@
use actix_web::HttpRequest;
use serde_json::Value;
use tokio_postgres::GenericClient;
use crate::config::Config;
@ -8,6 +9,11 @@ use crate::http_signatures::verify::{
verify_http_signature,
HttpSignatureVerificationError as HttpSignatureError,
};
use crate::json_signatures::verify::{
get_json_signature,
verify_json_signature,
JsonSignatureVerificationError as JsonSignatureError,
};
use crate::models::profiles::queries::get_profile_by_remote_actor_id;
use crate::models::profiles::types::DbActorProfile;
use crate::utils::crypto::deserialize_public_key;
@ -19,6 +25,12 @@ pub enum AuthenticationError {
#[error(transparent)]
HttpSignatureError(#[from] HttpSignatureError),
#[error(transparent)]
JsonSignatureError(#[from] JsonSignatureError),
#[error("no JSON signature")]
NoJsonSignature,
#[error("invalid key ID")]
InvalidKeyId(#[from] url::ParseError),
@ -83,6 +95,39 @@ pub async fn verify_signed_request(
Ok(actor_profile)
}
pub async fn verify_signed_activity(
config: &Config,
db_client: &impl GenericClient,
activity: &Value,
) -> Result<DbActorProfile, AuthenticationError> {
let signature_data = get_json_signature(activity).map_err(|error| {
match error {
JsonSignatureError::NoProof => AuthenticationError::NoJsonSignature,
other_error => other_error.into(),
}
})?;
let actor_id = key_id_to_actor_id(&signature_data.key_id)?;
let actor_profile = match get_or_import_profile_by_actor_id(
db_client,
&config.instance(),
&config.media_dir(),
&actor_id,
).await {
Ok(profile) => profile,
Err(HandlerError::DatabaseError(error)) => return Err(error.into()),
Err(other_error) => {
return Err(AuthenticationError::ActorError(other_error.to_string()));
},
};
let actor = actor_profile.actor_json.as_ref()
.ok_or(AuthenticationError::ActorError("invalid profile".to_string()))?;
let public_key = deserialize_public_key(&actor.public_key.public_key_pem)?;
verify_json_signature(&signature_data, &public_key)?;
Ok(actor_profile)
}
#[cfg(test)]
mod tests {
use super::*;

View file

@ -12,6 +12,7 @@ use crate::errors::{
};
use super::activity::{Activity, Object};
use super::authentication::{
verify_signed_activity,
verify_signed_request,
AuthenticationError,
};
@ -171,12 +172,25 @@ pub async fn receive_activity(
// Ignore Delete(Person) activities without HTTP signatures
return Ok(());
};
log::warn!("invalid signature: {}", error);
log::warn!("invalid HTTP signature: {}", error);
return Err(error.into());
},
};
let signer_id = signer.actor_id(&config.instance_url());
log::debug!("activity signed by {}", signer_id);
log::debug!("request signed by {}", signer_id);
// Verify embedded signature
match verify_signed_activity(config, db_client, activity_raw).await {
Ok(signer) => {
let signer_id = signer.actor_id(&config.instance_url());
log::info!("activity signed by {}", signer_id);
},
Err(AuthenticationError::NoJsonSignature) => (), // ignore
Err(other_error) => {
log::error!("invalid JSON signature: {}", other_error);
},
};
if config.blocked_instances.iter()
.any(|instance| signer.hostname.as_ref() == Some(instance))
{

View file

@ -1,28 +1,28 @@
use rsa::RsaPrivateKey;
use serde::Serialize;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::utils::crypto::sign_message;
/// Data Integrity Proof
/// https://w3c.github.io/vc-data-integrity/
#[derive(Serialize)]
#[derive(Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
struct Proof {
pub struct Proof {
#[serde(rename = "type")]
proof_type: String,
proof_purpose: String,
verification_method: String,
proof_value: String,
pub proof_type: String,
pub proof_purpose: String,
pub verification_method: String,
pub proof_value: String,
}
// Similar to https://identity.foundation/JcsEd25519Signature2020/
// - Canonicalization algorithm: JCS
// - Digest algorithm: SHA-256
// - Signature algorithm: RSASSA-PKCS1-v1_5
const PROOF_TYPE: &str = "JcsRsaSignature2022";
pub const PROOF_TYPE: &str = "JcsRsaSignature2022";
const PROOF_PURPOSE: &str = "assertionMethod";
pub const PROOF_PURPOSE: &str = "assertionMethod";
#[derive(thiserror::Error, Debug)]
pub enum JsonSignatureError {
@ -32,8 +32,8 @@ pub enum JsonSignatureError {
#[error("signing error")]
SigningError(#[from] rsa::errors::Error),
#[error("invalid value")]
InvalidValue,
#[error("invalid object")]
InvalidObject,
}
pub fn sign_object(
@ -56,7 +56,7 @@ pub fn sign_object(
let proof_value = serde_json::to_value(proof)?;
let mut object_value = serde_json::to_value(object)?;
let object_map = object_value.as_object_mut()
.ok_or(JsonSignatureError::InvalidValue)?;
.ok_or(JsonSignatureError::InvalidObject)?;
object_map.insert("proof".to_string(), proof_value);
Ok(object_value)
}

View file

@ -1 +1,2 @@
pub mod create;
pub mod verify;

View file

@ -0,0 +1,113 @@
use rsa::RsaPublicKey;
use serde_json::Value;
use crate::utils::crypto::verify_signature;
use super::create::{Proof, PROOF_TYPE, PROOF_PURPOSE};
pub struct SignatureData {
pub key_id: String,
pub message: String,
pub signature: String,
}
#[derive(thiserror::Error, Debug)]
pub enum JsonSignatureVerificationError {
#[error("invalid object")]
InvalidObject,
#[error("no proof")]
NoProof,
#[error("{0}")]
InvalidProof(&'static str),
#[error("canonicalization error")]
CanonicalizationError,
#[error("invalid encoding")]
InvalidEncoding(#[from] base64::DecodeError),
#[error("invalid signature")]
InvalidSignature,
}
type VerificationError = JsonSignatureVerificationError;
pub fn get_json_signature(
object: &Value,
) -> Result<SignatureData, VerificationError> {
let mut object = object.clone();
let object_map = object.as_object_mut()
.ok_or(VerificationError::InvalidObject)?;
let proof_value = object_map.remove("proof")
.ok_or(VerificationError::NoProof)?;
let proof: Proof = serde_json::from_value(proof_value)
.map_err(|_| VerificationError::InvalidProof("invalid proof"))?;
if proof.proof_type != PROOF_TYPE ||
proof.proof_purpose != PROOF_PURPOSE
{
return Err(VerificationError::InvalidProof("invalid proof"));
};
let canon = serde_jcs::to_string(&object)
.map_err(|_| VerificationError::CanonicalizationError)?;
let signature_data = SignatureData {
key_id: proof.verification_method,
message: canon,
signature: proof.proof_value,
};
Ok(signature_data)
}
pub fn verify_json_signature(
signature_data: &SignatureData,
signer_key: &RsaPublicKey,
) -> Result<(), VerificationError> {
let is_valid_signature = verify_signature(
signer_key,
&signature_data.message,
&signature_data.signature,
)?;
if !is_valid_signature {
return Err(VerificationError::InvalidSignature);
};
Ok(())
}
#[cfg(test)]
mod tests {
use serde_json::json;
use crate::json_signatures::create::sign_object;
use crate::utils::crypto::generate_weak_private_key;
use super::*;
#[test]
fn test_create_and_verify_signature() {
let signer_key = generate_weak_private_key().unwrap();
let signer_key_id = "https://example.org/users/test#main-key";
let object = json!({
"type": "Create",
"actor": "https://example.org/users/test",
"id": "https://example.org/objects/1",
"to": [
"https://example.org/users/yyy",
"https://example.org/users/xxx",
],
"object": {
"type": "Note",
"content": "test",
},
});
let signed_object = sign_object(
&object,
&signer_key,
signer_key_id,
).unwrap();
let signature_data = get_json_signature(&signed_object).unwrap();
assert_eq!(signature_data.key_id, signer_key_id);
let signer_public_key = RsaPublicKey::from(signer_key);
let result = verify_json_signature(&signature_data, &signer_public_key);
assert_eq!(result.is_ok(), true);
}
}