cometbft_p2p/
secret_connection.rs

1//! Encrypted connection between peers in a CometBFT network.
2
3use crate::{
4    Error, MAX_MSG_LEN, PublicKey, Result,
5    ed25519::{self, Signer},
6    encryption::{Frame, RecvState, SendState},
7    handshake, proto,
8    traits::{ReadMsg, TryCloneIo, WriteMsg},
9};
10use prost::Message;
11use std::io::{self, Read, Write};
12
13#[cfg(doc)]
14use crate::IdentitySecret;
15
16/// Encrypted connection between peers in a CometBFT network.
17///
18/// ## Sending and receiving messages
19///
20/// The [`SecretConnection`] type implements a message-oriented interface which can send and receive
21/// Protobuf-encoded messages that impl the [`prost::Message`] trait.
22///
23/// The [`ReadMsg`] and [`WriteMsg`] traits can be used for sending/receiving Protobuf messages.
24///
25/// ## Connection integrity and failures
26///
27/// Due to the underlying encryption mechanism (currently [RFC 8439]), when a
28/// read or write failure occurs, it is necessary to disconnect from the remote
29/// peer and attempt to reconnect.
30///
31/// [RFC 8439]: https://www.rfc-editor.org/rfc/rfc8439.html
32pub struct SecretConnection<Io> {
33    /// Message reader which holds the read-half of the I/O object and the associated symmetric
34    /// cipher state.
35    reader: SecretReader<Io>,
36
37    /// Message writer which holds the write-half of the I/O object and the associated symmetric
38    /// cipher state.
39    writer: SecretWriter<Io>,
40
41    /// Our identity's Ed25519 public key.
42    local_public_key: PublicKey,
43
44    /// Remote peer's Ed25519 public key.
45    peer_public_key: PublicKey,
46}
47
48impl<Io: Read + Write + Send + Sync + TryCloneIo> SecretConnection<Io> {
49    /// Performs a handshake and returns a new `SecretConnection`, authenticating ourselves with the
50    /// provided `Identity` (Ed25519 signing key).
51    ///
52    /// The [`IdentitySecret`] type can be used as an `identity_key`.
53    ///
54    /// # Errors
55    ///
56    /// - if sharing of the pubkey fails
57    /// - if sharing of the signature fails
58    /// - if receiving the signature fails
59    /// - if verifying the signature fails
60    pub fn new<Identity>(mut io: Io, identity_key: &Identity) -> Result<Self>
61    where
62        Identity: Signer<ed25519::Signature>,
63        ed25519::VerifyingKey: for<'a> From<&'a Identity>,
64    {
65        // Start a handshake process, generating a local ephemeral X25519 public key.
66        let local_public_key: PublicKey = ed25519::VerifyingKey::from(identity_key).into();
67        let (mut initial_state, initial_message) = handshake::InitialState::new();
68
69        // Send our ephemeral X25519 public key to the remote peer (unencrypted).
70        io.write_msg(&initial_message)?;
71
72        // Read the remote side's initial message containing their X25519 public key (unencrypted)
73        let peer_initial_message: handshake::InitialMessage = io.read_msg()?;
74
75        // Compute signature over the handshake transcript and initialize symmetric cipher state
76        // using shared secret computed using X25519.
77        let (challenge, cipher_state) = initial_state.got_key(peer_initial_message.pub_key)?;
78
79        // Create the async message reader and writer objects.
80        let io2 = io.try_clone()?;
81        let mut reader = SecretReader {
82            io,
83            recv_state: cipher_state.recv_state,
84        };
85        let mut writer = SecretWriter {
86            io: io2,
87            send_state: cipher_state.send_state,
88        };
89
90        // Send our identity's Ed25519 public key and signature over the transcript to the peer.
91        writer.write_msg(&proto::p2p::AuthSigMessage {
92            pub_key: Some(local_public_key.into()),
93            sig: challenge.sign_challenge(identity_key).to_vec(),
94        })?;
95
96        // Read the peer's Ed25519 public key and use it to verify their signature over the
97        // handshake transcript.
98        let auth_sig_msg: proto::p2p::AuthSigMessage = reader.read_msg()?;
99
100        // Verify the key and signature validate for our computed Merlin transcript hash
101        let peer_public_key = challenge.got_signature(auth_sig_msg)?;
102
103        // All good!
104        Ok(Self {
105            reader,
106            writer,
107            local_public_key,
108            peer_public_key,
109        })
110    }
111}
112
113impl<M: Message + Default, Io: Read> ReadMsg<M> for SecretConnection<Io> {
114    fn read_msg(&mut self) -> Result<M> {
115        self.reader.read_msg()
116    }
117}
118
119impl<M: Message, Io: Write> WriteMsg<M> for SecretConnection<Io> {
120    fn write_msg(&mut self, msg: &M) -> Result<()> {
121        self.writer.write_msg(msg)
122    }
123}
124
125impl<Io: Write> SecretConnection<Io> {
126    /// Flush the underlying I/O object's write buffer.
127    pub fn flush(&mut self) -> io::Result<()> {
128        self.writer.flush()
129    }
130}
131
132impl<Io> SecretConnection<Io> {
133    /// Get the local (i.e. our) [`PublicKey`].
134    pub fn local_public_key(&self) -> &PublicKey {
135        &self.local_public_key
136    }
137
138    /// Returns the remote peer's [`PublicKey`].
139    pub fn peer_public_key(&self) -> &PublicKey {
140        &self.peer_public_key
141    }
142
143    /// Split this [`SecretConnection`] into a [`SecretReader`] and [`SecretWriter`] which can be
144    /// used independently of each other.
145    pub fn split(self) -> (SecretReader<Io>, SecretWriter<Io>) {
146        (self.reader, self.writer)
147    }
148}
149
150/// Encrypted message reader type which wraps the read-half of an underlying I/O object.
151pub struct SecretReader<Io> {
152    /// Inner I/O reader object this connection type wraps.
153    io: Io,
154
155    /// Symmetric cipher state including the current nonce.
156    recv_state: RecvState,
157}
158
159impl<Io: Read> SecretReader<Io> {
160    /// Read and decrypt a frame from the network.
161    #[inline]
162    fn read_frame(&mut self) -> Result<Frame> {
163        let mut bytes = [0u8; Frame::ENCRYPTED_SIZE];
164        self.io.read_exact(&mut bytes)?;
165
166        let mut frame = Frame::from_ciphertext(bytes);
167        self.recv_state.decrypt_frame(&mut frame)?;
168        Ok(frame)
169    }
170}
171
172impl<M: Message + Default, Io: Read> ReadMsg<M> for SecretReader<Io> {
173    fn read_msg(&mut self) -> Result<M> {
174        let frame = self.read_frame()?;
175        let frame_plaintext = frame.plaintext()?;
176
177        // Decode the length prefix on the proto
178        let msg_len = proto::decode_length_delimiter_inclusive(frame_plaintext)?;
179
180        if msg_len > MAX_MSG_LEN {
181            return Err(Error::MessageSize { size: msg_len });
182        }
183
184        // Skip the heap if the proto fits in a single message frame
185        if frame_plaintext.len() == msg_len {
186            return Ok(M::decode_length_delimited(frame_plaintext)?);
187        }
188
189        let mut msg = Vec::with_capacity(msg_len);
190        msg.extend_from_slice(frame_plaintext);
191
192        while msg.len() < msg_len {
193            msg.extend_from_slice(self.read_frame()?.plaintext()?);
194        }
195
196        Ok(M::decode_length_delimited(msg.as_slice())?)
197    }
198}
199
200/// Encrypted message writer type which wraps the write-half of an underlying I/O object.
201pub struct SecretWriter<Io> {
202    /// Inner I/O writer object this connection type wraps.
203    io: Io,
204
205    /// Symmetric cipher state including the current nonce.
206    send_state: SendState,
207}
208
209impl<Io: Write> SecretWriter<Io> {
210    /// Flush the underlying I/O object's write buffer.
211    pub fn flush(&mut self) -> io::Result<()> {
212        self.io.flush()
213    }
214
215    /// Encrypt and write a frame to the network.
216    #[inline]
217    fn write_frame(&mut self, plaintext: &[u8]) -> Result<()> {
218        let mut frame = Frame::from_plaintext(plaintext)?;
219        self.send_state.encrypt_frame(&mut frame)?;
220        Ok(self.io.write_all(frame.ciphertext()?)?)
221    }
222}
223
224impl<M: Message, Io: Write> WriteMsg<M> for SecretWriter<Io> {
225    fn write_msg(&mut self, msg: &M) -> Result<()> {
226        let bytes = msg.encode_length_delimited_to_vec();
227
228        for chunk in bytes.chunks(Frame::MAX_PLAINTEXT_SIZE) {
229            self.write_frame(chunk)?;
230        }
231
232        Ok(())
233    }
234}
235
236// NOTE: tests are in `tests/secret_connection.rs`