Skip to content

Commit e247cb9

Browse files
committed
feat(#298): add openvpn tls hardening options
1 parent 8f9cbe7 commit e247cb9

4 files changed

Lines changed: 400 additions & 2 deletions

File tree

nmrs/src/api/builders/openvpn_builder.rs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,16 @@ pub struct OpenVpnBuilder {
6060
password: Option<String>,
6161
compression: Option<OpenVpnCompression>,
6262
proxy: Option<OpenVpnProxy>,
63+
tls_auth_key: Option<String>,
64+
tls_auth_direction: Option<u8>,
65+
tls_crypt: Option<String>,
66+
tls_crypt_v2: Option<String>,
67+
tls_version_min: Option<String>,
68+
tls_version_max: Option<String>,
69+
tls_cipher: Option<String>,
70+
remote_cert_tls: Option<String>,
71+
verify_x509_name: Option<(String, String)>,
72+
crl_verify: Option<String>,
6373
}
6474

6575
impl OpenVpnBuilder {
@@ -85,6 +95,16 @@ impl OpenVpnBuilder {
8595
password: None,
8696
compression: None,
8797
proxy: None,
98+
tls_auth_key: None,
99+
tls_auth_direction: None,
100+
tls_crypt: None,
101+
tls_crypt_v2: None,
102+
tls_version_min: None,
103+
tls_version_max: None,
104+
tls_cipher: None,
105+
remote_cert_tls: None,
106+
verify_x509_name: None,
107+
crl_verify: None,
88108
}
89109
}
90110

@@ -212,6 +232,74 @@ impl OpenVpnBuilder {
212232
self
213233
}
214234

235+
/// Sets the TLS authentication key path and optional direction.
236+
#[must_use]
237+
pub fn tls_auth(mut self, key_path: impl Into<String>, direction: Option<u8>) -> Self {
238+
self.tls_auth_key = Some(key_path.into());
239+
self.tls_auth_direction = direction;
240+
self
241+
}
242+
243+
/// Sets the TLS-Crypt key path.
244+
#[must_use]
245+
pub fn tls_crypt(mut self, key_path: impl Into<String>) -> Self {
246+
self.tls_crypt = Some(key_path.into());
247+
self
248+
}
249+
250+
/// Sets the TLS-Crypt-v2 key path.
251+
#[must_use]
252+
pub fn tls_crypt_v2(mut self, key_path: impl Into<String>) -> Self {
253+
self.tls_crypt_v2 = Some(key_path.into());
254+
self
255+
}
256+
257+
/// Sets the minimum TLS protocol version.
258+
#[must_use]
259+
pub fn tls_version_min(mut self, version: impl Into<String>) -> Self {
260+
self.tls_version_min = Some(version.into());
261+
self
262+
}
263+
264+
/// Sets the maximum TLS protocol version.
265+
#[must_use]
266+
pub fn tls_version_max(mut self, version: impl Into<String>) -> Self {
267+
self.tls_version_max = Some(version.into());
268+
self
269+
}
270+
271+
/// Sets the control channel TLS cipher suites.
272+
#[must_use]
273+
pub fn tls_cipher(mut self, cipher: impl Into<String>) -> Self {
274+
self.tls_cipher = Some(cipher.into());
275+
self
276+
}
277+
278+
/// Requires the remote certificate to be of a specific type.
279+
#[must_use]
280+
pub fn remote_cert_tls(mut self, cert_type: impl Into<String>) -> Self {
281+
self.remote_cert_tls = Some(cert_type.into());
282+
self
283+
}
284+
285+
/// Sets X.509 name verification for the remote certificate.
286+
#[must_use]
287+
pub fn verify_x509_name(
288+
mut self,
289+
name: impl Into<String>,
290+
name_type: impl Into<String>,
291+
) -> Self {
292+
self.verify_x509_name = Some((name.into(), name_type.into()));
293+
self
294+
}
295+
296+
/// Sets the path to a Certificate Revocation List.
297+
#[must_use]
298+
pub fn crl_verify(mut self, path: impl Into<String>) -> Self {
299+
self.crl_verify = Some(path.into());
300+
self
301+
}
302+
215303
/// Builds and validates the `OpenVpnConfig`.
216304
///
217305
/// # Errors
@@ -304,6 +392,16 @@ impl OpenVpnBuilder {
304392
password: self.password,
305393
compression: self.compression,
306394
proxy: self.proxy,
395+
tls_auth_key: self.tls_auth_key,
396+
tls_auth_direction: self.tls_auth_direction,
397+
tls_crypt: self.tls_crypt,
398+
tls_crypt_v2: self.tls_crypt_v2,
399+
tls_version_min: self.tls_version_min,
400+
tls_version_max: self.tls_version_max,
401+
tls_cipher: self.tls_cipher,
402+
remote_cert_tls: self.remote_cert_tls,
403+
verify_x509_name: self.verify_x509_name,
404+
crl_verify: self.crl_verify,
307405
})
308406
}
309407
}

