Skip to content

Commit

Permalink
Scaffold EAP Message Authenticator with HMACMD5
Browse files Browse the repository at this point in the history
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
  • Loading branch information
RageLtMan committed Aug 4, 2022
1 parent 7a14eae commit ae0382b
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 38 deletions.
58 changes: 31 additions & 27 deletions examples/eap_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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
Expand All @@ -44,9 +42,7 @@ async fn main() {
}
}

struct MyRequestHandler {
secret_provider: MySecretProvider
}
struct MyRequestHandler {}

impl MyRequestHandler {
// Authenticating peer NAS RADIUS server
Expand Down Expand Up @@ -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<usize, io::Error> {
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)
}
Expand All @@ -105,29 +112,26 @@ 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 => {
println!("No eap message found");
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 {
Expand Down
2 changes: 2 additions & 0 deletions radius/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
24 changes: 13 additions & 11 deletions radius/src/core/eap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -160,7 +163,7 @@ impl EAP {
let mut bytes = Vec::<u8>::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
Expand All @@ -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 {
Expand Down Expand Up @@ -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[..]);
Expand Down
155 changes: 155 additions & 0 deletions radius/src/core/message_authenticator.rs
Original file line number Diff line number Diff line change
@@ -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<Md5>;

///!
// 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<Packet, std::io::Error> {
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<T>(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(())
}
}
1 change: 1 addition & 0 deletions radius/src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ pub mod rfc7055;
pub mod rfc7155;
pub mod tag;
pub mod eap;
pub mod message_authenticator;

0 comments on commit ae0382b

Please sign in to comment.