1 | // Copyright 2018 Brian Smith. |
2 | // |
3 | // Permission to use, copy, modify, and/or distribute this software for any |
4 | // purpose with or without fee is hereby granted, provided that the above |
5 | // copyright notice and this permission notice appear in all copies. |
6 | // |
7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES |
8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY |
10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
14 | |
15 | //! QUIC Header Protection. |
16 | //! |
17 | //! See draft-ietf-quic-tls. |
18 | |
19 | use crate::{ |
20 | aead::{aes, chacha}, |
21 | cpu, error, hkdf, |
22 | }; |
23 | |
24 | /// A key for generating QUIC Header Protection masks. |
25 | pub struct HeaderProtectionKey { |
26 | inner: KeyInner, |
27 | algorithm: &'static Algorithm, |
28 | } |
29 | |
30 | #[allow (clippy::large_enum_variant, variant_size_differences)] |
31 | enum KeyInner { |
32 | Aes(aes::Key), |
33 | ChaCha20(chacha::Key), |
34 | } |
35 | |
36 | impl From<hkdf::Okm<'_, &'static Algorithm>> for HeaderProtectionKey { |
37 | fn from(okm: hkdf::Okm<&'static Algorithm>) -> Self { |
38 | let mut key_bytes: [u8; 32] = [0; super::MAX_KEY_LEN]; |
39 | let algorithm: &Algorithm = *okm.len(); |
40 | let key_bytes: &mut [u8] = &mut key_bytes[..algorithm.key_len()]; |
41 | okm.fill(out:key_bytes).unwrap(); |
42 | Self::new(algorithm, key_bytes).unwrap() |
43 | } |
44 | } |
45 | |
46 | impl HeaderProtectionKey { |
47 | /// Create a new header protection key. |
48 | /// |
49 | /// `key_bytes` must be exactly `algorithm.key_len` bytes long. |
50 | pub fn new( |
51 | algorithm: &'static Algorithm, |
52 | key_bytes: &[u8], |
53 | ) -> Result<Self, error::Unspecified> { |
54 | Ok(Self { |
55 | inner: (algorithm.init)(key_bytes, cpu::features())?, |
56 | algorithm, |
57 | }) |
58 | } |
59 | |
60 | /// Generate a new QUIC Header Protection mask. |
61 | /// |
62 | /// `sample` must be exactly `self.algorithm().sample_len()` bytes long. |
63 | pub fn new_mask(&self, sample: &[u8]) -> Result<[u8; 5], error::Unspecified> { |
64 | let sample = <&[u8; SAMPLE_LEN]>::try_from(sample)?; |
65 | |
66 | let out = (self.algorithm.new_mask)(&self.inner, *sample); |
67 | Ok(out) |
68 | } |
69 | |
70 | /// The key's algorithm. |
71 | #[inline (always)] |
72 | pub fn algorithm(&self) -> &'static Algorithm { |
73 | self.algorithm |
74 | } |
75 | } |
76 | |
77 | const SAMPLE_LEN: usize = super::TAG_LEN; |
78 | |
79 | /// QUIC sample for new key masks |
80 | pub type Sample = [u8; SAMPLE_LEN]; |
81 | |
82 | /// A QUIC Header Protection Algorithm. |
83 | pub struct Algorithm { |
84 | init: fn(key: &[u8], cpu_features: cpu::Features) -> Result<KeyInner, error::Unspecified>, |
85 | |
86 | new_mask: fn(key: &KeyInner, sample: Sample) -> [u8; 5], |
87 | |
88 | key_len: usize, |
89 | id: AlgorithmID, |
90 | } |
91 | |
92 | impl hkdf::KeyType for &'static Algorithm { |
93 | #[inline ] |
94 | fn len(&self) -> usize { |
95 | self.key_len() |
96 | } |
97 | } |
98 | |
99 | impl Algorithm { |
100 | /// The length of the key. |
101 | #[inline (always)] |
102 | pub fn key_len(&self) -> usize { |
103 | self.key_len |
104 | } |
105 | |
106 | /// The required sample length. |
107 | #[inline (always)] |
108 | pub fn sample_len(&self) -> usize { |
109 | SAMPLE_LEN |
110 | } |
111 | } |
112 | |
113 | derive_debug_via_id!(Algorithm); |
114 | |
115 | #[derive (Debug, Eq, PartialEq)] |
116 | enum AlgorithmID { |
117 | AES_128, |
118 | AES_256, |
119 | CHACHA20, |
120 | } |
121 | |
122 | impl PartialEq for Algorithm { |
123 | fn eq(&self, other: &Self) -> bool { |
124 | self.id == other.id |
125 | } |
126 | } |
127 | |
128 | impl Eq for Algorithm {} |
129 | |
130 | /// AES-128. |
131 | pub static AES_128: Algorithm = Algorithm { |
132 | key_len: 16, |
133 | init: aes_init_128, |
134 | new_mask: aes_new_mask, |
135 | id: AlgorithmID::AES_128, |
136 | }; |
137 | |
138 | /// AES-256. |
139 | pub static AES_256: Algorithm = Algorithm { |
140 | key_len: 32, |
141 | init: aes_init_256, |
142 | new_mask: aes_new_mask, |
143 | id: AlgorithmID::AES_256, |
144 | }; |
145 | |
146 | fn aes_init_128(key: &[u8], cpu_features: cpu::Features) -> Result<KeyInner, error::Unspecified> { |
147 | let aes_key: Key = aes::Key::new(bytes:key, aes::Variant::AES_128, cpu_features)?; |
148 | Ok(KeyInner::Aes(aes_key)) |
149 | } |
150 | |
151 | fn aes_init_256(key: &[u8], cpu_features: cpu::Features) -> Result<KeyInner, error::Unspecified> { |
152 | let aes_key: Key = aes::Key::new(bytes:key, aes::Variant::AES_256, cpu_features)?; |
153 | Ok(KeyInner::Aes(aes_key)) |
154 | } |
155 | |
156 | fn aes_new_mask(key: &KeyInner, sample: Sample) -> [u8; 5] { |
157 | let aes_key: &Key = match key { |
158 | KeyInner::Aes(key: &Key) => key, |
159 | _ => unreachable!(), |
160 | }; |
161 | |
162 | aes_key.new_mask(sample) |
163 | } |
164 | |
165 | /// ChaCha20. |
166 | pub static CHACHA20: Algorithm = Algorithm { |
167 | key_len: chacha::KEY_LEN, |
168 | init: chacha20_init, |
169 | new_mask: chacha20_new_mask, |
170 | id: AlgorithmID::CHACHA20, |
171 | }; |
172 | |
173 | fn chacha20_init(key: &[u8], _cpu_features: cpu::Features) -> Result<KeyInner, error::Unspecified> { |
174 | let chacha20_key: [u8; chacha::KEY_LEN] = key.try_into()?; |
175 | Ok(KeyInner::ChaCha20(chacha::Key::new(chacha20_key))) |
176 | } |
177 | |
178 | fn chacha20_new_mask(key: &KeyInner, sample: Sample) -> [u8; 5] { |
179 | let chacha20_key: &Key = match key { |
180 | KeyInner::ChaCha20(key: &Key) => key, |
181 | _ => unreachable!(), |
182 | }; |
183 | |
184 | chacha20_key.new_mask(sample) |
185 | } |
186 | |