Skip to main content

leodos_protocols/datalink/security/sdls/
mod.rs

1//! Space Data Link Security (SDLS) Protocol (CCSDS 355.0-B-2).
2//!
3//! Provides authentication, encryption, and authenticated encryption
4//! services for CCSDS data link frames (TM, TC, AOS, USLP).
5//!
6//! The protocol inserts a Security Header after the transfer frame
7//! header and an optional Security Trailer before the frame trailer.
8//! All field lengths are determined by the Security Association (SA),
9//! which is identified by the Security Parameter Index (SPI) in the
10//! Security Header.
11
12/// Extended Procedures PDU format (CCSDS 355.1-B-1).
13pub mod ep;
14mod gcm;
15/// Key management: OTAR, activation, verification, destruction.
16pub mod key;
17/// Security Association lifecycle management.
18pub mod sa_mgmt;
19mod security_header;
20
21pub use gcm::AesGcmCrypto;
22pub use security_header::SecurityHeader;
23pub use security_header::SecurityTrailer;
24
25/// Maximum Security Header size in bytes (per CCSDS 355.0-B-2 4.1.1.1.4).
26pub const MAX_SECURITY_HEADER_SIZE: usize = 64;
27
28/// Maximum MAC size in bytes (per Table 6-1).
29pub const MAX_MAC_SIZE: usize = 64;
30
31/// Maximum IV size in bytes (per Table 6-1).
32pub const MAX_IV_SIZE: usize = 32;
33
34/// Maximum Sequence Number size in bytes (per Table 6-1).
35pub const MAX_SN_SIZE: usize = 8;
36
37/// Maximum Pad Length field size in bytes (per Table 6-1).
38pub const MAX_PL_SIZE: usize = 2;
39
40/// The cryptographic service type of a Security Association.
41#[derive(Debug, Copy, Clone, Eq, PartialEq)]
42pub enum ServiceType {
43    /// Authentication only (MAC computed, data in cleartext).
44    Authentication,
45    /// Encryption only (data encrypted, no MAC).
46    Encryption,
47    /// Authenticated encryption (encrypt-then-MAC).
48    AuthenticatedEncryption,
49}
50
51/// Configuration for a Security Association (SA).
52///
53/// An SA defines the security context for a virtual channel or MAP.
54/// Both sender and receiver must share identical SA parameters.
55#[derive(Debug, Clone)]
56pub struct SecurityAssociation {
57    /// Security Parameter Index (1-65534; 0 and 65535 reserved).
58    pub spi: u16,
59    /// The cryptographic service type.
60    pub service_type: ServiceType,
61    /// Length of the IV field in the Security Header (0-32 bytes).
62    pub iv_len: u8,
63    /// Length of the Sequence Number field (0-8 bytes).
64    pub sn_len: u8,
65    /// Length of the Pad Length field (0-2 bytes).
66    pub pl_len: u8,
67    /// Length of the MAC field in the Security Trailer (0-64 bytes).
68    pub mac_len: u8,
69    /// Current anti-replay sequence number (sender: next to send).
70    pub sequence_number: u64,
71    /// Sequence number window for the receiver.
72    pub sequence_window: u64,
73    /// Authentication bit mask (applied before MAC computation).
74    /// Sized to cover the largest expected frame.
75    pub auth_mask: heapless::Vec<u8, 2048>,
76}
77
78impl SecurityAssociation {
79    /// Total size of the Security Header for this SA.
80    pub fn header_size(&self) -> usize {
81        2 + self.iv_len as usize + self.sn_len as usize + self.pl_len as usize
82    }
83
84    /// Total size of the Security Trailer for this SA.
85    pub fn trailer_size(&self) -> usize {
86        self.mac_len as usize
87    }
88
89    /// Total overhead added by SDLS (header + trailer).
90    pub fn overhead(&self) -> usize {
91        self.header_size() + self.trailer_size()
92    }
93}
94
95/// Errors from SDLS processing.
96#[derive(Debug, Copy, Clone, Eq, PartialEq, thiserror::Error)]
97pub enum Error {
98    /// The SPI value is reserved (0 or 65535).
99    #[error("reserved SPI: {0}")]
100    ReservedSpi(u16),
101    /// No SA found for the given SPI.
102    #[error("unknown SPI: {0}")]
103    UnknownSpi(u16),
104    /// The buffer is too small for the security fields.
105    #[error("buffer too small: need {required} bytes, have {available} bytes")]
106    BufferTooSmall {
107        /// Minimum bytes needed.
108        required: usize,
109        /// Actual bytes available.
110        available: usize,
111    },
112    /// The frame is too short to contain a Security Header.
113    #[error("frame too short to contain Security Header")]
114    FrameTooShort,
115    /// MAC verification failed.
116    #[error("MAC verification failed")]
117    MacVerificationFailed,
118    /// Anti-replay sequence number check failed.
119    #[error("sequence number rejected: got {received}, expected {expected}")]
120    SequenceNumberRejected {
121        /// The received sequence number.
122        received: u64,
123        /// The expected sequence number.
124        expected: u64,
125    },
126    /// Padding error during decryption.
127    #[error("padding error")]
128    PaddingError,
129    /// A crypto backend error occurred.
130    #[error("cryptographic operation failed")]
131    CryptoError,
132    /// Invalid EP procedure ID or service group.
133    #[error("invalid SDLS-EP procedure")]
134    InvalidProcedure,
135    /// Key ID not found in the key ring.
136    #[error("unknown key ID: {0}")]
137    UnknownKeyId(u16),
138    /// Duplicate key ID in the key ring.
139    #[error("duplicate key ID")]
140    DuplicateKeyId,
141    /// Key ring is full.
142    #[error("key ring full")]
143    KeyRingFull,
144    /// Invalid key length.
145    #[error("invalid key length")]
146    InvalidKeyLength,
147    /// Key is in the wrong state for this operation.
148    #[error("invalid key state")]
149    InvalidKeyState,
150    /// SA is in the wrong state for this operation.
151    #[error("invalid SA state")]
152    InvalidSaState,
153    /// Duplicate SPI in the SA table.
154    #[error("duplicate SPI: {0}")]
155    DuplicateSpi(u16),
156    /// SA table is full.
157    #[error("SA table full")]
158    SaTableFull,
159}
160
161/// Trait for pluggable cryptographic backends.
162///
163/// Implementations provide the actual encryption, decryption, and
164/// MAC computation. The SDLS framing layer is algorithm-agnostic;
165/// concrete algorithms (AES-GCM, CMAC, etc.) are supplied here.
166///
167/// For AEAD ciphers (AES-GCM), `encrypt` and `decrypt` handle
168/// both confidentiality and authentication via `aad` and `tag`.
169/// For authentication-only mode (CMAC), use `compute_mac`.
170pub trait CryptoProvider {
171    /// Encrypt data in place (AEAD). `aad` is additional
172    /// authenticated data (frame headers). The authentication
173    /// tag is written to `tag_out`. Returns padding byte count
174    /// (0 for GCM/CTR).
175    fn encrypt(
176        &self,
177        sa: &SecurityAssociation,
178        iv: &[u8],
179        aad: &[u8],
180        data: &mut [u8],
181        tag_out: &mut [u8],
182    ) -> Result<usize, Error>;
183
184    /// Decrypt data in place (AEAD). Verifies the authentication
185    /// tag against `aad` and ciphertext. Returns padding byte
186    /// count.
187    fn decrypt(
188        &self,
189        sa: &SecurityAssociation,
190        iv: &[u8],
191        aad: &[u8],
192        data: &mut [u8],
193        tag: &[u8],
194    ) -> Result<usize, Error>;
195
196    /// Compute a MAC for authentication-only mode. The `iv` is
197    /// passed for algorithms that need it (e.g. GMAC).
198    fn compute_mac(
199        &self,
200        sa: &SecurityAssociation,
201        iv: &[u8],
202        payload: &[u8],
203        mac_out: &mut [u8],
204    ) -> Result<(), Error>;
205}
206
207/// A no-op crypto provider for "clear mode" testing.
208///
209/// Per CCSDS 355.0-B-2, a clear-mode SA uses a no-op algorithm so
210/// the Security Header and Trailer are present but no actual crypto
211/// is performed. Useful for development and integration testing.
212pub struct ClearModeCrypto;
213
214impl CryptoProvider for ClearModeCrypto {
215    fn encrypt(
216        &self,
217        _sa: &SecurityAssociation,
218        _iv: &[u8],
219        _aad: &[u8],
220        _data: &mut [u8],
221        _tag_out: &mut [u8],
222    ) -> Result<usize, Error> {
223        Ok(0)
224    }
225
226    fn decrypt(
227        &self,
228        _sa: &SecurityAssociation,
229        _iv: &[u8],
230        _aad: &[u8],
231        _data: &mut [u8],
232        _tag: &[u8],
233    ) -> Result<usize, Error> {
234        Ok(0)
235    }
236
237    fn compute_mac(
238        &self,
239        sa: &SecurityAssociation,
240        _iv: &[u8],
241        _payload: &[u8],
242        mac_out: &mut [u8],
243    ) -> Result<(), Error> {
244        let len = sa.mac_len as usize;
245        mac_out[..len].fill(0);
246        Ok(())
247    }
248}
249
250/// Write a Security Header into the given buffer.
251///
252/// Returns the number of bytes written.
253pub fn write_security_header(
254    sa: &SecurityAssociation,
255    iv: &[u8],
256    sn: &[u8],
257    pad_len: u16,
258    out: &mut [u8],
259) -> Result<usize, Error> {
260    let hdr_size = sa.header_size();
261    if out.len() < hdr_size {
262        return Err(Error::BufferTooSmall {
263            required: hdr_size,
264            available: out.len(),
265        });
266    }
267    if sa.spi == 0 || sa.spi == 0xFFFF {
268        return Err(Error::ReservedSpi(sa.spi));
269    }
270
271    let mut pos = 0;
272
273    // SPI (16 bits, big-endian)
274    out[pos..pos + 2].copy_from_slice(&sa.spi.to_be_bytes());
275    pos += 2;
276
277    // IV
278    let iv_len = sa.iv_len as usize;
279    if iv_len > 0 {
280        out[pos..pos + iv_len].copy_from_slice(&iv[..iv_len]);
281        pos += iv_len;
282    }
283
284    // Sequence Number
285    let sn_len = sa.sn_len as usize;
286    if sn_len > 0 {
287        out[pos..pos + sn_len].copy_from_slice(&sn[..sn_len]);
288        pos += sn_len;
289    }
290
291    // Pad Length
292    let pl_len = sa.pl_len as usize;
293    if pl_len > 0 {
294        let pl_bytes = pad_len.to_be_bytes();
295        let start = 2 - pl_len;
296        out[pos..pos + pl_len].copy_from_slice(&pl_bytes[start..]);
297        pos += pl_len;
298    }
299
300    Ok(pos)
301}
302
303/// Read the SPI from the first 2 bytes of a security header.
304pub fn read_spi(header_bytes: &[u8]) -> Result<u16, Error> {
305    if header_bytes.len() < 2 {
306        return Err(Error::FrameTooShort);
307    }
308    Ok(u16::from_be_bytes([header_bytes[0], header_bytes[1]]))
309}
310
311/// Extract fields from a security header given the SA configuration.
312pub fn parse_security_header<'a>(
313    sa: &SecurityAssociation,
314    header_bytes: &'a [u8],
315) -> Result<SecurityHeader<'a>, Error> {
316    let hdr_size = sa.header_size();
317    if header_bytes.len() < hdr_size {
318        return Err(Error::FrameTooShort);
319    }
320    SecurityHeader::parse(sa, header_bytes)
321}
322
323/// Apply security processing to a frame (sending side).
324///
325/// Given a frame buffer with the transfer frame header already
326/// written, this function:
327/// 1. Writes the Security Header after `header_end`
328/// 2. Encrypts the data field if needed (AEAD for GCM)
329/// 3. Computes and writes the MAC if needed
330///
331/// The caller must have left room for the security overhead.
332pub fn apply_security(
333    sa: &mut SecurityAssociation,
334    crypto: &impl CryptoProvider,
335    frame: &mut [u8],
336    header_end: usize,
337    data_start: usize,
338    data_end: usize,
339) -> Result<(), Error> {
340    if sa.spi == 0 || sa.spi == 0xFFFF {
341        return Err(Error::ReservedSpi(sa.spi));
342    }
343
344    let trailer_start = data_end;
345    let mac_len = sa.mac_len as usize;
346    let trailer_end = trailer_start + mac_len;
347    if frame.len() < trailer_end {
348        return Err(Error::BufferTooSmall {
349            required: trailer_end,
350            available: frame.len(),
351        });
352    }
353
354    // Prepare IV and SN from sequence number
355    let mut iv_buf = [0u8; MAX_IV_SIZE];
356    let mut sn_buf = [0u8; MAX_SN_SIZE];
357    let iv_len = sa.iv_len as usize;
358    let sn_len = sa.sn_len as usize;
359
360    if iv_len > 0 {
361        let sn_bytes = sa.sequence_number.to_be_bytes();
362        let start = 8usize.saturating_sub(iv_len);
363        let copy_len = iv_len.min(8);
364        let iv_start = iv_len.saturating_sub(8);
365        iv_buf[iv_start..iv_start + copy_len].copy_from_slice(&sn_bytes[start..start + copy_len]);
366    }
367
368    if sn_len > 0 {
369        let sn_bytes = sa.sequence_number.to_be_bytes();
370        let start = 8 - sn_len;
371        sn_buf[..sn_len].copy_from_slice(&sn_bytes[start..]);
372    }
373
374    // Write Security Header first (needed as AAD for AEAD).
375    // pad_len is always 0 for GCM/CTR (CCSDS stream ciphers).
376    write_security_header(
377        sa,
378        &iv_buf[..iv_len],
379        &sn_buf[..sn_len],
380        0,
381        &mut frame[header_end..data_start],
382    )?;
383
384    let mut mac_buf = [0u8; MAX_MAC_SIZE];
385
386    match sa.service_type {
387        ServiceType::Authentication => {
388            let auth_end = data_end;
389            if sa.auth_mask.len() >= auth_end {
390                let mut masked = heapless::Vec::<u8, 2048>::new();
391                masked.resize(auth_end, 0).ok();
392                for i in 0..auth_end {
393                    masked[i] = frame[i] & sa.auth_mask[i];
394                }
395                crypto.compute_mac(
396                    sa,
397                    &iv_buf[..iv_len],
398                    &masked[..auth_end],
399                    &mut mac_buf[..mac_len],
400                )?;
401            } else {
402                crypto.compute_mac(
403                    sa,
404                    &iv_buf[..iv_len],
405                    &frame[..auth_end],
406                    &mut mac_buf[..mac_len],
407                )?;
408            }
409            frame[trailer_start..trailer_start + mac_len].copy_from_slice(&mac_buf[..mac_len]);
410        }
411        ServiceType::Encryption => {
412            crypto.encrypt(
413                sa,
414                &iv_buf[..iv_len],
415                &[],
416                &mut frame[data_start..data_end],
417                &mut [],
418            )?;
419        }
420        ServiceType::AuthenticatedEncryption => {
421            // AAD = frame header + security header (before data)
422            let (aad_part, rest) = frame.split_at_mut(data_start);
423            let data_len = data_end - data_start;
424
425            if sa.auth_mask.len() >= data_start {
426                let mut masked_aad = [0u8; 256];
427                for i in 0..aad_part.len() {
428                    masked_aad[i] = aad_part[i] & sa.auth_mask[i];
429                }
430                crypto.encrypt(
431                    sa,
432                    &iv_buf[..iv_len],
433                    &masked_aad[..aad_part.len()],
434                    &mut rest[..data_len],
435                    &mut mac_buf[..mac_len],
436                )?;
437            } else {
438                crypto.encrypt(
439                    sa,
440                    &iv_buf[..iv_len],
441                    aad_part,
442                    &mut rest[..data_len],
443                    &mut mac_buf[..mac_len],
444                )?;
445            }
446            rest[data_len..data_len + mac_len].copy_from_slice(&mac_buf[..mac_len]);
447        }
448    }
449
450    sa.sequence_number = sa.sequence_number.wrapping_add(1);
451
452    Ok(())
453}
454
455/// Process security on a received frame (receiving side).
456///
457/// Verifies authentication, checks anti-replay, and decrypts.
458/// For AEAD (AES-GCM), tag verification and decryption are
459/// combined in a single `decrypt` call.
460pub fn process_security(
461    sa: &mut SecurityAssociation,
462    crypto: &impl CryptoProvider,
463    frame: &mut [u8],
464    header_end: usize,
465    data_start: usize,
466    data_end: usize,
467) -> Result<(), Error> {
468    let spi = read_spi(&frame[header_end..])?;
469    if spi != sa.spi {
470        return Err(Error::UnknownSpi(spi));
471    }
472
473    let trailer_start = data_end;
474    let mac_len = sa.mac_len as usize;
475    let trailer_end = trailer_start + mac_len;
476    if frame.len() < trailer_end {
477        return Err(Error::FrameTooShort);
478    }
479
480    let (received_sn, iv_copy) = {
481        let sec_hdr = SecurityHeader::parse(sa, &frame[header_end..data_start])?;
482        let sn_val = sec_hdr.sequence_number_value();
483        let mut iv_buf = [0u8; MAX_IV_SIZE];
484        let iv = sec_hdr.iv();
485        iv_buf[..iv.len()].copy_from_slice(iv);
486        (sn_val, iv_buf)
487    };
488
489    let iv_len = sa.iv_len as usize;
490    let sn_len = sa.sn_len as usize;
491
492    match sa.service_type {
493        ServiceType::Authentication => {
494            let mut mac_buf = [0u8; MAX_MAC_SIZE];
495            let auth_end = data_end;
496
497            if sa.auth_mask.len() >= auth_end {
498                let mut masked = heapless::Vec::<u8, 2048>::new();
499                masked.resize(auth_end, 0).ok();
500                for i in 0..auth_end {
501                    masked[i] = frame[i] & sa.auth_mask[i];
502                }
503                crypto.compute_mac(
504                    sa,
505                    &iv_copy[..iv_len],
506                    &masked[..auth_end],
507                    &mut mac_buf[..mac_len],
508                )?;
509            } else {
510                crypto.compute_mac(
511                    sa,
512                    &iv_copy[..iv_len],
513                    &frame[..auth_end],
514                    &mut mac_buf[..mac_len],
515                )?;
516            }
517
518            let received_mac = &frame[trailer_start..trailer_start + mac_len];
519            if received_mac != &mac_buf[..mac_len] {
520                return Err(Error::MacVerificationFailed);
521            }
522
523            if sn_len > 0 {
524                check_sequence_number(sa, received_sn)?;
525            }
526        }
527        ServiceType::Encryption => {
528            crypto.decrypt(
529                sa,
530                &iv_copy[..iv_len],
531                &[],
532                &mut frame[data_start..data_end],
533                &[],
534            )?;
535        }
536        ServiceType::AuthenticatedEncryption => {
537            // Copy tag from trailer before splitting
538            let mut tag = [0u8; MAX_MAC_SIZE];
539            tag[..mac_len].copy_from_slice(&frame[trailer_start..trailer_start + mac_len]);
540
541            // AEAD decrypt + tag verification
542            let (aad_part, rest) = frame.split_at_mut(data_start);
543            let data_len = data_end - data_start;
544
545            if sa.auth_mask.len() >= data_start {
546                let mut masked_aad = [0u8; 256];
547                for i in 0..aad_part.len() {
548                    masked_aad[i] = aad_part[i] & sa.auth_mask[i];
549                }
550                crypto.decrypt(
551                    sa,
552                    &iv_copy[..iv_len],
553                    &masked_aad[..aad_part.len()],
554                    &mut rest[..data_len],
555                    &tag[..mac_len],
556                )?;
557            } else {
558                crypto.decrypt(
559                    sa,
560                    &iv_copy[..iv_len],
561                    aad_part,
562                    &mut rest[..data_len],
563                    &tag[..mac_len],
564                )?;
565            }
566
567            if sn_len > 0 {
568                check_sequence_number(sa, received_sn)?;
569            }
570        }
571    }
572
573    Ok(())
574}
575
576fn check_sequence_number(sa: &mut SecurityAssociation, received: u64) -> Result<(), Error> {
577    let expected = sa.sequence_number;
578    if received < expected {
579        return Err(Error::SequenceNumberRejected { received, expected });
580    }
581    let window = sa.sequence_window;
582    if window > 0 && received > expected.wrapping_add(window) {
583        return Err(Error::SequenceNumberRejected { received, expected });
584    }
585    sa.sequence_number = received.wrapping_add(1);
586    Ok(())
587}
588
589#[cfg(test)]
590mod tests {
591    use super::*;
592
593    fn test_sa() -> SecurityAssociation {
594        SecurityAssociation {
595            spi: 1,
596            service_type: ServiceType::Authentication,
597            iv_len: 4,
598            sn_len: 4,
599            pl_len: 0,
600            mac_len: 16,
601            sequence_number: 0,
602            sequence_window: 100,
603            auth_mask: heapless::Vec::new(),
604        }
605    }
606
607    #[test]
608    fn sa_sizes() {
609        let sa = test_sa();
610        // SPI(2) + IV(4) + SN(4) + PL(0) = 10
611        assert_eq!(sa.header_size(), 10);
612        assert_eq!(sa.trailer_size(), 16);
613        assert_eq!(sa.overhead(), 26);
614    }
615
616    #[test]
617    fn write_and_read_spi() {
618        let sa = test_sa();
619        let mut buf = [0u8; 64];
620        let iv = [0u8; 4];
621        let sn = [0u8; 4];
622        write_security_header(&sa, &iv, &sn, 0, &mut buf).unwrap();
623
624        let spi = read_spi(&buf).unwrap();
625        assert_eq!(spi, 1);
626    }
627
628    #[test]
629    fn reserved_spi_rejected() {
630        let mut sa = test_sa();
631        sa.spi = 0;
632        let mut buf = [0u8; 64];
633        let err = write_security_header(&sa, &[0; 4], &[0; 4], 0, &mut buf).unwrap_err();
634        assert_eq!(err, Error::ReservedSpi(0));
635
636        sa.spi = 0xFFFF;
637        let err = write_security_header(&sa, &[0; 4], &[0; 4], 0, &mut buf).unwrap_err();
638        assert_eq!(err, Error::ReservedSpi(0xFFFF));
639    }
640
641    #[test]
642    fn header_roundtrip() {
643        let sa = test_sa();
644        let mut buf = [0u8; 64];
645        let iv = [0x01, 0x02, 0x03, 0x04];
646        let sn = [0x00, 0x00, 0x00, 0x05];
647        let written = write_security_header(&sa, &iv, &sn, 0, &mut buf).unwrap();
648        assert_eq!(written, 10);
649
650        let hdr = SecurityHeader::parse(&sa, &buf[..written]).unwrap();
651        assert_eq!(hdr.spi(), 1);
652        assert_eq!(hdr.iv(), &[0x01, 0x02, 0x03, 0x04]);
653        assert_eq!(hdr.sequence_number(), &[0x00, 0x00, 0x00, 0x05]);
654        assert_eq!(hdr.sequence_number_value(), 5);
655        assert_eq!(hdr.pad_length(), 0);
656    }
657
658    #[test]
659    fn clear_mode_apply_and_process() {
660        // Simulate a frame: [5-byte fake header][sec hdr][data][mac]
661        let mut sa_send = test_sa();
662        let mut sa_recv = test_sa();
663        let crypto = ClearModeCrypto;
664
665        let header_end = 5;
666        let data_start = header_end + sa_send.header_size(); // 15
667        let data_end = data_start + 10; // 25
668        let mut frame = [0u8; 64];
669        // Write some fake frame header
670        frame[0..5].copy_from_slice(&[0xC0, 0x00, 0x2A, 0x00, 0x28]);
671        // Write some payload data
672        frame[data_start..data_end].copy_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
673
674        apply_security(
675            &mut sa_send,
676            &crypto,
677            &mut frame,
678            header_end,
679            data_start,
680            data_end,
681        )
682        .unwrap();
683
684        // SPI should be written at header_end
685        assert_eq!(read_spi(&frame[header_end..]).unwrap(), 1);
686        // Sequence number should have incremented
687        assert_eq!(sa_send.sequence_number, 1);
688
689        // Now process on the receive side
690        process_security(
691            &mut sa_recv,
692            &crypto,
693            &mut frame,
694            header_end,
695            data_start,
696            data_end,
697        )
698        .unwrap();
699
700        // Receiver sequence number should have advanced
701        assert_eq!(sa_recv.sequence_number, 1);
702        // Data should still be intact (clear mode)
703        assert_eq!(
704            &frame[data_start..data_end],
705            &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
706        );
707    }
708
709    #[test]
710    fn encryption_only_sa() {
711        let sa = SecurityAssociation {
712            spi: 2,
713            service_type: ServiceType::Encryption,
714            iv_len: 12,
715            sn_len: 0,
716            pl_len: 1,
717            mac_len: 0,
718            sequence_number: 0,
719            sequence_window: 0,
720            auth_mask: heapless::Vec::new(),
721        };
722        // SPI(2) + IV(12) + SN(0) + PL(1) = 15
723        assert_eq!(sa.header_size(), 15);
724        assert_eq!(sa.trailer_size(), 0);
725    }
726
727    #[test]
728    fn authenticated_encryption_sa() {
729        let sa = SecurityAssociation {
730            spi: 3,
731            service_type: ServiceType::AuthenticatedEncryption,
732            iv_len: 12,
733            sn_len: 0,
734            pl_len: 1,
735            mac_len: 16,
736            sequence_number: 0,
737            sequence_window: 100,
738            auth_mask: heapless::Vec::new(),
739        };
740        // SPI(2) + IV(12) + SN(0) + PL(1) = 15
741        assert_eq!(sa.header_size(), 15);
742        // MAC(16)
743        assert_eq!(sa.trailer_size(), 16);
744        assert_eq!(sa.overhead(), 31);
745    }
746
747    #[test]
748    fn spi_read_too_short() {
749        let err = read_spi(&[0x00]).unwrap_err();
750        assert_eq!(err, Error::FrameTooShort);
751    }
752
753    #[test]
754    fn header_with_pad_length() {
755        let sa = SecurityAssociation {
756            spi: 10,
757            service_type: ServiceType::Encryption,
758            iv_len: 0,
759            sn_len: 0,
760            pl_len: 2,
761            mac_len: 0,
762            sequence_number: 0,
763            sequence_window: 0,
764            auth_mask: heapless::Vec::new(),
765        };
766        let mut buf = [0u8; 16];
767        let written = write_security_header(&sa, &[], &[], 42, &mut buf).unwrap();
768        // SPI(2) + PL(2) = 4
769        assert_eq!(written, 4);
770
771        let hdr = SecurityHeader::parse(&sa, &buf[..written]).unwrap();
772        assert_eq!(hdr.spi(), 10);
773        assert_eq!(hdr.pad_length(), 42);
774    }
775
776    fn aes_gcm_sa() -> SecurityAssociation {
777        SecurityAssociation {
778            spi: 5,
779            service_type: ServiceType::AuthenticatedEncryption,
780            iv_len: 12,
781            sn_len: 0,
782            pl_len: 0,
783            mac_len: 16,
784            sequence_number: 1,
785            sequence_window: 100,
786            auth_mask: heapless::Vec::new(),
787        }
788    }
789
790    #[test]
791    fn aes_gcm_128_roundtrip() {
792        let key = [0x42u8; 16];
793        let crypto = AesGcmCrypto::new_128(&key);
794        let sa = aes_gcm_sa();
795
796        let iv = [0u8; 12];
797        let aad = [0xC0, 0x00, 0x2A];
798        let original = [1u8, 2, 3, 4, 5, 6, 7, 8];
799        let mut data = original;
800        let mut tag = [0u8; 16];
801
802        crypto.encrypt(&sa, &iv, &aad, &mut data, &mut tag).unwrap();
803        assert_ne!(data, original);
804
805        crypto.decrypt(&sa, &iv, &aad, &mut data, &tag).unwrap();
806        assert_eq!(data, original);
807    }
808
809    #[test]
810    fn aes_gcm_256_roundtrip() {
811        let key = [0x7Fu8; 32];
812        let crypto = AesGcmCrypto::new_256(&key);
813        let sa = aes_gcm_sa();
814
815        let iv = [0u8; 12];
816        let aad = b"header";
817        let original = *b"secret!!";
818        let mut data = original;
819        let mut tag = [0u8; 16];
820
821        crypto.encrypt(&sa, &iv, aad, &mut data, &mut tag).unwrap();
822        crypto.decrypt(&sa, &iv, aad, &mut data, &tag).unwrap();
823        assert_eq!(data, original);
824    }
825
826    #[test]
827    fn aes_gcm_tampered_ciphertext() {
828        let key = [0x42u8; 16];
829        let crypto = AesGcmCrypto::new_128(&key);
830        let sa = aes_gcm_sa();
831
832        let iv = [0u8; 12];
833        let aad = [0xC0];
834        let mut data = [1u8, 2, 3, 4];
835        let mut tag = [0u8; 16];
836
837        crypto.encrypt(&sa, &iv, &aad, &mut data, &mut tag).unwrap();
838
839        data[0] ^= 0xFF; // tamper
840        let err = crypto.decrypt(&sa, &iv, &aad, &mut data, &tag).unwrap_err();
841        assert_eq!(err, Error::MacVerificationFailed);
842    }
843
844    #[test]
845    fn aes_gcm_tampered_aad() {
846        let key = [0x42u8; 16];
847        let crypto = AesGcmCrypto::new_128(&key);
848        let sa = aes_gcm_sa();
849
850        let iv = [0u8; 12];
851        let aad = [0xC0];
852        let mut data = [1u8, 2, 3, 4];
853        let mut tag = [0u8; 16];
854
855        crypto.encrypt(&sa, &iv, &aad, &mut data, &mut tag).unwrap();
856
857        let bad_aad = [0xC1]; // tampered
858        let err = crypto
859            .decrypt(&sa, &iv, &bad_aad, &mut data, &tag)
860            .unwrap_err();
861        assert_eq!(err, Error::MacVerificationFailed);
862    }
863
864    #[test]
865    fn aes_gcm_apply_process_roundtrip() {
866        let key = [0xABu8; 16];
867        let crypto = AesGcmCrypto::new_128(&key);
868
869        let mut sa_send = aes_gcm_sa();
870        let mut sa_recv = aes_gcm_sa();
871
872        // Frame layout:
873        // [5B header][14B sec hdr][10B data][16B MAC]
874        // sec hdr = SPI(2) + IV(12) + SN(0) + PL(0) = 14
875        let header_end = 5;
876        let data_start = header_end + sa_send.header_size();
877        let data_end = data_start + 10;
878        let total = data_end + sa_send.trailer_size();
879        let mut frame = [0u8; 64];
880
881        // Fake frame header
882        frame[0..5].copy_from_slice(&[0xC0, 0x00, 0x2A, 0x00, 0x28]);
883        // Payload
884        frame[data_start..data_end].copy_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
885
886        apply_security(
887            &mut sa_send,
888            &crypto,
889            &mut frame,
890            header_end,
891            data_start,
892            data_end,
893        )
894        .unwrap();
895
896        assert_eq!(sa_send.sequence_number, 2);
897        // Data should be encrypted (not cleartext)
898        assert_ne!(
899            &frame[data_start..data_end],
900            &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
901        );
902        // MAC should be non-zero
903        assert_ne!(&frame[data_end..total], &[0u8; 16]);
904
905        // Process on receive side
906        process_security(
907            &mut sa_recv,
908            &crypto,
909            &mut frame,
910            header_end,
911            data_start,
912            data_end,
913        )
914        .unwrap();
915
916        // Data should be decrypted back
917        assert_eq!(
918            &frame[data_start..data_end],
919            &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
920        );
921    }
922
923    #[test]
924    fn aes_gcm_detect_tampered_frame() {
925        let key = [0xABu8; 16];
926        let crypto = AesGcmCrypto::new_128(&key);
927
928        let mut sa_send = aes_gcm_sa();
929        let mut sa_recv = aes_gcm_sa();
930
931        let header_end = 5;
932        let data_start = header_end + sa_send.header_size();
933        let data_end = data_start + 10;
934        let mut frame = [0u8; 64];
935
936        frame[0..5].copy_from_slice(&[0xC0, 0x00, 0x2A, 0x00, 0x28]);
937        frame[data_start..data_end].copy_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
938
939        apply_security(
940            &mut sa_send,
941            &crypto,
942            &mut frame,
943            header_end,
944            data_start,
945            data_end,
946        )
947        .unwrap();
948
949        // Tamper with encrypted data
950        frame[data_start] ^= 0xFF;
951
952        let err = process_security(
953            &mut sa_recv,
954            &crypto,
955            &mut frame,
956            header_end,
957            data_start,
958            data_end,
959        )
960        .unwrap_err();
961        assert_eq!(err, Error::MacVerificationFailed);
962    }
963}