From ae0382b5590d4c99793e55794752579a3b459a43 Mon Sep 17 00:00:00 2001 From: RageLtMan Date: Sun, 31 Jul 2022 07:18:07 -0400 Subject: [PATCH] Scaffold EAP Message Authenticator with HMACMD5 Implement a new type using the hmac and md-5 crates, namespacing md-5 as hmac_md5 to avoid collision with existing crate. Stub out MessageAuthenticator struct & implementation to create HMACMD5 signature buffers from Packet's encode() byte vector. Implement Message-Authenticator input structure for starting the chain of HMAC exchanges and verifying Access-Request messages. Testing: Implemented test to verify Access-Request message HMACMD5 - pass --- examples/eap_server.rs | 58 +++++---- radius/Cargo.toml | 2 + radius/src/core/eap.rs | 24 ++-- radius/src/core/message_authenticator.rs | 155 +++++++++++++++++++++++ radius/src/core/mod.rs | 1 + 5 files changed, 202 insertions(+), 38 deletions(-) create mode 100644 radius/src/core/message_authenticator.rs diff --git a/examples/eap_server.rs b/examples/eap_server.rs index 8444f62..ebf70b2 100644 --- a/examples/eap_server.rs +++ b/examples/eap_server.rs @@ -12,6 +12,7 @@ use radius::core::code::Code; use radius::core::packet::Packet; use radius::core::request::Request; use radius::core::eap::{EAP, EAPType, EAPCode}; +use radius::core::message_authenticator::MessageAuthenticator; use radius::core::rfc2865; use radius::core::rfc2869; use radius::server::{RequestHandler, SecretProvider, SecretProviderError, Server}; @@ -20,11 +21,8 @@ use radius::server::{RequestHandler, SecretProvider, SecretProviderError, Server async fn main() { env_logger::init(); - let req_handler = MyRequestHandler { - secret_provider: MySecretProvider {} - }; // start UDP listening - let mut server = Server::listen("0.0.0.0", 1812, req_handler, MySecretProvider {}) + let mut server = Server::listen("0.0.0.0", 1812, MyRequestHandler {}, MySecretProvider {}) .await .unwrap(); server.set_buffer_size(1500); // default value: 1500 @@ -44,9 +42,7 @@ async fn main() { } } -struct MyRequestHandler { - secret_provider: MySecretProvider -} +struct MyRequestHandler {} impl MyRequestHandler { // Authenticating peer NAS RADIUS server @@ -75,17 +71,28 @@ impl MyRequestHandler { // (other attributes) // <- EAP-Success pub async fn handle_eap_identity(&self, req_packet: &Packet, conn: &UdpSocket, req: &Request, ieap: &EAP ) -> Result { - let mut eap = EAP::new(); //ieap.clone(); + let incoming = match rfc2869::lookup_message_authenticator(&req_packet) { + Some(m) => MessageAuthenticator::from_bytes(&m), + None => { + println!("No authenticator found"); + MessageAuthenticator::new() + } + }; + // let validator = MessageAuthenticator::from_access_request(&req_packet); + // assert_eq!(validator, incoming); let mut ac_packet = req_packet.make_response_packet(Code::AccessChallenge); - eap.code = EAPCode::Response; + rfc2869::add_message_authenticator(&mut ac_packet, &incoming.value[..]); + // let mut eap = EAP::new(); + let mut eap = ieap.clone(); + eap.code = EAPCode::Request; eap.typ = EAPType::MD5Challenge; - eap.id = ieap.id; + // eap.id = ieap.id; eap.len = eap.recalc_len(); - println!("Response EAP: {}", &eap); rfc2869::add_eap_message(&mut ac_packet, &eap.to_bytes()[..]); - let ac_bytes = &ac_packet.encode().unwrap(); - let authenticator = Packet::create_respose_authenticator(&ac_bytes[..], &req_packet.encode().unwrap()[..], &req_packet.get_secret()); - rfc2869::add_message_authenticator(&mut ac_packet, &authenticator); + let outgoing = MessageAuthenticator::from_packet(&ac_packet); + ac_packet = outgoing.authenticate_packet(&ac_packet).unwrap(); + println!("Response EAP: {}", &eap); + println!("Respose Message-Authenticator: {}", &outgoing); let result = conn.send_to(&ac_packet.encode().unwrap(), req.get_remote_addr()).await.unwrap(); Ok(result) } @@ -105,12 +112,19 @@ impl RequestHandler<(), io::Error> for MyRequestHandler { info!("maybe usr pass looked up"); let maybe_eap_message = rfc2869::lookup_eap_message(req_packet); info!("maybe eap looked up"); - match maybe_eap_message { + let eap_message = match rfc2869::lookup_eap_message(req_packet) { Some(e) => { let ieap = EAP::from_bytes(&e); println!("EAP Message: {}", &ieap); - let result = self.handle_eap_identity(req_packet, conn, req, &ieap).await.unwrap(); - println!("Sent challenge-response {} bytes", &result); + match ieap.typ { + EAPType::Identity => { + let result = self.handle_eap_identity(&req_packet, conn, req, &ieap).await.unwrap(); + println!("Sent challenge-response {} bytes", &result); + } + _ => { + println!("EAPType not handled"); + } + } ieap }, None => { @@ -118,16 +132,6 @@ impl RequestHandler<(), io::Error> for MyRequestHandler { EAP::new() } }; - let maybe_message_authenticator = rfc2869::lookup_message_authenticator(req_packet); - let message_authenticator = match maybe_message_authenticator { - Some(m) => m.to_vec(), - None => { - println!("No authenticator found"); - vec![0u8;16] - } - }; - println!("Message Authenticator: {:#?}", &message_authenticator); - let user_name = maybe_user_name_attr.unwrap().unwrap(); let user_password = match maybe_user_password_attr { Some(e) => match e { diff --git a/radius/Cargo.toml b/radius/Cargo.toml index 33764ef..58881d6 100644 --- a/radius/Cargo.toml +++ b/radius/Cargo.toml @@ -21,6 +21,8 @@ thiserror = "1.0" log = "0.4.14" tokio = { version = "1.6.1", features = ["full"] } async-trait = "0.1.50" +hmac = "0.12" +hmac_md5 = { package = "md-5", version = "0.10"} [dev-dependencies] hex = "0.4" \ No newline at end of file diff --git a/radius/src/core/eap.rs b/radius/src/core/eap.rs index 0b78e72..4db6f0e 100644 --- a/radius/src/core/eap.rs +++ b/radius/src/core/eap.rs @@ -148,9 +148,12 @@ impl EAP { } /// Create an EAP message structure from a slice of bytes pub fn from_bytes(eap_bytes: &[u8]) -> Self { + let mut len_bytes = [0u8; 2]; + len_bytes[0] = eap_bytes[2]; + len_bytes[1] = eap_bytes[3]; let code = EAPCode::from(eap_bytes[0]); let id = eap_bytes[1].to_owned(); - let len = Self::len_from_bytes(&eap_bytes[2..4]); + let len = u16::from_be_bytes(len_bytes); let typ = EAPType::from(eap_bytes[4]); let data = eap_bytes[5..(len as usize)].to_owned(); EAP { code, id, len, typ, data } @@ -160,7 +163,7 @@ impl EAP { let mut bytes = Vec::::new(); bytes.push(self.code as u8); bytes.push(self.id); - bytes.extend(Self::len_to_bytes(self.len)); + bytes.extend(u16::to_be_bytes(self.len)); bytes.push(self.typ as u8); bytes.extend(self.data.clone()); return bytes @@ -169,14 +172,6 @@ impl EAP { pub fn recalc_len(&self) -> u16 { (5 + self.data.len()) as u16 } - /// Create a response message of the requested type from current state - fn len_from_bytes(bytes: &[u8]) -> u16 { - ((bytes[0] as u16) << 8) | bytes[1] as u16 - } - /// Format the raw pair of bytes in an EAP message buffer into an appropriate u16 - fn len_to_bytes(len: u16) -> [u8; 2] { - [(len >> 8) as u8, len as u8] - } } impl fmt::Display for EAP { @@ -216,13 +211,20 @@ mod tests { Ok(()) } #[test] + fn it_should_decode_eap_length() -> Result<(), ()> { + let eap_bytes = hex::decode("027200090174657374").unwrap(); + let eap = EAP::from_bytes(&eap_bytes[..]); + assert_eq!(eap.len, 9u16); + Ok(()) + } + #[test] fn it_should_decode_eap_data() -> Result<(), ()> { let eap_bytes = hex::decode("027200090174657374").unwrap(); let eap = EAP::from_bytes(&eap_bytes[..]); assert_eq!("test".to_owned(), String::from_utf8(eap.data).unwrap()); Ok(()) } - + #[test] fn it_should_marshal_eap_correctly() -> Result<(),()> { let eap_bytes = hex::decode("027200090174657374").unwrap(); let eap = EAP::from_bytes(&eap_bytes[..]); diff --git a/radius/src/core/message_authenticator.rs b/radius/src/core/message_authenticator.rs new file mode 100644 index 0000000..b078d4e --- /dev/null +++ b/radius/src/core/message_authenticator.rs @@ -0,0 +1,155 @@ +use std::fmt; +use hmac::{Hmac, Mac}; +use hmac_md5::Md5; +use crate::core::packet::Packet; +use crate::core::rfc2869; + +type HmacMd5 = Hmac; + +///! +// A RADIUS client receiving an Access-Accept, Access-Reject or +// Access-Challenge with a Message-Authenticator attribute present +// MUST calculate the correct value of the Message-Authenticator and +// silently discard the packet if it does not match the value sent. +// This attribute is not required in Access-Requests which include +// the User-Password attribute, but is useful for preventing attacks +// on other types of authentication. This attribute is intended to +// thwart attempts by an attacker to setup a "rogue" NAS, and perform +// online dictionary attacks against the RADIUS server. It does not +// afford protection against "offline" attacks where the attacker +// intercepts packets containing (for example) CHAP challenge and +// response, and performs a dictionary attack against those packets +// offline. +// A summary of the Message-Authenticator attribute format is shown +// below. The fields are transmitted from left to right. +// 0 1 2 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Type | Length | String... +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// Type +// 80 for Message-Authenticator +// Length +// 18 +// String +// When present in an Access-Request packet, Message-Authenticator is +// an HMAC-MD5 [RFC2104] hash of the entire Access-Request packet, +// including Type, ID, Length and Authenticator, using the shared +// secret as the key, as follows. +// Message-Authenticator = HMAC-MD5 (Type, Identifier, Length, +// Request Authenticator, Attributes) +// When the message integrity check is calculated the signature +// string should be considered to be sixteen octets of zero. +// For Access-Challenge, Access-Accept, and Access-Reject packets, +// the Message-Authenticator is calculated as follows, using the +// Request-Authenticator from the Access-Request this packet is in +// reply to: +// Message-Authenticator = HMAC-MD5 (Type, Identifier, Length, +// Request Authenticator, Attributes) +// When the message integrity check is calculated the signature +// string should be considered to be sixteen octets of zero. The +// shared secret is used as the key for the HMAC-MD5 message +// integrity check. The Message-Authenticator is calculated and +// inserted in the packet before the Response Authenticator is +// calculated. +///! + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct MessageAuthenticator { + pub value: [u8; 16] +} + +impl MessageAuthenticator { + + /// Create a new Message-Authenticator from a 16-byte slice + pub fn from_bytes(bytes: &[u8]) -> Self { + assert_eq!(bytes.len(), 16); + let mut arr = [0u8; 16]; + for (place, element) in arr.iter_mut().zip(bytes.iter()) { + *place = *element; + } + MessageAuthenticator { value: arr } + } + /// Create a new Message-Authenticator with zeroed-values + pub fn new() -> Self { + MessageAuthenticator { value: [0u8; 16] } + } + /// Create a new Message-Authenticator from input packet + pub fn from_packet(sb: &Packet) -> Self { + let mut mac = HmacMd5::new_from_slice(sb.get_secret()).unwrap(); + mac.update(&sb.encode().expect("Failed to encode packet for hmac-md5")[..]); + Self::from_bytes(&mac.finalize().into_bytes()[..]) + } + /// Create a new Message-Authenticator for an Access-Request RADIUS message. + /// Since this message type is the start of the HMAC-chain, it hashes itself with + /// the hash-input RADIUS message having a Message-Authenticator equivalent to the + /// new() method for this code (all zeroes). + pub fn from_access_request(pkt: &Packet) -> Self { + Self::from_packet( + // zero the existing Message-Authenticator + &Self::new().authenticate_packet(&pkt).unwrap() + ) + } + /// Attempt to create new Packet with signature buffer from this MessageAuthenticator's + /// value field. + pub fn authenticate_packet(&self, pkt: &Packet) -> Result { + let mut req_bytes = pkt.encode().unwrap(); + let secret = pkt.get_secret(); + let req_ma_bytes = rfc2869::lookup_message_authenticator(&pkt).unwrap(); + Self::replace_slice(&mut req_bytes, &req_ma_bytes, &self.value[..]); + let res = Packet::decode(&req_bytes, secret).unwrap(); + Ok(res) + } + // From https://stackoverflow.com/questions/54150353/how-to-find-and-replace-every-matching-slice-of-bytes-with-another-slice + fn replace_slice(buf: &mut [T], from: &[T], to: &[T]) + where + T: Clone + PartialEq, + { + for i in 0..=buf.len() - from.len() { + if buf[i..].starts_with(from) { + buf[i..(i + from.len())].clone_from_slice(to); + } + } + } +} + +impl fmt::Display for MessageAuthenticator { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:02X?}", self.value) + } +} + +#[cfg(test)] +mod tests { + use hex; + use crate::core::packet::Packet; + use crate::core::message_authenticator::MessageAuthenticator; + use crate::core::rfc2869; + + #[test] + fn it_should_authenticate_access_request() -> Result<(), ()> { + let msg_bytes = hex::decode("01160049b3a5cd2de262bcdbb589752a212e0b2f01067465737404067f00010105060000000150121ca1999b24d5224ddeca96fd7dabac270706000000014f0b023100090174657374").unwrap(); + let secret = b"somesecretval"; + let req_packet = Packet::decode(&msg_bytes, &secret[..]).unwrap(); + assert_eq!(req_packet.encode().unwrap(), msg_bytes); + let ma_bytes = rfc2869::lookup_message_authenticator(&req_packet).unwrap(); + let ma = MessageAuthenticator::from_bytes(&ma_bytes); + let ema = MessageAuthenticator::from_access_request(&req_packet); + + assert_eq!(ma, ema); + Ok(()) + } + #[test] + fn it_should_authenticate_other_access_request() -> Result<(), ()> { + let msg_bytes = hex::decode("013200497d39e88ba3783fc183b54d486c629e8501067465737404067f000101050600000001501208c9801fb78909a8b24ad4dc261b2d470706000000014f0b020200090174657374").unwrap(); + let secret = b"somesecretval"; + let req_packet = Packet::decode(&msg_bytes, &secret[..]).unwrap(); + assert_eq!(req_packet.encode().unwrap(), msg_bytes); + let ma_bytes = rfc2869::lookup_message_authenticator(&req_packet).unwrap(); + let ma = MessageAuthenticator::from_bytes(&ma_bytes); + let ema = MessageAuthenticator::from_access_request(&req_packet); + + assert_eq!(ma, ema); + Ok(()) + } +} \ No newline at end of file diff --git a/radius/src/core/mod.rs b/radius/src/core/mod.rs index dbd8013..2774519 100644 --- a/radius/src/core/mod.rs +++ b/radius/src/core/mod.rs @@ -31,3 +31,4 @@ pub mod rfc7055; pub mod rfc7155; pub mod tag; pub mod eap; +pub mod message_authenticator;