oasis_core_runtime/enclave_rpc/
session.rs

1//! Secure channel session.
2use std::{collections::HashSet, io::Write, mem, sync::Arc};
3
4use anyhow::Result;
5use thiserror::Error;
6
7use super::types::Message;
8use crate::{
9    common::{
10        crypto::signature::{self, PublicKey, Signature, Signer},
11        namespace::Namespace,
12        sgx::{ias, EnclaveIdentity, Quote, QuotePolicy},
13    },
14    consensus::{
15        registry::{EndorsedCapabilityTEE, VerifiedAttestation, VerifiedEndorsedCapabilityTEE},
16        state::registry::ImmutableState as RegistryState,
17        verifier::Verifier,
18    },
19    identity::Identity,
20};
21
22/// Noise protocol pattern.
23const NOISE_PATTERN: &str = "Noise_XX_25519_ChaChaPoly_SHA256";
24/// RAK signature session binding context.
25const RAK_SESSION_BINDING_CONTEXT: [u8; 8] = *b"EkRakRpc";
26
27/// Session-related error.
28#[derive(Error, Debug)]
29enum SessionError {
30    #[error("invalid input")]
31    InvalidInput,
32    #[error("invalid state")]
33    InvalidState,
34    #[error("session closed")]
35    Closed,
36    #[error("mismatched enclave identity")]
37    MismatchedEnclaveIdentity,
38    #[error("missing quote policy")]
39    MissingQuotePolicy,
40    #[error("remote node not set")]
41    NodeNotSet,
42    #[error("remote node already set")]
43    NodeAlreadySet,
44    #[error("remote node not registered")]
45    NodeNotRegistered,
46    #[error("RAK not published in the consensus layer")]
47    RAKNotFound,
48    #[error("runtime id not set")]
49    RuntimeNotSet,
50}
51
52/// Information about a session.
53pub struct SessionInfo {
54    /// RAK binding.
55    pub rak_binding: RAKBinding,
56    /// Verified TEE remote attestation.
57    pub verified_attestation: VerifiedAttestation,
58    /// Identifier of the node that endorsed the TEE.
59    pub endorsed_by: Option<PublicKey>,
60}
61
62enum State {
63    Handshake1(snow::HandshakeState),
64    Handshake2(snow::HandshakeState),
65    Transport(snow::TransportState),
66    UnauthenticatedTransport(snow::TransportState),
67    Closed,
68}
69
70/// An encrypted and authenticated RPC session.
71pub struct Session {
72    cfg: Config,
73    local_static_pub: Vec<u8>,
74    remote_node: Option<signature::PublicKey>,
75    info: Option<Arc<SessionInfo>>,
76    state: State,
77    buf: Vec<u8>,
78}
79
80impl Session {
81    fn new(handshake_state: snow::HandshakeState, local_static_pub: Vec<u8>, cfg: Config) -> Self {
82        Self {
83            cfg,
84            local_static_pub,
85            remote_node: None,
86            info: None,
87            state: State::Handshake1(handshake_state),
88            buf: vec![0u8; 65535],
89        }
90    }
91
92    /// Process incoming data.
93    ///
94    /// In case the session is in transport mode the returned result will
95    /// contained a parsed message. The `writer` will be used in case any
96    /// protocol replies need to be generated.
97    pub async fn process_data<W: Write>(
98        &mut self,
99        data: &[u8],
100        mut writer: W,
101    ) -> Result<Option<Message>> {
102        // Replace the state with a closed state. In case processing fails for whatever
103        // reason, this will cause the session to be torn down.
104        match mem::replace(&mut self.state, State::Closed) {
105            State::Handshake1(mut state) => {
106                if state.is_initiator() {
107                    // Initiator only sends in this state.
108                    if !data.is_empty() {
109                        return Err(SessionError::InvalidInput.into());
110                    }
111
112                    // -> e
113                    let len = state.write_message(&[], &mut self.buf)?;
114                    writer.write_all(&self.buf[..len])?;
115                } else {
116                    // <- e
117                    state.read_message(data, &mut self.buf)?;
118
119                    // -> e, ee, s, es
120                    let len = state.write_message(&self.get_rak_binding(), &mut self.buf)?;
121                    writer.write_all(&self.buf[..len])?;
122                }
123
124                self.state = State::Handshake2(state);
125            }
126            State::Handshake2(mut state) => {
127                // Process data sent during Handshake1 phase.
128                let len = state.read_message(data, &mut self.buf)?;
129                let remote_static = state
130                    .get_remote_static()
131                    .expect("dh exchange just happened");
132                let auth_info = self
133                    .verify_rak_binding(&self.buf[..len], remote_static)
134                    .await;
135
136                if state.is_initiator() {
137                    // -> s, se
138                    let len = state.write_message(&self.get_rak_binding(), &mut self.buf)?;
139                    writer.write_all(&self.buf[..len])?;
140                }
141
142                match auth_info {
143                    Ok(auth_info) => {
144                        self.info = auth_info;
145                        self.state = State::Transport(state.into_transport_mode()?);
146                    }
147                    Err(_) if state.is_initiator() => {
148                        // There was an error authenticating the session and we are the initiator.
149                        // Transition into unauthenticated transport state so we can notify the
150                        // other side of the close.
151                        self.state = State::UnauthenticatedTransport(state.into_transport_mode()?);
152                    }
153                    Err(err) => {
154                        // There was an authentication error and we are not the initiator, abort.
155                        return Err(err);
156                    }
157                }
158            }
159            State::Transport(mut state) => {
160                // TODO: Restore session in case of errors.
161                let len = state.read_message(data, &mut self.buf)?;
162                let msg = cbor::from_slice(&self.buf[..len])?;
163
164                self.state = State::Transport(state);
165                return Ok(Some(msg));
166            }
167            State::Closed | State::UnauthenticatedTransport(_) => {
168                return Err(SessionError::Closed.into());
169            }
170        }
171
172        Ok(None)
173    }
174
175    /// Write message to session.
176    ///
177    /// The `writer` will be used for protocol message output which should
178    /// be transmitted to the remote session counterpart.
179    pub fn write_message<W: Write>(&mut self, msg: Message, mut writer: W) -> Result<()> {
180        let state = match self.state {
181            State::Transport(ref mut state) => state,
182            State::UnauthenticatedTransport(ref mut state) if matches!(msg, Message::Close) => {
183                state
184            }
185            _ => return Err(SessionError::InvalidState.into()),
186        };
187
188        let len = state.write_message(&cbor::to_vec(msg), &mut self.buf)?;
189        writer.write_all(&self.buf[..len])?;
190
191        Ok(())
192    }
193
194    /// Mark the session as closed.
195    ///
196    /// After the session is closed it can no longer be used to transmit
197    /// or receive messages and any such use will result in an error.
198    pub fn close(&mut self) {
199        self.state = State::Closed;
200    }
201
202    fn get_rak_binding(&self) -> Vec<u8> {
203        match self.cfg.identity {
204            Some(ref identity) => {
205                if identity.quote().is_none() {
206                    return vec![];
207                }
208
209                let binding = identity
210                    .sign(&RAK_SESSION_BINDING_CONTEXT, &self.local_static_pub)
211                    .unwrap();
212
213                if self.cfg.use_endorsement {
214                    // Use endorsed TEE capability when available.
215                    if let Some(ect) = identity.endorsed_capability_tee() {
216                        return cbor::to_vec(RAKBinding::V2 { ect, binding });
217                    }
218                }
219
220                // Use the local RAK and quote.
221                let rak_pub = identity.public_rak();
222                let quote = identity.quote().expect("quote is configured");
223
224                cbor::to_vec(RAKBinding::V1 {
225                    rak_pub,
226                    binding,
227                    quote: (*quote).clone(),
228                })
229            }
230            None => vec![],
231        }
232    }
233
234    async fn verify_rak_binding(
235        &self,
236        rak_binding: &[u8],
237        remote_static: &[u8],
238    ) -> Result<Option<Arc<SessionInfo>>> {
239        if rak_binding.is_empty() {
240            // If enclave identity verification is required and no RAK binding
241            // has been provided, we must abort the session.
242            if self.cfg.remote_enclaves.is_some() {
243                return Err(SessionError::MismatchedEnclaveIdentity.into());
244            }
245            return Ok(None);
246        }
247
248        let policy = self
249            .cfg
250            .policy
251            .as_ref()
252            .ok_or(SessionError::MissingQuotePolicy)?;
253
254        let rak_binding: RAKBinding = cbor::from_slice(rak_binding)?;
255        let vect = rak_binding.verify(remote_static, &self.cfg.remote_enclaves, policy)?;
256
257        // Verify node identity if verification is enabled.
258        if self.cfg.consensus_verifier.is_some() {
259            let rak = rak_binding.rak_pub();
260            self.verify_node_identity(rak).await?;
261        }
262
263        Ok(Some(Arc::new(SessionInfo {
264            rak_binding,
265            verified_attestation: vect.verified_attestation,
266            endorsed_by: vect.node_id,
267        })))
268    }
269
270    /// Session information.
271    pub fn session_info(&self) -> Option<Arc<SessionInfo>> {
272        self.info.clone()
273    }
274
275    /// Whether the session handshake has completed and the session
276    /// is in transport mode.
277    pub fn is_connected(&self) -> bool {
278        matches!(self.state, State::Transport(_))
279    }
280
281    /// Whether the session is connected to one of the given nodes.
282    pub fn is_connected_to(&self, nodes: &Vec<signature::PublicKey>) -> bool {
283        nodes.iter().any(|&node| Some(node) == self.remote_node)
284    }
285
286    /// Whether the session is in closed state.
287    pub fn is_closed(&self) -> bool {
288        matches!(self.state, State::Closed)
289    }
290
291    /// Whether the session is in unauthenticated transport state. In this state the session can
292    /// only be used to transmit a close notification.
293    pub fn is_unauthenticated(&self) -> bool {
294        matches!(self.state, State::UnauthenticatedTransport(_))
295    }
296
297    /// Return remote node identifier.
298    pub fn get_remote_node(&self) -> Result<signature::PublicKey> {
299        self.remote_node.ok_or(SessionError::NodeNotSet.into())
300    }
301
302    /// Set the remote node identifier.
303    pub fn set_remote_node(&mut self, node: signature::PublicKey) -> Result<()> {
304        if self.remote_node.is_some() {
305            return Err(SessionError::NodeAlreadySet.into());
306        }
307        self.remote_node = Some(node);
308        Ok(())
309    }
310
311    /// Verify the identity of the remote node by comparing the given RAK with the trusted RAK
312    /// obtained from the consensus layer registry service.
313    async fn verify_node_identity(&self, rak: signature::PublicKey) -> Result<()> {
314        let consensus_verifier = self
315            .cfg
316            .consensus_verifier
317            .as_ref()
318            .expect("consensus verifier should be set");
319        let runtime_id = self
320            .cfg
321            .remote_runtime_id
322            .ok_or(SessionError::RuntimeNotSet)?;
323        let node = self.remote_node.ok_or(SessionError::NodeNotSet)?;
324
325        let consensus_state = consensus_verifier.latest_state().await?;
326        // TODO: Make this access async.
327        let node = tokio::task::block_in_place(move || -> Result<_> {
328            let registry_state = RegistryState::new(&consensus_state);
329            Ok(registry_state
330                .node(&node)?
331                .ok_or(SessionError::NodeNotRegistered)?)
332        })?;
333
334        let verified = node
335            .runtimes
336            .unwrap_or_default()
337            .iter()
338            .filter(|rt| rt.id == runtime_id)
339            .flat_map(|rt| &rt.capabilities.tee)
340            .any(|tee| tee.rak == rak);
341
342        if !verified {
343            return Err(SessionError::RAKNotFound.into());
344        }
345        Ok(())
346    }
347}
348
349/// Binding of the session's static public key to a remote attestation
350/// verification report through the use of the remote attestation key.
351///
352/// The signature chain is as follows:
353///
354/// * `avr` contains the remote attestation verification report which
355///   binds RAK to the remote attestation.
356/// * `rak_pub` contains the public part of RAK.
357/// * `binding` is signed by `rak_pub` and binds the session's static
358///   public key to RAK.
359#[derive(Clone, Debug, cbor::Encode, cbor::Decode)]
360#[cbor(tag = "v")]
361pub enum RAKBinding {
362    /// Old V0 format that only supported IAS quotes.
363    #[cbor(rename = 0, missing)]
364    V0 {
365        rak_pub: PublicKey,
366        binding: Signature,
367        avr: ias::AVR,
368    },
369
370    /// New V1 format that supports both IAS and PCS quotes.
371    #[cbor(rename = 1)]
372    V1 {
373        rak_pub: PublicKey,
374        binding: Signature,
375        quote: Quote,
376    },
377
378    /// V2 format which supports endorsed CapabilityTEE structures.
379    #[cbor(rename = 2)]
380    V2 {
381        ect: EndorsedCapabilityTEE,
382        binding: Signature,
383    },
384}
385
386impl RAKBinding {
387    /// Public part of the RAK.
388    pub fn rak_pub(&self) -> PublicKey {
389        match self {
390            Self::V0 { rak_pub, .. } => *rak_pub,
391            Self::V1 { rak_pub, .. } => *rak_pub,
392            Self::V2 { ect, .. } => ect.capability_tee.rak,
393        }
394    }
395
396    /// Signature from RAK, binding the session's static public key to RAK.
397    fn binding(&self) -> Signature {
398        match self {
399            Self::V0 { binding, .. } => *binding,
400            Self::V1 { binding, .. } => *binding,
401            Self::V2 { binding, .. } => *binding,
402        }
403    }
404
405    /// Verify the RAK binding.
406    pub fn verify(
407        &self,
408        remote_static: &[u8],
409        remote_enclaves: &Option<HashSet<EnclaveIdentity>>,
410        policy: &QuotePolicy,
411    ) -> Result<VerifiedEndorsedCapabilityTEE> {
412        let vect = self.verify_inner(policy)?;
413
414        // Ensure that the report data includes the hash of the node's RAK.
415        // NOTE: For V2 this check is part of verify_inner so it is not really needed.
416        Identity::verify_binding(&vect.verified_attestation.quote, &self.rak_pub())?;
417
418        // Verify MRENCLAVE/MRSIGNER.
419        if let Some(ref remote_enclaves) = remote_enclaves {
420            if !remote_enclaves.contains(&vect.verified_attestation.quote.identity) {
421                return Err(SessionError::MismatchedEnclaveIdentity.into());
422            }
423        }
424
425        // Verify remote static key binding.
426        self.binding()
427            .verify(&self.rak_pub(), &RAK_SESSION_BINDING_CONTEXT, remote_static)?;
428
429        Ok(vect)
430    }
431
432    fn verify_inner(&self, policy: &QuotePolicy) -> Result<VerifiedEndorsedCapabilityTEE> {
433        match self {
434            Self::V0 { ref avr, .. } => {
435                ias::verify(avr, &policy.ias.clone().unwrap_or_default()).map(|vq| vq.into())
436            }
437            Self::V1 { ref quote, .. } => quote.verify(policy).map(|vq| vq.into()),
438            Self::V2 { ref ect, .. } => ect.verify(policy),
439        }
440    }
441}
442
443/// Session configuration.
444#[derive(Clone, Default)]
445struct Config {
446    consensus_verifier: Option<Arc<dyn Verifier>>,
447    identity: Option<Arc<Identity>>,
448    remote_enclaves: Option<HashSet<EnclaveIdentity>>,
449    remote_runtime_id: Option<Namespace>,
450    use_endorsement: bool,
451    policy: Option<Arc<QuotePolicy>>,
452}
453
454/// Session builder.
455#[derive(Clone, Default)]
456pub struct Builder {
457    cfg: Config,
458}
459
460impl Builder {
461    /// Return remote enclave identities if configured in the builder.
462    pub fn get_remote_enclaves(&self) -> &Option<HashSet<EnclaveIdentity>> {
463        &self.cfg.remote_enclaves
464    }
465
466    /// Enable remote enclave identity verification.
467    pub fn remote_enclaves(mut self, enclaves: Option<HashSet<EnclaveIdentity>>) -> Self {
468        self.cfg.remote_enclaves = enclaves;
469        self
470    }
471
472    /// Return remote runtime ID if configured in the builder.
473    pub fn get_remote_runtime_id(&self) -> &Option<Namespace> {
474        &self.cfg.remote_runtime_id
475    }
476
477    /// Set remote runtime ID for node identity verification.
478    pub fn remote_runtime_id(mut self, id: Option<Namespace>) -> Self {
479        self.cfg.remote_runtime_id = id;
480        self
481    }
482
483    /// Enable remote node identity verification.
484    pub fn consensus_verifier(mut self, verifier: Option<Arc<dyn Verifier>>) -> Self {
485        self.cfg.consensus_verifier = verifier;
486        self
487    }
488
489    /// Return quote policy if configured in the builder.
490    pub fn get_quote_policy(&self) -> &Option<Arc<QuotePolicy>> {
491        &self.cfg.policy
492    }
493
494    /// Configure quote policy used for remote quote verification.
495    pub fn quote_policy(mut self, policy: Option<Arc<QuotePolicy>>) -> Self {
496        self.cfg.policy = policy;
497        self
498    }
499
500    /// Use endorsement from host node when establishing sessions.
501    pub fn use_endorsement(mut self, use_endorsement: bool) -> Self {
502        self.cfg.use_endorsement = use_endorsement;
503        self
504    }
505
506    /// Return the local identity if configured in the builder.
507    pub fn get_local_identity(&self) -> &Option<Arc<Identity>> {
508        &self.cfg.identity
509    }
510
511    /// Enable RAK binding.
512    pub fn local_identity(mut self, identity: Arc<Identity>) -> Self {
513        self.cfg.identity = Some(identity);
514        self
515    }
516
517    fn build<'a>(self) -> (snow::Builder<'a>, snow::Keypair, Config) {
518        let noise_builder = snow::Builder::new(NOISE_PATTERN.parse().unwrap());
519        let keypair = noise_builder.generate_keypair().unwrap();
520        let cfg = self.cfg;
521
522        (noise_builder, keypair, cfg)
523    }
524
525    /// Build initiator session.
526    pub fn build_initiator(self) -> Session {
527        let (builder, keypair, cfg) = self.build();
528        let session = builder
529            .local_private_key(&keypair.private)
530            .build_initiator()
531            .unwrap();
532        Session::new(session, keypair.public, cfg)
533    }
534
535    /// Build responder session.
536    pub fn build_responder(self) -> Session {
537        let (builder, keypair, cfg) = self.build();
538        let session = builder
539            .local_private_key(&keypair.private)
540            .build_responder()
541            .unwrap();
542        Session::new(session, keypair.public, cfg)
543    }
544}