chore: add source

This commit is contained in:
Jesús Pérez 2023-07-20 01:45:11 +01:00
parent 010f7a11c4
commit b8d6808789
4 changed files with 622 additions and 0 deletions

36
Cargo.toml Normal file
View File

@ -0,0 +1,36 @@
[package]
name = "pasetoken-lib"
authors = ["Jesus Perez <Jesus.Perez@tii.ae>"]
version = "0.1.0"
edition = "2021"
resolver = "2"
# For better Features Sync among project packages better use a tool like "configure -i" at project root
# Here you can enable and disable dependencies and change the crypto provider
# implementation used in SAL - just change the default features list
[features]
default = [ "log-flex" ]
log-flex = ["flexi_logger"]
log-trace = ["tracing","tracing-subscriber","tracing-appender"]
log-quiet = []
[dependencies]
log = { version = "0.4.19", features = ["max_level_trace","release_max_level_trace"], package = "log" }
# Log as log_flexi feature
flexi_logger = { version = "0.25.5", optional = true }
# Log as log_trace feature
tracing = { version = "0.1.37", optional = true }
tracing-subscriber = { version = "0.3.17", features = ["fmt","json"], optional = true }
tracing-appender = { version = "0.2.2", optional = true }
serde = { version = "1.0.171", features = ["derive"] }
serde_derive = "1.0.171"
serde_json = "1.0.103"
toml = "0.7.6"
pasetors = "0.6.7"
[dev-dependencies]
env_logger = "0.10.0"
test-log = "0.2.12"

39
src/lib.rs Normal file
View File

@ -0,0 +1,39 @@
pub mod pasetoken;
#[cfg(test)]
mod test_pasetoken;
use std::collections::HashMap;
pub use self::pasetoken::{ConfigPaSeToken, PaSeToken};
// pub use self::pasetoken::ConfigPaSeToken::{
// token_from_file_defs,
// from_content,
// make_footer,
// };
pub type BxDynResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;
pub fn generate_keys(path: &str, mode: bool) -> BxDynResult<()> {
let config_pasetoken: ConfigPaSeToken = ConfigPaSeToken::new(
String::from(""),
String::from(""),
mode,
String::from(""),
HashMap::new(),
HashMap::new(),
false
);
let pasetoken = config_pasetoken.pasetoken()?;
if mode {
pasetoken.to_path_bin(
&format!("{}/public.ky",path),
&format!("{}/secret.ky",path)
)
} else {
pasetoken.to_path(
&format!("{}/public.ky",path),
&format!("{}/secret.ky",path)
)
}
}

343
src/pasetoken.rs Normal file
View File

