Skip to content

Commit

Permalink
Merge branch '1.1' into chore/bindings-cleanup-is-alive
Browse files Browse the repository at this point in the history
  • Loading branch information
Brord van Wierst authored Sep 22, 2023
2 parents 359ee32 + ba10600 commit ac10b09
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 8 deletions.
30 changes: 30 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `Account::switch` command to allow changing accounts quickly;
- UX improvements (Ctrl+l, TAB completion/suggestions and more) during interactive account management;
- `WalletCommand::SetPow` command;
- Check for existing stronghold on `restore`;

### Changed

Expand All @@ -38,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `print_wallet_help` changed to `WalletCli::print_help`;
- `print_account_help` changed to `AccountCli::print_help`;
- `AccountCommand::Addresses` now prints an overview that includes NTs, NFTs, Aliases and Foundries;
- Restrict permissions of mnemonic file on Windows;

## 1.0.1 - 2023-MM-DD

Expand Down
4 changes: 4 additions & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,7 @@ serde_json = { version = "1.0.107", default-features = false }
thiserror = { version = "1.0.48", default-features = false }
tokio = { version = "1.32.0", default-features = false, features = ["fs"] }
zeroize = { version = "1.6.0", default-features = false }

[target.'cfg(target_os = "windows")'.dependencies]
winapi = { version = "0.3.9", default-features = false }
windows-acl = { version = "0.3.0", default-features = false }
25 changes: 17 additions & 8 deletions cli/src/command/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,14 +247,22 @@ pub async fn node_info_command(storage_path: &Path) -> Result<Wallet, Error> {
pub async fn restore_command(storage_path: &Path, snapshot_path: &Path, backup_path: &Path) -> Result<Wallet, Error> {
check_file_exists(backup_path).await?;

let password = get_password("Stronghold password", false)?;
let secret_manager = SecretManager::Stronghold(
StrongholdSecretManager::builder()
.password(password.clone())
.build(snapshot_path)?,
);
let wallet = Wallet::builder()
.with_secret_manager(secret_manager)
let mut builder = Wallet::builder();
if check_file_exists(snapshot_path).await.is_ok() {
println!(
"Detected a stronghold file at {}. Enter password to unlock:",
snapshot_path.to_str().unwrap()
);
let password = get_password("Stronghold password", false)?;
let secret_manager = SecretManager::Stronghold(
StrongholdSecretManager::builder()
.password(password.clone())
.build(snapshot_path)?,
);
builder = builder.with_secret_manager(secret_manager);
}

let wallet = builder
// Will be overwritten by the backup's value.
.with_client_options(ClientOptions::new().with_node(DEFAULT_NODE_URL)?)
.with_storage_path(storage_path.to_str().expect("invalid unicode"))
Expand All @@ -263,6 +271,7 @@ pub async fn restore_command(storage_path: &Path, snapshot_path: &Path, backup_p
.finish()
.await?;

let password = get_password("Stronghold backup password", false)?;
wallet.restore_backup(backup_path.into(), password, None, None).await?;

println_log_info!(
Expand Down
77 changes: 77 additions & 0 deletions cli/src/helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,83 @@ async fn write_mnemonic_to_file(path: &str, mnemonic: &str) -> Result<(), Error>
let mut file = open_options.open(path).await?;
file.write_all(format!("{mnemonic}\n").as_bytes()).await?;

#[cfg(windows)]
restrict_file_permissions(path)?;

Ok(())
}

// Slightly modified from https://github.com/sile/sloggers/blob/master/src/permissions.rs
#[cfg(windows)]
pub fn restrict_file_permissions<P: AsRef<Path>>(path: P) -> std::io::Result<()> {
use std::io;

use winapi::um::winnt::{FILE_GENERIC_READ, FILE_GENERIC_WRITE, PSID, STANDARD_RIGHTS_ALL};
use windows_acl::{
acl::{AceType, ACL},
helper::sid_to_string,
};

/// This is the security identifier in Windows for the owner of a file. See:
/// - https://docs.microsoft.com/en-us/troubleshoot/windows-server/identity/security-identifiers-in-windows#well-known-sids-all-versions-of-windows
const OWNER_SID_STR: &str = "S-1-3-4";
/// We don't need any of the `AceFlags` listed here:
/// - https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-ace_header
const OWNER_ACL_ENTRY_FLAGS: u8 = 0;
/// Generic Rights:
/// - https://docs.microsoft.com/en-us/windows/win32/fileio/file-security-and-access-rights
/// Individual Read/Write/Execute Permissions (referenced in generic rights link):
/// - https://docs.microsoft.com/en-us/windows/win32/wmisdk/file-and-directory-access-rights-constants
/// STANDARD_RIGHTS_ALL
/// - https://docs.microsoft.com/en-us/windows/win32/secauthz/access-mask
const OWNER_ACL_ENTRY_MASK: u32 = FILE_GENERIC_READ | FILE_GENERIC_WRITE | STANDARD_RIGHTS_ALL;

let path_str = path
.as_ref()
.to_str()
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Unable to open file path.".to_string()))?;

let mut acl = ACL::from_file_path(path_str, false)
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("Unable to retrieve ACL: {:?}", e)))?;

let owner_sid = windows_acl::helper::string_to_sid(OWNER_SID_STR)
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("Unable to convert SID: {:?}", e)))?;

