From 26f77924f8c8ffbe91510b92fc2f5637b0173e06 Mon Sep 17 00:00:00 2001 From: Amin Faez Date: Mon, 16 Dec 2024 13:01:49 +0100 Subject: [PATCH] docs(constant-time): add docs, examples and safety notices --- constant-time/src/compare.rs | 39 ++++++++++++++++++++++++++++++++-- constant-time/src/increment.rs | 12 +++++++++-- constant-time/src/lib.rs | 27 +++++++++++++++++++++++ constant-time/src/xor.rs | 15 +++++++++++-- 4 files changed, 87 insertions(+), 6 deletions(-) diff --git a/constant-time/src/compare.rs b/constant-time/src/compare.rs index 0e1db0a0..337081ea 100644 --- a/constant-time/src/compare.rs +++ b/constant-time/src/compare.rs @@ -2,14 +2,29 @@ use core::ptr; -/// Little endian memcmp version of quinier/memsec -/// https://github.com/quininer/memsec/blob/bbc647967ff6d20d6dccf1c85f5d9037fcadd3b0/src/lib.rs#L30 +/// Little endian memcmp version of [quinier/memsec](https://github.com/quininer/memsec/blob/bbc647967ff6d20d6dccf1c85f5d9037fcadd3b0/src/lib.rs#L30) /// /// # Panic & Safety /// /// Both input arrays must be at least of the indicated length. /// /// See [std::ptr::read_volatile] on safety. +/// +/// # Examples +/// ``` +/// let a = [1, 2, 3, 4]; +/// let b = [1, 2, 3, 4]; +/// let c = [1, 2, 2, 5]; +/// let d = [1, 2, 2, 4]; +/// +/// unsafe { +/// use rosenpass_constant_time::memcmp_le; +/// assert_eq!(memcmp_le(a.as_ptr(), b.as_ptr(), 4), 0); +/// assert!(memcmp_le(a.as_ptr(), c.as_ptr(), 4) < 0); +/// assert!(memcmp_le(a.as_ptr(), d.as_ptr(), 4) > 0); +/// assert_eq!(memcmp_le(a.as_ptr(), b.as_ptr(), 2), 0); +/// } +/// ``` #[inline(never)] pub unsafe fn memcmp_le(b1: *const u8, b2: *const u8, len: usize) -> i32 { let mut res = 0; @@ -77,3 +92,23 @@ pub fn compare(a: &[u8], b: &[u8]) -> i32 { assert!(a.len() == b.len()); unsafe { memcmp_le(a.as_ptr(), b.as_ptr(), a.len()) } } + +#[cfg(test)] +mod tests { + use crate::compare::memcmp_le; + + #[test] + fn memcmp_le_test() { + let a = [1, 2, 3, 4]; + let b = [1, 2, 3, 4]; + let c = [1, 2, 2, 5]; + let d = [1, 2, 2, 4]; + + unsafe { + assert_eq!(memcmp_le(a.as_ptr(), b.as_ptr(), 4), 0); + assert!(memcmp_le(a.as_ptr(), c.as_ptr(), 4) < 0); + assert!(memcmp_le(a.as_ptr(), d.as_ptr(), 4) > 0); + assert_eq!(memcmp_le(a.as_ptr(), b.as_ptr(), 2), 0); + } + } +} diff --git a/constant-time/src/increment.rs b/constant-time/src/increment.rs index 7483c9f3..1abc2ae3 100644 --- a/constant-time/src/increment.rs +++ b/constant-time/src/increment.rs @@ -6,8 +6,16 @@ use core::hint::black_box; /// and increment that integer. /// /// # Leaks -/// TODO: mention here if this function leaks any information, see -/// +/// This function may leak timing information in the following ways: +/// +/// - The function execution time is linearly proportional to the input length +/// - The number of carry operations that occur may affect timing slightly +/// - Memory access patterns are sequential and predictable +/// +/// The carry operation timing variation is mitigated through the use of black_box, +/// but the linear scaling with input size is inherent to the operation. +/// These timing characteristics are generally considered acceptable for most +/// cryptographic counter implementations. /// /// ## Tests /// For discussion on how to ensure the constant-time execution of this function, see diff --git a/constant-time/src/lib.rs b/constant-time/src/lib.rs index 683368c7..dd068898 100644 --- a/constant-time/src/lib.rs +++ b/constant-time/src/lib.rs @@ -7,6 +7,32 @@ //! ## TODO //! Figure out methodology to ensure that code is actually constant time, see //! +//! +//! # Examples +//! +//! ```rust +//! use rosenpass_constant_time::{memcmp, compare}; +//! +//! let a = [1, 2, 3, 4]; +//! let b = [1, 2, 3, 4]; +//! let c = [1, 2, 3, 5]; +//! +//! // Compare for equality +//! assert!(memcmp(&a, &b)); +//! assert!(!memcmp(&a, &c)); +//! +//! // Compare lexicographically +//! assert_eq!(compare(&a, &c), -1); // a < c +//! assert_eq!(compare(&c, &a), 1); // c > a +//! assert_eq!(compare(&a, &b), 0); // a == b +//! ``` +//! +//! # Security Notes +//! +//! While these functions aim to be constant-time, they may leak timing information in some cases: +//! +//! - Length mismatches between inputs are immediately detectable +//! - Execution time scales linearly with input size mod compare; mod increment; @@ -14,6 +40,7 @@ mod memcmp; mod xor; pub use compare::compare; +pub use compare::memcmp_le; pub use increment::increment; pub use memcmp::memcmp; pub use xor::xor; diff --git a/constant-time/src/xor.rs b/constant-time/src/xor.rs index ed247219..779e3103 100644 --- a/constant-time/src/xor.rs +++ b/constant-time/src/xor.rs @@ -5,12 +5,23 @@ use rosenpass_to::{with_destination, To}; /// Xors the source into the destination /// +/// Performs a constant-time XOR operation between two byte slices +/// +/// Takes a source slice and XORs it with the destination slice in-place using the +/// rosenpass_to trait for destination management. +/// /// # Panics /// If source and destination are of different sizes. /// /// # Leaks -/// TODO: mention here if this function leaks any information, see -/// +/// This function may leak timing information in the following ways: +/// +/// - The function execution time is linearly proportional to the input length +/// - Length mismatches between source and destination are immediately detectable via panic +/// - Memory access patterns follow a predictable sequential pattern +/// +/// These leaks are generally considered acceptable in most cryptographic contexts +/// as they don't reveal information about the actual content being XORed. /// /// ## Tests /// For discussion on how to ensure the constant-time execution of this function, see