use crate::error::{Error, Result};
use std::path::Path;
pub fn ensure_secure_dir(path: &Path) -> Result<()> {
std::fs::create_dir_all(path).map_err(|e| Error::DirectoryCreate {
path: path.to_path_buf(),
source: e,
})?;
set_secure_dir_permissions(path)
}
#[cfg(unix)]
pub fn set_secure_dir_permissions(path: &Path) -> Result<()> {
use std::fs;
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(path)
.map_err(|e| Error::FileRead {
path: path.to_path_buf(),
source: e,
})?
.permissions();
perms.set_mode(0o700);
fs::set_permissions(path, perms).map_err(|e| Error::FileWrite {
path: path.to_path_buf(),
source: e,
})
}
#[cfg(unix)]
pub fn set_secure_file_permissions(path: &Path) -> Result<()> {
use std::fs;
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(path)
.map_err(|e| Error::FileRead {
path: path.to_path_buf(),
source: e,
})?
.permissions();
perms.set_mode(0o600);
fs::set_permissions(path, perms).map_err(|e| Error::FileWrite {
path: path.to_path_buf(),
source: e,
})
}
#[cfg(not(unix))]
pub fn set_secure_file_permissions(path: &Path) -> Result<()> {
set_secure_acl(path)
}
#[cfg(not(unix))]
pub fn set_secure_dir_permissions(path: &Path) -> Result<()> {
set_secure_acl(path)
}
#[cfg(windows)]
mod win32_acl {
use super::{Error, Result};
use std::mem::MaybeUninit;
use std::os::windows::ffi::OsStrExt;
use std::path::Path;
use windows::Win32::Foundation::{CloseHandle, HANDLE, HLOCAL, LocalFree};
use windows::Win32::Security::Authorization::{
EXPLICIT_ACCESS_W, SE_FILE_OBJECT, SET_ACCESS, SetEntriesInAclW, SetNamedSecurityInfoW,
TRUSTEE_IS_SID, TRUSTEE_W,
};
use windows::Win32::Security::{
ACL, DACL_SECURITY_INFORMATION, GetTokenInformation, NO_INHERITANCE,
PROTECTED_DACL_SECURITY_INFORMATION, TOKEN_QUERY, TOKEN_USER, TokenUser,
};
use windows::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
use windows::core::PWSTR;
pub(super) struct OwnedHandle(HANDLE);
impl Drop for OwnedHandle {
fn drop(&mut self) {
unsafe {
let _ = CloseHandle(self.0);
}
}
}
pub(super) struct LocalAlloced<T>(*mut T);
impl<T> Drop for LocalAlloced<T> {
fn drop(&mut self) {
unsafe {
let _ = LocalFree(Some(HLOCAL(self.0.cast())));
}
}
}
pub(super) fn set_secure_acl(path: &Path) -> Result<()> {
let mut path_wide: Vec<u16> = path.as_os_str().encode_wide().chain(Some(0)).collect();
let token = {
let mut raw = HANDLE::default();
unsafe {
OpenProcessToken(
GetCurrentProcess(),
TOKEN_QUERY,
std::ptr::addr_of_mut!(raw),
)
.map_err(|e| Error::Config(format!("OpenProcessToken failed: {e}")))?;
}
OwnedHandle(raw) };
let token_user_buf: Vec<u8> = {
let mut needed = 0u32;
unsafe {
let _ = GetTokenInformation(
token.0,
TokenUser,
None,
0,
std::ptr::addr_of_mut!(needed),
);
}
let mut buf = vec![0u8; needed as usize];
unsafe {
GetTokenInformation(
token.0,
TokenUser,
Some(buf.as_mut_ptr().cast()),
needed,
std::ptr::addr_of_mut!(needed),
)
.map_err(|e| Error::Config(format!("GetTokenInformation failed: {e}")))?;
}
buf
};
drop(token);
let user_sid = {
let mut slot = MaybeUninit::<TOKEN_USER>::uninit();
unsafe {
std::ptr::copy_nonoverlapping(
token_user_buf.as_ptr(),
slot.as_mut_ptr().cast::<u8>(),
std::mem::size_of::<TOKEN_USER>(),
);
slot.assume_init().User.Sid
}
};
let access = EXPLICIT_ACCESS_W {
grfAccessPermissions: 0x001F_01FF, grfAccessMode: SET_ACCESS,
grfInheritance: NO_INHERITANCE,
Trustee: TRUSTEE_W {
TrusteeForm: TRUSTEE_IS_SID,
ptstrName: PWSTR(user_sid.0.cast::<u16>()),
..Default::default()
},
};
let acl = {
let mut raw: *mut ACL = std::ptr::null_mut();
unsafe {
SetEntriesInAclW(Some(&[access]), None, std::ptr::addr_of_mut!(raw))
.ok()
.map_err(|e| Error::Config(format!("SetEntriesInAclW failed: {e:?}")))?;
}
LocalAlloced(raw) };
let result = unsafe {
SetNamedSecurityInfoW(
PWSTR(path_wide.as_mut_ptr()),
SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION,
None,
None,
Some(acl.0),
None,
)
};
result
.ok()
.map_err(|e| Error::Config(format!("SetNamedSecurityInfoW failed: {e:?}")))
}
}
#[cfg(windows)]
use win32_acl::set_secure_acl;
#[cfg(not(any(unix, windows)))]
fn set_secure_acl(_path: &Path) -> Result<()> {
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::tempdir;
#[test]
fn test_secure_file_permissions() {
let dir = tempdir().unwrap();
let path = dir.path().join("test.txt");
fs::write(&path, "test").unwrap();
set_secure_file_permissions(&path).unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
assert_eq!(
fs::metadata(&path).unwrap().permissions().mode() & 0o777,
0o600
);
}
}
#[test]
fn test_secure_dir_permissions() {
let dir = tempdir().unwrap();
let path = dir.path().join("secure");
fs::create_dir_all(&path).unwrap();
set_secure_dir_permissions(&path).unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
assert_eq!(
fs::metadata(&path).unwrap().permissions().mode() & 0o777,
0o700
);
}
}
}