keyfork-{shard,prompt}: add QR code functionality
This commit is contained in:
parent
d08765b956
commit
6b61279656
|
@ -6,8 +6,9 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["mnemonic"]
|
default = ["mnemonic", "qrencode"]
|
||||||
mnemonic = ["keyfork-mnemonic-util"]
|
mnemonic = ["keyfork-mnemonic-util"]
|
||||||
|
qrencode = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
crossterm = { version = "0.27.0", default-features = false, features = ["use-dev-tty", "events", "bracketed-paste"] }
|
crossterm = { version = "0.27.0", default-features = false, features = ["use-dev-tty", "events", "bracketed-paste"] }
|
||||||
|
|
|
@ -3,12 +3,14 @@ use std::{io::{stdin, stdout}, str::FromStr};
|
||||||
use keyfork_prompt::*;
|
use keyfork_prompt::*;
|
||||||
use keyfork_mnemonic_util::Mnemonic;
|
use keyfork_mnemonic_util::Mnemonic;
|
||||||
|
|
||||||
pub fn main() -> Result<()> {
|
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let mut mgr = PromptManager::new(stdin(), stdout())?;
|
let mut mgr = PromptManager::new(stdin(), stdout())?;
|
||||||
mgr.prompt_passphrase("Passphrase: ")?;
|
mgr.prompt_passphrase("Passphrase: ")?;
|
||||||
let string = mgr.prompt_wordlist("Mnemonic: ", &Default::default())?;
|
let string = mgr.prompt_wordlist("Mnemonic: ", &Default::default())?;
|
||||||
let mnemonic = Mnemonic::from_str(&string).unwrap();
|
let mnemonic = Mnemonic::from_str(&string).unwrap();
|
||||||
let entropy = mnemonic.entropy();
|
let entropy = mnemonic.entropy();
|
||||||
mgr.prompt_message(Message::Text(format!("Your entropy is: {entropy:X?}")))?;
|
mgr.prompt_message(Message::Text(format!("Your entropy is: {entropy:X?}")))?;
|
||||||
|
let qrcode = qrencode::qrencode(&string)?;
|
||||||
|
mgr.prompt_message(Message::Data(qrcode))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,9 @@ mod raw_mode;
|
||||||
use alternate_screen::*;
|
use alternate_screen::*;
|
||||||
use raw_mode::*;
|
use raw_mode::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "qrencode")]
|
||||||
|
pub mod qrencode;
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("The given handler is not a TTY")]
|
#[error("The given handler is not a TTY")]
|
||||||
|
@ -261,7 +264,7 @@ where
|
||||||
let mut terminal = RawMode::new(&mut terminal)?;
|
let mut terminal = RawMode::new(&mut terminal)?;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let (cols, _) = terminal::size()?;
|
let (cols, rows) = terminal::size()?;
|
||||||
|
|
||||||
terminal
|
terminal
|
||||||
.queue(terminal::Clear(terminal::ClearType::All))?
|
.queue(terminal::Clear(terminal::ClearType::All))?
|
||||||
|
@ -289,11 +292,24 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Data(data) => {
|
Data(data) => {
|
||||||
for line in data.lines() {
|
let count = data.lines().count();
|
||||||
|
// NOTE: GE to allow a MoveDown(1)
|
||||||
|
if count >= rows as usize {
|
||||||
|
let msg = format!(
|
||||||
|
"{} {count} {} {rows} {}",
|
||||||
|
"Could not print data", "lines long (screen is", "lines long)"
|
||||||
|
);
|
||||||
terminal
|
terminal
|
||||||
.queue(Print(line))?
|
.queue(Print(msg))?
|
||||||
.queue(cursor::MoveDown(1))?
|
.queue(cursor::MoveDown(1))?
|
||||||
.queue(cursor::MoveToColumn(0))?;
|
.queue(cursor::MoveToColumn(0))?;
|
||||||
|
} else {
|
||||||
|
for line in data.lines() {
|
||||||
|
terminal
|
||||||
|
.queue(Print(line))?
|
||||||
|
.queue(cursor::MoveDown(1))?
|
||||||
|
.queue(cursor::MoveToColumn(0))?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
use std::{
|
||||||
|
io::Write,
|
||||||
|
process::{Command, Stdio},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
pub enum QrGenerationError {
|
||||||
|
#[error("{0}")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
|
||||||
|
#[error("{0}")]
|
||||||
|
StringParse(#[from] std::string::FromUtf8Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate a terminal-printable QR code for a given string. Uses the `qrencode` CLI utility.
|
||||||
|
pub fn qrencode(text: &str) -> Result<String, QrGenerationError> {
|
||||||
|
let mut qrencode = Command::new("qrencode")
|
||||||
|
.arg("-t")
|
||||||
|
.arg("ansiutf8")
|
||||||
|
.arg("-m")
|
||||||
|
.arg("2")
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.spawn()?;
|
||||||
|
if let Some(stdin) = qrencode.stdin.as_mut() {
|
||||||
|
stdin.write_all(text.as_bytes())?;
|
||||||
|
}
|
||||||
|
let output = qrencode.wait_with_output()?;
|
||||||
|
let result = String::from_utf8(output.stdout)?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
|
@ -8,7 +8,7 @@ use aes_gcm::{
|
||||||
Aes256Gcm, KeyInit,
|
Aes256Gcm, KeyInit,
|
||||||
};
|
};
|
||||||
use keyfork_mnemonic_util::{Mnemonic, Wordlist};
|
use keyfork_mnemonic_util::{Mnemonic, Wordlist};
|
||||||
use keyfork_prompt::{Message as PromptMessage, PromptManager};
|
use keyfork_prompt::{qrencode, Message as PromptMessage, PromptManager};
|
||||||
use sharks::{Share, Sharks};
|
use sharks::{Share, Sharks};
|
||||||
use x25519_dalek::{EphemeralSecret, PublicKey};
|
use x25519_dalek::{EphemeralSecret, PublicKey};
|
||||||
|
|
||||||
|
@ -47,10 +47,15 @@ pub fn remote_decrypt() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let our_key = EphemeralSecret::random();
|
let our_key = EphemeralSecret::random();
|
||||||
let key_mnemonic =
|
let key_mnemonic =
|
||||||
Mnemonic::from_entropy(PublicKey::from(&our_key).as_bytes(), Default::default())?;
|
Mnemonic::from_entropy(PublicKey::from(&our_key).as_bytes(), Default::default())?;
|
||||||
|
let combined_mnemonic = format!("{nonce_mnemonic} {key_mnemonic}");
|
||||||
pm.prompt_message(PromptMessage::Text(format!(
|
pm.prompt_message(PromptMessage::Text(format!(
|
||||||
"Our words: {nonce_mnemonic} {key_mnemonic}"
|
"Our words: {combined_mnemonic}"
|
||||||
)))?;
|
)))?;
|
||||||
|
|
||||||
|
if let Ok(qrcode) = qrencode::qrencode(&combined_mnemonic) {
|
||||||
|
pm.prompt_message(PromptMessage::Data(qrcode))?;
|
||||||
|
}
|
||||||
|
|
||||||
let their_words = pm.prompt_wordlist("Their words: ", &wordlist)?;
|
let their_words = pm.prompt_wordlist("Their words: ", &wordlist)?;
|
||||||
|
|
||||||
let mut pubkey_words = their_words.split_whitespace().take(24).peekable();
|
let mut pubkey_words = their_words.split_whitespace().take(24).peekable();
|
||||||
|
|
|
@ -14,7 +14,7 @@ use keyfork_derive_openpgp::derive_util::{
|
||||||
DerivationPath,
|
DerivationPath,
|
||||||
};
|
};
|
||||||
use keyfork_mnemonic_util::{Mnemonic, MnemonicFromStrError, MnemonicGenerationError, Wordlist};
|
use keyfork_mnemonic_util::{Mnemonic, MnemonicFromStrError, MnemonicGenerationError, Wordlist};
|
||||||
use keyfork_prompt::{Error as PromptError, Message as PromptMessage, PromptManager};
|
use keyfork_prompt::{qrencode, Error as PromptError, Message as PromptMessage, PromptManager};
|
||||||
use openpgp::{
|
use openpgp::{
|
||||||
armor::{Kind, Writer},
|
armor::{Kind, Writer},
|
||||||
cert::{Cert, CertParser, ValidCert},
|
cert::{Cert, CertParser, ValidCert},
|
||||||
|
@ -48,7 +48,7 @@ use smartcard::SmartcardManager;
|
||||||
const SHARD_METADATA_VERSION: u8 = 1;
|
const SHARD_METADATA_VERSION: u8 = 1;
|
||||||
const SHARD_METADATA_OFFSET: usize = 2;
|
const SHARD_METADATA_OFFSET: usize = 2;
|
||||||
|
|
||||||
use super::{HUNK_VERSION, SharksError};
|
use super::{SharksError, HUNK_VERSION};
|
||||||
|
|
||||||
// 256 bit share is 49 bytes + some amount of hunk bytes, gives us reasonable padding
|
// 256 bit share is 49 bytes + some amount of hunk bytes, gives us reasonable padding
|
||||||
const ENC_LEN: u8 = 4 * 16;
|
const ENC_LEN: u8 = 4 * 16;
|
||||||
|
@ -427,7 +427,6 @@ pub fn decrypt(
|
||||||
let shared_key =
|
let shared_key =
|
||||||
Aes256Gcm::new_from_slice(&shared_secret).expect("Invalid length of constant key size");
|
Aes256Gcm::new_from_slice(&shared_secret).expect("Invalid length of constant key size");
|
||||||
let bytes = shared_key.encrypt(their_nonce, share.as_slice())?;
|
let bytes = shared_key.encrypt(their_nonce, share.as_slice())?;
|
||||||
dbg!(bytes.len());
|
|
||||||
shared_key.decrypt(their_nonce, &bytes[..])?;
|
shared_key.decrypt(their_nonce, &bytes[..])?;
|
||||||
|
|
||||||
// NOTE: Padding length is less than u8::MAX because ENC_LEN < u8::MAX
|
// NOTE: Padding length is less than u8::MAX because ENC_LEN < u8::MAX
|
||||||
|
@ -451,11 +450,16 @@ pub fn decrypt(
|
||||||
|
|
||||||
// safety: size of out_bytes is constant and always % 4 == 0
|
// safety: size of out_bytes is constant and always % 4 == 0
|
||||||
let mnemonic = unsafe { Mnemonic::from_raw_entropy(&out_bytes, Default::default()) };
|
let mnemonic = unsafe { Mnemonic::from_raw_entropy(&out_bytes, Default::default()) };
|
||||||
|
let combined_mnemonic = format!("{our_mnemonic} {mnemonic}");
|
||||||
|
|
||||||
pm.prompt_message(PromptMessage::Text(format!(
|
pm.prompt_message(PromptMessage::Text(format!(
|
||||||
"Our words: {our_mnemonic} {mnemonic}"
|
"Our words: {combined_mnemonic}"
|
||||||
)))?;
|
)))?;
|
||||||
|
|
||||||
|
if let Ok(qrcode) = qrencode::qrencode(&combined_mnemonic) {
|
||||||
|
pm.prompt_message(PromptMessage::Data(qrcode))?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue