1use base64::{decode, encode};
2use failure::*;
3
4#[derive(Debug)]
5#[allow(non_camel_case_types)]
6pub enum EncryptionFormat {
7 GPG_KEY,
8}
9
10impl EncryptionFormat {
11 pub fn as_str(&self) -> String {
12 match *self {
13 EncryptionFormat::GPG_KEY => String::from("GPG_KEY"),
14 }
15 }
16 pub fn from_str(value: &str) -> Result<EncryptionFormat, failure::Error> {
17 match value {
18 "GPG_KEY" => Ok(EncryptionFormat::GPG_KEY),
19 _ => Err(format_err!("Unknown encryption format given: {}", value).into()),
20 }
21 }
22}
23
24pub struct UnsealedVault {
25 pub plain_secret: String,
26 pub format: EncryptionFormat,
27}
28
29pub trait SealableVault {
30 fn seal<F>(self, f: &F) -> Result<SealedVault, failure::Error>
31 where
32 F: Fn(UnsealedVault) -> Result<SealedVault, failure::Error>;
33}
34
35impl UnsealedVault {
36 pub fn new(plain_secret: String, format: EncryptionFormat) -> UnsealedVault {
37 UnsealedVault {
38 plain_secret,
39 format,
40 }
41 }
42}
43
44impl SealableVault for UnsealedVault {
45 fn seal<F>(self, f: &F) -> Result<SealedVault, failure::Error>
46 where
47 F: Fn(UnsealedVault) -> Result<SealedVault, failure::Error>,
48 {
49 f(self)
50 }
51}
52
53pub struct SealedVault {
54 pub secret: Vec<u8>,
55 pub format: EncryptionFormat,
56}
57
58pub trait OpenableVault {
59 fn unseal<F>(self, f: &F) -> Result<UnsealedVault, failure::Error>
60 where
61 F: Fn(SealedVault) -> Result<UnsealedVault, failure::Error>;
62 fn to_string(&self) -> String;
63}
64
65impl SealedVault {
66 pub fn new(secret: Vec<u8>, format: EncryptionFormat) -> SealedVault {
67 SealedVault { secret, format }
68 }
69}
70
71impl OpenableVault for SealedVault {
72 fn unseal<F>(self, f: &F) -> Result<UnsealedVault, failure::Error>
73 where
74 F: Fn(SealedVault) -> Result<UnsealedVault, failure::Error>,
75 {
76 f(self)
77 }
78
79 fn to_string(&self) -> String {
80 format!("CULPER.{}.{}", self.format.as_str(), encode(&self.secret),)
81 }
82}
83
84pub trait VaultHandler {
85 fn encrypt(&self, u: UnsealedVault) -> Result<SealedVault, failure::Error>;
86 fn decrypt(&self, s: SealedVault) -> Result<UnsealedVault, failure::Error>;
87}
88
89pub fn parse(value: &str) -> Result<SealedVault, failure::Error> {
90 let value_list: Vec<&str> = value.split('.').collect();
91 match value_list.as_slice() {
92 ["CULPER", encryption_format, secret_bytes] => Ok(SealedVault::new(
93 decode(secret_bytes).context("Failed to decode base64 payload")?,
94 EncryptionFormat::from_str(&encryption_format.to_string())?,
95 )),
96 _ => Err(format_err!("Could not parse string into Culper vault.")),
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn can_encrypt() {
106 let nuclear_codes =
107 UnsealedVault::new("zerozerozerozero".to_string(), EncryptionFormat::GPG_KEY);
108 let secret_nuclear_codes = nuclear_codes
109 .seal(&|vault: UnsealedVault| {
110 let secret = vault.plain_secret.chars().map(|c| match c {
111 'A'...'M' | 'a'...'m' => ((c as u8) + 13),
112 'N'...'Z' | 'n'...'z' => ((c as u8) - 13),
113 _ => c as u8,
114 });
115
116 Ok(SealedVault::new(secret.collect(), vault.format))
117 })
118 .unwrap();
119 assert_eq!(
120 "mrebmrebmrebmreb",
121 String::from_utf8(secret_nuclear_codes.secret).unwrap()
122 );
123 }
124}