use rand::Rng;
#[derive(Debug, Clone)]
pub struct JarmProbeOptions {
pub hostname: String,
pub port: u16,
pub version: TlsVersion,
pub cipher_list: CipherList,
pub cipher_order: CipherOrder,
pub grease: bool,
pub alpn: AlpnMode,
pub v13_mode: V13Mode,
pub extension_order: ExtensionOrder,
}
#[derive(Debug, Clone)]
pub struct JarmProbe {
pub options: JarmProbeOptions,
packet: Vec<u8>,
}
impl JarmProbe {
pub fn build(&self) -> Vec<u8> {
self.packet.clone()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TlsVersion {
SSL30 = 0x0300,
TLS10 = 0x0301,
TLS11 = 0x0302,
TLS12 = 0x0303,
TLS13 = 0x0304,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CipherList {
All,
No13, }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CipherOrder {
Forward,
Reverse,
TopHalf,
BottomHalf,
MiddleOut,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AlpnMode {
Alpn, RareAlpn, NoSupport,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum V13Mode {
Support13, Support12Only, NoSupport,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExtensionOrder {
Forward,
Reverse,
}
pub fn get_probes(hostname: &str, port: u16) -> Vec<JarmProbe> {
vec![
build_probe(JarmProbeOptions {
hostname: hostname.to_string(),
port,
version: TlsVersion::TLS12,
cipher_list: CipherList::All,
cipher_order: CipherOrder::Forward,
grease: false,
alpn: AlpnMode::Alpn,
v13_mode: V13Mode::Support12Only,
extension_order: ExtensionOrder::Reverse,
}),
build_probe(JarmProbeOptions {
hostname: hostname.to_string(),
port,
version: TlsVersion::TLS12,
cipher_list: CipherList::All,
cipher_order: CipherOrder::Reverse,
grease: false,
alpn: AlpnMode::Alpn,
v13_mode: V13Mode::Support12Only,
extension_order: ExtensionOrder::Forward,
}),
build_probe(JarmProbeOptions {
hostname: hostname.to_string(),
port,
version: TlsVersion::TLS12,
cipher_list: CipherList::All,
cipher_order: CipherOrder::TopHalf,
grease: false,
alpn: AlpnMode::NoSupport,
v13_mode: V13Mode::NoSupport,
extension_order: ExtensionOrder::Forward,
}),
build_probe(JarmProbeOptions {
hostname: hostname.to_string(),
port,
version: TlsVersion::TLS12,
cipher_list: CipherList::All,
cipher_order: CipherOrder::BottomHalf,
grease: false,
alpn: AlpnMode::RareAlpn,
v13_mode: V13Mode::NoSupport,
extension_order: ExtensionOrder::Forward,
}),
build_probe(JarmProbeOptions {
hostname: hostname.to_string(),
port,
version: TlsVersion::TLS12,
cipher_list: CipherList::All,
cipher_order: CipherOrder::MiddleOut,
grease: true,
alpn: AlpnMode::RareAlpn,
v13_mode: V13Mode::NoSupport,
extension_order: ExtensionOrder::Reverse,
}),
build_probe(JarmProbeOptions {
hostname: hostname.to_string(),
port,
version: TlsVersion::TLS11,
cipher_list: CipherList::All,
cipher_order: CipherOrder::Forward,
grease: false,
alpn: AlpnMode::Alpn,
v13_mode: V13Mode::NoSupport,
extension_order: ExtensionOrder::Forward,
}),
build_probe(JarmProbeOptions {
hostname: hostname.to_string(),
port,
version: TlsVersion::TLS13,
cipher_list: CipherList::All,
cipher_order: CipherOrder::Forward,
grease: false,
alpn: AlpnMode::Alpn,
v13_mode: V13Mode::Support13,
extension_order: ExtensionOrder::Reverse,
}),
build_probe(JarmProbeOptions {
hostname: hostname.to_string(),
port,
version: TlsVersion::TLS13,
cipher_list: CipherList::All,
cipher_order: CipherOrder::Reverse,
grease: false,
alpn: AlpnMode::Alpn,
v13_mode: V13Mode::Support13,
extension_order: ExtensionOrder::Forward,
}),
build_probe(JarmProbeOptions {
hostname: hostname.to_string(),
port,
version: TlsVersion::TLS13,
cipher_list: CipherList::No13,
cipher_order: CipherOrder::Forward,
grease: false,
alpn: AlpnMode::Alpn,
v13_mode: V13Mode::Support13,
extension_order: ExtensionOrder::Forward,
}),
build_probe(JarmProbeOptions {
hostname: hostname.to_string(),
port,
version: TlsVersion::TLS13,
cipher_list: CipherList::All,
cipher_order: CipherOrder::MiddleOut,
grease: true,
alpn: AlpnMode::Alpn,
v13_mode: V13Mode::Support13,
extension_order: ExtensionOrder::Reverse,
}),
]
}
fn build_probe(options: JarmProbeOptions) -> JarmProbe {
let packet = build_client_hello(&options);
JarmProbe { options, packet }
}
fn build_client_hello(opts: &JarmProbeOptions) -> Vec<u8> {
let mut payload = vec![0x16]; let mut hello = Vec::new();
match opts.version {
TlsVersion::SSL30 => {
payload.extend_from_slice(&[0x03, 0x00]);
hello.extend_from_slice(&[0x03, 0x00]);
}
TlsVersion::TLS10 => {
payload.extend_from_slice(&[0x03, 0x01]);
hello.extend_from_slice(&[0x03, 0x01]);
}
TlsVersion::TLS11 => {
payload.extend_from_slice(&[0x03, 0x02]);
hello.extend_from_slice(&[0x03, 0x02]);
}
TlsVersion::TLS12 => {
payload.extend_from_slice(&[0x03, 0x03]);
hello.extend_from_slice(&[0x03, 0x03]);
}
TlsVersion::TLS13 => {
payload.extend_from_slice(&[0x03, 0x01]); hello.extend_from_slice(&[0x03, 0x03]); }
}
let random = random_bytes(32);
hello.extend_from_slice(&random);
let session_id = random_bytes(32);
hello.push(session_id.len() as u8);
hello.extend_from_slice(&session_id);
let ciphers = get_ciphers(opts);
hello.extend_from_slice(&u16_to_bytes(ciphers.len() as u16));
hello.extend_from_slice(&ciphers);
hello.extend_from_slice(&[0x01, 0x00]);
let extensions = get_extensions(opts);
hello.extend_from_slice(&extensions);
let mut handshake = vec![0x01]; handshake.push(0x00); handshake.extend_from_slice(&u16_to_bytes(hello.len() as u16));
handshake.extend_from_slice(&hello);
payload.extend_from_slice(&u16_to_bytes(handshake.len() as u16));
payload.extend_from_slice(&handshake);
payload
}
fn get_ciphers(opts: &JarmProbeOptions) -> Vec<u8> {
let mut ciphers: Vec<[u8; 2]> = match opts.cipher_list {
CipherList::All => vec![
[0x00, 0x16],
[0x00, 0x33],
[0x00, 0x67],
[0xc0, 0x9e],
[0xc0, 0xa2],
[0x00, 0x9e],
[0x00, 0x39],
[0x00, 0x6b],
[0xc0, 0x9f],
[0xc0, 0xa3],
[0x00, 0x9f],
[0x00, 0x45],
[0x00, 0xbe],
[0x00, 0x88],
[0x00, 0xc4],
[0x00, 0x9a],
[0xc0, 0x08],
[0xc0, 0x09],
[0xc0, 0x23],
[0xc0, 0xac],
[0xc0, 0xae],
[0xc0, 0x2b],
[0xc0, 0x0a],
[0xc0, 0x24],
[0xc0, 0xad],
[0xc0, 0xaf],
[0xc0, 0x2c],
[0xc0, 0x72],
[0xc0, 0x73],
[0xcc, 0xa9],
[0x13, 0x02],
[0x13, 0x01],
[0xcc, 0x14],
[0xc0, 0x07],
[0xc0, 0x12],
[0xc0, 0x13],
[0xc0, 0x27],
[0xc0, 0x2f],
[0xc0, 0x14],
[0xc0, 0x28],
[0xc0, 0x30],
[0xc0, 0x60],
[0xc0, 0x61],
[0xc0, 0x76],
[0xc0, 0x77],
[0xcc, 0xa8],
[0x13, 0x05],
[0x13, 0x04],
[0x13, 0x03],
[0xcc, 0x13],
[0xc0, 0x11],
[0x00, 0x0a],
[0x00, 0x2f],
[0x00, 0x3c],
[0xc0, 0x9c],
[0xc0, 0xa0],
[0x00, 0x9c],
[0x00, 0x35],
[0x00, 0x3d],
[0xc0, 0x9d],
[0xc0, 0xa1],
[0x00, 0x9d],
[0x00, 0x41],
[0x00, 0xba],
[0x00, 0x84],
[0x00, 0xc0],
[0x00, 0x07],
[0x00, 0x04],
[0x00, 0x05],
],
CipherList::No13 => vec![
[0x00, 0x16],
[0x00, 0x33],
[0x00, 0x67],
[0xc0, 0x9e],
[0xc0, 0xa2],
[0x00, 0x9e],
[0x00, 0x39],
[0x00, 0x6b],
[0xc0, 0x9f],
[0xc0, 0xa3],
[0x00, 0x9f],
[0x00, 0x45],
[0x00, 0xbe],
[0x00, 0x88],
[0x00, 0xc4],
[0x00, 0x9a],
[0xc0, 0x08],
[0xc0, 0x09],
[0xc0, 0x23],
[0xc0, 0xac],
[0xc0, 0xae],
[0xc0, 0x2b],
[0xc0, 0x0a],
[0xc0, 0x24],
[0xc0, 0xad],
[0xc0, 0xaf],
[0xc0, 0x2c],
[0xc0, 0x72],
[0xc0, 0x73],
[0xcc, 0xa9],
[0xcc, 0x14],
[0xc0, 0x07],
[0xc0, 0x12],
[0xc0, 0x13],
[0xc0, 0x27],
[0xc0, 0x2f],
[0xc0, 0x14],
[0xc0, 0x28],
[0xc0, 0x30],
[0xc0, 0x60],
[0xc0, 0x61],
[0xc0, 0x76],
[0xc0, 0x77],
[0xcc, 0xa8],
[0xcc, 0x13],
[0xc0, 0x11],
[0x00, 0x0a],
[0x00, 0x2f],
[0x00, 0x3c],
[0xc0, 0x9c],
[0xc0, 0xa0],
[0x00, 0x9c],
[0x00, 0x35],
[0x00, 0x3d],
[0xc0, 0x9d],
[0xc0, 0xa1],
[0x00, 0x9d],
[0x00, 0x41],
[0x00, 0xba],
[0x00, 0x84],
[0x00, 0xc0],
[0x00, 0x07],
[0x00, 0x04],
[0x00, 0x05],
],
};
ciphers = reorder_ciphers(ciphers, opts.cipher_order);
if opts.grease {
ciphers.insert(0, random_grease());
}
let mut result = Vec::new();
for cipher in ciphers {
result.extend_from_slice(&cipher);
}
result
}
fn reorder_ciphers(mut ciphers: Vec<[u8; 2]>, order: CipherOrder) -> Vec<[u8; 2]> {
match order {
CipherOrder::Forward => ciphers,
CipherOrder::Reverse => {
ciphers.reverse();
ciphers
}
CipherOrder::TopHalf => {
let len = ciphers.len();
if len % 2 == 1 {
let mid = len / 2;
let mut result = vec![ciphers[mid]];
let reversed: Vec<_> = ciphers.into_iter().rev().collect();
result.extend_from_slice(&reversed[0..(len / 2)]);
result
} else {
let reversed: Vec<_> = ciphers.into_iter().rev().collect();
reversed[0..(len / 2)].to_vec()
}
}
CipherOrder::BottomHalf => {
let len = ciphers.len();
if len % 2 == 1 {
ciphers[(len / 2) + 1..].to_vec()
} else {
ciphers[len / 2..].to_vec()
}
}
CipherOrder::MiddleOut => {
let len = ciphers.len();
let middle = len / 2;
let mut result = Vec::new();
if len % 2 == 1 {
result.push(ciphers[middle]);
for i in 1..=middle {
result.push(ciphers[middle + i]);
result.push(ciphers[middle - i]);
}
} else {
for cipher in ciphers.iter().take(middle).rev() {
result.push(*cipher);
}
for cipher in ciphers.iter().skip(middle).take(len - middle) {
result.push(*cipher);
}
}
result
}
}
}
fn get_extensions(opts: &JarmProbeOptions) -> Vec<u8> {
let mut all_extensions = Vec::new();
if opts.grease {
all_extensions.extend_from_slice(&random_grease());
all_extensions.extend_from_slice(&[0x00, 0x00]);
}
all_extensions.extend_from_slice(&ext_server_name(&opts.hostname));
all_extensions.extend_from_slice(&[0x00, 0x17, 0x00, 0x00]);
all_extensions.extend_from_slice(&[0x00, 0x01, 0x00, 0x01, 0x01]);
all_extensions.extend_from_slice(&[0xff, 0x01, 0x00, 0x01, 0x00]);
all_extensions.extend_from_slice(&[
0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x00, 0x19,
]);
all_extensions.extend_from_slice(&[0x00, 0x0b, 0x00, 0x02, 0x01, 0x00]);
all_extensions.extend_from_slice(&[0x00, 0x23, 0x00, 0x00]);
if opts.alpn != AlpnMode::NoSupport {
all_extensions.extend_from_slice(&ext_alpn(opts));
}
all_extensions.extend_from_slice(&[
0x00, 0x0d, 0x00, 0x14, 0x00, 0x12, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01, 0x05, 0x03, 0x08,
0x05, 0x05, 0x01, 0x08, 0x06, 0x06, 0x01, 0x02, 0x01,
]);
all_extensions.extend_from_slice(&ext_key_share(opts.grease));
all_extensions.extend_from_slice(&[0x00, 0x2d, 0x00, 0x02, 0x01, 0x01]);
if opts.version == TlsVersion::TLS13 || opts.v13_mode == V13Mode::Support12Only {
all_extensions.extend_from_slice(&ext_supported_versions(opts));
}
let mut result = u16_to_bytes(all_extensions.len() as u16).to_vec();
result.extend_from_slice(&all_extensions);
result
}
fn ext_server_name(name: &str) -> Vec<u8> {
let mut ext = vec![0x00, 0x00];
let mut list = Vec::new();
list.push(0x00); list.extend_from_slice(&u16_to_bytes(name.len() as u16));
list.extend_from_slice(name.as_bytes());
let mut data = Vec::new();
data.extend_from_slice(&u16_to_bytes(list.len() as u16));
data.extend_from_slice(&list);
ext.extend_from_slice(&u16_to_bytes(data.len() as u16));
ext.extend_from_slice(&data);
ext
}
fn ext_alpn(opts: &JarmProbeOptions) -> Vec<u8> {
let mut ext = vec![0x00, 0x10];
let alpn_list: Vec<&[u8]> = match opts.alpn {
AlpnMode::RareAlpn => vec![
b"\x08http/0.9",
b"\x08http/1.0",
b"\x06spdy/1",
b"\x06spdy/2",
b"\x06spdy/3",
b"\x03h2c",
b"\x02hq",
],
AlpnMode::Alpn => vec![
b"\x08http/0.9",
b"\x08http/1.0",
b"\x08http/1.1",
b"\x06spdy/1",
b"\x06spdy/2",
b"\x06spdy/3",
b"\x02h2",
b"\x03h2c",
b"\x02hq",
],
AlpnMode::NoSupport => return Vec::new(),
};
let mut ordered_list = alpn_list;
if opts.extension_order == ExtensionOrder::Reverse {
ordered_list.reverse();
}
let mut all_alpn = Vec::new();
for alpn in ordered_list {
all_alpn.extend_from_slice(alpn);
}
ext.extend_from_slice(&u16_to_bytes((all_alpn.len() + 2) as u16));
ext.extend_from_slice(&u16_to_bytes(all_alpn.len() as u16));
ext.extend_from_slice(&all_alpn);
ext
}
fn ext_key_share(grease: bool) -> Vec<u8> {
let mut ext = vec![0x00, 0x33]; let mut share_ext = Vec::new();
if grease {
share_ext.extend_from_slice(&random_grease());
share_ext.extend_from_slice(&[0x00, 0x01, 0x00]);
}
share_ext.extend_from_slice(&[0x00, 0x1d]); share_ext.extend_from_slice(&[0x00, 0x20]); share_ext.extend_from_slice(&random_bytes(32));
let first_length = share_ext.len() + 2;
let second_length = share_ext.len();
ext.extend_from_slice(&u16_to_bytes(first_length as u16));
ext.extend_from_slice(&u16_to_bytes(second_length as u16));
ext.extend_from_slice(&share_ext);
ext
}
fn ext_supported_versions(opts: &JarmProbeOptions) -> Vec<u8> {
let mut ext = vec![0x00, 0x2b];
let mut versions: Vec<[u8; 2]> = match opts.v13_mode {
V13Mode::Support12Only => vec![[0x03, 0x01], [0x03, 0x02], [0x03, 0x03]],
V13Mode::Support13 => vec![[0x03, 0x01], [0x03, 0x02], [0x03, 0x03], [0x03, 0x04]],
V13Mode::NoSupport => vec![[0x03, 0x01], [0x03, 0x02], [0x03, 0x03]],
};
if opts.extension_order == ExtensionOrder::Reverse {
versions.reverse();
}
let mut ver = Vec::new();
if opts.grease {
ver.extend_from_slice(&random_grease());
}
for v in versions {
ver.extend_from_slice(&v);
}
ext.extend_from_slice(&u16_to_bytes((ver.len() + 1) as u16));
ext.push(ver.len() as u8);
ext.extend_from_slice(&ver);
ext
}
fn random_bytes(len: usize) -> Vec<u8> {
use rand::RngCore;
let mut rng = rand::thread_rng();
let mut bytes = vec![0u8; len];
rng.fill_bytes(&mut bytes);
bytes
}
fn random_grease() -> [u8; 2] {
let mut rng = rand::thread_rng();
let val = rng.gen_range(0..16);
let byte = 0x0a + (val << 4);
[byte, byte]
}
fn u16_to_bytes(val: u16) -> [u8; 2] {
val.to_be_bytes()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_probes() {
let probes = get_probes("example.com", 443);
assert_eq!(probes.len(), 10);
assert_eq!(probes[0].options.version, TlsVersion::TLS12);
assert_eq!(probes[0].options.cipher_order, CipherOrder::Forward);
assert!(!probes[0].options.grease);
assert_eq!(probes[9].options.version, TlsVersion::TLS13);
assert_eq!(probes[9].options.cipher_order, CipherOrder::MiddleOut);
assert!(probes[9].options.grease);
}
#[test]
fn test_cipher_reordering() {
let ciphers = vec![[0x01, 0x02], [0x03, 0x04], [0x05, 0x06], [0x07, 0x08]];
let forward = reorder_ciphers(ciphers.clone(), CipherOrder::Forward);
assert_eq!(forward, ciphers);
let reverse = reorder_ciphers(ciphers.clone(), CipherOrder::Reverse);
assert_eq!(
reverse,
vec![[0x07, 0x08], [0x05, 0x06], [0x03, 0x04], [0x01, 0x02]]
);
let middle_out = reorder_ciphers(ciphers.clone(), CipherOrder::MiddleOut);
assert_eq!(
middle_out,
vec![[0x03, 0x04], [0x01, 0x02], [0x05, 0x06], [0x07, 0x08]]
);
}
#[test]
fn test_client_hello_structure() {
let opts = JarmProbeOptions {
hostname: "example.com".to_string(),
port: 443,
version: TlsVersion::TLS12,
cipher_list: CipherList::All,
cipher_order: CipherOrder::Forward,
grease: false,
alpn: AlpnMode::Alpn,
v13_mode: V13Mode::Support12Only,
extension_order: ExtensionOrder::Forward,
};
let packet = build_client_hello(&opts);
assert_eq!(packet[0], 0x16);
assert_eq!(packet[1], 0x03);
assert_eq!(packet[2], 0x03);
assert!(packet.len() > 100);
}
}