From a996b082796308a4ee7c2124621156a32989aa0a Mon Sep 17 00:00:00 2001 From: Aaron Kaiser Date: Tue, 6 Feb 2024 15:33:59 +0100 Subject: [PATCH] refactor: replace lenses library with the zerocopy crate --- Cargo.lock | 31 ++++-- Cargo.toml | 3 +- flake.nix | 6 +- lenses/Cargo.toml | 16 --- lenses/readme.md | 3 - lenses/src/lib.rs | 206 ------------------------------------- rosenpass/Cargo.toml | 2 +- rosenpass/src/lib.rs | 9 +- rosenpass/src/msgs.rs | 134 +++++++++++------------- rosenpass/src/protocol.rs | 210 ++++++++++++++++++++------------------ 10 files changed, 194 insertions(+), 426 deletions(-) delete mode 100644 lenses/Cargo.toml delete mode 100644 lenses/readme.md delete mode 100644 lenses/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index f53b0c2..657d11d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1131,7 +1131,6 @@ dependencies = [ "rosenpass-cipher-traits", "rosenpass-ciphers", "rosenpass-constant-time", - "rosenpass-lenses", "rosenpass-secret-memory", "rosenpass-to", "rosenpass-util", @@ -1141,6 +1140,7 @@ dependencies = [ "test_bin", "thiserror", "toml", + "zerocopy", ] [[package]] @@ -1185,14 +1185,6 @@ dependencies = [ "stacker", ] -[[package]] -name = "rosenpass-lenses" -version = "0.1.0" -dependencies = [ - "paste", - "thiserror", -] - [[package]] name = "rosenpass-oqs" version = "0.1.0" @@ -1848,6 +1840,27 @@ dependencies = [ "memchr", ] +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zeroize" version = "1.7.0" diff --git a/Cargo.toml b/Cargo.toml index 35d7386..3647d92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,6 @@ members = [ "to", "fuzz", "secret-memory", - "lenses", ] default-members = [ @@ -31,7 +30,6 @@ rosenpass-ciphers = { path = "ciphers" } rosenpass-to = { path = "to" } rosenpass-secret-memory = { path = "secret-memory" } rosenpass-oqs = { path = "oqs" } -rosenpass-lenses = { path = "lenses" } criterion = "0.4.0" test_bin = "0.4.0" libfuzzer-sys = "0.4" @@ -59,3 +57,4 @@ mio = { version = "0.8.9", features = ["net", "os-poll"] } oqs-sys = { version = "0.8", default-features = false, features = ['classic_mceliece', 'kyber'] } blake2 = "0.10.6" chacha20poly1305 = { version = "0.10.1", default-features = false, features = [ "std", "heapless" ] } +zerocopy = { version = "0.7.32", features = ["derive"] } diff --git a/flake.nix b/flake.nix index 5d0a559..f166df5 100644 --- a/flake.nix +++ b/flake.nix @@ -112,9 +112,9 @@ # suitable Rust toolchain toolchain = with inputs.fenix.packages.${system}; combine [ - stable.cargo - stable.rustc - targets.${target}.stable.rust-std + latest.cargo + latest.rustc + targets.${target}.latest.rust-std ]; # naersk with a custom toolchain diff --git a/lenses/Cargo.toml b/lenses/Cargo.toml deleted file mode 100644 index 9304dee..0000000 --- a/lenses/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "rosenpass-lenses" -version = "0.1.0" -authors = ["Karolin Varner ", "wucke13 "] -edition = "2021" -license = "MIT OR Apache-2.0" -description = "Rosenpass internal library for parsing binary data securely" -homepage = "https://rosenpass.eu/" -repository = "https://github.com/rosenpass/rosenpass" -readme = "readme.md" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -paste = { workspace = true } -thiserror = { workspace = true } diff --git a/lenses/readme.md b/lenses/readme.md deleted file mode 100644 index c709695..0000000 --- a/lenses/readme.md +++ /dev/null @@ -1,3 +0,0 @@ -# Rosenpass internal binary parsing library - -This is an internal library; no guarantee is made about its API at this point in time. diff --git a/lenses/src/lib.rs b/lenses/src/lib.rs deleted file mode 100644 index f699c8a..0000000 --- a/lenses/src/lib.rs +++ /dev/null @@ -1,206 +0,0 @@ -use std::result::Result; - -/// Common trait shared by all Lenses -pub trait LenseView { - const LEN: usize; -} - -/// Error during lense creation -#[derive(thiserror::Error, Debug, Eq, PartialEq, Clone)] -pub enum LenseError { - #[error("buffer size mismatch")] - BufferSizeMismatch, -} - -pub type LenseResult = Result; - -impl LenseError { - pub fn ensure_exact_buffer_size(len: usize, required: usize) -> LenseResult<()> { - (len == required) - .then_some(()) - .ok_or(LenseError::BufferSizeMismatch) - } - - pub fn ensure_sufficient_buffer_size(len: usize, required: usize) -> LenseResult<()> { - (len >= required) - .then_some(()) - .ok_or(LenseError::BufferSizeMismatch) - } -} - -/// A macro to create data lenses. -#[macro_export] -macro_rules! lense( - // prefix @ offset ; optional meta ; field name : field length, ... - (token_muncher_ref @ $offset:expr ; $( $attr:meta )* ; $field:ident : $len:expr $(, $( $tail:tt )+ )?) => { - ::paste::paste!{ - - #[allow(rustdoc::broken_intra_doc_links)] - $( #[ $attr ] )* - /// - #[doc = lense!(maybe_docstring_link $len)] - /// bytes long - pub fn $field(&self) -> &__ContainerType::Output { - &self.0[$offset .. $offset + $len] - } - - /// The bytes until the - #[doc = lense!(maybe_docstring_link Self::$field)] - /// field - pub fn [< until_ $field >](&self) -> &__ContainerType::Output { - &self.0[0 .. $offset] - } - - // if the tail exits, consume it as well - $( - lense!{token_muncher_ref @ $offset + $len ; $( $tail )+ } - )? - } - }; - - // prefix @ offset ; optional meta ; field name : field length, ... - (token_muncher_mut @ $offset:expr ; $( $attr:meta )* ; $field:ident : $len:expr $(, $( $tail:tt )+ )?) => { - ::paste::paste!{ - - #[allow(rustdoc::broken_intra_doc_links)] - $( #[ $attr ] )* - /// - #[doc = lense!(maybe_docstring_link $len)] - /// bytes long - pub fn [< $field _mut >](&mut self) -> &mut __ContainerType::Output { - &mut self.0[$offset .. $offset + $len] - } - - // if the tail exits, consume it as well - $( - lense!{token_muncher_mut @ $offset + $len ; $( $tail )+ } - )? - } - }; - - // switch that yields literals unchanged, but creates docstring links to - // constants - // TODO the doc string link doesn't work if $x is taken from a generic, - (maybe_docstring_link $x:literal) => (stringify!($x)); - (maybe_docstring_link $x:expr) => (stringify!([$x])); - - // struct name < optional generics > := optional doc string field name : field length, ... -($type:ident $( < $( $generic:ident ),+ > )? := $( $( #[ $attr:meta ] )* $field:ident : $len:expr ),+) => (::paste::paste!{ - - #[allow(rustdoc::broken_intra_doc_links)] - /// A data lense to manipulate byte slices. - /// - //// # Fields - /// - $( - /// - ` - #[doc = stringify!($field)] - /// `: - #[doc = lense!(maybe_docstring_link $len)] - /// bytes - )+ - pub struct $type<__ContainerType $(, $( $generic ),+ )? > ( - __ContainerType, - // The phantom data is required, since all generics declared on a - // type need to be used on the type. - // https://doc.rust-lang.org/stable/error_codes/E0392.html - $( $( ::core::marker::PhantomData<$generic> ),+ )? - ); - - impl<__ContainerType $(, $( $generic: LenseView ),+ )? > $type<__ContainerType $(, $( $generic ),+ )? >{ - $( - /// Size in bytes of the field ` - #[doc = !($field)] - /// ` - pub const fn [< $field _len >]() -> usize{ - $len - } - )+ - - /// Verify that `len` exactly holds [Self] - pub fn check_size(len: usize) -> ::rosenpass_lenses::LenseResult<()> { - ::rosenpass_lenses::LenseError::ensure_exact_buffer_size(len, $( $len + )+ 0) - } - } - - // read-only accessor functions - impl<'a, __ContainerType $(, $( $generic: LenseView ),+ )?> $type<&'a __ContainerType $(, $( $generic ),+ )?> - where - __ContainerType: std::ops::Index> + ?Sized, - { - lense!{token_muncher_ref @ 0 ; $( $( $attr )* ; $field : $len ),+ } - - /// View into all bytes belonging to this Lense - pub fn all_bytes(&self) -> &__ContainerType::Output { - &self.0[0..Self::LEN] - } - } - - // mutable accessor functions - impl<'a, __ContainerType $(, $( $generic: LenseView ),+ )?> $type<&'a mut __ContainerType $(, $( $generic ),+ )?> - where - __ContainerType: std::ops::IndexMut> + ?Sized, - { - lense!{token_muncher_ref @ 0 ; $( $( $attr )* ; $field : $len ),+ } - lense!{token_muncher_mut @ 0 ; $( $( $attr )* ; $field : $len ),+ } - - /// View into all bytes belonging to this Lense - pub fn all_bytes(&self) -> &__ContainerType::Output { - &self.0[0..Self::LEN] - } - - /// View into all bytes belonging to this Lense - pub fn all_bytes_mut(&mut self) -> &mut __ContainerType::Output { - &mut self.0[0..Self::LEN] - } - } - - // lense trait, allowing us to know the implementing lenses size - impl<__ContainerType $(, $( $generic: LenseView ),+ )? > LenseView for $type<__ContainerType $(, $( $generic ),+ )? >{ - /// Number of bytes required to store this type in binary format - const LEN: usize = $( $len + )+ 0; - } - - /// Extension trait to allow checked creation of a lense over - /// some byte slice that contains a - #[doc = lense!(maybe_docstring_link $type)] - pub trait [< $type Ext >] { - type __ContainerType; - - /// Create a lense to the byte slice - fn [< $type:snake >] $(< $($generic : LenseView),* >)? (self) -> ::rosenpass_lenses::LenseResult< $type>; - - /// Create a lense to the byte slice, automatically truncating oversized buffers - fn [< $type:snake _ truncating >] $(< $($generic : LenseView),* >)? (self) -> ::rosenpass_lenses::LenseResult< $type>; - } - - impl<'a> [< $type Ext >] for &'a [u8] { - type __ContainerType = &'a [u8]; - - fn [< $type:snake >] $(< $($generic : LenseView),* >)? (self) -> ::rosenpass_lenses::LenseResult< $type> { - $type::::check_size(self.len())?; - Ok($type ( self, $( $( ::core::marker::PhantomData::<$generic> ),+ )? )) - } - - fn [< $type:snake _ truncating >] $(< $($generic : LenseView),* >)? (self) -> ::rosenpass_lenses::LenseResult< $type> { - let required_size = $( $len + )+ 0; - ::rosenpass_lenses::LenseError::ensure_sufficient_buffer_size(self.len(), required_size)?; - [< $type Ext >]::[< $type:snake >](&self[..required_size]) - } - } - - impl<'a> [< $type Ext >] for &'a mut [u8] { - type __ContainerType = &'a mut [u8]; - fn [< $type:snake >] $(< $($generic : LenseView),* >)? (self) -> ::rosenpass_lenses::LenseResult< $type> { - $type::::check_size(self.len())?; - Ok($type ( self, $( $( ::core::marker::PhantomData::<$generic> ),+ )? )) - } - - fn [< $type:snake _ truncating >] $(< $($generic : LenseView),* >)? (self) -> ::rosenpass_lenses::LenseResult< $type> { - let required_size = $( $len + )+ 0; - ::rosenpass_lenses::LenseError::ensure_sufficient_buffer_size(self.len(), required_size)?; - [< $type Ext >]::[< $type:snake >](&mut self[..required_size]) - } - } - }); -); diff --git a/rosenpass/Cargo.toml b/rosenpass/Cargo.toml index e906433..83d1680 100644 --- a/rosenpass/Cargo.toml +++ b/rosenpass/Cargo.toml @@ -20,7 +20,6 @@ rosenpass-ciphers = { workspace = true } rosenpass-cipher-traits = { workspace = true } rosenpass-to = { workspace = true } rosenpass-secret-memory = { workspace = true } -rosenpass-lenses = { workspace = true } anyhow = { workspace = true } static_assertions = { workspace = true } memoffset = { workspace = true } @@ -33,6 +32,7 @@ toml = { workspace = true } clap = { workspace = true } mio = { workspace = true } rand = { workspace = true } +zerocopy = { workspace = true } [build-dependencies] anyhow = { workspace = true } diff --git a/rosenpass/src/lib.rs b/rosenpass/src/lib.rs index 729e6bb..6791cd8 100644 --- a/rosenpass/src/lib.rs +++ b/rosenpass/src/lib.rs @@ -1,4 +1,4 @@ -use rosenpass_lenses::LenseError; +#![feature(offset_of)] pub mod app_server; pub mod cli; @@ -15,10 +15,3 @@ pub enum RosenpassError { InvalidMessageType(u8), } -impl From for RosenpassError { - fn from(value: LenseError) -> Self { - match value { - LenseError::BufferSizeMismatch => RosenpassError::BufferSizeMismatch, - } - } -} diff --git a/rosenpass/src/msgs.rs b/rosenpass/src/msgs.rs index ed03214..30721ee 100644 --- a/rosenpass/src/msgs.rs +++ b/rosenpass/src/msgs.rs @@ -6,129 +6,111 @@ //! always serialized instance of the data in question. This is closely related //! to the concept of lenses in function programming; more on that here: //! [https://sinusoid.es/misc/lager/lenses.pdf](https://sinusoid.es/misc/lager/lenses.pdf) -//! -//! # Example -//! -//! The following example uses the [`lense` macro](rosenpass_lenses::lense) to create a lense that -//! might be useful when dealing with UDP headers. -//! -//! ``` -//! use rosenpass_lenses::{lense, LenseView}; -//! use rosenpass::RosenpassError; -//! # fn main() -> Result<(), RosenpassError> { -//! -//! lense! {UdpDatagramHeader := -//! source_port: 2, -//! dest_port: 2, -//! length: 2, -//! checksum: 2 -//! } -//! -//! let mut buf = [0u8; 8]; -//! -//! // read-only lense, no check of size: -//! let lense = UdpDatagramHeader(&buf); -//! assert_eq!(lense.checksum(), &[0, 0]); -//! -//! // mutable lense, runtime check of size -//! let mut lense = buf.as_mut().udp_datagram_header()?; -//! lense.source_port_mut().copy_from_slice(&53u16.to_be_bytes()); // some DNS, anyone? -//! -//! // the original buffer is still available -//! assert_eq!(buf, [0, 53, 0, 0, 0, 0, 0, 0]); -//! -//! // read-only lense, runtime check of size -//! let lense = buf.as_ref().udp_datagram_header()?; -//! assert_eq!(lense.source_port(), &[0, 53]); -//! # Ok(()) -//! # } -//! ``` +//! To achieve this we utilize the zerocopy library. use super::RosenpassError; +use std::mem::size_of; use rosenpass_cipher_traits::Kem; use rosenpass_ciphers::kem::{EphemeralKem, StaticKem}; use rosenpass_ciphers::{aead, xaead, KEY_LEN}; -use rosenpass_lenses::{lense, LenseView}; +use zerocopy::{AsBytes, FromBytes, FromZeroes}; // Macro magic //////////////////////////////////////////////////////////////// -lense! { Envelope := + +#[repr(packed)] +#[derive(AsBytes, FromBytes, FromZeroes)] +pub struct Envelope { /// [MsgType] of this message - msg_type: 1, + pub msg_type: u8, /// Reserved for future use - reserved: 3, + pub reserved: [u8; 3], /// The actual Paylod - payload: M::LEN, + pub payload: M, /// Message Authentication Code (mac) over all bytes until (exclusive) /// `mac` itself - mac: 16, - /// Currently unused, TODO: do something with this - cookie: 16 + pub mac: [u8; 16], + /// Currently unused, TODO: do something with this + pub cookie: [u8; 16] } -lense! { InitHello := +#[repr(packed)] +#[derive(AsBytes, FromBytes, FromZeroes)] +pub struct InitHello { /// Randomly generated connection id - sidi: 4, + pub sidi: [u8; 4], /// Kyber 512 Ephemeral Public Key - epki: EphemeralKem::PK_LEN, + pub epki: [u8; EphemeralKem::PK_LEN], /// Classic McEliece Ciphertext - sctr: StaticKem::CT_LEN, + pub sctr: [u8; StaticKem::CT_LEN], /// Encryped: 16 byte hash of McEliece initiator static key - pidic: aead::TAG_LEN + 32, + pub pidic: [u8; aead::TAG_LEN + 32], /// Encrypted TAI64N Time Stamp (against replay attacks) - auth: aead::TAG_LEN + pub auth: [u8; aead::TAG_LEN], } -lense! { RespHello := +#[repr(packed)] +#[derive(AsBytes, FromBytes, FromZeroes)] +pub struct RespHello { /// Randomly generated connection id - sidr: 4, + pub sidr: [u8; 4], /// Copied from InitHello - sidi: 4, + pub sidi: [u8; 4], /// Kyber 512 Ephemeral Ciphertext - ecti: EphemeralKem::CT_LEN, + pub ecti: [u8; EphemeralKem::CT_LEN], /// Classic McEliece Ciphertext - scti: StaticKem::CT_LEN, + pub scti: [u8; StaticKem::CT_LEN], /// Empty encrypted message (just an auth tag) - auth: aead::TAG_LEN, + pub auth: [u8; aead::TAG_LEN], /// Responders handshake state in encrypted form - biscuit: BISCUIT_CT_LEN + pub biscuit: [u8; BISCUIT_CT_LEN], } -lense! { InitConf := +#[repr(packed)] +#[derive(AsBytes, FromBytes, FromZeroes)] +pub struct InitConf { /// Copied from InitHello - sidi: 4, + pub sidi: [u8; 4], /// Copied from RespHello - sidr: 4, + pub sidr: [u8; 4], /// Responders handshake state in encrypted form - biscuit: BISCUIT_CT_LEN, + pub biscuit: [u8; BISCUIT_CT_LEN], /// Empty encrypted message (just an auth tag) - auth: aead::TAG_LEN + pub auth: [u8; aead::TAG_LEN] } -lense! { EmptyData := +#[repr(packed)] +#[derive(AsBytes, FromBytes, FromZeroes)] +pub struct EmptyData { /// Copied from RespHello - sid: 4, + pub sid: [u8; 4], /// Nonce - ctr: 8, + pub ctr: [u8; 8], /// Empty encrypted message (just an auth tag) - auth: aead::TAG_LEN + pub auth: [u8; aead::TAG_LEN] } -lense! { Biscuit := +#[repr(packed)] +#[derive(AsBytes, FromBytes, FromZeroes)] +pub struct Biscuit { /// H(spki) – Ident ifies the initiator - pidi: KEY_LEN, + pub pidi: [u8; KEY_LEN], /// The biscuit number (replay protection) - biscuit_no: 12, + pub biscuit_no: [u8; 12], /// Chaining key - ck: KEY_LEN + pub ck: [u8; KEY_LEN] } -lense! { DataMsg := - dummy: 4 +#[repr(packed)] +#[derive(AsBytes, FromBytes, FromZeroes)] +pub struct DataMsg { + pub dummy: [u8; 4] } -lense! { CookieReply := - dummy: 4 +#[repr(packed)] +#[derive(AsBytes, FromBytes, FromZeroes)] +pub struct CookieReply { + pub dummy: [u8; 4] } // Traits ///////////////////////////////////////////////////////////////////// @@ -178,7 +160,7 @@ impl TryFrom for MsgType { } /// length in bytes of an unencrypted Biscuit (plain text) -pub const BISCUIT_PT_LEN: usize = Biscuit::<()>::LEN; +pub const BISCUIT_PT_LEN: usize = size_of::(); /// Length in bytes of an encrypted Biscuit (cipher text) pub const BISCUIT_CT_LEN: usize = BISCUIT_PT_LEN + xaead::NONCE_LEN + xaead::TAG_LEN; diff --git a/rosenpass/src/protocol.rs b/rosenpass/src/protocol.rs index 63af5fe..1562816 100644 --- a/rosenpass/src/protocol.rs +++ b/rosenpass/src/protocol.rs @@ -70,6 +70,7 @@ use std::collections::hash_map::{ HashMap, }; use std::convert::Infallible; +use std::mem::{size_of,offset_of}; use anyhow::{bail, ensure, Context, Result}; @@ -78,18 +79,18 @@ use rosenpass_ciphers::hash_domain::{SecretHashDomain, SecretHashDomainNamespace use rosenpass_ciphers::kem::{EphemeralKem, StaticKem}; use rosenpass_ciphers::{aead, xaead, KEY_LEN}; use rosenpass_constant_time as constant_time; -use rosenpass_lenses::LenseView; use rosenpass_secret_memory::{Public, Secret}; use rosenpass_util::{cat, mem::cpy_min, ord::max_usize, time::Timebase}; +use zerocopy::{AsBytes, FromBytes, Ref}; -use crate::{hash_domains, msgs::*}; +use crate::{hash_domains, msgs::*, RosenpassError}; // CONSTANTS & SETTINGS ////////////////////////// /// Size required to fit any message in binary form pub const RTX_BUFFER_SIZE: usize = max_usize( - > as LenseView>::LEN, - > as LenseView>::LEN, + size_of::>(), + size_of::>() ); /// A type for time, e.g. for backoff before re-tries @@ -739,11 +740,12 @@ impl CryptoServer { // TODO remove unnecessary copying between global tx_buf and per-peer buf // TODO move retransmission storage to io server pub fn initiate_handshake(&mut self, peer: PeerPtr, tx_buf: &mut [u8]) -> Result { - let mut msg = tx_buf.envelope_truncating::>()?; // Envelope::::default(); // TODO - self.handle_initiation(peer, msg.payload_mut().init_hello()?)?; - let len = self.seal_and_commit_msg(peer, MsgType::InitHello, msg)?; + // Envelope::::default(); // TODO + let mut msg = truncating_cast_into::>(tx_buf)?; + self.handle_initiation(peer, &mut msg.payload)?; + let len = self.seal_and_commit_msg(peer, MsgType::InitHello, &mut msg)?; peer.hs() - .store_msg_for_retransmission(self, &tx_buf[..len])?; + .store_msg_for_retransmission(self, &msg.as_bytes()[..len])?; Ok(len) } } @@ -793,50 +795,50 @@ impl CryptoServer { let peer = match rx_buf[0].try_into() { Ok(MsgType::InitHello) => { - let msg_in = rx_buf.envelope::>()?; + let msg_in: Ref<&[u8], Envelope> = Ref::new(rx_buf).ok_or(RosenpassError::BufferSizeMismatch)?; ensure!(msg_in.check_seal(self)?, seal_broken); - let mut msg_out = tx_buf.envelope_truncating::>()?; + let mut msg_out = truncating_cast_into::>(tx_buf)?; let peer = self.handle_init_hello( - msg_in.payload().init_hello()?, - msg_out.payload_mut().resp_hello()?, + &msg_in.payload, + &mut msg_out.payload, )?; - len = self.seal_and_commit_msg(peer, MsgType::RespHello, msg_out)?; + len = self.seal_and_commit_msg(peer, MsgType::RespHello, &mut msg_out)?; peer } Ok(MsgType::RespHello) => { - let msg_in = rx_buf.envelope::>()?; + let msg_in: Ref<&[u8], Envelope> = Ref::new(rx_buf).ok_or(RosenpassError::BufferSizeMismatch)?; ensure!(msg_in.check_seal(self)?, seal_broken); - let mut msg_out = tx_buf.envelope_truncating::>()?; + let mut msg_out = truncating_cast_into::>(tx_buf)?; let peer = self.handle_resp_hello( - msg_in.payload().resp_hello()?, - msg_out.payload_mut().init_conf()?, + &msg_in.payload, + &mut msg_out.payload, )?; - len = self.seal_and_commit_msg(peer, MsgType::InitConf, msg_out)?; + len = self.seal_and_commit_msg(peer, MsgType::InitConf, &mut msg_out)?; peer.hs() - .store_msg_for_retransmission(self, &tx_buf[..len])?; + .store_msg_for_retransmission(self, &msg_out.as_bytes()[..len])?; exchanged = true; peer } Ok(MsgType::InitConf) => { - let msg_in = rx_buf.envelope::>()?; + let msg_in: Ref<&[u8], Envelope> = Ref::new(rx_buf).ok_or(RosenpassError::BufferSizeMismatch)?; ensure!(msg_in.check_seal(self)?, seal_broken); - let mut msg_out = tx_buf.envelope_truncating::>()?; + let mut msg_out = truncating_cast_into::>(tx_buf)?; let peer = self.handle_init_conf( - msg_in.payload().init_conf()?, - msg_out.payload_mut().empty_data()?, + &msg_in.payload, + &mut msg_out.payload, )?; - len = self.seal_and_commit_msg(peer, MsgType::EmptyData, msg_out)?; + len = self.seal_and_commit_msg(peer, MsgType::EmptyData, &mut msg_out)?; exchanged = true; peer } Ok(MsgType::EmptyData) => { - let msg_in = rx_buf.envelope::>()?; + let msg_in: Ref<&[u8], Envelope> = Ref::new(rx_buf).ok_or(RosenpassError::BufferSizeMismatch)?; ensure!(msg_in.check_seal(self)?, seal_broken); - self.handle_resp_conf(msg_in.payload().empty_data()?)? + self.handle_resp_conf(&msg_in.payload)? } Ok(MsgType::DataMsg) => bail!("DataMsg handling not implemented!"), Ok(MsgType::CookieReply) => bail!("CookieReply handling not implemented!"), @@ -856,15 +858,15 @@ impl CryptoServer { /// /// The message type is explicitly required here because it is very easy to /// forget setting that, which creates subtle but far ranging errors. - pub fn seal_and_commit_msg( + pub fn seal_and_commit_msg( &mut self, peer: PeerPtr, msg_type: MsgType, - mut msg: Envelope<&mut [u8], M>, + msg: &mut Ref<&mut [u8], Envelope>, ) -> Result { - msg.msg_type_mut()[0] = msg_type as u8; + msg.msg_type = msg_type as u8; msg.seal(peer, self)?; - Ok( as LenseView>::LEN) + Ok(size_of::>()) } } @@ -1170,32 +1172,32 @@ impl IniHsPtr { // CRYPTO/HANDSHAKE HANDLING ///////////////////// -impl Envelope<&mut [u8], M> +impl Envelope where - M: LenseView, + M: AsBytes + FromBytes, { /// Calculate the message authentication code (`mac`) pub fn seal(&mut self, peer: PeerPtr, srv: &CryptoServer) -> Result<()> { let mac = hash_domains::mac()? .mix(peer.get(srv).spkt.secret())? - .mix(self.until_mac())?; - self.mac_mut() + .mix(&self.as_bytes()[..offset_of!(Self, mac)])?; + self.mac .copy_from_slice(mac.into_value()[..16].as_ref()); Ok(()) } } -impl Envelope<&[u8], M> +impl Envelope where - M: LenseView, + M: AsBytes + FromBytes, { /// Check the message authentication code pub fn check_seal(&self, srv: &CryptoServer) -> Result { let expected = hash_domains::mac()? .mix(srv.spkm.secret())? - .mix(self.until_mac())?; + .mix(&self.as_bytes()[..offset_of!(Self, mac)])?; Ok(constant_time::memcmp( - self.mac(), + &self.mac, &expected.into_value()[..16], )) } @@ -1282,15 +1284,15 @@ impl HandshakeState { biscuit_ct: &mut [u8], ) -> Result<&mut Self> { let mut biscuit = Secret::::zero(); // pt buffer - let mut biscuit = (&mut biscuit.secret_mut()[..]).biscuit()?; // lens view + let mut biscuit: Ref<&mut [u8], Biscuit> = Ref::new(biscuit.secret_mut().as_mut_slice()).unwrap(); // calculate pt contents biscuit - .pidi_mut() + .pidi .copy_from_slice(peer.get(srv).pidt()?.as_slice()); - biscuit.biscuit_no_mut().copy_from_slice(&*srv.biscuit_ctr); + biscuit.biscuit_no.copy_from_slice(&*srv.biscuit_ctr); biscuit - .ck_mut() + .ck .copy_from_slice(self.ck.clone().danger_into_secret().secret()); // calculate ad contents @@ -1311,7 +1313,7 @@ impl HandshakeState { n[0] |= (bk.0 as u8 & 0x1) << 7; let k = bk.get(srv).key.secret(); - let pt = biscuit.all_bytes(); + let pt = biscuit.as_bytes(); xaead::encrypt(biscuit_ct, k, &*n, &ad, pt)?; self.mix(biscuit_ct) @@ -1337,18 +1339,18 @@ impl HandshakeState { // Allocate and decrypt the biscuit data let mut biscuit = Secret::::zero(); // pt buf - let mut biscuit = (&mut biscuit.secret_mut()[..]).biscuit()?; // slice + let mut biscuit: Ref<&mut [u8], Biscuit> = Ref::new(biscuit.secret_mut().as_mut_slice()).unwrap(); xaead::decrypt( - biscuit.all_bytes_mut(), + biscuit.as_bytes_mut(), bk.get(srv).key.secret(), &ad, biscuit_ct, )?; // Reconstruct the biscuit fields - let no = BiscuitId::from_slice(biscuit.biscuit_no()); - let ck = SecretHashDomain::danger_from_secret(Secret::from_slice(biscuit.ck())).dup(); - let pid = PeerId::from_slice(biscuit.pidi()); + let no = BiscuitId::from_slice(&biscuit.biscuit_no); + let ck = SecretHashDomain::danger_from_secret(Secret::from_slice(&biscuit.ck)).dup(); + let pid = PeerId::from_slice(&biscuit.pidi); // Reconstruct the handshake state let mut hs = Self { sidi, sidr, ck }; @@ -1364,7 +1366,7 @@ impl HandshakeState { // indicates retransmission // TODO: Handle retransmissions without involving the crypto code ensure!( - constant_time::compare(biscuit.biscuit_no(), &*peer.get(srv).biscuit_used) >= 0, + constant_time::compare(&biscuit.biscuit_no, &*peer.get(srv).biscuit_used) >= 0, "Rejecting biscuit: Outdated biscuit number" ); @@ -1415,7 +1417,7 @@ impl CryptoServer { pub fn handle_initiation( &mut self, peer: PeerPtr, - mut ih: InitHello<&mut [u8]>, + ih: &mut InitHello, ) -> Result { let mut hs = InitiatorHandshake::zero_with_timestamp(self); @@ -1424,25 +1426,25 @@ impl CryptoServer { // IHI2 hs.core.sidi.randomize(); - ih.sidi_mut().copy_from_slice(&hs.core.sidi.value); + ih.sidi.copy_from_slice(&hs.core.sidi.value); // IHI3 EphemeralKem::keygen(hs.eski.secret_mut(), &mut *hs.epki)?; - ih.epki_mut().copy_from_slice(&hs.epki.value); + ih.epki.copy_from_slice(&hs.epki.value); // IHI4 - hs.core.mix(ih.sidi())?.mix(ih.epki())?; + hs.core.mix(ih.sidi.as_slice())?.mix(ih.epki.as_slice())?; // IHI5 hs.core .encaps_and_mix::( - ih.sctr_mut(), + ih.sctr.as_mut_slice(), peer.get(self).spkt.secret(), )?; // IHI6 hs.core - .encrypt_and_mix(ih.pidic_mut(), self.pidm()?.as_ref())?; + .encrypt_and_mix(ih.pidic.as_mut_slice(), self.pidm()?.as_ref())?; // IHI7 hs.core @@ -1450,7 +1452,7 @@ impl CryptoServer { .mix(peer.get(self).psk.secret())?; // IHI8 - hs.core.encrypt_and_mix(ih.auth_mut(), &[])?; + hs.core.encrypt_and_mix(ih.auth.as_mut_slice(), &[])?; // Update the handshake hash last (not changing any state on prior error peer.hs().insert(self, hs)?; @@ -1460,30 +1462,30 @@ impl CryptoServer { pub fn handle_init_hello( &mut self, - ih: InitHello<&[u8]>, - mut rh: RespHello<&mut [u8]>, + ih: &InitHello, + rh: &mut RespHello, ) -> Result { let mut core = HandshakeState::zero(); - core.sidi = SessionId::from_slice(ih.sidi()); + core.sidi = SessionId::from_slice(&ih.sidi); // IHR1 core.init(self.spkm.secret())?; // IHR4 - core.mix(ih.sidi())?.mix(ih.epki())?; + core.mix(&ih.sidi)?.mix(&ih.epki)?; // IHR5 core.decaps_and_mix::( self.sskm.secret(), self.spkm.secret(), - ih.sctr(), + &ih.sctr, )?; // IHR6 let peer = { let mut peerid = PeerId::zero(); - core.decrypt_and_mix(&mut *peerid, ih.pidic())?; + core.decrypt_and_mix(&mut *peerid, &ih.pidic)?; self.find_peer(peerid) .with_context(|| format!("No such peer {peerid:?}."))? }; @@ -1493,46 +1495,46 @@ impl CryptoServer { .mix(peer.get(self).psk.secret())?; // IHR8 - core.decrypt_and_mix(&mut [0u8; 0], ih.auth())?; + core.decrypt_and_mix(&mut [0u8; 0], &ih.auth)?; // RHR1 core.sidr.randomize(); - rh.sidi_mut().copy_from_slice(core.sidi.as_ref()); - rh.sidr_mut().copy_from_slice(core.sidr.as_ref()); + rh.sidi.copy_from_slice(core.sidi.as_ref()); + rh.sidr.copy_from_slice(core.sidr.as_ref()); // RHR3 - core.mix(rh.sidr())?.mix(rh.sidi())?; + core.mix(&rh.sidr)?.mix(&rh.sidi)?; // RHR4 - core.encaps_and_mix::(rh.ecti_mut(), ih.epki())?; + core.encaps_and_mix::(&mut rh.ecti, &ih.epki)?; // RHR5 core.encaps_and_mix::( - rh.scti_mut(), + &mut rh.scti, peer.get(self).spkt.secret(), )?; // RHR6 - core.store_biscuit(self, peer, rh.biscuit_mut())?; + core.store_biscuit(self, peer, &mut rh.biscuit)?; // RHR7 - core.encrypt_and_mix(rh.auth_mut(), &[])?; + core.encrypt_and_mix(&mut rh.auth, &[])?; Ok(peer) } pub fn handle_resp_hello( &mut self, - rh: RespHello<&[u8]>, - mut ic: InitConf<&mut [u8]>, + rh: &RespHello, + ic: &mut InitConf, ) -> Result { // RHI2 let peer = self - .lookup_handshake(SessionId::from_slice(rh.sidi())) + .lookup_handshake(SessionId::from_slice(&rh.sidi)) .with_context(|| { format!( "Got RespHello packet for non-existent session {:?}", - rh.sidi() + rh.sidi ) })? .peer(); @@ -1557,52 +1559,52 @@ impl CryptoServer { ensure!( exp == got, "Unexpected package in session {:?}. Expected {:?}, got {:?}.", - SessionId::from_slice(rh.sidi()), + SessionId::from_slice(&rh.sidi), exp, got ); let mut core = hs!().core.clone(); - core.sidr.copy_from_slice(rh.sidr()); + core.sidr.copy_from_slice(&rh.sidr); // TODO: decaps_and_mix should take Secret<> directly // to save us from the repetitive secret unwrapping // RHI3 - core.mix(rh.sidr())?.mix(rh.sidi())?; + core.mix(&rh.sidr)?.mix(&rh.sidi)?; // RHI4 core.decaps_and_mix::( hs!().eski.secret(), &*hs!().epki, - rh.ecti(), + &rh.ecti, )?; // RHI5 core.decaps_and_mix::( self.sskm.secret(), self.spkm.secret(), - rh.scti(), + &rh.scti, )?; // RHI6 - core.mix(rh.biscuit())?; + core.mix(&rh.biscuit)?; // RHI7 - core.decrypt_and_mix(&mut [0u8; 0], rh.auth())?; + core.decrypt_and_mix(&mut [0u8; 0], &rh.auth)?; // TODO: We should just authenticate the entire network package up to the auth // tag as a pattern instead of mixing in fields separately - ic.sidi_mut().copy_from_slice(rh.sidi()); - ic.sidr_mut().copy_from_slice(rh.sidr()); + ic.sidi.copy_from_slice(&rh.sidi); + ic.sidr.copy_from_slice(&rh.sidr); // ICI3 - core.mix(ic.sidi())?.mix(ic.sidr())?; - ic.biscuit_mut().copy_from_slice(rh.biscuit()); + core.mix(&ic.sidi)?.mix(&ic.sidr)?; + ic.biscuit.copy_from_slice(&rh.biscuit); // ICI4 - core.encrypt_and_mix(ic.auth_mut(), &[])?; + core.encrypt_and_mix(&mut ic.auth, &[])?; // Split() – We move the secrets into the session; we do not // delete the InitiatorHandshake, just clear it's secrets because @@ -1619,26 +1621,26 @@ impl CryptoServer { pub fn handle_init_conf( &mut self, - ic: InitConf<&[u8]>, - mut rc: EmptyData<&mut [u8]>, + ic: &InitConf, + rc: &mut EmptyData, ) -> Result { // (peer, bn) ← LoadBiscuit(InitConf.biscuit) // ICR1 let (peer, biscuit_no, mut core) = HandshakeState::load_biscuit( self, - ic.biscuit(), - SessionId::from_slice(ic.sidi()), - SessionId::from_slice(ic.sidr()), + &ic.biscuit, + SessionId::from_slice(&ic.sidi), + SessionId::from_slice(&ic.sidr), )?; // ICR2 core.encrypt_and_mix(&mut [0u8; aead::TAG_LEN], &[])?; // ICR3 - core.mix(ic.sidi())?.mix(ic.sidr())?; + core.mix(&ic.sidi)?.mix(&ic.sidr)?; // ICR4 - core.decrypt_and_mix(&mut [0u8; 0], ic.auth())?; + core.decrypt_and_mix(&mut [0u8; 0], &ic.auth)?; // ICR5 if constant_time::compare(&*biscuit_no, &*peer.get(self).biscuit_used) > 0 { @@ -1682,19 +1684,19 @@ impl CryptoServer { .get_mut(self) .as_mut() .context("Cannot send acknowledgement. No session.")?; - rc.sid_mut().copy_from_slice(&ses.sidt.value); - rc.ctr_mut().copy_from_slice(&ses.txnm.to_le_bytes()); + rc.sid.copy_from_slice(&ses.sidt.value); + rc.ctr.copy_from_slice(&ses.txnm.to_le_bytes()); ses.txnm += 1; // Increment nonce before encryption, just in case an error is raised - let n = cat!(aead::NONCE_LEN; rc.ctr(), &[0u8; 4]); + let n = cat!(aead::NONCE_LEN; &rc.ctr, &[0u8; 4]); let k = ses.txkm.secret(); - aead::encrypt(rc.auth_mut(), k, &n, &[], &[])?; // ct, k, n, ad, pt + aead::encrypt(&mut rc.auth, k, &n, &[], &[])?; // ct, k, n, ad, pt Ok(peer) } - pub fn handle_resp_conf(&mut self, rc: EmptyData<&[u8]>) -> Result { - let sid = SessionId::from_slice(rc.sid()); + pub fn handle_resp_conf(&mut self, rc: &EmptyData) -> Result { + let sid = SessionId::from_slice(&rc.sid); let hs = self .lookup_handshake(sid) .with_context(|| format!("Got RespConf packet for non-existent session {sid:?}"))?; @@ -1717,16 +1719,16 @@ impl CryptoServer { })?; // the unwrap can not fail, because the slice returned by ctr() is // guaranteed to have the correct size - let n = u64::from_le_bytes(rc.ctr().try_into().unwrap()); + let n = u64::from_le_bytes(rc.ctr.try_into().unwrap()); ensure!(n >= s.txnt, "Stale nonce"); s.txnt = n; aead::decrypt( // pt, k, n, ad, ct &mut [0u8; 0], s.txkt.secret(), - &cat!(aead::NONCE_LEN; rc.ctr(), &[0u8; 4]), + &cat!(aead::NONCE_LEN; &rc.ctr, &[0u8; 4]), &[], - rc.auth(), + &rc.auth, )?; } @@ -1737,6 +1739,10 @@ impl CryptoServer { } } +fn truncating_cast_into(buf: &mut [u8]) -> Result, RosenpassError> { + Ok(Ref::new(&mut buf[..size_of::()]).ok_or(RosenpassError::BufferSizeMismatch)?) +} + #[cfg(test)] mod test { use super::*;