1use crate::{SecretBytes, SecretResolver, SecurityError, SecurityResult, SecuritySecretRef};
14use std::fs::{self, File, OpenOptions};
15use std::io::Read;
16use std::path::{Component, Path, PathBuf};
17
18const MAX_FILE_SECRET_BYTES: u64 = 65_536;
19
20#[derive(Debug, Clone)]
22pub struct FileSecretResolver {
23 root: PathBuf,
24}
25
26impl FileSecretResolver {
27 pub fn new(root: impl Into<PathBuf>) -> Self {
29 Self { root: root.into() }
30 }
31}
32
33impl SecretResolver for FileSecretResolver {
34 fn resolve(&self, reference: &SecuritySecretRef) -> SecurityResult<SecretBytes> {
35 let relative = validate_relative_reference(&reference.0)?;
36 validate_private_directory(&self.root)?;
37 let canonical_root = fs::canonicalize(&self.root).map_err(unavailable)?;
38 let path = self.root.join(relative);
39 reject_symlink_components(&self.root, relative)?;
40 let canonical_path = fs::canonicalize(&path).map_err(unavailable)?;
41 if !canonical_path.starts_with(&canonical_root) {
42 return Err(SecurityError::InvalidSecretRef);
43 }
44 let mut file = open_no_follow(&path)?;
45 validate_private_file(&file)?;
46 let length = file.metadata().map_err(unavailable)?.len();
47 if length == 0 || length > MAX_FILE_SECRET_BYTES {
48 return Err(SecurityError::SecretUnavailable);
49 }
50 let mut value = Vec::with_capacity(length as usize);
51 file.read_to_end(&mut value).map_err(unavailable)?;
52 Ok(SecretBytes::new(value))
53 }
54}
55
56fn validate_relative_reference(value: &str) -> SecurityResult<&Path> {
57 let path = Path::new(value);
58 if value.is_empty()
59 || path.components().any(|component| {
60 matches!(
61 component,
62 Component::ParentDir | Component::RootDir | Component::Prefix(_)
63 )
64 })
65 {
66 return Err(SecurityError::InvalidSecretRef);
67 }
68 Ok(path)
69}
70
71fn reject_symlink_components(root: &Path, relative: &Path) -> SecurityResult<()> {
72 let mut current = root.to_path_buf();
73 for component in relative.components() {
74 current.push(component);
75 let metadata = fs::symlink_metadata(¤t).map_err(unavailable)?;
76 if metadata.file_type().is_symlink() || is_reparse_point(&metadata) {
77 return Err(SecurityError::InvalidSecretRef);
78 }
79 }
80 Ok(())
81}
82
83#[cfg(windows)]
84fn is_reparse_point(metadata: &fs::Metadata) -> bool {
85 use std::os::windows::fs::MetadataExt;
86 use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT;
87 metadata.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT != 0
88}
89
90#[cfg(not(windows))]
91fn is_reparse_point(_metadata: &fs::Metadata) -> bool {
92 false
93}
94
95#[cfg(unix)]
96fn open_no_follow(path: &Path) -> SecurityResult<File> {
97 use std::os::unix::fs::OpenOptionsExt;
98 OpenOptions::new()
99 .read(true)
100 .custom_flags(libc::O_NOFOLLOW)
101 .open(path)
102 .map_err(unavailable)
103}
104
105#[cfg(windows)]
106fn open_no_follow(path: &Path) -> SecurityResult<File> {
107 use std::os::windows::fs::OpenOptionsExt;
108 use windows_sys::Win32::Storage::FileSystem::FILE_FLAG_OPEN_REPARSE_POINT;
109 OpenOptions::new()
110 .read(true)
111 .custom_flags(FILE_FLAG_OPEN_REPARSE_POINT)
112 .open(path)
113 .map_err(unavailable)
114}
115
116#[cfg(all(not(unix), not(windows)))]
117fn open_no_follow(_path: &Path) -> SecurityResult<File> {
118 Err(SecurityError::SecretUnavailable)
119}
120
121#[cfg(unix)]
122fn validate_private_directory(path: &Path) -> SecurityResult<()> {
123 use std::os::unix::fs::{MetadataExt, PermissionsExt};
124 let metadata = fs::symlink_metadata(path).map_err(unavailable)?;
125 if metadata.file_type().is_symlink()
126 || !metadata.is_dir()
127 || metadata.permissions().mode() & 0o077 != 0
128 || metadata.uid() != unsafe { libc::geteuid() }
129 {
130 return Err(SecurityError::InvalidSecretRef);
131 }
132 Ok(())
133}
134
135#[cfg(windows)]
136fn validate_private_directory(path: &Path) -> SecurityResult<()> {
137 let metadata = fs::symlink_metadata(path).map_err(unavailable)?;
138 if metadata.file_type().is_symlink() || is_reparse_point(&metadata) || !metadata.is_dir() {
139 return Err(SecurityError::InvalidSecretRef);
140 }
141 windows_acl::validate_path_owner_acl(path)
142}
143
144#[cfg(all(not(unix), not(windows)))]
145fn validate_private_directory(_path: &Path) -> SecurityResult<()> {
146 Err(SecurityError::SecretUnavailable)
147}
148
149#[cfg(unix)]
150fn validate_private_file(file: &File) -> SecurityResult<()> {
151 use std::os::unix::fs::{MetadataExt, PermissionsExt};
152 let metadata = file.metadata().map_err(unavailable)?;
153 if !metadata.is_file()
154 || metadata.permissions().mode() & 0o077 != 0
155 || metadata.uid() != unsafe { libc::geteuid() }
156 {
157 return Err(SecurityError::InvalidSecretRef);
158 }
159 Ok(())
160}
161
162#[cfg(windows)]
163fn validate_private_file(file: &File) -> SecurityResult<()> {
164 let metadata = file.metadata().map_err(unavailable)?;
165 if !metadata.is_file() || is_reparse_point(&metadata) {
166 return Err(SecurityError::InvalidSecretRef);
167 }
168 windows_acl::validate_file_owner_acl(file)
169}
170
171#[cfg(all(not(unix), not(windows)))]
172fn validate_private_file(_file: &File) -> SecurityResult<()> {
173 Err(SecurityError::SecretUnavailable)
174}
175
176fn unavailable<T>(_error: T) -> SecurityError {
177 SecurityError::SecretUnavailable
178}
179
180#[cfg(windows)]
181pub(crate) mod windows_acl {
182 use super::*;
183 use std::ffi::c_void;
184 use std::os::windows::ffi::OsStrExt;
185 use std::os::windows::io::AsRawHandle;
186 use std::ptr;
187 use windows_sys::Win32::Foundation::{
188 CloseHandle, LocalFree, ERROR_SUCCESS, GENERIC_ALL, HANDLE,
189 };
190 use windows_sys::Win32::Security::Authorization::{
191 GetNamedSecurityInfoW, GetSecurityInfo, SetEntriesInAclW, SetNamedSecurityInfoW,
192 EXPLICIT_ACCESS_W, NO_MULTIPLE_TRUSTEE, SET_ACCESS, SE_FILE_OBJECT, TRUSTEE_IS_SID,
193 TRUSTEE_IS_USER, TRUSTEE_W,
194 };
195 use windows_sys::Win32::Security::{
196 EqualSid, GetAce, GetSecurityDescriptorControl, GetTokenInformation, TokenUser,
197 ACCESS_ALLOWED_ACE, ACL, DACL_SECURITY_INFORMATION, NO_INHERITANCE,
198 OWNER_SECURITY_INFORMATION, PROTECTED_DACL_SECURITY_INFORMATION, PSECURITY_DESCRIPTOR,
199 PSID, SE_DACL_PROTECTED, SUB_CONTAINERS_AND_OBJECTS_INHERIT, TOKEN_QUERY, TOKEN_USER,
200 };
201 use windows_sys::Win32::System::SystemServices::{
202 ACCESS_ALLOWED_ACE_TYPE, ACCESS_ALLOWED_CALLBACK_ACE_TYPE,
203 ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE, ACCESS_ALLOWED_COMPOUND_ACE_TYPE,
204 ACCESS_ALLOWED_OBJECT_ACE_TYPE,
205 };
206 use windows_sys::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
207
208 pub(crate) fn validate_path_owner_acl(path: &Path) -> SecurityResult<()> {
209 let wide = path
210 .as_os_str()
211 .encode_wide()
212 .chain(std::iter::once(0))
213 .collect::<Vec<_>>();
214 let mut owner = ptr::null_mut();
215 let mut dacl = ptr::null_mut();
216 let mut descriptor = ptr::null_mut();
217 let result = unsafe {
218 GetNamedSecurityInfoW(
219 wide.as_ptr(),
220 SE_FILE_OBJECT,
221 OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
222 &mut owner,
223 ptr::null_mut(),
224 &mut dacl,
225 ptr::null_mut(),
226 &mut descriptor,
227 )
228 };
229 validate_security_result(result, descriptor, owner, dacl)
230 }
231
232 pub(crate) fn validate_file_owner_acl(file: &File) -> SecurityResult<()> {
233 let mut owner = ptr::null_mut();
234 let mut dacl = ptr::null_mut();
235 let mut descriptor = ptr::null_mut();
236 let result = unsafe {
237 GetSecurityInfo(
238 file.as_raw_handle() as HANDLE,
239 SE_FILE_OBJECT,
240 OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
241 &mut owner,
242 ptr::null_mut(),
243 &mut dacl,
244 ptr::null_mut(),
245 &mut descriptor,
246 )
247 };
248 validate_security_result(result, descriptor, owner, dacl)
249 }
250
251 fn validate_security_result(
252 result: u32,
253 descriptor: PSECURITY_DESCRIPTOR,
254 owner: PSID,
255 dacl: *mut ACL,
256 ) -> SecurityResult<()> {
257 if result != ERROR_SUCCESS || descriptor.is_null() {
258 return Err(SecurityError::SecretUnavailable);
259 }
260 let descriptor = SecurityDescriptor(descriptor);
261 let validation = validate_owner_and_acl(descriptor.0, owner, dacl);
262 drop(descriptor);
263 validation
264 }
265
266 fn validate_owner_and_acl(
267 descriptor: PSECURITY_DESCRIPTOR,
268 owner: PSID,
269 dacl: *mut ACL,
270 ) -> SecurityResult<()> {
271 if owner.is_null() || dacl.is_null() {
272 return Err(SecurityError::InvalidSecretRef);
273 }
274 let mut control = 0;
275 let mut revision = 0;
276 if unsafe { GetSecurityDescriptorControl(descriptor, &mut control, &mut revision) } == 0
277 || control & SE_DACL_PROTECTED == 0
278 {
279 return Err(SecurityError::InvalidSecretRef);
280 }
281 let user = current_user()?;
282 if unsafe { EqualSid(owner, user.sid()?) } == 0 {
283 return Err(SecurityError::InvalidSecretRef);
284 }
285 validate_owner_only_acl(dacl, owner)
286 }
287
288 pub(crate) fn set_path_owner_acl(path: &Path, directory: bool) -> SecurityResult<()> {
289 let user = current_user()?;
290 let sid = user.sid()?;
291 let trustee = TRUSTEE_W {
292 pMultipleTrustee: ptr::null_mut(),
293 MultipleTrusteeOperation: NO_MULTIPLE_TRUSTEE,
294 TrusteeForm: TRUSTEE_IS_SID,
295 TrusteeType: TRUSTEE_IS_USER,
296 ptstrName: sid.cast(),
297 };
298 let entry = EXPLICIT_ACCESS_W {
299 grfAccessPermissions: GENERIC_ALL,
300 grfAccessMode: SET_ACCESS,
301 grfInheritance: if directory {
302 SUB_CONTAINERS_AND_OBJECTS_INHERIT
303 } else {
304 NO_INHERITANCE
305 },
306 Trustee: trustee,
307 };
308 let mut dacl = ptr::null_mut();
309 if unsafe { SetEntriesInAclW(1, &entry, ptr::null(), &mut dacl) } != ERROR_SUCCESS
310 || dacl.is_null()
311 {
312 return Err(SecurityError::SecretUnavailable);
313 }
314 let dacl = SecurityDescriptor(dacl.cast());
315 let mut wide = path
316 .as_os_str()
317 .encode_wide()
318 .chain(std::iter::once(0))
319 .collect::<Vec<_>>();
320 let result = unsafe {
321 SetNamedSecurityInfoW(
322 wide.as_mut_ptr(),
323 SE_FILE_OBJECT,
324 DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION,
325 ptr::null_mut(),
326 ptr::null_mut(),
327 dacl.0.cast(),
328 ptr::null(),
329 )
330 };
331 if result == ERROR_SUCCESS {
332 validate_path_owner_acl(path)
333 } else {
334 Err(SecurityError::SecretUnavailable)
335 }
336 }
337
338 fn validate_owner_only_acl(dacl: *mut ACL, owner: PSID) -> SecurityResult<()> {
339 let mut owner_allowed = false;
340 let ace_count = unsafe { (*dacl).AceCount };
341 for index in 0..u32::from(ace_count) {
342 let mut raw_ace: *mut c_void = ptr::null_mut();
343 if unsafe { GetAce(dacl, index, &mut raw_ace) } == 0 || raw_ace.is_null() {
344 return Err(SecurityError::SecretUnavailable);
345 }
346 let ace_type = unsafe { (*(raw_ace.cast::<ACCESS_ALLOWED_ACE>())).Header.AceType };
347 if ace_type == ACCESS_ALLOWED_ACE_TYPE as u8 {
348 let ace = raw_ace.cast::<ACCESS_ALLOWED_ACE>();
349 let sid = unsafe { ptr::addr_of_mut!((*ace).SidStart).cast::<c_void>() };
350 if unsafe { EqualSid(owner, sid) } == 0 {
351 return Err(SecurityError::InvalidSecretRef);
352 }
353 owner_allowed = true;
354 } else if is_other_allow_ace(ace_type) {
355 return Err(SecurityError::InvalidSecretRef);
356 }
357 }
358 if owner_allowed {
359 Ok(())
360 } else {
361 Err(SecurityError::InvalidSecretRef)
362 }
363 }
364
365 fn is_other_allow_ace(ace_type: u8) -> bool {
366 [
367 ACCESS_ALLOWED_COMPOUND_ACE_TYPE,
368 ACCESS_ALLOWED_OBJECT_ACE_TYPE,
369 ACCESS_ALLOWED_CALLBACK_ACE_TYPE,
370 ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE,
371 ]
372 .contains(&u32::from(ace_type))
373 }
374
375 fn current_user() -> SecurityResult<TokenUser> {
376 let mut token: HANDLE = ptr::null_mut();
377 if unsafe { OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token) } == 0 {
378 return Err(SecurityError::SecretUnavailable);
379 }
380 let token = TokenHandle(token);
381 let mut required = 0;
382 unsafe {
383 GetTokenInformation(token.0, TokenUser, ptr::null_mut(), 0, &mut required);
384 }
385 if required == 0 {
386 return Err(SecurityError::SecretUnavailable);
387 }
388 let mut buffer = vec![0_u8; required as usize];
389 if unsafe {
390 GetTokenInformation(
391 token.0,
392 TokenUser,
393 buffer.as_mut_ptr().cast(),
394 required,
395 &mut required,
396 )
397 } == 0
398 {
399 return Err(SecurityError::SecretUnavailable);
400 }
401 Ok(TokenUser { buffer })
402 }
403
404 struct TokenUser {
405 buffer: Vec<u8>,
406 }
407
408 impl TokenUser {
409 fn sid(&self) -> SecurityResult<PSID> {
410 if self.buffer.len() < std::mem::size_of::<TOKEN_USER>() {
411 return Err(SecurityError::SecretUnavailable);
412 }
413 let user = unsafe { self.buffer.as_ptr().cast::<TOKEN_USER>().read_unaligned() };
417 Ok(user.User.Sid)
418 }
419 }
420
421 struct TokenHandle(HANDLE);
422
423 impl Drop for TokenHandle {
424 fn drop(&mut self) {
425 unsafe {
426 CloseHandle(self.0);
427 }
428 }
429 }
430
431 struct SecurityDescriptor(PSECURITY_DESCRIPTOR);
432
433 impl Drop for SecurityDescriptor {
434 fn drop(&mut self) {
435 unsafe {
436 LocalFree(self.0);
437 }
438 }
439 }
440}