mirror of
https://github.com/cubixle/radius-rs.git
synced 2026-04-24 21:24:43 +01:00
Add packet package
This commit is contained in:
166
Cargo.lock
generated
166
Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -9,3 +9,5 @@ edition = "2018"
|
||||
[dependencies]
|
||||
md5 = "0.7.0"
|
||||
chrono = "0.4"
|
||||
rand = "0.7.3"
|
||||
num_enum = "0.5.1"
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::string::FromUtf8Error;
|
||||
|
||||
use chrono::{DateTime, Utc, TimeZone};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Attribute(pub(crate) Vec<u8>);
|
||||
|
||||
impl Attribute {
|
||||
|
||||
@@ -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<AVP>);
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Attributes(pub(crate) Vec<AVP>);
|
||||
|
||||
impl Attributes {
|
||||
pub fn parse_attributes(bs: &Vec<u8>) -> Result<Attributes, String> {
|
||||
@@ -47,4 +49,38 @@ impl Attributes {
|
||||
attribute,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn attributes_encoded_len(&self) -> Result<u16, String> {
|
||||
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<u8>) -> Vec<u8> {
|
||||
let mut encoded: Vec<u8> = 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;
|
||||
}
|
||||
}
|
||||
|
||||
16
src/code.rs
16
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod code;
|
||||
pub mod attribute;
|
||||
pub mod attributes;
|
||||
pub mod packet;
|
||||
|
||||
180
src/packet.rs
Normal file
180
src/packet.rs
Normal file
@@ -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<u8>,
|
||||
secret: Vec<u8>,
|
||||
attributes: Attributes,
|
||||
}
|
||||
|
||||
impl Packet {
|
||||
pub fn new(code: &Code, secret: &Vec<u8>) -> Self {
|
||||
let mut rng = rand::thread_rng();
|
||||
let authenticator = (0..16).map(|_| rng.gen()).collect::<Vec<u8>>();
|
||||
Packet {
|
||||
code: code.to_owned(),
|
||||
identifier: rng.gen(),
|
||||
authenticator,
|
||||
secret: secret.to_owned(),
|
||||
attributes: Attributes(vec![]),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(bs: &Vec<u8>, secret: &Vec<u8>) -> Result<Self, String> {
|
||||
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<Vec<u8>, 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<u8> = 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<Vec<u8>, 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<u8> = 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<u8>, request: Vec<u8>, secret: Vec<u8>) -> 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<u8>, secret: Vec<u8>) -> 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<u8> = "xyzzy5461".as_bytes().to_vec();
|
||||
let request: Vec<u8> = 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(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user