Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: In-place crypto #2385

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
de60a0f
feat: In-place crypto
larseggert Jan 23, 2025
03a4e15
aead_null
larseggert Jan 23, 2025
8cb421d
Merge branch 'main' into feat-inplace-crypto
larseggert Jan 23, 2025
813b17c
Merge branch 'main' into feat-inplace-crypto
larseggert Jan 23, 2025
ff398b6
WIP decrypt
larseggert Jan 23, 2025
bdd0900
More
larseggert Jan 24, 2025
de40e42
Merge branch 'main' into feat-inplace-crypto
larseggert Jan 24, 2025
1488a4e
fix(transport/packet): don't (mutably) borrow data multiple times
mxinden Jan 26, 2025
3aff846
Merge pull request #34 from mxinden/feat-inplace-crypto
larseggert Jan 28, 2025
f09cc19
Merge branch 'main' into feat-inplace-crypto
larseggert Jan 28, 2025
b0fcf23
Fixes
larseggert Jan 29, 2025
5c9bc3e
Merge branch 'main' into feat-inplace-crypto
larseggert Jan 29, 2025
8f94033
Minimize
larseggert Jan 29, 2025
5d8c5dc
Some suggestions from @martinthomson
larseggert Jan 29, 2025
3285712
More
larseggert Jan 29, 2025
2745a34
More
larseggert Jan 29, 2025
006eb03
Fix `AeadNull`
larseggert Jan 30, 2025
16d598e
More suggestions from @martinthomson
larseggert Jan 30, 2025
3b81fe5
clippy
larseggert Jan 30, 2025
5498522
fixme
larseggert Jan 30, 2025
12e9dde
Update neqo-crypto/src/aead.rs
larseggert Jan 31, 2025
e28fc82
Update neqo-crypto/src/aead.rs
larseggert Jan 31, 2025
a244f52
Update neqo-crypto/src/aead.rs
larseggert Jan 31, 2025
5690eeb
Update neqo-crypto/src/aead_null.rs
larseggert Jan 31, 2025
337a894
Merge branch 'main' into feat-inplace-crypto
larseggert Jan 31, 2025
f2c3558
Minimize diff
larseggert Jan 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion fuzz/fuzz_targets/packet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ fuzz_target!(|data: &[u8]| {
neqo_crypto::init().unwrap();

// Run the fuzzer
_ = PublicPacket::decode(data, decoder);
_ = PublicPacket::decode(&mut data.to_vec(), decoder);
});

