From d45e24e9b6e1bad5c02a97a9101572ece5baef35 Mon Sep 17 00:00:00 2001 From: Karolin Varner Date: Sat, 2 Dec 2023 10:56:18 +0100 Subject: [PATCH] feat: Move lenses into library --- Cargo.lock | 9 ++ Cargo.toml | 2 + lenses/Cargo.toml | 16 +++ lenses/readme.md | 3 + lenses/src/lib.rs | 206 +++++++++++++++++++++++++++++++++ rosenpass/Cargo.toml | 1 + rosenpass/src/lib.rs | 17 ++- rosenpass/src/msgs.rs | 231 +++----------------------------------- rosenpass/src/protocol.rs | 1 + 9 files changed, 263 insertions(+), 223 deletions(-) create mode 100644 lenses/Cargo.toml create mode 100644 lenses/readme.md create mode 100644 lenses/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 4b46a70..b4f59a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1115,6 +1115,7 @@ dependencies = [ "rosenpass-cipher-traits", "rosenpass-ciphers", "rosenpass-constant-time", + "rosenpass-lenses", "rosenpass-secret-memory", "rosenpass-sodium", "rosenpass-to", @@ -1167,6 +1168,14 @@ dependencies = [ "stacker", ] +[[package]] +name = "rosenpass-lenses" +version = "0.1.0" +dependencies = [ + "paste", + "thiserror", +] + [[package]] name = "rosenpass-oqs" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index b903cf9..3ab316f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ members = [ "to", "fuzz", "secret-memory", + "lenses", ] default-members = [ @@ -32,6 +33,7 @@ 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" diff --git a/lenses/Cargo.toml b/lenses/Cargo.toml new file mode 100644 index 0000000..9304dee --- /dev/null +++ b/lenses/Cargo.toml @@ -0,0 +1,16 @@ +[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 new file mode 100644 index 0000000..c709695 --- /dev/null +++ b/lenses/readme.md @@ -0,0 +1,3 @@ +# 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 new file mode 100644 index 0000000..f699c8a --- /dev/null +++ b/lenses/src/lib.rs @@ -0,0 +1,206 @@ +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 f8feb6f..2cad94d 100644 --- a/rosenpass/Cargo.toml +++ b/rosenpass/Cargo.toml @@ -21,6 +21,7 @@ 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 } diff --git a/rosenpass/src/lib.rs b/rosenpass/src/lib.rs index 1f37c25..729e6bb 100644 --- a/rosenpass/src/lib.rs +++ b/rosenpass/src/lib.rs @@ -1,3 +1,5 @@ +use rosenpass_lenses::LenseError; + pub mod app_server; pub mod cli; pub mod config; @@ -7,11 +9,16 @@ pub mod protocol; #[derive(thiserror::Error, Debug)] pub enum RosenpassError { - #[error("buffer size mismatch, required {required_size} but found {actual_size}")] - BufferSizeMismatch { - required_size: usize, - actual_size: usize, - }, + #[error("buffer size mismatch")] + BufferSizeMismatch, #[error("invalid message type")] 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 1aefba5..ed03214 100644 --- a/rosenpass/src/msgs.rs +++ b/rosenpass/src/msgs.rs @@ -9,14 +9,15 @@ //! //! # Example //! -//! The following example uses the [`data_lense` macro](crate::data_lense) to create a lense that +//! 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::{data_lense, RosenpassError, msgs::LenseView}; +//! use rosenpass_lenses::{lense, LenseView}; +//! use rosenpass::RosenpassError; //! # fn main() -> Result<(), RosenpassError> { //! -//! data_lense! {UdpDatagramHeader := +//! lense! {UdpDatagramHeader := //! source_port: 2, //! dest_port: 2, //! length: 2, @@ -47,217 +48,11 @@ use super::RosenpassError; use rosenpass_cipher_traits::Kem; use rosenpass_ciphers::kem::{EphemeralKem, StaticKem}; use rosenpass_ciphers::{aead, xaead, KEY_LEN}; +use rosenpass_lenses::{lense, LenseView}; // Macro magic //////////////////////////////////////////////////////////////// -/// A macro to create data lenses. Refer to the [`msgs` mod](crate::msgs) for -/// an example and further elaboration -// TODO implement TryFrom<[u8]> and From<[u8; Self::len()]> -#[macro_export] -macro_rules! data_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 = data_lense!(maybe_docstring_link $len)] - /// bytes long - pub fn $field(&self) -> &__ContainerType::Output { - &self.0[$offset .. $offset + $len] - } - - /// The bytes until the - #[doc = data_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 - $( - data_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 = data_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 - $( - data_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 = data_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` is sufficiently long to hold [Self] - pub fn check_size(len: usize) -> Result<(), RosenpassError>{ - let required_size = $( $len + )+ 0; - let actual_size = len; - if required_size != actual_size { - Err(RosenpassError::BufferSizeMismatch { - required_size, - actual_size, - }) - }else{ - Ok(()) - } - } - } - - // read-only accessor functions - impl<'a, __ContainerType $(, $( $generic: LenseView ),+ )?> $type<&'a __ContainerType $(, $( $generic ),+ )?> - where - __ContainerType: std::ops::Index> + ?Sized, - { - data_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, - { - data_lense!{token_muncher_ref @ 0 ; $( $( $attr )* ; $field : $len ),+ } - data_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 = data_lense!(maybe_docstring_link $type)] - pub trait [< $type Ext >] { - type __ContainerType; - - /// Create a lense to the byte slice - fn [< $type:snake >] $(< $($generic : LenseView),* >)? (self) -> Result< $type, RosenpassError>; - - /// Create a lense to the byte slice, automatically truncating oversized buffers - fn [< $type:snake _ truncating >] $(< $($generic : LenseView),* >)? (self) -> Result< $type, RosenpassError>; - } - - impl<'a> [< $type Ext >] for &'a [u8] { - type __ContainerType = &'a [u8]; - - fn [< $type:snake >] $(< $($generic : LenseView),* >)? (self) -> Result< $type, RosenpassError> { - $type::::check_size(self.len())?; - Ok($type ( self, $( $( ::core::marker::PhantomData::<$generic> ),+ )? )) - } - - fn [< $type:snake _ truncating >] $(< $($generic : LenseView),* >)? (self) -> Result< $type, RosenpassError> { - let required_size = $( $len + )+ 0; - let actual_size = self.len(); - if actual_size < required_size { - return Err(RosenpassError::BufferSizeMismatch { - required_size, - actual_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) -> Result< $type, RosenpassError> { - $type::::check_size(self.len())?; - Ok($type ( self, $( $( ::core::marker::PhantomData::<$generic> ),+ )? )) - } - - fn [< $type:snake _ truncating >] $(< $($generic : LenseView),* >)? (self) -> Result< $type, RosenpassError> { - let required_size = $( $len + )+ 0; - let actual_size = self.len(); - if actual_size < required_size { - return Err(RosenpassError::BufferSizeMismatch { - required_size, - actual_size, - }); - } - - [< $type Ext >]::[< $type:snake >](&mut self[..required_size]) - } - } - }); -); - -/// Common trait shared by all Lenses -pub trait LenseView { - const LEN: usize; -} - -data_lense! { Envelope := +lense! { Envelope := /// [MsgType] of this message msg_type: 1, /// Reserved for future use @@ -271,7 +66,7 @@ data_lense! { Envelope := cookie: 16 } -data_lense! { InitHello := +lense! { InitHello := /// Randomly generated connection id sidi: 4, /// Kyber 512 Ephemeral Public Key @@ -284,7 +79,7 @@ data_lense! { InitHello := auth: aead::TAG_LEN } -data_lense! { RespHello := +lense! { RespHello := /// Randomly generated connection id sidr: 4, /// Copied from InitHello @@ -299,7 +94,7 @@ data_lense! { RespHello := biscuit: BISCUIT_CT_LEN } -data_lense! { InitConf := +lense! { InitConf := /// Copied from InitHello sidi: 4, /// Copied from RespHello @@ -310,7 +105,7 @@ data_lense! { InitConf := auth: aead::TAG_LEN } -data_lense! { EmptyData := +lense! { EmptyData := /// Copied from RespHello sid: 4, /// Nonce @@ -319,7 +114,7 @@ data_lense! { EmptyData := auth: aead::TAG_LEN } -data_lense! { Biscuit := +lense! { Biscuit := /// H(spki) – Ident ifies the initiator pidi: KEY_LEN, /// The biscuit number (replay protection) @@ -328,11 +123,11 @@ data_lense! { Biscuit := ck: KEY_LEN } -data_lense! { DataMsg := +lense! { DataMsg := dummy: 4 } -data_lense! { CookieReply := +lense! { CookieReply := dummy: 4 } diff --git a/rosenpass/src/protocol.rs b/rosenpass/src/protocol.rs index c719609..c541d60 100644 --- a/rosenpass/src/protocol.rs +++ b/rosenpass/src/protocol.rs @@ -74,6 +74,7 @@ use rosenpass_cipher_traits::Kem; use rosenpass_ciphers::hash_domain::{SecretHashDomain, SecretHashDomainNamespace}; use rosenpass_ciphers::kem::{EphemeralKem, StaticKem}; use rosenpass_ciphers::{aead, xaead, KEY_LEN}; +use rosenpass_lenses::LenseView; use rosenpass_secret_memory::{Public, Secret}; use rosenpass_util::{cat, mem::cpy_min, ord::max_usize, time::Timebase}; use std::collections::hash_map::{