diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 078291e..1e5bd39 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -21,3 +21,7 @@ path = "server.rs" name = "client" path = "client.rs" +[[example]] +name = "eap_server" +path = "eap_server.rs" + diff --git a/examples/eap_server.rs b/examples/eap_server.rs new file mode 100644 index 0000000..53360f6 --- /dev/null +++ b/examples/eap_server.rs @@ -0,0 +1,178 @@ +#[macro_use] +extern crate log; + +use std::net::SocketAddr; +use std::{io, process}; + +use async_trait::async_trait; +use tokio::net::UdpSocket; +use tokio::signal; + +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}; + +#[tokio::main] +async fn main() { + env_logger::init(); + + // start UDP listening + let mut server = Server::listen("0.0.0.0", 1812, MyRequestHandler {}, MySecretProvider {}) + .await + .unwrap(); + server.set_buffer_size(1500); // default value: 1500 + server.set_skip_authenticity_validation(false); // default value: false + + // once it has reached here, a RADIUS server is now ready + info!( + "serve is now ready: {}", + server.get_listen_address().unwrap() + ); + + // start the loop to handle the RADIUS requests + let result = server.run(signal::ctrl_c()).await; + info!("{:?}", result); + if result.is_err() { + process::exit(1); + } +} + +struct MyRequestHandler {} + +impl MyRequestHandler { +// Authenticating peer NAS RADIUS server +// ------------------- --- ------------- +// <- EAP-Request/ +// Identity +// EAP-Response/ +// Identity (MyID) -> +// RADIUS Access-Request/ +// EAP-Message/EAP-Response/ +// (MyID) -> +// <- RADIUS +// Access-Challenge/ +// EAP-Message/EAP-Request +// OTP/OTP Challenge +// <- EAP-Request/ +// OTP/OTP Challenge +// EAP-Response/ +// OTP, OTPpw -> +// RADIUS Access-Request/ +// EAP-Message/EAP-Response/ +// OTP, OTPpw -> +// <- RADIUS +// Access-Accept/ +// EAP-Message/EAP-Success +// (other attributes) +// <- EAP-Success + pub async fn handle_eap_identity(&self, conn: &UdpSocket, req: &Request, ieap: &EAP ) -> Result { + // Verify Message-Authenticator from Identity Access-Request + let req_packet = req.get_packet(); + 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); + // Create response structure + let mut ac_packet = req_packet.make_response_packet(Code::AccessChallenge); + let mut eap = EAP::new(); + // let mut eap = ieap.clone(); + eap.code = EAPCode::Request; + eap.typ = EAPType::MD5Challenge; + eap.id = ieap.id; + // eap.data = b"challengeval".to_vec(); + eap.len = eap.recalc_len(); + rfc2869::add_eap_message(&mut ac_packet, &eap.to_bytes()[..]); + // Calculate final Message-Authenticator + let outgoing = MessageAuthenticator::for_response(&ac_packet); + // Apply final Message-Authenticator to outgoing buffer + 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) + } +} + +#[async_trait] +impl RequestHandler<(), io::Error> for MyRequestHandler { + async fn handle_radius_request( + &self, + conn: &UdpSocket, + req: &Request, + ) -> Result<(), io::Error> { + let req_packet = req.get_packet(); + // println!("Req:\n{:#?}", req_packet.clone()); + let maybe_user_name_attr = rfc2865::lookup_user_name(req_packet); + let maybe_user_password_attr = rfc2865::lookup_user_password(req_packet); + info!("maybe usr pass looked up"); + let maybe_eap_message = rfc2869::lookup_eap_message(req_packet); + info!("maybe eap looked up"); + let eap_message = match rfc2869::lookup_eap_message(req_packet) { + Some(e) => { + let ieap = EAP::from_bytes(&e); + println!("EAP Message: {}", &ieap); + match ieap.typ { + EAPType::Identity => { + let result = self.handle_eap_identity(conn, req, &ieap).await.unwrap(); + println!("Sent challenge-response {} bytes", &result); + } + _ => { + println!("EAPType not handled"); + } + } + ieap + }, + None => { + println!("No eap message found"); + EAP::new() + } + }; + let user_name = maybe_user_name_attr.unwrap().unwrap(); + let user_password = match maybe_user_password_attr { + Some(e) => match e { + Ok(m) => String::from_utf8(m).unwrap(), + Err(e) => { + error!("Could not decode user password due to:\n{}\n", e); + "".to_owned() + } + }, + None => { + info!("No user password found"); + "".to_owned() + } + }; + + let code = if user_name == "admin" && user_password == "p@ssw0rd" { + Code::AccessAccept + } else { + Code::AccessReject + }; + info!("response => {:?} to {}", code, req.get_remote_addr()); + + conn.send_to( + &req_packet.make_response_packet(code).encode().unwrap(), + req.get_remote_addr(), + ) + .await?; + Ok(()) + } +} + +struct MySecretProvider {} + +impl SecretProvider for MySecretProvider { + fn fetch_secret(&self, _remote_addr: SocketAddr) -> Result, SecretProviderError> { + let bs = b"somesecretval".to_vec(); + Ok(bs) + } +} diff --git a/radius/Cargo.toml b/radius/Cargo.toml index fb26ff3..58881d6 100644 --- a/radius/Cargo.toml +++ b/radius/Cargo.toml @@ -21,3 +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 new file mode 100644 index 0000000..4db6f0e --- /dev/null +++ b/radius/src/core/eap.rs @@ -0,0 +1,234 @@ +use std::fmt; +use std::convert::TryFrom; +use num_enum::TryFromPrimitive; + +///! From https://datatracker.ietf.org/doc/html/rfc2284 +/// +// A summary of the Request and Response packet format is shown below. +// The fields are transmitted from left to right. +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Code | Identifier | Length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Type | Type-Data ... +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- +// Code +// 1 for Request; +// 2 for Response. +// Identifier +// The Identifier field is one octet. The Identifier field MUST be +// the same if a Request packet is retransmitted due to a timeout +// while waiting for a Response. Any new (non-retransmission) +// Requests MUST modify the Identifier field. If a peer recieves a +// duplicate Request for which it has already sent a Response, it +// MUST resend it's Response. If a peer receives a duplicate Request +// before it has sent a Response to the initial Request (i.e. it's +// waiting for user input), it MUST silently discard the duplicate +// Request. +// Length +// The Length field is two octets and indicates the length of the EAP +// packet including the Code, Identifier, Length, Type, and Type-Data +// fields. Octets outside the range of the Length field should be +// treated as Data Link Layer padding and should be ignored on +// reception. +// Type +// The Type field is one octet. This field indicates the Type of +// Request or Response. Only one Type MUST be specified per EAP +// Request or Response. Normally, the Type field of the Response +// will be the same as the Type of the Request. However, there is +// also a Nak Response Type for indicating that a Request type is +// unacceptable to the peer. When sending a Nak in response to a +// Request, the peer MAY indicate an alternative desired +// authentication Type which it supports. An initial specification of +// Types follows in a later section of this document. +// Type-Data +// The Type-Data field varies with the Type of Request and the +// associated Response. +///! + +#[derive(Debug, Copy, Clone, PartialEq, TryFromPrimitive)] +#[repr(u8)] +pub enum EAPCode { + Request = 1, + Response = 2, + Success = 3, + Failure = 4, + Invalid = 0, +} + +impl EAPCode { + pub fn string(&self) -> &'static str { + match self { + EAPCode::Request => "EAP-Request", + EAPCode::Response => "EAP-Response", + EAPCode::Success => "EAP-Success", + EAPCode::Failure => "EAP-Failure", + EAPCode::Invalid => "EAP-Invalid", + } + } + pub fn from(value: u8) -> Self { + match EAPCode::try_from(value) { + Ok(code) => code, + Err(_) => EAPCode::Invalid, + } + } +} +// +// This section defines the initial set of EAP Types used in +// Request/Response exchanges. More Types may be defined in follow-on +// documents. The Type field is one octet and identifies the structure +// of an EAP Request or Response packet. The first 3 Types are +// considered special case Types. The remaining Types define +// authentication exchanges. The Nak Type is valid only for Response +// packets, it MUST NOT be sent in a Request. The Nak Type MUST only be +// sent in repsonse to a Request which uses an authentication Type code. +// All EAP implementatins MUST support Types 1-4. These Types, as well +// as types 5 and 6, are defined in this document. Follow-on RFCs will +// define additional EAP Types. + +// 1 Identity +// 2 Notification +// 3 Nak (Response only) +// 4 MD5-Challenge +// 5 One-Time Password (OTP) (RFC 1938) +// 6 Generic Token Card +// +#[derive(Debug, Copy, Clone, PartialEq, TryFromPrimitive)] +#[repr(u8)] +pub enum EAPType { + Identity = 1, + Notification = 2, + Nak = 3, + MD5Challenge = 4, + OneTimePass = 5, + TokenCard = 6, + Invalid = 0 +} + +impl EAPType { + pub fn string(&self) -> &'static str { + match self { + EAPType::Identity => "EAP-Identity", + EAPType::Notification => "EAP-Notification", + EAPType::Nak => "EAP-Nak", + EAPType::MD5Challenge => "EAP-MD5Challenge", + EAPType::OneTimePass => "EAP-OneTimePass", + EAPType::TokenCard => "EAP-TokenCard", + EAPType::Invalid => "EAP-Invalid", + } + } + pub fn from(value: u8) -> Self { + match EAPType::try_from(value) { + Ok(code) => code, + Err(_) => EAPType::Invalid, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct EAP { + pub code: EAPCode, + pub id: u8, + pub len: u16, + pub typ: EAPType, + pub data: Vec +} + +impl EAP { + /// Create an (invalid) EAP message structure + pub fn new() -> Self { + EAP { + code: EAPCode::from(0), + id: 0, + len: 5, // min size of fields with empty data + typ: EAPType::from(0), + data: vec![] + } + } + /// 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 = 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 } + } + /// Create wire-level byte structure from EAP message + pub fn to_bytes(&self) -> Vec { + let mut bytes = Vec::::new(); + bytes.push(self.code as u8); + bytes.push(self.id); + bytes.extend(u16::to_be_bytes(self.len)); + bytes.push(self.typ as u8); + bytes.extend(self.data.clone()); + return bytes + } + /// Provide updated value for length field based on current data + pub fn recalc_len(&self) -> u16 { + (5 + self.data.len()) as u16 + } +} + +impl fmt::Display for EAP { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Code: {}, ID: {}, Length: {}, Type: {}, Data Length: {}", + self.code.string(), self.id, self.len, self.typ.string(), self.data.len() + ) + } +} + +#[cfg(test)] +mod tests { + use hex; + use crate::core::eap::*; + + #[test] + fn it_should_decode_eap_id() -> Result<(), ()> { + let eap_bytes = hex::decode("027200090174657374").unwrap(); + let eap = EAP::from_bytes(&eap_bytes[..]); + assert_eq!(114, eap.id); + Ok(()) + } + #[test] + fn it_should_decode_eap_type() -> Result<(), ()> { + let eap_bytes = hex::decode("027200090174657374").unwrap(); + let eap = EAP::from_bytes(&eap_bytes[..]); + assert_eq!(EAPType::Identity, eap.typ); + Ok(()) + } + #[test] + fn it_should_decode_eap_code() -> Result<(), ()> { + let eap_bytes = hex::decode("027200090174657374").unwrap(); + let eap = EAP::from_bytes(&eap_bytes[..]); + assert_eq!(EAPCode::Response, eap.code); + 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[..]); + assert_eq!(eap_bytes, eap.to_bytes()); + Ok(()) + } +} \ No newline at end of file diff --git a/radius/src/core/message_authenticator.rs b/radius/src/core/message_authenticator.rs new file mode 100644 index 0000000..5f6c6ba --- /dev/null +++ b/radius/src/core/message_authenticator.rs @@ -0,0 +1,174 @@ +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() + ) + } + // 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) + // TODO: this is producing broken responses + pub fn for_response(pkt: &Packet) -> Self { + Self::from_packet( + // Use th RADIUS Request-Authenticator as the input + &Self::from_bytes(pkt.get_authenticator()) + .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 res = match rfc2869::lookup_message_authenticator(&pkt) { + Some(req_ma_bytes) => { + let mut req_bytes = pkt.encode().unwrap(); + let _ = Self::replace_slice(&mut req_bytes, &req_ma_bytes, &self.value[..]); + Packet::decode(&req_bytes, pkt.get_secret()).unwrap() + }, + None => { + let mut res = pkt.clone(); + rfc2869::add_message_authenticator(&mut res, &self.value); + res + } + }; + 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 Vec, from: &Vec, to: &[u8]) -> bool { + let mut found = false; + for i in 0..=buf.len() - from.len() { + if buf[i..].starts_with(from) { + buf[i..(i + from.len())].clone_from_slice(to); + found = true; + } + } + found + } +} + +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("019800436b2bbaa41b9081834827599838d2822001067465737404067f00010105060000000150127524cccba729c4ee2fa9f48c645a15294f0b022a00090174657374").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 b137f92..2774519 100644 --- a/radius/src/core/mod.rs +++ b/radius/src/core/mod.rs @@ -30,3 +30,5 @@ pub mod rfc6911; pub mod rfc7055; pub mod rfc7155; pub mod tag; +pub mod eap; +pub mod message_authenticator; diff --git a/radius/src/core/packet.rs b/radius/src/core/packet.rs index 5c9a718..e43d917 100644 --- a/radius/src/core/packet.rs +++ b/radius/src/core/packet.rs @@ -241,6 +241,19 @@ impl Packet { Ok(bs) } + pub fn create_respose_authenticator(response: &[u8], request: &[u8], secret: &[u8]) -> Vec { + md5::compute( + [ + &response[..4], + &request[4..RADIUS_PACKET_HEADER_LENGTH], + &response[RADIUS_PACKET_HEADER_LENGTH..], + &secret, + ] + .concat(), + ) + .to_vec() + } + /// Returns whether the Packet is authentic response or not. pub fn is_authentic_response(response: &[u8], request: &[u8], secret: &[u8]) -> bool { if response.len() < RADIUS_PACKET_HEADER_LENGTH @@ -249,18 +262,24 @@ impl Packet { { return false; } + Self::create_respose_authenticator(response, request,secret) + .eq(&response[4..RADIUS_PACKET_HEADER_LENGTH].to_vec()) + } + pub fn create_request_authenticator(request: &[u8], secret: &[u8]) -> Vec { md5::compute( [ - &response[..4], - &request[4..RADIUS_PACKET_HEADER_LENGTH], - &response[RADIUS_PACKET_HEADER_LENGTH..], + &request[..4], + &[ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ], + &request[RADIUS_PACKET_HEADER_LENGTH..], &secret, ] .concat(), ) .to_vec() - .eq(&response[4..RADIUS_PACKET_HEADER_LENGTH].to_vec()) } /// Returns whether the Packet is authentic request or not. @@ -271,20 +290,8 @@ impl Packet { match Code::from(request[0]) { Code::AccessRequest | Code::StatusServer => true, - Code::AccountingRequest | Code::DisconnectRequest | Code::CoARequest => md5::compute( - [ - &request[..4], - &[ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - ], - &request[RADIUS_PACKET_HEADER_LENGTH..], - &secret, - ] - .concat(), - ) - .to_vec() - .eq(&request[4..RADIUS_PACKET_HEADER_LENGTH].to_vec()), + Code::AccountingRequest | Code::DisconnectRequest | Code::CoARequest => Self::create_request_authenticator(request, secret) + .eq(&request[4..RADIUS_PACKET_HEADER_LENGTH].to_vec()), _ => false, } }