Compare commits
No commits in common. "919d4bcc95161ee8e9aa83ad9a3474f407faa9bc" and "aa61de33a260724c83c818ae5d4efc7aaf6ce884" have entirely different histories.
919d4bcc95
...
aa61de33a2
|
|
@ -1,8 +1,4 @@
|
||||||
{
|
{
|
||||||
"[rust]": {
|
|
||||||
"editor.formatOnSave": false,
|
|
||||||
"editor.formatOnPaste": false
|
|
||||||
},
|
|
||||||
"cmake.configureOnOpen": false,
|
"cmake.configureOnOpen": false,
|
||||||
"dart.analysisExcludedFolders": [
|
"dart.analysisExcludedFolders": [
|
||||||
"flutter_src",
|
"flutter_src",
|
||||||
|
|
@ -19,10 +15,6 @@
|
||||||
"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
370
README_DEV.rst
|
|
@ -1,370 +0,0 @@
|
||||||
=============================================
|
|
||||||
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
259
Taskfile.yaml
|
|
@ -1,259 +0,0 @@
|
||||||
# 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
168
ci.sh
|
|
@ -1,168 +0,0 @@
|
||||||
#!/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
|
|
||||||
|
|
@ -42,30 +42,30 @@ impl AeadAlgorithm {
|
||||||
/// Human-readable name, suitable for logs and diagnostics.
|
/// Human-readable name, suitable for logs and diagnostics.
|
||||||
pub fn name(self) -> &'static str {
|
pub fn name(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::AesGcm256 => "AES-256-GCM",
|
Self::AesGcm256 => "AES-256-GCM",
|
||||||
Self::ChaCha20Poly1305 => "ChaCha20-Poly1305",
|
Self::ChaCha20Poly1305 => "ChaCha20-Poly1305",
|
||||||
Self::XChaCha20Poly1305 => "XChaCha20-Poly1305",
|
Self::XChaCha20Poly1305 => "XChaCha20-Poly1305",
|
||||||
Self::Ascon128a => "Ascon-AEAD-128a",
|
Self::Ascon128a => "Ascon-AEAD-128a",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expected nonce length in bytes for this algorithm.
|
/// Expected nonce length in bytes for this algorithm.
|
||||||
pub fn nonce_len(self) -> usize {
|
pub fn nonce_len(self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
Self::AesGcm256 => 12,
|
Self::AesGcm256 => 12,
|
||||||
Self::ChaCha20Poly1305 => 12,
|
Self::ChaCha20Poly1305 => 12,
|
||||||
Self::XChaCha20Poly1305 => 24,
|
Self::XChaCha20Poly1305 => 24,
|
||||||
Self::Ascon128a => 16,
|
Self::Ascon128a => 16,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expected key length in bytes for this algorithm.
|
/// Expected key length in bytes for this algorithm.
|
||||||
pub fn key_len(self) -> usize {
|
pub fn key_len(self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
Self::AesGcm256 => 32,
|
Self::AesGcm256 => 32,
|
||||||
Self::ChaCha20Poly1305 => 32,
|
Self::ChaCha20Poly1305 => 32,
|
||||||
Self::XChaCha20Poly1305 => 32,
|
Self::XChaCha20Poly1305 => 32,
|
||||||
Self::Ascon128a => 16,
|
Self::Ascon128a => 16,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -112,12 +112,12 @@ impl KdfAlgorithm {
|
||||||
/// Human-readable name.
|
/// Human-readable name.
|
||||||
pub fn name(self) -> &'static str {
|
pub fn name(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Sha256 => "HKDF-SHA-256",
|
Self::Sha256 => "HKDF-SHA-256",
|
||||||
Self::Sha384 => "HKDF-SHA-384",
|
Self::Sha384 => "HKDF-SHA-384",
|
||||||
Self::Sha512 => "HKDF-SHA-512",
|
Self::Sha512 => "HKDF-SHA-512",
|
||||||
Self::Blake2b512 => "HKDF-BLAKE2b-512",
|
Self::Blake2b512 => "HKDF-BLAKE2b-512",
|
||||||
Self::Argon2id => "Argon2id",
|
Self::Argon2id => "Argon2id",
|
||||||
Self::Kmac256 => "KMAC256",
|
Self::Kmac256 => "KMAC256",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -223,24 +223,24 @@ impl HashAlgorithm {
|
||||||
/// Human-readable name.
|
/// Human-readable name.
|
||||||
pub fn name(self) -> &'static str {
|
pub fn name(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Sha256 => "SHA-256",
|
Self::Sha256 => "SHA-256",
|
||||||
Self::Sha384 => "SHA-384",
|
Self::Sha384 => "SHA-384",
|
||||||
Self::Sha512 => "SHA-512",
|
Self::Sha512 => "SHA-512",
|
||||||
Self::Blake2b512 => "BLAKE2b-512",
|
Self::Blake2b512 => "BLAKE2b-512",
|
||||||
Self::Sha3_256 => "SHA3-256",
|
Self::Sha3_256 => "SHA3-256",
|
||||||
Self::Sha3_512 => "SHA3-512",
|
Self::Sha3_512 => "SHA3-512",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Digest output length in bytes.
|
/// Digest output length in bytes.
|
||||||
pub fn digest_len(self) -> usize {
|
pub fn digest_len(self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
Self::Sha256 => 32,
|
Self::Sha256 => 32,
|
||||||
Self::Sha384 => 48,
|
Self::Sha384 => 48,
|
||||||
Self::Sha512 => 64,
|
Self::Sha512 => 64,
|
||||||
Self::Blake2b512 => 64,
|
Self::Blake2b512 => 64,
|
||||||
Self::Sha3_256 => 32,
|
Self::Sha3_256 => 32,
|
||||||
Self::Sha3_512 => 64,
|
Self::Sha3_512 => 64,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -285,10 +285,10 @@ impl KemAlgorithm {
|
||||||
/// Human-readable name.
|
/// Human-readable name.
|
||||||
pub fn name(self) -> &'static str {
|
pub fn name(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::X25519 => "X25519",
|
Self::X25519 => "X25519",
|
||||||
Self::X448 => "X448",
|
Self::X448 => "X448",
|
||||||
Self::MlKem768 => "ML-KEM-768",
|
Self::MlKem768 => "ML-KEM-768",
|
||||||
Self::MlKem1024 => "ML-KEM-1024",
|
Self::MlKem1024 => "ML-KEM-1024",
|
||||||
Self::ClassicMcEliece460896 => "Classic-McEliece-460896",
|
Self::ClassicMcEliece460896 => "Classic-McEliece-460896",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,11 @@ 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(&self, algo: HashAlgorithm, data: &[u8]) -> Result<Vec<u8>, CryptoError>;
|
fn hash(
|
||||||
|
&self,
|
||||||
|
algo: HashAlgorithm,
|
||||||
|
data: &[u8],
|
||||||
|
) -> Result<Vec<u8>, CryptoError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Key encapsulation mechanism operations.
|
/// Key encapsulation mechanism operations.
|
||||||
|
|
@ -120,7 +124,10 @@ 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(&self, algo: KemAlgorithm) -> Result<KemKeyPair, CryptoError>;
|
fn generate_keypair(
|
||||||
|
&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`.
|
||||||
///
|
///
|
||||||
|
|
@ -163,9 +170,7 @@ 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:
|
pub trait CryptoProvider: AeadProvider + KdfProvider + MacProvider + HashProvider + Send + Sync {
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,144 @@ impl ProviderRegistry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Tests
|
||||||
|
// ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "registry_tests.rs"]
|
mod tests {
|
||||||
mod tests;
|
//! 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,172 +0,0 @@
|
||||||
//! 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);
|
|
||||||
}
|
|
||||||
|
|
@ -14,10 +14,7 @@
|
||||||
//! 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::{
|
use std::{env, path::PathBuf};
|
||||||
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
|
||||||
|
|
@ -37,31 +34,29 @@ fn main() {
|
||||||
|
|
||||||
// ── 1. Locate wolfSSL ────────────────────────────────────────────────────
|
// ── 1. Locate wolfSSL ────────────────────────────────────────────────────
|
||||||
|
|
||||||
let (include_dir, lib_dir) = if let Ok(install_dir) = env::var("WOLFSSL_INSTALL_DIR") {
|
let (include_dir, lib_dir) =
|
||||||
// Use a pre-installed wolfSSL — skip the CMake build.
|
if let Ok(install_dir) = env::var("WOLFSSL_INSTALL_DIR") {
|
||||||
let p = PathBuf::from(&install_dir);
|
// Use a pre-installed wolfSSL — skip the CMake build.
|
||||||
println!(
|
let p = PathBuf::from(&install_dir);
|
||||||
"cargo:warning=Using pre-installed wolfSSL at {}",
|
println!("cargo:warning=Using pre-installed wolfSSL at {}", install_dir);
|
||||||
install_dir
|
(p.join("include"), p.join("lib"))
|
||||||
);
|
} else {
|
||||||
(p.join("include"), p.join("lib"))
|
// Build from source (default — uses our git submodule).
|
||||||
} else {
|
let source_dir = env::var("WOLFSSL_SOURCE_DIR")
|
||||||
// Build from source (default — uses our git submodule).
|
.map(PathBuf::from)
|
||||||
let source_dir = env::var("WOLFSSL_SOURCE_DIR")
|
.unwrap_or_else(|_| workspace_root.join("vendors/wolfssl"));
|
||||||
.map(PathBuf::from)
|
|
||||||
.unwrap_or_else(|_| workspace_root.join("vendors/wolfssl"));
|
|
||||||
|
|
||||||
if !source_dir.join("CMakeLists.txt").exists() {
|
if !source_dir.join("CMakeLists.txt").exists() {
|
||||||
panic!(
|
panic!(
|
||||||
"\n\nwolfSSL source not found at {}.\n\
|
"\n\nwolfSSL source not found at {}.\n\
|
||||||
Run `git submodule update --init --recursive` to fetch it,\n\
|
Run `git submodule update --init --recursive` to fetch it,\n\
|
||||||
or set WOLFSSL_SOURCE_DIR to an alternative path.\n",
|
or set WOLFSSL_SOURCE_DIR to an alternative path.\n",
|
||||||
source_dir.display()
|
source_dir.display()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
build_wolfssl_cmake(&source_dir, &out_dir)
|
build_wolfssl_cmake(&source_dir, &out_dir)
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── 2. Tell Cargo how to link ────────────────────────────────────────────
|
// ── 2. Tell Cargo how to link ────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -91,7 +86,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: &Path, _out_dir: &Path) -> (PathBuf, PathBuf) {
|
fn build_wolfssl_cmake(source_dir: &PathBuf, _out_dir: &PathBuf) -> (PathBuf, PathBuf) {
|
||||||
println!(
|
println!(
|
||||||
"cargo:warning=Building wolfSSL from source at {}",
|
"cargo:warning=Building wolfSSL from source at {}",
|
||||||
source_dir.display()
|
source_dir.display()
|
||||||
|
|
@ -158,47 +153,21 @@ fn build_wolfssl_cmake(source_dir: &Path, _out_dir: &Path) -> (PathBuf, PathBuf)
|
||||||
.cflag("-DWOLFCRYPT_ONLY")
|
.cflag("-DWOLFCRYPT_ONLY")
|
||||||
// Key & certificate generation.
|
// Key & certificate generation.
|
||||||
// Adds X.509 cert/CSR/key generation capability on top of wolfCrypt.
|
// Adds X.509 cert/CSR/key generation capability on top of wolfCrypt.
|
||||||
.define("WOLFSSL_KEYGEN", "yes") // -DWOLFSSL_KEY_GEN
|
.define("WOLFSSL_KEYGEN", "yes") // -DWOLFSSL_KEY_GEN
|
||||||
.define("WOLFSSL_CERTGEN", "yes") // -DWOLFSSL_CERT_GEN
|
.define("WOLFSSL_CERTGEN", "yes") // -DWOLFSSL_CERT_GEN
|
||||||
.define("WOLFSSL_CERTREQ", "yes") // -DWOLFSSL_CERT_REQ
|
.define("WOLFSSL_CERTREQ", "yes") // -DWOLFSSL_CERT_REQ
|
||||||
.define("WOLFSSL_CERTEXT", "yes"); // -DWOLFSSL_CERT_EXT
|
.define("WOLFSSL_CERTEXT", "yes"); // -DWOLFSSL_CERT_EXT
|
||||||
|
|
||||||
let install_path = cfg.build();
|
let install_path = cfg.build();
|
||||||
|
|
||||||
let include_dir = install_path.join("include");
|
let include_dir = install_path.join("include");
|
||||||
let lib_dir = install_path.join("lib");
|
let lib_dir = install_path.join("lib");
|
||||||
|
|
||||||
(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: &Path, out_dir: &Path) {
|
fn generate_bindings(include_dir: &PathBuf, out_dir: &PathBuf) {
|
||||||
let header = include_dir.join("wolfssl/wolfcrypt/aes.h");
|
let header = include_dir.join("wolfssl/wolfcrypt/aes.h");
|
||||||
|
|
||||||
if !header.exists() {
|
if !header.exists() {
|
||||||
|
|
@ -213,17 +182,7 @@ fn generate_bindings(include_dir: &Path, out_dir: &Path) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the CMake-generated options.h so that bindgen's clang sees the
|
let bindings = bindgen::Builder::default()
|
||||||
// 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())
|
||||||
|
|
@ -237,33 +196,31 @@ fn generate_bindings(include_dir: &Path, out_dir: &Path) {
|
||||||
.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
|
||||||
// Pass all defines from the generated options.h. This includes critical
|
// libclang sees the same feature flags as the C build, regardless of
|
||||||
// struct-affecting flags like GCM_TABLE_4BIT, HAVE___UINT128_T,
|
// whether wolfSSL's options.h is correctly resolved by clang.
|
||||||
// WOLFSSL_USE_ALIGN, etc. that change the layout of Gcm, Aes, and other types.
|
//
|
||||||
for def in &options_defines {
|
// AES-GCM (wc_AesGcmSetKey etc. are behind #ifdef HAVE_AESGCM)
|
||||||
builder = builder.clang_arg(def);
|
.clang_arg("-DHAVE_AESGCM")
|
||||||
}
|
// ChaCha20 / Poly1305 / XChaCha20
|
||||||
|
.clang_arg("-DHAVE_CHACHA")
|
||||||
// Pass additional defines that are added via .cflag() in the CMake build
|
.clang_arg("-DHAVE_POLY1305")
|
||||||
// (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.
|
||||||
// Set the cross-compilation target if it differs from the host.
|
.clang_arg("-DWOLFSSL_SHA384")
|
||||||
if !target.is_empty() {
|
.clang_arg("-DWOLFSSL_SHA512")
|
||||||
// Map Rust target triples to clang-compatible triples.
|
// SHA3 functions (wc_InitSha3_256, etc.)
|
||||||
let clang_target = match target.as_str() {
|
.clang_arg("-DWOLFSSL_SHA3")
|
||||||
"aarch64-apple-ios-sim" => "arm64-apple-ios-simulator".to_string(),
|
// HKDF via TLS-1.3 primitives (wc_Tls13_HKDF_Extract/Expand_Label)
|
||||||
other => other.to_string(),
|
.clang_arg("-DHAVE_HKDF")
|
||||||
};
|
// Curve25519 / Curve448 need HAVE_CURVE25519 / HAVE_CURVE448
|
||||||
builder = builder.clang_arg(format!("--target={}", clang_target));
|
.clang_arg("-DHAVE_CURVE25519")
|
||||||
}
|
.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.*")
|
||||||
|
|
@ -345,7 +302,7 @@ fn generate_bindings(include_dir: &Path, out_dir: &Path) {
|
||||||
.allowlist_type("WC_RNG")
|
.allowlist_type("WC_RNG")
|
||||||
.allowlist_type("OS_Seed")
|
.allowlist_type("OS_Seed")
|
||||||
.allowlist_type("wc_Sha256")
|
.allowlist_type("wc_Sha256")
|
||||||
.allowlist_type("wc_Sha512") // also used as wc_Sha384 (typedef)
|
.allowlist_type("wc_Sha512") // also used as wc_Sha384 (typedef)
|
||||||
.allowlist_type("wc_Sha3")
|
.allowlist_type("wc_Sha3")
|
||||||
.allowlist_type("Blake2b")
|
.allowlist_type("Blake2b")
|
||||||
.allowlist_type("curve25519_key")
|
.allowlist_type("curve25519_key")
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,11 @@ pub fn encrypt(
|
||||||
) -> Result<Vec<u8>, CryptoError> {
|
) -> Result<Vec<u8>, CryptoError> {
|
||||||
validate_key_nonce(algo, key, nonce)?;
|
validate_key_nonce(algo, key, nonce)?;
|
||||||
match algo {
|
match algo {
|
||||||
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 => Err(CryptoError::FeatureNotCompiled("Ascon-128a".into())),
|
AeadAlgorithm::Ascon128a =>
|
||||||
|
Err(CryptoError::FeatureNotCompiled("Ascon-128a".into())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,14 +47,11 @@ pub fn decrypt(
|
||||||
) -> Result<Vec<u8>, CryptoError> {
|
) -> Result<Vec<u8>, CryptoError> {
|
||||||
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 => {
|
AeadAlgorithm::ChaCha20Poly1305 => chacha20_poly1305_decrypt(key, nonce, ciphertext_and_tag, aad),
|
||||||
chacha20_poly1305_decrypt(key, nonce, ciphertext_and_tag, aad)
|
AeadAlgorithm::XChaCha20Poly1305 => xchacha20_poly1305_decrypt(key, nonce, ciphertext_and_tag, aad),
|
||||||
}
|
AeadAlgorithm::Ascon128a =>
|
||||||
AeadAlgorithm::XChaCha20Poly1305 => {
|
Err(CryptoError::FeatureNotCompiled("Ascon-128a".into())),
|
||||||
xchacha20_poly1305_decrypt(key, nonce, ciphertext_and_tag, aad)
|
|
||||||
}
|
|
||||||
AeadAlgorithm::Ascon128a => Err(CryptoError::FeatureNotCompiled("Ascon-128a".into())),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,22 +60,18 @@ pub fn decrypt(
|
||||||
// ──────────────────────────────────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
fn validate_key_nonce(algo: AeadAlgorithm, key: &[u8], nonce: &[u8]) -> Result<(), CryptoError> {
|
fn validate_key_nonce(algo: AeadAlgorithm, key: &[u8], nonce: &[u8]) -> Result<(), CryptoError> {
|
||||||
let expected_key = algo.key_len();
|
let expected_key = algo.key_len();
|
||||||
let expected_nonce = algo.nonce_len();
|
let expected_nonce = algo.nonce_len();
|
||||||
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(),
|
algo.name(), expected_key, key.len()
|
||||||
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(),
|
algo.name(), expected_nonce, nonce.len()
|
||||||
expected_nonce,
|
|
||||||
nonce.len()
|
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -91,7 +85,7 @@ const TAG_LEN: usize = 16;
|
||||||
|
|
||||||
fn aes_gcm_256_encrypt(
|
fn aes_gcm_256_encrypt(
|
||||||
key: &[u8],
|
key: &[u8],
|
||||||
nonce: &[u8], // 12 bytes
|
nonce: &[u8], // 12 bytes
|
||||||
plaintext: &[u8],
|
plaintext: &[u8],
|
||||||
aad: &[u8],
|
aad: &[u8],
|
||||||
) -> Result<Vec<u8>, CryptoError> {
|
) -> Result<Vec<u8>, CryptoError> {
|
||||||
|
|
@ -103,8 +97,12 @@ 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};
|
||||||
|
|
||||||
let layout = Layout::from_size_align(std::mem::size_of::<crate::sys::Aes>(), 16)
|
// sizeof(Aes) = 288 bytes; alignment = 16.
|
||||||
.expect("layout must be valid");
|
let layout = Layout::from_size_align(
|
||||||
|
std::mem::size_of::<crate::sys::Aes>().max(288),
|
||||||
|
16,
|
||||||
|
)
|
||||||
|
.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;
|
||||||
if aes_ptr.is_null() {
|
if aes_ptr.is_null() {
|
||||||
|
|
@ -118,20 +116,18 @@ 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!(
|
return Err(CryptoError::InternalError(format!("wc_AesInit returned {}", ret)));
|
||||||
"wc_AesInit returned {}",
|
|
||||||
ret
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let ret = crate::sys::wc_AesGcmSetKey(aes_ptr, key.as_ptr(), key.len() as u32);
|
let ret = crate::sys::wc_AesGcmSetKey(
|
||||||
|
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!(
|
return Err(CryptoError::InvalidKey(format!("wc_AesGcmSetKey returned {}", ret)));
|
||||||
"wc_AesGcmSetKey returned {}",
|
|
||||||
ret
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let ret = crate::sys::wc_AesGcmEncrypt(
|
let ret = crate::sys::wc_AesGcmEncrypt(
|
||||||
|
|
@ -143,20 +139,13 @@ 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() {
|
if aad.is_empty() { std::ptr::null() } else { aad.as_ptr() },
|
||||||
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!(
|
return Err(CryptoError::InternalError(format!("wc_AesGcmEncrypt returned {}", ret)));
|
||||||
"wc_AesGcmEncrypt returned {}",
|
|
||||||
ret
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,7 +160,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;
|
||||||
|
|
@ -183,8 +172,11 @@ 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(std::mem::size_of::<crate::sys::Aes>(), 16)
|
let layout = Layout::from_size_align(
|
||||||
.expect("layout must be valid");
|
std::mem::size_of::<crate::sys::Aes>().max(288),
|
||||||
|
16,
|
||||||
|
)
|
||||||
|
.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;
|
||||||
if aes_ptr.is_null() {
|
if aes_ptr.is_null() {
|
||||||
|
|
@ -198,20 +190,18 @@ 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!(
|
return Err(CryptoError::InternalError(format!("wc_AesInit returned {}", ret)));
|
||||||
"wc_AesInit returned {}",
|
|
||||||
ret
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let ret = crate::sys::wc_AesGcmSetKey(aes_ptr, key.as_ptr(), key.len() as u32);
|
let ret = crate::sys::wc_AesGcmSetKey(
|
||||||
|
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!(
|
return Err(CryptoError::InvalidKey(format!("wc_AesGcmSetKey returned {}", ret)));
|
||||||
"wc_AesGcmSetKey returned {}",
|
|
||||||
ret
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let ret = crate::sys::wc_AesGcmDecrypt(
|
let ret = crate::sys::wc_AesGcmDecrypt(
|
||||||
|
|
@ -223,11 +213,7 @@ 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() {
|
if aad.is_empty() { std::ptr::null() } else { aad.as_ptr() },
|
||||||
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);
|
||||||
|
|
@ -247,7 +233,7 @@ fn aes_gcm_256_decrypt(
|
||||||
|
|
||||||
fn chacha20_poly1305_encrypt(
|
fn chacha20_poly1305_encrypt(
|
||||||
key: &[u8],
|
key: &[u8],
|
||||||
nonce: &[u8], // 12 bytes
|
nonce: &[u8], // 12 bytes
|
||||||
plaintext: &[u8],
|
plaintext: &[u8],
|
||||||
aad: &[u8],
|
aad: &[u8],
|
||||||
) -> Result<Vec<u8>, CryptoError> {
|
) -> Result<Vec<u8>, CryptoError> {
|
||||||
|
|
@ -259,11 +245,7 @@ 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() {
|
if aad.is_empty() { std::ptr::null() } else { aad.as_ptr() },
|
||||||
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,
|
||||||
|
|
@ -271,10 +253,9 @@ fn chacha20_poly1305_encrypt(
|
||||||
tag_buf.as_mut_ptr(),
|
tag_buf.as_mut_ptr(),
|
||||||
);
|
);
|
||||||
if ret != 0 {
|
if ret != 0 {
|
||||||
return Err(CryptoError::InternalError(format!(
|
return Err(CryptoError::InternalError(
|
||||||
"wc_ChaCha20Poly1305_Encrypt returned {}",
|
format!("wc_ChaCha20Poly1305_Encrypt returned {}", ret)
|
||||||
ret
|
));
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -289,7 +270,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;
|
||||||
|
|
@ -301,11 +282,7 @@ 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() {
|
if aad.is_empty() { std::ptr::null() } else { aad.as_ptr() },
|
||||||
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,
|
||||||
|
|
@ -333,7 +310,7 @@ fn chacha20_poly1305_decrypt(
|
||||||
|
|
||||||
fn xchacha20_poly1305_encrypt(
|
fn xchacha20_poly1305_encrypt(
|
||||||
key: &[u8],
|
key: &[u8],
|
||||||
nonce: &[u8], // 24 bytes
|
nonce: &[u8], // 24 bytes
|
||||||
plaintext: &[u8],
|
plaintext: &[u8],
|
||||||
aad: &[u8],
|
aad: &[u8],
|
||||||
) -> Result<Vec<u8>, CryptoError> {
|
) -> Result<Vec<u8>, CryptoError> {
|
||||||
|
|
@ -348,11 +325,7 @@ fn xchacha20_poly1305_encrypt(
|
||||||
dst.len(),
|
dst.len(),
|
||||||
plaintext.as_ptr(),
|
plaintext.as_ptr(),
|
||||||
plaintext.len(),
|
plaintext.len(),
|
||||||
if aad.is_empty() {
|
if aad.is_empty() { std::ptr::null() } else { aad.as_ptr() },
|
||||||
std::ptr::null()
|
|
||||||
} else {
|
|
||||||
aad.as_ptr()
|
|
||||||
},
|
|
||||||
aad.len(),
|
aad.len(),
|
||||||
nonce.as_ptr(),
|
nonce.as_ptr(),
|
||||||
nonce.len(),
|
nonce.len(),
|
||||||
|
|
@ -360,10 +333,9 @@ fn xchacha20_poly1305_encrypt(
|
||||||
key.len(),
|
key.len(),
|
||||||
);
|
);
|
||||||
if ret != 0 {
|
if ret != 0 {
|
||||||
return Err(CryptoError::InternalError(format!(
|
return Err(CryptoError::InternalError(
|
||||||
"wc_XChaCha20Poly1305_Encrypt returned {}",
|
format!("wc_XChaCha20Poly1305_Encrypt returned {}", ret)
|
||||||
ret
|
));
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -372,13 +344,13 @@ fn xchacha20_poly1305_encrypt(
|
||||||
|
|
||||||
fn xchacha20_poly1305_decrypt(
|
fn xchacha20_poly1305_decrypt(
|
||||||
key: &[u8],
|
key: &[u8],
|
||||||
nonce: &[u8], // 24 bytes
|
nonce: &[u8], // 24 bytes
|
||||||
ciphertext_and_tag: &[u8],
|
ciphertext_and_tag: &[u8],
|
||||||
aad: &[u8],
|
aad: &[u8],
|
||||||
) -> 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;
|
||||||
|
|
@ -392,11 +364,7 @@ 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() {
|
if aad.is_empty() { std::ptr::null() } else { aad.as_ptr() },
|
||||||
std::ptr::null()
|
|
||||||
} else {
|
|
||||||
aad.as_ptr()
|
|
||||||
},
|
|
||||||
aad.len(),
|
aad.len(),
|
||||||
nonce.as_ptr(),
|
nonce.as_ptr(),
|
||||||
nonce.len(),
|
nonce.len(),
|
||||||
|
|
|
||||||
|
|
@ -43,130 +43,86 @@ 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(
|
caps = caps.with_aead(algo, AlgorithmCapability {
|
||||||
algo,
|
available,
|
||||||
AlgorithmCapability {
|
deterministic_io: true, // wolfSSL output is deterministic for same key/nonce
|
||||||
available,
|
efficiency_score: efficiency,
|
||||||
deterministic_io: true, // wolfSSL output is deterministic for same key/nonce
|
reliability_score: if available { 100 } else { 0 },
|
||||||
efficiency_score: efficiency,
|
});
|
||||||
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 [
|
for algo in [KdfAlgorithm::Sha256, KdfAlgorithm::Sha384, KdfAlgorithm::Sha512] {
|
||||||
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(
|
caps = caps.with_kdf(algo, AlgorithmCapability {
|
||||||
algo,
|
available,
|
||||||
AlgorithmCapability {
|
deterministic_io: true,
|
||||||
available,
|
efficiency_score: 80, // HKDF is fast; benchmark not run for KDF
|
||||||
deterministic_io: true,
|
reliability_score: if available { 100 } else { 0 },
|
||||||
efficiency_score: 80, // HKDF is fast; benchmark not run for KDF
|
});
|
||||||
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 {
|
let salt: &[u8] = if algo == KdfAlgorithm::Argon2id { b"saltsalt" } else { b"" };
|
||||||
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(
|
caps = caps.with_kdf(algo, AlgorithmCapability {
|
||||||
algo,
|
available,
|
||||||
AlgorithmCapability {
|
deterministic_io: true,
|
||||||
available,
|
efficiency_score: if algo == KdfAlgorithm::Argon2id { 10 } else { 70 },
|
||||||
deterministic_io: true,
|
reliability_score: if available { 100 } else { 0 },
|
||||||
efficiency_score: if algo == KdfAlgorithm::Argon2id {
|
});
|
||||||
10
|
|
||||||
} else {
|
|
||||||
70
|
|
||||||
},
|
|
||||||
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 [
|
for algo in [MacAlgorithm::HmacSha256, MacAlgorithm::HmacSha384, MacAlgorithm::HmacSha512] {
|
||||||
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(
|
caps = caps.with_mac(algo, AlgorithmCapability {
|
||||||
algo,
|
available,
|
||||||
AlgorithmCapability {
|
deterministic_io: true,
|
||||||
available,
|
efficiency_score: 85,
|
||||||
deterministic_io: true,
|
reliability_score: if available { 100 } else { 0 },
|
||||||
efficiency_score: 85,
|
});
|
||||||
reliability_score: if available { 100 } else { 0 },
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let available =
|
let available = crate::mac::compute_mac(MacAlgorithm::Blake2bMac, b"key-that-is-16-by", b"data").is_ok();
|
||||||
crate::mac::compute_mac(MacAlgorithm::Blake2bMac, b"key-that-is-16-by", b"data")
|
caps = caps.with_mac(MacAlgorithm::Blake2bMac, AlgorithmCapability {
|
||||||
.is_ok();
|
available,
|
||||||
caps = caps.with_mac(
|
deterministic_io: true,
|
||||||
MacAlgorithm::Blake2bMac,
|
efficiency_score: 90,
|
||||||
AlgorithmCapability {
|
reliability_score: if available { 100 } else { 0 },
|
||||||
available,
|
});
|
||||||
deterministic_io: true,
|
|
||||||
efficiency_score: 90,
|
|
||||||
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::Sha256, HashAlgorithm::Sha384, HashAlgorithm::Sha512,
|
||||||
HashAlgorithm::Sha384,
|
HashAlgorithm::Blake2b512, HashAlgorithm::Sha3_256, HashAlgorithm::Sha3_512,
|
||||||
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(
|
caps = caps.with_hash(algo, AlgorithmCapability {
|
||||||
algo,
|
available,
|
||||||
AlgorithmCapability {
|
deterministic_io: true,
|
||||||
available,
|
efficiency_score: 90,
|
||||||
deterministic_io: true,
|
reliability_score: if available { 100 } else { 0 },
|
||||||
efficiency_score: 90,
|
});
|
||||||
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(
|
caps = caps.with_kem(algo, AlgorithmCapability {
|
||||||
algo,
|
available,
|
||||||
AlgorithmCapability {
|
deterministic_io: false, // key generation is randomised
|
||||||
available,
|
efficiency_score: 80,
|
||||||
deterministic_io: false, // key generation is randomised
|
reliability_score: if available { 100 } else { 0 },
|
||||||
efficiency_score: 80,
|
});
|
||||||
reliability_score: if available { 100 } else { 0 },
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
for algo in [
|
for algo in [
|
||||||
KemAlgorithm::MlKem768,
|
KemAlgorithm::MlKem768, KemAlgorithm::MlKem1024,
|
||||||
KemAlgorithm::MlKem1024,
|
|
||||||
KemAlgorithm::ClassicMcEliece460896,
|
KemAlgorithm::ClassicMcEliece460896,
|
||||||
] {
|
] {
|
||||||
caps = caps.with_kem(algo, AlgorithmCapability::unavailable());
|
caps = caps.with_kem(algo, AlgorithmCapability::unavailable());
|
||||||
|
|
@ -197,7 +153,7 @@ pub fn run_benchmark() -> BenchmarkReport {
|
||||||
let mut raw_results: Vec<(AeadAlgorithm, f64)> = Vec::new();
|
let mut raw_results: Vec<(AeadAlgorithm, f64)> = Vec::new();
|
||||||
|
|
||||||
for algo in algos {
|
for algo in algos {
|
||||||
let key = vec![0u8; algo.key_len()];
|
let key = vec![0u8; algo.key_len()];
|
||||||
let nonce = vec![0u8; algo.nonce_len()];
|
let nonce = vec![0u8; algo.nonce_len()];
|
||||||
|
|
||||||
// Probe first; skip unavailable algorithms.
|
// Probe first; skip unavailable algorithms.
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,12 @@ use ccc_crypto_core::{algorithms::HashAlgorithm, error::CryptoError};
|
||||||
/// [`HashAlgorithm::digest_len`] for output lengths).
|
/// [`HashAlgorithm::digest_len`] for output lengths).
|
||||||
pub fn hash(algo: HashAlgorithm, data: &[u8]) -> Result<Vec<u8>, CryptoError> {
|
pub fn hash(algo: HashAlgorithm, data: &[u8]) -> Result<Vec<u8>, CryptoError> {
|
||||||
match algo {
|
match algo {
|
||||||
HashAlgorithm::Sha256 => sha256(data),
|
HashAlgorithm::Sha256 => sha256(data),
|
||||||
HashAlgorithm::Sha384 => sha384(data),
|
HashAlgorithm::Sha384 => sha384(data),
|
||||||
HashAlgorithm::Sha512 => sha512(data),
|
HashAlgorithm::Sha512 => sha512(data),
|
||||||
HashAlgorithm::Blake2b512 => blake2b_512(data),
|
HashAlgorithm::Blake2b512 => blake2b_512(data),
|
||||||
HashAlgorithm::Sha3_256 => sha3_256(data),
|
HashAlgorithm::Sha3_256 => sha3_256(data),
|
||||||
HashAlgorithm::Sha3_512 => sha3_512(data),
|
HashAlgorithm::Sha3_512 => sha3_512(data),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,9 +42,11 @@ 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 =
|
let layout = Layout::from_size_align(
|
||||||
Layout::from_size_align(std::mem::size_of::<crate::sys::wc_Sha256>().max(120), 16)
|
std::mem::size_of::<crate::sys::wc_Sha256>().max(120),
|
||||||
.expect("layout must be valid");
|
16,
|
||||||
|
)
|
||||||
|
.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;
|
||||||
if sha_ptr.is_null() {
|
if sha_ptr.is_null() {
|
||||||
|
|
@ -54,33 +56,23 @@ 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!(
|
return Err(CryptoError::InternalError(format!("wc_InitSha256 returned {ret}")));
|
||||||
"wc_InitSha256 returned {ret}"
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
let ret = crate::sys::wc_Sha256Update(
|
let ret = crate::sys::wc_Sha256Update(
|
||||||
sha_ptr,
|
sha_ptr,
|
||||||
if data.is_empty() {
|
if data.is_empty() { std::ptr::null() } else { data.as_ptr() },
|
||||||
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!(
|
return Err(CryptoError::InternalError(format!("wc_Sha256Update returned {ret}")));
|
||||||
"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!(
|
return Err(CryptoError::InternalError(format!("wc_Sha256Final returned {ret}")));
|
||||||
"wc_Sha256Final returned {ret}"
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(out)
|
Ok(out)
|
||||||
|
|
@ -93,23 +85,17 @@ 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!(
|
return Err(CryptoError::InternalError(format!("wc_InitSha384 returned {ret}")));
|
||||||
"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!(
|
return Err(CryptoError::InternalError(format!("wc_Sha384Update returned {ret}")));
|
||||||
"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!(
|
return Err(CryptoError::InternalError(format!("wc_Sha384Final returned {ret}")));
|
||||||
"wc_Sha384Final returned {ret}"
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(out)
|
Ok(out)
|
||||||
|
|
@ -121,23 +107,17 @@ 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!(
|
return Err(CryptoError::InternalError(format!("wc_InitSha512 returned {ret}")));
|
||||||
"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!(
|
return Err(CryptoError::InternalError(format!("wc_Sha512Update returned {ret}")));
|
||||||
"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!(
|
return Err(CryptoError::InternalError(format!("wc_Sha512Final returned {ret}")));
|
||||||
"wc_Sha512Final returned {ret}"
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(out)
|
Ok(out)
|
||||||
|
|
@ -155,23 +135,17 @@ 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!(
|
return Err(CryptoError::InternalError(format!("wc_InitSha3_256 returned {ret}")));
|
||||||
"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!(
|
return Err(CryptoError::InternalError(format!("wc_Sha3_256_Update returned {ret}")));
|
||||||
"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!(
|
return Err(CryptoError::InternalError(format!("wc_Sha3_256_Final returned {ret}")));
|
||||||
"wc_Sha3_256_Final returned {ret}"
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(out)
|
Ok(out)
|
||||||
|
|
@ -183,23 +157,17 @@ 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!(
|
return Err(CryptoError::InternalError(format!("wc_InitSha3_512 returned {ret}")));
|
||||||
"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!(
|
return Err(CryptoError::InternalError(format!("wc_Sha3_512_Update returned {ret}")));
|
||||||
"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!(
|
return Err(CryptoError::InternalError(format!("wc_Sha3_512_Final returned {ret}")));
|
||||||
"wc_Sha3_512_Final returned {ret}"
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(out)
|
Ok(out)
|
||||||
|
|
@ -219,22 +187,16 @@ 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!(
|
return Err(CryptoError::InternalError(format!("wc_InitBlake2b returned {ret}")));
|
||||||
"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!(
|
return Err(CryptoError::InternalError(format!("wc_Blake2bUpdate returned {ret}")));
|
||||||
"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!(
|
return Err(CryptoError::InternalError(format!("wc_Blake2bFinal returned {ret}")));
|
||||||
"wc_Blake2bFinal returned {ret}"
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(out)
|
Ok(out)
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,8 @@ 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 => Err(CryptoError::FeatureNotCompiled("KMAC256 (Phase 5+)".into())),
|
KdfAlgorithm::Kmac256 =>
|
||||||
|
Err(CryptoError::FeatureNotCompiled("KMAC256 (Phase 5+)".into())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,20 +103,16 @@ 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() {
|
if salt.is_empty() { std::ptr::null() } else { salt.as_ptr() },
|
||||||
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(format!(
|
return Err(CryptoError::InternalError(
|
||||||
"wc_Tls13_HKDF_Extract returned {ret}"
|
format!("wc_Tls13_HKDF_Extract returned {ret}")
|
||||||
)));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(prk)
|
Ok(prk)
|
||||||
|
|
@ -136,14 +133,14 @@ 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(format!(
|
return Err(CryptoError::InvalidInput(
|
||||||
"HKDF: requested length {length} exceeds maximum {max_len}"
|
format!("HKDF: requested length {length} exceeds maximum {max_len}")
|
||||||
)));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut okm = Zeroizing::new(vec![0u8; length]);
|
let mut okm = Zeroizing::new(vec![0u8; length]);
|
||||||
let mut t = Vec::new(); // T(i-1); starts empty per spec
|
let mut t = Vec::new(); // T(i-1); starts empty per spec
|
||||||
let mut pos = 0usize;
|
let mut pos = 0usize;
|
||||||
let mut counter = 1u8;
|
let mut counter = 1u8;
|
||||||
|
|
||||||
while pos < length {
|
while pos < length {
|
||||||
|
|
@ -159,9 +156,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
|
counter = counter.checked_add(1).ok_or_else(|| {
|
||||||
.checked_add(1)
|
CryptoError::InternalError("HKDF counter overflow".into())
|
||||||
.ok_or_else(|| CryptoError::InternalError("HKDF counter overflow".into()))?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(okm)
|
Ok(okm)
|
||||||
|
|
@ -197,7 +194,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()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -224,7 +221,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()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -235,10 +232,10 @@ fn argon2id(
|
||||||
// argon2_iterations: 3
|
// argon2_iterations: 3
|
||||||
// argon2_parallelism: 4
|
// argon2_parallelism: 4
|
||||||
let params = Params::new(
|
let params = Params::new(
|
||||||
65_536, // m_cost: 64 MiB
|
65_536, // m_cost: 64 MiB
|
||||||
3, // t_cost: iterations
|
3, // t_cost: iterations
|
||||||
4, // p_cost: parallelism (lanes)
|
4, // p_cost: parallelism (lanes)
|
||||||
Some(length), // output length
|
Some(length), // output length
|
||||||
)
|
)
|
||||||
.map_err(|e| CryptoError::InternalError(format!("Argon2 params error: {e}")))?;
|
.map_err(|e| CryptoError::InternalError(format!("Argon2 params error: {e}")))?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,15 @@ use ccc_crypto_core::{
|
||||||
pub fn generate_keypair(algo: KemAlgorithm) -> Result<KemKeyPair, CryptoError> {
|
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 => Err(CryptoError::FeatureNotCompiled(
|
KemAlgorithm::MlKem768 | KemAlgorithm::MlKem1024 =>
|
||||||
"ML-KEM is deferred to Phase 5 (requires wolfSSL PQ build)".into(),
|
Err(CryptoError::FeatureNotCompiled(
|
||||||
)),
|
"ML-KEM is deferred to Phase 5 (requires wolfSSL PQ build)".into()
|
||||||
KemAlgorithm::ClassicMcEliece460896 => Err(CryptoError::FeatureNotCompiled(
|
)),
|
||||||
"Classic-McEliece is deferred to Phase 5".into(),
|
KemAlgorithm::ClassicMcEliece460896 =>
|
||||||
)),
|
Err(CryptoError::FeatureNotCompiled(
|
||||||
|
"Classic-McEliece is deferred to Phase 5".into()
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,13 +44,15 @@ pub fn encapsulate(
|
||||||
) -> Result<KemEncapResult, CryptoError> {
|
) -> Result<KemEncapResult, CryptoError> {
|
||||||
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 => Err(CryptoError::FeatureNotCompiled(
|
KemAlgorithm::MlKem768 | KemAlgorithm::MlKem1024 =>
|
||||||
"ML-KEM is deferred to Phase 5".into(),
|
Err(CryptoError::FeatureNotCompiled(
|
||||||
)),
|
"ML-KEM is deferred to Phase 5".into()
|
||||||
KemAlgorithm::ClassicMcEliece460896 => Err(CryptoError::FeatureNotCompiled(
|
)),
|
||||||
"Classic-McEliece is deferred to Phase 5".into(),
|
KemAlgorithm::ClassicMcEliece460896 =>
|
||||||
)),
|
Err(CryptoError::FeatureNotCompiled(
|
||||||
|
"Classic-McEliece is deferred to Phase 5".into()
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,13 +65,15 @@ pub fn decapsulate(
|
||||||
) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
|
) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
|
||||||
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 => Err(CryptoError::FeatureNotCompiled(
|
KemAlgorithm::MlKem768 | KemAlgorithm::MlKem1024 =>
|
||||||
"ML-KEM is deferred to Phase 5".into(),
|
Err(CryptoError::FeatureNotCompiled(
|
||||||
)),
|
"ML-KEM is deferred to Phase 5".into()
|
||||||
KemAlgorithm::ClassicMcEliece460896 => Err(CryptoError::FeatureNotCompiled(
|
)),
|
||||||
"Classic-McEliece is deferred to Phase 5".into(),
|
KemAlgorithm::ClassicMcEliece460896 =>
|
||||||
)),
|
Err(CryptoError::FeatureNotCompiled(
|
||||||
|
"Classic-McEliece is deferred to Phase 5".into()
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,51 +96,41 @@ 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!(
|
return Err(CryptoError::InternalError(format!("wc_InitRng returned {}", ret)));
|
||||||
"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(format!(
|
return Err(CryptoError::InternalError(
|
||||||
"wc_curve25519_make_key returned {}",
|
format!("wc_curve25519_make_key returned {}", ret)
|
||||||
ret
|
));
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut pub_sz = public_key.len() as u32;
|
let mut pub_sz = public_key.len() as u32;
|
||||||
let mut priv_sz = private_key.len() as u32;
|
let mut priv_sz = private_key.len() as u32;
|
||||||
|
|
||||||
// Export in little-endian (EC25519_LITTLE_ENDIAN=0) so all key
|
// Export in little-endian (EC25519_LITTLE_ENDIAN=0) so all key
|
||||||
// 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(),
|
private_key.as_mut_ptr(), &mut priv_sz,
|
||||||
&mut priv_sz,
|
public_key.as_mut_ptr(), &mut pub_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(format!(
|
return Err(CryptoError::InternalError(
|
||||||
"wc_curve25519_export_key_raw_ex returned {}",
|
format!("wc_curve25519_export_key_raw_ex returned {}", ret)
|
||||||
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 {
|
Ok(KemKeyPair { public_key, private_key })
|
||||||
public_key,
|
|
||||||
private_key,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn x25519_encapsulate(recipient_pub: &[u8]) -> Result<KemEncapResult, CryptoError> {
|
fn x25519_encapsulate(recipient_pub: &[u8]) -> Result<KemEncapResult, CryptoError> {
|
||||||
|
|
@ -144,7 +140,7 @@ fn x25519_encapsulate(recipient_pub: &[u8]) -> Result<KemEncapResult, CryptoErro
|
||||||
let shared = x25519_dh(&ephemeral.private_key, recipient_pub)?;
|
let shared = x25519_dh(&ephemeral.private_key, recipient_pub)?;
|
||||||
Ok(KemEncapResult {
|
Ok(KemEncapResult {
|
||||||
// KemKeyPair implements ZeroizeOnDrop (adds Drop) so fields must be cloned.
|
// KemKeyPair implements ZeroizeOnDrop (adds Drop) so fields must be cloned.
|
||||||
ciphertext: ephemeral.public_key.clone(),
|
ciphertext: ephemeral.public_key.clone(),
|
||||||
shared_secret: (*shared).clone(),
|
shared_secret: (*shared).clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -167,63 +163,57 @@ 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()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut shared = Zeroizing::new(vec![0u8; 32]);
|
let mut shared = Zeroizing::new(vec![0u8; 32]);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut local_key: crate::sys::curve25519_key = std::mem::zeroed();
|
let mut local_key: crate::sys::curve25519_key = std::mem::zeroed();
|
||||||
let mut remote_key: crate::sys::curve25519_key = std::mem::zeroed();
|
let mut remote_key: crate::sys::curve25519_key = std::mem::zeroed();
|
||||||
|
|
||||||
// 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(format!(
|
return Err(CryptoError::InternalError(
|
||||||
"wc_curve25519_init (local) returned {}",
|
format!("wc_curve25519_init (local) returned {}", ret)
|
||||||
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(format!(
|
return Err(CryptoError::InternalError(
|
||||||
"wc_curve25519_init (remote) returned {}",
|
format!("wc_curve25519_init (remote) returned {}", ret)
|
||||||
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(),
|
private_key.as_ptr(), 32,
|
||||||
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(format!(
|
return Err(CryptoError::InvalidKey(
|
||||||
"wc_curve25519_import_private_ex returned {}",
|
format!("wc_curve25519_import_private_ex returned {}", ret)
|
||||||
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(),
|
public_key.as_ptr(), 32,
|
||||||
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(format!(
|
return Err(CryptoError::InvalidKey(
|
||||||
"wc_curve25519_import_public_ex returned {}",
|
format!("wc_curve25519_import_public_ex returned {}", ret)
|
||||||
ret
|
));
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut shared_sz = 32u32;
|
let mut shared_sz = 32u32;
|
||||||
|
|
@ -241,10 +231,9 @@ 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(format!(
|
return Err(CryptoError::InternalError(
|
||||||
"wc_curve25519_shared_secret_ex returned {}",
|
format!("wc_curve25519_shared_secret_ex returned {}", ret)
|
||||||
ret
|
));
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -261,66 +250,56 @@ fn x25519_dh(private_key: &[u8], public_key: &[u8]) -> Result<Zeroizing<Vec<u8>>
|
||||||
const X448_LE: i32 = 0;
|
const X448_LE: i32 = 0;
|
||||||
|
|
||||||
fn x448_generate() -> Result<KemKeyPair, CryptoError> {
|
fn x448_generate() -> Result<KemKeyPair, CryptoError> {
|
||||||
let mut public_key = vec![0u8; 56];
|
let mut public_key = vec![0u8; 56];
|
||||||
let mut private_key = vec![0u8; 56];
|
let mut private_key = vec![0u8; 56];
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut key: crate::sys::curve448_key = std::mem::zeroed();
|
let mut key: crate::sys::curve448_key = std::mem::zeroed();
|
||||||
let mut rng: crate::sys::WC_RNG = std::mem::zeroed();
|
let mut rng: crate::sys::WC_RNG = std::mem::zeroed();
|
||||||
|
|
||||||
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!(
|
return Err(CryptoError::InternalError(format!("wc_InitRng returned {}", ret)));
|
||||||
"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(format!(
|
return Err(CryptoError::InternalError(
|
||||||
"wc_curve448_make_key returned {}",
|
format!("wc_curve448_make_key returned {}", ret)
|
||||||
ret
|
));
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut pub_sz = public_key.len() as u32;
|
let mut pub_sz = public_key.len() as u32;
|
||||||
let mut priv_sz = private_key.len() as u32;
|
let mut priv_sz = private_key.len() as u32;
|
||||||
|
|
||||||
// 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(),
|
private_key.as_mut_ptr(), &mut priv_sz,
|
||||||
&mut priv_sz,
|
public_key.as_mut_ptr(), &mut pub_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(format!(
|
return Err(CryptoError::InternalError(
|
||||||
"wc_curve448_export_key_raw_ex returned {}",
|
format!("wc_curve448_export_key_raw_ex returned {}", ret)
|
||||||
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 {
|
Ok(KemKeyPair { public_key, private_key })
|
||||||
public_key,
|
|
||||||
private_key,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn x448_encapsulate(recipient_pub: &[u8]) -> Result<KemEncapResult, CryptoError> {
|
fn x448_encapsulate(recipient_pub: &[u8]) -> Result<KemEncapResult, CryptoError> {
|
||||||
let ephemeral = x448_generate()?;
|
let ephemeral = x448_generate()?;
|
||||||
let shared = x448_dh(&ephemeral.private_key, recipient_pub)?;
|
let shared = x448_dh(&ephemeral.private_key, recipient_pub)?;
|
||||||
Ok(KemEncapResult {
|
Ok(KemEncapResult {
|
||||||
ciphertext: ephemeral.public_key.clone(),
|
ciphertext: ephemeral.public_key.clone(),
|
||||||
shared_secret: (*shared).clone(),
|
shared_secret: (*shared).clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -340,64 +319,52 @@ 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(
|
return Err(CryptoError::InvalidKey("X448: keys must be 56 bytes each".into()));
|
||||||
"X448: keys must be 56 bytes each".into(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut shared = Zeroizing::new(vec![0u8; 56]);
|
let mut shared = Zeroizing::new(vec![0u8; 56]);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut local_key: crate::sys::curve448_key = std::mem::zeroed();
|
let mut local_key: crate::sys::curve448_key = std::mem::zeroed();
|
||||||
let mut remote_key: crate::sys::curve448_key = std::mem::zeroed();
|
let mut remote_key: crate::sys::curve448_key = std::mem::zeroed();
|
||||||
|
|
||||||
// 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(format!(
|
return Err(CryptoError::InternalError(
|
||||||
"wc_curve448_init (local) returned {}",
|
format!("wc_curve448_init (local) returned {}", ret)
|
||||||
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(format!(
|
return Err(CryptoError::InternalError(
|
||||||
"wc_curve448_init (remote) returned {}",
|
format!("wc_curve448_init (remote) returned {}", ret)
|
||||||
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(),
|
private_key.as_ptr(), 56, &mut local_key, X448_LE,
|
||||||
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(format!(
|
return Err(CryptoError::InvalidKey(
|
||||||
"wc_curve448_import_private_ex returned {}",
|
format!("wc_curve448_import_private_ex returned {}", ret)
|
||||||
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(),
|
public_key.as_ptr(), 56, &mut remote_key, X448_LE,
|
||||||
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(format!(
|
return Err(CryptoError::InvalidKey(
|
||||||
"wc_curve448_import_public_ex returned {}",
|
format!("wc_curve448_import_public_ex returned {}", ret)
|
||||||
ret
|
));
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut shared_sz = 56u32;
|
let mut shared_sz = 56u32;
|
||||||
|
|
@ -414,10 +381,9 @@ 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(format!(
|
return Err(CryptoError::InternalError(
|
||||||
"wc_curve448_shared_secret_ex returned {}",
|
format!("wc_curve448_shared_secret_ex returned {}", ret)
|
||||||
ret
|
));
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,17 +17,10 @@ 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(
|
#[allow(non_camel_case_types, non_snake_case, non_upper_case_globals, dead_code, clippy::all)]
|
||||||
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 ────────────────────────────────
|
||||||
//
|
//
|
||||||
|
|
@ -119,98 +112,48 @@ mod sys {
|
||||||
|
|
||||||
// ── AES-GCM ─────────────────────────────────────────────────────────
|
// ── AES-GCM ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
pub unsafe fn wc_AesGcmSetKey(aes: *mut Aes, key: *const c_uchar, len: c_uint) -> c_int {
|
pub unsafe fn wc_AesGcmSetKey(aes: *mut Aes, key: *const c_uchar, len: c_uint) -> c_int { unreachable!() }
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
pub unsafe fn wc_AesGcmEncrypt(
|
pub unsafe fn wc_AesGcmEncrypt(
|
||||||
aes: *mut Aes,
|
aes: *mut Aes, out: *mut c_uchar, in_: *const c_uchar, sz: c_uint,
|
||||||
out: *mut c_uchar,
|
iv: *const c_uchar, iv_sz: c_uint, auth_tag: *mut c_uchar, auth_tag_sz: c_uint,
|
||||||
in_: *const c_uchar,
|
auth_in: *const c_uchar, auth_in_sz: c_uint,
|
||||||
sz: c_uint,
|
) -> c_int { unreachable!() }
|
||||||
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,
|
aes: *mut Aes, out: *mut c_uchar, in_: *const c_uchar, sz: c_uint,
|
||||||
out: *mut c_uchar,
|
iv: *const c_uchar, iv_sz: c_uint, auth_tag: *const c_uchar, auth_tag_sz: c_uint,
|
||||||
in_: *const c_uchar,
|
auth_in: *const c_uchar, auth_in_sz: c_uint,
|
||||||
sz: c_uint,
|
) -> c_int { unreachable!() }
|
||||||
iv: *const c_uchar,
|
pub unsafe fn wc_AesInit(aes: *mut Aes, heap: *mut c_void, dev_id: c_int) -> c_int { unreachable!() }
|
||||||
iv_sz: c_uint,
|
pub unsafe fn wc_AesFree(aes: *mut Aes) { unreachable!() }
|
||||||
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,
|
key: *const c_uchar, iv: *const c_uchar,
|
||||||
iv: *const c_uchar,
|
aad: *const c_uchar, aad_sz: c_uint,
|
||||||
aad: *const c_uchar,
|
in_: *const c_uchar, in_sz: c_uint,
|
||||||
aad_sz: c_uint,
|
out: *mut c_uchar, auth_tag: *mut c_uchar,
|
||||||
in_: *const c_uchar,
|
) -> c_int { unreachable!() }
|
||||||
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,
|
key: *const c_uchar, iv: *const c_uchar,
|
||||||
iv: *const c_uchar,
|
aad: *const c_uchar, aad_sz: c_uint,
|
||||||
aad: *const c_uchar,
|
in_: *const c_uchar, in_sz: c_uint,
|
||||||
aad_sz: c_uint,
|
auth_tag: *const c_uchar, out: *mut c_uchar,
|
||||||
in_: *const c_uchar,
|
) -> c_int { unreachable!() }
|
||||||
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: *mut c_uchar, dst_space: usize,
|
||||||
dst_space: usize,
|
src: *const c_uchar, src_len: usize,
|
||||||
src: *const c_uchar,
|
ad: *const c_uchar, ad_len: usize,
|
||||||
src_len: usize,
|
nonce: *const c_uchar, nonce_len: usize,
|
||||||
ad: *const c_uchar,
|
key: *const c_uchar, key_len: usize,
|
||||||
ad_len: usize,
|
) -> c_int { unreachable!() }
|
||||||
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: *mut c_uchar, dst_space: usize,
|
||||||
dst_space: usize,
|
src: *const c_uchar, src_len: usize,
|
||||||
src: *const c_uchar,
|
ad: *const c_uchar, ad_len: usize,
|
||||||
src_len: usize,
|
nonce: *const c_uchar, nonce_len: usize,
|
||||||
ad: *const c_uchar,
|
key: *const c_uchar, key_len: usize,
|
||||||
ad_len: usize,
|
) -> c_int { unreachable!() }
|
||||||
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) ──────────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -252,161 +195,76 @@ 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,
|
b2b: *mut Blake2b, digest_sz: c_uint,
|
||||||
digest_sz: c_uint,
|
key: *const c_uchar, key_sz: c_uint,
|
||||||
key: *const c_uchar,
|
) -> c_int { unreachable!() }
|
||||||
key_sz: c_uint,
|
pub unsafe fn wc_Blake2bUpdate(b2b: *mut Blake2b, in_: *const c_uchar, in_sz: c_uint) -> c_int { unreachable!() }
|
||||||
) -> c_int {
|
pub unsafe fn wc_Blake2bFinal(b2b: *mut Blake2b, final_: *mut c_uchar, request_sz: c_uint) -> c_int { unreachable!() }
|
||||||
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: *const c_uchar, salt_len: c_uint,
|
||||||
salt_len: c_uint,
|
ikm: *mut c_uchar, ikm_len: c_uint,
|
||||||
ikm: *mut c_uchar,
|
|
||||||
ikm_len: c_uint,
|
|
||||||
digest: c_int,
|
digest: c_int,
|
||||||
) -> c_int {
|
) -> c_int { unreachable!() }
|
||||||
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_: *mut c_uchar, priv_sz: *mut c_uint,
|
||||||
priv_sz: *mut c_uint,
|
pub_: *mut c_uchar, pub_sz: *mut c_uint,
|
||||||
pub_: *mut c_uchar,
|
) -> c_int { unreachable!() }
|
||||||
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_: *const c_uchar, priv_sz: c_uint, key: *mut curve25519_key,
|
||||||
priv_sz: c_uint,
|
) -> c_int { unreachable!() }
|
||||||
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_: *const c_uchar, pub_sz: c_uint, key: *mut curve25519_key,
|
||||||
pub_sz: c_uint,
|
) -> c_int { unreachable!() }
|
||||||
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,
|
local: *mut curve25519_key, remote: *mut curve25519_key,
|
||||||
remote: *mut curve25519_key,
|
out: *mut c_uchar, out_sz: *mut c_uint, endian: c_int,
|
||||||
out: *mut c_uchar,
|
) -> c_int { unreachable!() }
|
||||||
out_sz: *mut c_uint,
|
pub unsafe fn wc_curve25519_free(key: *mut curve25519_key) { unreachable!() }
|
||||||
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(
|
pub unsafe fn wc_curve448_make_key(rng: *mut WC_RNG, key_sz: c_int, key: *mut curve448_key) -> c_int { unreachable!() }
|
||||||
rng: *mut WC_RNG,
|
pub unsafe fn wc_curve448_init(key: *mut curve448_key) -> c_int { unreachable!() }
|
||||||
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_: *mut c_uchar, priv_sz: *mut c_uint,
|
||||||
priv_sz: *mut c_uint,
|
pub_: *mut c_uchar, pub_sz: *mut c_uint,
|
||||||
pub_: *mut c_uchar,
|
) -> c_int { unreachable!() }
|
||||||
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_: *const c_uchar, priv_sz: c_uint, key: *mut curve448_key,
|
||||||
priv_sz: c_uint,
|
) -> c_int { unreachable!() }
|
||||||
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_: *const c_uchar, pub_sz: c_uint, key: *mut curve448_key,
|
||||||
pub_sz: c_uint,
|
) -> c_int { unreachable!() }
|
||||||
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,
|
local: *mut curve448_key, remote: *mut curve448_key,
|
||||||
remote: *mut curve448_key,
|
out: *mut c_uchar, out_sz: *mut c_uint, endian: c_int,
|
||||||
out: *mut c_uchar,
|
) -> c_int { unreachable!() }
|
||||||
out_sz: *mut c_uint,
|
pub unsafe fn wc_curve448_free(key: *mut curve448_key) { unreachable!() }
|
||||||
endian: c_int,
|
|
||||||
) -> c_int {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
pub unsafe fn wc_curve448_free(key: *mut curve448_key) {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -418,6 +276,7 @@ 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().register("wolfssl", Box::new(provider));
|
ccc_crypto_core::ProviderRegistry::global()
|
||||||
|
.register("wolfssl", Box::new(provider));
|
||||||
log::info!("[ccc-crypto-wolfssl] provider registered");
|
log::info!("[ccc-crypto-wolfssl] provider registered");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,15 +17,20 @@ 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(algo: MacAlgorithm, key: &[u8], data: &[u8]) -> Result<Vec<u8>, CryptoError> {
|
pub fn compute_mac(
|
||||||
|
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 => Err(CryptoError::UnsupportedAlgorithm(
|
MacAlgorithm::Poly1305 =>
|
||||||
"Poly1305 standalone MAC is not exposed in this Phase".into(),
|
Err(CryptoError::UnsupportedAlgorithm(
|
||||||
)),
|
"Poly1305 standalone MAC is not exposed in this Phase".into()
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -66,8 +71,11 @@ 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(std::mem::size_of::<crate::sys::Hmac>().max(456), 16)
|
let layout = Layout::from_size_align(
|
||||||
.expect("layout must be valid");
|
std::mem::size_of::<crate::sys::Hmac>().max(456),
|
||||||
|
16,
|
||||||
|
)
|
||||||
|
.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;
|
||||||
if hmac_ptr.is_null() {
|
if hmac_ptr.is_null() {
|
||||||
|
|
@ -82,40 +90,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!(
|
return Err(CryptoError::InternalError(format!("wc_HmacInit returned {}", ret)));
|
||||||
"wc_HmacInit returned {}",
|
|
||||||
ret
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let ret = crate::sys::wc_HmacSetKey(hmac_ptr, hash_type, key.as_ptr(), key.len() as u32);
|
let ret = crate::sys::wc_HmacSetKey(
|
||||||
|
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!(
|
return Err(CryptoError::InvalidKey(format!("wc_HmacSetKey returned {}", ret)));
|
||||||
"wc_HmacSetKey returned {}",
|
|
||||||
ret
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let ret = crate::sys::wc_HmacUpdate(hmac_ptr, data.as_ptr(), data.len() as u32);
|
let ret = crate::sys::wc_HmacUpdate(
|
||||||
|
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!(
|
return Err(CryptoError::InternalError(format!("wc_HmacUpdate returned {}", ret)));
|
||||||
"wc_HmacUpdate returned {}",
|
|
||||||
ret
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let ret = crate::sys::wc_HmacFinal(hmac_ptr, out.as_mut_ptr());
|
let ret = crate::sys::wc_HmacFinal(
|
||||||
|
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!(
|
return Err(CryptoError::InternalError(format!("wc_HmacFinal returned {}", ret)));
|
||||||
"wc_HmacFinal returned {}",
|
|
||||||
ret
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
crate::sys::wc_HmacFree(hmac_ptr);
|
crate::sys::wc_HmacFree(hmac_ptr);
|
||||||
|
|
@ -138,7 +146,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 1–64 bytes".into(),
|
"BLAKE2b-MAC: key must be 1–64 bytes".into()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,26 +157,30 @@ 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 =
|
let ret = crate::sys::wc_InitBlake2b_WithKey(
|
||||||
crate::sys::wc_InitBlake2b_WithKey(&mut b2b, OUT_LEN, key.as_ptr(), key.len() as u32);
|
&mut b2b,
|
||||||
|
OUT_LEN,
|
||||||
|
key.as_ptr(),
|
||||||
|
key.len() as u32,
|
||||||
|
);
|
||||||
if ret != 0 {
|
if ret != 0 {
|
||||||
return Err(CryptoError::InternalError(format!(
|
return Err(CryptoError::InternalError(
|
||||||
"wc_InitBlake2b_WithKey returned {ret}"
|
format!("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(format!(
|
return Err(CryptoError::InternalError(
|
||||||
"wc_Blake2bUpdate returned {ret}"
|
format!("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(format!(
|
return Err(CryptoError::InternalError(
|
||||||
"wc_Blake2bFinal returned {ret}"
|
format!("wc_Blake2bFinal returned {ret}")
|
||||||
)));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -189,13 +201,26 @@ 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
|
let diff: u8 = a.iter().zip(b.iter()).fold(0u8, |acc, (x, y)| acc | (x ^ y));
|
||||||
.iter()
|
|
||||||
.zip(b.iter())
|
|
||||||
.fold(0u8, |acc, (x, y)| acc | (x ^ y));
|
|
||||||
diff == 0
|
diff == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "mac_tests.rs"]
|
mod tests {
|
||||||
mod tests;
|
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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
//! 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"));
|
|
||||||
}
|
|
||||||
|
|
@ -12,11 +12,56 @@ 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::{AeadProvider, CryptoProvider, HashProvider, KdfProvider, KemProvider, MacProvider},
|
provider::{
|
||||||
types::{BenchmarkReport, KemEncapResult, KemKeyPair, SelfTestReport},
|
AeadProvider, CryptoProvider, HashProvider, KdfProvider, KemProvider, MacProvider,
|
||||||
|
},
|
||||||
|
types::{AlgoTestResult, BenchmarkReport, KemEncapResult, KemKeyPair, SelfTestReport},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{aead, capabilities, hash, kdf, kem, mac, self_test};
|
use crate::{aead, capabilities, hash, kdf, kem, mac};
|
||||||
|
|
||||||
|
// ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
// 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
|
||||||
|
|
@ -52,19 +97,14 @@ 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!(
|
log::info!("[ccc-crypto-wolfssl] benchmark complete ({} algos)", report.results.len());
|
||||||
"[ccc-crypto-wolfssl] benchmark complete ({} algos)",
|
|
||||||
report.results.len()
|
|
||||||
);
|
|
||||||
report
|
report
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for WolfSslProvider {
|
impl Default for WolfSslProvider {
|
||||||
fn default() -> Self {
|
fn default() -> Self { Self::new() }
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ──────────────────────────────────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
@ -73,23 +113,15 @@ impl Default for WolfSslProvider {
|
||||||
|
|
||||||
impl AeadProvider for WolfSslProvider {
|
impl AeadProvider for WolfSslProvider {
|
||||||
fn encrypt_aead(
|
fn encrypt_aead(
|
||||||
&self,
|
&self, algo: AeadAlgorithm, key: &[u8], nonce: &[u8],
|
||||||
algo: AeadAlgorithm,
|
plaintext: &[u8], aad: &[u8],
|
||||||
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,
|
&self, algo: AeadAlgorithm, key: &[u8], nonce: &[u8],
|
||||||
algo: AeadAlgorithm,
|
ciphertext_and_tag: &[u8], aad: &[u8],
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
@ -97,12 +129,7 @@ impl AeadProvider for WolfSslProvider {
|
||||||
|
|
||||||
impl KdfProvider for WolfSslProvider {
|
impl KdfProvider for WolfSslProvider {
|
||||||
fn derive_key(
|
fn derive_key(
|
||||||
&self,
|
&self, algo: KdfAlgorithm, ikm: &[u8], salt: &[u8], info: &[u8], length: usize,
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
@ -110,20 +137,13 @@ impl KdfProvider for WolfSslProvider {
|
||||||
|
|
||||||
impl MacProvider for WolfSslProvider {
|
impl MacProvider for WolfSslProvider {
|
||||||
fn compute_mac(
|
fn compute_mac(
|
||||||
&self,
|
&self, algo: MacAlgorithm, key: &[u8], data: &[u8],
|
||||||
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,
|
&self, algo: MacAlgorithm, key: &[u8], data: &[u8], mac_bytes: &[u8],
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
@ -142,17 +162,12 @@ impl KemProvider for WolfSslProvider {
|
||||||
kem::generate_keypair(algo)
|
kem::generate_keypair(algo)
|
||||||
}
|
}
|
||||||
fn encapsulate(
|
fn encapsulate(
|
||||||
&self,
|
&self, algo: KemAlgorithm, public_key: &[u8],
|
||||||
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,
|
&self, algo: KemAlgorithm, private_key: &[u8], ciphertext: &[u8],
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
@ -163,21 +178,25 @@ impl KemProvider for WolfSslProvider {
|
||||||
// ──────────────────────────────────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
impl CryptoProvider for WolfSslProvider {
|
impl CryptoProvider for WolfSslProvider {
|
||||||
fn provider_name(&self) -> &'static str {
|
fn provider_name(&self) -> &'static str { "wolfssl" }
|
||||||
"wolfssl"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn capabilities(&self) -> ProviderCapabilities {
|
fn capabilities(&self) -> ProviderCapabilities {
|
||||||
self.caps
|
self.caps.get_or_init(|| {
|
||||||
.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 {
|
||||||
self_test::run()
|
let mut results: Vec<AlgoTestResult> = Vec::new();
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
|
@ -185,4 +204,47 @@ 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()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,113 +0,0 @@
|
||||||
//! 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()),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -61,17 +61,17 @@ struct HashVec {
|
||||||
/// Both sides are symmetric: `decapsulate(algo, bob_private, alice_public)` is
|
/// Both sides are symmetric: `decapsulate(algo, bob_private, alice_public)` is
|
||||||
/// verified as a second assertion in `run_kem()`.
|
/// verified as a second assertion in `run_kem()`.
|
||||||
struct KemDhVec {
|
struct KemDhVec {
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
algo: KemAlgorithm,
|
algo: KemAlgorithm,
|
||||||
/// Alice's static private key (little-endian).
|
/// Alice's static private key (little-endian).
|
||||||
alice_private: &'static str,
|
alice_private: &'static str,
|
||||||
/// Alice's corresponding public key (little-endian), used for the Bob→Alice
|
/// Alice's corresponding public key (little-endian), used for the Bob→Alice
|
||||||
/// direction check.
|
/// direction check.
|
||||||
alice_public: &'static str,
|
alice_public: &'static str,
|
||||||
/// Bob's static private key (little-endian).
|
/// Bob's static private key (little-endian).
|
||||||
bob_private: &'static str,
|
bob_private: &'static str,
|
||||||
/// Bob's corresponding public key (little-endian).
|
/// Bob's corresponding public key (little-endian).
|
||||||
bob_public: &'static str,
|
bob_public: &'static str,
|
||||||
/// Expected shared secret (little-endian).
|
/// Expected shared secret (little-endian).
|
||||||
expected_shared: &'static str,
|
expected_shared: &'static str,
|
||||||
}
|
}
|
||||||
|
|
@ -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([' ', '\n'], "");
|
let s = s.replace(' ', "").replace('\n', "");
|
||||||
assert!(s.len().is_multiple_of(2), "odd-length hex: {s}");
|
assert!(s.len() % 2 == 0, "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,24 +347,12 @@ 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(_) => {
|
Ok(_) => { *failures += 1; println!(" [FAIL] {} (decrypt mismatch)", v.name); }
|
||||||
*failures += 1;
|
Err(e) => { *failures += 1; println!(" [FAIL] {} (decrypt error: {e})", v.name); }
|
||||||
println!(" [FAIL] {} (decrypt mismatch)", v.name);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
*failures += 1;
|
|
||||||
println!(" [FAIL] {} (decrypt error: {e})", v.name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(ct_tag) => {
|
Ok(ct_tag) => { *failures += 1; fail(v.name, &ct_tag, &expected); }
|
||||||
*failures += 1;
|
Err(e) => { *failures += 1; println!(" [FAIL] {} (encrypt error: {e})", v.name); }
|
||||||
fail(v.name, &ct_tag, &expected);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
*failures += 1;
|
|
||||||
println!(" [FAIL] {} (encrypt error: {e})", v.name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -379,14 +367,8 @@ 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) => {
|
Ok(out) => { *failures += 1; fail(v.name, &out, &expected); }
|
||||||
*failures += 1;
|
Err(e) => { *failures += 1; println!(" [FAIL] {} ({e})", v.name); }
|
||||||
fail(v.name, &out, &expected);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
*failures += 1;
|
|
||||||
println!(" [FAIL] {} ({e})", v.name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -403,24 +385,12 @@ 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) => {
|
Ok(false) => { *failures += 1; println!(" [FAIL] {} (verify false)", v.name); }
|
||||||
*failures += 1;
|
Err(e) => { *failures += 1; println!(" [FAIL] {} (verify error: {e})", v.name); }
|
||||||
println!(" [FAIL] {} (verify false)", v.name);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
*failures += 1;
|
|
||||||
println!(" [FAIL] {} (verify error: {e})", v.name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(tag) => {
|
Ok(tag) => { *failures += 1; fail(v.name, &tag, &expected); }
|
||||||
*failures += 1;
|
Err(e) => { *failures += 1; println!(" [FAIL] {} ({e})", v.name); }
|
||||||
fail(v.name, &tag, &expected);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
*failures += 1;
|
|
||||||
println!(" [FAIL] {} ({e})", v.name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -433,14 +403,8 @@ 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) => {
|
Ok(digest) => { *failures += 1; fail(v.name, &digest, &expected); }
|
||||||
*failures += 1;
|
Err(e) => { *failures += 1; println!(" [FAIL] {} ({e})", v.name); }
|
||||||
fail(v.name, &digest, &expected);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
*failures += 1;
|
|
||||||
println!(" [FAIL] {} ({e})", v.name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -467,28 +431,16 @@ 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) => {
|
Ok(shared) => { *failures += 1; fail(&test_a, &shared, &expected); }
|
||||||
*failures += 1;
|
Err(e) => { *failures += 1; println!(" [FAIL] {} ({e})", test_a); }
|
||||||
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) => {
|
Ok(shared) => { *failures += 1; fail(&test_b, &shared, &expected); }
|
||||||
*failures += 1;
|
Err(e) => { *failures += 1; println!(" [FAIL] {} ({e})", test_b); }
|
||||||
fail(&test_b, &shared, &expected);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
*failures += 1;
|
|
||||||
println!(" [FAIL] {} ({e})", test_b);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -503,33 +455,27 @@ 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) => {
|
Err(e) => { *failures += 1; println!(" [FAIL] {} (keygen: {e})", name); continue; }
|
||||||
*failures += 1;
|
Ok(kp) => {
|
||||||
println!(" [FAIL] {} (keygen: {e})", name);
|
match p.encapsulate(algo, &kp.public_key) {
|
||||||
continue;
|
Err(e) => { *failures += 1; println!(" [FAIL] {} (encap: {e})", name); }
|
||||||
}
|
Ok(encap) => {
|
||||||
Ok(kp) => match p.encapsulate(algo, &kp.public_key) {
|
match p.decapsulate(algo, &kp.private_key, &encap.ciphertext) {
|
||||||
Err(e) => {
|
Err(e) => { *failures += 1; println!(" [FAIL] {} (decap: {e})", name); }
|
||||||
*failures += 1;
|
Ok(decap_secret) => {
|
||||||
println!(" [FAIL] {} (encap: {e})", name);
|
if decap_secret.as_slice() == encap.shared_secret.as_slice() {
|
||||||
}
|
pass(&name);
|
||||||
Ok(encap) => match p.decapsulate(algo, &kp.private_key, &encap.ciphertext) {
|
} else {
|
||||||
Err(e) => {
|
*failures += 1;
|
||||||
*failures += 1;
|
println!(" [FAIL] {} (shared-secret mismatch)", name);
|
||||||
println!(" [FAIL] {} (decap: {e})", name);
|
println!(" encap: {}", hex::encode(&encap.shared_secret));
|
||||||
}
|
println!(" decap: {}", hex::encode(&decap_secret));
|
||||||
Ok(decap_secret) => {
|
}
|
||||||
if decap_secret.as_slice() == encap.shared_secret.as_slice() {
|
}
|
||||||
pass(&name);
|
|
||||||
} else {
|
|
||||||
*failures += 1;
|
|
||||||
println!(" [FAIL] {} (shared-secret mismatch)", name);
|
|
||||||
println!(" encap: {}", hex::encode(&encap.shared_secret));
|
|
||||||
println!(" decap: {}", hex::encode(&decap_secret));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -565,22 +511,10 @@ 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(
|
match p.decrypt_aead(AeadAlgorithm::XChaCha20Poly1305, &key, &nonce, &ct_tag, &aad) {
|
||||||
AeadAlgorithm::XChaCha20Poly1305,
|
|
||||||
&key,
|
|
||||||
&nonce,
|
|
||||||
&ct_tag,
|
|
||||||
&aad,
|
|
||||||
) {
|
|
||||||
Ok(recovered) if recovered == pt => pass(&rt_name),
|
Ok(recovered) if recovered == pt => pass(&rt_name),
|
||||||
Ok(_) => {
|
Ok(_) => { *failures += 1; println!(" [FAIL] {} (decrypt mismatch)", rt_name); }
|
||||||
*failures += 1;
|
Err(e) => { *failures += 1; println!(" [FAIL] {} (decrypt error: {e})", rt_name); }
|
||||||
println!(" [FAIL] {} (decrypt mismatch)", rt_name);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
*failures += 1;
|
|
||||||
println!(" [FAIL] {} (decrypt error: {e})", rt_name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Auth-failure check ────────────────────────────────────
|
// ── Auth-failure check ────────────────────────────────────
|
||||||
|
|
@ -588,24 +522,10 @@ 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(
|
match p.decrypt_aead(AeadAlgorithm::XChaCha20Poly1305, &key, &nonce, &tampered, &aad) {
|
||||||
AeadAlgorithm::XChaCha20Poly1305,
|
Err(ccc_crypto_core::error::CryptoError::AuthenticationFailed) => pass(&auth_name),
|
||||||
&key,
|
Ok(_) => { *failures += 1; println!(" [FAIL] {} (expected auth failure, got Ok)", auth_name); }
|
||||||
&nonce,
|
Err(e) => { *failures += 1; println!(" [FAIL] {} (wrong error type: {e})", auth_name); }
|
||||||
&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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue