Skip to content

Commit

Permalink
Snapshot migration v3age 2.0 (#474)
Browse files Browse the repository at this point in the history
* snapshot migration: v2->v3 (age)

* cargo: update crypto.rs dependency

* snapshot tests: updated test vectors

* Derive Display for Error

* Fix warnings

* snapshot: fail on associated data

* snapshot error

* snapshot: v2 + wallet + identity + refactor

* snapshot: v2 generalize value ctor + CryptoError

* snapshot: v2 factor out wallet/identity

* Split into module files

* Visibility and warnings

* publicly export error

* Fmt

* Bump crypto.rs

* snapshot age: update to crypto-0.16

* snapshot age: 0 work_factor for strong keys

* Remove redundant mod

* Clippy

* Add missing license headers

* refactor + security comments

* bump crypto-0.17.0

* snapshot errors

* runtime: xor_mut

* keyprovider: ctors cleanup

* zeroizing

* Zeroizing

* more Zeroizing

* deprecate NCM::refresh

* age release work factor

* fmt

* nits

* unsafe rewritten

* cleanup

* KeyProvider deperecated notes added

* undeprecate NCM::refresh

* fix build errors

* nits

* clippy + silenced warnings

* license new lines

* get_guards simplify

* deref, do not clone

* removed test migrate tool

* changes

* reexport engine from client

* cargo: iota-crypto 0.18.0

* fmt + clippy

* snapshot: removed unused empty associated data

* associated data + changelog

* lower case in deprecated messages

* nits + fmt

* error message: lower case

* error messages

* bump iota-crypto-0.20.0

* error message

* slip10 update

* deprecated removed

* changelog: typo

* better changelog message

* fmt

* removed unused allow deprecated

* typo

* removed irrelevant comment

* changes packages

* NCM better names

---------

Co-authored-by: Thibault Martinez <thibault@iota.org>
  • Loading branch information
semenov-vladyslav and thibault-martinez committed May 24, 2023
1 parent 0fabace commit 1e72f00
Show file tree
Hide file tree
Showing 58 changed files with 932 additions and 716 deletions.
2 changes: 1 addition & 1 deletion .changes/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
| iota-stronghold | The Client | Rust | Yes | Yes |
| stronghold-engine | The Engine | Rust | Yes | Yes |
| stronghold-p2p | Communication Subsystem | Rust | Yes | No |
| runtime | Secure Zone | Rust | Yes | No |
| stronghold-runtime | Secure Zone | Rust | Yes | No |
| vault | Engine's memory Store | Rust | No | No |
| snapshot | Engine's Persistence | Rust | No | No |
| store | Engine's Readable Storage Interface | Rust | No | No |
Expand Down
2 changes: 1 addition & 1 deletion .changes/secp256k1.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
---

Secp256k1 ECDSA + SLIP-10 support added.
Bump `iota-crypto` version to 0.19.0.
Bump `iota-crypto` version to 0.20.0.
11 changes: 11 additions & 0 deletions .changes/snapshot-migration-v3age-zeroize.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---

"iota-stronghold": minor
"stronghold-engine" : minor
"stronghold-runtime" : minor

---

Upgraded snapshot format to age-encryption.org/v1 with password-based recipient stanza. This resolves the issue with the previous snapshot format encryption being insecure if used with weak passwords. Snapshot encryption doesn't use associated data.
Added sensitive data zeroization which would otherwise leak in stack and heap memory in plaintext after use.
`KeyProvider` unsafe constructors `with_passphrase_truncated`, `with_passphrase_hashed_argon2` were removed, `with_passphrase_hashed` constructor should be used instead.
3 changes: 2 additions & 1 deletion bindings/native/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ iota_stronghold = { package = "iota_stronghold", path = "../../client/
engine = { package = "stronghold_engine", path = "../../engine", version = "1.0.0" }
tokio = { version = "1.15.0", features = ["full"] }
base64 = { version = "0.13.0" }
iota-crypto = { version = "0.19.0", default-features = false, features = [
iota-crypto = { version = "0.20.0", default-features = false, features = [
"aes-gcm",
"aes-kw",
"random",
Expand All @@ -39,3 +39,4 @@ iota-crypto = { version = "0.19.0", default-features = false, features = [
lazy_static = "1.4.0"
env_logger = { version = "0.9.0" }
log = { version = "0.4.14" }
zeroize = { version = "1.5.7", default-features = false }
3 changes: 2 additions & 1 deletion bindings/native/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright 2020-2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

extern crate core;

mod shared;
Expand Down Expand Up @@ -213,7 +214,7 @@ pub unsafe extern "C" fn stronghold_write_vault(

info!("[Rust] Got Stronghold instance from Box");

if let Err(err) = stronghold_wrapper.write_vault(key_as_hash, record_path, data.to_vec()) {
if let Err(err) = stronghold_wrapper.write_vault(key_as_hash, record_path, data.to_vec().into()) {
set_last_error(err);
return false;
}
Expand Down
8 changes: 6 additions & 2 deletions bindings/native/src/shared.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
// Copyright 2020-2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use crypto::hashes::{blake2b::Blake2b256, Digest};
use zeroize::Zeroizing;

pub fn hash_blake2b(input: String) -> Vec<u8> {
pub fn hash_blake2b(input: String) -> Zeroizing<Vec<u8>> {
let mut hasher = Blake2b256::new();
hasher.update(input.as_bytes());
hasher.finalize().to_vec()
let mut hash = Zeroizing::new(vec![0_u8; Blake2b256::output_size()]);
hasher.finalize_into((&mut hash[..]).into());
hash
}
59 changes: 24 additions & 35 deletions bindings/native/src/wrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use iota_stronghold::{
};
use log::*;
use thiserror::Error as DeriveError;
use zeroize::Zeroizing;

const CLIENT_PATH: &str = "wasp";
const VAULT_PATH: &str = "wasp";
Expand All @@ -27,33 +28,30 @@ pub struct StrongholdWrapper {
#[derive(Debug, DeriveError)]
#[non_exhaustive]
pub enum WrapperError {
#[error("Failed to open snapshot")]
#[error("failed to open snapshot")]
OpenSnapshot,

#[error("Failed to commit to snapshot")]
#[error("failed to commit to snapshot")]
CommitToSnapshot,

#[error("Failed to create client")]
#[error("failed to create client")]
CreateClient,

#[error("Failed to write client")]
#[error("failed to write client")]
WriteClient,

#[error("Failed to execute procedure: ({0})")]
#[error("failed to execute procedure: ({0})")]
ExecuteProcedure(String),
}

impl StrongholdWrapper {
pub fn from_file<R>(snapshot_path: String, key_as_hash: R) -> Result<Self, WrapperError>
where
R: AsRef<[u8]>,
{
pub fn from_file(snapshot_path: String, key_as_hash: Zeroizing<Vec<u8>>) -> Result<Self, WrapperError> {
let stronghold = Stronghold::default();

log::info!("[Rust] Loading snapshot => {}", snapshot_path);

let commit_snapshot_path = &SnapshotPath::from_path(snapshot_path.clone());
let key_provider = &KeyProvider::try_from(key_as_hash.as_ref().to_vec()).unwrap();
let key_provider = &KeyProvider::try_from(key_as_hash).unwrap();

let client = stronghold.load_client_from_snapshot(CLIENT_PATH, key_provider, commit_snapshot_path);

Expand All @@ -69,10 +67,7 @@ impl StrongholdWrapper {
})
}

pub fn create_new<R>(snapshot_path: String, key_as_hash: R) -> Result<Self, WrapperError>
where
R: AsRef<[u8]>,
{
pub fn create_new(snapshot_path: String, key_as_hash: Zeroizing<Vec<u8>>) -> Result<Self, WrapperError> {
let stronghold = Stronghold::default();

let client = match stronghold.create_client(CLIENT_PATH) {
Expand All @@ -99,14 +94,11 @@ impl StrongholdWrapper {
Ok(result)
}

fn commit_with_key<R>(&self, key_as_hash: R) -> Result<bool, WrapperError>
where
R: AsRef<[u8]>,
{
fn commit_with_key(&self, key_as_hash: Zeroizing<Vec<u8>>) -> Result<bool, WrapperError> {
log::info!("[Rust] Committing to snapshot");

let commit_snapshot_path = &SnapshotPath::from_path(self.snapshot_path.clone());
let key_provider = &KeyProvider::try_from(key_as_hash.as_ref().to_vec()).unwrap();
let key_provider = &KeyProvider::try_from(key_as_hash).unwrap();

match self
.stronghold
Expand Down Expand Up @@ -138,10 +130,12 @@ impl StrongholdWrapper {
Ok(output)
}

pub fn write_vault<R>(&self, key_as_hash: R, record_path: String, data: Vec<u8>) -> Result<bool, WrapperError>
where
R: AsRef<[u8]>,
{
pub fn write_vault(
&self,
key_as_hash: Zeroizing<Vec<u8>>,
record_path: String,
data: Zeroizing<Vec<u8>>,
) -> Result<bool, WrapperError> {
let location = Location::Generic {
record_path: record_path.as_bytes().to_vec(),
vault_path: VAULT_PATH.as_bytes().to_vec(),
Expand Down Expand Up @@ -174,10 +168,7 @@ impl StrongholdWrapper {
Ok(signature)
}

pub fn derive_seed<R>(&self, key_as_hash: R, address_index: u32) -> Result<ChainCode, WrapperError>
where
R: AsRef<[u8]>,
{
pub fn derive_seed(&self, key_as_hash: Zeroizing<Vec<u8>>, address_index: u32) -> Result<ChainCode, WrapperError> {
let seed_derived_path = format!("{RECORD_PATH_SEED}.{address_index}");

let seed_location = Location::Generic {
Expand Down Expand Up @@ -227,10 +218,7 @@ impl StrongholdWrapper {
}
}

pub fn generate_seed<R>(&self, key_as_hash: R) -> Result<bool, WrapperError>
where
R: AsRef<[u8]>,
{
pub fn generate_seed(&self, key_as_hash: Zeroizing<Vec<u8>>) -> Result<bool, WrapperError> {
let output = Location::Generic {
record_path: RECORD_PATH_SEED.as_bytes().to_vec(),
vault_path: VAULT_PATH.as_bytes().to_vec(),
Expand Down Expand Up @@ -259,10 +247,11 @@ impl StrongholdWrapper {
self.commit_with_key(key_as_hash)
}

pub fn generate_ed25519_keypair<R>(&self, key_as_hash: R, record_path: String) -> Result<bool, WrapperError>
where
R: AsRef<[u8]>,
{
pub fn generate_ed25519_keypair(
&self,
key_as_hash: Zeroizing<Vec<u8>>,
record_path: String,
) -> Result<bool, WrapperError> {
let output = Location::Generic {
record_path: record_path.as_bytes().to_vec(),
vault_path: VAULT_PATH.as_bytes().to_vec(),
Expand Down
4 changes: 2 additions & 2 deletions client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ insecure = [ ]

[dependencies]
thiserror = { version = "1.0.30" }
zeroize = { version = "1.5.7", default-features = false, features = [ "zeroize_derive" ] }
zeroize = { version = "1.5.7", default-features = false, features = [ "zeroize_derive", "serde" ] }
serde = { version = "1.0", features = [ "derive" ] }
iota-crypto = { version = "0.19.0", default-features = false, features = [
iota-crypto = { version = "0.20.0", default-features = false, features = [
"aes-gcm",
"blake2b",
"aes-kw",
Expand Down
1 change: 1 addition & 0 deletions client/benches/config.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright 2020-2021 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use criterion::{criterion_group, criterion_main, Criterion};

/// Primitve benchmark
Expand Down
8 changes: 6 additions & 2 deletions client/examples/cli/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright 2020-2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

#![allow(unused_imports)]

use std::{error::Error, hash::Hash, num::NonZeroUsize, str::FromStr};
Expand All @@ -18,6 +19,7 @@ use stronghold::{
};
use stronghold_utils::random as rand;
use thiserror::Error as DeriveError;
use zeroize::Zeroizing;

#[derive(Debug)]
pub struct ChainInput {
Expand Down Expand Up @@ -203,10 +205,12 @@ pub enum Command {
}

/// Calculates the Blake2b from a String
fn hash_blake2b(input: String) -> Vec<u8> {
fn hash_blake2b(input: String) -> Zeroizing<Vec<u8>> {
let mut hasher = Blake2b256::new();
hasher.update(input.as_bytes());
hasher.finalize().to_vec()
let mut hash = Zeroizing::new(vec![0_u8; Blake2b256::output_size()]);
hasher.finalize_into((&mut hash[..]).into());
hash
}

async fn command_write_and_read_from_store(key: String, value: String) -> Result<(), ClientError> {
Expand Down
4 changes: 2 additions & 2 deletions client/examples/repl/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ impl Command for BackupCommand {
};
let password = parameters[1].clone().as_bytes().to_vec();
let snapshot_path = SnapshotPath::from_path(&parameters[0]);
let keyprovider = KeyProvider::with_passphrase_truncated(password)?;
let keyprovider = KeyProvider::with_passphrase_hashed_blake2b(password)?;

stronghold.commit_with_keyprovider(&snapshot_path, &keyprovider)?;

Expand Down Expand Up @@ -211,7 +211,7 @@ impl Command for RestoreCommand {
};
let password = parameters[1].clone().as_bytes().to_vec();
let snapshot_path = SnapshotPath::from_path(&parameters[0]);
let keyprovider = KeyProvider::with_passphrase_truncated(password)?;
let keyprovider = KeyProvider::with_passphrase_hashed_blake2b(password)?;

stronghold.load_snapshot(&keyprovider, &snapshot_path)?;

Expand Down
1 change: 1 addition & 0 deletions client/fuzz/basic.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright 2020-2021 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

#![no_main]

fuzz_target!(|data: &[u8]| {});
1 change: 1 addition & 0 deletions client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#[cfg(feature = "std")]
pub use crate::{internal::Provider, security::*, types::*, utils::*};

pub use engine;
#[cfg(feature = "std")]
pub use engine::runtime::MemoryError;

Expand Down
31 changes: 7 additions & 24 deletions client/src/procedures/clientrunner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ use engine::{
vault::{BoxProvider, ClientId, DbView, Key, RecordHint, RecordId, VaultId},
};

use zeroize::Zeroizing;

use crate::{
derive_vault_id,
procedures::{
Expand Down Expand Up @@ -52,22 +54,10 @@ impl Runner for Client {
where
F: FnOnce([Buffer<u8>; N]) -> Result<T, FatalProcedureError>,
{
let mut ret = None;
let execute_procedure = |guard: [Buffer<u8>; N]| {
ret = Some(f(guard)?);
Ok(())
};

let keystore = self.keystore.read().map_err(|_| VaultError::LockPoisoned)?;
let db = self.db.read().map_err(|_| VaultError::LockPoisoned)?;
let ids: [(Key<Provider>, VaultId, RecordId); N] = resolve_locations!(self, locations, keystore)?;

let res = db.get_guards(ids, execute_procedure);

match res {
Ok(()) => Ok(ret.unwrap()),
Err(e) => Err(e),
}
db.get_guards(ids, f)
}

fn exec_proc<F, T, const N: usize>(
Expand All @@ -81,11 +71,9 @@ impl Runner for Client {
{
let (target_vid, target_rid) = target_location.resolve();

let mut ret = None;
let execute_procedure = |guards: [Buffer<u8>; N]| {
let Products { output: plain, secret } = f(guards)?;
ret = Some(plain);
Ok(secret)
Ok((secret, plain))
};

let random_hint = RecordHint::new(rand::variable_bytestring(DEFAULT_RANDOM_HINT_SIZE)).unwrap();
Expand All @@ -106,22 +94,17 @@ impl Runner for Client {
.get_key(target_vid)
.ok_or(VaultError::VaultNotFound(target_vid))?;

let res = db.exec_procedure(
db.exec_procedure(
sources,
&target_key,
target_vid,
target_rid,
random_hint,
execute_procedure,
);

match res {
Ok(()) => Ok(ret.unwrap()),
Err(e) => Err(e),
}
)
}

fn write_to_vault(&self, location: &Location, value: Vec<u8>) -> Result<(), RecordError> {
fn write_to_vault(&self, location: &Location, value: Zeroizing<Vec<u8>>) -> Result<(), RecordError> {
let (vault_id, record_id) = location.resolve();

let mut keystore = self.keystore.write().map_err(|_| RecordError::LockPoisoned)?;
Expand Down
Loading

0 comments on commit 1e72f00

Please sign in to comment.