diff --git a/crates/mpc-tls/src/client/conn.rs b/crates/mpc-tls/src/client/conn.rs deleted file mode 100644 index 3943820023..0000000000 --- a/crates/mpc-tls/src/client/conn.rs +++ /dev/null @@ -1,697 +0,0 @@ -use tracing::{debug, error, trace, warn}; -use crate::{ - MpcTlsLeader, - client::{error::Error, vecbuf::ChunkVecBuffer}, -}; -use async_trait::async_trait; -use std::{ - collections::VecDeque, - convert::TryFrom, - fmt, io, mem, - ops::{Deref, DerefMut}, -}; -use tls_core::{ - msgs::{ - alert::AlertMessagePayload, - base::Payload, - deframer::MessageDeframer, - enums::{AlertDescription, AlertLevel, ContentType, HandshakeType, ProtocolVersion}, - fragmenter::MessageFragmenter, - handshake::Random, - hsjoiner::HandshakeJoiner, - message::{Message, MessagePayload, OpaqueMessage, PlainMessage}, - }, - suites::SupportedCipherSuite, -}; - -/// Values of this structure are returned from -/// [`ClientConnection::process_new_packets`] and tell the caller the current I/O -/// state of the TLS connection. -#[derive(Debug, PartialEq)] -pub struct IoState { - tls_bytes_to_write: usize, - plaintext_bytes_to_read: usize, -} - -impl IoState { - /// How many bytes could be written by [`CommonState::write_tls`] if called - /// right now. A non-zero value implies [`CommonState::wants_write`]. - pub fn tls_bytes_to_write(&self) -> usize { - self.tls_bytes_to_write - } - - /// How many plaintext bytes could be obtained via - /// [`ClientConnection::read_plaintext`] without further I/O. - pub fn plaintext_bytes_to_read(&self) -> usize { - self.plaintext_bytes_to_read - } -} - -/// How many ChangeCipherSpec messages we accept and drop in TLS1.3 handshakes. -/// The spec says 1, but implementations (namely the boringssl test suite) get -/// this wrong. BoringSSL itself accepts up to 32. -static TLS13_MAX_DROPPED_CCS: u8 = 2u8; - -#[derive(Debug)] -pub(crate) struct ConnectionRandoms { - pub(crate) client: [u8; 32], - pub(crate) server: [u8; 32], -} - -impl ConnectionRandoms { - pub(crate) fn new(client: Random, server: Random) -> Self { - Self { - client: client.0, - server: server.0, - } - } -} - -fn is_valid_ccs(msg: &OpaqueMessage) -> bool { - // nb. this is prior to the record layer, so is unencrypted. see - // third paragraph of section 5 in RFC8446. - msg.typ == ContentType::ChangeCipherSpec && msg.payload.0 == [0x01] -} - -/// This represents a single TLS client connection. -pub struct ClientConnection { - state: Result, Error>, - common_state: CommonState, - message_deframer: MessageDeframer, - handshake_joiner: HandshakeJoiner, -} - -impl fmt::Debug for ClientConnection { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("ClientConnection").finish() - } -} - -impl ClientConnection { - pub(crate) fn new_inner(state: Box, common_state: CommonState) -> Self { - Self { - state: Ok(state), - common_state, - message_deframer: MessageDeframer::new(), - handshake_joiner: HandshakeJoiner::new(), - } - } - - /// Reads out any buffered plaintext received from the peer. Returns the - /// number of bytes read. - pub fn read_plaintext(&mut self, buf: &mut [u8]) -> io::Result { - self.common_state.received_plaintext.read(buf) - } - - /// Returns whether the MPC record layer has no buffered records. - pub fn is_empty(&self) -> bool { - self.common_state.backend.is_empty() - } - - /// Initiate the TLS protocol - pub async fn start(&mut self) -> Result<(), Error> { - let state = match mem::replace(&mut self.state, Err(Error::HandshakeNotComplete)) { - Ok(state) => state, - Err(e) => { - self.state = Err(e.clone()); - return Err(e); - } - }; - self.state = state.start(&mut self.common_state).await; - Ok(()) - } - - /// Signals that the server has closed the connection. - pub async fn server_closed(&mut self) -> Result<(), Error> { - self.common_state.backend.close_connection().await?; - Ok(()) - } - - async fn process_incoming_opaque( - &mut self, - msg: OpaqueMessage, - ) -> Result, Error> { - // Drop CCS messages during handshake in TLS1.3 - if msg.typ == ContentType::ChangeCipherSpec - && !self.common_state.may_receive_application_data - && self.common_state.is_tls13() - { - if !is_valid_ccs(&msg) - || self.common_state.received_middlebox_ccs > TLS13_MAX_DROPPED_CCS - { - // "An implementation which receives any other change_cipher_spec value or - // which receives a protected change_cipher_spec record MUST abort the - // handshake with an "unexpected_message" alert." - self.common_state - .send_fatal_alert(AlertDescription::UnexpectedMessage) - .await?; - return Err(Error::PeerMisbehavedError( - "illegal middlebox CCS received".into(), - )); - } else { - self.common_state.received_middlebox_ccs += 1; - trace!("Dropping CCS"); - return Ok(None); - } - } - - // Decrypt if demanded by current state. - if self.common_state.decrypting { - self.common_state.decrypt_incoming(msg).await?; - - Ok(None) - } else { - Ok(Some(msg.into_plain_message())) - } - } - - async fn process_incoming_plain( - &mut self, - msg: PlainMessage, - state: Box, - ) -> Result, Error> { - // For handshake messages, we need to join them before parsing - // and processing. - if self.handshake_joiner.want_message(&msg) { - match self.handshake_joiner.take_message(msg) { - Some(_) => {} - None => { - self.common_state - .send_fatal_alert(AlertDescription::DecodeError) - .await?; - return Err(Error::CorruptMessagePayload(ContentType::Handshake)); - } - } - return self.process_new_handshake_messages(state).await; - } - - // Now we can fully parse the message payload. - let msg = Message::try_from(msg)?; - - // For alerts, we have separate logic. - if let MessagePayload::Alert(alert) = &msg.payload { - self.common_state.process_alert(alert).await?; - return Ok(state); - } - - self.common_state.process_main_protocol(msg, state).await - } - - /// Processes any new packets read by a previous call to - /// [`ClientConnection::read_tls`]. - /// - /// Errors from this function relate to TLS protocol errors, and - /// are fatal to the connection. Future calls after an error will do - /// no new work and will return the same error. After an error is - /// received from [`process_new_packets`], you should not call [`read_tls`] - /// any more (it will fill up buffers to no purpose). However, you - /// may call the other methods on the connection, including `write`, - /// `send_close_notify`, and `write_tls`. Most likely you will want to - /// call `write_tls` to send any alerts queued by the error and then - /// close the underlying connection. - /// - /// Success from this function comes with some sundry state data - /// about the connection. - /// - /// [`read_tls`]: ClientConnection::read_tls - /// [`process_new_packets`]: ClientConnection::process_new_packets - pub async fn process_new_packets(&mut self) -> Result { - let mut state = match mem::replace(&mut self.state, Err(Error::HandshakeNotComplete)) { - Ok(state) => state, - Err(e) => { - self.state = Err(e.clone()); - return Err(e); - } - }; - - if self.message_deframer.desynced { - return Err(Error::CorruptMessage); - } - - // Process outgoing plaintext buffer and encrypt messages. - self.flush_plaintext().await?; - - // Process new messages. - while let Some(msg) = self.message_deframer.frames.pop_front() { - // If we're not decrypting yet, we process it immediately. Otherwise it will be - // pushed to the backend. - if let Some(plain) = self.process_incoming_opaque(msg).await? { - match self.process_incoming_plain(plain, state).await { - Ok(new) => state = new, - Err(e) => { - self.state = Err(e.clone()); - return Err(e); - } - } - } - } - self.backend.flush().await?; - - // Process pending decrypted messages. - while let Some(msg) = self.backend.next_incoming()? { - match self.process_incoming_plain(msg, state).await { - Ok(new) => state = new, - Err(e) => { - self.state = Err(e.clone()); - return Err(e); - } - } - } - - while let Some(msg) = self.backend.next_outgoing()? { - self.queue_tls_message(msg); - } - - self.state = Ok(state); - - Ok(self.common_state.current_io_state()) - } - - async fn process_new_handshake_messages( - &mut self, - mut state: Box, - ) -> Result, Error> { - self.common_state.aligned_handshake = self.handshake_joiner.is_empty(); - while let Some(msg) = self.handshake_joiner.frames.pop_front() { - state = self.common_state.process_main_protocol(msg, state).await?; - } - - Ok(state) - } - - /// Writes plaintext `buf` into an internal buffer. May not fully process the - /// whole buffer and returns the processed length. - pub fn write_plaintext(&mut self, buf: &[u8]) -> Result { - if buf.is_empty() { - // Don't send empty fragments. - return Ok(0); - } - - let len = self.sendable_plaintext.append_limited_copy(buf); - Ok(len) - } - - /// Read TLS content from `rd`. This method does internal - /// buffering, so `rd` can supply TLS messages in arbitrary- - /// sized chunks (like a socket or pipe might). - /// - /// You should call [`process_new_packets`] each time a call to - /// this function succeeds. - /// - /// The returned error only relates to IO on `rd`. TLS-level - /// errors are emitted from [`process_new_packets`]. - /// - /// This function returns `Ok(0)` when the underlying `rd` does - /// so. This typically happens when a socket is cleanly closed, - /// or a file is at EOF. - /// - /// [`process_new_packets`]: ClientConnection::process_new_packets - pub fn read_tls(&mut self, rd: &mut dyn io::Read) -> Result { - self.message_deframer.read(rd) - } -} - -impl Deref for ClientConnection { - type Target = CommonState; - - fn deref(&self) -> &Self::Target { - &self.common_state - } -} - -impl DerefMut for ClientConnection { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.common_state - } -} - -/// Connection state. -pub struct CommonState { - pub(crate) negotiated_version: Option, - pub(crate) backend: MpcTlsLeader, - /// Whether outgoing records are encrypted, activated by the CCS we send. - encrypting: bool, - /// Whether incoming records are decrypted, activated by the CCS the - /// server sends. - decrypting: bool, - pub(crate) suite: Option, - pub(crate) alpn_protocol: Option>, - aligned_handshake: bool, - pub(crate) may_send_application_data: bool, - pub(crate) may_receive_application_data: bool, - sent_fatal_alert: bool, - /// If the peer has sent close_notify. - has_received_close_notify: bool, - received_middlebox_ccs: u8, - message_fragmenter: MessageFragmenter, - received_plaintext: ChunkVecBuffer, - sendable_plaintext: ChunkVecBuffer, - pub(crate) sendable_tls: ChunkVecBuffer, -} - -impl CommonState { - pub(crate) fn new( - max_fragment_size: Option, - backend: MpcTlsLeader, - ) -> Result { - Ok(Self { - negotiated_version: None, - backend, - encrypting: false, - decrypting: false, - suite: None, - alpn_protocol: None, - aligned_handshake: true, - may_send_application_data: false, - may_receive_application_data: false, - sent_fatal_alert: false, - has_received_close_notify: false, - received_middlebox_ccs: 0, - message_fragmenter: MessageFragmenter::new(max_fragment_size) - .map_err(|_| Error::BadMaxFragmentSize)?, - received_plaintext: ChunkVecBuffer::new(Some(0)), - sendable_plaintext: ChunkVecBuffer::new(Some(DEFAULT_BUFFER_LIMIT)), - sendable_tls: ChunkVecBuffer::new(Some(DEFAULT_BUFFER_LIMIT)), - }) - } - - /// Returns true if the caller should call [`CommonState::write_tls`] as - /// soon as possible. - pub fn wants_write(&self) -> bool { - !self.sendable_tls.is_empty() - } - - /// Returns true if there is no plaintext data available to read - /// immediately. - pub fn plaintext_is_empty(&self) -> bool { - self.received_plaintext.is_empty() - } - - /// Returns true if the buffer for sendable plaintext is full. - pub fn sendable_plaintext_is_full(&self) -> bool { - self.sendable_plaintext.is_full() - } - - /// Returns true if the connection is currently performing the TLS - /// handshake. - /// - /// During this time plaintext written to the connection is buffered in - /// memory. After [`ClientConnection::process_new_packets`] has been called, - /// this might start to return `false` while the final handshake packets - /// still need to be extracted from the connection's buffers. - pub fn is_handshaking(&self) -> bool { - !(self.may_send_application_data && self.may_receive_application_data) - } - - pub(crate) fn is_tls13(&self) -> bool { - matches!(self.negotiated_version, Some(ProtocolVersion::TLSv1_3)) - } - - async fn process_main_protocol( - &mut self, - msg: Message, - mut state: Box, - ) -> Result, Error> { - // For TLS1.2, outside of the handshake, send rejection alerts for - // renegotiation requests. These can occur any time. - if self.may_receive_application_data - && !self.is_tls13() - && msg.is_handshake_type(HandshakeType::HelloRequest) - { - self.send_warning_alert(AlertDescription::NoRenegotiation) - .await?; - return Ok(state); - } - - match state.handle(self, msg).await { - Ok(next) => { - state = next; - Ok(state) - } - Err(e @ Error::InappropriateMessage { .. }) - | Err(e @ Error::InappropriateHandshakeMessage { .. }) => { - self.send_fatal_alert(AlertDescription::UnexpectedMessage) - .await?; - Err(e) - } - Err(e) => Err(e), - } - } - - // Changing the keys must not span any fragmented handshake - // messages. Otherwise the defragmented messages will have - // been protected with two different record layer protections, - // which is illegal. Not mentioned in RFC. - pub(crate) async fn check_aligned_handshake(&mut self) -> Result<(), Error> { - if !self.aligned_handshake { - self.send_fatal_alert(AlertDescription::UnexpectedMessage) - .await?; - Err(Error::PeerMisbehavedError( - "key epoch or handshake flight with pending fragment".to_string(), - )) - } else { - Ok(()) - } - } - - pub(crate) async fn illegal_param(&mut self, why: &str) -> Result { - self.send_fatal_alert(AlertDescription::IllegalParameter) - .await?; - Ok(Error::PeerMisbehavedError(why.to_string())) - } - - /// Starts encrypting outgoing records. Called when we send our - /// ChangeCipherSpec. - pub(crate) fn start_encrypting(&mut self) { - self.encrypting = true; - } - - /// Starts decrypting incoming records. Called when the server's - /// ChangeCipherSpec is received. - pub(crate) fn start_decrypting(&mut self) { - self.decrypting = true; - } - - pub(crate) async fn decrypt_incoming(&mut self, encr: OpaqueMessage) -> Result<(), Error> { - debug_assert!(self.decrypting); - self.backend.push_incoming(encr).await?; - - Ok(()) - } - - /// Fragment `m`, encrypt the fragments, and then queue - /// the encrypted fragments for sending. - /// - /// Unlike upstream rustls there is no sequence-space exhaustion guard: - /// the MPC record layer enforces the configured traffic limits, which - /// bound the number of records far below the sequence space. - pub(crate) async fn send_msg_encrypt(&mut self, m: PlainMessage) -> Result<(), Error> { - let mut plain_messages = VecDeque::new(); - self.message_fragmenter.fragment(m, &mut plain_messages); - - for m in plain_messages { - self.send_single_fragment(m).await?; - } - Ok(()) - } - - /// Like send_msg_encrypt, but operate on an appdata directly. - async fn send_appdata_encrypt(&mut self, payload: &[u8]) -> Result { - let mut plain_messages = VecDeque::new(); - self.message_fragmenter.fragment( - PlainMessage { - typ: ContentType::ApplicationData, - version: ProtocolVersion::TLSv1_2, - payload: Payload::new(payload), - }, - &mut plain_messages, - ); - - for m in plain_messages { - self.send_single_fragment(m).await?; - } - - Ok(payload.len()) - } - - async fn send_single_fragment(&mut self, m: PlainMessage) -> Result<(), Error> { - debug_assert!(self.encrypting); - self.backend.push_outgoing(m).await?; - - Ok(()) - } - - /// Writes TLS messages to `wr`. - /// - /// On success, this function returns `Ok(n)` where `n` is a number of bytes - /// written to `wr` (after encoding and encryption). - /// - /// After this function returns, the connection buffer may not yet be fully - /// flushed. The [`CommonState::wants_write`] function can be used to - /// check if the output buffer is empty. - pub fn write_tls(&mut self, wr: &mut dyn io::Write) -> Result { - self.sendable_tls.write_to(wr) - } - - pub(crate) async fn start_outgoing_traffic(&mut self) -> Result<(), Error> { - self.may_send_application_data = true; - self.flush_plaintext().await - } - - pub(crate) async fn start_traffic(&mut self) -> Result<(), Error> { - self.may_receive_application_data = true; - self.backend.start_traffic().await?; - self.start_outgoing_traffic().await - } - - /// Send and encrypt any buffered plaintext. Does nothing during handshake. - pub(crate) async fn flush_plaintext(&mut self) -> Result<(), Error> { - if !self.may_send_application_data { - return Ok(()); - } - - while let Some(buf) = self.sendable_plaintext.pop() { - self.send_appdata_encrypt(&buf).await?; - } - - Ok(()) - } - - // Put m into sendable_tls for writing. - pub(crate) fn queue_tls_message(&mut self, m: OpaqueMessage) { - self.sendable_tls.append(m.encode()); - } - - /// Send a raw TLS message, fragmenting it if needed. - pub(crate) async fn send_msg(&mut self, m: Message, must_encrypt: bool) -> Result<(), Error> { - if !must_encrypt { - let mut to_send = VecDeque::new(); - self.message_fragmenter.fragment(m.into(), &mut to_send); - for mm in to_send { - self.queue_tls_message(mm.into_unencrypted_opaque()); - } - Ok(()) - } else { - self.send_msg_encrypt(m.into()).await - } - } - - pub(crate) fn take_received_plaintext(&mut self, bytes: Payload) { - self.received_plaintext.append(bytes.0); - } - - async fn send_warning_alert(&mut self, desc: AlertDescription) -> Result<(), Error> { - warn!("Sending warning alert {:?}", desc); - self.send_warning_alert_no_log(desc).await - } - - async fn process_alert(&mut self, alert: &AlertMessagePayload) -> Result<(), Error> { - // Reject unknown AlertLevels. - if let AlertLevel::Unknown(_) = alert.level { - self.send_fatal_alert(AlertDescription::IllegalParameter) - .await?; - } - - // If we get a CloseNotify, make a note to declare EOF to our - // caller. - if alert.description == AlertDescription::CloseNotify { - self.has_received_close_notify = true; - return Ok(()); - } - - // Warnings are nonfatal for TLS1.2, but outlawed in TLS1.3 - // (except, for no good reason, user_cancelled). - if alert.level == AlertLevel::Warning { - if self.is_tls13() && alert.description != AlertDescription::UserCanceled { - self.send_fatal_alert(AlertDescription::DecodeError).await?; - } else { - warn!("TLS alert warning received: {:#?}", alert); - return Ok(()); - } - } - - error!("TLS alert received: {:#?}", alert); - Err(Error::AlertReceived(alert.description)) - } - - pub(crate) async fn send_fatal_alert(&mut self, desc: AlertDescription) -> Result<(), Error> { - warn!("Sending fatal alert {:?}", desc); - debug_assert!(!self.sent_fatal_alert); - let m = Message::build_alert(AlertLevel::Fatal, desc); - self.send_msg(m, self.encrypting).await?; - self.sent_fatal_alert = true; - Ok(()) - } - - /// Queues a close_notify warning alert to be sent in the next - /// [`CommonState::write_tls`] call. This informs the peer that the - /// connection is being closed. - pub async fn send_close_notify(&mut self) -> Result<(), Error> { - debug!("Sending warning alert {:?}", AlertDescription::CloseNotify); - self.send_warning_alert_no_log(AlertDescription::CloseNotify) - .await - } - - async fn send_warning_alert_no_log(&mut self, desc: AlertDescription) -> Result<(), Error> { - let m = Message::build_alert(AlertLevel::Warning, desc); - self.send_msg(m, self.encrypting).await - } - - /// Returns true if the caller should call [`ClientConnection::read_tls`] as soon - /// as possible. - /// - /// If there is pending plaintext data to read with - /// [`ClientConnection::read_plaintext`], this returns false. If the - /// application respects this mechanism, only one full TLS message will - /// be buffered. - pub fn wants_read(&self) -> bool { - // We want to read more data all the time, except when we have unprocessed - // plaintext. This provides back-pressure to the TCP buffers. We also - // don't want to read more after the peer has sent us a close - // notification. - // - // In the handshake case we don't have readable plaintext before the handshake - // has completed, but also don't want to read if we still have sendable - // tls. - self.received_plaintext.is_empty() - && !self.has_received_close_notify - && (self.may_send_application_data || self.sendable_tls.is_empty()) - } - - /// Enables or disables the decryption of incoming messages. - pub fn enable_decryption(&mut self, enable: bool) { - self.backend.enable_decryption(enable); - } - - /// Returns the context and transcript after the connection is closed. - /// - /// Returns `None` if the connection is not closed yet. - pub fn finish( - &mut self, - ) -> Option<(mpz_common::Context, tlsn_core::transcript::TlsTranscript)> { - self.backend.finish() - } - - fn current_io_state(&self) -> IoState { - IoState { - tls_bytes_to_write: self.sendable_tls.len(), - plaintext_bytes_to_read: self.received_plaintext.len(), - } - } -} - -/// A state of the TLS protocol state machine. -#[async_trait] -pub(crate) trait State: Send + Sync { - async fn start(self: Box, _cx: &mut CommonState) -> Result, Error> { - panic!("Start called on unexpected state") - } - - async fn handle( - self: Box, - cx: &mut CommonState, - message: Message, - ) -> Result, Error>; -} - -const DEFAULT_BUFFER_LIMIT: usize = 64 * 1024; diff --git a/crates/mpc-tls/src/client/mod.rs b/crates/mpc-tls/src/client/mod.rs deleted file mode 100644 index 87bc3f5d01..0000000000 --- a/crates/mpc-tls/src/client/mod.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! A TLS client implementation forked from [rustls](https://github.com/rustls/rustls) -//! version 0.20. -//! -//! Unlike upstream rustls, this client performs no cryptographic operations -//! itself: key exchange, the PRF and record encryption and decryption are -//! delegated to the [`MpcTlsLeader`](crate::MpcTlsLeader), which executes -//! them jointly with the verifier using MPC. The state machine in this -//! module drives the TLS protocol itself: message framing, handshake flow, -//! alerts and connection closure. -//! -//! Only TLS 1.2 cipher suites are currently enabled. The TLS 1.3 message -//! handling inherited from upstream is retained for future use, but is -//! unreachable as long as [`tls_core::versions::ALL_VERSIONS`] excludes -//! TLS 1.3. - -#[macro_use] -mod check; -mod config; -mod conn; -mod error; -mod hash_hs; -mod hs; -mod tls12; -mod tls13; -mod vecbuf; - -pub(crate) use tls_core::{anchors, verify, x509}; - -// The public interface is: -pub use crate::client::{ - anchors::RootCertStore, - config::{ClientConfig, ResolvesClientCert, ServerName}, - conn::{ClientConnection, CommonState, IoState}, - error::Error, -}; -pub use tls_core::key::{Certificate, PrivateKey}; - -/// Message signing interfaces and implementations. -pub mod sign; diff --git a/crates/mpc-tls/src/conn.rs b/crates/mpc-tls/src/conn.rs new file mode 100644 index 0000000000..d33396d522 --- /dev/null +++ b/crates/mpc-tls/src/conn.rs @@ -0,0 +1,737 @@ +//! TLS connection I/O and framing. +//! +//! This module factors the connection plumbing that is shared by both phases of +//! a live connection (handshaking and online) out of the leader's state: +//! +//! * [`TlsIo`] owns the pure TLS framing state — record (de)framing, message +//! fragmentation, the plaintext/ciphertext buffers and the record-protection +//! gates — with the methods that touch only framing. +//! * [`Conn`] bundles [`TlsIo`] with the [`MpcSession`] and provides the +//! operations that need both: encrypting/decrypting records through MPC, +//! sending wire messages to the follower, alerts, and the verify-data +//! computations. +//! +//! The handshake-phase-specific state (the handshake state machine, the +//! handshake joiner, client config) lives in the leader's `Handshaking` phase, +//! not here. + +use std::{collections::VecDeque, io}; + +use serio::SinkExt; +use tls_core::{ + cert::ServerCertDetails, + ke::ServerKxDetails, + key::PublicKey, + msgs::{ + alert::AlertMessagePayload, + base::Payload, + deframer::MessageDeframer, + enums::{ + AlertDescription, AlertLevel, ContentType, HandshakeType, NamedGroup, ProtocolVersion, + }, + fragmenter::MessageFragmenter, + handshake::{HandshakeMessagePayload, HandshakePayload, Random}, + hsjoiner::HandshakeJoiner, + message::{Message, MessagePayload, OpaqueMessage, PlainMessage}, + }, + suites::SupportedCipherSuite, +}; +use tracing::{debug, error, instrument, warn}; + +use crate::{ + MpcTlsError, + handshake::error::Error, + msg::{Decrypt, Encrypt, Message as MpcMessage, ServerHello}, + session::{MpcSession, opaque_into_parts}, + vecbuf::ChunkVecBuffer, +}; + +const DEFAULT_BUFFER_LIMIT: usize = 64 * 1024; + +/// How many ChangeCipherSpec messages we accept and drop in TLS1.3 handshakes. +/// The spec says 1, but implementations (namely the boringssl test suite) get +/// this wrong. BoringSSL itself accepts up to 32. +pub(crate) const TLS13_MAX_DROPPED_CCS: u8 = 2; + +/// Values returned from `process_new_packets` describing the current I/O state +/// of the connection. +#[derive(Debug, PartialEq)] +pub struct IoState { + tls_bytes_to_write: usize, + plaintext_bytes_to_read: usize, +} + +impl IoState { + /// How many bytes could be written by `write_tls` right now. + pub fn tls_bytes_to_write(&self) -> usize { + self.tls_bytes_to_write + } + + /// How many plaintext bytes could be read via `read_plaintext` without + /// further I/O. + pub fn plaintext_bytes_to_read(&self) -> usize { + self.plaintext_bytes_to_read + } +} + +/// The client and server randoms of a connection. +#[derive(Debug)] +pub(crate) struct ConnectionRandoms { + pub(crate) client: [u8; 32], + pub(crate) server: [u8; 32], +} + +impl ConnectionRandoms { + pub(crate) fn new(client: Random, server: Random) -> Self { + Self { + client: client.0, + server: server.0, + } + } +} + +/// Returns whether `msg` is a valid (unencrypted) ChangeCipherSpec record. +pub(crate) fn is_valid_ccs(msg: &OpaqueMessage) -> bool { + // nb. this is prior to the record layer, so is unencrypted. see + // third paragraph of section 5 in RFC8446. + msg.typ == ContentType::ChangeCipherSpec && msg.payload.0 == [0x01] +} + +/// TLS framing state shared by both connection phases. +pub(crate) struct TlsIo { + /// The negotiated protocol version. + pub(crate) negotiated_version: Option, + /// The negotiated cipher suite. + pub(crate) suite: Option, + /// The negotiated ALPN protocol. + pub(crate) alpn_protocol: Option>, + /// Whether outgoing records are encrypted, activated by the CCS we send. + encrypting: bool, + /// Whether incoming records are decrypted, activated by the server's CCS. + decrypting: bool, + sent_fatal_alert: bool, + has_received_close_notify: bool, + /// Whether the last processed handshake flight was aligned (no pending + /// fragment). Changing keys must not span a fragmented handshake message. + aligned_handshake: bool, + /// Count of middlebox-compatibility CCS records dropped during a TLS 1.3 + /// handshake. + received_middlebox_ccs: u8, + message_fragmenter: MessageFragmenter, + message_deframer: MessageDeframer, + handshake_joiner: HandshakeJoiner, + received_plaintext: ChunkVecBuffer, + sendable_plaintext: ChunkVecBuffer, + sendable_tls: ChunkVecBuffer, +} + +impl TlsIo { + /// Creates the framing state, validating the configured fragment size. + pub(crate) fn new(max_fragment_size: Option) -> Result { + Ok(Self { + negotiated_version: None, + suite: None, + alpn_protocol: None, + encrypting: false, + decrypting: false, + sent_fatal_alert: false, + has_received_close_notify: false, + aligned_handshake: true, + received_middlebox_ccs: 0, + message_fragmenter: MessageFragmenter::new(max_fragment_size) + .map_err(|_| Error::BadMaxFragmentSize)?, + message_deframer: MessageDeframer::new(), + handshake_joiner: HandshakeJoiner::new(), + received_plaintext: ChunkVecBuffer::new(Some(0)), + sendable_plaintext: ChunkVecBuffer::new(Some(DEFAULT_BUFFER_LIMIT)), + sendable_tls: ChunkVecBuffer::new(Some(DEFAULT_BUFFER_LIMIT)), + }) + } + + pub(crate) fn is_tls13(&self) -> bool { + matches!(self.negotiated_version, Some(ProtocolVersion::TLSv1_3)) + } + + pub(crate) fn decrypting(&self) -> bool { + self.decrypting + } + + /// Starts encrypting outgoing records. Called when we send our CCS. + pub(crate) fn start_encrypting(&mut self) { + self.encrypting = true; + } + + /// Starts decrypting incoming records. Called when the server's CCS is + /// received. + pub(crate) fn start_decrypting(&mut self) { + self.decrypting = true; + } + + pub(crate) fn has_received_close_notify(&self) -> bool { + self.has_received_close_notify + } + + pub(crate) fn set_received_close_notify(&mut self) { + self.has_received_close_notify = true; + } + + pub(crate) fn deframer_desynced(&self) -> bool { + self.message_deframer.desynced + } + + /// Reads TLS records from `rd` into the internal buffer. + pub(crate) fn read_tls(&mut self, rd: &mut dyn io::Read) -> io::Result { + self.message_deframer.read(rd) + } + + /// Writes buffered TLS records to `wr`. + pub(crate) fn write_tls(&mut self, wr: &mut dyn io::Write) -> io::Result { + self.sendable_tls.write_to(wr) + } + + /// Reads out buffered plaintext received from the peer. + pub(crate) fn read_plaintext(&mut self, buf: &mut [u8]) -> io::Result { + self.received_plaintext.read(buf) + } + + /// Buffers plaintext to be encrypted and sent to the peer. + pub(crate) fn write_plaintext(&mut self, buf: &[u8]) -> usize { + if buf.is_empty() { + // Don't send empty fragments. + return 0; + } + self.sendable_plaintext.append_limited_copy(buf) + } + + pub(crate) fn wants_write(&self) -> bool { + !self.sendable_tls.is_empty() + } + + pub(crate) fn plaintext_is_empty(&self) -> bool { + self.received_plaintext.is_empty() + } + + pub(crate) fn sendable_tls_is_empty(&self) -> bool { + self.sendable_tls.is_empty() + } + + pub(crate) fn sendable_plaintext_is_full(&self) -> bool { + self.sendable_plaintext.is_full() + } + + pub(crate) fn current_io_state(&self) -> IoState { + IoState { + tls_bytes_to_write: self.sendable_tls.len(), + plaintext_bytes_to_read: self.received_plaintext.len(), + } + } + + pub(crate) fn next_received_frame(&mut self) -> Option { + self.message_deframer.frames.pop_front() + } + + pub(crate) fn next_sendable_plaintext(&mut self) -> Option> { + self.sendable_plaintext.pop() + } + + pub(crate) fn queue_tls_message(&mut self, m: OpaqueMessage) { + self.sendable_tls.append(m.encode()); + } + + pub(crate) fn take_received_plaintext(&mut self, bytes: Payload) { + self.received_plaintext.append(bytes.0); + } + + pub(crate) fn aligned_handshake(&self) -> bool { + self.aligned_handshake + } + + pub(crate) fn received_middlebox_ccs(&self) -> u8 { + self.received_middlebox_ccs + } + + pub(crate) fn inc_received_middlebox_ccs(&mut self) { + self.received_middlebox_ccs += 1; + } + + /// Returns whether `msg` is a handshake message that must be reassembled + /// before processing. + pub(crate) fn joiner_wants(&self, msg: &PlainMessage) -> bool { + self.handshake_joiner.want_message(msg) + } + + /// Feeds a handshake message to the joiner. Returns `None` if it is + /// malformed. + pub(crate) fn join(&mut self, msg: PlainMessage) -> Option<()> { + self.handshake_joiner.take_message(msg).map(|_| ()) + } + + /// Marks whether the handshake flight just joined was aligned, returning + /// the next reassembled handshake message if any. + pub(crate) fn mark_aligned_handshake(&mut self) { + self.aligned_handshake = self.handshake_joiner.is_empty(); + } + + pub(crate) fn next_joined_message(&mut self) -> Option { + self.handshake_joiner.frames.pop_front() + } +} + +/// Server parameters of the TLS handshake, collected by the client during the +/// handshake and used to build the transcript at close. +#[derive(Debug)] +pub(crate) struct HandshakeData { + /// The server random. + pub(crate) server_random: Random, + /// The server ephemeral public key. + pub(crate) server_key: PublicKey, + /// The server certificate chain and certificate metadata. + pub(crate) server_cert_details: ServerCertDetails, + /// The server key exchange parameters and signature. + pub(crate) server_kx_details: ServerKxDetails, +} + +/// A live TLS connection: its I/O, its MPC session, and the facts negotiated +/// during the handshake. +/// +/// `Conn` owns the framing ([`TlsIo`]) and the [`MpcSession`], and provides the +/// operations that need both: sending records (encrypting application data +/// through MPC), receiving records (queuing them for MPC decryption), alerts, +/// the Finished verify-data computations and the key derivation. It is the +/// context the handshake state machine and the online router operate on, and is +/// shared unchanged across the handshaking and online phases. +pub(crate) struct Conn { + pub(crate) io: TlsIo, + pub(crate) session: MpcSession, + /// The client random, generated during setup; used in the ClientHello and + /// in the transcript's certificate binding at close. + pub(crate) client_random: Random, + /// Server handshake parameters, set by [`Conn::prepare_encryption`] and + /// used to build the transcript at close. + pub(crate) server_params: Option, + /// The handshake time, set by [`Conn::prepare_encryption`]. + pub(crate) time: Option, +} + +impl Conn { + pub(crate) fn new(io: TlsIo, session: MpcSession, client_random: Random) -> Self { + Self { + io, + session, + client_random, + server_params: None, + time: None, + } + } + + /// Whether the session keys have been derived and the record layer + /// prepared. + pub(crate) fn encryption_prepared(&self) -> bool { + self.server_params.is_some() + } + + /// Sends a protocol message to the follower. + pub(crate) async fn send_message(&mut self, msg: MpcMessage) -> Result<(), MpcTlsError> { + self.session.ctx_mut().io_mut().send(msg).await?; + Ok(()) + } + + /// Returns the client's ephemeral public key for the key exchange. + pub(crate) fn client_key_share(&self) -> Result { + self.session.client_key_share() + } + + /// Computes the session keys from the server's handshake parameters and + /// prepares the record layer for encryption. + #[instrument(level = "debug", skip_all, err)] + pub(crate) async fn prepare_encryption( + &mut self, + hs: HandshakeData, + ) -> Result<(), MpcTlsError> { + debug!("preparing encryption"); + + if hs.server_key.group != NamedGroup::secp256r1 { + return Err(MpcTlsError::hs("invalid server public keyshare")); + } + + let time = web_time::UNIX_EPOCH + .elapsed() + .expect("system time is available") + .as_secs(); + + self.send_message(MpcMessage::ServerHello(ServerHello { + time, + random: hs.server_random.0, + key: hs.server_key.clone(), + })) + .await?; + + let server_key = + p256::PublicKey::from_sec1_bytes(&hs.server_key.key).map_err(MpcTlsError::hs)?; + self.session + .compute_keys(hs.server_random.0, server_key) + .await?; + + self.server_params = Some(hs); + self.time = Some(time); + + debug!("encryption prepared"); + + Ok(()) + } + + #[instrument(level = "debug", skip_all, err)] + pub(crate) async fn get_client_finished_vd( + &mut self, + hash: Vec, + ) -> Result, MpcTlsError> { + debug!("computing client finished verify data"); + let hash: [u8; 32] = hash + .try_into() + .map_err(|_| MpcTlsError::hs("client finished handshake hash is not 32 bytes"))?; + + self.send_message(MpcMessage::ClientFinishedVd(hash)) + .await?; + let vd = self.session.compute_cf_vd(hash).await?; + + Ok(vd.to_vec()) + } + + #[instrument(level = "debug", skip_all, err)] + pub(crate) async fn get_server_finished_vd( + &mut self, + hash: Vec, + ) -> Result, MpcTlsError> { + debug!("computing server finished verify data"); + let hash: [u8; 32] = hash + .try_into() + .map_err(|_| MpcTlsError::hs("server finished handshake hash is not 32 bytes"))?; + + self.send_message(MpcMessage::ServerFinishedVd(hash)) + .await?; + let vd = self.session.compute_sf_vd(hash).await?; + + Ok(vd.to_vec()) + } + + /// Sends a raw TLS message, fragmenting it and encrypting if required. + pub(crate) async fn send_msg(&mut self, m: Message, must_encrypt: bool) -> Result<(), Error> { + if !must_encrypt { + let mut to_send = VecDeque::new(); + self.io.message_fragmenter.fragment(m.into(), &mut to_send); + for mm in to_send { + self.io.queue_tls_message(mm.into_unencrypted_opaque()); + } + Ok(()) + } else { + self.send_msg_encrypt(m.into()).await + } + } + + /// Fragments `m`, encrypts the fragments, and queues them for sending. + /// + /// Unlike upstream rustls there is no sequence-space exhaustion guard: the + /// MPC record layer enforces the configured traffic limits, which bound the + /// number of records far below the sequence space. + async fn send_msg_encrypt(&mut self, m: PlainMessage) -> Result<(), Error> { + let mut plain_messages = VecDeque::new(); + self.io.message_fragmenter.fragment(m, &mut plain_messages); + + for m in plain_messages { + self.send_single_fragment(m).await?; + } + Ok(()) + } + + pub(crate) async fn send_appdata_encrypt(&mut self, payload: &[u8]) -> Result { + let mut plain_messages = VecDeque::new(); + self.io.message_fragmenter.fragment( + PlainMessage { + typ: ContentType::ApplicationData, + version: ProtocolVersion::TLSv1_2, + payload: Payload::new(payload), + }, + &mut plain_messages, + ); + + for m in plain_messages { + self.send_single_fragment(m).await?; + } + + Ok(payload.len()) + } + + async fn send_single_fragment(&mut self, m: PlainMessage) -> Result<(), Error> { + debug_assert!(self.io.encrypting); + self.push_outgoing(m).await?; + Ok(()) + } + + #[instrument(level = "debug", skip_all, err)] + async fn push_outgoing(&mut self, msg: PlainMessage) -> Result<(), Error> { + debug!( + "encrypting outgoing message, type: {:?}, len: {}", + msg.typ, + msg.payload.0.len() + ); + + let PlainMessage { + typ, + version, + payload, + } = msg; + let plaintext = payload.0; + let len = plaintext.len(); + + // Only the contents of application data is hidden from the follower. + let public_plaintext = match typ { + ContentType::ApplicationData => None, + _ => Some(plaintext.clone()), + }; + + self.session + .push_encrypt(typ, version, len, Some(plaintext))?; + + self.send_message(MpcMessage::Encrypt(Encrypt { + typ, + version, + len, + plaintext: public_plaintext, + })) + .await?; + + Ok(()) + } + + pub(crate) async fn push_incoming(&mut self, msg: OpaqueMessage) -> Result<(), Error> { + let OpaqueMessage { + typ, + version, + payload, + } = msg; + let (explicit_nonce, ciphertext, tag) = opaque_into_parts(payload.0)?; + + debug!( + "received incoming message, type: {:?}, len: {}", + typ, + ciphertext.len() + ); + + self.session.push_decrypt( + typ, + version, + explicit_nonce.clone(), + ciphertext.clone(), + tag.clone(), + )?; + + self.send_message(MpcMessage::Decrypt(Decrypt { + typ, + version, + explicit_nonce, + ciphertext, + tag, + })) + .await?; + + Ok(()) + } + + pub(crate) fn next_incoming(&mut self) -> Option { + let record = self.session.next_decrypted().map(|record| PlainMessage { + typ: record.typ, + version: record.version, + payload: Payload::new( + record + .plaintext + .expect("leader should always know plaintext"), + ), + }); + + if let Some(record) = &record { + debug!( + "processing incoming message, type: {:?}, len: {}", + record.typ, + record.payload.0.len() + ); + } + + record + } + + pub(crate) fn next_outgoing(&mut self) -> Option { + let record = self.session.next_encrypted().map(|record| { + let mut payload = record.explicit_nonce; + payload.extend_from_slice(&record.ciphertext); + payload.extend_from_slice(&record.tag.expect("leader should always know tag")); + OpaqueMessage { + typ: record.typ, + version: record.version, + payload: Payload::new(payload), + } + }); + + if let Some(record) = &record { + debug!( + "sending outgoing message, type: {:?}, len: {}", + record.typ, + record.payload.0.len() + ); + } + + record + } + + /// Sends the server's traffic-start signal and starts the record layer. + pub(crate) async fn start_traffic(&mut self) -> Result<(), Error> { + self.session.start_traffic(); + self.send_message(MpcMessage::StartTraffic).await?; + Ok(()) + } + + /// Flushes the record layer if there is buffered work. + #[instrument(level = "debug", skip_all, err)] + pub(crate) async fn flush_records(&mut self, is_decrypting: bool) -> Result<(), Error> { + if !self.session.wants_flush() { + debug!("record layer is empty, skipping flush"); + return Ok(()); + } + + debug!("flushing record layer"); + self.send_message(MpcMessage::Flush { is_decrypting }) + .await?; + self.session.flush(is_decrypting).await?; + + Ok(()) + } + + pub(crate) async fn send_warning_alert(&mut self, desc: AlertDescription) -> Result<(), Error> { + warn!("Sending warning alert {:?}", desc); + self.send_warning_alert_no_log(desc).await + } + + async fn send_warning_alert_no_log(&mut self, desc: AlertDescription) -> Result<(), Error> { + let m = Message::build_alert(AlertLevel::Warning, desc); + let must_encrypt = self.io.encrypting; + self.send_msg(m, must_encrypt).await + } + + pub(crate) async fn send_fatal_alert(&mut self, desc: AlertDescription) -> Result<(), Error> { + warn!("Sending fatal alert {:?}", desc); + debug_assert!(!self.io.sent_fatal_alert); + let m = Message::build_alert(AlertLevel::Fatal, desc); + let must_encrypt = self.io.encrypting; + self.send_msg(m, must_encrypt).await?; + self.io.sent_fatal_alert = true; + Ok(()) + } + + /// Queues a close_notify warning alert to be sent in the next `write_tls`. + pub(crate) async fn send_close_notify(&mut self) -> Result<(), Error> { + debug!("Sending warning alert {:?}", AlertDescription::CloseNotify); + self.send_warning_alert_no_log(AlertDescription::CloseNotify) + .await + } + + /// Errors if the handshake is not aligned (a key change must not span a + /// fragmented handshake message), sending a fatal alert. + pub(crate) async fn check_aligned_handshake(&mut self) -> Result<(), Error> { + if !self.io.aligned_handshake() { + self.send_fatal_alert(AlertDescription::UnexpectedMessage) + .await?; + Err(Error::PeerMisbehavedError( + "key epoch or handshake flight with pending fragment".to_string(), + )) + } else { + Ok(()) + } + } + + /// Sends an `illegal_parameter` fatal alert and returns the corresponding + /// error. + pub(crate) async fn illegal_param(&mut self, why: &str) -> Result { + self.send_fatal_alert(AlertDescription::IllegalParameter) + .await?; + Ok(Error::PeerMisbehavedError(why.to_string())) + } + + pub(crate) async fn process_alert(&mut self, alert: &AlertMessagePayload) -> Result<(), Error> { + if let AlertLevel::Unknown(_) = alert.level { + self.send_fatal_alert(AlertDescription::IllegalParameter) + .await?; + } + + if alert.description == AlertDescription::CloseNotify { + self.io.set_received_close_notify(); + return Ok(()); + } + + if alert.level == AlertLevel::Warning { + if self.io.is_tls13() && alert.description != AlertDescription::UserCanceled { + self.send_fatal_alert(AlertDescription::DecodeError).await?; + } else { + warn!("TLS alert warning received: {:#?}", alert); + return Ok(()); + } + } + + error!("TLS alert received: {:#?}", alert); + Err(Error::AlertReceived(alert.description)) + } + + /// Processes a fully-parsed TLS message received while the connection is in + /// the online (post-handshake) phase. + /// + /// Alerts are handled by the caller before dispatch; this handles + /// application data, TLS 1.2 renegotiation rejection, and the TLS 1.3 + /// post-handshake messages (the latter dormant: the MPC backend never + /// completes a TLS 1.3 handshake, so the connection never reaches this + /// point under TLS 1.3). + pub(crate) async fn process_online(&mut self, msg: Message) -> Result<(), Error> { + // TLS 1.2 renegotiation requests are rejected outside the handshake. + // These can occur at any time. + if !self.io.is_tls13() && msg.is_handshake_type(HandshakeType::HelloRequest) { + self.send_warning_alert(AlertDescription::NoRenegotiation) + .await?; + return Ok(()); + } + + match msg.payload { + MessagePayload::ApplicationData(payload) => self.io.take_received_plaintext(payload), + MessagePayload::Handshake(HandshakeMessagePayload { + payload: HandshakePayload::NewSessionTicketTLS13(ref nst), + .. + }) if self.io.is_tls13() => { + if nst.has_duplicate_extension() { + self.send_fatal_alert(AlertDescription::IllegalParameter) + .await?; + return Err(Error::PeerMisbehavedError( + "peer sent duplicate NewSessionTicket extensions".into(), + )); + } + } + MessagePayload::Handshake(HandshakeMessagePayload { + payload: HandshakePayload::KeyUpdate(_), + .. + }) if self.io.is_tls13() => { + // A key update must not be interleaved with a fragmented + // handshake message, and the client does not support key + // updates. + self.check_aligned_handshake().await?; + self.send_fatal_alert(AlertDescription::InternalError) + .await?; + return Err(Error::General( + "received unsupported key update request from peer".to_string(), + )); + } + payload => { + return Err(Error::InappropriateMessage { + expect_types: vec![ContentType::ApplicationData], + got_type: payload.content_type(), + }); + } + } + + Ok(()) + } +} diff --git a/crates/mpc-tls/src/follower.rs b/crates/mpc-tls/src/follower.rs index 38a90841bb..e82d0e1222 100644 --- a/crates/mpc-tls/src/follower.rs +++ b/crates/mpc-tls/src/follower.rs @@ -1,15 +1,15 @@ -use crate::{ - Config, MpcTlsError, Role, SessionKeys, Vm, - msg::{Message, ServerHello}, - record_layer::{RecordLayer, aead::MpcAesGcm}, - utils::{alloc_session, flush_prf, verify_transcript}, -}; +//! MPC-TLS follower. +//! +//! The follower is the verifier-side peer of the +//! [`MpcTlsLeader`](crate::MpcTlsLeader). It runs no TLS protocol logic of its +//! own: it embeds an [`MpcSession`] and mirrors the leader's decisions, which +//! arrive as [`Message`]s, by running the same MPC operations on its session. + use hmac_sha256::{MSMode, Prf, PrfConfig}; use ke::KeyExchange; use key_exchange::{self as ke, MpcKeyExchange}; use mpz_common::{Context, Flush}; -use mpz_core::{Block, bitvec::BitVec}; -use mpz_memory_core::DecodeFutureTyped; +use mpz_core::Block; use mpz_ole::{Receiver as OLEReceiver, Sender as OLESender}; use mpz_ot::{ rcot::{RCOTReceiver, RCOTSender}, @@ -20,15 +20,20 @@ use mpz_ot::{ }; use mpz_share_conversion::{ShareConversionReceiver, ShareConversionSender}; use serio::stream::IoStreamExt; -use std::mem; use tls_core::msgs::enums::NamedGroup; use tlsn_core::{ connection::{CertBinding, CertBindingV1_2, TlsVersion}, transcript::TlsTranscript, }; - use tracing::{debug, instrument}; +use crate::{ + Config, MpcTlsError, Role, SessionKeys, Vm, + msg::{Message, ServerHello}, + record_layer::{RecordLayer, aead::MpcAesGcm}, + session::MpcSession, +}; + // Maximum handshake time difference in seconds. const MAX_TIME_DIFF: u64 = 5; @@ -36,8 +41,9 @@ const MAX_TIME_DIFF: u64 = 5; #[derive(Debug)] pub struct MpcTlsFollower { config: Config, - ctx: Context, - state: State, + /// The MPC session, taken out for [`MpcTlsFollower::preprocess`] (which + /// consumes the session) and replaced afterwards. + session: Option, } impl MpcTlsFollower { @@ -82,149 +88,59 @@ impl MpcTlsFollower { ); let record_layer = RecordLayer::new(Role::Follower, encrypter, decrypter); + let session = MpcSession::new(ctx, vm, ke, prf, record_layer); Self { config, - ctx, - state: State::Init { - vm, - ke, - prf, - record_layer, - }, + session: Some(session), } } + fn session_mut(&mut self) -> Result<&mut MpcSession, MpcTlsError> { + self.session + .as_mut() + .ok_or_else(|| MpcTlsError::state("follower session is not available")) + } + /// Allocates resources for the connection. pub fn alloc(&mut self) -> Result { - let State::Init { - vm, - mut ke, - mut prf, - mut record_layer, - } = self.state.take() - else { - return Err(MpcTlsError::state("must be in init state to allocate")); - }; - - let (keys, cf_vd, sf_vd) = { - let mut vm = vm - .try_lock() - .map_err(|_| MpcTlsError::other("VM lock is held"))?; - - alloc_session( - &mut *vm, - &self.config, - &mut *ke, - &mut prf, - &mut record_layer, - )? - }; - - self.state = State::Setup { - vm, - ke, - prf, - record_layer, - cf_vd, - sf_vd, - }; - - Ok(keys) + let config = self.config.clone(); + self.session_mut()?.alloc(&config) } /// Preprocesses the connection. #[instrument(skip_all, err)] pub async fn preprocess(&mut self) -> Result<(), MpcTlsError> { - let State::Setup { - vm, - mut ke, - prf, - mut record_layer, - cf_vd, - sf_vd, - } = self.state.take() - else { - return Err(MpcTlsError::state("must be in setup state to preprocess")); - }; - - let (ke, record_layer, _) = { - let mut vm = vm - .clone() - .try_lock_owned() - .map_err(|_| MpcTlsError::other("VM lock is held"))?; - self.ctx - .try_join3( - move |ctx| { - Box::pin(async move { - ke.setup(ctx) - .await - .map(|_| ke) - .map_err(MpcTlsError::preprocess) - }) - }, - move |ctx| { - Box::pin(async move { - record_layer - .preprocess(ctx) - .await - .map(|_| record_layer) - .map_err(MpcTlsError::preprocess) - }) - }, - move |ctx| { - Box::pin(async move { - vm.preprocess(ctx).await.map_err(MpcTlsError::preprocess)?; - vm.flush(ctx).await.map_err(MpcTlsError::preprocess)?; - - Ok::<_, MpcTlsError>(()) - }) - }, - ) - .await - .map_err(MpcTlsError::preprocess)?? - }; - - self.state = State::Ready { - vm, - ke, - prf, - record_layer, - cf_vd, - sf_vd, - }; - + let session = self + .session + .take() + .ok_or_else(|| MpcTlsError::state("must be in setup state to preprocess"))?; + self.session = Some(session.preprocess().await?); Ok(()) } - /// Runs the follower. + /// Runs the follower, mirroring the leader's MPC operations until the + /// connection is closed, then committing and verifying the transcript. #[instrument(skip_all, err)] pub async fn run(mut self) -> Result<(Context, TlsTranscript), MpcTlsError> { - let State::Ready { - vm, - mut ke, - mut prf, - mut record_layer, - cf_vd: mut cf_vd_fut, - sf_vd: mut sf_vd_fut, - } = self.state.take() - else { - return Err(MpcTlsError::state("must be in ready state to run")); - }; + let mut session = self + .session + .take() + .ok_or_else(|| MpcTlsError::state("must be in setup state to run"))?; let mut client_random = None; let mut server_hello: Option = None; - let mut expected_cf_vd = None; - let mut expected_sf_vd = None; + let mut cf_vd_computed = false; + let mut sf_vd_computed = false; loop { - let msg: Message = self.ctx.io_mut().expect_next().await?; + let msg: Message = session.ctx_mut().io_mut().expect_next().await?; match msg { Message::SetClientRandom(random) => { if client_random.is_some() { return Err(MpcTlsError::hs("client random already set")); } - prf.set_client_random(random); + session.set_client_random(random); client_random = Some(random); } Message::ServerHello(hello) => { @@ -241,71 +157,35 @@ impl MpcTlsFollower { return Err(MpcTlsError::hs("handshake time difference exceeds limit")); } - prf.set_server_random(hello.random)?; - let NamedGroup::secp256r1 = hello.key.group else { return Err(MpcTlsError::hs("unsupported server key group")); }; - ke.set_server_key( - p256::PublicKey::from_sec1_bytes(&hello.key.key) - .map_err(|_| MpcTlsError::hs("failed to parse server key"))?, - )?; - - let mut vm = vm - .try_lock() - .map_err(|_| MpcTlsError::other("VM lock is held"))?; - - ke.compute_shares(&mut self.ctx).await?; - ke.assign(&mut (*vm))?; + let server_key = p256::PublicKey::from_sec1_bytes(&hello.key.key) + .map_err(|_| MpcTlsError::hs("failed to parse server key"))?; - flush_prf(&mut prf, &mut *vm, &mut self.ctx).await?; - - ke.finalize().await?; - record_layer.setup(&mut self.ctx).await?; + session.compute_keys(hello.random, server_key).await?; server_hello = Some(hello); } Message::ClientFinishedVd(handshake_hash) => { - if expected_cf_vd.is_some() { + if cf_vd_computed { return Err(MpcTlsError::hs("client finished VD already computed")); } - let mut vm = vm - .try_lock() - .map_err(|_| MpcTlsError::other("VM lock is held"))?; - - prf.set_cf_hash(handshake_hash)?; - flush_prf(&mut prf, &mut *vm, &mut self.ctx).await?; - - expected_cf_vd = Some( - cf_vd_fut - .try_recv() - .map_err(MpcTlsError::hs)? - .ok_or(MpcTlsError::hs("client finished VD not computed"))?, - ); + session.compute_cf_vd(handshake_hash).await?; + cf_vd_computed = true; } Message::ServerFinishedVd(handshake_hash) => { - if expected_sf_vd.is_some() { + if sf_vd_computed { return Err(MpcTlsError::hs("server finished VD already computed")); } - let mut vm = vm - .try_lock() - .map_err(|_| MpcTlsError::other("VM lock is held"))?; - - prf.set_sf_hash(handshake_hash)?; - flush_prf(&mut prf, &mut *vm, &mut self.ctx).await?; - - expected_sf_vd = Some( - sf_vd_fut - .try_recv() - .map_err(MpcTlsError::hs)? - .ok_or(MpcTlsError::hs("server finished VD not computed"))?, - ); + session.compute_sf_vd(handshake_hash).await?; + sf_vd_computed = true; } Message::Encrypt(encrypt) => { - record_layer.push_encrypt( + session.push_encrypt( encrypt.typ, encrypt.version, encrypt.len, @@ -313,7 +193,7 @@ impl MpcTlsFollower { )?; } Message::Decrypt(decrypt) => { - record_layer.push_decrypt( + session.push_decrypt( decrypt.typ, decrypt.version, decrypt.explicit_nonce, @@ -322,12 +202,10 @@ impl MpcTlsFollower { )?; } Message::StartTraffic => { - record_layer.start_traffic(); + session.start_traffic(); } Message::Flush { is_decrypting } => { - record_layer - .flush(&mut self.ctx, vm.clone(), is_decrypting) - .await?; + session.flush(is_decrypting).await?; debug!("flushed record layer"); } Message::CloseConnection => { @@ -337,17 +215,18 @@ impl MpcTlsFollower { } debug!("committing"); - - let (sent_records, recv_records) = record_layer.commit(&mut self.ctx, vm).await?; - + let (sent_records, recv_records) = session.commit().await?; debug!("committed"); + if !cf_vd_computed { + return Err(MpcTlsError::hs("client finished VD not computed")); + } + if !sf_vd_computed { + return Err(MpcTlsError::hs("server finished VD not computed")); + } + let server_hello = server_hello.ok_or(MpcTlsError::hs("server hello not set"))?; let client_random = client_random.ok_or(MpcTlsError::hs("client random not set"))?; - let expected_cf_vd = - expected_cf_vd.ok_or(MpcTlsError::hs("client finished VD not computed"))?; - let expected_sf_vd = - expected_sf_vd.ok_or(MpcTlsError::hs("server finished VD not computed"))?; let binding = CertBinding::V1_2(CertBindingV1_2 { client_random, @@ -367,51 +246,10 @@ impl MpcTlsFollower { .build() .map_err(MpcTlsError::other)?; - verify_transcript(&transcript, expected_cf_vd, expected_sf_vd)?; - - Ok((self.ctx, transcript)) - } -} + session.verify_transcript(&transcript)?; -enum State { - Init { - vm: Vm, - ke: Box, - prf: Prf, - record_layer: RecordLayer, - }, - Setup { - vm: Vm, - ke: Box, - prf: Prf, - record_layer: RecordLayer, - cf_vd: DecodeFutureTyped, - sf_vd: DecodeFutureTyped, - }, - Ready { - vm: Vm, - ke: Box, - prf: Prf, - record_layer: RecordLayer, - cf_vd: DecodeFutureTyped, - sf_vd: DecodeFutureTyped, - }, - Error, -} - -impl State { - fn take(&mut self) -> Self { - mem::replace(self, State::Error) - } -} + let (ctx, _record_layer) = session.into_closed(); -impl std::fmt::Debug for State { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(match self { - Self::Init { .. } => "Init", - Self::Setup { .. } => "Setup", - Self::Ready { .. } => "Ready", - Self::Error => "Error", - }) + Ok((ctx, transcript)) } } diff --git a/crates/mpc-tls/src/client/README.md b/crates/mpc-tls/src/handshake/README.md similarity index 100% rename from crates/mpc-tls/src/client/README.md rename to crates/mpc-tls/src/handshake/README.md diff --git a/crates/mpc-tls/src/client/check.rs b/crates/mpc-tls/src/handshake/check.rs similarity index 86% rename from crates/mpc-tls/src/client/check.rs rename to crates/mpc-tls/src/handshake/check.rs index bab141e2ab..af7aae0688 100644 --- a/crates/mpc-tls/src/client/check.rs +++ b/crates/mpc-tls/src/handshake/check.rs @@ -1,14 +1,14 @@ -use crate::client::error::Error; -use tracing::warn; +use crate::handshake::error::Error; use tls_core::msgs::{ enums::{ContentType, HandshakeType}, message::MessagePayload, }; +use tracing::warn; /// For a Message $m, and a HandshakePayload enum member $payload_type, /// return Ok(payload) if $m is both a handshake message and one that -/// has the given $payload_type. If not, return Err(crate::client::Error) quoting -/// $handshake_type as the expected handshake type. +/// has the given $payload_type. If not, return Err(crate::handshake::Error) +/// quoting $handshake_type as the expected handshake type. macro_rules! require_handshake_msg( ( $m:expr_2021, $handshake_type:path, $payload_type:path ) => ( match &$m.payload { @@ -16,7 +16,7 @@ macro_rules! require_handshake_msg( payload: $payload_type(hm), .. }) => Ok(hm), - payload => Err($crate::client::check::inappropriate_handshake_message( + payload => Err($crate::handshake::check::inappropriate_handshake_message( payload, &[::tls_core::msgs::enums::ContentType::Handshake], &[$handshake_type])) @@ -33,7 +33,7 @@ macro_rules! require_handshake_msg_move( .. }) => Ok(hm), payload => - Err($crate::client::check::inappropriate_handshake_message( + Err($crate::handshake::check::inappropriate_handshake_message( &payload, &[::tls_core::msgs::enums::ContentType::Handshake], &[$handshake_type])) diff --git a/crates/mpc-tls/src/client/config.rs b/crates/mpc-tls/src/handshake/config.rs similarity index 79% rename from crates/mpc-tls/src/client/config.rs rename to crates/mpc-tls/src/handshake/config.rs index ee99ffc16b..045ff4138b 100644 --- a/crates/mpc-tls/src/client/config.rs +++ b/crates/mpc-tls/src/handshake/config.rs @@ -1,21 +1,9 @@ -use async_trait::async_trait; - -use crate::client::hs; -use crate::MpcTlsLeader; -use crate::client::{ - anchors::RootCertStore, - conn::{ClientConnection, CommonState, State}, - error::Error, - sign, verify, -}; +use crate::handshake::{anchors::RootCertStore, error::Error, sign, verify}; use std::sync::Arc; pub use tls_core::dns::*; use tls_core::{ key, - msgs::{ - enums::{CipherSuite, ProtocolVersion, SignatureScheme}, - message::Message, - }, + msgs::enums::{CipherSuite, ProtocolVersion, SignatureScheme}, suites::{DEFAULT_CIPHER_SUITES, SupportedCipherSuite}, versions, }; @@ -55,8 +43,10 @@ pub trait ResolvesClientCert: Send + Sync { /// /// # Defaults /// -/// * [`ClientConfig::max_fragment_size`]: the default is `None`: TLS packets are not fragmented to a specific size. -/// * [`ClientConfig::alpn_protocols`]: the default is empty -- no ALPN protocol is negotiated. +/// * [`ClientConfig::max_fragment_size`]: the default is `None`: TLS packets +/// are not fragmented to a specific size. +/// * [`ClientConfig::alpn_protocols`]: the default is empty -- no ALPN protocol +/// is negotiated. #[derive(Clone)] pub struct ClientConfig { /// List of ciphersuites, in preference order. @@ -72,7 +62,8 @@ pub struct ClientConfig { /// rustls enforces an arbitrary minimum of 32 bytes for this field. /// Out of range values are reported as errors from ClientConnection::new. /// - /// Setting this value to the TCP MSS may improve latency for stream-y workloads. + /// Setting this value to the TCP MSS may improve latency for stream-y + /// workloads. pub max_fragment_size: Option, /// How to decide what client auth certificate/keys to use. @@ -157,45 +148,6 @@ impl ClientConfig { } } -struct Initialized { - server_name: ServerName, - config: Arc, -} - -#[async_trait] -impl State for Initialized { - async fn start(self: Box, cx: &mut CommonState) -> Result, Error> { - hs::start_handshake(self.server_name, self.config, cx).await - } - - async fn handle( - self: Box, - _cx: &mut CommonState, - _message: Message, - ) -> Result, Error> { - unreachable!() - } -} - -impl ClientConnection { - /// Make a new ClientConnection. `config` controls how - /// we behave in the TLS protocol, `name` is the - /// name of the server we want to talk to. - pub fn new( - config: Arc, - backend: MpcTlsLeader, - name: ServerName, - ) -> Result { - let common_state = CommonState::new(config.max_fragment_size, backend)?; - let state = Box::new(Initialized { - server_name: name, - config, - }); - - Ok(Self::new_inner(state, common_state)) - } -} - // --- Client certificate resolvers (formerly handy.rs) --- struct FailResolveClientCert {} @@ -217,10 +169,7 @@ impl ResolvesClientCert for FailResolveClientCert { struct AlwaysResolvesClientCert(Arc); impl AlwaysResolvesClientCert { - fn new( - chain: Vec, - priv_key: &key::PrivateKey, - ) -> Result { + fn new(chain: Vec, priv_key: &key::PrivateKey) -> Result { let key = sign::any_supported_type(priv_key) .map_err(|_| Error::General("invalid private key".into()))?; Ok(Self(Arc::new(sign::CertifiedKey::new(chain, key)))) diff --git a/crates/mpc-tls/src/client/error.rs b/crates/mpc-tls/src/handshake/error.rs similarity index 100% rename from crates/mpc-tls/src/client/error.rs rename to crates/mpc-tls/src/handshake/error.rs diff --git a/crates/mpc-tls/src/client/hash_hs.rs b/crates/mpc-tls/src/handshake/hash_hs.rs similarity index 96% rename from crates/mpc-tls/src/client/hash_hs.rs rename to crates/mpc-tls/src/handshake/hash_hs.rs index 890687f2bd..c07d22e667 100644 --- a/crates/mpc-tls/src/client/hash_hs.rs +++ b/crates/mpc-tls/src/handshake/hash_hs.rs @@ -10,9 +10,10 @@ use tls_core::{ /// Early stage buffering of handshake payloads. /// -/// Before we know the hash algorithm to use to verify the handshake, we just buffer the messages. -/// During the handshake, we may restart the transcript due to a HelloRetryRequest, reverting -/// from the `HandshakeHash` to a `HandshakeHashBuffer` again. +/// Before we know the hash algorithm to use to verify the handshake, we just +/// buffer the messages. During the handshake, we may restart the transcript due +/// to a HelloRetryRequest, reverting from the `HandshakeHash` to a +/// `HandshakeHashBuffer` again. pub(crate) struct HandshakeHashBuffer { buffer: Vec, client_auth_enabled: bool, @@ -133,7 +134,6 @@ impl HandshakeHash { pub(crate) fn take_handshake_buf(&mut self) -> Option> { self.client_auth.take() } - } #[cfg(test)] diff --git a/crates/mpc-tls/src/client/hs.rs b/crates/mpc-tls/src/handshake/hs.rs similarity index 76% rename from crates/mpc-tls/src/client/hs.rs rename to crates/mpc-tls/src/handshake/hs.rs index 9d9c59d7a3..778a4df0bf 100644 --- a/crates/mpc-tls/src/client/hs.rs +++ b/crates/mpc-tls/src/handshake/hs.rs @@ -1,10 +1,11 @@ -use tracing::{debug, trace}; -use crate::client::{ - check::inappropriate_handshake_message, - conn::{CommonState, ConnectionRandoms, State}, - error::Error, - hash_hs::HandshakeHashBuffer, +use crate::{ + conn::{Conn, ConnectionRandoms}, + handshake::{ + ClientConfig, ResolvesClientCert, ServerName, check::inappropriate_handshake_message, + error::Error, hash_hs::HandshakeHashBuffer, sign, tls12, tls13, + }, }; +use std::sync::Arc; use tls_core::{ key::PublicKey, msgs::{ @@ -15,26 +16,81 @@ use tls_core::{ handshake::{ CertificateStatusRequest, ClientExtension, ClientHelloPayload, ConvertProtocolNameList, DistinguishedNames, ECPointFormatList, HandshakeMessagePayload, HandshakePayload, - HasServerExtensions, HelloRetryRequest, ProtocolNameList, Random, SCTList, SessionID, - ServerExtension, SupportedPointFormats, + HasServerExtensions, HelloRetryRequest, ProtocolNameList, Random, SCTList, + ServerExtension, SessionID, SupportedPointFormats, }, message::{Message, MessagePayload}, }, suites::SupportedCipherSuite, }; +use tracing::{debug, trace}; -use super::tls12; -use crate::client::{ClientConfig, ResolvesClientCert, ServerName, sign, tls13}; -use async_trait::async_trait; -use std::sync::Arc; +pub(crate) use tls_core::cert::ServerCertDetails; + +/// The TLS handshake state machine. +/// +/// This replaces the boxed-trait-object state machine of upstream rustls with a +/// closed enum. Each variant carries the typed state for one step of the +/// handshake; [`Handshake::handle`] dispatches an incoming TLS message to the +/// current state, which returns the next state. Handlers operate directly on +/// the live connection ([`Live`]), reaching both the TLS framing state and the +/// MPC session through its inherent methods. +pub(crate) enum Handshake { + ExpectServerHello(Box), + ExpectServerHelloOrHelloRetryRequest(Box), + Tls12ExpectCertificate(Box), + Tls12ExpectCertificateStatusOrServerKx(Box), + Tls12ExpectServerKx(Box), + Tls12ExpectServerDoneOrCertReq(Box), + Tls12ExpectServerDone(Box), + Tls12ExpectCcs(Box), + Tls12ExpectFinished(Box), + Tls13ExpectEncryptedExtensions(Box), + Tls13ExpectCertificateOrCertReq(Box), + Tls13ExpectCertificate(Box), + Tls13ExpectCertificateVerify(Box), + Tls13ExpectFinished(Box), + /// Terminal signal: the handshake is complete. The connection driver + /// transitions to the online phase on seeing this; it is never dispatched a + /// message. + Complete, +} + +impl Handshake { + /// Dispatches an incoming TLS message to the current handshake state, + /// returning the next state. + pub(crate) async fn handle(self, cx: &mut Conn, m: Message) -> Result { + match self { + Handshake::ExpectServerHello(s) => s.handle(cx, m).await, + Handshake::ExpectServerHelloOrHelloRetryRequest(s) => s.handle(cx, m).await, + Handshake::Tls12ExpectCertificate(s) => s.handle(cx, m).await, + Handshake::Tls12ExpectCertificateStatusOrServerKx(s) => s.handle(cx, m).await, + Handshake::Tls12ExpectServerKx(s) => s.handle(cx, m).await, + Handshake::Tls12ExpectServerDoneOrCertReq(s) => s.handle(cx, m).await, + Handshake::Tls12ExpectServerDone(s) => s.handle(cx, m).await, + Handshake::Tls12ExpectCcs(s) => s.handle(cx, m).await, + Handshake::Tls12ExpectFinished(s) => s.handle(cx, m).await, + Handshake::Tls13ExpectEncryptedExtensions(s) => s.handle(cx, m).await, + Handshake::Tls13ExpectCertificateOrCertReq(s) => s.handle(cx, m).await, + Handshake::Tls13ExpectCertificate(s) => s.handle(cx, m).await, + Handshake::Tls13ExpectCertificateVerify(s) => s.handle(cx, m).await, + Handshake::Tls13ExpectFinished(s) => s.handle(cx, m).await, + Handshake::Complete => Err(Error::General( + "handshake state machine stepped after completion".to_string(), + )), + } + } +} -pub(super) type NextState = Box; -pub(super) type NextStateOrError = Result; +/// The next handshake state. +pub(crate) type NextState = Handshake; +/// The next handshake state, or a fatal error. +pub(crate) type NextStateOrError = Result; -pub(super) async fn start_handshake( +pub(crate) async fn start_handshake( server_name: ServerName, config: Arc, - cx: &mut CommonState, + cx: &mut Conn, ) -> NextStateOrError { let mut transcript_buffer = HandshakeHashBuffer::new(); if config.client_auth_cert_resolver.has_certs() { @@ -49,12 +105,12 @@ pub(super) async fn start_handshake( let support_tls13 = config.supports_version(ProtocolVersion::TLSv1_3); let key_share = if support_tls13 { - Some(cx.backend.client_key_share()?) + Some(cx.client_key_share()?) } else { None }; - let random = cx.backend.client_random()?; + let random = cx.client_random; let hello_details = ClientHelloDetails::new(); let sent_tls13_fake_ccs = false; let may_send_sct_list = config.verifier.request_scts(); @@ -75,7 +131,7 @@ pub(super) async fn start_handshake( .await } -struct ExpectServerHello { +pub(crate) struct ExpectServerHello { config: Arc, server_name: ServerName, random: Random, @@ -87,14 +143,14 @@ struct ExpectServerHello { suite: Option, } -struct ExpectServerHelloOrHelloRetryRequest { +pub(crate) struct ExpectServerHelloOrHelloRetryRequest { next: ExpectServerHello, } #[allow(clippy::too_many_arguments)] async fn emit_client_hello_for_retry( config: Arc, - cx: &mut CommonState, + cx: &mut Conn, random: Random, mut transcript_buffer: HandshakeHashBuffer, mut sent_tls13_fake_ccs: bool, @@ -221,20 +277,22 @@ async fn emit_client_hello_for_retry( }; if support_tls13 && retryreq.is_none() { - Ok(Box::new(ExpectServerHelloOrHelloRetryRequest { next })) + Ok(Handshake::ExpectServerHelloOrHelloRetryRequest(Box::new( + ExpectServerHelloOrHelloRetryRequest { next }, + ))) } else { - Ok(Box::new(next)) + Ok(Handshake::ExpectServerHello(Box::new(next))) } } -pub(super) async fn process_alpn_protocol( - common: &mut CommonState, +pub(crate) async fn process_alpn_protocol( + common: &mut Conn, config: &ClientConfig, proto: Option<&[u8]>, ) -> Result<(), Error> { - common.alpn_protocol = proto.map(ToOwned::to_owned); + common.io.alpn_protocol = proto.map(ToOwned::to_owned); - if let Some(alpn_protocol) = &common.alpn_protocol + if let Some(alpn_protocol) = &common.io.alpn_protocol && !config.alpn_protocols.contains(alpn_protocol) { return Err(common @@ -245,6 +303,7 @@ pub(super) async fn process_alpn_protocol( debug!( "ALPN protocol is {:?}", common + .io .alpn_protocol .as_ref() .map(|v| String::from_utf8_lossy(v)) @@ -252,17 +311,12 @@ pub(super) async fn process_alpn_protocol( Ok(()) } -pub(super) fn sct_list_is_invalid(scts: &SCTList) -> bool { +pub(crate) fn sct_list_is_invalid(scts: &SCTList) -> bool { scts.is_empty() || scts.iter().any(|sct| sct.0.is_empty()) } -#[async_trait] -impl State for ExpectServerHello { - async fn handle( - mut self: Box, - cx: &mut CommonState, - m: Message, - ) -> NextStateOrError { +impl ExpectServerHello { + pub(crate) async fn handle(mut self: Box, cx: &mut Conn, m: Message) -> NextStateOrError { let server_hello = require_handshake_msg!(m, HandshakeType::ServerHello, HandshakePayload::ServerHello)?; trace!("We got ServerHello {:#?}", server_hello); @@ -290,8 +344,7 @@ impl State for ExpectServerHello { TLSv1_2 } _ => { - cx - .send_fatal_alert(AlertDescription::ProtocolVersion) + cx.send_fatal_alert(AlertDescription::ProtocolVersion) .await?; let msg = match server_version { TLSv1_2 | TLSv1_3 => "server's TLS version is disabled in client", @@ -308,9 +361,7 @@ impl State for ExpectServerHello { } if server_hello.has_duplicate_extension() { - cx - .send_fatal_alert(AlertDescription::DecodeError) - .await?; + cx.send_fatal_alert(AlertDescription::DecodeError).await?; return Err(Error::PeerMisbehavedError( "server sent duplicate extensions".to_string(), )); @@ -321,20 +372,18 @@ impl State for ExpectServerHello { .hello .server_sent_unsolicited_extensions(&server_hello.extensions, &allowed_unsolicited) { - cx - .send_fatal_alert(AlertDescription::UnsupportedExtension) + cx.send_fatal_alert(AlertDescription::UnsupportedExtension) .await?; return Err(Error::PeerMisbehavedError( "server sent unsolicited extension".to_string(), )); } - cx.negotiated_version = Some(version); + cx.io.negotiated_version = Some(version); // Extract ALPN protocol - if !cx.is_tls13() { - process_alpn_protocol(cx, &self.config, server_hello.get_alpn_protocol()) - .await?; + if !cx.io.is_tls13() { + process_alpn_protocol(cx, &self.config, server_hello.get_alpn_protocol()).await?; } // If ECPointFormats extension is supplied by the server, it must contain @@ -352,8 +401,7 @@ impl State for ExpectServerHello { let suite = match self.config.find_cipher_suite(server_hello.cipher_suite) { Some(suite) => suite, None => { - cx - .send_fatal_alert(AlertDescription::HandshakeFailure) + cx.send_fatal_alert(AlertDescription::HandshakeFailure) .await?; return Err(Error::PeerMisbehavedError( "server chose non-offered ciphersuite".to_string(), @@ -376,7 +424,7 @@ impl State for ExpectServerHello { _ => { debug!("Using ciphersuite {:?}", suite); self.suite = Some(suite); - cx.suite = Some(suite); + cx.io.suite = Some(suite); } } @@ -417,15 +465,11 @@ impl State for ExpectServerHello { } impl ExpectServerHelloOrHelloRetryRequest { - fn into_expect_server_hello(self) -> NextState { + fn into_expect_server_hello(self) -> Box { Box::new(self.next) } - async fn handle_hello_retry_request( - self, - cx: &mut CommonState, - m: Message, - ) -> NextStateOrError { + async fn handle_hello_retry_request(self, cx: &mut Conn, m: Message) -> NextStateOrError { let hrr = require_handshake_msg!( m, HandshakeType::HelloRetryRequest, @@ -460,8 +504,7 @@ impl ExpectServerHelloOrHelloRetryRequest { // Or has something unrecognised if hrr.has_unknown_extension() { - cx - .send_fatal_alert(AlertDescription::UnsupportedExtension) + cx.send_fatal_alert(AlertDescription::UnsupportedExtension) .await?; return Err(Error::PeerIncompatibleError( "server sent hrr with unhandled extension".to_string(), @@ -485,7 +528,7 @@ impl ExpectServerHelloOrHelloRetryRequest { // Or asks us to talk a protocol we didn't offer, or doesn't support HRR at all. match hrr.get_supported_versions() { Some(ProtocolVersion::TLSv1_3) => { - cx.negotiated_version = Some(ProtocolVersion::TLSv1_3); + cx.io.negotiated_version = Some(ProtocolVersion::TLSv1_3); } _ => { return Err(cx @@ -506,7 +549,7 @@ impl ExpectServerHelloOrHelloRetryRequest { }; // HRR selects the ciphersuite. - cx.suite = Some(cs); + cx.io.suite = Some(cs); // This is the draft19 change where the transcript became a tree let transcript = self.next.transcript_buffer.start_hash(cs.hash_algorithm()); @@ -541,11 +584,8 @@ impl ExpectServerHelloOrHelloRetryRequest { ) .await } -} -#[async_trait] -impl State for ExpectServerHelloOrHelloRetryRequest { - async fn handle(self: Box, cx: &mut CommonState, m: Message) -> NextStateOrError { + pub(crate) async fn handle(self: Box, cx: &mut Conn, m: Message) -> NextStateOrError { match m.payload { MessagePayload::Handshake(HandshakeMessagePayload { payload: HandshakePayload::ServerHello(..), @@ -564,10 +604,7 @@ impl State for ExpectServerHelloOrHelloRetryRequest { } } -pub(super) async fn send_cert_error_alert( - common: &mut CommonState, - err: Error, -) -> Result { +pub(crate) async fn send_cert_error_alert(common: &mut Conn, err: Error) -> Result { match err { Error::PeerMisbehavedError(_) => { common @@ -586,24 +623,22 @@ pub(super) async fn send_cert_error_alert( // --- Handshake details (formerly common.rs) --- -pub(crate) use tls_core::cert::ServerCertDetails; - -pub(super) struct ClientHelloDetails { - pub(super) sent_extensions: Vec, +pub(crate) struct ClientHelloDetails { + pub(crate) sent_extensions: Vec, } impl ClientHelloDetails { - pub(super) fn new() -> Self { + pub(crate) fn new() -> Self { Self { sent_extensions: Vec::new(), } } - pub(super) fn server_may_send_sct_list(&self) -> bool { + pub(crate) fn server_may_send_sct_list(&self) -> bool { self.sent_extensions.contains(&ExtensionType::SCT) } - pub(super) fn server_sent_unsolicited_extensions( + pub(crate) fn server_sent_unsolicited_extensions( &self, received_exts: &[ServerExtension], allowed_unsolicited: &[ExtensionType], @@ -621,7 +656,7 @@ impl ClientHelloDetails { } } -pub(super) enum ClientAuthDetails { +pub(crate) enum ClientAuthDetails { /// Send an empty `Certificate` and no `CertificateVerify`. Empty { auth_context_tls13: Option> }, /// Send a non-empty `Certificate` and a `CertificateVerify`. @@ -633,7 +668,7 @@ pub(super) enum ClientAuthDetails { } impl ClientAuthDetails { - pub(super) fn resolve( + pub(crate) fn resolve( resolver: &dyn ResolvesClientCert, canames: Option<&DistinguishedNames>, sigschemes: &[SignatureScheme], diff --git a/crates/mpc-tls/src/handshake/mod.rs b/crates/mpc-tls/src/handshake/mod.rs new file mode 100644 index 0000000000..6806dc42c9 --- /dev/null +++ b/crates/mpc-tls/src/handshake/mod.rs @@ -0,0 +1,40 @@ +//! TLS handshake protocol, forked from [rustls](https://github.com/rustls/rustls) +//! version 0.20. +//! +//! This module provides everything needed to perform the TLS handshake under +//! the [`MpcTlsLeader`](crate::MpcTlsLeader): the handshake state machine +//! ([`hs`], [`tls12`], [`tls13`]), client configuration, certificate +//! verification and message signing. Unlike upstream rustls the client performs +//! no cryptographic operations itself — the key exchange, the PRF and record +//! encryption/decryption are delegated to the MPC session owned by the leader. +//! The state machine operates directly on the live connection +//! ([`crate::conn`]'s `Conn`). Post-handshake (online) message routing lives on +//! [`Conn`](crate::conn::Conn), not here. +//! +//! Only TLS 1.2 cipher suites are currently enabled. The TLS 1.3 message +//! handling inherited from upstream is retained for future use, but is +//! unreachable as long as [`tls_core::versions::ALL_VERSIONS`] excludes +//! TLS 1.3. + +#[macro_use] +mod check; +mod config; +pub(crate) mod error; +pub(crate) mod hash_hs; +pub(crate) mod hs; +pub(crate) mod tls12; +pub(crate) mod tls13; + +pub(crate) use tls_core::{anchors, verify, x509}; + +// Public TLS-policy types. These are re-exported at the crate root, which is +// the public path; see `crate::lib`. +pub use crate::handshake::{ + anchors::RootCertStore, + config::{ClientConfig, ResolvesClientCert, ServerName}, + error::Error, +}; +pub use tls_core::key::{Certificate, PrivateKey}; + +/// Message signing interfaces and implementations. +pub mod sign; diff --git a/crates/mpc-tls/src/client/sign.rs b/crates/mpc-tls/src/handshake/sign.rs similarity index 99% rename from crates/mpc-tls/src/client/sign.rs rename to crates/mpc-tls/src/handshake/sign.rs index d659dd5781..1188f2a974 100644 --- a/crates/mpc-tls/src/client/sign.rs +++ b/crates/mpc-tls/src/handshake/sign.rs @@ -1,4 +1,4 @@ -use crate::client::{ +use crate::handshake::{ error::Error, x509::{wrap_in_asn1_len, wrap_in_sequence}, }; diff --git a/crates/mpc-tls/src/client/testdata/eddsakey.der b/crates/mpc-tls/src/handshake/testdata/eddsakey.der similarity index 100% rename from crates/mpc-tls/src/client/testdata/eddsakey.der rename to crates/mpc-tls/src/handshake/testdata/eddsakey.der diff --git a/crates/mpc-tls/src/client/testdata/nistp256key.der b/crates/mpc-tls/src/handshake/testdata/nistp256key.der similarity index 100% rename from crates/mpc-tls/src/client/testdata/nistp256key.der rename to crates/mpc-tls/src/handshake/testdata/nistp256key.der diff --git a/crates/mpc-tls/src/client/testdata/nistp256key.pkcs8.der b/crates/mpc-tls/src/handshake/testdata/nistp256key.pkcs8.der similarity index 100% rename from crates/mpc-tls/src/client/testdata/nistp256key.pkcs8.der rename to crates/mpc-tls/src/handshake/testdata/nistp256key.pkcs8.der diff --git a/crates/mpc-tls/src/client/testdata/nistp384key.der b/crates/mpc-tls/src/handshake/testdata/nistp384key.der similarity index 100% rename from crates/mpc-tls/src/client/testdata/nistp384key.der rename to crates/mpc-tls/src/handshake/testdata/nistp384key.der diff --git a/crates/mpc-tls/src/client/testdata/nistp384key.pkcs8.der b/crates/mpc-tls/src/handshake/testdata/nistp384key.pkcs8.der similarity index 100% rename from crates/mpc-tls/src/client/testdata/nistp384key.pkcs8.der rename to crates/mpc-tls/src/handshake/testdata/nistp384key.pkcs8.der diff --git a/crates/mpc-tls/src/client/testdata/rsa2048key.pkcs1.der b/crates/mpc-tls/src/handshake/testdata/rsa2048key.pkcs1.der similarity index 100% rename from crates/mpc-tls/src/client/testdata/rsa2048key.pkcs1.der rename to crates/mpc-tls/src/handshake/testdata/rsa2048key.pkcs1.der diff --git a/crates/mpc-tls/src/client/testdata/rsa2048key.pkcs8.der b/crates/mpc-tls/src/handshake/testdata/rsa2048key.pkcs8.der similarity index 100% rename from crates/mpc-tls/src/client/testdata/rsa2048key.pkcs8.der rename to crates/mpc-tls/src/handshake/testdata/rsa2048key.pkcs8.der diff --git a/crates/mpc-tls/src/client/tls12.rs b/crates/mpc-tls/src/handshake/tls12.rs similarity index 77% rename from crates/mpc-tls/src/client/tls12.rs rename to crates/mpc-tls/src/handshake/tls12.rs index d96ae1bbfb..a23a942f67 100644 --- a/crates/mpc-tls/src/client/tls12.rs +++ b/crates/mpc-tls/src/handshake/tls12.rs @@ -1,19 +1,18 @@ -use tracing::{debug, trace}; -use crate::client::{ - ClientConfig, ServerName, - check::{inappropriate_handshake_message, inappropriate_message}, - conn::{CommonState, ConnectionRandoms, State}, - error::Error, - hash_hs::HandshakeHash, - hs::{self, ClientAuthDetails, ServerCertDetails}, - sign::Signer, - verify, +use crate::{ + conn::{Conn, ConnectionRandoms, HandshakeData}, + handshake::{ + ClientConfig, ServerName, + check::{inappropriate_handshake_message, inappropriate_message}, + error::Error, + hash_hs::HandshakeHash, + hs::{self, ClientAuthDetails, Handshake, ServerCertDetails}, + sign::Signer, + verify, + }, }; -use async_trait::async_trait; #[allow(deprecated)] use ring::constant_time; use std::sync::Arc; -use crate::leader::HandshakeData; use tls_core::{ ke::ServerKxDetails, key::PublicKey, @@ -28,10 +27,11 @@ use tls_core::{ }, message::{Message, MessagePayload}, }, - suites::{tls12, SupportedCipherSuite, Tls12CipherSuite}, + suites::{SupportedCipherSuite, Tls12CipherSuite, tls12}, }; +use tracing::{debug, trace}; -pub(super) use server_hello::CompleteServerHelloHandling; +pub(crate) use server_hello::CompleteServerHelloHandling; mod server_hello { use tls_core::msgs::{ @@ -41,17 +41,17 @@ mod server_hello { use super::*; - pub(in crate::client) struct CompleteServerHelloHandling { - pub(in crate::client) config: Arc, - pub(in crate::client) server_name: ServerName, - pub(in crate::client) randoms: ConnectionRandoms, - pub(in crate::client) transcript: HandshakeHash, + pub(crate) struct CompleteServerHelloHandling { + pub(crate) config: Arc, + pub(crate) server_name: ServerName, + pub(crate) randoms: ConnectionRandoms, + pub(crate) transcript: HandshakeHash, } impl CompleteServerHelloHandling { - pub(in crate::client) async fn handle_server_hello( + pub(crate) async fn handle_server_hello( mut self, - cx: &mut CommonState, + cx: &mut Conn, suite: &'static Tls12CipherSuite, server_hello: &ServerHelloPayload, tls13_supported: bool, @@ -90,34 +90,35 @@ mod server_hello { None }; - Ok(Box::new(ExpectCertificate { - config: self.config, - server_name: self.server_name, - randoms: self.randoms, - transcript: self.transcript, - suite, - may_send_cert_status, - server_cert_sct_list, - })) + Ok(Handshake::Tls12ExpectCertificate(Box::new( + ExpectCertificate { + config: self.config, + server_name: self.server_name, + randoms: self.randoms, + transcript: self.transcript, + suite, + may_send_cert_status, + server_cert_sct_list, + }, + ))) } } } -struct ExpectCertificate { +pub(crate) struct ExpectCertificate { config: Arc, server_name: ServerName, randoms: ConnectionRandoms, transcript: HandshakeHash, - pub(super) suite: &'static Tls12CipherSuite, + pub(crate) suite: &'static Tls12CipherSuite, may_send_cert_status: bool, server_cert_sct_list: Option, } -#[async_trait] -impl State for ExpectCertificate { - async fn handle( +impl ExpectCertificate { + pub(crate) async fn handle( mut self: Box, - _cx: &mut CommonState, + _cx: &mut Conn, m: Message, ) -> hs::NextStateOrError { self.transcript.add_message(&m); @@ -128,32 +129,34 @@ impl State for ExpectCertificate { )?; if self.may_send_cert_status { - Ok(Box::new(ExpectCertificateStatusOrServerKx { - config: self.config, - server_name: self.server_name, - randoms: self.randoms, - transcript: self.transcript, - suite: self.suite, - server_cert_sct_list: self.server_cert_sct_list, - server_cert_chain, - })) + Ok(Handshake::Tls12ExpectCertificateStatusOrServerKx(Box::new( + ExpectCertificateStatusOrServerKx { + config: self.config, + server_name: self.server_name, + randoms: self.randoms, + transcript: self.transcript, + suite: self.suite, + server_cert_sct_list: self.server_cert_sct_list, + server_cert_chain, + }, + ))) } else { let server_cert = ServerCertDetails::new(server_cert_chain, vec![], self.server_cert_sct_list); - Ok(Box::new(ExpectServerKx { + Ok(Handshake::Tls12ExpectServerKx(Box::new(ExpectServerKx { config: self.config, server_name: self.server_name, randoms: self.randoms, transcript: self.transcript, suite: self.suite, server_cert, - })) + }))) } } } -struct ExpectCertificateStatusOrServerKx { +pub(crate) struct ExpectCertificateStatusOrServerKx { config: Arc, server_name: ServerName, randoms: ConnectionRandoms, @@ -163,13 +166,8 @@ struct ExpectCertificateStatusOrServerKx { server_cert_chain: CertificatePayload, } -#[async_trait] -impl State for ExpectCertificateStatusOrServerKx { - async fn handle( - self: Box, - cx: &mut CommonState, - m: Message, - ) -> hs::NextStateOrError { +impl ExpectCertificateStatusOrServerKx { + pub(crate) async fn handle(self: Box, cx: &mut Conn, m: Message) -> hs::NextStateOrError { match m.payload { MessagePayload::Handshake(HandshakeMessagePayload { payload: HandshakePayload::ServerKeyExchange(..), @@ -220,7 +218,7 @@ impl State for ExpectCertificateStatusOrServerKx { } } -struct ExpectCertificateStatus { +pub(crate) struct ExpectCertificateStatus { config: Arc, server_name: ServerName, randoms: ConnectionRandoms, @@ -230,11 +228,10 @@ struct ExpectCertificateStatus { server_cert_chain: CertificatePayload, } -#[async_trait] -impl State for ExpectCertificateStatus { - async fn handle( +impl ExpectCertificateStatus { + pub(crate) async fn handle( mut self: Box, - _cx: &mut CommonState, + _cx: &mut Conn, m: Message, ) -> hs::NextStateOrError { self.transcript.add_message(&m); @@ -256,18 +253,18 @@ impl State for ExpectCertificateStatus { self.server_cert_sct_list, ); - Ok(Box::new(ExpectServerKx { + Ok(Handshake::Tls12ExpectServerKx(Box::new(ExpectServerKx { config: self.config, server_name: self.server_name, randoms: self.randoms, transcript: self.transcript, suite: self.suite, server_cert, - })) + }))) } } -struct ExpectServerKx { +pub(crate) struct ExpectServerKx { config: Arc, server_name: ServerName, randoms: ConnectionRandoms, @@ -276,11 +273,10 @@ struct ExpectServerKx { server_cert: ServerCertDetails, } -#[async_trait] -impl State for ExpectServerKx { - async fn handle( +impl ExpectServerKx { + pub(crate) async fn handle( mut self: Box, - cx: &mut CommonState, + cx: &mut Conn, m: Message, ) -> hs::NextStateOrError { let opaque_kx = require_handshake_msg!( @@ -294,9 +290,7 @@ impl State for ExpectServerKx { Some(ecdhe) => ecdhe, None => { // We only support ECDHE - cx - .send_fatal_alert(AlertDescription::DecodeError) - .await?; + cx.send_fatal_alert(AlertDescription::DecodeError).await?; return Err(Error::CorruptMessagePayload(ContentType::Handshake)); } }; @@ -310,22 +304,24 @@ impl State for ExpectServerKx { debug!("ECDHE curve is {:?}", ecdhe.params.curve_params); } - Ok(Box::new(ExpectServerDoneOrCertReq { - config: self.config, - server_name: self.server_name, - randoms: self.randoms, - transcript: self.transcript, - suite: self.suite, - server_cert: self.server_cert, - server_kx, - })) + Ok(Handshake::Tls12ExpectServerDoneOrCertReq(Box::new( + ExpectServerDoneOrCertReq { + config: self.config, + server_name: self.server_name, + randoms: self.randoms, + transcript: self.transcript, + suite: self.suite, + server_cert: self.server_cert, + server_kx, + }, + ))) } } async fn emit_certificate( transcript: &mut HandshakeHash, cert_chain: CertificatePayload, - common: &mut CommonState, + common: &mut Conn, ) -> Result<(), Error> { let cert = Message { version: ProtocolVersion::TLSv1_2, @@ -341,7 +337,7 @@ async fn emit_certificate( async fn emit_clientkx( transcript: &mut HandshakeHash, - common: &mut CommonState, + common: &mut Conn, pubkey: &PublicKey, ) -> Result<(), Error> { let ecpoint = PayloadU8::new(pubkey.key.clone()); @@ -365,7 +361,7 @@ async fn emit_clientkx( async fn emit_certverify( transcript: &mut HandshakeHash, signer: &dyn Signer, - common: &mut CommonState, + common: &mut Conn, ) -> Result<(), Error> { let message = transcript .take_handshake_buf() @@ -387,7 +383,7 @@ async fn emit_certverify( common.send_msg(m, false).await } -async fn emit_ccs(common: &mut CommonState) -> Result<(), Error> { +async fn emit_ccs(common: &mut Conn) -> Result<(), Error> { let ccs = Message { version: ProtocolVersion::TLSv1_2, payload: MessagePayload::ChangeCipherSpec(ChangeCipherSpecPayload {}), @@ -399,7 +395,7 @@ async fn emit_ccs(common: &mut CommonState) -> Result<(), Error> { async fn emit_finished( verify_data: &[u8], transcript: &mut HandshakeHash, - common: &mut CommonState, + common: &mut Conn, ) -> Result<(), Error> { let verify_data_payload = Payload::new(verify_data); @@ -418,7 +414,7 @@ async fn emit_finished( // --- Either a CertificateRequest, or a ServerHelloDone. --- // Existence of the CertificateRequest tells us the server is asking for // client auth. Otherwise we go straight to ServerHelloDone. -struct ExpectServerDoneOrCertReq { +pub(crate) struct ExpectServerDoneOrCertReq { config: Arc, server_name: ServerName, randoms: ConnectionRandoms, @@ -428,11 +424,10 @@ struct ExpectServerDoneOrCertReq { server_kx: ServerKxDetails, } -#[async_trait] -impl State for ExpectServerDoneOrCertReq { - async fn handle( +impl ExpectServerDoneOrCertReq { + pub(crate) async fn handle( mut self: Box, - cx: &mut CommonState, + cx: &mut Conn, m: Message, ) -> hs::NextStateOrError { if matches!( @@ -472,7 +467,7 @@ impl State for ExpectServerDoneOrCertReq { } } -struct ExpectCertificateRequest { +pub(crate) struct ExpectCertificateRequest { config: Arc, server_name: ServerName, randoms: ConnectionRandoms, @@ -482,11 +477,10 @@ struct ExpectCertificateRequest { server_kx: ServerKxDetails, } -#[async_trait] -impl State for ExpectCertificateRequest { - async fn handle( +impl ExpectCertificateRequest { + pub(crate) async fn handle( mut self: Box, - _cx: &mut CommonState, + _cx: &mut Conn, m: Message, ) -> hs::NextStateOrError { let certreq = require_handshake_msg!( @@ -511,20 +505,22 @@ impl State for ExpectCertificateRequest { NO_CONTEXT, ); - Ok(Box::new(ExpectServerDone { - config: self.config, - server_name: self.server_name, - randoms: self.randoms, - transcript: self.transcript, - suite: self.suite, - server_cert: self.server_cert, - server_kx: self.server_kx, - client_auth: Some(client_auth), - })) + Ok(Handshake::Tls12ExpectServerDone(Box::new( + ExpectServerDone { + config: self.config, + server_name: self.server_name, + randoms: self.randoms, + transcript: self.transcript, + suite: self.suite, + server_cert: self.server_cert, + server_kx: self.server_kx, + client_auth: Some(client_auth), + }, + ))) } } -struct ExpectServerDone { +pub(crate) struct ExpectServerDone { config: Arc, server_name: ServerName, randoms: ConnectionRandoms, @@ -535,13 +531,8 @@ struct ExpectServerDone { client_auth: Option, } -#[async_trait] -impl State for ExpectServerDone { - async fn handle( - self: Box, - cx: &mut CommonState, - m: Message, - ) -> hs::NextStateOrError { +impl ExpectServerDone { + pub(crate) async fn handle(self: Box, cx: &mut Conn, m: Message) -> hs::NextStateOrError { match m.payload { MessagePayload::Handshake(HandshakeMessagePayload { payload: HandshakePayload::ServerHelloDone, @@ -631,9 +622,7 @@ impl State for ExpectServerDone { sig, ) { Ok(sig_verified) => sig_verified, - Err(e) => { - return Err(hs::send_cert_error_alert(cx, Error::CoreError(e)).await?) - } + Err(e) => return Err(hs::send_cert_error_alert(cx, Error::CoreError(e)).await?), } }; // 4. @@ -650,14 +639,12 @@ impl State for ExpectServerDone { match tls12::decode_ecdh_params::(st.server_kx.kx_params()) { Some(ecdh_params) => ecdh_params, None => { - cx - .send_fatal_alert(AlertDescription::DecodeError) - .await?; + cx.send_fatal_alert(AlertDescription::DecodeError).await?; return Err(Error::CorruptMessagePayload(ContentType::Handshake)); } }; - let key_share = cx.backend.client_key_share()?; + let key_share = cx.client_key_share()?; if key_share.group != ecdh_params.curve_params.named_group { return Err(Error::PeerMisbehavedError( "peer chose an unsupported group".to_string(), @@ -680,46 +667,37 @@ impl State for ExpectServerDone { let server_key_share = PublicKey::new(ecdh_params.curve_params.named_group, &ecdh_params.public.0); - cx.backend - .prepare_encryption(HandshakeData { - server_random: Random(st.randoms.server), - server_key: server_key_share, - server_cert_details: st.server_cert, - server_kx_details: st.server_kx, - }) - .await?; - cx.start_encrypting(); + cx.prepare_encryption(HandshakeData { + server_random: Random(st.randoms.server), + server_key: server_key_share, + server_cert_details: st.server_cert, + server_kx_details: st.server_kx, + }) + .await?; + cx.io.start_encrypting(); // 6. let hs = transcript.get_current_hash(); - let cf = cx - .backend - .get_client_finished_vd(hs.as_ref().to_vec()) - .await?; + let cf = cx.get_client_finished_vd(hs.as_ref().to_vec()).await?; emit_finished(&cf, &mut transcript, cx).await?; - Ok(Box::new(ExpectCcs { + Ok(Handshake::Tls12ExpectCcs(Box::new(ExpectCcs { transcript, cert_verified, sig_verified, - })) + }))) } } // -- Waiting for their CCS -- -struct ExpectCcs { +pub(crate) struct ExpectCcs { transcript: HandshakeHash, cert_verified: verify::ServerCertVerified, sig_verified: verify::HandshakeSignatureValid, } -#[async_trait] -impl State for ExpectCcs { - async fn handle( - self: Box, - cx: &mut CommonState, - m: Message, - ) -> hs::NextStateOrError { +impl ExpectCcs { + pub(crate) async fn handle(self: Box, cx: &mut Conn, m: Message) -> hs::NextStateOrError { match m.payload { MessagePayload::ChangeCipherSpec(..) => {} payload => { @@ -734,29 +712,24 @@ impl State for ExpectCcs { cx.check_aligned_handshake().await?; // nb. msgs layer validates trivial contents of CCS - cx.start_decrypting(); + cx.io.start_decrypting(); - Ok(Box::new(ExpectFinished { + Ok(Handshake::Tls12ExpectFinished(Box::new(ExpectFinished { transcript: self.transcript, cert_verified: self.cert_verified, sig_verified: self.sig_verified, - })) + }))) } } -struct ExpectFinished { +pub(crate) struct ExpectFinished { transcript: HandshakeHash, cert_verified: verify::ServerCertVerified, sig_verified: verify::HandshakeSignatureValid, } -#[async_trait] -impl State for ExpectFinished { - async fn handle( - self: Box, - cx: &mut CommonState, - m: Message, - ) -> hs::NextStateOrError { +impl ExpectFinished { + pub(crate) async fn handle(self: Box, cx: &mut Conn, m: Message) -> hs::NextStateOrError { let mut st = *self; let finished = require_handshake_msg!(m, HandshakeType::Finished, HandshakePayload::Finished)?; @@ -765,10 +738,7 @@ impl State for ExpectFinished { // Work out what verify_data we expect. let vh = st.transcript.get_current_hash(); - let expect_verify_data = cx - .backend - .get_server_finished_vd(vh.as_ref().to_vec()) - .await?; + let expect_verify_data = cx.get_server_finished_vd(vh.as_ref().to_vec()).await?; // Constant-time verification of this is relatively unimportant: they only // get one chance. But it can't hurt. @@ -777,9 +747,7 @@ impl State for ExpectFinished { match constant_time::verify_slices_are_equal(&expect_verify_data, &finished.0) { Ok(()) => verify::FinishedMessageVerified::assertion(), Err(_) => { - cx - .send_fatal_alert(AlertDescription::DecryptError) - .await?; + cx.send_fatal_alert(AlertDescription::DecryptError).await?; return Err(Error::DecryptError); } }; @@ -787,38 +755,11 @@ impl State for ExpectFinished { // Hash this message too. st.transcript.add_message(&m); - cx.start_traffic().await?; - Ok(Box::new(ExpectTraffic { - _cert_verified: st.cert_verified, - _sig_verified: st.sig_verified, - _fin_verified, - })) - } -} - -// -- Traffic transit state -- -struct ExpectTraffic { - _cert_verified: verify::ServerCertVerified, - _sig_verified: verify::HandshakeSignatureValid, - _fin_verified: verify::FinishedMessageVerified, -} + // The certificate, signature and Finished verifications are complete. + // Their proof tokens are not threaded further now that the handshake + // ends; the connection driver moves to the online phase on `Complete`. + let _ = (st.cert_verified, st.sig_verified, _fin_verified); -#[async_trait] -impl State for ExpectTraffic { - async fn handle( - self: Box, - cx: &mut CommonState, - m: Message, - ) -> hs::NextStateOrError { - match m.payload { - MessagePayload::ApplicationData(payload) => cx.take_received_plaintext(payload), - payload => { - return Err(inappropriate_message( - &payload, - &[ContentType::ApplicationData], - )); - } - } - Ok(self) + Ok(Handshake::Complete) } } diff --git a/crates/mpc-tls/src/client/tls13.rs b/crates/mpc-tls/src/handshake/tls13.rs similarity index 72% rename from crates/mpc-tls/src/client/tls13.rs rename to crates/mpc-tls/src/handshake/tls13.rs index c60e0d624a..4e8d24542d 100644 --- a/crates/mpc-tls/src/client/tls13.rs +++ b/crates/mpc-tls/src/handshake/tls13.rs @@ -1,36 +1,35 @@ -use tracing::{debug, trace, warn}; -use crate::client::{ - ClientConfig, ServerName, - check::inappropriate_handshake_message, - conn::{CommonState, State}, - error::Error, - hash_hs::HandshakeHash, - hs::{self, ClientAuthDetails, ClientHelloDetails, ServerCertDetails}, - sign, verify, +use crate::{ + conn::Conn, + handshake::{ + ClientConfig, ServerName, + check::inappropriate_handshake_message, + error::Error, + hash_hs::HandshakeHash, + hs::{self, ClientAuthDetails, ClientHelloDetails, Handshake, ServerCertDetails}, + sign::{self, CertifiedKey, Signer}, + verify, + }, }; #[allow(deprecated)] use ring::constant_time; +use std::sync::Arc; use tls_core::{ key::PublicKey, msgs::{ base::{Payload, PayloadU8}, ccs::ChangeCipherSpecPayload, enums::{ - AlertDescription, ContentType, ExtensionType, HandshakeType, KeyUpdateRequest, - ProtocolVersion, SignatureScheme, + AlertDescription, ContentType, ExtensionType, HandshakeType, ProtocolVersion, + SignatureScheme, }, handshake::{ CertificateEntry, CertificatePayloadTLS13, DigitallySignedStruct, EncryptedExtensions, - HandshakeMessagePayload, HandshakePayload, HasServerExtensions, - NewSessionTicketPayloadTLS13, ServerHelloPayload, + HandshakeMessagePayload, HandshakePayload, HasServerExtensions, ServerHelloPayload, }, message::{Message, MessagePayload}, }, }; - -use crate::client::sign::{CertifiedKey, Signer}; -use async_trait::async_trait; -use std::sync::Arc; +use tracing::{debug, trace, warn}; // Extensions we expect in plaintext in the ServerHello. static ALLOWED_PLAINTEXT_EXTS: &[ExtensionType] = &[ @@ -58,9 +57,9 @@ fn unsupported() -> Result<(), Error> { } #[allow(clippy::too_many_arguments)] -pub(super) async fn handle_server_hello( +pub(crate) async fn handle_server_hello( config: Arc, - cx: &mut CommonState, + cx: &mut Conn, server_hello: &ServerHelloPayload, server_name: ServerName, transcript: HandshakeHash, @@ -73,8 +72,7 @@ pub(super) async fn handle_server_hello( let their_key_share = match server_hello.get_key_share() { Some(ks) => ks, None => { - cx - .send_fatal_alert(AlertDescription::MissingExtension) + cx.send_fatal_alert(AlertDescription::MissingExtension) .await?; return Err(Error::PeerMisbehavedError("missing key share".to_string())); } @@ -94,16 +92,18 @@ pub(super) async fn handle_server_hello( emit_fake_ccs(&mut sent_tls13_fake_ccs, cx).await?; - Ok(Box::new(ExpectEncryptedExtensions { - config, - server_name, - transcript, - hello, - })) + Ok(Handshake::Tls13ExpectEncryptedExtensions(Box::new( + ExpectEncryptedExtensions { + config, + server_name, + transcript, + hello, + }, + ))) } async fn validate_server_hello( - common: &mut CommonState, + common: &mut Conn, server_hello: &ServerHelloPayload, ) -> Result<(), Error> { for ext in &server_hello.extensions { @@ -120,9 +120,9 @@ async fn validate_server_hello( Ok(()) } -pub(super) async fn emit_fake_ccs( +pub(crate) async fn emit_fake_ccs( sent_tls13_fake_ccs: &mut bool, - common: &mut CommonState, + common: &mut Conn, ) -> Result<(), Error> { if std::mem::replace(sent_tls13_fake_ccs, true) { return Ok(()); @@ -136,7 +136,7 @@ pub(super) async fn emit_fake_ccs( } async fn validate_encrypted_extensions( - common: &mut CommonState, + common: &mut Conn, hello: &ClientHelloDetails, exts: &EncryptedExtensions, ) -> Result<(), Error> { @@ -172,18 +172,17 @@ async fn validate_encrypted_extensions( Ok(()) } -struct ExpectEncryptedExtensions { +pub(crate) struct ExpectEncryptedExtensions { config: Arc, server_name: ServerName, transcript: HandshakeHash, hello: ClientHelloDetails, } -#[async_trait] -impl State for ExpectEncryptedExtensions { - async fn handle( +impl ExpectEncryptedExtensions { + pub(crate) async fn handle( mut self: Box, - cx: &mut CommonState, + cx: &mut Conn, m: Message, ) -> hs::NextStateOrError { let exts = require_handshake_msg!( @@ -202,29 +201,26 @@ impl State for ExpectEncryptedExtensions { return Err(Error::PeerMisbehavedError(msg)); } - Ok(Box::new(ExpectCertificateOrCertReq { - config: self.config, - server_name: self.server_name, - transcript: self.transcript, - may_send_sct_list: self.hello.server_may_send_sct_list(), - })) + Ok(Handshake::Tls13ExpectCertificateOrCertReq(Box::new( + ExpectCertificateOrCertReq { + config: self.config, + server_name: self.server_name, + transcript: self.transcript, + may_send_sct_list: self.hello.server_may_send_sct_list(), + }, + ))) } } -struct ExpectCertificateOrCertReq { +pub(crate) struct ExpectCertificateOrCertReq { config: Arc, server_name: ServerName, transcript: HandshakeHash, may_send_sct_list: bool, } -#[async_trait] -impl State for ExpectCertificateOrCertReq { - async fn handle( - self: Box, - cx: &mut CommonState, - m: Message, - ) -> hs::NextStateOrError { +impl ExpectCertificateOrCertReq { + pub(crate) async fn handle(self: Box, cx: &mut Conn, m: Message) -> hs::NextStateOrError { match m.payload { MessagePayload::Handshake(HandshakeMessagePayload { payload: HandshakePayload::CertificateTLS13(..), @@ -268,18 +264,17 @@ impl State for ExpectCertificateOrCertReq { // TLS1.3 version of CertificateRequest handling. We then move to expecting the // server Certificate. Unfortunately the CertificateRequest type changed in an // annoying way in TLS1.3. -struct ExpectCertificateRequest { +pub(crate) struct ExpectCertificateRequest { config: Arc, server_name: ServerName, transcript: HandshakeHash, may_send_sct_list: bool, } -#[async_trait] -impl State for ExpectCertificateRequest { - async fn handle( +impl ExpectCertificateRequest { + pub(crate) async fn handle( mut self: Box, - cx: &mut CommonState, + cx: &mut Conn, m: Message, ) -> hs::NextStateOrError { let certreq = &require_handshake_msg!( @@ -296,9 +291,7 @@ impl State for ExpectCertificateRequest { // Must be empty during handshake. if !certreq.context.0.is_empty() { warn!("Server sent non-empty certreq context"); - cx - .send_fatal_alert(AlertDescription::DecodeError) - .await?; + cx.send_fatal_alert(AlertDescription::DecodeError).await?; return Err(Error::CorruptMessagePayload(ContentType::Handshake)); } @@ -313,8 +306,7 @@ impl State for ExpectCertificateRequest { .collect::>(); if compat_sigschemes.is_empty() { - cx - .send_fatal_alert(AlertDescription::HandshakeFailure) + cx.send_fatal_alert(AlertDescription::HandshakeFailure) .await?; return Err(Error::PeerIncompatibleError( "server sent bad certreq schemes".to_string(), @@ -328,17 +320,19 @@ impl State for ExpectCertificateRequest { Some(certreq.context.0.clone()), ); - Ok(Box::new(ExpectCertificate { - config: self.config, - server_name: self.server_name, - transcript: self.transcript, - may_send_sct_list: self.may_send_sct_list, - client_auth: Some(client_auth), - })) + Ok(Handshake::Tls13ExpectCertificate(Box::new( + ExpectCertificate { + config: self.config, + server_name: self.server_name, + transcript: self.transcript, + may_send_sct_list: self.may_send_sct_list, + client_auth: Some(client_auth), + }, + ))) } } -struct ExpectCertificate { +pub(crate) struct ExpectCertificate { config: Arc, server_name: ServerName, transcript: HandshakeHash, @@ -346,11 +340,10 @@ struct ExpectCertificate { client_auth: Option, } -#[async_trait] -impl State for ExpectCertificate { - async fn handle( +impl ExpectCertificate { + pub(crate) async fn handle( mut self: Box, - cx: &mut CommonState, + cx: &mut Conn, m: Message, ) -> hs::NextStateOrError { let cert_chain = require_handshake_msg!( @@ -363,9 +356,7 @@ impl State for ExpectCertificate { // This is only non-empty for client auth. if !cert_chain.context.0.is_empty() { warn!("certificate with non-empty context during handshake"); - cx - .send_fatal_alert(AlertDescription::DecodeError) - .await?; + cx.send_fatal_alert(AlertDescription::DecodeError).await?; return Err(Error::CorruptMessagePayload(ContentType::Handshake)); } @@ -373,8 +364,7 @@ impl State for ExpectCertificate { || cert_chain.any_entry_has_unknown_extension() { warn!("certificate chain contains unsolicited/unknown extension"); - cx - .send_fatal_alert(AlertDescription::UnsupportedExtension) + cx.send_fatal_alert(AlertDescription::UnsupportedExtension) .await?; return Err(Error::PeerMisbehavedError( "bad cert chain extensions".to_string(), @@ -399,18 +389,20 @@ impl State for ExpectCertificate { } } - Ok(Box::new(ExpectCertificateVerify { - config: self.config, - server_name: self.server_name, - transcript: self.transcript, - server_cert, - client_auth: self.client_auth, - })) + Ok(Handshake::Tls13ExpectCertificateVerify(Box::new( + ExpectCertificateVerify { + config: self.config, + server_name: self.server_name, + transcript: self.transcript, + server_cert, + client_auth: self.client_auth, + }, + ))) } } // --- TLS1.3 CertificateVerify --- -struct ExpectCertificateVerify { +pub(crate) struct ExpectCertificateVerify { config: Arc, server_name: ServerName, transcript: HandshakeHash, @@ -418,11 +410,10 @@ struct ExpectCertificateVerify { client_auth: Option, } -#[async_trait] -impl State for ExpectCertificateVerify { - async fn handle( +impl ExpectCertificateVerify { + pub(crate) async fn handle( mut self: Box, - cx: &mut CommonState, + cx: &mut Conn, m: Message, ) -> hs::NextStateOrError { let cert_verify = require_handshake_msg!( @@ -471,12 +462,12 @@ impl State for ExpectCertificateVerify { self.transcript.add_message(&m); - Ok(Box::new(ExpectFinished { + Ok(Handshake::Tls13ExpectFinished(Box::new(ExpectFinished { transcript: self.transcript, client_auth: self.client_auth, cert_verified, sig_verified, - })) + }))) } } @@ -484,7 +475,7 @@ async fn emit_certificate_tls13( transcript: &mut HandshakeHash, certkey: Option<&CertifiedKey>, auth_context: Option>, - common: &mut CommonState, + common: &mut Conn, ) -> Result<(), Error> { let context = auth_context.unwrap_or_default(); @@ -515,7 +506,7 @@ async fn emit_certificate_tls13( async fn emit_certverify_tls13( transcript: &mut HandshakeHash, signer: &dyn Signer, - common: &mut CommonState, + common: &mut Conn, ) -> Result<(), Error> { let message = verify::construct_tls13_client_verify_message(&transcript.get_current_hash()); @@ -538,7 +529,7 @@ async fn emit_certverify_tls13( async fn emit_finished_tls13( verify_data: &[u8], transcript: &mut HandshakeHash, - common: &mut CommonState, + common: &mut Conn, ) -> Result<(), Error> { let verify_data_payload = Payload::new(verify_data); @@ -554,27 +545,21 @@ async fn emit_finished_tls13( common.send_msg(m, true).await } -struct ExpectFinished { +pub(crate) struct ExpectFinished { transcript: HandshakeHash, client_auth: Option, cert_verified: verify::ServerCertVerified, sig_verified: verify::HandshakeSignatureValid, } -#[async_trait] -impl State for ExpectFinished { - async fn handle( - self: Box, - cx: &mut CommonState, - m: Message, - ) -> hs::NextStateOrError { +impl ExpectFinished { + pub(crate) async fn handle(self: Box, cx: &mut Conn, m: Message) -> hs::NextStateOrError { let mut st = *self; let finished = require_handshake_msg!(m, HandshakeType::Finished, HandshakePayload::Finished)?; let handshake_hash = st.transcript.get_current_hash(); let expect_verify_data = cx - .backend .get_server_finished_vd(handshake_hash.as_ref().to_vec()) .await?; @@ -585,9 +570,7 @@ impl State for ExpectFinished { ) { Ok(()) => verify::FinishedMessageVerified::assertion(), Err(_) => { - cx - .send_fatal_alert(AlertDescription::DecryptError) - .await?; + cx.send_fatal_alert(AlertDescription::DecryptError).await?; return Err(Error::DecryptError); } }; @@ -601,21 +584,15 @@ impl State for ExpectFinished { ClientAuthDetails::Empty { auth_context_tls13: auth_context, } => { - emit_certificate_tls13(&mut st.transcript, None, auth_context, cx) - .await?; + emit_certificate_tls13(&mut st.transcript, None, auth_context, cx).await?; } ClientAuthDetails::Verify { certkey, signer, auth_context_tls13: auth_context, } => { - emit_certificate_tls13( - &mut st.transcript, - Some(&certkey), - auth_context, - cx, - ) - .await?; + emit_certificate_tls13(&mut st.transcript, Some(&certkey), auth_context, cx) + .await?; emit_certverify_tls13(&mut st.transcript, signer.as_ref(), cx).await?; } } @@ -623,7 +600,6 @@ impl State for ExpectFinished { let handshake_hash = st.transcript.get_current_hash(); let client_finished = cx - .backend .get_client_finished_vd(handshake_hash.as_ref().to_vec()) .await?; emit_finished_tls13(&client_finished, &mut st.transcript, cx).await?; @@ -635,91 +611,12 @@ impl State for ExpectFinished { // MPC backend. unsupported()?; - cx.start_traffic().await?; - - let st = ExpectTraffic { - _cert_verified: st.cert_verified, - _sig_verified: st.sig_verified, - _fin_verified: fin, - }; - - Ok(Box::new(st)) - } -} - -// -- Traffic transit state (TLS1.3) -- -// In this state we can be sent tickets, key updates, -// and application data. -struct ExpectTraffic { - _cert_verified: verify::ServerCertVerified, - _sig_verified: verify::HandshakeSignatureValid, - _fin_verified: verify::FinishedMessageVerified, -} - -impl ExpectTraffic { - #[allow(clippy::unnecessary_wraps)] - async fn handle_new_ticket_tls13( - &mut self, - cx: &mut CommonState, - nst: &NewSessionTicketPayloadTLS13, - ) -> Result<(), Error> { - if nst.has_duplicate_extension() { - cx - .send_fatal_alert(AlertDescription::IllegalParameter) - .await?; - return Err(Error::PeerMisbehavedError( - "peer sent duplicate NewSessionTicket extensions".into(), - )); - } - - Ok(()) - } - - async fn handle_key_update( - &mut self, - common: &mut CommonState, - _kur: &KeyUpdateRequest, - ) -> Result<(), Error> { - // Mustn't be interleaved with other handshake messages. - common.check_aligned_handshake().await?; - - // Client does not support key updates - common - .send_fatal_alert(AlertDescription::InternalError) - .await?; - - Err(Error::General( - "received unsupported key update request from peer".to_string(), - )) - } -} - -#[async_trait] -impl State for ExpectTraffic { - async fn handle( - mut self: Box, - cx: &mut CommonState, - m: Message, - ) -> hs::NextStateOrError { - match m.payload { - MessagePayload::ApplicationData(payload) => cx.take_received_plaintext(payload), - MessagePayload::Handshake(HandshakeMessagePayload { - payload: HandshakePayload::NewSessionTicketTLS13(ref new_ticket), - .. - }) => self.handle_new_ticket_tls13(cx, new_ticket).await?, - MessagePayload::Handshake(HandshakeMessagePayload { - payload: HandshakePayload::KeyUpdate(ref key_update), - .. - }) => self.handle_key_update(cx, key_update).await?, - payload => { - return Err(inappropriate_handshake_message( - &payload, - &[ContentType::ApplicationData, ContentType::Handshake], - &[HandshakeType::NewSessionTicket, HandshakeType::KeyUpdate], - )); - } - } + // The proof tokens are not threaded further now that the handshake + // ends; the connection driver moves to the online phase on `Complete`, + // where post-handshake messages (tickets, key updates) are routed by + // `crate::handshake::traffic`. + let _ = (st.cert_verified, st.sig_verified, fin); - Ok(self) + Ok(Handshake::Complete) } } diff --git a/crates/mpc-tls/src/leader.rs b/crates/mpc-tls/src/leader.rs index 09fbaf7779..83c44ccad7 100644 --- a/crates/mpc-tls/src/leader.rs +++ b/crates/mpc-tls/src/leader.rs @@ -1,16 +1,34 @@ -use crate::{ - Config, Role, SessionKeys, Vm, - error::MpcTlsError, - msg::{Decrypt, Encrypt, Message, ServerHello}, - record_layer::{RecordLayer, aead::MpcAesGcm}, - utils::{alloc_session, flush_prf, opaque_into_parts, verify_transcript}, -}; +//! MPC-TLS leader. +//! +//! The leader is the unified TLS client. Unlike upstream rustls it performs no +//! cryptographic operations itself: the key exchange, the PRF and record +//! encryption/decryption are delegated to an [`MpcSession`], which executes +//! them jointly with the follower using MPC. This module drives the TLS +//! protocol — message framing, the handshake flow, alerts and connection +//! closure — directly against that session, with no intermediate "backend" +//! abstraction. +//! +//! [`MpcTlsLeader`] is the single public type. Its lifecycle is the private +//! [`State`] enum: +//! +//! * [`State::Setup`] — MPC resources are allocated and preprocessed before the +//! handshake begins. +//! * [`State::Live`] — the connection is driven. A [`Live`] owns the connection +//! I/O and MPC session ([`Conn`]) plus a [`Phase`] that is either +//! [`Phase::Handshaking`] (the handshake state machine and its inputs) or +//! [`Phase::Online`] (application-data transfer). The phase flips in place +//! when the handshake completes, so a single `process_new_packets` call can +//! finish the handshake and process the first application records that +//! follow. The `Live` remains in place after the connection closes so +//! buffered plaintext can be drained before [`MpcTlsLeader::finish`]. + +use std::{io, mem, sync::Arc}; + use hmac_sha256::{MSMode, Prf, PrfConfig}; use ke::KeyExchange; use key_exchange::{self as ke, MpcKeyExchange}; use mpz_common::{Context, Flush}; -use mpz_core::{Block, bitvec::BitVec}; -use mpz_memory_core::DecodeFutureTyped; +use mpz_core::Block; use mpz_ole::{Receiver as OLEReceiver, Sender as OLESender}; use mpz_ot::{ rcot::{RCOTReceiver, RCOTSender}, @@ -22,14 +40,10 @@ use mpz_ot::{ use mpz_share_conversion::{ShareConversionReceiver, ShareConversionSender}; use serio::SinkExt; use tls_core::{ - cert::ServerCertDetails, - ke::ServerKxDetails, - key::PublicKey, msgs::{ - base::Payload, - enums::{ContentType, NamedGroup}, + enums::{AlertDescription, ContentType}, handshake::Random, - message::{OpaqueMessage, PlainMessage}, + message::{Message, MessagePayload, OpaqueMessage, PlainMessage}, }, verify::verify_sig_determine_alg, }; @@ -38,17 +52,69 @@ use tlsn_core::{ transcript::TlsTranscript, webpki::CertificateDer, }; +use tracing::{debug, instrument, trace}; -use tracing::{debug, instrument}; +use crate::{ + Config, MpcTlsError, Role, SessionKeys, Vm, + conn::{Conn, IoState, TLS13_MAX_DROPPED_CCS, TlsIo, is_valid_ccs}, + handshake::{ + ClientConfig, ServerName, + error::Error, + hs::{self, Handshake}, + }, + msg::Message as MpcMessage, + record_layer::{RecordLayer, aead::MpcAesGcm}, + session::MpcSession, +}; -/// MPC-TLS leader. -#[derive(Debug)] +/// MPC-TLS leader: the unified TLS-over-MPC client. pub struct MpcTlsLeader { - config: Config, + /// Whether incoming application data is decrypted while the connection is + /// active. Mirrored into [`Live::is_decrypting`] once the connection is + /// live; kept here so it can be read and toggled during setup. + is_decrypting: bool, state: State, +} - /// Whether the record layer is decrypting application data. - is_decrypting: bool, +impl std::fmt::Debug for MpcTlsLeader { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("MpcTlsLeader") + .field("state", &self.state) + .finish_non_exhaustive() + } +} + +/// The leader's lifecycle. +enum State { + /// MPC resources are being allocated and preprocessed before the handshake. + Setup(Box), + /// The TLS connection is being driven (and, after closure, drained). + Live(Box), + /// Transient poison used while transitioning between states. + Invalid, +} + +impl State { + fn take(&mut self) -> Self { + mem::replace(self, State::Invalid) + } +} + +impl std::fmt::Debug for State { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + State::Setup(_) => "Setup", + State::Live(_) => "Live", + State::Invalid => "Invalid", + }) + } +} + +/// Pre-handshake state: the MPC session being allocated and preprocessed. +struct Setup { + config: Config, + session: MpcSession, + client_random: Random, } impl MpcTlsLeader { @@ -95,600 +161,549 @@ impl MpcTlsLeader { ); let record_layer = RecordLayer::new(Role::Leader, encrypter, decrypter); + let session = MpcSession::new(ctx, vm, ke, prf, record_layer); + let client_random = Random::new().expect("rng is available"); let is_decrypting = !config.defer_decryption; Self { - config, - state: State::Init { - core: Core { - ctx, - vm, - ke, - prf, - record_layer, - }, - }, is_decrypting, + state: State::Setup(Box::new(Setup { + config, + session, + client_random, + })), } } /// Allocates resources for the connection. pub fn alloc(&mut self) -> Result { - let State::Init { mut core } = self.state.take() else { - return Err(MpcTlsError::state("must be in init state to allocate")); - }; - - let client_random = Random::new().expect("rng is available"); - - let (keys, cf_vd_fut, sf_vd_fut) = { - let mut vm = core - .vm - .try_lock() - .map_err(|_| MpcTlsError::other("VM lock is held"))?; - - alloc_session( - &mut *vm, - &self.config, - &mut *core.ke, - &mut core.prf, - &mut core.record_layer, - )? - }; - - self.state = State::Setup { - core, - client_random, - cf_vd_fut, - sf_vd_fut, + let State::Setup(setup) = &mut self.state else { + return Err(MpcTlsError::state("must be in setup state to allocate")); }; - Ok(keys) + setup.session.alloc(&setup.config) } /// Preprocesses the connection. #[instrument(level = "debug", skip_all, err)] pub async fn preprocess(&mut self) -> Result<(), MpcTlsError> { - let State::Setup { - core: - Core { - mut ctx, - vm, - ke, - mut prf, - record_layer, - }, - client_random, - cf_vd_fut, - sf_vd_fut, - } = self.state.take() - else { + let State::Setup(setup) = self.state.take() else { return Err(MpcTlsError::state("must be in setup state to preprocess")); }; + let Setup { + config, + session, + client_random, + } = *setup; - let mut vm_lock = vm - .clone() - .try_lock_owned() - .map_err(|_| MpcTlsError::other("VM lock is held"))?; - - let (ke, record_layer, _) = ctx - .try_join3( - move |ctx| { - Box::pin(async move { - let mut ke = ke; - ke.setup(ctx) - .await - .map(|_| ke) - .map_err(MpcTlsError::preprocess) - }) - }, - move |ctx| { - Box::pin(async move { - let mut record_layer = record_layer; - record_layer - .preprocess(ctx) - .await - .map(|_| record_layer) - .map_err(MpcTlsError::preprocess) - }) - }, - move |ctx| { - Box::pin(async move { - vm_lock - .preprocess(ctx) - .await - .map_err(MpcTlsError::preprocess)?; - vm_lock.flush(ctx).await.map_err(MpcTlsError::preprocess)?; - - Ok::<_, MpcTlsError>(()) - }) - }, - ) - .await - .map_err(MpcTlsError::preprocess)??; - - ctx.io_mut() - .send(Message::SetClientRandom(client_random.0)) - .await?; + let mut session = session.preprocess().await?; - prf.set_client_random(client_random.0); + session + .ctx_mut() + .io_mut() + .send(MpcMessage::SetClientRandom(client_random.0)) + .await?; + session.set_client_random(client_random.0); - self.state = State::Ready { - core: Core { - ctx, - vm, - ke, - prf, - record_layer, - }, + self.state = State::Setup(Box::new(Setup { + config, + session, client_random, - cf_vd_fut, - sf_vd_fut, - }; + })); Ok(()) } - /// Returns if incoming messages are decrypted. + /// Returns whether incoming application data is decrypted while the + /// connection is active. pub fn is_decrypting(&self) -> bool { self.is_decrypting } -} - -impl MpcTlsLeader { - /// Returns the client random. - pub(crate) fn client_random(&self) -> Result { - let State::Ready { client_random, .. } = &self.state else { - return Err(MpcTlsError::state( - "must be in ready state to get client random", - )); - }; - Ok(*client_random) + /// Enables or disables decryption of incoming application data. + pub fn enable_decryption(&mut self, enable: bool) { + self.is_decrypting = enable; + if let State::Live(live) = &mut self.state { + live.is_decrypting = enable; + } } - /// Returns the client key share for the key exchange. - pub(crate) fn client_key_share(&self) -> Result { - let State::Ready { core, .. } = &self.state else { - return Err(MpcTlsError::state( - "must be in ready state to get client key share", + /// Starts the TLS connection to `server_name`, emitting the ClientHello. + pub async fn start( + &mut self, + client_config: Arc, + server_name: ServerName, + ) -> Result<(), Error> { + // Build the framing first: this validates the configured fragment size, + // so a bad configuration is reported without tearing down the session. + let io = TlsIo::new(client_config.max_fragment_size)?; + + let State::Setup(setup) = self.state.take() else { + return Err(Error::General( + "must be in setup state to start the connection".to_string(), )); }; + let Setup { + session, + client_random, + .. + } = *setup; - let pk = core.ke.client_key()?; - - Ok(PublicKey::new( - NamedGroup::secp256r1, - &p256::EncodedPoint::from(pk).to_bytes(), - )) + let conn = Conn::new(io, session, client_random); + let mut live = Live::new(conn, client_config, server_name, self.is_decrypting); + let result = live.start_handshake().await; + self.state = State::Live(Box::new(live)); + result } - /// Computes the session keys from the handshake data collected by the - /// client, preparing the record layer for encryption. - #[instrument(level = "debug", skip_all, err)] - pub(crate) async fn prepare_encryption( - &mut self, - hs: HandshakeData, - ) -> Result<(), MpcTlsError> { - let State::Ready { - core: - Core { - mut ctx, - vm, - mut ke, - mut prf, - mut record_layer, - }, - client_random, - cf_vd_fut, - sf_vd_fut, - } = self.state.take() - else { - return Err(MpcTlsError::state( - "must be in ready state to prepare encryption", - )); - }; - - debug!("preparing encryption"); + /// Processes any new packets buffered by [`MpcTlsLeader::read_tls`]. + pub async fn process_new_packets(&mut self) -> Result { + self.live_mut()?.process_new_packets().await + } - if hs.server_key.group != NamedGroup::secp256r1 { - return Err(MpcTlsError::hs("invalid server public keyshare")); + /// Reads out buffered plaintext received from the peer. + pub fn read_plaintext(&mut self, buf: &mut [u8]) -> io::Result { + match &mut self.state { + State::Live(live) => live.conn.io.read_plaintext(buf), + _ => Ok(0), } + } - let time = web_time::UNIX_EPOCH - .elapsed() - .expect("system time is available") - .as_secs(); + /// Buffers plaintext to be encrypted and sent to the peer. + pub fn write_plaintext(&mut self, buf: &[u8]) -> Result { + match &mut self.state { + State::Live(live) => Ok(live.conn.io.write_plaintext(buf)), + _ => Ok(0), + } + } - ctx.io_mut() - .send(Message::ServerHello(ServerHello { - time, - random: hs.server_random.0, - key: hs.server_key.clone(), - })) - .await?; + /// Reads TLS records from `rd` into the internal buffer. + pub fn read_tls(&mut self, rd: &mut dyn io::Read) -> io::Result { + match &mut self.state { + State::Live(live) => live.conn.io.read_tls(rd), + _ => Ok(0), + } + } - prf.set_server_random(hs.server_random.0)?; + /// Writes buffered TLS records to `wr`. + pub fn write_tls(&mut self, wr: &mut dyn io::Write) -> io::Result { + match &mut self.state { + State::Live(live) => live.conn.io.write_tls(wr), + _ => Ok(0), + } + } - ke.set_server_key( - p256::PublicKey::from_sec1_bytes(&hs.server_key.key).map_err(MpcTlsError::hs)?, - )?; + /// Returns whether the caller should read more TLS data. + pub fn wants_read(&self) -> bool { + matches!(&self.state, State::Live(live) if live.wants_read()) + } - ke.compute_shares(&mut ctx).await?; + /// Returns whether the caller should write buffered TLS data. + pub fn wants_write(&self) -> bool { + matches!(&self.state, State::Live(live) if live.conn.io.wants_write()) + } - { - let mut vm = vm - .try_lock() - .map_err(|_| MpcTlsError::other("VM lock is held"))?; + /// Returns whether there is no plaintext available to read immediately. + pub fn plaintext_is_empty(&self) -> bool { + match &self.state { + State::Live(live) => live.conn.io.plaintext_is_empty(), + _ => true, + } + } - ke.assign(&mut (*vm))?; - flush_prf(&mut prf, &mut *vm, &mut ctx).await?; + /// Returns whether the sendable plaintext buffer is full. + pub fn sendable_plaintext_is_full(&self) -> bool { + match &self.state { + State::Live(live) => live.conn.io.sendable_plaintext_is_full(), + _ => false, + } + } - ke.finalize().await?; - record_layer.setup(&mut ctx).await?; + /// Returns whether the connection is currently performing the handshake. + pub fn is_handshaking(&self) -> bool { + match &self.state { + State::Live(live) => live.is_handshaking(), + _ => true, } + } - debug!("encryption prepared"); + /// Returns whether the record layer has no buffered records. + pub fn is_empty(&self) -> bool { + match &self.state { + State::Live(live) => live.conn.session.record_layer_is_empty(), + _ => true, + } + } - self.state = State::Active { - core: Core { - ctx, - vm, - ke, - prf, - record_layer, - }, - client_random, - cf_vd_fut, - sf_vd_fut, - cf_vd: None, - sf_vd: None, - time, - hs, - }; + /// Queues a close_notify alert to be sent to the peer. + pub async fn send_close_notify(&mut self) -> Result<(), Error> { + self.live_mut()?.conn.send_close_notify().await + } + /// Signals that the server has closed the connection, committing the + /// transcript. + pub async fn server_closed(&mut self) -> Result<(), Error> { + self.live_mut()?.close_connection().await?; Ok(()) } - #[instrument(level = "debug", skip_all, err)] - pub(crate) async fn get_client_finished_vd( - &mut self, - hash: Vec, - ) -> Result, MpcTlsError> { - let State::Active { - core, - cf_vd_fut, - cf_vd, - .. - } = &mut self.state - else { - return Err(MpcTlsError::state( - "must be in active state to get client finished vd", - )); - }; - - debug!("computing client finished verify data"); - - let hash: [u8; 32] = hash - .try_into() - .map_err(|_| MpcTlsError::hs("client finished handshake hash is not 32 bytes"))?; + /// Returns the I/O context and transcript once the connection is closed and + /// drained. Returns `None` if the connection is not closed yet. + pub fn finish(&mut self) -> Option<(Context, TlsTranscript)> { + match self.state.take() { + State::Live(live) => match live.into_finished() { + Ok((ctx, transcript)) => Some((ctx, transcript)), + Err(live) => { + self.state = State::Live(live); + None + } + }, + other => { + self.state = other; + None + } + } + } - core.ctx - .io_mut() - .send(Message::ClientFinishedVd(hash)) - .await?; + fn live_mut(&mut self) -> Result<&mut Live, Error> { + match &mut self.state { + State::Live(live) => Ok(live), + _ => Err(Error::HandshakeNotComplete), + } + } +} - let mut vm = core - .vm - .try_lock() - .map_err(|_| MpcTlsError::other("VM lock is held"))?; - core.prf.set_cf_hash(hash)?; - flush_prf(&mut core.prf, &mut *vm, &mut core.ctx).await?; +/// The live TLS-over-MPC connection. +/// +/// A thin lifecycle/phase driver over [`Conn`]: it pumps records through the +/// connection and dispatches them to the current [`Phase`]. The handshake state +/// machine and the online router operate directly on `&mut Conn`, not on +/// `Live`, so there is no forwarding layer here. +pub(crate) struct Live { + conn: Conn, + phase: Phase, + /// Whether incoming application data is decrypted while active. + is_decrypting: bool, + /// The committed transcript, set once the connection is closed. + transcript: Option, +} - let vd = cf_vd_fut - .try_recv() - .map_err(MpcTlsError::hs)? - .ok_or_else(|| MpcTlsError::hs("cf_vd is not decoded"))?; +/// Which phase of the connection is active. +enum Phase { + /// The handshake is in progress; holds the handshake state machine and its + /// inputs. + Handshaking(Box), + /// The handshake is complete; application data flows. No handshake state is + /// retained. + Online, +} - *cf_vd = Some(vd); +/// Handshake-phase-only state. +struct Handshaking { + /// The handshake state machine, or a latched fatal error. + handshake: Result, + client_config: Arc, + server_name: ServerName, +} - Ok(vd.to_vec()) +impl Live { + fn new( + conn: Conn, + client_config: Arc, + server_name: ServerName, + is_decrypting: bool, + ) -> Self { + Self { + conn, + phase: Phase::Handshaking(Box::new(Handshaking { + // The handshake state is installed by `start_handshake`; until + // then any attempt to drive the connection reports + // `HandshakeNotComplete`. + handshake: Err(Error::HandshakeNotComplete), + client_config, + server_name, + })), + is_decrypting, + transcript: None, + } } - #[instrument(level = "debug", skip_all, err)] - pub(crate) async fn get_server_finished_vd( - &mut self, - hash: Vec, - ) -> Result, MpcTlsError> { - let State::Active { - core, - sf_vd_fut, - sf_vd, - .. - } = &mut self.state - else { - return Err(MpcTlsError::state( - "must be in active state to get server finished vd", - )); + /// Initiates the TLS protocol by emitting the ClientHello. + /// + /// On failure the error is latched into the handshake state so subsequent + /// `process_new_packets` calls surface it rather than driving a + /// half-started connection. + async fn start_handshake(&mut self) -> Result<(), Error> { + let (config, server_name) = match &self.phase { + Phase::Handshaking(hs) => (hs.client_config.clone(), hs.server_name.clone()), + Phase::Online => { + return Err(Error::General("connection already started".to_string())); + } }; - debug!("computing server finished verify data"); - - let hash: [u8; 32] = hash - .try_into() - .map_err(|_| MpcTlsError::hs("server finished handshake hash is not 32 bytes"))?; - - core.ctx - .io_mut() - .send(Message::ServerFinishedVd(hash)) - .await?; - - let mut vm = core - .vm - .try_lock() - .map_err(|_| MpcTlsError::other("VM lock is held"))?; - core.prf.set_sf_hash(hash)?; - flush_prf(&mut core.prf, &mut *vm, &mut core.ctx).await?; - - let vd = sf_vd_fut - .try_recv() - .map_err(MpcTlsError::hs)? - .ok_or_else(|| MpcTlsError::hs("sf_vd is not decoded"))?; - - *sf_vd = Some(vd); + let result = hs::start_handshake(server_name, config, &mut self.conn).await; + + match &mut self.phase { + Phase::Handshaking(hs) => match result { + Ok(handshake) => { + hs.handshake = Ok(handshake); + Ok(()) + } + Err(e) => { + hs.handshake = Err(e.clone()); + Err(e) + } + }, + Phase::Online => Err(Error::General("connection already started".to_string())), + } + } - Ok(vd.to_vec()) + fn is_handshaking(&self) -> bool { + matches!(self.phase, Phase::Handshaking(_)) } - #[instrument(level = "debug", skip_all, err)] - pub(crate) async fn push_incoming(&mut self, msg: OpaqueMessage) -> Result<(), MpcTlsError> { - let State::Active { core, .. } = &mut self.state else { - return Err(MpcTlsError::state(format!( - "can not push incoming message in state: {}", - self.state - ))); - }; + fn is_online(&self) -> bool { + matches!(self.phase, Phase::Online) + } - let OpaqueMessage { - typ, - version, - payload, - } = msg; - let (explicit_nonce, ciphertext, tag) = opaque_into_parts(payload.0)?; - - debug!( - "received incoming message, type: {:?}, len: {}", - typ, - ciphertext.len() - ); + fn is_closed(&self) -> bool { + self.transcript.is_some() + } - core.record_layer.push_decrypt( - typ, - version, - explicit_nonce.clone(), - ciphertext.clone(), - tag.clone(), - )?; + fn wants_read(&self) -> bool { + // We want to read more data all the time, except when we have + // unprocessed plaintext (back-pressure) or the peer has sent a + // close_notify. During the handshake we also don't read while we still + // have TLS data queued to send. + self.conn.io.plaintext_is_empty() + && !self.conn.io.has_received_close_notify() + && (self.is_online() || self.conn.io.sendable_tls_is_empty()) + } - core.ctx - .io_mut() - .send(Message::Decrypt(Decrypt { - typ, - version, - explicit_nonce, - ciphertext, - tag, - })) - .await?; + /// Signals that the handshake is complete: starts application traffic and + /// transitions to the online phase. + async fn enter_online(&mut self) -> Result<(), Error> { + self.conn.start_traffic().await?; + self.phase = Phase::Online; + // Now that we may send application data, flush any plaintext that was + // buffered while the handshake was in progress. + self.flush_plaintext().await + } + /// Sends and encrypts any buffered plaintext. Does nothing during the + /// handshake. + async fn flush_plaintext(&mut self) -> Result<(), Error> { + if !self.is_online() { + return Ok(()); + } + while let Some(buf) = self.conn.io.next_sendable_plaintext() { + self.conn.send_appdata_encrypt(&buf).await?; + } Ok(()) } - pub(crate) fn next_incoming(&mut self) -> Result, MpcTlsError> { - let record_layer = match &mut self.state { - State::Ready { core, .. } | State::Active { core, .. } => &mut core.record_layer, - State::Closed { record_layer, .. } => record_layer, - state => { - return Err(MpcTlsError::state(format!( - "can not pull next incoming message in state: {state}", - ))); - } - }; - - let record = record_layer.next_decrypted().map(|record| PlainMessage { - typ: record.typ, - version: record.version, - payload: Payload::new( - record - .plaintext - .expect("leader should always know plaintext"), - ), - }); - - if let Some(record) = &record { - debug!( - "processing incoming message, type: {:?}, len: {}", - record.typ, - record.payload.0.len() - ); + /// Flushes the record layer if the connection is in a state where flushing + /// is meaningful. + async fn flush_records(&mut self) -> Result<(), Error> { + if !self.conn.encryption_prepared() { + debug!("handshake is not complete, skipping flush"); + return Ok(()); } - - Ok(record) + // The record layer is guaranteed to be empty after the connection was + // closed. + if self.is_closed() { + return Ok(()); + } + self.conn.flush_records(self.is_decrypting).await } - #[instrument(level = "debug", skip_all, err)] - pub(crate) async fn push_outgoing(&mut self, msg: PlainMessage) -> Result<(), MpcTlsError> { - let State::Active { core, .. } = &mut self.state else { - return Err(MpcTlsError::state(format!( - "can not push outgoing message in state: {}", - self.state - ))); - }; - - debug!( - "encrypting outgoing message, type: {:?}, len: {}", - msg.typ, - msg.payload.0.len() - ); - - let PlainMessage { - typ, - version, - payload, - } = msg; - let plaintext = payload.0; - let len = plaintext.len(); - - // Only the contents of application data is hidden from the follower. - let public_plaintext = match typ { - ContentType::ApplicationData => None, - _ => Some(plaintext.clone()), - }; - - core.record_layer - .push_encrypt(typ, version, len, Some(plaintext))?; + // --- Connection driving --- - core.ctx - .io_mut() - .send(Message::Encrypt(Encrypt { - typ, - version, - len, - plaintext: public_plaintext, - })) - .await?; + /// Processes any new packets read by a previous call to `read_tls`. + pub(crate) async fn process_new_packets(&mut self) -> Result { + if let Phase::Handshaking(hs) = &self.phase + && let Err(e) = &hs.handshake + { + return Err(e.clone()); + } - Ok(()) - } + if self.conn.io.deframer_desynced() { + return Err(Error::CorruptMessage); + } - pub(crate) fn next_outgoing(&mut self) -> Result, MpcTlsError> { - let record_layer = match &mut self.state { - State::Ready { core, .. } | State::Active { core, .. } => &mut core.record_layer, - State::Closed { record_layer, .. } => record_layer, - state => { - return Err(MpcTlsError::state(format!( - "can not pull next outgoing message in state: {state}", - ))); + // Process outgoing plaintext buffer and encrypt messages. + self.flush_plaintext().await?; + + // Process newly deframed records. + while let Some(msg) = self.conn.io.next_received_frame() { + let plain = match self.process_incoming_opaque(msg).await { + Ok(plain) => plain, + Err(e) => return Err(self.latch(e)), + }; + if let Some(plain) = plain + && let Err(e) = self.process_incoming_plain(plain).await + { + return Err(self.latch(e)); } - }; + } + + self.flush_records().await?; - let record = record_layer.next_encrypted().map(|record| { - let mut payload = record.explicit_nonce; - payload.extend_from_slice(&record.ciphertext); - payload.extend_from_slice(&record.tag.expect("leader should always know tag")); - OpaqueMessage { - typ: record.typ, - version: record.version, - payload: Payload::new(payload), + // Process pending decrypted messages. + while let Some(msg) = self.conn.next_incoming() { + if let Err(e) = self.process_incoming_plain(msg).await { + return Err(self.latch(e)); } - }); + } - if let Some(record) = &record { - debug!( - "sending outgoing message, type: {:?}, len: {}", - record.typ, - record.payload.0.len() - ); + while let Some(msg) = self.conn.next_outgoing() { + self.conn.io.queue_tls_message(msg); } - Ok(record) + Ok(self.conn.io.current_io_state()) } - pub(crate) async fn start_traffic(&mut self) -> Result<(), MpcTlsError> { - let State::Active { core, .. } = &mut self.state else { - return Err(MpcTlsError::state(format!( - "can not start traffic in state: {}", - self.state - ))); - }; + /// Latches a fatal error into the handshake state (so it is returned by + /// future calls) and returns it. + fn latch(&mut self, e: Error) -> Error { + if let Phase::Handshaking(hs) = &mut self.phase { + hs.handshake = Err(e.clone()); + } + e + } - core.record_layer.start_traffic(); - core.ctx.io_mut().send(Message::StartTraffic).await?; + async fn process_incoming_opaque( + &mut self, + msg: OpaqueMessage, + ) -> Result, Error> { + // Drop CCS messages during the TLS1.3 handshake. + if msg.typ == ContentType::ChangeCipherSpec + && self.is_handshaking() + && self.conn.io.is_tls13() + { + if !is_valid_ccs(&msg) || self.conn.io.received_middlebox_ccs() > TLS13_MAX_DROPPED_CCS + { + self.conn + .send_fatal_alert(AlertDescription::UnexpectedMessage) + .await?; + return Err(Error::PeerMisbehavedError( + "illegal middlebox CCS received".into(), + )); + } else { + self.conn.io.inc_received_middlebox_ccs(); + trace!("Dropping CCS"); + return Ok(None); + } + } - Ok(()) + if self.conn.io.decrypting() { + self.conn.push_incoming(msg).await?; + Ok(None) + } else { + Ok(Some(msg.into_plain_message())) + } } - #[instrument(level = "debug", skip_all, err)] - pub(crate) async fn flush(&mut self) -> Result<(), MpcTlsError> { - let core = match &mut self.state { - State::Ready { .. } => { - debug!("handshake is not complete, skipping flush"); - return Ok(()); + async fn process_incoming_plain(&mut self, msg: PlainMessage) -> Result<(), Error> { + // Handshake messages must be reassembled before processing. + if self.conn.io.joiner_wants(&msg) { + if self.conn.io.join(msg).is_none() { + self.conn + .send_fatal_alert(AlertDescription::DecodeError) + .await?; + return Err(Error::CorruptMessagePayload(ContentType::Handshake)); } - State::Active { core, .. } => core, - // The record layer is guaranteed to be empty after the connection - // was closed. - State::Closed { .. } => return Ok(()), - state => { - return Err(MpcTlsError::state(format!( - "can not flush record layer in state: {state}", - ))); + self.conn.io.mark_aligned_handshake(); + while let Some(msg) = self.conn.io.next_joined_message() { + self.process_message(msg).await?; } - }; - - if !core.record_layer.wants_flush() { - debug!("record layer is empty, skipping flush"); return Ok(()); } - debug!("flushing record layer"); + let msg = Message::try_from(msg)?; - core.ctx - .io_mut() - .send(Message::Flush { - is_decrypting: self.is_decrypting, - }) - .await?; + if let MessagePayload::Alert(alert) = &msg.payload { + self.conn.process_alert(alert).await?; + return Ok(()); + } - core.record_layer - .flush(&mut core.ctx, core.vm.clone(), self.is_decrypting) - .await + self.process_message(msg).await } - /// Returns whether the record layer has no buffered records. - pub(crate) fn is_empty(&self) -> bool { - match &self.state { - State::Active { core, .. } => core.record_layer.is_empty(), - State::Closed { record_layer, .. } => record_layer.is_empty(), - _ => true, + /// Dispatches a fully-parsed TLS message according to the current phase. + async fn process_message(&mut self, msg: Message) -> Result<(), Error> { + match &self.phase { + Phase::Handshaking(_) => self.step_handshake(msg).await, + Phase::Online => self.conn.process_online(msg).await, + } + } + + /// Steps the handshake state machine with `msg`, transitioning to the + /// online phase if the handshake completes. + async fn step_handshake(&mut self, msg: Message) -> Result<(), Error> { + let handshake = match &mut self.phase { + Phase::Handshaking(hs) => { + match mem::replace(&mut hs.handshake, Err(Error::HandshakeNotComplete)) { + Ok(handshake) => handshake, + Err(e) => return Err(e), + } + } + Phase::Online => return Err(Error::General("not in handshaking phase".to_string())), + }; + + let next = match handshake.handle(&mut self.conn, msg).await { + Ok(next) => next, + Err(e @ Error::InappropriateMessage { .. }) + | Err(e @ Error::InappropriateHandshakeMessage { .. }) => { + self.conn + .send_fatal_alert(AlertDescription::UnexpectedMessage) + .await?; + return Err(e); + } + Err(e) => return Err(e), + }; + + if matches!(next, Handshake::Complete) { + self.enter_online().await + } else { + if let Phase::Handshaking(hs) = &mut self.phase { + hs.handshake = Ok(next); + } + Ok(()) } } - /// Closes the connection. + // --- Connection closure --- + + /// Closes the connection, committing the transcript. The connection remains + /// in place afterwards so buffered plaintext can be drained. #[instrument(name = "close_connection", level = "debug", skip_all, err)] - pub(crate) async fn close_connection(&mut self) -> Result<(), MpcTlsError> { - let State::Active { - core: - Core { - mut ctx, - vm, - mut record_layer, - .. - }, - client_random, - cf_vd, - sf_vd, - time, - hs, - .. - } = self.state.take() - else { + async fn close_connection(&mut self) -> Result<(), MpcTlsError> { + if self.is_closed() { + return Ok(()); + } + if !self.conn.encryption_prepared() { return Err(MpcTlsError::state( - "must be in active state to close connection", + "cannot close connection before encryption is prepared", )); - }; + } debug!("closing connection"); - - ctx.io_mut().send(Message::CloseConnection).await?; + self.conn.send_message(MpcMessage::CloseConnection).await?; debug!("committing to transcript"); - - let (sent_records, recv_records) = record_layer.commit(&mut ctx, vm).await?; - + let (sent_records, recv_records) = self.conn.session.commit().await?; debug!("committed to transcript"); - let cf_vd = cf_vd.ok_or(MpcTlsError::state("client finished verify data not set"))?; - let sf_vd = sf_vd.ok_or(MpcTlsError::state("server finished verify data not set"))?; + let hs = self + .conn + .server_params + .as_ref() + .ok_or_else(|| MpcTlsError::state("server parameters not set"))?; + let time = self + .conn + .time + .ok_or_else(|| MpcTlsError::state("handshake time not set"))?; let server_cert_chain = hs .server_cert_details @@ -698,7 +713,7 @@ impl MpcTlsLeader { .collect(); let mut sig_msg = Vec::new(); - sig_msg.extend_from_slice(&client_random.0); + sig_msg.extend_from_slice(&self.conn.client_random.0); sig_msg.extend_from_slice(&hs.server_random.0); sig_msg.extend_from_slice(hs.server_kx_details.kx_params()); @@ -715,10 +730,11 @@ impl MpcTlsLeader { }; let binding = CertBinding::V1_2(CertBindingV1_2 { - client_random: client_random.0, + client_random: self.conn.client_random.0, server_random: hs.server_random.0, server_ephemeral_key: hs .server_key + .clone() .try_into() .expect("only supported key scheme should have been accepted"), }); @@ -734,112 +750,22 @@ impl MpcTlsLeader { .build() .map_err(MpcTlsError::other)?; - verify_transcript(&transcript, cf_vd, sf_vd)?; + self.conn.session.verify_transcript(&transcript)?; - self.state = State::Closed { - ctx, - record_layer, - transcript, - }; + self.transcript = Some(transcript); Ok(()) } - pub(crate) fn enable_decryption(&mut self, enable: bool) { - self.is_decrypting = enable; - } - - pub(crate) fn finish(&mut self) -> Option<(Context, TlsTranscript)> { - match self.state.take() { - State::Closed { - ctx, transcript, .. - } => Some((ctx, transcript)), - state => { - self.state = state; - None + /// Consumes the connection once it is closed, returning the I/O context and + /// transcript. Returns the connection unchanged if it is not closed yet. + fn into_finished(mut self: Box) -> Result<(Context, TlsTranscript), Box> { + match self.transcript.take() { + Some(transcript) => { + let (ctx, _record_layer) = self.conn.session.into_closed(); + Ok((ctx, transcript)) } + None => Err(self), } } } - -/// Server parameters of the TLS handshake, collected by the client and -/// handed over before the session keys are computed. -#[derive(Debug)] -pub(crate) struct HandshakeData { - /// The server random. - pub(crate) server_random: Random, - /// The server ephemeral public key. - pub(crate) server_key: PublicKey, - /// The server certificate chain and certificate metadata. - pub(crate) server_cert_details: ServerCertDetails, - /// The server key exchange parameters and signature. - pub(crate) server_kx_details: ServerKxDetails, -} - -/// The MPC machinery of the connection. -struct Core { - ctx: Context, - vm: Vm, - ke: Box, - prf: Prf, - record_layer: RecordLayer, -} - -enum State { - Init { - core: Core, - }, - Setup { - core: Core, - client_random: Random, - cf_vd_fut: DecodeFutureTyped, - sf_vd_fut: DecodeFutureTyped, - }, - Ready { - core: Core, - client_random: Random, - cf_vd_fut: DecodeFutureTyped, - sf_vd_fut: DecodeFutureTyped, - }, - Active { - core: Core, - client_random: Random, - cf_vd_fut: DecodeFutureTyped, - sf_vd_fut: DecodeFutureTyped, - cf_vd: Option<[u8; 12]>, - sf_vd: Option<[u8; 12]>, - time: u64, - hs: HandshakeData, - }, - Closed { - ctx: Context, - record_layer: RecordLayer, - transcript: TlsTranscript, - }, - Error, -} - -impl State { - fn take(&mut self) -> Self { - std::mem::replace(self, State::Error) - } -} - -impl std::fmt::Debug for State { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(match self { - Self::Init { .. } => "Init", - Self::Setup { .. } => "Setup", - Self::Ready { .. } => "Ready", - Self::Active { .. } => "Active", - Self::Closed { .. } => "Closed", - Self::Error => "Error", - }) - } -} - -impl std::fmt::Display for State { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::fmt::Debug::fmt(self, f) - } -} diff --git a/crates/mpc-tls/src/lib.rs b/crates/mpc-tls/src/lib.rs index 0c7e534d72..ee909854c8 100644 --- a/crates/mpc-tls/src/lib.rs +++ b/crates/mpc-tls/src/lib.rs @@ -4,21 +4,31 @@ #![deny(clippy::all)] #![forbid(unsafe_code)] -pub mod client; mod config; +mod conn; mod decode; mod error; pub(crate) mod follower; +pub(crate) mod handshake; pub(crate) mod leader; mod msg; mod record_layer; -pub(crate) mod utils; +mod session; +mod vecbuf; pub use config::{Config, ConfigBuilder, ConfigBuilderError}; +pub use conn::IoState; pub use error::MpcTlsError; pub use follower::MpcTlsFollower; pub use leader::MpcTlsLeader; +// TLS-client policy types. The handshake module is internal; these are the +// public surface for configuring the client and verifying the server. +pub use handshake::{ + Certificate, ClientConfig, Error as TlsError, PrivateKey, ResolvesClientCert, RootCertStore, + ServerName, sign, +}; + use std::sync::Arc; use mpz_memory_core::{ diff --git a/crates/mpc-tls/src/session.rs b/crates/mpc-tls/src/session.rs new file mode 100644 index 0000000000..4c98040d2e --- /dev/null +++ b/crates/mpc-tls/src/session.rs @@ -0,0 +1,524 @@ +//! Shared MPC session machinery. +//! +//! [`MpcSession`] owns the MPC primitives that drive a single TLS connection: +//! the key exchange, the PRF and the record layer, together with the VM and +//! the I/O context shared with the peer. Both the leader and the follower +//! embed an [`MpcSession`] and run the *same* MPC operations on it; what +//! differs between them is only the orchestration: the leader decides what to +//! do and announces it over [`crate::msg::Message`], while the follower mirrors +//! those decisions. Keeping the cryptographic operations here ensures the two +//! roles cannot drift apart. + +use hmac_sha256::{Prf, PrfOutput}; +use key_exchange::KeyExchange; +use mpz_common::Context; +use mpz_core::bitvec::BitVec; +use mpz_memory_core::{DecodeFutureTyped, MemoryExt, binary::Binary}; +use mpz_vm_core::Vm as VmTrait; +use tls_core::{ + key::PublicKey, + msgs::{ + alert::AlertMessagePayload, + codec::{Codec, Reader}, + enums::{AlertDescription, NamedGroup}, + }, +}; +use tlsn_core::transcript::{ContentType, Record, TlsTranscript}; + +use crate::{ + Config, MpcTlsError, SessionKeys, Vm, + record_layer::{EncryptedRecord, PlainRecord, RecordLayer}, +}; + +/// Length of the explicit nonce prefixing every AES-GCM record. +const EXPLICIT_NONCE_LEN: usize = 8; +/// Length of an AES-GCM authentication tag. +const TAG_LEN: usize = 16; + +/// The MPC machinery for a single TLS connection. +/// +/// This is the cryptographic heart shared by the leader and follower. It is +/// deliberately agnostic of the wire protocol between them: it exposes the MPC +/// operations as methods, and the role-specific code is responsible for the +/// ordering and the [`crate::msg::Message`] exchange around them. +pub(crate) struct MpcSession { + ctx: Context, + vm: Vm, + ke: Box, + prf: Prf, + record_layer: RecordLayer, + /// Decode future for the client Finished verify data, populated by + /// [`MpcSession::alloc`]. + cf_vd_fut: Option>, + /// Decode future for the server Finished verify data, populated by + /// [`MpcSession::alloc`]. + sf_vd_fut: Option>, + /// Client Finished verify data, populated by [`MpcSession::compute_cf_vd`]. + cf_vd: Option<[u8; 12]>, + /// Server Finished verify data, populated by [`MpcSession::compute_sf_vd`]. + sf_vd: Option<[u8; 12]>, +} + +impl std::fmt::Debug for MpcSession { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("MpcSession").finish_non_exhaustive() + } +} + +impl MpcSession { + /// Creates a new session from its MPC components. + pub(crate) fn new( + ctx: Context, + vm: Vm, + ke: Box, + prf: Prf, + record_layer: RecordLayer, + ) -> Self { + Self { + ctx, + vm, + ke, + prf, + record_layer, + cf_vd_fut: None, + sf_vd_fut: None, + cf_vd: None, + sf_vd: None, + } + } + + /// Returns a mutable reference to the I/O context shared with the peer. + pub(crate) fn ctx_mut(&mut self) -> &mut Context { + &mut self.ctx + } + + /// Allocates the MPC resources for the connection: the key exchange, the + /// PRF and the record layer. + /// + /// Returns the session keys; the Finished verify-data decode futures are + /// retained internally for [`MpcSession::compute_cf_vd`] and + /// [`MpcSession::compute_sf_vd`]. + pub(crate) fn alloc(&mut self, config: &Config) -> Result { + let mut vm = self + .vm + .try_lock() + .map_err(|_| MpcTlsError::other("VM lock is held"))?; + + let pms = self.ke.alloc(&mut *vm)?; + let PrfOutput { keys, cf_vd, sf_vd } = self.prf.alloc_pms(&mut *vm, pms)?; + self.record_layer.set_keys( + keys.client_write_key, + keys.client_iv, + keys.server_write_key, + keys.server_iv, + )?; + + let cf_vd = vm.decode(cf_vd).map_err(MpcTlsError::alloc)?; + let sf_vd = vm.decode(sf_vd).map_err(MpcTlsError::alloc)?; + + let server_write_mac_key = self.record_layer.alloc( + &mut *vm, + config.max_sent_records, + config.max_recv_records_online, + config.max_sent, + config.max_recv_online, + config.max_recv, + )?; + + drop(vm); + + self.cf_vd_fut = Some(cf_vd); + self.sf_vd_fut = Some(sf_vd); + + Ok(SessionKeys { + client_write_key: keys.client_write_key, + client_write_iv: keys.client_iv, + server_write_key: keys.server_write_key, + server_write_iv: keys.server_iv, + server_write_mac_key, + }) + } + + /// Preprocesses the connection, running the key exchange, record layer and + /// VM preprocessing concurrently. + /// + /// Takes the session by value because the concurrent tasks require owned + /// `'static` access to the key exchange and record layer. + pub(crate) async fn preprocess(self) -> Result { + let MpcSession { + mut ctx, + vm, + ke, + prf, + record_layer, + cf_vd_fut, + sf_vd_fut, + cf_vd, + sf_vd, + } = self; + + let mut vm_lock = vm + .clone() + .try_lock_owned() + .map_err(|_| MpcTlsError::other("VM lock is held"))?; + + let (ke, record_layer, _) = ctx + .try_join3( + move |ctx| { + Box::pin(async move { + let mut ke = ke; + ke.setup(ctx) + .await + .map(|_| ke) + .map_err(MpcTlsError::preprocess) + }) + }, + move |ctx| { + Box::pin(async move { + let mut record_layer = record_layer; + record_layer + .preprocess(ctx) + .await + .map(|_| record_layer) + .map_err(MpcTlsError::preprocess) + }) + }, + move |ctx| { + Box::pin(async move { + vm_lock + .preprocess(ctx) + .await + .map_err(MpcTlsError::preprocess)?; + vm_lock.flush(ctx).await.map_err(MpcTlsError::preprocess)?; + + Ok::<_, MpcTlsError>(()) + }) + }, + ) + .await + .map_err(MpcTlsError::preprocess)??; + + Ok(MpcSession { + ctx, + vm, + ke, + prf, + record_layer, + cf_vd_fut, + sf_vd_fut, + cf_vd, + sf_vd, + }) + } + + /// Sets the client random in the PRF. + pub(crate) fn set_client_random(&mut self, random: [u8; 32]) { + self.prf.set_client_random(random); + } + + /// Returns the client's ephemeral public key for the key exchange. + pub(crate) fn client_key_share(&self) -> Result { + let pk = self.ke.client_key()?; + Ok(PublicKey::new( + NamedGroup::secp256r1, + &p256::EncodedPoint::from(pk).to_bytes(), + )) + } + + /// Computes the session keys from the server's handshake parameters and + /// prepares the record layer for encryption. + pub(crate) async fn compute_keys( + &mut self, + server_random: [u8; 32], + server_key: p256::PublicKey, + ) -> Result<(), MpcTlsError> { + self.prf.set_server_random(server_random)?; + self.ke.set_server_key(server_key)?; + self.ke.compute_shares(&mut self.ctx).await?; + + let mut vm = self + .vm + .try_lock() + .map_err(|_| MpcTlsError::other("VM lock is held"))?; + + self.ke.assign(&mut *vm)?; + flush_prf(&mut self.prf, &mut *vm, &mut self.ctx).await?; + + self.ke.finalize().await?; + self.record_layer.setup(&mut self.ctx).await?; + + Ok(()) + } + + /// Computes the client Finished verify data from the handshake hash. + pub(crate) async fn compute_cf_vd(&mut self, hash: [u8; 32]) -> Result<[u8; 12], MpcTlsError> { + let mut vm = self + .vm + .try_lock() + .map_err(|_| MpcTlsError::other("VM lock is held"))?; + + self.prf.set_cf_hash(hash)?; + flush_prf(&mut self.prf, &mut *vm, &mut self.ctx).await?; + + let vd = self + .cf_vd_fut + .as_mut() + .ok_or_else(|| MpcTlsError::state("client finished verify data not allocated"))? + .try_recv() + .map_err(MpcTlsError::hs)? + .ok_or_else(|| MpcTlsError::hs("cf_vd is not decoded"))?; + + self.cf_vd = Some(vd); + + Ok(vd) + } + + /// Computes the server Finished verify data from the handshake hash. + pub(crate) async fn compute_sf_vd(&mut self, hash: [u8; 32]) -> Result<[u8; 12], MpcTlsError> { + let mut vm = self + .vm + .try_lock() + .map_err(|_| MpcTlsError::other("VM lock is held"))?; + + self.prf.set_sf_hash(hash)?; + flush_prf(&mut self.prf, &mut *vm, &mut self.ctx).await?; + + let vd = self + .sf_vd_fut + .as_mut() + .ok_or_else(|| MpcTlsError::state("server finished verify data not allocated"))? + .try_recv() + .map_err(MpcTlsError::hs)? + .ok_or_else(|| MpcTlsError::hs("sf_vd is not decoded"))?; + + self.sf_vd = Some(vd); + + Ok(vd) + } + + /// Buffers an outgoing record for encryption. + pub(crate) fn push_encrypt( + &mut self, + typ: tls_core::msgs::enums::ContentType, + version: tls_core::msgs::enums::ProtocolVersion, + len: usize, + plaintext: Option>, + ) -> Result<(), MpcTlsError> { + self.record_layer.push_encrypt(typ, version, len, plaintext) + } + + /// Buffers an incoming record for decryption. + pub(crate) fn push_decrypt( + &mut self, + typ: tls_core::msgs::enums::ContentType, + version: tls_core::msgs::enums::ProtocolVersion, + explicit_nonce: Vec, + ciphertext: Vec, + tag: Vec, + ) -> Result<(), MpcTlsError> { + self.record_layer + .push_decrypt(typ, version, explicit_nonce, ciphertext, tag) + } + + /// Returns the next encrypted record, if available. + pub(crate) fn next_encrypted(&mut self) -> Option { + self.record_layer.next_encrypted() + } + + /// Returns the next decrypted record, if available. + pub(crate) fn next_decrypted(&mut self) -> Option { + self.record_layer.next_decrypted() + } + + /// Signals the record layer to start processing application data. + pub(crate) fn start_traffic(&mut self) { + self.record_layer.start_traffic(); + } + + /// Returns whether the record layer has buffered operations to flush. + pub(crate) fn wants_flush(&self) -> bool { + self.record_layer.wants_flush() + } + + /// Returns whether the record layer has no buffered records. + pub(crate) fn record_layer_is_empty(&self) -> bool { + self.record_layer.is_empty() + } + + /// Flushes the record layer, executing buffered encrypt/decrypt operations. + pub(crate) async fn flush(&mut self, is_decrypting: bool) -> Result<(), MpcTlsError> { + self.record_layer + .flush(&mut self.ctx, self.vm.clone(), is_decrypting) + .await + } + + /// Commits to the record layer, returning the sent and received records. + pub(crate) async fn commit(&mut self) -> Result<(Vec, Vec), MpcTlsError> { + self.record_layer + .commit(&mut self.ctx, self.vm.clone()) + .await + } + + /// Verifies the Finished verify data in `transcript` against the values + /// computed in MPC, and that both directions of the connection were closed + /// properly. + pub(crate) fn verify_transcript(&self, transcript: &TlsTranscript) -> Result<(), MpcTlsError> { + let expected_cf_vd = self + .cf_vd + .ok_or_else(|| MpcTlsError::state("client finished verify data not computed"))?; + let expected_sf_vd = self + .sf_vd + .ok_or_else(|| MpcTlsError::state("server finished verify data not computed"))?; + + let cf_vd = transcript + .cf_vd() + .expect("client finished verify data should be available"); + if cf_vd != expected_cf_vd { + return Err(MpcTlsError::peer("client verify data is incorrect")); + } + + let sf_vd = transcript + .sf_vd() + .expect("server finished verify data should be available"); + if sf_vd != expected_sf_vd { + return Err(MpcTlsError::peer("server verify data is incorrect")); + } + + check_close_notify(transcript.sent())?; + check_close_notify(transcript.recv())?; + + Ok(()) + } + + /// Consumes the session after the connection is closed, returning the I/O + /// context and the record layer (which retains the committed records). + pub(crate) fn into_closed(self) -> (Context, RecordLayer) { + (self.ctx, self.record_layer) + } +} + +/// Flushes the PRF, executing the VM until the PRF has no more work. +async fn flush_prf( + prf: &mut Prf, + vm: &mut (dyn VmTrait + Send + Sync), + ctx: &mut Context, +) -> Result<(), MpcTlsError> { + while prf.wants_flush() { + prf.flush(&mut *vm).map_err(MpcTlsError::hs)?; + vm.execute_all(ctx).await.map_err(MpcTlsError::hs)?; + } + + Ok(()) +} + +/// Splits an opaque AES-GCM record into its explicit nonce, ciphertext and tag. +#[allow(clippy::type_complexity)] +pub(crate) fn opaque_into_parts( + mut msg: Vec, +) -> Result<(Vec, Vec, Vec), MpcTlsError> { + if msg.len() < EXPLICIT_NONCE_LEN + TAG_LEN { + return Err(MpcTlsError::record_layer("ciphertext record is too short")); + } + + let tag = msg.split_off(msg.len() - TAG_LEN); + let ciphertext = msg.split_off(EXPLICIT_NONCE_LEN); + let explicit_nonce = msg; + + Ok((explicit_nonce, ciphertext, tag)) +} + +/// Verifies that, if the last record is an alert, it is a `close_notify`. +fn check_close_notify(records: &[Record]) -> Result<(), MpcTlsError> { + let Some(last_record) = records.last() else { + return Ok(()); + }; + + match last_record.typ { + ContentType::ApplicationData => {} + ContentType::Alert => { + let payload = last_record + .plaintext + .as_ref() + .ok_or_else(|| MpcTlsError::peer("alert content was hidden from the follower"))?; + + let mut reader = Reader::init(payload); + let alert = AlertMessagePayload::read(&mut reader) + .ok_or_else(|| MpcTlsError::peer("alert message was malformed"))?; + + let AlertDescription::CloseNotify = alert.description else { + return Err(MpcTlsError::peer( + "last record is an alert that is not close notify", + )); + }; + } + typ => { + return Err(MpcTlsError::peer(format!( + "last record has unexpected record content type: {typ:?}", + ))); + } + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_opaque_into_parts() { + let msg = (0u8..32).collect::>(); + let (nonce, ciphertext, tag) = opaque_into_parts(msg).unwrap(); + assert_eq!(nonce, (0..8).collect::>()); + assert_eq!(ciphertext, (8..16).collect::>()); + assert_eq!(tag, (16..32).collect::>()); + } + + #[test] + fn test_opaque_into_parts_rejects_short_record() { + // A record shorter than nonce + tag must error, not panic. + for len in 0..24 { + assert!(opaque_into_parts(vec![0; len]).is_err()); + } + // An empty ciphertext is the acceptance boundary. + assert!(opaque_into_parts(vec![0; 24]).is_ok()); + } + + #[test] + fn test_check_close_notify() { + fn record(typ: ContentType, plaintext: Option>) -> Record { + Record { + seq: 0, + typ, + plaintext, + explicit_nonce: Vec::new(), + ciphertext: Vec::new(), + tag: None, + } + } + + let close_notify = AlertMessagePayload { + level: tls_core::msgs::enums::AlertLevel::Warning, + description: AlertDescription::CloseNotify, + }; + let mut payload = Vec::new(); + close_notify.encode(&mut payload); + + // No records, application data, or a trailing close_notify are fine. + assert!(check_close_notify(&[]).is_ok()); + assert!(check_close_notify(&[record(ContentType::ApplicationData, None)]).is_ok()); + assert!(check_close_notify(&[record(ContentType::Alert, Some(payload))]).is_ok()); + + // Hidden alert content, malformed alerts, other alerts and other + // content types are rejected. + assert!(check_close_notify(&[record(ContentType::Alert, None)]).is_err()); + assert!(check_close_notify(&[record(ContentType::Alert, Some(vec![0xff]))]).is_err()); + let unexpected = AlertMessagePayload { + level: tls_core::msgs::enums::AlertLevel::Fatal, + description: AlertDescription::HandshakeFailure, + }; + let mut payload = Vec::new(); + unexpected.encode(&mut payload); + assert!(check_close_notify(&[record(ContentType::Alert, Some(payload))]).is_err()); + assert!(check_close_notify(&[record(ContentType::Handshake, None)]).is_err()); + } +} diff --git a/crates/mpc-tls/src/utils.rs b/crates/mpc-tls/src/utils.rs deleted file mode 100644 index 92a0f3bd22..0000000000 --- a/crates/mpc-tls/src/utils.rs +++ /dev/null @@ -1,226 +0,0 @@ -use hmac_sha256::{Prf, PrfOutput}; -use key_exchange::KeyExchange; -use mpz_common::Context; -use mpz_core::bitvec::BitVec; -use mpz_memory_core::{DecodeFutureTyped, MemoryExt, binary::Binary}; -use mpz_vm_core::Vm; -use tls_core::msgs::{ - alert::AlertMessagePayload, - codec::{Codec, Reader}, - enums::AlertDescription, -}; -use tlsn_core::transcript::{ContentType, Record, TlsTranscript}; - -use crate::{Config, MpcTlsError, SessionKeys, record_layer::RecordLayer}; - -/// Length of the explicit nonce prefixing every AES-GCM record. -const EXPLICIT_NONCE_LEN: usize = 8; -/// Length of an AES-GCM authentication tag. -const TAG_LEN: usize = 16; - -/// Split an opaque message into its constituent parts. -/// -/// Returns the explicit nonce, ciphertext, and tag, respectively. -#[allow(clippy::type_complexity)] -pub(crate) fn opaque_into_parts( - mut msg: Vec, -) -> Result<(Vec, Vec, Vec), MpcTlsError> { - if msg.len() < EXPLICIT_NONCE_LEN + TAG_LEN { - return Err(MpcTlsError::record_layer("ciphertext record is too short")); - } - - let tag = msg.split_off(msg.len() - TAG_LEN); - let ciphertext = msg.split_off(EXPLICIT_NONCE_LEN); - let explicit_nonce = msg; - - Ok((explicit_nonce, ciphertext, tag)) -} - -/// Allocates the MPC resources for a connection: the key exchange, the PRF -/// and the record layer. -/// -/// Returns the session keys and the decode futures for the client and server -/// Finished verify data. -#[allow(clippy::type_complexity)] -pub(crate) fn alloc_session( - vm: &mut (dyn Vm + Send + Sync), - config: &Config, - ke: &mut (dyn KeyExchange + Send + Sync), - prf: &mut Prf, - record_layer: &mut RecordLayer, -) -> Result< - ( - SessionKeys, - DecodeFutureTyped, - DecodeFutureTyped, - ), - MpcTlsError, -> { - let pms = ke.alloc(&mut *vm)?; - let PrfOutput { keys, cf_vd, sf_vd } = prf.alloc_pms(&mut *vm, pms)?; - record_layer.set_keys( - keys.client_write_key, - keys.client_iv, - keys.server_write_key, - keys.server_iv, - )?; - - let cf_vd = vm.decode(cf_vd).map_err(MpcTlsError::alloc)?; - let sf_vd = vm.decode(sf_vd).map_err(MpcTlsError::alloc)?; - - let server_write_mac_key = record_layer.alloc( - &mut *vm, - config.max_sent_records, - config.max_recv_records_online, - config.max_sent, - config.max_recv_online, - config.max_recv, - )?; - - let keys = SessionKeys { - client_write_key: keys.client_write_key, - client_write_iv: keys.client_iv, - server_write_key: keys.server_write_key, - server_write_iv: keys.server_iv, - server_write_mac_key, - }; - - Ok((keys, cf_vd, sf_vd)) -} - -/// Flushes the PRF, executing the VM until the PRF has no more work. -pub(crate) async fn flush_prf( - prf: &mut Prf, - vm: &mut (dyn Vm + Send + Sync), - ctx: &mut Context, -) -> Result<(), MpcTlsError> { - while prf.wants_flush() { - prf.flush(&mut *vm).map_err(MpcTlsError::hs)?; - vm.execute_all(ctx).await.map_err(MpcTlsError::hs)?; - } - - Ok(()) -} - -/// Verifies the Finished verify data in `transcript` against the values -/// computed in MPC, and that both directions of the connection were closed -/// properly. -pub(crate) fn verify_transcript( - transcript: &TlsTranscript, - expected_cf_vd: [u8; 12], - expected_sf_vd: [u8; 12], -) -> Result<(), MpcTlsError> { - let cf_vd = transcript - .cf_vd() - .expect("client finished verify data should be available"); - if cf_vd != expected_cf_vd { - return Err(MpcTlsError::peer("client verify data is incorrect")); - } - - let sf_vd = transcript - .sf_vd() - .expect("server finished verify data should be available"); - if sf_vd != expected_sf_vd { - return Err(MpcTlsError::peer("server verify data is incorrect")); - } - - check_close_notify(transcript.sent())?; - check_close_notify(transcript.recv())?; - - Ok(()) -} - -pub(crate) fn check_close_notify(records: &[Record]) -> Result<(), MpcTlsError> { - let Some(last_record) = records.last() else { - return Ok(()); - }; - - match last_record.typ { - ContentType::ApplicationData => {} - ContentType::Alert => { - let payload = last_record - .plaintext - .as_ref() - .ok_or_else(|| MpcTlsError::peer("alert content was hidden from the follower"))?; - - let mut reader = Reader::init(payload); - let alert = AlertMessagePayload::read(&mut reader) - .ok_or_else(|| MpcTlsError::peer("alert message was malformed"))?; - - let AlertDescription::CloseNotify = alert.description else { - return Err(MpcTlsError::peer( - "last record is an alert that is not close notify", - )); - }; - } - typ => { - return Err(MpcTlsError::peer(format!( - "last record has unexpected record content type: {typ:?}", - ))); - } - } - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_opaque_into_parts() { - let msg = (0u8..32).collect::>(); - let (nonce, ciphertext, tag) = opaque_into_parts(msg).unwrap(); - assert_eq!(nonce, (0..8).collect::>()); - assert_eq!(ciphertext, (8..16).collect::>()); - assert_eq!(tag, (16..32).collect::>()); - } - - #[test] - fn test_opaque_into_parts_rejects_short_record() { - // A record shorter than nonce + tag must error, not panic. - for len in 0..24 { - assert!(opaque_into_parts(vec![0; len]).is_err()); - } - // An empty ciphertext is the acceptance boundary. - assert!(opaque_into_parts(vec![0; 24]).is_ok()); - } - - #[test] - fn test_check_close_notify() { - fn record(typ: ContentType, plaintext: Option>) -> Record { - Record { - seq: 0, - typ, - plaintext, - explicit_nonce: Vec::new(), - ciphertext: Vec::new(), - tag: None, - } - } - - let close_notify = AlertMessagePayload { - level: tls_core::msgs::enums::AlertLevel::Warning, - description: AlertDescription::CloseNotify, - }; - let mut payload = Vec::new(); - close_notify.encode(&mut payload); - - // No records, application data, or a trailing close_notify are fine. - assert!(check_close_notify(&[]).is_ok()); - assert!(check_close_notify(&[record(ContentType::ApplicationData, None)]).is_ok()); - assert!(check_close_notify(&[record(ContentType::Alert, Some(payload))]).is_ok()); - - // Hidden alert content, malformed alerts, other alerts and other - // content types are rejected. - assert!(check_close_notify(&[record(ContentType::Alert, None)]).is_err()); - assert!(check_close_notify(&[record(ContentType::Alert, Some(vec![0xff]))]).is_err()); - let unexpected = AlertMessagePayload { - level: tls_core::msgs::enums::AlertLevel::Fatal, - description: AlertDescription::HandshakeFailure, - }; - let mut payload = Vec::new(); - unexpected.encode(&mut payload); - assert!(check_close_notify(&[record(ContentType::Alert, Some(payload))]).is_err()); - assert!(check_close_notify(&[record(ContentType::Handshake, None)]).is_err()); - } -} diff --git a/crates/mpc-tls/src/client/vecbuf.rs b/crates/mpc-tls/src/vecbuf.rs similarity index 99% rename from crates/mpc-tls/src/client/vecbuf.rs rename to crates/mpc-tls/src/vecbuf.rs index 87a28dd306..c90a08493c 100644 --- a/crates/mpc-tls/src/client/vecbuf.rs +++ b/crates/mpc-tls/src/vecbuf.rs @@ -1,6 +1,5 @@ use std::{cmp, collections::VecDeque, io, io::Read}; - /// This is a byte buffer that is built from a vector /// of byte vectors. This avoids extra copies when /// appending a new byte vector, at the expense of diff --git a/crates/tlsn/src/prover/client/mpc.rs b/crates/tlsn/src/prover/client/mpc.rs index 733726ccc4..0b8d2ea6c8 100644 --- a/crates/tlsn/src/prover/client/mpc.rs +++ b/crates/tlsn/src/prover/client/mpc.rs @@ -6,7 +6,7 @@ use crate::{ prover::client::{DecryptState, TlsClient, TlsOutput}, }; use futures::{Future, FutureExt}; -use mpc_tls::{MpcTlsLeader, SessionKeys, client::ClientConnection}; +use mpc_tls::{ClientConfig, MpcTlsLeader, SessionKeys}; use mpz_common::Context; use rustls_pki_types::CertificateDer; use std::{ @@ -63,20 +63,16 @@ impl MpcTlsClient { server_name: ServerName, mpc_tls: MpcTlsLeader, ) -> Result { - let config = create_client_config(config)?; + let client_config = Arc::new(create_client_config(config)?); let decrypt = DecryptState { decrypt: AtomicBool::new(mpc_tls.is_decrypting()), }; - let tls = ClientConnection::new(Arc::new(config), mpc_tls, server_name).map_err(|e| { - TlsnError::config() - .with_msg("failed to create tls client connection") - .with_source(e) - })?; - let inner = InnerState { span, - tls, + tls: mpc_tls, + client_config, + server_name, vm, keys, decrypt: decrypt.is_decrypting(), @@ -95,7 +91,7 @@ impl MpcTlsClient { Ok(client) } - fn inner_client_mut(&mut self) -> Option<&mut ClientConnection> { + fn inner_client_mut(&mut self) -> Option<&mut MpcTlsLeader> { if let State::Active { inner } | State::CloseActive { inner } = &mut self.state { Some(&mut inner.tls) } else { @@ -103,7 +99,7 @@ impl MpcTlsClient { } } - fn inner_client(&self) -> Option<&ClientConnection> { + fn inner_client(&self) -> Option<&MpcTlsLeader> { if let State::Active { inner } | State::CloseActive { inner } = &self.state { Some(&inner.tls) } else { @@ -325,7 +321,9 @@ impl TlsClient for MpcTlsClient { struct InnerState { span: Span, - tls: ClientConnection, + tls: MpcTlsLeader, + client_config: Arc, + server_name: ServerName, vm: Arc>>, keys: SessionKeys, decrypt: bool, @@ -335,8 +333,10 @@ struct InnerState { impl InnerState { #[instrument(parent = &self.span, level = "debug", skip_all, err)] async fn start(mut self: Box) -> Result, TlsnError> { + let client_config = self.client_config.clone(); + let server_name = self.server_name.clone(); self.tls - .start() + .start(client_config, server_name) .await .map_err(|err| TlsnError::internal().with_source(err))?; Ok(self) @@ -421,10 +421,8 @@ impl InnerState { } } -fn create_client_config( - config: &TlsClientConfig, -) -> Result { - let root_store = mpc_tls::client::RootCertStore { +fn create_client_config(config: &TlsClientConfig) -> Result { + let root_store = mpc_tls::RootCertStore { roots: config .root_store() .roots @@ -443,12 +441,12 @@ fn create_client_config( }; let client_config = if let Some((cert, key)) = config.client_auth() { - mpc_tls::client::ClientConfig::new_with_client_auth( + mpc_tls::ClientConfig::new_with_client_auth( root_store, cert.iter() - .map(|cert| mpc_tls::client::Certificate(cert.0.clone())) + .map(|cert| mpc_tls::Certificate(cert.0.clone())) .collect(), - mpc_tls::client::PrivateKey(key.0.clone()), + mpc_tls::PrivateKey(key.0.clone()), ) .map_err(|e| { TlsnError::config() @@ -456,7 +454,7 @@ fn create_client_config( .with_source(e) })? } else { - mpc_tls::client::ClientConfig::new(root_store) + mpc_tls::ClientConfig::new(root_store) }; Ok(client_config)