Snap for 11216811 from 45d1b2ecfc3708f47b2ef8bfd41317f3cf551ed2 to 24Q1-release

Change-Id: I807817bfc16bf4d87b65dfe2c9cd7fbc036903bb
diff --git a/comm/InternalHalTaMessages.cddl b/comm/InternalHalTaMessages.cddl
new file mode 100644
index 0000000..7225382
--- /dev/null
+++ b/comm/InternalHalTaMessages.cddl
@@ -0,0 +1,25 @@
+; Clients of the HAL service call various methods on the HAL service; these
+; method invocations are internally translated into request messages
+; encoded as a CBOR 2-array holding:
+; - An integer indicating which request is present.
+; - An inner object holding the content for that specific request.
+PerformOpReq =
+  [SecretManagementOpCode, ProtectedRequestPacket] /
+  [DeleteIdsOpCode, [*SecretId]] /
+  [DeleteAllOpCode, nil]
+
+SecretManagementOpCode = 0x10
+DeleteIdsOpCode = 0x11
+DeleteAllOpCode = 0x12
+
+; Internally, a HAL request corresponds to a response message encoded as a CBOR 2-array holding:
+; - An integer return code value.
+; - One of:
+;    - If the return code is zero: a result value.
+;    - If the return code is non-zero: an error message.
+PerformOpResponse =
+    [0, PerformOpSuccessRsp] /
+    [error_code: int .ne 0, error_message: tstr]
+
+; The content of an OK response depends on the corresponding `HalRequest` message
+PerformOpSuccessRsp = ProtectedResponsePacket / nil
diff --git a/comm/src/lib.rs b/comm/src/lib.rs
index 9a10ac0..b1d0893 100644
--- a/comm/src/lib.rs
+++ b/comm/src/lib.rs
@@ -22,3 +22,4 @@
 
 mod cbor_convert;
 pub mod data_types;
