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}