sentinelpass_protocol/
token.rs1use crate::paths::default_ipc_token_path;
13use crate::{ProtocolError, Result};
14use rand::{rngs::OsRng, RngCore};
15use std::io::Write;
16use std::path::{Path, PathBuf};
17use tracing::warn;
18use zeroize::Zeroize;
19
20pub fn load_ipc_token() -> Result<String> {
22 load_ipc_token_from(&default_ipc_token_path())
23}
24
25pub fn native_host_capability_path() -> PathBuf {
30 crate::paths::get_config_dir().join("native_host.capability")
31}
32
33pub fn load_native_host_capability() -> Option<String> {
36 std::fs::read_to_string(native_host_capability_path())
37 .ok()
38 .map(|s| s.trim().to_string())
39 .filter(|s| !s.is_empty())
40}
41
42pub fn load_ipc_token_from(token_path: &Path) -> Result<String> {
45 let token = std::fs::read_to_string(token_path)?.trim().to_string();
46 if token.is_empty() {
47 return Err(ProtocolError::Ipc(format!(
48 "IPC token file is empty: {:?}",
49 token_path
50 )));
51 }
52 enforce_owner_only(token_path);
53 Ok(token)
54}
55
56pub fn load_or_create_ipc_token() -> Result<String> {
58 load_or_create_ipc_token_at(&default_ipc_token_path())
59}
60
61pub fn load_or_create_ipc_token_at(token_path: &Path) -> Result<String> {
63 if let Some(parent) = token_path.parent() {
64 if !parent.exists() {
65 std::fs::create_dir_all(parent)?;
69 #[cfg(unix)]
70 {
71 use std::os::unix::fs::PermissionsExt;
72 std::fs::set_permissions(parent, std::fs::Permissions::from_mode(0o700))?;
73 }
74 } else {
75 std::fs::create_dir_all(parent)?;
76 }
77 }
78
79 if token_path.exists() {
80 return load_ipc_token_from(token_path);
81 }
82
83 let mut token_bytes = [0u8; 32];
84 OsRng.fill_bytes(&mut token_bytes);
85 let token = hex::encode(token_bytes);
86 token_bytes.zeroize();
87
88 let mut options = std::fs::OpenOptions::new();
89 options.write(true).create_new(true);
90 #[cfg(unix)]
91 {
92 use std::os::unix::fs::OpenOptionsExt;
95 options.mode(0o600);
96 }
97 let mut file = options.open(token_path)?;
98 file.write_all(token.as_bytes())?;
99
100 #[cfg(unix)]
101 {
102 use std::os::unix::fs::PermissionsExt;
103 std::fs::set_permissions(token_path, std::fs::Permissions::from_mode(0o600))?;
104 }
105
106 Ok(token)
107}
108
109fn enforce_owner_only(token_path: &Path) {
113 #[cfg(unix)]
114 {
115 use std::os::unix::fs::PermissionsExt;
116 let owner_only = std::fs::metadata(token_path)
117 .map(|m| m.permissions().mode() & 0o077 == 0)
118 .unwrap_or(true); if !owner_only {
120 warn!(
121 "IPC token file {:?} is group/world-accessible; tightening to 0600",
122 token_path
123 );
124 let _ = std::fs::set_permissions(token_path, std::fs::Permissions::from_mode(0o600));
125 }
126 }
127 #[cfg(not(unix))]
128 {
129 let _ = token_path;
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[cfg(unix)]
138 #[test]
139 fn created_token_and_parent_are_owner_only() {
140 use std::os::unix::fs::PermissionsExt;
141
142 let outer = tempfile::TempDir::new().unwrap();
143 let token_path = outer.path().join("cfg").join("ipc.token");
144
145 load_or_create_ipc_token_at(&token_path).unwrap();
146
147 let mode = |p: &Path| std::fs::metadata(p).unwrap().permissions().mode() & 0o777;
148 assert_eq!(mode(&token_path), 0o600, "token must be born 0600");
149 assert_eq!(
150 mode(token_path.parent().unwrap()),
151 0o700,
152 "created parent dir must be 0700"
153 );
154
155 let again = load_or_create_ipc_token_at(&token_path).unwrap();
157 let first = std::fs::read_to_string(&token_path).unwrap();
158 assert_eq!(again.trim(), first.trim());
159 }
160
161 #[cfg(unix)]
162 #[test]
163 fn loose_token_mode_is_repaired_on_load() {
164 use std::os::unix::fs::PermissionsExt;
165
166 let outer = tempfile::TempDir::new().unwrap();
167 let token_path = outer.path().join("ipc.token");
168 load_or_create_ipc_token_at(&token_path).unwrap();
169
170 std::fs::set_permissions(&token_path, std::fs::Permissions::from_mode(0o644)).unwrap();
171 load_ipc_token_from(&token_path).unwrap();
172 let mode = std::fs::metadata(&token_path).unwrap().permissions().mode() & 0o777;
173 assert_eq!(mode, 0o600, "load must tighten a loose token mode");
174 }
175
176 #[cfg(unix)]
179 #[test]
180 fn preexisting_parent_directory_is_left_untouched() {
181 use std::os::unix::fs::PermissionsExt;
182
183 let outer = tempfile::TempDir::new().unwrap(); let token_path = outer.path().join("ipc.token");
185 load_or_create_ipc_token_at(&token_path).unwrap();
186 let loose = outer.path().join("loose");
190 std::fs::create_dir_all(&loose).unwrap();
191 std::fs::set_permissions(&loose, std::fs::Permissions::from_mode(0o755)).unwrap();
192 let token_path = loose.join("ipc.token");
193 load_or_create_ipc_token_at(&token_path).unwrap();
194 let mode = std::fs::metadata(&loose).unwrap().permissions().mode() & 0o777;
195 assert_eq!(mode, 0o755, "pre-existing parent must stay untouched");
196 }
197}