use eznacl::*;
use lazy_static::lazy_static;
use regex::Regex;
use crate::base::*;
use crate::types::*;
lazy_static! {
static ref INDEX_PATTERN: regex::Regex = Regex::new(r"^\d+$").unwrap();
static ref NAME_PATTERN: regex::Regex = Regex::new(r"\w+").unwrap();
static ref LANGUAGE_PATTERN: regex::Regex =
Regex::new(r"^[a-zA-Z]{2,3}(,[a-zA-Z]{2,3})*?$").unwrap();
}
pub fn verify_field(field: &str, s: &str) -> Result<(), LKCError> {
let trimmed = s.trim();
if trimmed != s {
return Err(LKCError::ErrBadFieldValue(String::from(field)));
}
if s.len() > 6144 {
return Err(LKCError::ErrOutOfRange);
}
match field {
"Type" => match s {
"User" | "Organization" | "None" => Ok(()),
_ => Err(LKCError::ErrBadFieldValue(String::from(field))),
},
"Index" => {
if INDEX_PATTERN.is_match(s) {
Ok(())
} else {
Err(LKCError::ErrBadFieldValue(String::from(field)))
}
}
"Name" => {
match s.chars().count() {
v if v > 0 && v < 65 => (),
_ => return Err(LKCError::ErrBadFieldValue(String::from(field))),
}
if !NAME_PATTERN.is_match(s) {
return Err(LKCError::ErrBadFieldValue(String::from(field)));
}
if CONTROL_CHARS_PATTERN.is_match(s) {
return Err(LKCError::ErrBadFieldValue(String::from(field)));
}
Ok(())
}
"Workspace-ID" => {
if !RANDOMID_PATTERN.is_match(s) {
Err(LKCError::ErrBadFieldValue(String::from(field)))
} else {
Ok(())
}
}
"User-ID" => {
match s.chars().count() {
v if v > 0 && v < 65 => (),
_ => return Err(LKCError::ErrBadFieldValue(String::from("User-ID"))),
}
if USERID_PATTERN.is_match(s) {
Ok(())
} else {
Err(LKCError::ErrBadFieldValue(String::from(field)))
}
}
"Domain" => {
if DOMAIN_PATTERN.is_match(s) {
Ok(())
} else {
Err(LKCError::ErrBadFieldValue(String::from(field)))
}
}
"Contact-Request-Verification-Key"
| "Contact-Request-Encryption-Key"
| "Encryption-Key"
| "Verification-Key"
| "Primary-Verification-Key"
| "Secondary-Verification-Key"
| "Revoke" => match CryptoString::from(s) {
Some(_) => Ok(()),
None => Err(LKCError::ErrBadFieldValue(String::from(field))),
},
"Time-To-Live" => {
if !INDEX_PATTERN.is_match(s) {
return Err(LKCError::ErrBadFieldValue(String::from(field)));
}
match s.parse::<u8>() {
Err(_) => Err(LKCError::ErrBadFieldValue(String::from(field))),
Ok(v) => {
if v > 0 && v < 31 {
Ok(())
} else {
Err(LKCError::ErrBadFieldValue(String::from(field)))
}
}
}
}
"Expires" => match chrono::NaiveDate::parse_from_str(s, "%Y%m%d") {
Ok(_) => Ok(()),
Err(_) => Err(LKCError::ErrBadFieldValue(String::from(field))),
},
"Timestamp" => match chrono::NaiveDateTime::parse_from_str(s, "%Y%m%dT%H%M%SZ") {
Ok(_) => Ok(()),
Err(_) => Err(LKCError::ErrBadFieldValue(String::from(field))),
},
"Language" => {
if LANGUAGE_PATTERN.is_match(s) {
Ok(())
} else {
Err(LKCError::ErrBadFieldValue(String::from(field)))
}
}
"Contact-Admin" | "Contact-Support" | "Contact-Abuse" => {
let parts = match s.split_once("/") {
Some(v) => v,
None => return Err(LKCError::ErrBadFieldValue(String::from(field))),
};
if RANDOMID_PATTERN.is_match(parts.0) && DOMAIN_PATTERN.is_match(parts.1) {
Ok(())
} else {
Err(LKCError::ErrBadFieldValue(String::from(field)))
}
}
_ => Err(LKCError::ErrUnsupportedField),
}
}
pub fn verify_authstr(astype: &str, cs: &CryptoString) -> Result<(), LKCError> {
let csstr = cs.to_string();
if csstr.trim() != cs.to_string() {
return Err(LKCError::ErrBadFieldValue(String::from(astype)));
}
match astype {
"Previous-Hash" | "Hash" => {
for ha in get_supported_hash_algorithms().iter() {
if cs.prefix() == ha {
return Ok(());
}
}
return Err(LKCError::ErrBadFieldValue(String::from(astype)));
}
"Custody-Signature" | "Organization-Signature" | "User-Signature" => {
for sa in get_supported_signing_algorithms().iter() {
if cs.prefix() == sa {
return Ok(());
}
}
return Err(LKCError::ErrBadFieldValue(String::from(astype)));
}
_ => Err(LKCError::ErrBadValue),
}
}
#[cfg(test)]
mod tests {
use crate::verifiers::*;
fn field_oktest(fieldname: &str, value: &str) -> Result<(), LKCError> {
match verify_field(fieldname, value) {
Ok(_) => Ok(()),
Err(e) => Err(LKCError::ErrProgramException(String::from(format!(
"verify_field returned an error on good value {} for field {}: {}",
value,
fieldname,
e.to_string()
)))),
}
}
fn field_failtest(fieldname: &str, value: &str) -> Result<(), LKCError> {
match verify_field(fieldname, value) {
Ok(_) => Err(LKCError::ErrProgramException(String::from(format!(
"verify_field failed to catch bad value {} for field {}",
value, fieldname
)))),
Err(_) => Ok(()),
}
}
fn authstr_oktest(fieldname: &str, value: &str) -> Result<(), LKCError> {
let cs = CryptoString::from(value).expect(&format!(
"Invalid string {} passed to authstr_oktest()",
value
));
match verify_authstr(fieldname, &cs) {
Ok(_) => Ok(()),
Err(e) => Err(LKCError::ErrProgramException(String::from(format!(
"verify_authstr returned an error on good value {} for authstr {}: {}",
value,
fieldname,
&e.to_string()
)))),
}
}
fn authstr_failtest(fieldname: &str, value: &str) -> Result<(), LKCError> {
let cs = match CryptoString::from(value) {
Some(v) => v,
None => return Ok(()),
};
match verify_authstr(fieldname, &cs) {
Ok(_) => Err(LKCError::ErrProgramException(String::from(format!(
"verify_authstr failed to catch bad value {} for authstr {}",
value, fieldname
)))),
Err(_) => Ok(()),
}
}
#[test]
fn test_verify_field() -> Result<(), LKCError> {
field_failtest("Foo", "bar")?;
field_oktest("Type", "User")?;
field_oktest("Type", "Organization")?;
field_oktest("Type", "None")?;
field_failtest("Type", "Foobar")?;
field_oktest("Index", "1")?;
field_oktest("Index", "100")?;
field_oktest("Index", "123456789")?;
field_failtest("Index", "")?;
field_failtest("Index", "Foobar")?;
field_failtest("Index", "-1")?;
field_oktest("Name", "Example, Inc.")?;
field_oktest("Name", "⭐WHEE⭐")?;
field_failtest("Name", "")?;
field_failtest("Name", &"A".repeat(70))?;
field_failtest("Name", " \t")?;
field_failtest("Name", " Leading/trailing spaces")?;
field_oktest("Workspace-ID", "11111111-aaaa-BBBB-4444-555555555555")?;
field_failtest("Workspace-ID", "")?;
field_failtest("Workspace-ID", "11111111aaaaBBBB4444555555555555")?;
field_oktest("User-ID", "csimons")?;
field_failtest("User-ID", "")?;
field_failtest("User-ID", &"A".repeat(70))?;
field_failtest("User-ID", " \t")?;
field_failtest("User-ID", "⭐WHEE⭐")?;
field_failtest("User-ID", "Leading/trailing spaces ")?;
field_failtest("User-ID", "EmbeddedControl\tCharacters")?;
field_oktest("Domain", "example.com")?;
field_oktest("Domain", "www.example.com")?;
field_oktest("Domain", "foo.www.example.com")?;
field_oktest("Domain", "localhost")?;
field_failtest("Domain", "csimons/example.com")?;
field_oktest(
"Encryption-Key",
"CURVE25519:@b?cjpeY;<&y+LSOA&yUQ&ZIrp(JGt{W$*V>ATLG",
)?;
field_failtest("Encryption-Key", "")?;
field_failtest("Encryption-Key", "CURVE25519:")?;
field_failtest("Encryption-Key", ":12345678901234567890")?;
field_oktest("Time-To-Live", "1")?;
field_oktest("Time-To-Live", "30")?;
field_failtest("Time-To-Live", "31")?;
field_failtest("Time-To-Live", "-1")?;
field_failtest("Time-To-Live", "1000")?;
field_oktest("Expires", "20240501")?;
field_failtest("Expires", "05012024")?;
field_failtest("Expires", "")?;
field_oktest("Timestamp", "20220501T120123Z")?;
field_failtest("Timestamp", "")?;
field_failtest("Timestamp", "20220501")?;
field_oktest("Language", "es")?;
field_oktest("Language", "de,fr,en")?;
field_failtest("Language", "en-US")?;
field_oktest(
"Contact-Admin",
"11111111-aaaa-BBBB-2222-3333eeeeffff/example.com",
)?;
field_oktest(
"Contact-Abuse",
"11111111-aaaa-BBBB-2222-3333eeeeffff/example.com",
)?;
field_oktest(
"Contact-Support",
"11111111-aaaa-BBBB-2222-3333eeeeffff/example.com",
)?;
Ok(())
}
#[test]
fn test_verify_authstr() -> Result<(), LKCError> {
authstr_oktest(
"Custody-Signature",
"ED25519:\
!i|YAkx94?xD%v7$E?P|EMAm$UnOl|dIg-z*olMSE@gb;hm%flES<VhPkUojV1LtQ^W%+UuVr%dT*B-H",
)?;
authstr_oktest(
"Organization-Signature",
"ED25519:\
!i|YAkx94?xD%v7$E?P|EMAm$UnOl|dIg-z*olMSE@gb;hm%flES<VhPkUojV1LtQ^W%+UuVr%dT*B-H",
)?;
authstr_oktest(
"User-Signature",
"ED25519:\
!i|YAkx94?xD%v7$E?P|EMAm$UnOl|dIg-z*olMSE@gb;hm%flES<VhPkUojV1LtQ^W%+UuVr%dT*B-H",
)?;
authstr_failtest("Custody-Signature", "ED25519:")?;
authstr_failtest(
"User-Signature",
":!i|YAkx94?xD%v7$E?P|EMAm$UnOl|dIg-z*olMSE@gb;hm%fl",
)?;
authstr_failtest("Organization-Signature", "")?;
authstr_failtest("Foo", "Bar")?;
authstr_oktest(
"Hash",
"BLAKE2B-256:_7!*bsd!3S!LGY~-F(8TAZP_gf{afIT~PH}CcCK+",
)?;
authstr_oktest(
"Previous-Hash",
"BLAKE2B-256:_7!*bsd!3S!LGY~-F(8TAZP_gf{afIT~PH}CcCK+",
)?;
authstr_failtest("Hash", "")?;
authstr_failtest("Previous-Hash", "")?;
authstr_oktest("Hash", "K12-256:TBn`x$Fg{o@KV|tYVC}t2|HGOu}$d&BEG_{YME%8")?;
authstr_oktest(
"Hash",
"BLAKE2B-256:_7!*bsd!3S!LGY~-F(8TAZP_gf{afIT~PH}CcCK+",
)?;
authstr_oktest(
"Hash",
"BLAKE2B-512:\
HCJl?n!oLOxnGr<&jtPCcrVN8&q!NGnLkBsbT$yZ>Nm1%<>93kr;&3yCj2VQYEG8t&!h(tS-IQKcGveU",
)?;
authstr_oktest(
"Hash",
"BLAKE3-256:2@{H9uT`<h3Y|4k9vriUC%mcbDWh&V%`oLY6T*o-",
)?;
authstr_oktest("Hash", "SHA-256:B4gOymCXJc)LnBdZmRX0GezcPb1`6Cv0AQD04@J@")?;
authstr_oktest(
"Hash",
"SHA-512:\
lTB;JOK!Baj3qQvvJ3b&<dWN0+_bP*+n8CKeZmr@ZZ$H<KiOzztCzD==B|iA@OO>%DCg>FD1`Ok@UETF",
)?;
authstr_oktest("Hash", "SHA3-256:B4qypIOay%>a>-&yOmNmdxI@)FJUO6YJidta>$4C")?;
authstr_oktest(
"Hash",
"SHA3-512:\
h(>d#^4MiHc%l>jVCZ8x8;SaC21#~W9+nzE3zof#91vv+wqOzJXG?oth6(`Hd2cj*MOU*;ZT{jDRBmC}",
)?;
Ok(())
}
}