dat 1.1.0

DAT - Data Access Token
Documentation
use crate::crypto_algorithm::CryptoAlgorithm;
use crate::crypto_key::CryptoKey;
use crate::dat::DatPayload;
use crate::error::DatError;
use crate::signature_algorithm::SignatureAlgorithm;
use crate::signature_key::{SignatureKey, SignatureKeyOutOption};
use crate::util::{decode_base64_url_no_pad, encode_base64_url_no_pad, encode_base64_url_no_pad_out, get_dat_dot_indices, now_unix_timestamp, to_kid, to_utf8};
use std::fmt::Display;
use std::fmt::Write;
use std::str::FromStr;

pub type DatSplit<'a> = [&'a str; 5];
pub trait Kid: PartialEq + Display + FromStr + Clone {}

impl<T> Kid for T where T: PartialEq + Display + FromStr + Clone {}

pub const CONV_VERSION: &str = "2";

#[derive(Clone)]
pub struct DatKey<T: Kid> {
    pub(crate) kid: T,
    pub(crate) signature_key: SignatureKey,
    pub(crate) crypto_key: CryptoKey,
    pub(crate) issue_begin: i64,
    pub(crate) issue_end: i64,
    pub(crate) token_ttl: i64,
}

impl <T: Kid> DatKey<T> {
    pub fn generate(kid: T, signature_algorithm: SignatureAlgorithm, crypto_algorithm: CryptoAlgorithm, issue_begin: i64, issue_end: i64, token_ttl: i64) -> Result<Self, DatError> {
        Self::from(kid, SignatureKey::generate(signature_algorithm), CryptoKey::generate(crypto_algorithm), issue_begin, issue_end, token_ttl)
    }

    pub fn from(kid: T, signature_key: SignatureKey, crypto_key: CryptoKey, issue_begin: i64, issue_end: i64, token_ttl: i64) -> Result<Self, DatError> {
        if kid.to_string().contains(['.', '\r', '\n']) {
            return Err(DatError::InvalidDatKidFormat);
        }
        Ok(DatKey { kid, signature_key, crypto_key, issue_begin, issue_end, token_ttl })
    }

    pub fn kid(&self) -> T { self.kid.clone() }
    pub fn signature_key(&self) -> SignatureKey { self.signature_key.clone() }
    pub fn crypto_key(&self) -> CryptoKey { self.crypto_key.clone() }
    pub fn issue_begin(&self) -> i64 { self.issue_begin }
    pub fn issue_end(&self) -> i64 { self.issue_end }
    pub fn token_ttl(&self) -> i64 { self.token_ttl }
    pub fn key_expire(&self) -> i64 { self.issue_end + self.token_ttl }

    pub fn format(&self, signature_key_out_option: SignatureKeyOutOption) -> Result<String, DatError> {
        let kid = self.kid.to_string();
        let signature_algorithm = self.signature_key.algorithm().to_str();
        let (sk, vk) = self.signature_key.to_bytes();
        if sk.is_empty() && signature_key_out_option != SignatureKeyOutOption::VERIFYING {
            return Err(DatError::VerifyOnlyKey)
        }
        let signature_key = match signature_key_out_option {
            SignatureKeyOutOption::FULL => format!("{}~{}", encode_base64_url_no_pad(sk), encode_base64_url_no_pad(vk)),
            SignatureKeyOutOption::SIGNING => encode_base64_url_no_pad(sk),
            SignatureKeyOutOption::VERIFYING => format!("~{}", encode_base64_url_no_pad(vk)),
        };
        let crypto_algorithm = self.crypto_key.algorithm().to_str();
        let crypto_key = encode_base64_url_no_pad(self.crypto_key.to_bytes());
        let issue_begin = self.issue_begin;
        let issue_end = self.issue_end;
        let token_ttl = self.token_ttl;
        Ok(format!("{CONV_VERSION}.{kid}.{signature_algorithm}.{signature_key}.{crypto_algorithm}.{crypto_key}.{issue_begin}.{issue_end}.{token_ttl}"))
    }

