feat(main): export and import encrypted secret key

This commit is contained in:
xeruf 2025-01-20 23:02:18 +01:00
parent 2e76250edc
commit 828114f5de
3 changed files with 222 additions and 35 deletions

155
Cargo.lock generated
View file

@ -38,6 +38,21 @@ dependencies = [
"cpufeatures", "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]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "1.1.3" version = "1.1.3"
@ -112,6 +127,18 @@ dependencies = [
"windows-sys 0.59.0", "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]] [[package]]
name = "arrayvec" name = "arrayvec"
version = "0.7.6" version = "0.7.6"
@ -311,6 +338,12 @@ dependencies = [
"bitcoin_hashes 0.14.0", "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]] [[package]]
name = "base64" name = "base64"
version = "0.22.1" version = "0.22.1"
@ -335,6 +368,15 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1"
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "bip39" name = "bip39"
version = "2.1.0" version = "2.1.0"
@ -422,6 +464,17 @@ version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" 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]] [[package]]
name = "block-buffer" name = "block-buffer"
version = "0.10.4" version = "0.10.4"
@ -595,6 +648,12 @@ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]]
name = "constant_time_eq"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.9.4" version = "0.9.4"
@ -647,6 +706,15 @@ dependencies = [
"typenum", "typenum",
] ]
[[package]]
name = "ctr"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
dependencies = [
"cipher",
]
[[package]] [[package]]
name = "data-encoding" name = "data-encoding"
version = "2.7.0" version = "2.7.0"
@ -847,6 +915,18 @@ dependencies = [
"windows-sys 0.52.0", "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]] [[package]]
name = "fnv" name = "fnv"
version = "1.0.7" version = "1.0.7"
@ -1362,6 +1442,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"libc", "libc",
"redox_syscall",
] ]
[[package]] [[package]]
@ -1464,6 +1545,7 @@ dependencies = [
name = "mostr" name = "mostr"
version = "0.8.0" version = "0.8.0"
dependencies = [ dependencies = [
"base64 0.22.1",
"chrono", "chrono",
"colog", "colog",
"colored", "colored",
@ -1478,6 +1560,7 @@ dependencies = [
"parse_datetime", "parse_datetime",
"regex", "regex",
"rustyline", "rustyline",
"simple_crypt",
"tokio", "tokio",
"whoami", "whoami",
] ]
@ -1523,7 +1606,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af7c1eebe17dd785e52e1f81149c1b50fa6ec92e4ac239840934d1ffbd4f631c" checksum = "af7c1eebe17dd785e52e1f81149c1b50fa6ec92e4ac239840934d1ffbd4f631c"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"base64", "base64 0.22.1",
"bech32", "bech32",
"bip39", "bip39",
"bitcoin", "bitcoin",
@ -1792,6 +1875,18 @@ dependencies = [
"universal-hash", "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]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.20" version = "0.2.20"
@ -1932,6 +2027,18 @@ dependencies = [
"windows-sys 0.52.0", "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]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.24" version = "0.1.24"
@ -1990,8 +2097,8 @@ checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
[[package]] [[package]]
name = "rustyline" name = "rustyline"
version = "14.0.0" version = "15.0.0"
source = "git+https://github.com/xeruf/rustyline?rev=465b14d#465b14d71b2fed2e992e212ec08560c0994e8e10" source = "git+https://github.com/xeruf/rustyline?rev=5364854#53648543f7511fb5271afed3748f2b08cc9810f3"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"cfg-if", "cfg-if",
@ -2193,6 +2300,22 @@ dependencies = [
"libc", "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]] [[package]]
name = "slab" name = "slab"
version = "0.4.9" version = "0.4.9"
@ -2264,6 +2387,17 @@ dependencies = [
"syn", "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]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.15.0" version = "3.15.0"
@ -2496,9 +2630,9 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"
version = "0.1.14" version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
[[package]] [[package]]
name = "universal-hash" name = "universal-hash"
@ -2871,6 +3005,17 @@ version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" 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]] [[package]]
name = "xdg-home" name = "xdg-home"
version = "1.3.0" version = "1.3.0"

View file

@ -22,13 +22,15 @@ log = "0.4"
env_logger = "0.11" env_logger = "0.11"
colog = "1.3" colog = "1.3"
colored = "2.2" colored = "2.2"
rustyline = { git = "https://github.com/xeruf/rustyline", rev = "465b14d" } rustyline = { git = "https://github.com/xeruf/rustyline", rev = "5364854" }
# OS-Specific Abstractions # OS-Specific Abstractions
keyring = "3" keyring = "3"
directories = "5.0" directories = "5.0"
whoami = "1.5" whoami = "1.5"
# slint = "1.8" # slint = "1.8"
# Application Utils # Application Utils
base64 = "0.22"
simple_crypt = "0.2"
itertools = "0.12" itertools = "0.12"
chrono = "0.4" chrono = "0.4"
parse_datetime = "0.5" parse_datetime = "0.5"

View file

@ -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::collections::{HashMap, VecDeque};
use std::env::{args, var}; use std::env::{args, var};
use std::fs; use std::fs;
@ -7,26 +31,6 @@ use std::iter::once;
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use std::time::Duration; 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::sync::mpsc;
use tokio::time::error::Elapsed; use tokio::time::error::Elapsed;
use tokio::time::timeout; use tokio::time::timeout;
@ -62,13 +66,13 @@ macro_rules! or_warn {
} }
} }
fn read_keys(readline: &mut DefaultEditor) -> Result<Keys> { fn read_keys(keys_entry: Entry, readline: &mut DefaultEditor) -> Result<Keys> {
let keys_entry = Entry::new("mostr", "keys")?;
if let Ok(pass) = keys_entry.get_secret() { if let Ok(pass) = keys_entry.get_secret() {
return Ok(SecretKey::from_slice(&pass).map(|s| Keys::new(s)) return Ok(SecretKey::from_slice(&pass).map(|s| Keys::new(s))
.inspect_err(|e| eprintln!("Invalid key in keychain: {e}"))?); .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() { let keys = if line.is_empty() {
info!("Generating and persisting new key"); info!("Generating and persisting new key");
Keys::generate() Keys::generate()
@ -76,11 +80,21 @@ fn read_keys(readline: &mut DefaultEditor) -> Result<Keys> {
Keys::from_str(&line) Keys::from_str(&line)
.inspect_err(|e| eprintln!("Invalid key provided: {e}"))? .inspect_err(|e| eprintln!("Invalid key provided: {e}"))?
}; };
or_warn!(keys_entry.set_secret(keys.secret_key().as_secret_bytes()), if let Err(e) = keys_entry.set_secret(keys.secret_key().as_secret_bytes()) {
"Could not persist keys"); if line.is_empty() {
return Err(e.into());
} else {
warn!("Could not persist keys: {}", e)
}
}
Ok(keys) Ok(keys)
} }
fn read_password(readline: &mut DefaultEditor, prompt: &str) -> Result<String> {
let line = readline.readline(prompt)?;
Ok(line)
}
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
@ -104,7 +118,6 @@ async fn main() -> Result<()> {
}; };
let mut rl = DefaultEditor::new()?; let mut rl = DefaultEditor::new()?;
rl.set_auto_add_history(true);
or_warn!( or_warn!(
rl.create_external_writer().map( rl.create_external_writer().map(
|wr| builder |wr| builder
@ -136,8 +149,34 @@ async fn main() -> Result<()> {
.inspect(|_| { or_warn!(fs::remove_file(key_file)); })); .inspect(|_| { or_warn!(fs::remove_file(key_file)); }));
} }
let keys = read_keys(&mut rl)?; let keys_entry = Entry::new("mostr", "keys")?;
let relays_file = config_dir.join("relays"); 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() let client = ClientBuilder::new()
.opts(Options::new() .opts(Options::new()
@ -146,8 +185,8 @@ async fn main() -> Result<()> {
) )
.signer(keys.clone()) .signer(keys.clone())
.build(); .build();
info!("My public key: {}", keys.public_key());
let relays_file = config_dir.join("relays");
// TODO use NewRelay message for all relays // TODO use NewRelay message for all relays
match var("MOSTR_RELAY") { match var("MOSTR_RELAY") {
Ok(relay) => { Ok(relay) => {
@ -268,6 +307,7 @@ async fn main() -> Result<()> {
} }
} }
rl.set_auto_add_history(true);
'repl: loop { 'repl: loop {
println!(); println!();
let tasks = relays.get(&selected_relay).unwrap(); let tasks = relays.get(&selected_relay).unwrap();