Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
use crate::*;
use age::secrecy::Secret;
use std::io::{Read, Write};
/// define universal dividends subcommands
#[derive(Clone, Default, Debug, clap::Parser)]
pub enum Subcommand {
#[default]
/// List available keys
List,
/// Show where vault stores secret
Where,
/// Generate a mnemonic
Generate,
/// Import mnemonic with interactive prompt
Import,
}
// encrypt input with passphrase
fn encrypt(input: &[u8], passphrase: String) -> Result<Vec<u8>, age::EncryptError> {
let encryptor = age::Encryptor::with_user_passphrase(Secret::new(passphrase));
let mut encrypted = vec![];
let mut writer = encryptor.wrap_output(age::armor::ArmoredWriter::wrap_output(
&mut encrypted,
age::armor::Format::AsciiArmor,
)?)?;
writer.write_all(input)?;
writer.finish().and_then(|armor| armor.finish())?;
Ok(encrypted)
}
// decrypt cypher with passphrase
fn decrypt(input: &[u8], passphrase: String) -> Result<Vec<u8>, age::DecryptError> {
let age::Decryptor::Passphrase(decryptor) =
age::Decryptor::new(age::armor::ArmoredReader::new(input))?
else {
unimplemented!()
};
let mut decrypted = vec![];
let mut reader = decryptor.decrypt(&Secret::new(passphrase.to_owned()), None)?;
reader.read_to_end(&mut decrypted)?;
Ok(decrypted)
}
/// handle ud commands
pub fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError> {
// match subcommand
match command {
Subcommand::List => {
if let Ok(entries) = std::fs::read_dir(data.project_dir.data_dir()) {
println!("available keys:");
entries.for_each(|e| println!("{}", e.unwrap().file_name().to_str().unwrap()));
} else {
println!("could not read project dir");
}
}
Subcommand::Where => {
println!("{}", data.project_dir.data_dir().to_str().unwrap());
}
Subcommand::Generate => {
// TODO allow custom word count
let mnemonic = bip39::Mnemonic::generate(12).unwrap();
println!("{mnemonic}");
}
Subcommand::Import => {
let mnemonic = rpassword::prompt_password("Mnemonic: ")?;
println!("Enter password to protect the key");
let password = rpassword::prompt_password("Password: ")?;
let address = store_mnemonic(&data, &mnemonic, password)?;
println!("Stored secret for {address}");
}
};
Ok(())
}
/// store mnemonic protected with password
pub fn store_mnemonic(
data: &Data,
mnemonic: &str,
password: String,
) -> Result<AccountId, GcliError> {
// check validity by deriving keypair
let keypair = pair_from_str(mnemonic)?;
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
let address = keypair.public();
// write encrypted mnemonic in file identified by pubkey
let path = data.project_dir.data_dir().join(address.to_string());
let mut file = std::fs::File::create(path)?;
file.write_all(&encrypt(mnemonic.as_bytes(), password).map_err(|e| anyhow!(e))?[..])?;
Ok(keypair.public().into())
}
/// try get secret in keystore
pub fn try_fetch_secret(data: &Data, address: AccountId) -> Result<Option<String>, GcliError> {
let path = data.project_dir.data_dir().join(address.to_string());
if path.exists() {
println!("Enter password to unlock account {address}");
let password = rpassword::prompt_password("Password: ")?;
let mut file = std::fs::OpenOptions::new().read(true).open(path)?;
let mut cypher = vec![];
file.read_to_end(&mut cypher)?;
let secret = decrypt(&cypher, password).map_err(|e| GcliError::Input(e.to_string()))?;
let secretstr = String::from_utf8(secret).map_err(|e| anyhow!(e))?;
Ok(Some(secretstr))
} else {
Ok(None)
}
}
// test that armored encryption/decryption work as intended
#[test]
fn test_encrypt_decrypt() {
let plaintext = b"Hello world!";
let passphrase = "this is not a good passphrase".to_string();
let encrypted = encrypt(plaintext, passphrase.clone()).unwrap();
let decrypted = decrypt(&encrypted, passphrase).unwrap();
assert_eq!(decrypted, plaintext);
}