@ -0,0 +1,343 @@
use pasetors::{
claims::{Claims, ClaimsValidationRules},
paserk::FormatAsPaserk,
footer::Footer,
keys::{Generate, AsymmetricKeyPair, AsymmetricSecretKey, AsymmetricPublicKey},
public, Public, version4::V4,
token::{UntrustedToken, TrustedToken},
};
use core::convert::TryFrom;
use std::collections::HashMap;
use serde::{Serialize,Deserialize,Deserializer,Serializer};
use std::io;
use std::io::prelude::*;
use std::fs::File;
type BxDynResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;
fn read_byte_file(path: &str) -> io::Result<Vec<u8>> {
let mut f = File::open(path)?;
let mut buffer = Vec::new();
// read the whole file
f.read_to_end(&mut buffer)?;
Ok(buffer)
}
fn read_file(path: &str) -> io::Result<String> {
let content = std::fs::read_to_string(path)?;
Ok(content)
}
fn default_config_pasetoken_string() -> String {
String::from("")
}
fn default_config_pasetoken_footer() -> Footer {
Footer::new()
}
fn serialize_config_pasetoken_footer<S>(_f: &Footer, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer {
//let buf = String::deserialize(deserializer)?;
let res = serializer.serialize_str("")?;
Ok(res)
}
fn deserialize_config_pasetoken_footer<'de, D>(deserializer: D) -> Result<Footer, D::Error>
where D: Deserializer<'de> {
let _buf = String::deserialize(deserializer)?;
Ok(Footer::new())
}
/// Struct to collect settings for PaSeToken, `footer` will be loaded from `new` call
/// From `serde` use `from_content` with content-file it will collect settings and load `footer`
/// To create a `PaSeToken` object use `pasetoken`
#[derive(Clone, Serialize, Debug,Deserialize, Default)]
pub struct ConfigPaSeToken {
pub public_path: String,
#[serde(default = "default_config_pasetoken_string")]
pub public_data: String,
pub secret_path: String,
#[serde(default = "default_config_pasetoken_string")]
pub secret_data: String,
pub is_bin: bool,
pub assert_val: String,
pub map_footer: HashMap::<String,String>,
#[serde(default = "default_config_pasetoken_footer",
deserialize_with = "deserialize_config_pasetoken_footer",
serialize_with = "serialize_config_pasetoken_footer"
)]
pub footer: Footer,
pub data: HashMap::<String,String>,
pub expire: bool,
}
#[allow(unused)]
impl ConfigPaSeToken {
pub fn new(
public_path: String,
secret_path: String,
is_bin: bool,
assert_val: String,
map_footer: HashMap::<String,String>,
data: HashMap::<String,String>,
expire: bool,
) -> Self {
Self {
public_path: public_path.to_owned(),
public_data: String::from(""),
secret_path: secret_path.to_owned(),
secret_data: String::from(""),
is_bin,
assert_val: assert_val.to_owned(),
map_footer: map_footer.to_owned(),
data: data.to_owned(),
footer: Self::make_footer(map_footer.to_owned()).unwrap_or(Footer::new()),
expire,
}
}
pub fn load_data(&self) -> (String,String){
(
read_file(&self.public_path).unwrap_or(String::from("")),
read_file(&self.secret_path).unwrap_or(String::from("")),
)
}
pub fn pasetoken(&self) -> BxDynResult<PaSeToken> {
Ok(PaSeToken::new(self, &self.assert_val,&self.footer)?)
}
pub fn make_footer(map_footer: HashMap<String,String>) -> BxDynResult<Footer> {
if map_footer.len() > 0 {
let mut data_footer = Footer::new();
for (key, value) in map_footer.iter() {
data_footer.add_additional(key, value.as_str())?;
}
Ok(data_footer)
} else {
Ok(Footer::new())
}
}
pub fn from_content(content: &str, frmt: &str) -> BxDynResult<Self> {
let mut cfg: ConfigPaSeToken = match frmt {
"toml" => toml::from_str(&content).unwrap_or_else(|e| {
eprintln!("Error parse config parsetoken: {}",e);
ConfigPaSeToken::default()
}),
_ => serde_json::from_str(&content).unwrap_or_else(|e| {
eprintln!("Error parse config parsetoken: {}",e);
ConfigPaSeToken::default()
}),
};
cfg.footer = Self::make_footer(cfg.map_footer.to_owned()).unwrap_or(Footer::new());
(cfg.public_data, cfg.secret_data) = cfg.load_data();
Ok(cfg)
}
pub fn generate_token(&self, path: &str, data: &HashMap::<String,String>, expire: bool) -> BxDynResult<String> {
//dbg!(data);
let pasetoken = self.pasetoken()?;
let token = pasetoken.generate(data, expire)?;
if ! path.is_empty() {
std::fs::write(path, &token)?;
}
Ok(token)
}
pub fn token_from_file_defs(def_path: &str, out_path: &str) -> BxDynResult<String> {
let content = std::fs::read_to_string(&def_path)?;
let frmt = if def_path.ends_with(".toml") { "toml" } else { "json" };
let cfg_pasetoken = ConfigPaSeToken::from_content(&content, frmt)?;
cfg_pasetoken.generate_token(out_path, &cfg_pasetoken.data, cfg_pasetoken.expire)
}
}
pub struct PaSeToken<'a> {
claims: Claims,
kp: AsymmetricKeyPair<V4>,
footer: Option<&'a Footer>,
assert: Option<&'a[u8]>,
}
/// PaSeToken manager: new from `ConfigPaSeToken` + `Footer` .
#[allow(unused)]
impl<'a> PaSeToken<'a> {
pub fn new(config: &'a ConfigPaSeToken, assert_val: &'a str, op_footer: &'a Footer) -> BxDynResult<Self> {
// Setup the default claims, which include `iat` and `nbf` as the current time and `exp` of one hour.
// Generate claims
// Add a custom `data` claim as well.
let mut claims = Claims::new()?;
for (key, value) in config.data.iter() {
claims.add_additional(key, value.to_owned())?;
}
if ! config.expire {
claims.non_expiring();
}
// Generate the keys if are empty
let kp = if config.public_path.is_empty() || config.secret_path.is_empty() {
AsymmetricKeyPair::<V4>::generate()?
} else {
if config.is_bin {
let (loaded_public, loaded_secret) = PaSeToken::from_files_bin(&config.public_path, &config.secret_path)?;
let public: &[u8] = &loaded_public;
let secret: &[u8] = &loaded_secret;
AsymmetricKeyPair::<V4> {
public: AsymmetricPublicKey::<V4>::from(public)?,
secret: AsymmetricSecretKey::<V4>::from(secret)?,
}
} else {
let loaded_public: String;
let loaded_secret: String;
if config.public_data.is_empty() || config.secret_data.is_empty() {
(loaded_public,loaded_secret) = PaSeToken::from_files(&config.public_path, &config.secret_path)?;
} else {
loaded_public = config.public_data.to_owned();
loaded_secret = config.secret_data.to_owned();
};
AsymmetricKeyPair::<V4> {
public: AsymmetricPublicKey::<V4>::try_from(loaded_public.as_str())?,
secret: AsymmetricSecretKey::<V4>::try_from(loaded_secret.as_str())?,
}
}
};
let assert: Option<&'a[u8]> = if assert_val.len() > 0 {
Some(config.assert_val.as_bytes())
} else {
None
};
let footer = if config.map_footer.len() > 0 {
Some(op_footer)
} else {
None
};
Ok(Self {
claims,
kp,
footer: footer.to_owned(),
assert: assert.to_owned(),
})
}
/// Get public key as String
pub fn parserk_public(&self) -> String {
let mut paserk = String::new();
match self.kp.public.fmt(&mut paserk) {
Ok(_) => paserk,
Err(_) => String::from(";"),
}
}
/// Get secret key as String
pub fn parserk_secret(&self) -> String {
let mut paserk = String::new();
match self.kp.secret.fmt(&mut paserk) {
Ok(_) => paserk,
Err(_) => String::from(""),
}
}
/// Load keys from binary files
pub fn from_files_bin(public_path: &str, secret_path: &str) -> BxDynResult<(Vec<u8>,Vec<u8>)> {
let public: Vec<u8> = read_byte_file(&public_path)?;
let secret: Vec<u8> = read_byte_file(&secret_path)?;
Ok((public, secret))
}
/// Write keys to binary files
pub fn to_path_bin(&self, public_path: &str, secret_path: &str ) -> BxDynResult<()> {
if ! public_path.is_empty() {
let public = self.kp.public.as_bytes();
std::fs::write(public_path, public)?;
}
if ! secret_path.is_empty() {
let secret = self.kp.secret.as_bytes();
std::fs::write(secret_path, secret)?;
}
Ok(())
}
/// Load keys from text files
pub fn from_files(public_path: &str, secret_path: &str) -> BxDynResult<(String,String)> {
let public: String = read_file(&public_path)?;
let secret: String = read_file(&secret_path)?;
Ok((public, secret))
}
/// Write keys to text files
pub fn to_path(&self, public_path: &str, secret_path: &str ) -> BxDynResult<()> {
if ! public_path.is_empty() {
let public = self.parserk_public();
std::fs::write(public_path, public)?;
}
if ! secret_path.is_empty() {
let secret = self.parserk_secret();
std::fs::write(secret_path, secret)?;
}
Ok(())
}
// Generate a Token adding some data form `claims` with expire (true (1 hour)/false (forever))
pub fn generate(&self, data: &HashMap::<String,String>, expire: bool) -> BxDynResult<String> {
let claims = if data.is_empty() {
self.claims.to_owned()
} else {
let mut claims = Claims::new()?;
for (key, value) in data.iter() {
claims.add_additional(key, value.to_owned())?;
}
if ! expire {
claims.non_expiring();
}
claims
};
let pase_token = public::sign(
&self.kp.secret,
&claims,
self.footer,
self.assert
)?;
// println!("Pub Token: {}", &pase_token);
Ok(pase_token)
}
/// Get a `TrustedToken` from string and apply validation rules.
pub fn trusted(&self, token: &str, expire: bool) -> BxDynResult<TrustedToken> {
// Decide how we want to validate the claims after verifying the token itself.
// The default verifies the `nbf`, `iat` and `exp` claims. `nbf` and `iat` are always
// expected to be present.
// NOTE: Custom claims, defined through `add_additional()`, are not validated. This must be done
// manually.
let mut validation_rules = ClaimsValidationRules::new();
if ! expire {
validation_rules.allow_non_expiring();
}
let untrusted_token = UntrustedToken::<Public, V4>::try_from(token)?;
let trusted_token = public::verify(
&self.kp.public,
&untrusted_token,
&validation_rules,
self.footer,
self.assert
)?;
Ok(trusted_token)
}
/// Get `claims` with some additional data
fn get_claims(&self, data: &HashMap<String,String>, expire: bool) -> BxDynResult<Claims> {
if data.len() == 0 {
Ok(self.claims.to_owned())
} else {
let mut claims = Claims::new()?;
for (key, value) in data.iter() {
claims.add_additional(key, value.to_owned())?;
}
if ! expire {
claims.non_expiring();
}
Ok(claims)
}
}
/// Validate example over `TrustedToken` and data in `claims`
/// implement this by looking at `claims` from `trusted_token.payload_claims`
pub fn validate(&self, trusted_token: TrustedToken, data: &HashMap::<String,String>, expire: bool) -> bool {
let source_claims = if data.len() == 0 {
self.claims.to_owned()
} else {
self.get_claims(data, expire).unwrap_or(self.claims.to_owned())
};
if let Some(claims) = trusted_token.payload_claims() {
println!("{:?}", claims.get_claim("data"));
println!("{:?}", claims.get_claim("iat"));
if let Some(val) = claims.get_claim("data") {
if let Some(src_data) = source_claims.get_claim("data") {
val == src_data
} else {
false
}
} else {
false
}
} else {
false
}
}
}

