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
15use super::{
16 chacha::{self, Counter, Overlapping},
17 poly1305, Aad, Nonce, Tag,
18};
19use crate::{
20 cpu,
21 error::InputTooLongError,
22 polyfill::{slice, sliceutil, u64_from_usize, usize_from_u64_saturated},
23};
24use cfg_if::cfg_if;
25
26cfg_if! {
27 if #[cfg(any(
28 all(target_arch = "aarch64", target_endian = "little"),
29 target_arch = "x86_64"))] {
30 use cpu::GetFeature as _;
31 mod integrated;
32 }
33}
34
35pub(super) const KEY_LEN: usize = chacha::KEY_LEN;
36
37const MAX_IN_OUT_LEN: usize = super::max_input_len(block_len:64, overhead_blocks_per_nonce:1);
38// https://tools.ietf.org/html/rfc8439#section-2.8
39const _MAX_IN_OUT_LEN_BOUNDED_BY_RFC: () =
40 assert!(MAX_IN_OUT_LEN == usize_from_u64_saturated(274_877_906_880u64));
41
42#[derive(Clone)]
43pub(super) struct Key(chacha::Key);
44
45impl Key {
46 pub(super) fn new(value: [u8; KEY_LEN]) -> Self {
47 Self(chacha::Key::new(value))
48 }
49}
50
51pub(super) fn seal(
52 key: &Key,
53 nonce: Nonce,
54 aad: Aad<&[u8]>,
55 in_out: &mut [u8],
56 cpu: cpu::Features,
57) -> Result<Tag, InputTooLongError> {
58 #[cfg(any(
59 all(target_arch = "aarch64", target_endian = "little"),
60 target_arch = "x86_64"
61 ))]
62 if let Some(required: Sse41) = cpu.get_feature() {
63 return integrated::seal(key, nonce, aad, in_out, required, optional_cpu_features:cpu.get_feature());
64 }
65
66 seal_fallback(key, nonce, aad, in_out, cpu)
67}
68
69pub(super) fn seal_fallback(
70 Key(chacha20_key: &Key): &Key,
71 nonce: Nonce,
72 aad: Aad<&[u8]>,
73 in_out: &mut [u8],
74 cpu: cpu::Features,
75) -> Result<Tag, InputTooLongError> {
76 let (counter: Counter, poly1305_key: Key) = begin(chacha20_key, nonce, aad, input:in_out, cpu)?;
77 let mut auth: Context = poly1305::Context::from_key(poly1305_key, cpu);
78
79 poly1305_update_padded_16(&mut auth, input:aad.as_ref());
80 chacha20_key.encrypt(counter, in_out.into(), cpu);
81 poly1305_update_padded_16(&mut auth, input:in_out);
82 Ok(finish(auth, aad.as_ref().len(), in_out.len()))
83}
84
85pub(super) fn open(
86 key: &Key,
87 nonce: Nonce,
88 aad: Aad<&[u8]>,
89 in_out: Overlapping<'_>,
90 cpu: cpu::Features,
91) -> Result<Tag, InputTooLongError> {
92 #[cfg(any(
93 all(target_arch = "aarch64", target_endian = "little"),
94 target_arch = "x86_64"
95 ))]
96 if let Some(required: Sse41) = cpu.get_feature() {
97 return integrated::open(key, nonce, aad, in_out, required, optional_cpu_features:cpu.get_feature());
98 }
99
100 open_fallback(key, nonce, aad, in_out, cpu)
101}
102
103pub(super) fn open_fallback(
104 Key(chacha20_key: &Key): &Key,
105 nonce: Nonce,
106 aad: Aad<&[u8]>,
107 in_out: Overlapping<'_>,
108 cpu: cpu::Features,
109) -> Result<Tag, InputTooLongError> {
110 let (counter: Counter, poly1305_key: Key) = begin(chacha20_key, nonce, aad, in_out.input(), cpu)?;
111 let mut auth: Context = poly1305::Context::from_key(poly1305_key, cpu);
112
113 poly1305_update_padded_16(&mut auth, input:aad.as_ref());
114 poly1305_update_padded_16(&mut auth, in_out.input());
115 let in_out_len: usize = in_out.len();
116 chacha20_key.encrypt(counter, in_out, cpu);
117 Ok(finish(auth, aad.as_ref().len(), in_out_len))
118}
119
120fn check_input_lengths(aad: Aad<&[u8]>, input: &[u8]) -> Result<(), InputTooLongError> {
121 if input.len() > MAX_IN_OUT_LEN {
122 return Err(InputTooLongError::new(imprecise_input_length:input.len()));
123 }
124
125 // RFC 8439 Section 2.8 says the maximum AAD length is 2**64 - 1, which is
126 // never larger than usize::MAX, so we don't need an explicit length
127 // check.
128 const _USIZE_BOUNDED_BY_U64: u64 = u64_from_usize(usize::MAX);
129 let _ = aad;
130
131 Ok(())
132}
133
134// Also used by chacha20_poly1305_openssh.
135pub(super) fn begin(
136 key: &chacha::Key,
137 nonce: Nonce,
138 aad: Aad<&[u8]>,
139 input: &[u8],
140 cpu: cpu::Features,
141) -> Result<(Counter, poly1305::Key), InputTooLongError> {
142 check_input_lengths(aad, input)?;
143
144 let mut key_bytes: [u8; 32] = [0u8; poly1305::KEY_LEN];
145 let counter: Counter = key.encrypt_single_block_with_ctr_0(nonce, &mut key_bytes, cpu);
146 let poly1305_key: Key = poly1305::Key::new(key_and_nonce:key_bytes);
147 Ok((counter, poly1305_key))
148}
149
150fn finish(auth: poly1305::Context, aad_len: usize, in_out_len: usize) -> Tag {
151 let mut block: [u8; 16] = [0u8; poly1305::BLOCK_LEN];
152 let (alen: &mut [u8], clen: &mut [u8]) = block.split_at_mut(mid:poly1305::BLOCK_LEN / 2);
153 alen.copy_from_slice(&u64::to_le_bytes(self:u64_from_usize(aad_len)));
154 clen.copy_from_slice(&u64::to_le_bytes(self:u64_from_usize(in_out_len)));
155 auth.finish(&block)
156}
157
158#[inline]
159fn poly1305_update_padded_16(ctx: &mut poly1305::Context, input: &[u8]) {
160 let (whole: AsChunks<'_, u8, 16>, remainder: &[u8]) = slice::as_chunks(slice:input);
161 ctx.update(input:whole);
162 if !remainder.is_empty() {
163 let mut block: [u8; 16] = [0u8; poly1305::BLOCK_LEN];
164 sliceutil::overwrite_at_start(&mut block, b:remainder);
165 ctx.update_block(input:block);
166 }
167}
168