From c307bcbe89bf6532cc88180c8f8c3395db039443 Mon Sep 17 00:00:00 2001 From: RageLtMan Date: Thu, 28 Jul 2022 08:35:22 -0400 Subject: [PATCH 1/5] Initial Scaffold for EAP Messages Implement EAP struct with Enums defined from RFC2284 and RFC3579. Implement basic parsing of data into the struct and human-readable output of contents. --- examples/Cargo.toml | 4 + examples/eap_server.rs | 111 ++++++++++++++++++++++++ radius/src/core/eap.rs | 193 +++++++++++++++++++++++++++++++++++++++++ radius/src/core/mod.rs | 1 + 4 files changed, 309 insertions(+) create mode 100644 examples/eap_server.rs create mode 100644 radius/src/core/eap.rs 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..8605ac3 --- /dev/null +++ b/examples/eap_server.rs @@ -0,0 +1,111 @@ +#[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::request::Request; +use radius::core::eap::EAP; +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 {} + +#[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 maybe_eap_message { + Some(e) => EAP::from_bytes(&e), + None => { + println!("No eap message found"); + EAP::new() + } + }; + println!("EAP Message: {}", &eap_message); + let maybe_message_authenticator = rfc2869::lookup_message_authenticator(req_packet); + match maybe_message_authenticator { + Some(m) => println!("Found authenticator:\n{:#?}\n",m), + None => println!("No authenticator found") + } + + 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"secret".to_vec(); + Ok(bs) + } +} diff --git a/radius/src/core/eap.rs b/radius/src/core/eap.rs new file mode 100644 index 0000000..da3e644 --- /dev/null +++ b/radius/src/core/eap.rs @@ -0,0 +1,193 @@ +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. +///! +///! +///! From https://datatracker.ietf.org/doc/html/rfc3579#page-14 +// Where the NAS sends an EAP-Request/Identity as the initial packet, +// the exchange appears as follows: +// 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 +///! +#[derive(Debug, Copy, Clone, PartialEq, TryFromPrimitive)] +#[repr(u8)] +pub enum EAPCode { + Request = 1, + Response = 2, + Invalid = 0, +} + +impl EAPCode { + pub fn string(&self) -> &'static str { + match self { + EAPCode::Request => "EAP-Request", + EAPCode::Response => "EAP-Response", + 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 { + pub fn new() -> Self { + EAP { + code: EAPCode::from(0), + id: 0, + len: 0, + typ: EAPType::from(0), + data: vec![] + } + } + pub fn from_bytes(eap_bytes: &[u8]) -> Self { + 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 typ = EAPType::from(eap_bytes[4]); + let data = eap_bytes[5..len as usize].to_owned(); + EAP { code, id, len, typ, data } + } + fn len_from_bytes(bytes: &[u8]) -> u16 { + ((bytes[0] as u16) << 8) | bytes[1] 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() + ) + } +} \ No newline at end of file diff --git a/radius/src/core/mod.rs b/radius/src/core/mod.rs index b137f92..dbd8013 100644 --- a/radius/src/core/mod.rs +++ b/radius/src/core/mod.rs @@ -30,3 +30,4 @@ pub mod rfc6911; pub mod rfc7055; pub mod rfc7155; pub mod tag; +pub mod eap; From 7a08fe0c47ca16e04d033571b364a42a3364b6d7 Mon Sep 17 00:00:00 2001 From: RageLtMan Date: Thu, 28 Jul 2022 19:28:00 -0400 Subject: [PATCH 2/5] Enhance EAP processing Write basic field tests for EAP elements, implement a to_bytes() method, misc touchups. Testing: EAP parsing tests pass --- examples/eap_server.rs | 21 ++++++--- radius/Cargo.toml | 3 ++ radius/src/core/eap.rs | 104 +++++++++++++++++++++++++++-------------- 3 files changed, 86 insertions(+), 42 deletions(-) diff --git a/examples/eap_server.rs b/examples/eap_server.rs index 8605ac3..c208f9b 100644 --- a/examples/eap_server.rs +++ b/examples/eap_server.rs @@ -19,8 +19,11 @@ 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, MyRequestHandler {}, MySecretProvider {}) + let mut server = Server::listen("0.0.0.0", 1812, req_handler, MySecretProvider {}) .await .unwrap(); server.set_buffer_size(1500); // default value: 1500 @@ -40,7 +43,9 @@ async fn main() { } } -struct MyRequestHandler {} +struct MyRequestHandler { + secret_provider: MySecretProvider +} #[async_trait] impl RequestHandler<(), io::Error> for MyRequestHandler { @@ -65,10 +70,14 @@ impl RequestHandler<(), io::Error> for MyRequestHandler { }; println!("EAP Message: {}", &eap_message); let maybe_message_authenticator = rfc2869::lookup_message_authenticator(req_packet); - match maybe_message_authenticator { - Some(m) => println!("Found authenticator:\n{:#?}\n",m), - None => println!("No authenticator found") - } + 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 { diff --git a/radius/Cargo.toml b/radius/Cargo.toml index fb26ff3..33764ef 100644 --- a/radius/Cargo.toml +++ b/radius/Cargo.toml @@ -21,3 +21,6 @@ thiserror = "1.0" log = "0.4.14" tokio = { version = "1.6.1", features = ["full"] } async-trait = "0.1.50" + +[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 da3e644..cf34d96 100644 --- a/radius/src/core/eap.rs +++ b/radius/src/core/eap.rs @@ -1,6 +1,7 @@ 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. @@ -45,50 +46,25 @@ use num_enum::TryFromPrimitive; // The Type-Data field varies with the Type of Request and the // associated Response. ///! -///! -///! From https://datatracker.ietf.org/doc/html/rfc3579#page-14 -// Where the NAS sends an EAP-Request/Identity as the initial packet, -// the exchange appears as follows: -// 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 -///! + #[derive(Debug, Copy, Clone, PartialEq, TryFromPrimitive)] #[repr(u8)] pub enum EAPCode { - Request = 1, + Request = 1, Response = 2, - Invalid = 0, + Success = 3, + Failure = 4, + Invalid = 0, } impl EAPCode { pub fn string(&self) -> &'static str { match self { - EAPCode::Request => "EAP-Request", + EAPCode::Request => "EAP-Request", EAPCode::Response => "EAP-Response", - EAPCode::Invalid => "EAP-Invalid" + EAPCode::Success => "EAP-Success", + EAPCode::Failure => "EAP-Failure", + EAPCode::Invalid => "EAP-Invalid", } } pub fn from(value: u8) -> Self { @@ -160,6 +136,7 @@ pub struct EAP { } impl EAP { + /// Create an (invalid) EAP message structure pub fn new() -> Self { EAP { code: EAPCode::from(0), @@ -169,17 +146,37 @@ impl EAP { data: vec![] } } + /// Create an EAP message structure from a slice of bytes pub fn from_bytes(eap_bytes: &[u8]) -> Self { 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 typ = EAPType::from(eap_bytes[4]); - let data = eap_bytes[5..len as usize].to_owned(); + println!("{}", len); + 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(Self::len_to_bytes(self.len)); + bytes.push(self.typ as u8); + bytes.extend(self.data.clone()); + return bytes + } + + /// 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 { @@ -190,4 +187,39 @@ impl fmt::Display for EAP { 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_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(()) + } } \ No newline at end of file From 7a14eae7eaa8a711c029115dca5143150b4d8ecb Mon Sep 17 00:00:00 2001 From: RageLtMan Date: Thu, 28 Jul 2022 20:25:34 -0400 Subject: [PATCH 3/5] Message-Authenticator issues Moved the segments of code which produce validators for message authentication into their own methods, using them inside existing is_authentic_* methods in Packet. Implemented EAP generation length fixes. Added mashalling test. Implemented an MD5-Challenge response using the EAP message struct defined in earlier commits in the test server, but failing in the client-side with "Conflicting response authenticator for reply ..." This seems to indicate that either the Message-Authenticator value being produced is incorrect, or, more likely, that i am using the code incorrectly. Testing: Passes all tests currently defined --- examples/eap_server.rs | 57 ++++++++++++++++++++++++++++++++++++--- radius/src/core/eap.rs | 17 ++++++++---- radius/src/core/packet.rs | 43 ++++++++++++++++------------- 3 files changed, 90 insertions(+), 27 deletions(-) diff --git a/examples/eap_server.rs b/examples/eap_server.rs index c208f9b..8444f62 100644 --- a/examples/eap_server.rs +++ b/examples/eap_server.rs @@ -9,8 +9,9 @@ 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; +use radius::core::eap::{EAP, EAPType, EAPCode}; use radius::core::rfc2865; use radius::core::rfc2869; use radius::server::{RequestHandler, SecretProvider, SecretProviderError, Server}; @@ -47,6 +48,49 @@ struct MyRequestHandler { secret_provider: MySecretProvider } +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, req_packet: &Packet, conn: &UdpSocket, req: &Request, ieap: &EAP ) -> Result { + let mut eap = EAP::new(); //ieap.clone(); + let mut ac_packet = req_packet.make_response_packet(Code::AccessChallenge); + eap.code = EAPCode::Response; + eap.typ = EAPType::MD5Challenge; + 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 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( @@ -61,14 +105,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"); - let eap_message = match maybe_eap_message { - Some(e) => EAP::from_bytes(&e), + match maybe_eap_message { + 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); + ieap + }, None => { println!("No eap message found"); EAP::new() } }; - println!("EAP Message: {}", &eap_message); let maybe_message_authenticator = rfc2869::lookup_message_authenticator(req_packet); let message_authenticator = match maybe_message_authenticator { Some(m) => m.to_vec(), diff --git a/radius/src/core/eap.rs b/radius/src/core/eap.rs index cf34d96..0b78e72 100644 --- a/radius/src/core/eap.rs +++ b/radius/src/core/eap.rs @@ -141,7 +141,7 @@ impl EAP { EAP { code: EAPCode::from(0), id: 0, - len: 0, + len: 5, // min size of fields with empty data typ: EAPType::from(0), data: vec![] } @@ -151,12 +151,10 @@ impl EAP { let code = EAPCode::from(eap_bytes[0]); let id = eap_bytes[1].to_owned(); let len = Self::len_from_bytes(&eap_bytes[2..4]); - println!("{}", len); 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(); @@ -167,12 +165,14 @@ impl EAP { 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 + } /// 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] @@ -222,4 +222,11 @@ mod tests { assert_eq!("test".to_owned(), String::from_utf8(eap.data).unwrap()); Ok(()) } + + 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/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, } } From ae0382b5590d4c99793e55794752579a3b459a43 Mon Sep 17 00:00:00 2001 From: RageLtMan Date: Sun, 31 Jul 2022 07:18:07 -0400 Subject: [PATCH 4/5] 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; From 93d80f6777a6947a644cb3f628eb488458950378 Mon Sep 17 00:00:00 2001 From: RageLtMan Date: Thu, 4 Aug 2022 22:25:16 -0400 Subject: [PATCH 5/5] Incremental Message-Authenticator improvements Small amount of cleanup and progress on responding to an Identity Access-Request. Generating bad Message-Authenticator values in for_response, likely not grokking the requisite field value correctly --- examples/eap_server.rs | 25 ++++++++------ radius/src/core/message_authenticator.rs | 43 +++++++++++++++++------- 2 files changed, 46 insertions(+), 22 deletions(-) diff --git a/examples/eap_server.rs b/examples/eap_server.rs index ebf70b2..53360f6 100644 --- a/examples/eap_server.rs +++ b/examples/eap_server.rs @@ -70,7 +70,9 @@ impl MyRequestHandler { // EAP-Message/EAP-Success // (other attributes) // <- EAP-Success - pub async fn handle_eap_identity(&self, req_packet: &Packet, conn: &UdpSocket, req: &Request, ieap: &EAP ) -> Result { + 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 => { @@ -78,18 +80,21 @@ impl MyRequestHandler { MessageAuthenticator::new() } }; - // let validator = MessageAuthenticator::from_access_request(&req_packet); - // assert_eq!(validator, incoming); + 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); - rfc2869::add_message_authenticator(&mut ac_packet, &incoming.value[..]); - // let mut eap = EAP::new(); - let mut eap = ieap.clone(); + 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.data = b"challengeval".to_vec(); eap.len = eap.recalc_len(); rfc2869::add_eap_message(&mut ac_packet, &eap.to_bytes()[..]); - let outgoing = MessageAuthenticator::from_packet(&ac_packet); + // 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); @@ -118,7 +123,7 @@ impl RequestHandler<(), io::Error> for MyRequestHandler { println!("EAP Message: {}", &ieap); match ieap.typ { EAPType::Identity => { - let result = self.handle_eap_identity(&req_packet, conn, req, &ieap).await.unwrap(); + let result = self.handle_eap_identity(conn, req, &ieap).await.unwrap(); println!("Sent challenge-response {} bytes", &result); } _ => { @@ -167,7 +172,7 @@ struct MySecretProvider {} impl SecretProvider for MySecretProvider { fn fetch_secret(&self, _remote_addr: SocketAddr) -> Result, SecretProviderError> { - let bs = b"secret".to_vec(); + let bs = b"somesecretval".to_vec(); Ok(bs) } } diff --git a/radius/src/core/message_authenticator.rs b/radius/src/core/message_authenticator.rs index b078d4e..5f6c6ba 100644 --- a/radius/src/core/message_authenticator.rs +++ b/radius/src/core/message_authenticator.rs @@ -60,7 +60,6 @@ pub struct MessageAuthenticator { } impl MessageAuthenticator { - /// Create a new Message-Authenticator from a 16-byte slice pub fn from_bytes(bytes: &[u8]) -> Self { assert_eq!(bytes.len(), 16); @@ -90,26 +89,46 @@ impl MessageAuthenticator { &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 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(); + 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 [T], from: &[T], to: &[T]) - where - T: Clone + PartialEq, - { + 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 } } @@ -141,7 +160,7 @@ mod tests { } #[test] fn it_should_authenticate_other_access_request() -> Result<(), ()> { - let msg_bytes = hex::decode("013200497d39e88ba3783fc183b54d486c629e8501067465737404067f000101050600000001501208c9801fb78909a8b24ad4dc261b2d470706000000014f0b020200090174657374").unwrap(); + 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);