Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions docs/HARDENING_LIBSODIUM.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Applying IMAGEHARDER-style hardening to libsodium

The same practices used in IMAGEHARDER (bounded inputs, explicit error mapping, metrics, and sandboxing) can be mirrored when embedding
[libsodium](https://github.com/jedisct1/libsodium) for cryptographic workloads. This note outlines a secure-integration profile
suitable for Xen domU/CI builders.

## Goals
- Stable, minimal FFI boundary with explicit error types.
- Deterministic builds that pin versions and compiler flags.
- Isolation of key material and misuse-resistant APIs.
- Observability: metrics, tamper-evident audit logs, and build provenance.

## Build strategy
- **Version pinning**: vendor a known-good libsodium release and verify with `sha256sum` before build.
- **Compiler flags**: prefer `-fstack-protector-strong -D_FORTIFY_SOURCE=3 -fPIC -O2 -pipe -fno-plt` plus
`-fstack-clash-protection` on supported toolchains. Enable `-fcf-protection=full` for CET-capable targets.
- **Linking**: prefer static linking in minimal domU images; use `RUSTFLAGS="-C target-feature=+crt-static"` when pairing with
Rust consumers to avoid dynamic search-path issues.
- **Reproducibility**: capture build metadata (compiler, flags, git SHA) in an SBOM; wire `generate-sbom.sh` as part of CI.

## FFI surface design
- Wrap each libsodium primitive in narrow functions that accept length-checked slices and return `Result<Output, CryptoError>`.
- Prohibit raw pointer exposure; translate C error codes into typed Rust errors with context.
- Add zeroization hooks (`zeroize::Zeroize`) for buffers that carry keys, nonces, or secrets.
- Enforce domain separation by tagging APIs per purpose (e.g., `Auth`, `Aead`, `Kdf`) to discourage misuse.

## Runtime safeguards
- **Initialization guard**: perform a one-time `sodium_init()` with failure handling before exposing any primitive.
- **Key lifecycle**: keep keys in guarded memory (e.g., `secrecy::SecretVec`) and avoid serialization. Provide key-generation
helpers that default to high-entropy RNGs (libsodium already defaults to `randombytes_buf`), and expose optional hardware RNG
seeding for dom0/domU if available.
- **Parameter validation**: validate nonce sizes, tag lengths, and buffer boundaries before calling into libsodium.
- **Constant-time expectations**: document which APIs are constant-time and block callers from using them for unrelated purposes
(e.g., no generic equality on MACs without constant-time compare).

## Observability and policy
- Emit Prometheus counters for successes/failures per primitive (without leaking secrets) and gauge unexpected parameter
rejections.
- Capture audit logs that include operation type, key identifier, and policy decisions; ship logs over mTLS to central storage.
- Include a policy module that enforces approved cipher suites (e.g., `xchacha20poly1305_ietf`) and blocks deprecated ones.

## Testing and validation
- **KATs**: integrate libsodium's known-answer tests and add fuzzers around the FFI boundary to catch length/parameter issues.
- **Memory checks**: run under `valgrind`/`ASan` in CI; ensure zeroization paths are covered.
- **Cross-platform**: validate builds under Xen domU, containers, and bare-metal to confirm consistent instruction sets and
`RDRAND` availability.

## Example integration sketch

```rust
// Pseudocode illustrating a narrow AEAD wrapper
pub fn seal(key: &SecretVec<u8>, nonce: &[u8; crypto_aead_xchacha20poly1305_ietf_NPUBBYTES],
aad: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, CryptoError> {
if plaintext.len() > MAX_MESSAGE || aad.len() > MAX_AAD {
return Err(CryptoError::InputTooLarge);
}

let mut ciphertext = vec![0u8; plaintext.len() + TAG_LEN];
let mut clen = 0usize;
let rc = unsafe {
crypto_aead_xchacha20poly1305_ietf_encrypt(
ciphertext.as_mut_ptr(),
&mut clen,
plaintext.as_ptr(),
plaintext.len() as u64,
aad.as_ptr(),
aad.len() as u64,
std::ptr::null(),
nonce.as_ptr(),
key.expose_secret().as_ptr(),
)
};

if rc != 0 {
return Err(CryptoError::EncryptionFailed);
}

ciphertext.truncate(clen);
Ok(ciphertext)
}
```

## Operational checklist
- Verify SBOM provenance for each release and store alongside build artifacts.
- Rotate keys on a fixed schedule; enforce non-reuse of nonces via per-key counters.
- Run `cargo deny` (or equivalent) to flag transitive CVEs in Rust bindings.
- Add a chaos test that simulates allocator failures to ensure safe unwinding.

Following this pattern mirrors IMAGEHARDER's focus on bounded inputs, explicit error handling, and strong observability—adapted to the
cryptographic domain served by libsodium.
65 changes: 65 additions & 0 deletions docs/INTEGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Integration guide

This guide shows how to embed IMAGEHARDER as a Git submodule and call the hardened decoder API from a parent project.

## 1) Add the repository as a submodule
```bash
git submodule add https://github.com/SWORDIntel/IMAGEHARDER.git external/imageharder
git submodule update --init --recursive
```

## 2) Wire the crate into your workspace
Add the crate using a path dependency so the parent project builds against the submodule source.

```toml
# parent-project/Cargo.toml
[dependencies]
image_harden = { path = "external/imageharder/image_harden" }
```

If you keep a Cargo workspace, also append the crate to `members`:
```toml
[workspace]
members = [
"image_harden", # if IMAGEHARDER lives at repo root
"external/imageharder/image_harden"
]
```

## 3) Call the public API
Use the hardened helpers directly. Each decoder returns either raw bytes (images/video) or `AudioData` for audio formats.

```rust
use image_harden::decode_png;

fn decode_png(bytes: &[u8]) -> Result<Vec<u8>, image_harden::ImageHardenError> {
decode_png(bytes)
}
```

## 4) Feature flags
Optional formats are disabled by default to keep builds lean. Enable only what you need in the dependency declaration:

```toml
[dependencies]
image_harden = { path = "external/imageharder/image_harden", features = ["avif", "jxl", "tiff", "openexr", "icc", "exif"] }
```

## 5) Metrics (optional)
The crate ships a tiny Prometheus exporter to surface decode metrics.

```rust
// Expose metrics on 0.0.0.0:9898
image_harden::metrics_server::start_metrics_server("0.0.0.0:9898")?;
```

## 6) Updating the submodule
Pull upstream changes and propagate to your workspace:
```bash
git submodule update --remote external/imageharder
cd external/imageharder && git checkout <tag-or-commit>
```

## Notes for Xen domU/CI builders
- The build is pure Rust by default; C-based optional codecs (e.g., libavif) require system headers and are isolated behind feature flags.
- The CLI entrypoint (`image_harden_cli`) exercises the same hardened limits as the library for parity testing.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
18 changes: 18 additions & 0 deletions docs/archive/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Archived root documents

The following documents were moved out of the repository root to keep the entrypoint clean for submodule consumers. Content is unchanged:

- `ADDITIONAL_FORMATS.md`
- `AUDIO_HARDENING.md`
- `COCKPIT_INTEGRATION.md`
- `KERNEL_BUILD.md`
- `LOAD_TESTING.md`
- `METEOR_LAKE_BUILD.md`
- `PRODUCTION_DEPLOYMENT.md`
- `PR_DESCRIPTION.md`
- `QUICKSTART.md`
- `SECURITY_ARCHITECTURE.md`
- `VIDEO_HARDENING.md`
- `mission.md`

Refer to these files for the original deep-dives on platform-specific builds, production deployment, and project background.
File renamed without changes.
File renamed without changes.
File renamed without changes.
159 changes: 159 additions & 0 deletions image_harden/src/api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
//! Public API surface for embedding IMAGEHARDER as a library.
//! This module wraps the individual decoders into a small, stable interface
//! that parent projects can depend on when the repository is consumed as a
//! Git submodule.

use crate::{
decode_flac, decode_gif, decode_heif, decode_jpeg, decode_mp3, decode_png, decode_svg,
decode_video, decode_vorbis, decode_webp, AudioData, ImageHardenError,
};

#[cfg(feature = "avif")]
use crate::formats::avif::decode_avif;
#[cfg(feature = "exif")]
use crate::formats::exif::validate_exif;
#[cfg(feature = "openexr")]
use crate::formats::exr::decode_exr;
#[cfg(feature = "icc")]
use crate::formats::icc::validate_icc_profile;
#[cfg(feature = "jxl")]
use crate::formats::jxl::decode_jxl;
#[cfg(feature = "tiff")]
use crate::formats::tiff::decode_tiff;

/// Supported media types for the unified decoder entrypoint.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MediaFormat {
Png,
Jpeg,
Gif,
WebP,
Heif,
Svg,
#[cfg(feature = "avif")]
Avif,
#[cfg(feature = "jxl")]
JpegXl,
#[cfg(feature = "tiff")]
Tiff,
#[cfg(feature = "openexr")]
OpenExr,
AudioMp3,
AudioVorbis,
AudioFlac,
VideoContainer,
}

