Keystore 2.0: Implement shared secret negotiation.
Perform shared secret negotiation between all eligible participants.
Bug: 160623310
Test: N/A
Change-Id: I7c0ddb92a8f0ebf9fc37434f662ba21aea15fff2
diff --git a/keystore2/src/keystore2_main.rs b/keystore2/src/keystore2_main.rs
index 5d99449..1ce3e14 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::apc::ApcManager;
use keystore2::authorization::AuthorizationManager;
use keystore2::entropy;
use keystore2::globals::ENFORCEMENTS;
use keystore2::remote_provisioning::RemoteProvisioningService;
use keystore2::service::KeystoreService;
use keystore2::user_manager::Maintenance;
+use keystore2::{apc::ApcManager, shared_secret_negotiation};
use log::{error, info};
use std::{panic, path::Path, sync::mpsc::channel};
use vpnprofilestore::VpnProfileStore;
@@ -76,6 +76,7 @@
});
entropy::register_feeder();
+ shared_secret_negotiation::perform_shared_secret_negotiation();
info!("Starting thread pool now.");
binder::ProcessState::start_thread_pool();
diff --git a/keystore2/src/lib.rs b/keystore2/src/lib.rs
index 0e51eff..2a00ae3 100644
--- a/keystore2/src/lib.rs
+++ b/keystore2/src/lib.rs
@@ -32,6 +32,7 @@
pub mod remote_provisioning;
pub mod security_level;
pub mod service;
+pub mod shared_secret_negotiation;
pub mod user_manager;
pub mod utils;
diff --git a/keystore2/src/shared_secret_negotiation.rs b/keystore2/src/shared_secret_negotiation.rs
new file mode 100644
index 0000000..afce533
--- /dev/null
+++ b/keystore2/src/shared_secret_negotiation.rs
@@ -0,0 +1,263 @@
+// 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 shared secret negotiation.
+
+use crate::error::{map_binder_status, map_binder_status_code, Error};
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::SecurityLevel::SecurityLevel;
+use android_hardware_security_keymint::binder::Strong;
+use android_hardware_security_sharedsecret::aidl::android::hardware::security::sharedsecret::{
+ ISharedSecret::ISharedSecret, SharedSecretParameters::SharedSecretParameters,
+};
+use android_security_compat::aidl::android::security::compat::IKeystoreCompatService::IKeystoreCompatService;
+use anyhow::{Context, Result};
+use keystore2_vintf::{get_aidl_instances, get_hidl_instances};
+use std::fmt::{self, Display, Formatter};
+
+/// This function initiates the shared secret negotiation. It starts a thread and then returns
+/// immediately. The thread consults the vintf manifest to enumerate expected negotiation
+/// participants. It then attempts to connect to all of these participants. If any connection
+/// fails the thread will retry once per second to connect to the failed instance(s) until all of
+/// the instances are connected. It then performs the negotiation.
+///
+/// During the first phase of the negotiation it will again try every second until
+/// all instances have responded successfully to account for instances that register early but
+/// are not fully functioning at this time due to hardware delays or boot order dependency issues.
+/// An error during the second phase or a checksum mismatch leads to a panic.
+pub fn perform_shared_secret_negotiation() {
+ std::thread::spawn(|| {
+ let participants = list_participants()
+ .expect("In perform_shared_secret_negotiation: Trying to list participants.");
+ let connected = connect_participants(participants);
+ negotiate_shared_secret(connected);
+ log::info!("Shared secret negotiation concluded successfully.");
+ });
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+enum SharedSecretParticipant {
+ /// Represents an instance of android.hardware.security.sharedsecret.ISharedSecret.
+ Aidl(String),
+ /// In the legacy case there can be at most one TEE and one Strongbox hal.
+ Hidl { is_strongbox: bool, version: (usize, usize) },
+}
+
+impl Display for SharedSecretParticipant {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ match self {
+ Self::Aidl(instance) => write!(
+ f,
+ "{}.{}/{}",
+ SHARED_SECRET_PACKAGE_NAME, SHARED_SECRET_INTERFACE_NAME, instance
+ ),
+ Self::Hidl { is_strongbox, version: (ma, mi) } => write!(
+ f,
+ "{}@V{}.{}::{}/{}",
+ KEYMASTER_PACKAGE_NAME,
+ ma,
+ mi,
+ KEYMASTER_INTERFACE_NAME,
+ if *is_strongbox { "strongbox" } else { "default" }
+ ),
+ }
+ }
+}
+
+#[derive(thiserror::Error, Debug)]
+enum SharedSecretError {
+ #[error("Shared parameter retrieval failed on instance {p} with error {e:?}.")]
+ ParameterRetrieval { e: Error, p: SharedSecretParticipant },
+ #[error("Shared secret computation failed on instance {p} with error {e:?}.")]
+ Computation { e: Error, p: SharedSecretParticipant },
+ #[error("Checksum comparison failed on instance {0}.")]
+ Checksum(SharedSecretParticipant),
+}
+
+fn filter_map_legacy_km_instances(
+ name: String,
+ version: (usize, usize),
+) -> Option<SharedSecretParticipant> {
+ match name.as_str() {
+ "default" => Some(SharedSecretParticipant::Hidl { is_strongbox: false, version }),
+ "strongbox" => Some(SharedSecretParticipant::Hidl { is_strongbox: true, version }),
+ _ => {
+ log::warn!("Found unexpected keymaster instance: \"{}\"", name);
+ log::warn!("Device is misconfigured. Allowed instances are:");
+ log::warn!(" * default");
+ log::warn!(" * strongbox");
+ None
+ }
+ }
+}
+
+static KEYMASTER_PACKAGE_NAME: &str = "android.hardware.keymaster";
+static KEYMASTER_INTERFACE_NAME: &str = "IKeymasterDevice";
+static SHARED_SECRET_PACKAGE_NAME: &str = "android.hardware.security.sharedsecret";
+static SHARED_SECRET_INTERFACE_NAME: &str = "ISharedSecret";
+static COMPAT_PACKAGE_NAME: &str = "android.security.compat";
+
+/// Lists participants.
+fn list_participants() -> Result<Vec<SharedSecretParticipant>> {
+ Ok([(4, 0), (4, 1)]
+ .iter()
+ .map(|(ma, mi)| {
+ get_hidl_instances(KEYMASTER_PACKAGE_NAME, *ma, *mi, KEYMASTER_INTERFACE_NAME)
+ .as_vec()
+ .with_context(|| format!("Trying to convert KM{}.{} names to vector.", *ma, *mi))
+ .map(|instances| {
+ instances
+ .into_iter()
+ .filter_map(|name| {
+ filter_map_legacy_km_instances(name.to_string(), (*ma, *mi))
+ })
+ .collect::<Vec<SharedSecretParticipant>>()
+ })
+ })
+ .collect::<Result<Vec<_>>>()
+ .map(|v| v.into_iter().flatten())
+ .and_then(|i| {
+ let participants_aidl: Vec<SharedSecretParticipant> =
+ get_aidl_instances(SHARED_SECRET_PACKAGE_NAME, 1, SHARED_SECRET_INTERFACE_NAME)
+ .as_vec()
+ .context("In list_participants: Trying to convert KM1.0 names to vector.")?
+ .into_iter()
+ .map(|name| SharedSecretParticipant::Aidl(name.to_string()))
+ .collect();
+ Ok(i.chain(participants_aidl.into_iter()))
+ })
+ .context("In list_participants.")?
+ .collect())
+}
+
+fn connect_participants(
+ mut participants: Vec<SharedSecretParticipant>,
+) -> Vec<(Strong<dyn ISharedSecret>, SharedSecretParticipant)> {
+ let mut connected_participants: Vec<(Strong<dyn ISharedSecret>, SharedSecretParticipant)> =
+ vec![];
+ loop {
+ let (connected, not_connected) = participants.into_iter().fold(
+ (connected_participants, vec![]),
+ |(mut connected, mut failed), e| {
+ match e {
+ SharedSecretParticipant::Aidl(instance_name) => {
+ let service_name =
+ format!("{}/{}", SHARED_SECRET_PACKAGE_NAME, instance_name);
+ match map_binder_status_code(binder::get_interface(&service_name)) {
+ Err(e) => {
+ log::warn!(
+ "Unable to connect \"{}\" with error:\n{:?}\nRetrying later.",
+ service_name,
+ e
+ );
+ failed.push(SharedSecretParticipant::Aidl(instance_name));
+ }
+ Ok(service) => connected
+ .push((service, SharedSecretParticipant::Aidl(instance_name))),
+ }
+ }
+ SharedSecretParticipant::Hidl { is_strongbox, version } => {
+ // This is a no-op if it was called before.
+ keystore2_km_compat::add_keymint_device_service();
+
+ // If we cannot connect to the compatibility service there is no way to
+ // recover.
+ // PANIC! - Unless you brought your towel.
+ let keystore_compat_service: Strong<dyn IKeystoreCompatService> =
+ map_binder_status_code(binder::get_interface(COMPAT_PACKAGE_NAME))
+ .expect(
+ "In connect_participants: Trying to connect to compat service.",
+ );
+
+ match map_binder_status(keystore_compat_service.getSharedSecret(
+ if is_strongbox {
+ SecurityLevel::STRONGBOX
+ } else {
+ SecurityLevel::TRUSTED_ENVIRONMENT
+ },
+ )) {
+ Err(e) => {
+ log::warn!(
+ concat!(
+ "Unable to connect keymaster device \"{}\" ",
+ "with error:\n{:?}\nRetrying later."
+ ),
+ if is_strongbox { "strongbox" } else { "TEE" },
+ e
+ );
+ failed
+ .push(SharedSecretParticipant::Hidl { is_strongbox, version });
+ }
+ Ok(service) => connected.push((
+ service,
+ SharedSecretParticipant::Hidl { is_strongbox, version },
+ )),
+ }
+ }
+ }
+ (connected, failed)
+ },
+ );
+ participants = not_connected;
+ connected_participants = connected;
+ if participants.is_empty() {
+ break;
+ }
+ std::thread::sleep(std::time::Duration::from_millis(1000));
+ }
+ connected_participants
+}
+
+fn negotiate_shared_secret(
+ participants: Vec<(Strong<dyn ISharedSecret>, SharedSecretParticipant)>,
+) {
+ // Phase 1: Get the sharing parameters from all participants.
+ let mut params = loop {
+ let result: Result<Vec<SharedSecretParameters>, SharedSecretError> = participants
+ .iter()
+ .map(|(s, p)| {
+ map_binder_status(s.getSharedSecretParameters())
+ .map_err(|e| SharedSecretError::ParameterRetrieval { e, p: (*p).clone() })
+ })
+ .collect();
+
+ match result {
+ Err(e) => {
+ log::warn!("{:?}", e);
+ log::warn!("Retrying in one second.");
+ std::thread::sleep(std::time::Duration::from_millis(1000));
+ }
+ Ok(params) => break params,
+ }
+ };
+
+ params.sort_unstable();
+
+ // Phase 2: Send the sorted sharing parameters to all participants.
+ participants
+ .into_iter()
+ .try_fold(None, |acc, (s, p)| {
+ match (acc, map_binder_status(s.computeSharedSecret(¶ms))) {
+ (None, Ok(new_sum)) => Ok(Some(new_sum)),
+ (Some(old_sum), Ok(new_sum)) => {
+ if old_sum == new_sum {
+ Ok(Some(old_sum))
+ } else {
+ Err(SharedSecretError::Checksum(p))
+ }
+ }
+ (_, Err(e)) => Err(SharedSecretError::Computation { e, p }),
+ }
+ })
+ .expect("Fatal: Shared secret computation failed.");
+}
diff --git a/keystore2/src/vintf/Android.bp b/keystore2/src/vintf/Android.bp
index 77ec57d..feec8ae 100644
--- a/keystore2/src/vintf/Android.bp
+++ b/keystore2/src/vintf/Android.bp
@@ -55,6 +55,7 @@
"--size_t-is-usize",
"--whitelist-function", "getHalNames",
"--whitelist-function", "getHalNamesAndVersions",
+ "--whitelist-function", "getHidlInstances",
"--whitelist-function", "getAidlInstances",
"--whitelist-function", "freeNames",
],
diff --git a/keystore2/src/vintf/lib.rs b/keystore2/src/vintf/lib.rs
index c3d6d8a..8730a3e 100644
--- a/keystore2/src/vintf/lib.rs
+++ b/keystore2/src/vintf/lib.rs
@@ -14,7 +14,9 @@
//! Bindings for getting the list of HALs.
-use keystore2_vintf_bindgen::{freeNames, getAidlInstances, getHalNames, getHalNamesAndVersions};
+use keystore2_vintf_bindgen::{
+ freeNames, getAidlInstances, getHalNames, getHalNamesAndVersions, getHidlInstances,
+};
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::str::Utf8Error;
@@ -65,6 +67,32 @@
/// Gets the instances of the given package, version, and interface tuple.
/// Note that this is not a zero-cost shim: it will make copies of the strings.
+pub fn get_hidl_instances(
+ package: &str,
+ major_version: usize,
+ minor_version: usize,
+ interface_name: &str,
+) -> HalNames {
+ let mut len: usize = 0;
+ let packages = CString::new(package).expect("Failed to make CString from package.");
+ let interface_name =
+ CString::new(interface_name).expect("Failed to make CString from interface_name.");
+ // Safety: We'll wrap this in HalNames to free the memory it allocates.
+ // It stores the size of the array it returns in len.
+ let raw_strs = unsafe {
+ getHidlInstances(
+ &mut len,
+ packages.as_ptr(),
+ major_version,
+ minor_version,
+ interface_name.as_ptr(),
+ )
+ };
+ HalNames { data: raw_strs, len }
+}
+
+/// Gets the instances of the given package, version, and interface tuple.
+/// Note that this is not a zero-cost shim: it will make copies of the strings.
pub fn get_aidl_instances(package: &str, version: usize, interface_name: &str) -> HalNames {
let mut len: usize = 0;
let packages = CString::new(package).expect("Failed to make CString from package.");
diff --git a/keystore2/src/vintf/vintf.cpp b/keystore2/src/vintf/vintf.cpp
index dbdc046..e407efa 100644
--- a/keystore2/src/vintf/vintf.cpp
+++ b/keystore2/src/vintf/vintf.cpp
@@ -43,6 +43,15 @@
return convert(names);
}
+char** getHidlInstances(size_t* len, const char* package, size_t major_version,
+ size_t minor_version, const char* interfaceName) {
+ android::vintf::Version version(major_version, minor_version);
+ auto manifest = android::vintf::VintfObject::GetDeviceHalManifest();
+ const auto names = manifest->getHidlInstances(package, version, interfaceName);
+ *len = names.size();
+ return convert(names);
+}
+
char** getAidlInstances(size_t* len, const char* package, size_t version,
const char* interfaceName) {
auto manifest = android::vintf::VintfObject::GetDeviceHalManifest();
diff --git a/keystore2/src/vintf/vintf.hpp b/keystore2/src/vintf/vintf.hpp
index 75e80f6..091e8e8 100644
--- a/keystore2/src/vintf/vintf.hpp
+++ b/keystore2/src/vintf/vintf.hpp
@@ -23,6 +23,8 @@
char** getHalNames(size_t* len);
char** getHalNamesAndVersions(size_t* len);
+char** getHidlInstances(size_t* len, const char* package, size_t major_version,
+ size_t minor_version, const char* interfaceName);
char** getAidlInstances(size_t* len, const char* package, size_t version,
const char* interfaceName);
void freeNames(char** names, size_t len);