pub mod boxed_message;
pub mod crypto;
pub mod env;
pub mod format;
pub mod handler;
pub mod json;
pub mod toml;
pub mod typed;
pub mod yaml;
use std::fs::{self, File};
use std::io::{Read, Write};
use std::path::Path;
use crypto::{CryptoError, Keypair};
pub use crypto::KeyBytes;
pub use format::FileFormat;
use format::FileFormatError;
use fs4::fs_std::FileExt;
pub use handler::{FormatError, FormatHandler, KEY_SIZE, PUBLIC_KEY_FIELD};
use json::JsonError;
pub use typed::{DecryptedContent, DecryptedValue};
use thiserror::Error;
use toml::TomlError;
use yaml::YamlError;
use zeroize::Zeroizing;
pub const MAX_FILE_SIZE: u64 = 10 * 1024 * 1024;
#[derive(Error, Debug)]
pub enum EjsonError {
#[error("crypto error: {0}")]
Crypto(#[from] CryptoError),
#[error("json error: {0}")]
Json(#[from] JsonError),
#[error("toml error: {0}")]
Toml(#[from] TomlError),
#[error("yaml error: {0}")]
Yaml(#[from] YamlError),
#[error("format error: {0}")]
Format(#[from] FormatError),
#[error("file format error: {0}")]
FileFormat(#[from] FileFormatError),
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("couldn't read key file")]
KeyFileError(String),
#[error("invalid private key")]
InvalidPrivateKey,
#[error("hex decode error: {0}")]
HexError(#[from] hex::FromHexError),
#[error("file too large (max {} bytes)", MAX_FILE_SIZE)]
FileTooLarge,
#[error("invalid path: {0}")]
InvalidPath(String),
}
pub fn generate_keypair() -> Result<(String, String), EjsonError> {
let kp = Keypair::generate()?;
Ok((kp.public_string(), kp.private_string()))
}
fn validate_path(path: &Path) -> Result<(), EjsonError> {
let path_str = path.to_string_lossy();
if path_str.contains('\0') {
return Err(EjsonError::InvalidPath(
"path contains null bytes".to_string(),
));
}
#[cfg(unix)]
{
use std::os::unix::fs::FileTypeExt;
if let Ok(metadata) = fs::symlink_metadata(path) {
let file_type = metadata.file_type();
if file_type.is_block_device()
|| file_type.is_char_device()
|| file_type.is_fifo()
|| file_type.is_socket()
{
return Err(EjsonError::InvalidPath(
"path is not a regular file".to_string(),
));
}
}
}
Ok(())
}
fn read_file_with_lock(path: &Path) -> Result<Vec<u8>, EjsonError> {
validate_path(path)?;
let file = File::open(path)?;
let metadata = file.metadata()?;
if metadata.len() > MAX_FILE_SIZE {
return Err(EjsonError::FileTooLarge);
}
file.lock_shared()?;
let mut data = Vec::with_capacity(metadata.len() as usize);
let mut reader = std::io::BufReader::new(&file);
reader.read_to_end(&mut data)?;
Ok(data)
}
pub fn encrypt<R: Read, W: Write>(mut input: R, mut output: W) -> Result<usize, EjsonError> {
let mut data = Vec::new();
input.read_to_end(&mut data)?;
if data.len() as u64 > MAX_FILE_SIZE {
return Err(EjsonError::FileTooLarge);
}
encrypt_data(&data, &mut output, FileFormat::Json)
}
pub fn encrypt_with_format<R: Read, W: Write>(
mut input: R,
mut output: W,
format: FileFormat,
) -> Result<usize, EjsonError> {
let mut data = Vec::new();
input.read_to_end(&mut data)?;
if data.len() as u64 > MAX_FILE_SIZE {
return Err(EjsonError::FileTooLarge);
}
encrypt_data(&data, &mut output, format)
}
fn encrypt_data<W: Write>(
data: &[u8],
output: &mut W,
format: FileFormat,
) -> Result<usize, EjsonError> {
let handler = format.handler();
let data = handler.preprocess(data)?;
let pubkey = handler.extract_public_key(&data)?;
let my_kp = Keypair::generate()?;
let encrypter = my_kp.encrypter(pubkey);
let encrypt_fn = |plaintext: &[u8]| encrypter.encrypt(plaintext).map_err(|e| e.to_string());
let new_data = handler.walk(&data, &encrypt_fn)?;
output.write_all(&new_data)?;
Ok(new_data.len())
}
pub fn encrypt_file_in_place<P: AsRef<Path>>(file_path: P) -> Result<usize, EjsonError> {
let file_path = file_path.as_ref();
validate_path(file_path)?;
let format = FileFormat::from_path(file_path)?;
let file = File::open(file_path)?;
let metadata = file.metadata()?;
if metadata.len() > MAX_FILE_SIZE {
return Err(EjsonError::FileTooLarge);
}
let permissions = metadata.permissions();
file.lock_exclusive()?;
let mut data = Vec::with_capacity(metadata.len() as usize);
let mut reader = std::io::BufReader::new(&file);
reader.read_to_end(&mut data)?;
let mut output = Vec::new();
let size = encrypt_data(&data, &mut output, format)?;
fs::write(file_path, &output)?;
fs::set_permissions(file_path, permissions)?;
Ok(size)
}
pub fn decrypt<R: Read, W: Write>(
mut input: R,
mut output: W,
keydir: &str,
user_supplied_private_key: &str,
) -> Result<(), EjsonError> {
let mut data = Vec::new();
input.read_to_end(&mut data)?;
if data.len() as u64 > MAX_FILE_SIZE {
return Err(EjsonError::FileTooLarge);
}
decrypt_data(
&data,
&mut output,
keydir,
user_supplied_private_key,
FileFormat::Json,
)
}
pub fn decrypt_with_format<R: Read, W: Write>(
mut input: R,
mut output: W,
keydir: &str,
user_supplied_private_key: &str,
format: FileFormat,
) -> Result<(), EjsonError> {
let mut data = Vec::new();
input.read_to_end(&mut data)?;
if data.len() as u64 > MAX_FILE_SIZE {
return Err(EjsonError::FileTooLarge);
}
decrypt_data(
&data,
&mut output,
keydir,
user_supplied_private_key,
format,
)
}
fn decrypt_data<W: Write>(
data: &[u8],
output: &mut W,
keydir: &str,
user_supplied_private_key: &str,
format: FileFormat,
) -> Result<(), EjsonError> {
let handler = format.handler();
let pubkey = handler.extract_public_key(data)?;
let privkey = find_private_key(&pubkey, keydir, user_supplied_private_key)?;
let kp = Keypair::from_keys(pubkey, privkey);
let decrypter = kp.decrypter();
let decrypt_fn = |ciphertext: &[u8]| {
if boxed_message::is_boxed_message(ciphertext) {
decrypter.decrypt(ciphertext).map_err(|e| e.to_string())
} else {
Ok(ciphertext.to_vec())
}
};
let new_data = handler.walk(data, &decrypt_fn)?;
output.write_all(&new_data)?;
Ok(())
}
pub fn decrypt_file<P: AsRef<Path>>(
file_path: P,
keydir: &str,
user_supplied_private_key: &str,
trim_underscore_prefix: bool,
) -> Result<Vec<u8>, EjsonError> {
let file_path = file_path.as_ref();
let format = FileFormat::from_path(file_path)?;
let data = read_file_with_lock(file_path)?;
let mut output = Vec::new();
decrypt_data(
&data,
&mut output,
keydir,
user_supplied_private_key,
format,
)?;
if trim_underscore_prefix {
output = trim_underscore_prefix_from_keys(&output, format)?;
}
Ok(output)
}
pub fn decrypt_bytes_typed(
data: &[u8],
keydir: &str,
user_supplied_private_key: &str,
format: FileFormat,
trim_underscore_prefix: bool,
) -> Result<DecryptedContent, EjsonError> {
if data.len() as u64 > MAX_FILE_SIZE {
return Err(EjsonError::FileTooLarge);
}
let mut output = Vec::new();
decrypt_data(data, &mut output, keydir, user_supplied_private_key, format)?;
let decrypted_bytes = if trim_underscore_prefix {
trim_underscore_prefix_from_keys(&output, format)?
} else {
output
};
let content = match format {
FileFormat::Json => {
let value: serde_json::Value =
serde_json::from_slice(&decrypted_bytes).map_err(|_| JsonError::InvalidJson)?;
DecryptedContent::Json(value)
}
FileFormat::Yaml => {
let value: serde_norway::Value = serde_norway::from_slice(&decrypted_bytes)
.map_err(|e| YamlError::InvalidYaml(e.to_string()))?;
DecryptedContent::Yaml(value)
}
FileFormat::Toml => {
let s = std::str::from_utf8(&decrypted_bytes)
.map_err(|e| TomlError::InvalidToml(e.to_string()))?;
let value: ::toml::Value =
::toml::from_str(s).map_err(|e| TomlError::InvalidToml(e.to_string()))?;
DecryptedContent::Toml(value)
}
};
Ok(content)
}
pub fn decrypt_file_typed<P: AsRef<Path>>(
file_path: P,
keydir: &str,
user_supplied_private_key: &str,
trim_underscore_prefix: bool,
) -> Result<DecryptedContent, EjsonError> {
let file_path = file_path.as_ref();
let format = FileFormat::from_path(file_path)?;
let decrypted_bytes = decrypt_file(
file_path,
keydir,
user_supplied_private_key,
trim_underscore_prefix,
)?;
let content = match format {
FileFormat::Json => {
let value: serde_json::Value =
serde_json::from_slice(&decrypted_bytes).map_err(|_| JsonError::InvalidJson)?;
DecryptedContent::Json(value)
}
FileFormat::Yaml => {
let value: serde_norway::Value = serde_norway::from_slice(&decrypted_bytes)
.map_err(|e| YamlError::InvalidYaml(e.to_string()))?;
DecryptedContent::Yaml(value)
}
FileFormat::Toml => {
let s = std::str::from_utf8(&decrypted_bytes)
.map_err(|e| TomlError::InvalidToml(e.to_string()))?;
let value: ::toml::Value =
::toml::from_str(s).map_err(|e| TomlError::InvalidToml(e.to_string()))?;
DecryptedContent::Toml(value)
}
};
Ok(content)
}
fn trim_underscore_prefix_from_keys(
data: &[u8],
format: FileFormat,
) -> Result<Vec<u8>, EjsonError> {
let handler = format.handler();
Ok(handler.trim_underscore_prefix_from_keys(data)?)
}
fn find_private_key(
pubkey: &KeyBytes,
keydir: &str,
user_supplied_private_key: &str,
) -> Result<KeyBytes, EjsonError> {
let privkey_string: Zeroizing<String> = if user_supplied_private_key.is_empty() {
Zeroizing::new(read_private_key_from_disk(pubkey, keydir)?)
} else {
Zeroizing::new(user_supplied_private_key.to_string())
};
let mut privkey_bytes = Zeroizing::new(hex::decode(privkey_string.trim())?);
if privkey_bytes.len() != 32 {
return Err(EjsonError::InvalidPrivateKey);
}
let privkey: KeyBytes = privkey_bytes
.as_slice()
.try_into()
.map_err(|_| EjsonError::InvalidPrivateKey)?;
privkey_bytes.iter_mut().for_each(|b| *b = 0);
Ok(privkey)
}
fn read_private_key_from_disk(pubkey: &KeyBytes, keydir: &str) -> Result<String, EjsonError> {
let pubkey_hex = hex::encode(pubkey);
let keydir_path = Path::new(keydir);
let key_path = keydir_path.join(&pubkey_hex);
validate_path(&key_path)?;
if let (Ok(canonical_keydir), Ok(canonical_key_path)) =
(fs::canonicalize(keydir_path), fs::canonicalize(&key_path))
&& !canonical_key_path.starts_with(&canonical_keydir)
{
return Err(EjsonError::InvalidPath(
"key path escapes keydir".to_string(),
));
}
fs::read_to_string(&key_path).map_err(|_| {
EjsonError::KeyFileError(format!(
"key file for public key {}... not found or unreadable",
&pubkey_hex[..8]
))
})
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_generate_keypair() {
let (pub_key, priv_key) = generate_keypair().unwrap();
assert_eq!(pub_key.len(), 64);
assert_eq!(priv_key.len(), 64);
}
#[test]
fn test_encrypt_decrypt_roundtrip() {
let kp = Keypair::generate().unwrap();
let pub_hex = kp.public_string();
let priv_hex = kp.private_string();
let temp_dir = TempDir::new().unwrap();
let keydir = temp_dir.path().to_str().unwrap();
let key_file = temp_dir.path().join(&pub_hex);
fs::write(&key_file, &priv_hex).unwrap();
let json = format!(
r#"{{"_public_key": "{pub_hex}", "secret": "my secret value", "_comment": "not encrypted"}}"#
);
let mut encrypted = Vec::new();
encrypt(json.as_bytes(), &mut encrypted).unwrap();
let encrypted_str = String::from_utf8_lossy(&encrypted);
assert!(encrypted_str.contains("EJ["));
assert!(!encrypted_str.contains("my secret value"));
assert!(encrypted_str.contains("not encrypted"));
let mut decrypted = Vec::new();
decrypt(&encrypted[..], &mut decrypted, keydir, "").unwrap();
let decrypted_str = String::from_utf8_lossy(&decrypted);
assert!(decrypted_str.contains("my secret value"));
assert!(!decrypted_str.contains("EJ["));
}
#[test]
fn test_encrypt_decrypt_toml_roundtrip() {
let kp = Keypair::generate().unwrap();
let pub_hex = kp.public_string();
let priv_hex = kp.private_string();
let temp_dir = TempDir::new().unwrap();
let keydir = temp_dir.path().to_str().unwrap();
let key_file = temp_dir.path().join(&pub_hex);
fs::write(&key_file, &priv_hex).unwrap();
let toml_content = format!(
r#"_public_key = "{pub_hex}"
secret = "my secret value"
_comment = "not encrypted"
[database]
password = "db_password"
_hint = "password hint"
"#
);
let mut encrypted = Vec::new();
encrypt_with_format(toml_content.as_bytes(), &mut encrypted, FileFormat::Toml).unwrap();
let encrypted_str = String::from_utf8_lossy(&encrypted);
assert!(encrypted_str.contains("EJ["));
assert!(!encrypted_str.contains("my secret value"));
assert!(!encrypted_str.contains("db_password"));
assert!(encrypted_str.contains("not encrypted")); assert!(encrypted_str.contains("password hint"));
let mut decrypted = Vec::new();
decrypt_with_format(&encrypted[..], &mut decrypted, keydir, "", FileFormat::Toml).unwrap();
let decrypted_str = String::from_utf8_lossy(&decrypted);
assert!(decrypted_str.contains("my secret value"));
assert!(decrypted_str.contains("db_password"));
assert!(!decrypted_str.contains("EJ["));
}
#[test]
fn test_format_detection_in_encrypt_file() {
let kp = Keypair::generate().unwrap();
let pub_hex = kp.public_string();
let temp_dir = TempDir::new().unwrap();
let etoml_path = temp_dir.path().join("secrets.etoml");
let toml_content = format!(
r#"_public_key = "{pub_hex}"
secret = "my secret"
"#
);
fs::write(&etoml_path, &toml_content).unwrap();
encrypt_file_in_place(&etoml_path).unwrap();
let encrypted = fs::read_to_string(&etoml_path).unwrap();
assert!(encrypted.contains("EJ["));
assert!(!encrypted.contains("my secret"));
}
#[test]
fn test_encrypt_decrypt_yaml_roundtrip() {
let kp = Keypair::generate().unwrap();
let pub_hex = kp.public_string();
let priv_hex = kp.private_string();
let temp_dir = TempDir::new().unwrap();
let keydir = temp_dir.path().to_str().unwrap();
let key_file = temp_dir.path().join(&pub_hex);
fs::write(&key_file, &priv_hex).unwrap();
let yaml_content = format!(
r#"_public_key: "{pub_hex}"
secret: "my secret value"
_comment: "not encrypted"
database:
password: "db_password"
_hint: "password hint"
"#
);
let mut encrypted = Vec::new();
encrypt_with_format(yaml_content.as_bytes(), &mut encrypted, FileFormat::Yaml).unwrap();
let encrypted_str = String::from_utf8_lossy(&encrypted);
assert!(encrypted_str.contains("EJ["));
assert!(!encrypted_str.contains("my secret value"));
assert!(!encrypted_str.contains("db_password"));
assert!(encrypted_str.contains("not encrypted")); assert!(encrypted_str.contains("password hint"));
let mut decrypted = Vec::new();
decrypt_with_format(&encrypted[..], &mut decrypted, keydir, "", FileFormat::Yaml).unwrap();
let decrypted_str = String::from_utf8_lossy(&decrypted);
assert!(decrypted_str.contains("my secret value"));
assert!(decrypted_str.contains("db_password"));
assert!(!decrypted_str.contains("EJ["));
}
#[test]
fn test_format_detection_in_encrypt_yaml_file() {
let kp = Keypair::generate().unwrap();
let pub_hex = kp.public_string();
let temp_dir = TempDir::new().unwrap();
let eyaml_path = temp_dir.path().join("secrets.eyaml");
let yaml_content = format!(
r#"_public_key: "{pub_hex}"
secret: "my secret"
"#
);
fs::write(&eyaml_path, &yaml_content).unwrap();
encrypt_file_in_place(&eyaml_path).unwrap();
let encrypted = fs::read_to_string(&eyaml_path).unwrap();
assert!(encrypted.contains("EJ["));
assert!(!encrypted.contains("my secret"));
}
#[test]
fn test_path_validation_rejects_null_bytes() {
let path = Path::new("/tmp/test\0file.ejson");
let result = validate_path(path);
assert!(result.is_err());
}
#[test]
fn test_invalid_private_key_length() {
let pubkey = [0u8; 32];
let result = find_private_key(&pubkey, "/nonexistent", "deadbeef"); assert!(matches!(result, Err(EjsonError::InvalidPrivateKey)));
}
#[test]
fn test_decrypt_file_typed_json() {
let kp = Keypair::generate().unwrap();
let pub_hex = kp.public_string();
let priv_hex = kp.private_string();
let temp_dir = TempDir::new().unwrap();
let keydir = temp_dir.path().to_str().unwrap();
let key_file = temp_dir.path().join(&pub_hex);
fs::write(&key_file, &priv_hex).unwrap();
let json_path = temp_dir.path().join("secrets.ejson");
let json_content = format!(
r#"{{"_public_key": "{pub_hex}", "environment": {{"FOO": "bar", "BAZ": "qux"}}, "secret": "value"}}"#
);
fs::write(&json_path, &json_content).unwrap();
encrypt_file_in_place(&json_path).unwrap();
let content = decrypt_file_typed(&json_path, keydir, "", false).unwrap();
let env = content.get("environment").expect("should have environment");
let foo = env.get("FOO").expect("should have FOO");
assert_eq!(foo.as_str(), Some("bar"));
let pairs: Vec<_> = env
.as_string_map()
.unwrap()
.filter_map(|(k, v)| v.as_str().map(|s| (k.to_string(), s.to_string())))
.collect();
assert_eq!(pairs.len(), 2);
assert!(pairs.contains(&("FOO".to_string(), "bar".to_string())));
assert!(pairs.contains(&("BAZ".to_string(), "qux".to_string())));
}
#[test]
fn test_decrypt_file_typed_toml() {
let kp = Keypair::generate().unwrap();
let pub_hex = kp.public_string();
let priv_hex = kp.private_string();
let temp_dir = TempDir::new().unwrap();
let keydir = temp_dir.path().to_str().unwrap();
let key_file = temp_dir.path().join(&pub_hex);
fs::write(&key_file, &priv_hex).unwrap();
let toml_path = temp_dir.path().join("secrets.etoml");
let toml_content = format!(
r#"_public_key = "{pub_hex}"
secret = "value"
[environment]
FOO = "bar"
BAZ = "qux"
"#
);
fs::write(&toml_path, &toml_content).unwrap();
encrypt_file_in_place(&toml_path).unwrap();
let content = decrypt_file_typed(&toml_path, keydir, "", false).unwrap();
let env = content.get("environment").expect("should have environment");
let foo = env.get("FOO").expect("should have FOO");
assert_eq!(foo.as_str(), Some("bar"));
let pairs: Vec<_> = env
.as_string_map()
.unwrap()
.filter_map(|(k, v)| v.as_str().map(|s| (k.to_string(), s.to_string())))
.collect();
assert_eq!(pairs.len(), 2);
assert!(pairs.contains(&("FOO".to_string(), "bar".to_string())));
assert!(pairs.contains(&("BAZ".to_string(), "qux".to_string())));
}
#[test]
fn test_decrypt_file_typed_yaml() {
let kp = Keypair::generate().unwrap();
let pub_hex = kp.public_string();
let priv_hex = kp.private_string();
let temp_dir = TempDir::new().unwrap();
let keydir = temp_dir.path().to_str().unwrap();
let key_file = temp_dir.path().join(&pub_hex);
fs::write(&key_file, &priv_hex).unwrap();
let yaml_path = temp_dir.path().join("secrets.eyaml");
let yaml_content = format!(
r#"_public_key: "{pub_hex}"
secret: "value"
environment:
FOO: "bar"
BAZ: "qux"
"#
);
fs::write(&yaml_path, &yaml_content).unwrap();
encrypt_file_in_place(&yaml_path).unwrap();
let content = decrypt_file_typed(&yaml_path, keydir, "", false).unwrap();
let env = content.get("environment").expect("should have environment");
let foo = env.get("FOO").expect("should have FOO");
assert_eq!(foo.as_str(), Some("bar"));
let pairs: Vec<_> = env
.as_string_map()
.unwrap()
.filter_map(|(k, v)| v.as_str().map(|s| (k.to_string(), s.to_string())))
.collect();
assert_eq!(pairs.len(), 2);
assert!(pairs.contains(&("FOO".to_string(), "bar".to_string())));
assert!(pairs.contains(&("BAZ".to_string(), "qux".to_string())));
}
#[test]
fn test_decrypt_file_typed_ejson2env_pattern() {
let kp = Keypair::generate().unwrap();
let pub_hex = kp.public_string();
let priv_hex = kp.private_string();
let temp_dir = TempDir::new().unwrap();
let keydir = temp_dir.path().to_str().unwrap();
let key_file = temp_dir.path().join(&pub_hex);
fs::write(&key_file, &priv_hex).unwrap();
let json_path = temp_dir.path().join("secrets.ejson");
let json_content = format!(
r#"{{"_public_key": "{pub_hex}", "environment": {{"DATABASE_URL": "postgres://localhost", "API_KEY": "secret123"}}}}"#
);
fs::write(&json_path, &json_content).unwrap();
encrypt_file_in_place(&json_path).unwrap();
let content = decrypt_file_typed(&json_path, keydir, "", false).unwrap();
let env_secrets: std::collections::HashMap<String, String> = content
.get("environment")
.and_then(|env| env.as_string_map())
.map(|map| {
map.filter_map(|(k, v)| v.as_str().map(|s| (k.to_string(), s.to_string())))
.collect()
})
.unwrap_or_default();
assert_eq!(env_secrets.len(), 2);
assert_eq!(
env_secrets.get("DATABASE_URL"),
Some(&"postgres://localhost".to_string())
);
assert_eq!(env_secrets.get("API_KEY"), Some(&"secret123".to_string()));
}
#[test]
fn test_decrypt_bytes_typed_json() {
let kp = Keypair::generate().unwrap();
let pub_hex = kp.public_string();
let priv_hex = kp.private_string();
let temp_dir = TempDir::new().unwrap();
let keydir = temp_dir.path().to_str().unwrap();
let key_file = temp_dir.path().join(&pub_hex);
fs::write(&key_file, &priv_hex).unwrap();
let json_content = format!(
r#"{{"_public_key": "{pub_hex}", "environment": {{"FOO": "bar", "BAZ": "qux"}}, "secret": "value"}}"#
);
let mut encrypted = Vec::new();
encrypt(json_content.as_bytes(), &mut encrypted).unwrap();
let content = decrypt_bytes_typed(&encrypted, keydir, "", FileFormat::Json, false).unwrap();
let env = content.get("environment").expect("should have environment");
let foo = env.get("FOO").expect("should have FOO");
assert_eq!(foo.as_str(), Some("bar"));
let pairs: Vec<_> = env
.as_string_map()
.unwrap()
.filter_map(|(k, v)| v.as_str().map(|s| (k.to_string(), s.to_string())))
.collect();
assert_eq!(pairs.len(), 2);
assert!(pairs.contains(&("FOO".to_string(), "bar".to_string())));
assert!(pairs.contains(&("BAZ".to_string(), "qux".to_string())));
}
#[test]
fn test_decrypt_bytes_typed_toml() {
let kp = Keypair::generate().unwrap();
let pub_hex = kp.public_string();
let priv_hex = kp.private_string();
let temp_dir = TempDir::new().unwrap();
let keydir = temp_dir.path().to_str().unwrap();
let key_file = temp_dir.path().join(&pub_hex);
fs::write(&key_file, &priv_hex).unwrap();
let toml_content = format!(
r#"_public_key = "{pub_hex}"
secret = "value"
[environment]
FOO = "bar"
BAZ = "qux"
"#
);
let mut encrypted = Vec::new();
encrypt_with_format(toml_content.as_bytes(), &mut encrypted, FileFormat::Toml).unwrap();
let content = decrypt_bytes_typed(&encrypted, keydir, "", FileFormat::Toml, false).unwrap();
let env = content.get("environment").expect("should have environment");
let foo = env.get("FOO").expect("should have FOO");
assert_eq!(foo.as_str(), Some("bar"));
}
#[test]
fn test_decrypt_bytes_typed_yaml() {
let kp = Keypair::generate().unwrap();
let pub_hex = kp.public_string();
let priv_hex = kp.private_string();
let temp_dir = TempDir::new().unwrap();
let keydir = temp_dir.path().to_str().unwrap();
let key_file = temp_dir.path().join(&pub_hex);
fs::write(&key_file, &priv_hex).unwrap();
let yaml_content = format!(
r#"_public_key: "{pub_hex}"
secret: "value"
environment:
FOO: "bar"
BAZ: "qux"
"#
);
let mut encrypted = Vec::new();
encrypt_with_format(yaml_content.as_bytes(), &mut encrypted, FileFormat::Yaml).unwrap();
let content = decrypt_bytes_typed(&encrypted, keydir, "", FileFormat::Yaml, false).unwrap();
let env = content.get("environment").expect("should have environment");
let foo = env.get("FOO").expect("should have FOO");
assert_eq!(foo.as_str(), Some("bar"));
}
#[test]
fn test_decrypt_bytes_typed_with_trim_underscore() {
let kp = Keypair::generate().unwrap();
let pub_hex = kp.public_string();
let priv_hex = kp.private_string();
let temp_dir = TempDir::new().unwrap();
let keydir = temp_dir.path().to_str().unwrap();
let key_file = temp_dir.path().join(&pub_hex);
fs::write(&key_file, &priv_hex).unwrap();
let json_content =
format!(r#"{{"_public_key": "{pub_hex}", "_environment": {{"_FOO": "bar"}}}}"#);
let mut encrypted = Vec::new();
encrypt(json_content.as_bytes(), &mut encrypted).unwrap();
let content = decrypt_bytes_typed(&encrypted, keydir, "", FileFormat::Json, true).unwrap();
let env = content
.get("environment")
.expect("should have environment (trimmed)");
let foo = env.get("FOO").expect("should have FOO (trimmed)");
assert_eq!(foo.as_str(), Some("bar"));
}
}