+pub mod wire;
diff --git a/comm/src/wire.rs b/comm/src/wire.rs
new file mode 100644
index 0000000..3559f6e
--- /dev/null
+++ b/comm/src/wire.rs
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+//! Rust types used for CBOR-encoded communication between HAL and TA,
+//! corresponding to the schema in `comm/InternalHalTaMessages.cddl`.
+
+#![allow(missing_docs)] // needed for `enumn::N`, sadly
+
+use alloc::{string::String, vec, vec::Vec};
+use ciborium::value::Value;
+use coset::{AsCborValue, CborSerializable, CoseError};
+use enumn::N;
+
+/// Wrapper type for communicating requests between the HAL service and the TA.
+/// This is an internal implementation detail, and is not visible on the API.
+#[derive(Debug, Clone)]
+pub enum PerformOpReq {
+    /// A secret management request holds a CBOR-encoded `COSE_Encrypt0`.
+    SecretManagement(Vec<u8>),
+
+    /// A (plaintext) request to delete some `SecretId`s.
+    DeleteIds(Vec<SecretId>),
+
+    /// A (plaintext) request to delete all data.
+    DeleteAll,
+}
+
+impl PerformOpReq {
+    pub fn code(&self) -> OpCode {
+        match self {
+            Self::SecretManagement(_) => OpCode::SecretManagement,
+            Self::DeleteIds(_) => OpCode::DeleteIds,
+            Self::DeleteAll => OpCode::DeleteAll,
+        }
+    }
+}
+
+impl AsCborValue for PerformOpReq {
+    fn to_cbor_value(self) -> Result<Value, CoseError> {
+        Ok(Value::Array(match self {
+            Self::SecretManagement(encrypt0) => {
+                vec![OpCode::SecretManagement.to_cbor_value()?, Value::Bytes(encrypt0)]
+            }
+            Self::DeleteIds(ids) => {
+                vec![
+                    OpCode::DeleteIds.to_cbor_value()?,
+                    Value::Array(
+                        ids.into_iter().map(|id| Value::Bytes(id.to_vec())).collect::<Vec<Value>>(),
+                    ),
+                ]
+            }
+            Self::DeleteAll => vec![OpCode::DeleteAll.to_cbor_value()?, Value::Null],
+        }))
+    }
+
+    fn from_cbor_value(value: Value) -> Result<Self, CoseError> {
+        let mut a = match value {
+            Value::Array(a) if a.len() == 2 => a,
+            _ => return cbor_type_error(&value, "arr len 2"),
+        };
+        let val = a.remove(1);
+        let code = OpCode::from_cbor_value(a.remove(0))?;
+        Ok(match code {
+            OpCode::SecretManagement => Self::SecretManagement(match val {
+                Value::Bytes(b) => b,
+                _ => return cbor_type_error(&val, "bstr"),
+            }),
+            OpCode::DeleteIds => {
+                let ids = match &val {
+                    Value::Array(a) => a,
+                    _ => return cbor_type_error(&val, "arr"),
+                };
+                let ids = ids
+                    .iter()
+                    .map(|id| match &id {
+                        Value::Bytes(b) => SecretId::try_from(b.as_slice())
+                            .map_err(|_e| CoseError::OutOfRangeIntegerValue),
+                        _ => cbor_type_error(&val, "bstr"),
+                    })
+                    .collect::<Result<Vec<_>, _>>()?;
+                Self::DeleteIds(ids)
+            }
+            OpCode::DeleteAll => {
+                if !val.is_null() {
+                    return cbor_type_error(&val, "nil");
+                }
+                Self::DeleteAll
+            }
+        })
+    }
+}
+
+impl CborSerializable for PerformOpReq {}
+
+/// Op code value to distinguish requests.
+#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash, N)]
+pub enum OpCode {
+    SecretManagement = 0x10,
+    DeleteIds = 0x11,
+    DeleteAll = 0x12,
+}
+
+impl AsCborValue for OpCode {
+    fn to_cbor_value(self) -> Result<Value, CoseError> {
+        Ok(Value::Integer((self as i32).into()))
+    }
+
+    fn from_cbor_value(value: Value) -> Result<Self, CoseError> {
+        let i = match value {
+            Value::Integer(i) => i,
+            _ => return cbor_type_error(&value, "int"),
+        };
+        let code: i32 = i.try_into().map_err(|_| CoseError::OutOfRangeIntegerValue)?;
+        OpCode::n(code).ok_or(CoseError::OutOfRangeIntegerValue)
+    }
+}
+
+/// Wrapper type for communicating responses between the HAL service and the TA.
+/// This is an internal implementation detail, and is not visible on the API.
+#[derive(Debug, Clone)]
+pub enum PerformOpResponse {
+    Success(PerformOpSuccessRsp),
+    Failure(ApiError),
+}
+
+impl PerformOpResponse {
+    pub fn err_code(&self) -> AidlErrorCode {
+        match self {
+            Self::Success(_) => AidlErrorCode::Ok,
+            Self::Failure(err) => err.err_code,
+        }
+    }
+}
+
+impl AsCborValue for PerformOpResponse {
+    fn to_cbor_value(self) -> Result<Value, CoseError> {
+        Ok(match self {
+            Self::Success(rsp) => {
+                Value::Array(vec![Value::Integer(0.into()), rsp.to_cbor_value()?])
+            }
+            Self::Failure(err) => Value::Array(vec![
+                Value::Integer((err.err_code as i32).into()),
+                Value::Text(err.msg),
+            ]),
+        })
+    }
+
+    fn from_cbor_value(value: Value) -> Result<Self, CoseError> {
+        let mut a = match value {
+            Value::Array(a) if a.len() == 2 => a,
+            _ => return cbor_type_error(&value, "arr len 2"),
+        };
+        let val = a.remove(1);
+        let err_code =
+            a.remove(0).as_integer().ok_or(CoseError::UnexpectedItem("non-int", "int"))?;
+        let err_code: i32 = err_code.try_into().map_err(|_| CoseError::OutOfRangeIntegerValue)?;
+        Ok(match err_code {
+            0 => Self::Success(PerformOpSuccessRsp::from_cbor_value(val)?),
+            err_code => {
+                let msg = match val {
+                    Value::Text(t) => t,
+                    _ => return cbor_type_error(&val, "tstr"),
+                };
+                let err_code = AidlErrorCode::n(err_code).unwrap_or(AidlErrorCode::InternalError);
+                Self::Failure(ApiError { err_code, msg })
+            }
+        })
+    }
+}
+
+impl CborSerializable for PerformOpResponse {}
+
+/// Inner response type holding the result of a successful request.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum PerformOpSuccessRsp {
+    ProtectedResponse(Vec<u8>),
+    Empty,
+}
+
+impl AsCborValue for PerformOpSuccessRsp {
+    fn to_cbor_value(self) -> Result<Value, CoseError> {
+        Ok(match self {
+            Self::ProtectedResponse(data) => Value::Bytes(data),
+            Self::Empty => Value::Null,
+        })
+    }
+
+    fn from_cbor_value(value: Value) -> Result<Self, CoseError> {
+        match value {
+            Value::Bytes(data) => Ok(Self::ProtectedResponse(data)),
+            Value::Null => Ok(Self::Empty),
+            _ => cbor_type_error(&value, "bstr/nil"),
+        }
+    }
+}
+
+impl CborSerializable for PerformOpSuccessRsp {}
+
+/// Return an error indicating that an unexpected CBOR type was encountered.
+pub fn cbor_type_error<T>(got: &Value, want: &'static str) -> Result<T, CoseError> {
+    let got = match got {
+        Value::Integer(_) => "int",
+        Value::Bytes(_) => "bstr",
+        Value::Text(_) => "tstr",
+        Value::Array(_) => "array",
+        Value::Map(_) => "map",
+        Value::Tag(_, _) => "tag",
+        Value::Float(_) => "float",
+        Value::Bool(_) => "bool",
+        Value::Null => "null",
+        _ => "unknown",
+    };
+    Err(CoseError::UnexpectedItem(got, want))
+}
+
+/// Identifier for a secret
+pub type SecretId = [u8; 64];
+
+/// Error information reported visibly on the external API.
+#[derive(Debug, Clone)]
+pub struct ApiError {
+    pub err_code: AidlErrorCode,
+    pub msg: String,
+}
+
+/// Error codes emitted as service specific errors at the HAL.
+/// Keep in sync with:
+/// hardware/interfaces/security/secretkeeper/aidl/
+///   android/hardware/security/secretkeeper/AidlErrorCode.aidl
+#[derive(Debug, Clone, Copy, N)]
+pub enum AidlErrorCode {
+    Ok = 0,
+    UnknownKeyId = 1,
+    InternalError = 2,
+    RequestMalformed = 3,
+}
diff --git a/core/src/cipher.rs b/core/src/cipher.rs
new file mode 100644
index 0000000..fb7eaab
--- /dev/null
+++ b/core/src/cipher.rs
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+//! Utilities for encryption and decryption.
+
+use crate::aidl_err;
+use alloc::vec::Vec;
+use authgraph_core::key::{AesKey, Nonce12};
+use authgraph_core::traits::{AesGcm, Rng};
+use coset::{iana, CborSerializable, CoseEncrypt0, CoseEncrypt0Builder, HeaderBuilder};
+use secretkeeper_comm::wire::{AidlErrorCode, ApiError};
+
+/// Decrypt a message.
+pub fn decrypt_message(
+    aes_gcm: &dyn AesGcm,
+    key: &AesKey,
+    encrypt0: &CoseEncrypt0,
+) -> Result<Vec<u8>, ApiError> {
+    let nonce12 = Nonce12::try_from(&encrypt0.unprotected.iv[..])
+        .map_err(|e| aidl_err!(InternalError, "nonce of unexpected size: {e:?}"))?;
+    encrypt0
+        .decrypt(&[], |ct, aad| aes_gcm.decrypt(key, ct, aad, &nonce12))
+        .map_err(|e| aidl_err!(InternalError, "failed to decrypt message: {e:?}"))
+}
+
+/// Encrypt a message.
+pub fn encrypt_message(
+    aes_gcm: &dyn AesGcm,
+    rng: &dyn Rng,
+    key: &AesKey,
+    session_id: &[u8],
+    msg: &[u8],
+) -> Result<Vec<u8>, ApiError> {
+    let nonce12 = Nonce12::new(rng);
+    let encrypt0 = CoseEncrypt0Builder::new()
+        .protected(
+            HeaderBuilder::new()
+                .algorithm(iana::Algorithm::A256GCM)
+                .key_id(session_id.to_vec())
+                .build(),
+        )
+        .unprotected(HeaderBuilder::new().iv(nonce12.0.to_vec()).build())
+        .try_create_ciphertext(msg, &[], |plaintext, aad| {
+            aes_gcm.encrypt(key, plaintext, aad, &nonce12)
+        })
+        .map_err(|e| aidl_err!(InternalError, "failed to encrypt message: {e:?}"))?
+        .build();
+    encrypt0
+        .to_vec()
+        .map_err(|e| aidl_err!(InternalError, "failed to serialize CoseEncrypt0: {e:?}"))
+}
diff --git a/core/src/lib.rs b/core/src/lib.rs
index 8868eed..99f7da1 100644
--- a/core/src/lib.rs
+++ b/core/src/lib.rs
@@ -20,5 +20,6 @@
 #![no_std]
 extern crate alloc;
 