#[cfg(any(not(fuzzing), windows))]
Expand Down
2 changes: 1 addition & 1 deletion neqo-bin/src/client/http09.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ impl super::Client for Connection {

fn process_multiple_input<'a>(
&mut self,
dgrams: impl IntoIterator<Item = Datagram<&'a [u8]>>,
dgrams: impl IntoIterator<Item = Datagram<&'a mut [u8]>>,
now: Instant,
) {
self.process_multiple_input(dgrams, now);
Expand Down
2 changes: 1 addition & 1 deletion neqo-bin/src/client/http3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ impl super::Client for Http3Client {

fn process_multiple_input<'a>(
&mut self,
dgrams: impl IntoIterator<Item = Datagram<&'a [u8]>>,
dgrams: impl IntoIterator<Item = Datagram<&'a mut [u8]>>,
now: Instant,
) {
self.process_multiple_input(dgrams, now);
Expand Down
2 changes: 1 addition & 1 deletion neqo-bin/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ trait Client {
fn process_output(&mut self, now: Instant) -> Output;
fn process_multiple_input<'a>(
&mut self,
dgrams: impl IntoIterator<Item = Datagram<&'a [u8]>>,
dgrams: impl IntoIterator<Item = Datagram<&'a mut [u8]>>,
now: Instant,
);
fn has_events(&self) -> bool;
Expand Down
2 changes: 1 addition & 1 deletion neqo-bin/src/server/http09.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ impl HttpServer {
}

impl super::HttpServer for HttpServer {
fn process(&mut self, dgram: Option<Datagram<&[u8]>>, now: Instant) -> Output {
fn process(&mut self, dgram: Option<Datagram<&mut [u8]>>, now: Instant) -> Output {
self.server.process(dgram, now)
}

Expand Down
2 changes: 1 addition & 1 deletion neqo-bin/src/server/http3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ impl Display for HttpServer {
}

impl super::HttpServer for HttpServer {
fn process(&mut self, dgram: Option<Datagram<&[u8]>>, now: Instant) -> neqo_http3::Output {
fn process(&mut self, dgram: Option<Datagram<&mut [u8]>>, now: Instant) -> neqo_http3::Output {
self.server.process(dgram, now)
}

Expand Down
4 changes: 2 additions & 2 deletions neqo-bin/src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ fn qns_read_response(filename: &str) -> Result<Vec<u8>, io::Error> {

#[allow(clippy::module_name_repetitions)]
pub trait HttpServer: Display {
fn process(&mut self, dgram: Option<Datagram<&[u8]>>, now: Instant) -> Output;
fn process(&mut self, dgram: Option<Datagram<&mut [u8]>>, now: Instant) -> Output;
fn process_events(&mut self, now: Instant);
fn has_events(&self) -> bool;
}
Expand Down Expand Up @@ -242,7 +242,7 @@ impl ServerRunner {
timeout: &mut Option<Pin<Box<Sleep>>>,
sockets: &mut [(SocketAddr, crate::udp::Socket)],
now: &dyn Fn() -> Instant,
mut input_dgram: Option<Datagram<&[u8]>>,
mut input_dgram: Option<Datagram<&mut [u8]>>,
) -> Result<(), io::Error> {
loop {
match server.process(input_dgram.take(), now()) {
Expand Down
16 changes: 12 additions & 4 deletions neqo-common/src/datagram.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use std::{net::SocketAddr, ops::Deref};
use std::{
net::SocketAddr,
ops::{Deref, DerefMut},
};

use crate::{hex_with_len, IpTos};

Expand Down Expand Up @@ -47,7 +50,6 @@ impl<D: AsRef<[u8]>> Datagram<D> {
}
}

#[cfg(test)]
impl<D: AsMut<[u8]> + AsRef<[u8]>> AsMut<[u8]> for Datagram<D> {
fn as_mut(&mut self) -> &mut [u8] {
self.d.as_mut()
Expand All @@ -65,6 +67,12 @@ impl Datagram<Vec<u8>> {
}
}

impl<D: AsRef<[u8]> + AsMut<[u8]>> DerefMut for Datagram<D> {
fn deref_mut(&mut self) -> &mut Self::Target {
AsMut::<[u8]>::as_mut(self)
}
}

impl<D: AsRef<[u8]>> Deref for Datagram<D> {
type Target = [u8];
#[must_use]
Expand All @@ -86,9 +94,9 @@ impl<D: AsRef<[u8]>> std::fmt::Debug for Datagram<D> {
}
}

impl<'a> Datagram<&'a [u8]> {
impl<'a> Datagram<&'a mut [u8]> {
#[must_use]
pub const fn from_slice(src: SocketAddr, dst: SocketAddr, tos: IpTos, d: &'a [u8]) -> Self {
pub fn from_slice(src: SocketAddr, dst: SocketAddr, tos: IpTos, d: &'a mut [u8]) -> Self {
Self { src, dst, tos, d }
}

Expand Down
80 changes: 74 additions & 6 deletions neqo-crypto/src/aead.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,14 +123,45 @@ impl RealAead {
c_uint::try_from(output.len())?,
)
}?;
Ok(&output[0..(l.try_into()?)])
Ok(&output[..l.try_into()?])
}

/// Decrypt a ciphertext.
/// Encrypt `data` consisting of `aad` and plaintext `data` in place.
///
/// The last `Aead::expansion` of `data` is overwritten by the AEAD tag by this function.
/// Therefore, a buffer should be provided that is that much larger than the plaintext.
///
/// # Panics
///
/// If `data` is shorter than `<self as Aead>::expansion()`.
///
/// # Errors
larseggert marked this conversation as resolved.
Show resolved Hide resolved
///
/// Note that NSS insists upon having extra space available for decryption, so
/// the buffer for `output` should be the same length as `input`, even though
/// the final result will be shorter.
/// If the input can't be protected or any input is too large for NSS.
pub fn encrypt_in_place<'a>(
&self,
count: u64,
aad: &[u8],
data: &'a mut [u8],
) -> Res<&'a mut [u8]> {
let mut l: c_uint = 0;
unsafe {
SSL_AeadEncrypt(
*self.ctx,
count,
aad.as_ptr(),
c_uint::try_from(aad.len())?,
data.as_ptr(),
c_uint::try_from(data.len() - self.expansion())?,
data.as_ptr(),
&mut l,
c_uint::try_from(data.len())?,
)
}?;
Ok(&mut data[..l.try_into()?])
}

/// Decrypt a ciphertext.
///
/// # Errors
///
Expand All @@ -144,6 +175,9 @@ impl RealAead {
) -> Res<&'a [u8]> {
let mut l: c_uint = 0;
unsafe {
// Note that NSS insists upon having extra space available for decryption, so
// the buffer for `output` should be the same length as `input`, even though
// the final result will be shorter.
SSL_AeadDecrypt(
*self.ctx,
count,
Expand All @@ -156,7 +190,41 @@ impl RealAead {
c_uint::try_from(output.len())?,
)
}?;
Ok(&output[0..(l.try_into()?)])
Ok(&output[..l.try_into()?])
}

/// Decrypt a ciphertext in place.
/// Returns a subslice of `data` (without the last `<self as Aead>::expansion()` bytes),
/// that has been decrypted in place.
///
larseggert marked this conversation as resolved.
Show resolved Hide resolved
/// # Errors
///
/// If the input isn't authenticated or any input is too large for NSS.
pub fn decrypt_in_place<'a>(
&self,
count: u64,
aad: &[u8],
data: &'a mut [u8],
) -> Res<&'a mut [u8]> {
let mut l: c_uint = 0;
unsafe {
// Note that NSS insists upon having extra space available for decryption, so
// the buffer for `output` should be the same length as `input`, even though
// the final result will be shorter.
SSL_AeadDecrypt(
*self.ctx,
count,
aad.as_ptr(),
c_uint::try_from(aad.len())?,
data.as_ptr(),
c_uint::try_from(data.len())?,
data.as_ptr(),
&mut l,
c_uint::try_from(data.len())?,
)
}?;
debug_assert_eq!(usize::try_from(l)?, data.len() - self.expansion());
Ok(&mut data[..l.try_into()?])
larseggert marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down
52 changes: 42 additions & 10 deletions neqo-crypto/src/aead_null.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,36 +42,68 @@ impl AeadNull {
) -> Res<&'a [u8]> {
let l = input.len();
output[..l].copy_from_slice(input);
output[l..l + 16].copy_from_slice(AEAD_NULL_TAG);
Ok(&output[..l + 16])
output[l..l + self.expansion()].copy_from_slice(AEAD_NULL_TAG);
Ok(&output[..l + self.expansion()])
}

#[allow(clippy::missing_errors_doc)]
pub fn decrypt<'a>(
pub fn encrypt_in_place<'a>(
&self,
_count: u64,
_aad: &[u8],
input: &[u8],
output: &'a mut [u8],
) -> Res<&'a [u8]> {
if input.len() < AEAD_NULL_TAG.len() {
data: &'a mut [u8],
) -> Res<&'a mut [u8]> {
let pos = data.len() - self.expansion();
data[pos..].copy_from_slice(AEAD_NULL_TAG);
Ok(data)
}

fn decrypt_check(&self, _count: u64, _aad: &[u8], input: &[u8]) -> Res<usize> {
if input.len() < self.expansion() {
return Err(Error::from(SEC_ERROR_BAD_DATA));
}

let len_encrypted = input.len() - AEAD_NULL_TAG.len();
let len_encrypted = input
.len()
.checked_sub(self.expansion())
.ok_or_else(|| Error::from(SEC_ERROR_BAD_DATA))?;
// Check that:
// 1) expansion is all zeros and
// 2) if the encrypted data is also supplied that at least some values are no zero
// (otherwise padding will be interpreted as a valid packet)
if &input[len_encrypted..] == AEAD_NULL_TAG
&& (len_encrypted == 0 || input[..len_encrypted].iter().any(|x| *x != 0x0))
{
output[..len_encrypted].copy_from_slice(&input[..len_encrypted]);
Ok(&output[..len_encrypted])
Ok(len_encrypted)
} else {
Err(Error::from(SEC_ERROR_BAD_DATA))
}
}

#[allow(clippy::missing_errors_doc)]
pub fn decrypt<'a>(
&self,
count: u64,
aad: &[u8],
input: &[u8],
output: &'a mut [u8],
) -> Res<&'a [u8]> {
self.decrypt_check(count, aad, input).map(|len| {
output[..len].copy_from_slice(&input[..len]);
&output[..len]
})
}

#[allow(clippy::missing_errors_doc)]
pub fn decrypt_in_place<'a>(
&self,
count: u64,
aad: &[u8],
data: &'a mut [u8],
) -> Res<&'a mut [u8]> {
self.decrypt_check(count, aad, data)
.map(move |len| &mut data[..len])
}
}

