Verify signed activities
This commit is contained in:
parent
077d942573
commit
367e3f37c7
5 changed files with 187 additions and 14 deletions
|
@ -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::*;
|
||||
|
|
|
@ -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))
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
pub mod create;
|
||||
pub mod verify;
|
||||
|
|
113
src/json_signatures/verify.rs
Normal file
113
src/json_signatures/verify.rs
Normal 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);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue