openstack_keystone_core/token/backend/fernet/
utils.rs1use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
16use chrono::{DateTime, Utc};
17use fernet::Fernet;
18use nix::sys::stat::{Mode, umask};
19use nix::unistd::{Gid, Uid, getegid, geteuid, setegid, seteuid};
20use rmp::{
21 Marker,
22 decode::{self, *},
23 encode::{self, *},
24};
25use secrecy::{ExposeSecret, SecretString};
26use std::collections::BTreeMap;
27use std::fs;
28use std::io::{self, Read, Write};
30use std::path::PathBuf;
31use tempfile::NamedTempFile;
32use tokio::fs as fs_async;
33use tracing::{error, info, trace, warn};
34use uuid::Uuid;
35
36use crate::token::error::TokenProviderError;
37
38#[derive(Clone, Debug, Default)]
40pub struct FernetUtils {
41 pub key_repository: PathBuf,
42 pub max_active_keys: usize,
43}
44
45impl FernetUtils {
46 fn validate_key_repository(&self) -> Result<bool, TokenProviderError> {
50 Ok(self.key_repository.exists())
51 }
52
53 fn create_tmp_new_key(
58 &self,
59 user_id: Option<u32>,
60 group_id: Option<u32>,
61 ) -> Result<(), TokenProviderError> {
62 let key = SecretString::new(Fernet::generate_key().into());
64 let target_path = self.key_repository.join("0.tmp");
65
66 let old_umask = umask(Mode::from_bits_truncate(0o177));
68 let _umask_guard = scopeguard::guard(old_umask, |old| {
69 umask(old);
70 });
71
72 if let (Some(uid), Some(gid)) = (user_id, group_id) {
73 let (old_euid, old_egid) = (geteuid(), getegid());
74
75 setegid(Gid::from_raw(gid)).map_err(|e| TokenProviderError::NixErrno {
76 context: "setting effective process GID".into(),
77 source: e,
78 })?;
79 seteuid(Uid::from_raw(uid)).map_err(|e| TokenProviderError::NixErrno {
80 context: "setting effective process UID".into(),
81 source: e,
82 })?;
83
84 let _id_guard = scopeguard::guard((old_euid, old_egid), |(u, g)| {
86 let _ = seteuid(u);
87 let _ = setegid(g);
88 });
89 }
90
91 let mut tmp_file = NamedTempFile::new_in(self.key_repository.clone())?;
94
95 tmp_file.write_all(key.expose_secret().as_bytes())?;
97 tmp_file.flush()?;
98
99 info!("Created new Fernet key at {:?}", target_path);
103 tmp_file.persist(&target_path)?;
104
105 Ok(())
106 }
107
108 fn become_valid_new_key(&self) -> Result<(), TokenProviderError> {
111 let tmp_key_file = self.key_repository.join("0.tmp");
112 let valid_key_file = self.key_repository.join("0");
113
114 if !tmp_key_file.exists() {
116 error!("Temporary key file not found: {:?}", tmp_key_file);
117 return Err(TokenProviderError::FernetKeysMissing);
118 }
119
120 fs::rename(&tmp_key_file, &valid_key_file)?;
123
124 let dir = fs::File::open(&self.key_repository)?;
127 dir.sync_all()?;
128
129 info!("Become a valid new key: {:?}", valid_key_file);
130 Ok(())
131 }
132
133 pub fn initialize_key_repository(&self) -> Result<(), TokenProviderError> {
134 self.create_tmp_new_key(None, None)?;
135 self.become_valid_new_key()?;
136 Ok(())
137 }
138
139 pub fn load_keys(&self) -> Result<impl IntoIterator<Item = Fernet>, TokenProviderError> {
140 info!("loading keys from {:?}", self.key_repository);
141 let mut keys: BTreeMap<i8, Fernet> = BTreeMap::new();
142 if self.validate_key_repository()? {
143 for entry in fs::read_dir(&self.key_repository)? {
144 let entry = entry?;
145 if let Ok(fname) = entry.file_name().into_string()
146 && let Ok(key_order) = fname.parse::<i8>()
147 {
148 trace!("Loading key {:?}", entry.file_name());
150 if let Some(fernet) = Fernet::new(
151 fs::read_to_string(entry.path())
152 .map_err(|e| TokenProviderError::FernetKeyRead {
153 source: e,
154 path: entry.path(),
155 })?
156 .trim_end(),
157 ) {
158 keys.insert(key_order, fernet);
159 } else {
160 warn!(
161 "The key {:?} is not usable for Fernet library",
162 entry.file_name()
163 )
164 }
165 }
166 }
167 }
168 if keys.is_empty() {
169 return Err(TokenProviderError::FernetKeysMissing);
170 }
171 Ok(keys.into_values().rev())
172 }
173
174 pub async fn load_keys_async(
175 &self,
176 ) -> Result<impl IntoIterator<Item = Fernet>, TokenProviderError> {
177 let mut keys: BTreeMap<i8, Fernet> = BTreeMap::new();
178 if self.validate_key_repository()? {
179 let mut entries = fs_async::read_dir(&self.key_repository).await?;
180 while let Some(entry) = entries.next_entry().await? {
181 if let Ok(fname) = entry.file_name().into_string()
182 && let Ok(key_order) = fname.parse::<i8>()
183 {
184 trace!("Loading key {:?}", entry.file_name());
186 if let Some(fernet) = Fernet::new(
187 fs::read_to_string(entry.path())
188 .map_err(|e| TokenProviderError::FernetKeyRead {
189 source: e,
190 path: entry.path(),
191 })?
192 .trim_end(),
193 ) {
194 keys.insert(key_order, fernet);
195 } else {
196 warn!(
197 "The key {:?} is not usable for Fernet library",
198 entry.file_name()
199 )
200 }
201 }
202 }
203 }
204 if keys.is_empty() {
205 return Err(TokenProviderError::FernetKeysMissing);
206 }
207 Ok(keys.into_values().rev())
208 }
209}
210
211pub fn read_bin_data<R: Read>(len: u32, rd: &mut R) -> Result<Vec<u8>, io::Error> {
213 let mut buf = Vec::with_capacity(len.min(1 << 16) as usize);
214 let bytes_read = rd.take(u64::from(len)).read_to_end(&mut buf)?;
215 if bytes_read != len as usize {
216 return Err(io::ErrorKind::UnexpectedEof.into());
217 }
218 Ok(buf)
219}
220
221pub fn read_str_data<R: Read>(len: u32, rd: &mut R) -> Result<String, io::Error> {
223 Ok(String::from_utf8_lossy(&read_bin_data(len, rd)?).into_owned())
224}
225
226pub fn write_str<W: RmpWrite>(wd: &mut W, data: &str) -> Result<(), TokenProviderError> {
228 encode::write_str(wd, data).map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
229 Ok(())
230}
231
232pub fn read_str<R: Read>(rd: &mut R) -> Result<String, TokenProviderError> {
234 match read_marker(rd).map_err(ValueReadError::from)? {
235 Marker::Bin8 => {
236 Ok(String::from_utf8_lossy(&read_bin_data(read_pfix(rd)?.into(), rd)?).to_string())
237 }
238 Marker::FixStr(len) => Ok(read_str_data(len.into(), rd)?),
239 other => Err(TokenProviderError::InvalidTokenUuidMarker(other)),
240 }
241}
242
243pub fn read_uuid(rd: &mut &[u8]) -> Result<String, TokenProviderError> {
248 match read_marker(rd).map_err(ValueReadError::from)? {
249 Marker::FixArray(_) => {
250 match read_marker(rd).map_err(ValueReadError::from)? {
251 Marker::True => {
252 if let Marker::Bin8 = read_marker(rd).map_err(ValueReadError::from)? {
256 return Ok(Uuid::try_from(read_bin_data(read_pfix(rd)?.into(), rd)?)?
257 .as_simple()
258 .to_string());
259 }
260 }
261 Marker::False => {
262 match read_marker(rd).map_err(ValueReadError::from)? {
264 Marker::Bin8 => {
265 return Ok(String::from_utf8_lossy(&read_bin_data(
266 read_pfix(rd)?.into(),
267 rd,
268 )?)
269 .to_string());
270 }
271 Marker::FixStr(len) => {
272 return Ok(read_str_data(len.into(), rd)?);
273 }
274 other => {
275 return Err(TokenProviderError::InvalidTokenUuidMarker(other));
276 }
277 }
278 }
279 other => {
280 return Err(TokenProviderError::InvalidTokenUuidMarker(other));
281 }
282 }
283 }
284 Marker::FixStr(len) => {
285 return Ok(read_str_data(len.into(), rd)?);
286 }
287 other => {
288 return Err(TokenProviderError::InvalidTokenUuidMarker(other));
289 }
290 }
291 Err(TokenProviderError::InvalidTokenUuid)
292}
293
294pub fn write_uuid<W: RmpWrite>(wd: &mut W, uid: &str) -> Result<(), TokenProviderError> {
299 match Uuid::parse_str(uid) {
300 Ok(uuid) => {
301 write_array_len(wd, 2).map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
302 write_bool(wd, true).map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
303 write_bin(wd, uuid.as_bytes())
304 .map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
305 }
306 _ => {
307 write_array_len(wd, 2).map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
308 write_bool(wd, false).map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
309 write_bin(wd, uid.as_bytes())
310 .map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
311 }
312 }
313 Ok(())
314}
315
316pub fn read_time(rd: &mut &[u8]) -> Result<DateTime<Utc>, TokenProviderError> {
318 DateTime::from_timestamp(read_f64(rd)?.round() as i64, 0)
319 .ok_or(TokenProviderError::InvalidToken)
320}
321
322pub fn write_time<W: RmpWrite>(wd: &mut W, time: DateTime<Utc>) -> Result<(), TokenProviderError> {
324 write_f64(wd, time.timestamp() as f64)
325 .map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
326 Ok(())
327}
328
329pub fn read_audit_ids(
331 rd: &mut &[u8],
332) -> Result<impl IntoIterator<Item = String> + use<>, TokenProviderError> {
333 if let Marker::FixArray(len) = read_marker(rd).map_err(ValueReadError::from)? {
334 let mut result: Vec<String> = Vec::new();
335 for _ in 0..len {
336 if let Marker::Bin8 = read_marker(rd).map_err(ValueReadError::from)? {
337 let dt = read_bin_data(read_pfix(rd)?.into(), rd)?;
338 let audit_id: String = URL_SAFE_NO_PAD.encode(dt);
339 result.push(audit_id);
340 } else {
341 return Err(TokenProviderError::InvalidToken);
342 }
343 }
344 return Ok(result.into_iter());
345 }
346 Err(TokenProviderError::InvalidToken)
347}
348
349pub fn write_audit_ids<W: RmpWrite, I: IntoIterator<Item = String>>(
351 wd: &mut W,
352 data: I,
353) -> Result<(), TokenProviderError> {
354 let vals = Vec::from_iter(data);
355 write_array_len(wd, vals.len() as u32)
356 .map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
357 for val in vals.iter() {
358 write_bin(
359 wd,
360 &URL_SAFE_NO_PAD
361 .decode(val)
362 .map_err(|_| TokenProviderError::AuditIdWrongFormat)?,
363 )
364 .map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
365 }
366 Ok(())
367}
368
369pub fn read_list_of_uuids(
371 rd: &mut &[u8],
372) -> Result<impl IntoIterator<Item = String> + use<>, TokenProviderError> {
373 if let Marker::FixArray(len) = read_marker(rd).map_err(ValueReadError::from)? {
374 let mut result: Vec<String> = Vec::new();
375 for _ in 0..len {
376 result.push(read_uuid(rd)?);
377 }
378 return Ok(result.into_iter());
379 }
380 Err(TokenProviderError::InvalidToken)
381}
382
383pub fn write_list_of_uuids<W: RmpWrite, I: IntoIterator<Item = V>, V: AsRef<str>>(
385 wd: &mut W,
386 data: I,
387) -> Result<(), TokenProviderError> {
388 let vals = Vec::from_iter(data);
389 write_array_len(wd, vals.len() as u32)
390 .map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
391 for val in vals.iter() {
392 write_uuid(wd, val.as_ref())?;
393 }
394 Ok(())
395}
396
397pub fn read_bool<R: Read>(rd: &mut R) -> Result<bool, TokenProviderError> {
399 Ok(decode::read_bool(rd)?)
400}
401
402pub fn write_bool<W: RmpWrite>(wd: &mut W, data: bool) -> Result<(), TokenProviderError> {
404 encode::write_bool(wd, data).map_err(|x| TokenProviderError::RmpEncode(x.to_string()))?;
405 Ok(())
406}
407
408#[cfg(test)]
409mod tests {
410 use super::FernetUtils;
411 use chrono::{Local, SubsecRound};
412 use std::fs::File;
413 use std::io::Write;
414 use tempfile::tempdir;
415
416 use super::*;
417
418 #[tokio::test]
419 async fn test_load_keys_valid() {
420 let tmp_dir = tempdir().unwrap();
421 for i in 0..5 {
422 let file_path = tmp_dir.path().join(format!("{i}"));
423 let mut tmp_file = File::create(file_path).unwrap();
424 write!(tmp_file, "{}", Fernet::generate_key()).unwrap();
425 }
426 let utils = FernetUtils {
427 key_repository: tmp_dir.keep(),
428 ..Default::default()
429 };
430 let keys: Vec<Fernet> = utils.load_keys().unwrap().into_iter().collect();
431 assert_eq!(5, keys.len());
432 }
433
434 #[tokio::test]
435 async fn test_load_keys_all_invalid() {
436 let tmp_dir = tempdir().unwrap();
437 for i in 0..5 {
438 let file_path = tmp_dir.path().join(format!("{i}"));
439 let mut tmp_file = File::create(file_path).unwrap();
440 write!(tmp_file, "{i}").unwrap();
441 }
442 let file_path = tmp_dir.path().join("dummy");
444 let mut tmp_file = File::create(file_path).unwrap();
445 write!(tmp_file, "foo").unwrap();
446
447 let utils = FernetUtils {
448 key_repository: tmp_dir.keep(),
449 ..Default::default()
450 };
451 let res = utils.load_keys();
452
453 if let Err(TokenProviderError::FernetKeysMissing) = res {
454 } else {
455 panic!("Should have raised an exception");
456 }
457 }
458
459 #[tokio::test]
460 async fn test_load_keys_trim() {
461 let tmp_dir = tempdir().unwrap();
462 for i in 0..5 {
463 let file_path = tmp_dir.path().join(format!("{i}"));
464 let mut tmp_file = File::create(file_path).unwrap();
465 writeln!(tmp_file, "{}", Fernet::generate_key()).unwrap();
466 }
467 let utils = FernetUtils {
468 key_repository: tmp_dir.keep(),
469 ..Default::default()
470 };
471 let keys: Vec<Fernet> = utils.load_keys().unwrap().into_iter().collect();
472 assert_eq!(5, keys.len());
473 }
474
475 #[test]
476 fn test_write_read_uuid_str() {
477 let mut buf = Vec::with_capacity(36);
478 let uuid = "abc";
479 write_uuid(&mut buf, uuid).unwrap();
480 let msg = buf.clone();
481 let mut decode_data = msg.as_slice();
482 let decoded = read_uuid(&mut decode_data).unwrap();
483 assert_eq!(uuid, decoded);
484 }
485
486 #[test]
487 fn test_write_read_uuid() {
488 let mut buf = Vec::with_capacity(36);
489 let test = Uuid::new_v4();
490 write_uuid(&mut buf, &test.to_string()).unwrap();
491 let msg = buf.clone();
492 let mut decode_data = msg.as_slice();
493 let decoded = read_uuid(&mut decode_data).unwrap();
494 assert_eq!(test.simple().to_string(), decoded);
495 }
496
497 #[test]
498 fn test_write_read_time() {
499 let test = Local::now().trunc_subsecs(0);
500 let mut buf = Vec::with_capacity(36);
501 write_time(&mut buf, test.into()).unwrap();
502 let msg = buf.clone();
503 let mut decode_data = msg.as_slice();
504 let decoded = read_time(&mut decode_data).unwrap();
505 assert_eq!(test, decoded);
506 }
507
508 #[test]
509 fn test_write_audit_ids() {
510 let test = vec!["Zm9vCg".into()];
511 let mut buf = Vec::with_capacity(36);
512 write_audit_ids(&mut buf, test.clone()).unwrap();
513 let msg = buf.clone();
514 let mut decode_data = msg.as_slice();
515 let decoded: Vec<String> = read_audit_ids(&mut decode_data)
516 .unwrap()
517 .into_iter()
518 .collect();
519 assert_eq!(test, decoded);
520 }
521
522 #[test]
523 fn test_write_bool() {
524 let test = true;
525 let mut buf = Vec::with_capacity(1);
526 write_bool(&mut buf, test).unwrap();
527 let msg = buf.clone();
528 let mut decode_data = msg.as_slice();
529 let decoded = read_bool(&mut decode_data).unwrap();
530 assert_eq!(test, decoded);
531 }
532}