let entries = acl.all().map_err(|e| {
io::Error::new(
io::ErrorKind::Other,
format!("Unable to enumerate ACL entries: {:?}", e),
)
})?;

// Add single entry for file owner.
acl.add_entry(
owner_sid.as_ptr() as PSID,
AceType::AccessAllow,
OWNER_ACL_ENTRY_FLAGS,
OWNER_ACL_ENTRY_MASK,
)
.map_err(|e| {
io::Error::new(
io::ErrorKind::Other,
format!("Failed to add ACL entry for SID {} error={}", OWNER_SID_STR, e),
)
})?;
// Remove all AccessAllow entries from the file that aren't the owner_sid.
for entry in &entries {
if let Some(ref entry_sid) = entry.sid {
let entry_sid_str = sid_to_string(entry_sid.as_ptr() as PSID).unwrap_or_else(|_| "BadFormat".to_string());
if entry_sid_str != OWNER_SID_STR {
acl.remove(entry_sid.as_ptr() as PSID, Some(AceType::AccessAllow), None)
.map_err(|_| {
io::Error::new(
io::ErrorKind::Other,
format!("Failed to remove ACL entry for SID {}", entry_sid_str),
)
})?;
}
}
}
Ok(())
}

Expand Down
20 changes: 20 additions & 0 deletions sdk/src/client/stronghold/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,16 @@ impl StrongholdAdapterBuilder {
/// [`password()`]: Self::password()
/// [`timeout()`]: Self::timeout()
pub fn build<P: AsRef<Path>>(self, snapshot_path: P) -> Result<StrongholdAdapter, Error> {
if snapshot_path.as_ref().is_dir() {
// TODO: Add Error in 2.0 as its breaking.
// Issue #1197
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("Path is not a file: {:?}", snapshot_path.as_ref().to_path_buf()),
)
.into());
}

// In any case, Stronghold - as a necessary component - needs to be present at this point.
let stronghold = self.stronghold.unwrap_or_default();

Expand Down Expand Up @@ -484,6 +494,16 @@ impl StrongholdAdapter {
///
/// [`unload_stronghold_snapshot()`]: Self::unload_stronghold_snapshot()
pub async fn write_stronghold_snapshot(&self, snapshot_path: Option<&Path>) -> Result<(), Error> {
if let Some(p) = snapshot_path {
if p.is_dir() {
// TODO: Add Error in 2.0 as its breaking.
// Issue #1197
return Err(
std::io::Error::new(std::io::ErrorKind::Other, format!("Path is not a file: {:?}", p)).into(),
);
}
}

// The key needs to be supplied first.
let locked_key_provider = self.key_provider.lock().await;
let key_provider = if let Some(key_provider) = &*locked_key_provider {
Expand Down

0 comments on commit ac10b09

Please sign in to comment.