Skip to main content

bitwarden_ipc/crypto_provider/noise/
handshake.rs

1//! This module implements the Noise NN handshake for IPC.
2//! Note: NN does not provide any sort of authentication, and the keys each
3//! side uses are just trusted. This means that a MITM with active tampering is possible and
4//! accepted. Thereby it is necessary that either the threat model of the application using IPC
5//! assumes that the IPC channel is not exposed to MITM attacks, or that the transport layer
6//! prevents MITM with active tampering.
7//!
8//! Protocol flow:
9//! 1. Initiator -> Responder: `HandshakeStartMessage { ciphersuite, noise_frame }`
10//! 2. Responder -> Initiator: `HandshakeFinishMessage { noise_frame }`
11//!
12//! After both messages are processed, each side derives split transport keys from the
13//! handshake state and constructs a `PersistentTransportState`.
14
15use std::fmt::{Display, Formatter};
16
17use serde::{Deserialize, Serialize};
18
19use crate::crypto_provider::noise::transport_state::{
20    PersistentTransportState, SymmetricKey, TransportCipher,
21};
22
23#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
24pub(crate) enum CipherSuite {
25    #[allow(non_camel_case_types)]
26    Noise_NN_25519_ChaChaPoly_BLAKE2s,
27    #[allow(non_camel_case_types)]
28    #[default]
29    Noise_NN_25519_AESGCM_SHA256,
30}
31
32impl CipherSuite {
33    /// Returns the transport cipher corresponding to this cipher suite.
34    pub(crate) fn transport_cipher(&self) -> TransportCipher {
35        match self {
36            Self::Noise_NN_25519_ChaChaPoly_BLAKE2s => TransportCipher::ChaCha20Poly1305,
37            Self::Noise_NN_25519_AESGCM_SHA256 => TransportCipher::Aes256Gcm,
38        }
39    }
40}
41
42impl Display for CipherSuite {
43    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
44        match self {
45            Self::Noise_NN_25519_ChaChaPoly_BLAKE2s => {
46                write!(f, "Noise_NN_25519_ChaChaPoly_BLAKE2s")
47            }
48            Self::Noise_NN_25519_AESGCM_SHA256 => {
49                write!(f, "Noise_NN_25519_AESGCM_SHA256")
50            }
51        }
52    }
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub(crate) struct HandshakeStartMessage {
57    pub(super) ciphersuite: CipherSuite,
58    pub(super) noise_frame: Vec<u8>,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
62#[serde(transparent)]
63pub(crate) struct HandshakeFinishMessage {
64    pub(super) noise_frame: Vec<u8>,
65}
66
67pub(crate) struct HandshakeInitiator {
68    ciphersuite: CipherSuite,
69    state: snow::HandshakeState,
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub(crate) struct WriteError;
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub(crate) struct ReadError;
77
78impl HandshakeInitiator {
79    pub(crate) fn new(ciphersuite: &CipherSuite) -> Self {
80        let builder = snow::Builder::new(
81            ciphersuite
82                .to_string()
83                .parse()
84                .expect("Ciphersuite should be valid"),
85        );
86        let handshake_state = builder
87            .build_initiator()
88            .expect("Handshake state should be buildable");
89        Self {
90            ciphersuite: *ciphersuite,
91            state: handshake_state,
92        }
93    }
94
95    pub(crate) fn write_start_message(&mut self) -> Result<HandshakeStartMessage, WriteError> {
96        let mut buf = [0u8; super::NOISE_MAX_MESSAGE_LEN];
97        let len = self
98            .state
99            .write_message(&[], &mut buf)
100            .map_err(|_| WriteError)?;
101        Ok(HandshakeStartMessage {
102            ciphersuite: self.ciphersuite,
103            noise_frame: buf[..len].to_vec(),
104        })
105    }
106
107    pub(crate) fn read_response_message(
108        &mut self,
109        message: &HandshakeFinishMessage,
110    ) -> Result<(), ReadError> {
111        let mut buf = [0u8; super::NOISE_MAX_MESSAGE_LEN];
112        self.state
113            .read_message(&message.noise_frame, &mut buf)
114            .map_err(|_| ReadError)?;
115        Ok(())
116    }
117}
118
119impl From<&mut HandshakeInitiator> for PersistentTransportState {
120    fn from(initiator: &mut HandshakeInitiator) -> Self {
121        let (i2r, r2i) = initiator.state.dangerously_get_raw_split();
122        PersistentTransportState::new(
123            SymmetricKey(i2r),
124            SymmetricKey(r2i),
125            initiator.ciphersuite.transport_cipher(),
126        )
127    }
128}
129
130pub(crate) struct HandshakeResponder {
131    ciphersuite: CipherSuite,
132    state: snow::HandshakeState,
133}
134
135impl HandshakeResponder {
136    pub(crate) fn new(ciphersuite: &CipherSuite) -> Self {
137        let builder = snow::Builder::new(
138            ciphersuite
139                .to_string()
140                .parse()
141                .expect("Ciphersuite should be valid"),
142        );
143        let handshake_state = builder
144            .build_responder()
145            .expect("Handshake state should be buildable");
146        Self {
147            ciphersuite: *ciphersuite,
148            state: handshake_state,
149        }
150    }
151
152    pub(crate) fn read_start_message(
153        &mut self,
154        message: &HandshakeStartMessage,
155    ) -> Result<(), ReadError> {
156        let mut buf = [0u8; super::NOISE_MAX_MESSAGE_LEN];
157        self.state
158            .read_message(&message.noise_frame, &mut buf)
159            .map_err(|_| ReadError)?;
160        Ok(())
161    }
162
163    pub(crate) fn write_response_message(&mut self) -> Result<HandshakeFinishMessage, WriteError> {
164        let mut buf = [0u8; super::NOISE_MAX_MESSAGE_LEN];
165        let len = self
166            .state
167            .write_message(&[], &mut buf)
168            .map_err(|_| WriteError)?;
169        Ok(HandshakeFinishMessage {
170            noise_frame: buf[..len].to_vec(),
171        })
172    }
173}
174
175impl From<&mut HandshakeResponder> for PersistentTransportState {
176    fn from(responder: &mut HandshakeResponder) -> Self {
177        let (i2r, r2i) = responder.state.dangerously_get_raw_split();
178        PersistentTransportState::new(
179            SymmetricKey(r2i),
180            SymmetricKey(i2r),
181            responder.ciphersuite.transport_cipher(),
182        )
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189    use crate::crypto_provider::noise::transport_state::assert_matching_pair;
190
191    #[test]
192    fn test_handshake() {
193        let mut initiator = HandshakeInitiator::new(&CipherSuite::default());
194        let mut responder = HandshakeResponder::new(&CipherSuite::default());
195
196        let init_message = initiator.write_start_message().unwrap();
197        responder.read_start_message(&init_message).unwrap();
198        let response_message = responder.write_response_message().unwrap();
199        initiator.read_response_message(&response_message).unwrap();
200
201        let initiator_transport_state = (&mut initiator).into();
202        let responder_transport_state = (&mut responder).into();
203        assert_matching_pair(&initiator_transport_state, &responder_transport_state);
204    }
205}