Sign outgoing activities
This commit is contained in:
parent
295150361c
commit
a3723e2e6d
7 changed files with 126 additions and 1 deletions
18
Cargo.lock
generated
18
Cargo.lock
generated
|
@ -1707,6 +1707,7 @@ dependencies = [
|
||||||
"rust-argon2",
|
"rust-argon2",
|
||||||
"secp256k1",
|
"secp256k1",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_jcs",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
"serial_test",
|
"serial_test",
|
||||||
|
@ -2742,6 +2743,12 @@ version = "1.0.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu-js"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6518fc26bced4d53678a22d6e423e9d8716377def84545fe328236e3af070e7f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "same-file"
|
name = "same-file"
|
||||||
version = "1.0.6"
|
version = "1.0.6"
|
||||||
|
@ -2850,6 +2857,17 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_jcs"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cacecf649bc1a7c5f0e299cc813977c6a78116abda2b93b1ee01735b71ead9a8"
|
||||||
|
dependencies = [
|
||||||
|
"ryu-js",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.64"
|
version = "1.0.64"
|
||||||
|
|
|
@ -62,6 +62,8 @@ secp256k1 = { version = "0.21.3", features = ["rand", "rand-std"] }
|
||||||
# Used for serialization/deserialization
|
# Used for serialization/deserialization
|
||||||
serde = { version = "1.0.136", features = ["derive"] }
|
serde = { version = "1.0.136", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
# Used to create JCS representations
|
||||||
|
serde_jcs = "0.1.0"
|
||||||
# Used to parse config file
|
# Used to parse config file
|
||||||
serde_yaml = "0.8.17"
|
serde_yaml = "0.8.17"
|
||||||
# Used to calculate SHA2 hashes
|
# Used to calculate SHA2 hashes
|
||||||
|
|
|
@ -12,6 +12,10 @@ use crate::http_signatures::create::{
|
||||||
create_http_signature,
|
create_http_signature,
|
||||||
HttpSignatureError,
|
HttpSignatureError,
|
||||||
};
|
};
|
||||||
|
use crate::json_signatures::create::{
|
||||||
|
sign_object,
|
||||||
|
JsonSignatureError,
|
||||||
|
};
|
||||||
use crate::models::users::types::User;
|
use crate::models::users::types::User;
|
||||||
use crate::utils::crypto::deserialize_private_key;
|
use crate::utils::crypto::deserialize_private_key;
|
||||||
use crate::utils::urls::get_hostname;
|
use crate::utils::urls::get_hostname;
|
||||||
|
@ -27,6 +31,9 @@ pub enum DelivererError {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
HttpSignatureError(#[from] HttpSignatureError),
|
HttpSignatureError(#[from] HttpSignatureError),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
JsonSignatureError(#[from] JsonSignatureError),
|
||||||
|
|
||||||
#[error("activity serialization error")]
|
#[error("activity serialization error")]
|
||||||
SerializationError(#[from] serde_json::Error),
|
SerializationError(#[from] serde_json::Error),
|
||||||
|
|
||||||
|
@ -115,7 +122,8 @@ async fn deliver_activity_worker(
|
||||||
),
|
),
|
||||||
ACTOR_KEY_SUFFIX,
|
ACTOR_KEY_SUFFIX,
|
||||||
);
|
);
|
||||||
let activity_json = serde_json::to_string(&activity)?;
|
let activity_signed = sign_object(&activity, &actor_key, &actor_key_id)?;
|
||||||
|
let activity_json = serde_json::to_string(&activity_signed)?;
|
||||||
if recipients.is_empty() {
|
if recipients.is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
1
src/activitypub/signatures.rs
Normal file
1
src/activitypub/signatures.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
|
94
src/json_signatures/create.rs
Normal file
94
src/json_signatures/create.rs
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
use rsa::RsaPrivateKey;
|
||||||
|
use serde::Serialize;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
use crate::utils::crypto::sign_message;
|
||||||
|
|
||||||
|
/// Data Integrity Proof
|
||||||
|
/// https://w3c.github.io/vc-data-integrity/
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct Proof {
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
proof_type: String,
|
||||||
|
proof_purpose: String,
|
||||||
|
verification_method: String,
|
||||||
|
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";
|
||||||
|
|
||||||
|
const PROOF_PURPOSE: &str = "assertionMethod";
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
pub enum JsonSignatureError {
|
||||||
|
#[error(transparent)]
|
||||||
|
JsonError(#[from] serde_json::Error),
|
||||||
|
|
||||||
|
#[error("signing error")]
|
||||||
|
SigningError(#[from] rsa::errors::Error),
|
||||||
|
|
||||||
|
#[error("invalid value")]
|
||||||
|
InvalidValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sign_object(
|
||||||
|
object: &impl Serialize,
|
||||||
|
signer_key: &RsaPrivateKey,
|
||||||
|
signer_key_id: &str,
|
||||||
|
) -> Result<Value, JsonSignatureError> {
|
||||||
|
// Canonicalize
|
||||||
|
// JCS: https://www.rfc-editor.org/rfc/rfc8785
|
||||||
|
let object_str = serde_jcs::to_string(object)?;
|
||||||
|
// Sign
|
||||||
|
let signature_b64 = sign_message(signer_key, &object_str)?;
|
||||||
|
// Insert proof
|
||||||
|
let proof = Proof {
|
||||||
|
proof_type: PROOF_TYPE.to_string(),
|
||||||
|
proof_purpose: PROOF_PURPOSE.to_string(),
|
||||||
|
verification_method: signer_key_id.to_string(),
|
||||||
|
proof_value: signature_b64,
|
||||||
|
};
|
||||||
|
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)?;
|
||||||
|
object_map.insert("proof".to_string(), proof_value);
|
||||||
|
Ok(object_value)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use serde_json::json;
|
||||||
|
use crate::utils::crypto::generate_weak_private_key;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sign_object() {
|
||||||
|
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 result = sign_object(&object, &signer_key, signer_key_id).unwrap();
|
||||||
|
let result_str = serde_json::to_string(&result).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
result_str,
|
||||||
|
r#"{"actor":"https://example.org/users/test","id":"https://example.org/objects/1","object":{"content":"test","type":"Note"},"proof":{"proofPurpose":"assertionMethod","proofValue":"P4ye1hDvrGQCCClzHfCU9xobMAeqlUfgEWGlZfOTE3WmjH8JC/OJwlsjUMOUwTVlyKStp+AY+zzU4z6mjZN0Ug==","type":"JcsRsaSignature2022","verificationMethod":"https://example.org/users/test#main-key"},"to":["https://example.org/users/yyy","https://example.org/users/xxx"],"type":"Create"}"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
1
src/json_signatures/mod.rs
Normal file
1
src/json_signatures/mod.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub mod create;
|
|
@ -9,6 +9,7 @@ mod frontend;
|
||||||
pub mod http;
|
pub mod http;
|
||||||
mod http_signatures;
|
mod http_signatures;
|
||||||
mod ipfs;
|
mod ipfs;
|
||||||
|
mod json_signatures;
|
||||||
pub mod logger;
|
pub mod logger;
|
||||||
pub mod mastodon_api;
|
pub mod mastodon_api;
|
||||||
mod models;
|
mod models;
|
||||||
|
|
Loading…
Reference in a new issue