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 anyhow::{Context, Result}; 24 use std::fs; 25 use std::io::ErrorKind; 26 use std::path::{Path, PathBuf}; 27 use std::time::Duration; 28 29 const ID_ROTATION_PERIOD: Duration = Duration::from_secs(30 * 24 * 60 * 60); // Thirty days. 30 static TIMESTAMP_FILE_NAME: &str = &"timestamp"; 31 32 /// The IdRotationState stores the path to the timestamp file for deferred usage. The data 33 /// partition is usually not available when Keystore 2.0 starts up. So this object is created 34 /// and passed down to the users of the feature which can then query the timestamp on demand. 35 #[derive(Debug, Clone)] 36 pub struct IdRotationState { 37 timestamp_path: PathBuf, 38 } 39 40 impl IdRotationState { 41 /// Creates a new IdRotationState. It holds the path to the timestamp file for deferred usage. new(keystore_db_path: &Path) -> Self42 pub fn new(keystore_db_path: &Path) -> Self { 43 let mut timestamp_path = keystore_db_path.to_owned(); 44 timestamp_path.push(TIMESTAMP_FILE_NAME); 45 Self { timestamp_path } 46 } 47 48 /// Reads the metadata of or creates the timestamp file. It returns true if the timestamp 49 /// file is younger than `ID_ROTATION_PERIOD`, i.e., 30 days. had_factory_reset_since_id_rotation(&self) -> Result<bool>50 pub fn had_factory_reset_since_id_rotation(&self) -> Result<bool> { 51 match fs::metadata(&self.timestamp_path) { 52 Ok(metadata) => { 53 let duration_since_factory_reset = metadata 54 .modified() 55 .context("File creation time not supported.")? 56 .elapsed() 57 .context("Failed to compute time elapsed since factory reset.")?; 58 Ok(duration_since_factory_reset < ID_ROTATION_PERIOD) 59 } 60 Err(e) => match e.kind() { 61 ErrorKind::NotFound => { 62 fs::File::create(&self.timestamp_path) 63 .context("Failed to create timestamp file.")?; 64 Ok(true) 65 } 66 _ => Err(e).context("Failed to open timestamp file."), 67 }, 68 } 69 .context("In had_factory_reset_since_id_rotation:") 70 } 71 } 72 73 #[cfg(test)] 74 mod test { 75 use super::*; 76 use keystore2_test_utils::TempDir; 77 use nix::sys::stat::utimes; 78 use nix::sys::time::{TimeVal, TimeValLike}; 79 use std::convert::TryInto; 80 use std::time::UNIX_EPOCH; 81 82 #[test] test_had_factory_reset_since_id_rotation() -> Result<()>83 fn test_had_factory_reset_since_id_rotation() -> Result<()> { 84 let temp_dir = TempDir::new("test_had_factory_reset_since_id_rotation_") 85 .expect("Failed to create temp dir."); 86 let id_rotation_state = IdRotationState::new(&temp_dir.path()); 87 88 let mut temp_file_path = temp_dir.path().to_owned(); 89 temp_file_path.push(TIMESTAMP_FILE_NAME); 90 91 // The timestamp file should not exist. 92 assert!(!temp_file_path.exists()); 93 94 // This should return true. 95 assert!(id_rotation_state.had_factory_reset_since_id_rotation()?); 96 97 // Now the timestamp file should exist. 98 assert!(temp_file_path.exists()); 99 100 // We should still return true because the timestamp file is young. 101 assert!(id_rotation_state.had_factory_reset_since_id_rotation()?); 102 103 // Now let's age the timestamp file by backdating the modification time. 104 let metadata = fs::metadata(&temp_file_path)?; 105 let mtime = metadata.modified()?; 106 let mtime = mtime.duration_since(UNIX_EPOCH)?; 107 let mtime = 108 mtime.checked_sub(ID_ROTATION_PERIOD).expect("Failed to subtract id rotation period"); 109 let mtime = TimeVal::seconds(mtime.as_secs().try_into().unwrap()); 110 111 let atime = metadata.accessed()?; 112 let atime = atime.duration_since(UNIX_EPOCH)?; 113 let atime = TimeVal::seconds(atime.as_secs().try_into().unwrap()); 114 115 utimes(&temp_file_path, &atime, &mtime)?; 116 117 // Now that the file has aged we should see false. 118 assert!(!id_rotation_state.had_factory_reset_since_id_rotation()?); 119 120 Ok(()) 121 } 122 } 123