Add attribute package

This commit is contained in:
moznion
2020-11-19 00:40:03 +09:00
parent 46eee46b8d
commit c386bce28e
4 changed files with 357 additions and 0 deletions

93
Cargo.lock generated
View File

@@ -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"

View File

@@ -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"

261
src/attribute.rs Normal file
View File

@@ -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<u8>);
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<u8>) -> 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<u8>, secret: &Vec<u8>, request_authenticator: &Vec<u8>) -> Result<Self, String> {
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<u8> = 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<Utc>) -> Self {
Attribute(u32::to_be_bytes(dt.timestamp() as u32).to_vec())
}
pub fn to_integer32(&self) -> Result<u32, String> {
const EXPECTED_SIZE: usize = std::mem::size_of::<u32>();
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, FromUtf8Error> {
String::from_utf8(self.0.to_vec())
}
pub fn to_bytes(&self) -> Vec<u8> {
self.0.to_vec()
}
pub fn to_ipv4(&self) -> Result<Ipv4Addr, String> {
const IPV4_SIZE: usize = std::mem::size_of::<Ipv4Addr>();
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<Ipv6Addr, String> {
const IPV6_SIZE: usize = std::mem::size_of::<Ipv6Addr>();
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<u8>, request_authenticator: &Vec<u8>) -> Result<Vec<u8>, 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<u8> = 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<DateTime<Utc>, String> {
let (int_bytes, _) = self.0.split_at(std::mem::size_of::<u32>());
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(())
}
}

View File

@@ -1 +1,2 @@
pub mod code;
pub mod attribute;