From 3ffb8b75be53c222a7b27294d93093290b051510 Mon Sep 17 00:00:00 2001 From: moznion Date: Sun, 22 Nov 2020 01:00:32 +0900 Subject: [PATCH] Add packet package --- Cargo.lock | 166 +++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 2 + src/attribute.rs | 1 + src/attributes.rs | 38 +++++++++- src/code.rs | 16 ++++- src/lib.rs | 1 + src/packet.rs | 180 ++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 401 insertions(+), 3 deletions(-) create mode 100644 src/packet.rs diff --git a/Cargo.lock b/Cargo.lock index 0cd5581..2f7d369 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,6 +6,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "chrono" version = "0.4.19" @@ -19,6 +25,28 @@ dependencies = [ "winapi", ] +[[package]] +name = "derivative" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb582b60359da160a9477ee80f15c8d784c477e69c217ef2cdd4169c24ea380f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "getrandom" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + [[package]] name = "libc" version = "0.2.80" @@ -50,12 +78,127 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_enum" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "226b45a5c2ac4dd696ed30fa6b94b057ad909c7b7fc2e0d0808192bced894066" +dependencies = [ + "derivative", + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c0fd9eba1d5db0994a239e09c1be402d35622277e35468ba891aa5e3188ce7e" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +dependencies = [ + "proc-macro2", +] + [[package]] name = "radius-rs" version = "0.1.0" dependencies = [ "chrono", "md5", + "num_enum", + "rand", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + +[[package]] +name = "serde" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a" + +[[package]] +name = "syn" +version = "1.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", ] [[package]] @@ -65,10 +208,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ "libc", - "wasi", + "wasi 0.10.0+wasi-snapshot-preview1", "winapi", ] +[[package]] +name = "toml" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645" +dependencies = [ + "serde", +] + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index 189968f..619ecfe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,5 @@ edition = "2018" [dependencies] md5 = "0.7.0" chrono = "0.4" +rand = "0.7.3" +num_enum = "0.5.1" diff --git a/src/attribute.rs b/src/attribute.rs index 3ab80a7..7507658 100644 --- a/src/attribute.rs +++ b/src/attribute.rs @@ -4,6 +4,7 @@ use std::string::FromUtf8Error; use chrono::{DateTime, Utc, TimeZone}; +#[derive(Debug, Clone, PartialEq)] pub struct Attribute(pub(crate) Vec); impl Attribute { diff --git a/src/attributes.rs b/src/attributes.rs index 91cb6fd..b077c07 100644 --- a/src/attributes.rs +++ b/src/attributes.rs @@ -4,12 +4,14 @@ pub type Type = u8; pub const TYPE_INVALID: Type = 1; +#[derive(Debug, Clone, PartialEq)] pub struct AVP { typ: Type, attribute: Attribute, } -pub struct Attributes(Vec); +#[derive(Debug, Clone, PartialEq)] +pub struct Attributes(pub(crate) Vec); impl Attributes { pub fn parse_attributes(bs: &Vec) -> Result { @@ -47,4 +49,38 @@ impl Attributes { attribute, }) } + + pub fn attributes_encoded_len(&self) -> Result { + let mut n: u16 = 0; + for attr in &self.0 { + let attr_len = attr.attribute.0.len(); + if attr_len > 253 { + return Err("attribute is too large".to_owned()); + } + + n += 1 + 1 + (attr_len as u16); + } + + Ok(n) + } + + pub fn encode(&self, data: Vec) -> Vec { + let mut encoded: Vec = Vec::new(); + + for attr in &self.0 { + let attr_len = attr.attribute.0.len(); + if attr_len > 253 { + continue; + } + let size = 1 + 1 + attr_len; + + encoded = Vec::new(); + encoded.push(attr.typ); + encoded.push(size as u8); + encoded.extend(&attr.attribute.0); + encoded = encoded[size..].to_owned(); + } + + return encoded; + } } diff --git a/src/code.rs b/src/code.rs index b46d7bd..6f25881 100644 --- a/src/code.rs +++ b/src/code.rs @@ -1,4 +1,9 @@ -#[derive(Debug)] +use std::convert::TryFrom; + +use num_enum::TryFromPrimitive; + +#[derive(Debug, Clone, PartialEq, TryFromPrimitive)] +#[repr(u8)] pub enum Code { AccessRequest = 1, AccessAccept = 2, @@ -15,6 +20,7 @@ pub enum Code { CoAACK = 44, CoANAK = 45, Reserved = 255, + Invalid = 0, } impl Code { @@ -35,6 +41,14 @@ impl Code { Code::CoAACK => "CoA-ACK", Code::CoANAK => "CoA-NAK", Code::Reserved => "Reserved", + Code::Invalid => "Invalid", + } + } + + pub fn from(value: u8) -> Self { + match Code::try_from(value) { + Ok(code) => code, + Err(_) => Code::Invalid } } } diff --git a/src/lib.rs b/src/lib.rs index 282021d..50fb29a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ pub mod code; pub mod attribute; pub mod attributes; +pub mod packet; diff --git a/src/packet.rs b/src/packet.rs new file mode 100644 index 0000000..5b16d78 --- /dev/null +++ b/src/packet.rs @@ -0,0 +1,180 @@ +use std::convert::TryInto; +use std::io::Write; + +use rand::Rng; + +use crate::attributes::Attributes; +use crate::code::Code; + +const MAX_PACKET_LENGTH: usize = 4096; + +#[derive(Debug, Clone, PartialEq)] +pub struct Packet { + code: Code, + identifier: u8, + authenticator: Vec, + secret: Vec, + attributes: Attributes, +} + +impl Packet { + pub fn new(code: &Code, secret: &Vec) -> Self { + let mut rng = rand::thread_rng(); + let authenticator = (0..16).map(|_| rng.gen()).collect::>(); + Packet { + code: code.to_owned(), + identifier: rng.gen(), + authenticator, + secret: secret.to_owned(), + attributes: Attributes(vec![]), + } + } + + pub fn parse(bs: &Vec, secret: &Vec) -> Result { + if bs.len() < 20 { + return Err("radius packet doesn't have enough length of bytes; that has to be at least 20 bytes".to_owned()); + } + + let len = match bs[2..4].try_into() { + Ok(v) => u16::from_be_bytes(v), + Err(e) => return Err(e.to_string()), + } as usize; + if len < 20 || len > MAX_PACKET_LENGTH || bs.len() < len { + return Err("invalid radius packat lengt".to_owned()); + } + + let attributes = match Attributes::parse_attributes(&bs[20..len].to_vec()) { + Ok(attributes) => attributes, + Err(e) => return Err(e), + }; + + Ok(Packet { + code: Code::from(bs[0]), + identifier: bs[1], + authenticator: bs[4..20].to_owned(), + secret: secret.to_owned(), + attributes, + }) + } + + pub fn response(&self, code: &Code) -> Self { + Packet { + code: code.clone(), + identifier: self.identifier, + authenticator: self.authenticator.clone(), + secret: self.secret.clone(), + attributes: Attributes(vec![]), + } + } + + pub fn encode(&self) -> Result, String> { + let bs = match self.marshal_binary() { + Ok(bs) => bs, + Err(e) => return Err(e.to_string()), + }; + + match self.code { + Code::AccessRequest | Code::StatusServer => Ok(bs), + Code::AccessAccept | Code::AccessReject | Code::AccountingRequest | Code::AccessChallenge | Code::DisconnectRequest | Code::DisconnectACK | Code::DisconnectNAK | Code::CoARequest | Code::CoAACK | Code::CoANAK => { + // TODO length checking + let mut buf: Vec = bs[..4].to_vec(); + match self.code { + Code::AccountingRequest | Code::DisconnectRequest | Code::CoARequest => { + buf.extend(vec![ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]); + } + _ => { + buf.extend(self.authenticator.clone()); + } + } + buf.extend(bs[20..].to_vec()); + buf.extend(&self.secret); + Ok(md5::compute(buf).to_vec()) + } + _ => Err("unknown packet code".to_owned()) + } + } + + pub fn marshal_binary(&self) -> Result, String> { + let attributes_len = match self.attributes.attributes_encoded_len() { + Ok(attributes_len) => attributes_len, + Err(e) => return Err(e.to_string()) + }; + + let size = 20 + attributes_len; + if size as usize > MAX_PACKET_LENGTH { + return Err("packet is too large".to_owned()); + } + + let mut bs: Vec = Vec::new(); + bs.push(self.code.clone() as u8); + bs.push(self.identifier); + bs.extend(u16::to_be_bytes(size).to_vec()); + bs.extend(self.authenticator.to_vec()); + Ok(self.attributes.encode(bs)) + } + + pub fn is_authentic_response(response: Vec, request: Vec, secret: Vec) -> bool { + if response.len() < 20 || request.len() < 20 || secret.len() == 0 { + return false; + } + + md5::compute([ + &response[..4], + &request[4..20], + &response[20..], // TODO length + &secret, + ].concat()).to_vec().eq(&response[4..20].to_vec()) + } + + pub fn is_authentic_request(request: Vec, secret: Vec) -> bool { + if request.len() < 20 || secret.len() == 0 { + return false; + } + + match Code::from(request[0]) { + Code::AccessRequest | Code::StatusServer => true, + Code::AccountingRequest | Code::DisconnectRequest | Code::CoARequest => { + md5::compute([ + &request[..4], + &vec![ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + &request[20..], // TODO length + &secret, + ].concat()).to_vec().eq(&request[4..20].to_vec()) + } + _ => false + } + } +} + +#[cfg(test)] +mod tests { + use crate::code::Code; + use crate::packet::Packet; + + #[test] + fn test_for_rfc2865_7_1() -> Result<(), String> { + // ref: https://tools.ietf.org/html/rfc2865#section-7.1 + + let secret: Vec = "xyzzy5461".as_bytes().to_vec(); + let request: Vec = vec![ + 0x01, 0x00, 0x00, 0x38, 0x0f, 0x40, 0x3f, 0x94, 0x73, 0x97, 0x80, 0x57, 0xbd, 0x83, 0xd5, 0xcb, + 0x98, 0xf4, 0x22, 0x7a, 0x01, 0x06, 0x6e, 0x65, 0x6d, 0x6f, 0x02, 0x12, 0x0d, 0xbe, 0x70, 0x8d, + 0x93, 0xd4, 0x13, 0xce, 0x31, 0x96, 0xe4, 0x3f, 0x78, 0x2a, 0x0a, 0xee, 0x04, 0x06, 0xc0, 0xa8, + 0x01, 0x10, 0x05, 0x06, 0x00, 0x00, 0x00, 0x03, + ]; + + let packet = Packet::parse(&request, &secret)?; + assert_eq!(packet.code, Code::AccessRequest); + assert_eq!(packet.identifier, 0); + + // TODO + + Ok(()) + } +}