Compare commits

...

9 Commits

Author SHA1 Message Date
JohnE 919d4bcc95 MOD: removed debug code 2026-03-13 19:27:34 -07:00
JohnE c9fd4542d7 FIX: build iOS fixes, parse the CMake-generated options.h to extract all #define directives 2026-03-13 19:26:50 -07:00
JohnE b1873b27be FIX: dummy forgot to export tests 2026-03-11 21:53:15 -07:00
JohnE 3ea964d3d9 MOD: cleanup move tests out of reg classes 2026-03-11 21:34:31 -07:00
JohnE cc72bb8919 FIX: test data was not accurate for AEAD 2026-03-11 21:33:48 -07:00
JohnE c82d472004 MOD: fix taskfile for default 2026-03-11 16:24:13 -07:00
JohnE 58389ad685 NEW: build scripts 2026-03-11 16:12:55 -07:00
JohnE 68836cfff4 MOD: formatting 2026-03-11 16:12:12 -07:00
JohnE 971f1aeaaa MOD: fixed warnings 2026-03-11 01:47:24 -07:00
21 changed files with 2079 additions and 786 deletions

10
.vscode/settings.json vendored
View File

@ -1,4 +1,8 @@
{ {
"[rust]": {
"editor.formatOnSave": false,
"editor.formatOnPaste": false
},
"cmake.configureOnOpen": false, "cmake.configureOnOpen": false,
"dart.analysisExcludedFolders": [ "dart.analysisExcludedFolders": [
"flutter_src", "flutter_src",
@ -15,6 +19,10 @@
"AllBlockComments" "AllBlockComments"
], ],
"chat.tools.terminal.autoApprove": { "chat.tools.terminal.autoApprove": {
"cargo search": true "cargo search": true,
"/^source \"\\$HOME/\\.cargo/env\" && cd /Volumes/LUM/source/letusmsg_proj/app/lum_ccc_rust && python3 -c \"\n# Self-test AES-GCM vector\nkey = 'feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308'\nnonce = 'cafebabefacedbaddecaf888'\naad = ''\npt = 'd9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255'\nct_tag = '522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662898015ad'\n\nprint\\('Self-test AES-GCM:'\\)\nprint\\(f' PT bytes: \\{len\\(pt\\)//2\\}'\\)\nprint\\(f' CT_TAG bytes: \\{len\\(ct_tag\\)//2\\}'\\)\nprint\\(f' Expected \\(PT \\+ 16 tag\\): \\{len\\(pt\\)//2 \\+ 16\\}'\\)\nprint\\(f' ct_tag IS ciphertext-only \\(no tag\\): \\{len\\(ct_tag\\)//2 == len\\(pt\\)//2\\}'\\)\nprint\\(\\)\n\n# Conformance AES-GCM vector \\(different: has AAD, shorter PT\\)\npt2 = 'd9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39'\nct_tag2 = '522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f66276fc6ece0f4e1768cddf8853bb2d551b'\nprint\\('Conformance AES-GCM TC16:'\\)\nprint\\(f' PT bytes: \\{len\\(pt2\\)//2\\}'\\)\nprint\\(f' CT_TAG bytes: \\{len\\(ct_tag2\\)//2\\}'\\)\nprint\\(f' CT_TAG == PT \\+ 16: \\{len\\(ct_tag2\\)//2 == len\\(pt2\\)//2 \\+ 16\\}'\\)\nprint\\(\\)\n\n# Self-test ChaCha vector\npt3 = '4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f6620273939'\nct_tag3 = 'd31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d26586cec64b6116'\nprint\\('Self-test ChaCha:'\\)\nprint\\(f' PT bytes: \\{len\\(pt3\\)//2\\}'\\)\nprint\\(f' CT_TAG bytes: \\{len\\(ct_tag3\\)//2\\}'\\)\nprint\\(f' CT_TAG == PT \\+ 16: \\{len\\(ct_tag3\\)//2 == len\\(pt3\\)//2 \\+ 16\\}'\\)\nprint\\(f' Ratio: CT_TAG is \\{len\\(ct_tag3\\)//2 - len\\(pt3\\)//2\\} bytes longer than PT'\\)\n\"\n$/": {
"approve": true,
"matchCommandLine": true
}
} }
} }

370
README_DEV.rst Normal file
View File

@ -0,0 +1,370 @@
=============================================
CCC Rust — Developer Build Guide
=============================================
:Last Updated: 2026-03-11
This document covers everything needed to build, test, and cross-compile the
``ccc_rust`` workspace on a development machine.
----
Prerequisites
=============
The table below lists all required tools. Versions shown are known-good; newer
patch releases should work.
+------------------+---------------+--------------------------------------------------+
| Tool | Min Version | Purpose |
+==================+===============+==================================================+
| **Rust (rustup)**| stable 1.85+ | Compiler toolchain (pinned via ``rust-toolchain`` |
| | | ``.toml``) |
+------------------+---------------+--------------------------------------------------+
| **CMake** | 3.24+ | Builds vendored wolfSSL from source |
+------------------+---------------+--------------------------------------------------+
| **Clang / LLVM** | 14+ | Required by ``bindgen`` for FFI generation |
+------------------+---------------+--------------------------------------------------+
| **Git** | 2.30+ | Submodule management |
+------------------+---------------+--------------------------------------------------+
| **task** (opt) | 3.x | Local task runner (``Taskfile.yaml``) |
+------------------+---------------+--------------------------------------------------+
| **Android NDK** | r25+ | Cross-compile for Android (optional on macOS) |
| (optional) | | |
+------------------+---------------+--------------------------------------------------+
Installing Dependencies
-----------------------
macOS (Homebrew)
~~~~~~~~~~~~~~~~
.. code-block:: shell
# Rust (via rustup — manages toolchains automatically)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
# CMake and LLVM (clang for bindgen)
brew install cmake llvm
# Task runner (optional — for Taskfile.yaml)
brew install go-task
# Xcode Command Line Tools (required for iOS/macOS targets)
xcode-select --install
Linux (apt)
~~~~~~~~~~~
.. code-block:: shell
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
sudo apt update && sudo apt install -y cmake clang libclang-dev git
# Task runner (optional)
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin
Windows
~~~~~~~
.. code-block:: shell
# Install rustup from https://rustup.rs
# Install CMake from https://cmake.org/download/ (add to PATH)
# Install LLVM/Clang from https://releases.llvm.org/ (add to PATH)
# Install Visual Studio Build Tools (C++ workload)
Android NDK (all platforms, optional)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Required only for ``aarch64-linux-android`` / ``x86_64-linux-android`` targets.
.. code-block:: shell
# Via Android Studio SDK Manager, or:
sdkmanager --install "ndk;28.2.13676358"
# Set environment variables (add to your shell profile):
export ANDROID_NDK_HOME="$HOME/Library/Android/sdk/ndk/27.0.12077973"
export PATH="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin:$PATH"
# On Linux, replace darwin-x86_64 with linux-x86_64.
----
First-Time Setup
================
.. code-block:: shell
# Clone the repository
git clone <repo-url> ccc_rust && cd ccc_rust
# Initialise the wolfSSL submodule (required — build fails without it)
git submodule update --init --recursive
# Verify the toolchain (rustup reads rust-toolchain.toml automatically)
rustc --version # should print stable ≥ 1.85
cmake --version # should print ≥ 3.24
----
Building
========
Native Build (Host Machine)
----------------------------
.. code-block:: shell
cargo build --workspace # debug
cargo build --workspace --release # release (optimised)
Cross-Compile Targets
---------------------
Cargo aliases are defined in ``.cargo/config.toml``:
.. code-block:: shell
# Apple
cargo build-ios # aarch64-apple-ios
cargo build-ios-sim # aarch64-apple-ios-sim
cargo build-macos-arm64 # aarch64-apple-darwin
cargo build-macos-x64 # x86_64-apple-darwin
cargo build-all-apple # all three Apple targets at once
# Android (requires NDK — see above)
cargo build-android-arm64 # aarch64-linux-android
cargo build-android-x64 # x86_64-linux-android
# Release variants: append --release
cargo build-ios --release
cargo build-macos-arm64 --release
wolfSSL Build Overrides
~~~~~~~~~~~~~~~~~~~~~~~~
The ``build.rs`` script accepts two environment variables:
``WOLFSSL_SOURCE_DIR``
Override the wolfSSL source path (default: ``vendors/wolfssl``).
Useful in CI when the submodule is at a non-standard location.
``WOLFSSL_INSTALL_DIR``
Point to a pre-installed wolfSSL (must contain ``include/wolfssl/``
and ``lib/libwolfssl.a``). Skips the CMake build entirely.
Stub FFI (no C build)
~~~~~~~~~~~~~~~~~~~~~~
For type-checking without building wolfSSL (CI lint jobs, docs):
.. code-block:: shell
cargo check -p ccc-crypto-wolfssl --features stub_ffi
----
Testing
=======
.. code-block:: shell
# Unit tests (all crates)
cargo test --workspace
# Conformance test binary (NIST / RFC vectors)
cargo run -p ccc-conformance-tests
# Run a single test by name
cargo test --workspace test_name
# Clippy (lint — treat warnings as errors)
cargo clippy --workspace -- -D warnings
# Format check
cargo fmt --check
----
Build Artifacts
===============
All output lands under ``target/``. The directory structure is:
::
target/
├── debug/ ← native host debug
│ ├── conformance ← conformance test binary
│ ├── libccc_crypto_core.rlib ← core crate (Rust lib)
│ ├── libccc_crypto_wolfssl.rlib ← wolfSSL provider (Rust lib)
│ └── build/ccc-crypto-wolfssl-*/out/
│ ├── lib/libwolfssl.a ← wolfSSL static library
│ └── include/wolfssl/ ← generated wolfSSL headers
├── release/ ← native host release
│ └── (same layout as debug/)
├── aarch64-apple-ios/
│ └── debug/ | release/ ← iOS artifacts
├── aarch64-apple-darwin/
│ └── debug/ | release/ ← macOS ARM64 artifacts
├── x86_64-apple-darwin/
│ └── debug/ | release/ ← macOS x86_64 artifacts
├── aarch64-linux-android/
│ └── debug/ | release/ ← Android ARM64 artifacts
└── x86_64-linux-android/
└── debug/ | release/ ← Android x86_64 artifacts
Key Artifacts per Target
~~~~~~~~~~~~~~~~~~~~~~~~
+---------------------------------+--------------------------------------------------------------+
| Artifact | Path |
+=================================+==============================================================+
| Core Rust library | ``target/<triple>/<profile>/libccc_crypto_core.rlib`` |
+---------------------------------+--------------------------------------------------------------+
| wolfSSL provider library | ``target/<triple>/<profile>/libccc_crypto_wolfssl.rlib`` |
+---------------------------------+--------------------------------------------------------------+
| wolfSSL static C library | ``target/<triple>/<profile>/build/ccc-crypto-wolfssl-*/out/`` |
| | ``lib/libwolfssl.a`` |
+---------------------------------+--------------------------------------------------------------+
| Conformance test binary | ``target/<triple>/<profile>/conformance`` |
+---------------------------------+--------------------------------------------------------------+
Where ``<triple>`` is the Rust target triple (e.g. ``aarch64-apple-ios``)
and ``<profile>`` is ``debug`` or ``release``.
.. note::
These crates produce **rlib** (Rust library) output. The Milestone 2
bridge crate (in the separate ``ccc_cryptography`` repository) adds
``crate-type = ["cdylib", "staticlib"]`` to produce the ``.so`` /
``.dylib`` / ``.a`` artifacts consumed by Flutter.
----
Publish
=======
The Rust crypto library remains a pure Rust crate with no Flutter or FFI-specific code.
Option 1
--------
Option 1: Build from source via Cargokit (recommended for development)
* Cargokit compiles the Rust code at build time for each platform
* No precompiled binaries are needed
Option 2
--------
Option 2: Cargokit precompiled binaries via GitHub Releases (recommended for pub.dev)
* The Rust crate is compiled into platform-specific binaries (staticlib for iOS/macOS, cdylib for Android)
* Binaries are uploaded to GitHub Releases
* Cargokit fetches the correct binary at build time based on the target platform
* This allows pub.dev users to get native performance without needing Rust or a build environment
Workflow
~~~~~~~~
* CI builds the Rust crate for all 5 platform targets
* CI uploads the .so/.dylib/.dll/.framework to a GitHub Release tagged with the Cargo package hash
* When consumers run flutter build, Cargokit downloads the precompiled binary instead of compiling
* If no precompiled binary exists, it falls back to source build
* Pros: Fast builds for consumers, no Rust toolchain needed, no repo bloat, built into FRB, single repo
* Cons: Requires CI pipeline to build + upload binaries
* Best for: pub.dev releases, distributing to teams without Rust installed
----
Workspace Layout
================
::
ccc_rust/
├── Cargo.toml workspace manifest
├── rust-toolchain.toml pinned stable toolchain + targets
├── Taskfile.yaml local task runner (task CLI)
├── ci.sh CI/CD build script
├── .cargo/config.toml cross-compile aliases + linker config
├── vendors/
│ ├── README.md submodule pin rationale
│ └── wolfssl/ git submodule → wolfSSL v5.7.2-stable
├── crates/
│ ├── ccc-crypto-core/ shared traits, enums, registry
│ └── ccc-crypto-wolfssl/ wolfSSL/wolfCrypt provider
├── tests/
│ └── conformance/ NIST/RFC conformance test binary
└── docs/ architecture + design documents
----
Troubleshooting
===============
wolfSSL source not found
~~~~~~~~~~~~~~~~~~~~~~~~~
::
wolfSSL source not found at .../vendors/wolfssl.
Run `git submodule update --init --recursive` to fetch it.
The wolfSSL submodule was not initialised. Run the command shown in the error.
Linker errors on Apple (undefined symbols)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Ensure the ``-lc++`` link arg is set for Apple targets. This is configured
in ``.cargo/config.toml`` and should work automatically. If you see linker
errors about C++ symbols, verify Xcode Command Line Tools are installed:
.. code-block:: shell
xcode-select --install
Android linker not found
~~~~~~~~~~~~~~~~~~~~~~~~~
::
linker `aarch64-linux-android21-clang` not found
The Android NDK toolchain is not in your PATH. See the "Android NDK" section
above.
bindgen fails / libclang not found
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``bindgen`` needs ``libclang``. On macOS, install LLVM via Homebrew:
.. code-block:: shell
brew install llvm
export LIBCLANG_PATH="$(brew --prefix llvm)/lib"
On Linux:
.. code-block:: shell
sudo apt install libclang-dev
----
Plugin Integration Notes
=========================
When building the Milestone 2 Flutter plugin (``ccc_cryptography``):
1. The bridge crate depends on these crates via git tag::
[dependencies]
ccc-crypto-core = { git = "...", tag = "v0.1.0" }
ccc-crypto-wolfssl = { git = "...", tag = "v0.1.0" }
2. The bridge crate sets ``crate-type = ["cdylib", "staticlib"]``.
3. Call ``ccc_crypto_wolfssl::init()`` from the bridge's ``ccc_init()``.
4. ``flutter_rust_bridge`` and all Dart/Flutter dependencies live exclusively
in the plugin repo — never in this one.

259
Taskfile.yaml Executable file
View File

@ -0,0 +1,259 @@
# https://taskfile.dev
version: "3"
vars:
APPLE_TARGETS: "aarch64-apple-ios aarch64-apple-darwin x86_64-apple-darwin"
tasks:
# ── Setup ────────────────────────────────────────────────────────────────
setup:
desc: Initialise submodules and verify toolchain
cmds:
- git submodule update --init --recursive
- rustc --version
- cmd: cmake --version | head -1
platforms: [darwin, linux]
- cmd: cmake --version
platforms: [windows]
status:
- test -f vendors/wolfssl/CMakeLists.txt
# ── Build ────────────────────────────────────────────────────────────────
build:
desc: Build all crates (debug)
deps: [setup]
cmds:
- cargo build --workspace
release:
desc: Build all crates (release)
deps: [setup]
cmds:
- cargo build --workspace --release
build:ios:
desc: Cross-compile for iOS (aarch64-apple-ios, release)
deps: [setup]
cmds:
- cargo build-ios --release
build:macos-arm64:
desc: Cross-compile for macOS ARM64 (release)
deps: [setup]
cmds:
- cargo build-macos-arm64 --release
build:macos-x64:
desc: Cross-compile for macOS x86_64 (release)
deps: [setup]
cmds:
- cargo build-macos-x64 --release
build:apple:
desc: Cross-compile all Apple targets (iOS + macOS, release)
deps: [setup]
cmds:
- cargo build-ios --release
- cargo build-macos-arm64 --release
- cargo build-macos-x64 --release
build:android-arm64:
desc: Cross-compile for Android ARM64 (release)
deps: [setup]
preconditions:
- sh: command -v aarch64-linux-android21-clang
msg: "Android NDK not in PATH. Set ANDROID_NDK_HOME and add toolchain bin/ to PATH."
cmds:
- cargo build-android-arm64 --release
build:android-x64:
desc: Cross-compile for Android x86_64 (release)
deps: [setup]
preconditions:
- sh: command -v x86_64-linux-android21-clang
msg: "Android NDK not in PATH. Set ANDROID_NDK_HOME and add toolchain bin/ to PATH."
cmds:
- cargo build-android-x64 --release
build:android:
desc: Cross-compile all Android targets (release)
cmds:
- task: build:android-arm64
- task: build:android-x64
# ── Windows (run on Windows only) ─────────────────────────────────────
build:windows-x64:
desc: Build for Windows x86_64 (release, run on Windows)
platforms: [windows]
deps: [setup]
cmds:
- cargo build --workspace --release --target x86_64-pc-windows-msvc
ci:windows:
desc: Full CI pipeline for Windows
platforms: [windows]
cmds:
- task: lint
- task: test
- task: conformance
- task: build:windows-x64
# ── Linux ────────────────────────────────────────────────────────────────
build:linux-x64:
desc: Build for Linux x86_64 (release, run on Linux)
platforms: [linux]
deps: [setup]
cmds:
- cargo build --workspace --release --target x86_64-unknown-linux-gnu
ci:linux:
desc: Full CI pipeline for Linux (lint + test + conformance + build + Android)
platforms: [linux]
cmds:
- task: lint
- task: test
- task: conformance
- task: build:linux-x64
- task: build:android
# ── Aggregate ────────────────────────────────────────────────────────────
build:all:
desc: Build for all targets (native + Apple + Android)
cmds:
- task: release
- task: build:apple
- task: build:android
# ── Test ─────────────────────────────────────────────────────────────────
test:
desc: Run all unit tests
deps: [setup]
cmds:
- cargo test --workspace
conformance:
desc: Run NIST/RFC conformance test vectors
deps: [setup]
cmds:
- cargo run -p ccc-conformance-tests
test:all:
desc: Run unit tests + conformance tests
cmds:
- task: test
- task: conformance
# ── Lint ─────────────────────────────────────────────────────────────────
fmt:
desc: Format code with rustfmt
cmds:
- cargo fmt
fmt:check:
desc: Check formatting (no changes)
cmds:
- cargo fmt --check
clippy:
desc: Run clippy with strict warnings
deps: [setup]
cmds:
- cargo clippy --workspace -- -D warnings
lint:
desc: Full lint (fmt check + clippy)
cmds:
- task: fmt:check
- task: clippy
# ── CI Pipeline ─────────────────────────────────────────────────────────
ci:
desc: Full CI pipeline (lint → test → conformance → release build)
cmds:
- task: lint
- task: test
- task: conformance
- task: release
ci:full:
desc: Full CI + all cross-compile targets (platform-aware)
cmds:
- task: ci
- task: build:apple
- task: build:android
ci:macos:
desc: Full CI pipeline for macOS (lint + test + conformance + Apple targets)
platforms: [darwin]
cmds:
- task: lint
- task: test
- task: conformance
- task: release
- task: build:apple
# ── Utility ─────────────────────────────────────────────────────────────
clean:
desc: Remove all build artifacts
cmds:
- cargo clean
check:
desc: Type-check without building (fast)
deps: [setup]
cmds:
- cargo check --workspace
check:stub:
desc: Type-check wolfSSL crate with stub_ffi (no C build)
cmds:
- cargo check -p ccc-crypto-wolfssl --features stub_ffi
artifacts:
desc: Show artifact locations for all built targets
platforms: [darwin, linux]
cmds:
- |
echo "=== Build artifacts ==="
for dir in target/debug target/release target/aarch64-apple-ios/release target/aarch64-apple-darwin/release target/x86_64-apple-darwin/release target/aarch64-linux-android/release target/x86_64-linux-android/release target/x86_64-unknown-linux-gnu/release; do
if [ -d "$dir" ]; then
echo ""
echo "── $dir ──"
find "$dir" -maxdepth 1 \( -name "*.rlib" -o -name "*.a" -o -name "*.dylib" -o -name "*.so" -o -name "conformance" \) 2>/dev/null | sort
find "$dir/build" -name "libwolfssl.a" 2>/dev/null | head -1
fi
done
artifacts:windows:
desc: Show artifact locations (Windows)
platforms: [windows]
cmds:
- cmd: |
echo === Build artifacts ===
for %%d in (target\debug target\release target\x86_64-pc-windows-msvc\release) do (
if exist "%%d" (
echo.
echo -- %%d --
dir /b "%%d\*.rlib" "%%d\*.lib" "%%d\*.dll" "%%d\*.exe" 2>nul
)
)
##### ##### ##### ##### ##### ##### ##### ##### ##### #####
# Default
#
# if we are executed with option of "--taskfile", and no task, so this default...that is why we pass it along
# --taskfile {{.TASKFILE}}
# we spent several hours fixing that out, because of PEBKAC
default:
cmds:
- task --list --taskfile {{.TASKFILE}}

