Module security_guide

Module security_guide 

Source
Expand description

§Security Hardening Guide

§Overview

The byte-array-ops library provides two distinct operational modes for handling byte arrays, designed to balance security and convenience based on your use case. These modes are mutually exclusive and determined at compile time through feature flags.

§Hardened Mode vs Insecure Mode

§Hardened Mode

Activation: Any sec_harden_* feature flag enabled

Philosophy: Security-first approach that restricts API surface to prevent accidental data leakage and improper sanitization of sensitive data.

What it restricts:

  • No Deref to Vec<u8>: Prevents accidental exposure of Vec methods that could transfer ownership or leak data
  • No to_vec() method: Prevents ownership transfer that bypasses sanitization
  • No Into<Vec<u8>>: Prevents conversion that could leave sensitive data unsanitized
  • truncate() panics: Blocks operations that cause reallocation and potential data remnance in deallocated memory
  • Display/Debug restricted: In release builds, only shows ByteArray length: N instead of actual byte data

What it provides:

  • Explicit methods: len(), is_empty(), get() - controlled access without Vec exposure
  • Controlled iteration: iter() and iter_mut() methods for safe byte access
  • Debug-build visibility: Full data display in debug builds for development convenience
  • Type safety: Strong guarantees that sensitive data won’t leak through unintended pathways

Use cases:

  • Cryptographic keys and material
  • Passwords, PINs, and secrets
  • Authentication tokens and session IDs
  • Any data where leakage has security implications

§Insecure Mode

Activation: Default (no hardening features enabled)

Philosophy: Convenience-first approach that provides full Vec<u8> compatibility for general-purpose byte manipulation.

What it provides:

  • Full Deref to Vec<u8>: All Vec methods available (push, pop, reserve, capacity, etc.)
  • Ownership transfer: to_vec() and Into<Vec<u8>> for seamless conversion
  • Working truncate(): Standard Vec::truncate behavior
  • Full Display/Debug: Complete byte data exposure in all builds
  • Maximum compatibility: Drop-in replacement for Vec<u8> in most scenarios

Why “insecure”:

  • User is responsible for proper data sanitization
  • Easy to accidentally leak data through logs, Debug output, or error messages
  • Reallocation operations can leave sensitive data in freed memory
  • No compile-time enforcement of security practices

Use cases:

  • General byte manipulation and processing
  • Network protocol implementation
  • File I/O and serialization
  • Performance-critical code where data is not sensitive
  • Prototyping and development

§Feature Flag Reference

§Individual Hardening Flags

These flags enable specific security features:

  • sec_harden_zeroize: Memory wiping on drop (planned v0.3.0)

    • Ensures sensitive data is zeroed when ByteArray goes out of scope
    • Prevents data from persisting in memory after use
  • sec_harden_const_time_ops: Constant-time comparisons (planned v0.3.0)

    • Prevents timing attacks on sensitive comparisons
    • Uses the subtle crate for constant-time operations
  • sec_harden_memlock: Prevent swapping to disk (planned v0.3.0)

    • Locks memory pages to prevent swap file leakage
    • Platform-specific implementation
  • sec_harden_memencrypt: In-memory encryption (planned v0.3.0)

    • Encrypts data while in memory
    • Combines zeroize and memlock features

§Convenience Tier Flags

These flags activate multiple hardening features at once:

  • sec_basic_hardening:

    • Activates: sec_harden_zeroize
    • Recommended for: Most sensitive data handling
  • sec_enhanced_hardening:

    • Activates: sec_basic_hardening + sec_harden_const_time_ops + sec_harden_memlock
    • Recommended for: Cryptographic material and high-security applications
  • sec_maximum_hardening:

    • Activates: All hardening features
    • Recommended for: Maximum security requirements

§API Differences Between Modes

§Available in Both Modes

These methods work identically in both modes:

use byte_array_ops::ByteArray;

// Construction
let _arr1 = ByteArray::new();
let _arr2 = ByteArray::with_capacity(10);
let _arr3 = ByteArray::from_hex("deadbeef").unwrap();
let _arr4 = ByteArray::from_bin("1010101").unwrap();
let _arr5 = ByteArray::init_zeros(5);
let arr = ByteArray::init_value(0xff, 3);

// Basic access
let _bytes = arr.as_bytes();      // Returns &[u8]
let _byte = arr[0];               // Indexing
let mut arr_mut = arr.clone();
let _mut_bytes = arr_mut.as_mut();  // Mutable slice access

