blob: 460caa77ffe222b31375368f4551b3b69a1ee0c0 [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};
29use std::time::Duration;
30
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 {
39 timestamp_path: PathBuf,
40}
41
42impl IdRotationState {
43 /// Creates a new IdRotationState. It holds the path to the timestamp file for deferred usage.
44 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.
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 }
Shaquille Johnson9da2e1c2022-09-19 12:39:01 +000071 .context(ks_err!())
Janis Danisevskis5cb52dc2021-04-07 16:31:18 -070072 }
73}
74
75#[cfg(test)]
76mod 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]
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.");
Chris Wailesd5aaaef2021-07-27 16:04:33 -070088 let id_rotation_state = IdRotationState::new(temp_dir.path());
Janis Danisevskis5cb52dc2021-04-07 16:31:18 -070089
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}