blob: de8bc6b339bd1fbc97778d758313b23099c67585 [file] [log] [blame]
/*
* 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.
*/
//! 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::vec::Vec;
use authgraph_core::ag_err;
use authgraph_core::error::Error;
use authgraph_core::key::{
AesKey, CertChain, EcSignKey, Identity, IdentityVerificationDecision,
EXPLICIT_KEY_DICE_CERT_CHAIN_VERSION, IDENTITY_VERSION,
};
use authgraph_core::traits::{AesGcm, CryptoTraitImpl, Device, EcDsa, Rng, Sha256};
use authgraph_wire::{ErrorCode, SESSION_ID_LEN};
use coset::{iana, CborSerializable, CoseEncrypt0};
use log::{error, trace, warn};
use secretkeeper_comm::data_types::{
error::Error as SkInternalError,
error::SecretkeeperError,
packet::{RequestPacket, ResponsePacket},
request::Request,
request_response_impl::{
GetSecretRequest, GetSecretResponse, GetVersionRequest, GetVersionResponse, Opcode,
StoreSecretRequest, StoreSecretResponse,
},
response::Response,
Id, SeqNum,
};
use secretkeeper_comm::wire::{
AidlErrorCode, ApiError, PerformOpReq, PerformOpResponse, PerformOpSuccessRsp, SecretId,
};
pub mod bootloader;
/// Current Secretkeeper version.
const CURRENT_VERSION: u64 = 1;
/// Default maximum number of live session keys.
const MAX_SESSIONS_DEFAULT: usize = 4;
/// 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.
rng: Box<dyn Rng>,
/// AuthGraph per-boot-key.
per_boot_key: AesKey,
/// The signing key corresponding to the leaf of the Secretkeeper identity DICE chain.
identity_sign_key: EcSignKey,
/// Secretkeeper identity.
identity: Identity,
/// Current sessions.
session_artifacts: VecDeque<SessionArtifacts>,
/// Maximum number of current sessions.
max_sessions: usize,
/// Storage of secrets (& sealing policy)
store: PolicyGatedStorage,
}
impl SecretkeeperTa {
/// Create a TA instance using the provided trait implementations.
pub fn new(
ag_impls: &mut CryptoTraitImpl,
storage_impl: Box<dyn KeyValueStore>,
identity_curve: iana::EllipticCurve,
) -> Result<Self, SkInternalError> {
Self::new_with_session_limit(ag_impls, storage_impl, identity_curve, MAX_SESSIONS_DEFAULT)
}
/// Create a TA instance using the provided trait implementations.
pub fn new_with_session_limit(
ag_impls: &mut CryptoTraitImpl,
storage_impl: Box<dyn KeyValueStore>,
identity_curve: iana::EllipticCurve,
max_sessions: usize,
) -> Result<Self, SkInternalError> {
// Create a per-boot-key for AuthGraph to use.
let aes_gcm = ag_impls.aes_gcm.box_clone();
let rng = ag_impls.rng.box_clone();
let per_boot_key = aes_gcm.generate_key(&mut *ag_impls.rng).map_err(|e| {
error!("Failed to generate per-boot key: {e:?}");
SkInternalError::UnexpectedError
})?;
let (identity_sign_key, mut pub_key) =
ag_impls.ecdsa.generate_key(identity_curve).map_err(|e| {
error!("Failed to generate {identity_curve:?} keypair: {e:?}");
SkInternalError::UnexpectedError
})?;
pub_key.canonicalize_cose_key();
let identity = Identity {
version: IDENTITY_VERSION,
cert_chain: CertChain {
version: EXPLICIT_KEY_DICE_CERT_CHAIN_VERSION,
root_key: pub_key,
dice_cert_chain: None,
},
policy: None,
};
let store = PolicyGatedStorage::init(storage_impl);
Ok(Self {
aes_gcm,
rng,
per_boot_key,
identity_sign_key,
identity,
max_sessions,
session_artifacts: VecDeque::new(),
store,
})
}
/// Process a single serialized request from the bootloader, returning a serialized response
/// (even on failure).
pub fn process_bootloader(&self, req_data: &[u8]) -> Vec<u8> {
use bootloader as bl;
let req = match bl::Request::from_slice(req_data) {
Ok(req) => req,
Err(e) => {
error!("Failed to deserialize bootloader request: {e:?}");
return bl::Response::Error(bl::OpCode::Unknown, bl::ErrorCode::CborFailure)
.into_vec();
}
};
let rsp = match req {
bl::Request::GetIdentityKey => {
let cose_key = self.identity.cert_chain.root_key.clone().get_key();
let cose_data = match cose_key.to_vec() {
Ok(data) => data,
Err(e) => {
error!("Failed to serialize COSE key: {e:?}");
return bl::Response::Error(
bl::OpCode::GetIdentityKey,
bl::ErrorCode::CborFailure,
)
.into_vec();
}
};
bl::Response::IdentityKey(cose_data)
}
};
rsp.into_vec()
}
/// 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> {
for id in ids {
self.store
.delete(&Id(id))
.map_err(|e| aidl_err!(InternalError, "failed to delete id: {e:?}"))?;
}
Ok(())
}
fn delete_all(&mut self) -> Result<(), ApiError> {
warn!("deleting all Secretkeeper secrets");
self.store.delete_all().map_err(|e| aidl_err!(InternalError, "failed to delete all: {e:?}"))
}
// "SSL added and removed here :-)"
fn secret_management(&mut self, encrypt0: &[u8]) -> Result<Vec<u8>, ApiError> {
let encrypt0 = CoseEncrypt0::from_slice(encrypt0)
.map_err(|_e| aidl_err!(RequestMalformed, "malformed COSE_Encrypt0"))?;
let kid: &[u8; SESSION_ID_LEN] = key_id(&encrypt0)
.try_into()
.map_err(|e| aidl_err!(RequestMalformed, "session key of unexpected size: {e:?}"))?;
let peer_cert_chain = self.peer_cert_chain(kid)?;
let req = self.decrypt_request(&encrypt0, kid)?;
let result = self.process_inner(&req, &peer_cert_chain);
// 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(&rsp_data, kid)
}
fn peer_cert_chain(&self, kid: &[u8; SESSION_ID_LEN]) -> Result<Vec<u8>, ApiError> {
let cert_chain = self
.session_artifacts
.iter()
.find_map(|info| {
if info.session_keys.session_id == *kid {
Some(info.session_keys.peer_cert_chain.clone())
} else {
None
}
})
.ok_or_else(|| aidl_err!(UnknownKeyId, "session info not found"))?;
Ok(cert_chain)
}
fn process_inner(
&mut self,
req: &[u8],
peer_cert_chain: &[u8],
) -> Result<Vec<u8>, SecretkeeperError> {
let req_packet = RequestPacket::from_slice(req).map_err(|e| {
error!("Failed to get Request packet from bytes: {e:?}");
SecretkeeperError::RequestMalformed
})?;
let rsp_packet =
match req_packet.opcode().map_err(|_| SecretkeeperError::RequestMalformed)? {
Opcode::GetVersion => Self::get_version(req_packet)?,
Opcode::StoreSecret => self.store_secret(req_packet, peer_cert_chain)?,
Opcode::GetSecret => self.get_secret(req_packet, peer_cert_chain)?,
_ => panic!("Unknown operation.."),
};
rsp_packet.to_vec().map_err(|_| SecretkeeperError::UnexpectedServerError)
}
fn get_version(req: RequestPacket) -> Result<ResponsePacket, SecretkeeperError> {
// Deserialization really just verifies the structural integrity of the request such
// as args being empty.
let _req = GetVersionRequest::deserialize_from_packet(req)
.map_err(|_| SecretkeeperError::RequestMalformed)?;
let rsp = GetVersionResponse { version: CURRENT_VERSION };
Ok(rsp.serialize_to_packet())
}
fn store_secret(
&mut self,
request: RequestPacket,
peer_cert_chain: &[u8],
) -> Result<ResponsePacket, SecretkeeperError> {
let request = StoreSecretRequest::deserialize_from_packet(request)
.map_err(|_| SecretkeeperError::RequestMalformed)?;
self.store.store(request.id, request.secret, request.sealing_policy, peer_cert_chain)?;
let response = StoreSecretResponse {};
Ok(response.serialize_to_packet())
}
fn get_secret(
&mut self,
request: RequestPacket,
peer_cert_chain: &[u8],
) -> Result<ResponsePacket, SecretkeeperError> {
let request = GetSecretRequest::deserialize_from_packet(request)
.map_err(|_| SecretkeeperError::RequestMalformed)?;
let secret =
self.store.get(&request.id, peer_cert_chain, request.updated_sealing_policy)?;
let response = GetSecretResponse { secret };
Ok(response.serialize_to_packet())
}
fn keys_for_session(
&mut self,
session_id: &[u8; SESSION_ID_LEN],
) -> Option<&mut SessionArtifacts> {
self.session_artifacts.iter_mut().find(|info| info.session_keys.session_id == *session_id)
}
fn decrypt_request(
&mut self,
encrypt0: &CoseEncrypt0,
kid: &[u8; SESSION_ID_LEN],
) -> Result<Vec<u8>, ApiError> {
let session_artifacts = self
.keys_for_session(kid)
.ok_or_else(|| aidl_err!(UnknownKeyId, "session info not found"))?;
let seq_num_incoming =
session_artifacts.seq_num_incoming.get_then_increment().map_err(|e| {
aidl_err!(InternalError, "Couldn't get the expected request seq_num: {e:?}")
})?;
let recv_key: &AesKey = &session_artifacts.session_keys.recv_key.clone();
cipher::decrypt_message(self.aes_gcm.as_ref(), recv_key, encrypt0, &seq_num_incoming)
}
fn encrypt_response(
&mut self,
rsp: &[u8],
kid: &[u8; SESSION_ID_LEN],
) -> Result<Vec<u8>, ApiError> {
let session_artifacts = self
.keys_for_session(kid)
.ok_or_else(|| aidl_err!(UnknownKeyId, "session info not found"))?;
let seq_num_outgoing = session_artifacts
.seq_num_outgoing
.get_then_increment()
.map_err(|e| aidl_err!(InternalError, "Couldn't get a seq number: {e:?}"))?;
let send_key: &AesKey = &session_artifacts.session_keys.send_key.clone();
cipher::encrypt_message(&*self.aes_gcm, &*self.rng, send_key, kid, rsp, &seq_num_outgoing)
}
}
impl Device for SecretkeeperTa {
fn get_or_create_per_boot_key(&self, _: &dyn AesGcm, _: &mut dyn Rng) -> Result<AesKey, Error> {
self.get_per_boot_key()
}
fn get_per_boot_key(&self) -> Result<AesKey, Error> {
Ok(self.per_boot_key.clone())
}
fn get_identity(&self) -> Result<(Option<EcSignKey>, Identity), Error> {
Ok((Some(self.identity_sign_key.clone()), self.identity.clone()))
}
fn get_cose_sign_algorithm(&self) -> Result<iana::Algorithm, Error> {
match &self.identity_sign_key {
EcSignKey::Ed25519(_) => Ok(iana::Algorithm::EdDSA),
EcSignKey::P256(_) => Ok(iana::Algorithm::ES256),
EcSignKey::P384(_) => Ok(iana::Algorithm::ES384),
}
}
fn sign_data(&self, _ecdsa: &dyn EcDsa, _data: &[u8]) -> Result<Vec<u8>, Error> {
// `get_identity()` returns a key, so signing should be handled elsewhere.
Err(ag_err!(Unimplemented, "unexpected signing request"))
}
fn evaluate_identity(
&self,
_curr: &Identity,
_prev: &Identity,
) -> Result<IdentityVerificationDecision, Error> {
Err(ag_err!(Unimplemented, "not yet required"))
}
fn record_shared_sessions(
&mut self,
peer_identity: &Identity,
session_id: &[u8; 32],
shared_keys: &[Vec<u8>; 2],
_sha256: &dyn Sha256,
) -> Result<(), Error> {
if self.session_artifacts.len() >= self.max_sessions {
warn!("Dropping oldest session key");
self.session_artifacts.pop_front();
}
let send_key =
authgraph_core::arc::decipher_arc(&self.per_boot_key, &shared_keys[0], &*self.aes_gcm)?;
let recv_key =
authgraph_core::arc::decipher_arc(&self.per_boot_key, &shared_keys[1], &*self.aes_gcm)?;
let peer_cert_chain = peer_identity
.cert_chain
.clone()
.to_vec()
.map_err(|e| ag_err!(InternalError, "Failed to encode peer's cert chain: {e:?}"))?;
// We assume that the session ID is unique and not already present in `session_keys`.
self.session_artifacts.push_back(SessionArtifacts {
session_keys: AgSessionKeys {
peer_cert_chain,
session_id: *session_id,
send_key: send_key.payload.try_into()?,
recv_key: recv_key.payload.try_into()?,
},
seq_num_outgoing: SeqNum::new(),
seq_num_incoming: SeqNum::new(),
});
Ok(())
}
fn validate_shared_sessions(
&self,
_peer_identity: &Identity,
_session_id: &[u8; 32],
_shared_keys: &[Vec<u8>],
_sha256: &dyn Sha256,
) -> Result<(), Error> {
// Secretkeeper does not need to validate shared session after successful Key Exchange.
Ok(())
}
}
fn key_id(encrypted_packet: &CoseEncrypt0) -> &[u8] {
&encrypted_packet.protected.header.key_id
}
// In addition to `session_keys` (received from authgraph key exchange protocol), Secretkeeper
// session maintains `sequence_numbers`. These change within session (for each message).
// Avoid cloning!
struct SessionArtifacts {
session_keys: AgSessionKeys,
// Sequence number to be used in next outgoing message encryption.
seq_num_outgoing: SeqNum,
// Sequence number to be used for decrypting the next incoming message.
seq_num_incoming: SeqNum,
}
struct AgSessionKeys {
peer_cert_chain: Vec<u8>,
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).
fn invalid_cbor_rsp_data() -> [u8; 3] {
[
0x82, // 2-arr
0x02, // int, value 2
0x60, // 0-tstr
]
}
#[cfg(test)]
mod tests {
use super::*;
use secretkeeper_comm::wire::{AidlErrorCode, ApiError, PerformOpResponse};
#[test]
fn test_invalid_data() {
// Cross-check that the hand-encoded invalid CBOR data matches an auto-encoded equivalent.
let rsp = PerformOpResponse::Failure(ApiError {
err_code: AidlErrorCode::InternalError,
msg: alloc::string::String::new(),
});
let rsp_data = rsp.to_vec().unwrap();
assert_eq!(rsp_data, invalid_cbor_rsp_data());
}
}