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)]
181mod 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::{CloseHandle, LocalFree, ERROR_SUCCESS, HANDLE};
188 use windows_sys::Win32::Security::Authorization::{
189 GetNamedSecurityInfoW, GetSecurityInfo, SE_FILE_OBJECT,
190 };
191 use windows_sys::Win32::Security::{
192 EqualSid, GetAce, GetTokenInformation, TokenUser, ACCESS_ALLOWED_ACE, ACL,
193 DACL_SECURITY_INFORMATION, OWNER_SECURITY_INFORMATION, PSECURITY_DESCRIPTOR, PSID,
194 TOKEN_QUERY, TOKEN_USER,
195 };
196 use windows_sys::Win32::System::SystemServices::{
197 ACCESS_ALLOWED_ACE_TYPE, ACCESS_ALLOWED_CALLBACK_ACE_TYPE,
198 ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE, ACCESS_ALLOWED_OBJECT_ACE_TYPE,
199 };
200 use windows_sys::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
201
202 pub(super) fn validate_path_owner_acl(path: &Path) -> SecurityResult<()> {
203 let wide = path
204 .as_os_str()
205 .encode_wide()
206 .chain(std::iter::once(0))
207 .collect::<Vec<_>>();
208 let mut owner = ptr::null_mut();
209 let mut dacl = ptr::null_mut();
210 let mut descriptor = ptr::null_mut();
211 let result = unsafe {
212 GetNamedSecurityInfoW(
213 wide.as_ptr(),
214 SE_FILE_OBJECT,
215 OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
216 &mut owner,
217 ptr::null_mut(),
218 &mut dacl,
219 ptr::null_mut(),
220 &mut descriptor,
221 )
222 };
223 validate_security_result(result, descriptor, owner, dacl)
224 }
225
226 pub(super) fn validate_file_owner_acl(file: &File) -> SecurityResult<()> {
227 let mut owner = ptr::null_mut();
228 let mut dacl = ptr::null_mut();
229 let mut descriptor = ptr::null_mut();
230 let result = unsafe {
231 GetSecurityInfo(
232 file.as_raw_handle() as HANDLE,
233 SE_FILE_OBJECT,
234 OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
235 &mut owner,
236 ptr::null_mut(),
237 &mut dacl,
238 ptr::null_mut(),
239 &mut descriptor,
240 )
241 };
242 validate_security_result(result, descriptor, owner, dacl)
243 }
244
245 fn validate_security_result(
246 result: u32,
247 descriptor: PSECURITY_DESCRIPTOR,
248 owner: PSID,
249 dacl: *mut ACL,
250 ) -> SecurityResult<()> {
251 if result != ERROR_SUCCESS || descriptor.is_null() {
252 return Err(SecurityError::SecretUnavailable);
253 }
254 let descriptor = SecurityDescriptor(descriptor);
255 let validation = validate_owner_and_acl(owner, dacl);
256 drop(descriptor);
257 validation
258 }
259
260 fn validate_owner_and_acl(owner: PSID, dacl: *mut ACL) -> SecurityResult<()> {
261 if owner.is_null() || dacl.is_null() {
262 return Err(SecurityError::InvalidSecretRef);
263 }
264 let user = current_user()?;
265 if unsafe { EqualSid(owner, user.sid()?) } == 0 {
266 return Err(SecurityError::InvalidSecretRef);
267 }
268 validate_owner_only_acl(dacl, owner)
269 }
270
271 fn validate_owner_only_acl(dacl: *mut ACL, owner: PSID) -> SecurityResult<()> {
272 let mut owner_allowed = false;
273 let ace_count = unsafe { (*dacl).AceCount };
274 for index in 0..u32::from(ace_count) {
275 let mut raw_ace: *mut c_void = ptr::null_mut();
276 if unsafe { GetAce(dacl, index, &mut raw_ace) } == 0 || raw_ace.is_null() {
277 return Err(SecurityError::SecretUnavailable);
278 }
279 let ace_type = unsafe { (*(raw_ace.cast::<ACCESS_ALLOWED_ACE>())).Header.AceType };
280 if ace_type == ACCESS_ALLOWED_ACE_TYPE as u8 {
281 let ace = raw_ace.cast::<ACCESS_ALLOWED_ACE>();
282 let sid = unsafe { ptr::addr_of_mut!((*ace).SidStart).cast::<c_void>() };
283 if unsafe { EqualSid(owner, sid) } == 0 {
284 return Err(SecurityError::InvalidSecretRef);
285 }
286 owner_allowed = true;
287 } else if is_other_allow_ace(ace_type) {
288 return Err(SecurityError::InvalidSecretRef);
289 }
290 }
291 if owner_allowed {
292 Ok(())
293 } else {
294 Err(SecurityError::InvalidSecretRef)
295 }
296 }
297
298 fn is_other_allow_ace(ace_type: u8) -> bool {
299 [
300 ACCESS_ALLOWED_OBJECT_ACE_TYPE,
301 ACCESS_ALLOWED_CALLBACK_ACE_TYPE,
302 ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE,
303 ]
304 .contains(&u32::from(ace_type))
305 }
306
307 fn current_user() -> SecurityResult<TokenUser> {
308 let mut token: HANDLE = ptr::null_mut();
309 if unsafe { OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token) } == 0 {
310 return Err(SecurityError::SecretUnavailable);
311 }
312 let token = TokenHandle(token);
313 let mut required = 0;
314 unsafe {
315 GetTokenInformation(token.0, TokenUser, ptr::null_mut(), 0, &mut required);
316 }
317 if required == 0 {
318 return Err(SecurityError::SecretUnavailable);
319 }
320 let mut buffer = vec![0_u8; required as usize];
321 if unsafe {
322 GetTokenInformation(
323 token.0,
324 TokenUser,
325 buffer.as_mut_ptr().cast(),
326 required,
327 &mut required,
328 )
329 } == 0
330 {
331 return Err(SecurityError::SecretUnavailable);
332 }
333 Ok(TokenUser { buffer })
334 }
335
336 struct TokenUser {
337 buffer: Vec<u8>,
338 }
339
340 impl TokenUser {
341 fn sid(&self) -> SecurityResult<PSID> {
342 if self.buffer.len() < std::mem::size_of::<TOKEN_USER>() {
343 return Err(SecurityError::SecretUnavailable);
344 }
345 let user = unsafe { self.buffer.as_ptr().cast::<TOKEN_USER>().read_unaligned() };
349 Ok(user.User.Sid)
350 }
351 }
352
353 struct TokenHandle(HANDLE);
354
355 impl Drop for TokenHandle {
356 fn drop(&mut self) {
357 unsafe {
358 CloseHandle(self.0);
359 }
360 }
361 }
362
363 struct SecurityDescriptor(PSECURITY_DESCRIPTOR);
364
365 impl Drop for SecurityDescriptor {
366 fn drop(&mut self) {
367 unsafe {
368 LocalFree(self.0);
369 }
370 }
371 }
372}