1use std::{
5 env::{self, VarError},
6 error::Error,
7 fs::File,
8 io::{self, Read, Write},
9 path::Path,
10 process::{Child, Command, Stdio},
11};
12
13pub enum SecretType {
15 Env(String),
17 File(String),
19 String(String),
21}
22
23pub enum DecryptMethod<'a> {
25 None,
27 GPG,
28 Custom(&'a mut Command),
30}
31
32pub fn read_secret(stype: SecretType, dm: &mut DecryptMethod) -> Result<String, Box<dyn Error>> {
34 let estr = match stype {
35 SecretType::Env(name) => read_env(&name).map_err(|e| Box::new(e) as Box<dyn Error>)?,
36 SecretType::File(path) => read_file(&path).map_err(|e| Box::new(e) as Box<dyn Error>)?,
37 SecretType::String(secret) => secret,
38 };
39 decrypt(estr, dm).map_err(|e| Box::new(e) as Box<dyn Error>)
40}
41
42pub fn read_env(env_name: &str) -> Result<String, VarError> {
45 env::var(env_name)
46}
47
48pub fn read_file(path: &str) -> io::Result<String> {
50 let path = Path::new(path);
51 let mut buf = String::new();
52 File::open(path)?.read_to_string(&mut buf)?;
53 Ok(buf)
54}
55
56fn decrypt(estr: String, dm: &mut DecryptMethod) -> io::Result<String> {
58 match dm {
59 DecryptMethod::None => Ok(estr),
60 DecryptMethod::GPG => {
61 let gpg = Command::new("gpg")
62 .args(["--no-tty", "-q", "-d", "-a"])
63 .stdin(Stdio::piped())
64 .stdout(Stdio::piped())
65 .spawn()
66 .expect("Failed to spawn `gpg`: please ensure you have it installed.");
67 let res = get_command_output(gpg, estr)?;
68 let res = res.chars().take(res.len() - 1).collect();
70 Ok(res)
71 }
72 DecryptMethod::Custom(command) => {
73 let command = command
74 .stdin(Stdio::piped())
75 .stdout(Stdio::piped())
76 .spawn()
77 .expect("Failed to spawn command");
78 get_command_output(command, estr)
79 }
80 }
81}
82
83fn get_command_output(mut command: Child, input: String) -> io::Result<String> {
84 let mut stdin = (&mut command).stdin.take().expect("Failed to take stdin");
85 stdin.write_all(input.as_bytes())?;
86 drop(stdin);
87 let output = command.wait_with_output()?;
88 let res = String::from_utf8(output.stdout).expect("Cannot convert utf8 bytes into String.");
89 Ok(res)
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 const LIB_VERSION: &str = "0.1.0";
96
97 #[test]
98 fn test_read_secret() -> Result<(), Box<dyn Error>> {
99 let mut dm = DecryptMethod::None;
100 let st = SecretType::Env("CARGO_PKG_VERSION".to_string());
101 let sr = read_secret(st, &mut dm)?;
102 assert_eq!(LIB_VERSION, sr);
103 Ok(())
104 }
105
106 #[test]
107 fn test_read_env() -> Result<(), VarError> {
108 let s = read_env("CARGO_PKG_VERSION")?;
110 assert_eq!(LIB_VERSION, s);
111 let failed = read_env("UNEXISTED_VALUE");
113 assert!(failed.is_err());
114 Ok(())
115 }
116
117 #[test]
118 fn test_read_file() -> io::Result<()> {
119 let sl = "El Psy Kongaroo";
120 let sr = read_file("/home/zarkli/projects/rust/read-secret/tests/pass_0")?;
121 assert_eq!(sl, sr);
122 let sr = read_file("tests/pass_0")?;
123 assert_eq!(sl, sr);
124 Ok(())
125 }
126
127 #[test]
128 fn test_decrypt() -> io::Result<()> {
129 let sl = "El Psy Kongaroo";
130 let mut dm = DecryptMethod::None;
132 let sr = sl;
133 let sr = decrypt(sr.to_string(), &mut dm)?;
134 assert_eq!(sr, sl);
135
136 let mut dm = DecryptMethod::GPG;
138 let sr = "-----BEGIN PGP MESSAGE-----
139
140hQGMAw4MNp4TmOFvAQwAuXN8xO+ca+Bz8bEFqnEB8cuxKYd0rCLa7UqN446DLnbj
1410g5IqyfhCgzNgbMN9LN3pYALwPrNEw6bSK6QoOn3ZtCOQKRSjH1WprRGUx3Fc+dO
142gDy8B79twcQyPFsy+3PbfDgQjxciNGuCXKBEp/cr+QFjAgX+wPTmoYv3xZGLHX5G
143tAsE9bB00AeyUdedDbn+V1YUW8mTZko4JtvXst3pRhaBHlina+MdaoFaQLzAhN0A
144jaVMrrm/L+WWAvrbdJvs8ew7QprENch2J0rXT5BY9tL8QRTnTLqrczQXtCLXMdk6
145nELkVDEvj/FloVKgGK10wj62eRgIp2eZOxY5GRkB6U8VEuDVzy9ryNWm8qiachsB
146GhibFWgjXOGxq/kEcmwZbzOC01KuqiGpI0MiGz3492detV2K2YGXsgbRZUwxb44B
147X0MFE43HySxRGdYZ7Q5E0HClTQedNw1YCo82DpELheGA9GOe1taQjX+gd98h5Fp0
148fmvb8an9JMgXwJDb0EHO0ksB6CaAfueLAR4sL8OpmUbeVg/kJv9fgvkHXvMMnFp+
149BafOEV3SWmeMynyfYk2g12wtph+Jm9EDq3PLokHAlfp0EYRqTSF0VDaHYdw=
150=LM7a
151-----END PGP MESSAGE-----";
152 let sr = decrypt(sr.to_string(), &mut dm)?;
153 assert_eq!(sl, sr);
154
155 let mut binding = Command::new("wc");
157 let custom_command = binding.args(["-c"]);
158 let mut dm = DecryptMethod::Custom(custom_command);
159 let sr = sl;
160 let sr = decrypt(sr.to_string(), &mut dm)?;
161 assert_eq!("15\n", sr);
162 Ok(())
163 }
164}