Merge "Revert^2 "Cryptographic security for MAX_BOOT_LEVEL""
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/apc.rs b/keystore2/src/apc.rs
index f8259ea..46b71dd 100644
--- a/keystore2/src/apc.rs
+++ b/keystore2/src/apc.rs
@@ -268,7 +268,7 @@
fn present_prompt(
&self,
- listener: &dyn IConfirmationCallback,
+ listener: &binder::Strong<dyn IConfirmationCallback>,
prompt_text: &str,
extra_data: &[u8],
locale: &str,
@@ -327,7 +327,7 @@
Ok(())
}
- fn cancel_prompt(&self, listener: &dyn IConfirmationCallback) -> Result<()> {
+ fn cancel_prompt(&self, listener: &binder::Strong<dyn IConfirmationCallback>) -> Result<()> {
let mut state = self.state.lock().unwrap();
let hal = match &mut state.session {
None => {
@@ -358,7 +358,7 @@
impl IProtectedConfirmation for ApcManager {
fn presentPrompt(
&self,
- listener: &dyn IConfirmationCallback,
+ listener: &binder::Strong<dyn IConfirmationCallback>,
prompt_text: &str,
extra_data: &[u8],
locale: &str,
@@ -369,7 +369,10 @@
Ok,
)
}
- fn cancelPrompt(&self, listener: &dyn IConfirmationCallback) -> BinderResult<()> {
+ fn cancelPrompt(
+ &self,
+ listener: &binder::Strong<dyn IConfirmationCallback>,
+ ) -> BinderResult<()> {
map_or_log_err(self.cancel_prompt(listener), Ok)
}
fn isSupported(&self) -> BinderResult<bool> {
diff --git a/keystore2/src/async_task.rs b/keystore2/src/async_task.rs
index 20a7458..4d0034a 100644
--- a/keystore2/src/async_task.rs
+++ b/keystore2/src/async_task.rs
@@ -197,7 +197,7 @@
enum Action {
QueuedFn(Box<dyn FnOnce(&mut Shelf) + Send>),
IdleFns(Vec<Arc<dyn Fn(&mut Shelf) + Send + Sync>>),
- };
+ }
let mut done_idle = false;
// When the worker starts, it takes the shelf and puts it on the stack.
diff --git a/keystore2/src/database.rs b/keystore2/src/database.rs
index f673d17..7a94600 100644
--- a/keystore2/src/database.rs
+++ b/keystore2/src/database.rs
@@ -41,6 +41,8 @@
//! from the database module these functions take permission check
//! callbacks.
+#![allow(clippy::needless_question_mark)]
+
use crate::impl_metadata; // This is in db_utils.rs
use crate::key_parameter::{KeyParameter, Tag};
use crate::permission::KeyPermSet;
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/key_parameter.rs b/keystore2/src/key_parameter.rs
index e536e45..74a9b23 100644
--- a/keystore2/src/key_parameter.rs
+++ b/keystore2/src/key_parameter.rs
@@ -90,6 +90,8 @@
//! * The termination condition which has an empty in list.
//! * The public interface, which does not have @marker and calls itself with an empty out list.
+#![allow(clippy::from_over_into, clippy::needless_question_mark)]
+
use std::convert::TryInto;
use crate::db_utils::SqlField;
diff --git a/keystore2/src/keystore2_main.rs b/keystore2/src/keystore2_main.rs
index a6bbb2d..b95add2 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();
@@ -74,7 +76,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/legacy_blob.rs b/keystore2/src/legacy_blob.rs
index c108b32..a3e440b 100644
--- a/keystore2/src/legacy_blob.rs
+++ b/keystore2/src/legacy_blob.rs
@@ -14,6 +14,8 @@
//! This module implements methods to load legacy keystore key blob files.
+#![allow(clippy::redundant_slicing)]
+
use crate::{
error::{Error as KsError, ResponseCode},
key_parameter::{KeyParameter, KeyParameterValue},
@@ -799,10 +801,18 @@
/// encoded with UID prefix.
fn list_user(&self, user_id: u32) -> Result<Vec<String>> {
let path = self.make_user_path_name(user_id);
- let dir =
- Self::with_retry_interrupted(|| fs::read_dir(path.as_path())).with_context(|| {
- format!("In list_user: Failed to open legacy blob database. {:?}", path)
- })?;
+ let dir = match Self::with_retry_interrupted(|| fs::read_dir(path.as_path())) {
+ Ok(dir) => dir,
+ Err(e) => match e.kind() {
+ ErrorKind::NotFound => return Ok(Default::default()),
+ _ => {
+ return Err(e).context(format!(
+ "In list_user: Failed to open legacy blob database. {:?}",
+ path
+ ))
+ }
+ },
+ };
let mut result: Vec<String> = Vec::new();
for entry in dir {
let file_name = entry.context("In list_user: Trying to access dir entry")?.file_name();
@@ -1138,7 +1148,7 @@
let encoded = LegacyBlobLoader::encode_alias(&alias_str);
let decoded = match LegacyBlobLoader::decode_alias(&encoded) {
Ok(d) => d,
- Err(_) => panic!(format!("random_alias: {:x?}\nencoded {}", random_alias, encoded)),
+ Err(_) => panic!("random_alias: {:x?}\nencoded {}", random_alias, encoded),
};
assert_eq!(random_alias.to_vec(), decoded.bytes().collect::<Vec<u8>>());
}
@@ -1350,4 +1360,14 @@
Ok(())
}
+
+ #[test]
+ fn list_non_existing_user() -> Result<()> {
+ let temp_dir = TempDir::new("list_non_existing_user")?;
+ let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
+
+ assert!(legacy_blob_loader.list_user(20)?.is_empty());
+
+ Ok(())
+ }
}
diff --git a/keystore2/src/lib.rs b/keystore2/src/lib.rs
index 0916af1..f851d3a 100644
--- a/keystore2/src/lib.rs
+++ b/keystore2/src/lib.rs
@@ -25,6 +25,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/permission.rs b/keystore2/src/permission.rs
index 45c4dc1..726c2ec 100644
--- a/keystore2/src/permission.rs
+++ b/keystore2/src/permission.rs
@@ -18,6 +18,8 @@
//! It also provides KeystorePerm and KeyPerm as convenience wrappers for the SELinux permission
//! defined by keystore2 and keystore2_key respectively.
+#![allow(clippy::from_over_into)]
+
use android_system_keystore2::aidl::android::system::keystore2::{
Domain::Domain, KeyDescriptor::KeyDescriptor, KeyPermission::KeyPermission,
};
diff --git a/keystore2/src/remote_provisioning.rs b/keystore2/src/remote_provisioning.rs
index 1c757c9..f99805d 100644
--- a/keystore2/src/remote_provisioning.rs
+++ b/keystore2/src/remote_provisioning.rs
@@ -19,6 +19,8 @@
//! certificate chains signed by some root authority and stored in a keystore SQLite
//! DB.
+#![allow(clippy::from_over_into, clippy::needless_question_mark, clippy::vec_init_then_push)]
+
use std::collections::HashMap;
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
diff --git a/keystore2/src/security_level.rs b/keystore2/src/security_level.rs
index 65512f1..c654c02 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))
@@ -275,6 +278,12 @@
},
)?;
+ // Remove Tag::PURPOSE from the operation_parameters, since some keymaster devices return
+ // an error on begin() if Tag::PURPOSE is in the operation_parameters.
+ let op_params: Vec<KeyParameter> =
+ operation_parameters.iter().filter(|p| p.tag != Tag::PURPOSE).cloned().collect();
+ let operation_parameters = op_params.as_slice();
+
let (immediate_hat, mut auth_info) = ENFORCEMENTS
.authorize_create(
purpose,
@@ -353,6 +362,7 @@
}
fn add_certificate_parameters(
+ &self,
uid: u32,
params: &[KeyParameter],
key: &KeyDescriptor,
@@ -374,6 +384,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 +469,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 +543,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);
diff --git a/keystore2/vpnprofilestore/lib.rs b/keystore2/vpnprofilestore/lib.rs
index f92eacd..f5adc1b 100644
--- a/keystore2/vpnprofilestore/lib.rs
+++ b/keystore2/vpnprofilestore/lib.rs
@@ -39,6 +39,10 @@
let mut db = Self {
conn: Connection::open(db_file).context("Failed to initialize SQLite connection.")?,
};
+
+ // On busy fail Immediately. It is unlikely to succeed given a bug in sqlite.
+ db.conn.busy_handler(None).context("Failed to set busy handler.")?;
+
db.init_tables().context("Trying to initialize vpnstore db.")?;
Ok(db)
}
@@ -377,7 +381,12 @@
mod db_test {
use super::*;
use keystore2_test_utils::TempDir;
+ use std::sync::Arc;
+ use std::thread;
+ use std::time::Duration;
+ use std::time::Instant;
+ static TEST_ALIAS: &str = &"test_alias";
static TEST_BLOB1: &[u8] = &[1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
static TEST_BLOB2: &[u8] = &[2, 2, 3, 4, 5, 6, 7, 8, 9, 0];
static TEST_BLOB3: &[u8] = &[3, 2, 3, 4, 5, 6, 7, 8, 9, 0];
@@ -440,4 +449,104 @@
db.get(2, "test1").expect("Failed to get profile.").as_deref()
);
}
+
+ #[test]
+ fn concurrent_vpn_profile_test() -> Result<()> {
+ let temp_dir = Arc::new(
+ TempDir::new("concurrent_vpn_profile_test_").expect("Failed to create temp dir."),
+ );
+
+ let db_path = temp_dir.build().push("vpnprofile.sqlite").to_owned();
+
+ let test_begin = Instant::now();
+
+ let mut db = DB::new(&db_path).expect("Failed to open database.");
+ const PROFILE_COUNT: u32 = 5000u32;
+ const PROFILE_DB_COUNT: u32 = 5000u32;
+
+ let mut actual_profile_count = PROFILE_COUNT;
+ // First insert PROFILE_COUNT profiles.
+ for count in 0..PROFILE_COUNT {
+ if Instant::now().duration_since(test_begin) >= Duration::from_secs(15) {
+ actual_profile_count = count;
+ break;
+ }
+ let alias = format!("test_alias_{}", count);
+ db.put(1, &alias, TEST_BLOB1).expect("Failed to add profile (1).");
+ }
+
+ // Insert more keys from a different thread and into a different namespace.
+ let db_path1 = db_path.clone();
+ let handle1 = thread::spawn(move || {
+ let mut db = DB::new(&db_path1).expect("Failed to open database.");
+
+ for count in 0..actual_profile_count {
+ if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) {
+ return;
+ }
+ let alias = format!("test_alias_{}", count);
+ db.put(2, &alias, TEST_BLOB2).expect("Failed to add profile (2).");
+ }
+
+ // Then delete them again.
+ for count in 0..actual_profile_count {
+ if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) {
+ return;
+ }
+ let alias = format!("test_alias_{}", count);
+ db.remove(2, &alias).expect("Remove Failed (2).");
+ }
+ });
+
+ // And start deleting the first set of profiles.
+ let db_path2 = db_path.clone();
+ let handle2 = thread::spawn(move || {
+ let mut db = DB::new(&db_path2).expect("Failed to open database.");
+
+ for count in 0..actual_profile_count {
+ if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) {
+ return;
+ }
+ let alias = format!("test_alias_{}", count);
+ db.remove(1, &alias).expect("Remove Failed (1)).");
+ }
+ });
+
+ // While a lot of inserting and deleting is going on we have to open database connections
+ // successfully and then insert and delete a specific profile.
+ let db_path3 = db_path.clone();
+ let handle3 = thread::spawn(move || {
+ for _count in 0..PROFILE_DB_COUNT {
+ if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) {
+ return;
+ }
+ let mut db = DB::new(&db_path3).expect("Failed to open database.");
+
+ db.put(3, &TEST_ALIAS, TEST_BLOB3).expect("Failed to add profile (3).");
+
+ db.remove(3, &TEST_ALIAS).expect("Remove failed (3).");
+ }
+ });
+
+ // While thread 3 is inserting and deleting TEST_ALIAS, we try to get the alias.
+ // This may yield an entry or none, but it must not fail.
+ let handle4 = thread::spawn(move || {
+ for _count in 0..PROFILE_DB_COUNT {
+ if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) {
+ return;
+ }
+ let mut db = DB::new(&db_path).expect("Failed to open database.");
+
+ // This may return Some or None but it must not fail.
+ db.get(3, &TEST_ALIAS).expect("Failed to get profile (4).");
+ }
+ });
+
+ handle1.join().expect("Thread 1 panicked.");
+ handle2.join().expect("Thread 2 panicked.");
+ handle3.join().expect("Thread 3 panicked.");
+ handle4.join().expect("Thread 4 panicked.");
+
+ Ok(())
+ }
}
diff --git a/ondevice-signing/KeystoreKey.cpp b/ondevice-signing/KeystoreKey.cpp
index 840b683..9b5e505 100644
--- a/ondevice-signing/KeystoreKey.cpp
+++ b/ondevice-signing/KeystoreKey.cpp
@@ -239,5 +239,10 @@
}
Result<std::vector<uint8_t>> KeystoreKey::getPublicKey() const {
- return extractPublicKeyFromX509(mKeyMetadata.certificate.value());
+ auto cert = mKeyMetadata.certificate;
+ if (cert) {
+ return extractPublicKeyFromX509(cert.value());
+ } else {
+ return Error() << "Key did not have a certificate";
+ }
}