ring/aead/chacha20_poly1305/
integrated.rs

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 AUTHOR DISCLAIMS ALL WARRANTIES
8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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
15use super::{
16    super::{NONCE_LEN, TAG_LEN},
17    chacha::Overlapping,
18    check_input_lengths, Aad, InputTooLongError, Key, Nonce, Tag, KEY_LEN,
19};
20use cfg_if::cfg_if;
21
22macro_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
37macro_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
52cfg_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
64pub(super) fn seal(
65    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            let _: Sse41 = required_cpu_features;
122            if matches!(optional_cpu_features, 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_sse41 }
130                tag = unsafe {
131                    chacha20_poly1305_seal_sse41(output, input, len, ad, ad_len, &mut data);
132                    &data.out.tag
133                };
134            }
135        }
136    }
137
138    Ok(Tag(*tag))
139}
140
141pub(super) fn open(
142    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                let _: Sse41 = required_cpu_features;
191                if matches!(optional_cpu_features, 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_sse41 }
199                    tag = unsafe {
200                        chacha20_poly1305_open_sse41(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)]
214pub(super) union InOut<T>
215where
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)]
228pub(super) struct Out {
229    pub(super) tag: [u8; TAG_LEN],
230}