Keystore 2.0: Implement unique id rotation on factory reset.
This patch implements unique id rotation on factory reset. It is assumed
that the timestamp file disappears on factory reset so the timestamp
file's creation time gives a lower bound on the time since the last
factory reset.
Bug: 184784809
Test: atest keystore2_test
Change-Id: Iaa1c74b0ccffe69d5d9c68e7c6dac98a13136437
diff --git a/keystore2/Android.bp b/keystore2/Android.bp
index aff824b..af177be 100644
--- a/keystore2/Android.bp
+++ b/keystore2/Android.bp
@@ -86,6 +86,7 @@
rustlibs: [
"libandroid_logger",
"libkeystore2_test_utils",
+ "libnix",
],
}
diff --git a/keystore2/src/id_rotation.rs b/keystore2/src/id_rotation.rs
new file mode 100644
index 0000000..dbf0fc9
--- /dev/null
+++ b/keystore2/src/id_rotation.rs
@@ -0,0 +1,122 @@
+// Copyright 2021, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! This module implements the unique id rotation privacy feature. Certain system components
+//! have the ability to include a per-app unique id into the key attestation. The key rotation
+//! feature assures that the unique id is rotated on factory reset at least once in a 30 day
+//! key rotation period.
+//!
+//! It is assumed that the timestamp file does not exist after a factory reset. So the creation
+//! time of the timestamp file provides a lower bound for the time since factory reset.
+
+use anyhow::{Context, Result};
+use std::fs;
+use std::io::ErrorKind;
+use std::path::{Path, PathBuf};
+use std::time::Duration;
+
+const ID_ROTATION_PERIOD: Duration = Duration::from_secs(30 * 24 * 60 * 60); // Thirty days.
+static TIMESTAMP_FILE_NAME: &str = &"timestamp";
+
+/// The IdRotationState stores the path to the timestamp file for deferred usage. The data
+/// partition is usually not available when Keystore 2.0 starts up. So this object is created
+/// and passed down to the users of the feature which can then query the timestamp on demand.
+#[derive(Debug, Clone)]
+pub struct IdRotationState {
+ timestamp_path: PathBuf,
+}
+
+impl IdRotationState {
+ /// Creates a new IdRotationState. It holds the path to the timestamp file for deferred usage.
+ pub fn new(keystore_db_path: &Path) -> Self {
+ let mut timestamp_path = keystore_db_path.to_owned();
+ timestamp_path.push(TIMESTAMP_FILE_NAME);
+ Self { timestamp_path }
+ }
+
+ /// Reads the metadata of or creates the timestamp file. It returns true if the timestamp
+ /// file is younger than `ID_ROTATION_PERIOD`, i.e., 30 days.
+ pub fn had_factory_reset_since_id_rotation(&self) -> Result<bool> {
+ match fs::metadata(&self.timestamp_path) {
+ Ok(metadata) => {
+ let duration_since_factory_reset = metadata
+ .modified()
+ .context("File creation time not supported.")?
+ .elapsed()
+ .context("Failed to compute time elapsed since factory reset.")?;
+ Ok(duration_since_factory_reset < ID_ROTATION_PERIOD)
+ }
+ Err(e) => match e.kind() {
+ ErrorKind::NotFound => {
+ fs::File::create(&self.timestamp_path)
+ .context("Failed to create timestamp file.")?;
+ Ok(true)
+ }
+ _ => Err(e).context("Failed to open timestamp file."),
+ },
+ }
+ .context("In had_factory_reset_since_id_rotation:")
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use keystore2_test_utils::TempDir;
+ use nix::sys::stat::utimes;
+ use nix::sys::time::{TimeVal, TimeValLike};
+ use std::convert::TryInto;
+ use std::time::UNIX_EPOCH;
+
+ #[test]
+ fn test_had_factory_reset_since_id_rotation() -> Result<()> {
+ let temp_dir = TempDir::new("test_had_factory_reset_since_id_rotation_")
+ .expect("Failed to create temp dir.");
+ let id_rotation_state = IdRotationState::new(&temp_dir.path());
+
+ let mut temp_file_path = temp_dir.path().to_owned();
+ temp_file_path.push(TIMESTAMP_FILE_NAME);
+
+ // The timestamp file should not exist.
+ assert!(!temp_file_path.exists());
+
+ // This should return true.
+ assert!(id_rotation_state.had_factory_reset_since_id_rotation()?);
+
+ // Now the timestamp file should exist.
+ assert!(temp_file_path.exists());
+
+ // We should still return true because the timestamp file is young.
+ assert!(id_rotation_state.had_factory_reset_since_id_rotation()?);
+
+ // Now let's age the timestamp file by backdating the modification time.
+ let metadata = fs::metadata(&temp_file_path)?;
+ let mtime = metadata.modified()?;
+ let mtime = mtime.duration_since(UNIX_EPOCH)?;
+ let mtime =
+ mtime.checked_sub(ID_ROTATION_PERIOD).expect("Failed to subtract id rotation period");
+ let mtime = TimeVal::seconds(mtime.as_secs().try_into().unwrap());
+
+ let atime = metadata.accessed()?;
+ let atime = atime.duration_since(UNIX_EPOCH)?;
+ let atime = TimeVal::seconds(atime.as_secs().try_into().unwrap());
+
+ utimes(&temp_file_path, &atime, &mtime)?;
+
+ // Now that the file has aged we should see false.
+ assert!(!id_rotation_state.had_factory_reset_since_id_rotation()?);
+
+ Ok(())
+ }
+}
diff --git a/keystore2/src/keystore2_main.rs b/keystore2/src/keystore2_main.rs
index e745697..df7ba26 100644
--- a/keystore2/src/keystore2_main.rs
+++ b/keystore2/src/keystore2_main.rs
@@ -14,13 +14,13 @@
//! This crate implements the Keystore 2.0 service entry point.
-use keystore2::authorization::AuthorizationManager;
use keystore2::entropy;
use keystore2::globals::ENFORCEMENTS;
use keystore2::maintenance::Maintenance;
use keystore2::remote_provisioning::RemoteProvisioningService;
use keystore2::service::KeystoreService;
use keystore2::{apc::ApcManager, shared_secret_negotiation};
+use keystore2::{authorization::AuthorizationManager, id_rotation::IdRotationState};
use log::{error, info};
use std::{panic, path::Path, sync::mpsc::channel};
use vpnprofilestore::VpnProfileStore;
@@ -57,12 +57,14 @@
// startup as Keystore 1.0 did because Keystore 2.0 is intended to run much earlier than
// Keystore 1.0. Instead we set a global variable to the database path.
// For the ground truth check the service startup rule for init (typically in keystore2.rc).
- if let Some(dir) = args.next() {
+ let id_rotation_state = if let Some(dir) = args.next() {
+ let db_path = Path::new(&dir);
*keystore2::globals::DB_PATH.lock().expect("Could not lock DB_PATH.") =
- Path::new(&dir).to_path_buf();
+ db_path.to_path_buf();
+ IdRotationState::new(&db_path)
} else {
- panic!("Must specify a working directory.");
- }
+ panic!("Must specify a database directory.");
+ };
let (confirmation_token_sender, confirmation_token_receiver) = channel();
@@ -81,7 +83,7 @@
info!("Starting thread pool now.");
binder::ProcessState::start_thread_pool();
- let ks_service = KeystoreService::new_native_binder().unwrap_or_else(|e| {
+ let ks_service = KeystoreService::new_native_binder(id_rotation_state).unwrap_or_else(|e| {
panic!("Failed to create service {} because of {:?}.", KS2_SERVICE_NAME, e);
});
binder::add_service(KS2_SERVICE_NAME, ks_service.as_binder()).unwrap_or_else(|e| {
diff --git a/keystore2/src/lib.rs b/keystore2/src/lib.rs
index 62dc16a..154b5b3 100644
--- a/keystore2/src/lib.rs
+++ b/keystore2/src/lib.rs
@@ -24,6 +24,7 @@
pub mod entropy;
pub mod error;
pub mod globals;
+pub mod id_rotation;
/// Internal Representation of Key Parameter and convenience functions.
pub mod key_parameter;
pub mod legacy_blob;
diff --git a/keystore2/src/security_level.rs b/keystore2/src/security_level.rs
index 50d697e..1cf770f 100644
--- a/keystore2/src/security_level.rs
+++ b/keystore2/src/security_level.rs
@@ -14,7 +14,7 @@
//! This crate implements the IKeystoreSecurityLevel interface.
-use crate::globals::get_keymint_device;
+use crate::{globals::get_keymint_device, id_rotation::IdRotationState};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
Algorithm::Algorithm, AttestationKey::AttestationKey,
HardwareAuthenticatorType::HardwareAuthenticatorType, IKeyMintDevice::IKeyMintDevice,
@@ -67,6 +67,7 @@
km_uuid: Uuid,
operation_db: OperationDb,
rem_prov_state: RemProvState,
+ id_rotation_state: IdRotationState,
}
// Blob of 32 zeroes used as empty masking key.
@@ -83,6 +84,7 @@
/// we need it for checking keystore permissions.
pub fn new_native_binder(
security_level: SecurityLevel,
+ id_rotation_state: IdRotationState,
) -> Result<(Strong<dyn IKeystoreSecurityLevel>, Uuid)> {
let (dev, hw_info, km_uuid) = get_keymint_device(&security_level)
.context("In KeystoreSecurityLevel::new_native_binder.")?;
@@ -93,6 +95,7 @@
km_uuid,
operation_db: OperationDb::new(),
rem_prov_state: RemProvState::new(security_level, km_uuid),
+ id_rotation_state,
});
result.as_binder().set_requesting_sid(true);
Ok((result, km_uuid))
@@ -353,6 +356,7 @@
}
fn add_certificate_parameters(
+ &self,
uid: u32,
params: &[KeyParameter],
key: &KeyDescriptor,
@@ -374,6 +378,14 @@
"In add_certificate_parameters: ",
"Caller does not have the permission to generate a unique ID"
))?;
+ if self.id_rotation_state.had_factory_reset_since_id_rotation().context(
+ "In add_certificate_parameters: Call to had_factory_reset_since_id_rotation failed."
+ )? {
+ result.push(KeyParameter{
+ tag: Tag::RESET_SINCE_ID_ROTATION,
+ value: KeyParameterValue::BoolValue(true),
+ })
+ }
}
// If the caller requests any device identifier attestation tag, check that they hold the
@@ -451,7 +463,8 @@
})
.context("In generate_key: Trying to get an attestation key")?,
};
- let params = Self::add_certificate_parameters(caller_uid, params, &key)
+ let params = self
+ .add_certificate_parameters(caller_uid, params, &key)
.context("In generate_key: Trying to get aaid.")?;
let km_dev: Strong<dyn IKeyMintDevice> = self.keymint.get_interface()?;
@@ -524,7 +537,8 @@
// import_key requires the rebind permission.
check_key_permission(KeyPerm::rebind(), &key, &None).context("In import_key.")?;
- let params = Self::add_certificate_parameters(caller_uid, params, &key)
+ let params = self
+ .add_certificate_parameters(caller_uid, params, &key)
.context("In import_key: Trying to get aaid.")?;
let format = params
diff --git a/keystore2/src/service.rs b/keystore2/src/service.rs
index 73bd526..1debe1b 100644
--- a/keystore2/src/service.rs
+++ b/keystore2/src/service.rs
@@ -17,7 +17,6 @@
use std::collections::HashMap;
-use crate::error::{self, map_or_log_err, ErrorCode};
use crate::permission::{KeyPerm, KeystorePerm};
use crate::security_level::KeystoreSecurityLevel;
use crate::utils::{
@@ -33,6 +32,10 @@
database::{KeyEntryLoadBits, KeyType, SubComponentType},
error::ResponseCode,
};
+use crate::{
+ error::{self, map_or_log_err, ErrorCode},
+ id_rotation::IdRotationState,
+};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::SecurityLevel::SecurityLevel;
use android_system_keystore2::aidl::android::system::keystore2::{
Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel,
@@ -53,21 +56,26 @@
impl KeystoreService {
/// Create a new instance of the Keystore 2.0 service.
- pub fn new_native_binder() -> Result<Strong<dyn IKeystoreService>> {
+ pub fn new_native_binder(
+ id_rotation_state: IdRotationState,
+ ) -> Result<Strong<dyn IKeystoreService>> {
let mut result: Self = Default::default();
- let (dev, uuid) =
- KeystoreSecurityLevel::new_native_binder(SecurityLevel::TRUSTED_ENVIRONMENT)
- .context(concat!(
- "In KeystoreService::new_native_binder: ",
- "Trying to construct mandatory security level TEE."
- ))
- .map(|(dev, uuid)| (Asp::new(dev.as_binder()), uuid))?;
+ let (dev, uuid) = KeystoreSecurityLevel::new_native_binder(
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ id_rotation_state.clone(),
+ )
+ .context(concat!(
+ "In KeystoreService::new_native_binder: ",
+ "Trying to construct mandatory security level TEE."
+ ))
+ .map(|(dev, uuid)| (Asp::new(dev.as_binder()), uuid))?;
result.i_sec_level_by_uuid.insert(uuid, dev);
result.uuid_by_sec_level.insert(SecurityLevel::TRUSTED_ENVIRONMENT, uuid);
// Strongbox is optional, so we ignore errors and turn the result into an Option.
- if let Ok((dev, uuid)) = KeystoreSecurityLevel::new_native_binder(SecurityLevel::STRONGBOX)
- .map(|(dev, uuid)| (Asp::new(dev.as_binder()), uuid))
+ if let Ok((dev, uuid)) =
+ KeystoreSecurityLevel::new_native_binder(SecurityLevel::STRONGBOX, id_rotation_state)
+ .map(|(dev, uuid)| (Asp::new(dev.as_binder()), uuid))
{
result.i_sec_level_by_uuid.insert(uuid, dev);
result.uuid_by_sec_level.insert(SecurityLevel::STRONGBOX, uuid);