1 // Copyright 2021, The Android Open Source Project 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 //! This module implements the unique id rotation privacy feature. Certain system components 16 //! have the ability to include a per-app unique id into the key attestation. The key rotation 17 //! feature assures that the unique id is rotated on factory reset at least once in a 30 day 18 //! key rotation period. 19 //! 20 //! It is assumed that the timestamp file does not exist after a factory reset. So the creation 21 //! time of the timestamp file provides a lower bound for the time since factory reset. 22 23 use crate::ks_err; 24 25 use anyhow::{Context, Result}; 26 use std::fs; 27 use std::io::ErrorKind; 28 use std::path::{Path, PathBuf}; 29 use std::time::Duration; 30 31 const ID_ROTATION_PERIOD: Duration = Duration::from_secs(30 * 24 * 60 * 60); // Thirty days. 32 static TIMESTAMP_FILE_NAME: &str = "timestamp"; 33 34 /// The IdRotationState stores the path to the timestamp file for deferred usage. The data 35 /// partition is usually not available when Keystore 2.0 starts up. So this object is created 36 /// and passed down to the users of the feature which can then query the timestamp on demand. 37 #[derive(Debug, Clone)] 38 pub struct IdRotationState { 39 timestamp_path: PathBuf, 40 } 41 42 impl IdRotationState { 43 /// Creates a new IdRotationState. It holds the path to the timestamp file for deferred usage. new(keystore_db_path: &Path) -> Self44 pub fn new(keystore_db_path: &Path) -> Self { 45 let mut timestamp_path = keystore_db_path.to_owned(); 46 timestamp_path.push(TIMESTAMP_FILE_NAME); 47 Self { timestamp_path } 48 } 49 50 /// Reads the metadata of or creates the timestamp file. It returns true if the timestamp 51 /// file is younger than `ID_ROTATION_PERIOD`, i.e., 30 days. had_factory_reset_since_id_rotation(&self) -> Result<bool>52 pub fn had_factory_reset_since_id_rotation(&self) -> Result<bool> { 53 match fs::metadata(&self.timestamp_path) { 54 Ok(metadata) => { 55 let duration_since_factory_reset = metadata 56 .modified() 57 .context("File creation time not supported.")? 58 .elapsed() 59 .context("Failed to compute time elapsed since factory reset.")?; 60 Ok(duration_since_factory_reset < ID_ROTATION_PERIOD) 61 } 62 Err(e) => match e.kind() { 63 ErrorKind::NotFound => { 64 fs::File::create(&self.timestamp_path) 65 .context("Failed to create timestamp file.")?; 66 Ok(true) 67 } 68 _ => Err(e).context("Failed to open timestamp file."), 69 }, 70 } 71 .context(ks_err!()) 72 } 73 } 74 75 #[cfg(test)] 76 mod test { 77 use super::*; 78 use keystore2_test_utils::TempDir; 79 use nix::sys::stat::utimes; 80 use nix::sys::time::{TimeVal, TimeValLike}; 81 use std::convert::TryInto; 82 use std::time::UNIX_EPOCH; 83 84 #[test] test_had_factory_reset_since_id_rotation() -> Result<()>85 fn test_had_factory_reset_since_id_rotation() -> Result<()> { 86 let temp_dir = TempDir::new("test_had_factory_reset_since_id_rotation_") 87 .expect("Failed to create temp dir."); 88 let id_rotation_state = IdRotationState::new(temp_dir.path()); 89 90 let mut temp_file_path = temp_dir.path().to_owned(); 91 temp_file_path.push(TIMESTAMP_FILE_NAME); 92 93 // The timestamp file should not exist. 94 assert!(!temp_file_path.exists()); 95 96 // This should return true. 97 assert!(id_rotation_state.had_factory_reset_since_id_rotation()?); 98 99 // Now the timestamp file should exist. 100 assert!(temp_file_path.exists()); 101 102 // We should still return true because the timestamp file is young. 103 assert!(id_rotation_state.had_factory_reset_since_id_rotation()?); 104 105 // Now let's age the timestamp file by backdating the modification time. 106 let metadata = fs::metadata(&temp_file_path)?; 107 let mtime = metadata.modified()?; 108 let mtime = mtime.duration_since(UNIX_EPOCH)?; 109 let mtime = 110 mtime.checked_sub(ID_ROTATION_PERIOD).expect("Failed to subtract id rotation period"); 111 let mtime = TimeVal::seconds(mtime.as_secs().try_into().unwrap()); 112 113 let atime = metadata.accessed()?; 114 let atime = atime.duration_since(UNIX_EPOCH)?; 115 let atime = TimeVal::seconds(atime.as_secs().try_into().unwrap()); 116 117 utimes(&temp_file_path, &atime, &mtime)?; 118 119 // Now that the file has aged we should see false. 120 assert!(!id_rotation_state.had_factory_reset_since_id_rotation()?); 121 122 Ok(()) 123 } 124 } 125