M src/cmds/createid.rs => src/cmds/createid.rs +21 -10
@@ 1,9 1,9 @@
-use std::path::Path;
-use std::process::exit;
-use age::secrecy::ExposeSecret;
-use regex::Regex;
use crate::db::Database;
use crate::identity::{IdKeyData, Identity};
+use age::secrecy::ExposeSecret;
+use regex::Regex;
+use std::path::Path;
+use std::process::exit;
pub fn create_id(db_path: &Path, name: String, email: String) -> anyhow::Result<()> {
let mut db = Database::load_or_create(db_path)?;
@@ 11,7 11,11 @@ pub fn create_id(db_path: &Path, name: String, email: String) -> anyhow::Result<
// sanity check to ensure this id does not already exist
for key in &db.keys {
if key.name == name && key.email == email {
- eprintln!("key '{:?}' (keyid {}) already exists in database", key, key.keys.keyid()?);
+ eprintln!(
+ "key '{:?}' (keyid {}) already exists in database",
+ key,
+ key.keys.keyid()?
+ );
eprintln!("if you want to recreate it, remove it first");
exit(1);
}
@@ 19,11 23,14 @@ pub fn create_id(db_path: &Path, name: String, email: String) -> anyhow::Result<
// Email is a fucking nightmare
// RFC5322, I hate you
- let email_re = Regex::new(r#"(?x)
+ let email_re = Regex::new(
+ r#"(?x)
^(?P<login>[^@\s]+)@
([[:word:]]+\.)*
[[:word:]]+$
- "#).unwrap();
+ "#,
+ )
+ .unwrap();
if !email_re.is_match(&email) {
eprintln!("`{email}` is not a valid RFC5322 email address");
@@ 37,14 44,18 @@ pub fn create_id(db_path: &Path, name: String, email: String) -> anyhow::Result<
let identity = Identity {
name,
email,
- keys: IdKeyData::Local(ss.to_string())
+ keys: IdKeyData::Local(ss.to_string()),
};
db.keys.push(identity.clone());
// save database
db.write(db_path)?;
- println!("wrote new key {:?} (keyid {}) to database", identity, identity.keys.keyid()?);
+ println!(
+ "wrote new key {:?} (keyid {}) to database",
+ identity,
+ identity.keys.keyid()?
+ );
Ok(())
-}>
\ No newline at end of file
+}
M src/cmds/import.rs => src/cmds/import.rs +18 -18
@@ 1,8 1,7 @@
-use std::convert::identity;
+use crate::db::Database;
+use crate::identity::{IdKeyData, Identity, unescape};
use std::path::Path;
use std::process::exit;
-use crate::db::Database;
-use crate::identity::{unescape, IdKeyData, Identity};
pub fn import_key(db_path: &Path, key: String, insert_anyway: bool) -> anyhow::Result<()> {
// decode the key
@@ 13,35 12,32 @@ pub fn import_key(db_path: &Path, key: String, insert_anyway: bool) -> anyhow::R
exit(1);
}
- let [_sentinel, key, name, email] = components.as_slice() else { unreachable!() };
+ let [_sentinel, key, name, email] = components.as_slice() else {
+ unreachable!()
+ };
let name = unescape(name.to_string())?;
let email = unescape(email.to_string())?;
let keys = match &key[0..3] {
- "age" => {
- IdKeyData::Peer(key.to_string())
- },
- "AGE" => {
- IdKeyData::Local(key.to_string())
- },
+ "age" => IdKeyData::Peer(key.to_string()),
+ "AGE" => IdKeyData::Local(key.to_string()),
_ => {
eprintln!("unsupported age key type; is this a sage key?");
exit(1);
}
};
- let identity = Identity {
- name,
- email,
- keys
- };
+ let identity = Identity { name, email, keys };
let mut db = Database::load_or_create(db_path)?;
for key in &db.keys {
if key.keys.keyid()? == identity.keys.keyid()? {
- println!("key {} already exists in database, skipping", key.keys.keyid()?);
+ println!(
+ "key {} already exists in database, skipping",
+ key.keys.keyid()?
+ );
return Ok(());
}
@@ 56,7 52,11 @@ pub fn import_key(db_path: &Path, key: String, insert_anyway: bool) -> anyhow::R
db.write(db_path)?;
- println!("imported {:?} (keyid {}) successfully", identity, identity.keys.keyid()?);
+ println!(
+ "imported {:?} (keyid {}) successfully",
+ identity,
+ identity.keys.keyid()?
+ );
Ok(())
-}>
\ No newline at end of file
+}
M src/cmds/ls.rs => src/cmds/ls.rs +21 -8
@@ 1,10 1,14 @@
+use crate::db::Database;
+use crate::identity::{IdKeyData, escape};
use std::path::Path;
use std::process::exit;
-use urlencoding::encode;
-use crate::db::Database;
-use crate::identity::{escape, IdKeyData};
-pub fn list_keys(db_path: &Path, local_only: bool, peer_only: bool, full_pks: bool) -> anyhow::Result<()> {
+pub fn list_keys(
+ db_path: &Path,
+ local_only: bool,
+ peer_only: bool,
+ full_pks: bool,
+) -> anyhow::Result<()> {
if local_only && peer_only {
eprintln!("cannot show only local and only peer keys, pick one");
exit(1);
@@ 19,19 23,28 @@ pub fn list_keys(db_path: &Path, local_only: bool, peer_only: bool, full_pks: bo
for key in &db.keys {
match key.keys {
IdKeyData::Local(_) => {
- if !show_local { continue };
+ if !show_local {
+ continue;
+ };
displayed += 1;
println!("local: {:?}", key);
}
IdKeyData::Peer(_) => {
- if !show_peer { continue };
+ if !show_peer {
+ continue;
+ };
displayed += 1;
println!("peer: {:?}", key);
}
}
if full_pks {
- println!(" pk: sage/{}/{}/{}", key.keys.pk()?, escape(key.name.clone()), escape(key.email.clone()));
+ println!(
+ " pk: sage/{}/{}/{}",
+ key.keys.pk()?,
+ escape(key.name.clone()),
+ escape(key.email.clone())
+ );
} else {
println!(" id: {}", key.keys.keyid()?);
}
@@ 42,4 55,4 @@ pub fn list_keys(db_path: &Path, local_only: bool, peer_only: bool, full_pks: bo
}
Ok(())
-}>
\ No newline at end of file
+}
M src/cmds/mod.rs => src/cmds/mod.rs +1 -1
@@ 1,4 1,4 @@
pub mod createid;
+pub mod import;
pub mod ls;
pub mod show;
-pub mod import;>
\ No newline at end of file
M src/cmds/show.rs => src/cmds/show.rs +18 -6
@@ 1,7 1,7 @@
+use crate::db::Database;
+use crate::identity::{IdKeyData, escape};
use std::path::Path;
use std::process::exit;
-use crate::db::Database;
-use crate::identity::{escape, IdKeyData};
pub fn show_key(db_path: &Path, search: String, expose_secret: bool) -> anyhow::Result<()> {
let db = Database::load_or_create(db_path)?;
@@ 21,12 21,24 @@ pub fn show_key(db_path: &Path, search: String, expose_secret: bool) -> anyhow::
let show_sk = expose_secret && matches!(each_match.keys, IdKeyData::Local(..));
if !show_sk {
- println!(" pk: sage/{}/{}/{}", each_match.keys.pk()?, escape(each_match.name.clone()), escape(each_match.email.clone()));
+ println!(
+ " pk: sage/{}/{}/{}",
+ each_match.keys.pk()?,
+ escape(each_match.name.clone()),
+ escape(each_match.email.clone())
+ );
} else {
- let IdKeyData::Local(k) = &each_match.keys else { continue };
- println!(" sk: sage/{}/{}/{}", k, escape(each_match.name.clone()), escape(each_match.email.clone()));
+ let IdKeyData::Local(k) = &each_match.keys else {
+ continue;
+ };
+ println!(
+ " sk: sage/{}/{}/{}",
+ k,
+ escape(each_match.name.clone()),
+ escape(each_match.email.clone())
+ );
}
}
Ok(())
-}>
\ No newline at end of file
+}
M src/db.rs => src/db.rs +35 -17
@@ 1,19 1,17 @@
-use std::path::{Path, PathBuf};
+use crate::identity::Identity;
use dirs::config_dir;
use nucleo::{Config, Matcher, Utf32Str};
use serde::{Deserialize, Serialize};
-use crate::identity::Identity;
+use std::path::{Path, PathBuf};
#[derive(Serialize, Deserialize)]
pub struct Database {
- pub keys: Vec<Identity>
+ pub keys: Vec<Identity>,
}
impl Database {
pub fn load_or_create(path: &Path) -> anyhow::Result<Self> {
if !database_exists(path) {
- let d = Database {
- keys: vec![]
- };
+ let d = Database { keys: vec![] };
write_database(d, path)?;
}
load_database(path)
@@ 22,19 20,39 @@ impl Database {
write_database(self, path)
}
pub fn fuzzy_search(&self, term: String) -> Vec<Identity> {
- if self.keys.is_empty() { return vec![] }
+ if self.keys.is_empty() {
+ return vec![];
+ }
let mut m = Matcher::new(Config::DEFAULT);
- let haystacks = self.keys.iter().map(|u| {
- (u.clone(), format!("{:?} {}", u, u.keys.pk().unwrap_or("<pk_unknown>".to_string())))
- }).collect::<Vec<_>>();
+ let haystacks = self
+ .keys
+ .iter()
+ .map(|u| {
+ (
+ u.clone(),
+ format!(
+ "{:?} {}",
+ u,
+ u.keys.pk().unwrap_or("<pk_unknown>".to_string())
+ ),
+ )
+ })
+ .collect::<Vec<_>>();
- let mut scores = haystacks.iter().map(|h| {
- (
- m.fuzzy_match(Utf32Str::Ascii(h.1.as_bytes()), Utf32Str::Ascii(term.as_bytes())).unwrap_or(0),
- h.0.clone()
- )
- }).collect::<Vec<_>>();
+ let mut scores = haystacks
+ .iter()
+ .map(|h| {
+ (
+ m.fuzzy_match(
+ Utf32Str::Ascii(h.1.as_bytes()),
+ Utf32Str::Ascii(term.as_bytes()),
+ )
+ .unwrap_or(0),
+ h.0.clone(),
+ )
+ })
+ .collect::<Vec<_>>();
scores.sort_by_key(|u| u.0);
scores.reverse();
@@ 71,4 89,4 @@ fn write_database(d: Database, path: &Path) -> anyhow::Result<()> {
pub fn db_default() -> PathBuf {
config_dir().unwrap().join("sage.toml")
-}>
\ No newline at end of file
+}
M src/identity.rs => src/identity.rs +7 -9
@@ 1,18 1,18 @@
-use std::fmt::{Debug, Formatter};
-use std::str::FromStr;
use anyhow::bail;
use serde::{Deserialize, Serialize};
+use std::fmt::{Debug, Formatter};
+use std::str::FromStr;
#[derive(Serialize, Deserialize, Clone)]
pub struct Identity {
pub name: String,
pub email: String,
- pub keys: IdKeyData
+ pub keys: IdKeyData,
}
#[derive(Serialize, Deserialize, Clone)]
pub enum IdKeyData {
Local(String),
- Peer(String)
+ Peer(String),
}
impl Debug for Identity {
@@ 31,16 31,14 @@ impl IdKeyData {
}
};
Ok(k.to_public().to_string())
- },
- IdKeyData::Peer(pk) => {
- Ok(pk.clone())
}
+ IdKeyData::Peer(pk) => Ok(pk.clone()),
}
}
pub fn keyid(&self) -> anyhow::Result<String> {
let pk = self.pk()?;
let s0 = &pk[4..12];
- Ok(format!("{s0}"))
+ Ok(s0.to_string())
}
}
@@ 49,4 47,4 @@ pub fn escape(i: String) -> String {
}
pub fn unescape(i: String) -> anyhow::Result<String> {
Ok(urlencoding::decode(&i)?.to_string())
-}>
\ No newline at end of file
+}
M src/main.rs => src/main.rs +13 -21
@@ 1,13 1,11 @@
-use std::path::PathBuf;
-use std::process::exit;
-use dirs::config_dir;
use crate::cmds::createid::create_id;
use crate::db::db_default;
use crate::wrapper::wrapper;
+use std::process::exit;
-mod identity;
-mod db;
mod cmds;
+mod db;
+mod identity;
mod wrapper;
fn main() -> anyhow::Result<()> {
@@ 18,7 16,9 @@ fn main() -> anyhow::Result<()> {
exit(0);
}
- let database = pargs.opt_value_from_str(["-D", "--database"])?.unwrap_or(db_default());
+ let database = pargs
+ .opt_value_from_str(["-D", "--database"])?
+ .unwrap_or(db_default());
let Some(subcommand) = pargs.subcommand()? else {
// run the wrapper
@@ 37,7 37,7 @@ fn main() -> anyhow::Result<()> {
};
create_id(&database, name, email)?;
- },
+ }
"ls" => {
cmds::ls::list_keys(
&database,
@@ 45,29 45,21 @@ fn main() -> anyhow::Result<()> {
pargs.contains(["-p", "--peer"]),
pargs.contains(["-f", "--full"]),
)?;
- },
+ }
"show" => {
let Ok(search) = pargs.free_from_str() else {
eprintln!("search term is required, --help for help");
exit(1);
};
- cmds::show::show_key(
- &database,
- search,
- pargs.contains(["-e", "--expose-secret"])
- )?;
- },
+ cmds::show::show_key(&database, search, pargs.contains(["-e", "--expose-secret"]))?;
+ }
"import" => {
let Ok(key) = pargs.free_from_str() else {
eprintln!("key is required, --help for help");
exit(1);
};
- cmds::import::import_key(
- &database,
- key,
- pargs.contains(["-i", "--import-anyway"])
- )?;
- },
+ cmds::import::import_key(&database, key, pargs.contains(["-i", "--import-anyway"]))?;
+ }
unknown => {
eprintln!("Unknown subcommand: {}", unknown);
print_help();
@@ 158,4 150,4 @@ import [OPTIONS] <key>
OPTIONS:
-i, --insert-anyway Insert even if a matching identity is already present
-";>
\ No newline at end of file
+";
M src/wrapper.rs => src/wrapper.rs +38 -25
@@ 1,19 1,17 @@
+use crate::db::Database;
+use crate::identity::IdKeyData;
+use anyhow::bail;
+use pico_args::Arguments;
use std::os::unix::prelude::CommandExt;
use std::path::{Path, PathBuf};
-use std::process::{exit, Command, Stdio};
+use std::process::{Command, exit};
use std::str::FromStr;
-use std::thread::sleep;
-use std::time::Duration;
-use anyhow::bail;
-use pico_args::Arguments;
use tempfile::NamedTempFile;
-use crate::db::Database;
-use crate::identity::IdKeyData;
#[derive(Debug)]
enum Mode {
Encrypt,
- Decrypt
+ Decrypt,
}
pub fn wrapper(mut pargs: Arguments, db_path: &Path) -> anyhow::Result<()> {
@@ 25,7 23,11 @@ pub fn wrapper(mut pargs: Arguments, db_path: &Path) -> anyhow::Result<()> {
bail!("cannot specify both --encrypt and --decrypt, pick one");
}
- let mode = if contains_decrypt { Mode::Decrypt } else { Mode::Encrypt };
+ let mode = if contains_decrypt {
+ Mode::Decrypt
+ } else {
+ Mode::Encrypt
+ };
let output: Option<PathBuf> = pargs.opt_value_from_str(["-o", "--output"])?;
let armor = pargs.contains(["-a", "--armor"]);
let passphrase = pargs.contains(["-p", "--passphrase"]);
@@ 37,11 39,12 @@ pub fn wrapper(mut pargs: Arguments, db_path: &Path) -> anyhow::Result<()> {
if let Some(r) = maybe_r {
recipients.push(r);
} else {
- break
+ break;
}
}
- if let Ok(Some(f)) = pargs.opt_value_from_str::<[&str; 2], PathBuf>(["-R", "--recipients-file"]) {
+ if let Ok(Some(f)) = pargs.opt_value_from_str::<[&str; 2], PathBuf>(["-R", "--recipients-file"])
+ {
recipients.extend(load_keyfile(&f)?);
}
@@ 50,32 53,37 @@ pub fn wrapper(mut pargs: Arguments, db_path: &Path) -> anyhow::Result<()> {
let maybe_i: Option<String> = pargs.opt_value_from_str(["-i", "--identity"])?;
if let Some(i) = maybe_i {
- if let Ok(path) = PathBuf::from_str(&i) && path.exists() {
+ let Ok(path) = PathBuf::from_str(&i);
+ if path.exists() {
identities.extend(load_keyfile(&path)?);
} else {
identities.push(i);
}
} else {
- break
+ break;
}
}
let input: Option<PathBuf> = pargs.opt_free_from_str()?;
if verbose {
- println!("resolved args: {:?} output={:?} armor?{:?} passphrase?{:?} recipients=>{:?} identities=>{:?} input={:?}", mode, output, armor, passphrase, recipients, identities, input);
+ println!(
+ "resolved args: {:?} output={:?} armor?{:?} passphrase?{:?} recipients=>{:?} identities=>{:?} input={:?}",
+ mode, output, armor, passphrase, recipients, identities, input
+ );
}
// Resolve recipients and identities to the database
let db = Database::load_or_create(db_path)?;
// resolve recipients
- let resolved_recipients = recipients.iter()
+ let resolved_recipients = recipients
+ .iter()
.map(|u| {
// is this already a valid key?
if u.starts_with("age1") {
// regular ol' pubkey
- return u.clone()
+ return u.clone();
}
// otherwise, resolve it in the database
let results = db.fuzzy_search(u.clone());
@@ 90,7 98,11 @@ pub fn wrapper(mut pargs: Arguments, db_path: &Path) -> anyhow::Result<()> {
let r = &results[0];
if verbose {
- eprintln!("resolved rcpt {u} => {:?} (keyid {})", r, r.keys.keyid().unwrap());
+ eprintln!(
+ "resolved rcpt {u} => {:?} (keyid {})",
+ r,
+ r.keys.keyid().unwrap()
+ );
}
r.keys.pk().unwrap()
@@ 142,10 154,13 @@ pub fn wrapper(mut pargs: Arguments, db_path: &Path) -> anyhow::Result<()> {
// execute age
let mut args = vec![];
- args.push(match mode {
- Mode::Encrypt => "--encrypt",
- Mode::Decrypt => "--decrypt"
- }.to_string());
+ args.push(
+ match mode {
+ Mode::Encrypt => "--encrypt",
+ Mode::Decrypt => "--decrypt",
+ }
+ .to_string(),
+ );
if let Some(o) = output {
args.push("--output".to_string());
args.push(o.display().to_string());
@@ 172,9 187,7 @@ pub fn wrapper(mut pargs: Arguments, db_path: &Path) -> anyhow::Result<()> {
eprintln!("exec age {:?}", args);
}
- Err(Command::new("age")
- .args(args)
- .exec())?;
+ Err(Command::new("age").args(args).exec())?;
Ok(())
}
@@ 186,4 199,4 @@ fn load_keyfile(path: &Path) -> anyhow::Result<Vec<String>> {
.filter(|u| !u.starts_with("#"))
.map(|u| u.to_string())
.collect())
-}>
\ No newline at end of file
+}