diff --git a/.github/workflows/qc.yaml b/.github/workflows/qc.yaml index 173c0d5..eda617a 100644 --- a/.github/workflows/qc.yaml +++ b/.github/workflows/qc.yaml @@ -25,6 +25,14 @@ jobs: - name: Run ShellCheck uses: ludeeus/action-shellcheck@master + rustfmt: + name: Rust Format + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Run Rust Formatting Script + run: bash format_rust_code.sh --mode check + cargo-audit: runs-on: ubuntu-latest steps: diff --git a/format_rust_code.sh b/format_rust_code.sh new file mode 100755 index 0000000..b5cc10f --- /dev/null +++ b/format_rust_code.sh @@ -0,0 +1,115 @@ +#!/usr/bin/env bash + +# Parse command line options +while [[ $# -gt 0 ]]; do + case "$1" in + --mode) + mode="$2" + shift 2 + ;; + *) + echo "Unknown option: $1" + exit 1 + ;; + esac +done + +# Check if mode is specified +if [ -z "$mode" ]; then + echo "Please specify the mode using --mode option. Valid modes are 'check' and 'fix'." + exit 1 +fi + +# Find all Markdown files in the current directory and its subdirectories +mapfile -t md_files < <(find . -type f -name "*.md") + +count=0 +# Iterate through each Markdown file +for file in "${md_files[@]}"; do + # Use awk to extract Rust code blocks enclosed within triple backticks + rust_code_blocks=$(awk '/```rust/{flag=1; next}/```/{flag=0} flag' "$file") + + # Count the number of Rust code blocks + num_fences=$(awk '/```rust/{f=1} f{if(/```/){f=0; count++}} END{print count}' "$file") + + if [ -n "$rust_code_blocks" ]; then + echo "Processing Rust code in $file" + # Iterate through each Rust code block + for ((i=1; i <= num_fences ; i++)); do + # Extract individual Rust code block using awk + current_rust_block=$(awk -v i="$i" '/```rust/{f=1; if (++count == i) next} f&&/```/{f=0;next} f' "$file") + # Variable to check if we have added the main function + add_main=0 + # Check if the Rust code block is already inside a function + if ! echo "$current_rust_block" | grep -q "fn main()"; then + # If not, wrap it in a main function + current_rust_block=$'fn main() {\n'"$current_rust_block"$'\n}' + add_main=1 + fi + if [ "$mode" == "check" ]; then + # Apply changes to the Rust code block + formatted_rust_code=$(echo "$current_rust_block" | rustfmt) + # Use rustfmt to format the Rust code block, remove first and last lines, and remove the first 4 spaces if added main function + if [ "$add_main" == 1 ]; then + formatted_rust_code=$(echo "$formatted_rust_code" | sed '1d;$d' | sed 's/^ //') + current_rust_block=$(echo "$current_rust_block" | sed '1d;') + current_rust_block=$(echo "$current_rust_block" | sed '$d') + fi + if [ "$formatted_rust_code" == "$current_rust_block" ]; then + echo "No changes needed in Rust code block $i in $file" + else + echo -e "\nChanges needed in Rust code block $i in $file:\n" + echo "$formatted_rust_code" + count=+1 + fi + + elif [ "$mode" == "fix" ]; then + # Replace current_rust_block with formatted_rust_code in the file + formatted_rust_code=$(echo "$current_rust_block" | rustfmt) + # Use rustfmt to format the Rust code block, remove first and last lines, and remove the first 4 spaces if added main function + if [ "$add_main" == 1 ]; then + formatted_rust_code=$(echo "$formatted_rust_code" | sed '1d;$d' | sed 's/^ //') + current_rust_block=$(echo "$current_rust_block" | sed '1d;') + current_rust_block=$(echo "$current_rust_block" | sed '$d') + fi + # Check if the formatted code is the same as the current Rust code block + if [ "$formatted_rust_code" == "$current_rust_block" ]; then + echo "No changes needed in Rust code block $i in $file" + else + echo "Formatting Rust code block $i in $file" + # Replace current_rust_block with formatted_rust_code in the file + # Use awk to find the line number of the pattern + + start_line=$(grep -n "^\`\`\`rust" "$file" | sed -n "${i}p" | cut -d: -f1) + end_line=$(grep -n "^\`\`\`" "$file" | awk -F: -v start_line="$start_line" '$1 > start_line {print $1; exit;}') + + if [ -n "$start_line" ] && [ -n "$end_line" ]; then + # Print lines before the Rust code block + head -n "$((start_line - 1))" "$file" + + # Print the formatted Rust code block + echo "\`\`\`rust" + echo "$formatted_rust_code" + echo "\`\`\`" + + # Print lines after the Rust code block + tail -n +"$((end_line + 1))" "$file" + else + # Rust code block not found or end line not found + cat "$file" + fi > tmpfile && mv tmpfile "$file" + + fi + else + echo "Unknown mode: $mode. Valid modes are 'check' and 'fix'." + exit 1 + fi + done + fi +done + +# CI failure if changes are needed +if [ $count -gt 0 ]; then + echo "CI failed: Changes needed in Rust code blocks." + exit 1 +fi diff --git a/rosenpass/src/cli.rs b/rosenpass/src/cli.rs index dc6b596..86cee92 100644 --- a/rosenpass/src/cli.rs +++ b/rosenpass/src/cli.rs @@ -87,6 +87,15 @@ pub enum Cli { force: bool, }, + /// Deprecated - use gen-keys instead + #[allow(rustdoc::broken_intra_doc_links)] + #[allow(rustdoc::invalid_html_tags)] + Keygen { + // NOTE yes, the legacy keygen argument initially really accepted "privet-key", not "secret-key"! + /// public-key private-key + args: Vec, + }, + /// Validate a configuration Validate { config_files: Vec }, @@ -119,6 +128,40 @@ impl Cli { config::Rosenpass::example_config().store(config_file)?; } + // Deprecated - use gen-keys instead + Keygen { args } => { + log::warn!("The 'keygen' command is deprecated. Please use the 'gen-keys' command instead."); + + let mut public_key: Option = None; + let mut secret_key: Option = None; + + // Manual arg parsing, since clap wants to prefix flags with "--" + let mut args = args.into_iter(); + loop { + match (args.next().as_ref().map(String::as_str), args.next()) { + (Some("private-key"), Some(opt)) | (Some("secret-key"), Some(opt)) => { + secret_key = Some(opt.into()); + } + (Some("public-key"), Some(opt)) => { + public_key = Some(opt.into()); + } + (Some(flag), _) => { + bail!("Unknown option `{}`", flag); + } + (_, _) => break, + }; + } + + if secret_key.is_none() { + bail!("private-key is required"); + } + if public_key.is_none() { + bail!("public-key is required"); + } + + generate_and_save_keypair(secret_key.unwrap(), public_key.unwrap())?; + } + GenKeys { config_file, public_key, @@ -160,12 +203,7 @@ impl Cli { } // generate the keys and store them in files - let mut ssk = crate::protocol::SSk::random(); - let mut spk = crate::protocol::SPk::random(); - StaticKem::keygen(ssk.secret_mut(), spk.secret_mut())?; - - ssk.store_secret(skf)?; - spk.store_secret(pkf)?; + generate_and_save_keypair(skf, pkf)?; } ExchangeConfig { config_file } => { @@ -246,3 +284,12 @@ impl Cli { srv.event_loop() } } + +/// generate secret and public keys, store in files according to the paths passed as arguments +fn generate_and_save_keypair(secret_key: PathBuf, public_key: PathBuf) -> anyhow::Result<()> { + let mut ssk = crate::protocol::SSk::random(); + let mut spk = crate::protocol::SPk::random(); + StaticKem::keygen(ssk.secret_mut(), spk.secret_mut())?; + ssk.store_secret(secret_key)?; + spk.store_secret(public_key) +} diff --git a/rosenpass/src/main.rs b/rosenpass/src/main.rs index c6c9d84..e4db49e 100644 --- a/rosenpass/src/main.rs +++ b/rosenpass/src/main.rs @@ -5,7 +5,8 @@ use std::process::exit; /// Catches errors, prints them through the logger, then exits pub fn main() { - env_logger::init(); + // default to displaying warning and error log messages only + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init(); let res = attempt!({ rosenpass_sodium::init()?; diff --git a/to/README.md b/to/README.md index b8e0146..5c34874 100644 --- a/to/README.md +++ b/to/README.md @@ -12,15 +12,17 @@ The crate provides chained functions to simplify allocating the destination para For now this crate is experimental; patch releases are guaranteed not to contain any breaking changes, but minor releases may. ```rust -use std::ops::BitXorAssign; -use rosenpass_to::{To, to, with_destination}; use rosenpass_to::ops::copy_array; +use rosenpass_to::{to, with_destination, To}; +use std::ops::BitXorAssign; // Destination functions return some value that implements the To trait. // Unfortunately dealing with lifetimes is a bit more finicky than it would# // be without destination parameters -fn xor_slice<'a, T>(src: &'a[T]) -> impl To<[T], ()> + 'a - where T: BitXorAssign + Clone { +fn xor_slice<'a, T>(src: &'a [T]) -> impl To<[T], ()> + 'a +where + T: BitXorAssign + Clone, +{ // Custom implementations of the to trait can be created, but the easiest with_destination(move |dst: &mut [T]| { assert!(src.len() == dst.len()); @@ -65,7 +67,7 @@ assert_eq!(&dst[..], &flip01[..]); // The builtin function copy_array supports to_value() since its // destination parameter is a fixed size array, which can be allocated // using default() -let dst : [u8; 4] = copy_array(flip01).to_value(); +let dst: [u8; 4] = copy_array(flip01).to_value(); assert_eq!(&dst, flip01); ``` @@ -84,7 +86,9 @@ Functions declared like this are more cumbersome to use when the destination par use std::ops::BitXorAssign; fn xor_slice(dst: &mut [T], src: &[T]) - where T: BitXorAssign + Clone { +where + T: BitXorAssign + Clone, +{ assert!(src.len() == dst.len()); for (d, s) in dst.iter_mut().zip(src.iter()) { *d ^= s.clone(); @@ -114,8 +118,8 @@ assert_eq!(&dst[..], &flip01[..]); There are a couple of ways to use a function with destination: ```rust -use rosenpass_to::{to, To}; use rosenpass_to::ops::{copy_array, copy_slice_least}; +use rosenpass_to::{to, To}; let mut dst = b" ".to_vec(); @@ -129,7 +133,8 @@ copy_slice_least(b"This is fin").to(&mut dst[..]); assert_eq!(&dst[..], b"This is fin"); // You can allocate the destination variable on the fly using `.to_this(...)` -let tmp = copy_slice_least(b"This is new---").to_this(|| b"This will be overwritten".to_owned()); +let tmp = + copy_slice_least(b"This is new---").to_this(|| b"This will be overwritten".to_owned()); assert_eq!(&tmp[..], b"This is new---verwritten"); // You can allocate the destination variable on the fly `.collect(..)` if it implements default @@ -147,8 +152,11 @@ assert_eq!(&tmp[..], b"Fixed"); The to crate provides basic functions with destination for copying data between slices and arrays. ```rust +use rosenpass_to::ops::{ + copy_array, copy_slice, copy_slice_least, copy_slice_least_src, try_copy_slice, + try_copy_slice_least_src, +}; use rosenpass_to::{to, To}; -use rosenpass_to::ops::{copy_array, copy_slice, copy_slice_least, copy_slice_least_src, try_copy_slice, try_copy_slice_least_src}; let mut dst = b" ".to_vec(); @@ -161,18 +169,33 @@ to(&mut dst[4..], copy_slice_least_src(b"!!!")); assert_eq!(&dst[..], b"Hell!!!orld"); // Copy a slice, copying as many bytes as possible -to(&mut dst[6..], copy_slice_least(b"xxxxxxxxxxxxxxxxxxxxxxxxxxxxx")); +to( + &mut dst[6..], + copy_slice_least(b"xxxxxxxxxxxxxxxxxxxxxxxxxxxxx"), +); assert_eq!(&dst[..], b"Hell!!xxxxx"); // Copy a slice, will return None and abort if the sizes do not much assert_eq!(Some(()), to(&mut dst[..], try_copy_slice(b"Hello World"))); assert_eq!(None, to(&mut dst[..], try_copy_slice(b"---"))); -assert_eq!(None, to(&mut dst[..], try_copy_slice(b"---------------------"))); +assert_eq!( + None, + to(&mut dst[..], try_copy_slice(b"---------------------")) +); assert_eq!(&dst[..], b"Hello World"); // Copy a slice, will return None and abort if source is longer than destination -assert_eq!(Some(()), to(&mut dst[4..], try_copy_slice_least_src(b"!!!"))); -assert_eq!(None, to(&mut dst[4..], try_copy_slice_least_src(b"-------------------------"))); +assert_eq!( + Some(()), + to(&mut dst[4..], try_copy_slice_least_src(b"!!!")) +); +assert_eq!( + None, + to( + &mut dst[4..], + try_copy_slice_least_src(b"-------------------------") + ) +); assert_eq!(&dst[..], b"Hell!!!orld"); // Copy fixed size arrays all at once @@ -186,12 +209,14 @@ assert_eq!(&dst, b"Hello"); The easiest way to declare a function with destination is to use the with_destination function. ```rust -use rosenpass_to::{To, to, with_destination}; use rosenpass_to::ops::copy_array; +use rosenpass_to::{to, with_destination, To}; /// Copy the given slice to the start of a vector, reusing its memory if possible fn copy_to_vec<'a, T>(src: &'a [T]) -> impl To, ()> + 'a - where T: Clone { +where + T: Clone, +{ with_destination(move |dst: &mut Vec| { dst.clear(); dst.extend_from_slice(src); @@ -217,7 +242,9 @@ The same pattern can be implemented without `to`, at the cost of being slightly ```rust /// Copy the given slice to the start of a vector, reusing its memory if possible fn copy_to_vec(dst: &mut Vec, src: &[T]) - where T: Clone { +where + T: Clone, +{ dst.clear(); dst.extend_from_slice(src); } @@ -240,11 +267,11 @@ Alternative functions are returned, that return a `to::Beside` value, containing destination variable and the return value. ```rust -use std::cmp::{min, max}; -use rosenpass_to::{To, to, with_destination, Beside}; +use rosenpass_to::{to, with_destination, Beside, To}; +use std::cmp::{max, min}; /// Copy an array of floats and calculate the average -pub fn copy_and_average<'a>(src: &'a[f64]) -> impl To<[f64], f64> + 'a { +pub fn copy_and_average<'a>(src: &'a [f64]) -> impl To<[f64], f64> + 'a { with_destination(move |dst: &mut [f64]| { assert!(src.len() == dst.len()); let mut sum = 0f64; @@ -300,8 +327,8 @@ assert_eq!(tmp, Beside([42f64; 3], 42f64)); When Beside values contain a `()`, `Option<()>`, or `Result<(), Error>` return value, they expose a special method called `.condense()`; this method consumes the Beside value and condenses destination and return value into one value. ```rust +use rosenpass_to::Beside; use std::result::Result; -use rosenpass_to::{Beside}; assert_eq!((), Beside((), ()).condense()); @@ -318,8 +345,8 @@ assert_eq!(Err(()), Beside(42, err_unit).condense()); When condense is implemented for a type, `.to_this(|| ...)`, `.to_value()`, and `.collect::<...>()` on the `To` trait can be used even with a return value: ```rust +use rosenpass_to::ops::try_copy_slice; use rosenpass_to::To; -use rosenpass_to::ops::try_copy_slice;; let tmp = try_copy_slice(b"Hello World").collect::<[u8; 11]>(); assert_eq!(tmp, Some(*b"Hello World")); @@ -337,8 +364,8 @@ assert_eq!(tmp, None); The same naturally also works for Results, but the example is a bit harder to motivate: ```rust +use rosenpass_to::{to, with_destination, To}; use std::result::Result; -use rosenpass_to::{to, To, with_destination}; #[derive(PartialEq, Eq, Debug, Default)] struct InvalidFloat; @@ -380,8 +407,8 @@ Condensation is implemented through a trait called CondenseBeside ([local](Conde If you can not implement this trait because its for an external type (see [orphan rule](https://doc.rust-lang.org/book/ch10-02-traits.html#implementing-a-trait-on-a-type)), this crate welcomes contributions of new Condensation rules. ```rust -use rosenpass_to::{To, with_destination, Beside, CondenseBeside}; use rosenpass_to::ops::copy_slice; +use rosenpass_to::{with_destination, Beside, CondenseBeside, To}; #[derive(PartialEq, Eq, Debug, Default)] struct MyTuple(Left, Right); @@ -396,7 +423,10 @@ impl CondenseBeside for MyTuple<(), Right> { } fn copy_slice_and_return_something<'a, T, U>(src: &'a [T], something: U) -> impl To<[T], U> + 'a - where T: Copy, U: 'a { +where + T: Copy, + U: 'a, +{ with_destination(move |dst: &mut [T]| { copy_slice(src).to(dst); something @@ -417,7 +447,7 @@ Using `with_destination(...)` is convenient, but since it uses closures it resul Implementing the ToTrait manual is the right choice for library use cases. ```rust -use rosenpass_to::{to, To, with_destination}; +use rosenpass_to::{to, with_destination, To}; struct TryCopySliceSource<'a, T: Copy> { src: &'a [T], @@ -425,17 +455,20 @@ struct TryCopySliceSource<'a, T: Copy> { impl<'a, T: Copy> To<[T], Option<()>> for TryCopySliceSource<'a, T> { fn to(self, dst: &mut [T]) -> Option<()> { - (self.src.len() == dst.len()) - .then(|| dst.copy_from_slice(self.src)) + (self.src.len() == dst.len()).then(|| dst.copy_from_slice(self.src)) } } fn try_copy_slice<'a, T>(src: &'a [T]) -> TryCopySliceSource<'a, T> - where T: Copy { +where + T: Copy, +{ TryCopySliceSource { src } } -let mut dst = try_copy_slice(b"Hello World").collect::<[u8; 11]>().unwrap(); +let mut dst = try_copy_slice(b"Hello World") + .collect::<[u8; 11]>() + .unwrap(); assert_eq!(&dst[..], b"Hello World"); assert_eq!(None, to(&mut dst[..], try_copy_slice(b"---"))); ``` @@ -445,8 +478,8 @@ assert_eq!(None, to(&mut dst[..], try_copy_slice(b"---"))); Destinations can also be used with methods. This example demonstrates using destinations in an extension trait for everything that implements `Borrow<[T]>` for any `T` and a concrete `To` trait implementation. ```rust +use rosenpass_to::{to, with_destination, To}; use std::borrow::Borrow; -use rosenpass_to::{to, To, with_destination}; struct TryCopySliceSource<'a, T: Copy> { src: &'a [T], @@ -454,24 +487,24 @@ struct TryCopySliceSource<'a, T: Copy> { impl<'a, T: Copy> To<[T], Option<()>> for TryCopySliceSource<'a, T> { fn to(self, dst: &mut [T]) -> Option<()> { - (self.src.len() == dst.len()) - .then(|| dst.copy_from_slice(self.src)) + (self.src.len() == dst.len()).then(|| dst.copy_from_slice(self.src)) } } trait TryCopySliceExt<'a, T: Copy> { - fn try_copy_slice(&'a self) -> TryCopySliceSource<'a, T>; + fn try_copy_slice(&'a self) -> TryCopySliceSource<'a, T>; } impl<'a, T: 'a + Copy, Ref: 'a + Borrow<[T]>> TryCopySliceExt<'a, T> for Ref { - fn try_copy_slice(&'a self) -> TryCopySliceSource<'a, T> { - TryCopySliceSource { - src: self.borrow() - } + fn try_copy_slice(&'a self) -> TryCopySliceSource<'a, T> { + TryCopySliceSource { src: self.borrow() } } } -let mut dst = b"Hello World".try_copy_slice().collect::<[u8; 11]>().unwrap(); +let mut dst = b"Hello World" + .try_copy_slice() + .collect::<[u8; 11]>() + .unwrap(); assert_eq!(&dst[..], b"Hello World"); assert_eq!(None, to(&mut dst[..], b"---".try_copy_slice())); ```