commonware_stream/public_key/
handshake.rs

1//! Operations over handshake messages.
2
3use super::{x25519, AUTHENTICATION_TAG_LENGTH};
4use crate::Error;
5use bytes::{Buf, BufMut};
6use chacha20poly1305::{
7    aead::{AeadMutInPlace, Tag},
8    ChaCha20Poly1305, Nonce,
9};
10use commonware_codec::{
11    varint::UInt, Encode, EncodeSize, Error as CodecError, FixedSize, Read, ReadExt, Write,
12};
13use commonware_cryptography::{PublicKey, Signer};
14use commonware_runtime::Clock;
15use commonware_utils::SystemTimeExt;
16use std::time::Duration;
17
18/// Handshake information that is signed over by some peer.
19pub struct Info<P: PublicKey> {
20    /// The public key of the recipient.
21    recipient: P,
22
23    /// The ephemeral public key of the sender.
24    ///
25    /// This is used to derive the shared secret for the encrypted connection.
26    ephemeral_public_key: x25519::PublicKey,
27
28    /// Timestamp of the handshake (in epoch milliseconds).
29    timestamp: u64,
30}
31
32impl<P: PublicKey> Info<P> {
33    /// Create a new hello.
34    pub fn new(recipient: P, ephemeral_public_key: x25519::PublicKey, timestamp: u64) -> Self {
35        Self {
36            recipient,
37            ephemeral_public_key,
38            timestamp,
39        }
40    }
41}
42
43impl<P: PublicKey> Write for Info<P> {
44    fn write(&self, buf: &mut impl BufMut) {
45        self.recipient.write(buf);
46        self.ephemeral_public_key.write(buf);
47        UInt(self.timestamp).write(buf);
48    }
49}
50
51impl<P: PublicKey> Read for Info<P> {
52    type Cfg = ();
53
54    fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
55        let recipient = P::read(buf)?;
56        let ephemeral_public_key = x25519::PublicKey::read(buf)?;
57        let timestamp = UInt::read(buf)?.into();
58        Ok(Info {
59            recipient,
60            ephemeral_public_key,
61            timestamp,
62        })
63    }
64}
65
66impl<P: PublicKey> EncodeSize for Info<P> {
67    fn encode_size(&self) -> usize {
68        self.recipient.encode_size()
69            + self.ephemeral_public_key.encode_size()
70            + UInt(self.timestamp).encode_size()
71    }
72}
73
74/// A signed hello message.
75///
76/// Allows recipient to verify that the sender has the private key
77/// of public key before sending any data.
78///
79/// By requiring the server to have their public key signed, they prevent
80/// a malicious peer from forwarding a handshake message from a previous
81/// connection with public key (which could be used to convince the server
82/// to start a useless handshake). Alternatively, we could require the
83/// dialer to sign some random bytes provided by the server but this would
84/// require the server to send a message to a peer before authorizing that
85/// it should connect to them.
86pub struct Hello<P: PublicKey> {
87    // The handshake info that was signed over
88    info: Info<P>,
89
90    // The public key of the sender
91    signer: P,
92
93    // The signature of the sender
94    signature: P::Signature,
95}
96
97impl<P: PublicKey> Hello<P> {
98    /// Sign a hello message.
99    pub fn sign<Sk: Signer<PublicKey = P, Signature = P::Signature>>(
100        crypto: &mut Sk,
101        namespace: &[u8],
102        info: Info<P>,
103    ) -> Self {
104        let signature = crypto.sign(Some(namespace), &info.encode());
105        Self {
106            info,
107            signer: crypto.public_key(),
108            signature,
109        }
110    }
111
112    /// Get the public key of the signer.
113    pub fn signer(&self) -> P {
114        self.signer.clone()
115    }
116
117    /// Get the ephemeral public key of the signer.
118    pub fn ephemeral(&self) -> x25519::PublicKey {
119        self.info.ephemeral_public_key
120    }
121
122    /// Verify a signed hello message.
123    pub fn verify<E: Clock>(
124        &self,
125        context: &E,
126        crypto: &P,
127        namespace: &[u8],
128        synchrony_bound: Duration,
129        max_handshake_age: Duration,
130    ) -> Result<(), Error> {
131        // Verify that the signature is for us
132        //
133        // If we didn't verify this, it would be trivial for any peer to impersonate another peer (even though
134        // they would not be able to decrypt any messages from the shared secret). This would prevent us
135        // from making a legitimate connection to the intended peer.
136        if *crypto != self.info.recipient {
137            return Err(Error::HelloNotForUs);
138        }
139
140        // Verify that the hello is not signed by us
141        //
142        // This could indicate a self-connection attempt, which is not allowed.
143        // It could also indicate a replay attack or a malformed message.
144        // Either way, fail early to avoid any potential issues.
145        if *crypto == self.signer {
146            return Err(Error::HelloUsesOurKey);
147        }
148
149        // Verify that the timestamp in the hello is recent
150        //
151        // This prevents an adversary from reopening an encrypted connection
152        // if a peer's ephemeral key is compromised (which would be stored in-memory
153        // unlike the peer identity) and/or from blocking a peer from connecting
154        // to others (if an adversary recovered a handshake message could open a
155        // connection to a peer first, peers only maintain one connection per peer).
156        let current_timestamp = context.current().epoch();
157        let hello_timestamp = Duration::from_millis(self.info.timestamp);
158        if hello_timestamp + max_handshake_age < current_timestamp {
159            return Err(Error::InvalidTimestampOld(self.info.timestamp));
160        }
161        if hello_timestamp > current_timestamp + synchrony_bound {
162            return Err(Error::InvalidTimestampFuture(self.info.timestamp));
163        }
164
165        // Verify signature
166        if !self
167            .signer
168            .verify(Some(namespace), &self.info.encode(), &self.signature)
169        {
170            return Err(Error::InvalidSignature);
171        }
172        Ok(())
173    }
174}
175
176impl<P: PublicKey> Write for Hello<P> {
177    fn write(&self, buf: &mut impl BufMut) {
178        self.info.write(buf);
179        self.signer.write(buf);
180        self.signature.write(buf);
181    }
182}
183
184impl<P: PublicKey> Read for Hello<P> {
185    type Cfg = ();
186
187    fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
188        let info = Info::read(buf)?;
189        let signer = P::read(buf)?;
190        let signature = P::Signature::read(buf)?;
191        Ok(Self {
192            info,
193            signer,
194            signature,
195        })
196    }
197}
198
199impl<P: PublicKey> EncodeSize for Hello<P> {
200    fn encode_size(&self) -> usize {
201        self.info.encode_size() + self.signer.encode_size() + self.signature.encode_size()
202    }
203}
204
205/// Key confirmation message used during the handshake.
206///
207/// This struct contains cryptographic proof that a party can correctly derive
208/// the shared secret from the Diffie-Hellman exchange. It prevents attacks where
209/// an adversary might forward [Hello] messages without actually knowing the
210/// corresponding private keys.
211pub struct Confirmation {
212    /// AEAD tag of the encrypted proof demonstrating knowledge of the shared secret.
213    tag: Tag<ChaCha20Poly1305>,
214}
215
216impl Confirmation {
217    /// Create a new [Confirmation] using the provided cipher and [Hello] transcript.
218    ///
219    /// The confirmation encrypts the hello transcript to demonstrate knowledge of the shared
220    /// secret and bind the confirmation to the entire hello exchange.
221    ///
222    /// # Security
223    ///
224    /// To prevent nonce-reuse, the cipher should **not** be the same cipher used for future
225    /// encrypted messages, but should be generated from the same shared secret. The function takes
226    /// ownership of the cipher to help prevent this.
227    pub fn create(mut cipher: ChaCha20Poly1305, transcript: &[u8]) -> Result<Self, Error> {
228        // Encrypt the confirmation using the transcript as associated data
229        let tag = cipher
230            .encrypt_in_place_detached(&Nonce::default(), transcript, &mut [])
231            .map_err(|_| Error::ConfirmationFailed)?;
232
233        Ok(Self { tag })
234    }
235
236    /// Verify the [Confirmation] using the provided cipher and [Hello] transcript.
237    ///
238    /// Returns Ok(()) if the confirmation is valid, otherwise returns an error.
239    pub fn verify(&self, mut cipher: ChaCha20Poly1305, transcript: &[u8]) -> Result<(), Error> {
240        // Decrypt the confirmation using the transcript as associated data
241        cipher
242            .decrypt_in_place_detached(&Nonce::default(), transcript, &mut [], &self.tag)
243            .map_err(|_| Error::InvalidConfirmation)?;
244        Ok(())
245    }
246}
247
248impl Write for Confirmation {
249    fn write(&self, buf: &mut impl BufMut) {
250        let tag_bytes: [u8; Self::SIZE] = self.tag.into();
251        tag_bytes.write(buf);
252    }
253}
254
255impl Read for Confirmation {
256    type Cfg = ();
257
258    fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
259        let tag = <[u8; Self::SIZE]>::read_cfg(buf, &())?;
260        Ok(Self { tag: tag.into() })
261    }
262}
263
264impl FixedSize for Confirmation {
265    const SIZE: usize = AUTHENTICATION_TAG_LENGTH;
266}
267
268#[cfg(test)]
269mod tests {
270    use super::*;
271    use crate::{
272        public_key::{Config, IncomingConnection},
273        utils::codec::send_frame,
274    };
275    use commonware_codec::DecodeExt;
276    use commonware_cryptography::{
277        ed25519::{PrivateKey, PublicKey as edPublicKey},
278        PrivateKeyExt as _, Verifier as _,
279    };
280    use commonware_runtime::{deterministic, mocks, Metrics, Runner, Spawner};
281    use x25519::PublicKey;
282
283    const TEST_NAMESPACE: &[u8] = b"test_namespace";
284    const ONE_MEGABYTE: usize = 1024 * 1024;
285
286    #[test]
287    fn test_hello_create_verify() {
288        // Initialize context
289        let executor = deterministic::Runner::default();
290        executor.start(|context| async move {
291            // Create participants
292            let mut sender = PrivateKey::from_seed(0);
293            let recipient = PrivateKey::from_seed(1).public_key();
294            let ephemeral_public_key = PublicKey::from_bytes([3u8; 32]);
295
296            // Create hello message
297            let timestamp = context.current().epoch_millis();
298            let hello = Hello::sign(
299                &mut sender,
300                TEST_NAMESPACE,
301                Info {
302                    timestamp,
303                    recipient: recipient.clone(),
304                    ephemeral_public_key,
305                },
306            );
307
308            // Decode the hello message
309            let hello =
310                Hello::<edPublicKey>::decode(hello.encode()).expect("failed to decode hello");
311
312            // Verify the timestamp
313            let synchrony_bound = Duration::from_secs(5);
314            let max_handshake_age = Duration::from_secs(5);
315            let hello_timestamp = Duration::from_millis(hello.info.timestamp);
316            let current_timestamp = Duration::from_millis(timestamp);
317            assert!(hello_timestamp <= current_timestamp + synchrony_bound);
318            assert!(hello_timestamp + max_handshake_age >= current_timestamp);
319
320            // Verify the signature
321            assert_eq!(hello.info.recipient, recipient);
322            assert_eq!(hello.info.ephemeral_public_key, ephemeral_public_key,);
323
324            // Verify signature
325            assert!(sender.public_key().verify(
326                Some(TEST_NAMESPACE),
327                &hello.info.encode(),
328                &hello.signature,
329            ));
330
331            // Verify using the hello struct
332            hello
333                .verify(
334                    &context,
335                    &recipient,
336                    TEST_NAMESPACE,
337                    synchrony_bound,
338                    max_handshake_age,
339                )
340                .unwrap();
341            assert_eq!(hello.signer, sender.public_key());
342            assert_eq!(hello.info.ephemeral_public_key, ephemeral_public_key);
343        });
344    }
345
346    #[test]
347    fn test_hello() {
348        // Initialize context
349        let executor = deterministic::Runner::default();
350        executor.start(|context| async move {
351            // Create participants
352            let mut sender = PrivateKey::from_seed(0);
353            let recipient = PrivateKey::from_seed(1);
354            let ephemeral_public_key = PublicKey::from_bytes([3u8; 32]);
355
356            // Create hello message
357            let hello = Hello::sign(
358                &mut sender,
359                TEST_NAMESPACE,
360                Info {
361                    timestamp: 0,
362                    recipient: recipient.public_key(),
363                    ephemeral_public_key,
364                },
365            );
366
367            // Setup a mock sink and stream
368            let (sink, _) = mocks::Channel::init();
369            let (mut stream_sender, stream) = mocks::Channel::init();
370
371            // Send message over stream
372            context.with_label("stream_sender").spawn(|_| async move {
373                send_frame(&mut stream_sender, &hello.encode(), ONE_MEGABYTE)
374                    .await
375                    .unwrap();
376            });
377
378            // Call the verify function
379            let config = Config {
380                crypto: recipient.clone(),
381                namespace: TEST_NAMESPACE.to_vec(),
382                max_message_size: ONE_MEGABYTE,
383                synchrony_bound: Duration::from_secs(5),
384                max_handshake_age: Duration::from_secs(5),
385                handshake_timeout: Duration::from_secs(5),
386            };
387            let result = IncomingConnection::verify(&context, config, sink, stream)
388                .await
389                .unwrap();
390
391            // Assert that the result is expected
392            assert_eq!(result.peer(), sender.public_key());
393            assert_eq!(result.ephemeral(), ephemeral_public_key);
394        });
395    }
396
397    #[test]
398    fn test_hello_not_for_us() {
399        // Initialize context
400        let executor = deterministic::Runner::default();
401        executor.start(|context| async move {
402            // Create participants
403            let mut sender = PrivateKey::from_seed(0);
404            let ephemeral_public_key = PublicKey::from_bytes([3u8; 32]);
405
406            // Create hello message
407            let hello = Hello::sign(
408                &mut sender,
409                TEST_NAMESPACE,
410                Info {
411                    timestamp: 0,
412                    recipient: PrivateKey::from_seed(1).public_key(),
413                    ephemeral_public_key,
414                },
415            );
416
417            // Setup a mock sink and stream
418            let (sink, _) = mocks::Channel::init();
419            let (mut stream_sender, stream) = mocks::Channel::init();
420
421            // Send message over stream
422            context.with_label("stream_sender").spawn(|_| async move {
423                send_frame(&mut stream_sender, &hello.encode(), ONE_MEGABYTE)
424                    .await
425                    .unwrap();
426            });
427
428            // Call the verify function
429            let config = Config {
430                crypto: PrivateKey::from_seed(2),
431                namespace: TEST_NAMESPACE.to_vec(),
432                max_message_size: ONE_MEGABYTE,
433                synchrony_bound: Duration::from_secs(5),
434                max_handshake_age: Duration::from_secs(5),
435                handshake_timeout: Duration::from_secs(5),
436            };
437            let result = IncomingConnection::verify(&context, config, sink, stream).await;
438
439            // Assert that the result is an error
440            assert!(matches!(result, Err(Error::HelloNotForUs)));
441        });
442    }
443
444    #[test]
445    fn test_incoming_hello_invalid_data() {
446        // Initialize context
447        let executor = deterministic::Runner::default();
448        executor.start(|context| async move {
449            // Setup a mock sink and stream
450            let (sink, _) = mocks::Channel::init();
451            let (mut stream_sender, stream) = mocks::Channel::init();
452
453            // Send invalid data over stream
454            context.with_label("stream_sender").spawn(|_| async move {
455                send_frame(&mut stream_sender, b"mock data", ONE_MEGABYTE)
456                    .await
457                    .unwrap();
458            });
459
460            // Call the verify function
461            let config = Config {
462                crypto: PrivateKey::from_seed(0),
463                namespace: TEST_NAMESPACE.to_vec(),
464                max_message_size: ONE_MEGABYTE,
465                synchrony_bound: Duration::from_secs(1),
466                max_handshake_age: Duration::from_secs(1),
467                handshake_timeout: Duration::from_secs(1),
468            };
469            let result = IncomingConnection::verify(&context, config, sink, stream).await;
470
471            // Assert that the result is an error
472            assert!(matches!(result, Err(Error::UnableToDecode(_))));
473        });
474    }
475
476    #[test]
477    fn test_incoming_hello_verify_timeout() {
478        let executor = deterministic::Runner::default();
479        executor.start(|context| async move {
480            // Setup a mock sink and stream
481            let (sink, _stream) = mocks::Channel::init();
482            let (_sink, stream) = mocks::Channel::init();
483
484            // Call the verify function for one peer, but never send the handshake from the other
485            let config = Config {
486                crypto: PrivateKey::from_seed(1),
487                namespace: TEST_NAMESPACE.to_vec(),
488                max_message_size: ONE_MEGABYTE,
489                synchrony_bound: Duration::from_secs(1),
490                max_handshake_age: Duration::from_secs(1),
491                handshake_timeout: Duration::from_secs(1),
492            };
493            let result = IncomingConnection::verify(&context, config, sink, stream).await;
494
495            // Assert that the result is an Err of type Error::HandshakeTimeout
496            assert!(matches!(result, Err(Error::HandshakeTimeout)));
497        });
498    }
499
500    #[test]
501    fn test_hello_verify_invalid_signature() {
502        let executor = deterministic::Runner::default();
503        executor.start(|context| async move {
504            let mut sender = PrivateKey::from_seed(0);
505            let recipient = PrivateKey::from_seed(1);
506            let ephemeral_public_key = x25519::PublicKey::from_bytes([0u8; 32]);
507
508            // The peer creates a valid hello intended for us
509            let hello = Hello::sign(
510                &mut sender,
511                TEST_NAMESPACE,
512                Info {
513                    timestamp: 0,
514                    recipient: recipient.public_key(),
515                    ephemeral_public_key,
516                },
517            );
518
519            // Tamper with the hello to make the signature invalid
520            let mut hello =
521                Hello::<edPublicKey>::decode(hello.encode()).expect("failed to decode hello");
522            hello.info.timestamp += 1;
523
524            // Verify the hello
525            let result = hello.verify(
526                &context,
527                &recipient.public_key(),
528                TEST_NAMESPACE,
529                Duration::from_secs(5),
530                Duration::from_secs(5),
531            );
532            assert!(matches!(result, Err(Error::InvalidSignature)));
533        });
534    }
535
536    #[test]
537    fn test_hello_verify_invalid_timestamp_old() {
538        let executor = deterministic::Runner::default();
539        executor.start(|context| async move {
540            let mut signer = PrivateKey::from_seed(0);
541            let recipient = PrivateKey::from_seed(1).public_key();
542            let ephemeral_public_key = x25519::PublicKey::from_bytes([0u8; 32]);
543
544            let timeout_duration = Duration::from_secs(5);
545            let synchrony_bound = Duration::from_secs(0);
546
547            // The peer creates a hello, setting the timestamp to 0.
548            let hello = Hello::sign(
549                &mut signer,
550                TEST_NAMESPACE,
551                Info {
552                    timestamp: 0,
553                    recipient: recipient.clone(),
554                    ephemeral_public_key,
555                },
556            );
557
558            // Time starts at 0 in deterministic executor.
559            // Sleep for the exact timeout duration.
560            context.sleep(timeout_duration).await;
561
562            // Verify the hello, it should be fine still.
563            hello
564                .verify(
565                    &context,
566                    &recipient,
567                    TEST_NAMESPACE,
568                    synchrony_bound,
569                    timeout_duration,
570                )
571                .unwrap();
572
573            // Timeout by waiting 1 more millisecond.
574            context.sleep(Duration::from_millis(1)).await;
575
576            // Verify that a timeout error is returned.
577            let result = hello.verify(
578                &context,
579                &recipient,
580                TEST_NAMESPACE,
581                synchrony_bound,
582                timeout_duration,
583            );
584            assert!(matches!(result, Err(Error::InvalidTimestampOld(t)) if t == 0));
585        });
586    }
587
588    #[test]
589    fn test_confirmation_create_and_verify() {
590        use chacha20poly1305::KeyInit;
591
592        let key = [1u8; 32];
593        let cipher = ChaCha20Poly1305::new(&key.into());
594        let transcript = b"test_transcript_data";
595
596        // Create confirmation
597        let confirmation = Confirmation::create(cipher, transcript).unwrap();
598
599        // Verify the confirmation with the same parameters
600        let cipher = ChaCha20Poly1305::new(&key.into());
601        confirmation.verify(cipher, transcript).unwrap();
602
603        // Verify that confirmation fails with different transcript
604        let different_transcript = b"different_transcript_data";
605        let cipher = ChaCha20Poly1305::new(&key.into());
606        let result = confirmation.verify(cipher, different_transcript);
607        assert!(matches!(result, Err(Error::InvalidConfirmation)));
608
609        // Verify that confirmation fails with different cipher
610        let different_key = [2u8; 32];
611        let different_cipher = ChaCha20Poly1305::new(&different_key.into());
612        let result = confirmation.verify(different_cipher, transcript);
613        assert!(matches!(result, Err(Error::InvalidConfirmation)));
614    }
615
616    #[test]
617    fn test_confirmation_encoding() {
618        use chacha20poly1305::KeyInit;
619        use commonware_codec::{DecodeExt, Encode};
620
621        let key = [1u8; 32];
622        let cipher = ChaCha20Poly1305::new(&key.into());
623        let transcript = b"test_transcript_for_encoding";
624
625        // Create and encode confirmation
626        let original_confirmation = Confirmation::create(cipher, transcript).unwrap();
627        let encoded = original_confirmation.encode();
628
629        // Decode and verify it matches
630        let decoded_confirmation = Confirmation::decode(encoded).unwrap();
631        let cipher = ChaCha20Poly1305::new(&key.into());
632        decoded_confirmation.verify(cipher, transcript).unwrap();
633    }
634
635    #[test]
636    fn test_hello_verify_invalid_timestamp_future() {
637        let executor = deterministic::Runner::default();
638        executor.start(|context| async move {
639            let mut signer = PrivateKey::from_seed(0);
640            let recipient = PrivateKey::from_seed(1).public_key();
641            let ephemeral_public_key = x25519::PublicKey::from_bytes([0u8; 32]);
642
643            let timeout_duration = Duration::from_secs(0);
644            const SYNCHRONY_BOUND_MILLIS: u64 = 5_000;
645            let synchrony_bound = Duration::from_millis(SYNCHRONY_BOUND_MILLIS);
646
647            // The peer creates a hello at the synchrony bound.
648            let hello_ok = Hello::sign(
649                &mut signer,
650                TEST_NAMESPACE,
651                Info{
652                    timestamp: SYNCHRONY_BOUND_MILLIS,
653                    recipient: recipient.clone(),
654                    ephemeral_public_key,
655                },
656            );
657
658            // Create a hello 1ms too far into the future.
659            let hello_late = Hello::sign(
660                &mut signer,
661                TEST_NAMESPACE,
662                Info{
663                    timestamp:SYNCHRONY_BOUND_MILLIS + 1,
664                    recipient: recipient.clone(),
665                    ephemeral_public_key,
666                },
667            );
668
669            // Verify the okay hello.
670            hello_ok.verify(
671                &context,
672                &recipient,
673                TEST_NAMESPACE,
674                synchrony_bound,
675                timeout_duration,
676            ).unwrap(); // no error
677
678            // Hello too far into the future fails.
679            let result = hello_late.verify(
680                &context,
681                &recipient,
682                TEST_NAMESPACE,
683                synchrony_bound,
684                timeout_duration,
685            );
686            assert!(matches!(result, Err(Error::InvalidTimestampFuture(t)) if t == SYNCHRONY_BOUND_MILLIS + 1));
687        });
688    }
689}