Skip to content

Commit

Permalink
allow encrypting/decrypting to/from multiple keys
Browse files Browse the repository at this point in the history
This uses the key `gpg_user_ids`, but keeps `gpg_user_id` as alias for
backwards compatibility.

Fixes oknozor#103.
  • Loading branch information
ibotty committed Oct 25, 2024
1 parent d3bd97d commit 496208d
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 31 deletions.
112 changes: 108 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ ignore-files = "1.3.0"
tokio = { version = "1.41.0", features = ["macros", "rt"] }
thiserror = "1.0.65"
shellexpand = "3.1.0"
serde_with = "3.11.0"

[features]
default = ["cli"]
Expand Down
43 changes: 21 additions & 22 deletions src/gpg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@ const PGP_FOOTER: &str = "\n-----END PGP MESSAGE-----";

#[derive(Clone, Debug)]
pub struct Gpg {
pub user_id: String,
pub user_ids: Vec<String>,
}

impl Gpg {
pub(crate) fn new(user_id: &str) -> Self {
Gpg {
user_id: user_id.to_string(),
}
pub(crate) fn new(user_ids: Vec<String>) -> Self {
Gpg { user_ids }
}

pub(crate) fn push_secret<S: AsRef<Path> + ?Sized>(
Expand All @@ -43,11 +41,12 @@ impl Gpg {
}

fn encrypt(&self, content: &str) -> Result<String> {
let mut child = Command::new("gpg")
.arg("--encrypt")
.arg("--armor")
.arg("-r")
.arg(&self.user_id)
let mut cmd_binding = Command::new("gpg");
let cmd = cmd_binding.arg("--encrypt").arg("--armor");
for user_id in &self.user_ids {
cmd.arg("-r").arg(user_id);
}
let mut child = cmd
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
Expand All @@ -73,12 +72,12 @@ impl Gpg {
}

fn decrypt(&self, content: &str) -> Result<String> {
let mut child = Command::new("gpg")
.arg("--decrypt")
.arg("--armor")
.arg("-r")
.arg(&self.user_id)
.arg("-q")
let mut cmd_binding = Command::new("gpg");
let cmd = cmd_binding.arg("--decrypt").arg("--armor").arg("-q");
for user_id in &self.user_ids {
cmd.arg("-r").arg(user_id);
}
let mut child = cmd
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
Expand Down Expand Up @@ -116,7 +115,7 @@ mod test {
use std::env;
use toml::Value;

const GPG_ID: &str = "[email protected]";
const GPG_IDS: [&'static str; 2] = ["[email protected]", "[email protected]"];

fn gpg_setup() {
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
Expand All @@ -131,7 +130,7 @@ mod test {

#[sealed_test(before = gpg_setup())]
fn should_encrypt() {
let gpg = Gpg::new(GPG_ID);
let gpg = Gpg::new(GPG_IDS.map(String::from).to_vec());

let result = gpg.encrypt("test");

Expand All @@ -140,7 +139,7 @@ mod test {

#[sealed_test(before = gpg_setup())]
fn should_not_encrypt_unkown_gpg_user() {
let gpg = Gpg::new("unknown.user");
let gpg = Gpg::new(vec!("unknown.user".to_string()));

let result = gpg.encrypt("test");

Expand All @@ -149,7 +148,7 @@ mod test {

#[sealed_test(before = gpg_setup())]
fn should_decrypt() -> Result<()> {
let gpg = Gpg::new(GPG_ID);
let gpg = Gpg::new(GPG_IDS.map(String::from).to_vec());

let encrypted = gpg.encrypt("value")?;
let decrypted = gpg.decrypt(&encrypted);
Expand All @@ -163,7 +162,7 @@ mod test {

#[sealed_test(before = gpg_setup())]
fn should_push_to_var() -> Result<()> {
let gpg = Gpg::new(GPG_ID);
let gpg = Gpg::new(GPG_IDS.map(String::from).to_vec());
std::fs::write("vars.toml", "")?;
gpg.push_secret("key", "value", "vars.toml")?;

Expand All @@ -177,7 +176,7 @@ mod test {

#[sealed_test(before = gpg_setup())]
fn should_decrypt_from_file() -> Result<()> {
let gpg = Gpg::new(GPG_ID);
let gpg = Gpg::new(GPG_IDS.map(String::from).to_vec());
std::fs::write("vars.toml", "")?;
gpg.push_secret("key", "value", "vars.toml")?;

Expand Down
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ impl Bombadil {
if let Some(gpg) = &self.gpg {
gpg.push_secret(key, value, var_file)
} else {
Err(anyhow!("No gpg_user_id in bombadil settings"))
Err(anyhow!("No gpg_user_ids in bombadil settings"))
}
}

Expand Down Expand Up @@ -521,7 +521,7 @@ impl Bombadil {
let path = config.get_dotfiles_path()?;

let gpg = match mode {
Mode::Gpg => config.gpg_user_id.map(|user_id| Gpg::new(&user_id)),
Mode::Gpg => config.gpg_user_ids.map(|user_ids| Gpg::new(user_ids)),
Mode::NoGpg => None,
};

Expand Down
11 changes: 8 additions & 3 deletions src/settings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use config::{ConfigError, File};
use dirs::home_dir;
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
use serde_with::{serde_as, OneOrMany};
use std::collections::HashMap;
use std::path::PathBuf;

Expand All @@ -19,9 +20,9 @@ lazy_static! {
pub static ref SETTINGS: Settings = Settings::get().unwrap_or_default();
pub static ref GPG: Option<Gpg> = {
SETTINGS
.gpg_user_id
.gpg_user_ids
.as_ref()
.map(|gpg| Gpg::new(gpg.as_str()))
.map(|user_ids| Gpg::new(user_ids.to_vec()))
};
}

Expand All @@ -40,13 +41,17 @@ pub fn dotfile_dir() -> PathBuf {
}

/// The Global bombadil configuration
#[serde_as]
#[derive(Debug, Deserialize, Serialize, Default)]
#[serde(deny_unknown_fields)]
pub struct Settings {
/// User define dotfiles directory, usually your versioned dotfiles
pub dotfiles_dir: PathBuf,

pub gpg_user_id: Option<String>,
#[serde(alias = "gpg_user_id")]
#[serde_as(as = "Option<OneOrMany<_>>")]
#[serde(default)]
pub gpg_user_ids: Option<Vec<String>>,

#[serde(default)]
pub settings: ActiveProfile,
Expand Down

0 comments on commit 496208d

Please sign in to comment.