forked from aws/s2n-tls
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
399 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
target/ | ||
Cargo.lock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
[package] | ||
name = "pcap" | ||
version = "0.1.0" | ||
edition = "2021" | ||
publish = false | ||
|
||
[dependencies] | ||
anyhow = "1.0.86" | ||
hex = "0.4.3" | ||
rtshark = "2.7.1" | ||
|
||
[dev-dependencies] | ||
# We want to test against the latest, local version of s2n | ||
s2n-tls-sys = { path = "../../bindings/rust/s2n-tls-sys" } | ||
s2n-tls = { path = "../../bindings/rust/s2n-tls", features = ["unstable-fingerprint"] } |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
use rtshark::Metadata; | ||
use rtshark::Packet; | ||
use rtshark::RTShark; | ||
|
||
pub(crate) fn read_all(mut tshark: RTShark) -> Vec<Packet> { | ||
let mut packets = Vec::new(); | ||
while let Ok(Some(packet)) = tshark.read() { | ||
packets.push(packet) | ||
} | ||
packets | ||
} | ||
|
||
pub(crate) fn get_metadata<'a>(packet: &'a Packet, key: &'a str) -> Option<&'a str> { | ||
let (layer_name, _) = key.split_once('.').expect("key is layer"); | ||
packet | ||
.layer_name(layer_name) | ||
.and_then(|layer| layer.metadata(key)) | ||
.map(Metadata::value) | ||
} | ||
|
||
pub(crate) fn get_all_metadata<'a>(packet: &'a Packet, key: &'a str) -> Vec<&'a str> { | ||
let (layer_name, _) = key.split_once('.').expect("key is layer"); | ||
if let Some(layer) = packet.layer_name(layer_name) { | ||
layer | ||
.iter() | ||
.filter(|metadata| metadata.name() == key) | ||
.map(Metadata::value) | ||
.collect() | ||
} else { | ||
Vec::new() | ||
} | ||
} | ||
|
||
pub fn all_pcaps() -> impl Iterator<Item = String> { | ||
std::fs::read_dir("data") | ||
.expect("Missing test pcap file") | ||
.filter_map(Result::ok) | ||
.filter_map(|entry| { | ||
let path = entry.path(); | ||
path.to_str().map(std::string::ToString::to_string) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
use crate::handshake_message::Builder as MessageBuilder; | ||
use crate::handshake_message::Message; | ||
use anyhow::*; | ||
use std::option::Option; | ||
|
||
use crate::capture::*; | ||
|
||
#[derive(Debug, Clone, Default)] | ||
pub struct ClientHello { | ||
pub message: Message, | ||
pub ja3_hash: Option<String>, | ||
pub ja3_str: Option<String>, | ||
} | ||
|
||
impl ClientHello { | ||
const JA3_HASH: &'static str = "tls.handshake.ja3"; | ||
const JA3_STR: &'static str = "tls.handshake.ja3_full"; | ||
|
||
fn from_message(message: Message) -> Self { | ||
let packet = &message.packet; | ||
let ja3_hash = get_metadata(packet, Self::JA3_HASH).map(str::to_string); | ||
let ja3_str = get_metadata(packet, Self::JA3_STR).map(str::to_string); | ||
Self { | ||
message, | ||
ja3_hash, | ||
ja3_str, | ||
} | ||
} | ||
} | ||
|
||
#[derive(Debug, Clone, Default)] | ||
pub struct Builder(MessageBuilder); | ||
|
||
impl Builder { | ||
pub fn inner(&mut self) -> &mut MessageBuilder { | ||
&mut self.0 | ||
} | ||
|
||
pub fn build(mut self) -> Result<Vec<ClientHello>> { | ||
self.0.set_type(1); | ||
|
||
let mut client_hellos = Vec::new(); | ||
for message in self.0.build()? { | ||
client_hellos.push(ClientHello::from_message(message)); | ||
} | ||
Ok(client_hellos) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn multiple_hellos() -> Result<()> { | ||
let mut builder = Builder::default(); | ||
builder | ||
.inner() | ||
.set_capture_file("data/multiple_hellos.pcap"); | ||
let hellos = builder.build()?; | ||
assert_eq!(hellos.len(), 5); | ||
Ok(()) | ||
} | ||
|
||
#[test] | ||
fn from_pcaps() -> Result<()> { | ||
let pcaps = all_pcaps(); | ||
|
||
for pcap in pcaps { | ||
let mut builder = Builder::default(); | ||
builder.inner().set_capture_file(&pcap); | ||
let hellos = builder.build().unwrap(); | ||
|
||
assert!(!hellos.is_empty()); | ||
for hello in hellos { | ||
assert!(hello.ja3_hash.is_some()); | ||
assert!(hello.ja3_str.is_some()); | ||
} | ||
} | ||
|
||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
use anyhow::*; | ||
use rtshark::Packet; | ||
use rtshark::RTSharkBuilder; | ||
use std::collections::HashSet; | ||
|
||
use crate::capture::*; | ||
|
||
#[derive(Debug, Clone, Default)] | ||
pub struct Message { | ||
pub message_type: String, | ||
pub packet: Packet, | ||
pub payloads: Vec<String>, | ||
pub frame_num: String, | ||
} | ||
|
||
impl Message { | ||
const FRAGMENT: &'static str = "tls.handshake.fragment"; | ||
const FRAGMENTS_COUNT: &'static str = "tls.handshake.fragment.count"; | ||
|
||
pub fn to_bytes(&self) -> Result<Vec<u8>> { | ||
let mut bytes = Vec::new(); | ||
for payload in &self.payloads { | ||
let hex = payload.replace(':', ""); | ||
bytes.extend(&hex::decode(&hex)?[5..]); | ||
} | ||
Ok(bytes) | ||
} | ||
|
||
fn from_packet(packet: Packet, frame_num: String, tcp_payloads: &Vec<Packet>) -> Result<Self> { | ||
let message_type = get_metadata(&packet, Builder::MESSAGE_TYPE) | ||
.context("Missing handshake message type")? | ||
.to_string(); | ||
|
||
let mut payload_frames = get_all_metadata(&packet, Self::FRAGMENT); | ||
if payload_frames.is_empty() { | ||
payload_frames.push(&frame_num); | ||
} | ||
let payload_frames: HashSet<&str> = HashSet::from_iter(payload_frames); | ||
|
||
let mut payloads = Vec::new(); | ||
for packet in tcp_payloads { | ||
if let Some(frame) = get_metadata(packet, Builder::FRAME_NUM) { | ||
if payload_frames.contains(frame) { | ||
let payload = get_metadata(packet, Builder::TCP_PAYLOAD) | ||
.context("Missing tcp payload")?; | ||
payloads.push(payload.to_string()); | ||
} | ||
} | ||
} | ||
|
||
let count = get_metadata(&packet, Self::FRAGMENTS_COUNT).unwrap_or("1"); | ||
if count != payloads.len().to_string() { | ||
bail!("Unable to find all tcp payloads for tls message") | ||
} | ||
|
||
Ok(Self { | ||
message_type, | ||
packet, | ||
payloads, | ||
frame_num, | ||
}) | ||
} | ||
} | ||
|
||
#[derive(Debug, Clone, Default)] | ||
pub struct Builder { | ||
message_type: Option<u16>, | ||
capture_file: Option<String>, | ||
} | ||
|
||
impl Builder { | ||
const TCP_PAYLOAD: &'static str = "tcp.payload"; | ||
const FRAME_NUM: &'static str = "frame.number"; | ||
const MESSAGE_TYPE: &'static str = "tls.handshake.type"; | ||
|
||
pub(crate) fn set_type(&mut self, message_type: u16) -> &mut Self { | ||
self.message_type = Some(message_type); | ||
self | ||
} | ||
|
||
pub fn set_capture_file(&mut self, file: &str) -> &mut Self { | ||
self.capture_file = Some(file.to_string()); | ||
self | ||
} | ||
|
||
fn build_from_capture(self, capture: rtshark::RTSharkBuilderReady) -> Result<Vec<Message>> { | ||
let tcp_capture = capture | ||
.display_filter(Self::TCP_PAYLOAD) | ||
.metadata_whitelist(Self::TCP_PAYLOAD) | ||
.metadata_whitelist(Self::FRAME_NUM) | ||
.spawn()?; | ||
let payloads = read_all(tcp_capture); | ||
|
||
let filter = if let Some(message_type) = self.message_type { | ||
format!("{} == {}", Self::MESSAGE_TYPE, message_type) | ||
} else { | ||
Self::MESSAGE_TYPE.to_string() | ||
}; | ||
let message_capture = capture.display_filter(&filter).spawn()?; | ||
|
||
let mut messages = Vec::new(); | ||
for packet in read_all(message_capture) { | ||
let frame_num = get_metadata(&packet, Builder::FRAME_NUM) | ||
.context("Missing frame number")? | ||
.to_string(); | ||
|
||
let context_msg = format!("Failed to parse frame {}", &frame_num); | ||
let message = | ||
Message::from_packet(packet, frame_num, &payloads).context(context_msg)?; | ||
|
||
messages.push(message); | ||
} | ||
Ok(messages) | ||
} | ||
|
||
pub(crate) fn build(mut self) -> Result<Vec<Message>> { | ||
let file = self | ||
.capture_file | ||
.take() | ||
.context("No capture file provided")?; | ||
let capture = RTSharkBuilder::builder().input_path(&file); | ||
self.build_from_capture(capture) | ||
.with_context(|| format!("Failed to parse capture file {}", &file)) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn fragmentation() -> Result<()> { | ||
let mut builder = Builder::default(); | ||
builder.set_capture_file("data/fragmented_ch.pcap"); | ||
let messages = builder.build()?; | ||
|
||
let first = messages.first().unwrap(); | ||
assert_eq!(first.payloads.len(), 3); | ||
let bytes = first.to_bytes()?; | ||
// Correct length read from wireshark | ||
assert_eq!(bytes.len(), 16262); | ||
|
||
Ok(()) | ||
} | ||
|
||
#[test] | ||
fn multiple_handshakes() -> Result<()> { | ||
let mut builder = Builder::default(); | ||
builder.set_capture_file("data/multiple_hellos.pcap"); | ||
let messages = builder.build()?; | ||
let count = messages.iter().filter(|m| m.message_type == "1").count(); | ||
assert_eq!(count, 5); | ||
Ok(()) | ||
} | ||
|
||
#[test] | ||
fn from_pcaps() -> Result<()> { | ||
let pcaps = all_pcaps(); | ||
|
||
for pcap in pcaps { | ||
let pcap: String = pcap; | ||
|
||
let mut builder = Builder::default(); | ||
builder.set_capture_file(&pcap); | ||
let messages = builder.build().unwrap(); | ||
assert!(!messages.is_empty()) | ||
} | ||
|
||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
pub mod capture; | ||
pub mod client_hello; | ||
pub mod handshake_message; |
Oops, something went wrong.