Janis Danisevskis | 5cb52dc | 2021-04-07 16:31:18 -0700 | [diff] [blame] | 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 | |
Shaquille Johnson | 9da2e1c | 2022-09-19 12:39:01 +0000 | [diff] [blame] | 23 | use crate::ks_err; |
| 24 | |
Janis Danisevskis | 5cb52dc | 2021-04-07 16:31:18 -0700 | [diff] [blame] | 25 | use anyhow::{Context, Result}; |
| 26 | use std::fs; |
| 27 | use std::io::ErrorKind; |
| 28 | use std::path::{Path, PathBuf}; |
Tri Vo | 74997ed | 2023-07-20 17:57:19 -0400 | [diff] [blame] | 29 | use std::time::{Duration, SystemTime}; |
Janis Danisevskis | 5cb52dc | 2021-04-07 16:31:18 -0700 | [diff] [blame] | 30 | |
| 31 | const ID_ROTATION_PERIOD: Duration = Duration::from_secs(30 * 24 * 60 * 60); // Thirty days. |
Chris Wailes | d5aaaef | 2021-07-27 16:04:33 -0700 | [diff] [blame] | 32 | static TIMESTAMP_FILE_NAME: &str = "timestamp"; |
Janis Danisevskis | 5cb52dc | 2021-04-07 16:31:18 -0700 | [diff] [blame] | 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 { |
Tri Vo | 74997ed | 2023-07-20 17:57:19 -0400 | [diff] [blame] | 39 | /// We consider the time of last factory reset to be the point in time when this timestamp file |
| 40 | /// is created. |
Janis Danisevskis | 5cb52dc | 2021-04-07 16:31:18 -0700 | [diff] [blame] | 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. |
| 46 | 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 | |
Tri Vo | 74997ed | 2023-07-20 17:57:19 -0400 | [diff] [blame] | 52 | /// Returns true iff a factory reset has occurred since the last ID rotation. |
| 53 | pub fn had_factory_reset_since_id_rotation( |
| 54 | &self, |
| 55 | creation_datetime: &SystemTime, |
| 56 | ) -> Result<bool> { |
Janis Danisevskis | 5cb52dc | 2021-04-07 16:31:18 -0700 | [diff] [blame] | 57 | match fs::metadata(&self.timestamp_path) { |
| 58 | Ok(metadata) => { |
Tri Vo | 74997ed | 2023-07-20 17:57:19 -0400 | [diff] [blame] | 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) |
Janis Danisevskis | 5cb52dc | 2021-04-07 16:31:18 -0700 | [diff] [blame] | 79 | } |
| 80 | Err(e) => match e.kind() { |
| 81 | ErrorKind::NotFound => { |
| 82 | fs::File::create(&self.timestamp_path) |
Tri Vo | 74997ed | 2023-07-20 17:57:19 -0400 | [diff] [blame] | 83 | .context(ks_err!("Failed to create timestamp file."))?; |
Janis Danisevskis | 5cb52dc | 2021-04-07 16:31:18 -0700 | [diff] [blame] | 84 | Ok(true) |
| 85 | } |
Tri Vo | 74997ed | 2023-07-20 17:57:19 -0400 | [diff] [blame] | 86 | _ => Err(e).context(ks_err!("Failed to open timestamp file.")), |
Janis Danisevskis | 5cb52dc | 2021-04-07 16:31:18 -0700 | [diff] [blame] | 87 | }, |
| 88 | } |
Shaquille Johnson | 9da2e1c | 2022-09-19 12:39:01 +0000 | [diff] [blame] | 89 | .context(ks_err!()) |
Janis Danisevskis | 5cb52dc | 2021-04-07 16:31:18 -0700 | [diff] [blame] | 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}; |
Tri Vo | 74997ed | 2023-07-20 17:57:19 -0400 | [diff] [blame] | 99 | use std::thread::sleep; |
Janis Danisevskis | 5cb52dc | 2021-04-07 16:31:18 -0700 | [diff] [blame] | 100 | |
Tri Vo | 74997ed | 2023-07-20 17:57:19 -0400 | [diff] [blame] | 101 | static TEMP_DIR_NAME: &str = "test_had_factory_reset_since_id_rotation_"; |
| 102 | |
| 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); |
Chris Wailes | d5aaaef | 2021-07-27 16:04:33 -0700 | [diff] [blame] | 107 | let id_rotation_state = IdRotationState::new(temp_dir.path()); |
Janis Danisevskis | 5cb52dc | 2021-04-07 16:31:18 -0700 | [diff] [blame] | 108 | |
Tri Vo | 74997ed | 2023-07-20 17:57:19 -0400 | [diff] [blame] | 109 | (temp_dir, timestamp_file_path, id_rotation_state) |
| 110 | } |
| 111 | |
| 112 | #[test] |
| 113 | fn test_timestamp_creation() { |
| 114 | let (_temp_dir, timestamp_file_path, id_rotation_state) = set_up(); |
| 115 | let creation_datetime = SystemTime::now(); |
Janis Danisevskis | 5cb52dc | 2021-04-07 16:31:18 -0700 | [diff] [blame] | 116 | |
| 117 | // The timestamp file should not exist. |
Tri Vo | 74997ed | 2023-07-20 17:57:19 -0400 | [diff] [blame] | 118 | assert!(!timestamp_file_path.exists()); |
Janis Danisevskis | 5cb52dc | 2021-04-07 16:31:18 -0700 | [diff] [blame] | 119 | |
Tri Vo | 74997ed | 2023-07-20 17:57:19 -0400 | [diff] [blame] | 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()); |
Janis Danisevskis | 5cb52dc | 2021-04-07 16:31:18 -0700 | [diff] [blame] | 123 | |
| 124 | // Now the timestamp file should exist. |
Tri Vo | 74997ed | 2023-07-20 17:57:19 -0400 | [diff] [blame] | 125 | assert!(timestamp_file_path.exists()); |
Janis Danisevskis | 5cb52dc | 2021-04-07 16:31:18 -0700 | [diff] [blame] | 126 | |
Tri Vo | 74997ed | 2023-07-20 17:57:19 -0400 | [diff] [blame] | 127 | let metadata = fs::metadata(×tamp_file_path).unwrap(); |
| 128 | assert!(metadata.modified().unwrap() > creation_datetime); |
| 129 | } |
Janis Danisevskis | 5cb52dc | 2021-04-07 16:31:18 -0700 | [diff] [blame] | 130 | |
Tri Vo | 74997ed | 2023-07-20 17:57:19 -0400 | [diff] [blame] | 131 | #[test] |
| 132 | fn test_existing_timestamp() { |
| 133 | let (_temp_dir, timestamp_file_path, id_rotation_state) = set_up(); |
Janis Danisevskis | 5cb52dc | 2021-04-07 16:31:18 -0700 | [diff] [blame] | 134 | |
Tri Vo | 74997ed | 2023-07-20 17:57:19 -0400 | [diff] [blame] | 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; |
Janis Danisevskis | 5cb52dc | 2021-04-07 16:31:18 -0700 | [diff] [blame] | 138 | |
Tri Vo | 74997ed | 2023-07-20 17:57:19 -0400 | [diff] [blame] | 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(); |
Janis Danisevskis | 5cb52dc | 2021-04-07 16:31:18 -0700 | [diff] [blame] | 144 | |
Tri Vo | 74997ed | 2023-07-20 17:57:19 -0400 | [diff] [blame] | 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()); |
Janis Danisevskis | 5cb52dc | 2021-04-07 16:31:18 -0700 | [diff] [blame] | 148 | |
Tri Vo | 74997ed | 2023-07-20 17:57:19 -0400 | [diff] [blame] | 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()); |
Janis Danisevskis | 5cb52dc | 2021-04-07 16:31:18 -0700 | [diff] [blame] | 169 | } |
| 170 | } |