radius_rust/protocol/
host.rs

1//! Shared base for RADIUS Client & Server implementations
2
3
4use super::dictionary::{ Dictionary, DictionaryAttribute, DictionaryValue };
5use super::error::RadiusError;
6use super::radius_packet::{ RadiusAttribute, RadiusMsgType, RadiusPacket, TypeCode };
7
8use hmac::{ Hmac, Mac };
9use md5::Md5;
10
11
12const IGNORE_VERIFY_ATTRIBUTE: &str = "Message-Authenticator";
13type HmacMd5 = Hmac<Md5>;
14
15
16#[derive(Debug)]
17/// Generic struct that holds Server & Client common functions and attributes
18pub struct Host {
19    auth_port:  u16,
20    acct_port:  u16,
21    coa_port:   u16,
22    dictionary: Dictionary
23}
24
25impl Host{
26    /// Initialises host instance only with Dictionary (ports should be set through *set_port()*,
27    /// otherwise default to 0)
28    pub fn with_dictionary(dictionary: Dictionary) -> Host {
29        Host {
30            auth_port:  0,
31            acct_port:  0,
32            coa_port:   0,
33            dictionary
34        }
35    }
36
37    /// Sets remote port, that responsible for specific RADIUS Message Type
38    pub fn set_port(&mut self, msg_type: RadiusMsgType, port: u16) {
39        match msg_type {
40            RadiusMsgType::AUTH => self.auth_port = port,
41            RadiusMsgType::ACCT => self.acct_port = port,
42            RadiusMsgType::COA  => self.coa_port  = port,
43        }
44    }
45
46    #[allow(dead_code)]
47    /// Initialises host instance with all required fields
48    pub fn initialise_host(auth_port: u16, acct_port: u16, coa_port: u16, dictionary: Dictionary) -> Host {
49        Host { auth_port, acct_port, coa_port, dictionary }
50    }
51
52
53    /// Creates RadiusAttribute with given name (name is checked against Dictionary)
54    pub fn create_attribute_by_name(&self, attribute_name: &str, value: Vec<u8>) -> Result<RadiusAttribute, RadiusError> {
55        RadiusAttribute::create_by_name(&self.dictionary, attribute_name, value).ok_or(RadiusError::MalformedAttributeError { error: format!("Failed to create: {:?} attribute. Check if attribute exists in provided dictionary file", attribute_name) })
56    }
57
58    /// Creates RadiusAttribute with given id (id is checked against Dictionary)
59    pub fn create_attribute_by_id(&self, attribute_id: u8, value: Vec<u8>) -> Result<RadiusAttribute, RadiusError> {
60        RadiusAttribute::create_by_id(&self.dictionary, attribute_id, value).ok_or(RadiusError::MalformedAttributeError { error: format!("Failed to create: attribute with ID {}. Check if attribute exists in provided dictionary file", attribute_id) })
61    }
62
63    /// Returns port of RADIUS server, that receives given type of RADIUS message/packet
64    pub fn port(&self, code: &TypeCode) -> Option<u16> {
65        match code {
66            TypeCode::AccessRequest     => Some(self.auth_port),
67            TypeCode::AccountingRequest => Some(self.acct_port),
68            TypeCode::CoARequest        => Some(self.coa_port),
69            _                           => None
70        }
71    }
72
73    /// Returns host's dictionary instance
74    pub fn dictionary(&self) -> &Dictionary {
75        &self.dictionary
76    }
77
78    #[allow(dead_code)]
79    /// Returns VALUE from dictionary with given attribute & value name
80    pub fn dictionary_value_by_attr_and_value_name(&self, attr_name: &str, value_name: &str) -> Option<&DictionaryValue> {
81        self.dictionary.values().iter().find(|&value| value.name() == value_name && value.attribute_name() == attr_name)
82    }
83
84    /// Returns ATTRIBUTE from dictionary with given id
85    pub fn dictionary_attribute_by_id(&self, packet_attr_id: u8) -> Option<&DictionaryAttribute> {
86        self.dictionary.attributes().iter().find(|&attr| attr.code() == packet_attr_id)
87    }
88
89    #[allow(dead_code)]
90    /// Returns ATTRIBUTE from dictionary with given name
91    pub fn dictionary_attribute_by_name(&self, packet_attr_name: &str) -> Option<&DictionaryAttribute> {
92        self.dictionary.attributes().iter().find(|&attr| attr.name() == packet_attr_name)
93    }
94
95    /// Initialises RadiusPacket from bytes
96    pub fn initialise_packet_from_bytes(&self, packet: &[u8]) -> Result<RadiusPacket, RadiusError> {
97        RadiusPacket::initialise_packet_from_bytes(&self.dictionary, packet)
98    }
99
100    /// Verifies that RadiusPacket attributes have valid values
101    ///
102    /// Note: doesn't verify Message-Authenticator attribute, because it is HMAC-MD5 hash, not an
103    /// ASCII string
104    pub fn verify_packet_attributes(&self, packet: &[u8]) -> Result<(), RadiusError> {
105        let _packet_tmp = RadiusPacket::initialise_packet_from_bytes(&self.dictionary, &packet)?;
106
107        for packet_attr in _packet_tmp.attributes().iter().filter(|&attr| attr.name() != IGNORE_VERIFY_ATTRIBUTE) {
108            match self.dictionary_attribute_by_id(packet_attr.id()) {
109                None             => return Err( RadiusError::ValidationError {error: format!("Attribute with ID {} may not exist in provided dictionary file, thus verification failed", packet_attr.id())} ),
110                Some(_dict_attr) => {
111                    let _dict_attr_data_type = _dict_attr.code_type();
112                    match packet_attr.verify_original_value(_dict_attr_data_type) {
113                        Err(err) => return Err( RadiusError::ValidationError {error: err.to_string()} ),
114                        _        => continue
115                    }
116                }
117            }
118        }
119        Ok(())
120    }
121
122    /// Verifies Message-Authenticator value
123    pub fn verify_message_authenticator(&self, secret: &str, packet: &[u8]) -> Result<(), RadiusError> {
124        // Step 1. Get Message-Authenticator from packet
125        let mut _packet_tmp   = RadiusPacket::initialise_packet_from_bytes(&self.dictionary, &packet)?;
126        let original_msg_auth = _packet_tmp.message_authenticator()?.to_vec();
127
128        // Step 2. Set Message-Authenticator in packet to [0; 16]
129        let zeroed_authenticator = [0; 16];
130        _packet_tmp.override_message_authenticator(zeroed_authenticator.to_vec())?;
131
132        // Step 3. Calculate HMAC-MD5 for the packet
133        let mut calculated_msg_auth = HmacMd5::new_from_slice(secret.as_bytes()).map_err(|error| RadiusError::ValidationError { error: error.to_string() })?;
134        calculated_msg_auth.update(&_packet_tmp.to_bytes());
135
136        // Step 4. Compare calculated hash with the one extracted in Step 1
137        match calculated_msg_auth.verify_slice(&original_msg_auth) {
138            Ok(())   => Ok(()),
139            Err(_) => Err( RadiusError::ValidationError {error: String::from("Packet Message-Authenticator mismatch")} )
140        }
141    }
142}
143
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148    use crate::protocol::dictionary::SupportedAttributeTypes;
149
150    #[test]
151    fn test_get_dictionary_value_by_attr_and_value_name() {
152        let dictionary = Dictionary::from_file("./dict_examples/integration_dict").unwrap();
153        let host       = Host::initialise_host(1812, 1813, 3799, dictionary);
154
155        let dict_value = host.dictionary_value_by_attr_and_value_name("Service-Type", "Login-User").unwrap();
156
157        assert_eq!("Service-Type", dict_value.attribute_name());
158        assert_eq!("Login-User",   dict_value.name());
159        assert_eq!("1",            dict_value.value());
160    }
161
162    #[test]
163    fn test_get_dictionary_value_by_attr_and_value_name_error() {
164        let dictionary = Dictionary::from_file("./dict_examples/integration_dict").unwrap();
165        let host       = Host::initialise_host(1812, 1813, 3799, dictionary);
166
167        let dict_value = host.dictionary_value_by_attr_and_value_name("Service-Type", "Lin-User");
168        assert_eq!(None, dict_value);
169    }
170
171    #[test]
172    fn test_get_dictionary_attribute_by_id() {
173        let dictionary = Dictionary::from_file("./dict_examples/integration_dict").unwrap();
174        let host       = Host::initialise_host(1812, 1813, 3799, dictionary);
175
176        let dict_attr = host.dictionary_attribute_by_id(80).unwrap();
177
178        assert_eq!("Message-Authenticator",                    dict_attr.name());
179        assert_eq!(80,                                         dict_attr.code());
180        assert_eq!(&Some(SupportedAttributeTypes::ByteString), dict_attr.code_type());
181    }
182
183    #[test]
184    fn test_get_dictionary_attribute_by_id_error() {
185        let dictionary = Dictionary::from_file("./dict_examples/integration_dict").unwrap();
186        let host       = Host::initialise_host(1812, 1813, 3799, dictionary);
187
188        let dict_attr = host.dictionary_attribute_by_id(255);
189        assert_eq!(None, dict_attr);
190    }
191
192    #[test]
193    fn test_verify_packet_attributes() {
194        let dictionary = Dictionary::from_file("./dict_examples/integration_dict").unwrap();
195        let host       = Host::initialise_host(1812, 1813, 3799, dictionary);
196
197        let packet_bytes = [4, 43, 0, 86, 215, 189, 213, 172, 57, 94, 141, 70, 134, 121, 101, 57, 187, 220, 227, 73, 4, 6, 192, 168, 1, 10, 5, 6, 0, 0, 0, 0, 32, 10, 116, 114, 105, 108, 108, 105, 97, 110, 30, 19, 48, 48, 45, 48, 52, 45, 53, 70, 45, 48, 48, 45, 48, 70, 45, 68, 49, 31, 19, 48, 48, 45, 48, 49, 45, 50, 52, 45, 56, 48, 45, 66, 51, 45, 57, 67, 8, 6, 10, 0, 0, 100];
198
199        match host.verify_packet_attributes(&packet_bytes) {
200            Err(_err) => {
201                assert!(false)
202            },
203            _        => assert!(true)
204        }
205    }
206
207    #[test]
208    fn test_verify_packet_attributes_fail() {
209        let dictionary = Dictionary::from_file("./dict_examples/integration_dict").unwrap();
210        let host       = Host::initialise_host(1812, 1813, 3799, dictionary);
211
212        let packet_bytes = [4, 43, 0, 85, 215, 189, 213, 172, 57, 94, 141, 70, 134, 121, 101, 57, 187, 220, 227, 73, 4, 5, 192, 168, 10, 5, 6, 0, 0, 0, 0, 32, 10, 116, 114, 105, 108, 108, 105, 97, 110, 30, 19, 48, 48, 45, 48, 52, 45, 53, 70, 45, 48, 48, 45, 48, 70, 45, 68, 49, 31, 19, 48, 48, 45, 48, 49, 45, 50, 52, 45, 56, 48, 45, 66, 51, 45, 57, 67, 8, 6, 10, 0, 0, 100];
213
214        match host.verify_packet_attributes(&packet_bytes) {
215            Err(err) => {
216                assert_eq!(err.to_string(), String::from("Verification failed for incoming Radius packet: Attribute in Radius packet is malformed: invalid IPv4 bytes"))
217            },
218            _        => assert!(false)
219        }
220    }
221
222    #[test]
223    fn test_verify_message_authenticator_valid() {
224        let dictionary = Dictionary::from_file("./dict_examples/integration_dict").unwrap();
225        let host       = Host::initialise_host(1812, 1813, 3799, dictionary);
226        let secret     = "secret";
227
228        let packet_bytes = [1, 120, 0, 185, 49, 79, 108, 150, 27, 203, 166, 51, 193, 68, 15, 76, 208, 114, 171, 48, 1, 9, 116, 101, 115, 116, 105, 110, 103, 80, 18, 164, 201, 132, 0, 209, 101, 200, 189, 252, 251, 120, 224, 74, 190, 232, 197, 2, 66, 85, 125, 163, 190, 40, 210, 235, 231, 112, 96, 7, 94, 27, 95, 241, 63, 23, 81, 25, 136, 36, 209, 238, 119, 131, 113, 118, 14, 160, 16, 94, 184, 143, 37, 193, 138, 124, 238, 85, 197, 21, 17, 206, 158, 87, 132, 239, 59, 82, 183, 175, 54, 124, 138, 5, 245, 166, 195, 181, 106, 41, 31, 129, 183, 4, 6, 192, 168, 1, 10, 5, 6, 0, 0, 0, 0, 6, 6, 0, 0, 0, 2, 32, 10, 116, 114, 105, 108, 108, 105, 97, 110, 30, 19, 48, 48, 45, 48, 52, 45, 53, 70, 45, 48, 48, 45, 48, 70, 45, 68, 49, 31, 19, 48, 48, 45, 48, 49, 45, 50, 52, 45, 56, 48, 45, 66, 51, 45, 57, 67, 8, 6, 10, 0, 0, 100];
229
230        match host.verify_message_authenticator(&secret, &packet_bytes) {
231            Err(_err) => {
232                assert!(false)
233            },
234            _        => assert!(true)
235        }
236    }
237
238    #[test]
239    fn test_verify_message_authenticator_wo_authenticator() {
240        let dictionary = Dictionary::from_file("./dict_examples/integration_dict").unwrap();
241        let host       = Host::initialise_host(1812, 1813, 3799, dictionary);
242        let secret     = "secret";
243
244        let packet_bytes = [4, 43, 0, 86, 215, 189, 213, 172, 57, 94, 141, 70, 134, 121, 101, 57, 187, 220, 227, 73, 4, 6, 192, 168, 1, 10, 5, 6, 0, 0, 0, 0, 32, 10, 116, 114, 105, 108, 108, 105, 97, 110, 30, 19, 48, 48, 45, 48, 52, 45, 53, 70, 45, 48, 48, 45, 48, 70, 45, 68, 49, 31, 19, 48, 48, 45, 48, 49, 45, 50, 52, 45, 56, 48, 45, 66, 51, 45, 57, 67, 8, 6, 10, 0, 0, 100];
245
246        match host.verify_message_authenticator(&secret, &packet_bytes) {
247            Err(err) => {
248                assert_eq!(err.to_string(), String::from("Radius packet is malformed: Message-Authenticator attribute not found in packet"))
249            },
250            _        => assert!(false)
251        }
252    }
253
254    #[test]
255    fn test_verify_message_authenticator_invalid() {
256        let dictionary = Dictionary::from_file("./dict_examples/integration_dict").unwrap();
257        let host       = Host::initialise_host(1812, 1813, 3799, dictionary);
258        let secret     = "secret";
259
260        let packet_bytes = [1, 94, 0, 190, 241, 228, 181, 142, 185, 194, 157, 205, 159, 0, 91, 199, 171, 119, 68, 44, 1, 9, 116, 101, 115, 116, 105, 110, 103, 80, 23, 109, 101, 115, 115, 97, 103, 101, 45, 97, 117, 116, 104, 101, 110, 116, 105, 99, 97, 116, 111, 114, 2, 66, 167, 81, 185, 84, 173, 104, 91, 10, 145, 109, 156, 169, 227, 109, 100, 76, 86, 227, 61, 253, 129, 35, 109, 115, 54, 140, 66, 106, 193, 70, 145, 39, 106, 105, 142, 215, 21, 166, 142, 80, 145, 217, 202, 252, 172, 33, 17, 12, 159, 105, 157, 144, 221, 221, 94, 48, 158, 22, 62, 191, 16, 177, 137, 131, 4, 6, 192, 168, 1, 10, 5, 6, 0, 0, 0, 0, 6, 6, 0, 0, 0, 2, 32, 10, 116, 114, 105, 108, 108, 105, 97, 110, 30, 19, 48, 48, 45, 48, 52, 45, 53, 70, 45, 48, 48, 45, 48, 70, 45, 68, 49, 31, 19, 48, 48, 45, 48, 49, 45, 50, 52, 45, 56, 48, 45, 66, 51, 45, 57, 67, 8, 6, 10, 0, 0, 100];
261
262        match host.verify_message_authenticator(&secret, &packet_bytes) {
263            Err(err) => {
264                assert_eq!(err.to_string(), String::from("Verification failed for incoming Radius packet: Packet Message-Authenticator mismatch"))
265            },
266            _        => assert!(false)
267        }
268    }
269}