aranya_crypto/tls/
suite.rs

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