[WIP] Security work While on holiday

This commit is contained in:
Alex 2026-08-12 22:45:28 +02:00
commit 7f0231e3f1
Signed by: alex
SSH key fingerprint: SHA256:D1+Ub8o0v4K5y1JNivW8IxEOelqLSvPmUzBbDIoZkRQ
109 changed files with 19694 additions and 5210 deletions

View file

@ -1,23 +1,208 @@
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataTypeId, DataValue, TypeMap};
use mtp::crypto::{CryptoError, Keyring, SignaturePublicKey, SignatureScheme, verify_ed25519};
use std::collections::HashMap;
struct Ed25519Verifier(SignaturePublicKey);
use mtp::codec::{
CommunicationType, CommunicationValue, DataType, DataTypeId, DataValue, InMemoryReplayGuard,
ProtectionPolicy, ProtectionPurpose, SignaturePolicy, TypeMap,
forward_relay_frame, open_protected_with, open_relay_content, open_relay_metadata_with,
};
use mtp::crypto::{Keyring, PublicKeyBundle};
impl SignatureScheme for Ed25519Verifier {
fn sign(&self, _msg: &[u8]) -> Result<Vec<u8>, CryptoError> {
Err(CryptoError::SigningFailed)
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()
));
}
fn verify(&self, msg: &[u8], signature: &[u8]) -> Result<(), CryptoError> {
verify_ed25519(&self.0, msg, signature)
let opened = open_protected_with(
msg,
std::slice::from_ref(&host_keyring),
None,
|signer_id| resolve_signer_key(signer_id, registered_clients).map(|key| vec![key]),
Some(DIRECT_DESTINATION_ID),
ProtectionPurpose::from(DIRECT_SIGNATURE_PURPOSE),
ProtectionPurpose::from(DIRECT_ENCRYPTION_PURPOSE),
SIGNATURE_POLICY,
Some(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(
msg,
std::slice::from_ref(&host_keyring),
None,
|signer_id| {
resolve_signer_key(signer_id, registered_clients).map(|key| vec![key])
},
SIGNATURE_POLICY,
Some(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(
&metadata,
host_keyring,
&resolve_signer_key(metadata.signer_id(), registered_clients)
.ok_or("metadata signer key disappeared")?,
FINAL_RECIPIENT_ID,
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")?,
@ -59,13 +244,34 @@ pub fn process_and_respond(
.ok_or("missing SecurePayload type mapping")?,
);
let description = msg.get_data(DataType::Description);
let timestamp = msg.get_data(DataType::Timestamp);
let data = msg.get_data(DataType::Data);
let flags = msg.get_data(DataType::Flags);
let value = msg.get_data(DataType::Value);
let binary = msg.get_data(DataType::BinaryData);
let items = msg.get_data(DataType::Items);
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: {}",
@ -82,13 +288,8 @@ pub fn process_and_respond(
let mut sig_status = String::from("SignedPayload: not present");
let mut secure_status = String::from("SecurePayload: not present");
let enc = msg.get_data(DataType::EncryptedPayload);
if matches!(enc, DataValue::EncryptedContainer(_)) {
let mut dv = enc.clone();
if dv
.decrypt_into_container(host_keyring, b"demo-aad")
.is_some()
{
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());
@ -99,13 +300,19 @@ pub fn process_and_respond(
}
}
let sig = msg.get_data(DataType::SignedPayload);
if matches!(sig, DataValue::SignedContainer(_)) {
if let Some(sig @ DataValue::Signed(_)) = msg.get_data(DataType::SignedPayload) {
if let Some(pk_bundle) = client_pk {
let verifier = Ed25519Verifier(pk_bundle.sig_cl_public_key.clone());
let mut dv = sig.clone();
if dv.verify_into_container(&verifier).is_some() {
if let Some(entries) = dv.as_container() {
let signer_id = sig.as_signed().map(|signed| signed.signer_id);
if let Some(signer_id) = signer_id
&& sig
.verify(signer_id, pk_bundle, mtp::codec::ProtectionPurpose::from(2))
.is_ok()
{
let dv = sig
.clone()
.into_verified(signer_id, pk_bundle, mtp::codec::ProtectionPurpose::from(2))
.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());
}
@ -119,17 +326,23 @@ pub fn process_and_respond(
}
}
let secure = msg.get_data(DataType::SecurePayload);
if matches!(secure, DataValue::SignedEncryptedContainer(_)) {
if let Some(secure @ DataValue::Encrypted(_)) = msg.get_data(DataType::SecurePayload) {
if let Some(pk_bundle) = client_pk {
let verifier = Ed25519Verifier(pk_bundle.sig_cl_public_key.clone());
let mut dv = secure.clone();
if dv
.decrypt_signed_encrypted_container(host_keyring, b"demo-aad")
.is_some()
&& dv.verify_into_container(&verifier).is_some()
if let Ok(opened) = secure.decrypt(host_keyring, mtp::codec::ProtectionPurpose::from(4))
&& let Some(signed) = opened.as_signed()
&& opened
.verify(
signed.signer_id,
pk_bundle,
mtp::codec::ProtectionPurpose::from(3),
)
.is_ok()
{
if let Some(entries) = dv.as_container() {
let signer_id = signed.signer_id;
let dv = opened
.into_verified(signer_id, pk_bundle, mtp::codec::ProtectionPurpose::from(3))
.ok();
if let Some(entries) = dv.and_then(|value| value.as_container()) {
println!(" Verified SecurePayload: {:?}", entries);
secure_status = format!(
"SecurePayload decrypted+verified OK ({} entries)",
@ -149,11 +362,13 @@ pub fn process_and_respond(
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_err(|e| e.to_string())?
.as_secs();
.as_millis();
Ok(CommunicationValue::from_comm(CommunicationType::Pong, tm)
.add_data(desc_id, description.clone())
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!(
@ -161,8 +376,14 @@ pub fn process_and_respond(
enc_status, sig_status, secure_status
)),
)
.add_data(flags_id, flags.clone())
.add_data(value_id, value.clone())
.add_data(bin_id, binary.clone())
.add_data(items_id, items.clone()))
.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)
}