blob: ab990395f7683d05a01f47d06c7451fc3143ad96 [file] [edit]
// Copyright 2023 Google LLC
//
// 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.
//
////////////////////////////////////////////////////////////////////////////////
//! Implementation of a HAL service for AuthGraph.
use crate::{channel::SerializedChannel, errcode_to_binder, Innto, TryInnto};
use android_hardware_security_authgraph::aidl::android::hardware::security::authgraph::{
Arc::Arc, IAuthGraphKeyExchange::BnAuthGraphKeyExchange,
IAuthGraphKeyExchange::IAuthGraphKeyExchange, Identity::Identity, KeInitResult::KeInitResult,
Key::Key, PubKey::PubKey, SessionIdSignature::SessionIdSignature, SessionInfo::SessionInfo,
SessionInitiationInfo::SessionInitiationInfo,
};
use authgraph_wire as wire;
use log::{error, warn};
use std::ffi::CString;
use wire::{
cbor::{AsCborValue, CborError},
Code, ErrorCode, PerformOpReq, PerformOpResponse, PerformOpRsp,
};
/// Emit a failure for a failed CBOR conversion.
#[inline]
pub fn failed_cbor(err: CborError) -> binder::Status {
binder::Status::new_service_specific_error(
ErrorCode::InternalError as i32,
Some(&CString::new(format!("CBOR conversion failed: {:?}", err)).unwrap()),
)
}
/// Implementation of the AuthGraph key exchange HAL service, communicating with a TA
/// in a secure environment via a communication channel.
pub struct AuthGraphService<T: SerializedChannel + 'static> {
channel: T,
}
impl<T: SerializedChannel + 'static> AuthGraphService<T> {
/// Construct a new instance that uses the provided channel.
pub fn new(channel: T) -> Self {
Self { channel }
}
/// Create a new instance wrapped in a proxy object.
pub fn new_as_binder(channel: T) -> binder::Strong<dyn IAuthGraphKeyExchange> {
BnAuthGraphKeyExchange::new_binder(Self::new(channel), binder::BinderFeatures::default())
}
/// Execute the given request, by serializing it and sending it down the internal channel. Then
/// read and deserialize the response.
fn execute_req(&self, req: PerformOpReq) -> binder::Result<PerformOpRsp> {
let code = req.code();
let req_data = req.into_vec().map_err(failed_cbor)?;
if req_data.len() > T::MAX_SIZE {
error!(
"HAL operation {:?} encodes bigger {} than max size {}",
code,
req_data.len(),
T::MAX_SIZE
);
return Err(errcode_to_binder(ErrorCode::InternalError));
}
// Pass the request to the channel, and read the response.
let rsp_data = self.channel.execute(&req_data)?;
let outer_rsp = PerformOpResponse::from_slice(&rsp_data).map_err(failed_cbor)?;
if outer_rsp.error_code != ErrorCode::Ok {
warn!("HAL: command {:?} failed: {:?}", code, outer_rsp.error_code);
return Err(errcode_to_binder(outer_rsp.error_code));
}
// On an OK outer response, the inner response should be populated with a message that
// matches the request.
outer_rsp.rsp.ok_or_else(|| {
binder::Status::new_service_specific_error(
ErrorCode::InternalError as i32,
Some(&CString::new("missing inner rsp in OK outer rsp".to_string()).unwrap()),
)
})
}
}
/// Macro to extract a specific variant from a [`PerformOpRsp`].
macro_rules! require_op_rsp {
{ $rsp:expr => $variant:ident } => {
match $rsp {
PerformOpRsp::$variant(rsp) => Ok(rsp),
_ => Err(failed_cbor(CborError::UnexpectedItem("wrong rsp code", "rsp code"))),
}
}
}
impl<T: SerializedChannel> binder::Interface for AuthGraphService<T> {}
/// Implement the `IAuthGraphKeyExchange` interface by translating incoming method invocations
/// into request messages, which are passed to the TA. The corresponding response from the TA
/// is then parsed and the return values extracted.
impl<T: SerializedChannel> IAuthGraphKeyExchange for AuthGraphService<T> {
fn create(&self) -> binder::Result<SessionInitiationInfo> {
let req = PerformOpReq::Create(wire::CreateRequest {});
let wire::CreateResponse { ret } = require_op_rsp!(self.execute_req(req)? => Create)?;
Ok(ret.innto())
}
fn init(
&self,
peer_pub_key: &PubKey,
peer_id: &Identity,
peer_nonce: &[u8],
peer_version: i32,
) -> binder::Result<KeInitResult> {
let req = PerformOpReq::Init(wire::InitRequest {
peer_pub_key: unsigned_pub_key(peer_pub_key)?,
peer_id: peer_id.identity.to_vec(),
peer_nonce: peer_nonce.to_vec(),
peer_version,
});
let wire::InitResponse { ret } = require_op_rsp!(self.execute_req(req)? => Init)?;
Ok(ret.innto())
}
fn finish(
&self,
peer_pub_key: &PubKey,
peer_id: &Identity,
peer_signature: &SessionIdSignature,
peer_nonce: &[u8],
peer_version: i32,
own_key: &Key,
) -> binder::Result<SessionInfo> {
let req = PerformOpReq::Finish(wire::FinishRequest {
peer_pub_key: unsigned_pub_key(peer_pub_key)?,
peer_id: peer_id.identity.to_vec(),
peer_signature: peer_signature.signature.to_vec(),
peer_nonce: peer_nonce.to_vec(),
peer_version,
own_key: own_key.clone().try_innto()?,
});
let wire::FinishResponse { ret } = require_op_rsp!(self.execute_req(req)? => Finish)?;
Ok(ret.innto())
}
fn authenticationComplete(
&self,
peer_signature: &SessionIdSignature,
shared_keys: &[Arc; 2],
) -> binder::Result<[Arc; 2]> {
let req = PerformOpReq::AuthenticationComplete(wire::AuthenticationCompleteRequest {
peer_signature: peer_signature.signature.to_vec(),
shared_keys: [shared_keys[0].arc.to_vec(), shared_keys[1].arc.to_vec()],
});
let wire::AuthenticationCompleteResponse { ret } =
require_op_rsp!(self.execute_req(req)? => AuthenticationComplete)?;
let [arc0, arc1] = ret;
Ok([Arc { arc: arc0 }, Arc { arc: arc1 }])
}
}
/// Extract (and require) an unsigned public key as bytes from a [`PubKey`].
fn unsigned_pub_key(pub_key: &PubKey) -> binder::Result<Vec<u8>> {
match pub_key {
PubKey::PlainKey(key) => Ok(key.plainPubKey.to_vec()),
PubKey::SignedKey(_) => Err(binder::Status::new_exception(
binder::ExceptionCode::ILLEGAL_ARGUMENT,
Some(&CString::new("expected unsigned public key").unwrap()),
)),
}
}