use anyhow::{bail, Result};
pub const KEY_METHOD_2: u8 = 2;
const PRE_MASTER_LEN: usize = 48;
const RANDOM_LEN: usize = 32;
const MAX_STRING_LEN: usize = 4096;
#[derive(Debug, Clone)]
pub struct ClientKeyMethod2 {
pub pre_master: Vec<u8>,
pub random1: Vec<u8>,
pub random2: Vec<u8>,
pub options: String,
pub username: String,
pub password: String,
pub peer_info: String,
}
fn read_string(buf: &[u8], pos: &mut usize) -> Result<Option<String>> {
if buf.len() < *pos + 2 {
return Ok(None);
}
let len = u16::from_be_bytes([buf[*pos], buf[*pos + 1]]) as usize;
if len > MAX_STRING_LEN {
bail!("control-channel string length {} is implausible", len);
}
if buf.len() < *pos + 2 + len {
return Ok(None);
}
let start = *pos + 2;
let bytes = &buf[start..start + len];
*pos = start + len;
let bytes = match bytes.last() {
Some(0) => &bytes[..bytes.len() - 1],
_ => bytes,
};
Ok(Some(String::from_utf8_lossy(bytes).into_owned()))
}
fn write_string(out: &mut Vec<u8>, s: &str) {
let len = s.len() + 1;
out.extend_from_slice(&(len as u16).to_be_bytes());
out.extend_from_slice(s.as_bytes());
out.push(0);
}
fn write_empty_string(out: &mut Vec<u8>) {
out.extend_from_slice(&0u16.to_be_bytes());
}
pub fn parse_client_key_method_2(buf: &[u8]) -> Result<Option<(ClientKeyMethod2, usize)>> {
let fixed = 4 + 1 + PRE_MASTER_LEN + RANDOM_LEN + RANDOM_LEN;
if buf.len() < fixed {
return Ok(None);
}
let leading = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]);
if leading != 0 {
bail!(
"control channel does not start with a key-method-2 message (leading u32 is {:#010x})",
leading
);
}
let key_method = buf[4] & 0x07;
if key_method != KEY_METHOD_2 {
bail!(
"unsupported OpenVPN key method {} (only key method 2 exists in any supported release)",
key_method
);
}
let mut pos = 5;
let pre_master = buf[pos..pos + PRE_MASTER_LEN].to_vec();
pos += PRE_MASTER_LEN;
let random1 = buf[pos..pos + RANDOM_LEN].to_vec();
pos += RANDOM_LEN;
let random2 = buf[pos..pos + RANDOM_LEN].to_vec();
pos += RANDOM_LEN;
let options = match read_string(buf, &mut pos)? {
Some(s) => s,
None => return Ok(None),
};
let username = match read_string(buf, &mut pos)? {
Some(s) => s,
None => return Ok(None),
};
let password = match read_string(buf, &mut pos)? {
Some(s) => s,
None => return Ok(None),
};
let peer_info = match read_string(buf, &mut pos)? {
Some(s) => s,
None => return Ok(None),
};
Ok(Some((
ClientKeyMethod2 {
pre_master,
random1,
random2,
options,
username,
password,
peer_info,
},
pos,
)))
}
pub fn build_server_key_method_2(random1: &[u8; 32], random2: &[u8; 32], options: &str) -> Vec<u8> {
let mut out = Vec::with_capacity(128 + options.len());
out.extend_from_slice(&0u32.to_be_bytes());
out.push(KEY_METHOD_2);
out.extend_from_slice(random1);
out.extend_from_slice(random2);
write_string(&mut out, options);
write_empty_string(&mut out); write_empty_string(&mut out); write_empty_string(&mut out); out
}
pub fn server_options_from_client(client_options: &str) -> String {
const FALLBACK: &str = "V4,dev-type tun,link-mtu 1549,tun-mtu 1500,proto UDPv4,\
cipher AES-256-GCM,auth SHA1,keysize 256,key-method 2,tls-server";
if client_options.is_empty() {
return FALLBACK.to_string();
}
match client_options.strip_suffix("tls-client") {
Some(head) => format!("{}tls-server", head),
None => client_options.to_string(),
}
}
pub fn peer_info_map(peer_info: &str) -> serde_json::Map<String, serde_json::Value> {
let mut map = serde_json::Map::new();
for line in peer_info.split(['\n', '\0']) {
let line = line.trim();
if line.is_empty() {
continue;
}
match line.split_once('=') {
Some((k, v)) => {
map.insert(k.to_string(), serde_json::Value::String(v.to_string()));
}
None => {
map.insert(line.to_string(), serde_json::Value::Bool(true));
}
}
}
map
}