Skip to content

Instantly share code, notes, and snippets.

@schierlm
Last active October 11, 2023 22:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save schierlm/64b770168f02699112512f931113bdb3 to your computer and use it in GitHub Desktop.
Save schierlm/64b770168f02699112512f931113bdb3 to your computer and use it in GitHub Desktop.
IOTA Stronghold Dump

IOTA Stronghold Dump

This utility can be used to decrypt and dump data stored in a IOTA Stronghold file (as hex dump) if you know the passphrase. It is based on a patched version of the Stronghold library.

Note that while a IOTA Stronghold created by Firefly Wallet contains the BIP39 Seed, it does not contain the BIP39 Mnemonic from which the Seed is derived.

Usage

git clone https://github.com/iotaledger/stronghold.rs
cd stronghold.rs
git checkout iota-stronghold-v2.0.0
patch -p1 <Version-2.0.0.patch
cargo run --example dump name.stronghold PASSPHRASE_HERE
diff --git a/client/examples/dump/main.rs b/client/examples/dump/main.rs
new file mode 100644
index 00000000..e470753f
--- /dev/null
+++ b/client/examples/dump/main.rs
@@ -0,0 +1,21 @@
+use std::env;
+use core::num::NonZeroU32;
+use iota_stronghold as stronghold;
+use stronghold::{KeyProvider, SnapshotPath, Stronghold};
+use zeroize::Zeroizing;
+
+const PBKDF_SALT: &[u8] = b"wallet.rs";
+
+#[tokio::main]
+async fn main() {
+ let PBKDF_ITER: NonZeroU32 = NonZeroU32::new(100).unwrap();
+ let args: Vec<String> = env::args().collect();
+ let stronghold = Stronghold::default();
+ let snapshot_path = SnapshotPath::from_path(args[1].to_string());
+ let mut buffer = [0u8; 64];
+ crypto::keys::pbkdf::PBKDF2_HMAC_SHA512(args[2].to_string().as_bytes(), PBKDF_SALT, PBKDF_ITER, buffer.as_mut());
+ let keyprovider = KeyProvider::with_passphrase_truncated(buffer[..32].to_vec()).unwrap();
+ print!("Loading snapshot from {}\n", &snapshot_path);
+ stronghold.load_snapshot(&keyprovider, &snapshot_path).expect("Failed to load snapshot");
+ stronghold.dump();
+}
diff --git a/client/src/types/stronghold.rs b/client/src/types/stronghold.rs
index 0c3e3ab3..88d5ee71 100644
--- a/client/src/types/stronghold.rs
+++ b/client/src/types/stronghold.rs
@@ -7,7 +7,7 @@ use crate::{
Snapshot, SnapshotPath, Store, UseKey,
};
use crypto::keys::x25519;
-use engine::vault::ClientId;
+use engine::vault::{ClientId, types::DataTransaction};
use std::{
collections::{hash_map::Entry, HashMap},
ops::Deref,
@@ -380,4 +380,83 @@ impl Stronghold {
}
Ok(())
}
+
+ fn print_hex(prefix: &str, data: &[u8]) {
+ let mut pfx = prefix;
+ for line in 0 .. (data.len() + 15) / 16 {
+ print!("{:25} ", pfx);
+ let len = std::cmp::min(16, data.len() - line * 16);
+
+ for i in 0 .. len {
+ let mut ch = data[line * 16 + i];
+ if ch < 32 || ch > 126 {
+ ch = b'.';
+ }
+ print!("{}", ch as char);
+ }
+ for i in len .. 16 {
+ print!(" ");
+ }
+ print!(" ");
+ for i in 0 .. len {
+ print!("{:02x} ", data[line * 16 + i]);
+ }
+ pfx = "";
+ println!();
+ }
+ }
+
+ pub fn dump(&self) {
+ println!("Dumping clients...");
+ for key in self.snapshot.read().unwrap().deref().clients() {
+ Self::print_hex("Client:", &key.0.as_ref());
+ let state = self.snapshot.read().unwrap().deref().get_state(key).unwrap();
+ for vaultid in state.0.keys() {
+ Self::print_hex(" Key Vault:", &vaultid.0.as_ref());
+ let boxed = &state.0.get(vaultid).unwrap().key.boxed;
+ boxed.unlock();
+ Self::print_hex(" Key:", boxed.as_slice());
+ boxed.lock();
+ }
+ for vaultid in state.1.vaults.keys() {
+ Self::print_hex(" DB Vault:", &vaultid.0.as_ref());
+ let vault = &state.1.vaults.get(vaultid).unwrap();
+ let key = &vault.key.key.boxed;
+ key.unlock();
+ Self::print_hex(" Key:", key.as_slice());
+ key.lock();
+ for entry in vault.entries.clone() {
+ Self::print_hex(" ChainID:", entry.0.as_ref());
+ let record = &entry.1;
+ Self::print_hex(" Record ID:", record.id.as_ref());
+ Self::print_hex(" Record Data:", record.data.as_ref());
+ if entry.1.revoke.is_some() {
+ Self::print_hex(" Record Revoke:", &record.revoke.as_ref().unwrap().as_ref());
+ }
+ Self::print_hex(" Record Blob:", record.blob.as_ref());
+ println!(" Decrypted data transaction metadata:");
+ let ttxn = record.get_transaction(&vault.key).unwrap();
+ let txn = ttxn.typed::<DataTransaction>().unwrap();
+ Self::print_hex(" Type ID:", &u64::to_be_bytes(txn.type_id.u64()));
+ Self::print_hex(" Len:", &u64::to_be_bytes(txn.len.u64()));
+ Self::print_hex(" ID:", txn.id.as_ref());
+ Self::print_hex(" Blob ID:", txn.blob.as_ref());
+ Self::print_hex(" Record hint:", txn.record_hint.as_ref());
+ let blob = &entry.1.get_blob(&vault.key, entry.0).unwrap().boxed;
+ blob.unlock();
+ Self::print_hex(" Decrypted data:", blob.as_slice());
+ if blob.as_slice().len() == 64 {
+ println!(" (!!! MIGHT BE BIP39 SEED !!!)");
+ }
+ blob.lock();
+ }
+ let cache = state.clone().2;
+ for cache_key in cache.keys() {
+ Self::print_hex(" Cache key:", &cache_key);
+ Self::print_hex(" Value:", cache.get(&cache_key).unwrap());
+ }
+ }
+ }
+ println!("Done.");
+ }
}
diff --git a/engine/runtime/src/boxed.rs b/engine/runtime/src/boxed.rs
index 452e4ffc..41bba757 100644
--- a/engine/runtime/src/boxed.rs
+++ b/engine/runtime/src/boxed.rs
@@ -28,7 +28,7 @@ type RefCount = u8;
/// A protected piece of memory.
#[derive(Eq)]
-pub(crate) struct Boxed<T: Bytes> {
+pub struct Boxed<T: Bytes> {
// the pointer to the underlying protected memory
ptr: NonNull<T>,
// The number of elements of type `T` that can be stored in the pointer.
@@ -92,7 +92,7 @@ impl<T: Bytes> Boxed<T> {
self.len * T::size()
}
- pub(crate) fn unlock(&self) -> &Self {
+ pub fn unlock(&self) -> &Self {
self.retain(Prot::ReadOnly);
self
}
@@ -102,7 +102,7 @@ impl<T: Bytes> Boxed<T> {
self
}
- pub(crate) fn lock(&self) {
+ pub fn lock(&self) {
self.release()
}
@@ -126,7 +126,7 @@ impl<T: Bytes> Boxed<T> {
unsafe { self.ptr.as_mut() }
}
- pub(crate) fn as_slice(&self) -> &[T] {
+ pub fn as_slice(&self) -> &[T] {
assert!(self.prot.get() != Prot::NoAccess, "May not call Boxed while locked");
unsafe { slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
diff --git a/engine/runtime/src/memories/buffer.rs b/engine/runtime/src/memories/buffer.rs
index 2b32dcab..7f8fff9f 100644
--- a/engine/runtime/src/memories/buffer.rs
+++ b/engine/runtime/src/memories/buffer.rs
@@ -23,7 +23,7 @@ use serde::{
/// This shall always be short lived
#[derive(Clone, Eq)]
pub struct Buffer<T: Bytes> {
- boxed: Boxed<T>, // the boxed type of current GuardedVec,
+ pub boxed: Boxed<T>, // the boxed type of current GuardedVec,
}
pub struct Ref<'a, T: Bytes> {
diff --git a/engine/src/vault.rs b/engine/src/vault.rs
index cfbc0c04..b2a834f2 100644
--- a/engine/src/vault.rs
+++ b/engine/src/vault.rs
@@ -18,7 +18,7 @@
mod base64;
mod crypto_box;
-mod types;
+pub mod types;
pub mod view;
pub use crate::vault::{
diff --git a/engine/src/vault/view.rs b/engine/src/vault/view.rs
index 3874686f..303603d0 100644
--- a/engine/src/vault/view.rs
+++ b/engine/src/vault/view.rs
@@ -59,21 +59,21 @@ pub struct DbView<P: BoxProvider> {
/// A enclave of data that is encrypted under one [`Key`].
#[derive(Deserialize, Serialize, Clone)]
pub struct Vault<P: BoxProvider> {
- key: Key<P>,
- entries: HashMap<ChainId, Record>,
+ pub key: Key<P>,
+ pub entries: HashMap<ChainId, Record>,
}
/// A bit of data inside of a [`Vault`].
#[derive(Deserialize, Serialize, Clone)]
pub struct Record {
/// record id.
- id: ChainId,
+ pub id: ChainId,
/// data transaction metadata.
- data: SealedTransaction,
+ pub data: SealedTransaction,
/// revocation transaction metadata.
- revoke: Option<SealedTransaction>,
+ pub revoke: Option<SealedTransaction>,
/// encrypted data in blob format.
- blob: SealedBlob,
+ pub blob: SealedBlob,
}
impl<P: BoxProvider> DbView<P> {
@@ -461,7 +461,7 @@ impl Record {
})
}
- fn get_transaction<P: BoxProvider>(&self, key: &Key<P>) -> Result<Transaction, RecordError<P::Error>> {
+ pub fn get_transaction<P: BoxProvider>(&self, key: &Key<P>) -> Result<Transaction, RecordError<P::Error>> {
// check if a revocation transaction exists.
if self.revoke.is_none() {
// decrypt data transaction.
@@ -497,7 +497,7 @@ impl Record {
}
/// Get the blob from this [`Record`].
- fn get_blob<P: BoxProvider>(&self, key: &Key<P>, id: ChainId) -> Result<Buffer<u8>, RecordError<P::Error>> {
+ pub fn get_blob<P: BoxProvider>(&self, key: &Key<P>, id: ChainId) -> Result<Buffer<u8>, RecordError<P::Error>> {
// check if ids match
if self.id != id {
return Err(RecordError::RecordNotFound(id));
--
2.41.0.windows.1
diff --git a/client/examples/dump/main.rs b/client/examples/dump/main.rs
new file mode 100644
index 00000000..3f57ee0f
--- /dev/null
+++ b/client/examples/dump/main.rs
@@ -0,0 +1,25 @@
+use std::env;
+use crypto::hashes::{blake2b::Blake2b256, Digest};
+use iota_stronghold as stronghold;
+use stronghold::{KeyProvider, SnapshotPath, Stronghold};
+use zeroize::Zeroizing;
+
+fn hash_blake2b(input: String) -> Zeroizing<Vec<u8>> {
+ let mut hasher = Blake2b256::new();
+ hasher.update(input.as_bytes());
+ let mut hash = Zeroizing::new(vec![0_u8; Blake2b256::output_size()]);
+ hasher.finalize_into((&mut hash[..]).into());
+ hash
+}
+
+#[tokio::main]
+async fn main() {
+ let args: Vec<String> = env::args().collect();
+ let stronghold = Stronghold::default();
+ let snapshot_path = SnapshotPath::from_path(args[1].to_string());
+ let key = hash_blake2b(args[2].to_string());
+ let keyprovider = KeyProvider::try_from(key).expect("Failed to load key");
+ print!("Loading snapshot from {}\n", &snapshot_path);
+ stronghold.load_snapshot(&keyprovider, &snapshot_path).expect("Failed to load snapshot");
+ stronghold.dump();
+}
diff --git a/client/src/types/stronghold.rs b/client/src/types/stronghold.rs
index 33ff9ded..cfa5acc6 100644
--- a/client/src/types/stronghold.rs
+++ b/client/src/types/stronghold.rs
@@ -8,7 +8,7 @@ use crate::{
Snapshot, SnapshotPath, Store, UseKey,
};
use crypto::keys::x25519;
-use engine::vault::ClientId;
+use engine::vault::{ClientId, types::DataTransaction};
use std::{
collections::{hash_map::Entry, HashMap},
ops::Deref,
@@ -381,4 +381,83 @@ impl Stronghold {
}
Ok(())
}
+
+ fn print_hex(prefix: &str, data: &[u8]) {
+ let mut pfx = prefix;
+ for line in 0 .. (data.len() + 15) / 16 {
+ print!("{:25} ", pfx);
+ let len = std::cmp::min(16, data.len() - line * 16);
+
+ for i in 0 .. len {
+ let mut ch = data[line * 16 + i];
+ if ch < 32 || ch > 126 {
+ ch = b'.';
+ }
+ print!("{}", ch as char);
+ }
+ for i in len .. 16 {
+ print!(" ");
+ }
+ print!(" ");
+ for i in 0 .. len {
+ print!("{:02x} ", data[line * 16 + i]);
+ }
+ pfx = "";
+ println!();
+ }
+ }
+
+ pub fn dump(&self) {
+ println!("Dumping clients...");
+ for key in self.snapshot.read().unwrap().deref().clients() {
+ Self::print_hex("Client:", &key.0.as_ref());
+ let state = self.snapshot.read().unwrap().deref().get_state(key).unwrap();
+ for vaultid in state.0.keys() {
+ Self::print_hex(" Key Vault:", &vaultid.0.as_ref());
+ let boxed = &state.0.get(vaultid).unwrap().key.boxed;
+ boxed.unlock();
+ Self::print_hex(" Key:", boxed.as_slice());
+ boxed.lock();
+ }
+ for vaultid in state.1.vaults.keys() {
+ Self::print_hex(" DB Vault:", &vaultid.0.as_ref());
+ let vault = &state.1.vaults.get(vaultid).unwrap();
+ let key = &vault.key.key.boxed;
+ key.unlock();
+ Self::print_hex(" Key:", key.as_slice());
+ key.lock();
+ for entry in vault.entries.clone() {
+ Self::print_hex(" ChainID:", entry.0.as_ref());
+ let record = &entry.1;
+ Self::print_hex(" Record ID:", record.id.as_ref());
+ Self::print_hex(" Record Data:", record.data.as_ref());
+ if entry.1.revoke.is_some() {
+ Self::print_hex(" Record Revoke:", &record.revoke.as_ref().unwrap().as_ref());
+ }
+ Self::print_hex(" Record Blob:", record.blob.as_ref());
+ println!(" Decrypted data transaction metadata:");
+ let ttxn = record.get_transaction(&vault.key).unwrap();
+ let txn = ttxn.typed::<DataTransaction>().unwrap();
+ Self::print_hex(" Type ID:", &u64::to_be_bytes(txn.type_id.u64()));
+ Self::print_hex(" Len:", &u64::to_be_bytes(txn.len.u64()));
+ Self::print_hex(" ID:", txn.id.as_ref());
+ Self::print_hex(" Blob ID:", txn.blob.as_ref());
+ Self::print_hex(" Record hint:", txn.record_hint.as_ref());
+ let blob = &entry.1.get_blob(&vault.key, entry.0).unwrap().boxed;
+ blob.unlock();
+ Self::print_hex(" Decrypted data:", blob.as_slice());
+ if blob.as_slice().len() == 64 {
+ println!(" (!!! MIGHT BE BIP39 SEED !!!)");
+ }
+ blob.lock();
+ }
+ let cache = state.clone().2;
+ for cache_key in cache.keys() {
+ Self::print_hex(" Cache key:", &cache_key);
+ Self::print_hex(" Value:", cache.get(&cache_key).unwrap());
+ }
+ }
+ }
+ println!("Done.");
+ }
}
diff --git a/engine/runtime/src/boxed.rs b/engine/runtime/src/boxed.rs
index 160cf7af..7bd48ca4 100644
--- a/engine/runtime/src/boxed.rs
+++ b/engine/runtime/src/boxed.rs
@@ -28,7 +28,7 @@ type RefCount = u8;
/// A protected piece of memory.
#[derive(Eq)]
-pub(crate) struct Boxed<T: Bytes> {
+pub struct Boxed<T: Bytes> {
// the pointer to the underlying protected memory
ptr: NonNull<T>,
// The number of elements of type `T` that can be stored in the pointer.
@@ -92,7 +92,7 @@ impl<T: Bytes> Boxed<T> {
self.len * T::size()
}
- pub(crate) fn unlock(&self) -> &Self {
+ pub fn unlock(&self) -> &Self {
self.retain(Prot::ReadOnly);
self
}
@@ -102,7 +102,7 @@ impl<T: Bytes> Boxed<T> {
self
}
- pub(crate) fn lock(&self) {
+ pub fn lock(&self) {
self.release()
}
@@ -126,7 +126,7 @@ impl<T: Bytes> Boxed<T> {
unsafe { self.ptr.as_mut() }
}
- pub(crate) fn as_slice(&self) -> &[T] {
+ pub fn as_slice(&self) -> &[T] {
assert!(self.prot.get() != Prot::NoAccess, "May not call Boxed while locked");
unsafe { slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
diff --git a/engine/runtime/src/memories/buffer.rs b/engine/runtime/src/memories/buffer.rs
index a7599713..c7dbd0a7 100644
--- a/engine/runtime/src/memories/buffer.rs
+++ b/engine/runtime/src/memories/buffer.rs
@@ -23,7 +23,7 @@ use serde::{
/// This shall always be short lived
#[derive(Clone, Eq)]
pub struct Buffer<T: Bytes> {
- boxed: Boxed<T>, // the boxed type of current GuardedVec,
+ pub boxed: Boxed<T>, // the boxed type of current GuardedVec,
}
pub struct Ref<'a, T: Bytes> {
diff --git a/engine/src/vault.rs b/engine/src/vault.rs
index cfbc0c04..b2a834f2 100644
--- a/engine/src/vault.rs
+++ b/engine/src/vault.rs
@@ -18,7 +18,7 @@
mod base64;
mod crypto_box;
-mod types;
+pub mod types;
pub mod view;
pub use crate::vault::{
diff --git a/engine/src/vault/view.rs b/engine/src/vault/view.rs
index 3dc178c0..c914fc35 100644
--- a/engine/src/vault/view.rs
+++ b/engine/src/vault/view.rs
@@ -60,21 +60,21 @@ pub struct DbView<P: BoxProvider> {
/// A enclave of data that is encrypted under one [`Key`].
#[derive(Deserialize, Serialize, Clone)]
pub struct Vault<P: BoxProvider> {
- key: Key<P>,
- entries: HashMap<ChainId, Record>,
+ pub key: Key<P>,
+ pub entries: HashMap<ChainId, Record>,
}
/// A bit of data inside of a [`Vault`].
#[derive(Deserialize, Serialize, Clone)]
pub struct Record {
/// record id.
- id: ChainId,
+ pub id: ChainId,
/// data transaction metadata.
- data: SealedTransaction,
+ pub data: SealedTransaction,
/// revocation transaction metadata.
- revoke: Option<SealedTransaction>,
+ pub revoke: Option<SealedTransaction>,
/// encrypted data in blob format.
- blob: SealedBlob,
+ pub blob: SealedBlob,
}
impl<P: BoxProvider> DbView<P> {
@@ -464,7 +464,7 @@ impl Record {
})
}
- fn get_transaction<P: BoxProvider>(&self, key: &Key<P>) -> Result<Transaction, RecordError<P::Error>> {
+ pub fn get_transaction<P: BoxProvider>(&self, key: &Key<P>) -> Result<Transaction, RecordError<P::Error>> {
// check if a revocation transaction exists.
if self.revoke.is_none() {
// decrypt data transaction.
@@ -500,7 +500,7 @@ impl Record {
}
/// Get the blob from this [`Record`].
- fn get_blob<P: BoxProvider>(&self, key: &Key<P>, id: ChainId) -> Result<Buffer<u8>, RecordError<P::Error>> {
+ pub fn get_blob<P: BoxProvider>(&self, key: &Key<P>, id: ChainId) -> Result<Buffer<u8>, RecordError<P::Error>> {
// check if ids match
if self.id != id {
return Err(RecordError::RecordNotFound(id));
--
2.41.0.windows.1
This file has been truncated, but you can view the full file.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment