Skip to main content

ap_noise/
persistence.rs

1//! State persistence for multi-device transport
2//!
3//! This module provides serialization and deserialization of transport state
4//! using CBOR encoding. Only transport state can be persisted - handshake state
5//! contains ephemeral keys and cannot be safely serialized.
6
7use serde::{Deserialize, Serialize};
8
9use super::ciphersuite::Ciphersuite;
10use super::transport::MultiDeviceTransport;
11use crate::error::NoiseProtocolError;
12use crate::symmetric_key::SymmetricKey;
13
14/// Persistent transport state
15///
16/// Contains all necessary information to restore a transport session:
17/// - Cipher suite in use
18/// - Send and receive keys
19/// - Send and receive rekey counters
20/// - Last rekeyed timestamp
21/// - Rekey interval
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct PersistentTransportState {
24    /// Cipher suite
25    ciphersuite: Ciphersuite,
26    /// Send key (32 bytes)
27    send_key: SymmetricKey,
28    /// Receive key (32 bytes)
29    recv_key: SymmetricKey,
30    /// Send rekey counter
31    send_rekey_counter: u64,
32    /// Receive rekey counter
33    recv_rekey_counter: u64,
34    /// Last rekeyed timestamp
35    last_rekeyed_time: u64,
36    /// Rekey interval in seconds
37    rekey_interval: u64,
38}
39
40impl From<&MultiDeviceTransport> for PersistentTransportState {
41    fn from(transport: &MultiDeviceTransport) -> Self {
42        let (send_key, recv_key) = transport.keys();
43
44        Self {
45            ciphersuite: transport.ciphersuite(),
46            send_key,
47            recv_key,
48            send_rekey_counter: transport.send_rekey_counter(),
49            recv_rekey_counter: transport.recv_rekey_counter(),
50            last_rekeyed_time: transport.last_rekeyed_time(),
51            rekey_interval: transport.rekey_interval(),
52        }
53    }
54}
55
56impl From<PersistentTransportState> for MultiDeviceTransport {
57    fn from(state: PersistentTransportState) -> Self {
58        MultiDeviceTransport::restore_from_state(
59            state.ciphersuite,
60            state.send_key,
61            state.recv_key,
62            state.send_rekey_counter,
63            state.recv_rekey_counter,
64            state.last_rekeyed_time,
65            state.rekey_interval,
66        )
67    }
68}
69
70impl PersistentTransportState {
71    /// Serialize to CBOR bytes
72    pub fn to_bytes(&self) -> Result<Vec<u8>, NoiseProtocolError> {
73        let mut bytes = Vec::new();
74        ciborium::ser::into_writer(self, &mut bytes)
75            .map_err(|_| NoiseProtocolError::CborEncodeFailed)?;
76        Ok(bytes)
77    }
78
79    /// Deserialize from CBOR bytes
80    pub fn from_bytes(bytes: &[u8]) -> Result<Self, NoiseProtocolError> {
81        ciborium::de::from_reader(bytes).map_err(|_| NoiseProtocolError::CborDecodeFailed)
82    }
83}
84
85/// Convenience functions for direct transport serialization
86impl MultiDeviceTransport {
87    /// Serialize transport state to CBOR bytes
88    pub fn save_state(&self) -> Result<Vec<u8>, NoiseProtocolError> {
89        let persistent: PersistentTransportState = self.into();
90        persistent.to_bytes()
91    }
92
93    /// Restore transport state from CBOR bytes
94    pub fn restore_state(bytes: &[u8]) -> Result<Self, NoiseProtocolError> {
95        let persistent = PersistentTransportState::from_bytes(bytes)?;
96        Ok(persistent.into())
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103    use crate::symmetric_key::{SYMMETRIC_KEY_TEST_VECTOR_1, SYMMETRIC_KEY_TEST_VECTOR_2};
104
105    fn setup_sender_receiver() -> (MultiDeviceTransport, MultiDeviceTransport) {
106        let send_key = SYMMETRIC_KEY_TEST_VECTOR_1;
107        let recv_key = SYMMETRIC_KEY_TEST_VECTOR_2;
108
109        // Use variables for swapped keys
110        let sender_send_key = send_key.clone();
111        let sender_recv_key = recv_key.clone();
112        let receiver_send_key = recv_key.clone();
113        let receiver_recv_key = send_key.clone();
114
115        let sender = MultiDeviceTransport::new(
116            Ciphersuite::ClassicalNNpsk2_25519_XChaCha20Poly1035,
117            sender_send_key,
118            sender_recv_key,
119        );
120
121        let receiver = MultiDeviceTransport::new(
122            Ciphersuite::ClassicalNNpsk2_25519_XChaCha20Poly1035,
123            receiver_send_key,
124            receiver_recv_key,
125        );
126
127        (sender, receiver)
128    }
129
130    #[test]
131    fn test_persistent_state_roundtrip() {
132        let (mut sender, _) = setup_sender_receiver();
133        sender.set_send_rekey_counter(42);
134        sender.set_recv_rekey_counter(43);
135        sender.set_last_rekeyed_time(1000);
136
137        // Convert to persistent state
138        let persistent: PersistentTransportState = (&sender).into();
139        let restored: MultiDeviceTransport = persistent.into();
140
141        assert_eq!(
142            restored.ciphersuite(),
143            Ciphersuite::ClassicalNNpsk2_25519_XChaCha20Poly1035
144        );
145        assert_eq!(restored.send_rekey_counter(), 42);
146        assert_eq!(restored.recv_rekey_counter(), 43);
147        assert_eq!(restored.last_rekeyed_time(), 1000);
148
149        assert_eq!(sender.send_key(), restored.send_key());
150        assert_eq!(sender.recv_key(), restored.recv_key());
151    }
152}