blob: e32b675c948764dd44a4834ba6488c229a1727e7 [file] [log] [blame]
// 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};
use std::time::Duration;
/// 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>> {
// 4.1 implementation always also register as 4.0. So only the highest version of each
// "default" and "strongbox" makes the cut.
let mut legacy_default_found: bool = false;
let mut legacy_strongbox_found: bool = false;
Ok([(4, 1), (4, 0)]
.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)).and_then(
|sp| {
if let SharedSecretParticipant::Hidl {
is_strongbox: true,
..
} = &sp
{
if !legacy_strongbox_found {
legacy_strongbox_found = true;
return Some(sp);
}
} else if !legacy_default_found {
legacy_default_found = true;
return Some(sp);
}
None
},
)
})
.collect::<Vec<SharedSecretParticipant>>()
})
})
.collect::<Result<Vec<_>>>()
.map(|v| v.into_iter().flatten())
.and_then(|i| {
Ok(i.chain(
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::<Vec<_>>()
.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, SHARED_SECRET_INTERFACE_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(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(Duration::from_millis(1000));
}
Ok(params) => break params,
}
};
params.sort_unstable();
// Phase 2: Send the sorted sharing parameters to all participants.
let negotiation_result = participants.into_iter().try_fold(None, |acc, (s, p)| {
match (acc, map_binder_status(s.computeSharedSecret(&params))) {
(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 }),
}
});
if let Err(e) = negotiation_result {
log::error!("In negotiate_shared_secret: {:?}.", e);
if let SharedSecretError::Checksum(_) = e {
log::error!(concat!(
"This means that this device is NOT PROVISIONED CORRECTLY.\n",
"User authorization and other security functions will not work\n",
"as expected. Please contact your OEM for instructions.",
));
}
}
}