+pub mod cipher;
 pub mod store;
 pub mod ta;
diff --git a/core/src/ta.rs b/core/src/ta.rs
index 2e30aa4..01161d9 100644
--- a/core/src/ta.rs
+++ b/core/src/ta.rs
@@ -16,9 +16,11 @@
 
 //! Implementation of a SecretKeeper trusted application (TA).
 
+use crate::cipher;
 use crate::store::{KeyValueStore, PolicyGatedStorage};
 use alloc::boxed::Box;
 use alloc::collections::VecDeque;
+use alloc::string::ToString;
 use alloc::vec::Vec;
 use authgraph_core::ag_err;
 use authgraph_core::error::Error;
@@ -28,8 +30,8 @@
 };
 use authgraph_core::traits::{AesGcm, CryptoTraitImpl, Device, EcDsa, Rng, Sha256};
 use authgraph_wire::{ErrorCode, SESSION_ID_LEN};
-use coset::{iana, CborSerializable};
-use log::{error, warn};
+use coset::{iana, CborSerializable, CoseEncrypt0};
+use log::{error, trace, warn};
 use secretkeeper_comm::data_types::{
     error::Error as SkInternalError,
     error::SecretkeeperError,
@@ -41,6 +43,9 @@
     },
     response::Response,
 };
