From b81068a3569473f9be39fdfd4356d61042282350 Mon Sep 17 00:00:00 2001 From: Jorik Cronenberg Date: Mon, 9 Sep 2024 15:43:27 +0200 Subject: [PATCH] Implement 8021x (EAP) in network settings --- rust/agama-lib/share/profile.schema.json | 75 +++++ rust/agama-lib/src/network/settings.rs | 32 +++ rust/agama-server/src/network/error.rs | 4 + rust/agama-server/src/network/model.rs | 196 ++++++++++++- rust/agama-server/src/network/nm/dbus.rs | 341 +++++++++++++++++++++++ 5 files changed, 647 insertions(+), 1 deletion(-) diff --git a/rust/agama-lib/share/profile.schema.json b/rust/agama-lib/share/profile.schema.json index 25fce02c4d..3cf99b7cb2 100644 --- a/rust/agama-lib/share/profile.schema.json +++ b/rust/agama-lib/share/profile.schema.json @@ -231,6 +231,81 @@ } } } + }, + "ieee_8021x": { + "type": "object", + "properties": { + "eap": { + "type": "array", + "items": [ + { + "type": "string", + "enum": [ + "leap", + "md5", + "tls", + "peap", + "ttls", + "pwd", + "fast" + ] + } + ] + }, + "phase2_auth": { + "type": "string", + "enum": [ + "pap", + "chap", + "mschap", + "mschapv2", + "gtc", + "otp", + "md5", + "tls" + ] + }, + "identity": { + "type": "string" + }, + "password": { + "type": "string" + }, + "ca_cert": { + "title": "Path to ca_cert", + "type": "string" + }, + "ca_cert_password": { + "type": "string" + }, + "client_cert": { + "title": "Path to client_cert", + "type": "string" + }, + "client_cert_password": { + "type": "string" + }, + "private_key": { + "title": "Path to private_key", + "type": "string" + }, + "private_key_password": { + "type": "string" + }, + "anonymous_identity": { + "type": "string" + }, + "peap_version": { + "type": "string", + "enum": [ + "0", + "1" + ] + }, + "peap_label": { + "type": "boolean" + } + } } } } diff --git a/rust/agama-lib/src/network/settings.rs b/rust/agama-lib/src/network/settings.rs index 618447dde0..e1ffcf5d30 100644 --- a/rust/agama-lib/src/network/settings.rs +++ b/rust/agama-lib/src/network/settings.rs @@ -63,6 +63,36 @@ impl Default for BondSettings { } } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct IEEE8021XSettings { + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub eap: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub phase2_auth: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub identity: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub password: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub ca_cert: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub ca_cert_password: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub client_cert: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub client_cert_password: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub private_key: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub private_key_password: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub anonymous_identity: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub peap_version: Option, + #[serde(skip_serializing_if = "std::ops::Not::not")] + pub peap_label: bool, +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct NetworkDevice { pub id: String, @@ -106,6 +136,8 @@ pub struct NetworkConnection { pub status: Option, #[serde(skip_serializing_if = "is_zero", default)] pub mtu: u32, + #[serde(skip_serializing_if = "Option::is_none")] + pub ieee_8021x: Option, } fn is_zero(u: &u32) -> bool { diff --git a/rust/agama-server/src/network/error.rs b/rust/agama-server/src/network/error.rs index 348f0494f6..a3f349582d 100644 --- a/rust/agama-server/src/network/error.rs +++ b/rust/agama-server/src/network/error.rs @@ -36,6 +36,10 @@ pub enum NetworkStateError { InvalidWEPAuthAlg(String), #[error("Invalid WEP key type: '{0}'")] InvalidWEPKeyType(u32), + #[error("Invalid EAP method: '{0}'")] + InvalidEAPMethod(String), + #[error("Invalid phase2 authentication method: '{0}'")] + InvalidPhase2AuthMethod(String), } impl From for zbus::fdo::Error { diff --git a/rust/agama-server/src/network/model.rs b/rust/agama-server/src/network/model.rs index bbe2f8668d..4c3dd0b043 100644 --- a/rust/agama-server/src/network/model.rs +++ b/rust/agama-server/src/network/model.rs @@ -3,7 +3,9 @@ //! * This module contains the types that represent the network concepts. They are supposed to be //! agnostic from the real network service (e.g., NetworkManager). use crate::network::error::NetworkStateError; -use agama_lib::network::settings::{BondSettings, NetworkConnection, WirelessSettings}; +use agama_lib::network::settings::{ + BondSettings, IEEE8021XSettings, NetworkConnection, WirelessSettings, +}; use agama_lib::network::types::{BondMode, DeviceState, DeviceType, Status, SSID}; use cidr::IpInet; use serde::{Deserialize, Serialize}; @@ -490,6 +492,7 @@ pub struct Connection { pub port_config: PortConfig, pub match_config: MatchConfig, pub config: ConnectionConfig, + pub ieee_8021x_config: Option, } impl Connection { @@ -560,6 +563,7 @@ impl Default for Connection { port_config: Default::default(), match_config: Default::default(), config: Default::default(), + ieee_8021x_config: Default::default(), } } } @@ -599,6 +603,10 @@ impl TryFrom for Connection { connection.config = config.into(); } + if let Some(ieee_8021x_config) = conn.ieee_8021x { + connection.ieee_8021x_config = Some(IEEE8021XConfig::try_from(ieee_8021x_config)?); + } + connection.ip_config.addresses = conn.addresses; connection.ip_config.nameservers = conn.nameservers; connection.ip_config.dns_searchlist = conn.dns_searchlist; @@ -629,6 +637,9 @@ impl TryFrom for NetworkConnection { let interface = conn.interface; let status = Some(conn.status); let mtu = conn.mtu; + let ieee_8021x: Option = conn + .ieee_8021x_config + .and_then(|x| IEEE8021XSettings::try_from(x).ok()); let mut connection = NetworkConnection { id, @@ -644,6 +655,7 @@ impl TryFrom for NetworkConnection { interface, addresses, mtu, + ieee_8021x, ..Default::default() }; @@ -1353,3 +1365,185 @@ pub enum NetworkChange { /// device gets renamed. DeviceUpdated(String, Device), } + +#[derive(Default, Debug, PartialEq, Clone, Serialize)] +pub struct IEEE8021XConfig { + pub eap: Vec, + pub phase2_auth: Option, + pub identity: Option, + pub password: Option, + pub ca_cert: Option, + pub ca_cert_password: Option, + pub client_cert: Option, + pub client_cert_password: Option, + pub private_key: Option, + pub private_key_password: Option, + pub anonymous_identity: Option, + pub peap_version: Option, + pub peap_label: bool, +} + +impl TryFrom for IEEE8021XConfig { + type Error = NetworkStateError; + + fn try_from(value: IEEE8021XSettings) -> Result { + let eap = value + .eap + .iter() + .map(|x| { + EAPMethod::from_str(x) + .map_err(|_| NetworkStateError::InvalidEAPMethod(x.to_string())) + }) + .collect::, NetworkStateError>>()?; + let phase2_auth = + if let Some(phase2_auth) = &value.phase2_auth { + Some(Phase2AuthMethod::from_str(phase2_auth).map_err(|_| { + NetworkStateError::InvalidPhase2AuthMethod(phase2_auth.to_string()) + })?) + } else { + None + }; + + Ok(IEEE8021XConfig { + eap, + phase2_auth, + identity: value.identity, + password: value.password, + ca_cert: value.ca_cert, + ca_cert_password: value.ca_cert_password, + client_cert: value.client_cert, + client_cert_password: value.client_cert_password, + private_key: value.private_key, + private_key_password: value.private_key_password, + anonymous_identity: value.anonymous_identity, + peap_version: value.peap_version, + peap_label: value.peap_label, + }) + } +} + +impl TryFrom for IEEE8021XSettings { + type Error = NetworkStateError; + + fn try_from(value: IEEE8021XConfig) -> Result { + let eap = value + .eap + .iter() + .map(|x| x.to_string()) + .collect::>(); + let phase2_auth = value.phase2_auth.map(|phase2_auth| phase2_auth.to_string()); + + Ok(IEEE8021XSettings { + eap, + phase2_auth, + identity: value.identity, + password: value.password, + ca_cert: value.ca_cert, + ca_cert_password: value.ca_cert_password, + client_cert: value.client_cert, + client_cert_password: value.client_cert_password, + private_key: value.private_key, + private_key_password: value.private_key_password, + anonymous_identity: value.anonymous_identity, + peap_version: value.peap_version, + peap_label: value.peap_label, + }) + } +} + +#[derive(Debug, Error)] +#[error("Invalid eap method: {0}")] +pub struct InvalidEAPMethod(String); + +#[derive(Debug, PartialEq, Clone, Serialize)] +pub enum EAPMethod { + LEAP, + MD5, + TLS, + PEAP, + TTLS, + PWD, + FAST, +} + +impl FromStr for EAPMethod { + type Err = InvalidEAPMethod; + + fn from_str(s: &str) -> Result { + match s { + "leap" => Ok(Self::LEAP), + "md5" => Ok(Self::MD5), + "tls" => Ok(Self::TLS), + "peap" => Ok(Self::PEAP), + "ttls" => Ok(Self::TTLS), + "pwd" => Ok(Self::PWD), + "fast" => Ok(Self::FAST), + _ => Err(InvalidEAPMethod(s.to_string())), + } + } +} + +impl fmt::Display for EAPMethod { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let value = match &self { + Self::LEAP => "leap", + Self::MD5 => "md5", + Self::TLS => "tls", + Self::PEAP => "peap", + Self::TTLS => "ttls", + Self::PWD => "pwd", + Self::FAST => "fast", + }; + write!(f, "{}", value) + } +} + +#[derive(Debug, Error)] +#[error("Invalid phase2-auth method: {0}")] +pub struct InvalidPhase2AuthMethod(String); + +#[derive(Debug, PartialEq, Clone, Serialize)] +pub enum Phase2AuthMethod { + PAP, + CHAP, + MSCHAP, + MSCHAPV2, + GTC, + OTP, + MD5, + TLS, +} + +impl FromStr for Phase2AuthMethod { + type Err = InvalidPhase2AuthMethod; + + fn from_str(s: &str) -> Result { + match s { + "pap" => Ok(Self::PAP), + "chap" => Ok(Self::CHAP), + "mschap" => Ok(Self::MSCHAP), + "mschapv2" => Ok(Self::MSCHAPV2), + "gtc" => Ok(Self::GTC), + "otp" => Ok(Self::OTP), + "md5" => Ok(Self::MD5), + "tls" => Ok(Self::TLS), + _ => Err(InvalidPhase2AuthMethod(s.to_string())), + } + } +} + +impl fmt::Display for Phase2AuthMethod { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let value = match self { + Self::PAP => "pap", + Self::CHAP => "chap", + Self::MSCHAP => "mschap", + Self::MSCHAPV2 => "mschapv2", + Self::GTC => "gtc", + Self::OTP => "otp", + Self::MD5 => "md5", + Self::TLS => "tls", + }; + write!(f, "{}", value) + } +} diff --git a/rust/agama-server/src/network/nm/dbus.rs b/rust/agama-server/src/network/nm/dbus.rs index b86853a8ad..489fe761f4 100644 --- a/rust/agama-server/src/network/nm/dbus.rs +++ b/rust/agama-server/src/network/nm/dbus.rs @@ -25,6 +25,7 @@ const BRIDGE_KEY: &str = "bridge"; const BRIDGE_PORT_KEY: &str = "bridge-port"; const INFINIBAND_KEY: &str = "infiniband"; const TUN_KEY: &str = "tun"; +const IEEE_8021X_KEY: &str = "802-1x"; /// Converts a connection struct into a HashMap that can be sent over D-Bus. /// @@ -137,6 +138,10 @@ pub fn connection_to_dbus<'a>( PortConfig::None => {} } + if let Some(ieee_8021x_config) = &conn.ieee_8021x_config { + result.insert(IEEE_8021X_KEY, ieee_8021x_config_to_dbus(ieee_8021x_config)); + } + result.insert("connection", connection_dbus); result } @@ -151,6 +156,10 @@ pub fn connection_from_dbus(conn: OwnedNestedHash) -> Option { connection.port_config = PortConfig::Bridge(bridge_port_config); } + if let Some(ieee_8021x_config) = ieee_8021x_config_from_dbus(&conn) { + connection.ieee_8021x_config = Some(ieee_8021x_config); + } + if let Some(wireless_config) = wireless_config_from_dbus(&conn) { connection.config = ConnectionConfig::Wireless(wireless_config); return Some(connection); @@ -981,6 +990,161 @@ fn vlan_config_from_dbus(conn: &OwnedNestedHash) -> Option { }) } +fn ieee_8021x_config_to_dbus(config: &IEEE8021XConfig) -> HashMap<&str, zvariant::Value> { + let mut ieee_8021x_config: HashMap<&str, zvariant::Value> = HashMap::from([( + "eap", + config + .eap + .iter() + .map(|x| x.to_string()) + .collect::>() + .into(), + )]); + + if let Some(phase2_auth) = &config.phase2_auth { + ieee_8021x_config.insert("phase2-auth", phase2_auth.to_string().into()); + } + if let Some(identity) = &config.identity { + ieee_8021x_config.insert("identity", identity.into()); + } + if let Some(password) = &config.password { + ieee_8021x_config.insert("password", password.into()); + } + if let Some(ca_cert) = &config.ca_cert { + ieee_8021x_config.insert("ca-cert", format_nm_path(ca_cert).into_bytes().into()); + } + if let Some(ca_cert_password) = &config.ca_cert_password { + ieee_8021x_config.insert("ca-cert-password", ca_cert_password.into()); + } + if let Some(client_cert) = &config.client_cert { + ieee_8021x_config.insert( + "client-cert", + format_nm_path(client_cert).into_bytes().into(), + ); + } + if let Some(client_cert_password) = &config.client_cert_password { + ieee_8021x_config.insert("client-cert-password", client_cert_password.into()); + } + if let Some(private_key) = &config.private_key { + ieee_8021x_config.insert( + "private-key", + format_nm_path(private_key).into_bytes().into(), + ); + } + if let Some(private_key_password) = &config.private_key_password { + ieee_8021x_config.insert("private-key-password", private_key_password.into()); + } + if let Some(anonymous_identity) = &config.anonymous_identity { + ieee_8021x_config.insert("anonymous-identity", anonymous_identity.into()); + } + if let Some(peap_version) = &config.peap_version { + ieee_8021x_config.insert("phase1-peapver", peap_version.into()); + } + ieee_8021x_config.insert( + "phase1-peaplabel", + if config.peap_label { "1" } else { "0" }.into(), + ); + + ieee_8021x_config +} + +fn format_nm_path(path: &String) -> String { + format!("file://{path}\0") +} + +fn ieee_8021x_config_from_dbus(conn: &OwnedNestedHash) -> Option { + let ieee_8021x = conn.get(IEEE_8021X_KEY)?; + + let mut ieee_8021x_config = IEEE8021XConfig::default(); + + if let Some(eap) = ieee_8021x.get("eap") { + let eap: &zvariant::Array = eap.downcast_ref()?; + let eap: Vec<&str> = eap + .iter() + .map(|x| x.downcast_ref::()) + .collect::>>()?; + let eap: Vec = eap + .iter() + .map(|x| EAPMethod::from_str(x)) + .collect::, InvalidEAPMethod>>() + .ok()?; + ieee_8021x_config.eap = eap; + } + + if let Some(phase2_auth) = ieee_8021x.get("phase2-auth") { + ieee_8021x_config.phase2_auth = + Some(Phase2AuthMethod::from_str(phase2_auth.downcast_ref::()?).ok()?); + } + if let Some(identity) = ieee_8021x.get("identity") { + ieee_8021x_config.identity = Some(identity.downcast_ref::()?.to_string()); + } + if let Some(password) = ieee_8021x.get("password") { + ieee_8021x_config.password = Some(password.downcast_ref::()?.to_string()); + } + if let Some(ca_cert) = ieee_8021x.get("ca-cert") { + let ca_cert: &zvariant::Array = ca_cert.downcast_ref()?; + println!("{:?}", ca_cert); + let ca_cert: String = ca_cert + .get() + .iter() + .map(|u| *u.downcast_ref::().unwrap() as char) + .collect(); + println!("{:?}", ca_cert); + ieee_8021x_config.ca_cert = strip_nm_file_path(ca_cert); + println!("{:?}", ieee_8021x_config.ca_cert); + } + if let Some(ca_cert_password) = ieee_8021x.get("ca-cert-password") { + ieee_8021x_config.ca_cert_password = + Some(ca_cert_password.downcast_ref::()?.to_string()); + } + if let Some(client_cert) = ieee_8021x.get("client-cert") { + let client_cert: &zvariant::Array = client_cert.downcast_ref()?; + let client_cert: String = client_cert + .get() + .iter() + .map(|u| *u.downcast_ref::().unwrap() as char) + .collect(); + ieee_8021x_config.client_cert = strip_nm_file_path(client_cert); + } + if let Some(client_cert_password) = ieee_8021x.get("client-cert-password") { + ieee_8021x_config.client_cert_password = + Some(client_cert_password.downcast_ref::()?.to_string()); + } + if let Some(private_key) = ieee_8021x.get("private-key") { + let private_key: &zvariant::Array = private_key.downcast_ref()?; + let private_key: String = private_key + .get() + .iter() + .map(|u| *u.downcast_ref::().unwrap() as char) + .collect(); + ieee_8021x_config.private_key = strip_nm_file_path(private_key); + } + if let Some(private_key_password) = ieee_8021x.get("private-key-password") { + ieee_8021x_config.private_key_password = + Some(private_key_password.downcast_ref::()?.to_string()); + } + if let Some(anonymous_identity) = ieee_8021x.get("anonymous-identity") { + ieee_8021x_config.anonymous_identity = + Some(anonymous_identity.downcast_ref::()?.to_string()); + } + if let Some(peap_version) = ieee_8021x.get("phase1-peapver") { + ieee_8021x_config.peap_version = Some(peap_version.downcast_ref::()?.to_string()); + } + if let Some(peap_label) = ieee_8021x.get("phase1-peaplabel") { + ieee_8021x_config.peap_label = peap_label.downcast_ref::()? == "1"; + } + + Some(ieee_8021x_config) +} + +// Strips NetworkManager path from "file://{path}\0" so only path remains. +fn strip_nm_file_path(path: String) -> Option { + let stripped_path = path + .strip_prefix("file://") + .and_then(|x| x.strip_suffix("\0"))?; + Some(stripped_path.to_string()) +} + /// Determines whether a value is empty. /// /// TODO: Generalize for other kind of values, like dicts or arrays. @@ -1280,6 +1444,90 @@ mod test { assert_eq!(infiniband.transport_mode, InfinibandTransportMode::Datagram); } + #[test] + fn test_connection_from_dbus_ieee_8021x() { + let connection_section = HashMap::from([ + ("id".to_string(), Value::new("eap0").to_owned()), + ( + "uuid".to_string(), + Value::new(Uuid::new_v4().to_string()).to_owned(), + ), + ]); + + let ieee_8021x_section = HashMap::from([ + ( + "eap".to_string(), + Value::new(vec!["md5", "leap"]).to_owned(), + ), + ("phase2-auth".to_string(), Value::new("gtc").to_owned()), + ("identity".to_string(), Value::new("test_user").to_owned()), + ("password".to_string(), Value::new("test_pw").to_owned()), + ( + "ca-cert".to_string(), + Value::new("file:///path/to/ca_cert.pem\0".as_bytes()).to_owned(), + ), + ( + "ca-cert-password".to_string(), + Value::new("ca_cert_pw").to_owned(), + ), + ( + "client-cert".to_string(), + Value::new("not_valid_value".as_bytes()).to_owned(), + ), + ( + "client-cert-password".to_string(), + Value::new("client_cert_pw").to_owned(), + ), + ( + "private-key".to_string(), + Value::new("file://relative_path/private_key\0".as_bytes()).to_owned(), + ), + ( + "private-key-password".to_string(), + Value::new("private_key_pw").to_owned(), + ), + ( + "anonymous-identity".to_string(), + Value::new("anon_identity").to_owned(), + ), + ("phase1-peaplabel".to_string(), Value::new("0").to_owned()), + ("phase1-peapver".to_string(), Value::new("1").to_owned()), + ]); + + let dbus_conn = HashMap::from([ + ("connection".to_string(), connection_section), + (super::IEEE_8021X_KEY.to_string(), ieee_8021x_section), + (super::LOOPBACK_KEY.to_string(), HashMap::new().to_owned()), + ]); + + let connection = connection_from_dbus(dbus_conn).unwrap(); + let Some(config) = &connection.ieee_8021x_config else { + panic!("No eap config set") + }; + assert_eq!(config.eap, vec![EAPMethod::MD5, EAPMethod::LEAP]); + assert_eq!(config.phase2_auth, Some(Phase2AuthMethod::GTC)); + assert_eq!(config.identity, Some("test_user".to_string())); + assert_eq!(config.password, Some("test_pw".to_string())); + assert_eq!(config.ca_cert, Some("/path/to/ca_cert.pem".to_string())); + assert_eq!(config.ca_cert_password, Some("ca_cert_pw".to_string())); + assert_eq!(config.client_cert, None); + assert_eq!( + config.client_cert_password, + Some("client_cert_pw".to_string()) + ); + assert_eq!( + config.private_key, + Some("relative_path/private_key".to_string()) + ); + assert_eq!( + config.private_key_password, + Some("private_key_pw".to_string()) + ); + assert_eq!(config.anonymous_identity, Some("anon_identity".to_string())); + assert_eq!(config.peap_version, Some("1".to_string())); + assert!(!config.peap_label); + } + #[test] fn test_dbus_from_infiniband_connection() { let config = InfinibandConfig { @@ -1404,6 +1652,99 @@ mod test { assert_eq!(wep_key1, "hello"); } + #[test] + fn test_dbus_from_ieee_8021x() { + let ieee_8021x_config = IEEE8021XConfig { + eap: vec![ + EAPMethod::from_str("tls").unwrap(), + EAPMethod::from_str("peap").unwrap(), + ], + phase2_auth: Some(Phase2AuthMethod::MSCHAPV2), + identity: Some("test_user".to_string()), + password: Some("test_pw".to_string()), + ca_cert: Some("/path/to/ca_cert.pem".to_string()), + ca_cert_password: Some("ca_cert_pw".to_string()), + client_cert: Some("/client_cert".to_string()), + client_cert_password: Some("client_cert_pw".to_string()), + private_key: Some("relative_path/private_key".to_string()), + private_key_password: Some("private_key_pw".to_string()), + anonymous_identity: Some("anon_identity".to_string()), + peap_version: Some("0".to_string()), + peap_label: true, + }; + let mut conn = build_base_connection(); + conn.ieee_8021x_config = Some(ieee_8021x_config); + let conn_dbus = connection_to_dbus(&conn, None); + + let config = conn_dbus.get(super::IEEE_8021X_KEY).unwrap(); + let eap: &Array = config.get("eap").unwrap().downcast_ref().unwrap(); + let eap: Vec<&str> = eap + .iter() + .map(|x| x.downcast_ref::().unwrap()) + .collect(); + assert_eq!(eap, ["tls".to_string(), "peap".to_string()]); + let identity: &str = config.get("identity").unwrap().downcast_ref().unwrap(); + assert_eq!(identity, "test_user"); + let phase2_auth: &str = config.get("phase2-auth").unwrap().downcast_ref().unwrap(); + assert_eq!(phase2_auth, "mschapv2"); + let password: &str = config.get("password").unwrap().downcast_ref().unwrap(); + assert_eq!(password, "test_pw"); + let ca_cert: &Array = config.get("ca-cert").unwrap().downcast_ref().unwrap(); + let ca_cert: String = ca_cert + .iter() + .map(|x| *x.downcast_ref::().unwrap() as char) + .collect(); + assert_eq!(ca_cert, "file:///path/to/ca_cert.pem\0"); + let ca_cert_password: &str = config + .get("ca-cert-password") + .unwrap() + .downcast_ref() + .unwrap(); + assert_eq!(ca_cert_password, "ca_cert_pw"); + let client_cert: &Array = config.get("client-cert").unwrap().downcast_ref().unwrap(); + let client_cert: String = client_cert + .iter() + .map(|x| *x.downcast_ref::().unwrap() as char) + .collect(); + assert_eq!(client_cert, "file:///client_cert\0"); + let client_cert_password: &str = config + .get("client-cert-password") + .unwrap() + .downcast_ref() + .unwrap(); + assert_eq!(client_cert_password, "client_cert_pw"); + let private_key: &Array = config.get("private-key").unwrap().downcast_ref().unwrap(); + let private_key: String = private_key + .iter() + .map(|x| *x.downcast_ref::().unwrap() as char) + .collect(); + assert_eq!(private_key, "file://relative_path/private_key\0"); + let private_key_password: &str = config + .get("private-key-password") + .unwrap() + .downcast_ref() + .unwrap(); + assert_eq!(private_key_password, "private_key_pw"); + let anonymous_identity: &str = config + .get("anonymous-identity") + .unwrap() + .downcast_ref() + .unwrap(); + assert_eq!(anonymous_identity, "anon_identity"); + let peap_version: &str = config + .get("phase1-peapver") + .unwrap() + .downcast_ref() + .unwrap(); + assert_eq!(peap_version, "0"); + let peap_label: &str = config + .get("phase1-peaplabel") + .unwrap() + .downcast_ref() + .unwrap(); + assert_eq!(peap_label, "1"); + } + #[test] fn test_dbus_from_ethernet_connection() { let ethernet = build_base_connection();