diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..c5fe9e4 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,64 @@ +on: [push, pull_request] + +name: Test + +jobs: + test: + name: cargo test + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - stable + - beta + - nightly + - 1.41.0 + steps: + - name: checkout + uses: actions/checkout@v2 + - name: toolchain + uses: actions-rs/toolchain@v1 + 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 + with: + command: test + args: --no-default-features --features "std i128 const-generics" + - name: no std build + uses: actions-rs/cargo@v1 + with: + command: build + args: --no-default-features --target thumbv7em-none-eabi diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ef3b7f8..0000000 --- a/.travis.yml +++ /dev/null @@ -1,41 +0,0 @@ -language: rust -cache: cargo - -rust: - - stable - - beta - - nightly - -matrix: - include: - # Test nightly feature - - rust: nightly - env: NAME="nightly feature" - script: cargo test --features nightly - # Test MSRV - - rust: 1.41.0 - env: NAME="MSRV test" - script: cargo test --no-default-features --features std - # Test if crate can be truly built without std - - env: TARGET=thumbv7em-none-eabi - script: cargo build --no-default-features --target $TARGET - install: - - rustup target add $TARGET - -script: - - cargo test && cargo test --no-default-features && - cargo test --no-default-features --features std && - cargo test --no-default-features --features "std i128" && - cargo test --no-default-features --features "std core_hint_black_box" && - cargo test --no-default-features --features "std const-generics" && - cargo test --no-default-features --features "std i128 core_hint_black_box" && - cargo test --no-default-features --features "std i128 core_hint_black_box const-generics" - -notifications: - slack: - rooms: - - dalek-cryptography:Xxv9WotKYWdSoKlgKNqXiHoD#dalek-bots - -cache: - directories: - - /home/travis/.cargo diff --git a/Cargo.toml b/Cargo.toml index 64532ed..ae7361c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ name = "subtle" # - 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.5.0" +version = "2.6.0" edition = "2018" authors = ["Isis Lovecruft ", "Henry de Valence "] @@ -30,6 +30,7 @@ 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 = [] diff --git a/LICENSE b/LICENSE index 927c02c..9e2751f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,5 @@ Copyright (c) 2016-2017 Isis Agora Lovecruft, Henry de Valence. All rights reserved. +Copyright (c) 2016-2024 Isis Agora Lovecruft. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are diff --git a/README.md b/README.md index de77575..a9eb261 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ instead of `bool` which are intended to execute in constant-time. The `Choice` type is a wrapper around a `u8` that holds a `0` or `1`. ```toml -subtle = "2.5" +subtle = "2.6" ``` This crate represents a “best-effort” attempt, since side-channels @@ -26,10 +26,6 @@ prevent this refinement, the crate tries to hide the value of a `Choice`'s inner `u8` by passing it through a volatile read. For more information, see the _About_ section below. -Rust versions from 1.66 or higher support a new best-effort optimization -barrier ([`core::hint::black_box`]). To use the new optimization barrier, -enable the `core_hint_black_box` feature. - Rust versions from 1.51 or higher have const generics support. You may enable `const-generics` feautre to have `subtle` traits implemented for arrays `[T; N]`. @@ -59,11 +55,8 @@ Old versions of the optimization barrier in `impl From for Choice` were based on Tim Maclean's [work on `rust-timing-shield`][rust-timing-shield], which attempts to provide a more comprehensive approach for preventing software side-channels in Rust code. - From version `2.2`, it was based on Diane Hosfelt and Amber Sprenkels' work on -"Secret Types in Rust". Version `2.5` adds the `core_hint_black_box` feature, -which uses the original method through the [`core::hint::black_box`] function -from the Rust standard library. +"Secret Types in Rust". `subtle` is authored by isis agora lovecruft and Henry de Valence. @@ -78,5 +71,4 @@ effort is fundamentally limited. **USE AT YOUR OWN RISK** [docs]: https://docs.rs/subtle -[`core::hint::black_box`]: https://doc.rust-lang.org/core/hint/fn.black_box.html [rust-timing-shield]: https://www.chosenplaintext.ca/open-source/rust-timing-shield/security diff --git a/src/lib.rs b/src/lib.rs index 795eade..b6e42c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,7 +11,7 @@ #![no_std] #![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.5.0")] +#![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.5" +//! subtle = "2.6" //! ``` //! //! This crate represents a “best-effort” attempt, since side-channels @@ -41,10 +41,6 @@ //! inner `u8` by passing it through a volatile read. For more information, see //! the _About_ section below. //! -//! Rust versions from 1.66 or higher support a new best-effort optimization -//! barrier ([`core::hint::black_box`]). To use the new optimization barrier, -//! enable the `core_hint_black_box` feature. -//! //! Rust versions from 1.51 or higher have const generics support. You may enable //! `const-generics` feautre to have `subtle` traits implemented for arrays `[T; N]`. //! @@ -74,11 +70,8 @@ //! based on Tim Maclean's [work on `rust-timing-shield`][rust-timing-shield], //! which attempts to provide a more comprehensive approach for preventing //! software side-channels in Rust code. -//! //! From version `2.2`, it was based on Diane Hosfelt and Amber Sprenkels' work on -//! "Secret Types in Rust". Version `2.5` adds the `core_hint_black_box` feature, -//! which uses the original method through the [`core::hint::black_box`] function -//! from the Rust standard library. +//! "Secret Types in Rust". //! //! `subtle` is authored by isis agora lovecruft and Henry de Valence. //! @@ -93,7 +86,6 @@ //! **USE AT YOUR OWN RISK** //! //! [docs]: https://docs.rs/subtle -//! [`core::hint::black_box`]: https://doc.rust-lang.org/core/hint/fn.black_box.html //! [rust-timing-shield]: https://www.chosenplaintext.ca/open-source/rust-timing-shield/security #[cfg(feature = "std")] @@ -104,6 +96,9 @@ use core::cmp; 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) @@ -224,36 +219,26 @@ impl Not for Choice { /// 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: u8) -> u8 { - debug_assert!((input == 0u8) | (input == 1u8)); - +fn black_box(input: T) -> T { unsafe { // Optimization barrier // - // Unsafe is ok, because: - // - &input is not NULL; - // - size of input is not zero; - // - u8 is neither Sync, nor Send; - // - u8 is Copy, so input is always live; - // - u8 type is always properly aligned. - core::ptr::read_volatile(&input as *const u8) + // 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) } } -#[cfg(feature = "core_hint_black_box")] -#[inline(never)] -fn black_box(input: u8) -> u8 { - debug_assert!((input == 0u8) | (input == 1u8)); - core::hint::black_box(input) -} - impl From for Choice { #[inline] fn from(input: u8) -> Choice { + debug_assert!((input == 0u8) | (input == 1u8)); + // Our goal is to prevent the compiler from inferring that the value held inside the - // resulting `Choice` struct is really an `i1` instead of an `i8`. + // resulting `Choice` struct is really a `bool` instead of a `u8`. Choice(black_box(input)) } } @@ -270,6 +255,9 @@ impl From for Choice { /// assert_eq!(x.ct_eq(&y).unwrap_u8(), 0); /// assert_eq!(x.ct_eq(&x).unwrap_u8(), 1); /// ``` +// +// #[inline] is specified on these function prototypes to signify that they +#[allow(unused_attributes)] // should be in the actual implementation pub trait ConstantTimeEq { /// Determine if two items are equal. /// @@ -280,6 +268,7 @@ pub trait ConstantTimeEq { /// * `Choice(1u8)` if `self == other`; /// * `Choice(0u8)` if `self != other`. #[inline] + #[allow(unused_attributes)] fn ct_eq(&self, other: &Self) -> Choice; /// Determine if two items are NOT equal. @@ -397,6 +386,9 @@ impl ConstantTimeEq for cmp::Ordering { /// /// This trait also provides generic implementations of conditional /// assignment and conditional swaps. +// +// #[inline] is specified on these function prototypes to signify that they +#[allow(unused_attributes)] // should be in the actual implementation pub trait ConditionallySelectable: Copy { /// Select `a` or `b` according to `choice`. /// @@ -423,6 +415,7 @@ pub trait ConditionallySelectable: Copy { /// # } /// ``` #[inline] + #[allow(unused_attributes)] fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self; /// Conditionally assign `other` to `self`, according to `choice`. @@ -604,12 +597,16 @@ where /// A generic implementation of `ConditionallyNegatable` is provided /// for types `T` which are `ConditionallySelectable` and have `Neg` /// implemented on `&T`. +// +// #[inline] is specified on these function prototypes to signify that they +#[allow(unused_attributes)] // should be in the actual implementation pub trait ConditionallyNegatable { /// Negate `self` if `choice == Choice(1)`; otherwise, leave it /// unchanged. /// /// This function should execute in constant time. #[inline] + #[allow(unused_attributes)] fn conditional_negate(&mut self, choice: Choice); } @@ -801,6 +798,22 @@ impl CtOption { Self::conditional_select(&self, &f, is_none) } + + /// Convert the `CtOption` wrapper into an `Option`, depending on whether + /// the underlying `is_some` `Choice` was a `0` or a `1` once unwrapped. + /// + /// # Note + /// + /// This function exists to avoid ending up with ugly, verbose and/or bad handled + /// conversions from the `CtOption` wraps to an `Option` or `Result`. + /// This implementation doesn't intend to be constant-time nor try to protect the + /// leakage of the `T` since the `Option` will do it anyways. + /// + /// It's equivalent to the corresponding `From` impl, however this version is + /// friendlier for type inference. + pub fn into_option(self) -> Option { + self.into() + } } impl ConditionallySelectable for CtOption { @@ -974,3 +987,21 @@ impl ConstantTimeLess for cmp::Ordering { (a as u8).ct_lt(&(b as u8)) } } + +/// Wrapper type which implements an optimization barrier for all accesses. +#[derive(Clone, Copy, Debug)] +pub struct BlackBox(T); + +impl BlackBox { + /// Constructs a new instance of `BlackBox` which will wrap the specified value. + /// + /// All access to the inner value will be mediated by a `black_box` optimization barrier. + pub const fn new(value: T) -> Self { + Self(value) + } + + /// Read the inner value, applying an optimization barrier on access. + pub fn get(self) -> T { + black_box(self.0) + } +} diff --git a/tests/mod.rs b/tests/mod.rs index f6b3982..888b9d0 100644 --- a/tests/mod.rs +++ b/tests/mod.rs @@ -58,7 +58,7 @@ macro_rules! generate_integer_conditional_select_tests { let x: $t = 0; // all 0 bits let y: $t = !0; // all 1 bits - assert_eq!(<$t>::conditional_select(&x, &y, 0.into()), 0); + assert_eq!(<$t>::conditional_select(&x, &y, 0.into()), x); assert_eq!(<$t>::conditional_select(&x, &y, 1.into()), y); let mut z = x; @@ -423,3 +423,10 @@ 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); } + +#[test] +fn black_box_round_trip() { + let n = 42u64; + let black_box = BlackBox::new(n); + assert_eq!(n, black_box.get()); +}