/// Decoder output variants.
#[derive(Debug, Clone)]
pub enum DecodedMedia {
Image(Vec<u8>),
Audio(AudioData),
Video(Vec<u8>),
}

/// Optional knobs for decoding. Currently only video uses an option
/// to specify the sandboxed WASM path.
#[derive(Debug, Default, Clone)]
pub struct DecoderOptions {
pub video_wasm_path: Option<String>,
}

/// Convenience wrapper that exposes a minimal surface area for downstream
/// consumers. Use `decode` for sensible defaults or `decode_with_options` to
/// pass explicit configuration.
pub struct HardenedDecoder;

impl HardenedDecoder {
/// Returns the crate version to make it easy to verify the linked build.
pub fn version() -> &'static str {
env!("CARGO_PKG_VERSION")
}

/// Decode using defaults.
pub fn decode(format: MediaFormat, data: &[u8]) -> Result<DecodedMedia, ImageHardenError> {
Self::decode_with_options(format, data, &DecoderOptions::default())
}

/// Decode with explicit options (e.g., WASM path for video validation).
pub fn decode_with_options(
format: MediaFormat,
data: &[u8],
options: &DecoderOptions,
) -> Result<DecodedMedia, ImageHardenError> {
match format {
MediaFormat::Png => decode_png(data).map(DecodedMedia::Image),
MediaFormat::Jpeg => decode_jpeg(data).map(DecodedMedia::Image),
MediaFormat::Gif => decode_gif(data).map(DecodedMedia::Image),
MediaFormat::WebP => decode_webp(data).map(DecodedMedia::Image),
MediaFormat::Heif => decode_heif(data).map(DecodedMedia::Image),
MediaFormat::Svg => decode_svg(data).map(DecodedMedia::Image),
#[cfg(feature = "avif")]
MediaFormat::Avif => decode_avif(data).map(DecodedMedia::Image),
#[cfg(feature = "jxl")]
MediaFormat::JpegXl => decode_jxl(data).map(DecodedMedia::Image),
#[cfg(feature = "tiff")]
MediaFormat::Tiff => decode_tiff(data).map(DecodedMedia::Image),
#[cfg(feature = "openexr")]
MediaFormat::OpenExr => decode_exr(data).map(DecodedMedia::Image),
MediaFormat::AudioMp3 => decode_mp3(data).map(DecodedMedia::Audio),
MediaFormat::AudioVorbis => decode_vorbis(data).map(DecodedMedia::Audio),
MediaFormat::AudioFlac => decode_flac(data).map(DecodedMedia::Audio),
MediaFormat::VideoContainer => {
decode_video(data, options.video_wasm_path.as_deref().unwrap_or(""))
.map(DecodedMedia::Video)
}
}
}
}

/// Report which formats are available in the current build based on feature
/// flags. Useful for capability advertisement in parent applications.
pub fn supported_formats() -> Vec<&'static str> {
let mut formats = vec![
"png", "jpeg", "gif", "webp", "heif", "svg", "mp3", "vorbis", "flac", "video",
];

#[cfg(feature = "avif")]
{
formats.push("avif");
}
#[cfg(feature = "jxl")]
{
formats.push("jpegxl");
}
#[cfg(feature = "tiff")]
{
formats.push("tiff");
}
#[cfg(feature = "openexr")]
{
formats.push("openexr");
}
#[cfg(feature = "icc")]
{
formats.push("icc");
}
#[cfg(feature = "exif")]
{
formats.push("exif");
}

formats
}

/// Validate metadata payloads without decoding image data.
#[cfg(any(feature = "icc", feature = "exif"))]
pub fn validate_metadata(data: &[u8]) -> Result<(), ImageHardenError> {
#[cfg(feature = "icc")]
{
validate_icc_profile(data)?;
}

#[cfg(feature = "exif")]
{
validate_exif(data)?;
}

Ok(())
}
Loading
Loading