diff --git a/Cargo.lock b/Cargo.lock index d317a87..0cd5581 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,98 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi", +] + +[[package]] +name = "libc" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" + +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + [[package]] name = "radius-rs" version = "0.1.0" +dependencies = [ + "chrono", + "md5", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi", + "winapi", +] + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index f0978e9..189968f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,5 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +md5 = "0.7.0" +chrono = "0.4" diff --git a/src/attribute.rs b/src/attribute.rs new file mode 100644 index 0000000..37343e3 --- /dev/null +++ b/src/attribute.rs @@ -0,0 +1,261 @@ +use std::convert::TryInto; +use std::net::{Ipv4Addr, Ipv6Addr}; +use std::string::FromUtf8Error; + +use chrono::{DateTime, Utc, TimeZone}; + +pub struct Attribute(Vec); + +impl Attribute { + pub fn from_integer32(v: &u32) -> Self { + Attribute(u32::to_be_bytes(*v).to_vec()) + } + + pub fn from_string(v: &String) -> Self { + Attribute(v.as_bytes().to_vec()) + } + + pub fn from_bytes(v: &Vec) -> Self { + Attribute(v.to_vec()) + } + + pub fn from_ipv4(v: &Ipv4Addr) -> Self { + Attribute(v.octets().to_vec()) + } + + pub fn from_ipv6(v: &Ipv6Addr) -> Self { + Attribute(v.octets().to_vec()) + } + + pub fn from_user_password(plain_text: &Vec, secret: &Vec, request_authenticator: &Vec) -> Result { + if plain_text.len() > 128 { + return Err("the length of plain_text has to be within 128, but the given value is longer".to_owned()); + } + + if secret.len() <= 0 { + return Err("secret hasn't be empty, but the given value is empty".to_owned()); + } + + if request_authenticator.len() != 16 { + return Err("request_authenticator has to have 16-bytes payload, but the given value doesn't".to_owned()); + } + + let mut enc: Vec = Vec::new(); + + let digest = md5::compute([&secret[..], &request_authenticator[..]].concat()); + enc.extend(digest.to_vec()); + + let (head, _) = plain_text.split_at(16); + + let mut i = 0; + for b in head { + enc[i] ^= b; + i += 1; + } + + i = 16; + while i < plain_text.len() { + let digest = md5::compute([&secret[..], &enc[i - 16..i]].concat()); + enc.extend(digest.to_vec()); + + let mut j = 0; + for b in &plain_text[i..i + 16] { // TODO this has to be 16 bounds, is this correct? + enc[i + j] ^= b; + j += 1; + } + + i += 16; + } + + Ok(Attribute(enc)) + } + + pub fn from_date(dt: &DateTime) -> Self { + Attribute(u32::to_be_bytes(dt.timestamp() as u32).to_vec()) + } + + pub fn to_integer32(&self) -> Result { + const EXPECTED_SIZE: usize = std::mem::size_of::(); + if self.0.len() != EXPECTED_SIZE { + return Err("invalid attribute length for integer".to_owned()); + } + + let (int_bytes, _) = self.0.split_at(EXPECTED_SIZE); + match int_bytes.try_into() { + Ok(boxed_array) => Ok(u32::from_be_bytes(boxed_array)), + Err(e) => Err(e.to_string()), + } + } + + pub fn to_string(&self) -> Result { + String::from_utf8(self.0.to_vec()) + } + + pub fn to_bytes(&self) -> Vec { + self.0.to_vec() + } + + pub fn to_ipv4(&self) -> Result { + const IPV4_SIZE: usize = std::mem::size_of::(); + if self.0.len() != IPV4_SIZE { + return Err("invalid attribute length for ipv4 address".to_owned()); + } + + let (int_bytes, _) = self.0.split_at(IPV4_SIZE); + match int_bytes.try_into() { + Ok::<[u8; IPV4_SIZE], _>(boxed_array) => Ok(Ipv4Addr::from(boxed_array)), + Err(e) => Err(e.to_string()), + } + } + + pub fn to_ipv6(&self) -> Result { + const IPV6_SIZE: usize = std::mem::size_of::(); + if self.0.len() != IPV6_SIZE { + return Err("invalid attribute length for ipv6 address".to_owned()); + } + + let (int_bytes, _) = self.0.split_at(IPV6_SIZE); + match int_bytes.try_into() { + Ok::<[u8; IPV6_SIZE], _>(boxed_array) => Ok(Ipv6Addr::from(boxed_array)), + Err(e) => Err(e.to_string()), + } + } + + pub fn to_user_password(&self, secret: &Vec, request_authenticator: &Vec) -> Result, String> { + if self.0.len() < 16 || self.0.len() > 128 { + return Err(format!("invalid attribute length {}", self.0.len())); + } + + if secret.len() <= 0 { + return Err("secret hasn't be empty, but the given value is empty".to_owned()); + } + + if request_authenticator.len() != 16 { + return Err("request_authenticator has to have 16-bytes payload, but the given value doesn't".to_owned()); + } + + let mut dec: Vec = Vec::new(); + + let digest = md5::compute([&secret[..], &request_authenticator[..]].concat()); + dec.extend(digest.to_vec()); + + let (head, _) = self.0.split_at(16); + + let mut i = 0; + let mut maybe_first_zero_byte_idx = Option::None; + for b in head { + dec[i] ^= b; + if dec[i] == 0 && maybe_first_zero_byte_idx.is_none() { + maybe_first_zero_byte_idx = Option::Some(i) + } + i += 1; + } + + i = 16; + while i < self.0.len() { + let digest = md5::compute([&secret[..], &self.0[i - 16..i]].concat()); + dec.extend(digest.to_vec()); + + let mut j = 0; + for b in &self.0[i..i + 16] { // TODO this has to be 16 bounds, is this correct? + dec[i + j] ^= b; + if dec[i + j] == 0 && maybe_first_zero_byte_idx.is_none() { + maybe_first_zero_byte_idx = Option::Some(i + j) + } + j += 1; + } + + i += 16; + } + + match maybe_first_zero_byte_idx { + None => Ok(dec), + Some(idx) => Ok(dec[..idx].to_vec()) + } + } + + pub fn to_date(&self) -> Result, String> { + let (int_bytes, _) = self.0.split_at(std::mem::size_of::()); + match int_bytes.try_into() { + Ok(boxed_array) => { + let timestamp = u32::from_be_bytes(boxed_array); + Ok(Utc.timestamp(timestamp as i64, 0)) + } + Err(e) => Err(e.to_string()), + } + } +} + +#[cfg(test)] +mod tests { + use std::net::{Ipv4Addr, Ipv6Addr}; + use std::string::FromUtf8Error; + + use crate::attribute::Attribute; + use chrono::Utc; + + #[test] + fn it_should_convert_attribute_to_integer32() -> Result<(), String> { + assert_eq!(Attribute(vec![1, 2, 3, 4]).to_integer32()?, 16909060); + Ok(()) + } + + #[test] + fn it_should_convert_attribute_to_string() -> Result<(), FromUtf8Error> { + assert_eq!( + Attribute(vec![0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64]).to_string()?, + "Hello, World" + ); + Ok(()) + } + + #[test] + fn it_should_convert_ipv4() -> Result<(), String> { + let given_ipv4 = Ipv4Addr::new(192, 0, 2, 1); + let ipv4_attr = Attribute::from_ipv4(&given_ipv4); + assert_eq!( + ipv4_attr.to_ipv4()?, + given_ipv4, + ); + Ok(()) + } + + #[test] + fn it_should_convert_ipv6() -> Result<(), String> { + let given_ipv6 = Ipv6Addr::new(0x2001, 0x0db8, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001); + let ipv6_attr = Attribute::from_ipv6(&given_ipv6); + assert_eq!( + ipv6_attr.to_ipv6()?, + given_ipv6, + ); + Ok(()) + } + + #[test] + fn it_should_convert_user_password() { + let plain_text = b"texttexttexttexttexttexttexttext".to_vec(); + let secret = b"secret".to_vec(); + let request_authenticator = b"0123456789abcdef".to_vec(); + let user_password_attr_result = Attribute::from_user_password(&plain_text, &secret, &request_authenticator); + let user_password_attr = user_password_attr_result.unwrap(); + assert_eq!( + user_password_attr.0, + vec![0xb7, 0xb0, 0xcb, 0x5d, 0x4f, 0x96, 0xd4, 0x75, 0x1c, 0xea, 0x3a, 0xb6, 0xf, 0xc, 0xea, 0xa5, 0xc9, 0x22, 0xac, 0x26, 0x28, 0x23, 0x93, 0xef, 0x19, 0x67, 0xcc, 0xeb, 0x9d, 0x33, 0xd7, 0x46], + ); + assert_eq!( + user_password_attr.to_user_password(&secret, &request_authenticator).unwrap(), + plain_text, + ); + } + + #[test] + fn it_should_convert_date() -> Result<(), String> { + let now = Utc::now(); + let attr = Attribute::from_date(&now); + assert_eq!( + attr.to_date()?.timestamp(), + now.timestamp(), + ); + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 9de50d4..37d8952 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1,2 @@ pub mod code; +pub mod attribute;