use std::{collections::HashSet, io::Write, mem, sync::Arc};
use anyhow::Result;
use thiserror::Error;
use super::types::Message;
use crate::{
common::{
crypto::signature::{self, PublicKey, Signature, Signer},
namespace::Namespace,
sgx::{ias, EnclaveIdentity, Quote, QuotePolicy},
},
consensus::{
registry::{EndorsedCapabilityTEE, VerifiedAttestation, VerifiedEndorsedCapabilityTEE},
state::registry::ImmutableState as RegistryState,
verifier::Verifier,
},
identity::Identity,
};
const NOISE_PATTERN: &str = "Noise_XX_25519_ChaChaPoly_SHA256";
const RAK_SESSION_BINDING_CONTEXT: [u8; 8] = *b"EkRakRpc";
#[derive(Error, Debug)]
enum SessionError {
#[error("invalid input")]
InvalidInput,
#[error("invalid state")]
InvalidState,
#[error("session closed")]
Closed,
#[error("mismatched enclave identity")]
MismatchedEnclaveIdentity,
#[error("missing quote policy")]
MissingQuotePolicy,
#[error("remote node not set")]
NodeNotSet,
#[error("remote node already set")]
NodeAlreadySet,
#[error("remote node not registered")]
NodeNotRegistered,
#[error("RAK not published in the consensus layer")]
RAKNotFound,
#[error("runtime id not set")]
RuntimeNotSet,
}
pub struct SessionInfo {
pub rak_binding: RAKBinding,
pub verified_attestation: VerifiedAttestation,
pub endorsed_by: Option<PublicKey>,
}
enum State {
Handshake1(snow::HandshakeState),
Handshake2(snow::HandshakeState),
Transport(snow::TransportState),
UnauthenticatedTransport(snow::TransportState),
Closed,
}
pub struct Session {
cfg: Config,
local_static_pub: Vec<u8>,
remote_node: Option<signature::PublicKey>,
info: Option<Arc<SessionInfo>>,
state: State,
buf: Vec<u8>,
}
impl Session {
fn new(handshake_state: snow::HandshakeState, local_static_pub: Vec<u8>, cfg: Config) -> Self {
Self {
cfg,
local_static_pub,
remote_node: None,
info: None,
state: State::Handshake1(handshake_state),
buf: vec![0u8; 65535],
}
}
pub async fn process_data<W: Write>(
&mut self,
data: &[u8],
mut writer: W,
) -> Result<Option<Message>> {
match mem::replace(&mut self.state, State::Closed) {
State::Handshake1(mut state) => {
if state.is_initiator() {
if !data.is_empty() {
return Err(SessionError::InvalidInput.into());
}
let len = state.write_message(&[], &mut self.buf)?;
writer.write_all(&self.buf[..len])?;
} else {
state.read_message(data, &mut self.buf)?;
let len = state.write_message(&self.get_rak_binding(), &mut self.buf)?;
writer.write_all(&self.buf[..len])?;
}
self.state = State::Handshake2(state);
}
State::Handshake2(mut state) => {
let len = state.read_message(data, &mut self.buf)?;
let remote_static = state
.get_remote_static()
.expect("dh exchange just happened");
let auth_info = self
.verify_rak_binding(&self.buf[..len], remote_static)
.await;
if state.is_initiator() {
let len = state.write_message(&self.get_rak_binding(), &mut self.buf)?;
writer.write_all(&self.buf[..len])?;
}
match auth_info {
Ok(auth_info) => {
self.info = auth_info;
self.state = State::Transport(state.into_transport_mode()?);
}
Err(_) if state.is_initiator() => {
self.state = State::UnauthenticatedTransport(state.into_transport_mode()?);
}
Err(err) => {
return Err(err);
}
}
}
State::Transport(mut state) => {
let len = state.read_message(data, &mut self.buf)?;
let msg = cbor::from_slice(&self.buf[..len])?;
self.state = State::Transport(state);
return Ok(Some(msg));
}
State::Closed | State::UnauthenticatedTransport(_) => {
return Err(SessionError::Closed.into());
}
}
Ok(None)
}
pub fn write_message<W: Write>(&mut self, msg: Message, mut writer: W) -> Result<()> {
let state = match self.state {
State::Transport(ref mut state) => state,
State::UnauthenticatedTransport(ref mut state) if matches!(msg, Message::Close) => {
state
}
_ => return Err(SessionError::InvalidState.into()),
};
let len = state.write_message(&cbor::to_vec(msg), &mut self.buf)?;
writer.write_all(&self.buf[..len])?;
Ok(())
}
pub fn close(&mut self) {
self.state = State::Closed;
}
fn get_rak_binding(&self) -> Vec<u8> {
match self.cfg.identity {
Some(ref identity) => {
if identity.quote().is_none() {
return vec![];
}
let binding = identity
.sign(&RAK_SESSION_BINDING_CONTEXT, &self.local_static_pub)
.unwrap();
if self.cfg.use_endorsement {
if let Some(ect) = identity.endorsed_capability_tee() {
return cbor::to_vec(RAKBinding::V2 { ect, binding });
}
}
let rak_pub = identity.public_rak();
let quote = identity.quote().expect("quote is configured");
cbor::to_vec(RAKBinding::V1 {
rak_pub,
binding,
quote: (*quote).clone(),
})
}
None => vec![],
}
}
async fn verify_rak_binding(
&self,
rak_binding: &[u8],
remote_static: &[u8],
) -> Result<Option<Arc<SessionInfo>>> {
if rak_binding.is_empty() {
if self.cfg.remote_enclaves.is_some() {
return Err(SessionError::MismatchedEnclaveIdentity.into());
}
return Ok(None);
}
let policy = self
.cfg
.policy
.as_ref()
.ok_or(SessionError::MissingQuotePolicy)?;
let rak_binding: RAKBinding = cbor::from_slice(rak_binding)?;
let vect = rak_binding.verify(remote_static, &self.cfg.remote_enclaves, policy)?;
if self.cfg.consensus_verifier.is_some() {
let rak = rak_binding.rak_pub();
self.verify_node_identity(rak).await?;
}
Ok(Some(Arc::new(SessionInfo {
rak_binding,
verified_attestation: vect.verified_attestation,
endorsed_by: vect.node_id,
})))
}
pub fn session_info(&self) -> Option<Arc<SessionInfo>> {
self.info.clone()
}
pub fn is_connected(&self) -> bool {
matches!(self.state, State::Transport(_))
}
pub fn is_connected_to(&self, nodes: &Vec<signature::PublicKey>) -> bool {
nodes.iter().any(|&node| Some(node) == self.remote_node)
}
pub fn is_closed(&self) -> bool {
matches!(self.state, State::Closed)
}
pub fn is_unauthenticated(&self) -> bool {
matches!(self.state, State::UnauthenticatedTransport(_))
}
pub fn get_remote_node(&self) -> Result<signature::PublicKey> {
self.remote_node.ok_or(SessionError::NodeNotSet.into())
}
pub fn set_remote_node(&mut self, node: signature::PublicKey) -> Result<()> {
if self.remote_node.is_some() {
return Err(SessionError::NodeAlreadySet.into());
}
self.remote_node = Some(node);
Ok(())
}
async fn verify_node_identity(&self, rak: signature::PublicKey) -> Result<()> {
let consensus_verifier = self
.cfg
.consensus_verifier
.as_ref()
.expect("consensus verifier should be set");
let runtime_id = self
.cfg
.remote_runtime_id
.ok_or(SessionError::RuntimeNotSet)?;
let node = self.remote_node.ok_or(SessionError::NodeNotSet)?;
let consensus_state = consensus_verifier.latest_state().await?;
let node = tokio::task::block_in_place(move || -> Result<_> {
let registry_state = RegistryState::new(&consensus_state);
Ok(registry_state
.node(&node)?
.ok_or(SessionError::NodeNotRegistered)?)
})?;
let verified = node
.runtimes
.unwrap_or_default()
.iter()
.filter(|rt| rt.id == runtime_id)
.flat_map(|rt| &rt.capabilities.tee)
.any(|tee| tee.rak == rak);
if !verified {
return Err(SessionError::RAKNotFound.into());
}
Ok(())
}
}
#[derive(Clone, Debug, cbor::Encode, cbor::Decode)]
#[cbor(tag = "v")]
pub enum RAKBinding {
#[cbor(rename = 0, missing)]
V0 {
rak_pub: PublicKey,
binding: Signature,
avr: ias::AVR,
},
#[cbor(rename = 1)]
V1 {
rak_pub: PublicKey,
binding: Signature,
quote: Quote,
},
#[cbor(rename = 2)]
V2 {
ect: EndorsedCapabilityTEE,
binding: Signature,
},
}
impl RAKBinding {
pub fn rak_pub(&self) -> PublicKey {
match self {
Self::V0 { rak_pub, .. } => *rak_pub,
Self::V1 { rak_pub, .. } => *rak_pub,
Self::V2 { ect, .. } => ect.capability_tee.rak,
}
}
fn binding(&self) -> Signature {
match self {
Self::V0 { binding, .. } => *binding,
Self::V1 { binding, .. } => *binding,
Self::V2 { binding, .. } => *binding,
}
}
pub fn verify(
&self,
remote_static: &[u8],
remote_enclaves: &Option<HashSet<EnclaveIdentity>>,
policy: &QuotePolicy,
) -> Result<VerifiedEndorsedCapabilityTEE> {
let vect = self.verify_inner(policy)?;
Identity::verify_binding(&vect.verified_attestation.quote, &self.rak_pub())?;
if let Some(ref remote_enclaves) = remote_enclaves {
if !remote_enclaves.contains(&vect.verified_attestation.quote.identity) {
return Err(SessionError::MismatchedEnclaveIdentity.into());
}
}
self.binding()
.verify(&self.rak_pub(), &RAK_SESSION_BINDING_CONTEXT, remote_static)?;
Ok(vect)
}
fn verify_inner(&self, policy: &QuotePolicy) -> Result<VerifiedEndorsedCapabilityTEE> {
match self {
Self::V0 { ref avr, .. } => {
ias::verify(avr, &policy.ias.clone().unwrap_or_default()).map(|vq| vq.into())
}
Self::V1 { ref quote, .. } => quote.verify(policy).map(|vq| vq.into()),
Self::V2 { ref ect, .. } => ect.verify(policy),
}
}
}
#[derive(Clone, Default)]
struct Config {
consensus_verifier: Option<Arc<dyn Verifier>>,
identity: Option<Arc<Identity>>,
remote_enclaves: Option<HashSet<EnclaveIdentity>>,
remote_runtime_id: Option<Namespace>,
use_endorsement: bool,
policy: Option<Arc<QuotePolicy>>,
}
#[derive(Clone, Default)]
pub struct Builder {
cfg: Config,
}
impl Builder {
pub fn get_remote_enclaves(&self) -> &Option<HashSet<EnclaveIdentity>> {
&self.cfg.remote_enclaves
}
pub fn remote_enclaves(mut self, enclaves: Option<HashSet<EnclaveIdentity>>) -> Self {
self.cfg.remote_enclaves = enclaves;
self
}
pub fn get_remote_runtime_id(&self) -> &Option<Namespace> {
&self.cfg.remote_runtime_id
}
pub fn remote_runtime_id(mut self, id: Option<Namespace>) -> Self {
self.cfg.remote_runtime_id = id;
self
}
pub fn consensus_verifier(mut self, verifier: Option<Arc<dyn Verifier>>) -> Self {
self.cfg.consensus_verifier = verifier;
self
}
pub fn get_quote_policy(&self) -> &Option<Arc<QuotePolicy>> {
&self.cfg.policy
}
pub fn quote_policy(mut self, policy: Option<Arc<QuotePolicy>>) -> Self {
self.cfg.policy = policy;
self
}
pub fn use_endorsement(mut self, use_endorsement: bool) -> Self {
self.cfg.use_endorsement = use_endorsement;
self
}
pub fn get_local_identity(&self) -> &Option<Arc<Identity>> {
&self.cfg.identity
}
pub fn local_identity(mut self, identity: Arc<Identity>) -> Self {
self.cfg.identity = Some(identity);
self
}
fn build<'a>(self) -> (snow::Builder<'a>, snow::Keypair, Config) {
let noise_builder = snow::Builder::new(NOISE_PATTERN.parse().unwrap());
let keypair = noise_builder.generate_keypair().unwrap();
let cfg = self.cfg;
(noise_builder, keypair, cfg)
}
pub fn build_initiator(self) -> Session {
let (builder, keypair, cfg) = self.build();
let session = builder
.local_private_key(&keypair.private)
.build_initiator()
.unwrap();
Session::new(session, keypair.public, cfg)
}
pub fn build_responder(self) -> Session {
let (builder, keypair, cfg) = self.build();
let session = builder
.local_private_key(&keypair.private)
.build_responder()
.unwrap();
Session::new(session, keypair.public, cfg)
}
}