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