commonware_stream/public_key/
mod.rs

1//! Communicate with an authenticated peer over an encrypted connection.
2//!
3//! Encrypted communication with a peer, identified by a developer-specified
4//! cryptographic identity (i.e. BLS, ed25519, etc.).
5//! Implements its own encrypted transport layer (No TLS, No X.509 Certificates,
6//! No Protocol Negotiation) that exclusively uses said cryptographic identities
7//! to authenticate incoming connections (dropping any that aren't explicitly
8//! authorized). Uses ChaCha20-Poly1305 for encryption of messages.
9//!
10//! # Design
11//!
12//! ## Handshake
13//!
14//! When establishing a connection with a peer, a simple handshake is performed
15//! to authenticate each other and to establish a shared secret for connection
16//! encryption (explained below). This simple handshake is done in lieu of using
17//! TLS, Noise, WireGuard, etc. because it supports the usage of arbitrary
18//! cryptographic schemes, there is no protocol negotiation (only one way to
19//! connect), because it only takes a few hundred lines of code to implement
20//! (not having any features is a feature in safety-critical code), and because
21//! it can be simulated deterministically.
22//!
23//! In any handshake, the dialer is the party that attempts to connect to some known address/identity (public key)
24//! and the recipient of this connection is the listener. Upon forming a TCP connection, the dialer sends a signed
25//! handshake message to the listener. Besides the signature and the public key of the dialer, the handshake message
26//! contains:
27//!
28//! - The receiver's public key.
29//! - The sender's ephemeral public key (used to establish a shared secret).
30//! - The current timestamp (used to prevent replay attacks).
31//!
32//! The listener verifies the public keys are well-formatted, the timestamp is valid (not too old/not too far in the future),
33//! and that the signature is valid. If all these checks pass, the listener checks to see if it is already connected or dialing
34//! this peer. If it is, it drops the connection. If it isn't, it sends back its own signed handshake message (same as above)
35//! and considers the connection established.
36//!
37//! Upon receiving the listener's handshake message, the dialer verifies the same data as the listener and additionally verifies
38//! that the public key returned matches what they expected at the address. If all these checks pass, the dialer considers the
39//! connection established. If not, the dialer drops the connection.
40//!
41//! To better protect against malicious peers that create and/or accept connections but do not participate in handshakes,
42//! a configurable deadline is enforced for any handshake to be completed. This allows for the underlying runtime to maintain
43//! a standard read/write timeout for connections without making it easier for malicious peers to keep useless connections open.
44//!
45//! ## Encryption
46//!
47//! During the handshake (described above), a shared x25519 secret is established using a
48//! Diffie-Hellman Key Exchange. This x25519 secret is then used to create a ChaCha20-Poly1305
49//! cipher for encrypting all messages exchanged with the peer.
50//!
51//! Each peer maintains a pair of ChaCha20-Poly1305 nonces (12 bytes), one for itself and one for
52//! the other. Each nonce is constructed using a counter that starts at either 1 for the dialer, or
53//! 0 for the listener. For each message sent, the relevant counter is incremented by 2, ensuring that
54//! the two counters have disjoint nonce spaces.
55//!
56//! The nonce is the least-significant 12 bytes of the counter, encoded big-endian. This provides 2^95 unique nonces
57//! per sender, sufficient for over 1 trillion years at 1 billion messages/second—far exceeding practical limits. This approach
58//! ensures well-behaving peers, as long as they both stay online, remain connected indefinitely (maximizing the stability
59//! of any p2p construction). In an unlikely case of overflow, the connection would terminate, and a new handshake would be required.
60//!
61//! This simple coordination prevents nonce reuse (which would allow for messages to be decrypted) and saves a small amount of
62//! bandwidth (no need to send the nonce alongside the encrypted message). This "pedantic" construction of the nonce
63//! also avoids accidental reuse of a nonce over long-lived connections (when setting it to be a small hash as in XChaCha20-Poly1305).
64
65use commonware_cryptography::Scheme;
66use std::time::Duration;
67
68mod connection;
69pub use connection::{Connection, IncomingConnection, Receiver, Sender};
70mod handshake;
71mod nonce;
72mod x25519;
73
74/// Configuration for a connection.
75///
76/// # Warning
77///
78/// It is recommended to synchronize this configuration with any relevant peer.
79/// If this is not synchronized, connections could be unnecessarily dropped,
80/// or messages could be parsed incorrectly.
81#[derive(Clone)]
82pub struct Config<C: Scheme> {
83    /// Cryptographic primitives.
84    pub crypto: C,
85
86    /// Prefix for all signed messages. Should be unique to the application.
87    /// Used to avoid replay attacks across different applications
88    pub namespace: Vec<u8>,
89
90    /// Maximum size allowed for messages (in bytes).
91    /// Used to prevent DoS attacks.
92    pub max_message_size: usize,
93
94    /// Time into the future that a timestamp can be and still be considered valid.
95    /// Used to handle clock skew between peers.
96    pub synchrony_bound: Duration,
97
98    /// Maximum age of a handshake message before it is considered stale.
99    pub max_handshake_age: Duration,
100
101    /// Timeout for completing the handshake process.
102    pub handshake_timeout: Duration,
103}