use std::fmt;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use chrono::prelude::Local;
use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
use super::pubkey::PublicKey;
use crate::{error::Error, Result};
#[derive(Debug)]
pub enum AllowedSignerParsingError {
InvalidQuotes,
MissingPrincipals,
InvalidPrincipals,
MissingKey,
DuplicateOptions(String),
InvalidOption(String),
InvalidKey,
InvalidTimestamp,
InvalidTimestamps,
UnexpectedEnd,
}
impl fmt::Display for AllowedSignerParsingError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AllowedSignerParsingError::InvalidQuotes => write!(f, "error parsing quotes"),
AllowedSignerParsingError::MissingPrincipals => write!(f, "missing principals"),
AllowedSignerParsingError::InvalidPrincipals => write!(f, "invalid principals"),
AllowedSignerParsingError::MissingKey => write!(f, "missing public key data"),
AllowedSignerParsingError::DuplicateOptions(ref v) => write!(f, "option {} specified more than once", v),
AllowedSignerParsingError::InvalidOption(ref v) => write!(f, "invalid option {}", v),
AllowedSignerParsingError::InvalidKey => write!(f, "invalid public key"),
AllowedSignerParsingError::InvalidTimestamp => write!(f, "invalid timestamp"),
AllowedSignerParsingError::InvalidTimestamps => write!(f, "conflicting valid-before and valid-after options"),
AllowedSignerParsingError::UnexpectedEnd => write!(f, "unexpected data at the end"),
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct AllowedSigner {
pub principals: Vec<String>,
pub cert_authority: bool,
pub namespaces: Option<Vec<String>>,
pub valid_after: Option<i64>,
pub valid_before: Option<i64>,
pub key: PublicKey,
}
#[derive(Debug, PartialEq, Eq)]
pub struct AllowedSigners(pub Vec<AllowedSigner>);
impl AllowedSigner {
pub fn from_string(s: &str) -> Result<AllowedSigner> {
let mut tokenizer = AllowedSignerSplitter::new(s);
let principals = tokenizer.next(true)?
.ok_or(Error::InvalidAllowedSigner(AllowedSignerParsingError::MissingPrincipals))?;
let principals = principals.trim_matches('"');
let principals: Vec<String> = principals.split(',')
.map(|s| s.to_string())
.collect();
if principals.iter().any(|p| p.is_empty()) {
return Err(Error::InvalidAllowedSigner(AllowedSignerParsingError::InvalidPrincipals));
}
let mut cert_authority = false;
let mut namespaces = None;
let mut valid_after = None;
let mut valid_before = None;
let kt = loop {
let option = tokenizer.next(false)?
.ok_or(Error::InvalidAllowedSigner(AllowedSignerParsingError::MissingKey))?;
let (option_key, option_value) = match option.split_once('=') {
Some(v) => v,
None => (option.as_str(), ""),
};
let option_value = option_value.trim_matches('"');
if option_value.contains("\"") {
return Err(Error::InvalidAllowedSigner(AllowedSignerParsingError::InvalidQuotes));
}
match option_key.to_lowercase().as_str() {
"cert-authority" => cert_authority = true,
"namespaces" => {
if namespaces.is_some() {
return Err(
Error::InvalidAllowedSigner(AllowedSignerParsingError::DuplicateOptions("namespaces".to_string()))
);
}
let namespaces_value: Vec<&str> = option_value.split(',')
.filter(|e| !e.is_empty())
.collect();
namespaces = Some(
namespaces_value.iter()
.map(|s| s.to_string())
.collect()
);
},
"valid-after" => {
if valid_after.is_some() {
return Err(
Error::InvalidAllowedSigner(AllowedSignerParsingError::DuplicateOptions("valid-after".to_string()))
);
}
valid_after = Some(parse_timestamp(option_value)
.map_err(
|_| Error::InvalidAllowedSigner(AllowedSignerParsingError::InvalidOption("valid-after".to_string())))?
);
},
"valid-before" => {
if valid_before.is_some() {
return Err(
Error::InvalidAllowedSigner(AllowedSignerParsingError::DuplicateOptions("valid-before".to_string()))
);
}
valid_before = Some(parse_timestamp(option_value)
.map_err(
|_| Error::InvalidAllowedSigner(AllowedSignerParsingError::InvalidOption("valid-before".to_string())))?
);
},
_ => break option,
};
};
let key_data = tokenizer.next(false)?
.ok_or(Error::InvalidAllowedSigner(AllowedSignerParsingError::InvalidKey))?;
let key = PublicKey::from_string(format!("{} {}", kt, key_data).as_str())
.map_err(|_| Error::InvalidAllowedSigner(AllowedSignerParsingError::InvalidKey))?;
if let (Some(valid_before), Some(valid_after)) = (&valid_before, &valid_after) {
if valid_before <= valid_after {
return Err(
Error::InvalidAllowedSigner(AllowedSignerParsingError::InvalidTimestamps),
);
}
}
if !tokenizer.is_empty_after_trim() {
return Err(
Error::InvalidAllowedSigner(AllowedSignerParsingError::UnexpectedEnd),
);
}
Ok(AllowedSigner{
principals,
cert_authority,
namespaces,
valid_after,
valid_before,
key,
})
}
}
impl fmt::Display for AllowedSigner {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut output = String::new();
output.push_str(&self.principals.join(","));
if self.cert_authority {
output.push_str(" cert-authority");
}
if let Some(ref namespaces) = self.namespaces {
output.push_str(&format!(" namespaces={}", namespaces.join(",")));
}
if let Some(ref valid_after) = self.valid_after {
output.push_str(&format!(" valid-after={}", valid_after));
}
if let Some(ref valid_before) = self.valid_before {
output.push_str(&format!(" valid-before={}", valid_before));
}
output.push_str(&format!(" {}", self.key));
write!(f, "{}", output)
}
}
impl AllowedSigners {
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<AllowedSigners> {
let mut contents = String::new();
File::open(path)?.read_to_string(&mut contents)?;
AllowedSigners::from_string(&contents)
}
pub fn from_string(s: &str) -> Result<AllowedSigners> {
let mut allowed_signers = Vec::new();
for (line_number, line) in s.lines().enumerate() {
let line = line.trim();
if line.is_empty() || line.starts_with("#") {
continue;
}
let allowed_signer = match AllowedSigner::from_string(line) {
Ok(v) => v,
Err(Error::InvalidAllowedSigner(e)) => {
return Err(Error::InvalidAllowedSigners(e, line_number));
},
Err(_) => {
return Err(Error::ParsingError);
},
};
allowed_signers.push(allowed_signer);
}
Ok(AllowedSigners(allowed_signers))
}
}
struct AllowedSignerSplitter {
buffer: Vec<String>,
}
impl AllowedSignerSplitter {
fn new(s: &str) -> Self {
let mut buffer = Vec::new();
let mut last = 0;
for (index, matched) in s.match_indices([' ', '"', '#']) {
if last != index {
buffer.push(s[last..index].to_owned());
}
buffer.push(matched.to_owned());
last = index + matched.len();
}
if last < s.len() {
buffer.push(s[last..].to_owned());
}
buffer.reverse();
Self { buffer }
}
fn is_empty_after_trim(&mut self) -> bool {
self.trim();
return self.buffer.is_empty();
}
fn next(&mut self, opening_quotes_allowed: bool) -> Result<Option<String>> {
if self.is_empty_after_trim() {
return Ok(None);
}
if self.buffer[0] == "\"" {
if opening_quotes_allowed {
return self.split_quote().map(|v| Some(v));
} else {
return Err(Error::InvalidAllowedSigner(AllowedSignerParsingError::InvalidQuotes));
}
}
let mut s = String::new();
while let Some(last) = self.buffer.pop() {
if [" ", "\"", "#"].contains(&last.as_str()) {
self.buffer.push(last);
break;
}
s.push_str(&last);
}
if let Some(last) = self.buffer.last() {
if last == "\"" {
s.push_str(self.split_quote()?.as_str());
if let Some(last) = self.buffer.last() {
if ![" ", "#"].contains(&last.as_str()) {
return Err(Error::InvalidAllowedSigner(AllowedSignerParsingError::InvalidQuotes));
}
}
}
}
Ok(Some(s))
}
fn trim(&mut self) {
while let Some(last) = self.buffer.last(){
match last.as_str() {
" " => {
self.buffer.pop();
},
"#" => {
self.buffer.clear()
},
_ => break,
};
}
}
fn split_quote(&mut self) -> Result<String> {
match self.buffer.pop() {
Some(v) => {
if v != "\"" {
return Err(Error::InvalidAllowedSigner(AllowedSignerParsingError::InvalidQuotes));
}
},
None => return Err(Error::InvalidAllowedSigner(AllowedSignerParsingError::InvalidQuotes)),
}
let mut s = String::from("\"");
loop {
let token = self.buffer.pop()
.ok_or(Error::InvalidAllowedSigner(AllowedSignerParsingError::InvalidQuotes))?;
s.push_str(&token);
if token == "\"" {
break;
}
}
Ok(s)
}
}
fn parse_timestamp(s: &str) -> Result<i64> {
let mut s = s.trim_matches('"');
let is_utc = s.ends_with('Z');
if s.len() % 2 == 1 && !is_utc {
return Err(Error::InvalidAllowedSigner(AllowedSignerParsingError::InvalidTimestamp));
}
if is_utc {
s = s.trim_end_matches('Z');
}
let datetime = match s.len() {
8 => {
let date = NaiveDate::parse_from_str(s, "%Y%m%d")
.map_err(|_| Error::InvalidAllowedSigner(AllowedSignerParsingError::InvalidTimestamp))?;
date.and_time(NaiveTime::from_hms_opt(0, 0, 0).expect("initializing NaiveTime from constants should not fail"))
},
12 => {
NaiveDateTime::parse_from_str(s, "%Y%m%d%H%M")
.map_err(|_| Error::InvalidAllowedSigner(AllowedSignerParsingError::InvalidTimestamp))?
},
14 => {
NaiveDateTime::parse_from_str(s, "%Y%m%d%H%M%S")
.map_err(|_| Error::InvalidAllowedSigner(AllowedSignerParsingError::InvalidTimestamp))?
},
_ => return Err(Error::InvalidAllowedSigner(AllowedSignerParsingError::InvalidTimestamp)),
};
let timestamp = if is_utc {
datetime.and_utc()
.timestamp()
} else {
datetime.and_local_timezone(Local)
.unwrap()
.timestamp()
};
Ok(timestamp)
}