• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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