use std::{
env::{self, VarError},
error::Error,
fs::File,
io::{self, Read, Write},
path::Path,
process::{Child, Command, Stdio},
};
pub enum SecretType {
Env(String),
File(String),
String(String),
}
pub enum DecryptMethod<'a> {
None,
GPG,
Custom(&'a mut Command),
}
pub fn read_secret(stype: SecretType, dm: &mut DecryptMethod) -> Result<String, Box<dyn Error>> {
let estr = match stype {
SecretType::Env(name) => read_env(&name).map_err(|e| Box::new(e) as Box<dyn Error>)?,
SecretType::File(path) => read_file(&path).map_err(|e| Box::new(e) as Box<dyn Error>)?,
SecretType::String(secret) => secret,
};
decrypt(estr, dm).map_err(|e| Box::new(e) as Box<dyn Error>)
}
pub fn read_env(env_name: &str) -> Result<String, VarError> {
env::var(env_name)
}
pub fn read_file(path: &str) -> io::Result<String> {
let path = Path::new(path);
let mut buf = String::new();
File::open(path)?.read_to_string(&mut buf)?;
Ok(buf)
}
fn decrypt(estr: String, dm: &mut DecryptMethod) -> io::Result<String> {
match dm {
DecryptMethod::None => Ok(estr),
DecryptMethod::GPG => {
let gpg = Command::new("gpg")
.args(["--no-tty", "-q", "-d", "-a"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("Failed to spawn `gpg`: please ensure you have it installed.");
let res = get_command_output(gpg, estr)?;
let res = res.chars().take(res.len() - 1).collect();
Ok(res)
}
DecryptMethod::Custom(command) => {
let command = command
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("Failed to spawn command");
get_command_output(command, estr)
}
}
}
fn get_command_output(mut command: Child, input: String) -> io::Result<String> {
let mut stdin = (&mut command).stdin.take().expect("Failed to take stdin");
stdin.write_all(input.as_bytes())?;
drop(stdin);
let output = command.wait_with_output()?;
let res = String::from_utf8(output.stdout).expect("Cannot convert utf8 bytes into String.");
Ok(res)
}
#[cfg(test)]
mod tests {
use super::*;
const LIB_VERSION: &str = "0.1.0";
#[test]
fn test_read_secret() -> Result<(), Box<dyn Error>> {
let mut dm = DecryptMethod::None;
let st = SecretType::Env("CARGO_PKG_VERSION".to_string());
let sr = read_secret(st, &mut dm)?;
assert_eq!(LIB_VERSION, sr);
Ok(())
}
#[test]
fn test_read_env() -> Result<(), VarError> {
let s = read_env("CARGO_PKG_VERSION")?;
assert_eq!(LIB_VERSION, s);
let failed = read_env("UNEXISTED_VALUE");
assert!(failed.is_err());
Ok(())
}
#[test]
fn test_read_file() -> io::Result<()> {
let sl = "El Psy Kongaroo";
let sr = read_file("/home/zarkli/projects/rust/read-secret/tests/pass_0")?;
assert_eq!(sl, sr);
let sr = read_file("tests/pass_0")?;
assert_eq!(sl, sr);
Ok(())
}
#[test]
fn test_decrypt() -> io::Result<()> {
let sl = "El Psy Kongaroo";
let mut dm = DecryptMethod::None;
let sr = sl;
let sr = decrypt(sr.to_string(), &mut dm)?;
assert_eq!(sr, sl);
let mut dm = DecryptMethod::GPG;
let sr = "-----BEGIN PGP MESSAGE-----
hQGMAw4MNp4TmOFvAQwAuXN8xO+ca+Bz8bEFqnEB8cuxKYd0rCLa7UqN446DLnbj
0g5IqyfhCgzNgbMN9LN3pYALwPrNEw6bSK6QoOn3ZtCOQKRSjH1WprRGUx3Fc+dO
gDy8B79twcQyPFsy+3PbfDgQjxciNGuCXKBEp/cr+QFjAgX+wPTmoYv3xZGLHX5G
tAsE9bB00AeyUdedDbn+V1YUW8mTZko4JtvXst3pRhaBHlina+MdaoFaQLzAhN0A
jaVMrrm/L+WWAvrbdJvs8ew7QprENch2J0rXT5BY9tL8QRTnTLqrczQXtCLXMdk6
nELkVDEvj/FloVKgGK10wj62eRgIp2eZOxY5GRkB6U8VEuDVzy9ryNWm8qiachsB
GhibFWgjXOGxq/kEcmwZbzOC01KuqiGpI0MiGz3492detV2K2YGXsgbRZUwxb44B
X0MFE43HySxRGdYZ7Q5E0HClTQedNw1YCo82DpELheGA9GOe1taQjX+gd98h5Fp0
fmvb8an9JMgXwJDb0EHO0ksB6CaAfueLAR4sL8OpmUbeVg/kJv9fgvkHXvMMnFp+
BafOEV3SWmeMynyfYk2g12wtph+Jm9EDq3PLokHAlfp0EYRqTSF0VDaHYdw=
=LM7a
-----END PGP MESSAGE-----";
let sr = decrypt(sr.to_string(), &mut dm)?;
assert_eq!(sl, sr);
let mut binding = Command::new("wc");
let custom_command = binding.args(["-c"]);
let mut dm = DecryptMethod::Custom(custom_command);
let sr = sl;
let sr = decrypt(sr.to_string(), &mut dm)?;
assert_eq!("15\n", sr);
Ok(())
}
}