Compare commits
7 Commits
keyfork-zb
...
main
Author | SHA1 | Date |
---|---|---|
|
9b2a8a5967 | |
|
e7be91bdd4 | |
|
739921d915 | |
|
64c75085f4 | |
|
00e35bcb7d | |
|
d0019a93f0 | |
|
020fa4d25e |
40
CHANGELOG.md
40
CHANGELOG.md
|
@ -1,3 +1,43 @@
|
||||||
|
# Keyfork v0.3.3
|
||||||
|
|
||||||
|
This release introduces a checksum verification mechanism for Remote Shard.
|
||||||
|
|
||||||
|
### Changes in keyfork-prompt:
|
||||||
|
|
||||||
|
```
|
||||||
|
e7be91b keyfork-{shard,prompt}: add Yes/No prompt for verifying QR codes
|
||||||
|
```
|
||||||
|
|
||||||
|
### Changes in keyfork-shard:
|
||||||
|
|
||||||
|
```
|
||||||
|
e7be91b keyfork-{shard,prompt}: add Yes/No prompt for verifying QR codes
|
||||||
|
739921d WIP: add checksum to shard
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
# Keyfork v0.3.2
|
||||||
|
|
||||||
|
This is another bugfix release, allowing the derivation of Shard keys.
|
||||||
|
|
||||||
|
|
||||||
|
### Changes in keyfork:
|
||||||
|
|
||||||
|
```
|
||||||
|
6ffcdc3 add derivation path for Shard keys
|
||||||
|
```
|
||||||
|
|
||||||
|
# Keyfork v0.3.1
|
||||||
|
|
||||||
|
This is a bugfix release, resolving an issue with Keyfork Shard not having a
|
||||||
|
exit condition for when a valid QR code was scanned.
|
||||||
|
|
||||||
|
### Changes in keyfork-shard:
|
||||||
|
|
||||||
|
```
|
||||||
|
d0019a9 keyfork-shard: break loop when receiving valid QR code
|
||||||
|
```
|
||||||
|
|
||||||
# Keyfork v0.3.0
|
# Keyfork v0.3.0
|
||||||
|
|
||||||
The Wizard is Dead. Long Live the Mnemonic Generator.
|
The Wizard is Dead. Long Live the Mnemonic Generator.
|
||||||
|
|
|
@ -1797,7 +1797,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "keyfork"
|
name = "keyfork"
|
||||||
version = "0.3.0"
|
version = "0.3.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"card-backend-pcsc",
|
"card-backend-pcsc",
|
||||||
|
@ -1944,7 +1944,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "keyfork-prompt"
|
name = "keyfork-prompt"
|
||||||
version = "0.2.2"
|
version = "0.2.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"keyfork-bug",
|
"keyfork-bug",
|
||||||
"keyfork-crossterm",
|
"keyfork-crossterm",
|
||||||
|
@ -1967,7 +1967,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "keyfork-shard"
|
name = "keyfork-shard"
|
||||||
version = "0.3.2"
|
version = "0.3.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes-gcm",
|
"aes-gcm",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
|
|
@ -35,17 +35,17 @@ keyforkd-models = { version = "0.2.0", path = "crates/daemon/keyforkd-models", r
|
||||||
keyfork-derive-openpgp = { version = "0.1.2", path = "crates/derive/keyfork-derive-openpgp", registry = "distrust", default-features = false }
|
keyfork-derive-openpgp = { version = "0.1.2", path = "crates/derive/keyfork-derive-openpgp", registry = "distrust", default-features = false }
|
||||||
keyfork-derive-path-data = { version = "0.1.1", path = "crates/derive/keyfork-derive-path-data", registry = "distrust", default-features = false }
|
keyfork-derive-path-data = { version = "0.1.1", path = "crates/derive/keyfork-derive-path-data", registry = "distrust", default-features = false }
|
||||||
keyfork-derive-util = { version = "0.2.0", path = "crates/derive/keyfork-derive-util", registry = "distrust", default-features = false }
|
keyfork-derive-util = { version = "0.2.0", path = "crates/derive/keyfork-derive-util", registry = "distrust", default-features = false }
|
||||||
keyfork-shard = { version = "0.3.0", path = "crates/keyfork-shard", registry = "distrust", default-features = false }
|
keyfork-shard = { version = "0.3.4", path = "crates/keyfork-shard", registry = "distrust", default-features = false }
|
||||||
keyfork-qrcode = { version = "0.1.1", path = "crates/qrcode/keyfork-qrcode", registry = "distrust", default-features = false }
|
keyfork-qrcode = { version = "0.1.1", path = "crates/qrcode/keyfork-qrcode", registry = "distrust", default-features = false }
|
||||||
keyfork-zbar = { version = "0.1.0", path = "crates/qrcode/keyfork-zbar", registry = "distrust", default-features = false }
|
keyfork-zbar = { version = "0.1.0", path = "crates/qrcode/keyfork-zbar", registry = "distrust", default-features = false }
|
||||||
keyfork-zbar-sys = { version = "0.1.0", path = "crates/qrcode/keyfork-zbar-sys", registry = "distrust", default-features = false }
|
keyfork-zbar-sys = { version = "0.1.0", path = "crates/qrcode/keyfork-zbar-sys", registry = "distrust", default-features = false }
|
||||||
keyfork-bin = { version = "0.1.0", path = "crates/util/keyfork-bin", registry = "distrust", default-features = false }
|
keyfork-bin = { version = "0.1.0", path = "crates/util/keyfork-bin", registry = "distrust", default-features = false }
|
||||||
keyfork-bug = { version = "0.1.0", path = "crates/util/keyfork-bug", registry = "distrust", default-features = false }
|
keyfork-bug = { version = "0.1.1", path = "crates/util/keyfork-bug", registry = "distrust", default-features = false }
|
||||||
keyfork-crossterm = { version = "0.27.1", path = "crates/util/keyfork-crossterm", registry = "distrust", default-features = false }
|
keyfork-crossterm = { version = "0.27.1", path = "crates/util/keyfork-crossterm", registry = "distrust", default-features = false }
|
||||||
keyfork-entropy = { version = "0.1.1", path = "crates/util/keyfork-entropy", registry = "distrust", default-features = false }
|
keyfork-entropy = { version = "0.1.1", path = "crates/util/keyfork-entropy", registry = "distrust", default-features = false }
|
||||||
keyfork-frame = { version = "0.1.0", path = "crates/util/keyfork-frame", registry = "distrust", default-features = false }
|
keyfork-frame = { version = "0.1.0", path = "crates/util/keyfork-frame", registry = "distrust", default-features = false }
|
||||||
keyfork-mnemonic = { version = "0.4.0", path = "crates/util/keyfork-mnemonic", registry = "distrust", default-features = false }
|
keyfork-mnemonic = { version = "0.4.0", path = "crates/util/keyfork-mnemonic", registry = "distrust", default-features = false }
|
||||||
keyfork-prompt = { version = "0.2.0", path = "crates/util/keyfork-prompt", registry = "distrust", default-features = false }
|
keyfork-prompt = { version = "0.2.3", path = "crates/util/keyfork-prompt", registry = "distrust", default-features = false }
|
||||||
keyfork-slip10-test-data = { version = "0.1.0", path = "crates/util/keyfork-slip10-test-data", registry = "distrust", default-features = false }
|
keyfork-slip10-test-data = { version = "0.1.0", path = "crates/util/keyfork-slip10-test-data", registry = "distrust", default-features = false }
|
||||||
smex = { version = "0.1.0", path = "crates/util/smex", registry = "distrust", default-features = false }
|
smex = { version = "0.1.0", path = "crates/util/smex", registry = "distrust", default-features = false }
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "keyfork-shard"
|
name = "keyfork-shard"
|
||||||
version = "0.3.2"
|
version = "0.3.4"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "AGPL-3.0-only"
|
license = "AGPL-3.0-only"
|
||||||
|
|
||||||
|
|
|
@ -23,9 +23,9 @@ use keyfork_prompt::{
|
||||||
mnemonic::{MnemonicSetValidator, MnemonicValidator, WordLength},
|
mnemonic::{MnemonicSetValidator, MnemonicValidator, WordLength},
|
||||||
Validator,
|
Validator,
|
||||||
},
|
},
|
||||||
Message as PromptMessage, PromptHandler,
|
Message as PromptMessage, PromptHandler, YesNo,
|
||||||
};
|
};
|
||||||
use sha2::Sha256;
|
use sha2::{Digest, Sha256};
|
||||||
use x25519_dalek::{EphemeralSecret, PublicKey};
|
use x25519_dalek::{EphemeralSecret, PublicKey};
|
||||||
|
|
||||||
const PLAINTEXT_LENGTH: u8 = 32 // shard
|
const PLAINTEXT_LENGTH: u8 = 32 // shard
|
||||||
|
@ -59,6 +59,21 @@ impl std::fmt::Display for RetryScanMnemonic {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn calculate_checksum(slice: &[u8]) -> Vec<u8> {
|
||||||
|
// generate a verification checksum
|
||||||
|
// this checksum should be expensive to calculate
|
||||||
|
let mut payload = vec![];
|
||||||
|
for _ in 0..1_000_000 {
|
||||||
|
payload.extend(slice);
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
hasher.update(&payload);
|
||||||
|
let result = hasher.finalize();
|
||||||
|
payload.clear();
|
||||||
|
payload.extend(result);
|
||||||
|
}
|
||||||
|
payload
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "openpgp")]
|
#[cfg(feature = "openpgp")]
|
||||||
pub mod openpgp;
|
pub mod openpgp;
|
||||||
|
|
||||||
|
@ -273,14 +288,33 @@ pub trait Format {
|
||||||
.expect(bug!(POISONED_MUTEX))
|
.expect(bug!(POISONED_MUTEX))
|
||||||
.prompt_message(PromptMessage::Text(QRCODE_PROMPT.to_string()))?;
|
.prompt_message(PromptMessage::Text(QRCODE_PROMPT.to_string()))?;
|
||||||
loop {
|
loop {
|
||||||
if let Ok(Some(qrcode_content)) = keyfork_qrcode::scan_camera(
|
if let Ok(Some(qrcode_content)) =
|
||||||
std::time::Duration::from_secs(*QRCODE_TIMEOUT),
|
keyfork_qrcode::scan_camera(std::time::Duration::from_secs(*QRCODE_TIMEOUT), 0)
|
||||||
0,
|
{
|
||||||
) {
|
|
||||||
let decoded_data = BASE64_STANDARD
|
let decoded_data = BASE64_STANDARD
|
||||||
.decode(qrcode_content)
|
.decode(qrcode_content)
|
||||||
.expect(bug!("qrcode should contain base64 encoded data"));
|
.expect(bug!("qrcode should contain base64 encoded data"));
|
||||||
pubkey_data = Some(decoded_data.try_into().map_err(|_| InvalidData)?);
|
let data: [u8; 32] = decoded_data.try_into().map_err(|_| InvalidData)?;
|
||||||
|
let checksum = calculate_checksum(&data);
|
||||||
|
let small_sum = &checksum[..8];
|
||||||
|
let small_mnemonic = Mnemonic::from_raw_bytes(small_sum);
|
||||||
|
|
||||||
|
let mut prompt = prompt.lock().expect(bug!(POISONED_MUTEX));
|
||||||
|
let question =
|
||||||
|
format!("Do these words match the expected words? {small_mnemonic}");
|
||||||
|
let response = keyfork_prompt::prompt_choice(
|
||||||
|
&mut **prompt,
|
||||||
|
&question,
|
||||||
|
&[YesNo::No, YesNo::Yes],
|
||||||
|
)?;
|
||||||
|
if response == YesNo::No {
|
||||||
|
prompt.prompt_message(PromptMessage::Text(String::from(
|
||||||
|
"Could not establish secure channel, exiting.",
|
||||||
|
)))?;
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pubkey_data = Some(data);
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
let mut prompt = prompt.lock().expect(bug!(POISONED_MUTEX));
|
let mut prompt = prompt.lock().expect(bug!(POISONED_MUTEX));
|
||||||
|
@ -535,15 +569,21 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Erro
|
||||||
&BASE64_STANDARD.encode(qrcode_data),
|
&BASE64_STANDARD.encode(qrcode_data),
|
||||||
ErrorCorrection::Highest,
|
ErrorCorrection::Highest,
|
||||||
) {
|
) {
|
||||||
|
let checksum = calculate_checksum(key_mnemonic.as_bytes());
|
||||||
|
let small_sum = &checksum[..8];
|
||||||
|
let small_mnemonic = Mnemonic::from_raw_bytes(small_sum);
|
||||||
pm.prompt_message(PromptMessage::Text(format!(
|
pm.prompt_message(PromptMessage::Text(format!(
|
||||||
concat!(
|
concat!(
|
||||||
"QR code #{iter} will be displayed after this prompt. ",
|
"QR code #{iter} will be displayed after this prompt. ",
|
||||||
"Send the QR code to the next shardholder. ",
|
"Send the QR code to the next shardholder. ",
|
||||||
"Only the next shardholder should scan the QR code."
|
"Only the next shardholder should scan the QR code. ",
|
||||||
),
|
),
|
||||||
iter = iter
|
iter = iter,
|
||||||
)))?;
|
)))?;
|
||||||
pm.prompt_message(PromptMessage::Data(qrcode))?;
|
pm.prompt_message(PromptMessage::Data(qrcode))?;
|
||||||
|
pm.prompt_message(PromptMessage::Text(format!(
|
||||||
|
"The following should be sent to verify the QR code: {small_mnemonic}"
|
||||||
|
)))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -562,10 +602,9 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Erro
|
||||||
{
|
{
|
||||||
pm.prompt_message(PromptMessage::Text(QRCODE_PROMPT.to_string()))?;
|
pm.prompt_message(PromptMessage::Text(QRCODE_PROMPT.to_string()))?;
|
||||||
loop {
|
loop {
|
||||||
if let Ok(Some(qrcode_content)) = keyfork_qrcode::scan_camera(
|
if let Ok(Some(qrcode_content)) =
|
||||||
std::time::Duration::from_secs(*QRCODE_TIMEOUT),
|
keyfork_qrcode::scan_camera(std::time::Duration::from_secs(*QRCODE_TIMEOUT), 0)
|
||||||
0,
|
{
|
||||||
) {
|
|
||||||
let decoded_data = BASE64_STANDARD
|
let decoded_data = BASE64_STANDARD
|
||||||
.decode(qrcode_content)
|
.decode(qrcode_content)
|
||||||
.expect(bug!("qrcode should contain base64 encoded data"));
|
.expect(bug!("qrcode should contain base64 encoded data"));
|
||||||
|
@ -578,6 +617,7 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Erro
|
||||||
let _ =
|
let _ =
|
||||||
pubkey_data.insert(decoded_data[..32].try_into().map_err(|_| InvalidData)?);
|
pubkey_data.insert(decoded_data[..32].try_into().map_err(|_| InvalidData)?);
|
||||||
let _ = payload_data.insert(decoded_data[32..].to_vec());
|
let _ = payload_data.insert(decoded_data[32..].to_vec());
|
||||||
|
break;
|
||||||
} else {
|
} else {
|
||||||
let choice = keyfork_prompt::prompt_choice(
|
let choice = keyfork_prompt::prompt_choice(
|
||||||
&mut *pm,
|
&mut *pm,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "keyfork"
|
name = "keyfork"
|
||||||
version = "0.3.0"
|
version = "0.3.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "AGPL-3.0-only"
|
license = "AGPL-3.0-only"
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ license = "AGPL-3.0-only"
|
||||||
default = [
|
default = [
|
||||||
"completion",
|
"completion",
|
||||||
"qrcode-decode-backend-rqrr",
|
"qrcode-decode-backend-rqrr",
|
||||||
"sequoia-crypto-backend-nettle",
|
"sequoia-crypto-backend-nettle",
|
||||||
]
|
]
|
||||||
|
|
||||||
completion = ["dep:clap_complete"]
|
completion = ["dep:clap_complete"]
|
||||||
|
|
|
@ -58,6 +58,9 @@ pub enum Path {
|
||||||
|
|
||||||
/// The Disaster Recovery index.
|
/// The Disaster Recovery index.
|
||||||
DisasterRecovery,
|
DisasterRecovery,
|
||||||
|
|
||||||
|
/// The Shard index.
|
||||||
|
Shard,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Path {
|
impl std::fmt::Display for Path {
|
||||||
|
@ -71,6 +74,7 @@ impl Path {
|
||||||
match self {
|
match self {
|
||||||
Path::Default => "default",
|
Path::Default => "default",
|
||||||
Path::DisasterRecovery => "disaster-recovery",
|
Path::DisasterRecovery => "disaster-recovery",
|
||||||
|
Path::Shard => "shard",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,6 +82,7 @@ impl Path {
|
||||||
match self {
|
match self {
|
||||||
Self::Default => paths::OPENPGP.clone(),
|
Self::Default => paths::OPENPGP.clone(),
|
||||||
Self::DisasterRecovery => paths::OPENPGP_DISASTER_RECOVERY.clone(),
|
Self::DisasterRecovery => paths::OPENPGP_DISASTER_RECOVERY.clone(),
|
||||||
|
Self::Shard => paths::OPENPGP_SHARD.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "keyfork-prompt"
|
name = "keyfork-prompt"
|
||||||
version = "0.2.2"
|
version = "0.2.3"
|
||||||
description = "Prompt management utilities for Keyfork"
|
description = "Prompt management utilities for Keyfork"
|
||||||
repository = "https://git.distrust.co/public/keyfork"
|
repository = "https://git.distrust.co/public/keyfork"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
|
@ -83,6 +83,34 @@ impl<T: Choice> Choice for &T {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A Yes/No Choice.
|
||||||
|
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||||
|
pub enum YesNo {
|
||||||
|
/// Yes.
|
||||||
|
Yes,
|
||||||
|
|
||||||
|
/// No.
|
||||||
|
No,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for YesNo {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
YesNo::Yes => f.write_str("Yes"),
|
||||||
|
YesNo::No => f.write_str("No"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Choice for YesNo {
|
||||||
|
fn identifier(&self) -> Option<char> {
|
||||||
|
match self {
|
||||||
|
YesNo::Yes => Some('y'),
|
||||||
|
YesNo::No => Some('n'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub type BoxResult = std::result::Result<(), Box<dyn std::error::Error>>;
|
pub type BoxResult = std::result::Result<(), Box<dyn std::error::Error>>;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue