diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7c1616a..319d1bd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,63 +2,91 @@ on: [push, pull_request] name: Test +permissions: + contents: read + +env: + RUSTFLAGS: "-D warnings" + jobs: + no_std: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.85.0 # MSRV + - stable + target: + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v6 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + targets: ${{ matrix.target }} + - run: cargo build --target ${{ matrix.target }} + test: - name: cargo test runs-on: ubuntu-latest strategy: matrix: rust: + - 1.85.0 # MSRV - stable - beta - nightly - - 1.60.0 steps: - name: checkout - uses: actions/checkout@v2 - - name: toolchain - uses: actions-rs/toolchain@v1 + uses: actions/checkout@v6 + - uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: ${{ matrix.rust }} - target: thumbv7em-none-eabi - override: true - - name: test - uses: actions-rs/cargo@v1 - with: - command: test - - name: nightly - uses: actions-rs/cargo@v1 - with: - command: test - args: --features nightly - - name: no-default-features - uses: actions-rs/cargo@v1 - with: - command: test - args: --no-default-features - - name: std - uses: actions-rs/cargo@v1 - with: - command: test - args: --no-default-features --features std - - name: std const-generics - uses: actions-rs/cargo@v1 - with: - command: test - args: --no-default-features --features "std const-generics" - - name: std i128 - uses: actions-rs/cargo@v1 - with: - command: test - args: --no-default-features --features "std i128" - - name: std i128 const-generics - uses: actions-rs/cargo@v1 + - run: cargo check + - run: cargo test + - run: cargo test --release + + # Test using `cargo careful` + test-careful: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: dtolnay/rust-toolchain@nightly + - run: cargo install cargo-careful + - run: cargo careful test --all-features + + # Test using `cargo miri` + test-miri: + runs-on: ubuntu-latest + env: + MIRIFLAGS: "-Zmiri-symbolic-alignment-check -Zmiri-strict-provenance" + strategy: + matrix: + target: + - x86_64-unknown-linux-gnu + - s390x-unknown-linux-gnu + steps: + - uses: actions/checkout@v6 + - uses: dtolnay/rust-toolchain@nightly + - run: rustup component add miri && cargo miri setup + - run: cargo miri test --target ${{ matrix.target }} --all-features --lib + + clippy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: dtolnay/rust-toolchain@master with: - command: test - args: --no-default-features --features "std i128 const-generics" - - name: no std build - uses: actions-rs/cargo@v1 + toolchain: 1.92.0 # Pinned to prevent breakages + components: clippy + - run: cargo clippy --workspace --all-features --lib --bins --tests -- -D warnings + + rustfmt: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: dtolnay/rust-toolchain@master with: - command: build - args: --no-default-features --target thumbv7em-none-eabi + toolchain: stable + components: rustfmt + - run: cargo fmt --all -- --check diff --git a/Cargo.toml b/Cargo.toml index ae7361c..331a803 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,11 +2,11 @@ name = "subtle" # Before incrementing: # - update CHANGELOG -# - update html_root_url # - update README if necessary by semver # - if any updates were made to the README, also update the module documentation in src/lib.rs -version = "2.6.0" -edition = "2018" +version = "3.0.0-pre" +edition = "2024" +rust-version = "1.85.0" authors = ["Isis Lovecruft ", "Henry de Valence "] readme = "README.md" @@ -17,23 +17,7 @@ documentation = "https://docs.rs/subtle" categories = ["cryptography", "no-std"] keywords = ["cryptography", "crypto", "constant-time", "utilities"] description = "Pure-Rust traits and utilities for constant-time cryptographic implementations." -exclude = [ - "**/.gitignore", - ".travis.yml", -] - -[badges] -travis-ci = { repository = "dalek-cryptography/subtle", branch = "master"} +exclude = [".github", ".gitignore"] [dev-dependencies] -rand = { version = "0.8" } - -[features] -const-generics = [] -# DEPRECATED: As of 2.5.1, this feature does nothing. -core_hint_black_box = [] -default = ["std", "i128"] -std = [] -i128 = [] -# DEPRECATED: As of 2.4.1, this feature does nothing. -nightly = [] +rand = { version = "0.9" } diff --git a/src/lib.rs b/src/lib.rs index 9fc143b..6d284d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,9 +9,9 @@ // - Henry de Valence #![no_std] +#![allow(clippy::inline_fn_without_body)] #![deny(missing_docs)] #![doc(html_logo_url = "https://doc.dalek.rs/assets/dalek-logo-clear.png")] -#![doc(html_root_url = "https://docs.rs/subtle/2.6.0")] //! # subtle [![](https://img.shields.io/crates/v/subtle.svg)](https://crates.io/crates/subtle) [![](https://img.shields.io/badge/dynamic/json.svg?label=docs&uri=https%3A%2F%2Fcrates.io%2Fapi%2Fv1%2Fcrates%2Fsubtle%2Fversions&query=%24.versions%5B0%5D.num&colorB=4F74A6)](https://doc.dalek.rs/subtle) [![](https://travis-ci.org/dalek-cryptography/subtle.svg?branch=master)](https://travis-ci.org/dalek-cryptography/subtle) //! @@ -22,7 +22,7 @@ //! type is a wrapper around a `u8` that holds a `0` or `1`. //! //! ```toml -//! subtle = "2.6" +//! subtle = "3.0.0-pre" //! ``` //! //! This crate represents a “best-effort” attempt, since side-channels @@ -58,7 +58,7 @@ //! //! ## Minimum Supported Rust Version //! -//! Rust **1.41** or higher. +//! Rust **1.85** or higher. //! //! Minimum supported Rust version can be changed in the future, but it will be done with a minor version bump. //! @@ -88,17 +88,11 @@ //! [docs]: https://docs.rs/subtle //! [rust-timing-shield]: https://www.chosenplaintext.ca/open-source/rust-timing-shield/security -#[cfg(feature = "std")] -#[macro_use] -extern crate std; - use core::cmp; +use core::hint::black_box; use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Neg, Not}; use core::option::Option; -#[cfg(feature = "core_hint_black_box")] -use core::hint::black_box; - /// The `Choice` struct represents a choice for use in conditional assignment. /// /// It is a wrapper around a `u8`, which should have the value either `1` (true) @@ -209,30 +203,6 @@ impl Not for Choice { } } -/// This function is a best-effort attempt to prevent the compiler from knowing -/// anything about the value of the returned `u8`, other than its type. -/// -/// Because we want to support stable Rust, we don't have access to inline -/// assembly or test::black_box, so we use the fact that volatile values will -/// never be elided to register values. -/// -/// Note: Rust's notion of "volatile" is subject to change over time. While this -/// code may break in a non-destructive way in the future, “constant-time” code -/// is a continually moving target, and this is better than doing nothing. -#[cfg(not(feature = "core_hint_black_box"))] -#[inline(never)] -fn black_box(input: T) -> T { - unsafe { - // Optimization barrier - // - // SAFETY: - // - &input is not NULL because we own input; - // - input is Copy and always live; - // - input is always properly aligned. - core::ptr::read_volatile(&input) - } -} - impl From for Choice { #[inline] fn from(input: u8) -> Choice { @@ -371,7 +341,6 @@ generate_integer_equal!(u8, i8, 8); generate_integer_equal!(u16, i16, 16); generate_integer_equal!(u32, i32, 32); generate_integer_equal!(u64, i64, 64); -#[cfg(feature = "i128")] generate_integer_equal!(u128, i128, 128); generate_integer_equal!(usize, isize, ::core::mem::size_of::() * 8); @@ -468,7 +437,7 @@ pub trait ConditionallySelectable: Copy { #[inline] fn conditional_swap(a: &mut Self, b: &mut Self, choice: Choice) { let t: Self = *a; - a.conditional_assign(&b, choice); + a.conditional_assign(b, choice); b.conditional_assign(&t, choice); } } @@ -542,7 +511,6 @@ generate_integer_conditional_select!( u8 i8); generate_integer_conditional_select!( u16 i16); generate_integer_conditional_select!( u32 i32); generate_integer_conditional_select!( u64 i64); -#[cfg(feature = "i128")] generate_integer_conditional_select!(u128 i128); /// `Ordering` is `#[repr(i8)]` where: @@ -572,7 +540,6 @@ impl ConditionallySelectable for Choice { } } -#[cfg(feature = "const-generics")] impl ConditionallySelectable for [T; N] where T: ConditionallySelectable, @@ -676,10 +643,7 @@ impl CtOption { /// exposed. #[inline] pub fn new(value: T, is_some: Choice) -> CtOption { - CtOption { - value: value, - is_some: is_some, - } + CtOption { value, is_some } } /// Returns the contained value, consuming the `self` value. @@ -915,7 +879,6 @@ generate_unsigned_integer_greater!(u8, 8); generate_unsigned_integer_greater!(u16, 16); generate_unsigned_integer_greater!(u32, 32); generate_unsigned_integer_greater!(u64, 64); -#[cfg(feature = "i128")] generate_unsigned_integer_greater!(u128, 128); impl ConstantTimeGreater for cmp::Ordering { @@ -976,7 +939,6 @@ impl ConstantTimeLess for u8 {} impl ConstantTimeLess for u16 {} impl ConstantTimeLess for u32 {} impl ConstantTimeLess for u64 {} -#[cfg(feature = "i128")] impl ConstantTimeLess for u128 {} impl ConstantTimeLess for cmp::Ordering { diff --git a/tests/mod.rs b/tests/mod.rs index 888b9d0..f8b2d0e 100644 --- a/tests/mod.rs +++ b/tests/mod.rs @@ -1,7 +1,6 @@ use std::cmp; -use rand::rngs::OsRng; -use rand::RngCore; +use rand::{RngCore, TryRngCore, rngs::OsRng}; use subtle::*; @@ -11,7 +10,7 @@ fn slices_equal_different_lengths() { let a: [u8; 3] = [0, 0, 0]; let b: [u8; 4] = [0, 0, 0, 0]; - assert_eq!((&a).ct_eq(&b).unwrap_u8(), 1); + assert_eq!(a.ct_eq(&b).unwrap_u8(), 1); } #[test] @@ -19,15 +18,15 @@ fn slices_equal() { let a: [u8; 8] = [1, 2, 3, 4, 5, 6, 7, 8]; let b: [u8; 8] = [1, 2, 3, 4, 4, 3, 2, 1]; - let a_eq_a = (&a).ct_eq(&a); - let a_eq_b = (&a).ct_eq(&b); + let a_eq_a = a.ct_eq(&a); + let a_eq_b = a.ct_eq(&b); assert_eq!(a_eq_a.unwrap_u8(), 1); assert_eq!(a_eq_b.unwrap_u8(), 0); let c: [u8; 16] = [0u8; 16]; - let a_eq_c = (&a).ct_eq(&c); + let a_eq_c = a.ct_eq(&c); assert_eq!(a_eq_c.unwrap_u8(), 0); } @@ -54,7 +53,7 @@ fn conditional_assign_i64() { } macro_rules! generate_integer_conditional_select_tests { - ($($t:ty)*) => ($( + ($($t:ty),*) => ($( let x: $t = 0; // all 0 bits let y: $t = !0; // all 1 bits @@ -80,10 +79,8 @@ macro_rules! generate_integer_conditional_select_tests { #[test] fn integer_conditional_select() { - generate_integer_conditional_select_tests!(u8 u16 u32 u64); - generate_integer_conditional_select_tests!(i8 i16 i32 i64); - #[cfg(feature = "i128")] - generate_integer_conditional_select_tests!(i128 u128); + generate_integer_conditional_select_tests!(u8, u16, u32, u64, i128); + generate_integer_conditional_select_tests!(i8, i16, i32, i64, u128); } #[test] @@ -124,11 +121,8 @@ macro_rules! generate_integer_equal_tests { #[test] fn integer_equal() { - generate_integer_equal_tests!(u8, u16, u32, u64); - generate_integer_equal_tests!(i8, i16, i32, i64); - #[cfg(feature = "i128")] - generate_integer_equal_tests!(i128, u128); - generate_integer_equal_tests!(isize, usize); + generate_integer_equal_tests!(u8, u16, u32, u64, u128, usize); + generate_integer_equal_tests!(i8, i16, i32, i64, i128, isize); } #[test] @@ -147,18 +141,18 @@ fn conditional_select_choice() { let t = Choice::from(1); let f = Choice::from(0); - assert_eq!(bool::from(Choice::conditional_select(&t, &f, f)), true); - assert_eq!(bool::from(Choice::conditional_select(&t, &f, t)), false); - assert_eq!(bool::from(Choice::conditional_select(&f, &t, f)), false); - assert_eq!(bool::from(Choice::conditional_select(&f, &t, t)), true); + assert!(bool::from(Choice::conditional_select(&t, &f, f))); + assert!(!bool::from(Choice::conditional_select(&t, &f, t))); + assert!(!bool::from(Choice::conditional_select(&f, &t, f))); + assert!(bool::from(Choice::conditional_select(&f, &t, t))); } #[test] fn choice_equal() { - assert!(Choice::from(0).ct_eq(&Choice::from(0)).unwrap_u8() == 1); - assert!(Choice::from(0).ct_eq(&Choice::from(1)).unwrap_u8() == 0); - assert!(Choice::from(1).ct_eq(&Choice::from(0)).unwrap_u8() == 0); - assert!(Choice::from(1).ct_eq(&Choice::from(1)).unwrap_u8() == 1); + assert_eq!(Choice::from(0).ct_eq(&Choice::from(0)).unwrap_u8(), 1); + assert_eq!(Choice::from(0).ct_eq(&Choice::from(1)).unwrap_u8(), 0); + assert_eq!(Choice::from(1).ct_eq(&Choice::from(0)).unwrap_u8(), 0); + assert_eq!(Choice::from(1).ct_eq(&Choice::from(1)).unwrap_u8(), 1); } #[test] @@ -290,16 +284,66 @@ fn test_ctoption() { )); // Test (in)equality - assert!(CtOption::new(1, Choice::from(0)).ct_eq(&CtOption::new(1, Choice::from(1))).unwrap_u8() == 0); - assert!(CtOption::new(1, Choice::from(1)).ct_eq(&CtOption::new(1, Choice::from(0))).unwrap_u8() == 0); - assert!(CtOption::new(1, Choice::from(0)).ct_eq(&CtOption::new(2, Choice::from(1))).unwrap_u8() == 0); - assert!(CtOption::new(1, Choice::from(1)).ct_eq(&CtOption::new(2, Choice::from(0))).unwrap_u8() == 0); - assert!(CtOption::new(1, Choice::from(0)).ct_eq(&CtOption::new(1, Choice::from(0))).unwrap_u8() == 1); - assert!(CtOption::new(1, Choice::from(0)).ct_eq(&CtOption::new(2, Choice::from(0))).unwrap_u8() == 1); - assert!(CtOption::new(1, Choice::from(1)).ct_eq(&CtOption::new(2, Choice::from(1))).unwrap_u8() == 0); - assert!(CtOption::new(1, Choice::from(1)).ct_eq(&CtOption::new(2, Choice::from(1))).unwrap_u8() == 0); - assert!(CtOption::new(1, Choice::from(1)).ct_eq(&CtOption::new(1, Choice::from(1))).unwrap_u8() == 1); - assert!(CtOption::new(1, Choice::from(1)).ct_eq(&CtOption::new(1, Choice::from(1))).unwrap_u8() == 1); + assert_eq!( + CtOption::new(1, Choice::from(0)) + .ct_eq(&CtOption::new(1, Choice::from(1))) + .unwrap_u8(), + 0 + ); + assert_eq!( + CtOption::new(1, Choice::from(1)) + .ct_eq(&CtOption::new(1, Choice::from(0))) + .unwrap_u8(), + 0 + ); + assert_eq!( + CtOption::new(1, Choice::from(0)) + .ct_eq(&CtOption::new(2, Choice::from(1))) + .unwrap_u8(), + 0 + ); + assert_eq!( + CtOption::new(1, Choice::from(1)) + .ct_eq(&CtOption::new(2, Choice::from(0))) + .unwrap_u8(), + 0 + ); + assert_eq!( + CtOption::new(1, Choice::from(0)) + .ct_eq(&CtOption::new(1, Choice::from(0))) + .unwrap_u8(), + 1 + ); + assert_eq!( + CtOption::new(1, Choice::from(0)) + .ct_eq(&CtOption::new(2, Choice::from(0))) + .unwrap_u8(), + 1 + ); + assert_eq!( + CtOption::new(1, Choice::from(1)) + .ct_eq(&CtOption::new(2, Choice::from(1))) + .unwrap_u8(), + 0 + ); + assert_eq!( + CtOption::new(1, Choice::from(1)) + .ct_eq(&CtOption::new(2, Choice::from(1))) + .unwrap_u8(), + 0 + ); + assert_eq!( + CtOption::new(1, Choice::from(1)) + .ct_eq(&CtOption::new(1, Choice::from(1))) + .unwrap_u8(), + 1 + ); + assert_eq!( + CtOption::new(1, Choice::from(1)) + .ct_eq(&CtOption::new(1, Choice::from(1))) + .unwrap_u8(), + 1 + ); } #[test] @@ -313,8 +357,9 @@ fn unwrap_none_ctoption() { macro_rules! generate_greater_than_test { ($ty: ty) => { for _ in 0..100 { - let x = OsRng.next_u64() as $ty; - let y = OsRng.next_u64() as $ty; + let mut rng = OsRng.unwrap_err(); + let x = rng.next_u64() as $ty; + let y = rng.next_u64() as $ty; let z = x.ct_gt(&y); println!("x={}, y={}, z={:?}", x, y, z); @@ -327,7 +372,7 @@ macro_rules! generate_greater_than_test { assert!(z.unwrap_u8() == 1); } } - } + }; } #[test] @@ -350,7 +395,6 @@ fn greater_than_u64() { generate_greater_than_test!(u64); } -#[cfg(feature = "i128")] #[test] fn greater_than_u128() { generate_greater_than_test!(u128); @@ -358,8 +402,18 @@ fn greater_than_u128() { #[test] fn greater_than_ordering() { - assert_eq!(cmp::Ordering::Less.ct_gt(&cmp::Ordering::Greater).unwrap_u8(), 0); - assert_eq!(cmp::Ordering::Greater.ct_gt(&cmp::Ordering::Less).unwrap_u8(), 1); + assert_eq!( + cmp::Ordering::Less + .ct_gt(&cmp::Ordering::Greater) + .unwrap_u8(), + 0 + ); + assert_eq!( + cmp::Ordering::Greater + .ct_gt(&cmp::Ordering::Less) + .unwrap_u8(), + 1 + ); } #[test] @@ -367,7 +421,7 @@ fn greater_than_ordering() { /// gives the correct result. (This fails using the bit-twiddling algorithm that /// go/crypto/subtle uses.) fn less_than_twos_compliment_minmax() { - let z = 1u32.ct_lt(&(2u32.pow(31)-1)); + let z = 1u32.ct_lt(&(2u32.pow(31) - 1)); assert!(z.unwrap_u8() == 1); } @@ -375,8 +429,9 @@ fn less_than_twos_compliment_minmax() { macro_rules! generate_less_than_test { ($ty: ty) => { for _ in 0..100 { - let x = OsRng.next_u64() as $ty; - let y = OsRng.next_u64() as $ty; + let mut rng = OsRng.unwrap_err(); + let x = rng.next_u64() as $ty; + let y = rng.next_u64() as $ty; let z = x.ct_gt(&y); println!("x={}, y={}, z={:?}", x, y, z); @@ -389,7 +444,7 @@ macro_rules! generate_less_than_test { assert!(z.unwrap_u8() == 1); } } - } + }; } #[test] @@ -412,7 +467,6 @@ fn less_than_u64() { generate_less_than_test!(u64); } -#[cfg(feature = "i128")] #[test] fn less_than_u128() { generate_less_than_test!(u128); @@ -420,8 +474,18 @@ fn less_than_u128() { #[test] fn less_than_ordering() { - assert_eq!(cmp::Ordering::Greater.ct_lt(&cmp::Ordering::Less).unwrap_u8(), 0); - assert_eq!(cmp::Ordering::Less.ct_lt(&cmp::Ordering::Greater).unwrap_u8(), 1); + assert_eq!( + cmp::Ordering::Greater + .ct_lt(&cmp::Ordering::Less) + .unwrap_u8(), + 0 + ); + assert_eq!( + cmp::Ordering::Less + .ct_lt(&cmp::Ordering::Greater) + .unwrap_u8(), + 1 + ); } #[test]