1use std::io;
7use std::path::Path;
8
9pub fn secure_file_perms(path: &Path) -> io::Result<()> {
11 crate::platform::set_mode(path, 0o600)
12}
13
14pub fn secure_dir_perms(path: &Path) -> io::Result<()> {
16 crate::platform::set_mode(path, 0o700)
17}
18
19pub fn ensure_file_private(path: &Path) -> io::Result<Option<u32>> {
24 crate::platform::ensure_private(path, 0o600)
25}
26
27pub fn write_private(path: &Path, contents: &[u8]) -> io::Result<()> {
41 crate::platform::write_with_mode(path, contents, 0o600)
42}
43
44#[cfg(test)]
50mod tests {
51 use super::*;
52
53 #[cfg(unix)]
54 fn mode_of(path: &Path) -> u32 {
55 use std::os::unix::fs::PermissionsExt;
56 std::fs::metadata(path).unwrap().permissions().mode() & 0o777
57 }
58
59 #[cfg(unix)]
60 fn set_mode(path: &Path, mode: u32) {
61 use std::os::unix::fs::PermissionsExt;
62 std::fs::set_permissions(path, std::fs::Permissions::from_mode(mode)).unwrap();
63 }
64
65 #[cfg(windows)]
71 fn env_lock() -> std::sync::MutexGuard<'static, ()> {
72 crate::platform::ENV_LOCK.lock().expect("env lock")
73 }
74
75 #[test]
81 fn write_private_creates_an_owner_only_file_with_the_content() {
82 #[cfg(windows)]
83 let _env = env_lock();
84 let dir = tempfile::tempdir().unwrap();
85 let path = dir.path().join("secret");
86
87 write_private(&path, b"the key").unwrap();
88 assert_eq!(std::fs::read(&path).unwrap(), b"the key");
89 #[cfg(unix)]
90 assert_eq!(mode_of(&path), 0o600);
91 }
92
93 #[test]
97 fn write_private_retightens_an_existing_permissive_file() {
98 #[cfg(windows)]
99 let _env = env_lock();
100 let dir = tempfile::tempdir().unwrap();
101 let path = dir.path().join("secret");
102 std::fs::write(&path, b"old").unwrap();
103 #[cfg(unix)]
104 set_mode(&path, 0o644);
105
106 write_private(&path, b"new").unwrap();
107 assert_eq!(std::fs::read(&path).unwrap(), b"new");
108 #[cfg(unix)]
109 assert_eq!(mode_of(&path), 0o600);
110 }
111
112 #[test]
114 fn write_private_propagates_an_unwritable_path() {
115 let dir = tempfile::tempdir().unwrap();
116 let path = dir.path().join("no-such-dir").join("secret");
117 assert!(write_private(&path, b"x").is_err());
118 }
119
120 #[test]
121 fn secure_file_perms_restricts_on_unix_and_succeeds_everywhere() {
122 #[cfg(windows)]
123 let _env = env_lock();
124 let dir = tempfile::tempdir().unwrap();
125 let path = dir.path().join("secret");
126 std::fs::write(&path, b"x").unwrap();
127 #[cfg(unix)]
128 set_mode(&path, 0o644);
129
130 secure_file_perms(&path).unwrap();
131
132 #[cfg(unix)]
133 assert_eq!(mode_of(&path), 0o600);
134 }
135
136 #[test]
137 fn secure_dir_perms_restricts_on_unix_and_succeeds_everywhere() {
138 #[cfg(windows)]
139 let _env = env_lock();
140 let dir = tempfile::tempdir().unwrap();
141 let sub = dir.path().join("d");
142 std::fs::create_dir(&sub).unwrap();
143 #[cfg(unix)]
144 set_mode(&sub, 0o755);
145
146 secure_dir_perms(&sub).unwrap();
147
148 #[cfg(unix)]
149 assert_eq!(mode_of(&sub), 0o700);
150 }
151
152 #[test]
153 fn secure_file_perms_missing_path_behavior() {
154 #[cfg(windows)]
155 let _env = env_lock();
156 let dir = tempfile::tempdir().unwrap();
162 let missing = dir.path().join("nope");
163 let result = secure_file_perms(&missing);
164 #[cfg(any(unix, windows))]
165 assert!(result.is_err());
166 #[cfg(not(any(unix, windows)))]
167 assert!(result.is_ok());
168 }
169
170 #[test]
171 fn ensure_file_private_tightens_permissive_file_on_unix() {
172 #[cfg(windows)]
173 let _env = env_lock();
174 let dir = tempfile::tempdir().unwrap();
175 let path = dir.path().join("cfg");
176 std::fs::write(&path, b"x").unwrap();
177 #[cfg(unix)]
178 set_mode(&path, 0o644);
179
180 let previous = ensure_file_private(&path).unwrap();
181
182 #[cfg(unix)]
183 {
184 assert_eq!(previous, Some(0o100644));
185 assert_eq!(mode_of(&path), 0o600);
186 }
187 #[cfg(not(unix))]
189 assert_eq!(previous, None);
190 }
191
192 #[test]
193 fn ensure_file_private_leaves_private_file_untouched() {
194 #[cfg(windows)]
195 let _env = env_lock();
196 let dir = tempfile::tempdir().unwrap();
197 let path = dir.path().join("cfg");
198 std::fs::write(&path, b"x").unwrap();
199 #[cfg(unix)]
200 set_mode(&path, 0o600);
201
202 assert_eq!(ensure_file_private(&path).unwrap(), None);
203
204 #[cfg(unix)]
205 assert_eq!(mode_of(&path), 0o600);
206 }
207
208 #[test]
209 fn ensure_file_private_is_noop_for_missing_path() {
210 let dir = tempfile::tempdir().unwrap();
211 let missing = dir.path().join("nope");
212 assert_eq!(ensure_file_private(&missing).unwrap(), None);
213 }
214}