168
ci.sh Executable file
View File

@ -0,0 +1,168 @@
#!/usr/bin/env bash
# ──────────────────────────────────────────────────────────────────────────────
# ci.sh — CI/CD build script for ccc_rust
#
# Usage:
# ./ci.sh Run the full CI pipeline (lint + test + cross-build)
# ./ci.sh lint Lint only (fmt + clippy)
# ./ci.sh test Unit tests + conformance vectors
# ./ci.sh build Native debug build
# ./ci.sh release Native release build
# ./ci.sh apple Cross-compile all Apple targets (iOS + macOS)
# ./ci.sh android Cross-compile Android targets (requires NDK)
# ./ci.sh all Full pipeline + all cross targets
# ──────────────────────────────────────────────────────────────────────────────
set -euo pipefail
# Source cargo env if not already in PATH.
if ! command -v cargo &>/dev/null; then
# shellcheck source=/dev/null
source "$HOME/.cargo/env"
fi
RED='\033[0;31m'
GREEN='\033[0;32m'
CYAN='\033[0;36m'
BOLD='\033[1m'
RESET='\033[0m'
step() { echo -e "\n${CYAN}${BOLD}── $1 ──${RESET}"; }
pass() { echo -e "${GREEN}$1${RESET}"; }
fail() { echo -e "${RED}$1${RESET}"; exit 1; }
# ── Submodule check ──────────────────────────────────────────────────────────
check_submodule() {
if [ ! -f "vendors/wolfssl/CMakeLists.txt" ]; then
step "Initialising wolfSSL submodule"
git submodule update --init --recursive
fi
}
# ── Stages ───────────────────────────────────────────────────────────────────
do_lint() {
step "Format check"
cargo fmt --check || fail "cargo fmt --check failed"
pass "Format OK"
step "Clippy"
cargo clippy --workspace -- -D warnings || fail "clippy failed"
pass "Clippy OK"
}
do_test() {
step "Unit tests"
cargo test --workspace || fail "cargo test failed"
pass "Unit tests passed"
step "Conformance vectors"
cargo run -p ccc-conformance-tests || fail "conformance tests failed"
pass "Conformance tests passed"
}
do_build() {
step "Build (debug)"
cargo build --workspace || fail "debug build failed"
pass "Debug build OK"
}
do_release() {
step "Build (release)"
cargo build --workspace --release || fail "release build failed"
pass "Release build OK"
}
do_apple() {
step "Build iOS (aarch64-apple-ios)"
cargo build-ios --release || fail "iOS build failed"
pass "iOS build OK"
step "Build macOS ARM64 (aarch64-apple-darwin)"
cargo build-macos-arm64 --release || fail "macOS ARM64 build failed"
pass "macOS ARM64 build OK"
step "Build macOS x64 (x86_64-apple-darwin)"
cargo build-macos-x64 --release || fail "macOS x64 build failed"
pass "macOS x64 build OK"
}
do_android() {
if ! command -v aarch64-linux-android21-clang &>/dev/null; then
echo -e "${RED}Android NDK not in PATH — skipping Android targets.${RESET}"
echo "Set ANDROID_NDK_HOME and add the toolchain bin/ to PATH."
return 1
fi
step "Build Android ARM64 (aarch64-linux-android)"
cargo build-android-arm64 --release || fail "Android ARM64 build failed"
pass "Android ARM64 build OK"
step "Build Android x64 (x86_64-linux-android)"
cargo build-android-x64 --release || fail "Android x64 build failed"
pass "Android x64 build OK"
}
do_full() {
do_lint
do_test
do_release
}
do_linux() {
step "Build Linux x64 (x86_64-unknown-linux-gnu)"
cargo build --workspace --release --target x86_64-unknown-linux-gnu \
|| fail "Linux x64 build failed"
pass "Linux x64 build OK"
}
do_windows() {
step "Build Windows x64 (x86_64-pc-windows-msvc)"
cargo build --workspace --release --target x86_64-pc-windows-msvc \
|| fail "Windows x64 build failed"
pass "Windows x64 build OK"
}
do_all() {
do_full
do_apple
do_android || true # Don't fail the pipeline if NDK is absent.
}
# ── Summary ──────────────────────────────────────────────────────────────────
summary() {
echo ""
step "Artifact locations"
echo " Native (release): target/release/"
echo " iOS: target/aarch64-apple-ios/release/"
echo " macOS ARM64: target/aarch64-apple-darwin/release/"
echo " macOS x64: target/x86_64-apple-darwin/release/"
echo " Linux x64: target/x86_64-unknown-linux-gnu/release/"
echo " Windows x64: target/x86_64-pc-windows-msvc/release/"
echo " Android ARM64: target/aarch64-linux-android/release/"
echo " Android x64: target/x86_64-linux-android/release/"
echo ""
pass "CI pipeline complete"
}
# ── Main ─────────────────────────────────────────────────────────────────────
check_submodule
case "${1:-}" in
lint) do_lint ;;
test) do_test ;;
build) do_build ;;
release) do_release ;;
apple) do_apple && summary ;;
android) do_android && summary ;;
linux) do_linux && summary ;;
windows) do_windows && summary ;;
all) do_all && summary ;;
"") do_full && summary ;;
*)
echo "Usage: $0 {lint|test|build|release|apple|android|linux|windows|all}"
exit 1
;;
esac

View File

