mtp/example/server/src/handlers.rs
Alex Emmet b331b9f6a3
Some checks failed
CI / checks (push) Failing after 16m34s
[Fix] Clean
2026-08-18 21:53:02 +02:00

410 lines
15 KiB
Rust

use std::collections::HashMap;
use mtp::codec::{
CommunicationType, CommunicationValue, DataType, DataTypeId, DataValue, InMemoryReplayGuard,
ProtectedOpenOptions, ProtectionPolicy, ProtectionPurpose, RelayOpenOptions, SignaturePolicy,
TypeMap,
forward_relay_frame, open_protected_with_checked,
open_relay_content_with_limits_without_replay,
open_relay_metadata_with_checked,
};
use mtp::crypto::{Keyring, PublicKeyBundle};
const DIRECT_DESTINATION_ID: u64 = 1;
const METADATA_RELAY_ID: u64 = 1;
const FINAL_RECIPIENT_ID: u64 = 7_002;
const DIRECT_SIGNATURE_PURPOSE: u8 = 0x40;
const DIRECT_ENCRYPTION_PURPOSE: u8 = 0x41;
const SIGNATURE_POLICY: ProtectionPolicy = ProtectionPolicy {
signature: SignaturePolicy::Ed25519,
};
fn resolve_signer_key(
signer_id: u64,
registered_clients: &HashMap<u64, PublicKeyBundle>,
) -> Option<PublicKeyBundle> {
registered_clients.get(&signer_id).cloned()
}
fn pong(tm: &TypeMap, data: impl Into<String>) -> Result<CommunicationValue, String> {
let desc_id = DataTypeId(
tm.data_id_enum(DataType::Description)
.ok_or("missing Description type mapping")?,
);
let ts_id = DataTypeId(
tm.data_id_enum(DataType::Timestamp)
.ok_or("missing Timestamp type mapping")?,
);
let data_id = DataTypeId(
tm.data_id_enum(DataType::Data)
.ok_or("missing Data type mapping")?,
);
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_err(|e| e.to_string())?
.as_millis();
CommunicationValue::from_comm(CommunicationType::Pong, tm)
.add_data(desc_id, DataValue::Str("MTP example response".into()))
.map_err(|e| e.to_string())?
.add_data(ts_id, DataValue::UnsignedNumber(now as u128))
.map_err(|e| e.to_string())?
.add_data(data_id, DataValue::Str(data.into()))
.map_err(|e| e.to_string())
}
fn process_direct_protected(
msg: &CommunicationValue,
tm: &TypeMap,
client_pk: Option<&PublicKeyBundle>,
registered_clients: &HashMap<u64, PublicKeyBundle>,
host_keyring: &Keyring,
accepted_messages: &mut InMemoryReplayGuard,
) -> Result<CommunicationValue, String> {
if msg.receiver() != Some(DIRECT_DESTINATION_ID) {
return Err(format!(
"direct protected frame was addressed to {:?}, expected destination {DIRECT_DESTINATION_ID}",
msg.receiver()
));
}
let opened = open_protected_with_checked(
msg,
std::slice::from_ref(&host_keyring),
None,
|signer_id| resolve_signer_key(signer_id, registered_clients).map(|key| vec![key]),
ProtectedOpenOptions::new(
Some(DIRECT_DESTINATION_ID),
ProtectionPurpose::from(DIRECT_SIGNATURE_PURPOSE),
ProtectionPurpose::from(DIRECT_ENCRYPTION_PURPOSE),
SIGNATURE_POLICY,
),
accepted_messages,
)
.map_err(|e| format!("direct protected message could not be authenticated: {e}"))?;
let signer_id = opened.signer_id;
let message_id = opened.message_id;
let value = opened
.content
.as_container()
.ok_or("direct protected application value is not a container")?;
let text_id = DataTypeId(
tm.data_id_enum(DataType::ExampleText)
.ok_or("missing ExampleText type mapping")?,
);
let number_id = DataTypeId(
tm.data_id_enum(DataType::ExampleNumber)
.ok_or("missing ExampleNumber type mapping")?,
);
let text = value
.iter()
.find(|(id, _)| *id == text_id)
.and_then(|(_, value)| value.as_str())
.ok_or("direct protected value is missing ExampleText")?;
let number = value
.iter()
.find(|(id, _)| *id == number_id)
.and_then(|(_, value)| value.as_unsigned_number())
.ok_or("direct protected value is missing ExampleNumber")?;
println!(
" Direct protected message: signer={signer_id}, message_id={message_id}, transport_key_available={}, ExampleText={text:?}, ExampleNumber={number}",
client_pk.is_some()
);
if client_pk.is_none() {
println!(
" Protected signer was verified from the registered key map; transport is unauthenticated"
);
}
pong(
tm,
format!("direct protected value verified for signer {signer_id}"),
)
}
fn process_sealed_relay(
msg: &CommunicationValue,
registered_clients: &HashMap<u64, PublicKeyBundle>,
host_keyring: &Keyring,
accepted_messages: &mut InMemoryReplayGuard,
) -> Result<CommunicationValue, String> {
if msg.receiver() != Some(METADATA_RELAY_ID) {
return Err(format!(
"sealed relay next hop was {:?}, expected metadata relay {METADATA_RELAY_ID}",
msg.receiver()
));
}
let metadata = open_relay_metadata_with_checked(
msg,
std::slice::from_ref(&host_keyring),
None,
|signer_id| {
resolve_signer_key(signer_id, registered_clients).map(|key| vec![key])
},
RelayOpenOptions::new(SIGNATURE_POLICY),
accepted_messages,
)
.map_err(|e| format!("metadata relay could not authenticate metadata: {e}"))?;
println!(
" Metadata relay opened message_id={} signer={} final_recipient={} metadata={:?}",
metadata.message_id(),
metadata.signer_id(),
metadata.final_recipient_id(),
metadata.metadata()
);
println!(
" Metadata relay retained opaque encrypted content ({} bytes)",
metadata
.encrypted_content()
.to_bytes()
.map_err(|e| format!("opaque content serialization failed: {e}"))?
.len()
);
let content_result = open_relay_content_with_limits_without_replay(
&metadata,
&[host_keyring],
&[resolve_signer_key(metadata.signer_id(), registered_clients)
.ok_or("metadata signer key disappeared")?],
Some(FINAL_RECIPIENT_ID),
RelayOpenOptions::new(SIGNATURE_POLICY),
);
if content_result.is_ok() {
return Err("metadata relay unexpectedly decrypted final-recipient content".into());
}
println!(" Metadata relay cannot decrypt final-recipient content (expected)");
forward_relay_frame(msg, metadata.final_recipient_id())
.map_err(|e| format!("metadata relay forwarding failed: {e}"))
}
pub fn process_and_respond(
msg: &CommunicationValue,
tm: &TypeMap,
client_pk: Option<&mtp::crypto::PublicKeyBundle>,
registered_clients: &HashMap<u64, PublicKeyBundle>,
host_keyring: &Keyring,
accepted_direct_messages: &mut InMemoryReplayGuard,
accepted_relay_messages: &mut InMemoryReplayGuard,
) -> Result<CommunicationValue, String> {
if msg.is_type(CommunicationType::ProtectedMessage) {
return process_direct_protected(
msg,
tm,
client_pk,
registered_clients,
host_keyring,
accepted_direct_messages,
);
}
if msg.is_type(CommunicationType::Relay) {
return process_sealed_relay(
msg,
registered_clients,
host_keyring,
accepted_relay_messages,
);
}
let desc_id = DataTypeId(
tm.data_id_enum(DataType::Description)
.ok_or("missing Description type mapping")?,
);
let ts_id = DataTypeId(
tm.data_id_enum(DataType::Timestamp)
.ok_or("missing Timestamp type mapping")?,
);
let data_id = DataTypeId(
tm.data_id_enum(DataType::Data)
.ok_or("missing Data type mapping")?,
);
let flags_id = DataTypeId(
tm.data_id_enum(DataType::Flags)
.ok_or("missing Flags type mapping")?,
);
let value_id = DataTypeId(
tm.data_id_enum(DataType::Value)
.ok_or("missing Value type mapping")?,
);
let bin_id = DataTypeId(
tm.data_id_enum(DataType::BinaryData)
.ok_or("missing BinaryData type mapping")?,
);
let items_id = DataTypeId(
tm.data_id_enum(DataType::Items)
.ok_or("missing Items type mapping")?,
);
let _enc_id = DataTypeId(
tm.data_id_enum(DataType::EncryptedPayload)
.ok_or("missing EncryptedPayload type mapping")?,
);
let _sig_id = DataTypeId(
tm.data_id_enum(DataType::SignedPayload)
.ok_or("missing SignedPayload type mapping")?,
);
let _secure_id = DataTypeId(
tm.data_id_enum(DataType::SecurePayload)
.ok_or("missing SecurePayload type mapping")?,
);
let description = msg
.get_data(DataType::Description)
.cloned()
.unwrap_or(DataValue::Null);
let timestamp = msg
.get_data(DataType::Timestamp)
.cloned()
.unwrap_or(DataValue::Null);
let data = msg
.get_data(DataType::Data)
.cloned()
.unwrap_or(DataValue::Null);
let flags = msg
.get_data(DataType::Flags)
.cloned()
.unwrap_or(DataValue::Null);
let value = msg
.get_data(DataType::Value)
.cloned()
.unwrap_or(DataValue::Null);
let binary = msg
.get_data(DataType::BinaryData)
.cloned()
.unwrap_or(DataValue::Null);
let items = msg
.get_data(DataType::Items)
.cloned()
.unwrap_or(DataValue::Null);
println!(
" Description: {}",
description.as_str().unwrap_or("(missing)")
);
println!(" Timestamp: {:?}", timestamp.as_unsigned_number());
println!(" Data: {}", data.as_str().unwrap_or("(missing)"));
println!(" Flags: {:?}", flags.as_bool());
println!(" Value: {:?}", value.as_float());
println!(" Binary: {:?}", binary.as_bytes());
println!(" Items: {:?}", items.as_array());
let mut enc_status = String::from("EncryptedPayload: not present");
let mut sig_status = String::from("SignedPayload: not present");
let mut secure_status = String::from("SecurePayload: not present");
if let Some(enc @ DataValue::Encrypted(_)) = msg.get_data(DataType::EncryptedPayload) {
if let Ok(dv) = enc.decrypt(host_keyring, mtp::codec::ProtectionPurpose::from(1)) {
if let Some(entries) = dv.as_container() {
println!(" Decrypted EncryptedPayload: {:?}", entries);
enc_status = format!("EncryptedPayload decrypted OK ({} entries)", entries.len());
}
} else {
eprintln!(" EncryptedPayload decryption failed");
enc_status = String::from("EncryptedPayload: decryption FAILED");
}
}
if let Some(sig @ DataValue::Signed(_)) = msg.get_data(DataType::SignedPayload) {
if let Some(pk_bundle) = client_pk {
let signer_id = sig.as_signed().map(|signed| signed.signer_id);
if let Some(signer_id) = signer_id
&& sig
.verify_with_policy(
signer_id,
pk_bundle,
mtp::codec::ProtectionPurpose::from(2),
SIGNATURE_POLICY,
)
.is_ok()
{
let dv = sig
.clone()
.into_verified_with_policy(
signer_id,
pk_bundle,
mtp::codec::ProtectionPurpose::from(2),
SIGNATURE_POLICY,
)
.ok();
if let Some(entries) = dv.and_then(|value| value.as_container()) {
println!(" Verified SignedPayload: {:?}", entries);
sig_status = format!("SignedPayload verified OK ({} entries)", entries.len());
}
} else {
eprintln!(" SignedPayload verification failed");
sig_status = String::from("SignedPayload: verification FAILED");
}
} else {
eprintln!(" SignedPayload cannot be verified; no client public key available");
sig_status = String::from("SignedPayload: no client public key available");
}
}
if let Some(secure @ DataValue::Encrypted(_)) = msg.get_data(DataType::SecurePayload) {
if let Some(pk_bundle) = client_pk {
if let Ok(opened) = secure.decrypt(host_keyring, mtp::codec::ProtectionPurpose::from(4))
&& let Some(signed) = opened.as_signed()
&& opened
.verify_with_policy(
signed.signer_id,
pk_bundle,
mtp::codec::ProtectionPurpose::from(3),
SIGNATURE_POLICY,
)
.is_ok()
{
let signer_id = signed.signer_id;
let dv = opened
.into_verified_with_policy(
signer_id,
pk_bundle,
mtp::codec::ProtectionPurpose::from(3),
SIGNATURE_POLICY,
)
.ok();
if let Some(entries) = dv.and_then(|value| value.as_container()) {
println!(" Verified SecurePayload: {:?}", entries);
secure_status = format!(
"SecurePayload decrypted+verified OK ({} entries)",
entries.len()
);
}
} else {
eprintln!(" SecurePayload decryption/verification failed");
secure_status = String::from("SecurePayload: decryption/verification FAILED");
}
} else {
eprintln!(" SecurePayload cannot be verified; no client public key available");
secure_status = String::from("SecurePayload: no client public key available");
}
}
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_err(|e| e.to_string())?
.as_millis();
let response = CommunicationValue::from_comm(CommunicationType::Pong, tm)
.add_data(desc_id, description)
.map_err(|e| e.to_string())?
.add_data(ts_id, DataValue::UnsignedNumber(now as u128))
.map_err(|e| e.to_string())?
.add_data(
data_id,
DataValue::Str(format!(
"{}. {}. {}.",
enc_status, sig_status, secure_status
)),
)
.map_err(|e| e.to_string())?
.add_data(flags_id, flags)
.map_err(|e| e.to_string())?
.add_data(value_id, value)
.map_err(|e| e.to_string())?
.add_data(bin_id, binary)
.map_err(|e| e.to_string())?
.add_data(items_id, items)
.map_err(|e| e.to_string())?;
Ok(response)
}