+use secretkeeper_comm::wire::{
+    AidlErrorCode, ApiError, PerformOpReq, PerformOpResponse, PerformOpSuccessRsp, SecretId,
+};
 
 /// Current Secretkeeper version.
 const CURRENT_VERSION: u64 = 1;
@@ -61,13 +66,24 @@
     0x65, 0x66,
 ];
 
+/// Macro to build an [`ApiError`] instance.
+/// E.g. use: `aidl_err!(InternalError, "some {} format", arg)`.
+#[macro_export]
+macro_rules! aidl_err {
+    { $error_code:ident, $($arg:tt)+ } => {
+        ApiError {
+            err_code: AidlErrorCode::$error_code,
+            msg: alloc::format!("{}:{}: {}", file!(), line!(), format_args!($($arg)+))
+        }
+    };
+}
+
 /// Secretkeeper trusted application instance.
 pub struct SecretkeeperTa {
     /// AES-GCM implementation.
     aes_gcm: Box<dyn AesGcm>,
 
     /// RNG implementation.
-    #[allow(dead_code)] // TODO: remove this
     rng: Box<dyn Rng>,
 
     /// AuthGraph per-boot-key.
@@ -129,19 +145,72 @@
         })
     }
 
-    /// Process a single serialized request, returning a serialized response.
-    pub fn process(&mut self, req: &[u8]) -> Vec<u8> {
-        // TODO(b/291224769) The request will need to be decrypted & response need to be encrypted
-        // with key & related artifacts pre-shared via Authgraph Key Exchange HAL.
-        self.process_inner(req).unwrap_or_else(
-            // SecretkeeperError is also a valid 'Response', serialize to a response packet.
-            |sk_err| {
-                Response::serialize_to_packet(&sk_err)
-                    .to_vec()
-                    .expect("Panicking due to serialization failing")
-            },
-        )
+    /// Process a single serialized request, returning a serialized response (even on failure).
+    pub fn process(&mut self, req_data: &[u8]) -> Vec<u8> {
+        let (req_code, rsp) = match PerformOpReq::from_slice(req_data) {
+            Ok(req) => {
+                trace!("-> TA: received request {:?}", req.code());
+                (Some(req.code()), self.process_req(req))
+            }
+            Err(e) => {
+                error!("failed to decode CBOR request: {:?}", e);
+                (None, PerformOpResponse::Failure(aidl_err!(InternalError, "CBOR decode failure")))
+            }
+        };
+        trace!("<- TA: send response for {:?} rc {:?}", req_code, rsp.err_code());
+        rsp.to_vec().unwrap_or_else(|e| {
+            error!("failed to encode CBOR response: {:?}", e);
+            invalid_cbor_rsp_data().to_vec()
+        })
     }
+
+    /// Process a single request, returning a [`PerformOpResponse`].
+    fn process_req(&mut self, req: PerformOpReq) -> PerformOpResponse {
+        let code = req.code();
+        let result = match req {
+            PerformOpReq::SecretManagement(encrypt0) => {
+                self.secret_management(&encrypt0).map(PerformOpSuccessRsp::ProtectedResponse)
+            }
+            PerformOpReq::DeleteIds(ids) => {
+                self.delete_ids(ids).map(|_| PerformOpSuccessRsp::Empty)
+            }
+            PerformOpReq::DeleteAll => self.delete_all().map(|_| PerformOpSuccessRsp::Empty),
+        };
+        match result {
+            Ok(rsp) => PerformOpResponse::Success(rsp),
+            Err(err) => {
+                warn!("failing {:?} request: {:?}", code, err);
+                PerformOpResponse::Failure(err)
+            }
+        }
+    }
+
+    fn delete_ids(&mut self, ids: Vec<SecretId>) -> Result<(), ApiError> {
+        Err(ApiError {
+            err_code: AidlErrorCode::InternalError,
+            msg: alloc::format!("TODO with {ids:?}"),
+        })
+    }
+
+    fn delete_all(&mut self) -> Result<(), ApiError> {
+        Err(ApiError { err_code: AidlErrorCode::InternalError, msg: "TODO".to_string() })
+    }
+
+    fn secret_management(&mut self, encrypt0: &[u8]) -> Result<Vec<u8>, ApiError> {
+        let (req, session_keys) = self.decrypt_request(encrypt0)?;
+        let result = self.process_inner(&req);
+
+        // An inner failure still converts to a response message that gets encrypted.
+        let rsp_data = match result {
+            Ok(data) => data,
+            Err(e) => e
+                .serialize_to_packet()
+                .to_vec()
+                .map_err(|_e| aidl_err!(InternalError, "failed to encode err rsp"))?,
+        };
+        self.encrypt_response(&session_keys, &rsp_data)
+    }
+
     fn process_inner(&mut self, req: &[u8]) -> Result<Vec<u8>, SecretkeeperError> {
         let req_packet = RequestPacket::from_slice(req).map_err(|e| {
             error!("Failed to get Request packet from bytes: {e:?}");
@@ -154,7 +223,6 @@
                 Opcode::GetSecret => self.get_secret(req_packet)?,
                 _ => panic!("Unknown operation.."),
             };
-
         rsp_packet.to_vec().map_err(|_| SecretkeeperError::UnexpectedServerError)
     }
 
@@ -199,10 +267,34 @@
         Ok(response.serialize_to_packet())
     }
 
-    #[allow(dead_code)] // TODO: remove this
+    // "SSL added and removed here :-)"
     fn keys_for_session(&self, session_id: &[u8; SESSION_ID_LEN]) -> Option<&SessionKeyInfo> {
         self.session_keys.iter().find(|info| info.session_id == *session_id)
     }
+    fn decrypt_request(&self, req: &[u8]) -> Result<(Vec<u8>, SessionKeyInfo), ApiError> {
+        let encrypt0 = CoseEncrypt0::from_slice(req)
+            .map_err(|_e| aidl_err!(RequestMalformed, "malformed COSE_Encrypt0"))?;
+        let session_keys = self
+            .keys_for_session(&encrypt0.protected.header.key_id[..].try_into().map_err(|e| {
+                aidl_err!(RequestMalformed, "session key of unexpected size: {e:?}")
+            })?)
+            .ok_or_else(|| aidl_err!(UnknownKeyId, "session key not found"))?;
+        let payload = cipher::decrypt_message(&*self.aes_gcm, &session_keys.recv_key, &encrypt0)?;
+        Ok((payload, session_keys.clone()))
+    }
+    fn encrypt_response(
+        &self,
+        session_keys: &SessionKeyInfo,
+        rsp: &[u8],
+    ) -> Result<Vec<u8>, ApiError> {
+        cipher::encrypt_message(
+            &*self.aes_gcm,
+            &*self.rng,
+            &session_keys.send_key,
+            &session_keys.session_id,
+            rsp,
+        )
+    }
 }
 
 impl Device for SecretkeeperTa {
@@ -283,10 +375,22 @@
     }
 }
 
