use eznacl::*;
use std::collections::HashMap;
use crate::base::*;
use crate::entry::*;
pub fn parse_entries(data: &str) -> Result<Vec<Entry>, LKCError> {
let mut out = Vec::<Entry>::new();
let mut card_type = String::from("");
let mut accumulator = Vec::<&str>::new();
let mut line_index: usize = 1;
for line in data.split("\r\n") {
let trimmed = line.trim();
if trimmed.len() == 0 {
line_index += 1;
continue;
}
if trimmed == "----- BEGIN ENTRY -----" {
accumulator.clear();
} else if line == "----- END ENTRY -----" {
let entry = match &*card_type {
"User" | "Organization" => Entry::from(&accumulator.join("\r\n"))?,
_ => return Err(LKCError::ErrInvalidKeycard),
};
out.push(entry);
} else {
let parts = trimmed.splitn(2, ":").collect::<Vec<&str>>();
if parts.len() != 2 {
return Err(LKCError::ErrBadFieldValue(String::from(trimmed)));
}
let field_name = match parts.get(0) {
Some(v) => v.clone(),
None => {
return Err(LKCError::ErrBadFieldValue(String::from(format!(
"Invalid line {}",
line_index
))))
}
};
if field_name == "Type" {
if card_type.len() > 0 {
if card_type != parts[1] {
return Err(LKCError::ErrBadFieldValue(String::from(
"entry type does not match keycard type",
)));
}
} else {
card_type = String::from(parts[1]);
}
}
accumulator.push(trimmed);
}
line_index += 1;
}
Ok(out)
}
#[derive(Debug)]
#[cfg_attr(feature = "use_serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Keycard {
_type: EntryType,
pub entries: Vec<Entry>,
}
impl Keycard {
pub fn new(t: EntryType) -> Keycard {
Keycard {
_type: t,
entries: Vec::<Entry>::new(),
}
}
pub fn from(data: &str) -> Result<Keycard, LKCError> {
let mut cardtype = EntryType::None;
let mut entries = parse_entries(data)?;
if entries.len() > 0 {
match entries[0].get_field("Index")?.as_str() {
"Organization" => cardtype = EntryType::Organization,
"User" => cardtype = EntryType::User,
_ => return Err(LKCError::ErrBadFieldValue(String::from("Type"))),
}
}
let mut out = Keycard::new(cardtype);
out.entries.append(&mut entries);
Ok(out)
}
pub fn find(&self, hash: &CryptoString) -> Result<&Entry, LKCError> {
for i in 0..self.entries.len() {
if self.entries[i].get_authstr("Hash")? == hash {
return Ok(&self.entries[i]);
}
}
Err(LKCError::ErrNotFound)
}
pub fn get_current(&self) -> Option<&Entry> {
let entrycount = self.entries.len();
if entrycount == 0 {
return None;
}
self.entries.get(&entrycount - 1)
}
pub fn get_current_mut(&mut self) -> Option<&mut Entry> {
let entrycount = self.entries.len();
if entrycount == 0 {
return None;
}
self.entries.get_mut(&entrycount - 1)
}
pub fn get_type(&self) -> EntryType {
self._type
}
pub fn get_owner(&self) -> Result<String, LKCError> {
let current = match self.get_current() {
Some(v) => v,
None => return Err(LKCError::ErrEmptyData),
};
current.get_owner()
}
pub fn get_text(&self) -> Result<String, LKCError> {
let mut parts = Vec::<String>::new();
for i in 0..self.entries.len() {
parts.push(format!(
"----- BEGIN ENTRY -----\r\n\
{}\
----- END ENTRY -----\r\n",
self.entries[i].get_full_text("")?
));
}
Ok(parts.join(""))
}
pub fn chain(
&mut self,
spair: &SigningPair,
expires: u16,
) -> Result<HashMap<&'static str, CryptoString>, LKCError> {
let (newentry, keys) = {
let entry = match self.get_current() {
Some(v) => v,
None => return Err(LKCError::ErrEmptyData),
};
match entry.get_field("Type")?.as_str() {
"Organization" | "User" => (),
_ => return Err(LKCError::ErrInvalidKeycard),
}
entry.chain(spair, expires)?
};
self.entries.push(newentry);
Ok(keys)
}
pub fn cross_sign(&mut self, signing_pair: &SigningPair) -> Result<(), LKCError> {
let entrycount = self.entries.len();
if entrycount == 0 {
return Err(LKCError::ErrEmptyData);
}
let current = self.get_current_mut().unwrap();
if current.get_field("Type")? != "User" {
return Err(LKCError::ErrTypeMismatch);
}
current.sign("Organization-Signature", &signing_pair)
}
pub fn user_sign(
&mut self,
hash_algorithm: &str,
signing_pair: &SigningPair,
) -> Result<(), LKCError> {
let entrycount = self.entries.len();
if entrycount == 0 {
return Err(LKCError::ErrEmptyData);
}
let current = self.get_current_mut().unwrap();
if current.get_field("Type")? != "User" {
return Err(LKCError::ErrTypeMismatch);
}
current.hash(hash_algorithm)?;
current.sign("User-Signature", &signing_pair)
}
pub fn verify(&self) -> Result<(), LKCError> {
match self.entries.len() {
0 => return Err(LKCError::ErrEmptyData),
1 => return Ok(()),
_ => (),
}
for i in 0..self.entries.len() - 1 {
self.entries[i + 1].verify_chain(&self.entries[i])?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::*;
#[test]
pub fn test_parse_entries() -> Result<(), LKCError> {
let testname = "test_parse_entries";
let card_data = "----- BEGIN ENTRY -----\r\n\
Type:Organization\r\n\
Index:1\r\n\
Name:Example, Inc.\r\n\
Domain:example.com\r\n\
Contact-Admin:c590b44c-798d-4055-8d72-725a7942f3f6/example.com\r\n\
Language:en\r\n\
Primary-Verification-Key:ED25519:r#r*RiXIN-0n)BzP3bv`LA&t4LFEQNF0Q@$N~RF*\r\n\
Encryption-Key:CURVE25519:SNhj2K`hgBd8>G>lW$!pXiM7S-B!Fbd9jT2&{{Az\r\n\
Time-To-Live:14\r\n\
Expires:20230818\r\n\
Timestamp:20220818T172640Z\r\n\
Hash:BLAKE3-256:ocU4XayQkNEHh-zHevRuX;YJmKp4AD2eo_R|9I31\r\n\
Organization-Signature:ED25519:Rlusb?3WRvd95Hc<aYat$GH2AszxNVvF8Hly&eYyqys0on&vx=tCAKbe~!owEi5HQnTafpEdoJ*F&`TZ\r\n\
----- END ENTRY -----\r\n\
----- BEGIN ENTRY -----\r\n\
Type:Organization\r\n\
Index:2\r\n\
Name:Example, Inc.\r\n\
Domain:example.com\r\n\
Contact-Admin:c590b44c-798d-4055-8d72-725a7942f3f6/example.com\r\n\
Language:en\r\n\
Primary-Verification-Key:ED25519:f!7Asqr9w7v=fsX@<?_s*}Btn>WfrgOZk?M)YOGr\r\n\
Secondary-Verification-Key:ED25519:r#r*RiXIN-0n)BzP3bv`LA&t4LFEQNF0Q@$N~RF*\r\n\
Encryption-Key:CURVE25519:Yoj)=FNDj>sc0U^JCypwu=W1~c!C`1^xlul{|GT`\r\n\
Time-To-Live:14\r\n\
Expires:20230818\r\n\
Timestamp:20220818T172640Z\r\n\
Custody-Signature:ED25519:N603IF=MJ-Se<!g+%3rx{mUlOp7}XqIwE<SGEhG~R;@(1gzM|V7lcXw++%NPGuS2zS@yRLpSxmc0oW-I\r\n\
Previous-Hash:BLAKE3-256:ocU4XayQkNEHh-zHevRuX;YJmKp4AD2eo_R|9I31\r\n\
Hash:BLAKE3-256:vg65eVmF~r#^zG*gwF2XIEl3*l;J>aB*iLGkN?m6\r\n\
Organization-Signature:ED25519:^O)N7oeF9Af)7fS{Kde_3hPcne{CLfmm3f{%w1`08xp9Df_v9Fc~zCe<k~-$_yzNA<I3*5&J_0<UlNE5\r\n\
----- END ENTRY -----\r\n";
let mut out = Keycard::new(EntryType::Organization);
let mut entries = match parse_entries(card_data) {
Ok(v) => v,
Err(e) => {
return Err(LKCError::ErrProgramException(format!(
"{}: failed to parse entries: {}",
testname,
e.to_string()
)))
}
};
out.entries.append(&mut entries);
Ok(())
}
}