204
src/test_pasetoken.rs Normal file
View File

@ -0,0 +1,204 @@
/// Tests using `test-log` dependency to capture `log` macros.
/// use command: cargo test -- --nocapture
#[cfg(test)]
mod tests {
use log::{info,error};
use test_log::test;
use crate::pasetoken::ConfigPaSeToken;
use std::collections::HashMap;
type BxDynResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;
pub fn test_token<'a>(
public_path: &str,
secret_path: &str,
mode: bool,
data: &HashMap<String,String>,
file_public: &str,
file_secret: &str
) -> BxDynResult<bool> {
let expire = true;
let config_pasetoken: ConfigPaSeToken = ConfigPaSeToken::new(
public_path.to_owned(),
secret_path.to_owned(),
mode,
String::from("implicit assertion"),
HashMap::new(),
data.to_owned(),
false
);
let pase_token = config_pasetoken.pasetoken()?;
let token = pase_token.generate(&data, expire).unwrap_or_else(|e|{
error!("Error generate token: {}",e);
String::from("")
});
println!("token: {}", &token);
let is_valid = match pase_token.trusted(&token, expire) {
Ok(trusted_token) => pase_token.validate(trusted_token, &data, expire),
Err(_e) => false,
};
info!("Is valid: {}", &is_valid);
// let h: PaSeToken = pase_token.try_into().unwrap();
if mode {
pase_token.to_path_bin(file_public, file_secret)?;
} else {
info!("parserk public: {}", pase_token.parserk_public());
info!("parserk secret: {}", pase_token.parserk_secret());
pase_token.to_path(file_public, file_secret)?;
}
Ok(is_valid)
}
pub fn clean_files(list: Vec<String>) {
for file in list {
match std::fs::remove_file(&file) {
Ok(_) => info!("File {} Removed", &file),
Err(e) => error!("Error removing file {}: {}", &file,e),
};
}
}
#[test]
pub fn chek_token_bin<'a>() {
let mut data = HashMap::new();
let public_path = String::from("/tmp/bin_public_bin");
let secret_path = String::from("/tmp/bin_secret_bin");
data.insert("data".to_string(),"A public, signed message".to_string());
let mode = true; // to save files as binary
match test_token("","", mode, &data, &public_path, &secret_path) {
Ok(_) =>
match test_token(&public_path, &secret_path, mode, &data, "", "") {
Ok(_) => assert!(true),
Err(e) => {
error!("Error: {}",e);
assert!(false);
},
},
Err(e) => {
error!("Error: {}",e);
assert!(false);
},
}
clean_files(vec![public_path, secret_path]);
}
#[test]
pub fn chek_token_val() {
let mut data = HashMap::new();
let public_path = String::from("/tmp/public");
let secret_path = String::from("/tmp/secret");
data.insert("data".to_string(),"A public, signed message".to_string());
let mode = false; // to save files as parserk
match test_token("","", mode, &data, &public_path, &secret_path) {
Ok(_) =>
match test_token(&public_path, &secret_path, mode, &data, "", "") {
Ok(_) => assert!(true),
Err(e) => {
error!("Error: {}",e);
assert!(false);
},
},
Err(e) => {
error!("Error: {}",e);
assert!(false);
},
}
clean_files(vec![public_path, secret_path]);
}
#[test]
pub fn serde_json() {
let mut data = HashMap::new();
let public_path = String::from("/tmp/public");
let secret_path = String::from("/tmp/secret");
data.insert("data".to_string(),"A public, signed message".to_string());
let mode = false; // to save files as parserk
let config_pasetoken: ConfigPaSeToken = ConfigPaSeToken::new(
public_path.to_owned(),
secret_path.to_owned(),
mode,
String::from("implicit assertion"),
HashMap::new(),
data.to_owned(),
false
);
match serde_json::to_string(&config_pasetoken) {
Ok(res) => {
println!("{}",&res);
match serde_json::from_str::<ConfigPaSeToken>(&res) {
Ok(_) => assert!(true),
Err(e) => {
error!("Error: {}",e);
assert!(false);
},
};
},
Err(e) => {
error!("Error: {}",e);
assert!(false);
},
}
}
#[test]
pub fn from_content() {
let mut data = HashMap::new();
let public_path = String::from("/tmp/public");
let secret_path = String::from("/tmp/secret");
data.insert("data".to_string(),"A public, signed message".to_string());
let mode = false; // to save files as parserk
let config_pasetoken: ConfigPaSeToken = ConfigPaSeToken::new(
public_path.to_owned(),
secret_path.to_owned(),
mode,
String::from("implicit assertion"),
HashMap::new(),
data.to_owned(),
false
);
match serde_json::to_string(&config_pasetoken) {
Ok(res) =>
match ConfigPaSeToken::from_content(&res, "json") {
Ok(_) => assert!(true),
Err(e) => {
error!("Error: {}",e);
assert!(false);
},
},
Err(e) => {
error!("Error: {}",e);
assert!(false);
},
}
}
#[test]
pub fn token_from_file_defs() {
let config_pasetoken: ConfigPaSeToken = ConfigPaSeToken::new(
String::from(""),
String::from(""),
false,
String::from("implicit assertion"),
HashMap::new(),
HashMap::new(),
false
);
match serde_json::to_string(&config_pasetoken) {
Ok(content) =>
match ConfigPaSeToken::from_content(&content, "json") {
Ok(cfg) => {
let token = cfg.generate_token(
"",
&cfg.data,
cfg.expire
).unwrap_or_else(|e|{
eprintln!("Token generation error: {}",e);
String::from("")
});
assert_eq!(token.is_empty(),false);
},
Err(e) => {
error!("Error config from content: {}",e);
assert!(false);
},
},
Err(e) => {
error!("Error paseteoken config: {}",e);
assert!(false);
},
};
}
}