    pub fn to_payload(&self, dat: &'_ str) -> Result<DatPayload, DatError> {
        self.to_payload_with_indices(dat, get_dat_dot_indices(dat)?)
    }
    pub fn to_payload_with_indices(&self, dat: &'_ str, di: [usize; 4]) -> Result<DatPayload, DatError> {
        let expire = dat[0 .. di[0]]
            .parse::<i64>()
            .map_err(|_| DatError::InvalidDatFormat)?;

        if expire < now_unix_timestamp() {
            return Err(DatError::InvalidDatFormat)
        }

        let kid = to_kid(&dat[di[0] + 1 .. di[1]])?;
        let plain_base64 = &dat[di[1] + 1 .. di[2]];
        let secure_base64 = &dat[di[2] + 1 .. di[3]];
        let sign = &dat[di[3] + 1 ..];
        if sign.is_empty() {
            return Err(DatError::InvalidDatFormat);
        }
        let body = &dat[0 ..di[3]];

        if self.kid != kid {
            return Err(DatError::InvalidDatFormat);
        }
        if self.signature_key.verify(body.as_bytes(), &*decode_base64_url_no_pad(sign)?).is_err() {
            return Err(DatError::InvalidDatFormat)
        }
        self.decode_payload_base64(plain_base64, secure_base64)
    }
    pub(crate) fn decode_payload_base64(&self, plain_base64: &str, secure_base64: &str) -> Result<DatPayload, DatError> {
        let plain = to_utf8(decode_base64_url_no_pad(plain_base64)?)?;
        let secure = to_utf8(self.crypto_key.decrypt(decode_base64_url_no_pad(secure_base64)?.as_slice())?)?;
        Ok((plain, secure))
    }
    pub fn to_dat<U: AsRef<[u8]>>(&self, plain: U, secure: U) -> Result<String, DatError> {
        let sk = &self.signature_key;
        // (byte size + 2) * 4 / 3 = base 64 size
        // 100 is padding : expire + kid + (dot * 4) + (base64 pad 12)...
        let mut dat = String::with_capacity(100 + ((plain.as_ref().len() + secure.as_ref().len() + sk.signature_size()) * 4 / 3));
        write!(dat, "{}.{}.", now_unix_timestamp() + self.token_ttl, self.kid).unwrap();
        encode_base64_url_no_pad_out(plain.as_ref(), &mut dat); // plain
        dat.push('.');
        encode_base64_url_no_pad_out(self.crypto_key.encrypt(secure.as_ref())?, &mut dat); // secure
        dat.push('.');
        encode_base64_url_no_pad_out(sk.sign(dat[0..dat.len() - 1].as_bytes()), &mut dat); // sign
        Ok(dat)
    }
}

impl <T: Kid> FromStr for DatKey<T> {
    type Err = DatError;
    fn from_str(format: &str) -> Result<Self, Self::Err> {
        let split = format.split(".").collect::<Vec<&str>>();
        match split[0] {
            "2" | "1" => {
                return if split.len() == 9 {
                    let kid = to_kid(split[1])?;
                    let signature_algorithm = SignatureAlgorithm::from_str(split[2])?;
                    let signature_key_str = split[3];
                    let signature_key = if let Some(pos) = signature_key_str.find('~') {
                        if pos == 0 { // verifying key only
                            SignatureKey::from_bytes(signature_algorithm, &[], &*decode_base64_url_no_pad(signature_key_str[1..].as_bytes())?)
                        } else { // signing key ~ verifying key
                            SignatureKey::from_bytes(signature_algorithm, &*decode_base64_url_no_pad(signature_key_str[..pos].as_bytes())?, &*decode_base64_url_no_pad(signature_key_str[pos + 1..].as_bytes())?)
                        }
                    } else { // signing key only
                        SignatureKey::from_bytes(signature_algorithm, &*decode_base64_url_no_pad(signature_key_str)?, &[])
                    }?;
                    let crypto_algorithm = CryptoAlgorithm::from_str(split[4])?;
                    let crypto_key = CryptoKey::from_bytes(crypto_algorithm, &*decode_base64_url_no_pad(split[5])?)?;
                    let issue_begin = split[6].parse::<i64>().map_err(|_| DatError::InvalidDatKeyFormat)?;
                    let issue_end = split[7].parse::<i64>().map_err(|_| DatError::InvalidDatKeyFormat)?;
                    let token_ttl = split[8].parse::<i64>().map_err(|_| DatError::InvalidDatKeyFormat)?;
                    DatKey::from(kid, signature_key, crypto_key, issue_begin, issue_end, token_ttl)
                } else {
                    Err(DatError::InvalidDatKeyFormat)
                }
            },
            _ => {}
        }
        Err(DatError::UnSupportDatKeyVersion)
    }
}

impl <T: Kid> PartialEq for DatKey<T> {
    fn eq(&self, other: &Self) -> bool {
        self.kid.eq(&other.kid)
    }
}