From 828114f5de592b58d76e80cddda3fb6ebddc39c7 Mon Sep 17 00:00:00 2001 From: xeruf <27jf@pm.me> Date: Mon, 20 Jan 2025 23:02:18 +0100 Subject: [PATCH] feat(main): export and import encrypted secret key --- Cargo.lock | 155 ++++++++++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 4 +- src/main.rs | 98 +++++++++++++++++++++++---------- 3 files changed, 222 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6f58532..7d8b4fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,6 +38,21 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "aes-gcm-siv" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae0784134ba9375416d469ec31e7c5f9fa94405049cf08c5ce5b4698be673e0d" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "polyval", + "subtle", + "zeroize", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -112,6 +127,18 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "anyhow" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + [[package]] name = "arrayvec" version = "0.7.6" @@ -311,6 +338,12 @@ dependencies = [ "bitcoin_hashes 0.14.0", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -335,6 +368,15 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bip39" version = "2.1.0" @@ -422,6 +464,17 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +[[package]] +name = "blake2b_simd" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -595,6 +648,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "core-foundation" version = "0.9.4" @@ -647,6 +706,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "data-encoding" version = "2.7.0" @@ -847,6 +915,18 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.59.0", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1362,6 +1442,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags", "libc", + "redox_syscall", ] [[package]] @@ -1464,6 +1545,7 @@ dependencies = [ name = "mostr" version = "0.8.0" dependencies = [ + "base64 0.22.1", "chrono", "colog", "colored", @@ -1478,6 +1560,7 @@ dependencies = [ "parse_datetime", "regex", "rustyline", + "simple_crypt", "tokio", "whoami", ] @@ -1523,7 +1606,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af7c1eebe17dd785e52e1f81149c1b50fa6ec92e4ac239840934d1ffbd4f631c" dependencies = [ "async-trait", - "base64", + "base64 0.22.1", "bech32", "bip39", "bitcoin", @@ -1792,6 +1875,18 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -1932,6 +2027,18 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rust-argon2" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5885493fdf0be6cdff808d1533ce878d21cfa49c7086fa00c66355cd9141bfc" +dependencies = [ + "base64 0.21.7", + "blake2b_simd", + "constant_time_eq", + "crossbeam-utils", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -1990,8 +2097,8 @@ checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "rustyline" -version = "14.0.0" -source = "git+https://github.com/xeruf/rustyline?rev=465b14d#465b14d71b2fed2e992e212ec08560c0994e8e10" +version = "15.0.0" +source = "git+https://github.com/xeruf/rustyline?rev=5364854#53648543f7511fb5271afed3748f2b08cc9810f3" dependencies = [ "bitflags", "cfg-if", @@ -2193,6 +2300,22 @@ dependencies = [ "libc", ] +[[package]] +name = "simple_crypt" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a335d088ffc07695a1aee7b94b72a70ba438bed139cf3f3397fcc6c102d113" +dependencies = [ + "aes-gcm-siv", + "anyhow", + "bincode", + "log", + "rust-argon2", + "serde", + "serde_derive", + "tar", +] + [[package]] name = "slab" version = "0.4.9" @@ -2264,6 +2387,17 @@ dependencies = [ "syn", ] +[[package]] +name = "tar" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c65998313f8e17d0d553d28f91a0df93e4dbbbf770279c7bc21ca0f09ea1a1f6" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "tempfile" version = "3.15.0" @@ -2496,9 +2630,9 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" -version = "0.1.14" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] name = "universal-hash" @@ -2871,6 +3005,17 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +[[package]] +name = "xattr" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e105d177a3871454f754b33bb0ee637ecaaac997446375fd3e5d43a2ed00c909" +dependencies = [ + "libc", + "linux-raw-sys", + "rustix", +] + [[package]] name = "xdg-home" version = "1.3.0" diff --git a/Cargo.toml b/Cargo.toml index 2bf7b77..70c18a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,13 +22,15 @@ log = "0.4" env_logger = "0.11" colog = "1.3" colored = "2.2" -rustyline = { git = "https://github.com/xeruf/rustyline", rev = "465b14d" } +rustyline = { git = "https://github.com/xeruf/rustyline", rev = "5364854" } # OS-Specific Abstractions keyring = "3" directories = "5.0" whoami = "1.5" # slint = "1.8" # Application Utils +base64 = "0.22" +simple_crypt = "0.2" itertools = "0.12" chrono = "0.4" parse_datetime = "0.5" diff --git a/src/main.rs b/src/main.rs index cb483b5..c378b0c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,27 @@ +use crate::event_sender::MostrMessage; +use crate::hashtag::Hashtag; +use crate::helpers::*; +use crate::kinds::{format_tag_basic, match_event_tag, Prio, BASIC_KINDS, PROPERTY_COLUMNS, PROP_KINDS}; +use crate::task::{State, StateChange, Task, MARKER_PROPERTY}; +use crate::tasks::{referenced_event, PropertyCollection, StateFilter, TasksRelay}; + +use base64::prelude::BASE64_STANDARD; +use base64::Engine; +use chrono::{DateTime, Local, TimeZone, Utc}; +use colored::Colorize; +use directories::ProjectDirs; +use env_logger::{Builder, Target, WriteStyle}; +use itertools::Itertools; +use keyring::Entry; +use keyring::Error::NoEntry; +use log::{debug, error, info, trace, warn, LevelFilter}; +use nostr_sdk::bitcoin::hex::DisplayHex; +use nostr_sdk::prelude::*; +use nostr_sdk::serde_json::Serializer; +use regex::Regex; +use rustyline::config::Configurer; +use rustyline::error::ReadlineError; +use rustyline::DefaultEditor; use std::collections::{HashMap, VecDeque}; use std::env::{args, var}; use std::fs; @@ -7,26 +31,6 @@ use std::iter::once; use std::path::PathBuf; use std::str::FromStr; use std::time::Duration; - -use crate::event_sender::MostrMessage; -use crate::hashtag::Hashtag; -use crate::helpers::*; -use crate::kinds::{format_tag_basic, match_event_tag, Prio, BASIC_KINDS, PROPERTY_COLUMNS, PROP_KINDS}; -use crate::task::{State, StateChange, Task, MARKER_PROPERTY}; -use crate::tasks::{referenced_event, PropertyCollection, StateFilter, TasksRelay}; -use chrono::{DateTime, Local, TimeZone, Utc}; -use colored::Colorize; -use directories::ProjectDirs; -use env_logger::{Builder, Target, WriteStyle}; -use itertools::Itertools; -use keyring::Entry; -use log::{debug, error, info, trace, warn, LevelFilter}; -use nostr_sdk::prelude::*; -use nostr_sdk::serde_json::Serializer; -use regex::Regex; -use rustyline::config::Configurer; -use rustyline::error::ReadlineError; -use rustyline::DefaultEditor; use tokio::sync::mpsc; use tokio::time::error::Elapsed; use tokio::time::timeout; @@ -62,13 +66,13 @@ macro_rules! or_warn { } } -fn read_keys(readline: &mut DefaultEditor) -> Result { - let keys_entry = Entry::new("mostr", "keys")?; +fn read_keys(keys_entry: Entry, readline: &mut DefaultEditor) -> Result { if let Ok(pass) = keys_entry.get_secret() { return Ok(SecretKey::from_slice(&pass).map(|s| Keys::new(s)) .inspect_err(|e| eprintln!("Invalid key in keychain: {e}"))?); } - let line = readline.readline("Secret key? (leave blank to generate and save a new keypair) ")?; + let line = read_password(readline, "Secret key? (leave blank to generate and save a new keypair) ")?; + let keys = if line.is_empty() { info!("Generating and persisting new key"); Keys::generate() @@ -76,11 +80,21 @@ fn read_keys(readline: &mut DefaultEditor) -> Result { Keys::from_str(&line) .inspect_err(|e| eprintln!("Invalid key provided: {e}"))? }; - or_warn!(keys_entry.set_secret(keys.secret_key().as_secret_bytes()), - "Could not persist keys"); + if let Err(e) = keys_entry.set_secret(keys.secret_key().as_secret_bytes()) { + if line.is_empty() { + return Err(e.into()); + } else { + warn!("Could not persist keys: {}", e) + } + } Ok(keys) } +fn read_password(readline: &mut DefaultEditor, prompt: &str) -> Result { + let line = readline.readline(prompt)?; + + Ok(line) +} #[tokio::main] async fn main() -> Result<()> { @@ -104,7 +118,6 @@ async fn main() -> Result<()> { }; let mut rl = DefaultEditor::new()?; - rl.set_auto_add_history(true); or_warn!( rl.create_external_writer().map( |wr| builder @@ -136,8 +149,34 @@ async fn main() -> Result<()> { .inspect(|_| { or_warn!(fs::remove_file(key_file)); })); } - let keys = read_keys(&mut rl)?; - let relays_file = config_dir.join("relays"); + let keys_entry = Entry::new("mostr", "keys")?; + let keys = + if args.peek().is_some_and(|arg| arg.trim_start_matches('-') == "import") { + let key = rl.readline("Enter your encrypted or plaintext secret key: ")?; + + let mut guard = rl.set_cursor_visibility(false)?; + let enc_pwd = read_password(&mut rl, "Please enter the encryption password you used: ")?; + guard.take(); + + let data = simple_crypt::decrypt(&(BASE64_STANDARD.decode(key)?), enc_pwd.as_bytes())?; + let keys = Keys::new(SecretKey::from_slice(&data)?); + if keys_entry.get_secret().is_err_and(|e| matches!(e, NoEntry)) || + rl.readline(&format!("Override stored key with given keypair, public key: {} (y/n)? ", keys.public_key()))? == "y" { + keys_entry.set_secret(keys.secret_key().as_secret_bytes())?; + } + keys + } else { + read_keys(keys_entry, &mut rl)? + }; + + info!("My active public key: {}", keys.public_key()); + if args.peek().is_some_and(|arg| arg.trim_start_matches('-') == "export") { + let enc_pwd = read_password(&mut rl, "Please enter an encryption password for your secret key: ")?; + let data = simple_crypt::encrypt(keys.secret_key().as_secret_bytes(), enc_pwd.as_bytes())?; + println!("Your encrypted key: {}", BASE64_STANDARD.encode(&data)); + // TODO optionally delete + return Ok(()); + } let client = ClientBuilder::new() .opts(Options::new() @@ -146,8 +185,8 @@ async fn main() -> Result<()> { ) .signer(keys.clone()) .build(); - info!("My public key: {}", keys.public_key()); + let relays_file = config_dir.join("relays"); // TODO use NewRelay message for all relays match var("MOSTR_RELAY") { Ok(relay) => { @@ -268,6 +307,7 @@ async fn main() -> Result<()> { } } + rl.set_auto_add_history(true); 'repl: loop { println!(); let tasks = relays.get(&selected_relay).unwrap();