Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 117 additions & 1 deletion nmrs/src/api/builders/wifi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ pub fn build_wifi_connection(
models::WifiSecurity::Open => builder.open(),
models::WifiSecurity::WpaPsk { psk } => builder.wpa_psk(psk),
models::WifiSecurity::WpaEap { opts } => builder.wpa_eap(opts.clone()),
models::WifiSecurity::Wpa3Eap192bit { opts } => builder.wpa3_eap_192_bit(opts.clone()),
};

builder.build()
Expand Down Expand Up @@ -190,9 +191,15 @@ mod tests {
anonymous_identity: Some("anonymous@example.com".into()),
domain_suffix_match: Some("example.com".into()),
ca_cert_path: None,
ca_cert_blob: None,
system_ca_certs: true,
method: EapMethod::Peap,
phase2: Phase2::Mschapv2,
private_key_path: None,
private_key_blob: None,
private_key_password: None,
client_cert_path: None,
client_cert_blob: None,
};
let conn = build_wifi_connection(
"enterprise",
Expand Down Expand Up @@ -227,9 +234,15 @@ mod tests {
anonymous_identity: None,
domain_suffix_match: None,
ca_cert_path: Some("file:///etc/ssl/certs/ca.pem".into()),
ca_cert_blob: None,
system_ca_certs: false,
method: EapMethod::Ttls,
phase2: Phase2::Pap,
private_key_path: None,
private_key_blob: None,
private_key_password: None,
client_cert_path: None,
client_cert_blob: None,
};
let conn = build_wifi_connection(
"eduroam",
Expand All @@ -241,7 +254,110 @@ mod tests {
assert_eq!(e1x.get("phase2-auth"), Some(&Value::from("pap")));
assert_eq!(
e1x.get("ca-cert"),
Some(&Value::from("file:///etc/ssl/certs/ca.pem".to_string()))
Some(&Value::from(b"file:///etc/ssl/certs/ca.pem\0".to_vec()))
);
// system-ca-certs should NOT be present when false
assert!(e1x.get("system-ca-certs").is_none());
}

#[test]
fn builds_eap_tls_connection() {
let eap_opts = EapOptions {
identity: "student@uni.edu".into(),
password: String::new(),
anonymous_identity: None,
domain_suffix_match: None,
ca_cert_path: Some("file:///etc/ssl/certs/ca.pem".into()),
ca_cert_blob: None,
system_ca_certs: false,
method: EapMethod::Tls,
phase2: Phase2::Mschapv2,
private_key_path: Some("file:///etc/ssl/private/client.key".into()),
private_key_blob: None,
private_key_password: Some("password".into()),
client_cert_path: Some("file:///etc/ssl/certs/client.crt".into()),
client_cert_blob: None,
};
let conn = build_wifi_connection(
"eduroam",
&WifiSecurity::WpaEap { opts: eap_opts },
&default_opts(),
);

let security = conn.get("802-11-wireless-security").unwrap();
assert_eq!(security.get("key-mgmt"), Some(&Value::from("wpa-eap")));

let e1x = conn.get("802-1x").unwrap();
assert_eq!(e1x.get("phase2-auth"), None);
assert_eq!(
e1x.get("private-key"),
Some(&Value::from(
b"file:///etc/ssl/private/client.key\0".to_vec()
))
);
assert_eq!(
e1x.get("private-key-password"),
Some(&Value::from("password"))
);
assert_eq!(
e1x.get("client-cert"),
Some(&Value::from(b"file:///etc/ssl/certs/client.crt\0".to_vec()))
);
assert_eq!(
e1x.get("ca-cert"),
Some(&Value::from(b"file:///etc/ssl/certs/ca.pem\0".to_vec()))
);
// system-ca-certs should NOT be present when false
assert!(e1x.get("system-ca-certs").is_none());
}

#[test]
fn builds_eap_192bit_connection() {
let eap_opts = EapOptions {
identity: "student@uni.edu".into(),
password: String::new(),
anonymous_identity: None,
domain_suffix_match: None,
ca_cert_path: None,
ca_cert_blob: Some(b"ca_cert_blob".into()),
system_ca_certs: false,
method: EapMethod::Tls,
phase2: Phase2::Mschapv2,
private_key_path: None,
private_key_blob: Some(b"private_key_blob".into()),
private_key_password: Some("password".into()),
client_cert_path: None,
client_cert_blob: Some(b"client_cert_blob".into()),
};
let conn = build_wifi_connection(
"eduroam",
&WifiSecurity::Wpa3Eap192bit { opts: eap_opts },
&default_opts(),
);

let security = conn.get("802-11-wireless-security").unwrap();
assert_eq!(
security.get("key-mgmt"),
Some(&Value::from("wpa-eap-suite-b-192"))
);

let e1x = conn.get("802-1x").unwrap();
assert_eq!(e1x.get("phase2-auth"), None);
assert_eq!(
e1x.get("private-key"),
Some(&Value::from(b"private_key_blob".to_vec()))
);
assert_eq!(
e1x.get("private-key-password"),
Some(&Value::from("password"))
);
assert_eq!(
e1x.get("client-cert"),
Some(&Value::from(b"client_cert_blob".to_vec()))
);
assert_eq!(
e1x.get("ca-cert"),
Some(&Value::from(b"ca_cert_blob".to_vec()))
);
// system-ca-certs should NOT be present when false
assert!(e1x.get("system-ca-certs").is_none());
Expand Down
94 changes: 80 additions & 14 deletions nmrs/src/api/builders/wifi_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,24 @@ impl WifiConnectionBuilder {

/// Configures WPA-EAP (Enterprise) security with 802.1X authentication.
///
/// Supports PEAP and TTLS methods with various inner authentication protocols.
/// Supports PEAP, TTLS, and TLS methods with various inner authentication protocols.
#[must_use]
pub fn wpa_eap(mut self, opts: models::EapOptions) -> Self {
pub fn wpa_eap(self, opts: models::EapOptions) -> Self {
self.wpa_eap_shared("wpa-eap", opts)
}

/// Configures WPA3-EAP (Enterprise) with 192bit security with 802.1X authentication.
///
/// Supports only EAP-TLS.
#[must_use]
pub fn wpa3_eap_192_bit(self, opts: models::EapOptions) -> Self {
self.wpa_eap_shared("wpa-eap-suite-b-192", opts)
}

#[must_use]
fn wpa_eap_shared(mut self, key_mgmt: &'static str, opts: models::EapOptions) -> Self {
let mut security = HashMap::new();
security.insert("key-mgmt", Value::from("wpa-eap"));
security.insert("key-mgmt", Value::from(key_mgmt));
security.insert("auth-alg", Value::from("open"));

self.inner = self
Expand All @@ -190,26 +203,49 @@ impl WifiConnectionBuilder {
let eap_str = match opts.method {
EapMethod::Peap => "peap",
EapMethod::Ttls => "ttls",
EapMethod::Tls => "tls",
};
e1x.insert("eap", Self::string_array(&[eap_str]));
e1x.insert("identity", Value::from(opts.identity));
e1x.insert("password", Value::from(opts.password));

if let Some(ai) = opts.anonymous_identity {
e1x.insert("anonymous-identity", Value::from(ai));
match opts.method {
EapMethod::Peap | EapMethod::Ttls => {
e1x.insert("password", Value::from(opts.password));

if let Some(ai) = opts.anonymous_identity {
e1x.insert("anonymous-identity", Value::from(ai));
}

let p2 = match opts.phase2 {
models::Phase2::Mschapv2 => "mschapv2",
models::Phase2::Pap => "pap",
};
e1x.insert("phase2-auth", Value::from(p2));
}
EapMethod::Tls => {
if let Some(cert) =
Self::path_or_blob("private_key", opts.private_key_path, opts.private_key_blob)
{
e1x.insert("private-key", cert);
}

if let Some(password) = opts.private_key_password {
e1x.insert("private-key-password", Value::from(password));
}

if let Some(cert) =
Self::path_or_blob("client_cert", opts.client_cert_path, opts.client_cert_blob)
{
e1x.insert("client-cert", cert);
}
}
}

let p2 = match opts.phase2 {
models::Phase2::Mschapv2 => "mschapv2",
models::Phase2::Pap => "pap",
};
e1x.insert("phase2-auth", Value::from(p2));

if opts.system_ca_certs {
e1x.insert("system-ca-certs", Value::from(true));
}
if let Some(cert) = opts.ca_cert_path {
e1x.insert("ca-cert", Value::from(cert));
if let Some(cert) = Self::path_or_blob("ca_cert", opts.ca_cert_path, opts.ca_cert_blob) {
e1x.insert("ca-cert", cert);
}
if let Some(dom) = opts.domain_suffix_match {
e1x.insert("domain-suffix-match", Value::from(dom));
Expand Down Expand Up @@ -372,6 +408,30 @@ impl WifiConnectionBuilder {
let vals: Vec<String> = xs.iter().map(|s| s.to_string()).collect();
Value::from(vals)
}

fn path_or_blob(
attribute: &str,
path: Option<String>,
blob: Option<Vec<u8>>,
) -> Option<Value<'static>> {
match (path, blob) {
(None, None) => None,
(Some(path), None) => Some(Self::path(path)),
(None, Some(blob)) => Some(Self::blob(blob)),
(Some(_), Some(_)) => {
panic!("Cannot specify both {attribute}_path and {attribute}_blob.");
}
Comment thread
cachebag marked this conversation as resolved.
}
}

fn path(mut value: String) -> Value<'static> {
value.push('\0');
Value::from(value.into_bytes())
}

Comment thread
cachebag marked this conversation as resolved.
fn blob(value: Vec<u8>) -> Value<'static> {
Value::from(value)
}
}

#[cfg(test)]
Expand Down Expand Up @@ -439,9 +499,15 @@ mod tests {
anonymous_identity: Some("anon@example.com".into()),
domain_suffix_match: Some("example.com".into()),
ca_cert_path: None,
ca_cert_blob: None,
system_ca_certs: true,
method: EapMethod::Peap,
phase2: Phase2::Mschapv2,
private_key_path: None,
private_key_blob: None,
private_key_password: None,
client_cert_path: None,
client_cert_blob: None,
};

let settings = WifiConnectionBuilder::new("Enterprise")
Expand Down
Loading