@ -110,11 +110,7 @@ pub trait MacProvider {
/// Cryptographic hash operations. /// Cryptographic hash operations.
pub trait HashProvider { pub trait HashProvider {
/// Compute a digest of `data` using the specified algorithm. /// Compute a digest of `data` using the specified algorithm.
fn hash( fn hash(&self, algo: HashAlgorithm, data: &[u8]) -> Result<Vec<u8>, CryptoError>;
&self,
algo: HashAlgorithm,
data: &[u8],
) -> Result<Vec<u8>, CryptoError>;
} }
/// Key encapsulation mechanism operations. /// Key encapsulation mechanism operations.
@ -124,10 +120,7 @@ pub trait HashProvider {
/// functions separately and looks up a `KemProvider` from the registry. /// functions separately and looks up a `KemProvider` from the registry.
pub trait KemProvider { pub trait KemProvider {
/// Generate a new KEM key pair. /// Generate a new KEM key pair.
fn generate_keypair( fn generate_keypair(&self, algo: KemAlgorithm) -> Result<KemKeyPair, CryptoError>;
&self,
algo: KemAlgorithm,
) -> Result<KemKeyPair, CryptoError>;
/// Encapsulate a shared secret for the holder of `public_key`. /// Encapsulate a shared secret for the holder of `public_key`.
/// ///
@ -170,7 +163,9 @@ pub trait KemProvider {
/// `ProviderRegistry::global().register(name, Box::new(provider))`. /// `ProviderRegistry::global().register(name, Box::new(provider))`.
/// 5. Call `ccc_crypto_<name>::init()` from `ccc-flutter-bridge/src/lib.rs`. /// 5. Call `ccc_crypto_<name>::init()` from `ccc-flutter-bridge/src/lib.rs`.
/// 6. Add NIST test vectors for every algorithm you expose. /// 6. Add NIST test vectors for every algorithm you expose.
pub trait CryptoProvider: AeadProvider + KdfProvider + MacProvider + HashProvider + Send + Sync { pub trait CryptoProvider:
AeadProvider + KdfProvider + MacProvider + HashProvider + Send + Sync
{
/// The provider's canonical name (e.g. `"wolfssl"`). /// The provider's canonical name (e.g. `"wolfssl"`).
/// ///
/// Must be lowercase, no spaces, match the `CccCryptoProvider` enum value /// Must be lowercase, no spaces, match the `CccCryptoProvider` enum value

View File

@ -94,144 +94,6 @@ impl ProviderRegistry {
} }
} }
// ──────────────────────────────────────────────────────────────────────────────
// Tests
// ──────────────────────────────────────────────────────────────────────────────
#[cfg(test)] #[cfg(test)]
mod tests { #[path = "registry_tests.rs"]
//! Registry unit tests use a stub provider to avoid any native dependency. mod tests;
use super::*;
use crate::{
algorithms::{AeadAlgorithm, HashAlgorithm, KdfAlgorithm, MacAlgorithm},
capabilities::ProviderCapabilities,
error::CryptoError,
provider::{AeadProvider, CryptoProvider, HashProvider, KdfProvider, MacProvider},
types::{AlgoBenchResult, AlgoTestResult, BenchmarkReport, SelfTestReport},
};
use zeroize::Zeroizing;
// ── Stub provider ──────────────────────────────────────────────────────────
struct StubProvider;
impl AeadProvider for StubProvider {
fn encrypt_aead(
&self, _a: AeadAlgorithm, _k: &[u8], _n: &[u8], pt: &[u8], _aad: &[u8],
) -> Result<Vec<u8>, CryptoError> {
Ok(pt.to_vec())
}
fn decrypt_aead(
&self, _a: AeadAlgorithm, _k: &[u8], _n: &[u8], ct: &[u8], _aad: &[u8],
) -> Result<Vec<u8>, CryptoError> {
Ok(ct.to_vec())
}
}
impl KdfProvider for StubProvider {
fn derive_key(
&self, _a: KdfAlgorithm, _ikm: &[u8], _salt: &[u8], _info: &[u8], length: usize,
) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
Ok(Zeroizing::new(vec![0u8; length]))
}
}
impl MacProvider for StubProvider {
fn compute_mac(
&self, _a: MacAlgorithm, _k: &[u8], _data: &[u8],
) -> Result<Vec<u8>, CryptoError> {
Ok(vec![0u8; 32])
}
fn verify_mac(
&self, _a: MacAlgorithm, _k: &[u8], _data: &[u8], _mac: &[u8],
) -> Result<bool, CryptoError> {
Ok(true)
}
}
impl HashProvider for StubProvider {
fn hash(
&self, _a: HashAlgorithm, data: &[u8],
) -> Result<Vec<u8>, CryptoError> {
Ok(data.to_vec())
}
}
impl CryptoProvider for StubProvider {
fn provider_name(&self) -> &'static str { "stub" }
fn capabilities(&self) -> ProviderCapabilities {
ProviderCapabilities::empty("stub")
}
fn self_test(&self) -> SelfTestReport {
SelfTestReport::finalise("stub", vec![AlgoTestResult {
algo_id: 12, algo_name: "stub".into(), passed: true, error_message: None,
}])
}
fn benchmark(&self) -> BenchmarkReport {
BenchmarkReport {
provider_name: "stub".into(),
results: vec![AlgoBenchResult {
algo_id: 12, algo_name: "stub".into(),
throughput_mbps: 999.0, efficiency_score: 100,
}],
}
}
}
// ── Tests ──────────────────────────────────────────────────────────────────
fn fresh_registry() -> ProviderRegistry {
ProviderRegistry { inner: Mutex::new(HashMap::new()) }
}
#[test]
fn register_and_get() {
let reg = fresh_registry();
assert!(reg.get("stub").is_none());
reg.register("stub", Box::new(StubProvider));
assert!(reg.get("stub").is_some());
}
#[test]
fn list_providers() {
let reg = fresh_registry();
assert!(reg.list().is_empty());
reg.register("stub", Box::new(StubProvider));
let names = reg.list();
assert_eq!(names.len(), 1);
assert_eq!(names[0], "stub");
}
#[test]
fn contains_and_unregister() {
let reg = fresh_registry();
reg.register("stub", Box::new(StubProvider));
assert!(reg.contains("stub"));
reg.unregister("stub");
assert!(!reg.contains("stub"));
}
#[test]
fn register_replaces_existing() {
let reg = fresh_registry();
reg.register("stub", Box::new(StubProvider));
reg.register("stub", Box::new(StubProvider)); // replace
assert_eq!(reg.list().len(), 1);
}
#[test]
fn stub_provider_roundtrip() {
let reg = fresh_registry();
reg.register("stub", Box::new(StubProvider));
let provider = reg.get("stub").unwrap();
let plaintext = b"hello world";
let ct = provider
.encrypt_aead(AeadAlgorithm::AesGcm256, &[0u8; 32], &[0u8; 12], plaintext, b"")
.unwrap();
let pt = provider
.decrypt_aead(AeadAlgorithm::AesGcm256, &[0u8; 32], &[0u8; 12], &ct, b"")
.unwrap();
assert_eq!(pt, plaintext);
}
}

View File

@ -0,0 +1,172 @@
//! Registry unit tests — use a stub provider to avoid any native dependency.
use super::*;
use crate::{
algorithms::{AeadAlgorithm, HashAlgorithm, KdfAlgorithm, MacAlgorithm},
capabilities::ProviderCapabilities,
error::CryptoError,
provider::{AeadProvider, CryptoProvider, HashProvider, KdfProvider, MacProvider},
types::{AlgoBenchResult, AlgoTestResult, BenchmarkReport, SelfTestReport},
};
use zeroize::Zeroizing;
// ── Stub provider ──────────────────────────────────────────────────────────
struct StubProvider;
impl AeadProvider for StubProvider {
fn encrypt_aead(
&self,
_a: AeadAlgorithm,
_k: &[u8],
_n: &[u8],
pt: &[u8],
_aad: &[u8],
) -> Result<Vec<u8>, CryptoError> {
Ok(pt.to_vec())
}
fn decrypt_aead(
&self,
_a: AeadAlgorithm,
_k: &[u8],
_n: &[u8],
ct: &[u8],
_aad: &[u8],
) -> Result<Vec<u8>, CryptoError> {
Ok(ct.to_vec())
}
}
impl KdfProvider for StubProvider {
fn derive_key(
&self,
_a: KdfAlgorithm,
_ikm: &[u8],
_salt: &[u8],
_info: &[u8],
length: usize,
) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
Ok(Zeroizing::new(vec![0u8; length]))
}
}
impl MacProvider for StubProvider {
fn compute_mac(
&self,
_a: MacAlgorithm,
_k: &[u8],
_data: &[u8],
) -> Result<Vec<u8>, CryptoError> {
Ok(vec![0u8; 32])
}
fn verify_mac(
&self,
_a: MacAlgorithm,
_k: &[u8],
_data: &[u8],
_mac: &[u8],
) -> Result<bool, CryptoError> {
Ok(true)
}
}
impl HashProvider for StubProvider {
fn hash(&self, _a: HashAlgorithm, data: &[u8]) -> Result<Vec<u8>, CryptoError> {
Ok(data.to_vec())
}
}
impl CryptoProvider for StubProvider {
fn provider_name(&self) -> &'static str {
"stub"
}
fn capabilities(&self) -> ProviderCapabilities {
ProviderCapabilities::empty("stub")
}
fn self_test(&self) -> SelfTestReport {
SelfTestReport::finalise(
"stub",
vec![AlgoTestResult {
algo_id: 12,
algo_name: "stub".into(),
passed: true,
error_message: None,
}],
)
}
fn benchmark(&self) -> BenchmarkReport {
BenchmarkReport {
provider_name: "stub".into(),
results: vec![AlgoBenchResult {
algo_id: 12,
algo_name: "stub".into(),
throughput_mbps: 999.0,
efficiency_score: 100,
}],
}
}
}
// ── Tests ──────────────────────────────────────────────────────────────────
fn fresh_registry() -> ProviderRegistry {
ProviderRegistry {
inner: Mutex::new(HashMap::new()),
}
}
#[test]
fn register_and_get() {
let reg = fresh_registry();
assert!(reg.get("stub").is_none());
reg.register("stub", Box::new(StubProvider));
assert!(reg.get("stub").is_some());
}
#[test]
fn list_providers() {
let reg = fresh_registry();
assert!(reg.list().is_empty());
reg.register("stub", Box::new(StubProvider));
let names = reg.list();
assert_eq!(names.len(), 1);
assert_eq!(names[0], "stub");
}
#[test]
fn contains_and_unregister() {
let reg = fresh_registry();
reg.register("stub", Box::new(StubProvider));
assert!(reg.contains("stub"));
reg.unregister("stub");
assert!(!reg.contains("stub"));
}
#[test]
fn register_replaces_existing() {
let reg = fresh_registry();
reg.register("stub", Box::new(StubProvider));
reg.register("stub", Box::new(StubProvider)); // replace
assert_eq!(reg.list().len(), 1);
}
#[test]
fn stub_provider_roundtrip() {
let reg = fresh_registry();
reg.register("stub", Box::new(StubProvider));
let provider = reg.get("stub").unwrap();
let plaintext = b"hello world";
let ct = provider
.encrypt_aead(
AeadAlgorithm::AesGcm256,
&[0u8; 32],
&[0u8; 12],
plaintext,
b"",
)
.unwrap();
let pt = provider
.decrypt_aead(AeadAlgorithm::AesGcm256, &[0u8; 32], &[0u8; 12], &ct, b"")
.unwrap();
assert_eq!(pt, plaintext);
}

View File

@ -14,7 +14,10 @@
//! a pre-installed wolfSSL at this path. The path must contain //! a pre-installed wolfSSL at this path. The path must contain
//! `include/wolfssl/` and `lib/libwolfssl.a`. //! `include/wolfssl/` and `lib/libwolfssl.a`.
use std::{env, path::PathBuf}; use std::{
env,
path::{Path, PathBuf},
};
fn main() { fn main() {
// When the `stub_ffi` feature is enabled (e.g. for type-checking without // When the `stub_ffi` feature is enabled (e.g. for type-checking without
@ -34,11 +37,13 @@ fn main() {
// ── 1. Locate wolfSSL ──────────────────────────────────────────────────── // ── 1. Locate wolfSSL ────────────────────────────────────────────────────
let (include_dir, lib_dir) = let (include_dir, lib_dir) = if let Ok(install_dir) = env::var("WOLFSSL_INSTALL_DIR") {
if let Ok(install_dir) = env::var("WOLFSSL_INSTALL_DIR") {
// Use a pre-installed wolfSSL — skip the CMake build. // Use a pre-installed wolfSSL — skip the CMake build.
let p = PathBuf::from(&install_dir); let p = PathBuf::from(&install_dir);
println!("cargo:warning=Using pre-installed wolfSSL at {}", install_dir); println!(
"cargo:warning=Using pre-installed wolfSSL at {}",
install_dir
);
(p.join("include"), p.join("lib")) (p.join("include"), p.join("lib"))
} else { } else {
// Build from source (default — uses our git submodule). // Build from source (default — uses our git submodule).
@ -86,7 +91,7 @@ fn main() {
} }
/// Build wolfSSL from source using CMake. Returns `(include_dir, lib_dir)`. /// Build wolfSSL from source using CMake. Returns `(include_dir, lib_dir)`.
fn build_wolfssl_cmake(source_dir: &PathBuf, _out_dir: &PathBuf) -> (PathBuf, PathBuf) { fn build_wolfssl_cmake(source_dir: &Path, _out_dir: &Path) -> (PathBuf, PathBuf) {
println!( println!(
"cargo:warning=Building wolfSSL from source at {}", "cargo:warning=Building wolfSSL from source at {}",
source_dir.display() source_dir.display()
@ -166,8 +171,34 @@ fn build_wolfssl_cmake(source_dir: &PathBuf, _out_dir: &PathBuf) -> (PathBuf, Pa
(include_dir, lib_dir) (include_dir, lib_dir)
} }
/// Parse the CMake-generated `options.h` to extract all `#define` directives.
/// Returns a list of `-DNAME` clang args.
fn parse_options_defines(include_dir: &Path) -> Vec<String> {
let options_h = include_dir.join("wolfssl/options.h");
if !options_h.exists() {
return Vec::new();
}
let content = std::fs::read_to_string(&options_h).expect("failed to read options.h");
let mut defines = Vec::new();
for line in content.lines() {
let line = line.trim();
if !line.starts_with("#define ") {
continue;
}
let rest = &line["#define ".len()..];
let name = rest.split_whitespace().next().unwrap_or("");
// Skip the header guard macro.
if name.is_empty() || name == "WOLFSSL_OPTIONS_H" {
continue;
}
defines.push(format!("-D{}", name));
}
defines
}
/// Run bindgen over the wolfCrypt headers we actually use. /// Run bindgen over the wolfCrypt headers we actually use.
fn generate_bindings(include_dir: &PathBuf, out_dir: &PathBuf) { fn generate_bindings(include_dir: &Path, out_dir: &Path) {
let header = include_dir.join("wolfssl/wolfcrypt/aes.h"); let header = include_dir.join("wolfssl/wolfcrypt/aes.h");
if !header.exists() { if !header.exists() {
@ -182,7 +213,17 @@ fn generate_bindings(include_dir: &PathBuf, out_dir: &PathBuf) {
return; return;
} }
let bindings = bindgen::Builder::default() // Parse the CMake-generated options.h so that bindgen's clang sees the
// exact same preprocessor flags as the compiled wolfSSL library. This is
// essential for correct struct layout — e.g. GCM_TABLE_4BIT adds a 512-byte
// M0 table to the Gcm struct that changes the size of Aes.
let options_defines = parse_options_defines(include_dir);
// For cross-compilation, tell bindgen's clang the target triple so that
// platform-specific types (pointer size, alignment, builtins) are correct.
let target = env::var("TARGET").unwrap_or_default();
let mut builder = bindgen::Builder::default()
// Include all wolfCrypt headers we need. // Include all wolfCrypt headers we need.
.header(include_dir.join("wolfssl/wolfcrypt/aes.h").to_str().unwrap()) .header(include_dir.join("wolfssl/wolfcrypt/aes.h").to_str().unwrap())
.header(include_dir.join("wolfssl/wolfcrypt/chacha20_poly1305.h").to_str().unwrap()) .header(include_dir.join("wolfssl/wolfcrypt/chacha20_poly1305.h").to_str().unwrap())
@ -196,31 +237,33 @@ fn generate_bindings(include_dir: &PathBuf, out_dir: &PathBuf) {
.header(include_dir.join("wolfssl/wolfcrypt/curve25519.h").to_str().unwrap()) .header(include_dir.join("wolfssl/wolfcrypt/curve25519.h").to_str().unwrap())
.header(include_dir.join("wolfssl/wolfcrypt/curve448.h").to_str().unwrap()) .header(include_dir.join("wolfssl/wolfcrypt/curve448.h").to_str().unwrap())
.header(include_dir.join("wolfssl/wolfcrypt/random.h").to_str().unwrap()) .header(include_dir.join("wolfssl/wolfcrypt/random.h").to_str().unwrap())
.clang_arg(format!("-I{}", include_dir.display())) .clang_arg(format!("-I{}", include_dir.display()));
// Pass all required preprocessor defines explicitly so that bindgen's
// libclang sees the same feature flags as the C build, regardless of // Pass all defines from the generated options.h. This includes critical
// whether wolfSSL's options.h is correctly resolved by clang. // struct-affecting flags like GCM_TABLE_4BIT, HAVE___UINT128_T,
// // WOLFSSL_USE_ALIGN, etc. that change the layout of Gcm, Aes, and other types.
// AES-GCM (wc_AesGcmSetKey etc. are behind #ifdef HAVE_AESGCM) for def in &options_defines {
.clang_arg("-DHAVE_AESGCM") builder = builder.clang_arg(def);
// ChaCha20 / Poly1305 / XChaCha20 }
.clang_arg("-DHAVE_CHACHA")
.clang_arg("-DHAVE_POLY1305") // Pass additional defines that are added via .cflag() in the CMake build
// (not cmake options, so they don't appear in options.h).
builder = builder
.clang_arg("-DHAVE_XCHACHA") .clang_arg("-DHAVE_XCHACHA")
// BLAKE2 is not a cmake option — enable via clang preprocessor flags
// so that the headers expose the BLAKE2b/BLAKE2s function prototypes.
.clang_arg("-DHAVE_BLAKE2") .clang_arg("-DHAVE_BLAKE2")
.clang_arg("-DHAVE_BLAKE2B") .clang_arg("-DHAVE_BLAKE2B");
// Required so sha512.h exposes SHA384 typedef + functions.
.clang_arg("-DWOLFSSL_SHA384") // Set the cross-compilation target if it differs from the host.
.clang_arg("-DWOLFSSL_SHA512") if !target.is_empty() {
// SHA3 functions (wc_InitSha3_256, etc.) // Map Rust target triples to clang-compatible triples.
.clang_arg("-DWOLFSSL_SHA3") let clang_target = match target.as_str() {
// HKDF via TLS-1.3 primitives (wc_Tls13_HKDF_Extract/Expand_Label) "aarch64-apple-ios-sim" => "arm64-apple-ios-simulator".to_string(),
.clang_arg("-DHAVE_HKDF") other => other.to_string(),
// Curve25519 / Curve448 need HAVE_CURVE25519 / HAVE_CURVE448 };
.clang_arg("-DHAVE_CURVE25519") builder = builder.clang_arg(format!("--target={}", clang_target));
.clang_arg("-DHAVE_CURVE448") }
let bindings = builder
// Only generate bindings for the types/functions we use. // Only generate bindings for the types/functions we use.
.allowlist_function("wc_Aes.*") .allowlist_function("wc_Aes.*")
.allowlist_function("wc_AesGcm.*") .allowlist_function("wc_AesGcm.*")

View File

@ -29,8 +29,7 @@ pub fn encrypt(
AeadAlgorithm::AesGcm256 => aes_gcm_256_encrypt(key, nonce, plaintext, aad), AeadAlgorithm::AesGcm256 => aes_gcm_256_encrypt(key, nonce, plaintext, aad),
AeadAlgorithm::ChaCha20Poly1305 => chacha20_poly1305_encrypt(key, nonce, plaintext, aad), AeadAlgorithm::ChaCha20Poly1305 => chacha20_poly1305_encrypt(key, nonce, plaintext, aad),
AeadAlgorithm::XChaCha20Poly1305 => xchacha20_poly1305_encrypt(key, nonce, plaintext, aad), AeadAlgorithm::XChaCha20Poly1305 => xchacha20_poly1305_encrypt(key, nonce, plaintext, aad),
AeadAlgorithm::Ascon128a => AeadAlgorithm::Ascon128a => Err(CryptoError::FeatureNotCompiled("Ascon-128a".into())),
Err(CryptoError::FeatureNotCompiled("Ascon-128a".into())),
} }
} }
@ -48,10 +47,13 @@ pub fn decrypt(
validate_key_nonce(algo, key, nonce)?; validate_key_nonce(algo, key, nonce)?;
match algo { match algo {
AeadAlgorithm::AesGcm256 => aes_gcm_256_decrypt(key, nonce, ciphertext_and_tag, aad), AeadAlgorithm::AesGcm256 => aes_gcm_256_decrypt(key, nonce, ciphertext_and_tag, aad),
AeadAlgorithm::ChaCha20Poly1305 => chacha20_poly1305_decrypt(key, nonce, ciphertext_and_tag, aad), AeadAlgorithm::ChaCha20Poly1305 => {
AeadAlgorithm::XChaCha20Poly1305 => xchacha20_poly1305_decrypt(key, nonce, ciphertext_and_tag, aad), chacha20_poly1305_decrypt(key, nonce, ciphertext_and_tag, aad)
AeadAlgorithm::Ascon128a => }
Err(CryptoError::FeatureNotCompiled("Ascon-128a".into())), AeadAlgorithm::XChaCha20Poly1305 => {
xchacha20_poly1305_decrypt(key, nonce, ciphertext_and_tag, aad)
}
AeadAlgorithm::Ascon128a => Err(CryptoError::FeatureNotCompiled("Ascon-128a".into())),
} }
} }
@ -65,13 +67,17 @@ fn validate_key_nonce(algo: AeadAlgorithm, key: &[u8], nonce: &[u8]) -> Result<(
if key.len() != expected_key { if key.len() != expected_key {
return Err(CryptoError::InvalidKey(format!( return Err(CryptoError::InvalidKey(format!(
"{}: expected {}-byte key, got {}", "{}: expected {}-byte key, got {}",
algo.name(), expected_key, key.len() algo.name(),
expected_key,
key.len()
))); )));
} }
if nonce.len() != expected_nonce { if nonce.len() != expected_nonce {
return Err(CryptoError::InvalidNonce(format!( return Err(CryptoError::InvalidNonce(format!(
"{}: expected {}-byte nonce, got {}", "{}: expected {}-byte nonce, got {}",
algo.name(), expected_nonce, nonce.len() algo.name(),
expected_nonce,
nonce.len()
))); )));
} }
Ok(()) Ok(())
@ -97,11 +103,7 @@ fn aes_gcm_256_encrypt(
// alignment so the C code's SIMD operations work correctly on ARM64. // alignment so the C code's SIMD operations work correctly on ARM64.
use std::alloc::{alloc_zeroed, dealloc, Layout}; use std::alloc::{alloc_zeroed, dealloc, Layout};
// sizeof(Aes) = 288 bytes; alignment = 16. let layout = Layout::from_size_align(std::mem::size_of::<crate::sys::Aes>(), 16)
let layout = Layout::from_size_align(
std::mem::size_of::<crate::sys::Aes>().max(288),
16,
)
.expect("layout must be valid"); .expect("layout must be valid");
let aes_ptr = alloc_zeroed(layout) as *mut crate::sys::Aes; let aes_ptr = alloc_zeroed(layout) as *mut crate::sys::Aes;
@ -116,18 +118,20 @@ fn aes_gcm_256_encrypt(
); );
if ret != 0 { if ret != 0 {
dealloc(aes_ptr as *mut u8, layout); dealloc(aes_ptr as *mut u8, layout);
return Err(CryptoError::InternalError(format!("wc_AesInit returned {}", ret))); return Err(CryptoError::InternalError(format!(
"wc_AesInit returned {}",
ret
)));
} }
let ret = crate::sys::wc_AesGcmSetKey( let ret = crate::sys::wc_AesGcmSetKey(aes_ptr, key.as_ptr(), key.len() as u32);
aes_ptr,
key.as_ptr(),
key.len() as u32,
);
if ret != 0 { if ret != 0 {
crate::sys::wc_AesFree(aes_ptr); crate::sys::wc_AesFree(aes_ptr);
dealloc(aes_ptr as *mut u8, layout); dealloc(aes_ptr as *mut u8, layout);
return Err(CryptoError::InvalidKey(format!("wc_AesGcmSetKey returned {}", ret))); return Err(CryptoError::InvalidKey(format!(
"wc_AesGcmSetKey returned {}",
ret
)));
} }
let ret = crate::sys::wc_AesGcmEncrypt( let ret = crate::sys::wc_AesGcmEncrypt(
@ -139,13 +143,20 @@ fn aes_gcm_256_encrypt(
nonce.len() as u32, nonce.len() as u32,
tag_buf.as_mut_ptr(), tag_buf.as_mut_ptr(),
TAG_LEN as u32, TAG_LEN as u32,
if aad.is_empty() { std::ptr::null() } else { aad.as_ptr() }, if aad.is_empty() {
std::ptr::null()
} else {
aad.as_ptr()
},
aad.len() as u32, aad.len() as u32,
); );
crate::sys::wc_AesFree(aes_ptr); crate::sys::wc_AesFree(aes_ptr);
dealloc(aes_ptr as *mut u8, layout); dealloc(aes_ptr as *mut u8, layout);
if ret != 0 { if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_AesGcmEncrypt returned {}", ret))); return Err(CryptoError::InternalError(format!(
"wc_AesGcmEncrypt returned {}",
ret
)));
} }
} }
@ -160,7 +171,7 @@ fn aes_gcm_256_decrypt(
) -> Result<Vec<u8>, CryptoError> { ) -> Result<Vec<u8>, CryptoError> {
if ciphertext_and_tag.len() < TAG_LEN { if ciphertext_and_tag.len() < TAG_LEN {
return Err(CryptoError::InvalidInput( return Err(CryptoError::InvalidInput(
"AES-256-GCM: ciphertext too short (no room for tag)".into() "AES-256-GCM: ciphertext too short (no room for tag)".into(),
)); ));
} }
let ct_len = ciphertext_and_tag.len() - TAG_LEN; let ct_len = ciphertext_and_tag.len() - TAG_LEN;
@ -172,10 +183,7 @@ fn aes_gcm_256_decrypt(
// alignment so the C code's SIMD operations work correctly on ARM64. // alignment so the C code's SIMD operations work correctly on ARM64.
use std::alloc::{alloc_zeroed, dealloc, Layout}; use std::alloc::{alloc_zeroed, dealloc, Layout};
let layout = Layout::from_size_align( let layout = Layout::from_size_align(std::mem::size_of::<crate::sys::Aes>(), 16)
std::mem::size_of::<crate::sys::Aes>().max(288),
16,
)
.expect("layout must be valid"); .expect("layout must be valid");
let aes_ptr = alloc_zeroed(layout) as *mut crate::sys::Aes; let aes_ptr = alloc_zeroed(layout) as *mut crate::sys::Aes;
@ -190,18 +198,20 @@ fn aes_gcm_256_decrypt(
); );
if ret != 0 { if ret != 0 {
dealloc(aes_ptr as *mut u8, layout); dealloc(aes_ptr as *mut u8, layout);
return Err(CryptoError::InternalError(format!("wc_AesInit returned {}", ret))); return Err(CryptoError::InternalError(format!(
"wc_AesInit returned {}",
ret
)));
} }
let ret = crate::sys::wc_AesGcmSetKey( let ret = crate::sys::wc_AesGcmSetKey(aes_ptr, key.as_ptr(), key.len() as u32);
aes_ptr,
key.as_ptr(),
key.len() as u32,
);
if ret != 0 { if ret != 0 {
crate::sys::wc_AesFree(aes_ptr); crate::sys::wc_AesFree(aes_ptr);
dealloc(aes_ptr as *mut u8, layout); dealloc(aes_ptr as *mut u8, layout);
return Err(CryptoError::InvalidKey(format!("wc_AesGcmSetKey returned {}", ret))); return Err(CryptoError::InvalidKey(format!(
"wc_AesGcmSetKey returned {}",
ret
)));
} }
let ret = crate::sys::wc_AesGcmDecrypt( let ret = crate::sys::wc_AesGcmDecrypt(
@ -213,7 +223,11 @@ fn aes_gcm_256_decrypt(
nonce.len() as u32, nonce.len() as u32,
tag.as_ptr(), tag.as_ptr(),
TAG_LEN as u32, TAG_LEN as u32,
if aad.is_empty() { std::ptr::null() } else { aad.as_ptr() }, if aad.is_empty() {
std::ptr::null()
} else {
aad.as_ptr()
},
aad.len() as u32, aad.len() as u32,
); );
crate::sys::wc_AesFree(aes_ptr); crate::sys::wc_AesFree(aes_ptr);
@ -245,7 +259,11 @@ fn chacha20_poly1305_encrypt(
let ret = crate::sys::wc_ChaCha20Poly1305_Encrypt( let ret = crate::sys::wc_ChaCha20Poly1305_Encrypt(
key.as_ptr(), key.as_ptr(),
nonce.as_ptr(), nonce.as_ptr(),
if aad.is_empty() { std::ptr::null() } else { aad.as_ptr() }, if aad.is_empty() {
std::ptr::null()
} else {
aad.as_ptr()
},
aad.len() as u32, aad.len() as u32,
plaintext.as_ptr(), plaintext.as_ptr(),
plaintext.len() as u32, plaintext.len() as u32,
@ -253,9 +271,10 @@ fn chacha20_poly1305_encrypt(
tag_buf.as_mut_ptr(), tag_buf.as_mut_ptr(),
); );
if ret != 0 { if ret != 0 {
return Err(CryptoError::InternalError( return Err(CryptoError::InternalError(format!(
format!("wc_ChaCha20Poly1305_Encrypt returned {}", ret) "wc_ChaCha20Poly1305_Encrypt returned {}",
)); ret
)));
} }
} }
@ -270,7 +289,7 @@ fn chacha20_poly1305_decrypt(
) -> Result<Vec<u8>, CryptoError> { ) -> Result<Vec<u8>, CryptoError> {
if ciphertext_and_tag.len() < TAG_LEN { if ciphertext_and_tag.len() < TAG_LEN {
return Err(CryptoError::InvalidInput( return Err(CryptoError::InvalidInput(
"ChaCha20-Poly1305: ciphertext too short".into() "ChaCha20-Poly1305: ciphertext too short".into(),
)); ));
} }
let ct_len = ciphertext_and_tag.len() - TAG_LEN; let ct_len = ciphertext_and_tag.len() - TAG_LEN;
@ -282,7 +301,11 @@ fn chacha20_poly1305_decrypt(
let ret = crate::sys::wc_ChaCha20Poly1305_Decrypt( let ret = crate::sys::wc_ChaCha20Poly1305_Decrypt(
key.as_ptr(), key.as_ptr(),
nonce.as_ptr(), nonce.as_ptr(),
if aad.is_empty() { std::ptr::null() } else { aad.as_ptr() }, if aad.is_empty() {
std::ptr::null()
} else {
aad.as_ptr()
},
aad.len() as u32, aad.len() as u32,
ciphertext.as_ptr(), ciphertext.as_ptr(),
ct_len as u32, ct_len as u32,
@ -325,7 +348,11 @@ fn xchacha20_poly1305_encrypt(
dst.len(), dst.len(),
plaintext.as_ptr(), plaintext.as_ptr(),
plaintext.len(), plaintext.len(),
if aad.is_empty() { std::ptr::null() } else { aad.as_ptr() }, if aad.is_empty() {
std::ptr::null()
} else {
aad.as_ptr()
},
aad.len(), aad.len(),
nonce.as_ptr(), nonce.as_ptr(),
nonce.len(), nonce.len(),
@ -333,9 +360,10 @@ fn xchacha20_poly1305_encrypt(
key.len(), key.len(),
); );
if ret != 0 { if ret != 0 {
return Err(CryptoError::InternalError( return Err(CryptoError::InternalError(format!(
format!("wc_XChaCha20Poly1305_Encrypt returned {}", ret) "wc_XChaCha20Poly1305_Encrypt returned {}",
)); ret
)));
} }
} }
@ -350,7 +378,7 @@ fn xchacha20_poly1305_decrypt(
) -> Result<Vec<u8>, CryptoError> { ) -> Result<Vec<u8>, CryptoError> {
if ciphertext_and_tag.len() < TAG_LEN { if ciphertext_and_tag.len() < TAG_LEN {
return Err(CryptoError::InvalidInput( return Err(CryptoError::InvalidInput(
"XChaCha20-Poly1305: ciphertext too short".into() "XChaCha20-Poly1305: ciphertext too short".into(),
)); ));
} }
let pt_len = ciphertext_and_tag.len() - TAG_LEN; let pt_len = ciphertext_and_tag.len() - TAG_LEN;
@ -364,7 +392,11 @@ fn xchacha20_poly1305_decrypt(
dst.len(), dst.len(),
ciphertext_and_tag.as_ptr(), ciphertext_and_tag.as_ptr(),
ciphertext_and_tag.len(), ciphertext_and_tag.len(),
if aad.is_empty() { std::ptr::null() } else { aad.as_ptr() }, if aad.is_empty() {
std::ptr::null()
} else {
aad.as_ptr()
},
aad.len(), aad.len(),
nonce.as_ptr(), nonce.as_ptr(),
nonce.len(), nonce.len(),

View File

@ -43,86 +43,130 @@ pub fn probe_capabilities(benchmarks: Option<&BenchmarkReport>) -> ProviderCapab
.map(|r| r.efficiency_score) .map(|r| r.efficiency_score)
.unwrap_or(0); .unwrap_or(0);
caps = caps.with_aead(algo, AlgorithmCapability { caps = caps.with_aead(
algo,
AlgorithmCapability {
available, available,
deterministic_io: true, // wolfSSL output is deterministic for same key/nonce deterministic_io: true, // wolfSSL output is deterministic for same key/nonce
efficiency_score: efficiency, efficiency_score: efficiency,
reliability_score: if available { 100 } else { 0 }, reliability_score: if available { 100 } else { 0 },
}); },
);
} }
// Ascon-128a is not in this wolfSSL build. // Ascon-128a is not in this wolfSSL build.
caps = caps.with_aead(AeadAlgorithm::Ascon128a, AlgorithmCapability::unavailable()); caps = caps.with_aead(AeadAlgorithm::Ascon128a, AlgorithmCapability::unavailable());
// ── KDF ────────────────────────────────────────────────────────────────── // ── KDF ──────────────────────────────────────────────────────────────────
for algo in [KdfAlgorithm::Sha256, KdfAlgorithm::Sha384, KdfAlgorithm::Sha512] { for algo in [
KdfAlgorithm::Sha256,
KdfAlgorithm::Sha384,
KdfAlgorithm::Sha512,
] {
let available = crate::kdf::derive_key(algo, b"key", b"salt", b"info", 32).is_ok(); let available = crate::kdf::derive_key(algo, b"key", b"salt", b"info", 32).is_ok();
caps = caps.with_kdf(algo, AlgorithmCapability { caps = caps.with_kdf(
algo,
AlgorithmCapability {
available, available,
deterministic_io: true, deterministic_io: true,
efficiency_score: 80, // HKDF is fast; benchmark not run for KDF efficiency_score: 80, // HKDF is fast; benchmark not run for KDF
reliability_score: if available { 100 } else { 0 }, reliability_score: if available { 100 } else { 0 },
}); },
);
} }
for algo in [KdfAlgorithm::Blake2b512, KdfAlgorithm::Argon2id] { for algo in [KdfAlgorithm::Blake2b512, KdfAlgorithm::Argon2id] {
let salt: &[u8] = if algo == KdfAlgorithm::Argon2id { b"saltsalt" } else { b"" }; let salt: &[u8] = if algo == KdfAlgorithm::Argon2id {
b"saltsalt"
} else {
b""
};
let available = crate::kdf::derive_key(algo, b"key", salt, b"", 32).is_ok(); let available = crate::kdf::derive_key(algo, b"key", salt, b"", 32).is_ok();
caps = caps.with_kdf(algo, AlgorithmCapability { caps = caps.with_kdf(
algo,
AlgorithmCapability {
available, available,
deterministic_io: true, deterministic_io: true,
efficiency_score: if algo == KdfAlgorithm::Argon2id { 10 } else { 70 }, efficiency_score: if algo == KdfAlgorithm::Argon2id {
10
} else {
70
},
reliability_score: if available { 100 } else { 0 }, reliability_score: if available { 100 } else { 0 },
}); },
);
} }
caps = caps.with_kdf(KdfAlgorithm::Kmac256, AlgorithmCapability::unavailable()); caps = caps.with_kdf(KdfAlgorithm::Kmac256, AlgorithmCapability::unavailable());
// ── MAC ────────────────────────────────────────────────────────────────── // ── MAC ──────────────────────────────────────────────────────────────────
for algo in [MacAlgorithm::HmacSha256, MacAlgorithm::HmacSha384, MacAlgorithm::HmacSha512] { for algo in [
MacAlgorithm::HmacSha256,
MacAlgorithm::HmacSha384,
MacAlgorithm::HmacSha512,
] {
let available = crate::mac::compute_mac(algo, b"key", b"data").is_ok(); let available = crate::mac::compute_mac(algo, b"key", b"data").is_ok();
caps = caps.with_mac(algo, AlgorithmCapability { caps = caps.with_mac(
algo,
AlgorithmCapability {
available, available,
deterministic_io: true, deterministic_io: true,
efficiency_score: 85, efficiency_score: 85,
reliability_score: if available { 100 } else { 0 }, reliability_score: if available { 100 } else { 0 },
}); },
);
} }
{ {
let available = crate::mac::compute_mac(MacAlgorithm::Blake2bMac, b"key-that-is-16-by", b"data").is_ok(); let available =
caps = caps.with_mac(MacAlgorithm::Blake2bMac, AlgorithmCapability { crate::mac::compute_mac(MacAlgorithm::Blake2bMac, b"key-that-is-16-by", b"data")
.is_ok();
caps = caps.with_mac(
MacAlgorithm::Blake2bMac,
AlgorithmCapability {
available, available,
deterministic_io: true, deterministic_io: true,
efficiency_score: 90, efficiency_score: 90,
reliability_score: if available { 100 } else { 0 }, reliability_score: if available { 100 } else { 0 },
}); },
);
} }
caps = caps.with_mac(MacAlgorithm::Poly1305, AlgorithmCapability::unavailable()); caps = caps.with_mac(MacAlgorithm::Poly1305, AlgorithmCapability::unavailable());
// ── Hash ───────────────────────────────────────────────────────────────── // ── Hash ─────────────────────────────────────────────────────────────────
for algo in [ for algo in [
HashAlgorithm::Sha256, HashAlgorithm::Sha384, HashAlgorithm::Sha512, HashAlgorithm::Sha256,
HashAlgorithm::Blake2b512, HashAlgorithm::Sha3_256, HashAlgorithm::Sha3_512, HashAlgorithm::Sha384,
HashAlgorithm::Sha512,
HashAlgorithm::Blake2b512,
HashAlgorithm::Sha3_256,
HashAlgorithm::Sha3_512,
] { ] {
let available = crate::hash::hash(algo, b"probe").is_ok(); let available = crate::hash::hash(algo, b"probe").is_ok();
caps = caps.with_hash(algo, AlgorithmCapability { caps = caps.with_hash(
algo,
AlgorithmCapability {
available, available,
deterministic_io: true, deterministic_io: true,
efficiency_score: 90, efficiency_score: 90,
reliability_score: if available { 100 } else { 0 }, reliability_score: if available { 100 } else { 0 },
}); },
);
} }
// ── KEM ────────────────────────────────────────────────────────────────── // ── KEM ──────────────────────────────────────────────────────────────────
for algo in [KemAlgorithm::X25519, KemAlgorithm::X448] { for algo in [KemAlgorithm::X25519, KemAlgorithm::X448] {
let available = crate::kem::generate_keypair(algo).is_ok(); let available = crate::kem::generate_keypair(algo).is_ok();
caps = caps.with_kem(algo, AlgorithmCapability { caps = caps.with_kem(
algo,
AlgorithmCapability {
available, available,
deterministic_io: false, // key generation is randomised deterministic_io: false, // key generation is randomised
efficiency_score: 80, efficiency_score: 80,
reliability_score: if available { 100 } else { 0 }, reliability_score: if available { 100 } else { 0 },
}); },
);
} }
for algo in [ for algo in [
KemAlgorithm::MlKem768, KemAlgorithm::MlKem1024, KemAlgorithm::MlKem768,
KemAlgorithm::MlKem1024,
KemAlgorithm::ClassicMcEliece460896, KemAlgorithm::ClassicMcEliece460896,
] { ] {
caps = caps.with_kem(algo, AlgorithmCapability::unavailable()); caps = caps.with_kem(algo, AlgorithmCapability::unavailable());

View File

@ -42,10 +42,8 @@ fn sha256(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
use std::alloc::{alloc_zeroed, dealloc, Layout}; use std::alloc::{alloc_zeroed, dealloc, Layout};
// Size reported by C's sizeof(wc_Sha256) = 120 bytes; alignment = 16. // Size reported by C's sizeof(wc_Sha256) = 120 bytes; alignment = 16.
let layout = Layout::from_size_align( let layout =
std::mem::size_of::<crate::sys::wc_Sha256>().max(120), Layout::from_size_align(std::mem::size_of::<crate::sys::wc_Sha256>().max(120), 16)
16,
)
.expect("layout must be valid"); .expect("layout must be valid");
let sha_ptr = alloc_zeroed(layout) as *mut crate::sys::wc_Sha256; let sha_ptr = alloc_zeroed(layout) as *mut crate::sys::wc_Sha256;
@ -56,23 +54,33 @@ fn sha256(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
let ret = crate::sys::wc_InitSha256(sha_ptr); let ret = crate::sys::wc_InitSha256(sha_ptr);
if ret != 0 { if ret != 0 {
dealloc(sha_ptr as *mut u8, layout); dealloc(sha_ptr as *mut u8, layout);
return Err(CryptoError::InternalError(format!("wc_InitSha256 returned {ret}"))); return Err(CryptoError::InternalError(format!(
"wc_InitSha256 returned {ret}"
)));
} }
let ret = crate::sys::wc_Sha256Update( let ret = crate::sys::wc_Sha256Update(
sha_ptr, sha_ptr,
if data.is_empty() { std::ptr::null() } else { data.as_ptr() }, if data.is_empty() {
std::ptr::null()
} else {
data.as_ptr()
},
data.len() as u32, data.len() as u32,
); );
if ret != 0 { if ret != 0 {
crate::sys::wc_Sha256Free(sha_ptr); crate::sys::wc_Sha256Free(sha_ptr);
dealloc(sha_ptr as *mut u8, layout); dealloc(sha_ptr as *mut u8, layout);
return Err(CryptoError::InternalError(format!("wc_Sha256Update returned {ret}"))); return Err(CryptoError::InternalError(format!(
"wc_Sha256Update returned {ret}"
)));
} }
let ret = crate::sys::wc_Sha256Final(sha_ptr, out.as_mut_ptr()); let ret = crate::sys::wc_Sha256Final(sha_ptr, out.as_mut_ptr());
crate::sys::wc_Sha256Free(sha_ptr); crate::sys::wc_Sha256Free(sha_ptr);
dealloc(sha_ptr as *mut u8, layout); dealloc(sha_ptr as *mut u8, layout);
if ret != 0 { if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_Sha256Final returned {ret}"))); return Err(CryptoError::InternalError(format!(
"wc_Sha256Final returned {ret}"
)));
} }
} }
Ok(out) Ok(out)
@ -85,17 +93,23 @@ fn sha384(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
let mut sha: crate::sys::wc_Sha512 = std::mem::zeroed(); let mut sha: crate::sys::wc_Sha512 = std::mem::zeroed();
let ret = crate::sys::wc_InitSha384(&mut sha); let ret = crate::sys::wc_InitSha384(&mut sha);
if ret != 0 { if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_InitSha384 returned {ret}"))); return Err(CryptoError::InternalError(format!(
"wc_InitSha384 returned {ret}"
)));
} }
let ret = crate::sys::wc_Sha384Update(&mut sha, data.as_ptr(), data.len() as u32); let ret = crate::sys::wc_Sha384Update(&mut sha, data.as_ptr(), data.len() as u32);
if ret != 0 { if ret != 0 {
crate::sys::wc_Sha384Free(&mut sha); crate::sys::wc_Sha384Free(&mut sha);
return Err(CryptoError::InternalError(format!("wc_Sha384Update returned {ret}"))); return Err(CryptoError::InternalError(format!(
"wc_Sha384Update returned {ret}"
)));
} }
let ret = crate::sys::wc_Sha384Final(&mut sha, out.as_mut_ptr()); let ret = crate::sys::wc_Sha384Final(&mut sha, out.as_mut_ptr());
crate::sys::wc_Sha384Free(&mut sha); crate::sys::wc_Sha384Free(&mut sha);
if ret != 0 { if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_Sha384Final returned {ret}"))); return Err(CryptoError::InternalError(format!(
"wc_Sha384Final returned {ret}"
)));
} }
} }
Ok(out) Ok(out)
@ -107,17 +121,23 @@ fn sha512(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
let mut sha: crate::sys::wc_Sha512 = std::mem::zeroed(); let mut sha: crate::sys::wc_Sha512 = std::mem::zeroed();
let ret = crate::sys::wc_InitSha512(&mut sha); let ret = crate::sys::wc_InitSha512(&mut sha);
if ret != 0 { if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_InitSha512 returned {ret}"))); return Err(CryptoError::InternalError(format!(
"wc_InitSha512 returned {ret}"
)));
} }
let ret = crate::sys::wc_Sha512Update(&mut sha, data.as_ptr(), data.len() as u32); let ret = crate::sys::wc_Sha512Update(&mut sha, data.as_ptr(), data.len() as u32);
if ret != 0 { if ret != 0 {
crate::sys::wc_Sha512Free(&mut sha); crate::sys::wc_Sha512Free(&mut sha);
return Err(CryptoError::InternalError(format!("wc_Sha512Update returned {ret}"))); return Err(CryptoError::InternalError(format!(
"wc_Sha512Update returned {ret}"
)));
} }
let ret = crate::sys::wc_Sha512Final(&mut sha, out.as_mut_ptr()); let ret = crate::sys::wc_Sha512Final(&mut sha, out.as_mut_ptr());
crate::sys::wc_Sha512Free(&mut sha); crate::sys::wc_Sha512Free(&mut sha);
if ret != 0 { if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_Sha512Final returned {ret}"))); return Err(CryptoError::InternalError(format!(
"wc_Sha512Final returned {ret}"
)));
} }
} }
Ok(out) Ok(out)
@ -135,17 +155,23 @@ fn sha3_256(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
let mut sha: crate::sys::wc_Sha3 = std::mem::zeroed(); let mut sha: crate::sys::wc_Sha3 = std::mem::zeroed();
let ret = crate::sys::wc_InitSha3_256(&mut sha, std::ptr::null_mut(), -2); let ret = crate::sys::wc_InitSha3_256(&mut sha, std::ptr::null_mut(), -2);
if ret != 0 { if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_InitSha3_256 returned {ret}"))); return Err(CryptoError::InternalError(format!(
"wc_InitSha3_256 returned {ret}"
)));
} }
let ret = crate::sys::wc_Sha3_256_Update(&mut sha, data.as_ptr(), data.len() as u32); let ret = crate::sys::wc_Sha3_256_Update(&mut sha, data.as_ptr(), data.len() as u32);
if ret != 0 { if ret != 0 {
crate::sys::wc_Sha3_256_Free(&mut sha); crate::sys::wc_Sha3_256_Free(&mut sha);
return Err(CryptoError::InternalError(format!("wc_Sha3_256_Update returned {ret}"))); return Err(CryptoError::InternalError(format!(
"wc_Sha3_256_Update returned {ret}"
)));
} }
let ret = crate::sys::wc_Sha3_256_Final(&mut sha, out.as_mut_ptr()); let ret = crate::sys::wc_Sha3_256_Final(&mut sha, out.as_mut_ptr());
crate::sys::wc_Sha3_256_Free(&mut sha); crate::sys::wc_Sha3_256_Free(&mut sha);
if ret != 0 { if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_Sha3_256_Final returned {ret}"))); return Err(CryptoError::InternalError(format!(
"wc_Sha3_256_Final returned {ret}"
)));
} }
} }
Ok(out) Ok(out)
@ -157,17 +183,23 @@ fn sha3_512(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
let mut sha: crate::sys::wc_Sha3 = std::mem::zeroed(); let mut sha: crate::sys::wc_Sha3 = std::mem::zeroed();
let ret = crate::sys::wc_InitSha3_512(&mut sha, std::ptr::null_mut(), -2); let ret = crate::sys::wc_InitSha3_512(&mut sha, std::ptr::null_mut(), -2);
if ret != 0 { if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_InitSha3_512 returned {ret}"))); return Err(CryptoError::InternalError(format!(
"wc_InitSha3_512 returned {ret}"
)));
} }
let ret = crate::sys::wc_Sha3_512_Update(&mut sha, data.as_ptr(), data.len() as u32); let ret = crate::sys::wc_Sha3_512_Update(&mut sha, data.as_ptr(), data.len() as u32);
if ret != 0 { if ret != 0 {
crate::sys::wc_Sha3_512_Free(&mut sha); crate::sys::wc_Sha3_512_Free(&mut sha);
return Err(CryptoError::InternalError(format!("wc_Sha3_512_Update returned {ret}"))); return Err(CryptoError::InternalError(format!(
"wc_Sha3_512_Update returned {ret}"
)));
} }
let ret = crate::sys::wc_Sha3_512_Final(&mut sha, out.as_mut_ptr()); let ret = crate::sys::wc_Sha3_512_Final(&mut sha, out.as_mut_ptr());
crate::sys::wc_Sha3_512_Free(&mut sha); crate::sys::wc_Sha3_512_Free(&mut sha);
if ret != 0 { if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_Sha3_512_Final returned {ret}"))); return Err(CryptoError::InternalError(format!(
"wc_Sha3_512_Final returned {ret}"
)));
} }
} }
Ok(out) Ok(out)
@ -187,16 +219,22 @@ pub fn blake2b_512(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
let mut b2b: crate::sys::Blake2b = std::mem::zeroed(); let mut b2b: crate::sys::Blake2b = std::mem::zeroed();
let ret = crate::sys::wc_InitBlake2b(&mut b2b, OUT_LEN); let ret = crate::sys::wc_InitBlake2b(&mut b2b, OUT_LEN);
if ret != 0 { if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_InitBlake2b returned {ret}"))); return Err(CryptoError::InternalError(format!(
"wc_InitBlake2b returned {ret}"
)));
} }
let ret = crate::sys::wc_Blake2bUpdate(&mut b2b, data.as_ptr(), data.len() as u32); let ret = crate::sys::wc_Blake2bUpdate(&mut b2b, data.as_ptr(), data.len() as u32);
if ret != 0 { if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_Blake2bUpdate returned {ret}"))); return Err(CryptoError::InternalError(format!(
"wc_Blake2bUpdate returned {ret}"
)));
} }
// requestSz = OUT_LEN → produce the full 64-byte digest. // requestSz = OUT_LEN → produce the full 64-byte digest.
let ret = crate::sys::wc_Blake2bFinal(&mut b2b, out.as_mut_ptr(), OUT_LEN); let ret = crate::sys::wc_Blake2bFinal(&mut b2b, out.as_mut_ptr(), OUT_LEN);
if ret != 0 { if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_Blake2bFinal returned {ret}"))); return Err(CryptoError::InternalError(format!(
"wc_Blake2bFinal returned {ret}"
)));
} }
} }
Ok(out) Ok(out)

