zoe_client/pqxdh/
session.rs

1use serde::{Deserialize, Serialize};
2
3use std::time::{SystemTime, UNIX_EPOCH};
4use zoe_wire_protocol::{
5    Content, Kind, Message, MessageFull, Tag, VerifyingKey,
6    inbox::pqxdh::{PqxdhSharedSecret, encrypt_pqxdh_session_message},
7};
8
9use super::{PqxdhError, Result};
10
11pub type PqxdhSessionId = [u8; 32];
12
13/// A PQXDH session for secure communication
14///
15/// This struct represents an established PQXDH session between two parties.
16/// It contains the shared cryptographic material and state needed to encrypt
17/// and decrypt messages within the session.
18///
19/// ## Key Features
20/// - **Shared Secret**: Cryptographic material derived from PQXDH key exchange
21/// - **Sequence Numbers**: Monotonic counter for replay protection
22/// - **Session Channel IDs**: A hash of the session channel id prefix and the target key, provides unlinkability
23/// - **Serializable**: Can be persisted and restored across application restarts
24///
25/// ## Security Properties
26/// - Forward secrecy through ephemeral key material
27/// - Replay protection via sequence numbering
28/// - Unlinkability through randomized channel identifiers
29/// - Post-quantum resistance via CRYSTALS-Kyber
30#[derive(Serialize, Deserialize, Clone)]
31pub(super) struct PqxdhSession {
32    pub(super) shared_secret: PqxdhSharedSecret,
33    /// Current sequence number for this session (stored as u64 for serialization)
34    pub(super) sequence_number: u64,
35    /// The channel Id we are listening for, derived from the session channel id prefix
36    pub(super) my_session_channel_id: PqxdhSessionId,
37    /// The session id channel they will be listening to, derived from the session channel id prefix
38    pub(super) their_session_channel_id: PqxdhSessionId,
39    /// The key of the sender of the messages
40    pub(super) their_key: VerifyingKey,
41}
42
43impl PqxdhSession {
44    /// Get the channel they are listening for
45    pub fn publish_channel_tag(&self) -> Tag {
46        Tag::Channel {
47            id: self.their_session_channel_id.to_vec(),
48            relays: vec![],
49        }
50    }
51
52    /// Get the channel tag we want to be listening for
53    pub fn listening_channel_tag(&self) -> Tag {
54        Tag::Channel {
55            id: self.my_session_channel_id.to_vec(),
56            relays: vec![],
57        }
58    }
59
60    /// Get the next sequence number and increment the internal counter
61    pub fn next_sequence_number(&mut self) -> u64 {
62        let current = self.sequence_number;
63        self.sequence_number += 1;
64        current
65    }
66
67    /// Sends a message in an established PQXDH session
68    ///
69    /// This method encrypts and sends a message over an already established PQXDH session.
70    /// The message is encrypted using the session's shared secret and includes sequence
71    /// numbering for replay protection.
72    ///
73    /// # Arguments
74    /// * `messages_service` - The messages service for publishing the encrypted message
75    /// * `client_keypair` - The sender's keypair for message authentication
76    /// * `payload` - The user data to encrypt and send
77    ///
78    /// # Security Features
79    /// - Messages are encrypted with the session's shared secret
80    /// - Sequence numbers prevent replay attacks
81    /// - Messages are sent to the session's private channel ID
82    /// - Each message uses fresh randomness for encryption
83    pub fn gen_next_message<T: Serialize>(
84        &mut self,
85        client_keypair: &zoe_wire_protocol::KeyPair,
86        payload: &T,
87        kind: Kind,
88    ) -> Result<MessageFull> {
89        // Serialize the payload
90        let payload_bytes = postcard::to_stdvec(payload)?;
91
92        // Encrypt as session message
93        let sequence = self.next_sequence_number();
94        let mut rng = rand::thread_rng();
95        let session_message =
96            encrypt_pqxdh_session_message(&self.shared_secret, &payload_bytes, sequence, &mut rng)
97                .map_err(|e| PqxdhError::Crypto(e.to_string()))?;
98
99        // Send the session message
100        let pqxdh_content = zoe_wire_protocol::PqxdhEncryptedContent::Session(session_message);
101
102        let timestamp = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
103        let message = Message::new_v0(
104            Content::PqxdhEncrypted(pqxdh_content),
105            client_keypair.public_key(),
106            timestamp,
107            kind,
108            vec![self.publish_channel_tag()],
109        );
110
111        MessageFull::new(message, client_keypair).map_err(|e| {
112            PqxdhError::MessageCreation(format!("Failed to create session message: {e}"))
113        })
114    }
115
116    /// Creates a PQXDH session from an established shared secret and channel ID (for responders)
117    ///
118    /// This constructor is used by service providers to create a session after successfully
119    /// processing an initial PQXDH message. It initializes the session with the derived
120    /// shared secret and the channel ID extracted from the initial message.
121    ///
122    /// # Arguments
123    /// * `shared_secret` - The cryptographic material derived from PQXDH key exchange
124    /// * `my_session_channel_id` - The channel ID we are listening for
125    /// * `their_session_channel_id` - The channel ID they are listening for
126    /// * `sender_key` - The public key of the sender of the initial message
127    ///
128    /// # Returns
129    /// Returns a new `PqxdhSession` ready for encrypting and decrypting messages
130    ///
131    /// # Usage
132    /// Typically called after `extract_initial_payload()` to create a session
133    /// that can be used for ongoing communication with the client.
134    pub fn from_shared_secret(
135        shared_secret: PqxdhSharedSecret,
136        my_session_channel_id: PqxdhSessionId,
137        their_session_channel_id: PqxdhSessionId,
138        their_key: VerifyingKey,
139    ) -> Self {
140        Self {
141            shared_secret,
142            sequence_number: 1,
143            my_session_channel_id,
144            their_session_channel_id,
145            their_key,
146        }
147    }
148}