// Type conversions
// From<Vec<u8>>, From<&[u8]>, From<[u8; N]>
let _from_vec: ByteArray = vec![0x01, 0x02].into();
let _from_slice: ByteArray = [0x01, 0x02].as_slice().into();
let _from_array: ByteArray = [0x01, 0x02, 0x03].into();

§Hardened Mode Only

Methods that exist only in hardened mode:

arr.len();           // Explicit length access
arr.is_empty();      // Explicit emptiness check
let _byte = arr.get(0);      // Checked element access
let _iter = arr.iter();          // Immutable iterator
// let _iter_mut = _arr_mut.iter_mut();      // Mutable iterator (requires hardening features)

// This panics in hardened mode:
// _arr_mut.truncate(2);     // unimplemented!()

§Insecure Mode Only

Methods and traits that exist only in insecure mode:

use byte_array_ops::ByteArray;
use byte_array_ops::try_hex;

let arr = ByteArray::default();
let _arr2 = arr.to_vec();                // Ownership transfer

let arr = ByteArray::default();
let _v: Vec<u8> = arr.into(); // Into conversion
let mut arr = try_hex!("deadbeef").expect("failed to convert");
arr.truncate(2);             // Works normally

// Through Deref to Vec<u8>:
let _len = arr.len();           // Via Deref
let _empty = arr.is_empty();      // Via Deref
let _cap = arr.capacity();      // Via Deref
let _byte = arr.get(0);      // Via Deref
let _first = arr.first();         // Via Deref
let _last = arr.last();          // Via Deref
let _iter = arr.iter();          // Via Deref to Vec
// Note: iter_mut() requires DerefMut which is not implemented
// ... all other Vec methods

§Migration Between Modes

§Switching from Insecure to Hardened

Breaking changes:

  1. No more to_vec() or Into<Vec<u8>>:
use byte_array_ops::ByteArray;
let arr = ByteArray::from_hex("deadbeef").unwrap();

// OLD (insecure):
// let v: Vec<u8> = arr.to_vec();
// let v: Vec<u8> = arr.into();

// NEW (hardened):
// Use as_bytes() for borrowing:
let slice: &[u8] = arr.as_bytes();
// Or iterate and collect:
let v: Vec<u8> = arr.iter().copied().collect();
  1. No more Deref to Vec:
use byte_array_ops::ByteArray;

// OLD (insecure):
// let cap = arr.capacity();  // Via Deref
// arr.reserve(10);           // Via Deref

// NEW (hardened):
// Capacity/reserve not available - plan memory upfront:
let arr = ByteArray::with_capacity(100);
  1. No more truncate():
use byte_array_ops::ByteArray;
let arr = ByteArray::from_hex("deadbeefcafe").unwrap();

// OLD (insecure):
// arr.truncate(10);

// NEW (hardened):
// Reconstruct or use different approach:
let new_arr: ByteArray = arr.iter()
    .take(4)
    .copied()
    .collect::<Vec<u8>>()
    .into();
  1. Display/Debug changes:
use byte_array_ops::ByteArray;
let arr = ByteArray::from_hex("deadbeef").unwrap();

// Both modes support Debug/Display
println!("Array: {:?}", arr);

// In hardened release builds: shows "ByteArray length: 4"
// In hardened debug builds: shows full data
// In insecure mode: always shows full data

§Switching from Hardened to Insecure

Generally straightforward - just remove hardening feature flags. Your code will gain access to more methods.

Considerations:

  • Review all uses of truncate() - ensure proper cleanup if sensitive
  • Audit Display/Debug usage - data will now leak in logs
  • Consider if Deref access introduces security risks
  • Verify no implicit conversions to Vec expose sensitive data

§Security Considerations

§Data Remnance

Problem: When memory is reallocated (grow, shrink, truncate), old data may remain in freed memory pages.

Hardened approach:

  • Blocks operations that cause reallocation (truncate() panics)
  • Future: zeroize feature will clear memory on drop

Insecure approach:

  • Allows reallocation operations
  • User responsible for manual cleanup if needed

§Logging and Debug Output

Problem: Debug output and logging can expose sensitive data to log files, terminal history, or monitoring systems.

Hardened approach:

  • Release builds show only ByteArray length: N
  • Debug builds show data for development convenience
  • Requires explicit action to expose data

Insecure approach:

  • Full data always visible in Debug/Display
  • Easy to accidentally log sensitive information

§Ownership Transfer

Problem: Converting to Vec<u8> and passing ownership means you lose control over how data is sanitized.

