blob: 59040476f1af174e45b0425960a6789158c00d2b [file] [log] [blame]
Janis Danisevskis5cb52dc2021-04-07 16:31:18 -07001// 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 Johnson9da2e1c2022-09-19 12:39:01 +000023use crate::ks_err;
24
Janis Danisevskis5cb52dc2021-04-07 16:31:18 -070025use anyhow::{Context, Result};
26use std::fs;
27use std::io::ErrorKind;
28use std::path::{Path, PathBuf};
Tri Vo74997ed2023-07-20 17:57:19 -040029use std::time::{Duration, SystemTime};
Janis Danisevskis5cb52dc2021-04-07 16:31:18 -070030
31const ID_ROTATION_PERIOD: Duration = Duration::from_secs(30 * 24 * 60 * 60); // Thirty days.
Chris Wailesd5aaaef2021-07-27 16:04:33 -070032static TIMESTAMP_FILE_NAME: &str = "timestamp";
Janis Danisevskis5cb52dc2021-04-07 16:31:18 -070033
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)]
38pub struct IdRotationState {
Tri Vo74997ed2023-07-20 17:57:19 -040039 /// We consider the time of last factory reset to be the point in time when this timestamp file
40 /// is created.
Janis Danisevskis5cb52dc2021-04-07 16:31:18 -070041 timestamp_path: PathBuf,
42}
43
44impl 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 Vo74997ed2023-07-20 17:57:19 -040052 /// 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 Danisevskis5cb52dc2021-04-07 16:31:18 -070057 match fs::metadata(&self.timestamp_path) {
58 Ok(metadata) => {
Tri Vo74997ed2023-07-20 17:57:19 -040059 // 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 Danisevskis5cb52dc2021-04-07 16:31:18 -070079 }
80 Err(e) => match e.kind() {
81 ErrorKind::NotFound => {
82 fs::File::create(&self.timestamp_path)
Tri Vo74997ed2023-07-20 17:57:19 -040083 .context(ks_err!("Failed to create timestamp file."))?;
Janis Danisevskis5cb52dc2021-04-07 16:31:18 -070084 Ok(true)
85 }
Tri Vo74997ed2023-07-20 17:57:19 -040086 _ => Err(e).context(ks_err!("Failed to open timestamp file.")),
Janis Danisevskis5cb52dc2021-04-07 16:31:18 -070087 },
88 }
Shaquille Johnson9da2e1c2022-09-19 12:39:01 +000089 .context(ks_err!())
Janis Danisevskis5cb52dc2021-04-07 16:31:18 -070090 }
91}
92
93#[cfg(test)]
94mod test {
95 use super::*;
96 use keystore2_test_utils::TempDir;
97 use nix::sys::stat::utimes;
98 use nix::sys::time::{TimeVal, TimeValLike};
Tri Vo74997ed2023-07-20 17:57:19 -040099 use std::thread::sleep;
Janis Danisevskis5cb52dc2021-04-07 16:31:18 -0700100
Tri Vo74997ed2023-07-20 17:57:19 -0400101 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 Wailesd5aaaef2021-07-27 16:04:33 -0700107 let id_rotation_state = IdRotationState::new(temp_dir.path());
Janis Danisevskis5cb52dc2021-04-07 16:31:18 -0700108
Tri Vo74997ed2023-07-20 17:57:19 -0400109 (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 Danisevskis5cb52dc2021-04-07 16:31:18 -0700116
117 // The timestamp file should not exist.
Tri Vo74997ed2023-07-20 17:57:19 -0400118 assert!(!timestamp_file_path.exists());
Janis Danisevskis5cb52dc2021-04-07 16:31:18 -0700119
Tri Vo74997ed2023-07-20 17:57:19 -0400120 // 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 Danisevskis5cb52dc2021-04-07 16:31:18 -0700123
124 // Now the timestamp file should exist.
Tri Vo74997ed2023-07-20 17:57:19 -0400125 assert!(timestamp_file_path.exists());
Janis Danisevskis5cb52dc2021-04-07 16:31:18 -0700126
Tri Vo74997ed2023-07-20 17:57:19 -0400127 let metadata = fs::metadata(&timestamp_file_path).unwrap();
128 assert!(metadata.modified().unwrap() > creation_datetime);
129 }
Janis Danisevskis5cb52dc2021-04-07 16:31:18 -0700130
Tri Vo74997ed2023-07-20 17:57:19 -0400131 #[test]
132 fn test_existing_timestamp() {
133 let (_temp_dir, timestamp_file_path, id_rotation_state) = set_up();
Janis Danisevskis5cb52dc2021-04-07 16:31:18 -0700134
Tri Vo74997ed2023-07-20 17:57:19 -0400135 // 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 Danisevskis5cb52dc2021-04-07 16:31:18 -0700138
Tri Vo74997ed2023-07-20 17:57:19 -0400139 // Create timestamp file and backdate it back to Unix epoch.
140 fs::File::create(&timestamp_file_path).unwrap();
141 let mtime = TimeVal::seconds(0);
142 let atime = TimeVal::seconds(0);
143 utimes(&timestamp_file_path, &atime, &mtime).unwrap();
Janis Danisevskis5cb52dc2021-04-07 16:31:18 -0700144
Tri Vo74997ed2023-07-20 17:57:19 -0400145 // 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 Danisevskis5cb52dc2021-04-07 16:31:18 -0700148
Tri Vo74997ed2023-07-20 17:57:19 -0400149 // 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(&timestamp_file_path, &atime, &mtime).unwrap();
168 assert!(id_rotation_state.had_factory_reset_since_id_rotation(&creation_datetime).unwrap());
Janis Danisevskis5cb52dc2021-04-07 16:31:18 -0700169 }
170}