open_envault/crypto/
mod.rs1pub mod keys;
9pub mod store;
10pub mod util;
11pub mod value;
12
13use anyhow::{Context, Result};
14use std::{
15 env, fs,
16 path::{Path, PathBuf},
17};
18
19pub use keys::{Identity, Recipient};
20
21pub fn default_key_dir() -> PathBuf {
23 env::var_os("XDG_CONFIG_HOME")
24 .map(PathBuf::from)
25 .unwrap_or_else(|| {
26 PathBuf::from(env::var("HOME").unwrap_or_else(|_| ".".into())).join(".config")
27 })
28 .join("open_envault")
29 .join("keys")
30}
31
32fn atomic_write(path: &Path, content: &[u8]) -> Result<()> {
34 let parent = path.parent().unwrap_or_else(|| Path::new("."));
35 let file_name = path
36 .file_name()
37 .map(|n| n.to_string_lossy().into_owned())
38 .unwrap_or_else(|| "out".into());
39 let tmp = parent.join(format!(".{file_name}.{}.tmp", std::process::id()));
40 {
41 let mut file = fs::File::create(&tmp)?;
42 std::io::Write::write_all(&mut file, content)?;
43 file.sync_all()?;
44 }
45 fs::rename(&tmp, path)?;
46 Ok(())
47}
48
49pub fn encrypt(content: &str, path: &Path, recipients: &[String]) -> Result<()> {
51 let text = store::encrypt(content, &store::recipients_from_strings(recipients)?)?;
52 atomic_write(path, text.as_bytes())
53}
54
55pub fn decrypt_for(path: &Path, environment: Option<&str>) -> Result<String> {
58 let mut identities = keys::identities_from_env();
59 if let Some(name) = environment {
60 let key_path = default_key_dir().join(format!("{name}.txt"));
61 if key_path.is_file()
62 && let Ok(identity) = keys::read_identity_file(&key_path)
63 {
64 identities.push(identity);
65 }
66 }
67 if identities.is_empty() {
68 anyhow::bail!(
69 "no age identity available; set OPENENCRYPT_AGE_KEY/SOPS_AGE_KEY or a key file"
70 );
71 }
72 let text = fs::read_to_string(path)
73 .with_context(|| format!("read encrypted file {}", path.display()))?;
74 store::decrypt(&text, &identities)
75}
76
77pub fn decrypt(path: &Path) -> Result<String> {
79 decrypt_for(path, None)
80}
81
82pub fn recipients_of(path: &Path) -> Result<Vec<String>> {
84 let text = fs::read_to_string(path)
85 .with_context(|| format!("read encrypted file {}", path.display()))?;
86 store::list_recipients(&text)
87}
88
89pub fn generate_key(path: &Path) -> Result<String> {
92 let (text, _public) = keys::generate_identity()?;
93 if let Some(parent) = path.parent() {
94 fs::create_dir_all(parent)?;
95 }
96 fs::write(path, text.as_bytes())
97 .with_context(|| format!("write key file {}", path.display()))?;
98 #[cfg(unix)]
99 {
100 use std::os::unix::fs::PermissionsExt;
101 fs::set_permissions(path, fs::Permissions::from_mode(0o600))
102 .with_context(|| format!("chmod key file {}", path.display()))?;
103 }
104 Ok(text)
105}