Skip to main content

kevy_replicate/
replica_secure.rs

1//! The replica side of a Noise-protected replication link: the handshake,
2//! then sealed writes and decrypted reads under the plaintext protocol.
3
4use std::io::{self, Read, Write};
5use std::net::TcpStream;
6
7use kevy_noise::{Frames, Initiator, Keypair, MAX_MESSAGE, Transport, frame};
8
9use crate::replica::{ReplicaClient, connect_stream, encode_replicate_from, parse_ack_line};
10use crate::replica_error::ReplicaError;
11
12/// Must match the primary's.
13const PROLOGUE: &[u8] = b"kevy-replicate\x001";
14const TAG: usize = 16;
15
16/// What a replica needs to open a secure link: its own key, and the
17/// primary's public key it will accept.
18///
19/// ```
20/// use kevy_noise::Keypair;
21/// use kevy_replicate::replica::ReplicaSecurity;
22///
23/// let sec = ReplicaSecurity::new(Keypair::from_secret([2; 32]), [9; 32]);
24/// assert_eq!(sec.primary_key, [9; 32]);
25/// ```
26#[derive(Debug, Clone)]
27#[non_exhaustive]
28pub struct ReplicaSecurity {
29    /// The replica's static key pair; a primary with `replica_keys` set
30    /// lists its public half.
31    ///
32    /// ```
33    /// let sec = kevy_replicate::replica::ReplicaSecurity::new(kevy_noise::Keypair::from_secret([2; 32]), [9; 32]);
34    /// assert_ne!(sec.local.public(), [0; 32]);
35    /// ```
36    pub local: Keypair,
37    /// The primary's public key; a primary holding any other key cannot
38    /// complete the handshake.
39    ///
40    /// ```
41    /// let primary = kevy_noise::Keypair::from_secret([1; 32]);
42    /// let sec = kevy_replicate::replica::ReplicaSecurity::new(
43    ///     kevy_noise::Keypair::from_secret([2; 32]),
44    ///     primary.public(),
45    /// );
46    /// assert_eq!(sec.primary_key, primary.public());
47    /// ```
48    pub primary_key: [u8; 32],
49}
50
51impl ReplicaSecurity {
52    /// The replica's key pair `local` and the public key the primary must
53    /// present. Neither has a default: without them the link authenticates
54    /// nobody.
55    ///
56    /// ```
57    /// let sec = kevy_replicate::replica::ReplicaSecurity::new(
58    ///     kevy_noise::Keypair::from_secret([2; 32]),
59    ///     [7; 32],
60    /// );
61    /// assert_eq!(sec.primary_key, [7; 32]);
62    /// ```
63    pub fn new(local: Keypair, primary_key: [u8; 32]) -> Self {
64        Self { local, primary_key }
65    }
66}
67
68pub(crate) struct ClientNoise {
69    transport: Transport,
70    frames: Frames,
71}
72
73impl core::fmt::Debug for ClientNoise {
74    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
75        f.write_str("ClientNoise")
76    }
77}
78
79fn invalid(e: impl std::fmt::Display) -> io::Error {
80    io::Error::new(io::ErrorKind::InvalidData, e.to_string())
81}
82
83#[cfg(not(target_arch = "wasm32"))]
84fn fresh_secret() -> io::Result<[u8; 32]> {
85    let mut secret = [0u8; 32];
86    kevy_sys::fill_random(&mut secret)?;
87    Ok(secret)
88}
89
90// no entropy source, and no TCP replication to protect, on wasm32
91#[cfg(target_arch = "wasm32")]
92fn fresh_secret() -> io::Result<[u8; 32]> {
93    Err(io::ErrorKind::Unsupported.into())
94}
95
96pub(crate) fn handshake(
97    sock: &mut TcpStream,
98    sec: &ReplicaSecurity,
99) -> Result<ClientNoise, ReplicaError> {
100    let ephemeral = Keypair::from_secret(fresh_secret()?);
101    let (m1, init) = Initiator::start(&sec.local, &sec.primary_key, ephemeral, PROLOGUE, b"")
102        .map_err(invalid)?;
103    sock.write_all(&frame(&m1).map_err(invalid)?)?;
104    let mut frames = Frames::default();
105    let mut chunk = [0u8; 256];
106    let reply = loop {
107        if let Some(m) = frames.next() {
108            break m;
109        }
110        let n = sock.read(&mut chunk)?;
111        if n == 0 {
112            return Err(ReplicaError::HandshakeRejected);
113        }
114        frames.push(&chunk[..n]);
115    };
116    // the primary answers only if it accepted our key; a wrong primary key
117    // fails here
118    let (_, transport) = init.finish(&reply).map_err(invalid)?;
119    Ok(ClientNoise { transport, frames })
120}
121
122impl ClientNoise {
123    pub(crate) fn write(&mut self, sock: &mut TcpStream, plain: &[u8]) -> io::Result<()> {
124        let mut out = Vec::with_capacity(plain.len() + 2 * TAG + 4);
125        for chunk in plain.chunks(MAX_MESSAGE - TAG) {
126            let sealed = self.transport.seal(chunk).map_err(invalid)?;
127            out.extend(frame(&sealed).map_err(invalid)?);
128        }
129        sock.write_all(&out)
130    }
131
132    /// One socket read; the plaintext of every frame it completes is
133    /// appended to `out`. Returns the raw byte count, `0` at end of stream.
134    pub(crate) fn read(
135        &mut self,
136        sock: &mut TcpStream,
137        chunk: &mut [u8],
138        out: &mut Vec<u8>,
139    ) -> io::Result<usize> {
140        let n = sock.read(chunk)?;
141        self.frames.push(&chunk[..n]);
142        while let Some(m) = self.frames.next() {
143            out.extend(self.transport.open(&m).map_err(invalid)?);
144        }
145        Ok(n)
146    }
147}
148
149impl ReplicaClient {
150    /// The Noise IK half of [`Self::connect_with`]: the primary must
151    /// present `security.primary_key`, and it sees this replica's key
152    /// before it answers. Everything after the handshake is encrypted.
153    pub(crate) fn connect_noise<A: std::net::ToSocketAddrs>(
154        addr: A,
155        opts: &crate::replica::ConnectOptions,
156        security: &ReplicaSecurity,
157    ) -> Result<Self, ReplicaError> {
158        let connect_timeout = opts.timeout;
159        let replica_id = opts.replica_id.as_str();
160        let mut sock = connect_stream(addr, connect_timeout)?;
161        sock.set_read_timeout(Some(connect_timeout))?;
162        let mut noise = handshake(&mut sock, security)?;
163        noise.write(&mut sock, &encode_replicate_from(opts.from, replica_id))?;
164        let mut plain = Vec::new();
165        let mut chunk = [0u8; 256];
166        let line_end = loop {
167            if let Some(i) = plain.windows(2).position(|w| w == b"\r\n") {
168                break i + 2;
169            }
170            if plain.len() > 256 {
171                return Err(ReplicaError::AckMalformed);
172            }
173            if noise.read(&mut sock, &mut chunk, &mut plain)? == 0 {
174                return Err(ReplicaError::HandshakeRejected);
175            }
176        };
177        let primary_at_handshake = parse_ack_line(&plain[..line_end])?;
178        sock.set_read_timeout(None)?;
179        sock.set_nonblocking(false)?;
180        let mut buf = Vec::with_capacity(8 * 1024);
181        // frames that arrived in the same message as the +ACK
182        buf.extend_from_slice(&plain[line_end..]);
183        Ok(ReplicaClient {
184            sock,
185            buf,
186            cursor: 0,
187            primary_at_handshake,
188            expected_offset: opts.from.offset,
189            in_snapshot: false,
190            noise: Some(noise),
191        })
192    }
193}