-#[allow(dead_code)] // TODO: remove this
+#[derive(Clone)]
 struct SessionKeyInfo {
     _peer_identity: Identity,
     session_id: [u8; 32],
     send_key: AesKey,
     recv_key: AesKey,
 }
+
+/// Hand-encoded [`PerformOpResponse`] data for [`AidlErrorCode::InternalError`].
+/// Does not perform CBOR serialization (and so is suitable for error reporting if/when
+/// CBOR serialization fails).
+// TODO: add a unit test that confirms that this hand-encoded data is correct
+fn invalid_cbor_rsp_data() -> [u8; 3] {
+    [
+        0x82, // 2-arr
+        0x02, // int, value 2
+        0x60, // 0-tstr
+    ]
+}
diff --git a/hal/Android.bp b/hal/Android.bp
index 74b1d8a..f72db6b 100644
--- a/hal/Android.bp
+++ b/hal/Android.bp
@@ -28,6 +28,8 @@
     rustlibs: [
         "libauthgraph_hal",
         "libbinder_rs",
+        "libcoset",
         "liblog_rust",
+        "libsecretkeeper_comm_nostd",
     ],
 }
diff --git a/hal/src/lib.rs b/hal/src/lib.rs
index 9ea4eab..ee12e20 100644
--- a/hal/src/lib.rs
+++ b/hal/src/lib.rs
@@ -24,6 +24,9 @@
 };
 use authgraph_hal::channel::SerializedChannel;
 use authgraph_hal::service::AuthGraphService;
