| 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 | chacha::{self, Counter, Overlapping}, |
| 17 | poly1305, Aad, Nonce, Tag, |
| 18 | }; |
| 19 | use crate::{ |
| 20 | cpu, |
| 21 | error::InputTooLongError, |
| 22 | polyfill::{slice, sliceutil, u64_from_usize, usize_from_u64_saturated}, |
| 23 | }; |
| 24 | use cfg_if::cfg_if; |
| 25 | |
| 26 | cfg_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 | |
| 35 | pub(super) const KEY_LEN: usize = chacha::KEY_LEN; |
| 36 | |
| 37 | const 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 |
| 39 | const _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)] |
| 43 | pub(super) struct Key(chacha::Key); |
| 44 | |
| 45 | impl Key { |
| 46 | pub(super) fn new(value: [u8; KEY_LEN]) -> Self { |
| 47 | Self(chacha::Key::new(value)) |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | pub(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 | |
| 69 | pub(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 | |
| 85 | pub(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 | |
| 103 | pub(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 | |
| 120 | fn 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. |
| 135 | pub(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 | |
| 150 | fn 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 ] |
| 159 | fn 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 | |