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    Noise_NN_25519_AESGCM_SHA256,
29    #[allow(non_camel_case_types)]
30    #[default]
31    Noise_NN_P256_AESGCM_SHA256,
32}
33
34impl CipherSuite {
35    /// Returns the transport cipher corresponding to this cipher suite.
36    pub(crate) fn transport_cipher(&self) -> TransportCipher {
37        match self {
38            Self::Noise_NN_25519_ChaChaPoly_BLAKE2s => TransportCipher::ChaCha20Poly1305,
39            Self::Noise_NN_25519_AESGCM_SHA256 => TransportCipher::Aes256Gcm,
40            Self::Noise_NN_P256_AESGCM_SHA256 => TransportCipher::Aes256Gcm,
41        }
42    }
43}
44
45impl Display for CipherSuite {
46    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
47        match self {
48            Self::Noise_NN_25519_ChaChaPoly_BLAKE2s => {
49                write!(f, "Noise_NN_25519_ChaChaPoly_BLAKE2s")
50            }
51            Self::Noise_NN_25519_AESGCM_SHA256 => {
52                write!(f, "Noise_NN_25519_AESGCM_SHA256")
53            }
54            Self::Noise_NN_P256_AESGCM_SHA256 => {
55                write!(f, "Noise_NN_P256_AESGCM_SHA256")
56            }
57        }
58    }
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub(crate) struct HandshakeStartMessage {
63    pub(super) ciphersuite: CipherSuite,
64    pub(super) noise_frame: Vec<u8>,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
68#[serde(transparent)]
69pub(crate) struct HandshakeFinishMessage {
70    pub(super) noise_frame: Vec<u8>,
71}
72
73pub(crate) struct HandshakeInitiator {
74    ciphersuite: CipherSuite,
75    state: snow::HandshakeState,
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub(crate) struct WriteError;
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub(crate) struct ReadError;
83
84impl HandshakeInitiator {
85    pub(crate) fn new(ciphersuite: &CipherSuite) -> Self {
86        let builder = snow::Builder::with_resolver(
87            ciphersuite
88                .to_string()
89                .parse()
90                .expect("Ciphersuite should be valid"),
91            Box::new(bitwarden_random::SdkCryptoResolver),
92        );
93        let handshake_state = builder
94            .build_initiator()
95            .expect("Handshake state should be buildable");
96        Self {
97            ciphersuite: *ciphersuite,
98            state: handshake_state,
99        }
100    }
101
102    pub(crate) fn write_start_message(&mut self) -> Result<HandshakeStartMessage, WriteError> {
103        let mut buf = [0u8; super::NOISE_MAX_MESSAGE_LEN];
104        let len = self
105            .state
106            .write_message(&[], &mut buf)
107            .map_err(|_| WriteError)?;
108        Ok(HandshakeStartMessage {
109            ciphersuite: self.ciphersuite,
110            noise_frame: buf[..len].to_vec(),
111        })
112    }
113
114    pub(crate) fn read_response_message(
115        &mut self,
116        message: &HandshakeFinishMessage,
117    ) -> Result<(), ReadError> {
118        let mut buf = [0u8; super::NOISE_MAX_MESSAGE_LEN];
119        self.state
120            .read_message(&message.noise_frame, &mut buf)
121            .map_err(|_| ReadError)?;
122        Ok(())
123    }
124}
125
126impl From<&mut HandshakeInitiator> for PersistentTransportState {
127    fn from(initiator: &mut HandshakeInitiator) -> Self {
128        let (i2r, r2i) = initiator.state.dangerously_get_raw_split();
129        PersistentTransportState::new(
130            SymmetricKey(i2r),
131            SymmetricKey(r2i),
132            initiator.ciphersuite.transport_cipher(),
133        )
134    }
135}
136
137pub(crate) struct HandshakeResponder {
138    ciphersuite: CipherSuite,
139    state: snow::HandshakeState,
140}
141
142impl HandshakeResponder {
143    pub(crate) fn new(ciphersuite: &CipherSuite) -> Self {
144        let builder = snow::Builder::with_resolver(
145            ciphersuite
146                .to_string()
147                .parse()
148                .expect("Ciphersuite should be valid"),
149            Box::new(bitwarden_random::SdkCryptoResolver),
150        );
151        let handshake_state = builder
152            .build_responder()
153            .expect("Handshake state should be buildable");
154        Self {
155            ciphersuite: *ciphersuite,
156            state: handshake_state,
157        }
158    }
159
160    pub(crate) fn read_start_message(
161        &mut self,
162        message: &HandshakeStartMessage,
163    ) -> Result<(), ReadError> {
164        let mut buf = [0u8; super::NOISE_MAX_MESSAGE_LEN];
165        self.state
166            .read_message(&message.noise_frame, &mut buf)
167            .map_err(|_| ReadError)?;
168        Ok(())
169    }
170
171    pub(crate) fn write_response_message(&mut self) -> Result<HandshakeFinishMessage, WriteError> {
172        let mut buf = [0u8; super::NOISE_MAX_MESSAGE_LEN];
173        let len = self
174            .state
175            .write_message(&[], &mut buf)
176            .map_err(|_| WriteError)?;
177        Ok(HandshakeFinishMessage {
178            noise_frame: buf[..len].to_vec(),
179        })
180    }
181}
182
183impl From<&mut HandshakeResponder> for PersistentTransportState {
184    fn from(responder: &mut HandshakeResponder) -> Self {
185        let (i2r, r2i) = responder.state.dangerously_get_raw_split();
186        PersistentTransportState::new(
187            SymmetricKey(r2i),
188            SymmetricKey(i2r),
189            responder.ciphersuite.transport_cipher(),
190        )
191    }
192}
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197    use crate::crypto_provider::noise::transport_state::assert_matching_pair;
198
199    fn run_handshake(ciphersuite: &CipherSuite) {
200        let mut initiator = HandshakeInitiator::new(ciphersuite);
201        let mut responder = HandshakeResponder::new(ciphersuite);
202
203        let init_message = initiator.write_start_message().unwrap();
204        responder.read_start_message(&init_message).unwrap();
205        let response_message = responder.write_response_message().unwrap();
206        initiator.read_response_message(&response_message).unwrap();
207
208        let initiator_transport_state = (&mut initiator).into();
209        let responder_transport_state = (&mut responder).into();
210        assert_matching_pair(&initiator_transport_state, &responder_transport_state);
211    }
212
213    #[test]
214    fn test_handshake() {
215        for ciphersuite in [
216            CipherSuite::Noise_NN_25519_ChaChaPoly_BLAKE2s,
217            CipherSuite::Noise_NN_25519_AESGCM_SHA256,
218            CipherSuite::Noise_NN_P256_AESGCM_SHA256,
219        ] {
220            run_handshake(&ciphersuite);
221        }
222    }
223}