p2p/webrtc/connection_auth.rs
1//! WebRTC connection authentication.
2//!
3//! This module provides cryptographic authentication for WebRTC connections
4//! using SDP hashes and public key encryption to prevent man-in-the-middle
5//! attacks. The authentication mechanism ensures that WebRTC connections are
6//! established only between legitimate peers with verified identities.
7//!
8//! ## Security Model
9//!
10//! The connection authentication process works by:
11//!
12//! 1. **SDP Hash Combination**: Combining the SDP hashes from both the WebRTC
13//! offer and answer to create a unique authentication token
14//! 2. **Public Key Encryption**: Encrypting the authentication data using the
15//! recipient's public key to ensure only they can decrypt it
16//! 3. **Mutual Verification**: Both parties verify each other's ability to
17//! decrypt the authentication data, proving they possess the correct private
18//! keys
19//!
20//! ## Authentication Flow
21//!
22//! ```text
23//! Peer A Peer B
24//! | |
25//! | 1. Create Offer (with SDP) |
26//! |------------------------------------> |
27//! | |
28//! | 2. Create Answer (with SDP) |
29//! | <------------------------------------|
30//! | |
31//! | 3. Generate ConnectionAuth from |
32//! | both SDP hashes |
33//! | |
34//! | 4. Encrypt with peer's public key |
35//! |------------------------------------> |
36//! | |
37//! | 5. Decrypt and verify |
38//! | <------------------------------------|
39//! | |
40//! | 6. Connection authenticated ✓ |
41//! ```
42//!
43//! ## Security Properties
44//!
45//! - **Identity Verification**: Ensures both parties possess the private keys
46//! corresponding to their advertised public keys
47//! - **Man-in-the-Middle Protection**: Prevents attackers from intercepting and
48//! modifying the connection establishment process
49//! - **Replay Attack Prevention**: Uses unique SDP hashes for each connection
50//! attempt, preventing replay attacks
51
52use rand::{CryptoRng, Rng};
53use serde::{Deserialize, Serialize};
54
55use crate::identity::{PublicKey, SecretKey};
56
57use super::{Answer, Offer};
58
59/// Connection authentication data derived from WebRTC signaling.
60///
61/// `ConnectionAuth` contains the authentication material generated from the
62/// SDP (Session Description Protocol) hashes of both the WebRTC offer and
63/// answer.
64/// This creates a unique, connection-specific authentication token that can be
65/// used to verify the authenticity of the WebRTC connection.
66///
67/// ## Construction
68///
69/// The authentication data is created by concatenating the SDP hashes from both
70/// the offer and answer messages:
71///
72/// ```text
73/// ConnectionAuth = SDP_Hash(Offer) || SDP_Hash(Answer)
74/// ```
75///
76/// This ensures that both parties contributed to the authentication material
77/// and that any tampering with either the offer or answer would be detected.
78///
79/// ## Security Properties
80///
81/// - **Uniqueness**: Each connection attempt generates unique SDP data,
82/// preventing replay attacks
83/// - **Integrity**: Any modification to the offer or answer changes the hashes,
84/// invalidating the authentication
85/// - **Binding**: Cryptographically binds the authentication to the specific
86/// WebRTC session parameters
87///
88/// ## Usage
89///
90/// ```rust
91/// use mina_p2p::webrtc::{ConnectionAuth, Offer, Answer};
92///
93/// let connection_auth = ConnectionAuth::new(&offer, &answer);
94/// let encrypted_auth = connection_auth.encrypt(&my_secret_key, &peer_public_key, rng)?;
95/// ```
96#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
97pub struct ConnectionAuth(Vec<u8>);
98
99/// Encrypted connection authentication data.
100///
101/// `ConnectionAuthEncrypted` represents the connection authentication data after
102/// it has been encrypted using public key cryptography. The encrypted data is
103/// stored in a fixed-size array of 92 bytes, which corresponds to the output
104/// size of the encryption algorithm used.
105///
106/// ## Encryption Process
107///
108/// The encryption uses the recipient's public key to ensure that only the
109/// intended recipient can decrypt and verify the authentication data. This
110/// prevents man-in-the-middle attackers from forging authentication tokens.
111///
112/// ## Fixed Size
113///
114/// The 92-byte fixed size is determined by the cryptographic parameters:
115/// - The encryption algorithm produces a deterministic output size
116/// - Fixed sizing enables efficient serialization and network transmission
117/// - Prevents information leakage through size analysis
118///
119/// ## Network Transmission
120///
121/// This type is designed for transmission over the network and includes
122/// serialization support for JSON and binary formats.
123///
124/// ## Example
125///
126/// ```rust
127/// // After receiving encrypted authentication data
128/// let decrypted_auth = encrypted_auth.decrypt(&my_secret_key, &peer_public_key)?;
129/// // Verify that the decrypted data matches expected values
130/// ```
131#[derive(Debug, Clone)]
132pub struct ConnectionAuthEncrypted(Box<[u8; 92]>);
133
134impl ConnectionAuth {
135 /// Creates new connection authentication data from WebRTC offer and answer.
136 ///
137 /// This method generates connection authentication data by concatenating the
138 /// SDP hashes from both the WebRTC offer and answer messages. The resulting
139 /// authentication token is unique to this specific connection attempt and
140 /// binds the authentication to the exact WebRTC session parameters.
141 ///
142 /// # Parameters
143 ///
144 /// * `offer` - The WebRTC offer containing SDP data and peer information
145 /// * `answer` - The WebRTC answer containing SDP data and peer information
146 ///
147 /// # Returns
148 ///
149 /// A new `ConnectionAuth` instance containing the concatenated SDP hashes.
150 ///
151 /// # Security Considerations
152 ///
153 /// The authentication data is derived from both the offer and answer, ensuring
154 /// that any tampering with either message will result in different authentication
155 /// data. This prevents attackers from modifying signaling messages without
156 /// detection.
157 ///
158 /// # Example
159 ///
160 /// ```rust
161 /// use mina_p2p::webrtc::ConnectionAuth;
162 ///
163 /// let auth = ConnectionAuth::new(&offer, &answer);
164 /// // Use auth for connection verification
165 /// ```
166 pub fn new(offer: &Offer, answer: &Answer) -> Self {
167 Self([offer.sdp_hash(), answer.sdp_hash()].concat())
168 }
169
170 /// Encrypts the connection authentication data using public key cryptography.
171 ///
172 /// This method encrypts the authentication data using the recipient's
173 /// public key, ensuring that only the intended recipient (who possesses the
174 /// corresponding private key) can decrypt and verify the authentication
175 /// token.
176 ///
177 /// # Parameters
178 ///
179 /// * `sec_key` - The sender's secret key used for encryption
180 /// * `other_pk` - The recipient's public key used for encryption
181 /// * `rng` - A cryptographically secure random number generator
182 ///
183 /// # Returns
184 ///
185 /// * `Some(ConnectionAuthEncrypted)` if encryption succeeds
186 /// * `None` if encryption fails (e.g., due to invalid keys or cryptographic
187 /// errors)
188 ///
189 /// # Security Properties
190 ///
191 /// - **Confidentiality**: Only the holder of the corresponding private key can
192 /// decrypt the authentication data
193 /// - **Authenticity**: The encryption process provides assurance about the
194 /// sender's identity
195 ///
196 /// # Example
197 ///
198 /// ```rust
199 /// use rand::thread_rng;
200 ///
201 /// let mut rng = thread_rng();
202 /// let encrypted_auth = connection_auth.encrypt(&my_secret_key, &peer_public_key, &mut rng);
203 ///
204 /// if let Some(encrypted) = encrypted_auth {
205 /// // Send encrypted authentication data to peer
206 /// }
207 /// ```
208 pub fn encrypt(
209 &self,
210 sec_key: &SecretKey,
211 other_pk: &PublicKey,
212 rng: impl Rng + CryptoRng,
213 ) -> Option<ConnectionAuthEncrypted> {
214 let bytes = sec_key.encrypt_raw(other_pk, rng, &self.0).ok()?;
215 bytes.try_into().ok()
216 }
217}
218
219impl ConnectionAuthEncrypted {
220 /// Decrypts the connection authentication data using public key cryptography.
221 ///
222 /// This method decrypts the authentication data using the recipient's
223 /// secret key and the sender's public key. Successful decryption proves
224 /// that the sender possesses the private key corresponding to their
225 /// advertised public key, providing authentication and preventing
226 /// man-in-the-middle attacks.
227 ///
228 /// # Parameters
229 ///
230 /// * `sec_key` - The recipient's secret key used for decryption
231 /// * `other_pk` - The sender's public key used for decryption
232 ///
233 /// # Returns
234 ///
235 /// * `Some(ConnectionAuth)` if decryption succeeds and authentication is valid
236 /// * `None` if decryption fails (e.g., due to invalid keys, corrupted data, or
237 /// cryptographic errors)
238 ///
239 /// # Security Verification
240 ///
241 /// Successful decryption provides several security guarantees:
242 ///
243 /// - **Identity Verification**: The sender possesses the private key
244 /// corresponding to their public key
245 /// - **Message Integrity**: The encrypted data has not been tampered with
246 /// - **Authenticity**: The authentication data came from the claimed sender
247 ///
248 /// # Usage in Authentication Flow
249 ///
250 /// This method is typically called during the final stage of WebRTC connection
251 /// establishment to verify the peer's identity before allowing the connection
252 /// to proceed.
253 ///
254 /// # Example
255 ///
256 /// ```rust
257 /// // After receiving encrypted authentication data from peer
258 /// if let Some(decrypted_auth) = encrypted_auth.decrypt(&my_secret_key, &peer_public_key) {
259 /// // Authentication successful, proceed with connection
260 /// println!("Peer authentication verified");
261 /// } else {
262 /// // Authentication failed, reject connection
263 /// println!("Peer authentication failed");
264 /// }
265 /// ```
266 pub fn decrypt(&self, sec_key: &SecretKey, other_pk: &PublicKey) -> Option<ConnectionAuth> {
267 sec_key
268 .decrypt_raw(other_pk, &*self.0)
269 .map(ConnectionAuth)
270 .ok()
271 }
272}
273
274impl Serialize for ConnectionAuthEncrypted {
275 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
276 where
277 S: serde::Serializer,
278 {
279 self.0.to_vec().serialize(serializer)
280 }
281}
282
283impl<'de> Deserialize<'de> for ConnectionAuthEncrypted {
284 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
285 where
286 D: serde::Deserializer<'de>,
287 {
288 Vec::deserialize(deserializer).and_then(|v| {
289 use serde::de::Error;
290 v.try_into().map_err(Error::custom)
291 })
292 }
293}
294
295impl TryFrom<Vec<u8>> for ConnectionAuthEncrypted {
296 type Error = &'static str;
297
298 fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
299 value.as_slice().try_into()
300 }
301}
302
303impl TryFrom<&[u8]> for ConnectionAuthEncrypted {
304 type Error = &'static str;
305
306 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
307 value
308 .try_into()
309 .map(|v| Self(Box::new(v)))
310 .map_err(|_| "ConnectionAuthEncrypted not in expected size")
311 }
312}
313
314impl AsRef<[u8]> for ConnectionAuthEncrypted {
315 fn as_ref(&self) -> &[u8] {
316 &*self.0
317 }
318}