Hardened approach:

  • No to_vec() or Into<Vec<u8>> conversion
  • Forces borrowing via as_bytes() or explicit iteration
  • Maintains control over data lifecycle

Insecure approach:

  • Easy conversion to Vec<u8>
  • Once converted, no security guarantees
  • User must track and sanitize manually

§Iterator Safety

Problem: Mutable iterators can modify data in place without proper cleanup tracking.

Hardened approach:

  • Provides iter() and iter_mut() explicitly
  • Future: may add Drop impl to zero unconsumed bytes

Insecure approach:

  • Access to Vec’s iterators through Deref
  • No special safety guarantees

§Implementation Details

§File Structure

src/core/trust/
├── hardened/
│   ├── mod.rs       # Module orchestration
│   ├── vec.rs       # len, is_empty, get, truncate (panics)
│   ├── iter.rs      # iter, iter_mut implementations
│   └── display.rs   # Display, Debug (length-only in release)
└── insecure/
    ├── mod.rs       # Module orchestration
    ├── vec.rs       # to_vec, truncate, From, Deref implementations
    └── display.rs   # Display, Debug (full data always)

§Conditional Compilation

The trust module uses mutual exclusion to ensure only one mode compiles:

// Simplified view - see src/lib.rs for actual implementation

Guarantees:

  • Only one mode compiles at a time
  • No runtime overhead from conditional checks
  • Clear compile-time errors if methods not available

§Iterator Types

Iterator types (ByteArrayIter, ByteArrayIterMut) are shared between modes:

  • Defined in: core/iter.rs
  • Used by: Both hardened and insecure modes
  • Hardened mode: iter() methods in hardened/iter.rs
  • Insecure mode: Access through Deref to Vec

§Examples

§Hardened Mode Example

use byte_array_ops::ByteArray;

// Enable hardening in Cargo.toml:
// byte-array-ops = { version = "*", features = ["sec_basic_hardening"] }

fn process_key(key_hex: &str) -> Result<(), Box<dyn std::error::Error>> {
    let key = ByteArray::from_hex(key_hex)?;

    // ✓ Allowed: Explicit access
    println!("Key length: {}", key.len());

    // ✓ Allowed: Iteration
    for byte in key.iter() {
        let _ = byte; // process byte
    }

    // ✓ Allowed: Borrowing
    let slice: &[u8] = key.as_bytes();
    let _ = slice;

    // ✗ Not allowed in hardened mode (would panic):
    // let mut key = key;
    // key.truncate(16);

    // ✗ Not allowed: No such method in hardened mode
    // let v = key.to_vec();

    // ✗ Not allowed: No Deref to Vec in hardened mode
    // let cap = key.capacity();

    Ok(())
}

// Test it works

§Insecure Mode Example

use byte_array_ops::ByteArray;

// Default mode (no features needed)

fn process_data(data_hex: &str) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
    let mut arr = ByteArray::from_hex(data_hex)?;

    // ✓ All operations available
    if arr.len() > 4 {
        arr.truncate(4);
    }

    // ✓ Deref access
    println!("Capacity: {}", arr.capacity());

    // ✓ Ownership transfer
    let vec: Vec<u8> = arr.to_vec();

    Ok(vec)
}

// Test it works

§Future Enhancements

§Version 0.3.0 (Planned)

  • Zeroize on drop: Automatic memory clearing when ByteArray is dropped
  • Constant-time operations: Timing-attack resistant comparisons
  • Memory locking: Prevent swapping sensitive data to disk
  • Memory encryption: Encrypt data while in memory

§Under Consideration

  • Drop impl for iterators: Zero unconsumed bytes when iterator is dropped
  • SecureReallocationProvider trait: Safe reallocation with guaranteed cleanup
  • Audit logging: Optional compile-time tracking of sensitive data access
  • Type-level security markers: Compile-time distinction between sensitive and non-sensitive ByteArrays

§Best Practices

  1. Choose the right mode for your use case:

    • Sensitive data → Hardened mode
    • General processing → Insecure mode
  2. Plan capacity upfront in hardened mode:

    • Use ByteArray::with_capacity() to avoid reallocation
  3. Avoid Debug/Display with sensitive data:

    • Even in hardened mode, debug builds show data
    • Never log sensitive ByteArrays in production
  4. Audit conversion points:

    • Review all as_bytes() calls for potential leakage
    • Check iterator usage for proper cleanup
  5. Test both modes if supporting both:

    • CI should test with and without hardening features
    • Document which mode your library expects
  6. Document security requirements:

    • Tell users which mode to use
    • Explain implications of choosing insecure mode

§References