+use coset::CborSerializable;
+use secretkeeper_comm::wire::{PerformOpSuccessRsp, PerformOpResponse, ApiError, PerformOpReq, AidlErrorCode, SecretId};
+use std::ffi::CString;
 
 /// Implementation of the Secretkeeper HAL service, communicating with a TA
 /// in a secure environment via a communication channel.
@@ -55,10 +58,80 @@
 /// Implement the `ISecretkeeper` interface.
 impl<T: SerializedChannel> ISecretkeeper for SecretkeeperService<T> {
     fn processSecretManagementRequest(&self, req: &[u8]) -> binder::Result<Vec<u8>> {
-        // Pass the request to the channel, and read the response.
-        self.channel.execute(req)
+        let wrapper = PerformOpReq::SecretManagement(req.to_vec());
+        let wrapper_data = wrapper.to_vec().map_err(failed_cbor)?;
+        let rsp_data = self.channel.execute(&wrapper_data)?;
+        let rsp = PerformOpResponse::from_slice(&rsp_data).map_err(failed_cbor)?;
+        match rsp {
+            PerformOpResponse::Success(PerformOpSuccessRsp::ProtectedResponse(data)) => Ok(data),
+            PerformOpResponse::Success(_) => Err(unexpected_response_type()),
+            PerformOpResponse::Failure(err) => Err(service_specific_error(err)),
+        }
     }
     fn getAuthGraphKe(&self) -> binder::Result<binder::Strong<dyn IAuthGraphKeyExchange>> {
         Ok(self.authgraph.clone())
     }
 }