impl fmt::Debug for AeadNull {
Expand Down
10 changes: 7 additions & 3 deletions neqo-http3/src/connection_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -841,7 +841,11 @@ impl Http3Client {
}

/// This function combines `process_input` and `process_output` function.
pub fn process(&mut self, dgram: Option<Datagram<impl AsRef<[u8]>>>, now: Instant) -> Output {
pub fn process(
&mut self,
dgram: Option<Datagram<impl AsRef<[u8]> + AsMut<[u8]>>>,
now: Instant,
) -> Output {
qtrace!("[{self}] Process");
if let Some(d) = dgram {
self.process_input(d, now);
Expand All @@ -859,13 +863,13 @@ impl Http3Client {
/// packets need to be sent or if a timer needs to be updated.
///
/// [1]: ../neqo_transport/enum.ConnectionEvent.html
pub fn process_input(&mut self, dgram: Datagram<impl AsRef<[u8]>>, now: Instant) {
pub fn process_input(&mut self, dgram: Datagram<impl AsRef<[u8]> + AsMut<[u8]>>, now: Instant) {
self.process_multiple_input(iter::once(dgram), now);
}

pub fn process_multiple_input(
&mut self,
dgrams: impl IntoIterator<Item = Datagram<impl AsRef<[u8]>>>,
dgrams: impl IntoIterator<Item = Datagram<impl AsRef<[u8]> + AsMut<[u8]>>>,
now: Instant,
) {
let mut dgrams = dgrams.into_iter().peekable();
Expand Down
6 changes: 5 additions & 1 deletion neqo-http3/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,11 @@ impl Http3Server {
self.process(None::<Datagram>, now)
}

pub fn process(&mut self, dgram: Option<Datagram<impl AsRef<[u8]>>>, now: Instant) -> Output {
pub fn process(
&mut self,
dgram: Option<Datagram<impl AsRef<[u8]> + AsMut<[u8]>>>,
now: Instant,
) -> Output {
qtrace!("[{self}] Process");
let out = self.server.process(dgram, now);
self.process_http3(now);
Expand Down
Loading
Loading