Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: mnemonic rotation #281

Merged
merged 11 commits into from
Apr 22, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "tofnd"
version = "0.9.1"
version = "0.10.0"
authors = ["Gus Gutoski <[email protected]>", "Stelios Daveas <[email protected]>"]
edition = "2018"
license = "MIT OR Apache-2.0"
Expand Down
2 changes: 1 addition & 1 deletion proto
Submodule proto updated 2 files
+1 −1 common.proto
+1 −1 multisig.proto
2 changes: 1 addition & 1 deletion src/kv_manager/sled_bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use super::types::{KeyReservation, DEFAULT_RESERVE};

use crate::encrypted_sled;

/// Reserves a key. New's key value is [DEFAULT_RESERVE].
/// Reserves a key. New key's value is [DEFAULT_RESERVE].
/// Returns [SledErr] of [LogicalErr] on failure.
pub(super) fn handle_reserve(
kv: &encrypted_sled::Db,
Expand Down
116 changes: 95 additions & 21 deletions src/mnemonic/cmd_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,18 +90,25 @@ impl KvManager {
)
}

pub async fn seed_key_iter(&self) -> InnerMnemonicResult<impl Iterator<Item = String>> {
pub async fn seed_key_iter(&self) -> InnerMnemonicResult<Vec<String>> {
let count = self.seed_count().await?;
if count == 0 {
return Err(KvErr(KvError::GetErr(InnerKvError::LogicalErr(
"no mnemonic found".to_owned(),
))));
}

// The old mnemonics are stored in descending order
let range = (0..1).chain(count..0).map(|i| {
match i {
0 => String::from(MNEMONIC_KEY), // latest mnemonic is preserved in the original key
_ => std::format!("{}_{}", MNEMONIC_KEY, i), // i is 0-indexed
}
});
// To optimize performance, iterate from the latest mnemonic to the oldest
// Latest mnemonic is stored under 'mnemonic'
// Second latest mnemonic is stored under 'mnemonic_x' where x is the seed count
// Older mnemonics are stored in decreasing order of index
let mut keys = vec![String::from(MNEMONIC_KEY)];

for i in (1..count).rev() {
keys.push(format!("{}_{}", MNEMONIC_KEY, i))
}

Ok(range)
Ok(keys)
}

/// async function that handles all mnemonic commands
Expand Down Expand Up @@ -138,15 +145,11 @@ impl KvManager {
match self.kv().get(MNEMONIC_COUNT_KEY).await {
Ok(encoded_count) => Ok(deserialize(&encoded_count)
.ok_or(KvErr(KvError::GetErr(InnerKvError::DeserializationErr)))?),
Err(KvError::GetErr(_)) => {
let count = if self.kv().exists(MNEMONIC_KEY).await? {
1
} else {
0
};

Ok(count)
}
// if MNEMONIC_COUNT_KEY does not exist then mnemonic count is either 0 or 1
Err(KvError::GetErr(_)) => Ok(match self.kv().exists(MNEMONIC_KEY).await? {
true => 1,
false => 0,
}),
Err(_) => {
error!("");
Err(PasswordErr(String::from("")))
Expand Down Expand Up @@ -206,8 +209,10 @@ impl KvManager {
KvErr(err)
})?;

// Insert before updating the count to minimize state corruption if it fails in the middle
self.put_entropy(reservation, entropy).await?;

// If delete isn't successful, the previous mnemonic count will still allow tofnd to work
self.kv().delete(MNEMONIC_COUNT_KEY).await.map_err(|err| {
error!("could not delete mnemonic count: {:?}", err);
KvErr(err)
Expand All @@ -225,6 +230,7 @@ impl KvManager {
let encoded_count = serialize(&(count + 1))
.map_err(|_| KvErr(KvError::PutErr(InnerKvError::SerializationErr)))?;

// If the new count isn't written, tofnd will still work with the latest mnemonic
self.kv()
.put(count_reservation, encoded_count)
.await
Expand All @@ -240,16 +246,18 @@ impl KvManager {
async fn handle_create(&self) -> InnerMnemonicResult<()> {
info!("Creating mnemonic");

// create a new entropy
let new_entropy = bip39_new_w24();
if self.kv().exists(MNEMONIC_KEY).await? {
error!("Mnemonic was already created");
return Err(KvErr(KvError::ReserveErr(InnerKvError::LogicalErr(
"mnemonic was already present".to_owned(),
))));
}

// create a new entropy
let new_entropy = bip39_new_w24();

self.handle_insert(new_entropy.clone()).await?;

Ok(self.io().entropy_to_file(new_entropy)?)
}

Expand Down Expand Up @@ -291,6 +299,7 @@ impl KvManager {
// create a new entropy
let new_entropy = bip39_new_w24();

// export right away in case of intermediate failures
self.io().entropy_to_file(new_entropy.clone())?;

let current_entropy: Entropy = self
Expand Down Expand Up @@ -326,7 +335,7 @@ impl KvManager {

#[cfg(test)]
mod tests {
use std::path::PathBuf;
use std::{io::Read, path::PathBuf};
use testdir::testdir;

use crate::{
Expand Down Expand Up @@ -426,4 +435,69 @@ mod tests {
Err(InnerMnemonicError::FileIoErr(FileIoError::Exists(_)))
));
}

#[traced_test]
#[tokio::test]
async fn test_rotate() {
let testdir = testdir!();
let rotations = 5;

// create a service
let kv = get_kv_manager(testdir);

let path = std::path::Path::new(kv.io().export_path());

let mut seeds: Vec<SecretRecoveryKey> = vec![];

for i in 0..rotations {
if i == 0 {
assert!(kv.handle_create().await.is_ok());
} else {
assert!(kv.handle_rotate().await.is_ok());
}

assert!(kv.io().check_if_not_exported().is_err());

let mut file = std::fs::File::open(path).expect("can't read exported mnemonic");

let mut phrase = String::new();
assert!(file.read_to_string(&mut phrase).is_ok());

assert!(std::fs::remove_file(path).is_ok());

seeds.push(
bip39_seed(
bip39_from_phrase(Password(phrase)).unwrap(),
Password(MNEMONIC_PASSWORD.to_owned()),
)
.unwrap()
.as_bytes()
.try_into()
.unwrap(),
);
}

let mut ordered_keys = vec![MNEMONIC_KEY.into()];

for i in (1..rotations).rev() {
ordered_keys.push(format!("{}_{}", MNEMONIC_KEY, i));
}

assert_eq!(
ordered_keys,
kv.seed_key_iter().await.expect("failed to get seed keys")
);

for (i, key) in kv.seed_key_iter().await.unwrap().iter().enumerate() {
let seed = kv.get_seed(key).await.expect("failed to retrieve seed");

assert_eq!(
format!("{:?}", seeds[rotations - i - 1]),
format!("{:?}", seed),
"seed {} with key {}",
i,
key
);
}
}
}
21 changes: 12 additions & 9 deletions src/multisig/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,22 @@ impl MultisigService {
}

/// Given a `key_uid` and `pub_key`, find the matching mnemonic.
/// If `pub_key` is empty, use the currently active mnemonic.
/// If `pub_key` is [None], use the currently active mnemonic.
pub(super) async fn find_matching_seed(
&self,
key_uid: &str,
pub_key: &[u8],
pub_key: &Option<Vec<u8>>,
) -> TofndResult<SecretRecoveryKey> {
if pub_key.is_empty() {
return self
.kv_manager
.seed()
.await
.map_err(|_| anyhow!("could not find current mnemonic"));
}
let pub_key = match pub_key {
Some(key) => key,
None => {
return self
.kv_manager
.seed()
.await
.map_err(|_| anyhow!("could not find current mnemonic"))
}
};

let seed_key_iter = self
.kv_manager
Expand Down
4 changes: 2 additions & 2 deletions src/multisig/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ impl SignRequest {
key_uid: key_uid.to_string(),
msg_to_sign: vec![32; 32],
party_uid: String::default(),
pub_key: vec![0; 0],
pub_key: None,
}
}
}
Expand Down Expand Up @@ -209,7 +209,7 @@ async fn test_key_presence() {

let presence_request = KeyPresenceRequest {
key_uid: "key_uid".to_string(),
pub_key: vec![0; 0],
pub_key: None,
};

let response = client
Expand Down
2 changes: 1 addition & 1 deletion src/tests/tofnd_party.rs
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ impl Party for TofndParty {
async fn execute_key_presence(&mut self, key_uid: String) -> bool {
let key_presence_request = proto::KeyPresenceRequest {
key_uid,
pub_key: vec![0; 0],
pub_key: None,
};

let response = self
Expand Down