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, SystemTime}; 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 /// We consider the time of last factory reset to be the point in time when this timestamp file 40 /// is created. 41 timestamp_path: PathBuf, 42 } 43 44 impl IdRotationState { 45 /// Creates a new IdRotationState. It holds the path to the timestamp file for deferred usage. new(keystore_db_path: &Path) -> Self46 pub fn new(keystore_db_path: &Path) -> Self { 47 let mut timestamp_path = keystore_db_path.to_owned(); 48 timestamp_path.push(TIMESTAMP_FILE_NAME); 49 Self { timestamp_path } 50 } 51 52 /// Returns true iff a factory reset has occurred since the last ID rotation. had_factory_reset_since_id_rotation( &self, creation_datetime: &SystemTime, ) -> Result<bool>53 pub fn had_factory_reset_since_id_rotation( 54 &self, 55 creation_datetime: &SystemTime, 56 ) -> Result<bool> { 57 match fs::metadata(&self.timestamp_path) { 58 Ok(metadata) => { 59 // For Tag::UNIQUE_ID, temporal counter value is defined as Tag::CREATION_DATETIME 60 // divided by 2592000000, dropping any remainder. Temporal counter value is 61 // effectively the index of the ID rotation period that we are currently in, with 62 // each ID rotation period being 30 days. 63 let temporal_counter_value = creation_datetime 64 .duration_since(SystemTime::UNIX_EPOCH) 65 .context(ks_err!("Failed to get epoch time"))? 66 .as_millis() 67 / ID_ROTATION_PERIOD.as_millis(); 68 69 // Calculate the beginning of the current ID rotation period, which is also the 70 // last time ID was rotated. 71 let id_rotation_time: SystemTime = SystemTime::UNIX_EPOCH 72 .checked_add(ID_ROTATION_PERIOD * temporal_counter_value.try_into()?) 73 .context(ks_err!("Failed to get ID rotation time."))?; 74 75 let factory_reset_time = 76 metadata.modified().context(ks_err!("File creation time not supported."))?; 77 78 Ok(id_rotation_time <= factory_reset_time) 79 } 80 Err(e) => match e.kind() { 81 ErrorKind::NotFound => { 82 fs::File::create(&self.timestamp_path) 83 .context(ks_err!("Failed to create timestamp file."))?; 84 Ok(true) 85 } 86 _ => Err(e).context(ks_err!("Failed to open timestamp file.")), 87 }, 88 } 89 .context(ks_err!()) 90 } 91 } 92 93 #[cfg(test)] 94 mod test { 95 use super::*; 96 use keystore2_test_utils::TempDir; 97 use nix::sys::stat::utimes; 98 use nix::sys::time::{TimeVal, TimeValLike}; 99 use std::thread::sleep; 100 101 static TEMP_DIR_NAME: &str = "test_had_factory_reset_since_id_rotation_"; 102 set_up() -> (TempDir, PathBuf, IdRotationState)103 fn set_up() -> (TempDir, PathBuf, IdRotationState) { 104 let temp_dir = TempDir::new(TEMP_DIR_NAME).expect("Failed to create temp dir."); 105 let mut timestamp_file_path = temp_dir.path().to_owned(); 106 timestamp_file_path.push(TIMESTAMP_FILE_NAME); 107 let id_rotation_state = IdRotationState::new(temp_dir.path()); 108 109 (temp_dir, timestamp_file_path, id_rotation_state) 110 } 111 112 #[test] test_timestamp_creation()113 fn test_timestamp_creation() { 114 let (_temp_dir, timestamp_file_path, id_rotation_state) = set_up(); 115 let creation_datetime = SystemTime::now(); 116 117 // The timestamp file should not exist. 118 assert!(!timestamp_file_path.exists()); 119 120 // Trigger timestamp file creation one second later. 121 sleep(Duration::new(1, 0)); 122 assert!(id_rotation_state.had_factory_reset_since_id_rotation(&creation_datetime).unwrap()); 123 124 // Now the timestamp file should exist. 125 assert!(timestamp_file_path.exists()); 126 127 let metadata = fs::metadata(×tamp_file_path).unwrap(); 128 assert!(metadata.modified().unwrap() > creation_datetime); 129 } 130 131 #[test] test_existing_timestamp()132 fn test_existing_timestamp() { 133 let (_temp_dir, timestamp_file_path, id_rotation_state) = set_up(); 134 135 // Let's start with at a known point in time, so that it's easier to control which ID 136 // rotation period we're in. 137 let mut creation_datetime = SystemTime::UNIX_EPOCH; 138 139 // Create timestamp file and backdate it back to Unix epoch. 140 fs::File::create(×tamp_file_path).unwrap(); 141 let mtime = TimeVal::seconds(0); 142 let atime = TimeVal::seconds(0); 143 utimes(×tamp_file_path, &atime, &mtime).unwrap(); 144 145 // Timestamp file was backdated to the very beginning of the current ID rotation period. 146 // So, this should return true. 147 assert!(id_rotation_state.had_factory_reset_since_id_rotation(&creation_datetime).unwrap()); 148 149 // Move time forward, but stay in the same ID rotation period. 150 creation_datetime += Duration::from_millis(1); 151 152 // We should still return true because we're in the same ID rotation period. 153 assert!(id_rotation_state.had_factory_reset_since_id_rotation(&creation_datetime).unwrap()); 154 155 // Move time to the next ID rotation period. 156 creation_datetime += ID_ROTATION_PERIOD; 157 158 // Now we should see false. 159 assert!(!id_rotation_state 160 .had_factory_reset_since_id_rotation(&creation_datetime) 161 .unwrap()); 162 163 // Move timestamp to the future. This shouldn't ever happen, but even in this edge case ID 164 // must be rotated. 165 let mtime = TimeVal::seconds((ID_ROTATION_PERIOD.as_secs() * 10).try_into().unwrap()); 166 let atime = TimeVal::seconds((ID_ROTATION_PERIOD.as_secs() * 10).try_into().unwrap()); 167 utimes(×tamp_file_path, &atime, &mtime).unwrap(); 168 assert!(id_rotation_state.had_factory_reset_since_id_rotation(&creation_datetime).unwrap()); 169 } 170 } 171