From 72d11cac112a1a7e0cd7eecceee2470f7eb249cf Mon Sep 17 00:00:00 2001 From: Tom Gouville Date: Mon, 29 Apr 2024 14:19:23 +0200 Subject: [PATCH 01/29] adding flight of packets to the knowledge --- puffin/src/algebra/mod.rs | 10 ++++++++++ puffin/src/trace.rs | 16 ++++++++++++++++ tlspuffin/src/protocol.rs | 4 ++++ tlspuffin/src/query.rs | 6 ++++++ 4 files changed, 36 insertions(+) diff --git a/puffin/src/algebra/mod.rs b/puffin/src/algebra/mod.rs index 514172f25..80625837c 100644 --- a/puffin/src/algebra/mod.rs +++ b/puffin/src/algebra/mod.rs @@ -80,6 +80,10 @@ where 0 } } + + fn flight() -> Option { + None + } } /// Determines whether two instances match. We can also ask it how specific it is. @@ -87,6 +91,8 @@ pub trait Matcher: Debug + Clone + Hash + serde::Serialize + DeserializeOwned + fn matches(&self, matcher: &Self) -> bool; fn specificity(&self) -> u32; + + fn flight() -> Option; } #[derive(Debug, Clone, Hash, PartialEq, Serialize, Deserialize)] @@ -100,6 +106,10 @@ impl Matcher for AnyMatcher { fn specificity(&self) -> u32 { 0 } + + fn flight() -> Option { + None + } } impl, O: OpaqueProtocolMessage> TryFrom<&MessageResult> for AnyMatcher { diff --git a/puffin/src/trace.rs b/puffin/src/trace.rs index 629f5e73f..b360a6abe 100644 --- a/puffin/src/trace.rs +++ b/puffin/src/trace.rs @@ -537,11 +537,18 @@ impl OutputAction { { ctx.next_state(step.agent)?; + let mut flight = vec![]; + while let Some(message_result) = ctx.take_message_from_outbound(step.agent)? { let matcher = message_result.create_matcher::(); let MessageResult(message, opaque_message) = message_result; + match &message { + Some(m) => flight.push(m.clone()), + _ => (), + }; + let knowledge = message .and_then(|message| message.extract_knowledge().ok()) .unwrap_or_default(); @@ -574,6 +581,15 @@ impl OutputAction { ctx.add_knowledge(knowledge) } } + + let flight_knowledge = Knowledge:: { + agent_name: step.agent, + matcher: M::flight().into(), + data: Box::new(flight), + }; + flight_knowledge.debug_print(ctx, &step.agent); + ctx.add_knowledge(flight_knowledge); + Ok(()) } } diff --git a/tlspuffin/src/protocol.rs b/tlspuffin/src/protocol.rs index 6205fc780..c0b477e70 100644 --- a/tlspuffin/src/protocol.rs +++ b/tlspuffin/src/protocol.rs @@ -177,6 +177,10 @@ impl Matcher for msgs::enums::HandshakeType { fn specificity(&self) -> u32 { 1 } + + fn flight() -> Option { + None + } } #[derive(Clone, Debug, PartialEq)] diff --git a/tlspuffin/src/query.rs b/tlspuffin/src/query.rs index e175e9ee0..418962531 100644 --- a/tlspuffin/src/query.rs +++ b/tlspuffin/src/query.rs @@ -10,6 +10,7 @@ use crate::tls::rustls::msgs::{ /// It uses [rustls::msgs::enums::{ContentType,HandshakeType}]. #[derive(Debug, Deserialize, Serialize, Clone, Copy, Hash, Eq, PartialEq)] pub enum TlsQueryMatcher { + Flight, ChangeCipherSpec, Alert, Handshake(Option), @@ -20,6 +21,7 @@ pub enum TlsQueryMatcher { impl Matcher for TlsQueryMatcher { fn matches(&self, matcher: &TlsQueryMatcher) -> bool { match matcher { + TlsQueryMatcher::Flight => matches!(self, TlsQueryMatcher::Flight), TlsQueryMatcher::Handshake(query_handshake_type) => match self { TlsQueryMatcher::Handshake(handshake_type) => { handshake_type.matches(query_handshake_type) @@ -44,6 +46,10 @@ impl Matcher for TlsQueryMatcher { _ => 0, } } + + fn flight() -> Option { + Some(Self::Flight) + } } impl TryFrom<&MessageResult> for TlsQueryMatcher { From 599106d974ed736391130b38d192aac435f5fd79 Mon Sep 17 00:00:00 2001 From: Tom Gouville Date: Mon, 29 Apr 2024 15:19:43 +0200 Subject: [PATCH 02/29] tlspuffin: introducing fn_decrypt_handshake_flight + client_attacker_full now works with openssl and boringssl --- tlspuffin/src/tls/fn_utils.rs | 45 ++++ tlspuffin/src/tls/mod.rs | 2 + tlspuffin/src/tls/seeds.rs | 383 +--------------------------------- 3 files changed, 57 insertions(+), 373 deletions(-) diff --git a/tlspuffin/src/tls/fn_utils.rs b/tlspuffin/src/tls/fn_utils.rs index cb5335bc4..f1d184fdb 100644 --- a/tlspuffin/src/tls/fn_utils.rs +++ b/tlspuffin/src/tls/fn_utils.rs @@ -80,6 +80,40 @@ pub fn fn_decrypt_handshake( .map_err(|_err| FnError::Crypto("Failed to create Message from decrypted data".to_string())) } +/// Decrypt a whole flight of handshake messages and return a Vec of decrypted messages +pub fn fn_decrypt_handshake_flight( + flight: &Vec, + server_hello_transcript: &HandshakeHash, + server_key_share: &Option>, + psk: &Option>, + group: &NamedGroup, + client: &bool, + sequence: &u64, +) -> Result, FnError> { + let mut sequence_number = *sequence; + + let mut decrypted_flight = vec![]; + + for msg in flight { + if let MessagePayload::ApplicationData(_) = &msg.payload { + let mut decrypted_msg = fn_decrypt_multiple_handshake_messages( + &msg, + server_hello_transcript, + server_key_share, + psk, + group, + client, + &sequence_number, + )?; + + decrypted_flight.append(&mut decrypted_msg); + sequence_number += 1; + } + } + + Ok(decrypted_flight) +} + /// Decrypt an Application data message containing multiple handshake messages /// and return a vec of handshake messages pub fn fn_decrypt_multiple_handshake_messages( @@ -123,6 +157,17 @@ pub fn fn_decrypt_multiple_handshake_messages( Ok(messages) } +pub fn fn_find_server_hello(messages: &Vec) -> Result { + for msg in messages { + if let MessagePayload::Handshake(x) = &msg.payload { + if x.typ == HandshakeType::ServerHello { + return Ok(msg.clone()); + } + } + } + Err(FnError::Unknown("no server hello".to_owned())) +} + pub fn fn_find_server_certificate(messages: &Vec) -> Result { for msg in messages { if let MessagePayload::Handshake(x) = &msg.payload { diff --git a/tlspuffin/src/tls/mod.rs b/tlspuffin/src/tls/mod.rs index 3eb4ec450..485693759 100644 --- a/tlspuffin/src/tls/mod.rs +++ b/tlspuffin/src/tls/mod.rs @@ -206,7 +206,9 @@ define_signature!( fn_new_transcript fn_append_transcript fn_decrypt_handshake + fn_decrypt_handshake_flight fn_decrypt_multiple_handshake_messages + fn_find_server_hello fn_find_server_certificate fn_find_server_certificate_request fn_find_server_ticket diff --git a/tlspuffin/src/tls/seeds.rs b/tlspuffin/src/tls/seeds.rs index 67c467769..d3fef3510 100644 --- a/tlspuffin/src/tls/seeds.rs +++ b/tlspuffin/src/tls/seeds.rs @@ -778,6 +778,7 @@ pub fn seed_server_attacker_full(client: AgentName) -> Trace { } } +// TODO: `BAD_SIGNATURE` error with BoringSSL pub fn seed_client_attacker_auth(server: AgentName) -> Trace { let client_hello = term! { fn_client_hello( @@ -805,167 +806,9 @@ pub fn seed_client_attacker_auth(server: AgentName) -> Trace { ) }; - /*let encrypted_extensions = term! { - fn_decrypt_handshake( - ((server, 0)[Some(TlsQueryMatcher::ApplicationData)]), // Ticket from last session - (fn_server_hello_transcript(((server, 0)))), - (fn_get_server_key_share(((server, 0)))), - fn_no_psk, - fn_named_group_secp384r1, - fn_true, - fn_seq_0 - ) - };*/ - - // ApplicationData 0 is EncryptedExtensions - let certificate_request_message = term! { - fn_decrypt_handshake( - ((server, 1)[Some(TlsQueryMatcher::ApplicationData)]), // Ticket from last session - (fn_server_hello_transcript(((server, 0)))), - (fn_get_server_key_share(((server, 0)))), - fn_no_psk, - fn_named_group_secp384r1, - fn_true, - fn_seq_1 - ) - }; - - let certificate = term! { - fn_certificate13( - (fn_get_context((@certificate_request_message))), - (fn_append_certificate_entry( - (fn_certificate_entry( - fn_bob_cert - )), - fn_empty_certificate_chain - )) - ) - }; - - let certificate_verify = term! { - fn_certificate_verify( - fn_rsa_pss_signature_algorithm, - (fn_rsa_sign_client( - (fn_certificate_transcript(((server, 0)))), - fn_bob_key, - fn_rsa_pss_signature_algorithm - )) - ) - }; - - let client_finished = term! { - fn_finished( - (fn_verify_data( - (fn_server_finished_transcript(((server, 0)))), - (fn_server_hello_transcript(((server, 0)))), - (fn_get_server_key_share(((server, 0)))), - fn_no_psk, - fn_named_group_secp384r1 - )) - ) - }; - - Trace { - prior_traces: vec![], - descriptors: vec![AgentDescriptor { - name: server, - tls_version: TLSVersion::V1_3, - typ: AgentType::Server, - client_authentication: true, - ..AgentDescriptor::default() - }], - steps: vec![ - Step { - agent: server, - action: Action::Input(InputAction { - recipe: term! { - @client_hello - }, - }), - }, - Step { - agent: server, - action: Action::Input(InputAction { - recipe: term! { - fn_encrypt_handshake( - (@certificate), - (fn_server_hello_transcript(((server, 0)))), - (fn_get_server_key_share(((server, 0)))), - fn_no_psk, - fn_named_group_secp384r1, - fn_true, - fn_seq_0 // sequence 0 - ) - }, - }), - }, - Step { - agent: server, - action: Action::Input(InputAction { - recipe: term! { - fn_encrypt_handshake( - (@certificate_verify), - (fn_server_hello_transcript(((server, 0)))), - (fn_get_server_key_share(((server, 0)))), - fn_no_psk, - fn_named_group_secp384r1, - fn_true, - fn_seq_1 // sequence 1 - ) - }, - }), - }, - Step { - agent: server, - action: Action::Input(InputAction { - recipe: term! { - fn_encrypt_handshake( - (@client_finished), - (fn_server_hello_transcript(((server, 0)))), - (fn_get_server_key_share(((server, 0)))), - fn_no_psk, - fn_named_group_secp384r1, - fn_true, - fn_seq_2 // sequence 2 - ) - }, - }), - }, - ], - } -} - -// TODO: `BAD_SIGNATURE` error -pub fn seed_client_attacker_auth_boring(server: AgentName) -> Trace { - let client_hello = term! { - fn_client_hello( - fn_protocol_version12, - fn_new_random, - fn_new_session_id, - (fn_append_cipher_suite( - (fn_new_cipher_suites()), - fn_cipher_suite13_aes_128_gcm_sha256 - )), - fn_compressions, - (fn_client_extensions_append( - (fn_client_extensions_append( - (fn_client_extensions_append( - (fn_client_extensions_append( - fn_client_extensions_new, - (fn_support_group_extension(fn_named_group_secp384r1)) - )), - fn_signature_algorithm_extension - )), - (fn_key_share_deterministic_extension(fn_named_group_secp384r1)) - )), - fn_supported_versions13_extension - )) - ) - }; - let extensions = term! { - fn_decrypt_multiple_handshake_messages( - ((server, 0)[Some(TlsQueryMatcher::ApplicationData)]), // Encrypted Extensions + fn_decrypt_handshake_flight( + ((server, 0)[Some(TlsQueryMatcher::Flight)]), // The first flight of messages sent by the server (fn_server_hello_transcript(((server, 0)))), (fn_get_server_key_share(((server, 0)[Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ServerHello)))]))), fn_no_psk, @@ -977,7 +820,7 @@ pub fn seed_client_attacker_auth_boring(server: AgentName) -> Trace Trace { - _seed_client_attacker_full_boring(server).0 -} - -/// Seed which contains the whole transcript in the tree. This is rather huge >300 symbols -pub fn _seed_client_attacker_full_boring( - server: AgentName, -) -> ( - Trace, - Term, - Term, - Term, -) { - let client_hello = term! { - fn_client_hello( - fn_protocol_version12, - fn_new_random, - fn_new_session_id, - (fn_append_cipher_suite( - (fn_new_cipher_suites()), - fn_cipher_suite13_aes_128_gcm_sha256 - )), - fn_compressions, - (fn_client_extensions_append( - (fn_client_extensions_append( - (fn_client_extensions_append( - (fn_client_extensions_append( - fn_client_extensions_new, - (fn_support_group_extension(fn_named_group_secp384r1)) - )), - fn_signature_algorithm_extension - )), - (fn_key_share_deterministic_extension(fn_named_group_secp384r1)) - )), - fn_supported_versions13_extension - )) - ) - }; - - let server_hello_transcript = term! { - fn_append_transcript( - (fn_append_transcript( - fn_new_transcript, - (@client_hello) // ClientHello - )), - ((server, 0)[Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ServerHello)))]) // plaintext ServerHello - ) - }; - - // ((0, 1)) could be a CCS the server sends one - let extensions = term! { - fn_decrypt_multiple_handshake_messages( - ((server, 0)[Some(TlsQueryMatcher::ApplicationData)]), // Encrypted Extensions + fn_decrypt_handshake_flight( + ((server, 0)[Some(TlsQueryMatcher::Flight)]), // The first flight of messages sent by the server (@server_hello_transcript), (fn_get_server_key_share(((server, 0)[Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ServerHello)))]))), fn_no_psk, @@ -1909,7 +1548,7 @@ pub fn _seed_client_attacker_full_boring( }), }, OutputAction::new_step(server), - /*Step { + Step { agent: server, action: Action::Input(InputAction { recipe: term! { @@ -1925,7 +1564,7 @@ pub fn _seed_client_attacker_full_boring( }, }), }, - OutputAction::new_step(server),*/ + OutputAction::new_step(server), ], }; @@ -2166,10 +1805,8 @@ pub fn create_corpus() -> Vec<(Trace, &'static str)> { seed_successful12_with_tickets: cfg(all(feature = "tls12", feature = "tls12-session-resumption")), // Client Attackers seed_client_attacker: cfg(feature = "tls13"), - seed_client_attacker_full: cfg(all(feature = "tls13", not(feature = "boringssl-binding"))), - seed_client_attacker_full_boring: cfg(all(feature = "tls13", feature = "boringssl-binding")), - seed_client_attacker_auth: cfg(all(feature = "tls13", feature = "client-authentication-transcript-extraction", not(feature = "boringssl-binding"))), - seed_client_attacker_auth_boring: cfg(all(feature = "tls13", feature = "client-authentication-transcript-extraction", feature = "boringssl-binding")), + seed_client_attacker_full: cfg(feature = "tls13"), + seed_client_attacker_auth: cfg(all(feature = "tls13", feature = "client-authentication-transcript-extraction")), seed_client_attacker12: cfg(feature = "tls12"), // Session resumption seed_session_resumption_dhe: cfg(all(feature = "tls13", feature = "tls13-session-resumption")), From 146a72e7280203baf584248df4876bcde968dfab Mon Sep 17 00:00:00 2001 From: Tom Gouville Date: Mon, 29 Apr 2024 17:04:34 +0200 Subject: [PATCH 03/29] Doing query on Flight using Rust Type instead of Matcher --- puffin/src/algebra/mod.rs | 10 ---------- puffin/src/protocol.rs | 18 +++++++++++++++++- puffin/src/trace.rs | 10 ++++++---- tlspuffin/src/protocol.rs | 4 ---- tlspuffin/src/query.rs | 4 ---- tlspuffin/src/tls/fn_utils.rs | 6 +++--- tlspuffin/src/tls/seeds.rs | 7 ++++--- 7 files changed, 30 insertions(+), 29 deletions(-) diff --git a/puffin/src/algebra/mod.rs b/puffin/src/algebra/mod.rs index 80625837c..514172f25 100644 --- a/puffin/src/algebra/mod.rs +++ b/puffin/src/algebra/mod.rs @@ -80,10 +80,6 @@ where 0 } } - - fn flight() -> Option { - None - } } /// Determines whether two instances match. We can also ask it how specific it is. @@ -91,8 +87,6 @@ pub trait Matcher: Debug + Clone + Hash + serde::Serialize + DeserializeOwned + fn matches(&self, matcher: &Self) -> bool; fn specificity(&self) -> u32; - - fn flight() -> Option; } #[derive(Debug, Clone, Hash, PartialEq, Serialize, Deserialize)] @@ -106,10 +100,6 @@ impl Matcher for AnyMatcher { fn specificity(&self) -> u32 { 0 } - - fn flight() -> Option { - None - } } impl, O: OpaqueProtocolMessage> TryFrom<&MessageResult> for AnyMatcher { diff --git a/puffin/src/protocol.rs b/puffin/src/protocol.rs index cfc0638c0..4554467d7 100644 --- a/puffin/src/protocol.rs +++ b/puffin/src/protocol.rs @@ -1,4 +1,4 @@ -use std::fmt::Debug; +use std::{fmt::Debug, marker::PhantomData}; use crate::{ algebra::{signature::Signature, Matcher}, @@ -9,6 +9,22 @@ use crate::{ variable_data::VariableData, }; +/// Store a message flight, a vec of all the messages sent by the PUT between two steps +#[derive(Debug, Clone)] +pub struct MessageFlight, O: OpaqueProtocolMessage> { + pub messages: Vec, + phantom: PhantomData, +} + +impl, O: OpaqueProtocolMessage> MessageFlight { + pub fn new() -> Self { + MessageFlight { + messages: vec![], + phantom: PhantomData, + } + } +} + /// A structured message. This type defines how all possible messages of a protocol. /// Usually this is implemented using an `enum`. pub trait ProtocolMessage: Clone + Debug { diff --git a/puffin/src/trace.rs b/puffin/src/trace.rs index b360a6abe..ac2c8fd28 100644 --- a/puffin/src/trace.rs +++ b/puffin/src/trace.rs @@ -32,7 +32,9 @@ use crate::{ algebra::{dynamic_function::TypeShape, error::FnError, remove_prefix, Matcher, Term}, claims::{Claim, GlobalClaimList, SecurityViolationPolicy}, error::Error, - protocol::{MessageResult, OpaqueProtocolMessage, ProtocolBehavior, ProtocolMessage}, + protocol::{ + MessageFlight, MessageResult, OpaqueProtocolMessage, ProtocolBehavior, ProtocolMessage, + }, put::{PutDescriptor, PutOptions}, put_registry::PutRegistry, variable_data::VariableData, @@ -537,7 +539,7 @@ impl OutputAction { { ctx.next_state(step.agent)?; - let mut flight = vec![]; + let mut flight = MessageFlight::new(); while let Some(message_result) = ctx.take_message_from_outbound(step.agent)? { let matcher = message_result.create_matcher::(); @@ -545,7 +547,7 @@ impl OutputAction { let MessageResult(message, opaque_message) = message_result; match &message { - Some(m) => flight.push(m.clone()), + Some(m) => flight.messages.push(m.clone()), _ => (), }; @@ -584,7 +586,7 @@ impl OutputAction { let flight_knowledge = Knowledge:: { agent_name: step.agent, - matcher: M::flight().into(), + matcher: None, data: Box::new(flight), }; flight_knowledge.debug_print(ctx, &step.agent); diff --git a/tlspuffin/src/protocol.rs b/tlspuffin/src/protocol.rs index c0b477e70..6205fc780 100644 --- a/tlspuffin/src/protocol.rs +++ b/tlspuffin/src/protocol.rs @@ -177,10 +177,6 @@ impl Matcher for msgs::enums::HandshakeType { fn specificity(&self) -> u32 { 1 } - - fn flight() -> Option { - None - } } #[derive(Clone, Debug, PartialEq)] diff --git a/tlspuffin/src/query.rs b/tlspuffin/src/query.rs index 418962531..87bf42214 100644 --- a/tlspuffin/src/query.rs +++ b/tlspuffin/src/query.rs @@ -46,10 +46,6 @@ impl Matcher for TlsQueryMatcher { _ => 0, } } - - fn flight() -> Option { - Some(Self::Flight) - } } impl TryFrom<&MessageResult> for TlsQueryMatcher { diff --git a/tlspuffin/src/tls/fn_utils.rs b/tlspuffin/src/tls/fn_utils.rs index f1d184fdb..ce46da1a4 100644 --- a/tlspuffin/src/tls/fn_utils.rs +++ b/tlspuffin/src/tls/fn_utils.rs @@ -6,7 +6,7 @@ use std::convert::TryFrom; use puffin::{ algebra::error::FnError, codec::{Codec, Reader}, - variable_data::VariableData, + protocol::MessageFlight, }; use crate::tls::{ @@ -82,7 +82,7 @@ pub fn fn_decrypt_handshake( /// Decrypt a whole flight of handshake messages and return a Vec of decrypted messages pub fn fn_decrypt_handshake_flight( - flight: &Vec, + flight: &MessageFlight, server_hello_transcript: &HandshakeHash, server_key_share: &Option>, psk: &Option>, @@ -94,7 +94,7 @@ pub fn fn_decrypt_handshake_flight( let mut decrypted_flight = vec![]; - for msg in flight { + for msg in &flight.messages { if let MessagePayload::ApplicationData(_) = &msg.payload { let mut decrypted_msg = fn_decrypt_multiple_handshake_messages( &msg, diff --git a/tlspuffin/src/tls/seeds.rs b/tlspuffin/src/tls/seeds.rs index d3fef3510..7d508cff3 100644 --- a/tlspuffin/src/tls/seeds.rs +++ b/tlspuffin/src/tls/seeds.rs @@ -5,11 +5,11 @@ use puffin::{ agent::{AgentDescriptor, AgentName, AgentType, TLSVersion}, algebra::Term, + protocol::MessageFlight, term, trace::{Action, InputAction, OutputAction, Step, Trace}, }; -use super::rustls::msgs::handshake::EncryptedExtensions; use crate::{ query::TlsQueryMatcher, tls::{ @@ -17,6 +17,7 @@ use crate::{ rustls::msgs::{ enums::{CipherSuite, Compression, HandshakeType, ProtocolVersion}, handshake::{Random, ServerExtension, SessionID}, + message::{Message, OpaqueMessage}, }, }, }; @@ -808,7 +809,7 @@ pub fn seed_client_attacker_auth(server: AgentName) -> Trace { let extensions = term! { fn_decrypt_handshake_flight( - ((server, 0)[Some(TlsQueryMatcher::Flight)]), // The first flight of messages sent by the server + ((server, 0)/MessageFlight), // The first flight of messages sent by the server (fn_server_hello_transcript(((server, 0)))), (fn_get_server_key_share(((server, 0)[Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ServerHello)))]))), fn_no_psk, @@ -1445,7 +1446,7 @@ pub fn _seed_client_attacker_full( let extensions = term! { fn_decrypt_handshake_flight( - ((server, 0)[Some(TlsQueryMatcher::Flight)]), // The first flight of messages sent by the server + ((server, 0)/MessageFlight), // The first flight of messages sent by the server (@server_hello_transcript), (fn_get_server_key_share(((server, 0)[Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ServerHello)))]))), fn_no_psk, From 2cf2e889e7b32bbe99a309e7d7560cd6eaf9a68f Mon Sep 17 00:00:00 2001 From: Tom Gouville Date: Mon, 29 Apr 2024 17:16:02 +0200 Subject: [PATCH 04/29] add function symbol to create flights --- tlspuffin/src/tls/fn_utils.rs | 13 +++++++++++++ tlspuffin/src/tls/mod.rs | 2 ++ 2 files changed, 15 insertions(+) diff --git a/tlspuffin/src/tls/fn_utils.rs b/tlspuffin/src/tls/fn_utils.rs index ce46da1a4..ed0d2a4f3 100644 --- a/tlspuffin/src/tls/fn_utils.rs +++ b/tlspuffin/src/tls/fn_utils.rs @@ -80,6 +80,19 @@ pub fn fn_decrypt_handshake( .map_err(|_err| FnError::Crypto("Failed to create Message from decrypted data".to_string())) } +pub fn fn_new_flight() -> Result, FnError> { + Ok(MessageFlight::new()) +} + +pub fn fn_append_flight( + flight: &MessageFlight, + msg: &Message, +) -> Result, FnError> { + let mut new_flight = flight.clone(); + new_flight.messages.push(msg.clone()); + Ok(new_flight) +} + /// Decrypt a whole flight of handshake messages and return a Vec of decrypted messages pub fn fn_decrypt_handshake_flight( flight: &MessageFlight, diff --git a/tlspuffin/src/tls/mod.rs b/tlspuffin/src/tls/mod.rs index 485693759..3c9c0623a 100644 --- a/tlspuffin/src/tls/mod.rs +++ b/tlspuffin/src/tls/mod.rs @@ -203,6 +203,8 @@ define_signature!( fn_weak_export_cipher_suite fn_secure_rsa_cipher_suite12 // utils + fn_new_flight + fn_append_flight fn_new_transcript fn_append_transcript fn_decrypt_handshake From 8be0efb351063477b796546e627555d332c0613a Mon Sep 17 00:00:00 2001 From: Tom Gouville Date: Mon, 29 Apr 2024 18:25:07 +0200 Subject: [PATCH 05/29] removing mentions of client_attacker_full_boring --- tlspuffin/src/boringssl/deterministic.rs | 8 ++------ tlspuffin/src/tls/seeds.rs | 6 +----- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/tlspuffin/src/boringssl/deterministic.rs b/tlspuffin/src/boringssl/deterministic.rs index 622aa3345..fecffccd7 100644 --- a/tlspuffin/src/boringssl/deterministic.rs +++ b/tlspuffin/src/boringssl/deterministic.rs @@ -12,12 +12,8 @@ mod tests { use puffin::{put::PutOptions, trace::TraceContext}; use crate::{ - boringssl::deterministic::reset_rand, put_registry::tls_registry, - tls::{ - seeds::{create_corpus, seed_client_attacker_full_boring}, - trace_helper::TraceHelper, - }, + tls::{seeds::seed_client_attacker_full, trace_helper::TraceHelper}, }; // TODO: This test only works in a single threaded cargo test execution @@ -25,7 +21,7 @@ mod tests { fn test_boringssl_no_randomness_full() { let put_registry = tls_registry(); - let trace = seed_client_attacker_full_boring.build_trace(); + let trace = seed_client_attacker_full.build_trace(); let mut ctx1 = TraceContext::new(&put_registry, PutOptions::default()); ctx1.set_deterministic(true); let _ = trace.execute(&mut ctx1); diff --git a/tlspuffin/src/tls/seeds.rs b/tlspuffin/src/tls/seeds.rs index 7d508cff3..0469c4616 100644 --- a/tlspuffin/src/tls/seeds.rs +++ b/tlspuffin/src/tls/seeds.rs @@ -1871,11 +1871,7 @@ pub mod tests { fn test_seed_client_attacker_full() { use crate::tls::trace_helper::TraceExecutor; - let ctx = if cfg!(not(feature = "boringssl-binding")) { - seed_client_attacker_full.execute_trace() - } else { - seed_client_attacker_full_boring.execute_trace() - }; + let ctx = seed_client_attacker_full.execute_trace(); assert!(ctx.agents_successful()); } From b76fbcdf6da4aebd6d5feb1011b8af6af5f98307 Mon Sep 17 00:00:00 2001 From: Tom Gouville Date: Thu, 16 May 2024 11:54:06 +0200 Subject: [PATCH 06/29] removing duplicate way of decrypting handshake and using message flight decryption for all traces --- tlspuffin/src/tls/fn_utils.rs | 41 --------------------------- tlspuffin/src/tls/mod.rs | 2 -- tlspuffin/src/tls/seeds.rs | 22 ++++++--------- tlspuffin/src/tls/vulnerabilities.rs | 42 +++++++++++++++++++--------- 4 files changed, 38 insertions(+), 69 deletions(-) diff --git a/tlspuffin/src/tls/fn_utils.rs b/tlspuffin/src/tls/fn_utils.rs index ed0d2a4f3..5f847ea68 100644 --- a/tlspuffin/src/tls/fn_utils.rs +++ b/tlspuffin/src/tls/fn_utils.rs @@ -50,36 +50,6 @@ pub fn fn_append_transcript( Ok(new_transcript) } -pub fn fn_decrypt_handshake( - application_data: &Message, - server_hello_transcript: &HandshakeHash, - server_key_share: &Option>, - psk: &Option>, - group: &NamedGroup, - client: &bool, - sequence: &u64, -) -> Result { - let (suite, key, _) = tls13_handshake_traffic_secret( - server_hello_transcript, - server_key_share, - psk, - !*client, - group, - )?; - let decrypter = suite - .tls13() - .ok_or_else(|| FnError::Crypto("No tls 1.3 suite".to_owned()))? - .derive_decrypter(&key); - let message = decrypter - .decrypt( - PlainMessage::from(application_data.clone()).into_unencrypted_opaque(), - *sequence, - ) - .map_err(|_err| FnError::Crypto("Failed to decrypt it fn_decrypt_handshake".to_string()))?; - Message::try_from(message) - .map_err(|_err| FnError::Crypto("Failed to create Message from decrypted data".to_string())) -} - pub fn fn_new_flight() -> Result, FnError> { Ok(MessageFlight::new()) } @@ -170,17 +140,6 @@ pub fn fn_decrypt_multiple_handshake_messages( Ok(messages) } -pub fn fn_find_server_hello(messages: &Vec) -> Result { - for msg in messages { - if let MessagePayload::Handshake(x) = &msg.payload { - if x.typ == HandshakeType::ServerHello { - return Ok(msg.clone()); - } - } - } - Err(FnError::Unknown("no server hello".to_owned())) -} - pub fn fn_find_server_certificate(messages: &Vec) -> Result { for msg in messages { if let MessagePayload::Handshake(x) = &msg.payload { diff --git a/tlspuffin/src/tls/mod.rs b/tlspuffin/src/tls/mod.rs index 3c9c0623a..b45cee56e 100644 --- a/tlspuffin/src/tls/mod.rs +++ b/tlspuffin/src/tls/mod.rs @@ -207,10 +207,8 @@ define_signature!( fn_append_flight fn_new_transcript fn_append_transcript - fn_decrypt_handshake fn_decrypt_handshake_flight fn_decrypt_multiple_handshake_messages - fn_find_server_hello fn_find_server_certificate fn_find_server_certificate_request fn_find_server_ticket diff --git a/tlspuffin/src/tls/seeds.rs b/tlspuffin/src/tls/seeds.rs index 0469c4616..80af92c22 100644 --- a/tlspuffin/src/tls/seeds.rs +++ b/tlspuffin/src/tls/seeds.rs @@ -1674,11 +1674,11 @@ pub fn seed_session_resumption_dhe_full( ) }; - let resumption_encrypted_extensions = term! { - fn_decrypt_handshake( - ((server, 0)[Some(TlsQueryMatcher::ApplicationData)]), // Encrypted Extensions + let resumption_decrypted_handshake = term! { + fn_decrypt_handshake_flight( + ((server, 0)/MessageFlight), // The first flight of messages sent by the server (@resumption_server_hello_transcript), - (fn_get_server_key_share(((server, 0)[Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ServerHello)))]))), // + (fn_get_server_key_share(((server, 0)[Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ServerHello)))]))), (fn_psk((@psk))), fn_named_group_secp384r1, fn_true, @@ -1686,6 +1686,10 @@ pub fn seed_session_resumption_dhe_full( ) }; + let resumption_encrypted_extensions = term! { + fn_find_encrypted_extensions((@resumption_decrypted_handshake)) + }; + let resumption_encrypted_extension_transcript = term! { fn_append_transcript( (@resumption_server_hello_transcript), @@ -1694,15 +1698,7 @@ pub fn seed_session_resumption_dhe_full( }; let resumption_server_finished = term! { - fn_decrypt_handshake( - ((server, 1)[Some(TlsQueryMatcher::ApplicationData)]), // Server Handshake Finished - (@resumption_server_hello_transcript), - (fn_get_server_key_share(((server, 0)[Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ServerHello)))]))), // - (fn_psk((@psk))), - fn_named_group_secp384r1, - fn_true, - fn_seq_1 // sequence 1 - ) + fn_find_server_finished((@resumption_decrypted_handshake)) }; let resumption_server_finished_transcript = term! { diff --git a/tlspuffin/src/tls/vulnerabilities.rs b/tlspuffin/src/tls/vulnerabilities.rs index 9989688e1..3b3d3cc60 100644 --- a/tlspuffin/src/tls/vulnerabilities.rs +++ b/tlspuffin/src/tls/vulnerabilities.rs @@ -2,13 +2,21 @@ use puffin::{ agent::{AgentDescriptor, AgentName, AgentType, TLSVersion}, + protocol::MessageFlight, term, trace::{Action, InputAction, OutputAction, Step, Trace}, }; use crate::{ query::TlsQueryMatcher, - tls::{fn_impl::*, rustls::msgs::enums::HandshakeType, seeds::*}, + tls::{ + fn_impl::*, + rustls::msgs::{ + enums::HandshakeType, + message::{Message, OpaqueMessage}, + }, + seeds::*, + }, }; /// https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-25638 @@ -39,19 +47,23 @@ pub fn seed_cve_2022_25638(server: AgentName) -> Trace { ) }; - // ApplicationData 0 is EncryptedExtensions - let certificate_request_message = term! { - fn_decrypt_handshake( - ((server, 1)[Some(TlsQueryMatcher::ApplicationData)]), // Ticket from last session + let decrypted_handshake = term! { + fn_decrypt_handshake_flight( + ((server, 0)/MessageFlight), // The first flight of messages sent by the server (fn_server_hello_transcript(((server, 0)))), - (fn_get_server_key_share(((server, 0)))), + (fn_get_server_key_share(((server, 0)[Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ServerHello)))]))), fn_no_psk, fn_named_group_secp384r1, fn_true, - fn_seq_1 + fn_seq_0 // sequence 0 ) }; + // ApplicationData 0 is EncryptedExtensions + let certificate_request_message = term! { + fn_find_server_certificate_request((@decrypted_handshake)) + }; + let certificate_rsa = term! { fn_certificate13( (fn_get_context((@certificate_request_message))), @@ -192,19 +204,23 @@ pub fn seed_cve_2022_25640(server: AgentName) -> Trace { ) }; - // ApplicationData 0 is EncryptedExtensions - let certificate_request_message = term! { - fn_decrypt_handshake( - ((server, 1)[Some(TlsQueryMatcher::ApplicationData)]), + let decrypted_handshake = term! { + fn_decrypt_handshake_flight( + ((server, 0)/MessageFlight), // The first flight of messages sent by the server (fn_server_hello_transcript(((server, 0)))), - (fn_get_server_key_share(((server, 0)))), + (fn_get_server_key_share(((server, 0)[Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ServerHello)))]))), fn_no_psk, fn_named_group_secp384r1, fn_true, - fn_seq_1 + fn_seq_0 // sequence 0 ) }; + // ApplicationData 0 is EncryptedExtensions + let certificate_request_message = term! { + fn_find_server_certificate_request((@decrypted_handshake)) + }; + let certificate = term! { fn_certificate13( (fn_get_context((@certificate_request_message))), From c179aaae5d0fcb491101c9c5758f3a0c590a1faf Mon Sep 17 00:00:00 2001 From: Tom Gouville Date: Fri, 17 May 2024 10:44:06 +0200 Subject: [PATCH 07/29] removing Flight from TlsQueryMatcher --- tlspuffin/src/query.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tlspuffin/src/query.rs b/tlspuffin/src/query.rs index 87bf42214..e175e9ee0 100644 --- a/tlspuffin/src/query.rs +++ b/tlspuffin/src/query.rs @@ -10,7 +10,6 @@ use crate::tls::rustls::msgs::{ /// It uses [rustls::msgs::enums::{ContentType,HandshakeType}]. #[derive(Debug, Deserialize, Serialize, Clone, Copy, Hash, Eq, PartialEq)] pub enum TlsQueryMatcher { - Flight, ChangeCipherSpec, Alert, Handshake(Option), @@ -21,7 +20,6 @@ pub enum TlsQueryMatcher { impl Matcher for TlsQueryMatcher { fn matches(&self, matcher: &TlsQueryMatcher) -> bool { match matcher { - TlsQueryMatcher::Flight => matches!(self, TlsQueryMatcher::Flight), TlsQueryMatcher::Handshake(query_handshake_type) => match self { TlsQueryMatcher::Handshake(handshake_type) => { handshake_type.matches(query_handshake_type) From 91dfd8f0abcfc45dea7c5199461fa7f014083897 Mon Sep 17 00:00:00 2001 From: Tom Gouville Date: Sun, 19 May 2024 12:43:28 +0200 Subject: [PATCH 08/29] Use Flights to send messages to the PUT --- puffin/src/protocol.rs | 69 +++++++++++++++++++++++++++++++++- puffin/src/stream.rs | 11 ++++-- puffin/src/trace.rs | 31 +++++++++++---- sshpuffin/src/libssh/mod.rs | 4 +- tlspuffin/src/boringssl/mod.rs | 4 +- tlspuffin/src/openssl/mod.rs | 4 +- tlspuffin/src/tcp/mod.rs | 10 ++--- tlspuffin/src/tls/fn_utils.rs | 45 ++++++++++++++++++++++ tlspuffin/src/tls/mod.rs | 1 + tlspuffin/src/wolfssl/mod.rs | 6 +-- 10 files changed, 159 insertions(+), 26 deletions(-) diff --git a/puffin/src/protocol.rs b/puffin/src/protocol.rs index 4554467d7..c3df6e8b9 100644 --- a/puffin/src/protocol.rs +++ b/puffin/src/protocol.rs @@ -1,9 +1,11 @@ use std::{fmt::Debug, marker::PhantomData}; +use log::debug; + use crate::{ algebra::{signature::Signature, Matcher}, claims::{Claim, SecurityViolationPolicy}, - codec::Codec, + codec::{Codec, Reader}, error::Error, trace::Trace, variable_data::VariableData, @@ -23,6 +25,71 @@ impl, O: OpaqueProtocolMessage> MessageFlight { phantom: PhantomData, } } + + pub fn debug(&self, info: &str) { + debug!("{}: {:?}", info, self); + } +} + +/// Store a flight of opaque messages, a vec of all the messages sent by the PUT between two steps +#[derive(Debug, Clone)] +pub struct OpaqueMessageFlight { + pub messages: Vec, +} + +impl OpaqueMessageFlight { + pub fn new() -> Self { + OpaqueMessageFlight { messages: vec![] } + } + + pub fn debug(&self, info: &str) { + debug!("{}: {:?}", info, self); + } + + pub fn get_encoding(self) -> Vec { + let mut buf = Vec::new(); + self.encode(&mut buf); + buf + } +} + +impl, O: OpaqueProtocolMessage> From for MessageFlight { + fn from(value: M) -> Self { + MessageFlight { + messages: vec![value], + phantom: PhantomData, + } + } +} + +impl, O: OpaqueProtocolMessage> From> + for OpaqueMessageFlight +{ + fn from(value: MessageFlight) -> Self { + OpaqueMessageFlight { + messages: value.messages.iter().map(|m| m.create_opaque()).collect(), + } + } +} + +impl Codec for OpaqueMessageFlight { + fn encode(&self, bytes: &mut Vec) { + for msg in &self.messages { + msg.encode(bytes); + } + } + + fn read(_reader: &mut Reader) -> Option { + None + } +} + +impl From for OpaqueMessageFlight { + fn from(value: O) -> Self { + OpaqueMessageFlight { + messages: vec![value], + } + } } /// A structured message. This type defines how all possible messages of a protocol. diff --git a/puffin/src/stream.rs b/puffin/src/stream.rs index 734f9e132..cb95f90b7 100644 --- a/puffin/src/stream.rs +++ b/puffin/src/stream.rs @@ -27,11 +27,14 @@ use log::error; use crate::{ codec::Codec, error::Error, - protocol::{MessageResult, OpaqueProtocolMessage, ProtocolMessage, ProtocolMessageDeframer}, + protocol::{ + MessageResult, OpaqueMessageFlight, OpaqueProtocolMessage, ProtocolMessage, + ProtocolMessageDeframer, + }, }; pub trait Stream, O: OpaqueProtocolMessage> { - fn add_to_inbound(&mut self, opaque_message: &O); + fn add_to_inbound(&mut self, message_flight: &OpaqueMessageFlight); /// Takes a single TLS message from the outbound channel fn take_message_from_outbound(&mut self) -> Result>, Error>; @@ -74,8 +77,8 @@ where E: Into, M: TryInto, { - fn add_to_inbound(&mut self, opaque_message: &D::OpaqueProtocolMessage) { - opaque_message.encode(self.inbound.get_mut()); + fn add_to_inbound(&mut self, message_flight: &OpaqueMessageFlight) { + message_flight.encode(self.inbound.get_mut()); } fn take_message_from_outbound( diff --git a/puffin/src/trace.rs b/puffin/src/trace.rs index ac2c8fd28..74a35b691 100644 --- a/puffin/src/trace.rs +++ b/puffin/src/trace.rs @@ -33,7 +33,8 @@ use crate::{ claims::{Claim, GlobalClaimList, SecurityViolationPolicy}, error::Error, protocol::{ - MessageFlight, MessageResult, OpaqueProtocolMessage, ProtocolBehavior, ProtocolMessage, + MessageFlight, MessageResult, OpaqueMessageFlight, OpaqueProtocolMessage, ProtocolBehavior, + ProtocolMessage, }, put::{PutDescriptor, PutOptions}, put_registry::PutRegistry, @@ -241,10 +242,10 @@ impl TraceContext { pub fn add_to_inbound( &mut self, agent_name: AgentName, - message: &PB::OpaqueProtocolMessage, + message_flight: &OpaqueMessageFlight, ) -> Result<(), Error> { self.find_agent_mut(agent_name) - .map(|agent| agent.put_mut().add_to_inbound(message)) + .map(|agent| agent.put_mut().add_to_inbound(message_flight)) } pub fn next_state(&mut self, agent_name: AgentName) -> Result<(), Error> { @@ -632,19 +633,35 @@ impl InputAction { // message controlled by the attacker let evaluated = self.recipe.evaluate(ctx)?; - if let Some(msg) = evaluated.as_ref().downcast_ref::() { + if let Some(flight) = evaluated + .as_ref() + .downcast_ref::>() + { + flight.debug("Input message flight"); + + ctx.add_to_inbound(step.agent, &flight.clone().into())?; + } else if let Some(flight) = evaluated + .as_ref() + .downcast_ref::>() + { + flight.debug("Input opaque message flight"); + + ctx.add_to_inbound(step.agent, &flight)?; + } else if let Some(msg) = evaluated.as_ref().downcast_ref::() { msg.debug("Input message"); - ctx.add_to_inbound(step.agent, &msg.create_opaque())?; + let message_flight: MessageFlight = + msg.clone().into(); + ctx.add_to_inbound(step.agent, &message_flight.into())?; } else if let Some(opaque_message) = evaluated .as_ref() .downcast_ref::() { opaque_message.debug("Input opaque message"); - ctx.add_to_inbound(step.agent, opaque_message)?; + ctx.add_to_inbound(step.agent, &opaque_message.clone().into())?; } else { return Err(FnError::Unknown(String::from( - "Recipe is not a `ProtocolMessage`, `OpaqueProtocolMessage`!", + "Recipe is not a `ProtocolMessage`, `OpaqueProtocolMessage`, `MessageFlight`!", )) .into()); } diff --git a/sshpuffin/src/libssh/mod.rs b/sshpuffin/src/libssh/mod.rs index 24ce69ab1..a6f8d444f 100644 --- a/sshpuffin/src/libssh/mod.rs +++ b/sshpuffin/src/libssh/mod.rs @@ -23,7 +23,7 @@ use puffin::{ agent::{AgentDescriptor, AgentName, AgentType}, codec::Codec, error::Error, - protocol::MessageResult, + protocol::{MessageResult, OpaqueMessageFlight}, put::{Put, PutName}, put_registry::{Factory, PutKind}, stream::Stream, @@ -212,7 +212,7 @@ pub struct LibSSL { impl LibSSL {} impl Stream for LibSSL { - fn add_to_inbound(&mut self, result: &RawSshMessage) { + fn add_to_inbound(&mut self, result: &OpaqueMessageFlight) { let mut buffer = Vec::new(); Codec::encode(result, &mut buffer); diff --git a/tlspuffin/src/boringssl/mod.rs b/tlspuffin/src/boringssl/mod.rs index fefeffcf5..47f78d948 100644 --- a/tlspuffin/src/boringssl/mod.rs +++ b/tlspuffin/src/boringssl/mod.rs @@ -13,7 +13,7 @@ use log::{debug, info, trace}; use puffin::{ agent::{AgentDescriptor, AgentName, AgentType}, error::Error, - protocol::MessageResult, + protocol::{MessageResult, OpaqueMessageFlight}, put::{Put, PutName}, put_registry::{Factory, PutKind}, stream::{MemoryStream, Stream}, @@ -149,7 +149,7 @@ impl Drop for BoringSSL { } impl Stream for BoringSSL { - fn add_to_inbound(&mut self, result: &OpaqueMessage) { + fn add_to_inbound(&mut self, result: &OpaqueMessageFlight) { as Stream>::add_to_inbound( self.stream.get_mut(), result, diff --git a/tlspuffin/src/openssl/mod.rs b/tlspuffin/src/openssl/mod.rs index 3a4313e9c..0c3001ae3 100644 --- a/tlspuffin/src/openssl/mod.rs +++ b/tlspuffin/src/openssl/mod.rs @@ -9,7 +9,7 @@ use openssl::{ use puffin::{ agent::{AgentDescriptor, AgentName, AgentType}, error::Error, - protocol::MessageResult, + protocol::{MessageResult, OpaqueMessageFlight}, put::{Put, PutName}, put_registry::{Factory, PutKind}, stream::{MemoryStream, Stream}, @@ -142,7 +142,7 @@ impl Drop for OpenSSL { } impl Stream for OpenSSL { - fn add_to_inbound(&mut self, result: &OpaqueMessage) { + fn add_to_inbound(&mut self, result: &OpaqueMessageFlight) { as Stream>::add_to_inbound( self.stream.get_mut(), result, diff --git a/tlspuffin/src/tcp/mod.rs b/tlspuffin/src/tcp/mod.rs index f86b7be83..27de3777b 100644 --- a/tlspuffin/src/tcp/mod.rs +++ b/tlspuffin/src/tcp/mod.rs @@ -15,7 +15,7 @@ use log::{debug, error, info, warn}; use puffin::{ agent::{AgentDescriptor, AgentName, AgentType}, error::Error, - protocol::MessageResult, + protocol::{MessageResult, OpaqueMessageFlight}, put::{Put, PutDescriptor, PutName}, put_registry::{Factory, PutKind}, stream::Stream, @@ -279,8 +279,8 @@ impl TcpPut for TcpServerPut { } impl Stream for TcpServerPut { - fn add_to_inbound(&mut self, opaque_message: &OpaqueMessage) { - self.write_to_stream(&opaque_message.clone().encode()) + fn add_to_inbound(&mut self, opaque_flight: &OpaqueMessageFlight) { + self.write_to_stream(&opaque_flight.clone().get_encoding()) .unwrap(); } @@ -292,8 +292,8 @@ impl Stream for TcpServerPut { } impl Stream for TcpClientPut { - fn add_to_inbound(&mut self, opaque_message: &OpaqueMessage) { - self.write_to_stream(&opaque_message.clone().encode()) + fn add_to_inbound(&mut self, opaque_flight: &OpaqueMessageFlight) { + self.write_to_stream(&opaque_flight.clone().get_encoding()) .unwrap(); } diff --git a/tlspuffin/src/tls/fn_utils.rs b/tlspuffin/src/tls/fn_utils.rs index 5f847ea68..74c66aebe 100644 --- a/tlspuffin/src/tls/fn_utils.rs +++ b/tlspuffin/src/tls/fn_utils.rs @@ -94,6 +94,11 @@ pub fn fn_decrypt_handshake_flight( } } + println!("Decrypting handshake"); + for msg in &decrypted_flight { + println!("DECRYPTED: {:?}", msg); + } + Ok(decrypted_flight) } @@ -214,6 +219,46 @@ pub fn fn_psk(some: &Vec) -> Result>, FnError> { Ok(Some(some.clone())) } +/// Decrypt a whole flight of application messages and return a Vec of decrypted messages +pub fn fn_decrypt_application_flight( + flight: &MessageFlight, + server_hello_transcript: &HandshakeHash, + server_finished_transcript: &HandshakeHash, + server_key_share: &Option>, + psk: &Option>, + group: &NamedGroup, + client: &bool, + sequence: &u64, +) -> Result, FnError> { + let mut sequence_number = *sequence; + + let mut decrypted_flight = vec![]; + + for msg in &flight.messages { + if let MessagePayload::ApplicationData(_) = &msg.payload { + let decrypted_msg = fn_decrypt_application( + &msg, + server_hello_transcript, + server_finished_transcript, + server_key_share, + psk, + group, + client, + &sequence_number, + )?; + + decrypted_flight.push(decrypted_msg); + sequence_number += 1; + } + } + + for msg in &decrypted_flight { + println!("DECRYPTED: {:?}", msg); + } + + Ok(decrypted_flight) +} + pub fn fn_decrypt_application( application_data: &Message, server_hello_transcript: &HandshakeHash, diff --git a/tlspuffin/src/tls/mod.rs b/tlspuffin/src/tls/mod.rs index b45cee56e..ceb4cb968 100644 --- a/tlspuffin/src/tls/mod.rs +++ b/tlspuffin/src/tls/mod.rs @@ -209,6 +209,7 @@ define_signature!( fn_append_transcript fn_decrypt_handshake_flight fn_decrypt_multiple_handshake_messages + fn_decrypt_application_flight fn_find_server_certificate fn_find_server_certificate_request fn_find_server_ticket diff --git a/tlspuffin/src/wolfssl/mod.rs b/tlspuffin/src/wolfssl/mod.rs index d848b9d23..ace0da79d 100644 --- a/tlspuffin/src/wolfssl/mod.rs +++ b/tlspuffin/src/wolfssl/mod.rs @@ -8,7 +8,7 @@ use puffin::{ agent::{AgentDescriptor, AgentName, AgentType, TLSVersion}, algebra::dynamic_function::TypeShape, error::Error, - protocol::MessageResult, + protocol::{MessageResult, OpaqueMessageFlight}, put::{Put, PutName}, put_registry::{Factory, PutKind}, stream::{MemoryStream, Stream}, @@ -148,11 +148,11 @@ pub struct WolfSSL { } impl Stream for WolfSSL { - fn add_to_inbound(&mut self, opaque_message: &OpaqueMessage) { + fn add_to_inbound(&mut self, opaque_flight: &OpaqueMessageFlight) { let raw_stream = self.stream.get_mut(); as Stream>::add_to_inbound( raw_stream, - opaque_message, + opaque_flight, ) } From c2d5cfc7ad2472cbf6381dd813914a3a8faefe49 Mon Sep 17 00:00:00 2001 From: Tom Gouville Date: Tue, 21 May 2024 14:04:21 +0200 Subject: [PATCH 09/29] adding a seed successful using flights to forward messages --- puffin/src/trace.rs | 7 +++-- tlspuffin/src/tls/seeds.rs | 54 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/puffin/src/trace.rs b/puffin/src/trace.rs index 74a35b691..378a25a64 100644 --- a/puffin/src/trace.rs +++ b/puffin/src/trace.rs @@ -547,10 +547,9 @@ impl OutputAction { let MessageResult(message, opaque_message) = message_result; - match &message { - Some(m) => flight.messages.push(m.clone()), - _ => (), - }; + if let Some(m) = &message { + flight.messages.push(m.clone()); + } let knowledge = message .and_then(|message| message.extract_knowledge().ok()) diff --git a/tlspuffin/src/tls/seeds.rs b/tlspuffin/src/tls/seeds.rs index 80af92c22..097ae44fb 100644 --- a/tlspuffin/src/tls/seeds.rs +++ b/tlspuffin/src/tls/seeds.rs @@ -267,6 +267,49 @@ pub fn seed_successful(client: AgentName, server: AgentName) -> Trace Trace { + Trace { + prior_traces: vec![], + descriptors: vec![ + AgentDescriptor::new_client(client, TLSVersion::V1_3), + AgentDescriptor::new_server(server, TLSVersion::V1_3), + ], + steps: vec![ + OutputAction::new_step(client), + // Client Hello Client -> Server + Step { + agent: server, + action: Action::Input(InputAction { + recipe: term! { + (client, 0)/MessageFlight + }, + }), + }, + // ServerHello/EncryptedExtensions/Certificate/CertificateVerify/ServerFinished -> Client + Step { + agent: client, + action: Action::Input(InputAction { + recipe: term! { + (server, 0)/MessageFlight + }, + }), + }, + // Client Finished -> server + Step { + agent: server, + action: Action::Input(InputAction { + recipe: term! { + (client, 1)/MessageFlight + }, + }), + }, + ], + } +} + /// Seed which triggers a MITM attack. It changes the cipher suite. This should fail. pub fn seed_successful_mitm(client: AgentName, server: AgentName) -> Trace { Trace { @@ -1796,6 +1839,7 @@ pub fn create_corpus() -> Vec<(Trace, &'static str)> { corpus!( // Full Handshakes seed_successful: cfg(feature = "tls13"), + seed_successful_with_flights: cfg(feature = "tls13"), seed_successful_with_ccs: cfg(feature = "tls13"), seed_successful_with_tickets: cfg(feature = "tls13"), seed_successful12: cfg(all(feature = "tls12", not(feature = "tls12-session-resumption"))), @@ -1914,6 +1958,16 @@ pub mod tests { assert!(ctx.agents_successful()); } + #[cfg(feature = "tls13")] // require version which supports TLS 1.3 + #[cfg(not(feature = "boringssl-binding"))] + #[test] + fn test_seed_successful_with_flights() { + use crate::tls::trace_helper::TraceExecutor; + + let ctx = seed_successful_with_flights.execute_trace(); + assert!(ctx.agents_successful()); + } + #[cfg(feature = "tls13")] // require version which supports TLS 1.3 #[cfg(not(feature = "boringssl-binding"))] #[test] From 8278fc9fdb5b8f1d2278aa7c2c1c8dced0a0b85e Mon Sep 17 00:00:00 2001 From: Tom Gouville Date: Mon, 27 May 2024 14:02:44 +0200 Subject: [PATCH 10/29] allow to query knowledge without specifying agent name --- puffin/src/algebra/macros.rs | 4 ++-- puffin/src/algebra/mod.rs | 16 ++++++++++------ puffin/src/algebra/signature.rs | 4 ++-- puffin/src/algebra/term.rs | 8 +++++++- puffin/src/trace.rs | 6 +++--- tlspuffin/src/lib.rs | 12 ++++++------ 6 files changed, 30 insertions(+), 20 deletions(-) diff --git a/puffin/src/algebra/macros.rs b/puffin/src/algebra/macros.rs index e3460900f..eceae49c2 100644 --- a/puffin/src/algebra/macros.rs +++ b/puffin/src/algebra/macros.rs @@ -17,7 +17,7 @@ macro_rules! term { use $crate::algebra::signature::Signature; use $crate::algebra::Term; - let var = Signature::new_var($($req_type)?, $agent, None, $counter); // TODO: verify hat using here None is fine. Before a refactor it was: Some(TlsMessageType::Handshake(None)) + let var = Signature::new_var($($req_type)?, Some($agent), None, $counter); // TODO: verify hat using here None is fine. Before a refactor it was: Some(TlsMessageType::Handshake(None)) Term::Variable(var) }}; @@ -35,7 +35,7 @@ macro_rules! term { use $crate::algebra::signature::Signature; use $crate::algebra::Term; - let var = Signature::new_var($($req_type)?, $agent, $message_type, $counter); + let var = Signature::new_var($($req_type)?, Some($agent), $message_type, $counter); Term::Variable(var) }}; diff --git a/puffin/src/algebra/mod.rs b/puffin/src/algebra/mod.rs index 514172f25..bd61c8b7d 100644 --- a/puffin/src/algebra/mod.rs +++ b/puffin/src/algebra/mod.rs @@ -616,8 +616,12 @@ mod tests { //println!("TypeId of vec array {:?}", data.type_id()); - let variable: Variable = - Signature::new_var(TypeShape::of::>(), AgentName::first(), None, 0); + let variable: Variable = Signature::new_var( + TypeShape::of::>(), + Some(AgentName::first()), + None, + 0, + ); let generated_term = Term::Application( hmac256, @@ -681,7 +685,7 @@ mod tests { Term::Application(Signature::new_function(&example_op_c), vec![]), Term::Variable( Signature::new_var_with_type::( - AgentName::first(), + Some(AgentName::first()), None, 0, ), @@ -689,7 +693,7 @@ mod tests { ], ), Term::Variable(Signature::new_var_with_type::( - AgentName::first(), + Some(AgentName::first()), None, 0, )), @@ -702,7 +706,7 @@ mod tests { Signature::new_function(&example_op_c), vec![ Term::Variable(Signature::new_var_with_type::( - AgentName::first(), + Some(AgentName::first()), None, 0, )), @@ -710,7 +714,7 @@ mod tests { ], ), Term::Variable(Signature::new_var_with_type::( - AgentName::first(), + Some(AgentName::first()), None, 0, )), diff --git a/puffin/src/algebra/signature.rs b/puffin/src/algebra/signature.rs index 9b3d30c40..7f5fc771c 100644 --- a/puffin/src/algebra/signature.rs +++ b/puffin/src/algebra/signature.rs @@ -88,7 +88,7 @@ impl Signature { } pub fn new_var_with_type( - agent_name: AgentName, + agent_name: Option, matcher: Option, counter: u16, ) -> Variable { @@ -98,7 +98,7 @@ impl Signature { pub fn new_var( type_shape: TypeShape, - agent_name: AgentName, + agent_name: Option, matcher: Option, counter: u16, ) -> Variable { diff --git a/puffin/src/algebra/term.rs b/puffin/src/algebra/term.rs index ac2182b08..abb9cb287 100644 --- a/puffin/src/algebra/term.rs +++ b/puffin/src/algebra/term.rs @@ -114,7 +114,13 @@ impl Term { Term::Variable(variable) => context .find_variable(variable.typ, &variable.query) .map(|data| data.boxed_any()) - .or_else(|| context.find_claim(variable.query.agent_name, variable.typ)) + .or_else(|| { + if let Some(agent_name) = variable.query.agent_name { + context.find_claim(agent_name, variable.typ) + } else { + None + } + }) .ok_or_else(|| Error::Term(format!("Unable to find variable {}!", variable))), Term::Application(func, args) => { let mut dynamic_args: Vec> = Vec::new(); diff --git a/puffin/src/trace.rs b/puffin/src/trace.rs index 378a25a64..af529eb8f 100644 --- a/puffin/src/trace.rs +++ b/puffin/src/trace.rs @@ -43,7 +43,7 @@ use crate::{ #[derive(Debug, Deserialize, Serialize, Clone, Copy, Hash, Eq, PartialEq)] pub struct Query { - pub agent_name: AgentName, + pub agent_name: Option, pub matcher: Option, pub counter: u16, // in case an agent sends multiple messages of the same type } @@ -52,7 +52,7 @@ impl fmt::Display for Query { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, - "({}, {})[{:?}]", + "({:?}, {})[{:?}]", self.agent_name, self.counter, self.matcher ) } @@ -224,7 +224,7 @@ impl TraceContext { let data: &dyn VariableData = knowledge.data.as_ref(); if query_type_id == data.type_id() - && query.agent_name == knowledge.agent_name + && (query.agent_name == None || query.agent_name == Some(knowledge.agent_name)) && knowledge.matcher.matches(&query.matcher) { possibilities.push(knowledge); diff --git a/tlspuffin/src/lib.rs b/tlspuffin/src/lib.rs index 1b83219af..1e23d0c2e 100644 --- a/tlspuffin/src/lib.rs +++ b/tlspuffin/src/lib.rs @@ -38,32 +38,32 @@ //! Signature::new_function(&fn_client_hello), //! vec![ //! Term::Variable(Signature::new_var_with_type::( -//! client, +//! Some(client), //! Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ClientHello))), //! 0 //! )), //! Term::Variable(Signature::new_var_with_type::( -//! client, +//! Some(client), //! Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ClientHello))), //! 0 //! )), //! Term::Variable(Signature::new_var_with_type::( -//! client, +//! Some(client), //! Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ClientHello))), //! 0 //! )), //! Term::Variable(Signature::new_var_with_type::, _>( -//! client, +//! Some(client), //! Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ClientHello))), //! 0 //! )), //! Term::Variable(Signature::new_var_with_type::, _>( -//! client, +//! Some(client), //! Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ClientHello))), //! 0 //! )), //! Term::Variable(Signature::new_var_with_type::, _>( -//! client, +//! Some(client), //! Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ClientHello))), //! 0 //! )), From a488f2c9ef59b6b4fa589f4810514a181c11f2ef Mon Sep 17 00:00:00 2001 From: Tom Gouville Date: Mon, 27 May 2024 17:14:24 +0200 Subject: [PATCH 11/29] removing debug print --- tlspuffin/src/tls/fn_utils.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tlspuffin/src/tls/fn_utils.rs b/tlspuffin/src/tls/fn_utils.rs index 74c66aebe..4c3102458 100644 --- a/tlspuffin/src/tls/fn_utils.rs +++ b/tlspuffin/src/tls/fn_utils.rs @@ -94,11 +94,6 @@ pub fn fn_decrypt_handshake_flight( } } - println!("Decrypting handshake"); - for msg in &decrypted_flight { - println!("DECRYPTED: {:?}", msg); - } - Ok(decrypted_flight) } @@ -252,10 +247,6 @@ pub fn fn_decrypt_application_flight( } } - for msg in &decrypted_flight { - println!("DECRYPTED: {:?}", msg); - } - Ok(decrypted_flight) } From 052058a97102378754aaef33589f5293ca2f5f31 Mon Sep 17 00:00:00 2001 From: Tom Gouville Date: Thu, 30 May 2024 13:44:46 +0200 Subject: [PATCH 12/29] small fixes + new function symbols for OpaqueMessageFlight --- puffin/src/trace.rs | 2 +- tlspuffin/src/tls/fn_utils.rs | 13 +++++++++++++ tlspuffin/src/tls/mod.rs | 2 ++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/puffin/src/trace.rs b/puffin/src/trace.rs index af529eb8f..acc6b6914 100644 --- a/puffin/src/trace.rs +++ b/puffin/src/trace.rs @@ -660,7 +660,7 @@ impl InputAction { ctx.add_to_inbound(step.agent, &opaque_message.clone().into())?; } else { return Err(FnError::Unknown(String::from( - "Recipe is not a `ProtocolMessage`, `OpaqueProtocolMessage`, `MessageFlight`!", + "Recipe is not a `ProtocolMessage`, `OpaqueProtocolMessage`, `MessageFlight`, `OpaqueMessageFlight` !", )) .into()); } diff --git a/tlspuffin/src/tls/fn_utils.rs b/tlspuffin/src/tls/fn_utils.rs index 4c3102458..07a40d442 100644 --- a/tlspuffin/src/tls/fn_utils.rs +++ b/tlspuffin/src/tls/fn_utils.rs @@ -63,6 +63,19 @@ pub fn fn_append_flight( Ok(new_flight) } +pub fn fn_new_opaque_flight() -> Result, FnError> { + Ok(OpaqueMessageFlight::new()) +} + +pub fn fn_append_opaque_flight( + flight: &OpaqueMessageFlight, + msg: &Message, +) -> Result, FnError> { + let mut new_flight = flight.clone(); + new_flight.messages.push(msg.clone()); + Ok(new_flight) +} + /// Decrypt a whole flight of handshake messages and return a Vec of decrypted messages pub fn fn_decrypt_handshake_flight( flight: &MessageFlight, diff --git a/tlspuffin/src/tls/mod.rs b/tlspuffin/src/tls/mod.rs index ceb4cb968..28b8a141c 100644 --- a/tlspuffin/src/tls/mod.rs +++ b/tlspuffin/src/tls/mod.rs @@ -205,6 +205,8 @@ define_signature!( // utils fn_new_flight fn_append_flight + fn_new_opaque_flight + fn_append_opaque_flight fn_new_transcript fn_append_transcript fn_decrypt_handshake_flight From 2bcd38bc58eec6319832bb3e8caf971812763447 Mon Sep 17 00:00:00 2001 From: Tom Gouville Date: Thu, 30 May 2024 13:51:02 +0200 Subject: [PATCH 13/29] fixing error in fn_append_opaque_flight --- tlspuffin/src/tls/fn_utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tlspuffin/src/tls/fn_utils.rs b/tlspuffin/src/tls/fn_utils.rs index ba92cb0b9..6bac061dc 100644 --- a/tlspuffin/src/tls/fn_utils.rs +++ b/tlspuffin/src/tls/fn_utils.rs @@ -69,7 +69,7 @@ pub fn fn_new_opaque_flight() -> Result, FnEr pub fn fn_append_opaque_flight( flight: &OpaqueMessageFlight, - msg: &Message, + msg: &OpaqueMessage, ) -> Result, FnError> { let mut new_flight = flight.clone(); new_flight.messages.push(msg.clone()); From 0b50087b68743e8b1756851f0ccb7ccceb0b2d0c Mon Sep 17 00:00:00 2001 From: Tom Gouville Date: Thu, 30 May 2024 16:57:08 +0200 Subject: [PATCH 14/29] rewriting seed client_attacker_mitm --- tlspuffin/src/tls/seeds.rs | 61 +++----------------------------------- 1 file changed, 4 insertions(+), 57 deletions(-) diff --git a/tlspuffin/src/tls/seeds.rs b/tlspuffin/src/tls/seeds.rs index 097ae44fb..cbfc4d7f7 100644 --- a/tlspuffin/src/tls/seeds.rs +++ b/tlspuffin/src/tls/seeds.rs @@ -339,74 +339,21 @@ pub fn seed_successful_mitm(client: AgentName, server: AgentName) -> Trace Client - Step { - agent: client, - action: Action::Input(InputAction { - recipe: term! { - fn_server_hello( - ((server, 0)), - ((server, 0)), - ((server, 0)), - ((server, 0)), - ((server, 0)), - ((server, 0)) - ) - }, - }), - }, - // Encrypted Extensions Server -> Client - Step { - agent: client, - action: Action::Input(InputAction { - recipe: term! { - fn_application_data( - ((server, 0)[Some(TlsQueryMatcher::ApplicationData)]/Vec) - ) - }, - }), - }, - // Certificate Server -> Client - Step { - agent: client, - action: Action::Input(InputAction { - recipe: term! { - fn_application_data( - ((server, 1)[Some(TlsQueryMatcher::ApplicationData)]/Vec) - ) - }, - }), - }, - // Certificate Verify Server -> Client - Step { - agent: client, - action: Action::Input(InputAction { - recipe: term! { - fn_application_data( - ((server, 2)[Some(TlsQueryMatcher::ApplicationData)]/Vec) - ) - }, - }), - }, - // Finish Server -> Client + // ServerHello/EncryptedExtensions/Certificate/CertificateVerify/ServerFinished -> Client Step { agent: client, action: Action::Input(InputAction { recipe: term! { - fn_application_data( - ((server, 3)[Some(TlsQueryMatcher::ApplicationData)]/Vec) - ) + (server, 0)/MessageFlight }, }), }, - // Finished Client -> Server + // Client Finished -> server Step { agent: server, action: Action::Input(InputAction { recipe: term! { - fn_application_data( - ((client, 0)[Some(TlsQueryMatcher::ApplicationData)]/Vec) - ) + (client, 1)/MessageFlight }, }), }, From 4751e6f0a73c3cf49b9925aefb21348d75108a27 Mon Sep 17 00:00:00 2001 From: Tom Gouville Date: Fri, 31 May 2024 10:33:02 +0200 Subject: [PATCH 15/29] adding TODO to the seeds that are not working with boringssl --- tlspuffin/src/tls/seeds.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/tlspuffin/src/tls/seeds.rs b/tlspuffin/src/tls/seeds.rs index cbfc4d7f7..3880f95c3 100644 --- a/tlspuffin/src/tls/seeds.rs +++ b/tlspuffin/src/tls/seeds.rs @@ -167,6 +167,7 @@ pub fn seed_successful_client_auth(client: AgentName, server: AgentName) -> Trac } } +// TODO: `[BAD_DECRYPT] [DECRYPTION_FAILED_OR_BAD_RECORD_MAC]` error with BoringSSL pub fn seed_successful(client: AgentName, server: AgentName) -> Trace { Trace { prior_traces: vec![], @@ -267,6 +268,7 @@ pub fn seed_successful(client: AgentName, server: AgentName) -> Trace Trace Trace Trace { let mut trace = seed_successful(client, server); @@ -554,6 +558,7 @@ pub fn seed_successful_with_ccs(client: AgentName, server: AgentName) -> Trace Trace { let curve = term! { fn_get_any_client_curve( @@ -1135,6 +1141,7 @@ pub fn _seed_client_attacker12( (trace, client_verify_data) } +// TODO: `Unable to find variable (Some(AgentName(0)), 4)[Some(ApplicationData)]/Message!` error with BoringSSL pub fn seed_session_resumption_dhe( initial_server: AgentName, server: AgentName, @@ -1259,6 +1266,7 @@ pub fn seed_session_resumption_dhe( } } +// TODO: `Unable to find variable (Some(AgentName(0)), 4)[Some(ApplicationData)]/Message!` error with BoringSSL pub fn seed_session_resumption_ke( initial_server: AgentName, server: AgentName, @@ -1786,7 +1794,12 @@ pub fn create_corpus() -> Vec<(Trace, &'static str)> { corpus!( // Full Handshakes seed_successful: cfg(feature = "tls13"), - seed_successful_with_flights: cfg(feature = "tls13"), + // TODO: seed_successful_with_flights should supersede seed_succesful in the futur + // when accessing all inner elements of a flight will be possible with queries and + // not specific function symbols + // for now, this trace give the ability to forward a whole flight of packets in one + // step + // seed_successful_with_flights: cfg(feature = "tls13"), seed_successful_with_ccs: cfg(feature = "tls13"), seed_successful_with_tickets: cfg(feature = "tls13"), seed_successful12: cfg(all(feature = "tls12", not(feature = "tls12-session-resumption"))), From d7fadd8587139e362cc19862ed17fb69036f08a5 Mon Sep 17 00:00:00 2001 From: Tom Gouville Date: Mon, 3 Jun 2024 16:32:06 +0200 Subject: [PATCH 16/29] Adding traits ProtocolMessageFlight and OpaqueProtocolMessageFlight --- puffin/src/algebra/mod.rs | 67 ++++++++++++++++++- puffin/src/protocol.rs | 96 ++++++---------------------- puffin/src/put.rs | 2 +- puffin/src/stream.rs | 17 +++-- puffin/src/trace.rs | 17 +++-- sshpuffin/src/libssh/mod.rs | 8 +-- sshpuffin/src/protocol.rs | 92 +++++++++++++++++++++++++- tlspuffin/src/boringssl/mod.rs | 23 ++++--- tlspuffin/src/openssl/mod.rs | 12 ++-- tlspuffin/src/protocol.rs | 91 +++++++++++++++++++++++++- tlspuffin/src/tcp/mod.rs | 12 ++-- tlspuffin/src/tls/fn_utils.rs | 54 ++++++++-------- tlspuffin/src/tls/seeds.rs | 19 +++--- tlspuffin/src/tls/vulnerabilities.rs | 15 ++--- tlspuffin/src/wolfssl/mod.rs | 16 ++--- 15 files changed, 366 insertions(+), 175 deletions(-) diff --git a/puffin/src/algebra/mod.rs b/puffin/src/algebra/mod.rs index bd61c8b7d..1897b116a 100644 --- a/puffin/src/algebra/mod.rs +++ b/puffin/src/algebra/mod.rs @@ -127,7 +127,8 @@ pub mod test_signature { define_signature, error::Error, protocol::{ - OpaqueProtocolMessage, ProtocolBehavior, ProtocolMessage, ProtocolMessageDeframer, + OpaqueProtocolMessage, OpaqueProtocolMessageFlight, ProtocolBehavior, ProtocolMessage, + ProtocolMessageDeframer, ProtocolMessageFlight, }, put::{Put, PutName}, put_registry::{Factory, PutKind}, @@ -486,6 +487,68 @@ pub mod test_signature { } } + #[derive(Debug, Clone)] + pub struct TestMessageFlight; + + impl ProtocolMessageFlight for TestMessageFlight { + fn new() -> Self { + Self {} + } + + fn push(&mut self, _msg: TestMessage) { + panic!("Not implemented for test stub"); + } + + fn debug(&self, _info: &str) { + panic!("Not implemented for test stub"); + } + } + + impl From for TestMessageFlight { + fn from(_value: TestMessage) -> Self { + Self {} + } + } + + #[derive(Debug, Clone)] + pub struct TestOpaqueMessageFlight; + + impl OpaqueProtocolMessageFlight for TestOpaqueMessageFlight { + fn new() -> Self { + Self {} + } + + fn push(&mut self, _msg: TestOpaqueMessage) { + panic!("Not implemented for test stub"); + } + + fn debug(&self, _info: &str) { + panic!("Not implemented for test stub"); + } + } + + impl From for TestOpaqueMessageFlight { + fn from(_value: TestOpaqueMessage) -> Self { + Self {} + } + } + + impl Codec for TestOpaqueMessageFlight { + fn encode(&self, _bytes: &mut Vec) { + panic!("Not implemented for test stub"); + } + + fn read(_: &mut Reader) -> Option { + panic!("Not implemented for test stub"); + } + } + + impl From for TestOpaqueMessageFlight { + fn from(_value: TestMessageFlight) -> Self { + Self {} + } + } + #[derive(Debug, PartialEq)] pub struct TestProtocolBehavior; @@ -495,6 +558,8 @@ pub mod test_signature { type ProtocolMessage = TestMessage; type OpaqueProtocolMessage = TestOpaqueMessage; type Matcher = AnyMatcher; + type ProtocolMessageFlight = TestMessageFlight; + type OpaqueProtocolMessageFlight = TestOpaqueMessageFlight; fn signature() -> &'static Signature { panic!("Not implemented for test stub"); diff --git a/puffin/src/protocol.rs b/puffin/src/protocol.rs index 4658f047a..af6f04e19 100644 --- a/puffin/src/protocol.rs +++ b/puffin/src/protocol.rs @@ -1,97 +1,37 @@ -use std::{fmt::Debug, marker::PhantomData}; - -use log::debug; +use std::fmt::Debug; use crate::{ algebra::{signature::Signature, Matcher}, claims::{Claim, SecurityViolationPolicy}, - codec::{Codec, Reader}, + codec::Codec, error::Error, trace::Trace, variable_data::VariableData, }; /// Store a message flight, a vec of all the messages sent by the PUT between two steps -#[derive(Debug, Clone)] -pub struct MessageFlight, O: OpaqueProtocolMessage> { - pub messages: Vec, - phantom: PhantomData, -} - -impl, O: OpaqueProtocolMessage> MessageFlight { - pub fn new() -> Self { - MessageFlight { - messages: vec![], - phantom: PhantomData, - } - } - - pub fn debug(&self, info: &str) { - debug!("{}: {:?}", info, self); - } +pub trait ProtocolMessageFlight, O: OpaqueProtocolMessage>: + Clone + Debug + From +{ + fn new() -> Self; + fn push(&mut self, msg: M); + fn debug(&self, info: &str); } /// Store a flight of opaque messages, a vec of all the messages sent by the PUT between two steps -#[derive(Debug, Clone)] -pub struct OpaqueMessageFlight { - pub messages: Vec, -} - -impl OpaqueMessageFlight { - pub fn new() -> Self { - OpaqueMessageFlight { messages: vec![] } - } - - pub fn debug(&self, info: &str) { - debug!("{}: {:?}", info, self); - } - - pub fn get_encoding(self) -> Vec { +pub trait OpaqueProtocolMessageFlight: + Clone + Debug + Codec + From +{ + fn new() -> Self; + fn debug(&self, info: &str); + fn push(&mut self, msg: O); + fn get_encoding(self) -> Vec { let mut buf = Vec::new(); self.encode(&mut buf); buf } } -impl, O: OpaqueProtocolMessage> From for MessageFlight { - fn from(value: M) -> Self { - MessageFlight { - messages: vec![value], - phantom: PhantomData, - } - } -} - -impl, O: OpaqueProtocolMessage> From> - for OpaqueMessageFlight -{ - fn from(value: MessageFlight) -> Self { - OpaqueMessageFlight { - messages: value.messages.iter().map(|m| m.create_opaque()).collect(), - } - } -} - -impl Codec for OpaqueMessageFlight { - fn encode(&self, bytes: &mut Vec) { - for msg in &self.messages { - msg.encode(bytes); - } - } - - fn read(_reader: &mut Reader) -> Option { - None - } -} - -impl From for OpaqueMessageFlight { - fn from(value: O) -> Self { - OpaqueMessageFlight { - messages: vec![value], - } - } -} - /// A structured message. This type defines how all possible messages of a protocol. /// Usually this is implemented using an `enum`. pub trait ProtocolMessage: Clone + Debug { @@ -133,6 +73,12 @@ pub trait ProtocolBehavior: 'static { type ProtocolMessage: ProtocolMessage; type OpaqueProtocolMessage: OpaqueProtocolMessage; + type ProtocolMessageFlight: ProtocolMessageFlight< + Self::ProtocolMessage, + Self::OpaqueProtocolMessage, + >; + type OpaqueProtocolMessageFlight: OpaqueProtocolMessageFlight + + From; type Matcher: Matcher + for<'a> TryFrom<&'a MessageResult>; diff --git a/puffin/src/put.rs b/puffin/src/put.rs index c3eb1b507..9b064194c 100644 --- a/puffin/src/put.rs +++ b/puffin/src/put.rs @@ -67,7 +67,7 @@ pub struct PutDescriptor { /// Generic trait used to define the interface with a concrete library /// implementing the protocol. pub trait Put: - Stream + 'static + Stream + 'static { /// Process incoming buffer, internal progress, can fill in the output buffer fn progress(&mut self, agent_name: &AgentName) -> Result<(), Error>; diff --git a/puffin/src/stream.rs b/puffin/src/stream.rs index ec63fd8d5..1a1123498 100644 --- a/puffin/src/stream.rs +++ b/puffin/src/stream.rs @@ -25,16 +25,16 @@ use std::{ use log::error; use crate::{ - codec::Codec, error::Error, protocol::{ - MessageResult, OpaqueMessageFlight, OpaqueProtocolMessage, ProtocolMessage, + MessageResult, OpaqueProtocolMessage, OpaqueProtocolMessageFlight, ProtocolMessage, ProtocolMessageDeframer, }, }; -pub trait Stream, O: OpaqueProtocolMessage> { - fn add_to_inbound(&mut self, message_flight: &OpaqueMessageFlight); +pub trait Stream, O: OpaqueProtocolMessage, F: OpaqueProtocolMessageFlight> +{ + fn add_to_inbound(&mut self, message_flight: &F); /// Takes a single TLS message from the outbound channel fn take_message_from_outbound(&mut self) -> Result>, Error>; @@ -70,14 +70,19 @@ impl MemoryStream { } } -impl Stream for MemoryStream +impl< + M, + D: ProtocolMessageDeframer, + E, + F: OpaqueProtocolMessageFlight, + > Stream for MemoryStream where M: ProtocolMessage, D::OpaqueProtocolMessage: TryInto, E: Into, M: TryInto, { - fn add_to_inbound(&mut self, message_flight: &OpaqueMessageFlight) { + fn add_to_inbound(&mut self, message_flight: &F) { message_flight.encode(self.inbound.get_mut()); } diff --git a/puffin/src/trace.rs b/puffin/src/trace.rs index 9305b3bf2..ed5db44f7 100644 --- a/puffin/src/trace.rs +++ b/puffin/src/trace.rs @@ -33,8 +33,8 @@ use crate::{ claims::{Claim, GlobalClaimList, SecurityViolationPolicy}, error::Error, protocol::{ - MessageFlight, MessageResult, OpaqueMessageFlight, OpaqueProtocolMessage, ProtocolBehavior, - ProtocolMessage, + MessageResult, OpaqueProtocolMessage, OpaqueProtocolMessageFlight, ProtocolBehavior, + ProtocolMessage, ProtocolMessageFlight, }, put::{PutDescriptor, PutOptions}, put_registry::PutRegistry, @@ -242,7 +242,7 @@ impl TraceContext { pub fn add_to_inbound( &mut self, agent_name: AgentName, - message_flight: &OpaqueMessageFlight, + message_flight: &PB::OpaqueProtocolMessageFlight, ) -> Result<(), Error> { self.find_agent_mut(agent_name) .map(|agent| agent.put_mut().add_to_inbound(message_flight)) @@ -540,7 +540,7 @@ impl OutputAction { { ctx.next_state(step.agent)?; - let mut flight = MessageFlight::new(); + let mut flight = PB::ProtocolMessageFlight::new(); while let Some(message_result) = ctx.take_message_from_outbound(step.agent)? { let matcher = message_result.create_matcher::(); @@ -548,7 +548,7 @@ impl OutputAction { let MessageResult(message, opaque_message) = message_result; if let Some(m) = &message { - flight.messages.push(m.clone()); + flight.push(m.clone()); } let knowledge = message @@ -634,14 +634,14 @@ impl InputAction { if let Some(flight) = evaluated .as_ref() - .downcast_ref::>() + .downcast_ref::() { flight.debug("Input message flight"); ctx.add_to_inbound(step.agent, &flight.clone().into())?; } else if let Some(flight) = evaluated .as_ref() - .downcast_ref::>() + .downcast_ref::() { flight.debug("Input opaque message flight"); @@ -649,8 +649,7 @@ impl InputAction { } else if let Some(msg) = evaluated.as_ref().downcast_ref::() { msg.debug("Input message"); - let message_flight: MessageFlight = - msg.clone().into(); + let message_flight: PB::ProtocolMessageFlight = msg.clone().into(); ctx.add_to_inbound(step.agent, &message_flight.into())?; } else if let Some(opaque_message) = evaluated .as_ref() diff --git a/sshpuffin/src/libssh/mod.rs b/sshpuffin/src/libssh/mod.rs index a6f8d444f..b7155c72e 100644 --- a/sshpuffin/src/libssh/mod.rs +++ b/sshpuffin/src/libssh/mod.rs @@ -23,7 +23,7 @@ use puffin::{ agent::{AgentDescriptor, AgentName, AgentType}, codec::Codec, error::Error, - protocol::{MessageResult, OpaqueMessageFlight}, + protocol::MessageResult, put::{Put, PutName}, put_registry::{Factory, PutKind}, stream::Stream, @@ -36,7 +36,7 @@ use crate::{ SessionOption, SessionState, SshAuthResult, SshBind, SshBindOption, SshKey, SshRequest, SshResult, SshSession, }, - protocol::SshProtocolBehavior, + protocol::{RawSshMessageFlight, SshProtocolBehavior}, put_registry::LIBSSH_PUT, ssh::{ deframe::SshMessageDeframer, @@ -211,8 +211,8 @@ pub struct LibSSL { impl LibSSL {} -impl Stream for LibSSL { - fn add_to_inbound(&mut self, result: &OpaqueMessageFlight) { +impl Stream for LibSSL { + fn add_to_inbound(&mut self, result: &RawSshMessageFlight) { let mut buffer = Vec::new(); Codec::encode(result, &mut buffer); diff --git a/sshpuffin/src/protocol.rs b/sshpuffin/src/protocol.rs index da2c31d32..091efe3eb 100644 --- a/sshpuffin/src/protocol.rs +++ b/sshpuffin/src/protocol.rs @@ -1,18 +1,106 @@ +use log::debug; use puffin::{ algebra::{signature::Signature, AnyMatcher}, - protocol::ProtocolBehavior, + codec::{Codec, Reader}, + protocol::{ + OpaqueProtocolMessageFlight, ProtocolBehavior, ProtocolMessage, ProtocolMessageDeframer, + ProtocolMessageFlight, + }, trace::Trace, }; use crate::{ claim::SshClaim, ssh::{ + deframe::SshMessageDeframer, message::{RawSshMessage, SshMessage}, SSH_SIGNATURE, }, violation::SshSecurityViolationPolicy, }; +#[derive(Debug, Clone)] +pub struct SshMessageFlight { + pub messages: Vec, +} + +impl ProtocolMessageFlight for SshMessageFlight { + fn new() -> Self { + Self { messages: vec![] } + } + + fn push(&mut self, msg: SshMessage) { + self.messages.push(msg); + } + + fn debug(&self, info: &str) { + debug!("{}: {:?}", info, self); + } +} + +impl From for SshMessageFlight { + fn from(value: SshMessage) -> Self { + Self { + messages: vec![value], + } + } +} + +#[derive(Debug, Clone)] +pub struct RawSshMessageFlight { + pub messages: Vec, +} + +impl OpaqueProtocolMessageFlight for RawSshMessageFlight { + fn new() -> Self { + Self { messages: vec![] } + } + + fn push(&mut self, msg: RawSshMessage) { + self.messages.push(msg); + } + + fn debug(&self, info: &str) { + debug!("{}: {:?}", info, self); + } +} + +impl Codec for RawSshMessageFlight { + fn encode(&self, bytes: &mut Vec) { + for msg in &self.messages { + msg.encode(bytes); + } + } + + fn read(reader: &mut Reader) -> Option { + let mut deframer = SshMessageDeframer::new(); + let mut flight = Self::new(); + + let _ = deframer.read(&mut reader.rest()); + while let Some(msg) = deframer.pop_frame() { + flight.push(msg); + } + + Some(flight) + } +} + +impl From for RawSshMessageFlight { + fn from(value: SshMessageFlight) -> Self { + Self { + messages: value.messages.iter().map(|m| m.create_opaque()).collect(), + } + } +} + +impl From for RawSshMessageFlight { + fn from(value: RawSshMessage) -> Self { + Self { + messages: vec![value], + } + } +} + #[derive(Clone, Debug, PartialEq)] pub struct SshProtocolBehavior {} @@ -22,6 +110,8 @@ impl ProtocolBehavior for SshProtocolBehavior { type ProtocolMessage = SshMessage; type OpaqueProtocolMessage = RawSshMessage; type Matcher = AnyMatcher; + type ProtocolMessageFlight = SshMessageFlight; + type OpaqueProtocolMessageFlight = RawSshMessageFlight; fn signature() -> &'static Signature { &SSH_SIGNATURE diff --git a/tlspuffin/src/boringssl/mod.rs b/tlspuffin/src/boringssl/mod.rs index 47f78d948..30d7148ca 100644 --- a/tlspuffin/src/boringssl/mod.rs +++ b/tlspuffin/src/boringssl/mod.rs @@ -13,7 +13,7 @@ use log::{debug, info, trace}; use puffin::{ agent::{AgentDescriptor, AgentName, AgentType}, error::Error, - protocol::{MessageResult, OpaqueMessageFlight}, + protocol::MessageResult, put::{Put, PutName}, put_registry::{Factory, PutKind}, stream::{MemoryStream, Stream}, @@ -27,7 +27,7 @@ use crate::{ ClaimData, ClaimDataTranscript, TlsClaim, TranscriptCertificate, TranscriptClientFinished, TranscriptServerFinished, TranscriptServerHello, }, - protocol::TLSProtocolBehavior, + protocol::{OpaqueMessageFlight, TLSProtocolBehavior}, put::TlsPutConfig, put_registry::BORINGSSL_PUT, static_certs::{ALICE_CERT, ALICE_PRIVATE_KEY, BOB_CERT, BOB_PRIVATE_KEY, EVE_CERT}, @@ -148,12 +148,13 @@ impl Drop for BoringSSL { } } -impl Stream for BoringSSL { - fn add_to_inbound(&mut self, result: &OpaqueMessageFlight) { - as Stream>::add_to_inbound( - self.stream.get_mut(), - result, - ) +impl Stream for BoringSSL { + fn add_to_inbound(&mut self, result: &OpaqueMessageFlight) { + as Stream< + Message, + OpaqueMessage, + OpaqueMessageFlight + >>::add_to_inbound(self.stream.get_mut(), result) } fn take_message_from_outbound( @@ -161,7 +162,11 @@ impl Stream for BoringSSL { ) -> Result>, Error> { let memory_stream = self.stream.get_mut(); - MemoryStream::take_message_from_outbound(memory_stream) + as Stream< + Message, + OpaqueMessage, + OpaqueMessageFlight + >>::take_message_from_outbound(memory_stream) } } diff --git a/tlspuffin/src/openssl/mod.rs b/tlspuffin/src/openssl/mod.rs index 0c3001ae3..f618e8b55 100644 --- a/tlspuffin/src/openssl/mod.rs +++ b/tlspuffin/src/openssl/mod.rs @@ -9,7 +9,7 @@ use openssl::{ use puffin::{ agent::{AgentDescriptor, AgentName, AgentType}, error::Error, - protocol::{MessageResult, OpaqueMessageFlight}, + protocol::MessageResult, put::{Put, PutName}, put_registry::{Factory, PutKind}, stream::{MemoryStream, Stream}, @@ -19,7 +19,7 @@ use puffin::{ use crate::{ openssl::util::{set_max_protocol_version, static_rsa_cert}, - protocol::TLSProtocolBehavior, + protocol::{OpaqueMessageFlight, TLSProtocolBehavior}, put::TlsPutConfig, put_registry::OPENSSL111_PUT, static_certs::{ALICE_CERT, ALICE_PRIVATE_KEY, BOB_CERT, BOB_PRIVATE_KEY, EVE_CERT}, @@ -141,9 +141,9 @@ impl Drop for OpenSSL { } } -impl Stream for OpenSSL { - fn add_to_inbound(&mut self, result: &OpaqueMessageFlight) { - as Stream>::add_to_inbound( +impl Stream for OpenSSL { + fn add_to_inbound(&mut self, result: &OpaqueMessageFlight) { + as Stream>::add_to_inbound( self.stream.get_mut(), result, ) @@ -155,7 +155,7 @@ impl Stream for OpenSSL { let memory_stream = self.stream.get_mut(); //memory_stream.take_message_from_outbound() - MemoryStream::take_message_from_outbound(memory_stream) + as Stream>::take_message_from_outbound(memory_stream) } } diff --git a/tlspuffin/src/protocol.rs b/tlspuffin/src/protocol.rs index 3034b63ea..73c0479b2 100644 --- a/tlspuffin/src/protocol.rs +++ b/tlspuffin/src/protocol.rs @@ -1,7 +1,12 @@ +use log::debug; use puffin::{ algebra::{signature::Signature, Matcher}, + codec::{Codec, Reader}, error::Error, - protocol::{OpaqueProtocolMessage, ProtocolBehavior, ProtocolMessage, ProtocolMessageDeframer}, + protocol::{ + OpaqueProtocolMessage, OpaqueProtocolMessageFlight, ProtocolBehavior, ProtocolMessage, + ProtocolMessageDeframer, ProtocolMessageFlight, + }, trace::Trace, variable_data::VariableData, }; @@ -25,6 +30,88 @@ use crate::{ }, }; +#[derive(Debug, Clone)] +pub struct MessageFlight { + pub messages: Vec, +} + +impl ProtocolMessageFlight for MessageFlight { + fn new() -> Self { + Self { messages: vec![] } + } + + fn push(&mut self, msg: Message) { + self.messages.push(msg); + } + + fn debug(&self, info: &str) { + debug!("{}: {:?}", info, self); + } +} + +impl From for MessageFlight { + fn from(value: Message) -> Self { + Self { + messages: vec![value], + } + } +} + +#[derive(Debug, Clone)] +pub struct OpaqueMessageFlight { + pub messages: Vec, +} + +impl OpaqueProtocolMessageFlight for OpaqueMessageFlight { + fn new() -> Self { + Self { messages: vec![] } + } + + fn push(&mut self, msg: OpaqueMessage) { + self.messages.push(msg); + } + + fn debug(&self, info: &str) { + debug!("{}: {:?}", info, self); + } +} + +impl Codec for OpaqueMessageFlight { + fn encode(&self, bytes: &mut Vec) { + for msg in &self.messages { + msg.encode(bytes); + } + } + + fn read(reader: &mut Reader) -> Option { + let mut deframer = MessageDeframer::new(); + let mut flight = Self::new(); + + let _ = deframer.read(&mut reader.rest()); + while let Some(msg) = deframer.pop_frame() { + flight.push(msg); + } + + Some(flight) + } +} + +impl From for OpaqueMessageFlight { + fn from(value: MessageFlight) -> Self { + Self { + messages: value.messages.iter().map(|m| m.create_opaque()).collect(), + } + } +} + +impl From for OpaqueMessageFlight { + fn from(value: OpaqueMessage) -> Self { + Self { + messages: vec![value], + } + } +} + impl ProtocolMessage for Message { fn create_opaque(&self) -> OpaqueMessage { msgs::message::PlainMessage::from(self.clone()).into_unencrypted_opaque() @@ -189,6 +276,8 @@ impl ProtocolBehavior for TLSProtocolBehavior { type ProtocolMessage = Message; type OpaqueProtocolMessage = OpaqueMessage; type Matcher = TlsQueryMatcher; + type ProtocolMessageFlight = MessageFlight; + type OpaqueProtocolMessageFlight = OpaqueMessageFlight; fn signature() -> &'static Signature { &TLS_SIGNATURE diff --git a/tlspuffin/src/tcp/mod.rs b/tlspuffin/src/tcp/mod.rs index 27de3777b..a7bb9aa13 100644 --- a/tlspuffin/src/tcp/mod.rs +++ b/tlspuffin/src/tcp/mod.rs @@ -15,7 +15,7 @@ use log::{debug, error, info, warn}; use puffin::{ agent::{AgentDescriptor, AgentName, AgentType}, error::Error, - protocol::{MessageResult, OpaqueMessageFlight}, + protocol::{MessageResult, OpaqueProtocolMessageFlight}, put::{Put, PutDescriptor, PutName}, put_registry::{Factory, PutKind}, stream::Stream, @@ -24,7 +24,7 @@ use puffin::{ }; use crate::{ - protocol::TLSProtocolBehavior, + protocol::{OpaqueMessageFlight, TLSProtocolBehavior}, put_registry::TCP_PUT, tls::rustls::msgs::{ deframer::MessageDeframer, @@ -278,8 +278,8 @@ impl TcpPut for TcpServerPut { } } -impl Stream for TcpServerPut { - fn add_to_inbound(&mut self, opaque_flight: &OpaqueMessageFlight) { +impl Stream for TcpServerPut { + fn add_to_inbound(&mut self, opaque_flight: &OpaqueMessageFlight) { self.write_to_stream(&opaque_flight.clone().get_encoding()) .unwrap(); } @@ -291,8 +291,8 @@ impl Stream for TcpServerPut { } } -impl Stream for TcpClientPut { - fn add_to_inbound(&mut self, opaque_flight: &OpaqueMessageFlight) { +impl Stream for TcpClientPut { + fn add_to_inbound(&mut self, opaque_flight: &OpaqueMessageFlight) { self.write_to_stream(&opaque_flight.clone().get_encoding()) .unwrap(); } diff --git a/tlspuffin/src/tls/fn_utils.rs b/tlspuffin/src/tls/fn_utils.rs index 6bac061dc..4097b4f5b 100644 --- a/tlspuffin/src/tls/fn_utils.rs +++ b/tlspuffin/src/tls/fn_utils.rs @@ -6,27 +6,30 @@ use std::convert::TryFrom; use puffin::{ algebra::error::FnError, codec::{Codec, Reader}, - protocol::{MessageFlight, OpaqueMessageFlight}, + protocol::{OpaqueProtocolMessageFlight, ProtocolMessageFlight}, }; -use crate::tls::{ - key_exchange::{tls12_key_exchange, tls12_new_secrets}, - key_schedule::*, - rustls::{ - conn::Side, - hash_hs::HandshakeHash, - key::Certificate, - msgs::{ - base::PayloadU8, - enums::{HandshakeType, NamedGroup}, - handshake::{ - CertificateEntry, CertificateExtension, CertificateExtensions, - HandshakeMessagePayload, HandshakePayload, Random, ServerECDHParams, +use crate::{ + protocol::{MessageFlight, OpaqueMessageFlight}, + tls::{ + key_exchange::{tls12_key_exchange, tls12_new_secrets}, + key_schedule::*, + rustls::{ + conn::Side, + hash_hs::HandshakeHash, + key::Certificate, + msgs::{ + base::PayloadU8, + enums::{HandshakeType, NamedGroup}, + handshake::{ + CertificateEntry, CertificateExtension, CertificateExtensions, + HandshakeMessagePayload, HandshakePayload, Random, ServerECDHParams, + }, + message::{Message, MessagePayload, OpaqueMessage, PlainMessage}, }, - message::{Message, MessagePayload, OpaqueMessage, PlainMessage}, + tls12, + tls13::key_schedule::KeyScheduleEarly, }, - tls12, - tls13::key_schedule::KeyScheduleEarly, }, }; @@ -50,27 +53,24 @@ pub fn fn_append_transcript( Ok(new_transcript) } -pub fn fn_new_flight() -> Result, FnError> { +pub fn fn_new_flight() -> Result { Ok(MessageFlight::new()) } -pub fn fn_append_flight( - flight: &MessageFlight, - msg: &Message, -) -> Result, FnError> { +pub fn fn_append_flight(flight: &MessageFlight, msg: &Message) -> Result { let mut new_flight = flight.clone(); new_flight.messages.push(msg.clone()); Ok(new_flight) } -pub fn fn_new_opaque_flight() -> Result, FnError> { +pub fn fn_new_opaque_flight() -> Result { Ok(OpaqueMessageFlight::new()) } pub fn fn_append_opaque_flight( - flight: &OpaqueMessageFlight, + flight: &OpaqueMessageFlight, msg: &OpaqueMessage, -) -> Result, FnError> { +) -> Result { let mut new_flight = flight.clone(); new_flight.messages.push(msg.clone()); Ok(new_flight) @@ -78,7 +78,7 @@ pub fn fn_append_opaque_flight( /// Decrypt a whole flight of handshake messages and return a Vec of decrypted messages pub fn fn_decrypt_handshake_flight( - flight: &MessageFlight, + flight: &MessageFlight, server_hello_transcript: &HandshakeHash, server_key_share: &Option>, psk: &Option>, @@ -229,7 +229,7 @@ pub fn fn_psk(some: &Vec) -> Result>, FnError> { /// Decrypt a whole flight of application messages and return a Vec of decrypted messages pub fn fn_decrypt_application_flight( - flight: &MessageFlight, + flight: &MessageFlight, server_hello_transcript: &HandshakeHash, server_finished_transcript: &HandshakeHash, server_key_share: &Option>, diff --git a/tlspuffin/src/tls/seeds.rs b/tlspuffin/src/tls/seeds.rs index 3880f95c3..7648e462b 100644 --- a/tlspuffin/src/tls/seeds.rs +++ b/tlspuffin/src/tls/seeds.rs @@ -5,19 +5,18 @@ use puffin::{ agent::{AgentDescriptor, AgentName, AgentType, TLSVersion}, algebra::Term, - protocol::MessageFlight, term, trace::{Action, InputAction, OutputAction, Step, Trace}, }; use crate::{ + protocol::MessageFlight, query::TlsQueryMatcher, tls::{ fn_impl::*, rustls::msgs::{ enums::{CipherSuite, Compression, HandshakeType, ProtocolVersion}, handshake::{Random, ServerExtension, SessionID}, - message::{Message, OpaqueMessage}, }, }, }; @@ -286,7 +285,7 @@ pub fn seed_successful_with_flights( agent: server, action: Action::Input(InputAction { recipe: term! { - (client, 0)/MessageFlight + (client, 0)/MessageFlight }, }), }, @@ -295,7 +294,7 @@ pub fn seed_successful_with_flights( agent: client, action: Action::Input(InputAction { recipe: term! { - (server, 0)/MessageFlight + (server, 0)/MessageFlight }, }), }, @@ -304,7 +303,7 @@ pub fn seed_successful_with_flights( agent: server, action: Action::Input(InputAction { recipe: term! { - (client, 1)/MessageFlight + (client, 1)/MessageFlight }, }), }, @@ -346,7 +345,7 @@ pub fn seed_successful_mitm(client: AgentName, server: AgentName) -> Trace + (server, 0)/MessageFlight }, }), }, @@ -355,7 +354,7 @@ pub fn seed_successful_mitm(client: AgentName, server: AgentName) -> Trace + (client, 1)/MessageFlight }, }), }, @@ -805,7 +804,7 @@ pub fn seed_client_attacker_auth(server: AgentName) -> Trace { let extensions = term! { fn_decrypt_handshake_flight( - ((server, 0)/MessageFlight), // The first flight of messages sent by the server + ((server, 0)/MessageFlight), // The first flight of messages sent by the server (fn_server_hello_transcript(((server, 0)))), (fn_get_server_key_share(((server, 0)[Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ServerHello)))]))), fn_no_psk, @@ -1444,7 +1443,7 @@ pub fn _seed_client_attacker_full( let extensions = term! { fn_decrypt_handshake_flight( - ((server, 0)/MessageFlight), // The first flight of messages sent by the server + ((server, 0)/MessageFlight), // The first flight of messages sent by the server (@server_hello_transcript), (fn_get_server_key_share(((server, 0)[Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ServerHello)))]))), fn_no_psk, @@ -1674,7 +1673,7 @@ pub fn seed_session_resumption_dhe_full( let resumption_decrypted_handshake = term! { fn_decrypt_handshake_flight( - ((server, 0)/MessageFlight), // The first flight of messages sent by the server + ((server, 0)/MessageFlight), // The first flight of messages sent by the server (@resumption_server_hello_transcript), (fn_get_server_key_share(((server, 0)[Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ServerHello)))]))), (fn_psk((@psk))), diff --git a/tlspuffin/src/tls/vulnerabilities.rs b/tlspuffin/src/tls/vulnerabilities.rs index c6789c203..d88361aa2 100644 --- a/tlspuffin/src/tls/vulnerabilities.rs +++ b/tlspuffin/src/tls/vulnerabilities.rs @@ -2,21 +2,14 @@ use puffin::{ agent::{AgentDescriptor, AgentName, AgentType, TLSVersion}, - protocol::MessageFlight, term, trace::{Action, InputAction, OutputAction, Step, Trace}, }; use crate::{ + protocol::MessageFlight, query::TlsQueryMatcher, - tls::{ - fn_impl::*, - rustls::msgs::{ - enums::HandshakeType, - message::{Message, OpaqueMessage}, - }, - seeds::*, - }, + tls::{fn_impl::*, rustls::msgs::enums::HandshakeType, seeds::*}, }; /// @@ -49,7 +42,7 @@ pub fn seed_cve_2022_25638(server: AgentName) -> Trace { let decrypted_handshake = term! { fn_decrypt_handshake_flight( - ((server, 0)/MessageFlight), // The first flight of messages sent by the server + ((server, 0)/MessageFlight), // The first flight of messages sent by the server (fn_server_hello_transcript(((server, 0)))), (fn_get_server_key_share(((server, 0)[Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ServerHello)))]))), fn_no_psk, @@ -206,7 +199,7 @@ pub fn seed_cve_2022_25640(server: AgentName) -> Trace { let decrypted_handshake = term! { fn_decrypt_handshake_flight( - ((server, 0)/MessageFlight), // The first flight of messages sent by the server + ((server, 0)/MessageFlight), // The first flight of messages sent by the server (fn_server_hello_transcript(((server, 0)))), (fn_get_server_key_share(((server, 0)[Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ServerHello)))]))), fn_no_psk, diff --git a/tlspuffin/src/wolfssl/mod.rs b/tlspuffin/src/wolfssl/mod.rs index ace0da79d..03ce4982c 100644 --- a/tlspuffin/src/wolfssl/mod.rs +++ b/tlspuffin/src/wolfssl/mod.rs @@ -2,13 +2,12 @@ use std::{cell::RefCell, io::ErrorKind, ops::Deref, rc::Rc}; -use foreign_types::{ForeignType, ForeignTypeRef}; -use log::{error, warn}; +use log::error; use puffin::{ agent::{AgentDescriptor, AgentName, AgentType, TLSVersion}, algebra::dynamic_function::TypeShape, error::Error, - protocol::{MessageResult, OpaqueMessageFlight}, + protocol::MessageResult, put::{Put, PutName}, put_registry::{Factory, PutKind}, stream::{MemoryStream, Stream}, @@ -29,7 +28,7 @@ use crate::{ TranscriptCertificate, TranscriptClientFinished, TranscriptServerFinished, TranscriptServerHello, }, - protocol::TLSProtocolBehavior, + protocol::{OpaqueMessageFlight, TLSProtocolBehavior}, put::TlsPutConfig, put_registry::WOLFSSL520_PUT, static_certs::{ALICE_CERT, ALICE_PRIVATE_KEY, BOB_CERT, BOB_PRIVATE_KEY, EVE_CERT}, @@ -147,10 +146,10 @@ pub struct WolfSSL { config: TlsPutConfig, } -impl Stream for WolfSSL { - fn add_to_inbound(&mut self, opaque_flight: &OpaqueMessageFlight) { +impl Stream for WolfSSL { + fn add_to_inbound(&mut self, opaque_flight: &OpaqueMessageFlight) { let raw_stream = self.stream.get_mut(); - as Stream>::add_to_inbound( + as Stream>::add_to_inbound( raw_stream, opaque_flight, ) @@ -159,7 +158,8 @@ impl Stream for WolfSSL { fn take_message_from_outbound( &mut self, ) -> Result>, Error> { - self.stream.get_mut().take_message_from_outbound() + let raw_stream = self.stream.get_mut(); + as Stream>::take_message_from_outbound(raw_stream) } } From ef775e896dbb1deaa82772125992091bf02a7cb2 Mon Sep 17 00:00:00 2001 From: Tom Gouville Date: Tue, 4 Jun 2024 16:39:31 +0200 Subject: [PATCH 17/29] removing duplicate trace seed_successful --- tlspuffin/src/tls/seeds.rs | 262 ++++++++++++++++--------------------- 1 file changed, 114 insertions(+), 148 deletions(-) diff --git a/tlspuffin/src/tls/seeds.rs b/tlspuffin/src/tls/seeds.rs index 7648e462b..609182b7a 100644 --- a/tlspuffin/src/tls/seeds.rs +++ b/tlspuffin/src/tls/seeds.rs @@ -168,110 +168,6 @@ pub fn seed_successful_client_auth(client: AgentName, server: AgentName) -> Trac // TODO: `[BAD_DECRYPT] [DECRYPTION_FAILED_OR_BAD_RECORD_MAC]` error with BoringSSL pub fn seed_successful(client: AgentName, server: AgentName) -> Trace { - Trace { - prior_traces: vec![], - descriptors: vec![ - AgentDescriptor::new_client(client, TLSVersion::V1_3), - AgentDescriptor::new_server(server, TLSVersion::V1_3), - ], - steps: vec![ - OutputAction::new_step(client), - // Client Hello Client -> Server - Step { - agent: server, - action: Action::Input(InputAction { - recipe: term! { - fn_client_hello( - ((client, 0)), - ((client, 0)), - ((client, 0)), - ((client, 0)), - ((client, 0)), - ((client, 0)) - ) - }, - }), - }, - // Server Hello Server -> Client - Step { - agent: client, - action: Action::Input(InputAction { - recipe: term! { - fn_server_hello( - ((server, 0)[Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ServerHello)))]/ProtocolVersion), - ((server, 0)[Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ServerHello)))]/Random), - ((server, 0)[Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ServerHello)))]/SessionID), - ((server, 0)[Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ServerHello)))]/CipherSuite), - ((server, 0)[Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ServerHello)))]/Compression), - ((server, 0)[Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ServerHello)))]/Vec) - ) - }, - }), - }, - // Encrypted Extensions Server -> Client - Step { - agent: client, - action: Action::Input(InputAction { - recipe: term! { - fn_application_data( - ((server, 0)[Some(TlsQueryMatcher::ApplicationData)]/Vec) - ) - }, - }), - }, - // Certificate Server -> Client - Step { - agent: client, - action: Action::Input(InputAction { - recipe: term! { - fn_application_data( - ((server, 1)[Some(TlsQueryMatcher::ApplicationData)]/Vec) - ) - }, - }), - }, - // Certificate Verify Server -> Client - Step { - agent: client, - action: Action::Input(InputAction { - recipe: term! { - fn_application_data( - ((server, 2)[Some(TlsQueryMatcher::ApplicationData)]/Vec) - ) - }, - }), - }, - // Finish Server -> Client - Step { - agent: client, - action: Action::Input(InputAction { - recipe: term! { - fn_application_data( - ((server, 3)[Some(TlsQueryMatcher::ApplicationData)]/Vec) - ) - }, - }), - }, - // Finished Client -> Server - Step { - agent: server, - action: Action::Input(InputAction { - recipe: term! { - fn_application_data( - ((client, 0)[Some(TlsQueryMatcher::ApplicationData)]/Vec) - ) - }, - }), - }, - ], - } -} - -// TODO: `[BAD_DECRYPT] [DECRYPTION_FAILED_OR_BAD_RECORD_MAC]` error with BoringSSL -pub fn seed_successful_with_flights( - client: AgentName, - server: AgentName, -) -> Trace { Trace { prior_traces: vec![], descriptors: vec![ @@ -527,34 +423,120 @@ pub fn seed_successful12(client: AgentName, server: AgentName) -> Trace Trace { - let mut trace = seed_successful(client, server); - - // CCS Server -> Client - trace.steps.insert( - 3, - Step { - agent: client, - action: Action::Input(InputAction { - recipe: term! { - fn_change_cipher_spec - }, - }), - }, - ); - - trace.steps.insert( - 8, - Step { - agent: server, - action: Action::Input(InputAction { - recipe: term! { - fn_change_cipher_spec - }, - }), - }, - ); - - trace + Trace { + prior_traces: vec![], + descriptors: vec![ + AgentDescriptor::new_client(client, TLSVersion::V1_3), + AgentDescriptor::new_server(server, TLSVersion::V1_3), + ], + steps: vec![ + OutputAction::new_step(client), + // Client Hello Client -> Server + Step { + agent: server, + action: Action::Input(InputAction { + recipe: term! { + fn_client_hello( + ((client, 0)), + ((client, 0)), + ((client, 0)), + ((client, 0)), + ((client, 0)), + ((client, 0)) + ) + }, + }), + }, + // Server Hello Server -> Client + Step { + agent: client, + action: Action::Input(InputAction { + recipe: term! { + fn_server_hello( + ((server, 0)[Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ServerHello)))]/ProtocolVersion), + ((server, 0)[Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ServerHello)))]/Random), + ((server, 0)[Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ServerHello)))]/SessionID), + ((server, 0)[Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ServerHello)))]/CipherSuite), + ((server, 0)[Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ServerHello)))]/Compression), + ((server, 0)[Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ServerHello)))]/Vec) + ) + }, + }), + }, + // CCS Server -> Client + Step { + agent: client, + action: Action::Input(InputAction { + recipe: term! { + fn_change_cipher_spec + }, + }), + }, + // Encrypted Extensions Server -> Client + Step { + agent: client, + action: Action::Input(InputAction { + recipe: term! { + fn_application_data( + ((server, 0)[Some(TlsQueryMatcher::ApplicationData)]/Vec) + ) + }, + }), + }, + // Certificate Server -> Client + Step { + agent: client, + action: Action::Input(InputAction { + recipe: term! { + fn_application_data( + ((server, 1)[Some(TlsQueryMatcher::ApplicationData)]/Vec) + ) + }, + }), + }, + // Certificate Verify Server -> Client + Step { + agent: client, + action: Action::Input(InputAction { + recipe: term! { + fn_application_data( + ((server, 2)[Some(TlsQueryMatcher::ApplicationData)]/Vec) + ) + }, + }), + }, + // Finish Server -> Client + Step { + agent: client, + action: Action::Input(InputAction { + recipe: term! { + fn_application_data( + ((server, 3)[Some(TlsQueryMatcher::ApplicationData)]/Vec) + ) + }, + }), + }, + Step { + agent: server, + action: Action::Input(InputAction { + recipe: term! { + fn_change_cipher_spec + }, + }), + }, + // Finished Client -> Server + Step { + agent: server, + action: Action::Input(InputAction { + recipe: term! { + fn_application_data( + ((client, 0)[Some(TlsQueryMatcher::ApplicationData)]/Vec) + ) + }, + }), + }, + ], + } } // TODO: `[BAD_DECRYPT] [DECRYPTION_FAILED_OR_BAD_RECORD_MAC]` error with BoringSSL @@ -1793,12 +1775,6 @@ pub fn create_corpus() -> Vec<(Trace, &'static str)> { corpus!( // Full Handshakes seed_successful: cfg(feature = "tls13"), - // TODO: seed_successful_with_flights should supersede seed_succesful in the futur - // when accessing all inner elements of a flight will be possible with queries and - // not specific function symbols - // for now, this trace give the ability to forward a whole flight of packets in one - // step - // seed_successful_with_flights: cfg(feature = "tls13"), seed_successful_with_ccs: cfg(feature = "tls13"), seed_successful_with_tickets: cfg(feature = "tls13"), seed_successful12: cfg(all(feature = "tls12", not(feature = "tls12-session-resumption"))), @@ -1923,16 +1899,6 @@ pub mod tests { fn test_seed_successful_with_flights() { use crate::tls::trace_helper::TraceExecutor; - let ctx = seed_successful_with_flights.execute_trace(); - assert!(ctx.agents_successful()); - } - - #[cfg(feature = "tls13")] // require version which supports TLS 1.3 - #[cfg(not(feature = "boringssl-binding"))] - #[test] - fn test_seed_successful() { - use crate::tls::trace_helper::TraceExecutor; - let ctx = seed_successful.execute_trace(); assert!(ctx.agents_successful()); } From 82b83edcb67139d6b7b7dec90f8a98b46efbedd6 Mon Sep 17 00:00:00 2001 From: Tom Gouville Date: Thu, 6 Jun 2024 11:53:17 +0200 Subject: [PATCH 18/29] using `Source` instead of `AgentName` to find the origin of a knowledge --- puffin/src/algebra/macros.rs | 6 ++- puffin/src/algebra/signature.rs | 11 +++-- puffin/src/algebra/term.rs | 4 +- puffin/src/trace.rs | 76 ++++++++++++++++++++++++--------- tlspuffin/src/lib.rs | 15 ++++--- 5 files changed, 76 insertions(+), 36 deletions(-) diff --git a/puffin/src/algebra/macros.rs b/puffin/src/algebra/macros.rs index 3434d661a..a0a7cabbb 100644 --- a/puffin/src/algebra/macros.rs +++ b/puffin/src/algebra/macros.rs @@ -16,8 +16,9 @@ macro_rules! term { (($agent:expr, $counter:expr) $(>$req_type:expr)?) => {{ use $crate::algebra::signature::Signature; use $crate::algebra::Term; + use $crate::trace::Source; - let var = Signature::new_var($($req_type)?, Some($agent), None, $counter); // TODO: verify hat using here None is fine. Before a refactor it was: Some(TlsMessageType::Handshake(None)) + let var = Signature::new_var($($req_type)?, Some(Source::Agent($agent)), None, $counter); // TODO: verify hat using here None is fine. Before a refactor it was: Some(TlsMessageType::Handshake(None)) Term::Variable(var) }}; @@ -34,8 +35,9 @@ macro_rules! term { (($agent:expr, $counter:expr) [$message_type:expr] $(>$req_type:expr)?) => {{ use $crate::algebra::signature::Signature; use $crate::algebra::Term; + use $crate::trace::Source; - let var = Signature::new_var($($req_type)?, Some($agent), $message_type, $counter); + let var = Signature::new_var($($req_type)?, Some(Source::Agent($agent)), $message_type, $counter); Term::Variable(var) }}; diff --git a/puffin/src/algebra/signature.rs b/puffin/src/algebra/signature.rs index 3d7fc1a0e..b56fa8f3c 100644 --- a/puffin/src/algebra/signature.rs +++ b/puffin/src/algebra/signature.rs @@ -8,7 +8,6 @@ use once_cell::sync::Lazy; use super::atoms::Function; use crate::{ - agent::AgentName, algebra::{ atoms::Variable, dynamic_function::{ @@ -16,7 +15,7 @@ use crate::{ }, Matcher, }, - trace::Query, + trace::{Query, Source}, }; pub type FunctionDefinition = (DynamicFunctionShape, Box); @@ -87,22 +86,22 @@ impl Signature { } pub fn new_var_with_type( - agent_name: Option, + source: Option, matcher: Option, counter: u16, ) -> Variable { let type_shape = TypeShape::of::(); - Self::new_var(type_shape, agent_name, matcher, counter) + Self::new_var(type_shape, source, matcher, counter) } pub fn new_var( type_shape: TypeShape, - agent_name: Option, + source: Option, matcher: Option, counter: u16, ) -> Variable { let query = Query { - agent_name, + source, matcher, counter, }; diff --git a/puffin/src/algebra/term.rs b/puffin/src/algebra/term.rs index ea2e940d6..c924af2ec 100644 --- a/puffin/src/algebra/term.rs +++ b/puffin/src/algebra/term.rs @@ -10,7 +10,7 @@ use crate::{ algebra::{dynamic_function::TypeShape, error::FnError, Matcher}, error::Error, protocol::ProtocolBehavior, - trace::TraceContext, + trace::{Source, TraceContext}, }; /// A first-order term: either a [`Variable`] or an application of an [`Function`]. @@ -115,7 +115,7 @@ impl Term { .find_variable(variable.typ, &variable.query) .map(|data| data.boxed_any()) .or_else(|| { - if let Some(agent_name) = variable.query.agent_name { + if let Some(Source::Agent(agent_name)) = variable.query.source { context.find_claim(agent_name, variable.typ) } else { None diff --git a/puffin/src/trace.rs b/puffin/src/trace.rs index ed5db44f7..2b4867717 100644 --- a/puffin/src/trace.rs +++ b/puffin/src/trace.rs @@ -41,9 +41,9 @@ use crate::{ variable_data::VariableData, }; -#[derive(Debug, Deserialize, Serialize, Clone, Copy, Hash, Eq, PartialEq)] +#[derive(Debug, Deserialize, Serialize, Clone, Hash, Eq, PartialEq)] pub struct Query { - pub agent_name: Option, + pub source: Option, pub matcher: Option, pub counter: u16, // in case an agent sends multiple messages of the same type } @@ -53,7 +53,7 @@ impl fmt::Display for Query { write!( f, "({:?}, {})[{:?}]", - self.agent_name, self.counter, self.matcher + self.source, self.counter, self.matcher ) } } @@ -64,18 +64,36 @@ impl Knowledge { } } +/// [Source] stores the origin of a knowledge, whether the agent name or +/// the label of the precomputation that produced it +#[derive(Debug, PartialEq, Eq, Clone, Hash, Deserialize, Serialize)] +pub enum Source { + Agent(AgentName), + Label(String), +} + +impl fmt::Display for Source { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Agent(x) => write!(f, "agent:{}", x), + Self::Label(x) => write!(f, "label:{}", x), + } + } +} + /// [Knowledge] describes an atomic piece of knowledge inferred by the /// [`crate::protocol::ProtocolMessage::extract_knowledge`] function -/// [Knowledge] is made of the data, the agent that produced the output, the TLS message type and the internal type. +/// [Knowledge] is made of the data, the source of the output, the +/// TLS message type and the internal type. #[derive(Debug)] pub struct Knowledge { - pub agent_name: AgentName, + pub source: Source, pub matcher: Option, pub data: Box, } impl Knowledge { - pub fn debug_print(&self, ctx: &TraceContext, agent_name: &AgentName) + pub fn debug_print(&self, ctx: &TraceContext, source: &Source) where PB: ProtocolBehavior, { @@ -84,14 +102,15 @@ impl Knowledge { "New knowledge {}: {} (counter: {})", &self, remove_prefix(self.data.type_name()), - ctx.number_matching_message(*agent_name, data_type_id, &self.matcher) + ctx.number_matching_message_with_source(source.clone(), data_type_id, &self.matcher) ); trace!("Knowledge data: {:?}", self.data); } } + impl fmt::Display for Knowledge { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "({})/{:?}", self.agent_name, self.matcher) + write!(f, "({})/{:?}", self.source, self.matcher) } } @@ -181,23 +200,38 @@ impl TraceContext { self.knowledge.push(knowledge) } - /// Count the number of sub-messages of type `type_id`. - pub fn number_matching_message( + /// Count the number of sub-messages of type `type_id` with the correct source + pub fn number_matching_message_with_source( &self, - agent: AgentName, + source: Source, type_id: TypeId, tls_message_type: &Option, ) -> usize { self.knowledge .iter() .filter(|knowledge| { - knowledge.agent_name == agent + knowledge.source == source && knowledge.matcher == *tls_message_type && knowledge.data.as_ref().type_id() == type_id }) .count() } + /// Count the number of sub-messages of type [type_id] in the output message [in_step_id]. + pub fn number_matching_message( + &self, + type_id: TypeId, + tls_message_type: &Option, + ) -> usize { + self.knowledge + .iter() + .filter(|knowledge| { + knowledge.matcher == *tls_message_type + && knowledge.data.as_ref().type_id() == type_id + }) + .count() + } + pub fn find_claim( &self, agent_name: AgentName, @@ -224,7 +258,7 @@ impl TraceContext { let data: &dyn VariableData = knowledge.data.as_ref(); if query_type_id == data.type_id() - && (query.agent_name == None || query.agent_name == Some(knowledge.agent_name)) + && (query.source == None || query.source == Some(knowledge.source.clone())) && knowledge.matcher.matches(&query.matcher) { possibilities.push(knowledge); @@ -561,35 +595,39 @@ impl OutputAction { knowledge.len() + opaque_knowledge.len() ); // +1 because of the OpaqueMessage below + let source = Source::Agent(step.agent); + for variable in knowledge { let knowledge = Knowledge:: { - agent_name: step.agent, + source: source.clone(), matcher: matcher.clone(), data: variable, }; - knowledge.debug_print(ctx, &step.agent); + knowledge.debug_print(ctx, &source); ctx.add_knowledge(knowledge) } for variable in opaque_knowledge { let knowledge = Knowledge:: { - agent_name: step.agent, + source: source.clone(), matcher: None, // none because we can not trust the decoding of tls_message_type, because the message could be encrypted like in TLS 1.2 data: variable, }; - knowledge.debug_print(ctx, &step.agent); + knowledge.debug_print(ctx, &source); ctx.add_knowledge(knowledge) } } + let source = Source::Agent(step.agent); + let flight_knowledge = Knowledge:: { - agent_name: step.agent, + source: source.clone(), matcher: None, data: Box::new(flight), }; - flight_knowledge.debug_print(ctx, &step.agent); + flight_knowledge.debug_print(ctx, &source); ctx.add_knowledge(flight_knowledge); Ok(()) diff --git a/tlspuffin/src/lib.rs b/tlspuffin/src/lib.rs index 2648302f5..ea760c3a7 100644 --- a/tlspuffin/src/lib.rs +++ b/tlspuffin/src/lib.rs @@ -11,7 +11,7 @@ //! //! ```rust //! use puffin::agent::{AgentName, AgentDescriptor, TLSVersion::*}; -//! use puffin::trace::{Step, TraceContext, Trace, Action, InputAction, OutputAction, Query}; +//! use puffin::trace::{Step, Source, TraceContext, Trace, Action, InputAction, OutputAction, Query}; //! use puffin::algebra::{Term, signature::Signature}; //! use tlspuffin::tls::fn_impl::fn_client_hello; //! use tlspuffin::tls::rustls::msgs::handshake::{SessionID, Random, ClientExtension}; @@ -38,32 +38,32 @@ //! Signature::new_function(&fn_client_hello), //! vec![ //! Term::Variable(Signature::new_var_with_type::( -//! Some(client), +//! Some(Source::Agent(client)), //! Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ClientHello))), //! 0 //! )), //! Term::Variable(Signature::new_var_with_type::( -//! Some(client), +//! Some(Source::Agent(client)), //! Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ClientHello))), //! 0 //! )), //! Term::Variable(Signature::new_var_with_type::( -//! Some(client), +//! Some(Source::Agent(client)), //! Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ClientHello))), //! 0 //! )), //! Term::Variable(Signature::new_var_with_type::, _>( -//! Some(client), +//! Some(Source::Agent(client)), //! Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ClientHello))), //! 0 //! )), //! Term::Variable(Signature::new_var_with_type::, _>( -//! Some(client), +//! Some(Source::Agent(client)), //! Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ClientHello))), //! 0 //! )), //! Term::Variable(Signature::new_var_with_type::, _>( -//! Some(client), +//! Some(Source::Agent(client)), //! Some(TlsQueryMatcher::Handshake(Some(HandshakeType::ClientHello))), //! 0 //! )), @@ -86,6 +86,7 @@ //! use tlspuffin::tls::rustls::msgs::enums::{Compression, HandshakeType, ProtocolVersion, CipherSuite}; //! use puffin::algebra::Term; //! use tlspuffin::query::TlsQueryMatcher; +//! use puffin::trace::Source; //! //! let client = AgentName::first(); //! let term: Term = term! { From 095a599cd8be5dd0054d21a8677651d4f496fa26 Mon Sep 17 00:00:00 2001 From: Tom Gouville Date: Thu, 13 Jun 2024 18:44:39 +0200 Subject: [PATCH 19/29] using MessageFlight as output from PUTs and introducing ExtractKnowledge trait --- Cargo.lock | 1 + puffin/src/algebra/mod.rs | 96 ++++++--- puffin/src/protocol.rs | 86 ++++---- puffin/src/put.rs | 7 +- puffin/src/stream.rs | 99 +++------ puffin/src/trace.rs | 218 +++++++++++--------- sshpuffin/Cargo.toml | 1 + sshpuffin/src/libssh/mod.rs | 60 +----- sshpuffin/src/main.rs | 1 + sshpuffin/src/protocol.rs | 76 ++++++- sshpuffin/src/query.rs | 15 ++ sshpuffin/src/ssh/deframe.rs | 7 +- sshpuffin/src/ssh/message.rs | 196 +++++++++++++++--- sshpuffin/src/ssh/seeds.rs | 8 +- tlspuffin/src/boringssl/mod.rs | 27 ++- tlspuffin/src/openssl/mod.rs | 34 ++- tlspuffin/src/protocol.rs | 364 +++++++++++++++++++++++++-------- tlspuffin/src/query.rs | 32 +-- tlspuffin/src/tcp/mod.rs | 99 +++------ tlspuffin/src/tls/seeds.rs | 2 +- tlspuffin/src/wolfssl/mod.rs | 22 +- 21 files changed, 865 insertions(+), 586 deletions(-) create mode 100644 sshpuffin/src/query.rs diff --git a/Cargo.lock b/Cargo.lock index 49f2334ce..410e559da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2114,6 +2114,7 @@ dependencies = [ "log", "puffin", "rand", + "serde", "sha2", "test-log", "thiserror", diff --git a/puffin/src/algebra/mod.rs b/puffin/src/algebra/mod.rs index 1897b116a..dd1f5b962 100644 --- a/puffin/src/algebra/mod.rs +++ b/puffin/src/algebra/mod.rs @@ -34,11 +34,7 @@ use once_cell::sync::OnceCell; use serde::{de::DeserializeOwned, Deserialize, Serialize}; pub use self::term::*; -use crate::{ - algebra::signature::Signature, - error::Error, - protocol::{MessageResult, OpaqueProtocolMessage, ProtocolMessage}, -}; +use crate::algebra::signature::Signature; pub mod atoms; pub mod dynamic_function; @@ -102,14 +98,6 @@ impl Matcher for AnyMatcher { } } -impl, O: OpaqueProtocolMessage> TryFrom<&MessageResult> for AnyMatcher { - type Error = Error; - - fn try_from(_: &MessageResult) -> Result { - Ok(AnyMatcher) - } -} - #[cfg(test)] #[allow(clippy::ptr_arg)] pub mod test_signature { @@ -127,13 +115,13 @@ pub mod test_signature { define_signature, error::Error, protocol::{ - OpaqueProtocolMessage, OpaqueProtocolMessageFlight, ProtocolBehavior, ProtocolMessage, - ProtocolMessageDeframer, ProtocolMessageFlight, + ExtractKnowledge, OpaqueProtocolMessage, OpaqueProtocolMessageFlight, ProtocolBehavior, + ProtocolMessage, ProtocolMessageDeframer, ProtocolMessageFlight, }, put::{Put, PutName}, put_registry::{Factory, PutKind}, term, - trace::{Action, InputAction, Step, Trace, TraceContext}, + trace::{Action, InputAction, Knowledge, Source, Step, Trace, TraceContext}, variable_data::VariableData, VERSION_STR, }; @@ -428,12 +416,19 @@ pub mod test_signature { } } - impl OpaqueProtocolMessage for TestOpaqueMessage { + impl OpaqueProtocolMessage for TestOpaqueMessage { fn debug(&self, _info: &str) { panic!("Not implemented for test stub"); } + } - fn extract_knowledge(&self) -> Result>, Error> { + impl ExtractKnowledge for TestOpaqueMessage { + fn extract_knowledge( + &self, + _: &mut Vec>, + _: Option, + _: &Source, + ) -> Result<(), Error> { panic!("Not implemented for test stub"); } } @@ -452,7 +447,7 @@ pub mod test_signature { } } - impl ProtocolMessage for TestMessage { + impl ProtocolMessage for TestMessage { fn create_opaque(&self) -> TestOpaqueMessage { panic!("Not implemented for test stub"); } @@ -460,15 +455,22 @@ pub mod test_signature { fn debug(&self, _info: &str) { panic!("Not implemented for test stub"); } + } - fn extract_knowledge(&self) -> Result>, Error> { + impl ExtractKnowledge for TestMessage { + fn extract_knowledge( + &self, + _: &mut Vec>, + _: Option, + _: &Source, + ) -> Result<(), Error> { panic!("Not implemented for test stub"); } } pub struct TestMessageDeframer; - impl ProtocolMessageDeframer for TestMessageDeframer { + impl ProtocolMessageDeframer for TestMessageDeframer { type OpaqueProtocolMessage = TestOpaqueMessage; fn pop_frame(&mut self) -> Option { @@ -490,7 +492,9 @@ pub mod test_signature { #[derive(Debug, Clone)] pub struct TestMessageFlight; - impl ProtocolMessageFlight for TestMessageFlight { + impl ProtocolMessageFlight + for TestMessageFlight + { fn new() -> Self { Self {} } @@ -504,6 +508,25 @@ pub mod test_signature { } } + impl TryFrom for TestMessageFlight { + type Error = (); + + fn try_from(_value: TestOpaqueMessageFlight) -> Result { + Ok(Self) + } + } + + impl ExtractKnowledge for TestMessageFlight { + fn extract_knowledge( + &self, + _: &mut Vec>, + _: Option, + _: &Source, + ) -> Result<(), Error> { + panic!("Not implemented for test stub"); + } + } + impl From for TestMessageFlight { fn from(_value: TestMessage) -> Self { Self {} @@ -513,7 +536,7 @@ pub mod test_signature { #[derive(Debug, Clone)] pub struct TestOpaqueMessageFlight; - impl OpaqueProtocolMessageFlight for TestOpaqueMessageFlight { + impl OpaqueProtocolMessageFlight for TestOpaqueMessageFlight { fn new() -> Self { Self {} } @@ -527,6 +550,17 @@ pub mod test_signature { } } + impl ExtractKnowledge for TestOpaqueMessageFlight { + fn extract_knowledge( + &self, + _: &mut Vec>, + _: Option, + _: &Source, + ) -> Result<(), Error> { + panic!("Not implemented for test stub"); + } + } + impl From for TestOpaqueMessageFlight { fn from(_value: TestOpaqueMessage) -> Self { Self {} @@ -622,7 +656,7 @@ mod tests { put::PutOptions, put_registry::{Factory, PutRegistry}, term, - trace::{Knowledge, TraceContext}, + trace::{Knowledge, Source, TraceContext}, }; #[allow(dead_code)] @@ -683,7 +717,7 @@ mod tests { let variable: Variable = Signature::new_var( TypeShape::of::>(), - Some(AgentName::first()), + Some(Source::Agent(AgentName::first())), None, 0, ); @@ -705,8 +739,8 @@ mod tests { let put_registry = PutRegistry::::new([("teststub", dummy_factory())], "teststub"); let mut context = TraceContext::new(&put_registry, PutOptions::default()); - context.add_knowledge(Knowledge { - agent_name: AgentName::first(), + context.knowledge_store.add_knowledge(Knowledge { + source: Source::Agent(AgentName::first()), matcher: None, data: Box::new(data), }); @@ -750,7 +784,7 @@ mod tests { Term::Application(Signature::new_function(&example_op_c), vec![]), Term::Variable( Signature::new_var_with_type::( - Some(AgentName::first()), + Some(Source::Agent(AgentName::first())), None, 0, ), @@ -758,7 +792,7 @@ mod tests { ], ), Term::Variable(Signature::new_var_with_type::( - Some(AgentName::first()), + Some(Source::Agent(AgentName::first())), None, 0, )), @@ -771,7 +805,7 @@ mod tests { Signature::new_function(&example_op_c), vec![ Term::Variable(Signature::new_var_with_type::( - Some(AgentName::first()), + Some(Source::Agent(AgentName::first())), None, 0, )), @@ -779,7 +813,7 @@ mod tests { ], ), Term::Variable(Signature::new_var_with_type::( - Some(AgentName::first()), + Some(Source::Agent(AgentName::first())), None, 0, )), diff --git a/puffin/src/protocol.rs b/puffin/src/protocol.rs index af6f04e19..74bbeeb35 100644 --- a/puffin/src/protocol.rs +++ b/puffin/src/protocol.rs @@ -5,13 +5,31 @@ use crate::{ claims::{Claim, SecurityViolationPolicy}, codec::Codec, error::Error, - trace::Trace, - variable_data::VariableData, + trace::{Knowledge, Source, Trace}, }; +/// Provide a way to extract knowledge out of a Message/OpaqueMessage or any type that +/// might be used in a precomputation +pub trait ExtractKnowledge { + /// Fill `knowledges` with new knowledge gathered form the type implementing ExtractKnowledge + /// by recursively calling extract_knowledge on all contained element + /// This will put source as the source of all the produced knowledges, matcher is also passed + /// recursively but might be overriten by a type with a more specific matcher + fn extract_knowledge( + &self, + knowledges: &mut Vec>, + matcher: Option, + source: &Source, + ) -> Result<(), Error>; +} + /// Store a message flight, a vec of all the messages sent by the PUT between two steps -pub trait ProtocolMessageFlight, O: OpaqueProtocolMessage>: - Clone + Debug + From +pub trait ProtocolMessageFlight< + Mt: Matcher, + M: ProtocolMessage, + O: OpaqueProtocolMessage, + OF: OpaqueProtocolMessageFlight, +>: Clone + Debug + From + TryFrom + Into + ExtractKnowledge { fn new() -> Self; fn push(&mut self, msg: M); @@ -19,8 +37,8 @@ pub trait ProtocolMessageFlight, O: OpaqueProtocolMessage> } /// Store a flight of opaque messages, a vec of all the messages sent by the PUT between two steps -pub trait OpaqueProtocolMessageFlight: - Clone + Debug + Codec + From +pub trait OpaqueProtocolMessageFlight>: + Clone + Debug + Codec + From + ExtractKnowledge { fn new() -> Self; fn debug(&self, info: &str); @@ -34,25 +52,24 @@ pub trait OpaqueProtocolMessageFlight: /// A structured message. This type defines how all possible messages of a protocol. /// Usually this is implemented using an `enum`. -pub trait ProtocolMessage: Clone + Debug { +pub trait ProtocolMessage>: + Clone + Debug + ExtractKnowledge +{ fn create_opaque(&self) -> O; fn debug(&self, info: &str); - fn extract_knowledge(&self) -> Result>, Error>; } /// A non-structured version of [`ProtocolMessage`]. This can be used for example for encrypted messages /// which do not have a structure. -pub trait OpaqueProtocolMessage: Clone + Debug + Codec { +pub trait OpaqueProtocolMessage: Clone + Debug + Codec + ExtractKnowledge { fn debug(&self, info: &str); - - fn extract_knowledge(&self) -> Result>, Error>; } /// Deframes a stream of bytes into distinct [OpaqueProtocolMessages](OpaqueProtocolMessage). /// A deframer is usually state-ful. This means it produces as many messages from the input bytes /// and stores them. -pub trait ProtocolMessageDeframer { - type OpaqueProtocolMessage: OpaqueProtocolMessage; +pub trait ProtocolMessageDeframer { + type OpaqueProtocolMessage: OpaqueProtocolMessage; fn pop_frame(&mut self) -> Option; fn read(&mut self, rd: &mut dyn std::io::Read) -> std::io::Result; @@ -68,55 +85,24 @@ pub trait ProtocolMessageDeframer { /// sequences of them. Finally, there is a [matcher](Matcher) which allows traces to include /// queries for [knowledge](crate::trace::Knowledge). pub trait ProtocolBehavior: 'static { + type Matcher: Matcher; type Claim: Claim; type SecurityViolationPolicy: SecurityViolationPolicy; - type ProtocolMessage: ProtocolMessage; - type OpaqueProtocolMessage: OpaqueProtocolMessage; + type ProtocolMessage: ProtocolMessage; + type OpaqueProtocolMessage: OpaqueProtocolMessage; type ProtocolMessageFlight: ProtocolMessageFlight< + Self::Matcher, Self::ProtocolMessage, Self::OpaqueProtocolMessage, + Self::OpaqueProtocolMessageFlight, >; - type OpaqueProtocolMessageFlight: OpaqueProtocolMessageFlight + type OpaqueProtocolMessageFlight: OpaqueProtocolMessageFlight + From; - type Matcher: Matcher - + for<'a> TryFrom<&'a MessageResult>; - /// Get the signature that is used in the protocol fn signature() -> &'static Signature; /// Creates a sane initial seed corpus. fn create_corpus() -> Vec<(Trace, &'static str)>; } - -pub struct MessageResult, O: OpaqueProtocolMessage>(pub Option, pub O); - -impl, O: OpaqueProtocolMessage> MessageResult { - /// Extracts as much data from the message as possible. Depending on the protocol, - /// the extraction can be more fine-grained to more coarse. - pub fn extract_knowledge(&self) -> Result>, Error> { - let opaque_knowledge = self.1.extract_knowledge(); - - if let Some(message) = &self.0 { - if let Ok(opaque_knowledge) = opaque_knowledge { - message.extract_knowledge().map(|mut knowledge| { - knowledge.extend(opaque_knowledge); - knowledge - }) - } else { - message.extract_knowledge() - } - } else { - opaque_knowledge - } - } - - pub fn create_matcher(&self) -> Option - where - PB: ProtocolBehavior, - { - // TODO: Should we return here or use None? - <::Matcher as TryFrom<&MessageResult>>::try_from(self).ok() - } -} diff --git a/puffin/src/put.rs b/puffin/src/put.rs index 9b064194c..00e7d43c8 100644 --- a/puffin/src/put.rs +++ b/puffin/src/put.rs @@ -67,7 +67,12 @@ pub struct PutDescriptor { /// Generic trait used to define the interface with a concrete library /// implementing the protocol. pub trait Put: - Stream + 'static + Stream< + PB::Matcher, + PB::ProtocolMessage, + PB::OpaqueProtocolMessage, + PB::OpaqueProtocolMessageFlight, + > + 'static { /// Process incoming buffer, internal progress, can fill in the output buffer fn progress(&mut self, agent_name: &AgentName) -> Result<(), Error>; diff --git a/puffin/src/stream.rs b/puffin/src/stream.rs index 1a1123498..26e207ec0 100644 --- a/puffin/src/stream.rs +++ b/puffin/src/stream.rs @@ -17,27 +17,25 @@ //! If Bob is an [`Agent`](crate::agent::Agent), which has an underlying *PUT state* then OpenSSL may write into the //! *outbound channel* of Bob. -use std::{ - io, - io::{ErrorKind, Read, Write}, -}; - -use log::error; +use std::io::{self, Read, Write}; use crate::{ + algebra::Matcher, error::Error, - protocol::{ - MessageResult, OpaqueProtocolMessage, OpaqueProtocolMessageFlight, ProtocolMessage, - ProtocolMessageDeframer, - }, + protocol::{OpaqueProtocolMessage, OpaqueProtocolMessageFlight, ProtocolMessage}, }; -pub trait Stream, O: OpaqueProtocolMessage, F: OpaqueProtocolMessageFlight> +pub trait Stream< + Mt: Matcher, + M: ProtocolMessage, + O: OpaqueProtocolMessage, + OF: OpaqueProtocolMessageFlight, +> { - fn add_to_inbound(&mut self, message_flight: &F); + fn add_to_inbound(&mut self, message_flight: &OF); /// Takes a single TLS message from the outbound channel - fn take_message_from_outbound(&mut self) -> Result>, Error>; + fn take_message_from_outbound(&mut self) -> Result, Error>; } /// Describes in- or outbound channels of an [`crate::agent::Agent`]. Each [`crate::agent::Agent`] can send and receive data. @@ -54,84 +52,41 @@ pub type Channel = io::Cursor>; /// /// **Note: There need to be two separate buffer! Else for example a TLS socket would read and write /// into the same buffer** -pub struct MemoryStream { +pub struct MemoryStream { inbound: Channel, outbound: Channel, - deframer: D, } -impl MemoryStream { - pub fn new(deframer: D) -> Self { +impl MemoryStream { + pub fn new() -> Self { Self { inbound: io::Cursor::new(Vec::new()), outbound: io::Cursor::new(Vec::new()), - deframer, } } } impl< - M, - D: ProtocolMessageDeframer, - E, - F: OpaqueProtocolMessageFlight, - > Stream for MemoryStream -where - M: ProtocolMessage, - D::OpaqueProtocolMessage: TryInto, - E: Into, - M: TryInto, + Mt: Matcher, + M: ProtocolMessage, + O: OpaqueProtocolMessage, + OF: OpaqueProtocolMessageFlight, + > Stream for MemoryStream { - fn add_to_inbound(&mut self, message_flight: &F) { + fn add_to_inbound(&mut self, message_flight: &OF) { message_flight.encode(self.inbound.get_mut()); } - fn take_message_from_outbound( - &mut self, - ) -> Result>, Error> { - // Retry to read if no more frames in the deframer buffer - let opaque_message = loop { - if let Some(opaque_message) = self.deframer.pop_frame() { - break Some(opaque_message); - } else { - match self.deframer.read(&mut self.outbound.get_ref().as_slice()) { - Ok(v) => { - self.outbound.set_position(0); - self.outbound.get_mut().clear(); - if v == 0 { - break None; - } - } - Err(err) => match err.kind() { - ErrorKind::WouldBlock => { - // This is not a hard error. It just means we will should read again from - // the TCPStream in the next steps. - break None; - } - _ => return Err(err.into()), - }, - } - } - }; + fn take_message_from_outbound(&mut self) -> Result, Error> { + let flight = OF::read_bytes(&mut self.outbound.get_ref().as_slice()); + self.outbound.set_position(0); + self.outbound.get_mut().clear(); - if let Some(opaque_message) = opaque_message { - let message = match opaque_message.clone().try_into() { - Ok(message) => Some(message), - Err(err) => { - error!("Failed to decode message! This means we maybe need to remove logical checks from rustls! {}", err.into()); - None - } - }; - - Ok(Some(MessageResult(message, opaque_message))) - } else { - // no message to return - Ok(None) - } + Ok(flight) } } -impl Read for MemoryStream { +impl Read for MemoryStream { fn read(&mut self, buf: &mut [u8]) -> io::Result { let n = self.inbound.read(buf)?; @@ -150,7 +105,7 @@ impl Read for MemoryStream { } } -impl Write for MemoryStream { +impl Write for MemoryStream { fn write(&mut self, buf: &[u8]) -> io::Result { self.outbound.write(buf) } diff --git a/puffin/src/trace.rs b/puffin/src/trace.rs index 2b4867717..b89c5f590 100644 --- a/puffin/src/trace.rs +++ b/puffin/src/trace.rs @@ -22,6 +22,7 @@ use std::{ marker::PhantomData, }; +use clap::error::Result; use log::{debug, trace, warn}; use serde::{Deserialize, Serialize}; @@ -33,7 +34,7 @@ use crate::{ claims::{Claim, GlobalClaimList, SecurityViolationPolicy}, error::Error, protocol::{ - MessageResult, OpaqueProtocolMessage, OpaqueProtocolMessageFlight, ProtocolBehavior, + ExtractKnowledge, OpaqueProtocolMessage, OpaqueProtocolMessageFlight, ProtocolBehavior, ProtocolMessage, ProtocolMessageFlight, }, put::{PutDescriptor, PutOptions}, @@ -82,7 +83,7 @@ impl fmt::Display for Source { } /// [Knowledge] describes an atomic piece of knowledge inferred by the -/// [`crate::protocol::ProtocolMessage::extract_knowledge`] function +/// [`crate::protocol::ExtractKnowledge::extract_knowledge`] function /// [Knowledge] is made of the data, the source of the output, the /// TLS message type and the internal type. #[derive(Debug)] @@ -97,7 +98,7 @@ impl Knowledge { where PB: ProtocolBehavior, { - let data_type_id = self.data.as_ref().type_id(); + let data_type_id = self.data.type_id(); debug!( "New knowledge {}: {} (counter: {})", &self, @@ -114,6 +115,91 @@ impl fmt::Display for Knowledge { } } +#[derive(Debug)] +pub struct KnowledgeStore { + knowledge: Vec>, +} + +impl KnowledgeStore { + pub fn new() -> Self { + Self { knowledge: vec![] } + } + + pub fn add_knowledge(&mut self, knowledge: Knowledge) { + self.knowledge.push(knowledge); + } + + pub fn do_extract_knowledge + 'static>( + &mut self, + data: T, + source: Source, + ) -> Result { + let count_before = self.knowledge.len(); + data.extract_knowledge(&mut self.knowledge, None, &source)?; + + Ok(self.knowledge.len() - count_before) + } + + pub fn number_matching_message_with_source( + &self, + source: Source, + type_id: TypeId, + tls_message_type: &Option, + ) -> usize { + self.knowledge + .iter() + .filter(|knowledge| { + knowledge.source == source + && knowledge.matcher == *tls_message_type + && knowledge.data.type_id() == type_id + }) + .count() + } + + /// Count the number of sub-messages of type `type_id` in the output message. + pub fn number_matching_message( + &self, + type_id: TypeId, + tls_message_type: &Option, + ) -> usize { + self.knowledge + .iter() + .filter(|knowledge| { + knowledge.matcher == *tls_message_type && knowledge.data.type_id() == type_id + }) + .count() + } + + /// Returns the variable which matches best -> highest specificity + /// If we want a variable with lower specificity, then we can just query less specific + pub fn find_variable( + &self, + query_type_shape: TypeShape, + query: &Query, + ) -> Option<&(dyn VariableData)> { + let query_type_id: TypeId = query_type_shape.into(); + + let mut possibilities: Vec<&Knowledge> = Vec::new(); + + for knowledge in &self.knowledge { + let data: &dyn VariableData = knowledge.data.as_ref(); + + if query_type_id == data.type_id() + && (query.source == None || query.source == Some(knowledge.source.clone())) + && knowledge.matcher.matches(&query.matcher) + { + possibilities.push(knowledge); + } + } + + possibilities.sort_by_key(|a| a.specificity()); + + possibilities + .get(query.counter as usize) + .map(|possibility| possibility.data.as_ref()) + } +} + /// The [`TraceContext`] contains a list of [`VariableData`], which is known as the knowledge /// of the attacker. [`VariableData`] can contain data of various types like for example /// client and server extensions, cipher suits or session ID It also holds the concrete @@ -122,7 +208,7 @@ impl fmt::Display for Knowledge { #[derive(Debug)] pub struct TraceContext { /// The knowledge of the attacker - knowledge: Vec>, + pub knowledge_store: KnowledgeStore, agents: Vec>, claims: GlobalClaimList<::Claim>, @@ -139,9 +225,9 @@ impl fmt::Display for TraceContext { write!( f, "Knowledge [not displaying other fields] (size={}):", - self.knowledge.len() + self.knowledge_store.knowledge.len() )?; - for k in &self.knowledge { + for k in &self.knowledge_store.knowledge { write!(f, "\n {}, -- {:?}", k, k)?; } Ok(()) @@ -155,7 +241,8 @@ impl PartialEq for TraceContext { && self.deterministic_put == other.deterministic_put && self.default_put_options == other.default_put_options && self.non_default_put_descriptors == other.non_default_put_descriptors - && format!("{:?}", self.knowledge) == format!("{:?}", other.knowledge) + && format!("{:?}", self.knowledge_store.knowledge) + == format!("{:?}", other.knowledge_store.knowledge) && format!("{:?}", self.claims) == format!("{:?}", other.claims) } } @@ -167,7 +254,7 @@ impl TraceContext { let claims = GlobalClaimList::new(); Self { - knowledge: vec![], + knowledge_store: KnowledgeStore::new(), agents: vec![], claims, non_default_put_descriptors: Default::default(), @@ -196,10 +283,6 @@ impl TraceContext { Ok(()) } - pub fn add_knowledge(&mut self, knowledge: Knowledge) { - self.knowledge.push(knowledge) - } - /// Count the number of sub-messages of type `type_id` with the correct source pub fn number_matching_message_with_source( &self, @@ -207,29 +290,18 @@ impl TraceContext { type_id: TypeId, tls_message_type: &Option, ) -> usize { - self.knowledge - .iter() - .filter(|knowledge| { - knowledge.source == source - && knowledge.matcher == *tls_message_type - && knowledge.data.as_ref().type_id() == type_id - }) - .count() + self.knowledge_store + .number_matching_message_with_source(source, type_id, tls_message_type) } - /// Count the number of sub-messages of type [type_id] in the output message [in_step_id]. + /// Count the number of sub-messages of type `type_id` in the output message. pub fn number_matching_message( &self, type_id: TypeId, tls_message_type: &Option, ) -> usize { - self.knowledge - .iter() - .filter(|knowledge| { - knowledge.matcher == *tls_message_type - && knowledge.data.as_ref().type_id() == type_id - }) - .count() + self.knowledge_store + .number_matching_message(type_id, tls_message_type) } pub fn find_claim( @@ -250,26 +322,7 @@ impl TraceContext { query_type_shape: TypeShape, query: &Query, ) -> Option<&(dyn VariableData)> { - let query_type_id: TypeId = query_type_shape.into(); - - let mut possibilities: Vec<&Knowledge> = Vec::new(); - - for knowledge in &self.knowledge { - let data: &dyn VariableData = knowledge.data.as_ref(); - - if query_type_id == data.type_id() - && (query.source == None || query.source == Some(knowledge.source.clone())) - && knowledge.matcher.matches(&query.matcher) - { - possibilities.push(knowledge); - } - } - - possibilities.sort_by_key(|a| a.specificity()); - - possibilities - .get(query.counter as usize) - .map(|possibility| possibility.data.as_ref()) + self.knowledge_store.find_variable(query_type_shape, query) } /// Adds data to the inbound [`Channel`] of the [`Agent`] referenced by the parameter "agent". @@ -292,7 +345,7 @@ impl TraceContext { pub fn take_message_from_outbound( &mut self, agent_name: AgentName, - ) -> Result>, Error> { + ) -> Result, Error> { let agent = self.find_agent_mut(agent_name)?; agent.put_mut().take_message_from_outbound() } @@ -574,61 +627,34 @@ impl OutputAction { { ctx.next_state(step.agent)?; - let mut flight = PB::ProtocolMessageFlight::new(); - - while let Some(message_result) = ctx.take_message_from_outbound(step.agent)? { - let matcher = message_result.create_matcher::(); - - let MessageResult(message, opaque_message) = message_result; - - if let Some(m) = &message { - flight.push(m.clone()); - } - - let knowledge = message - .and_then(|message| message.extract_knowledge().ok()) - .unwrap_or_default(); - let opaque_knowledge = opaque_message.extract_knowledge()?; - - debug!( - "Knowledge increased by {:?}", - knowledge.len() + opaque_knowledge.len() - ); // +1 because of the OpaqueMessage below + let source = Source::Agent(step.agent); - let source = Source::Agent(step.agent); + let opaque_flight_result = ctx.take_message_from_outbound(step.agent)?; - for variable in knowledge { - let knowledge = Knowledge:: { - source: source.clone(), - matcher: matcher.clone(), - data: variable, - }; + if let Some(opaque_flight) = opaque_flight_result { + let flight = TryInto::::try_into(opaque_flight.clone()); - knowledge.debug_print(ctx, &source); - ctx.add_knowledge(knowledge) + if let Ok(num) = ctx + .knowledge_store + .do_extract_knowledge(opaque_flight, source.clone()) + { + debug!("Knowledge increased by {}", num); } - for variable in opaque_knowledge { - let knowledge = Knowledge:: { - source: source.clone(), - matcher: None, // none because we can not trust the decoding of tls_message_type, because the message could be encrypted like in TLS 1.2 - data: variable, - }; - - knowledge.debug_print(ctx, &source); - ctx.add_knowledge(knowledge) + if let Ok(f) = flight { + if let Ok(num) = ctx.knowledge_store.do_extract_knowledge(f, source.clone()) { + debug!("Knowledge increased by {}", num); + } } } - let source = Source::Agent(step.agent); - - let flight_knowledge = Knowledge:: { - source: source.clone(), - matcher: None, - data: Box::new(flight), - }; - flight_knowledge.debug_print(ctx, &source); - ctx.add_knowledge(flight_knowledge); + // let flight_knowledge = Knowledge:: { + // agent_name: step.agent, + // matcher: None, + // data: Box::new(flight), + // }; + // flight_knowledge.debug_print(ctx, &step.agent); + // ctx.add_knowledge(flight_knowledge); Ok(()) } diff --git a/sshpuffin/Cargo.toml b/sshpuffin/Cargo.toml index 93acd0755..ce8da16e7 100644 --- a/sshpuffin/Cargo.toml +++ b/sshpuffin/Cargo.toml @@ -21,6 +21,7 @@ puffin = { path = "../puffin" } log = "0.4" thiserror = "1.0" +serde = { version = "1.0.137", features = ["derive"] } # libssh diff --git a/sshpuffin/src/libssh/mod.rs b/sshpuffin/src/libssh/mod.rs index b7155c72e..b3dd9a3ab 100644 --- a/sshpuffin/src/libssh/mod.rs +++ b/sshpuffin/src/libssh/mod.rs @@ -11,7 +11,7 @@ use std::{ fs, - io::{ErrorKind, Write}, + io::{Read, Write}, os::unix::{ io::{IntoRawFd, RawFd}, net::{UnixListener, UnixStream}, @@ -23,7 +23,6 @@ use puffin::{ agent::{AgentDescriptor, AgentName, AgentType}, codec::Codec, error::Error, - protocol::MessageResult, put::{Put, PutName}, put_registry::{Factory, PutKind}, stream::Stream, @@ -38,10 +37,8 @@ use crate::{ }, protocol::{RawSshMessageFlight, SshProtocolBehavior}, put_registry::LIBSSH_PUT, - ssh::{ - deframe::SshMessageDeframer, - message::{RawSshMessage, SshMessage}, - }, + query::SshQueryMatcher, + ssh::message::{RawSshMessage, SshMessage}, }; pub mod ssh; @@ -147,7 +144,6 @@ pub fn new_libssh_factory() -> Box> { put_fd, agent_descriptor: agent_descriptor.clone(), session, - deframer: SshMessageDeframer::default(), state: PutState::ExchangingKeys, })) } @@ -203,7 +199,6 @@ pub struct LibSSL { fuzz_stream: UnixStream, agent_descriptor: AgentDescriptor, session: SshSession, - deframer: SshMessageDeframer, state: PutState, put_fd: RawFd, @@ -211,7 +206,7 @@ pub struct LibSSL { impl LibSSL {} -impl Stream for LibSSL { +impl Stream for LibSSL { fn add_to_inbound(&mut self, result: &RawSshMessageFlight) { let mut buffer = Vec::new(); Codec::encode(result, &mut buffer); @@ -219,50 +214,11 @@ impl Stream for LibSSL { self.fuzz_stream.write_all(&buffer).unwrap(); } - fn take_message_from_outbound( - &mut self, - ) -> Result>, Error> { - // Retry to read if no more frames in the deframer buffer - let opaque_message = loop { - if let Some(opaque_message) = self.deframer.frames.pop_front() { - break Some(opaque_message); - } else { - match self.deframer.read(&mut self.fuzz_stream) { - Ok(v) => { - if v == 0 { - break None; - } - } - Err(err) => match err.kind() { - ErrorKind::WouldBlock => { - // This is not a hard error. It just means we will should read again from - // the TCPStream in the next steps. - break None; - } - _ => return Err(err.into()), - }, - } - } - }; + fn take_message_from_outbound(&mut self) -> Result, Error> { + let mut buf = vec![]; + let _ = self.fuzz_stream.read_to_end(&mut buf); - if let Some(opaque_message) = opaque_message { - let message = if let RawSshMessage::Packet(packet) = &opaque_message { - match SshMessage::try_from(packet) { - Ok(message) => Some(message), - Err(err) => { - log::error!("Failed to decode message! This means we maybe need to remove logical checks from rustls! {}", err); - None - } - } - } else { - None - }; - - Ok(Some(MessageResult(message, opaque_message))) - } else { - // no message to return - Ok(None) - } + Ok(RawSshMessageFlight::read_bytes(&mut buf)) } } diff --git a/sshpuffin/src/main.rs b/sshpuffin/src/main.rs index b9c1b25ed..6c51c8481 100644 --- a/sshpuffin/src/main.rs +++ b/sshpuffin/src/main.rs @@ -2,6 +2,7 @@ mod claim; mod libssh; mod protocol; mod put_registry; +mod query; mod ssh; mod violation; diff --git a/sshpuffin/src/protocol.rs b/sshpuffin/src/protocol.rs index 091efe3eb..5c83ec158 100644 --- a/sshpuffin/src/protocol.rs +++ b/sshpuffin/src/protocol.rs @@ -1,16 +1,18 @@ use log::debug; use puffin::{ - algebra::{signature::Signature, AnyMatcher}, + algebra::signature::Signature, codec::{Codec, Reader}, + error::Error, protocol::{ - OpaqueProtocolMessageFlight, ProtocolBehavior, ProtocolMessage, ProtocolMessageDeframer, - ProtocolMessageFlight, + ExtractKnowledge, OpaqueProtocolMessageFlight, ProtocolBehavior, ProtocolMessage, + ProtocolMessageDeframer, ProtocolMessageFlight, }, - trace::Trace, + trace::{Knowledge, Source, Trace}, }; use crate::{ claim::SshClaim, + query::SshQueryMatcher, ssh::{ deframe::SshMessageDeframer, message::{RawSshMessage, SshMessage}, @@ -24,7 +26,9 @@ pub struct SshMessageFlight { pub messages: Vec, } -impl ProtocolMessageFlight for SshMessageFlight { +impl ProtocolMessageFlight + for SshMessageFlight +{ fn new() -> Self { Self { messages: vec![] } } @@ -46,12 +50,31 @@ impl From for SshMessageFlight { } } +impl ExtractKnowledge for SshMessageFlight { + fn extract_knowledge( + &self, + knowledges: &mut Vec>, + matcher: Option, + source: &Source, + ) -> Result<(), Error> { + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.clone()), + }); + for msg in &self.messages { + msg.extract_knowledge(knowledges, matcher, source)?; + } + Ok(()) + } +} + #[derive(Debug, Clone)] pub struct RawSshMessageFlight { pub messages: Vec, } -impl OpaqueProtocolMessageFlight for RawSshMessageFlight { +impl OpaqueProtocolMessageFlight for RawSshMessageFlight { fn new() -> Self { Self { messages: vec![] } } @@ -65,6 +88,45 @@ impl OpaqueProtocolMessageFlight for RawSshMessageFlight { } } +impl ExtractKnowledge for RawSshMessageFlight { + fn extract_knowledge( + &self, + knowledges: &mut Vec>, + matcher: Option, + source: &Source, + ) -> Result<(), Error> { + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.clone()), + }); + for msg in &self.messages { + msg.extract_knowledge(knowledges, matcher, source)?; + } + Ok(()) + } +} + +impl TryFrom for SshMessageFlight { + type Error = (); + + fn try_from(value: RawSshMessageFlight) -> Result { + let flight = Self { + messages: value + .messages + .iter() + .filter_map(|m| (*m).clone().try_into().ok()) + .collect(), + }; + + if flight.messages.len() == 0 { + Err(()) + } else { + Ok(flight) + } + } +} + impl Codec for RawSshMessageFlight { fn encode(&self, bytes: &mut Vec) { for msg in &self.messages { @@ -109,7 +171,7 @@ impl ProtocolBehavior for SshProtocolBehavior { type SecurityViolationPolicy = SshSecurityViolationPolicy; type ProtocolMessage = SshMessage; type OpaqueProtocolMessage = RawSshMessage; - type Matcher = AnyMatcher; + type Matcher = SshQueryMatcher; type ProtocolMessageFlight = SshMessageFlight; type OpaqueProtocolMessageFlight = RawSshMessageFlight; diff --git a/sshpuffin/src/query.rs b/sshpuffin/src/query.rs new file mode 100644 index 000000000..073a7c044 --- /dev/null +++ b/sshpuffin/src/query.rs @@ -0,0 +1,15 @@ +use puffin::algebra::Matcher; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize, Clone, Copy, Hash, Eq, PartialEq)] +pub enum SshQueryMatcher {} + +impl Matcher for SshQueryMatcher { + fn matches(&self, _matcher: &Self) -> bool { + true + } + + fn specificity(&self) -> u32 { + 0 + } +} diff --git a/sshpuffin/src/ssh/deframe.rs b/sshpuffin/src/ssh/deframe.rs index 0a0ca2ba1..5a9fadd0f 100644 --- a/sshpuffin/src/ssh/deframe.rs +++ b/sshpuffin/src/ssh/deframe.rs @@ -2,7 +2,10 @@ use std::{collections::VecDeque, io}; use puffin::{codec, codec::Codec, protocol::ProtocolMessageDeframer}; -use crate::ssh::message::{OnWireData, RawSshMessage}; +use crate::{ + query::SshQueryMatcher, + ssh::message::{OnWireData, RawSshMessage}, +}; const MAX_WIRE_SIZE: usize = 35000; @@ -143,7 +146,7 @@ impl SshMessageDeframer { } } -impl ProtocolMessageDeframer for SshMessageDeframer { +impl ProtocolMessageDeframer for SshMessageDeframer { type OpaqueProtocolMessage = RawSshMessage; fn pop_frame(&mut self) -> Option { diff --git a/sshpuffin/src/ssh/message.rs b/sshpuffin/src/ssh/message.rs index f9435910f..0abe02a0f 100644 --- a/sshpuffin/src/ssh/message.rs +++ b/sshpuffin/src/ssh/message.rs @@ -2,10 +2,12 @@ use log::debug; use puffin::{ codec::{Codec, Reader}, error::Error, - protocol::{OpaqueProtocolMessage, ProtocolMessage}, - variable_data::VariableData, + protocol::{ExtractKnowledge, OpaqueProtocolMessage, ProtocolMessage}, + trace::{Knowledge, Source}, }; +use crate::query::SshQueryMatcher; + #[derive(Clone, Debug)] pub struct OnWireData(pub Vec); @@ -264,7 +266,7 @@ impl TryFrom<&BinaryPacket> for SshMessage { SshMessage::read(&mut reader).ok_or_else(|| "Can not parse payload".to_string()) } } -impl ProtocolMessage for SshMessage { +impl ProtocolMessage for SshMessage { fn create_opaque(&self) -> RawSshMessage { let mut payload = Vec::new(); self.encode(&mut payload); @@ -281,9 +283,32 @@ impl ProtocolMessage for SshMessage { fn debug(&self, info: &str) { debug!("{}: {:?}", info, self) } +} + +impl TryFrom for SshMessage { + type Error = (); - fn extract_knowledge(&self) -> Result>, Error> { - let knowledge: Vec> = match &self { + fn try_from(value: RawSshMessage) -> Result { + let message = if let RawSshMessage::Packet(packet) = &value { + match SshMessage::try_from(packet) { + Ok(message) => Some(message), + Err(_) => None, + } + } else { + None + }; + message.ok_or(()) + } +} + +impl ExtractKnowledge for SshMessage { + fn extract_knowledge( + &self, + knowledges: &mut Vec>, + matcher: Option, + source: &Source, + ) -> Result<(), Error> { + match &self { SshMessage::KexInit(KexInitMessage { cookie, kex_algorithms, @@ -298,51 +323,154 @@ impl ProtocolMessage for SshMessage { languages_server_to_client, first_kex_packet_follows, }) => { - vec![ - Box::new(*cookie), - Box::new(kex_algorithms.clone()), - Box::new(server_host_key_algorithms.clone()), - Box::new(encryption_algorithms_server_to_client.clone()), - Box::new(encryption_algorithms_client_to_server.clone()), - Box::new(mac_algorithms_client_to_server.clone()), - Box::new(mac_algorithms_server_to_client.clone()), - Box::new(compression_algorithms_client_to_server.clone()), - Box::new(compression_algorithms_server_to_client.clone()), - Box::new(languages_client_to_server.clone()), - Box::new(languages_server_to_client.clone()), - Box::new(*first_kex_packet_follows), - ] + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(*cookie), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(kex_algorithms.clone()), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(server_host_key_algorithms.clone()), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(encryption_algorithms_server_to_client.clone()), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(encryption_algorithms_client_to_server.clone()), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(mac_algorithms_client_to_server.clone()), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(mac_algorithms_server_to_client.clone()), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(compression_algorithms_client_to_server.clone()), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(compression_algorithms_server_to_client.clone()), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(languages_client_to_server.clone()), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(languages_server_to_client.clone()), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(*first_kex_packet_follows), + }); } SshMessage::KexEcdhInit(KexEcdhInitMessage { ephemeral_public_key, - }) => vec![Box::new(ephemeral_public_key.clone())], + }) => { + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(ephemeral_public_key.clone()), + }); + } SshMessage::KexEcdhReply(KexEcdhReplyMessage { public_host_key, ephemeral_public_key, signature, - }) => vec![ - Box::new(public_host_key.clone()), - Box::new(ephemeral_public_key.clone()), - Box::new(signature.clone()), - ], - SshMessage::NewKeys => vec![], + }) => { + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(public_host_key.clone()), + }); + + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(ephemeral_public_key.clone()), + }); + + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(signature.clone()), + }); + } + + SshMessage::NewKeys => {} }; - Ok(knowledge) + Ok(()) } } -impl OpaqueProtocolMessage for RawSshMessage { +impl OpaqueProtocolMessage for RawSshMessage { fn debug(&self, info: &str) { debug!("{}: {:?}", info, self) } +} - fn extract_knowledge(&self) -> Result>, Error> { - Ok(match &self { - RawSshMessage::Banner(banner) => vec![Box::new(self.clone()), Box::new(banner.clone())], - RawSshMessage::Packet(_) => vec![Box::new(self.clone())], - RawSshMessage::OnWire(onwire) => vec![Box::new(self.clone()), Box::new(onwire.clone())], - }) +impl ExtractKnowledge for RawSshMessage { + fn extract_knowledge( + &self, + knowledges: &mut Vec>, + matcher: Option, + source: &Source, + ) -> Result<(), Error> { + match &self { + RawSshMessage::Banner(banner) => { + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.clone()), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(banner.clone()), + }); + } + RawSshMessage::Packet(_) => { + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.clone()), + }); + } + RawSshMessage::OnWire(onwire) => { + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.clone()), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(onwire.clone()), + }); + } + }; + Ok(()) } } diff --git a/sshpuffin/src/ssh/seeds.rs b/sshpuffin/src/ssh/seeds.rs index 893784be5..0fe7e586c 100644 --- a/sshpuffin/src/ssh/seeds.rs +++ b/sshpuffin/src/ssh/seeds.rs @@ -1,13 +1,15 @@ use puffin::{ agent::{AgentDescriptor, AgentName, AgentType, TLSVersion}, - algebra::AnyMatcher, term, trace::{InputAction, OutputAction, Trace}, }; -use crate::ssh::{fn_impl::*, message::*}; +use crate::{ + query::SshQueryMatcher, + ssh::{fn_impl::*, message::*}, +}; -pub fn seed_successful(client: AgentName, server: AgentName) -> Trace { +pub fn seed_successful(client: AgentName, server: AgentName) -> Trace { Trace { prior_traces: vec![], descriptors: vec![ diff --git a/tlspuffin/src/boringssl/mod.rs b/tlspuffin/src/boringssl/mod.rs index 30d7148ca..6bc8e5037 100644 --- a/tlspuffin/src/boringssl/mod.rs +++ b/tlspuffin/src/boringssl/mod.rs @@ -13,7 +13,6 @@ use log::{debug, info, trace}; use puffin::{ agent::{AgentDescriptor, AgentName, AgentType}, error::Error, - protocol::MessageResult, put::{Put, PutName}, put_registry::{Factory, PutKind}, stream::{MemoryStream, Stream}, @@ -30,11 +29,9 @@ use crate::{ protocol::{OpaqueMessageFlight, TLSProtocolBehavior}, put::TlsPutConfig, put_registry::BORINGSSL_PUT, + query::TlsQueryMatcher, static_certs::{ALICE_CERT, ALICE_PRIVATE_KEY, BOB_CERT, BOB_PRIVATE_KEY, EVE_CERT}, - tls::rustls::msgs::{ - deframer::MessageDeframer, - message::{Message, OpaqueMessage}, - }, + tls::rustls::msgs::message::{Message, OpaqueMessage}, }; #[cfg(feature = "deterministic")] @@ -137,7 +134,7 @@ pub fn new_boringssl_factory() -> Box> { } pub struct BoringSSL { - stream: SslStream>, + stream: SslStream, config: TlsPutConfig, } @@ -148,24 +145,24 @@ impl Drop for BoringSSL { } } -impl Stream for BoringSSL { +impl Stream for BoringSSL { fn add_to_inbound(&mut self, result: &OpaqueMessageFlight) { - as Stream< + >::add_to_inbound(self.stream.get_mut(), result) } - fn take_message_from_outbound( - &mut self, - ) -> Result>, Error> { + fn take_message_from_outbound(&mut self) -> Result, Error> { let memory_stream = self.stream.get_mut(); - as Stream< + >::take_message_from_outbound(memory_stream) } } @@ -258,7 +255,7 @@ impl BoringSSL { AgentType::Client => Self::create_client(agent_descriptor)?, }; - let stream = SslStream::new(ssl, MemoryStream::new(MessageDeframer::new()))?; + let stream = SslStream::new(ssl, MemoryStream::new())?; let agent_name = agent_descriptor.name; diff --git a/tlspuffin/src/openssl/mod.rs b/tlspuffin/src/openssl/mod.rs index f618e8b55..3e149da9a 100644 --- a/tlspuffin/src/openssl/mod.rs +++ b/tlspuffin/src/openssl/mod.rs @@ -9,7 +9,6 @@ use openssl::{ use puffin::{ agent::{AgentDescriptor, AgentName, AgentType}, error::Error, - protocol::MessageResult, put::{Put, PutName}, put_registry::{Factory, PutKind}, stream::{MemoryStream, Stream}, @@ -22,11 +21,9 @@ use crate::{ protocol::{OpaqueMessageFlight, TLSProtocolBehavior}, put::TlsPutConfig, put_registry::OPENSSL111_PUT, + query::TlsQueryMatcher, static_certs::{ALICE_CERT, ALICE_PRIVATE_KEY, BOB_CERT, BOB_PRIVATE_KEY, EVE_CERT}, - tls::rustls::msgs::{ - deframer::MessageDeframer, - message::{Message, OpaqueMessage}, - }, + tls::rustls::msgs::message::{Message, OpaqueMessage}, }; mod bindings; @@ -129,7 +126,7 @@ pub fn new_openssl_factory() -> Box> { } pub struct OpenSSL { - stream: SslStream>, + stream: SslStream, ctx: SslContext, config: TlsPutConfig, } @@ -141,21 +138,21 @@ impl Drop for OpenSSL { } } -impl Stream for OpenSSL { +impl Stream for OpenSSL { fn add_to_inbound(&mut self, result: &OpaqueMessageFlight) { - as Stream>::add_to_inbound( - self.stream.get_mut(), - result, - ) + >::add_to_inbound(self.stream.get_mut(), result) } - fn take_message_from_outbound( - &mut self, - ) -> Result>, Error> { + fn take_message_from_outbound(&mut self) -> Result, Error> { let memory_stream = self.stream.get_mut(); //memory_stream.take_message_from_outbound() - as Stream>::take_message_from_outbound(memory_stream) + >::take_message_from_outbound(memory_stream) } } @@ -312,16 +309,13 @@ impl OpenSSL { fn new_stream( ctx: &SslContextRef, config: &TlsPutConfig, - ) -> Result>, ErrorStack> { + ) -> Result, ErrorStack> { let ssl = match config.descriptor.typ { AgentType::Server => Self::create_server(ctx)?, AgentType::Client => Self::create_client(ctx)?, }; - Ok(SslStream::new( - ssl, - MemoryStream::new(MessageDeframer::new()), - )?) + Ok(SslStream::new(ssl, MemoryStream::new())?) } fn create_server_ctx(descriptor: &AgentDescriptor) -> Result { diff --git a/tlspuffin/src/protocol.rs b/tlspuffin/src/protocol.rs index 73c0479b2..dd8210878 100644 --- a/tlspuffin/src/protocol.rs +++ b/tlspuffin/src/protocol.rs @@ -4,10 +4,10 @@ use puffin::{ codec::{Codec, Reader}, error::Error, protocol::{ - OpaqueProtocolMessage, OpaqueProtocolMessageFlight, ProtocolBehavior, ProtocolMessage, - ProtocolMessageDeframer, ProtocolMessageFlight, + ExtractKnowledge, OpaqueProtocolMessage, OpaqueProtocolMessageFlight, ProtocolBehavior, + ProtocolMessage, ProtocolMessageDeframer, ProtocolMessageFlight, }, - trace::Trace, + trace::{Knowledge, Source, Trace}, variable_data::VariableData, }; @@ -35,7 +35,9 @@ pub struct MessageFlight { pub messages: Vec, } -impl ProtocolMessageFlight for MessageFlight { +impl ProtocolMessageFlight + for MessageFlight +{ fn new() -> Self { Self { messages: vec![] } } @@ -62,7 +64,7 @@ pub struct OpaqueMessageFlight { pub messages: Vec, } -impl OpaqueProtocolMessageFlight for OpaqueMessageFlight { +impl OpaqueProtocolMessageFlight for OpaqueMessageFlight { fn new() -> Self { Self { messages: vec![] } } @@ -90,6 +92,8 @@ impl Codec for OpaqueMessageFlight { let _ = deframer.read(&mut reader.rest()); while let Some(msg) = deframer.pop_frame() { flight.push(msg); + // continue to read the buffer + let _ = deframer.read(&mut reader.rest()); } Some(flight) @@ -104,6 +108,26 @@ impl From for OpaqueMessageFlight { } } +impl TryFrom for MessageFlight { + type Error = (); + + fn try_from(value: OpaqueMessageFlight) -> Result { + let flight = Self { + messages: value + .messages + .iter() + .filter_map(|m| (*m).clone().try_into().ok()) + .collect(), + }; + + if flight.messages.len() == 0 { + Err(()) + } else { + Ok(flight) + } + } +} + impl From for OpaqueMessageFlight { fn from(value: OpaqueMessage) -> Self { Self { @@ -112,131 +136,293 @@ impl From for OpaqueMessageFlight { } } -impl ProtocolMessage for Message { +impl ProtocolMessage for Message { fn create_opaque(&self) -> OpaqueMessage { msgs::message::PlainMessage::from(self.clone()).into_unencrypted_opaque() } fn debug(&self, info: &str) { debug_message_with_info(info, self); } +} + +impl ExtractKnowledge for MessageFlight { + fn extract_knowledge( + &self, + knowledges: &mut Vec>, + matcher: Option, + source: &Source, + ) -> Result<(), Error> { + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.clone()), + }); + + for msg in &self.messages { + msg.extract_knowledge(knowledges, matcher, source)?; + } + Ok(()) + } +} + +impl ExtractKnowledge for OpaqueMessageFlight { + fn extract_knowledge( + &self, + knowledges: &mut Vec>, + matcher: Option, + source: &Source, + ) -> Result<(), Error> { + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.clone()), + }); + for msg in &self.messages { + msg.extract_knowledge(knowledges, matcher, source)?; + } + Ok(()) + } +} +impl ExtractKnowledge for Message { /// Extracts knowledge from a [`crate::tls::rustls::msgs::message::Message`]. /// Only plaintext messages yield more knowledge than their binary payload. /// If a message is an ApplicationData (TLS 1.3) or an encrypted Heartbeet /// or Handhake message (TLS 1.2), then only the message itself and the /// binary payload is returned. - fn extract_knowledge(&self) -> Result>, Error> { - Ok(match &self.payload { + fn extract_knowledge( + &self, + knowledges: &mut Vec>, + _: Option, + source: &Source, + ) -> Result<(), Error> { + match &self.payload { MessagePayload::Alert(alert) => { - vec![ - Box::new(self.clone()), - Box::new(alert.description), - Box::new(alert.level), - ] + let matcher = TlsQueryMatcher::Alert; + + knowledges.push(Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(self.clone()), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(alert.description), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(alert.level), + }); } MessagePayload::Handshake(hs) => { + let matcher = TlsQueryMatcher::Handshake(Some(hs.typ)); + knowledges.push(Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(self.clone()), + }); match &hs.payload { HandshakePayload::HelloRequest => { - vec![Box::new(self.clone()), Box::new(hs.typ)] + knowledges.push(Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(hs.typ), + }); } HandshakePayload::ClientHello(ch) => { - let vars: Vec> = vec![ - Box::new(self.clone()), - Box::new(hs.typ), - Box::new(ch.random), - Box::new(ch.session_id), - Box::new(ch.client_version), - Box::new(ch.extensions.clone()), - Box::new(ch.compression_methods.clone()), - Box::new(ch.cipher_suites.clone()), - ]; - - let extensions = ch - .extensions - .iter() - .map(|extension| Box::new(extension.clone()) as Box); - let compression_methods = ch - .compression_methods - .iter() - .map(|compression| Box::new(*compression) as Box); - let cipher_suites = ch - .cipher_suites - .iter() - .map(|cipher_suite| Box::new(*cipher_suite) as Box); - - vars.into_iter() - .chain(extensions) // also add all extensions individually - .chain(compression_methods) - .chain(cipher_suites) - .collect::>>() + knowledges.push(Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(hs.typ), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(ch.random), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(ch.session_id), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(ch.client_version), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(ch.extensions.clone()), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(ch.compression_methods.clone()), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(ch.cipher_suites.clone()), + }); + + knowledges.extend(ch.extensions.iter().map(|extension| Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(extension.clone()) as Box, + })); + knowledges.extend(ch.compression_methods.iter().map(|compression| { + Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(*compression) as Box, + } + })); + knowledges.extend(ch.cipher_suites.iter().map(|cipher_suite| Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(*cipher_suite) as Box, + })); } HandshakePayload::ServerHello(sh) => { - let vars: Vec> = vec![ - Box::new(self.clone()), - Box::new(hs.typ), - Box::new(sh.random), - Box::new(sh.session_id), - Box::new(sh.cipher_suite), - Box::new(sh.compression_method), - Box::new(sh.legacy_version), - Box::new(sh.extensions.clone()), - ]; - - let server_extensions = sh.extensions.iter().map(|extension| { - Box::new(extension.clone()) as Box - // it is important to cast here: https://stackoverflow.com/questions/48180008/how-can-i-box-the-contents-of-an-iterator-of-a-type-that-implements-a-trait + knowledges.push(Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(hs.typ), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(sh.random), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(sh.session_id), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(sh.cipher_suite), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(sh.compression_method), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(sh.legacy_version), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(sh.extensions.clone()), }); - vars.into_iter() - .chain(server_extensions) - .collect::>>() + knowledges.extend(sh.extensions.iter().map(|extension| Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(extension.clone()) as Box, + })); } HandshakePayload::Certificate(c) => { - vec![Box::new(self.clone()), Box::new(c.0.clone())] + knowledges.push(Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(c.0.clone()), + }); } HandshakePayload::ServerKeyExchange(ske) => match ske { ServerKeyExchangePayload::ECDHE(ecdhe) => { // this path wont be taken because we do not know the key exchange algorithm // in advance - vec![Box::new(self.clone()), Box::new(ecdhe.clone())] + knowledges.push(Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(ecdhe.clone()), + }); } ServerKeyExchangePayload::Unknown(unknown) => { - vec![Box::new(self.clone()), Box::new(unknown.0.clone())] + knowledges.push(Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(unknown.0.clone()), + }); } }, - HandshakePayload::ServerHelloDone => { - vec![Box::new(self.clone())] - } + HandshakePayload::ServerHelloDone => {} HandshakePayload::ClientKeyExchange(cke) => { - vec![Box::new(self.clone()), Box::new(cke.0.clone())] + knowledges.push(Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(cke.0.clone()), + }); } HandshakePayload::NewSessionTicket(ticket) => { - vec![ - Box::new(self.clone()), - Box::new(ticket.lifetime_hint as u64), - Box::new(ticket.ticket.0.clone()), - ] + knowledges.push(Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(ticket.lifetime_hint as u64), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(ticket.ticket.0.clone()), + }); } _ => return Err(Error::Extraction()), } } - MessagePayload::ChangeCipherSpec(_ccs) => { - vec![] - } + MessagePayload::ChangeCipherSpec(_ccs) => {} MessagePayload::ApplicationData(opaque) => { - vec![Box::new(self.clone()), Box::new(opaque.0.clone())] + let matcher = TlsQueryMatcher::ApplicationData; + knowledges.push(Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(self.clone()), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(opaque.0.clone()), + }); } MessagePayload::Heartbeat(h) => { - vec![Box::new(self.clone()), Box::new(h.payload.clone())] + let matcher = TlsQueryMatcher::Heartbeat; + knowledges.push(Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(self.clone()), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(h.payload.0.clone()), + }); } MessagePayload::TLS12EncryptedHandshake(tls12encrypted) => { - vec![Box::new(self.clone()), Box::new(tls12encrypted.0.clone())] + let matcher = TlsQueryMatcher::Handshake(None); + knowledges.push(Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(self.clone()), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher: Some(matcher), + data: Box::new(tls12encrypted.0.clone()), + }); } - }) + } + Ok(()) } } -impl ProtocolMessageDeframer for MessageDeframer { +impl ProtocolMessageDeframer for MessageDeframer { type OpaqueProtocolMessage = OpaqueMessage; fn pop_frame(&mut self) -> Option { @@ -247,13 +433,25 @@ impl ProtocolMessageDeframer for MessageDeframer { } } -impl OpaqueProtocolMessage for OpaqueMessage { +impl OpaqueProtocolMessage for OpaqueMessage { fn debug(&self, info: &str) { debug_opaque_message_with_info(info, self); } +} - fn extract_knowledge(&self) -> Result>, Error> { - Ok(vec![Box::new(self.clone())]) +impl ExtractKnowledge for OpaqueMessage { + fn extract_knowledge( + &self, + knowledges: &mut Vec>, + matcher: Option, + source: &Source, + ) -> Result<(), Error> { + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.clone()), + }); + Ok(()) } } diff --git a/tlspuffin/src/query.rs b/tlspuffin/src/query.rs index b0e2371c0..183aa6a58 100644 --- a/tlspuffin/src/query.rs +++ b/tlspuffin/src/query.rs @@ -1,10 +1,7 @@ -use puffin::{algebra::Matcher, error::Error, protocol::MessageResult}; +use puffin::algebra::Matcher; use serde::{Deserialize, Serialize}; -use crate::tls::rustls::msgs::{ - enums::{ContentType, HandshakeType}, - message::{Message, MessagePayload, OpaqueMessage}, -}; +use crate::tls::rustls::msgs::enums::HandshakeType; /// [TlsQueryMatcher] contains TLS-related typing information, this is to be distinguished from the *.typ fields /// It uses [rustls::msgs::enums::{ContentType,HandshakeType}]. @@ -45,28 +42,3 @@ impl Matcher for TlsQueryMatcher { } } } - -impl TryFrom<&MessageResult> for TlsQueryMatcher { - type Error = Error; - - fn try_from( - message_result: &MessageResult, - ) -> Result { - let tls_opaque_type = message_result.1.typ; - match (tls_opaque_type, message_result) { - (ContentType::Handshake, MessageResult(Some(message), _)) => match &message.payload { - MessagePayload::Handshake(handshake_payload) => { - Ok(TlsQueryMatcher::Handshake(Some(handshake_payload.typ))) - } - MessagePayload::TLS12EncryptedHandshake(_) => Ok(TlsQueryMatcher::Handshake(None)), - _ => Err(Error::Extraction()), - }, - (ContentType::Handshake, _) => Ok(TlsQueryMatcher::Handshake(None)), - (ContentType::ApplicationData, _) => Ok(TlsQueryMatcher::ApplicationData), - (ContentType::Heartbeat, _) => Ok(TlsQueryMatcher::Heartbeat), - (ContentType::Alert, _) => Ok(TlsQueryMatcher::Alert), - (ContentType::ChangeCipherSpec, _) => Ok(TlsQueryMatcher::ChangeCipherSpec), - (ContentType::Unknown(_), _) => Err(Error::Extraction()), - } - } -} diff --git a/tlspuffin/src/tcp/mod.rs b/tlspuffin/src/tcp/mod.rs index a7bb9aa13..cebaa1d32 100644 --- a/tlspuffin/src/tcp/mod.rs +++ b/tlspuffin/src/tcp/mod.rs @@ -1,21 +1,21 @@ use std::{ ffi::OsStr, - io, - io::{ErrorKind, Write}, + io::{self, ErrorKind, Read, Write}, net::{AddrParseError, IpAddr, SocketAddr, TcpListener, TcpStream, ToSocketAddrs}, path::Path, process::{Child, Command, Stdio}, str::FromStr, - sync::{mpsc, mpsc::channel}, + sync::mpsc::{self, channel}, thread, time::Duration, }; -use log::{debug, error, info, warn}; +use log::{debug, info, warn}; use puffin::{ agent::{AgentDescriptor, AgentName, AgentType}, + codec::Codec, error::Error, - protocol::{MessageResult, OpaqueProtocolMessageFlight}, + protocol::OpaqueProtocolMessageFlight, put::{Put, PutDescriptor, PutName}, put_registry::{Factory, PutKind}, stream::Stream, @@ -26,10 +26,8 @@ use puffin::{ use crate::{ protocol::{OpaqueMessageFlight, TLSProtocolBehavior}, put_registry::TCP_PUT, - tls::rustls::msgs::{ - deframer::MessageDeframer, - message::{Message, OpaqueMessage}, - }, + query::TlsQueryMatcher, + tls::rustls::msgs::message::{Message, OpaqueMessage}, }; pub fn new_tcp_factory() -> Box> { @@ -117,11 +115,9 @@ pub fn new_tcp_factory() -> Box> { } trait TcpPut { - fn deframer_mut(&mut self) -> &mut MessageDeframer; - fn write_to_stream(&mut self, buf: &[u8]) -> io::Result<()>; - fn read_to_deframer(&mut self) -> io::Result; + fn read_to_flight(&mut self) -> Result, Error>; } /// A PUT which is backed by a TCP stream to a server. @@ -132,23 +128,21 @@ trait TcpPut { /// ``` pub struct TcpClientPut { stream: TcpStream, - deframer: MessageDeframer, agent_descriptor: AgentDescriptor, process: Option, } impl TcpPut for TcpClientPut { - fn deframer_mut(&mut self) -> &mut MessageDeframer { - &mut self.deframer - } - fn write_to_stream(&mut self, buf: &[u8]) -> io::Result<()> { self.stream.write_all(buf)?; self.stream.flush() } - fn read_to_deframer(&mut self) -> io::Result { - self.deframer.read(&mut self.stream) + fn read_to_flight(&mut self) -> Result, Error> { + let mut buf = vec![]; + let _ = self.stream.read_to_end(&mut buf); + let flight = OpaqueMessageFlight::read_bytes(&mut buf); + Ok(flight) } } @@ -162,7 +156,6 @@ impl TcpClientPut { Ok(Self { stream, - deframer: Default::default(), agent_descriptor: agent_descriptor.clone(), process: None, }) @@ -203,7 +196,6 @@ impl TcpClientPut { pub struct TcpServerPut { stream: Option<(TcpStream, TcpListener)>, stream_receiver: mpsc::Receiver<(TcpStream, TcpListener)>, - deframer: MessageDeframer, agent_descriptor: AgentDescriptor, process: Option, } @@ -235,7 +227,6 @@ impl TcpServerPut { Ok(Self { stream: None, stream_receiver, - deframer: Default::default(), agent_descriptor: agent_descriptor.clone(), process: None, }) @@ -259,10 +250,6 @@ impl TcpServerPut { } impl TcpPut for TcpServerPut { - fn deframer_mut(&mut self) -> &mut MessageDeframer { - &mut self.deframer - } - fn write_to_stream(&mut self, buf: &[u8]) -> io::Result<()> { self.receive_stream(); let stream = &mut self.stream.as_mut().unwrap().0; @@ -271,79 +258,41 @@ impl TcpPut for TcpServerPut { Ok(()) } - fn read_to_deframer(&mut self) -> io::Result { + fn read_to_flight(&mut self) -> Result, Error> { self.receive_stream(); - let stream = &mut self.stream.as_mut().unwrap().0; - self.deframer.read(stream) + let mut buf = vec![]; + let _ = self.stream.as_mut().unwrap().0.read_to_end(&mut buf); + let flight = OpaqueMessageFlight::read_bytes(&mut buf); + Ok(flight) } } -impl Stream for TcpServerPut { +impl Stream for TcpServerPut { fn add_to_inbound(&mut self, opaque_flight: &OpaqueMessageFlight) { self.write_to_stream(&opaque_flight.clone().get_encoding()) .unwrap(); } - fn take_message_from_outbound( - &mut self, - ) -> Result>, Error> { + fn take_message_from_outbound(&mut self) -> Result, Error> { take_message_from_outbound(self) } } -impl Stream for TcpClientPut { +impl Stream for TcpClientPut { fn add_to_inbound(&mut self, opaque_flight: &OpaqueMessageFlight) { self.write_to_stream(&opaque_flight.clone().get_encoding()) .unwrap(); } - fn take_message_from_outbound( - &mut self, - ) -> Result>, Error> { + fn take_message_from_outbound(&mut self) -> Result, Error> { take_message_from_outbound(self) } } fn take_message_from_outbound( put: &mut P, -) -> Result>, Error> { - // Retry to read if no more frames in the deframer buffer - let opaque_message = loop { - if let Some(opaque_message) = put.deframer_mut().frames.pop_front() { - break Some(opaque_message); - } else { - match put.read_to_deframer() { - Ok(v) => { - if v == 0 { - break None; - } - } - Err(err) => match err.kind() { - ErrorKind::WouldBlock => { - // This is not a hard error. It just means we will should read again from - // the TCPStream in the next steps. - break None; - } - _ => return Err(err.into()), - }, - } - } - }; - - if let Some(opaque_message) = opaque_message { - let message = match Message::try_from(opaque_message.clone().into_plain_message()) { - Ok(message) => Some(message), - Err(err) => { - error!("Failed to decode message! This means we maybe need to remove logical checks from rustls! {}", err); - None - } - }; - - Ok(Some(MessageResult(message, opaque_message))) - } else { - // no message to return - Ok(None) - } +) -> Result, Error> { + put.read_to_flight() } fn addr_from_config(put_descriptor: &PutDescriptor) -> Result { diff --git a/tlspuffin/src/tls/seeds.rs b/tlspuffin/src/tls/seeds.rs index 609182b7a..479b7ee22 100644 --- a/tlspuffin/src/tls/seeds.rs +++ b/tlspuffin/src/tls/seeds.rs @@ -1896,7 +1896,7 @@ pub mod tests { #[cfg(feature = "tls13")] // require version which supports TLS 1.3 #[cfg(not(feature = "boringssl-binding"))] #[test] - fn test_seed_successful_with_flights() { + fn test_seed_successful() { use crate::tls::trace_helper::TraceExecutor; let ctx = seed_successful.execute_trace(); diff --git a/tlspuffin/src/wolfssl/mod.rs b/tlspuffin/src/wolfssl/mod.rs index 03ce4982c..9454b469e 100644 --- a/tlspuffin/src/wolfssl/mod.rs +++ b/tlspuffin/src/wolfssl/mod.rs @@ -7,7 +7,6 @@ use puffin::{ agent::{AgentDescriptor, AgentName, AgentType, TLSVersion}, algebra::dynamic_function::TypeShape, error::Error, - protocol::MessageResult, put::{Put, PutName}, put_registry::{Factory, PutKind}, stream::{MemoryStream, Stream}, @@ -31,9 +30,9 @@ use crate::{ protocol::{OpaqueMessageFlight, TLSProtocolBehavior}, put::TlsPutConfig, put_registry::WOLFSSL520_PUT, + query::TlsQueryMatcher, static_certs::{ALICE_CERT, ALICE_PRIVATE_KEY, BOB_CERT, BOB_PRIVATE_KEY, EVE_CERT}, tls::rustls::msgs::{ - deframer::MessageDeframer, enums::HandshakeType, message::{Message, OpaqueMessage}, }, @@ -141,25 +140,23 @@ impl From for Error { } pub struct WolfSSL { - stream: SslStream>, + stream: SslStream, ctx: SslContext, config: TlsPutConfig, } -impl Stream for WolfSSL { +impl Stream for WolfSSL { fn add_to_inbound(&mut self, opaque_flight: &OpaqueMessageFlight) { let raw_stream = self.stream.get_mut(); - as Stream>::add_to_inbound( + >::add_to_inbound( raw_stream, opaque_flight, ) } - fn take_message_from_outbound( - &mut self, - ) -> Result>, Error> { + fn take_message_from_outbound(&mut self) -> Result, Error> { let raw_stream = self.stream.get_mut(); - as Stream>::take_message_from_outbound(raw_stream) + >::take_message_from_outbound(raw_stream) } } @@ -209,16 +206,13 @@ impl WolfSSL { fn new_stream( ctx: &SslContextRef, config: &TlsPutConfig, - ) -> Result>, WolfSSLErrorStack> { + ) -> Result, WolfSSLErrorStack> { let ssl = match config.descriptor.typ { AgentType::Server => Self::create_server(ctx)?, AgentType::Client => Self::create_client(ctx)?, }; - Ok(SslStream::new( - ssl, - MemoryStream::new(MessageDeframer::new()), - )?) + Ok(SslStream::new(ssl, MemoryStream::new())?) } } From e88c75d952bbbd8aaf1447e17eb273f10439138e Mon Sep 17 00:00:00 2001 From: Tom Gouville Date: Thu, 20 Jun 2024 14:24:33 +0200 Subject: [PATCH 20/29] recursive extract_knowledge for tls protocol --- tlspuffin/src/protocol.rs | 491 +++++++++++++++++++++++--------------- 1 file changed, 293 insertions(+), 198 deletions(-) diff --git a/tlspuffin/src/protocol.rs b/tlspuffin/src/protocol.rs index dd8210878..b941e916a 100644 --- a/tlspuffin/src/protocol.rs +++ b/tlspuffin/src/protocol.rs @@ -16,13 +16,16 @@ use crate::{ debug::{debug_message_with_info, debug_opaque_message_with_info}, query::TlsQueryMatcher, tls::{ - rustls::{ - msgs, - msgs::{ - deframer::MessageDeframer, - handshake::{HandshakePayload, ServerKeyExchangePayload}, - message::{Message, MessagePayload, OpaqueMessage}, + rustls::msgs::{ + self, + base::Payload, + deframer::MessageDeframer, + handshake::{ + CertificatePayload, ClientHelloPayload, ECDHEServerKeyExchange, + HandshakeMessagePayload, HandshakePayload, NewSessionTicketPayload, + ServerHelloPayload, ServerKeyExchangePayload, }, + message::{Message, MessagePayload, OpaqueMessage}, }, seeds::create_corpus, violation::TlsSecurityViolationPolicy, @@ -196,224 +199,67 @@ impl ExtractKnowledge for Message { _: Option, source: &Source, ) -> Result<(), Error> { - match &self.payload { - MessagePayload::Alert(alert) => { - let matcher = TlsQueryMatcher::Alert; + let matcher = match &self.payload { + MessagePayload::Alert(_) => Some(TlsQueryMatcher::Alert), + MessagePayload::Handshake(hs) => Some(TlsQueryMatcher::Handshake(Some(hs.typ))), + MessagePayload::ChangeCipherSpec(_) => None, + MessagePayload::ApplicationData(_) => Some(TlsQueryMatcher::ApplicationData), + MessagePayload::Heartbeat(_) => Some(TlsQueryMatcher::Heartbeat), + MessagePayload::TLS12EncryptedHandshake(_) => Some(TlsQueryMatcher::Handshake(None)), + }; + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.clone()), + }); + + self.payload + .extract_knowledge(knowledges, matcher, source)?; + Ok(()) + } +} + +impl ExtractKnowledge for MessagePayload { + fn extract_knowledge( + &self, + knowledges: &mut Vec>, + matcher: Option, + source: &Source, + ) -> Result<(), Error> { + match &self { + MessagePayload::Alert(alert) => { knowledges.push(Knowledge { source: source.clone(), - matcher: Some(matcher), - data: Box::new(self.clone()), - }); - knowledges.push(Knowledge { - source: source.clone(), - matcher: Some(matcher), + matcher, data: Box::new(alert.description), }); knowledges.push(Knowledge { source: source.clone(), - matcher: Some(matcher), + matcher, data: Box::new(alert.level), }); } - MessagePayload::Handshake(hs) => { - let matcher = TlsQueryMatcher::Handshake(Some(hs.typ)); - knowledges.push(Knowledge { - source: source.clone(), - matcher: Some(matcher), - data: Box::new(self.clone()), - }); - match &hs.payload { - HandshakePayload::HelloRequest => { - knowledges.push(Knowledge { - source: source.clone(), - matcher: Some(matcher), - data: Box::new(hs.typ), - }); - } - HandshakePayload::ClientHello(ch) => { - knowledges.push(Knowledge { - source: source.clone(), - matcher: Some(matcher), - data: Box::new(hs.typ), - }); - knowledges.push(Knowledge { - source: source.clone(), - matcher: Some(matcher), - data: Box::new(ch.random), - }); - knowledges.push(Knowledge { - source: source.clone(), - matcher: Some(matcher), - data: Box::new(ch.session_id), - }); - knowledges.push(Knowledge { - source: source.clone(), - matcher: Some(matcher), - data: Box::new(ch.client_version), - }); - knowledges.push(Knowledge { - source: source.clone(), - matcher: Some(matcher), - data: Box::new(ch.extensions.clone()), - }); - knowledges.push(Knowledge { - source: source.clone(), - matcher: Some(matcher), - data: Box::new(ch.compression_methods.clone()), - }); - knowledges.push(Knowledge { - source: source.clone(), - matcher: Some(matcher), - data: Box::new(ch.cipher_suites.clone()), - }); - - knowledges.extend(ch.extensions.iter().map(|extension| Knowledge { - source: source.clone(), - matcher: Some(matcher), - data: Box::new(extension.clone()) as Box, - })); - knowledges.extend(ch.compression_methods.iter().map(|compression| { - Knowledge { - source: source.clone(), - matcher: Some(matcher), - data: Box::new(*compression) as Box, - } - })); - knowledges.extend(ch.cipher_suites.iter().map(|cipher_suite| Knowledge { - source: source.clone(), - matcher: Some(matcher), - data: Box::new(*cipher_suite) as Box, - })); - } - HandshakePayload::ServerHello(sh) => { - knowledges.push(Knowledge { - source: source.clone(), - matcher: Some(matcher), - data: Box::new(hs.typ), - }); - knowledges.push(Knowledge { - source: source.clone(), - matcher: Some(matcher), - data: Box::new(sh.random), - }); - knowledges.push(Knowledge { - source: source.clone(), - matcher: Some(matcher), - data: Box::new(sh.session_id), - }); - knowledges.push(Knowledge { - source: source.clone(), - matcher: Some(matcher), - data: Box::new(sh.cipher_suite), - }); - knowledges.push(Knowledge { - source: source.clone(), - matcher: Some(matcher), - data: Box::new(sh.compression_method), - }); - knowledges.push(Knowledge { - source: source.clone(), - matcher: Some(matcher), - data: Box::new(sh.legacy_version), - }); - knowledges.push(Knowledge { - source: source.clone(), - matcher: Some(matcher), - data: Box::new(sh.extensions.clone()), - }); - - knowledges.extend(sh.extensions.iter().map(|extension| Knowledge { - source: source.clone(), - matcher: Some(matcher), - data: Box::new(extension.clone()) as Box, - })); - } - HandshakePayload::Certificate(c) => { - knowledges.push(Knowledge { - source: source.clone(), - matcher: Some(matcher), - data: Box::new(c.0.clone()), - }); - } - HandshakePayload::ServerKeyExchange(ske) => match ske { - ServerKeyExchangePayload::ECDHE(ecdhe) => { - // this path wont be taken because we do not know the key exchange algorithm - // in advance - knowledges.push(Knowledge { - source: source.clone(), - matcher: Some(matcher), - data: Box::new(ecdhe.clone()), - }); - } - ServerKeyExchangePayload::Unknown(unknown) => { - knowledges.push(Knowledge { - source: source.clone(), - matcher: Some(matcher), - data: Box::new(unknown.0.clone()), - }); - } - }, - HandshakePayload::ServerHelloDone => {} - HandshakePayload::ClientKeyExchange(cke) => { - knowledges.push(Knowledge { - source: source.clone(), - matcher: Some(matcher), - data: Box::new(cke.0.clone()), - }); - } - HandshakePayload::NewSessionTicket(ticket) => { - knowledges.push(Knowledge { - source: source.clone(), - matcher: Some(matcher), - data: Box::new(ticket.lifetime_hint as u64), - }); - knowledges.push(Knowledge { - source: source.clone(), - matcher: Some(matcher), - data: Box::new(ticket.ticket.0.clone()), - }); - } - _ => return Err(Error::Extraction()), - } - } + MessagePayload::Handshake(hs) => hs.extract_knowledge(knowledges, matcher, source)?, MessagePayload::ChangeCipherSpec(_ccs) => {} MessagePayload::ApplicationData(opaque) => { - let matcher = TlsQueryMatcher::ApplicationData; knowledges.push(Knowledge { source: source.clone(), - matcher: Some(matcher), - data: Box::new(self.clone()), - }); - knowledges.push(Knowledge { - source: source.clone(), - matcher: Some(matcher), + matcher, data: Box::new(opaque.0.clone()), }); } MessagePayload::Heartbeat(h) => { - let matcher = TlsQueryMatcher::Heartbeat; - knowledges.push(Knowledge { - source: source.clone(), - matcher: Some(matcher), - data: Box::new(self.clone()), - }); knowledges.push(Knowledge { source: source.clone(), - matcher: Some(matcher), + matcher, data: Box::new(h.payload.0.clone()), }); } MessagePayload::TLS12EncryptedHandshake(tls12encrypted) => { - let matcher = TlsQueryMatcher::Handshake(None); - knowledges.push(Knowledge { - source: source.clone(), - matcher: Some(matcher), - data: Box::new(self.clone()), - }); knowledges.push(Knowledge { source: source.clone(), - matcher: Some(matcher), + matcher, data: Box::new(tls12encrypted.0.clone()), }); } @@ -422,6 +268,255 @@ impl ExtractKnowledge for Message { } } +impl ExtractKnowledge for HandshakeMessagePayload { + fn extract_knowledge( + &self, + knowledges: &mut Vec>, + matcher: Option, + source: &Source, + ) -> Result<(), Error> { + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.typ), + }); + self.payload + .extract_knowledge(knowledges, matcher, source)?; + Ok(()) + } +} + +impl ExtractKnowledge for HandshakePayload { + fn extract_knowledge( + &self, + knowledges: &mut Vec>, + matcher: Option, + source: &Source, + ) -> Result<(), Error> { + match &self { + HandshakePayload::HelloRequest => {} + HandshakePayload::ClientHello(ch) => { + ch.extract_knowledge(knowledges, matcher, source)?; + } + HandshakePayload::ServerHello(sh) => { + sh.extract_knowledge(knowledges, matcher, source)?; + } + HandshakePayload::Certificate(c) => { + c.extract_knowledge(knowledges, matcher, source)?; + } + HandshakePayload::ServerKeyExchange(ske) => { + ske.extract_knowledge(knowledges, matcher, source)?; + } + HandshakePayload::ServerHelloDone => {} + HandshakePayload::ClientKeyExchange(cke) => { + cke.extract_knowledge(knowledges, matcher, source)?; + } + HandshakePayload::NewSessionTicket(ticket) => { + ticket.extract_knowledge(knowledges, matcher, source)?; + } + _ => return Err(Error::Extraction()), + } + Ok(()) + } +} + +impl ExtractKnowledge for CertificatePayload { + fn extract_knowledge( + &self, + knowledges: &mut Vec>, + matcher: Option, + source: &Source, + ) -> Result<(), Error> { + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.0.clone()), + }); + Ok(()) + } +} + +impl ExtractKnowledge for ServerKeyExchangePayload { + fn extract_knowledge( + &self, + knowledges: &mut Vec>, + matcher: Option, + source: &Source, + ) -> Result<(), Error> { + match self { + ServerKeyExchangePayload::ECDHE(ecdhe) => { + // this path wont be taken because we do not know the key exchange algorithm + // in advance + ecdhe.extract_knowledge(knowledges, matcher, source)?; + } + ServerKeyExchangePayload::Unknown(unknown) => { + unknown.extract_knowledge(knowledges, matcher, source)?; + } + } + Ok(()) + } +} + +impl ExtractKnowledge for ECDHEServerKeyExchange { + fn extract_knowledge( + &self, + knowledges: &mut Vec>, + matcher: Option, + source: &Source, + ) -> Result<(), Error> { + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.clone()), + }); + Ok(()) + } +} + +impl ExtractKnowledge for Payload { + fn extract_knowledge( + &self, + knowledges: &mut Vec>, + matcher: Option, + source: &Source, + ) -> Result<(), Error> { + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.0.clone()), + }); + Ok(()) + } +} + +impl ExtractKnowledge for ClientHelloPayload { + fn extract_knowledge( + &self, + knowledges: &mut Vec>, + matcher: Option, + source: &Source, + ) -> Result<(), Error> { + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.random), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.session_id), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.client_version), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.extensions.clone()), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.compression_methods.clone()), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.cipher_suites.clone()), + }); + + knowledges.extend(self.extensions.iter().map(|extension| Knowledge { + source: source.clone(), + matcher, + data: Box::new(extension.clone()) as Box, + })); + knowledges.extend( + self.compression_methods + .iter() + .map(|compression| Knowledge { + source: source.clone(), + matcher, + data: Box::new(*compression) as Box, + }), + ); + knowledges.extend(self.cipher_suites.iter().map(|cipher_suite| Knowledge { + source: source.clone(), + matcher, + data: Box::new(*cipher_suite) as Box, + })); + Ok(()) + } +} + +impl ExtractKnowledge for NewSessionTicketPayload { + fn extract_knowledge( + &self, + knowledges: &mut Vec>, + matcher: Option, + source: &Source, + ) -> Result<(), Error> { + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.lifetime_hint as u64), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.ticket.0.clone()), + }); + Ok(()) + } +} + +impl ExtractKnowledge for ServerHelloPayload { + fn extract_knowledge( + &self, + knowledges: &mut Vec>, + matcher: Option, + source: &Source, + ) -> Result<(), Error> { + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.random), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.session_id), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.cipher_suite), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.compression_method), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.legacy_version), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.extensions.clone()), + }); + knowledges.extend(self.extensions.iter().map(|extension| Knowledge { + source: source.clone(), + matcher, + data: Box::new(extension.clone()) as Box, + })); + Ok(()) + } +} + impl ProtocolMessageDeframer for MessageDeframer { type OpaqueProtocolMessage = OpaqueMessage; From 5eff41e5a7ed141e67d39a9ba9b92b6c44e15add Mon Sep 17 00:00:00 2001 From: Tom Gouville Date: Wed, 26 Jun 2024 16:51:08 +0200 Subject: [PATCH 21/29] using MessageFlight instead of Vec in fn_utils --- tlspuffin/src/tls/fn_utils.rs | 36 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/tlspuffin/src/tls/fn_utils.rs b/tlspuffin/src/tls/fn_utils.rs index 4097b4f5b..450262661 100644 --- a/tlspuffin/src/tls/fn_utils.rs +++ b/tlspuffin/src/tls/fn_utils.rs @@ -85,14 +85,14 @@ pub fn fn_decrypt_handshake_flight( group: &NamedGroup, client: &bool, sequence: &u64, -) -> Result, FnError> { +) -> Result { let mut sequence_number = *sequence; - let mut decrypted_flight = vec![]; + let mut decrypted_flight = MessageFlight::new(); for msg in &flight.messages { if let MessagePayload::ApplicationData(_) = &msg.payload { - let mut decrypted_msg = fn_decrypt_multiple_handshake_messages( + let decrypted_msg = fn_decrypt_multiple_handshake_messages( &msg, server_hello_transcript, server_key_share, @@ -102,7 +102,7 @@ pub fn fn_decrypt_handshake_flight( &sequence_number, )?; - decrypted_flight.append(&mut decrypted_msg); + decrypted_flight.messages.extend(decrypted_msg); sequence_number += 1; } } @@ -153,8 +153,8 @@ pub fn fn_decrypt_multiple_handshake_messages( Ok(messages) } -pub fn fn_find_server_certificate(messages: &Vec) -> Result { - for msg in messages { +pub fn fn_find_server_certificate(flight: &MessageFlight) -> Result { + for msg in &flight.messages { if let MessagePayload::Handshake(x) = &msg.payload { if x.typ == HandshakeType::Certificate { return Ok(msg.clone()); @@ -164,8 +164,8 @@ pub fn fn_find_server_certificate(messages: &Vec) -> Result) -> Result { - for msg in messages { +pub fn fn_find_server_ticket(flight: &MessageFlight) -> Result { + for msg in &flight.messages { if let MessagePayload::Handshake(x) = &msg.payload { if x.typ == HandshakeType::NewSessionTicket { return Ok(msg.clone()); @@ -175,8 +175,8 @@ pub fn fn_find_server_ticket(messages: &Vec) -> Result) -> Result { - for msg in messages { +pub fn fn_find_server_certificate_request(flight: &MessageFlight) -> Result { + for msg in &flight.messages { if let MessagePayload::Handshake(x) = &msg.payload { if x.typ == HandshakeType::CertificateRequest { return Ok(msg.clone()); @@ -186,8 +186,8 @@ pub fn fn_find_server_certificate_request(messages: &Vec) -> Result) -> Result { - for msg in messages { +pub fn fn_find_encrypted_extensions(flight: &MessageFlight) -> Result { + for msg in &flight.messages { if let MessagePayload::Handshake(x) = &msg.payload { if x.typ == HandshakeType::EncryptedExtensions { return Ok(msg.clone()); @@ -197,8 +197,8 @@ pub fn fn_find_encrypted_extensions(messages: &Vec) -> Result) -> Result { - for msg in messages { +pub fn fn_find_server_certificate_verify(flight: &MessageFlight) -> Result { + for msg in &flight.messages { if let MessagePayload::Handshake(x) = &msg.payload { if x.typ == HandshakeType::CertificateVerify { return Ok(msg.clone()); @@ -208,8 +208,8 @@ pub fn fn_find_server_certificate_verify(messages: &Vec) -> Result) -> Result { - for msg in messages { +pub fn fn_find_server_finished(flight: &MessageFlight) -> Result { + for msg in &flight.messages { if let MessagePayload::Handshake(x) = &msg.payload { if x.typ == HandshakeType::Finished { return Ok(msg.clone()); @@ -237,10 +237,10 @@ pub fn fn_decrypt_application_flight( group: &NamedGroup, client: &bool, sequence: &u64, -) -> Result, FnError> { +) -> Result { let mut sequence_number = *sequence; - let mut decrypted_flight = vec![]; + let mut decrypted_flight = MessageFlight::new(); for msg in &flight.messages { if let MessagePayload::ApplicationData(_) = &msg.payload { From 73d4fc37c03c184c76f7ee32d9ec248e0e26bd19 Mon Sep 17 00:00:00 2001 From: Tom Gouville Date: Thu, 27 Jun 2024 12:56:18 +0200 Subject: [PATCH 22/29] fixing regression on `tls::vulnerabilities::tests::test_seed_cve_2022_38152` --- tlspuffin/src/tls/seeds.rs | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/tlspuffin/src/tls/seeds.rs b/tlspuffin/src/tls/seeds.rs index 479b7ee22..d57ba6ab4 100644 --- a/tlspuffin/src/tls/seeds.rs +++ b/tlspuffin/src/tls/seeds.rs @@ -1528,23 +1528,23 @@ pub fn _seed_client_attacker_full( }), }, OutputAction::new_step(server), - Step { - agent: server, - action: Action::Input(InputAction { - recipe: term! { - fn_encrypt_application( - fn_alert_close_notify, - (@server_hello_transcript), - (@server_finished_transcript), - (fn_get_server_key_share(((server, 0)))), - fn_no_psk, - fn_named_group_secp384r1, - fn_seq_0 // sequence 0 - ) - }, - }), - }, - OutputAction::new_step(server), + // Step { + // agent: server, + // action: Action::Input(InputAction { + // recipe: term! { + // fn_encrypt_application( + // fn_alert_close_notify, + // (@server_hello_transcript), + // (@server_finished_transcript), + // (fn_get_server_key_share(((server, 0)))), + // fn_no_psk, + // fn_named_group_secp384r1, + // fn_seq_0 // sequence 0 + // ) + // }, + // }), + // }, + // OutputAction::new_step(server), ], }; From 8fb0e17167dda7e2497909b84931d2dbe2f52579 Mon Sep 17 00:00:00 2001 From: Tom Gouville Date: Thu, 27 Jun 2024 15:33:42 +0200 Subject: [PATCH 23/29] adding more knowledge for tls --- tlspuffin/src/protocol.rs | 45 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tlspuffin/src/protocol.rs b/tlspuffin/src/protocol.rs index b941e916a..331d2d4ef 100644 --- a/tlspuffin/src/protocol.rs +++ b/tlspuffin/src/protocol.rs @@ -227,6 +227,11 @@ impl ExtractKnowledge for MessagePayload { matcher: Option, source: &Source, ) -> Result<(), Error> { + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.clone()), + }); match &self { MessagePayload::Alert(alert) => { knowledges.push(Knowledge { @@ -275,6 +280,11 @@ impl ExtractKnowledge for HandshakeMessagePayload { matcher: Option, source: &Source, ) -> Result<(), Error> { + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.clone()), + }); knowledges.push(Knowledge { source: source.clone(), matcher, @@ -293,6 +303,11 @@ impl ExtractKnowledge for HandshakePayload { matcher: Option, source: &Source, ) -> Result<(), Error> { + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.clone()), + }); match &self { HandshakePayload::HelloRequest => {} HandshakePayload::ClientHello(ch) => { @@ -327,6 +342,11 @@ impl ExtractKnowledge for CertificatePayload { matcher: Option, source: &Source, ) -> Result<(), Error> { + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.clone()), + }); knowledges.push(Knowledge { source: source.clone(), matcher, @@ -343,6 +363,11 @@ impl ExtractKnowledge for ServerKeyExchangePayload { matcher: Option, source: &Source, ) -> Result<(), Error> { + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.clone()), + }); match self { ServerKeyExchangePayload::ECDHE(ecdhe) => { // this path wont be taken because we do not know the key exchange algorithm @@ -380,6 +405,11 @@ impl ExtractKnowledge for Payload { matcher: Option, source: &Source, ) -> Result<(), Error> { + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.clone()), + }); knowledges.push(Knowledge { source: source.clone(), matcher, @@ -396,6 +426,11 @@ impl ExtractKnowledge for ClientHelloPayload { matcher: Option, source: &Source, ) -> Result<(), Error> { + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.clone()), + }); knowledges.push(Knowledge { source: source.clone(), matcher, @@ -457,6 +492,11 @@ impl ExtractKnowledge for NewSessionTicketPayload { matcher: Option, source: &Source, ) -> Result<(), Error> { + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.clone()), + }); knowledges.push(Knowledge { source: source.clone(), matcher, @@ -478,6 +518,11 @@ impl ExtractKnowledge for ServerHelloPayload { matcher: Option, source: &Source, ) -> Result<(), Error> { + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.clone()), + }); knowledges.push(Knowledge { source: source.clone(), matcher, From 71ba24e3989c87b9e5515edde7466cb46f6a1720 Mon Sep 17 00:00:00 2001 From: Tom Gouville Date: Tue, 2 Jul 2024 10:44:20 +0200 Subject: [PATCH 24/29] using flights with seed_session_resumption_dhe + add debugging lines --- puffin/src/protocol.rs | 2 +- puffin/src/trace.rs | 1 + tlspuffin/src/tls/seeds.rs | 12 +++++++----- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/puffin/src/protocol.rs b/puffin/src/protocol.rs index 74bbeeb35..09516b7ec 100644 --- a/puffin/src/protocol.rs +++ b/puffin/src/protocol.rs @@ -10,7 +10,7 @@ use crate::{ /// Provide a way to extract knowledge out of a Message/OpaqueMessage or any type that /// might be used in a precomputation -pub trait ExtractKnowledge { +pub trait ExtractKnowledge: std::fmt::Debug { /// Fill `knowledges` with new knowledge gathered form the type implementing ExtractKnowledge /// by recursively calling extract_knowledge on all contained element /// This will put source as the source of all the produced knowledges, matcher is also passed diff --git a/puffin/src/trace.rs b/puffin/src/trace.rs index b89c5f590..739bc09de 100644 --- a/puffin/src/trace.rs +++ b/puffin/src/trace.rs @@ -135,6 +135,7 @@ impl KnowledgeStore { source: Source, ) -> Result { let count_before = self.knowledge.len(); + log::trace!("Extracting knowledge on : {:?}", data); data.extract_knowledge(&mut self.knowledge, None, &source)?; Ok(self.knowledge.len() - count_before) diff --git a/tlspuffin/src/tls/seeds.rs b/tlspuffin/src/tls/seeds.rs index d57ba6ab4..c47728cdc 100644 --- a/tlspuffin/src/tls/seeds.rs +++ b/tlspuffin/src/tls/seeds.rs @@ -1122,26 +1122,28 @@ pub fn _seed_client_attacker12( (trace, client_verify_data) } -// TODO: `Unable to find variable (Some(AgentName(0)), 4)[Some(ApplicationData)]/Message!` error with BoringSSL +// TODO: `"Unable to find variable (Some(Agent(AgentName(0))), 1)[None]/MessageFlight!"` error with BoringSSL pub fn seed_session_resumption_dhe( initial_server: AgentName, server: AgentName, ) -> Trace { let initial_handshake = seed_client_attacker(initial_server); - let new_ticket_message = term! { - fn_decrypt_application( - ((initial_server, 4)[Some(TlsQueryMatcher::ApplicationData)]), // Ticket from last session + let extensions = term! { + fn_decrypt_application_flight( + ((initial_server, 1)/MessageFlight), // The first flight of messages sent by the server (fn_server_hello_transcript(((initial_server, 0)))), (fn_server_finished_transcript(((initial_server, 0)))), (fn_get_server_key_share(((initial_server, 0)))), fn_no_psk, fn_named_group_secp384r1, fn_true, - fn_seq_0 // sequence restarts at 0 because we are decrypting now traffic + fn_seq_0 // sequence 0 ) }; + let new_ticket_message = term! {fn_find_server_ticket((@extensions))}; + let client_hello = term! { fn_client_hello( fn_protocol_version12, From b612052b01e5c70b40ba8516df88d7f88ac98c2e Mon Sep 17 00:00:00 2001 From: Tom Gouville Date: Tue, 2 Jul 2024 10:56:27 +0200 Subject: [PATCH 25/29] removing redundant function implem --- puffin/src/protocol.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/puffin/src/protocol.rs b/puffin/src/protocol.rs index 09516b7ec..993a59c47 100644 --- a/puffin/src/protocol.rs +++ b/puffin/src/protocol.rs @@ -43,11 +43,6 @@ pub trait OpaqueProtocolMessageFlight> fn new() -> Self; fn debug(&self, info: &str); fn push(&mut self, msg: O); - fn get_encoding(self) -> Vec { - let mut buf = Vec::new(); - self.encode(&mut buf); - buf - } } /// A structured message. This type defines how all possible messages of a protocol. From 22af810f1529ac075f46cc1b397a96ef8a0b776f Mon Sep 17 00:00:00 2001 From: Tom Gouville Date: Tue, 2 Jul 2024 11:16:21 +0200 Subject: [PATCH 26/29] using flights with seed_session_resumption_ke --- tlspuffin/src/tls/seeds.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tlspuffin/src/tls/seeds.rs b/tlspuffin/src/tls/seeds.rs index c47728cdc..b0eae1d1a 100644 --- a/tlspuffin/src/tls/seeds.rs +++ b/tlspuffin/src/tls/seeds.rs @@ -1256,19 +1256,21 @@ pub fn seed_session_resumption_ke( ) -> Trace { let initial_handshake = seed_client_attacker(initial_server); - let new_ticket_message = term! { - fn_decrypt_application( - ((initial_server, 4)[Some(TlsQueryMatcher::ApplicationData)]), // Ticket from last session + let extensions = term! { + fn_decrypt_application_flight( + ((initial_server, 1)/MessageFlight), // The first flight of messages sent by the server (fn_server_hello_transcript(((initial_server, 0)))), (fn_server_finished_transcript(((initial_server, 0)))), (fn_get_server_key_share(((initial_server, 0)))), fn_no_psk, fn_named_group_secp384r1, fn_true, - fn_seq_0 // sequence restarts at 0 because we are decrypting now traffic + fn_seq_0 // sequence 0 ) }; + let new_ticket_message = term! {fn_find_server_ticket((@extensions))}; + let client_hello = term! { fn_client_hello( fn_protocol_version12, From be134dd242dd18496f0075f7c9f0f61ce753aef9 Mon Sep 17 00:00:00 2001 From: Tom Gouville Date: Tue, 2 Jul 2024 11:16:21 +0200 Subject: [PATCH 27/29] using flights with seed_session_resumption_ke --- tlspuffin/src/tls/seeds.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tlspuffin/src/tls/seeds.rs b/tlspuffin/src/tls/seeds.rs index c47728cdc..a00b41301 100644 --- a/tlspuffin/src/tls/seeds.rs +++ b/tlspuffin/src/tls/seeds.rs @@ -1249,26 +1249,28 @@ pub fn seed_session_resumption_dhe( } } -// TODO: `Unable to find variable (Some(AgentName(0)), 4)[Some(ApplicationData)]/Message!` error with BoringSSL +// TODO: `Unable to find variable (Some(Agent(AgentName(0))), 1)[None]/MessageFlight!` error with BoringSSL pub fn seed_session_resumption_ke( initial_server: AgentName, server: AgentName, ) -> Trace { let initial_handshake = seed_client_attacker(initial_server); - let new_ticket_message = term! { - fn_decrypt_application( - ((initial_server, 4)[Some(TlsQueryMatcher::ApplicationData)]), // Ticket from last session + let extensions = term! { + fn_decrypt_application_flight( + ((initial_server, 1)/MessageFlight), // The first flight of messages sent by the server (fn_server_hello_transcript(((initial_server, 0)))), (fn_server_finished_transcript(((initial_server, 0)))), (fn_get_server_key_share(((initial_server, 0)))), fn_no_psk, fn_named_group_secp384r1, fn_true, - fn_seq_0 // sequence restarts at 0 because we are decrypting now traffic + fn_seq_0 // sequence 0 ) }; + let new_ticket_message = term! {fn_find_server_ticket((@extensions))}; + let client_hello = term! { fn_client_hello( fn_protocol_version12, From 887d8620d4d03d52b4b6b2257a37095662837951 Mon Sep 17 00:00:00 2001 From: Tom Gouville Date: Tue, 2 Jul 2024 11:41:36 +0200 Subject: [PATCH 28/29] cleaning --- puffin/src/algebra/term.rs | 2 +- tlspuffin/src/tcp/mod.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/puffin/src/algebra/term.rs b/puffin/src/algebra/term.rs index c924af2ec..5f1c3ab65 100644 --- a/puffin/src/algebra/term.rs +++ b/puffin/src/algebra/term.rs @@ -118,7 +118,7 @@ impl Term { if let Some(Source::Agent(agent_name)) = variable.query.source { context.find_claim(agent_name, variable.typ) } else { - None + todo!("Implement querying by label"); } }) .ok_or_else(|| Error::Term(format!("Unable to find variable {}!", variable))), diff --git a/tlspuffin/src/tcp/mod.rs b/tlspuffin/src/tcp/mod.rs index cebaa1d32..8132ba686 100644 --- a/tlspuffin/src/tcp/mod.rs +++ b/tlspuffin/src/tcp/mod.rs @@ -15,7 +15,6 @@ use puffin::{ agent::{AgentDescriptor, AgentName, AgentType}, codec::Codec, error::Error, - protocol::OpaqueProtocolMessageFlight, put::{Put, PutDescriptor, PutName}, put_registry::{Factory, PutKind}, stream::Stream, From ecd330d31859afb6f4ba545b15ad21b577d00e4a Mon Sep 17 00:00:00 2001 From: Tom Gouville Date: Wed, 3 Jul 2024 16:02:03 +0200 Subject: [PATCH 29/29] impl ExtractKnowledge for more type in tlspuffin --- tlspuffin/src/protocol.rs | 103 +++++++++++++++++++++++++++----------- 1 file changed, 73 insertions(+), 30 deletions(-) diff --git a/tlspuffin/src/protocol.rs b/tlspuffin/src/protocol.rs index 331d2d4ef..a2936e7ed 100644 --- a/tlspuffin/src/protocol.rs +++ b/tlspuffin/src/protocol.rs @@ -18,13 +18,16 @@ use crate::{ tls::{ rustls::msgs::{ self, + alert::AlertMessagePayload, base::Payload, + ccs::ChangeCipherSpecPayload, deframer::MessageDeframer, handshake::{ CertificatePayload, ClientHelloPayload, ECDHEServerKeyExchange, HandshakeMessagePayload, HandshakePayload, NewSessionTicketPayload, ServerHelloPayload, ServerKeyExchangePayload, }, + heartbeat::HeartbeatPayload, message::{Message, MessagePayload, OpaqueMessage}, }, seeds::create_corpus, @@ -233,46 +236,86 @@ impl ExtractKnowledge for MessagePayload { data: Box::new(self.clone()), }); match &self { - MessagePayload::Alert(alert) => { - knowledges.push(Knowledge { - source: source.clone(), - matcher, - data: Box::new(alert.description), - }); - knowledges.push(Knowledge { - source: source.clone(), - matcher, - data: Box::new(alert.level), - }); - } + MessagePayload::Alert(alert) => alert.extract_knowledge(knowledges, matcher, source)?, MessagePayload::Handshake(hs) => hs.extract_knowledge(knowledges, matcher, source)?, - MessagePayload::ChangeCipherSpec(_ccs) => {} - MessagePayload::ApplicationData(opaque) => { - knowledges.push(Knowledge { - source: source.clone(), - matcher, - data: Box::new(opaque.0.clone()), - }); + MessagePayload::ChangeCipherSpec(ccs) => { + ccs.extract_knowledge(knowledges, matcher, source)? } - MessagePayload::Heartbeat(h) => { - knowledges.push(Knowledge { - source: source.clone(), - matcher, - data: Box::new(h.payload.0.clone()), - }); + MessagePayload::ApplicationData(opaque) => { + opaque.extract_knowledge(knowledges, matcher, source)? } + MessagePayload::Heartbeat(h) => h.extract_knowledge(knowledges, matcher, source)?, MessagePayload::TLS12EncryptedHandshake(tls12encrypted) => { - knowledges.push(Knowledge { - source: source.clone(), - matcher, - data: Box::new(tls12encrypted.0.clone()), - }); + tls12encrypted.extract_knowledge(knowledges, matcher, source)? } } Ok(()) } } +impl ExtractKnowledge for ChangeCipherSpecPayload { + fn extract_knowledge( + &self, + knowledges: &mut Vec>, + matcher: Option, + source: &Source, + ) -> Result<(), Error> { + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.clone()), + }); + + Ok(()) + } +} +impl ExtractKnowledge for HeartbeatPayload { + fn extract_knowledge( + &self, + knowledges: &mut Vec>, + matcher: Option, + source: &Source, + ) -> Result<(), Error> { + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.clone()), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.payload.0.clone()), + }); + Ok(()) + } +} + +impl ExtractKnowledge for AlertMessagePayload { + fn extract_knowledge( + &self, + knowledges: &mut Vec>, + matcher: Option, + source: &Source, + ) -> Result<(), Error> { + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.clone()), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.description), + }); + knowledges.push(Knowledge { + source: source.clone(), + matcher, + data: Box::new(self.level), + }); + Ok(()) + } +} + impl ExtractKnowledge for HandshakeMessagePayload { fn extract_knowledge( &self,