nmrs/src/api/builders/vpn.rs

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,39 @@ pub fn build_openvpn_connection(
224224
}
225225
}
226226

227+
// TLS hardening options
228+
if let Some(ref key) = config.tls_auth_key {
229+
vpn_data.push(("tls-auth".into(), key.clone()));
230+
if let Some(dir) = config.tls_auth_direction {
231+
vpn_data.push(("ta-dir".into(), dir.to_string()));
232+
}
233+
}
234+
if let Some(ref key) = config.tls_crypt {
235+
vpn_data.push(("tls-crypt".into(), key.clone()));
236+
}
237+
if let Some(ref key) = config.tls_crypt_v2 {
238+
vpn_data.push(("tls-crypt-v2".into(), key.clone()));
239+
}
240+
if let Some(ref ver) = config.tls_version_min {
241+
vpn_data.push(("tls-version-min".into(), ver.clone()));
242+
}
243+
if let Some(ref ver) = config.tls_version_max {
244+
vpn_data.push(("tls-version-max".into(), ver.clone()));
245+
}
246+
if let Some(ref cipher) = config.tls_cipher {
247+
vpn_data.push(("tls-cipher".into(), cipher.clone()));
248+
}
249+
if let Some(ref cert_type) = config.remote_cert_tls {
250+
vpn_data.push(("remote-cert-tls".into(), cert_type.clone()));
251+
}
252+
if let Some((ref name, ref name_type)) = config.verify_x509_name {
253+
vpn_data.push(("verify-x509-name".into(), name.clone()));
254+
vpn_data.push(("verify-x509-type".into(), name_type.clone()));
255+
}
256+
if let Some(ref path) = config.crl_verify {
257+
vpn_data.push(("crl-verify".into(), path.clone()));
258+
}
259+
227260
if let Some(ref proxy) = config.proxy {
228261
match proxy {
229262
OpenVpnProxy::Http {
@@ -995,6 +1028,19 @@ mod tests {
9951028
);
9961029
}
9971030

1031+
fn get_vpn_data_value(
1032+
settings: &HashMap<&str, HashMap<&str, Value>>,
1033+
key: &str,
1034+
) -> Option<String> {
1035+
let vpn = settings.get("vpn")?;
1036+
let data = vpn.get("data")?;
1037+
if let Value::Dict(dict) = data {
1038+
let val: String = dict.get::<Value, String>(&Value::from(key)).ok()??;
1039+
return Some(val);
1040+
}
1041+
None
1042+
}
1043+
9981044
#[test]
9991045
fn openvpn_vpn_secrets_has_dict_signature() {
10001046
let config = create_openvpn_config()
@@ -1011,4 +1057,141 @@ mod tests {
10111057
"vpn.secrets must be a{{ss}} for NetworkManager"
10121058
);
10131059
}
1060+
1061+
#[test]
1062+
fn openvpn_tls_auth_key_and_direction() {
1063+
let config = create_openvpn_config().with_tls_auth("/etc/openvpn/ta.key", Some(1));
1064+
let opts = create_test_options();
1065+
let settings = build_openvpn_connection(&config, &opts).unwrap();
1066+
assert_eq!(
1067+
get_vpn_data_value(&settings, "tls-auth").as_deref(),
1068+
Some("/etc/openvpn/ta.key")
1069+
);
1070+
assert_eq!(
1071+
get_vpn_data_value(&settings, "ta-dir").as_deref(),
1072+
Some("1")
1073+
);
1074+
}
1075+
1076+
#[test]
1077+
fn openvpn_tls_auth_key_without_direction() {
1078+
let config = create_openvpn_config().with_tls_auth("/etc/openvpn/ta.key", None);
1079+
let opts = create_test_options();
1080+
let settings = build_openvpn_connection(&config, &opts).unwrap();
1081+
assert_eq!(
1082+
get_vpn_data_value(&settings, "tls-auth").as_deref(),
1083+
Some("/etc/openvpn/ta.key")
1084+
);
1085+
assert!(get_vpn_data_value(&settings, "ta-dir").is_none());
1086+
}
1087+
1088+
#[test]
1089+
fn openvpn_tls_crypt() {
1090+
let config = create_openvpn_config().with_tls_crypt("/etc/openvpn/tls-crypt.key");
1091+
let opts = create_test_options();
1092+
let settings = build_openvpn_connection(&config, &opts).unwrap();
1093+
assert_eq!(
1094+
get_vpn_data_value(&settings, "tls-crypt").as_deref(),
1095+
Some("/etc/openvpn/tls-crypt.key")
1096+
);
1097+
}
1098+
1099+
#[test]
1100+
fn openvpn_tls_crypt_v2() {
1101+
let config = create_openvpn_config().with_tls_crypt_v2("/etc/openvpn/tls-crypt-v2.key");
1102+
let opts = create_test_options();
1103+
let settings = build_openvpn_connection(&config, &opts).unwrap();
1104+
assert_eq!(
1105+
get_vpn_data_value(&settings, "tls-crypt-v2").as_deref(),
1106+
Some("/etc/openvpn/tls-crypt-v2.key")
1107+
);
1108+
}
1109+
1110+
#[test]
1111+
fn openvpn_tls_version_min() {
1112+
let config = create_openvpn_config().with_tls_version_min("1.2");
1113+
let opts = create_test_options();
1114+
let settings = build_openvpn_connection(&config, &opts).unwrap();
1115+
assert_eq!(
1116+
get_vpn_data_value(&settings, "tls-version-min").as_deref(),
1117+
Some("1.2")
1118+
);
1119+
}
1120+
1121+
#[test]
1122+
fn openvpn_tls_version_max() {
1123+
let config = create_openvpn_config().with_tls_version_max("1.3");
1124+
let opts = create_test_options();
1125+
let settings = build_openvpn_connection(&config, &opts).unwrap();
1126+
assert_eq!(
1127+
get_vpn_data_value(&settings, "tls-version-max").as_deref(),
1128+
Some("1.3")
1129+
);
1130+
}
1131+
1132+
#[test]
1133+
fn openvpn_tls_cipher() {
1134+
let config =
1135+
create_openvpn_config().with_tls_cipher("TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384");
1136+
let opts = create_test_options();
1137+
let settings = build_openvpn_connection(&config, &opts).unwrap();
1138+
assert_eq!(
1139+
get_vpn_data_value(&settings, "tls-cipher").as_deref(),
1140+
Some("TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384")
1141+
);
1142+
}
1143+
1144+
#[test]
1145+
fn openvpn_remote_cert_tls() {
1146+
let config = create_openvpn_config().with_remote_cert_tls("server");
1147+
let opts = create_test_options();
1148+
let settings = build_openvpn_connection(&config, &opts).unwrap();
1149+
assert_eq!(
1150+
get_vpn_data_value(&settings, "remote-cert-tls").as_deref(),
1151+
Some("server")
1152+
);
1153+
}
1154+
1155+
#[test]
1156+
fn openvpn_verify_x509_name() {
1157+
let config = create_openvpn_config().with_verify_x509_name("vpn.example.com", "name");
1158+
let opts = create_test_options();
1159+
let settings = build_openvpn_connection(&config, &opts).unwrap();
1160+
assert_eq!(
1161+
get_vpn_data_value(&settings, "verify-x509-name").as_deref(),
1162+
Some("vpn.example.com")
1163+
);
1164+
assert_eq!(
1165+
get_vpn_data_value(&settings, "verify-x509-type").as_deref(),
1166+
Some("name")
1167+
);
1168+
}
1169+
1170+
#[test]
1171+
fn openvpn_crl_verify() {
1172+
let config = create_openvpn_config().with_crl_verify("/etc/openvpn/crl.pem");
1173+
let opts = create_test_options();
1174+
let settings = build_openvpn_connection(&config, &opts).unwrap();
1175+
assert_eq!(
1176+
get_vpn_data_value(&settings, "crl-verify").as_deref(),
1177+
Some("/etc/openvpn/crl.pem")
1178+
);
1179+
}
1180+
1181+
#[test]
1182+
fn openvpn_tls_options_absent_by_default() {
1183+
let config = create_openvpn_config();
1184+
let opts = create_test_options();
1185+
let settings = build_openvpn_connection(&config, &opts).unwrap();
1186+
assert!(get_vpn_data_value(&settings, "tls-auth").is_none());
1187+
assert!(get_vpn_data_value(&settings, "ta-dir").is_none());
1188+
assert!(get_vpn_data_value(&settings, "tls-crypt").is_none());
1189+
assert!(get_vpn_data_value(&settings, "tls-crypt-v2").is_none());
1190+
assert!(get_vpn_data_value(&settings, "tls-version-min").is_none());
1191+
assert!(get_vpn_data_value(&settings, "tls-version-max").is_none());
1192+
assert!(get_vpn_data_value(&settings, "tls-cipher").is_none());
1193+
assert!(get_vpn_data_value(&settings, "remote-cert-tls").is_none());
1194+
assert!(get_vpn_data_value(&settings, "verify-x509-name").is_none());
1195+
assert!(get_vpn_data_value(&settings, "crl-verify").is_none());
1196+
}
10141197
}

0 commit comments

Comments
 (0)