| // |
| // Copyright (C) 2024 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. |
| |
| #![no_std] |
| #![no_main] |
| #![feature(c_size_t)] |
| |
| #[macro_use] |
| extern crate alloc; |
| |
| mod client_id; |
| mod command; |
| mod config; |
| mod crypto; |
| mod error; |
| mod invoke; |
| mod software; |
| mod storage; |
| mod stubs; |
| |
| use alloc::boxed::Box; |
| use alloc::collections::vec_deque::VecDeque; |
| use alloc::vec::Vec; |
| use client_id::ClientId; |
| use core::ffi::c_void; |
| use kmr_ta::device::DeviceHmac; |
| use log::info; |
| use optee_utee::{ |
| ta_close_session, ta_create, ta_destroy, ta_invoke_command, ta_open_session, Uuid, |
| }; |
| use optee_utee::{Error, ErrorKind, ParamType, Parameters, Result}; |
| use spin::Mutex; |
| use spin::Once; |
| |
| // SAFETY: |
| // |
| // Each OP-TEE TA, per GlobalPlatform internal core API spec v1.1+, |
| // is essentially a single-threaded environment, in which all TA |
| // entry points (create, destroy, open_session, close_session, and |
| // invoke_command) are executed sequencially as by the TEE OS. |
| // |
| // Thus it is safe to mark the global `kmr_ta::KeyMintTa` variable |
| // as Send. |
| // |
| // We use spin::Once and spin::Mutex for readability rather than |
| // necessity. |
| struct SingleThreadedTa(kmr_ta::KeyMintTa); |
| unsafe impl Send for SingleThreadedTa {} |
| |
| fn keymint_ta() -> &'static Mutex<SingleThreadedTa> { |
| static KEYMINT_TA: Once<Mutex<SingleThreadedTa>> = Once::new(); |
| KEYMINT_TA.call_once(|| { |
| let ta = kmr_ta::KeyMintTa::new( |
| kmr_ta::HardwareInfo { |
| version_number: 3, |
| security_level: kmr_common::wire::keymint::SecurityLevel::TrustedEnvironment, |
| impl_name: "TEE KeyMint in Rust", |
| author_name: "Google", |
| unique_id: "TEE KeyMint TA", |
| }, |
| kmr_ta::RpcInfo::V3(kmr_ta::RpcInfoV3 { |
| author_name: "Google", |
| unique_id: "TEE KeyMint TA", |
| fused: false, |
| supported_num_of_keys_in_csr: kmr_wire::rpc::MINIMUM_SUPPORTED_KEYS_IN_CSR, |
| }), |
| kmr_common::crypto::Implementation { |
| rng: Box::new(crypto::rng::Rng), |
| clock: Some(Box::new(crypto::clock::Clock)), |
| compare: Box::new(crypto::eq::Eq), |
| aes: Box::new(crypto::aes::Aes), |
| des: Box::new(crypto::des::Des), |
| hmac: Box::new(crypto::mac::Hmac), |
| rsa: Box::new(crypto::rsa::Rsa), |
| ec: Box::new(crypto::ec::Ec), |
| ckdf: Box::new(crypto::mac::AesCmac), |
| hkdf: Box::new(crypto::mac::Hmac), |
| sha256: Box::new(crypto::sha::Sha256), |
| }, |
| kmr_ta::device::Implementation { |
| keys: Box::new(software::keys::Keys), |
| sign_info: Some(Box::new(software::attest::CertSignInfo::new())), |
| attest_ids: None, |
| sdd_mgr: Some(Box::new(storage::sdsm::SecureDeletionSecretManager::new())), |
| bootloader: Box::new(kmr_ta::device::BootloaderDone), |
| sk_wrapper: None, |
| tup: Box::new(kmr_ta::device::TrustedPresenceUnsupported), |
| legacy_key: None, |
| rpc: Box::new(software::rpc::RpcArtifacts::new( |
| kmr_ta::device::CsrSigningAlgorithm::EdDSA, |
| )), |
| }, |
| ); |
| Mutex::new(SingleThreadedTa(ta)) |
| }) |
| } |
| |
| // The maximum size an incoming TIPC message can be. |
| // |
| // This can be used to pre-allocate buffer space in order to ensure that your |
| // read buffer can always hold an incoming message. |
| const KEYMINT_MAX_BUFFER_LENGTH: usize = 4096; |
| |
| // The maximum size of a message's payload. A longer message is split up into |
| // multiple segments. |
| const KEYMINT_MAX_MESSAGE_CONTENT_SIZE: usize = 4000; |
| |
| // The Keymint TA session context. |
| // |
| // `ipc_resp` holds the last response of the ta that is to be transferred to the |
| // client upon request. It is a Vec of Vec because a longer message is broken |
| // into multiple sub-messages. |
| // |
| // `ipc_resp` is only used by command::READ and command::WRITE handlers. |
| struct SessionContext { |
| ipc_resp: Option<VecDeque<Vec<u8>>>, |
| } |
| |
| // This default trait is required by ta_* macros that sets up the optional |
| // session context parameter in ta_open_session, ta_close_session, and |
| // ta_invoke_command. |
| impl Default for SessionContext { |
| fn default() -> Self { |
| info!("Creating SessionContext..."); |
| Self { ipc_resp: None } |
| } |
| } |
| |
| #[ta_create] |
| fn create() -> Result<()> { |
| let config = optee_logger::OpteeLoggerConfig::default(); |
| optee_logger::init_with_config(config); |
| |
| info!("Hello from OP-TEE Keymint in Rust!"); |
| |
| Ok(()) |
| } |
| |
| #[ta_open_session] |
| fn open_session(_params: &mut Parameters, _sess_ctx: &mut SessionContext) -> Result<()> { |
| info!("Keymint TA connected"); |
| Ok(()) |
| } |
| |
| #[ta_close_session] |
| fn close_session(_sess_ctx: &mut SessionContext) { |
| info!("Keymint TA disconnected"); |
| } |
| |
| #[ta_destroy] |
| fn destroy() { |
| info!("Keymint TA unloading... "); |
| } |
| |
| #[ta_invoke_command] |
| fn invoke_command( |
| sess_ctx: &mut SessionContext, |
| cmd_id: u32, |
| params: &mut Parameters, |
| ) -> Result<()> { |
| match cmd_id { |
| command::GET_AUTH_TOKEN_KEY => handle_authtoken_key_request(sess_ctx, params), |
| command::WRITE => handle_ipc_request(sess_ctx, params), |
| command::READ => handle_ipc_response(sess_ctx, params), |
| #[cfg(feature = "dev")] |
| command::RUN_TEST => { |
| crypto::tests::run(); |
| storage::sdsm::tests::run(); |
| Ok(()) |
| } |
| #[cfg(feature = "dev")] |
| command::KILL => { |
| info!("Killing the TA as requested..."); |
| panic!("TA killed"); |
| } |
| _ => Err(Error::new(ErrorKind::NotSupported)), |
| } |
| } |
| |
| fn handle_ipc_request(keymint_ctx: &mut SessionContext, params: &mut Parameters) -> Result<()> { |
| if keymint_ctx.ipc_resp.is_some() { |
| // The response of the previous IPC has not been cleared. |
| return Err(Error::new(ErrorKind::Busy)); |
| } |
| |
| if params.0.raw().is_null() { |
| return Err(Error::new(ErrorKind::BadParameters)); |
| } |
| // SAFETY: as_memref() dereferences the raw TEE_Param pointer, which is non-null. |
| let mut p = unsafe { params.0.as_memref()? }; |
| |
| // Check MemrefInput here because as_memref() does not differentiate |
| // between input and output. |
| if let ParamType::MemrefInput = p.param_type() { |
| let req = p.buffer(); |
| // Deny requests that are too big. |
| if req.len() > KEYMINT_MAX_BUFFER_LENGTH { |
| return Err(Error::new(ErrorKind::Overflow)); |
| } |
| let resp = keymint_ta().lock().0.process(req); |
| keymint_ctx.ipc_resp = Some(VecDeque::from( |
| kmr_ta::split_rsp(resp.as_slice(), KEYMINT_MAX_MESSAGE_CONTENT_SIZE) |
| .map_err(|_| Error::new(ErrorKind::Generic))?, |
| )); |
| Ok(()) |
| } else { |
| Err(Error::new(ErrorKind::BadParameters)) |
| } |
| } |
| |
| fn handle_ipc_response(keymint_ctx: &mut SessionContext, params: &mut Parameters) -> Result<()> { |
| // No outstanding response. |
| if keymint_ctx.ipc_resp.is_none() { |
| return Err(Error::new(ErrorKind::NoData)); |
| } |
| |
| if params.0.raw().is_null() { |
| return Err(Error::new(ErrorKind::BadParameters)); |
| } |
| // SAFETY: as_memref() dereferences the raw TEE_Param pointer, which is non-null. |
| let mut p = unsafe { params.0.as_memref()? }; |
| |
| // Check MemrefOutput here because as_memref() does not differentiate |
| // between input and output. |
| if let ParamType::MemrefOutput = p.param_type() { |
| // Take all packets out. Unwrap() never panics. |
| let mut packets = keymint_ctx.ipc_resp.take().unwrap(); |
| // Send only the packet at the front. Unwrap() never panics. |
| let packet = packets.pop_front().unwrap(); |
| if packet.len() > p.buffer().len() { |
| return Err(Error::new(ErrorKind::ShortBuffer)); |
| } |
| p.set_updated_size(packet.len()); |
| p.buffer().copy_from_slice(packet.as_slice()); |
| if packets.len() > 0 { |
| // Put any remaining packets back for the next request. |
| keymint_ctx.ipc_resp = Some(packets); |
| } |
| Ok(()) |
| } else { |
| Err(Error::new(ErrorKind::BadParameters)) |
| } |
| } |
| |
| fn client_is_gatekeeper_ta() -> bool { |
| let gatekeeper_uuid = Uuid::parse_str(invoke::TA_GATEKEEPER_UUID).unwrap(); |
| |
| match ClientId::detect() { |
| ClientId::TA(uuid) => uuid == gatekeeper_uuid, |
| _ => false, |
| } |
| } |
| |
| fn handle_authtoken_key_request( |
| _keymint_ctx: &mut SessionContext, |
| params: &mut Parameters, |
| ) -> Result<()> { |
| if !client_is_gatekeeper_ta() { |
| return Err(Error::new(ErrorKind::AccessDenied)); |
| } |
| |
| if params.1.raw().is_null() { |
| return Err(Error::new(ErrorKind::BadParameters)); |
| } |
| // SAFETY: as_memref() dereferences the raw TEE_Param pointer, which is non-null. |
| let mut p = unsafe { params.1.as_memref()? }; |
| |
| // Check MemrefOutput here because as_memref() does not differentiate |
| // between input and output. |
| if let ParamType::MemrefOutput = p.param_type() { |
| let hmac_key = match software::keys::AuthTokenHmac::try_new() { |
| Ok(hmac) => hmac.get_hmac_key().unwrap(), |
| _ => return Err(Error::new(ErrorKind::Generic)), |
| }; |
| |
| if hmac_key.0.len() > p.buffer().len() { |
| return Err(Error::new(ErrorKind::ShortBuffer)); |
| } |
| |
| p.set_updated_size(hmac_key.0.len()); |
| p.buffer().copy_from_slice(hmac_key.0.as_slice()); |
| Ok(()) |
| } else { |
| Err(Error::new(ErrorKind::BadParameters)) |
| } |
| } |