Skip to main content

leodos_protocols/datalink/security/sdls/
key.rs

1//! Key management for SDLS Extended Procedures (CCSDS 355.1-B-1).
2//!
3//! Keys follow a state machine:
4//!   PREACTIVE → ACTIVE → DEACTIVATED → DESTROYED
5//!
6//! Master keys decrypt session keys delivered via OTAR.
7//! Session keys are used for frame encryption/authentication.
8
9use super::CryptoProvider;
10use super::Error;
11
12/// Maximum key length in bytes (AES-256).
13pub const MAX_KEY_LEN: usize = 32;
14
15/// Maximum number of keys in a key ring.
16pub const MAX_KEYS: usize = 128;
17
18/// Key state in the lifecycle.
19#[derive(Debug, Copy, Clone, Eq, PartialEq)]
20pub enum KeyState {
21    /// Loaded but not yet activated.
22    Preactive,
23    /// Available for cryptographic operations.
24    Active,
25    /// Disabled, no longer usable for new operations.
26    Deactivated,
27    /// Cryptographically wiped.
28    Destroyed,
29}
30
31/// A managed cryptographic key.
32#[derive(Clone)]
33pub struct ManagedKey {
34    /// Key identifier.
35    pub key_id: u16,
36    /// Current lifecycle state.
37    pub state: KeyState,
38    /// Key material (AES-128 = 16 bytes, AES-256 = 32 bytes).
39    pub material: [u8; MAX_KEY_LEN],
40    /// Actual key length in bytes.
41    pub key_len: u8,
42}
43
44impl ManagedKey {
45    /// Creates a new key in PREACTIVE state.
46    pub fn new(key_id: u16, material: &[u8]) -> Result<Self, Error> {
47        if material.is_empty() || material.len() > MAX_KEY_LEN {
48            return Err(Error::InvalidKeyLength);
49        }
50        let mut key = Self {
51            key_id,
52            state: KeyState::Preactive,
53            material: [0u8; MAX_KEY_LEN],
54            key_len: material.len() as u8,
55        };
56        key.material[..material.len()].copy_from_slice(material);
57        Ok(key)
58    }
59
60    /// Transition from PREACTIVE to ACTIVE.
61    pub fn activate(&mut self) -> Result<(), Error> {
62        (self.state == KeyState::Preactive)
63            .then(|| self.state = KeyState::Active)
64            .ok_or(Error::InvalidKeyState)
65    }
66
67    /// Transition from ACTIVE to DEACTIVATED.
68    pub fn deactivate(&mut self) -> Result<(), Error> {
69        (self.state == KeyState::Active)
70            .then(|| self.state = KeyState::Deactivated)
71            .ok_or(Error::InvalidKeyState)
72    }
73
74    /// Cryptographically destroy the key material.
75    pub fn destroy(&mut self) {
76        self.material = [0u8; MAX_KEY_LEN];
77        self.key_len = 0;
78        self.state = KeyState::Destroyed;
79    }
80
81    /// Returns the key material slice.
82    pub fn material(&self) -> &[u8] {
83        &self.material[..self.key_len as usize]
84    }
85}
86
87/// A key ring holding master and session keys.
88pub struct KeyRing {
89    keys: heapless::Vec<ManagedKey, MAX_KEYS>,
90}
91
92impl KeyRing {
93    /// Creates an empty key ring.
94    pub fn new() -> Self {
95        Self {
96            keys: heapless::Vec::new(),
97        }
98    }
99
100    /// Loads a key into the ring in PREACTIVE state.
101    pub fn load(&mut self, key_id: u16, material: &[u8]) -> Result<(), Error> {
102        if self.find(key_id).is_some() {
103            return Err(Error::DuplicateKeyId);
104        }
105        let key = ManagedKey::new(key_id, material)?;
106        self.keys.push(key).map_err(|_| Error::KeyRingFull)
107    }
108
109    /// Finds a key by ID.
110    pub fn find(&self, key_id: u16) -> Option<&ManagedKey> {
111        self.keys.iter().find(|k| k.key_id == key_id)
112    }
113
114    /// Finds a key by ID (mutable).
115    pub fn find_mut(&mut self, key_id: u16) -> Option<&mut ManagedKey> {
116        self.keys.iter_mut().find(|k| k.key_id == key_id)
117    }
118
119    /// Activates a key by ID.
120    pub fn activate(&mut self, key_id: u16) -> Result<(), Error> {
121        self.find_mut(key_id)
122            .ok_or(Error::UnknownKeyId(key_id))?
123            .activate()
124    }
125
126    /// Deactivates a key by ID.
127    pub fn deactivate(&mut self, key_id: u16) -> Result<(), Error> {
128        self.find_mut(key_id)
129            .ok_or(Error::UnknownKeyId(key_id))?
130            .deactivate()
131    }
132
133    /// Destroys a key by ID.
134    pub fn destroy(&mut self, key_id: u16) -> Result<(), Error> {
135        self.find_mut(key_id)
136            .ok_or(Error::UnknownKeyId(key_id))?
137            .destroy();
138        Ok(())
139    }
140
141    /// Processes an OTAR delivery: decrypts session keys using a
142    /// master key and loads them into the ring.
143    ///
144    /// `master_key_id` — the key used to decrypt the encrypted key block.
145    /// `iv` — initialization vector for decryption.
146    /// `encrypted_block` — encrypted payload containing (key_id, key_material) pairs.
147    /// `crypto` — cryptographic backend for decryption.
148    /// `sa` — security association for the master key context.
149    pub fn otar(
150        &mut self,
151        master_key_id: u16,
152        iv: &[u8],
153        encrypted_block: &mut [u8],
154        tag: &[u8],
155        crypto: &impl CryptoProvider,
156        sa: &super::SecurityAssociation,
157    ) -> Result<usize, Error> {
158        let master = self
159            .find(master_key_id)
160            .ok_or(Error::UnknownKeyId(master_key_id))?;
161        if master.state != KeyState::Active {
162            return Err(Error::InvalidKeyState);
163        }
164
165        crypto.decrypt(sa, iv, &[], encrypted_block, tag)?;
166
167        let mut pos = 0;
168        let mut count = 0;
169        while pos + 4 <= encrypted_block.len() {
170            let key_id = u16::from_be_bytes([encrypted_block[pos], encrypted_block[pos + 1]]);
171            let key_len = u16::from_be_bytes([encrypted_block[pos + 2], encrypted_block[pos + 3]])
172                as usize;
173            pos += 4;
174            if pos + key_len > encrypted_block.len() {
175                break;
176            }
177            let material = &encrypted_block[pos..pos + key_len];
178            self.load(key_id, material)?;
179            pos += key_len;
180            count += 1;
181        }
182
183        Ok(count)
184    }
185
186    /// Returns an iterator over all keys for inventory queries.
187    pub fn inventory(&self) -> impl Iterator<Item = (u16, KeyState)> + '_ {
188        self.keys.iter().map(|k| (k.key_id, k.state))
189    }
190
191    /// Verifies a key via challenge-response.
192    ///
193    /// Encrypts `challenge` with the specified key and returns
194    /// the encrypted result in `response_out`.
195    pub fn verify(
196        &self,
197        key_id: u16,
198        iv: &[u8],
199        challenge: &[u8],
200        response_out: &mut [u8],
201        tag_out: &mut [u8],
202        crypto: &impl CryptoProvider,
203        sa: &super::SecurityAssociation,
204    ) -> Result<(), Error> {
205        let key = self.find(key_id).ok_or(Error::UnknownKeyId(key_id))?;
206        if key.state != KeyState::Active {
207            return Err(Error::InvalidKeyState);
208        }
209        let len = challenge.len().min(response_out.len());
210        response_out[..len].copy_from_slice(&challenge[..len]);
211        crypto.encrypt(sa, iv, &[], &mut response_out[..len], tag_out)
212            .map(|_| ())
213    }
214}
215
216#[cfg(test)]
217mod tests {
218    use super::*;
219
220    #[test]
221    fn key_lifecycle() {
222        let mut key = ManagedKey::new(1, &[0xAA; 16]).unwrap();
223        assert_eq!(key.state, KeyState::Preactive);
224        assert_eq!(key.material(), &[0xAA; 16]);
225
226        assert!(key.deactivate().is_err());
227        key.activate().unwrap();
228        assert_eq!(key.state, KeyState::Active);
229
230        assert!(key.activate().is_err());
231        key.deactivate().unwrap();
232        assert_eq!(key.state, KeyState::Deactivated);
233
234        key.destroy();
235        assert_eq!(key.state, KeyState::Destroyed);
236        assert_eq!(key.key_len, 0);
237    }
238
239    #[test]
240    fn keyring_load_activate() {
241        let mut ring = KeyRing::new();
242        ring.load(1, &[0x42; 16]).unwrap();
243        ring.load(2, &[0x7F; 32]).unwrap();
244
245        assert!(ring.load(1, &[0x00; 16]).is_err());
246
247        ring.activate(1).unwrap();
248        assert_eq!(ring.find(1).unwrap().state, KeyState::Active);
249
250        assert!(ring.activate(99).is_err());
251    }
252
253    #[test]
254    fn keyring_destroy() {
255        let mut ring = KeyRing::new();
256        ring.load(5, &[0xFF; 16]).unwrap();
257        ring.destroy(5).unwrap();
258        assert_eq!(ring.find(5).unwrap().state, KeyState::Destroyed);
259    }
260
261    #[test]
262    fn keyring_inventory() {
263        let mut ring = KeyRing::new();
264        ring.load(1, &[0x11; 16]).unwrap();
265        ring.load(2, &[0x22; 16]).unwrap();
266        ring.activate(1).unwrap();
267
268        let inv: heapless::Vec<(u16, KeyState), MAX_KEYS> = ring.inventory().collect();
269        assert_eq!(inv.len(), 2);
270        assert_eq!(inv[0], (1, KeyState::Active));
271        assert_eq!(inv[1], (2, KeyState::Preactive));
272    }
273
274    #[test]
275    fn invalid_key_length() {
276        assert!(ManagedKey::new(1, &[]).is_err());
277        assert!(ManagedKey::new(1, &[0u8; 33]).is_err());
278    }
279}