+
+// TODO: fold these into `impl ISecretkeeper` above.
+#[allow(non_snake_case)]
+impl<T: SerializedChannel> SecretkeeperService<T> {
+    /// Delete some secrets.
+    pub fn deleteById(&self, ids: &[Vec<u8>]) -> binder::Result<()> {
+        let ids: Vec<SecretId> = ids
+            .iter()
+            .map(|id| SecretId::try_from(id.as_slice()))
+            .collect::<Result<Vec<_>, _>>()
+            .map_err(|_e| {
+                binder::Status::new_exception(
+                    binder::ExceptionCode::ILLEGAL_ARGUMENT,
+                    Some(&std::ffi::CString::new("secret ID of wrong length".to_string()).unwrap()),
+                )
+            })?;
+        let wrapper = PerformOpReq::DeleteIds(ids);
+        let wrapper_data = wrapper.to_vec().map_err(failed_cbor)?;
+        let rsp_data = self.channel.execute(&wrapper_data)?;
+        let rsp = PerformOpResponse::from_slice(&rsp_data).map_err(failed_cbor)?;
+        match rsp {
+            PerformOpResponse::Success(PerformOpSuccessRsp::Empty) => Ok(()),
+            PerformOpResponse::Success(_) => Err(unexpected_response_type()),
+            PerformOpResponse::Failure(err) => Err(service_specific_error(err)),
+        }
+    }
+    /// No more secrets.
+    pub fn deleteAll(&self) -> binder::Result<()> {
+        let wrapper = PerformOpReq::DeleteAll;
+        let wrapper_data = wrapper.to_vec().map_err(failed_cbor)?;
+        let rsp_data = self.channel.execute(&wrapper_data)?;
+        let rsp = PerformOpResponse::from_slice(&rsp_data).map_err(failed_cbor)?;
+        match rsp {
+            PerformOpResponse::Success(PerformOpSuccessRsp::Empty) => Ok(()),
+            PerformOpResponse::Success(_) => Err(unexpected_response_type()),
+            PerformOpResponse::Failure(err) => Err(service_specific_error(err)),
+        }
+    }
+}
+
+/// Emit a failure for a failed CBOR conversion.
+fn failed_cbor(err: coset::CoseError) -> binder::Status {
+    binder::Status::new_exception(
+        binder::ExceptionCode::BAD_PARCELABLE,
+        Some(&std::ffi::CString::new(format!("CBOR conversion failed: {err:?}")).unwrap()),
+    )
+}
+
+/// Emit a service specific error.
+fn service_specific_error(err: ApiError) -> binder::Status {
+    binder::Status::new_service_specific_error(
+        err.err_code as i32,
+        Some(&CString::new(err.msg).unwrap()),
+    )
+}
+
+/// Emit an error indicating an unexpected type of response received from the TA.
+fn unexpected_response_type() -> binder::Status {
+    service_specific_error(ApiError {
+        err_code: AidlErrorCode::InternalError,
+        msg: "unexpected response type".to_string(),
+    })
+}