aranya_crypto/aqc/
suite.rs

1use core::fmt;
2
3use serde::{Deserialize, Serialize};
4use zerocopy::{Immutable, IntoBytes, KnownLayout};
5
6/// A TLS 1.3 cipher suite.
7#[repr(u16)]
8#[derive(
9    Copy, Clone, Debug, Eq, PartialEq, Immutable, IntoBytes, KnownLayout, Serialize, Deserialize,
10)]
11#[non_exhaustive]
12pub enum CipherSuiteId {
13    /// TLS_AES_128_GCM_SHA256
14    TlsAes128GcmSha256 = u16::to_be(0x1301),
15    /// TLS_AES_256_GCM_SHA384
16    TlsAes256GcmSha384 = u16::to_be(0x1302),
17    /// TLS_CHACHA20_POLY1305_SHA256
18    TlsChaCha20Poly1305Sha256 = u16::to_be(0x1303),
19    /// TLS_AES_128_CCM_SHA256
20    TlsAes128CcmSha256 = u16::to_be(0x1304),
21    /// TLS_AES_128_CCM_8_SHA256
22    TlsAes128Ccm8Sha256 = u16::to_be(0x1305),
23}
24
25impl CipherSuiteId {
26    /// Returns all of the cipher suites.
27    pub const fn all() -> &'static [Self] {
28        use CipherSuiteId::*;
29        &[
30            TlsAes128GcmSha256,
31            TlsAes256GcmSha384,
32            TlsChaCha20Poly1305Sha256,
33            TlsAes128CcmSha256,
34            TlsAes128Ccm8Sha256,
35        ]
36    }
37
38    /// Converts the cipher suite to its (big endian) byte
39    /// representation.
40    pub const fn to_bytes(self) -> [u8; 2] {
41        zerocopy::transmute!(self)
42    }
43
44    /// Attempts to create a cipher suite from its (big endian)
45    /// byte representation.
46    ///
47    /// It returns `None` if `bytes` is not a valid cipher suite.
48    pub const fn try_from_bytes(bytes: [u8; 2]) -> Option<Self> {
49        use CipherSuiteId::*;
50        let id = match u16::from_be_bytes(bytes) {
51            0x1301 => TlsAes128GcmSha256,
52            0x1302 => TlsAes256GcmSha384,
53            0x1303 => TlsChaCha20Poly1305Sha256,
54            0x1304 => TlsAes128CcmSha256,
55            0x1305 => TlsAes128Ccm8Sha256,
56            _ => return None,
57        };
58        Some(id)
59    }
60}
61
62impl fmt::Display for CipherSuiteId {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        use CipherSuiteId::*;
65        let name = match self {
66            TlsAes128GcmSha256 => "TLS_AES_128_GCM_SHA256",
67            TlsAes256GcmSha384 => "TLS_AES_256_GCM_SHA384",
68            TlsChaCha20Poly1305Sha256 => "TLS_CHACHA20_POLY1305_SHA256",
69            TlsAes128CcmSha256 => "TLS_AES_128_CCM_SHA256",
70            TlsAes128Ccm8Sha256 => "TLS_AES_128_CCM_8_SHA256",
71        };
72        name.fmt(f)
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn test_cipher_suite_id() {
82        use CipherSuiteId::*;
83        let tests = [
84            ([0x13, 0x01], Some(TlsAes128GcmSha256)),
85            ([0x13, 0x02], Some(TlsAes256GcmSha384)),
86            ([0x13, 0x03], Some(TlsChaCha20Poly1305Sha256)),
87            ([0x13, 0x04], Some(TlsAes128CcmSha256)),
88            ([0x13, 0x05], Some(TlsAes128Ccm8Sha256)),
89            ([0x13, 0x00], None),
90            ([0x13, 0x06], None),
91        ];
92        for (idx, (bytes, suite)) in tests.into_iter().enumerate() {
93            let got = CipherSuiteId::try_from_bytes(bytes);
94            assert_eq!(got, suite, "#{idx}");
95
96            let Some(suite) = suite else {
97                continue;
98            };
99
100            assert_eq!(suite.to_bytes(), bytes, "#{idx}");
101
102            // Ensure that the `zerocopy` impls match the manual
103            // methods.
104            let got = suite.as_bytes();
105            let want = suite.to_bytes();
106            assert_eq!(got, want, "#{idx}");
107        }
108    }
109
110    #[test]
111    fn test_cipher_suite_round_trip() {
112        for &suite in CipherSuiteId::all() {
113            let got = CipherSuiteId::try_from_bytes(suite.to_bytes());
114            assert_eq!(got, Some(suite), "{suite}");
115        }
116    }
117}