View File

@ -54,8 +54,7 @@ pub fn derive_key(
KdfAlgorithm::Sha512 => hkdf(WC_HASH_TYPE_SHA512, ikm, salt, info, length), KdfAlgorithm::Sha512 => hkdf(WC_HASH_TYPE_SHA512, ikm, salt, info, length),
KdfAlgorithm::Blake2b512 => blake2b_kdf(ikm, salt, info, length), KdfAlgorithm::Blake2b512 => blake2b_kdf(ikm, salt, info, length),
KdfAlgorithm::Argon2id => argon2id(ikm, salt, length), KdfAlgorithm::Argon2id => argon2id(ikm, salt, length),
KdfAlgorithm::Kmac256 => KdfAlgorithm::Kmac256 => Err(CryptoError::FeatureNotCompiled("KMAC256 (Phase 5+)".into())),
Err(CryptoError::FeatureNotCompiled("KMAC256 (Phase 5+)".into())),
} }
} }
@ -103,16 +102,20 @@ fn hkdf_extract(
unsafe { unsafe {
let ret = crate::sys::wc_Tls13_HKDF_Extract( let ret = crate::sys::wc_Tls13_HKDF_Extract(
prk.as_mut_ptr(), prk.as_mut_ptr(),
if salt.is_empty() { std::ptr::null() } else { salt.as_ptr() }, if salt.is_empty() {
std::ptr::null()
} else {
salt.as_ptr()
},
salt.len() as u32, salt.len() as u32,
ikm_buf.as_mut_ptr(), ikm_buf.as_mut_ptr(),
ikm_buf.len() as u32, ikm_buf.len() as u32,
hash_type, hash_type,
); );
if ret != 0 { if ret != 0 {
return Err(CryptoError::InternalError( return Err(CryptoError::InternalError(format!(
format!("wc_Tls13_HKDF_Extract returned {ret}") "wc_Tls13_HKDF_Extract returned {ret}"
)); )));
} }
} }
Ok(prk) Ok(prk)
@ -133,9 +136,9 @@ fn hkdf_expand(
// Maximum HKDF output is 255 * HashLen (RFC 5869 §2.3). // Maximum HKDF output is 255 * HashLen (RFC 5869 §2.3).
let max_len = 255 * hash_len; let max_len = 255 * hash_len;
if length > max_len { if length > max_len {
return Err(CryptoError::InvalidInput( return Err(CryptoError::InvalidInput(format!(
format!("HKDF: requested length {length} exceeds maximum {max_len}") "HKDF: requested length {length} exceeds maximum {max_len}"
)); )));
} }
let mut okm = Zeroizing::new(vec![0u8; length]); let mut okm = Zeroizing::new(vec![0u8; length]);
@ -156,9 +159,9 @@ fn hkdf_expand(
let copy_len = (length - pos).min(hash_len); let copy_len = (length - pos).min(hash_len);
okm[pos..pos + copy_len].copy_from_slice(&t[..copy_len]); okm[pos..pos + copy_len].copy_from_slice(&t[..copy_len]);
pos += copy_len; pos += copy_len;
counter = counter.checked_add(1).ok_or_else(|| { counter = counter
CryptoError::InternalError("HKDF counter overflow".into()) .checked_add(1)
})?; .ok_or_else(|| CryptoError::InternalError("HKDF counter overflow".into()))?;
} }
Ok(okm) Ok(okm)
@ -194,7 +197,7 @@ fn blake2b_kdf(
// callers should use HKDF-SHA512 instead. // callers should use HKDF-SHA512 instead.
if length > digest.len() { if length > digest.len() {
return Err(CryptoError::InvalidInput( return Err(CryptoError::InvalidInput(
"BLAKE2b-512 KDF: requested length exceeds 64 bytes; use HKDF-SHA512".into() "BLAKE2b-512 KDF: requested length exceeds 64 bytes; use HKDF-SHA512".into(),
)); ));
} }
@ -221,7 +224,7 @@ fn argon2id(
// Argon2 requires at least 8 bytes of salt. // Argon2 requires at least 8 bytes of salt.
if salt.len() < 8 { if salt.len() < 8 {
return Err(CryptoError::InvalidInput( return Err(CryptoError::InvalidInput(
"Argon2id requires at least 8-byte salt".into() "Argon2id requires at least 8-byte salt".into(),
)); ));
} }

View File

@ -21,13 +21,11 @@ pub fn generate_keypair(algo: KemAlgorithm) -> Result<KemKeyPair, CryptoError> {
match algo { match algo {
KemAlgorithm::X25519 => x25519_generate(), KemAlgorithm::X25519 => x25519_generate(),
KemAlgorithm::X448 => x448_generate(), KemAlgorithm::X448 => x448_generate(),
KemAlgorithm::MlKem768 | KemAlgorithm::MlKem1024 => KemAlgorithm::MlKem768 | KemAlgorithm::MlKem1024 => Err(CryptoError::FeatureNotCompiled(
Err(CryptoError::FeatureNotCompiled( "ML-KEM is deferred to Phase 5 (requires wolfSSL PQ build)".into(),
"ML-KEM is deferred to Phase 5 (requires wolfSSL PQ build)".into()
)), )),
KemAlgorithm::ClassicMcEliece460896 => KemAlgorithm::ClassicMcEliece460896 => Err(CryptoError::FeatureNotCompiled(
Err(CryptoError::FeatureNotCompiled( "Classic-McEliece is deferred to Phase 5".into(),
"Classic-McEliece is deferred to Phase 5".into()
)), )),
} }
} }
@ -45,13 +43,11 @@ pub fn encapsulate(
match algo { match algo {
KemAlgorithm::X25519 => x25519_encapsulate(recipient_public_key), KemAlgorithm::X25519 => x25519_encapsulate(recipient_public_key),
KemAlgorithm::X448 => x448_encapsulate(recipient_public_key), KemAlgorithm::X448 => x448_encapsulate(recipient_public_key),
KemAlgorithm::MlKem768 | KemAlgorithm::MlKem1024 => KemAlgorithm::MlKem768 | KemAlgorithm::MlKem1024 => Err(CryptoError::FeatureNotCompiled(
Err(CryptoError::FeatureNotCompiled( "ML-KEM is deferred to Phase 5".into(),
"ML-KEM is deferred to Phase 5".into()
)), )),
KemAlgorithm::ClassicMcEliece460896 => KemAlgorithm::ClassicMcEliece460896 => Err(CryptoError::FeatureNotCompiled(
Err(CryptoError::FeatureNotCompiled( "Classic-McEliece is deferred to Phase 5".into(),
"Classic-McEliece is deferred to Phase 5".into()
)), )),
} }
} }
@ -66,13 +62,11 @@ pub fn decapsulate(
match algo { match algo {
KemAlgorithm::X25519 => x25519_decapsulate(private_key, peer_ephemeral_public_key), KemAlgorithm::X25519 => x25519_decapsulate(private_key, peer_ephemeral_public_key),
KemAlgorithm::X448 => x448_decapsulate(private_key, peer_ephemeral_public_key), KemAlgorithm::X448 => x448_decapsulate(private_key, peer_ephemeral_public_key),
KemAlgorithm::MlKem768 | KemAlgorithm::MlKem1024 => KemAlgorithm::MlKem768 | KemAlgorithm::MlKem1024 => Err(CryptoError::FeatureNotCompiled(
Err(CryptoError::FeatureNotCompiled( "ML-KEM is deferred to Phase 5".into(),
"ML-KEM is deferred to Phase 5".into()
)), )),
KemAlgorithm::ClassicMcEliece460896 => KemAlgorithm::ClassicMcEliece460896 => Err(CryptoError::FeatureNotCompiled(
Err(CryptoError::FeatureNotCompiled( "Classic-McEliece is deferred to Phase 5".into(),
"Classic-McEliece is deferred to Phase 5".into()
)), )),
} }
} }
@ -96,15 +90,19 @@ fn x25519_generate() -> Result<KemKeyPair, CryptoError> {
let ret = crate::sys::wc_InitRng(&mut rng); let ret = crate::sys::wc_InitRng(&mut rng);
if ret != 0 { if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_InitRng returned {}", ret))); return Err(CryptoError::InternalError(format!(
"wc_InitRng returned {}",
ret
)));
} }
let ret = crate::sys::wc_curve25519_make_key(&mut rng, 32, &mut key); let ret = crate::sys::wc_curve25519_make_key(&mut rng, 32, &mut key);
if ret != 0 { if ret != 0 {
crate::sys::wc_FreeRng(&mut rng); crate::sys::wc_FreeRng(&mut rng);
return Err(CryptoError::InternalError( return Err(CryptoError::InternalError(format!(
format!("wc_curve25519_make_key returned {}", ret) "wc_curve25519_make_key returned {}",
)); ret
)));
} }
let mut pub_sz = public_key.len() as u32; let mut pub_sz = public_key.len() as u32;
@ -114,23 +112,29 @@ fn x25519_generate() -> Result<KemKeyPair, CryptoError> {
// material is in RFC 7748 canonical format. // material is in RFC 7748 canonical format.
let ret = crate::sys::wc_curve25519_export_key_raw_ex( let ret = crate::sys::wc_curve25519_export_key_raw_ex(
&mut key, &mut key,
private_key.as_mut_ptr(), &mut priv_sz, private_key.as_mut_ptr(),
public_key.as_mut_ptr(), &mut pub_sz, &mut priv_sz,
public_key.as_mut_ptr(),
&mut pub_sz,
X25519_LE, X25519_LE,
); );
if ret != 0 { if ret != 0 {
crate::sys::wc_curve25519_free(&mut key); crate::sys::wc_curve25519_free(&mut key);
crate::sys::wc_FreeRng(&mut rng); crate::sys::wc_FreeRng(&mut rng);
return Err(CryptoError::InternalError( return Err(CryptoError::InternalError(format!(
format!("wc_curve25519_export_key_raw_ex returned {}", ret) "wc_curve25519_export_key_raw_ex returned {}",
)); ret
)));
} }
crate::sys::wc_curve25519_free(&mut key); crate::sys::wc_curve25519_free(&mut key);
crate::sys::wc_FreeRng(&mut rng); crate::sys::wc_FreeRng(&mut rng);
} }
Ok(KemKeyPair { public_key, private_key }) Ok(KemKeyPair {
public_key,
private_key,
})
} }
fn x25519_encapsulate(recipient_pub: &[u8]) -> Result<KemEncapResult, CryptoError> { fn x25519_encapsulate(recipient_pub: &[u8]) -> Result<KemEncapResult, CryptoError> {
@ -163,7 +167,7 @@ fn x25519_decapsulate(
fn x25519_dh(private_key: &[u8], public_key: &[u8]) -> Result<Zeroizing<Vec<u8>>, CryptoError> { fn x25519_dh(private_key: &[u8], public_key: &[u8]) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
if private_key.len() != 32 || public_key.len() != 32 { if private_key.len() != 32 || public_key.len() != 32 {
return Err(CryptoError::InvalidKey( return Err(CryptoError::InvalidKey(
"X25519: keys must be 32 bytes each".into() "X25519: keys must be 32 bytes each".into(),
)); ));
} }
@ -176,44 +180,50 @@ fn x25519_dh(private_key: &[u8], public_key: &[u8]) -> Result<Zeroizing<Vec<u8>>
// Initialise both key structs before use (sets dp, calls fe_init, etc.). // Initialise both key structs before use (sets dp, calls fe_init, etc.).
let ret = crate::sys::wc_curve25519_init(&mut local_key); let ret = crate::sys::wc_curve25519_init(&mut local_key);
if ret != 0 { if ret != 0 {
return Err(CryptoError::InternalError( return Err(CryptoError::InternalError(format!(
format!("wc_curve25519_init (local) returned {}", ret) "wc_curve25519_init (local) returned {}",
)); ret
)));
} }
let ret = crate::sys::wc_curve25519_init(&mut remote_key); let ret = crate::sys::wc_curve25519_init(&mut remote_key);
if ret != 0 { if ret != 0 {
crate::sys::wc_curve25519_free(&mut local_key); crate::sys::wc_curve25519_free(&mut local_key);
return Err(CryptoError::InternalError( return Err(CryptoError::InternalError(format!(
format!("wc_curve25519_init (remote) returned {}", ret) "wc_curve25519_init (remote) returned {}",
)); ret
)));
} }
// Import private key in little-endian (EC25519_LITTLE_ENDIAN = 0). // Import private key in little-endian (EC25519_LITTLE_ENDIAN = 0).
let ret = crate::sys::wc_curve25519_import_private_ex( let ret = crate::sys::wc_curve25519_import_private_ex(
private_key.as_ptr(), 32, private_key.as_ptr(),
32,
&mut local_key, &mut local_key,
X25519_LE, X25519_LE,
); );
if ret != 0 { if ret != 0 {
crate::sys::wc_curve25519_free(&mut local_key); crate::sys::wc_curve25519_free(&mut local_key);
crate::sys::wc_curve25519_free(&mut remote_key); crate::sys::wc_curve25519_free(&mut remote_key);
return Err(CryptoError::InvalidKey( return Err(CryptoError::InvalidKey(format!(
format!("wc_curve25519_import_private_ex returned {}", ret) "wc_curve25519_import_private_ex returned {}",
)); ret
)));
} }
// Import remote public key in little-endian. // Import remote public key in little-endian.
let ret = crate::sys::wc_curve25519_import_public_ex( let ret = crate::sys::wc_curve25519_import_public_ex(
public_key.as_ptr(), 32, public_key.as_ptr(),
32,
&mut remote_key, &mut remote_key,
X25519_LE, X25519_LE,
); );
if ret != 0 { if ret != 0 {
crate::sys::wc_curve25519_free(&mut local_key); crate::sys::wc_curve25519_free(&mut local_key);
crate::sys::wc_curve25519_free(&mut remote_key); crate::sys::wc_curve25519_free(&mut remote_key);
return Err(CryptoError::InvalidKey( return Err(CryptoError::InvalidKey(format!(
format!("wc_curve25519_import_public_ex returned {}", ret) "wc_curve25519_import_public_ex returned {}",
)); ret
)));
} }
let mut shared_sz = 32u32; let mut shared_sz = 32u32;
@ -231,9 +241,10 @@ fn x25519_dh(private_key: &[u8], public_key: &[u8]) -> Result<Zeroizing<Vec<u8>>
crate::sys::wc_curve25519_free(&mut remote_key); crate::sys::wc_curve25519_free(&mut remote_key);
if ret != 0 { if ret != 0 {
return Err(CryptoError::InternalError( return Err(CryptoError::InternalError(format!(
format!("wc_curve25519_shared_secret_ex returned {}", ret) "wc_curve25519_shared_secret_ex returned {}",
)); ret
)));
} }
} }
@ -259,15 +270,19 @@ fn x448_generate() -> Result<KemKeyPair, CryptoError> {
let ret = crate::sys::wc_InitRng(&mut rng); let ret = crate::sys::wc_InitRng(&mut rng);
if ret != 0 { if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_InitRng returned {}", ret))); return Err(CryptoError::InternalError(format!(
"wc_InitRng returned {}",
ret
)));
} }
let ret = crate::sys::wc_curve448_make_key(&mut rng, 56, &mut key); let ret = crate::sys::wc_curve448_make_key(&mut rng, 56, &mut key);
if ret != 0 { if ret != 0 {
crate::sys::wc_FreeRng(&mut rng); crate::sys::wc_FreeRng(&mut rng);
return Err(CryptoError::InternalError( return Err(CryptoError::InternalError(format!(
format!("wc_curve448_make_key returned {}", ret) "wc_curve448_make_key returned {}",
)); ret
)));
} }
let mut pub_sz = public_key.len() as u32; let mut pub_sz = public_key.len() as u32;
@ -276,23 +291,29 @@ fn x448_generate() -> Result<KemKeyPair, CryptoError> {
// Export in little-endian (EC448_LITTLE_ENDIAN=0), RFC 7748 canonical format. // Export in little-endian (EC448_LITTLE_ENDIAN=0), RFC 7748 canonical format.
let ret = crate::sys::wc_curve448_export_key_raw_ex( let ret = crate::sys::wc_curve448_export_key_raw_ex(
&mut key, &mut key,
private_key.as_mut_ptr(), &mut priv_sz, private_key.as_mut_ptr(),
public_key.as_mut_ptr(), &mut pub_sz, &mut priv_sz,
public_key.as_mut_ptr(),
&mut pub_sz,
X448_LE, X448_LE,
); );
if ret != 0 { if ret != 0 {
crate::sys::wc_curve448_free(&mut key); crate::sys::wc_curve448_free(&mut key);
crate::sys::wc_FreeRng(&mut rng); crate::sys::wc_FreeRng(&mut rng);
return Err(CryptoError::InternalError( return Err(CryptoError::InternalError(format!(
format!("wc_curve448_export_key_raw_ex returned {}", ret) "wc_curve448_export_key_raw_ex returned {}",
)); ret
)));
} }
crate::sys::wc_curve448_free(&mut key); crate::sys::wc_curve448_free(&mut key);
crate::sys::wc_FreeRng(&mut rng); crate::sys::wc_FreeRng(&mut rng);
} }
Ok(KemKeyPair { public_key, private_key }) Ok(KemKeyPair {
public_key,
private_key,
})
} }
fn x448_encapsulate(recipient_pub: &[u8]) -> Result<KemEncapResult, CryptoError> { fn x448_encapsulate(recipient_pub: &[u8]) -> Result<KemEncapResult, CryptoError> {
@ -319,7 +340,9 @@ fn x448_decapsulate(
/// pattern) before importing keys. /// pattern) before importing keys.
fn x448_dh(private_key: &[u8], public_key: &[u8]) -> Result<Zeroizing<Vec<u8>>, CryptoError> { fn x448_dh(private_key: &[u8], public_key: &[u8]) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
if private_key.len() != 56 || public_key.len() != 56 { if private_key.len() != 56 || public_key.len() != 56 {
return Err(CryptoError::InvalidKey("X448: keys must be 56 bytes each".into())); return Err(CryptoError::InvalidKey(
"X448: keys must be 56 bytes each".into(),
));
} }
let mut shared = Zeroizing::new(vec![0u8; 56]); let mut shared = Zeroizing::new(vec![0u8; 56]);
@ -331,40 +354,50 @@ fn x448_dh(private_key: &[u8], public_key: &[u8]) -> Result<Zeroizing<Vec<u8>>,
// Initialise both key structs before use. // Initialise both key structs before use.
let ret = crate::sys::wc_curve448_init(&mut local_key); let ret = crate::sys::wc_curve448_init(&mut local_key);
if ret != 0 { if ret != 0 {
return Err(CryptoError::InternalError( return Err(CryptoError::InternalError(format!(
format!("wc_curve448_init (local) returned {}", ret) "wc_curve448_init (local) returned {}",
)); ret
)));
} }
let ret = crate::sys::wc_curve448_init(&mut remote_key); let ret = crate::sys::wc_curve448_init(&mut remote_key);
if ret != 0 { if ret != 0 {
crate::sys::wc_curve448_free(&mut local_key); crate::sys::wc_curve448_free(&mut local_key);
return Err(CryptoError::InternalError( return Err(CryptoError::InternalError(format!(
format!("wc_curve448_init (remote) returned {}", ret) "wc_curve448_init (remote) returned {}",
)); ret
)));
} }
// Import private key in little-endian (EC448_LITTLE_ENDIAN = 0). // Import private key in little-endian (EC448_LITTLE_ENDIAN = 0).
let ret = crate::sys::wc_curve448_import_private_ex( let ret = crate::sys::wc_curve448_import_private_ex(
private_key.as_ptr(), 56, &mut local_key, X448_LE, private_key.as_ptr(),
56,
&mut local_key,
X448_LE,
); );
if ret != 0 { if ret != 0 {
crate::sys::wc_curve448_free(&mut local_key); crate::sys::wc_curve448_free(&mut local_key);
crate::sys::wc_curve448_free(&mut remote_key); crate::sys::wc_curve448_free(&mut remote_key);
return Err(CryptoError::InvalidKey( return Err(CryptoError::InvalidKey(format!(
format!("wc_curve448_import_private_ex returned {}", ret) "wc_curve448_import_private_ex returned {}",
)); ret
)));
} }
// Import remote public key in little-endian. // Import remote public key in little-endian.
let ret = crate::sys::wc_curve448_import_public_ex( let ret = crate::sys::wc_curve448_import_public_ex(
public_key.as_ptr(), 56, &mut remote_key, X448_LE, public_key.as_ptr(),
56,
&mut remote_key,
X448_LE,
); );
if ret != 0 { if ret != 0 {
crate::sys::wc_curve448_free(&mut local_key); crate::sys::wc_curve448_free(&mut local_key);
crate::sys::wc_curve448_free(&mut remote_key); crate::sys::wc_curve448_free(&mut remote_key);
return Err(CryptoError::InvalidKey( return Err(CryptoError::InvalidKey(format!(
format!("wc_curve448_import_public_ex returned {}", ret) "wc_curve448_import_public_ex returned {}",
)); ret
)));
} }
let mut shared_sz = 56u32; let mut shared_sz = 56u32;
@ -381,9 +414,10 @@ fn x448_dh(private_key: &[u8], public_key: &[u8]) -> Result<Zeroizing<Vec<u8>>,
crate::sys::wc_curve448_free(&mut remote_key); crate::sys::wc_curve448_free(&mut remote_key);
if ret != 0 { if ret != 0 {
return Err(CryptoError::InternalError( return Err(CryptoError::InternalError(format!(
format!("wc_curve448_shared_secret_ex returned {}", ret) "wc_curve448_shared_secret_ex returned {}",
)); ret
)));
} }
} }

View File

@ -17,10 +17,17 @@ pub mod kdf;
pub mod kem; pub mod kem;
pub mod mac; pub mod mac;
pub mod provider; pub mod provider;
mod self_test;
// FFI bindings generated by bindgen in build.rs. // FFI bindings generated by bindgen in build.rs.
// When wolfSSL headers are not present (e.g. docs.rs), use a stub. // When wolfSSL headers are not present (e.g. docs.rs), use a stub.
#[allow(non_camel_case_types, non_snake_case, non_upper_case_globals, dead_code, clippy::all)] #[allow(
non_camel_case_types,
non_snake_case,
non_upper_case_globals,
dead_code,
clippy::all
)]
mod sys { mod sys {
// ── Manual ABI-critical type definitions ──────────────────────────────── // ── Manual ABI-critical type definitions ────────────────────────────────
// //
@ -112,48 +119,98 @@ mod sys {
// ── AES-GCM ───────────────────────────────────────────────────────── // ── AES-GCM ─────────────────────────────────────────────────────────
pub unsafe fn wc_AesGcmSetKey(aes: *mut Aes, key: *const c_uchar, len: c_uint) -> c_int { unreachable!() } pub unsafe fn wc_AesGcmSetKey(aes: *mut Aes, key: *const c_uchar, len: c_uint) -> c_int {
unreachable!()
}
pub unsafe fn wc_AesGcmEncrypt( pub unsafe fn wc_AesGcmEncrypt(
aes: *mut Aes, out: *mut c_uchar, in_: *const c_uchar, sz: c_uint, aes: *mut Aes,
iv: *const c_uchar, iv_sz: c_uint, auth_tag: *mut c_uchar, auth_tag_sz: c_uint, out: *mut c_uchar,
auth_in: *const c_uchar, auth_in_sz: c_uint, in_: *const c_uchar,
) -> c_int { unreachable!() } sz: c_uint,
iv: *const c_uchar,
iv_sz: c_uint,
auth_tag: *mut c_uchar,
auth_tag_sz: c_uint,
auth_in: *const c_uchar,
auth_in_sz: c_uint,
) -> c_int {
unreachable!()
}
pub unsafe fn wc_AesGcmDecrypt( pub unsafe fn wc_AesGcmDecrypt(
aes: *mut Aes, out: *mut c_uchar, in_: *const c_uchar, sz: c_uint, aes: *mut Aes,
iv: *const c_uchar, iv_sz: c_uint, auth_tag: *const c_uchar, auth_tag_sz: c_uint, out: *mut c_uchar,
auth_in: *const c_uchar, auth_in_sz: c_uint, in_: *const c_uchar,
) -> c_int { unreachable!() } sz: c_uint,
pub unsafe fn wc_AesInit(aes: *mut Aes, heap: *mut c_void, dev_id: c_int) -> c_int { unreachable!() } iv: *const c_uchar,
pub unsafe fn wc_AesFree(aes: *mut Aes) { unreachable!() } iv_sz: c_uint,
auth_tag: *const c_uchar,
auth_tag_sz: c_uint,
auth_in: *const c_uchar,
auth_in_sz: c_uint,
) -> c_int {
unreachable!()
}
pub unsafe fn wc_AesInit(aes: *mut Aes, heap: *mut c_void, dev_id: c_int) -> c_int {
unreachable!()
}
pub unsafe fn wc_AesFree(aes: *mut Aes) {
unreachable!()
}
// ── ChaCha20-Poly1305 ──────────────────────────────────────────────── // ── ChaCha20-Poly1305 ────────────────────────────────────────────────
pub unsafe fn wc_ChaCha20Poly1305_Encrypt( pub unsafe fn wc_ChaCha20Poly1305_Encrypt(
key: *const c_uchar, iv: *const c_uchar, key: *const c_uchar,
aad: *const c_uchar, aad_sz: c_uint, iv: *const c_uchar,
in_: *const c_uchar, in_sz: c_uint, aad: *const c_uchar,
out: *mut c_uchar, auth_tag: *mut c_uchar, aad_sz: c_uint,
) -> c_int { unreachable!() } in_: *const c_uchar,
in_sz: c_uint,
out: *mut c_uchar,
auth_tag: *mut c_uchar,
) -> c_int {
unreachable!()
}
pub unsafe fn wc_ChaCha20Poly1305_Decrypt( pub unsafe fn wc_ChaCha20Poly1305_Decrypt(
key: *const c_uchar, iv: *const c_uchar, key: *const c_uchar,
aad: *const c_uchar, aad_sz: c_uint, iv: *const c_uchar,
in_: *const c_uchar, in_sz: c_uint, aad: *const c_uchar,
auth_tag: *const c_uchar, out: *mut c_uchar, aad_sz: c_uint,
) -> c_int { unreachable!() } in_: *const c_uchar,
in_sz: c_uint,
auth_tag: *const c_uchar,
out: *mut c_uchar,
) -> c_int {
unreachable!()
}
pub unsafe fn wc_XChaCha20Poly1305_Encrypt( pub unsafe fn wc_XChaCha20Poly1305_Encrypt(
dst: *mut c_uchar, dst_space: usize, dst: *mut c_uchar,
src: *const c_uchar, src_len: usize, dst_space: usize,
ad: *const c_uchar, ad_len: usize, src: *const c_uchar,
nonce: *const c_uchar, nonce_len: usize, src_len: usize,
key: *const c_uchar, key_len: usize, ad: *const c_uchar,
) -> c_int { unreachable!() } ad_len: usize,
nonce: *const c_uchar,
nonce_len: usize,
key: *const c_uchar,
key_len: usize,
) -> c_int {
unreachable!()
}
pub unsafe fn wc_XChaCha20Poly1305_Decrypt( pub unsafe fn wc_XChaCha20Poly1305_Decrypt(
dst: *mut c_uchar, dst_space: usize, dst: *mut c_uchar,
src: *const c_uchar, src_len: usize, dst_space: usize,
ad: *const c_uchar, ad_len: usize, src: *const c_uchar,
nonce: *const c_uchar, nonce_len: usize, src_len: usize,
key: *const c_uchar, key_len: usize, ad: *const c_uchar,
) -> c_int { unreachable!() } ad_len: usize,
nonce: *const c_uchar,
nonce_len: usize,
key: *const c_uchar,
key_len: usize,
) -> c_int {
unreachable!()
}
// ── SHA-256 (init/update/final) ────────────────────────────────────── // ── SHA-256 (init/update/final) ──────────────────────────────────────
@ -195,76 +252,161 @@ mod sys {
pub unsafe fn wc_InitBlake2b(b2b: *mut Blake2b, digest_sz: c_uint) -> c_int { unreachable!() } pub unsafe fn wc_InitBlake2b(b2b: *mut Blake2b, digest_sz: c_uint) -> c_int { unreachable!() }
pub unsafe fn wc_InitBlake2b_WithKey( pub unsafe fn wc_InitBlake2b_WithKey(
b2b: *mut Blake2b, digest_sz: c_uint, b2b: *mut Blake2b,
key: *const c_uchar, key_sz: c_uint, digest_sz: c_uint,
) -> c_int { unreachable!() } key: *const c_uchar,
pub unsafe fn wc_Blake2bUpdate(b2b: *mut Blake2b, in_: *const c_uchar, in_sz: c_uint) -> c_int { unreachable!() } key_sz: c_uint,
pub unsafe fn wc_Blake2bFinal(b2b: *mut Blake2b, final_: *mut c_uchar, request_sz: c_uint) -> c_int { unreachable!() } ) -> c_int {
unreachable!()
}
pub unsafe fn wc_Blake2bUpdate(
b2b: *mut Blake2b,
in_: *const c_uchar,
in_sz: c_uint,
) -> c_int {
unreachable!()
}
pub unsafe fn wc_Blake2bFinal(
b2b: *mut Blake2b,
final_: *mut c_uchar,
request_sz: c_uint,
) -> c_int {
unreachable!()
}
// ── HMAC ──────────────────────────────────────────────────────────── // ── HMAC ────────────────────────────────────────────────────────────
pub unsafe fn wc_HmacInit(hmac: *mut Hmac, heap: *mut c_void, dev_id: c_int) -> c_int { unreachable!() } pub unsafe fn wc_HmacInit(hmac: *mut Hmac, heap: *mut c_void, dev_id: c_int) -> c_int { unreachable!() }
pub unsafe fn wc_HmacSetKey(hmac: *mut Hmac, type_: c_int, key: *const c_uchar, key_sz: c_uint) -> c_int { unreachable!() }
pub unsafe fn wc_HmacUpdate(hmac: *mut Hmac, in_: *const c_uchar, in_sz: c_uint) -> c_int { unreachable!() } pub unsafe fn wc_HmacUpdate(hmac: *mut Hmac, in_: *const c_uchar, in_sz: c_uint) -> c_int { unreachable!() }
pub unsafe fn wc_HmacFinal(hmac: *mut Hmac, out: *mut c_uchar) -> c_int { unreachable!() } pub unsafe fn wc_HmacFinal(hmac: *mut Hmac, out: *mut c_uchar) -> c_int { unreachable!() }
pub unsafe fn wc_HmacFree(hmac: *mut Hmac) { unreachable!() } pub unsafe fn wc_HmacFree(hmac: *mut Hmac) { unreachable!() }
pub unsafe fn wc_HmacSetKey(
hmac: *mut Hmac,
type_: c_int,
key: *const c_uchar,
key_sz: c_uint,
) -> c_int {
unreachable!()
}
// ── HKDF (TLS-1.3 Extract — RFC 5869 Extract step) ────────────────── // ── HKDF (TLS-1.3 Extract — RFC 5869 Extract step) ──────────────────
pub unsafe fn wc_Tls13_HKDF_Extract( pub unsafe fn wc_Tls13_HKDF_Extract(
prk: *mut c_uchar, prk: *mut c_uchar,
salt: *const c_uchar, salt_len: c_uint, salt: *const c_uchar,
ikm: *mut c_uchar, ikm_len: c_uint, salt_len: c_uint,
ikm: *mut c_uchar,
ikm_len: c_uint,
digest: c_int, digest: c_int,
) -> c_int { unreachable!() } ) -> c_int {
unreachable!()
}
// ── RNG ───────────────────────────────────────────────────────────── // ── RNG ─────────────────────────────────────────────────────────────
pub unsafe fn wc_InitRng(rng: *mut WC_RNG) -> c_int { unreachable!() } pub unsafe fn wc_InitRng(rng: *mut WC_RNG) -> c_int { unreachable!() }
pub unsafe fn wc_RNG_GenerateBlock(rng: *mut WC_RNG, buf: *mut c_uchar, sz: c_uint) -> c_int { unreachable!() }
pub unsafe fn wc_FreeRng(rng: *mut WC_RNG) -> c_int { unreachable!() } pub unsafe fn wc_FreeRng(rng: *mut WC_RNG) -> c_int { unreachable!() }
pub unsafe fn wc_RNG_GenerateBlock(
rng: *mut WC_RNG,
buf: *mut c_uchar,
sz: c_uint,
) -> c_int {
unreachable!()
}
// ── Curve25519 ─────────────────────────────────────────────────────── // ── Curve25519 ───────────────────────────────────────────────────────
pub unsafe fn wc_curve25519_make_key(rng: *mut WC_RNG, key_sz: c_int, key: *mut curve25519_key) -> c_int { unreachable!() }
pub unsafe fn wc_curve25519_init(key: *mut curve25519_key) -> c_int { unreachable!() } pub unsafe fn wc_curve25519_init(key: *mut curve25519_key) -> c_int { unreachable!() }
pub unsafe fn wc_curve25519_make_key(
rng: *mut WC_RNG,
key_sz: c_int,
key: *mut curve25519_key,
) -> c_int {
unreachable!()
}
pub unsafe fn wc_curve25519_export_key_raw( pub unsafe fn wc_curve25519_export_key_raw(
key: *mut curve25519_key, key: *mut curve25519_key,
priv_: *mut c_uchar, priv_sz: *mut c_uint, priv_: *mut c_uchar,
pub_: *mut c_uchar, pub_sz: *mut c_uint, priv_sz: *mut c_uint,
) -> c_int { unreachable!() } pub_: *mut c_uchar,
pub_sz: *mut c_uint,
) -> c_int {
unreachable!()
}
pub unsafe fn wc_curve25519_import_private( pub unsafe fn wc_curve25519_import_private(
priv_: *const c_uchar, priv_sz: c_uint, key: *mut curve25519_key, priv_: *const c_uchar,
) -> c_int { unreachable!() } priv_sz: c_uint,
key: *mut curve25519_key,
) -> c_int {
unreachable!()
}
pub unsafe fn wc_curve25519_import_public( pub unsafe fn wc_curve25519_import_public(
pub_: *const c_uchar, pub_sz: c_uint, key: *mut curve25519_key, pub_: *const c_uchar,
) -> c_int { unreachable!() } pub_sz: c_uint,
key: *mut curve25519_key,
) -> c_int {
unreachable!()
}
pub unsafe fn wc_curve25519_shared_secret_ex( pub unsafe fn wc_curve25519_shared_secret_ex(
local: *mut curve25519_key, remote: *mut curve25519_key, local: *mut curve25519_key,
out: *mut c_uchar, out_sz: *mut c_uint, endian: c_int, remote: *mut curve25519_key,
) -> c_int { unreachable!() } out: *mut c_uchar,
pub unsafe fn wc_curve25519_free(key: *mut curve25519_key) { unreachable!() } out_sz: *mut c_uint,
endian: c_int,
) -> c_int {
unreachable!()
}
pub unsafe fn wc_curve25519_free(key: *mut curve25519_key) {
unreachable!()
}
// ── Curve448 ───────────────────────────────────────────────────────── // ── Curve448 ─────────────────────────────────────────────────────────
pub unsafe fn wc_curve448_make_key(rng: *mut WC_RNG, key_sz: c_int, key: *mut curve448_key) -> c_int { unreachable!() } pub unsafe fn wc_curve448_make_key(
pub unsafe fn wc_curve448_init(key: *mut curve448_key) -> c_int { unreachable!() } rng: *mut WC_RNG,
key_sz: c_int,
key: *mut curve448_key,
) -> c_int {
unreachable!()
}
pub unsafe fn wc_curve448_init(key: *mut curve448_key) -> c_int {
unreachable!()
}
pub unsafe fn wc_curve448_export_key_raw( pub unsafe fn wc_curve448_export_key_raw(
key: *mut curve448_key, key: *mut curve448_key,
priv_: *mut c_uchar, priv_sz: *mut c_uint, priv_: *mut c_uchar,
pub_: *mut c_uchar, pub_sz: *mut c_uint, priv_sz: *mut c_uint,
) -> c_int { unreachable!() } pub_: *mut c_uchar,
pub_sz: *mut c_uint,
) -> c_int {
unreachable!()
}
pub unsafe fn wc_curve448_import_private( pub unsafe fn wc_curve448_import_private(
priv_: *const c_uchar, priv_sz: c_uint, key: *mut curve448_key, priv_: *const c_uchar,
) -> c_int { unreachable!() } priv_sz: c_uint,
key: *mut curve448_key,
) -> c_int {
unreachable!()
}
pub unsafe fn wc_curve448_import_public( pub unsafe fn wc_curve448_import_public(
pub_: *const c_uchar, pub_sz: c_uint, key: *mut curve448_key, pub_: *const c_uchar,
) -> c_int { unreachable!() } pub_sz: c_uint,
key: *mut curve448_key,
) -> c_int {
unreachable!()
}
pub unsafe fn wc_curve448_shared_secret_ex( pub unsafe fn wc_curve448_shared_secret_ex(
local: *mut curve448_key, remote: *mut curve448_key, local: *mut curve448_key,
out: *mut c_uchar, out_sz: *mut c_uint, endian: c_int, remote: *mut curve448_key,
) -> c_int { unreachable!() } out: *mut c_uchar,
pub unsafe fn wc_curve448_free(key: *mut curve448_key) { unreachable!() } out_sz: *mut c_uint,
endian: c_int,
) -> c_int {
unreachable!()
}
pub unsafe fn wc_curve448_free(key: *mut curve448_key) {
unreachable!()
}
} }
} }
@ -276,7 +418,6 @@ pub use provider::WolfSslProvider;
/// Call this once from `ccc_init()` in the bridge crate. /// Call this once from `ccc_init()` in the bridge crate.
pub fn init() { pub fn init() {
let provider = WolfSslProvider::new(); let provider = WolfSslProvider::new();
ccc_crypto_core::ProviderRegistry::global() ccc_crypto_core::ProviderRegistry::global().register("wolfssl", Box::new(provider));
.register("wolfssl", Box::new(provider));
log::info!("[ccc-crypto-wolfssl] provider registered"); log::info!("[ccc-crypto-wolfssl] provider registered");
} }

