[WIP] Security work While on holiday
This commit is contained in:
parent
a81ac4efca
commit
7f0231e3f1
109 changed files with 19694 additions and 5210 deletions
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue