Skip to content

Commit

Permalink
CLI: Restrict file permissions on Windows (#1281)
Browse files Browse the repository at this point in the history
* Restrict file permissions on Windows

* ORDER

* Cleanup

* Changelog

---------

Co-authored-by: Thibault Martinez <[email protected]>
  • Loading branch information
Thoralf-M and thibault-martinez authored Sep 22, 2023
1 parent 59929bf commit b9033f9
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 0 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.

1 change: 1 addition & 0 deletions cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,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 }
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

0 comments on commit b9033f9

Please sign in to comment.