// 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 crate::globals::get_keymint_device; 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::BpSharedSecret, ISharedSecret::ISharedSecret, SharedSecretParameters::SharedSecretParameters, }; use android_security_compat::aidl::android::security::compat::IKeystoreCompatService::IKeystoreCompatService; use anyhow::Result; use binder::get_declared_instances; use keystore2_hal_names::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 gets hal names from the android ServiceManager. 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."); // Once shared secret negotiation is done, the StrongBox and TEE have a common key that // can be used to authenticate a possible RootOfTrust transfer. transfer_root_of_trust(); }); } #[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, "{}/{}", ::get_descriptor(), 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 { 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 COMPAT_PACKAGE_NAME: &str = "android.security.compat"; /// Lists participants. fn list_participants() -> Result> { // 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() .flat_map(|(ma, mi)| { get_hidl_instances(KEYMASTER_PACKAGE_NAME, *ma, *mi, KEYMASTER_INTERFACE_NAME) .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::>() }) .chain({ get_declared_instances(::get_descriptor()) .unwrap() .into_iter() .map(SharedSecretParticipant::Aidl) .collect::>() .into_iter() }) .collect()) } fn connect_participants( mut participants: Vec, ) -> Vec<(Strong, SharedSecretParticipant)> { let mut connected_participants: Vec<(Strong, 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!( "{}/{}", ::get_descriptor(), 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 = 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, SharedSecretParticipant)>, ) { // Phase 1: Get the sharing parameters from all participants. let mut params = loop { let result: Result, 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(¶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 }), } }); 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.", )); } } } /// Perform RootOfTrust transfer from TEE to StrongBox (if available). pub fn transfer_root_of_trust() { let strongbox = match get_keymint_device(&SecurityLevel::STRONGBOX) { Ok((s, _, _)) => s, Err(_e) => { log::info!("No StrongBox Keymint available, so no RoT transfer"); return; } }; // Ask the StrongBox KeyMint for a challenge. let challenge = match strongbox.getRootOfTrustChallenge() { Ok(data) => data, Err(e) => { // If StrongBox doesn't provide a challenge, it might be because: // - it already has RootOfTrust information // - it's a KeyMint v1 implementation that doesn't understand the method. // In either case, we're done. log::info!("StrongBox does not provide a challenge, so no RoT transfer: {:?}", e); return; } }; // Get the RoT info from the TEE let tee = match get_keymint_device(&SecurityLevel::TRUSTED_ENVIRONMENT) { Ok((s, _, _)) => s, Err(e) => { log::error!("No TEE KeyMint implementation found! {:?}", e); return; } }; let root_of_trust = match tee.getRootOfTrust(&challenge) { Ok(rot) => rot, Err(e) => { log::error!("TEE KeyMint failed to return RootOfTrust info: {:?}", e); return; } }; // The RootOfTrust information is CBOR-serialized data, but we don't need to parse it. // Just pass it on to the StrongBox KeyMint instance. let result = strongbox.sendRootOfTrust(&root_of_trust); if let Err(e) = result { log::error!("Failed to send RootOfTrust to StrongBox: {:?}", e); } log::info!("RootOfTrust transfer process complete"); }