1 | // Copyright 2015-2025 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 | use super::{ |
16 | super::{NONCE_LEN, TAG_LEN}, |
17 | chacha::Overlapping, |
18 | check_input_lengths, Aad, InputTooLongError, Key, Nonce, Tag, KEY_LEN, |
19 | }; |
20 | use cfg_if::cfg_if; |
21 | |
22 | macro_rules! declare_open { |
23 | ( $name:ident ) => { |
24 | prefixed_extern! { |
25 | fn $name( |
26 | out_plaintext: *mut u8, |
27 | ciphertext: *const u8, |
28 | plaintext_len: usize, |
29 | ad: *const u8, |
30 | ad_len: usize, |
31 | data: &mut InOut<open_data_in>, |
32 | ); |
33 | } |
34 | }; |
35 | } |
36 | |
37 | macro_rules! declare_seal { |
38 | ( $name:ident ) => { |
39 | prefixed_extern! { |
40 | fn $name( |
41 | out_ciphertext: *mut u8, |
42 | plaintext: *const u8, |
43 | plaintext_len: usize, |
44 | ad: *const u8, |
45 | ad_len: usize, |
46 | data: &mut InOut<seal_data_in>, |
47 | ); |
48 | } |
49 | }; |
50 | } |
51 | |
52 | cfg_if! { |
53 | if #[cfg(all(target_arch = "aarch64" , target_endian = "little" ))] { |
54 | use crate::cpu::arm::Neon; |
55 | type RequiredCpuFeatures = Neon; |
56 | type OptionalCpuFeatures = (); |
57 | } else { |
58 | use crate::cpu::intel::{Avx2, Bmi2, Sse41}; |
59 | type RequiredCpuFeatures = Sse41; |
60 | type OptionalCpuFeatures = (Avx2, Bmi2); |
61 | } |
62 | } |
63 | |
64 | pub(super) fn seal( |
65 | Key(key: &Key): &Key, |
66 | nonce: Nonce, |
67 | aad: Aad<&[u8]>, |
68 | in_out: &mut [u8], |
69 | required_cpu_features: RequiredCpuFeatures, |
70 | optional_cpu_features: Option<OptionalCpuFeatures>, |
71 | ) -> Result<Tag, InputTooLongError> { |
72 | check_input_lengths(aad, in_out)?; |
73 | |
74 | // XXX: BoringSSL uses `alignas(16)` on `key` instead of on the |
75 | // structure, but Rust can't do that yet; see |
76 | // https://github.com/rust-lang/rust/issues/73557. |
77 | // |
78 | // Keep in sync with the anonymous struct of BoringSSL's |
79 | // `chacha20_poly1305_seal_data`. |
80 | #[repr (align(16), C)] |
81 | #[derive (Clone, Copy)] |
82 | struct seal_data_in { |
83 | key: [u32; KEY_LEN / 4], |
84 | counter: u32, |
85 | nonce: [u8; NONCE_LEN], |
86 | extra_ciphertext: *const u8, |
87 | extra_ciphertext_len: usize, |
88 | } |
89 | |
90 | let mut data = InOut { |
91 | input: seal_data_in { |
92 | key: *key.words_less_safe(), |
93 | counter: 0, |
94 | nonce: *nonce.as_ref(), |
95 | extra_ciphertext: core::ptr::null(), |
96 | extra_ciphertext_len: 0, |
97 | }, |
98 | }; |
99 | |
100 | // Encrypts `plaintext_len` bytes from `plaintext` and writes them to `out_ciphertext`. |
101 | |
102 | let output = in_out.as_mut_ptr(); |
103 | let input = in_out.as_ptr(); |
104 | let len = in_out.len(); |
105 | let ad = aad.as_ref().as_ptr(); |
106 | let ad_len = aad.as_ref().len(); |
107 | |
108 | #[allow (clippy::needless_late_init)] |
109 | let tag; |
110 | |
111 | cfg_if! { |
112 | if #[cfg(all(target_arch = "aarch64" , target_endian = "little" ))] { |
113 | declare_seal! { chacha20_poly1305_seal } |
114 | let _: Neon = required_cpu_features; |
115 | let _: Option<()> = optional_cpu_features; |
116 | tag = unsafe { |
117 | chacha20_poly1305_seal(output, input, len, ad, ad_len, &mut data); |
118 | &data.out.tag |
119 | }; |
120 | } else { |
121 | if matches!((required_cpu_features, optional_cpu_features), |
122 | (Sse41 { .. }, Some((Avx2 { .. }, Bmi2 { .. })))) { |
123 | declare_seal! { chacha20_poly1305_seal_avx2 } |
124 | tag = unsafe { |
125 | chacha20_poly1305_seal_avx2(output, input, len, ad, ad_len, &mut data); |
126 | &data.out.tag |
127 | }; |
128 | } else { |
129 | declare_seal! { chacha20_poly1305_seal_nohw } |
130 | tag = unsafe { |
131 | chacha20_poly1305_seal_nohw(output, input, len, ad, ad_len, &mut data); |
132 | &data.out.tag |
133 | }; |
134 | } |
135 | } |
136 | } |
137 | |
138 | Ok(Tag(*tag)) |
139 | } |
140 | |
141 | pub(super) fn open( |
142 | Key(key: &Key): &Key, |
143 | nonce: Nonce, |
144 | aad: Aad<&[u8]>, |
145 | in_out: Overlapping<'_>, |
146 | required_cpu_features: RequiredCpuFeatures, |
147 | optional_cpu_features: Option<OptionalCpuFeatures>, |
148 | ) -> Result<Tag, InputTooLongError> { |
149 | check_input_lengths(aad, in_out.input())?; |
150 | |
151 | // XXX: BoringSSL uses `alignas(16)` on `key` instead of on the |
152 | // structure, but Rust can't do that yet; see |
153 | // https://github.com/rust-lang/rust/issues/73557. |
154 | // |
155 | // Keep in sync with the anonymous struct of BoringSSL's |
156 | // `chacha20_poly1305_open_data`. |
157 | #[derive (Copy, Clone)] |
158 | #[repr (align(16), C)] |
159 | struct open_data_in { |
160 | key: [u32; KEY_LEN / 4], |
161 | counter: u32, |
162 | nonce: [u8; NONCE_LEN], |
163 | } |
164 | |
165 | let mut data = InOut { |
166 | input: open_data_in { |
167 | key: *key.words_less_safe(), |
168 | counter: 0, |
169 | nonce: *nonce.as_ref(), |
170 | }, |
171 | }; |
172 | |
173 | in_out.with_input_output_len(|input, output, len| { |
174 | let ad = aad.as_ref().as_ptr(); |
175 | let ad_len = aad.as_ref().len(); |
176 | |
177 | #[allow (clippy::needless_late_init)] |
178 | let tag; |
179 | |
180 | cfg_if! { |
181 | if #[cfg(all(target_arch = "aarch64" , target_endian = "little" ))] { |
182 | declare_open! { chacha20_poly1305_open } |
183 | let _: Neon = required_cpu_features; |
184 | let _: Option<()> = optional_cpu_features; |
185 | tag = unsafe { |
186 | chacha20_poly1305_open(output, input, len, ad, ad_len, &mut data); |
187 | &data.out.tag |
188 | }; |
189 | } else { |
190 | if matches!((required_cpu_features, optional_cpu_features), |
191 | (Sse41 { .. }, Some((Avx2 { .. }, Bmi2 { .. })))) { |
192 | declare_open! { chacha20_poly1305_open_avx2 } |
193 | tag = unsafe { |
194 | chacha20_poly1305_open_avx2(output, input, len, ad, ad_len, &mut data); |
195 | &data.out.tag |
196 | }; |
197 | } else { |
198 | declare_open! { chacha20_poly1305_open_nohw } |
199 | tag = unsafe { |
200 | chacha20_poly1305_open_nohw(output, input, len, ad, ad_len, &mut data); |
201 | &data.out.tag |
202 | }; |
203 | } |
204 | } |
205 | } |
206 | |
207 | Ok(Tag(*tag)) |
208 | }) |
209 | } |
210 | |
211 | // Keep in sync with BoringSSL's `chacha20_poly1305_open_data` and |
212 | // `chacha20_poly1305_seal_data`. |
213 | #[repr (C)] |
214 | pub(super) union InOut<T> |
215 | where |
216 | T: Copy, |
217 | { |
218 | pub(super) input: T, |
219 | pub(super) out: Out, |
220 | } |
221 | |
222 | // It isn't obvious whether the assembly code works for tags that aren't |
223 | // 16-byte aligned. In practice it will always be 16-byte aligned because it |
224 | // is embedded in a union where the other member of the union is 16-byte |
225 | // aligned. |
226 | #[derive (Clone, Copy)] |
227 | #[repr (align(16), C)] |
228 | pub(super) struct Out { |
229 | pub(super) tag: [u8; TAG_LEN], |
230 | } |
231 | |