View File

@ -17,19 +17,14 @@ const WC_HASH_TYPE_SHA512: i32 = 8;
// ────────────────────────────────────────────────────────────────────────────── // ──────────────────────────────────────────────────────────────────────────────
/// Compute a MAC tag over `data` using the specified algorithm and `key`. /// Compute a MAC tag over `data` using the specified algorithm and `key`.
pub fn compute_mac( pub fn compute_mac(algo: MacAlgorithm, key: &[u8], data: &[u8]) -> Result<Vec<u8>, CryptoError> {
algo: MacAlgorithm,
key: &[u8],
data: &[u8],
) -> Result<Vec<u8>, CryptoError> {
match algo { match algo {
MacAlgorithm::HmacSha256 => hmac(WC_HASH_TYPE_SHA256, key, data, 32), MacAlgorithm::HmacSha256 => hmac(WC_HASH_TYPE_SHA256, key, data, 32),
MacAlgorithm::HmacSha384 => hmac(WC_HASH_TYPE_SHA384, key, data, 48), MacAlgorithm::HmacSha384 => hmac(WC_HASH_TYPE_SHA384, key, data, 48),
MacAlgorithm::HmacSha512 => hmac(WC_HASH_TYPE_SHA512, key, data, 64), MacAlgorithm::HmacSha512 => hmac(WC_HASH_TYPE_SHA512, key, data, 64),
MacAlgorithm::Blake2bMac => blake2b_mac(key, data), MacAlgorithm::Blake2bMac => blake2b_mac(key, data),
MacAlgorithm::Poly1305 => MacAlgorithm::Poly1305 => Err(CryptoError::UnsupportedAlgorithm(
Err(CryptoError::UnsupportedAlgorithm( "Poly1305 standalone MAC is not exposed in this Phase".into(),
"Poly1305 standalone MAC is not exposed in this Phase".into()
)), )),
} }
} }
@ -71,10 +66,7 @@ pub(crate) fn hmac(
use std::alloc::{alloc_zeroed, dealloc, Layout}; use std::alloc::{alloc_zeroed, dealloc, Layout};
// sizeof(Hmac) = 456 bytes; alignment = 16 (from embedded wc_Sha256). // sizeof(Hmac) = 456 bytes; alignment = 16 (from embedded wc_Sha256).
let layout = Layout::from_size_align( let layout = Layout::from_size_align(std::mem::size_of::<crate::sys::Hmac>().max(456), 16)
std::mem::size_of::<crate::sys::Hmac>().max(456),
16,
)
.expect("layout must be valid"); .expect("layout must be valid");
let hmac_ptr = alloc_zeroed(layout) as *mut crate::sys::Hmac; let hmac_ptr = alloc_zeroed(layout) as *mut crate::sys::Hmac;
@ -90,40 +82,40 @@ pub(crate) fn hmac(
); );
if ret != 0 { if ret != 0 {
dealloc(hmac_ptr as *mut u8, layout); dealloc(hmac_ptr as *mut u8, layout);
return Err(CryptoError::InternalError(format!("wc_HmacInit returned {}", ret))); return Err(CryptoError::InternalError(format!(
"wc_HmacInit returned {}",
ret
)));
} }
let ret = crate::sys::wc_HmacSetKey( let ret = crate::sys::wc_HmacSetKey(hmac_ptr, hash_type, key.as_ptr(), key.len() as u32);
hmac_ptr,
hash_type,
key.as_ptr(),
key.len() as u32,
);
if ret != 0 { if ret != 0 {
crate::sys::wc_HmacFree(hmac_ptr); crate::sys::wc_HmacFree(hmac_ptr);
dealloc(hmac_ptr as *mut u8, layout); dealloc(hmac_ptr as *mut u8, layout);
return Err(CryptoError::InvalidKey(format!("wc_HmacSetKey returned {}", ret))); return Err(CryptoError::InvalidKey(format!(
"wc_HmacSetKey returned {}",
ret
)));
} }
let ret = crate::sys::wc_HmacUpdate( let ret = crate::sys::wc_HmacUpdate(hmac_ptr, data.as_ptr(), data.len() as u32);
hmac_ptr,
data.as_ptr(),
data.len() as u32,
);
if ret != 0 { if ret != 0 {
crate::sys::wc_HmacFree(hmac_ptr); crate::sys::wc_HmacFree(hmac_ptr);
dealloc(hmac_ptr as *mut u8, layout); dealloc(hmac_ptr as *mut u8, layout);
return Err(CryptoError::InternalError(format!("wc_HmacUpdate returned {}", ret))); return Err(CryptoError::InternalError(format!(
"wc_HmacUpdate returned {}",
ret
)));
} }
let ret = crate::sys::wc_HmacFinal( let ret = crate::sys::wc_HmacFinal(hmac_ptr, out.as_mut_ptr());
hmac_ptr,
out.as_mut_ptr(),
);
if ret != 0 { if ret != 0 {
crate::sys::wc_HmacFree(hmac_ptr); crate::sys::wc_HmacFree(hmac_ptr);
dealloc(hmac_ptr as *mut u8, layout); dealloc(hmac_ptr as *mut u8, layout);
return Err(CryptoError::InternalError(format!("wc_HmacFinal returned {}", ret))); return Err(CryptoError::InternalError(format!(
"wc_HmacFinal returned {}",
ret
)));
} }
crate::sys::wc_HmacFree(hmac_ptr); crate::sys::wc_HmacFree(hmac_ptr);
@ -146,7 +138,7 @@ pub(crate) fn hmac(
fn blake2b_mac(key: &[u8], data: &[u8]) -> Result<Vec<u8>, CryptoError> { fn blake2b_mac(key: &[u8], data: &[u8]) -> Result<Vec<u8>, CryptoError> {
if key.is_empty() || key.len() > 64 { if key.is_empty() || key.len() > 64 {
return Err(CryptoError::InvalidKey( return Err(CryptoError::InvalidKey(
"BLAKE2b-MAC: key must be 164 bytes".into() "BLAKE2b-MAC: key must be 164 bytes".into(),
)); ));
} }
@ -157,30 +149,26 @@ fn blake2b_mac(key: &[u8], data: &[u8]) -> Result<Vec<u8>, CryptoError> {
let mut b2b: crate::sys::Blake2b = std::mem::zeroed(); let mut b2b: crate::sys::Blake2b = std::mem::zeroed();
// Initialise with digest size and key for keyed (MAC) mode. // Initialise with digest size and key for keyed (MAC) mode.
let ret = crate::sys::wc_InitBlake2b_WithKey( let ret =
&mut b2b, crate::sys::wc_InitBlake2b_WithKey(&mut b2b, OUT_LEN, key.as_ptr(), key.len() as u32);
OUT_LEN,
key.as_ptr(),
key.len() as u32,
);
if ret != 0 { if ret != 0 {
return Err(CryptoError::InternalError( return Err(CryptoError::InternalError(format!(
format!("wc_InitBlake2b_WithKey returned {ret}") "wc_InitBlake2b_WithKey returned {ret}"
)); )));
} }
let ret = crate::sys::wc_Blake2bUpdate(&mut b2b, data.as_ptr(), data.len() as u32); let ret = crate::sys::wc_Blake2bUpdate(&mut b2b, data.as_ptr(), data.len() as u32);
if ret != 0 { if ret != 0 {
return Err(CryptoError::InternalError( return Err(CryptoError::InternalError(format!(
format!("wc_Blake2bUpdate returned {ret}") "wc_Blake2bUpdate returned {ret}"
)); )));
} }
let ret = crate::sys::wc_Blake2bFinal(&mut b2b, out.as_mut_ptr(), OUT_LEN); let ret = crate::sys::wc_Blake2bFinal(&mut b2b, out.as_mut_ptr(), OUT_LEN);
if ret != 0 { if ret != 0 {
return Err(CryptoError::InternalError( return Err(CryptoError::InternalError(format!(
format!("wc_Blake2bFinal returned {ret}") "wc_Blake2bFinal returned {ret}"
)); )));
} }
} }
@ -201,26 +189,13 @@ fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
} }
// Accumulate XOR differences; the branch is on the final accumulated value // Accumulate XOR differences; the branch is on the final accumulated value
// only, not on any individual byte. // only, not on any individual byte.
let diff: u8 = a.iter().zip(b.iter()).fold(0u8, |acc, (x, y)| acc | (x ^ y)); let diff: u8 = a
.iter()
.zip(b.iter())
.fold(0u8, |acc, (x, y)| acc | (x ^ y));
diff == 0 diff == 0
} }
#[cfg(test)] #[cfg(test)]
mod tests { #[path = "mac_tests.rs"]
use super::constant_time_eq; mod tests;
#[test]
fn ct_eq_same() {
assert!(constant_time_eq(b"hello", b"hello"));
}
#[test]
fn ct_eq_different() {
assert!(!constant_time_eq(b"hello", b"Hello"));
}
#[test]
fn ct_eq_different_lengths() {
assert!(!constant_time_eq(b"hi", b"hello"));
}
}

