culper-lib 0.1.0

library of common code for culper & culper-server
Documentation
use base64::{decode, encode};
use failure::*;

#[derive(Debug)]
#[allow(non_camel_case_types)]
pub enum EncryptionFormat {
    GPG_KEY,
}

impl EncryptionFormat {
    pub fn as_str(&self) -> String {
        match *self {
            EncryptionFormat::GPG_KEY => String::from("GPG_KEY"),
        }
    }
    pub fn from_str(value: &str) -> Result<EncryptionFormat, failure::Error> {
        match value {
            "GPG_KEY" => Ok(EncryptionFormat::GPG_KEY),
            _ => Err(format_err!("Unknown encryption format given: {}", value).into()),
        }
    }
}

pub struct UnsealedVault {
    pub plain_secret: String,
    pub format: EncryptionFormat,
}

pub trait SealableVault {
    fn seal<F>(self, f: &F) -> Result<SealedVault, failure::Error>
    where
        F: Fn(UnsealedVault) -> Result<SealedVault, failure::Error>;
}

impl UnsealedVault {
    pub fn new(plain_secret: String, format: EncryptionFormat) -> UnsealedVault {
        UnsealedVault {
            plain_secret,
            format,
        }
    }
}

impl SealableVault for UnsealedVault {
    fn seal<F>(self, f: &F) -> Result<SealedVault, failure::Error>
    where
        F: Fn(UnsealedVault) -> Result<SealedVault, failure::Error>,
    {
        f(self)
    }
}

pub struct SealedVault {
    pub secret: Vec<u8>,
    pub format: EncryptionFormat,
}

pub trait OpenableVault {
    fn unseal<F>(self, f: &F) -> Result<UnsealedVault, failure::Error>
    where
        F: Fn(SealedVault) -> Result<UnsealedVault, failure::Error>;
    fn to_string(&self) -> String;
}

impl SealedVault {
    pub fn new(secret: Vec<u8>, format: EncryptionFormat) -> SealedVault {
        SealedVault { secret, format }
    }
}

impl OpenableVault for SealedVault {
    fn unseal<F>(self, f: &F) -> Result<UnsealedVault, failure::Error>
    where
        F: Fn(SealedVault) -> Result<UnsealedVault, failure::Error>,
    {
        f(self)
    }

    fn to_string(&self) -> String {
        format!("CULPER.{}.{}", self.format.as_str(), encode(&self.secret),)
    }
}

pub trait VaultHandler {
    fn encrypt(&self, u: UnsealedVault) -> Result<SealedVault, failure::Error>;
    fn decrypt(&self, s: SealedVault) -> Result<UnsealedVault, failure::Error>;
}

pub fn parse(value: &str) -> Result<SealedVault, failure::Error> {
    let value_list: Vec<&str> = value.split('.').collect();
    match value_list.as_slice() {
        ["CULPER", encryption_format, secret_bytes] => Ok(SealedVault::new(
            decode(secret_bytes).context("Failed to decode base64 payload")?,
            EncryptionFormat::from_str(&encryption_format.to_string())?,
        )),
        _ => Err(format_err!("Could not parse string into Culper vault.")),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn can_encrypt() {
        let nuclear_codes =
            UnsealedVault::new("zerozerozerozero".to_string(), EncryptionFormat::GPG_KEY);
        let secret_nuclear_codes = nuclear_codes
            .seal(&|vault: UnsealedVault| {
                let secret = vault.plain_secret.chars().map(|c| match c {
                    'A'...'M' | 'a'...'m' => ((c as u8) + 13),
                    'N'...'Z' | 'n'...'z' => ((c as u8) - 13),
                    _ => c as u8,
                });

                Ok(SealedVault::new(secret.collect(), vault.format))
            })
            .unwrap();
        assert_eq!(
            "mrebmrebmrebmreb",
            String::from_utf8(secret_nuclear_codes.secret).unwrap()
        );
    }
}