View File

@ -0,0 +1,18 @@
//! Unit tests for constant-time comparison in the MAC module.
use super::constant_time_eq;
#[test]
fn ct_eq_same() {
assert!(constant_time_eq(b"hello", b"hello"));
}
#[test]
fn ct_eq_different() {
assert!(!constant_time_eq(b"hello", b"Hello"));
}
#[test]
fn ct_eq_different_lengths() {
assert!(!constant_time_eq(b"hi", b"hello"));
}

View File

@ -12,56 +12,11 @@ use ccc_crypto_core::{
algorithms::{AeadAlgorithm, HashAlgorithm, KdfAlgorithm, KemAlgorithm, MacAlgorithm}, algorithms::{AeadAlgorithm, HashAlgorithm, KdfAlgorithm, KemAlgorithm, MacAlgorithm},
capabilities::ProviderCapabilities, capabilities::ProviderCapabilities,
error::CryptoError, error::CryptoError,
provider::{ provider::{AeadProvider, CryptoProvider, HashProvider, KdfProvider, KemProvider, MacProvider},
AeadProvider, CryptoProvider, HashProvider, KdfProvider, KemProvider, MacProvider, types::{BenchmarkReport, KemEncapResult, KemKeyPair, SelfTestReport},
},
types::{AlgoTestResult, BenchmarkReport, KemEncapResult, KemKeyPair, SelfTestReport},
}; };
use crate::{aead, capabilities, hash, kdf, kem, mac}; use crate::{aead, capabilities, hash, kdf, kem, mac, self_test};
// ──────────────────────────────────────────────────────────────────────────────
// Embedded NIST / RFC test vectors for self_test()
// These are checked at runtime; failures gate the provider from being marked
// `available` in the capability catalog.
// ──────────────────────────────────────────────────────────────────────────────
struct AeadVector {
algo: AeadAlgorithm,
key: &'static str,
nonce: &'static str,
aad: &'static str,
pt: &'static str,
ct_tag: &'static str, // ciphertext || tag, hex-encoded
}
/// NIST SP 800-38D and RFC 8439 test vectors.
static AEAD_VECTORS: &[AeadVector] = &[
// ── AES-256-GCM: NIST CAVS test case GCM-256/96/128 (test case 1) ───────
AeadVector {
algo: AeadAlgorithm::AesGcm256,
key: "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308",
nonce: "cafebabefacedbaddecaf888",
aad: "",
pt: "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a72\
1c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255",
ct_tag: "522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa\
8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662898015ad",
},
// ── ChaCha20-Poly1305: RFC 8439 §2.8.2 test vector ──────────────────────
AeadVector {
algo: AeadAlgorithm::ChaCha20Poly1305,
key: "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
nonce: "070000004041424344454647",
aad: "50515253c0c1c2c3c4c5c6c7",
pt: "4c616469657320616e642047656e746c656d656e206f662074686520636c617373\
206f6620273939",
ct_tag: "d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63\
dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b369\
2ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc3\
ff4def08e4b7a9de576d26586cec64b6116",
},
];
// ────────────────────────────────────────────────────────────────────────────── // ──────────────────────────────────────────────────────────────────────────────
// WolfSslProvider // WolfSslProvider
@ -97,14 +52,19 @@ impl WolfSslProvider {
self.bench.get_or_init(|| { self.bench.get_or_init(|| {
log::info!("[ccc-crypto-wolfssl] running throughput benchmark…"); log::info!("[ccc-crypto-wolfssl] running throughput benchmark…");
let report = capabilities::run_benchmark(); let report = capabilities::run_benchmark();
log::info!("[ccc-crypto-wolfssl] benchmark complete ({} algos)", report.results.len()); log::info!(
"[ccc-crypto-wolfssl] benchmark complete ({} algos)",
report.results.len()
);
report report
}) })
} }
} }
impl Default for WolfSslProvider { impl Default for WolfSslProvider {
fn default() -> Self { Self::new() } fn default() -> Self {
Self::new()
}
} }
// ────────────────────────────────────────────────────────────────────────────── // ──────────────────────────────────────────────────────────────────────────────
@ -113,15 +73,23 @@ impl Default for WolfSslProvider {
impl AeadProvider for WolfSslProvider { impl AeadProvider for WolfSslProvider {
fn encrypt_aead( fn encrypt_aead(
&self, algo: AeadAlgorithm, key: &[u8], nonce: &[u8], &self,
plaintext: &[u8], aad: &[u8], algo: AeadAlgorithm,
key: &[u8],
nonce: &[u8],
plaintext: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, CryptoError> { ) -> Result<Vec<u8>, CryptoError> {
aead::encrypt(algo, key, nonce, plaintext, aad) aead::encrypt(algo, key, nonce, plaintext, aad)
} }
fn decrypt_aead( fn decrypt_aead(
&self, algo: AeadAlgorithm, key: &[u8], nonce: &[u8], &self,
ciphertext_and_tag: &[u8], aad: &[u8], algo: AeadAlgorithm,
key: &[u8],
nonce: &[u8],
ciphertext_and_tag: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, CryptoError> { ) -> Result<Vec<u8>, CryptoError> {
aead::decrypt(algo, key, nonce, ciphertext_and_tag, aad) aead::decrypt(algo, key, nonce, ciphertext_and_tag, aad)
} }
@ -129,7 +97,12 @@ impl AeadProvider for WolfSslProvider {
impl KdfProvider for WolfSslProvider { impl KdfProvider for WolfSslProvider {
fn derive_key( fn derive_key(
&self, algo: KdfAlgorithm, ikm: &[u8], salt: &[u8], info: &[u8], length: usize, &self,
algo: KdfAlgorithm,
ikm: &[u8],
salt: &[u8],
info: &[u8],
length: usize,
) -> Result<Zeroizing<Vec<u8>>, CryptoError> { ) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
kdf::derive_key(algo, ikm, salt, info, length) kdf::derive_key(algo, ikm, salt, info, length)
} }
@ -137,13 +110,20 @@ impl KdfProvider for WolfSslProvider {
impl MacProvider for WolfSslProvider { impl MacProvider for WolfSslProvider {
fn compute_mac( fn compute_mac(
&self, algo: MacAlgorithm, key: &[u8], data: &[u8], &self,
algo: MacAlgorithm,
key: &[u8],
data: &[u8],
) -> Result<Vec<u8>, CryptoError> { ) -> Result<Vec<u8>, CryptoError> {
mac::compute_mac(algo, key, data) mac::compute_mac(algo, key, data)
} }
fn verify_mac( fn verify_mac(
&self, algo: MacAlgorithm, key: &[u8], data: &[u8], mac_bytes: &[u8], &self,
algo: MacAlgorithm,
key: &[u8],
data: &[u8],
mac_bytes: &[u8],
) -> Result<bool, CryptoError> { ) -> Result<bool, CryptoError> {
mac::verify_mac(algo, key, data, mac_bytes) mac::verify_mac(algo, key, data, mac_bytes)
} }
@ -162,12 +142,17 @@ impl KemProvider for WolfSslProvider {
kem::generate_keypair(algo) kem::generate_keypair(algo)
} }
fn encapsulate( fn encapsulate(
&self, algo: KemAlgorithm, public_key: &[u8], &self,
algo: KemAlgorithm,
public_key: &[u8],
) -> Result<KemEncapResult, CryptoError> { ) -> Result<KemEncapResult, CryptoError> {
kem::encapsulate(algo, public_key) kem::encapsulate(algo, public_key)
} }
fn decapsulate( fn decapsulate(
&self, algo: KemAlgorithm, private_key: &[u8], ciphertext: &[u8], &self,
algo: KemAlgorithm,
private_key: &[u8],
ciphertext: &[u8],
) -> Result<Zeroizing<Vec<u8>>, CryptoError> { ) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
kem::decapsulate(algo, private_key, ciphertext) kem::decapsulate(algo, private_key, ciphertext)
} }
@ -178,25 +163,21 @@ impl KemProvider for WolfSslProvider {
// ────────────────────────────────────────────────────────────────────────────── // ──────────────────────────────────────────────────────────────────────────────
impl CryptoProvider for WolfSslProvider { impl CryptoProvider for WolfSslProvider {
fn provider_name(&self) -> &'static str { "wolfssl" } fn provider_name(&self) -> &'static str {
"wolfssl"
}
fn capabilities(&self) -> ProviderCapabilities { fn capabilities(&self) -> ProviderCapabilities {
self.caps.get_or_init(|| { self.caps
.get_or_init(|| {
let bench = self.get_or_run_benchmark(); let bench = self.get_or_run_benchmark();
capabilities::probe_capabilities(Some(bench)) capabilities::probe_capabilities(Some(bench))
}).clone() })
.clone()
} }
fn self_test(&self) -> SelfTestReport { fn self_test(&self) -> SelfTestReport {
let mut results: Vec<AlgoTestResult> = Vec::new(); self_test::run()
// Run AEAD test vectors.
for v in AEAD_VECTORS {
let result = run_aead_vector(v);
results.push(result);
}
SelfTestReport::finalise("wolfssl", results)
} }
fn benchmark(&self) -> BenchmarkReport { fn benchmark(&self) -> BenchmarkReport {
@ -204,47 +185,4 @@ impl CryptoProvider for WolfSslProvider {
} }
} }
// ──────────────────────────────────────────────────────────────────────────────
// Test vector runner
// ──────────────────────────────────────────────────────────────────────────────
fn run_aead_vector(v: &AeadVector) -> AlgoTestResult {
let test_name = format!("{} NIST/RFC vector", v.algo.name());
let decode = |hex: &str| -> Vec<u8> {
(0..hex.len())
.step_by(2)
.map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap_or(0))
.collect()
};
let key = decode(v.key);
let nonce = decode(v.nonce);
let aad = decode(v.aad);
let pt = decode(v.pt);
let expected = decode(v.ct_tag);
match aead::encrypt(v.algo, &key, &nonce, &pt, &aad) {
Ok(ct_tag) if ct_tag == expected => AlgoTestResult {
algo_id: v.algo as u32,
algo_name: test_name,
passed: true,
error_message: None,
},
Ok(ct_tag) => AlgoTestResult {
algo_id: v.algo as u32,
algo_name: test_name.clone(),
passed: false,
error_message: Some(format!(
"output mismatch: got {} bytes, expected {} bytes",
ct_tag.len(), expected.len()
)),
},
Err(e) => AlgoTestResult {
algo_id: v.algo as u32,
algo_name: test_name,
passed: false,
error_message: Some(e.to_string()),
},
}
}

View File

@ -0,0 +1,113 @@
//! Embedded NIST / RFC test vectors for the power-on self-test.
//!
//! These are checked at runtime by [`WolfSslProvider::self_test()`]; failures
//! gate the provider from being marked `available` in the capability catalog.
use ccc_crypto_core::{
algorithms::AeadAlgorithm,
types::{AlgoTestResult, SelfTestReport},
};
use crate::aead;
// ──────────────────────────────────────────────────────────────────────────────
// AEAD vectors
// ──────────────────────────────────────────────────────────────────────────────
struct AeadVector {
algo: AeadAlgorithm,
key: &'static str,
nonce: &'static str,
aad: &'static str,
pt: &'static str,
ct_tag: &'static str, // ciphertext || tag, hex-encoded
}
/// NIST SP 800-38D and RFC 8439 test vectors.
static AEAD_VECTORS: &[AeadVector] = &[
// ── AES-256-GCM: NIST SP 800-38D Test Case 16 ───────────────────────
// Key: feffe9...308308 × 2 (AES-256)
// Nonce: cafebabefacedbaddecaf888
// AAD: feedfacedeadbeeffeedfacedeadbeefabaddad2 (20 bytes)
// PT: d9313225...637b39 (60 bytes)
// ct_tag = ciphertext (60 bytes) || GHASH tag (16 bytes) = 76 bytes
AeadVector {
algo: AeadAlgorithm::AesGcm256,
key: "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308",
nonce: "cafebabefacedbaddecaf888",
aad: "feedfacedeadbeeffeedfacedeadbeefabaddad2",
pt: "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a72\
1c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39",
ct_tag: "522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa\
8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662\
76fc6ece0f4e1768cddf8853bb2d551b",
},
// ── ChaCha20-Poly1305: RFC 8439 §2.8.2 ──────────────────────────────
// PT: "Ladies and Gentlemen of the class of '99" (40 bytes)
// ct_tag = ciphertext (40 bytes) || Poly1305 tag (16 bytes) = 56 bytes
AeadVector {
algo: AeadAlgorithm::ChaCha20Poly1305,
key: "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
nonce: "070000004041424344454647",
aad: "50515253c0c1c2c3c4c5c6c7",
pt: "4c616469657320616e642047656e746c656d656e206f662074686520636c617373\
206f6620273939",
ct_tag: "d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d6\
3dbea45e8ca96712f180d4e9016c65a7dde15e3106075ebd",
},
];
// ──────────────────────────────────────────────────────────────────────────────
// Runner
// ──────────────────────────────────────────────────────────────────────────────
/// Run all embedded self-test vectors and return a [`SelfTestReport`].
pub(crate) fn run() -> SelfTestReport {
let mut results: Vec<AlgoTestResult> = Vec::new();
for v in AEAD_VECTORS {
results.push(run_aead_vector(v));
}
SelfTestReport::finalise("wolfssl", results)
}
fn run_aead_vector(v: &AeadVector) -> AlgoTestResult {
let test_name = format!("{} NIST/RFC vector", v.algo.name());
let decode = |hex: &str| -> Vec<u8> {
(0..hex.len())
.step_by(2)
.map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap_or(0))
.collect()
};
let key = decode(v.key);
let nonce = decode(v.nonce);
let aad = decode(v.aad);
let pt = decode(v.pt);
let expected = decode(v.ct_tag);
match aead::encrypt(v.algo, &key, &nonce, &pt, &aad) {
Ok(ct_tag) if ct_tag == expected => AlgoTestResult {
algo_id: v.algo as u32,
algo_name: test_name,
passed: true,
error_message: None,
},
Ok(ct_tag) => AlgoTestResult {
algo_id: v.algo as u32,
algo_name: test_name.clone(),
passed: false,
error_message: Some(format!(
"output mismatch: got {} bytes, expected {} bytes",
ct_tag.len(),
expected.len()
)),
},
Err(e) => AlgoTestResult {
algo_id: v.algo as u32,
algo_name: test_name,
passed: false,
error_message: Some(e.to_string()),
},
}
}

View File

@ -311,8 +311,8 @@ static XCHACHA20_PROBES: &[XChaChaProbe] = &[
// ────────────────────────────────────────────────────────────────────────────── // ──────────────────────────────────────────────────────────────────────────────
fn from_hex(s: &str) -> Vec<u8> { fn from_hex(s: &str) -> Vec<u8> {
let s = s.replace(' ', "").replace('\n', ""); let s = s.replace([' ', '\n'], "");
assert!(s.len() % 2 == 0, "odd-length hex: {s}"); assert!(s.len().is_multiple_of(2), "odd-length hex: {s}");
(0..s.len()) (0..s.len())
.step_by(2) .step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap()) .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
@ -347,12 +347,24 @@ fn run_aead(p: &WolfSslProvider, failures: &mut usize) {
// Verify round-trip decrypt as well. // Verify round-trip decrypt as well.
match p.decrypt_aead(v.algo, &key, &nonce, &ct_tag, &aad) { match p.decrypt_aead(v.algo, &key, &nonce, &ct_tag, &aad) {
Ok(recovered) if recovered == pt => pass(v.name), Ok(recovered) if recovered == pt => pass(v.name),
Ok(_) => { *failures += 1; println!(" [FAIL] {} (decrypt mismatch)", v.name); } Ok(_) => {
Err(e) => { *failures += 1; println!(" [FAIL] {} (decrypt error: {e})", v.name); } *failures += 1;
println!(" [FAIL] {} (decrypt mismatch)", v.name);
}
Err(e) => {
*failures += 1;
println!(" [FAIL] {} (decrypt error: {e})", v.name);
} }
} }
Ok(ct_tag) => { *failures += 1; fail(v.name, &ct_tag, &expected); } }
Err(e) => { *failures += 1; println!(" [FAIL] {} (encrypt error: {e})", v.name); } Ok(ct_tag) => {
*failures += 1;
fail(v.name, &ct_tag, &expected);
}
Err(e) => {
*failures += 1;
println!(" [FAIL] {} (encrypt error: {e})", v.name);
}
} }
} }
} }
@ -367,8 +379,14 @@ fn run_kdf(p: &WolfSslProvider, failures: &mut usize) {
match p.derive_key(v.algo, &ikm, &salt, &info, v.length) { match p.derive_key(v.algo, &ikm, &salt, &info, v.length) {
Ok(out) if out.as_slice() == expected.as_slice() => pass(v.name), Ok(out) if out.as_slice() == expected.as_slice() => pass(v.name),
Ok(out) => { *failures += 1; fail(v.name, &out, &expected); } Ok(out) => {
Err(e) => { *failures += 1; println!(" [FAIL] {} ({e})", v.name); } *failures += 1;
fail(v.name, &out, &expected);
}
Err(e) => {
*failures += 1;
println!(" [FAIL] {} ({e})", v.name);
}
} }
} }
} }
@ -385,12 +403,24 @@ fn run_mac(p: &WolfSslProvider, failures: &mut usize) {
// Also verify constant-time path. // Also verify constant-time path.
match p.verify_mac(v.algo, &key, &data, &tag) { match p.verify_mac(v.algo, &key, &data, &tag) {
Ok(true) => pass(v.name), Ok(true) => pass(v.name),
Ok(false) => { *failures += 1; println!(" [FAIL] {} (verify false)", v.name); } Ok(false) => {
Err(e) => { *failures += 1; println!(" [FAIL] {} (verify error: {e})", v.name); } *failures += 1;
println!(" [FAIL] {} (verify false)", v.name);
}
Err(e) => {
*failures += 1;
println!(" [FAIL] {} (verify error: {e})", v.name);
} }
} }
Ok(tag) => { *failures += 1; fail(v.name, &tag, &expected); } }
Err(e) => { *failures += 1; println!(" [FAIL] {} ({e})", v.name); } Ok(tag) => {
*failures += 1;
fail(v.name, &tag, &expected);
}
Err(e) => {
*failures += 1;
println!(" [FAIL] {} ({e})", v.name);
}
} }
} }
} }
@ -403,8 +433,14 @@ fn run_hash(p: &WolfSslProvider, failures: &mut usize) {
match p.hash(v.algo, &data) { match p.hash(v.algo, &data) {
Ok(digest) if digest == expected => pass(v.name), Ok(digest) if digest == expected => pass(v.name),
Ok(digest) => { *failures += 1; fail(v.name, &digest, &expected); } Ok(digest) => {
Err(e) => { *failures += 1; println!(" [FAIL] {} ({e})", v.name); } *failures += 1;
fail(v.name, &digest, &expected);
}
Err(e) => {
*failures += 1;
println!(" [FAIL] {} ({e})", v.name);
}
} }
} }
} }
@ -431,16 +467,28 @@ fn run_kem(p: &WolfSslProvider, failures: &mut usize) {
let test_a = format!("{} (Alice→Bob)", v.name); let test_a = format!("{} (Alice→Bob)", v.name);
match p.decapsulate(v.algo, &alice_priv, &bob_pub) { match p.decapsulate(v.algo, &alice_priv, &bob_pub) {
Ok(shared) if shared.as_slice() == expected.as_slice() => pass(&test_a), Ok(shared) if shared.as_slice() == expected.as_slice() => pass(&test_a),
Ok(shared) => { *failures += 1; fail(&test_a, &shared, &expected); } Ok(shared) => {
Err(e) => { *failures += 1; println!(" [FAIL] {} ({e})", test_a); } *failures += 1;
fail(&test_a, &shared, &expected);
}
Err(e) => {
*failures += 1;
println!(" [FAIL] {} ({e})", test_a);
}
} }
// Bob→Alice direction (symmetric). // Bob→Alice direction (symmetric).
let test_b = format!("{} (Bob→Alice)", v.name); let test_b = format!("{} (Bob→Alice)", v.name);
match p.decapsulate(v.algo, &bob_priv, &alice_pub) { match p.decapsulate(v.algo, &bob_priv, &alice_pub) {
Ok(shared) if shared.as_slice() == expected.as_slice() => pass(&test_b), Ok(shared) if shared.as_slice() == expected.as_slice() => pass(&test_b),
Ok(shared) => { *failures += 1; fail(&test_b, &shared, &expected); } Ok(shared) => {
Err(e) => { *failures += 1; println!(" [FAIL] {} ({e})", test_b); } *failures += 1;
fail(&test_b, &shared, &expected);
}
Err(e) => {
*failures += 1;
println!(" [FAIL] {} ({e})", test_b);
}
} }
} }
} }
@ -455,13 +503,21 @@ fn run_kem_roundtrip(p: &WolfSslProvider, failures: &mut usize) {
for algo in [KemAlgorithm::X25519, KemAlgorithm::X448] { for algo in [KemAlgorithm::X25519, KemAlgorithm::X448] {
let name = format!("{:?} ephemeral roundtrip", algo); let name = format!("{:?} ephemeral roundtrip", algo);
match p.generate_keypair(algo) { match p.generate_keypair(algo) {
Err(e) => { *failures += 1; println!(" [FAIL] {} (keygen: {e})", name); continue; } Err(e) => {
Ok(kp) => { *failures += 1;
match p.encapsulate(algo, &kp.public_key) { println!(" [FAIL] {} (keygen: {e})", name);
Err(e) => { *failures += 1; println!(" [FAIL] {} (encap: {e})", name); } continue;
Ok(encap) => { }
match p.decapsulate(algo, &kp.private_key, &encap.ciphertext) { Ok(kp) => match p.encapsulate(algo, &kp.public_key) {
Err(e) => { *failures += 1; println!(" [FAIL] {} (decap: {e})", name); } Err(e) => {
*failures += 1;
println!(" [FAIL] {} (encap: {e})", name);
}
Ok(encap) => match p.decapsulate(algo, &kp.private_key, &encap.ciphertext) {
Err(e) => {
*failures += 1;
println!(" [FAIL] {} (decap: {e})", name);
}
Ok(decap_secret) => { Ok(decap_secret) => {
if decap_secret.as_slice() == encap.shared_secret.as_slice() { if decap_secret.as_slice() == encap.shared_secret.as_slice() {
pass(&name); pass(&name);
@ -472,10 +528,8 @@ fn run_kem_roundtrip(p: &WolfSslProvider, failures: &mut usize) {
println!(" decap: {}", hex::encode(&decap_secret)); println!(" decap: {}", hex::encode(&decap_secret));
} }
} }
} },
} },
}
}
} }
} }
} }
@ -511,10 +565,22 @@ fn run_xchacha20_kat(p: &WolfSslProvider, failures: &mut usize) {
// Print the ct_tag for pinning purposes. // Print the ct_tag for pinning purposes.
println!(" [INFO] {} ct_tag = {}", v.name, hex::encode(&ct_tag)); println!(" [INFO] {} ct_tag = {}", v.name, hex::encode(&ct_tag));
match p.decrypt_aead(AeadAlgorithm::XChaCha20Poly1305, &key, &nonce, &ct_tag, &aad) { match p.decrypt_aead(
AeadAlgorithm::XChaCha20Poly1305,
&key,
&nonce,
&ct_tag,
&aad,
) {
Ok(recovered) if recovered == pt => pass(&rt_name), Ok(recovered) if recovered == pt => pass(&rt_name),
Ok(_) => { *failures += 1; println!(" [FAIL] {} (decrypt mismatch)", rt_name); } Ok(_) => {
Err(e) => { *failures += 1; println!(" [FAIL] {} (decrypt error: {e})", rt_name); } *failures += 1;
println!(" [FAIL] {} (decrypt mismatch)", rt_name);
}
Err(e) => {
*failures += 1;
println!(" [FAIL] {} (decrypt error: {e})", rt_name);
}
} }
// ── Auth-failure check ──────────────────────────────────── // ── Auth-failure check ────────────────────────────────────
@ -522,10 +588,24 @@ fn run_xchacha20_kat(p: &WolfSslProvider, failures: &mut usize) {
let auth_name = format!("{} [auth-fail]", v.name); let auth_name = format!("{} [auth-fail]", v.name);
let mut tampered = ct_tag.clone(); let mut tampered = ct_tag.clone();
*tampered.last_mut().unwrap() ^= 0xff; *tampered.last_mut().unwrap() ^= 0xff;
match p.decrypt_aead(AeadAlgorithm::XChaCha20Poly1305, &key, &nonce, &tampered, &aad) { match p.decrypt_aead(
Err(ccc_crypto_core::error::CryptoError::AuthenticationFailed) => pass(&auth_name), AeadAlgorithm::XChaCha20Poly1305,
Ok(_) => { *failures += 1; println!(" [FAIL] {} (expected auth failure, got Ok)", auth_name); } &key,
Err(e) => { *failures += 1; println!(" [FAIL] {} (wrong error type: {e})", auth_name); } &nonce,
&tampered,
&aad,
) {
Err(ccc_crypto_core::error::CryptoError::AuthenticationFailed) => {
pass(&auth_name)
}
Ok(_) => {
*failures += 1;
println!(" [FAIL] {} (expected auth failure, got Ok)", auth_name);
}
Err(e) => {
*failures += 1;
println!(" [FAIL] {} (wrong error type: {e})", auth_name);
}
} }
} }
} }