NEW: first draft of crates

This commit is contained in:
JohnE 2026-02-23 14:59:04 -08:00
parent df1de9d99d
commit 3c9320ffa5
36 changed files with 6088 additions and 1 deletions

29
.cargo/config.toml Normal file
View File

@ -0,0 +1,29 @@
# Cross-compilation target aliases
# Usage: cargo build-ios / cargo build-android-arm64 etc.
[alias]
build-ios = "build --target aarch64-apple-ios"
build-ios-sim = "build --target aarch64-apple-ios-sim"
build-android-arm64 = "build --target aarch64-linux-android"
build-android-x64 = "build --target x86_64-linux-android"
build-macos-arm64 = "build --target aarch64-apple-darwin"
build-macos-x64 = "build --target x86_64-apple-darwin"
build-all-apple = "build --target aarch64-apple-ios --target aarch64-apple-darwin --target x86_64-apple-darwin"
# Link flags for Apple targets (needed when linking wolfSSL static lib)
[target.aarch64-apple-ios]
rustflags = ["-C", "link-arg=-lc++"]
[target.aarch64-apple-darwin]
rustflags = ["-C", "link-arg=-lc++"]
[target.x86_64-apple-darwin]
rustflags = ["-C", "link-arg=-lc++"]
# Android NDK sysroot — set ANDROID_NDK_HOME in your environment.
# cargokit (used by flutter_rust_bridge) handles this automatically during
# `flutter build apk` / `flutter build appbundle`.
[target.aarch64-linux-android]
linker = "aarch64-linux-android21-clang"
[target.x86_64-linux-android]
linker = "x86_64-linux-android21-clang"

21
.gitignore vendored Normal file
View File

@ -0,0 +1,21 @@
# Generated by Cargo
# will have compiled files and executables
debug
target
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# Generated by cargo mutants
# Contains mutation testing data
**/mutants.out*/
# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "vendors/wolfssl"]
path = vendors/wolfssl
url = https://github.com/wolfSSL/wolfssl

View File

@ -11,5 +11,10 @@
"**/var/**": true,
"**/bin/**": true
},
"foldOnOpen.targets": ["AllBlockComments"]
"foldOnOpen.targets": [
"AllBlockComments"
],
"chat.tools.terminal.autoApprove": {
"cargo search": true
}
}

1063
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

29
Cargo.toml Normal file
View File

@ -0,0 +1,29 @@
[workspace]
resolver = "2"
members = [
"crates/ccc-crypto-core",
"crates/ccc-crypto-wolfssl",
"crates/ccc-flutter-bridge",
"tests/conformance",
]
[workspace.package]
version = "0.1.0"
edition = "2021"
authors = ["LetUsMsg Engineering"]
license = "MIT"
repository = "https://github.com/letusmsg/ccc_rust"
# Shared dependency versions — all crates inherit from here.
[workspace.dependencies]
zeroize = { version = "1.8", features = ["derive"] }
thiserror = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
log = "0.4"
once_cell = "1.19"
anyhow = "1.0"
env_logger = "0.11"
# ccc-crypto-core is referenced by every crate in the workspace.
ccc-crypto-core = { path = "crates/ccc-crypto-core" }

View File

@ -0,0 +1,18 @@
[package]
name = "ccc-crypto-core"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
description = "Shared traits, algorithm enumerations, and provider registry for the CCC crypto system."
[dependencies]
zeroize.workspace = true
thiserror.workspace = true
serde.workspace = true
serde_json.workspace = true
log.workspace = true
once_cell.workspace = true
[dev-dependencies]
# Nothing external needed for registry unit tests.

View File

@ -0,0 +1,295 @@
//! Algorithm enumerations for the CCC crypto system.
//!
//! **Important**: every discriminant value matches the corresponding integer
//! constant in `cipher_constants.dart`. Do NOT change them without making a
//! matching change in Dart.
use serde::{Deserialize, Serialize};
// ──────────────────────────────────────────────────────────────────────────────
// AEAD (Authenticated Encryption with Associated Data)
// Dart constants 1019
// ──────────────────────────────────────────────────────────────────────────────
/// Authenticated encryption algorithms supported by the CCC system.
///
/// Discriminant values match `cipher_constants.dart` (e.g. `AES_GCM_256 = 12`).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)]
pub enum AeadAlgorithm {
/// AES-256-GCM. Dart constant `12`.
AesGcm256 = 12,
/// ChaCha20-Poly1305 (12-byte nonce). Dart constant `13`.
ChaCha20Poly1305 = 13,
/// XChaCha20-Poly1305 (24-byte nonce). Dart constant `14`.
XChaCha20Poly1305 = 14,
/// Ascon-AEAD-128a (lightweight AEAD). Dart constant `15`.
Ascon128a = 15,
}
impl AeadAlgorithm {
/// Convert from the raw `u32` value used in FFI / Dart interop.
pub fn from_u32(v: u32) -> Option<Self> {
match v {
12 => Some(Self::AesGcm256),
13 => Some(Self::ChaCha20Poly1305),
14 => Some(Self::XChaCha20Poly1305),
15 => Some(Self::Ascon128a),
_ => None,
}
}
/// Human-readable name, suitable for logs and diagnostics.
pub fn name(self) -> &'static str {
match self {
Self::AesGcm256 => "AES-256-GCM",
Self::ChaCha20Poly1305 => "ChaCha20-Poly1305",
Self::XChaCha20Poly1305 => "XChaCha20-Poly1305",
Self::Ascon128a => "Ascon-AEAD-128a",
}
}
/// Expected nonce length in bytes for this algorithm.
pub fn nonce_len(self) -> usize {
match self {
Self::AesGcm256 => 12,
Self::ChaCha20Poly1305 => 12,
Self::XChaCha20Poly1305 => 24,
Self::Ascon128a => 16,
}
}
/// Expected key length in bytes for this algorithm.
pub fn key_len(self) -> usize {
match self {
Self::AesGcm256 => 32,
Self::ChaCha20Poly1305 => 32,
Self::XChaCha20Poly1305 => 32,
Self::Ascon128a => 16,
}
}
}
// ──────────────────────────────────────────────────────────────────────────────
// KDF (Key Derivation Functions)
// Dart constants 14
// ──────────────────────────────────────────────────────────────────────────────
/// Key derivation function algorithms.
///
/// Discriminant values match `cipher_constants.dart` (e.g. `SHA_256 = 1`).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)]
pub enum KdfAlgorithm {
/// HKDF / chain-KDF using SHA-256. Dart constant `1`.
Sha256 = 1,
/// HKDF / chain-KDF using SHA-384. Dart constant `2`.
Sha384 = 2,
/// HKDF / chain-KDF using SHA-512. Dart constant `3`.
Sha512 = 3,
/// HKDF / chain-KDF using BLAKE2b-512. Dart constant `4`.
Blake2b512 = 4,
/// Argon2id (password / memory-hard KDF). Dart constant `5`.
Argon2id = 5,
/// KMAC256 (used by Ultra-mode seq_seed). Dart constant `6`.
Kmac256 = 6,
}
impl KdfAlgorithm {
/// Convert from the raw `u32` value used in FFI / Dart interop.
pub fn from_u32(v: u32) -> Option<Self> {
match v {
1 => Some(Self::Sha256),
2 => Some(Self::Sha384),
3 => Some(Self::Sha512),
4 => Some(Self::Blake2b512),
5 => Some(Self::Argon2id),
6 => Some(Self::Kmac256),
_ => None,
}
}
/// Human-readable name.
pub fn name(self) -> &'static str {
match self {
Self::Sha256 => "HKDF-SHA-256",
Self::Sha384 => "HKDF-SHA-384",
Self::Sha512 => "HKDF-SHA-512",
Self::Blake2b512 => "HKDF-BLAKE2b-512",
Self::Argon2id => "Argon2id",
Self::Kmac256 => "KMAC256",
}
}
}
// ──────────────────────────────────────────────────────────────────────────────
// MAC (Message Authentication Codes)
// Dart constants 3035
// ──────────────────────────────────────────────────────────────────────────────
/// Message authentication code algorithms.
///
/// Discriminant values match `cipher_constants.dart`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)]
pub enum MacAlgorithm {
/// HMAC-SHA-256. Dart constant `30`.
HmacSha256 = 30,
/// HMAC-SHA-384. Dart constant `31`.
HmacSha384 = 31,
/// HMAC-SHA-512. Dart constant `32`.
HmacSha512 = 32,
/// BLAKE2b-MAC (keyed BLAKE2b). Dart constant `33`.
Blake2bMac = 33,
/// Poly1305 standalone. Dart constant `35`.
Poly1305 = 35,
}
impl MacAlgorithm {
/// Convert from the raw `u32` value.
pub fn from_u32(v: u32) -> Option<Self> {
match v {
30 => Some(Self::HmacSha256),
31 => Some(Self::HmacSha384),
32 => Some(Self::HmacSha512),
33 => Some(Self::Blake2bMac),
35 => Some(Self::Poly1305),
_ => None,
}
}
/// Human-readable name.
pub fn name(self) -> &'static str {
match self {
Self::HmacSha256 => "HMAC-SHA-256",
Self::HmacSha384 => "HMAC-SHA-384",
Self::HmacSha512 => "HMAC-SHA-512",
Self::Blake2bMac => "BLAKE2b-MAC",
Self::Poly1305 => "Poly1305",
}
}
/// Output / tag length in bytes.
pub fn tag_len(self) -> usize {
match self {
Self::HmacSha256 => 32,
Self::HmacSha384 => 48,
Self::HmacSha512 => 64,
Self::Blake2bMac => 64,
Self::Poly1305 => 16,
}
}
}
// ──────────────────────────────────────────────────────────────────────────────
// Hash
// Dart constants 4049
// ──────────────────────────────────────────────────────────────────────────────
/// Cryptographic hash algorithms.
///
/// Discriminant values match `cipher_constants.dart`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)]
pub enum HashAlgorithm {
/// SHA-256. Dart constant `40`.
Sha256 = 40,
/// SHA-384. Dart constant `41`.
Sha384 = 41,
/// SHA-512. Dart constant `42`.
Sha512 = 42,
/// BLAKE2b-512. Dart constant `43`.
Blake2b512 = 43,
/// SHA3-256. Dart constant `44`.
Sha3_256 = 44,
/// SHA3-512. Dart constant `45`.
Sha3_512 = 45,
}
impl HashAlgorithm {
/// Convert from the raw `u32` value.
pub fn from_u32(v: u32) -> Option<Self> {
match v {
40 => Some(Self::Sha256),
41 => Some(Self::Sha384),
42 => Some(Self::Sha512),
43 => Some(Self::Blake2b512),
44 => Some(Self::Sha3_256),
45 => Some(Self::Sha3_512),
_ => None,
}
}
/// Human-readable name.
pub fn name(self) -> &'static str {
match self {
Self::Sha256 => "SHA-256",
Self::Sha384 => "SHA-384",
Self::Sha512 => "SHA-512",
Self::Blake2b512 => "BLAKE2b-512",
Self::Sha3_256 => "SHA3-256",
Self::Sha3_512 => "SHA3-512",
}
}
/// Digest output length in bytes.
pub fn digest_len(self) -> usize {
match self {
Self::Sha256 => 32,
Self::Sha384 => 48,
Self::Sha512 => 64,
Self::Blake2b512 => 64,
Self::Sha3_256 => 32,
Self::Sha3_512 => 64,
}
}
}
// ──────────────────────────────────────────────────────────────────────────────
// KEM (Key Encapsulation Mechanisms)
// Dart constants 5059 (new range, not yet in cipher_constants.dart)
// ──────────────────────────────────────────────────────────────────────────────
/// Key encapsulation mechanism algorithms.
///
/// Values 5059 are a new range reserved for KEM algorithms; add the
/// matching constants to `cipher_constants.dart` when wiring the Dart layer.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)]
pub enum KemAlgorithm {
/// X25519 ECDH. Dart constant `50`.
X25519 = 50,
/// X448 ECDH. Dart constant `51`.
X448 = 51,
/// ML-KEM-768 (FIPS 203). Dart constant `52`.
MlKem768 = 52,
/// ML-KEM-1024 (FIPS 203). Dart constant `53`.
MlKem1024 = 53,
/// Classic McEliece 460896. Dart constant `54`.
ClassicMcEliece460896 = 54,
}
impl KemAlgorithm {
/// Convert from the raw `u32` value.
pub fn from_u32(v: u32) -> Option<Self> {
match v {
50 => Some(Self::X25519),
51 => Some(Self::X448),
52 => Some(Self::MlKem768),
53 => Some(Self::MlKem1024),
54 => Some(Self::ClassicMcEliece460896),
_ => None,
}
}
/// Human-readable name.
pub fn name(self) -> &'static str {
match self {
Self::X25519 => "X25519",
Self::X448 => "X448",
Self::MlKem768 => "ML-KEM-768",
Self::MlKem1024 => "ML-KEM-1024",
Self::ClassicMcEliece460896 => "Classic-McEliece-460896",
}
}
}

View File

@ -0,0 +1,135 @@
//! Capability structs describing what each provider can do.
//!
//! [`ProviderCapabilities`] is populated by a provider at startup via a live
//! probe call (not hard-coded). The populated map is then exposed to Dart
//! via the `ccc_provider_capabilities()` bridge function, replacing the former
//! compile-time stub in `ccc_provider_spec.dart`.
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::algorithms::{AeadAlgorithm, HashAlgorithm, KdfAlgorithm, KemAlgorithm, MacAlgorithm};
// ──────────────────────────────────────────────────────────────────────────────
/// Runtime capability descriptor for a single algorithm within a provider.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AlgorithmCapability {
/// Whether the algorithm is usable on this platform / build.
///
/// Determined by a live probe call at startup, not by compile-time flags.
pub available: bool,
/// Whether the algorithm produces byte-identical output for the same
/// key/nonce/plaintext across providers.
///
/// Must be `true` for any algorithm used in the shared cipher chain.
/// Cross-provider conformance tests verify this at build time.
pub deterministic_io: bool,
/// Throughput score normalised to 0100 (0 = slowest, 100 = fastest).
///
/// Populated after `WolfSslProvider::benchmark()` runs at app start.
/// Stored so Dart can rank providers when multiple are available.
pub efficiency_score: u8,
/// Reliability score normalised to 0100.
///
/// Derived from the self-test pass rate: `(passed / total) * 100`.
pub reliability_score: u8,
}
impl AlgorithmCapability {
/// Create a placeholder capability that marks an algorithm as unavailable.
///
/// Used while the provider is initialising or when a probe fails.
pub fn unavailable() -> Self {
Self {
available: false,
deterministic_io: false,
efficiency_score: 0,
reliability_score: 0,
}
}
}
// ──────────────────────────────────────────────────────────────────────────────
/// The complete runtime capability matrix for one crypto provider.
///
/// Returned by [`CryptoProvider::capabilities()`] and forwarded to Dart as
/// `CapabilitiesDto`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProviderCapabilities {
/// The provider's canonical name (matches `CccCryptoProvider` enum in Dart).
pub provider_name: String,
/// AEAD algorithm capabilities, keyed by [`AeadAlgorithm`].
pub aead: HashMap<AeadAlgorithm, AlgorithmCapability>,
/// KDF algorithm capabilities, keyed by [`KdfAlgorithm`].
pub kdf: HashMap<KdfAlgorithm, AlgorithmCapability>,
/// MAC algorithm capabilities, keyed by [`MacAlgorithm`].
pub mac: HashMap<MacAlgorithm, AlgorithmCapability>,
/// Hash algorithm capabilities, keyed by [`HashAlgorithm`].
pub hash: HashMap<HashAlgorithm, AlgorithmCapability>,
/// KEM algorithm capabilities, keyed by [`KemAlgorithm`].
pub kem: HashMap<KemAlgorithm, AlgorithmCapability>,
}
impl ProviderCapabilities {
/// Create an empty capability set for the given provider.
pub fn empty(provider_name: impl Into<String>) -> Self {
Self {
provider_name: provider_name.into(),
aead: HashMap::new(),
kdf: HashMap::new(),
mac: HashMap::new(),
hash: HashMap::new(),
kem: HashMap::new(),
}
}
/// Register an AEAD capability entry.
pub fn with_aead(mut self, algo: AeadAlgorithm, cap: AlgorithmCapability) -> Self {
self.aead.insert(algo, cap);
self
}
/// Register a KDF capability entry.
pub fn with_kdf(mut self, algo: KdfAlgorithm, cap: AlgorithmCapability) -> Self {
self.kdf.insert(algo, cap);
self
}
/// Register a MAC capability entry.
pub fn with_mac(mut self, algo: MacAlgorithm, cap: AlgorithmCapability) -> Self {
self.mac.insert(algo, cap);
self
}
/// Register a Hash capability entry.
pub fn with_hash(mut self, algo: HashAlgorithm, cap: AlgorithmCapability) -> Self {
self.hash.insert(algo, cap);
self
}
/// Register a KEM capability entry.
pub fn with_kem(mut self, algo: KemAlgorithm, cap: AlgorithmCapability) -> Self {
self.kem.insert(algo, cap);
self
}
/// Return `true` if any algorithm in any category is available.
pub fn has_any_available(&self) -> bool {
self.aead.values().any(|c| c.available)
|| self.kdf.values().any(|c| c.available)
|| self.mac.values().any(|c| c.available)
|| self.hash.values().any(|c| c.available)
|| self.kem.values().any(|c| c.available)
}
}

View File

@ -0,0 +1,46 @@
//! Unified error type for all CCC crypto operations.
use thiserror::Error;
/// All errors that can arise from CCC crypto provider operations.
///
/// Every provider trait method returns `Result<_, CryptoError>`. The bridge
/// crate converts this into a Dart `Result` / exception.
#[derive(Debug, Error)]
pub enum CryptoError {
/// The requested algorithm is not supported by this provider or is not
/// available on the current platform build.
#[error("unsupported algorithm: {0}")]
UnsupportedAlgorithm(String),
/// The provided key has the wrong length or invalid content.
#[error("invalid key: {0}")]
InvalidKey(String),
/// The provided nonce has the wrong length.
#[error("invalid nonce: {0}")]
InvalidNonce(String),
/// AEAD / MAC authentication tag verification failed.
///
/// This is the only error that should be surfaced to end users (as a
/// generic "decryption failed" message; do not leak details).
#[error("authentication failed")]
AuthenticationFailed,
/// The input data is malformed or has wrong length.
#[error("invalid input: {0}")]
InvalidInput(String),
/// The algorithm requires a feature (e.g. PQ build) that is not compiled
/// into the native library.
#[error("feature not compiled: {0}")]
FeatureNotCompiled(String),
/// An unexpected internal error from the underlying native library.
///
/// The `String` contains library-specific diagnostic text; do not surface
/// this to end users.
#[error("internal error: {0}")]
InternalError(String),
}

View File

@ -0,0 +1,24 @@
//! # ccc-crypto-core
//!
//! Shared algorithm enumerations, capability structs, provider traits, and the
//! global [`ProviderRegistry`] for the CCC (Copious Cipher Chain) cryptography
//! system.
//!
//! All algorithm discriminant values match the integer constants defined in
//! `cipher_constants.dart` exactly, so the Dart cipher-sequencing logic
//! requires zero changes when calling into Rust.
pub mod algorithms;
pub mod capabilities;
pub mod error;
pub mod provider;
pub mod registry;
pub mod types;
// Re-export everything a provider crate or the bridge crate needs.
pub use algorithms::{AeadAlgorithm, HashAlgorithm, KdfAlgorithm, KemAlgorithm, MacAlgorithm};
pub use capabilities::{AlgorithmCapability, ProviderCapabilities};
pub use error::CryptoError;
pub use provider::{AeadProvider, CryptoProvider, HashProvider, KdfProvider, KemProvider, MacProvider};
pub use registry::ProviderRegistry;
pub use types::{AlgoTestResult, BenchmarkReport, KemEncapResult, KemKeyPair, SelfTestReport};

View File

@ -0,0 +1,197 @@
//! Provider traits that every crypto backend must implement.
//!
//! A concrete provider (e.g. `WolfSslProvider`) implements [`CryptoProvider`],
//! which in turn requires all five primitive traits: [`AeadProvider`],
//! [`KdfProvider`], [`MacProvider`], [`HashProvider`], and optionally
//! [`KemProvider`].
//!
//! The trait objects are `Send + Sync` so they can live in the global
//! [`ProviderRegistry`][crate::registry::ProviderRegistry] behind a
//! `Mutex`.
use zeroize::Zeroizing;
use crate::{
algorithms::{AeadAlgorithm, HashAlgorithm, KdfAlgorithm, KemAlgorithm, MacAlgorithm},
capabilities::ProviderCapabilities,
error::CryptoError,
types::{BenchmarkReport, KemEncapResult, KemKeyPair, SelfTestReport},
};
// ──────────────────────────────────────────────────────────────────────────────
// Primitive traits
// ──────────────────────────────────────────────────────────────────────────────
/// AEAD (Authenticated Encryption with Associated Data) operations.
pub trait AeadProvider {
/// Encrypt `plaintext` and return `ciphertext || tag`.
///
/// # Parameters
/// - `algo` Which AEAD algorithm to use.
/// - `key` Raw key bytes. Length must match [`AeadAlgorithm::key_len`].
/// - `nonce` Nonce / IV bytes. Length must match [`AeadAlgorithm::nonce_len`].
/// - `plaintext` Data to encrypt.
/// - `aad` Additional associated data (authenticated but not encrypted).
///
/// # Returns
/// Concatenated ciphertext + authentication tag.
fn encrypt_aead(
&self,
algo: AeadAlgorithm,
key: &[u8],
nonce: &[u8],
plaintext: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, CryptoError>;
/// Decrypt and authenticate `ciphertext || tag`, returning the plaintext.
///
/// Returns [`CryptoError::AuthenticationFailed`] if the tag does not verify.
fn decrypt_aead(
&self,
algo: AeadAlgorithm,
key: &[u8],
nonce: &[u8],
ciphertext_and_tag: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, CryptoError>;
}
/// Key derivation function operations.
pub trait KdfProvider {
/// Derive a key of `length` bytes from input keying material.
///
/// # Parameters
/// - `algo` Which KDF to use.
/// - `ikm` Input keying material.
/// - `salt` Optional salt (pass an empty slice to use the zero salt).
/// - `info` Context / label bytes (HKDF `info` field or equivalent).
/// - `length` Desired output byte length.
///
/// # Returns
/// A `Zeroizing<Vec<u8>>` that is zeroed automatically on drop.
fn derive_key(
&self,
algo: KdfAlgorithm,
ikm: &[u8],
salt: &[u8],
info: &[u8],
length: usize,
) -> Result<Zeroizing<Vec<u8>>, CryptoError>;
}
/// Message authentication code operations.
pub trait MacProvider {
/// Compute a MAC tag over `data` using `key`.
fn compute_mac(
&self,
algo: MacAlgorithm,
key: &[u8],
data: &[u8],
) -> Result<Vec<u8>, CryptoError>;
/// Verify that `mac` is the correct tag for `data` under `key`.
///
/// Implementations **must** use constant-time comparison to avoid timing
/// side-channels.
///
/// Returns `Ok(true)` if verification succeeds, `Ok(false)` if the tag
/// does not match (do not return an error for mismatches — the caller
/// decides how to handle false).
fn verify_mac(
&self,
algo: MacAlgorithm,
key: &[u8],
data: &[u8],
mac: &[u8],
) -> Result<bool, CryptoError>;
}
/// Cryptographic hash operations.
pub trait HashProvider {
/// Compute a digest of `data` using the specified algorithm.
fn hash(
&self,
algo: HashAlgorithm,
data: &[u8],
) -> Result<Vec<u8>, CryptoError>;
}
/// Key encapsulation mechanism operations.
///
/// Not required by [`CryptoProvider`] — providers that only implement symmetric
/// operations simply do not implement this trait. The bridge exposes KEM
/// functions separately and looks up a `KemProvider` from the registry.
pub trait KemProvider {
/// Generate a new KEM key pair.
fn generate_keypair(
&self,
algo: KemAlgorithm,
) -> Result<KemKeyPair, CryptoError>;
/// Encapsulate a shared secret for the holder of `public_key`.
///
/// Returns `(ciphertext, shared_secret)`. The caller sends `ciphertext`
/// to the peer and uses `shared_secret` locally.
fn encapsulate(
&self,
algo: KemAlgorithm,
public_key: &[u8],
) -> Result<KemEncapResult, CryptoError>;
/// Decapsulate a shared secret from `ciphertext` using `private_key`.
///
/// Returns the same `shared_secret` that the encapsulator obtained.
fn decapsulate(
&self,
algo: KemAlgorithm,
private_key: &[u8],
ciphertext: &[u8],
) -> Result<Zeroizing<Vec<u8>>, CryptoError>;
}
// ──────────────────────────────────────────────────────────────────────────────
// Umbrella trait
// ──────────────────────────────────────────────────────────────────────────────
/// The combined interface every full-featured crypto provider must satisfy.
///
/// Implementors are automatically usable as `Box<dyn CryptoProvider>` in the
/// [`ProviderRegistry`][crate::registry::ProviderRegistry].
///
/// # Implementing a new provider
///
/// 1. Create a new crate under `crates/ccc-crypto-<name>/`.
/// 2. Implement `AeadProvider`, `KdfProvider`, `MacProvider`, `HashProvider`
/// for your provider struct.
/// 3. Implement `CryptoProvider` (add `capabilities()`, `self_test()`,
/// `benchmark()`, `provider_name()`).
/// 4. In your `init()` function call
/// `ProviderRegistry::global().register(name, Box::new(provider))`.
/// 5. Call `ccc_crypto_<name>::init()` from `ccc-flutter-bridge/src/lib.rs`.
/// 6. Add NIST test vectors for every algorithm you expose.
pub trait CryptoProvider: AeadProvider + KdfProvider + MacProvider + HashProvider + Send + Sync {
/// The provider's canonical name (e.g. `"wolfssl"`).
///
/// Must be lowercase, no spaces, match the `CccCryptoProvider` enum value
/// in `ccc_provider_spec.dart`.
fn provider_name(&self) -> &'static str;
/// Return the full runtime capability matrix for this provider.
///
/// Called once at startup after [`CryptoProvider::self_test`] and
/// [`CryptoProvider::benchmark`] have completed.
fn capabilities(&self) -> ProviderCapabilities;
/// Run embedded NIST/RFC test vectors for every implemented algorithm.
///
/// This must be a deterministic, side-effect-free operation that can be
/// called at any time (including in CI without native hardware).
fn self_test(&self) -> SelfTestReport;
/// Measure throughput for every implemented AEAD algorithm.
///
/// Encrypt 1 MB of random data × 100 iterations and normalise the
/// result into `efficiency_score: u8` (0100).
fn benchmark(&self) -> BenchmarkReport;
}

View File

@ -0,0 +1,237 @@
//! Global provider registry.
//!
//! [`ProviderRegistry`] is a lazily-initialised, thread-safe map from provider
//! name to a boxed [`CryptoProvider`]. Each provider crate's `init()` function
//! registers its implementation here; the bridge crate calls those `init()`
//! functions from `ccc_init()`.
//!
//! # Example
//!
//! ```rust,ignore
//! // In ccc-crypto-wolfssl:
//! pub fn init() {
//! ProviderRegistry::global().register("wolfssl", Box::new(WolfSslProvider::new()));
//! }
//!
//! // In the bridge:
//! pub fn ccc_init() {
//! ccc_crypto_wolfssl::init();
//! }
//!
//! // Anywhere:
//! let registry = ProviderRegistry::global();
//! let provider = registry.get("wolfssl").expect("wolfssl not registered");
//! let ct = provider.encrypt_aead(...)?;
//! ```
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use once_cell::sync::OnceCell;
use crate::provider::CryptoProvider;
// ──────────────────────────────────────────────────────────────────────────────
/// Thread-safe registry mapping provider names to boxed [`CryptoProvider`]
/// trait objects.
pub struct ProviderRegistry {
inner: Mutex<HashMap<String, Arc<dyn CryptoProvider>>>,
}
impl ProviderRegistry {
/// Return the global singleton registry.
///
/// Initialised on first call. Safe to call from any thread.
pub fn global() -> &'static Self {
static INSTANCE: OnceCell<ProviderRegistry> = OnceCell::new();
INSTANCE.get_or_init(|| ProviderRegistry {
inner: Mutex::new(HashMap::new()),
})
}
/// Register a provider under `name`.
///
/// If a provider with the same name already exists it is replaced.
/// `name` must be lowercase, no spaces (e.g. `"wolfssl"`).
///
/// # Panics
///
/// Panics if the internal mutex is poisoned (indicates an earlier panic
/// in a thread holding the lock — treat as unrecoverable).
pub fn register(&self, name: &str, provider: Box<dyn CryptoProvider>) {
let mut map = self.inner.lock().expect("ProviderRegistry mutex poisoned");
log::info!("[ccc-crypto] registered provider '{}'", name);
map.insert(name.to_string(), Arc::from(provider));
}
/// Retrieve a provider by name.
///
/// Returns `None` if no provider with that name has been registered.
pub fn get(&self, name: &str) -> Option<Arc<dyn CryptoProvider>> {
let map = self.inner.lock().expect("ProviderRegistry mutex poisoned");
map.get(name).cloned()
}
/// Return the list of all registered provider names.
pub fn list(&self) -> Vec<String> {
let map = self.inner.lock().expect("ProviderRegistry mutex poisoned");
map.keys().cloned().collect()
}
/// Return `true` if the named provider has been registered.
pub fn contains(&self, name: &str) -> bool {
let map = self.inner.lock().expect("ProviderRegistry mutex poisoned");
map.contains_key(name)
}
/// Remove a provider from the registry.
///
/// Primarily for use in tests.
pub fn unregister(&self, name: &str) {
let mut map = self.inner.lock().expect("ProviderRegistry mutex poisoned");
map.remove(name);
}
}
// ──────────────────────────────────────────────────────────────────────────────
// Tests
// ──────────────────────────────────────────────────────────────────────────────
#[cfg(test)]
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);
}
}

View File

@ -0,0 +1,98 @@
//! Shared value types used across provider traits and the bridge API.
use serde::{Deserialize, Serialize};
use zeroize::{Zeroize, ZeroizeOnDrop};
// ──────────────────────────────────────────────────────────────────────────────
// KEM types
// ──────────────────────────────────────────────────────────────────────────────
/// A KEM key pair returned by [`KemProvider::generate_keypair`].
///
/// Both key buffers are zeroed automatically when dropped.
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct KemKeyPair {
/// The public key (safe to transmit to the peer).
pub public_key: Vec<u8>,
/// The private key (must never leave the device).
pub private_key: Vec<u8>,
}
/// The result of a KEM encapsulation operation.
///
/// The shared secret is zeroed when dropped. The ciphertext is public.
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct KemEncapResult {
/// The ciphertext to send to the holder of the private key.
pub ciphertext: Vec<u8>,
/// The shared secret derived locally.
pub shared_secret: Vec<u8>,
}
// ──────────────────────────────────────────────────────────────────────────────
// Self-test and benchmark report types
// ──────────────────────────────────────────────────────────────────────────────
/// The outcome of a single algorithm self-test.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AlgoTestResult {
/// Algorithm discriminant (same as the `u32` value passed over FFI).
pub algo_id: u32,
/// Human-readable algorithm name.
pub algo_name: String,
/// Whether the test vector verification passed.
pub passed: bool,
/// Error message if `passed` is `false`; `None` otherwise.
pub error_message: Option<String>,
}
/// The complete self-test report for one provider.
///
/// Produced by [`CryptoProvider::self_test`] and forwarded to Dart via
/// `ccc_self_test()`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SelfTestReport {
/// Provider name.
pub provider_name: String,
/// Per-algorithm test results.
pub results: Vec<AlgoTestResult>,
/// `true` if and only if every result in `results` has `passed == true`.
pub all_passed: bool,
}
impl SelfTestReport {
/// Compute `all_passed` from the results list and return a finalised report.
pub fn finalise(provider_name: impl Into<String>, results: Vec<AlgoTestResult>) -> Self {
let all_passed = results.iter().all(|r| r.passed);
Self {
provider_name: provider_name.into(),
results,
all_passed,
}
}
}
/// Per-algorithm throughput measurement from the provider benchmark.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AlgoBenchResult {
/// Algorithm discriminant.
pub algo_id: u32,
/// Human-readable algorithm name.
pub algo_name: String,
/// Measured throughput in MB/s (1 MB × 100 iterations).
pub throughput_mbps: f64,
/// Normalised score 0100 (relative to the fastest algorithm measured).
pub efficiency_score: u8,
}
/// The complete benchmark report for one provider.
///
/// Produced by [`CryptoProvider::benchmark`]. Results are used to populate
/// `AlgorithmCapability::efficiency_score` in [`ProviderCapabilities`].
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BenchmarkReport {
/// Provider name.
pub provider_name: String,
/// Per-algorithm benchmark results.
pub results: Vec<AlgoBenchResult>,
}

View File

@ -0,0 +1,28 @@
[package]
name = "ccc-crypto-wolfssl"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
description = "wolfSSL / wolfCrypt provider for the CCC crypto system."
[dependencies]
ccc-crypto-core.workspace = true
zeroize.workspace = true
thiserror.workspace = true
log.workspace = true
[build-dependencies]
# cmake crate drives the wolfSSL CMake build from source.
cmake = "0.1"
# bindgen generates Rust FFI bindings to the wolfCrypt C headers.
bindgen = "0.72"
[dev-dependencies]
# hex for decoding NIST test vector strings in tests.
hex = "0.4"
[features]
# Enable this when building without the wolfSSL C library
# (e.g. on docs.rs, or for type-checking in CI without cmake).
stub_ffi = []

View File

@ -0,0 +1,204 @@
//! Build script for ccc-crypto-wolfssl.
//!
//! Responsibilities:
//! 1. Locate the wolfSSL source tree (vendored submodule).
//! 2. Build wolfSSL as a static library using CMake.
//! 3. Generate Rust FFI bindings to the wolfCrypt headers via bindgen.
//!
//! # Environment variables
//!
//! * `WOLFSSL_SOURCE_DIR` Override the default wolfSSL source path.
//! Useful for CI environments where the submodule may be at a non-standard
//! location. Defaults to `<workspace_root>/vendors/wolfssl`.
//! * `WOLFSSL_INSTALL_DIR` If set, skip the CMake build and link against
//! a pre-installed wolfSSL at this path. The path must contain
//! `include/wolfssl/` and `lib/libwolfssl.a`.
use std::{env, path::PathBuf};
fn main() {
// When the `stub_ffi` feature is enabled (e.g. for type-checking without
// the wolfSSL C library), skip all build steps and write an empty bindings
// file so the `include!()` in lib.rs still compiles.
if std::env::var("CARGO_FEATURE_STUB_FFI").is_ok() {
let out_dir = std::env::var("OUT_DIR").unwrap();
let bindings_path = std::path::Path::new(&out_dir).join("wolfcrypt_bindings.rs");
std::fs::write(&bindings_path, "// stub — no wolfSSL headers\n").unwrap();
return;
}
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
// Workspace root is two levels up from crates/ccc-crypto-wolfssl/
let workspace_root = manifest_dir.join("../..").canonicalize().unwrap();
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
// ── 1. Locate wolfSSL ────────────────────────────────────────────────────
let (include_dir, lib_dir) =
if let Ok(install_dir) = env::var("WOLFSSL_INSTALL_DIR") {
// Use a pre-installed wolfSSL — skip the CMake build.
let p = PathBuf::from(&install_dir);
println!("cargo:warning=Using pre-installed wolfSSL at {}", install_dir);
(p.join("include"), p.join("lib"))
} else {
// Build from source (default — uses our git submodule).
let source_dir = env::var("WOLFSSL_SOURCE_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| workspace_root.join("vendors/wolfssl"));
if !source_dir.join("CMakeLists.txt").exists() {
panic!(
"\n\nwolfSSL source not found at {}.\n\
Run `git submodule update --init --recursive` to fetch it,\n\
or set WOLFSSL_SOURCE_DIR to an alternative path.\n",
source_dir.display()
);
}
build_wolfssl_cmake(&source_dir, &out_dir)
};
// ── 2. Tell Cargo how to link ────────────────────────────────────────────
println!("cargo:rustc-link-search=native={}", lib_dir.display());
println!("cargo:rustc-link-lib=static=wolfssl");
// Link system libraries required by wolfSSL on each platform.
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
match target_os.as_str() {
"macos" | "ios" => {
println!("cargo:rustc-link-lib=framework=Security");
println!("cargo:rustc-link-lib=c++");
}
"linux" | "android" => {
println!("cargo:rustc-link-lib=stdc++");
}
_ => {}
}
// Re-run the build script if any of these change.
println!("cargo:rerun-if-env-changed=WOLFSSL_SOURCE_DIR");
println!("cargo:rerun-if-env-changed=WOLFSSL_INSTALL_DIR");
// ── 3. Generate FFI bindings via bindgen ─────────────────────────────────
generate_bindings(&include_dir, &out_dir);
}
/// Build wolfSSL from source using CMake. Returns `(include_dir, lib_dir)`.
fn build_wolfssl_cmake(source_dir: &PathBuf, out_dir: &PathBuf) -> (PathBuf, PathBuf) {
println!(
"cargo:warning=Building wolfSSL from source at {}",
source_dir.display()
);
let mut cfg = cmake::Config::new(source_dir);
// ── Core algorithm selection ─────────────────────────────────────────────
// Enable exactly the algorithms the CCC system needs in Phase 4.
// Additional algorithms can be enabled here for Phase 5+ PQ support.
cfg
// Build as a static library.
.define("BUILD_SHARED_LIBS", "OFF")
.define("WOLFSSL_BUILD_SHARED_LIBS", "OFF")
// Disable TLS/SSL stack — we only need the wolfCrypt algorithms.
.define("WOLFSSL_NO_TLS", "ON")
// AEAD
.define("WOLFSSL_AES_GCM", "ON")
.define("WOLFSSL_CHACHA", "ON")
.define("WOLFSSL_POLY1305", "ON")
// Hash
.define("WOLFSSL_SHA224", "ON")
.define("WOLFSSL_SHA384", "ON")
.define("WOLFSSL_SHA512", "ON")
.define("WOLFSSL_SHA3", "ON")
.define("WOLFSSL_BLAKE2", "ON")
// KDF
.define("WOLFSSL_HKDF", "ON")
.define("WOLFSSL_PWDBASED", "ON") // PBKDF2, Argon2
// MAC
.define("WOLFSSL_HMAC", "ON")
// Asymmetric (needed for X25519 ratchet)
.define("WOLFSSL_CURVE25519", "ON")
.define("WOLFSSL_CURVE448", "ON")
// RNG (needed internally)
.define("WOLFSSL_RNG", "ON")
// Minimise binary size.
.define("WOLFSSL_CRYPTONLY", "ON")
.define("WOLFSSL_MIN_RSA_BITS", "2048")
.define("WOLFSSL_NO_FILESYSTEM", "ON");
let install_path = cfg.build();
let include_dir = install_path.join("include");
let lib_dir = install_path.join("lib");
(include_dir, lib_dir)
}
/// Run bindgen over the wolfCrypt headers we actually use.
fn generate_bindings(include_dir: &PathBuf, out_dir: &PathBuf) {
let header = include_dir.join("wolfssl/wolfcrypt/aes.h");
if !header.exists() {
// Bindings are only generated when wolfSSL headers are present.
// In environments without the C library (e.g. pure Rust CI), a
// pre-generated bindings file can be committed at src/bindings.rs.
println!(
"cargo:warning=wolfSSL headers not found at {}; \
using pre-generated bindings if available.",
include_dir.display()
);
return;
}
let bindings = bindgen::Builder::default()
// Include all wolfCrypt headers we need.
.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/sha256.h").to_str().unwrap())
.header(include_dir.join("wolfssl/wolfcrypt/sha512.h").to_str().unwrap())
.header(include_dir.join("wolfssl/wolfcrypt/sha3.h").to_str().unwrap())
.header(include_dir.join("wolfssl/wolfcrypt/blake2.h").to_str().unwrap())
.header(include_dir.join("wolfssl/wolfcrypt/hmac.h").to_str().unwrap())
.header(include_dir.join("wolfssl/wolfcrypt/kdf.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/random.h").to_str().unwrap())
.clang_arg(format!("-I{}", include_dir.display()))
// Only generate bindings for the types/functions we whitelist.
.allowlist_function("wc_Aes.*")
.allowlist_function("wc_ChaCha20Poly1305.*")
.allowlist_function("wc_Sha.*")
.allowlist_function("wc_Blake2.*")
.allowlist_function("wc_Hmac.*")
.allowlist_function("wc_HKDF.*")
.allowlist_function("wc_Pbkdf2.*")
.allowlist_function("wc_Argon2.*")
.allowlist_function("wc_curve25519.*")
.allowlist_function("wc_curve448.*")
.allowlist_function("wc_InitRng.*")
.allowlist_function("wc_RNG.*")
.allowlist_function("wc_FreeRng.*")
.allowlist_type("Aes")
.allowlist_type("Hmac")
.allowlist_type("WC_RNG")
.allowlist_type("curve25519.*")
.allowlist_type("curve448.*")
.allowlist_var("AES_.*")
.allowlist_var("WC_.*")
.allowlist_var("SHA.*")
.allowlist_var("BLAKE2.*")
// Silence warnings for types we don't control.
.blocklist_type("__.*")
.derive_debug(false)
.layout_tests(false)
.generate()
.expect("bindgen failed to generate wolfCrypt bindings");
bindings
.write_to_file(out_dir.join("wolfcrypt_bindings.rs"))
.expect("failed to write wolfCrypt bindings");
println!("cargo:rerun-if-changed=build.rs");
}

View File

@ -0,0 +1,303 @@
//! AEAD (Authenticated Encryption with Associated Data) implementations.
//!
//! Provides AES-256-GCM and ChaCha20-Poly1305 via wolfCrypt.
//!
//! # Wire format (matches ccc_iso.dart)
//!
//! The output of [`encrypt`] is: `ciphertext || tag (16 bytes)`.
//! This byte layout is identical to the Dart implementation in `ccc_iso.dart`
//! so that cross-provider conformance tests pass.
use ccc_crypto_core::{algorithms::AeadAlgorithm, error::CryptoError};
// ──────────────────────────────────────────────────────────────────────────────
// Public entry points (called from WolfSslProvider)
// ──────────────────────────────────────────────────────────────────────────────
/// Encrypt using the specified AEAD algorithm.
///
/// Output: `ciphertext || 16-byte auth tag`.
pub fn encrypt(
algo: AeadAlgorithm,
key: &[u8],
nonce: &[u8],
plaintext: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, CryptoError> {
validate_key_nonce(algo, key, nonce)?;
match algo {
AeadAlgorithm::AesGcm256 => aes_gcm_256_encrypt(key, nonce, plaintext, aad),
AeadAlgorithm::ChaCha20Poly1305 => chacha20_poly1305_encrypt(key, nonce, plaintext, aad),
AeadAlgorithm::XChaCha20Poly1305 => xchacha20_poly1305_encrypt(key, nonce, plaintext, aad),
AeadAlgorithm::Ascon128a =>
Err(CryptoError::FeatureNotCompiled("Ascon-128a".into())),
}
}
/// Decrypt using the specified AEAD algorithm.
///
/// Input: `ciphertext || 16-byte auth tag`. Returns plaintext or
/// [`CryptoError::AuthenticationFailed`] if the tag does not verify.
pub fn decrypt(
algo: AeadAlgorithm,
key: &[u8],
nonce: &[u8],
ciphertext_and_tag: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, CryptoError> {
validate_key_nonce(algo, key, nonce)?;
match algo {
AeadAlgorithm::AesGcm256 => aes_gcm_256_decrypt(key, nonce, ciphertext_and_tag, aad),
AeadAlgorithm::ChaCha20Poly1305 => chacha20_poly1305_decrypt(key, nonce, ciphertext_and_tag, aad),
AeadAlgorithm::XChaCha20Poly1305 => xchacha20_poly1305_decrypt(key, nonce, ciphertext_and_tag, aad),
AeadAlgorithm::Ascon128a =>
Err(CryptoError::FeatureNotCompiled("Ascon-128a".into())),
}
}
// ──────────────────────────────────────────────────────────────────────────────
// Validation helpers
// ──────────────────────────────────────────────────────────────────────────────
fn validate_key_nonce(algo: AeadAlgorithm, key: &[u8], nonce: &[u8]) -> Result<(), CryptoError> {
let expected_key = algo.key_len();
let expected_nonce = algo.nonce_len();
if key.len() != expected_key {
return Err(CryptoError::InvalidKey(format!(
"{}: expected {}-byte key, got {}",
algo.name(), expected_key, key.len()
)));
}
if nonce.len() != expected_nonce {
return Err(CryptoError::InvalidNonce(format!(
"{}: expected {}-byte nonce, got {}",
algo.name(), expected_nonce, nonce.len()
)));
}
Ok(())
}
const TAG_LEN: usize = 16;
// ──────────────────────────────────────────────────────────────────────────────
// AES-256-GCM
// ──────────────────────────────────────────────────────────────────────────────
fn aes_gcm_256_encrypt(
key: &[u8],
nonce: &[u8], // 12 bytes
plaintext: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, CryptoError> {
let mut out = vec![0u8; plaintext.len() + TAG_LEN];
let (ct_buf, tag_buf) = out.split_at_mut(plaintext.len());
unsafe {
// wolfCrypt AES-GCM encrypt:
// wc_AesGcmEncrypt(aes, out, input, sz, iv, ivSz, authTag, authTagSz, authIn, authInSz)
//
// We use the one-shot wc_AesGcmEncrypt which handles Aes struct init internally
// via wc_AesGcmSetKey.
let mut aes: crate::sys::Aes = std::mem::zeroed();
let ret = crate::sys::wc_AesGcmSetKey(
&mut aes,
key.as_ptr(),
key.len() as u32,
);
if ret != 0 {
return Err(CryptoError::InvalidKey(format!("wc_AesGcmSetKey returned {}", ret)));
}
let ret = crate::sys::wc_AesGcmEncrypt(
&mut aes,
ct_buf.as_mut_ptr(),
plaintext.as_ptr(),
plaintext.len() as u32,
nonce.as_ptr(),
nonce.len() as u32,
tag_buf.as_mut_ptr(),
TAG_LEN as u32,
if aad.is_empty() { std::ptr::null() } else { aad.as_ptr() },
aad.len() as u32,
);
if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_AesGcmEncrypt returned {}", ret)));
}
}
Ok(out)
}
fn aes_gcm_256_decrypt(
key: &[u8],
nonce: &[u8],
ciphertext_and_tag: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, CryptoError> {
if ciphertext_and_tag.len() < TAG_LEN {
return Err(CryptoError::InvalidInput(
"AES-256-GCM: ciphertext too short (no room for tag)".into()
));
}
let ct_len = ciphertext_and_tag.len() - TAG_LEN;
let (ciphertext, tag) = ciphertext_and_tag.split_at(ct_len);
let mut plaintext = vec![0u8; ct_len];
unsafe {
let mut aes: crate::sys::Aes = std::mem::zeroed();
let ret = crate::sys::wc_AesGcmSetKey(
&mut aes,
key.as_ptr(),
key.len() as u32,
);
if ret != 0 {
return Err(CryptoError::InvalidKey(format!("wc_AesGcmSetKey returned {}", ret)));
}
let ret = crate::sys::wc_AesGcmDecrypt(
&mut aes,
plaintext.as_mut_ptr(),
ciphertext.as_ptr(),
ct_len as u32,
nonce.as_ptr(),
nonce.len() as u32,
tag.as_ptr(),
TAG_LEN as u32,
if aad.is_empty() { std::ptr::null() } else { aad.as_ptr() },
aad.len() as u32,
);
if ret != 0 {
// wolfCrypt returns AES_GCM_AUTH_E (-180) on auth failure.
return Err(CryptoError::AuthenticationFailed);
}
}
Ok(plaintext)
}
// ──────────────────────────────────────────────────────────────────────────────
// ChaCha20-Poly1305 (12-byte nonce)
// ──────────────────────────────────────────────────────────────────────────────
fn chacha20_poly1305_encrypt(
key: &[u8],
nonce: &[u8], // 12 bytes
plaintext: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, CryptoError> {
let mut out = vec![0u8; plaintext.len() + TAG_LEN];
let (ct_buf, tag_buf) = out.split_at_mut(plaintext.len());
unsafe {
// wc_ChaCha20Poly1305_Encrypt(key, nonce, aad, aadSz, input, inputSz, output, authTag)
let ret = crate::sys::wc_ChaCha20Poly1305_Encrypt(
key.as_ptr(),
nonce.as_ptr(),
if aad.is_empty() { std::ptr::null() } else { aad.as_ptr() },
aad.len() as u32,
plaintext.as_ptr(),
plaintext.len() as u32,
ct_buf.as_mut_ptr(),
tag_buf.as_mut_ptr(),
);
if ret != 0 {
return Err(CryptoError::InternalError(
format!("wc_ChaCha20Poly1305_Encrypt returned {}", ret)
));
}
}
Ok(out)
}
fn chacha20_poly1305_decrypt(
key: &[u8],
nonce: &[u8],
ciphertext_and_tag: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, CryptoError> {
if ciphertext_and_tag.len() < TAG_LEN {
return Err(CryptoError::InvalidInput(
"ChaCha20-Poly1305: ciphertext too short".into()
));
}
let ct_len = ciphertext_and_tag.len() - TAG_LEN;
let (ciphertext, tag) = ciphertext_and_tag.split_at(ct_len);
let mut plaintext = vec![0u8; ct_len];
unsafe {
// wc_ChaCha20Poly1305_Decrypt(key, nonce, aad, aadSz, input, inputSz, authTag, output)
let ret = crate::sys::wc_ChaCha20Poly1305_Decrypt(
key.as_ptr(),
nonce.as_ptr(),
if aad.is_empty() { std::ptr::null() } else { aad.as_ptr() },
aad.len() as u32,
ciphertext.as_ptr(),
ct_len as u32,
tag.as_ptr(),
plaintext.as_mut_ptr(),
);
if ret != 0 {
return Err(CryptoError::AuthenticationFailed);
}
}
Ok(plaintext)
}
// ──────────────────────────────────────────────────────────────────────────────
// XChaCha20-Poly1305 (24-byte nonce)
// ──────────────────────────────────────────────────────────────────────────────
//
// wolfCrypt does not expose a single XChaCha20-Poly1305 one-shot function.
// We derive a sub-key from the first 16 bytes of the nonce using HChaCha20,
// then apply ChaCha20-Poly1305 with the last 12 bytes for the nonce.
// This matches the XChaCha20-Poly1305 construction in RFC 8439 §2.
fn xchacha20_poly1305_encrypt(
key: &[u8],
nonce: &[u8], // 24 bytes
plaintext: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, CryptoError> {
let (subkey, chacha_nonce) = xchacha_derive_subkey(key, nonce)?;
chacha20_poly1305_encrypt(&subkey, &chacha_nonce, plaintext, aad)
}
fn xchacha20_poly1305_decrypt(
key: &[u8],
nonce: &[u8], // 24 bytes
ciphertext_and_tag: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, CryptoError> {
let (subkey, chacha_nonce) = xchacha_derive_subkey(key, nonce)?;
chacha20_poly1305_decrypt(&subkey, &chacha_nonce, ciphertext_and_tag, aad)
}
/// Derive a 32-byte sub-key and a 12-byte ChaCha nonce from a 32-byte key
/// and a 24-byte XChaCha nonce, following RFC 8439 §2.
fn xchacha_derive_subkey(key: &[u8], nonce: &[u8]) -> Result<(Vec<u8>, Vec<u8>), CryptoError> {
debug_assert_eq!(nonce.len(), 24);
// HChaCha20 takes key[32] + nonce[0..16] → 32-byte subkey.
let mut subkey = vec![0u8; 32];
unsafe {
// wc_HChaCha20(output, key, nonce_16bytes) — available in wolfCrypt.
let ret = crate::sys::wc_HChaCha20(
subkey.as_mut_ptr() as *mut u32,
key.as_ptr() as *const u32,
nonce.as_ptr() as *const u32,
);
if ret != 0 {
return Err(CryptoError::InternalError(
format!("wc_HChaCha20 returned {}", ret)
));
}
}
// ChaCha nonce = [0u8 × 4] || nonce[16..24]
let mut chacha_nonce = vec![0u8; 12];
chacha_nonce[4..12].copy_from_slice(&nonce[16..24]);
Ok((subkey, chacha_nonce))
}

View File

@ -0,0 +1,200 @@
//! Runtime capability probing and benchmarking for wolfSSL.
//!
//! [`probe_capabilities`] runs a one-byte encrypt/decrypt through each
//! algorithm at startup and flips `available` based on whether it succeeds.
//!
//! [`run_benchmark`] runs 100 × 1 MB encryptions per AEAD algorithm to
//! produce throughput scores. Results are stored in the returned
//! [`BenchmarkReport`] and normalised to a 0100 `efficiency_score`.
use std::time::Instant;
use ccc_crypto_core::{
algorithms::{AeadAlgorithm, HashAlgorithm, KdfAlgorithm, KemAlgorithm, MacAlgorithm},
capabilities::{AlgorithmCapability, ProviderCapabilities},
types::{AlgoBenchResult, BenchmarkReport},
};
// ──────────────────────────────────────────────────────────────────────────────
// Capability probing
// ──────────────────────────────────────────────────────────────────────────────
/// Probe every algorithm by performing a minimal one-shot operation.
///
/// Sets `available = true` only when the wolfCrypt call returns `0` (success).
/// Sets `efficiency_score` / `reliability_score` from `benchmarks` results
/// when provided, otherwise leaves them at 0 (to be populated later).
pub fn probe_capabilities(benchmarks: Option<&BenchmarkReport>) -> ProviderCapabilities {
let mut caps = ProviderCapabilities::empty("wolfssl");
// ── AEAD ─────────────────────────────────────────────────────────────────
for algo in [
AeadAlgorithm::AesGcm256,
AeadAlgorithm::ChaCha20Poly1305,
AeadAlgorithm::XChaCha20Poly1305,
] {
let nonce_len = algo.nonce_len();
let key = vec![0u8; algo.key_len()];
let nonce = vec![0u8; nonce_len];
let available = crate::aead::encrypt(algo, &key, &nonce, b"x", b"").is_ok();
let efficiency = benchmarks
.and_then(|b| b.results.iter().find(|r| r.algo_id == algo as u32))
.map(|r| r.efficiency_score)
.unwrap_or(0);
caps = caps.with_aead(algo, AlgorithmCapability {
available,
deterministic_io: true, // wolfSSL output is deterministic for same key/nonce
efficiency_score: efficiency,
reliability_score: if available { 100 } else { 0 },
});
}
// Ascon-128a is not in this wolfSSL build.
caps = caps.with_aead(AeadAlgorithm::Ascon128a, AlgorithmCapability::unavailable());
// ── KDF ──────────────────────────────────────────────────────────────────
for algo in [KdfAlgorithm::Sha256, KdfAlgorithm::Sha384, KdfAlgorithm::Sha512] {
let available = crate::kdf::derive_key(algo, b"key", b"salt", b"info", 32).is_ok();
caps = caps.with_kdf(algo, AlgorithmCapability {
available,
deterministic_io: true,
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] {
let salt: &[u8] = if algo == KdfAlgorithm::Argon2id { b"saltsalt" } else { b"" };
let available = crate::kdf::derive_key(algo, b"key", salt, b"", 32).is_ok();
caps = caps.with_kdf(algo, AlgorithmCapability {
available,
deterministic_io: true,
efficiency_score: if algo == KdfAlgorithm::Argon2id { 10 } else { 70 },
reliability_score: if available { 100 } else { 0 },
});
}
caps = caps.with_kdf(KdfAlgorithm::Kmac256, AlgorithmCapability::unavailable());
// ── MAC ──────────────────────────────────────────────────────────────────
for algo in [MacAlgorithm::HmacSha256, MacAlgorithm::HmacSha384, MacAlgorithm::HmacSha512] {
let available = crate::mac::compute_mac(algo, b"key", b"data").is_ok();
caps = caps.with_mac(algo, AlgorithmCapability {
available,
deterministic_io: true,
efficiency_score: 85,
reliability_score: if available { 100 } else { 0 },
});
}
{
let available = crate::mac::compute_mac(MacAlgorithm::Blake2bMac, b"key-that-is-16-by", b"data").is_ok();
caps = caps.with_mac(MacAlgorithm::Blake2bMac, AlgorithmCapability {
available,
deterministic_io: true,
efficiency_score: 90,
reliability_score: if available { 100 } else { 0 },
});
}
caps = caps.with_mac(MacAlgorithm::Poly1305, AlgorithmCapability::unavailable());
// ── Hash ─────────────────────────────────────────────────────────────────
for algo in [
HashAlgorithm::Sha256, HashAlgorithm::Sha384, HashAlgorithm::Sha512,
HashAlgorithm::Blake2b512, HashAlgorithm::Sha3_256, HashAlgorithm::Sha3_512,
] {
let available = crate::hash::hash(algo, b"probe").is_ok();
caps = caps.with_hash(algo, AlgorithmCapability {
available,
deterministic_io: true,
efficiency_score: 90,
reliability_score: if available { 100 } else { 0 },
});
}
// ── KEM ──────────────────────────────────────────────────────────────────
for algo in [KemAlgorithm::X25519, KemAlgorithm::X448] {
let available = crate::kem::generate_keypair(algo).is_ok();
caps = caps.with_kem(algo, AlgorithmCapability {
available,
deterministic_io: false, // key generation is randomised
efficiency_score: 80,
reliability_score: if available { 100 } else { 0 },
});
}
for algo in [
KemAlgorithm::MlKem768, KemAlgorithm::MlKem1024,
KemAlgorithm::ClassicMcEliece460896,
] {
caps = caps.with_kem(algo, AlgorithmCapability::unavailable());
}
caps
}
// ──────────────────────────────────────────────────────────────────────────────
// Throughput benchmarking
// ──────────────────────────────────────────────────────────────────────────────
/// Run 100 × 1 MB AEAD encryptions for each available algorithm.
///
/// Returns a [`BenchmarkReport`] with raw throughput values and normalised
/// `efficiency_score` (0100) relative to the fastest algorithm measured.
pub fn run_benchmark() -> BenchmarkReport {
const MB: usize = 1024 * 1024;
const ITERS: u32 = 100;
let plaintext = vec![0xABu8; MB];
let algos = [
AeadAlgorithm::AesGcm256,
AeadAlgorithm::ChaCha20Poly1305,
AeadAlgorithm::XChaCha20Poly1305,
];
let mut raw_results: Vec<(AeadAlgorithm, f64)> = Vec::new();
for algo in algos {
let key = vec![0u8; algo.key_len()];
let nonce = vec![0u8; algo.nonce_len()];
// Probe first; skip unavailable algorithms.
if crate::aead::encrypt(algo, &key, &nonce, b"x", b"").is_err() {
continue;
}
let start = Instant::now();
for _ in 0..ITERS {
let _ = crate::aead::encrypt(algo, &key, &nonce, &plaintext, b"");
}
let elapsed_secs = start.elapsed().as_secs_f64();
let throughput = (MB as f64 * ITERS as f64) / (1024.0 * 1024.0 * elapsed_secs);
raw_results.push((algo, throughput));
}
// Normalise to 0100 relative to the fastest result.
let max_throughput = raw_results
.iter()
.map(|(_, t)| *t)
.fold(f64::NEG_INFINITY, f64::max);
let results: Vec<AlgoBenchResult> = raw_results
.into_iter()
.map(|(algo, throughput)| {
let score = if max_throughput > 0.0 {
((throughput / max_throughput) * 100.0).round() as u8
} else {
0
};
AlgoBenchResult {
algo_id: algo as u32,
algo_name: algo.name().into(),
throughput_mbps: throughput,
efficiency_score: score,
}
})
.collect();
BenchmarkReport {
provider_name: "wolfssl".into(),
results,
}
}

View File

@ -0,0 +1,135 @@
//! Cryptographic hash implementations via wolfCrypt.
//!
//! Covers SHA-256, SHA-384, SHA-512, SHA3-256, SHA3-512, and BLAKE2b-512.
use ccc_crypto_core::{algorithms::HashAlgorithm, error::CryptoError};
// ──────────────────────────────────────────────────────────────────────────────
// Public entry point
// ──────────────────────────────────────────────────────────────────────────────
/// Compute a digest of `data` using the specified hash algorithm.
///
/// Returns the full digest for the selected algorithm (see
/// [`HashAlgorithm::digest_len`] for output lengths).
pub fn hash(algo: HashAlgorithm, data: &[u8]) -> Result<Vec<u8>, CryptoError> {
match algo {
HashAlgorithm::Sha256 => sha256(data),
HashAlgorithm::Sha384 => sha384(data),
HashAlgorithm::Sha512 => sha512(data),
HashAlgorithm::Blake2b512 => blake2b_512(data),
HashAlgorithm::Sha3_256 => sha3_256(data),
HashAlgorithm::Sha3_512 => sha3_512(data),
}
}
// ──────────────────────────────────────────────────────────────────────────────
// SHA-2 family
// ──────────────────────────────────────────────────────────────────────────────
fn sha256(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
let mut out = vec![0u8; 32];
unsafe {
// wolfCrypt: wc_Sha256Hash(data, dataSz, digest)
let ret = crate::sys::wc_Sha256Hash(
data.as_ptr(),
data.len() as u32,
out.as_mut_ptr(),
);
if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_Sha256Hash returned {}", ret)));
}
}
Ok(out)
}
fn sha384(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
let mut out = vec![0u8; 48];
unsafe {
let ret = crate::sys::wc_Sha384Hash(
data.as_ptr(),
data.len() as u32,
out.as_mut_ptr(),
);
if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_Sha384Hash returned {}", ret)));
}
}
Ok(out)
}
fn sha512(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
let mut out = vec![0u8; 64];
unsafe {
let ret = crate::sys::wc_Sha512Hash(
data.as_ptr(),
data.len() as u32,
out.as_mut_ptr(),
);
if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_Sha512Hash returned {}", ret)));
}
}
Ok(out)
}
// ──────────────────────────────────────────────────────────────────────────────
// SHA-3 family
// ──────────────────────────────────────────────────────────────────────────────
fn sha3_256(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
let mut out = vec![0u8; 32];
unsafe {
// wolfCrypt: wc_Sha3_256Hash(data, dataSz, digest)
let ret = crate::sys::wc_Sha3_256Hash(
data.as_ptr(),
data.len() as u32,
out.as_mut_ptr(),
);
if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_Sha3_256Hash returned {}", ret)));
}
}
Ok(out)
}
fn sha3_512(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
let mut out = vec![0u8; 64];
unsafe {
let ret = crate::sys::wc_Sha3_512Hash(
data.as_ptr(),
data.len() as u32,
out.as_mut_ptr(),
);
if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_Sha3_512Hash returned {}", ret)));
}
}
Ok(out)
}
// ──────────────────────────────────────────────────────────────────────────────
// BLAKE2b-512
// ──────────────────────────────────────────────────────────────────────────────
fn blake2b_512(data: &[u8]) -> Result<Vec<u8>, CryptoError> {
let mut out = vec![0u8; 64];
unsafe {
// wc_Blake2bHash(out, outLen, key, keyLen, data, dataSz)
// No key = unkeyed hash (pass NULL, 0).
let ret = crate::sys::wc_Blake2bHash(
out.as_mut_ptr(),
64u32,
std::ptr::null(),
0u32,
data.as_ptr(),
data.len() as u32,
);
if ret != 0 {
return Err(CryptoError::InternalError(
format!("wc_Blake2bHash returned {}", ret)
));
}
}
Ok(out)
}

View File

@ -0,0 +1,167 @@
//! Key derivation function implementations via wolfCrypt.
//!
//! Covers HKDF (SHA-256 / SHA-384 / SHA-512), Argon2id, and BLAKE2b-based KDF.
use zeroize::Zeroizing;
use ccc_crypto_core::{algorithms::KdfAlgorithm, error::CryptoError};
// ──────────────────────────────────────────────────────────────────────────────
// wolfCrypt hash type constants (passed to wc_HKDF)
// ──────────────────────────────────────────────────────────────────────────────
/// wolfCrypt `wc_HashType` values for hash algorithms used in HKDF.
/// These match the `enum wc_HashType` values in `wolfssl/wolfcrypt/hash.h`.
const WC_HASH_TYPE_SHA256: i32 = 8;
const WC_HASH_TYPE_SHA384: i32 = 9;
const WC_HASH_TYPE_SHA512: i32 = 10;
// ──────────────────────────────────────────────────────────────────────────────
// Public entry point
// ──────────────────────────────────────────────────────────────────────────────
/// Derive a key of `length` bytes from input keying material.
pub fn derive_key(
algo: KdfAlgorithm,
ikm: &[u8],
salt: &[u8],
info: &[u8],
length: usize,
) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
match algo {
KdfAlgorithm::Sha256 => hkdf(WC_HASH_TYPE_SHA256, ikm, salt, info, length),
KdfAlgorithm::Sha384 => hkdf(WC_HASH_TYPE_SHA384, ikm, salt, info, length),
KdfAlgorithm::Sha512 => hkdf(WC_HASH_TYPE_SHA512, ikm, salt, info, length),
KdfAlgorithm::Blake2b512 => blake2b_kdf(ikm, salt, info, length),
KdfAlgorithm::Argon2id => argon2id(ikm, salt, length),
KdfAlgorithm::Kmac256 =>
Err(CryptoError::FeatureNotCompiled("KMAC256 (Phase 5+)".into())),
}
}
// ──────────────────────────────────────────────────────────────────────────────
// HKDF (RFC 5869)
// ──────────────────────────────────────────────────────────────────────────────
/// HKDF using wolfCrypt's `wc_HKDF()`.
///
/// `wc_HKDF(type, ikm, ikmSz, salt, saltSz, info, infoSz, okm, okmSz)`
fn hkdf(
hash_type: i32,
ikm: &[u8],
salt: &[u8],
info: &[u8],
length: usize,
) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
let mut out = Zeroizing::new(vec![0u8; length]);
unsafe {
let ret = crate::sys::wc_HKDF(
hash_type,
ikm.as_ptr(),
ikm.len() as u32,
if salt.is_empty() { std::ptr::null() } else { salt.as_ptr() },
salt.len() as u32,
if info.is_empty() { std::ptr::null() } else { info.as_ptr() },
info.len() as u32,
out.as_mut_ptr(),
length as u32,
);
if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_HKDF returned {}", ret)));
}
}
Ok(out)
}
// ──────────────────────────────────────────────────────────────────────────────
// BLAKE2b-based KDF
// ──────────────────────────────────────────────────────────────────────────────
/// BLAKE2b-512 used as a KDF: `BLAKE2b(ikm || salt || info)` → `length` bytes.
///
/// This is the same construction the Dart `RatchetEngine._kdf()` uses when
/// `kdfFunction == blake2b512`. Output is truncated to `length` bytes.
fn blake2b_kdf(
ikm: &[u8],
salt: &[u8],
info: &[u8],
length: usize,
) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
use crate::hash;
use ccc_crypto_core::algorithms::HashAlgorithm;
// Concatenate ikm || salt || info and hash the result.
let mut input = Vec::with_capacity(ikm.len() + salt.len() + info.len());
input.extend_from_slice(ikm);
input.extend_from_slice(salt);
input.extend_from_slice(info);
let digest = hash::hash(HashAlgorithm::Blake2b512, &input)?;
// Truncate or zero-extend to the requested length.
// For lengths > 64 (BLAKE2b-512 output) this KDF is not appropriate —
// callers should use HKDF-SHA512 instead.
if length > digest.len() {
return Err(CryptoError::InvalidInput(
"BLAKE2b-512 KDF: requested length exceeds 64 bytes; use HKDF-SHA512".into()
));
}
let mut out = Zeroizing::new(vec![0u8; length]);
out.copy_from_slice(&digest[..length]);
Ok(out)
}
// ──────────────────────────────────────────────────────────────────────────────
// Argon2id
// ──────────────────────────────────────────────────────────────────────────────
/// Argon2id KDF via wolfCrypt's `wc_Argon2()`.
///
/// Uses the memory and iteration parameters from `DEFAULT_CIPHER_PARAMS` in
/// `cipher_constants.dart`: 64 MB memory, 4 threads, 3 iterations.
fn argon2id(
password: &[u8], // treated as ikm / password
salt: &[u8],
length: usize,
) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
// Argon2id requires at least 8 bytes of salt.
if salt.len() < 8 {
return Err(CryptoError::InvalidInput(
"Argon2id requires at least 8-byte salt".into()
));
}
let mut out = Zeroizing::new(vec![0u8; length]);
unsafe {
// wolfCrypt Argon2:
// typedef struct Argon2_t { ... } Argon2;
// int wc_Argon2Hash(Argon2* arg2, const byte* pwd, word32 pwdSz,
// const byte* salt, word32 saltSz, byte* out, word32 outSz)
//
// We use the simplified wc_Argon2id_Hash wrapper (available in wolfCrypt ≥ 5.5).
let ret = crate::sys::wc_Argon2id_Hash(
out.as_mut_ptr(),
length as u32,
password.as_ptr(),
password.len() as u32,
salt.as_ptr(),
salt.len() as u32,
std::ptr::null(), // secret
0u32, // secretLen
std::ptr::null(), // additional data
0u32, // additional data len
64 * 1024, // memory kb (64 MB, matches DEFAULT_CIPHER_PARAMS)
3u32, // iterations (matches DEFAULT_CIPHER_PARAMS)
4u32, // parallelism (matches DEFAULT_CIPHER_PARAMS: 4 cores)
);
if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_Argon2id_Hash returned {}", ret)));
}
}
Ok(out)
}

View File

@ -0,0 +1,313 @@
//! Key Encapsulation Mechanism (KEM) implementations via wolfCrypt.
//!
//! Phase 4 covers X25519 and X448 (classical ECDH-based KEMs).
//! ML-KEM-768/1024 are stubbed with a descriptive error — they will be wired
//! in Phase 5 once the wolfSSL PQ build is validated.
use zeroize::Zeroizing;
use ccc_crypto_core::{
algorithms::KemAlgorithm,
error::CryptoError,
types::{KemEncapResult, KemKeyPair},
};
// ──────────────────────────────────────────────────────────────────────────────
// Public entry points
// ──────────────────────────────────────────────────────────────────────────────
/// Generate a new KEM key pair using the specified algorithm.
pub fn generate_keypair(algo: KemAlgorithm) -> Result<KemKeyPair, CryptoError> {
match algo {
KemAlgorithm::X25519 => x25519_generate(),
KemAlgorithm::X448 => x448_generate(),
KemAlgorithm::MlKem768 | KemAlgorithm::MlKem1024 =>
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()
)),
}
}
/// Encapsulate a shared secret for the holder of `public_key`.
///
/// For X25519 / X448 this performs an ephemeral DH exchange:
/// - Generate a fresh ephemeral key pair.
/// - Compute DH shared secret with the recipient's public key.
/// - Return `(ephemeral_public_key, shared_secret)`.
pub fn encapsulate(
algo: KemAlgorithm,
recipient_public_key: &[u8],
) -> Result<KemEncapResult, CryptoError> {
match algo {
KemAlgorithm::X25519 => x25519_encapsulate(recipient_public_key),
KemAlgorithm::X448 => x448_encapsulate(recipient_public_key),
KemAlgorithm::MlKem768 | KemAlgorithm::MlKem1024 =>
Err(CryptoError::FeatureNotCompiled(
"ML-KEM is deferred to Phase 5".into()
)),
KemAlgorithm::ClassicMcEliece460896 =>
Err(CryptoError::FeatureNotCompiled(
"Classic-McEliece is deferred to Phase 5".into()
)),
}
}
/// Decapsulate a shared secret from `ciphertext` (the peer's ephemeral public key)
/// using our `private_key`.
pub fn decapsulate(
algo: KemAlgorithm,
private_key: &[u8],
peer_ephemeral_public_key: &[u8],
) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
match algo {
KemAlgorithm::X25519 => x25519_decapsulate(private_key, peer_ephemeral_public_key),
KemAlgorithm::X448 => x448_decapsulate(private_key, peer_ephemeral_public_key),
KemAlgorithm::MlKem768 | KemAlgorithm::MlKem1024 =>
Err(CryptoError::FeatureNotCompiled(
"ML-KEM is deferred to Phase 5".into()
)),
KemAlgorithm::ClassicMcEliece460896 =>
Err(CryptoError::FeatureNotCompiled(
"Classic-McEliece is deferred to Phase 5".into()
)),
}
}
// ──────────────────────────────────────────────────────────────────────────────
// X25519
// wolfCrypt API: wc_curve25519_* (wolfssl/wolfcrypt/curve25519.h)
// Key sizes: private = 32 bytes, public = 32 bytes, shared secret = 32 bytes.
// ──────────────────────────────────────────────────────────────────────────────
fn x25519_generate() -> Result<KemKeyPair, CryptoError> {
let mut public_key = vec![0u8; 32];
let mut private_key = vec![0u8; 32];
unsafe {
let mut key: crate::sys::curve25519_key = std::mem::zeroed();
let mut rng: crate::sys::WC_RNG = std::mem::zeroed();
let ret = crate::sys::wc_InitRng(&mut rng);
if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_InitRng returned {}", ret)));
}
let ret = crate::sys::wc_curve25519_make_key(&mut rng, 32, &mut key);
if ret != 0 {
crate::sys::wc_FreeRng(&mut rng);
return Err(CryptoError::InternalError(
format!("wc_curve25519_make_key returned {}", ret)
));
}
let mut pub_sz = public_key.len() as u32;
let mut priv_sz = private_key.len() as u32;
crate::sys::wc_curve25519_export_key_raw(
&mut key,
private_key.as_mut_ptr(), &mut priv_sz,
public_key.as_mut_ptr(), &mut pub_sz,
);
crate::sys::wc_curve25519_free(&mut key);
crate::sys::wc_FreeRng(&mut rng);
}
Ok(KemKeyPair { public_key, private_key })
}
fn x25519_encapsulate(recipient_pub: &[u8]) -> Result<KemEncapResult, CryptoError> {
// Generate a fresh ephemeral key pair.
let ephemeral = x25519_generate()?;
// Compute DH shared secret: ephemeral_private × recipient_public.
let shared = x25519_dh(&ephemeral.private_key, recipient_pub)?;
Ok(KemEncapResult {
// KemKeyPair implements ZeroizeOnDrop (adds Drop) so fields must be cloned.
ciphertext: ephemeral.public_key.clone(),
shared_secret: (*shared).clone(),
})
}
fn x25519_decapsulate(
private_key: &[u8],
peer_ephemeral_pub: &[u8],
) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
x25519_dh(private_key, peer_ephemeral_pub)
}
/// Raw X25519 Diffie-Hellman: `shared = scalar_mult(private_key, public_key)`.
fn x25519_dh(private_key: &[u8], public_key: &[u8]) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
if private_key.len() != 32 || public_key.len() != 32 {
return Err(CryptoError::InvalidKey(
"X25519: keys must be 32 bytes each".into()
));
}
let mut shared = Zeroizing::new(vec![0u8; 32]);
unsafe {
let mut local_key: crate::sys::curve25519_key = std::mem::zeroed();
let mut remote_key: crate::sys::curve25519_key = std::mem::zeroed();
// Import private key.
let ret = crate::sys::wc_curve25519_import_private(
private_key.as_ptr(), 32,
&mut local_key,
);
if ret != 0 {
return Err(CryptoError::InvalidKey(
format!("wc_curve25519_import_private returned {}", ret)
));
}
// Import remote public key.
let ret = crate::sys::wc_curve25519_import_public(
public_key.as_ptr(), 32,
&mut remote_key,
);
if ret != 0 {
crate::sys::wc_curve25519_free(&mut local_key);
return Err(CryptoError::InvalidKey(
format!("wc_curve25519_import_public returned {}", ret)
));
}
let mut shared_sz = 32u32;
// EC_VALUE_SAME_KEY = 1 (little-endian) for Curve25519 in wolfCrypt
let endian = 1i32;
let ret = crate::sys::wc_curve25519_shared_secret_ex(
&mut local_key,
&mut remote_key,
shared.as_mut_ptr(),
&mut shared_sz,
endian,
);
crate::sys::wc_curve25519_free(&mut local_key);
crate::sys::wc_curve25519_free(&mut remote_key);
if ret != 0 {
return Err(CryptoError::InternalError(
format!("wc_curve25519_shared_secret_ex returned {}", ret)
));
}
}
Ok(shared)
}
// ──────────────────────────────────────────────────────────────────────────────
// X448
// wolfCrypt API: wc_curve448_* (wolfssl/wolfcrypt/curve448.h)
// Key sizes: private = 56 bytes, public = 56 bytes, shared secret = 56 bytes.
// ──────────────────────────────────────────────────────────────────────────────
fn x448_generate() -> Result<KemKeyPair, CryptoError> {
let mut public_key = vec![0u8; 56];
let mut private_key = vec![0u8; 56];
unsafe {
let mut key: crate::sys::curve448_key = std::mem::zeroed();
let mut rng: crate::sys::WC_RNG = std::mem::zeroed();
let ret = crate::sys::wc_InitRng(&mut rng);
if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_InitRng returned {}", ret)));
}
let ret = crate::sys::wc_curve448_make_key(&mut rng, 56, &mut key);
if ret != 0 {
crate::sys::wc_FreeRng(&mut rng);
return Err(CryptoError::InternalError(
format!("wc_curve448_make_key returned {}", ret)
));
}
let mut pub_sz = public_key.len() as u32;
let mut priv_sz = private_key.len() as u32;
crate::sys::wc_curve448_export_key_raw(
&mut key,
private_key.as_mut_ptr(), &mut priv_sz,
public_key.as_mut_ptr(), &mut pub_sz,
);
crate::sys::wc_curve448_free(&mut key);
crate::sys::wc_FreeRng(&mut rng);
}
Ok(KemKeyPair { public_key, private_key })
}
fn x448_encapsulate(recipient_pub: &[u8]) -> Result<KemEncapResult, CryptoError> {
let ephemeral = x448_generate()?;
let shared = x448_dh(&ephemeral.private_key, recipient_pub)?;
Ok(KemEncapResult {
ciphertext: ephemeral.public_key.clone(),
shared_secret: (*shared).clone(),
})
}
fn x448_decapsulate(
private_key: &[u8],
peer_ephemeral_pub: &[u8],
) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
x448_dh(private_key, peer_ephemeral_pub)
}
fn x448_dh(private_key: &[u8], public_key: &[u8]) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
if private_key.len() != 56 || public_key.len() != 56 {
return Err(CryptoError::InvalidKey("X448: keys must be 56 bytes each".into()));
}
let mut shared = Zeroizing::new(vec![0u8; 56]);
unsafe {
let mut local_key: crate::sys::curve448_key = std::mem::zeroed();
let mut remote_key: crate::sys::curve448_key = std::mem::zeroed();
let ret = crate::sys::wc_curve448_import_private(
private_key.as_ptr(), 56, &mut local_key,
);
if ret != 0 {
return Err(CryptoError::InvalidKey(
format!("wc_curve448_import_private returned {}", ret)
));
}
let ret = crate::sys::wc_curve448_import_public(
public_key.as_ptr(), 56, &mut remote_key,
);
if ret != 0 {
crate::sys::wc_curve448_free(&mut local_key);
return Err(CryptoError::InvalidKey(
format!("wc_curve448_import_public returned {}", ret)
));
}
let mut shared_sz = 56u32;
let endian = 1i32; // EC_VALUE_SAME_KEY little-endian
let ret = crate::sys::wc_curve448_shared_secret_ex(
&mut local_key,
&mut remote_key,
shared.as_mut_ptr(),
&mut shared_sz,
endian,
);
crate::sys::wc_curve448_free(&mut local_key);
crate::sys::wc_curve448_free(&mut remote_key);
if ret != 0 {
return Err(CryptoError::InternalError(
format!("wc_curve448_shared_secret_ex returned {}", ret)
));
}
}
Ok(shared)
}

View File

@ -0,0 +1,206 @@
//! # ccc-crypto-wolfssl
//!
//! wolfSSL / wolfCrypt implementation of the [`ccc_crypto_core::CryptoProvider`]
//! trait. This crate builds wolfSSL from source (vendored git submodule) and
//! wraps the C functions in safe Rust using FFI bindings generated by bindgen.
//!
//! ## Usage
//!
//! Call [`init()`] once at application start (from the bridge crate's
//! `ccc_init()` function). That registers a [`WolfSslProvider`] into the
//! global [`ProviderRegistry`][ccc_crypto_core::registry::ProviderRegistry].
pub mod aead;
pub mod capabilities;
pub mod hash;
pub mod kdf;
pub mod kem;
pub mod mac;
pub mod provider;
// FFI bindings generated by bindgen in build.rs.
// When wolfSSL headers are not present (e.g. docs.rs), use a stub.
#[allow(non_camel_case_types, non_snake_case, non_upper_case_globals, dead_code, clippy::all)]
mod sys {
// In a real build this pulls in the bindgen-generated file.
// The `include!` macro resolves at compile time to the OUT_DIR path.
// We guard it behind a cfg so the module still compiles without headers.
#[cfg(not(feature = "stub_ffi"))]
include!(concat!(env!("OUT_DIR"), "/wolfcrypt_bindings.rs"));
// Stub module for environments without the wolfSSL C library.
#[cfg(feature = "stub_ffi")]
pub use self::stubs::*;
#[cfg(feature = "stub_ffi")]
pub mod stubs {
//! Compile-time stubs for all wolfCrypt C types and functions used in
//! this crate. These allow `cargo check --features stub_ffi` to succeed
//! without the wolfSSL shared library or headers being present.
//!
//! **None of these functions will ever be called** — the `stub_ffi`
//! feature is only used for type-checking and documentation builds.
#![allow(non_camel_case_types, dead_code, unused_variables)]
use std::os::raw::{c_int, c_uchar, c_uint};
// ── Opaque C struct stubs ────────────────────────────────────────────
/// Stub for wolfCrypt `Aes` struct.
#[repr(C)]
pub struct Aes([u8; 512]);
/// Stub for wolfCrypt `Hmac` struct.
#[repr(C)]
pub struct Hmac([u8; 512]);
/// Stub for wolfCrypt `WC_RNG` struct.
#[repr(C)]
pub struct WC_RNG([u8; 256]);
/// Stub for wolfCrypt `curve25519_key` struct.
#[repr(C)]
pub struct curve25519_key([u8; 256]);
/// Stub for wolfCrypt `curve448_key` struct.
#[repr(C)]
pub struct curve448_key([u8; 256]);
// ── AES-GCM ─────────────────────────────────────────────────────────
pub unsafe fn wc_AesGcmSetKey(aes: *mut Aes, key: *const c_uchar, len: c_uint) -> c_int { unreachable!() }
pub unsafe fn wc_AesGcmEncrypt(
aes: *mut Aes, out: *mut c_uchar, in_: *const c_uchar, sz: c_uint,
iv: *const c_uchar, iv_sz: c_uint, auth_tag: *mut c_uchar, auth_tag_sz: c_uint,
auth_in: *const c_uchar, auth_in_sz: c_uint,
) -> c_int { unreachable!() }
pub unsafe fn wc_AesGcmDecrypt(
aes: *mut Aes, out: *mut c_uchar, in_: *const c_uchar, sz: c_uint,
iv: *const c_uchar, iv_sz: c_uint, auth_tag: *const c_uchar, auth_tag_sz: c_uint,
auth_in: *const c_uchar, auth_in_sz: c_uint,
) -> c_int { unreachable!() }
// ── ChaCha20-Poly1305 ────────────────────────────────────────────────
pub unsafe fn wc_ChaCha20Poly1305_Encrypt(
key: *const c_uchar, iv: *const c_uchar,
aad: *const c_uchar, aad_sz: c_uint,
in_: *const c_uchar, in_sz: c_uint,
out: *mut c_uchar, auth_tag: *mut c_uchar,
) -> c_int { unreachable!() }
pub unsafe fn wc_ChaCha20Poly1305_Decrypt(
key: *const c_uchar, iv: *const c_uchar,
aad: *const c_uchar, aad_sz: c_uint,
in_: *const c_uchar, in_sz: c_uint,
auth_tag: *const c_uchar, out: *mut c_uchar,
) -> c_int { unreachable!() }
// ── HChaCha20 (for XChaCha20 sub-key) ───────────────────────────────
// wc_HChaCha20(out: *mut u32, key: *const u32, nonce_16: *const u32) -> c_int
pub unsafe fn wc_HChaCha20(
out: *mut std::os::raw::c_uint,
key: *const std::os::raw::c_uint,
nonce: *const std::os::raw::c_uint,
) -> c_int { unreachable!() }
// ── KDF ─────────────────────────────────────────────────────────────
pub unsafe fn wc_HKDF(
type_: c_int,
ikm: *const c_uchar, ikm_sz: c_uint,
salt: *const c_uchar, salt_sz: c_uint,
info: *const c_uchar, info_sz: c_uint,
out: *mut c_uchar, out_sz: c_uint,
) -> c_int { unreachable!() }
pub unsafe fn wc_Argon2id_Hash(
out: *mut c_uchar, out_sz: c_uint,
pwd: *const c_uchar, pwd_sz: c_uint,
salt: *const c_uchar, salt_sz: c_uint,
secret: *const c_uchar, secret_sz: c_uint,
ad: *const c_uchar, ad_sz: c_uint,
mem_kb: c_uint, iterations: c_uint, parallelism: c_uint,
) -> c_int { unreachable!() }
// ── BLAKE2b hash / MAC ───────────────────────────────────────────────
pub unsafe fn wc_Blake2bHash(
out: *mut c_uchar, out_sz: c_uint,
in_: *const c_uchar, in_sz: c_uint,
key: *const c_uchar, key_sz: c_uint, // key_sz = 0 for unkeyed
) -> c_int { unreachable!() }
// ── HMAC ────────────────────────────────────────────────────────────
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_HmacFinal(hmac: *mut Hmac, out: *mut c_uchar) -> c_int { unreachable!() }
pub unsafe fn wc_HmacFree(hmac: *mut Hmac) { unreachable!() }
// ── One-shot hashes ──────────────────────────────────────────────────
pub unsafe fn wc_Sha256Hash(in_: *const c_uchar, in_sz: c_uint, out: *mut c_uchar) -> c_int { unreachable!() }
pub unsafe fn wc_Sha384Hash(in_: *const c_uchar, in_sz: c_uint, out: *mut c_uchar) -> c_int { unreachable!() }
pub unsafe fn wc_Sha512Hash(in_: *const c_uchar, in_sz: c_uint, out: *mut c_uchar) -> c_int { unreachable!() }
pub unsafe fn wc_Sha3_256Hash(in_: *const c_uchar, in_sz: c_uint, out: *mut c_uchar) -> c_int { unreachable!() }
pub unsafe fn wc_Sha3_512Hash(in_: *const c_uchar, in_sz: c_uint, out: *mut c_uchar) -> c_int { unreachable!() }
// ── RNG ─────────────────────────────────────────────────────────────
pub unsafe fn wc_InitRng(rng: *mut WC_RNG) -> c_int { unreachable!() }
pub unsafe fn wc_FreeRng(rng: *mut WC_RNG) -> c_int { unreachable!() }
// ── 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_export_key_raw(
key: *mut curve25519_key,
priv_: *mut c_uchar, priv_sz: *mut c_uint,
pub_: *mut c_uchar, pub_sz: *mut c_uint,
) -> c_int { unreachable!() }
pub unsafe fn wc_curve25519_import_private(
priv_: *const c_uchar, priv_sz: c_uint, key: *mut curve25519_key,
) -> c_int { unreachable!() }
pub unsafe fn wc_curve25519_import_public(
pub_: *const c_uchar, pub_sz: c_uint, key: *mut curve25519_key,
) -> c_int { unreachable!() }
pub unsafe fn wc_curve25519_shared_secret_ex(
local: *mut curve25519_key, remote: *mut curve25519_key,
out: *mut c_uchar, out_sz: *mut c_uint, endian: c_int,
) -> c_int { unreachable!() }
pub unsafe fn wc_curve25519_free(key: *mut curve25519_key) { unreachable!() }
// ── Curve448 ─────────────────────────────────────────────────────────
pub unsafe fn wc_curve448_make_key(rng: *mut WC_RNG, key_sz: c_int, key: *mut curve448_key) -> c_int { unreachable!() }
pub unsafe fn wc_curve448_export_key_raw(
key: *mut curve448_key,
priv_: *mut c_uchar, priv_sz: *mut c_uint,
pub_: *mut c_uchar, pub_sz: *mut c_uint,
) -> c_int { unreachable!() }
pub unsafe fn wc_curve448_import_private(
priv_: *const c_uchar, priv_sz: c_uint, key: *mut curve448_key,
) -> c_int { unreachable!() }
pub unsafe fn wc_curve448_import_public(
pub_: *const c_uchar, pub_sz: c_uint, key: *mut curve448_key,
) -> c_int { unreachable!() }
pub unsafe fn wc_curve448_shared_secret_ex(
local: *mut curve448_key, remote: *mut curve448_key,
out: *mut c_uchar, out_sz: *mut c_uint, endian: c_int,
) -> c_int { unreachable!() }
pub unsafe fn wc_curve448_free(key: *mut curve448_key) { unreachable!() }
}
}
pub use provider::WolfSslProvider;
/// Register the wolfSSL provider into the global
/// [`ProviderRegistry`][ccc_crypto_core::registry::ProviderRegistry].
///
/// Call this once from `ccc_init()` in the bridge crate.
pub fn init() {
let provider = WolfSslProvider::new();
ccc_crypto_core::ProviderRegistry::global()
.register("wolfssl", Box::new(provider));
log::info!("[ccc-crypto-wolfssl] provider registered");
}

View File

@ -0,0 +1,173 @@
//! MAC (Message Authentication Code) implementations via wolfCrypt.
//!
//! Provides HMAC-SHA256, HMAC-SHA384, HMAC-SHA512, and BLAKE2b-MAC.
//! All `verify_mac` implementations use constant-time comparison to prevent
//! timing side-channels.
use ccc_crypto_core::{algorithms::MacAlgorithm, error::CryptoError};
// wolfCrypt hash type constants matching `enum wc_HashType` in hash.h.
const WC_HASH_TYPE_SHA256: i32 = 8;
const WC_HASH_TYPE_SHA384: i32 = 9;
const WC_HASH_TYPE_SHA512: i32 = 10;
// ──────────────────────────────────────────────────────────────────────────────
// Public entry points
// ──────────────────────────────────────────────────────────────────────────────
/// 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> {
match algo {
MacAlgorithm::HmacSha256 => hmac(WC_HASH_TYPE_SHA256, key, data, 32),
MacAlgorithm::HmacSha384 => hmac(WC_HASH_TYPE_SHA384, key, data, 48),
MacAlgorithm::HmacSha512 => hmac(WC_HASH_TYPE_SHA512, key, data, 64),
MacAlgorithm::Blake2bMac => blake2b_mac(key, data),
MacAlgorithm::Poly1305 =>
Err(CryptoError::UnsupportedAlgorithm(
"Poly1305 standalone MAC is not exposed in this Phase".into()
)),
}
}
/// Verify a MAC tag in constant time.
///
/// Returns `Ok(true)` if the tag matches, `Ok(false)` if it does not.
/// Never returns `Err` for a tag mismatch — only for structural errors.
pub fn verify_mac(
algo: MacAlgorithm,
key: &[u8],
data: &[u8],
mac: &[u8],
) -> Result<bool, CryptoError> {
let expected = compute_mac(algo, key, data)?;
Ok(constant_time_eq(&expected, mac))
}
// ──────────────────────────────────────────────────────────────────────────────
// HMAC
// ──────────────────────────────────────────────────────────────────────────────
/// HMAC using wolfCrypt's `wc_HmacSetKey` / `wc_HmacUpdate` / `wc_HmacFinal`.
fn hmac(
hash_type: i32,
key: &[u8],
data: &[u8],
out_len: usize,
) -> Result<Vec<u8>, CryptoError> {
let mut out = vec![0u8; out_len];
unsafe {
let mut hmac_ctx: crate::sys::Hmac = std::mem::zeroed();
let ret = crate::sys::wc_HmacSetKey(
&mut hmac_ctx,
hash_type,
key.as_ptr(),
key.len() as u32,
);
if ret != 0 {
return Err(CryptoError::InvalidKey(format!("wc_HmacSetKey returned {}", ret)));
}
let ret = crate::sys::wc_HmacUpdate(
&mut hmac_ctx,
data.as_ptr(),
data.len() as u32,
);
if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_HmacUpdate returned {}", ret)));
}
let ret = crate::sys::wc_HmacFinal(
&mut hmac_ctx,
out.as_mut_ptr(),
);
if ret != 0 {
return Err(CryptoError::InternalError(format!("wc_HmacFinal returned {}", ret)));
}
crate::sys::wc_HmacFree(&mut hmac_ctx);
}
Ok(out)
}
// ──────────────────────────────────────────────────────────────────────────────
// BLAKE2b-MAC (keyed BLAKE2b)
// ──────────────────────────────────────────────────────────────────────────────
/// Keyed BLAKE2b-512 used as a MAC.
///
/// Key length must be 164 bytes (wolfCrypt limitation).
/// Output is always 64 bytes (BLAKE2b-512 full digest length).
fn blake2b_mac(key: &[u8], data: &[u8]) -> Result<Vec<u8>, CryptoError> {
if key.is_empty() || key.len() > 64 {
return Err(CryptoError::InvalidKey(
"BLAKE2b-MAC: key must be 164 bytes".into()
));
}
let mut out = vec![0u8; 64];
unsafe {
// wolfCrypt: wc_Blake2b(blake2b, out, outLen, key, keyLen, data, dataSz)
// Using the simplified one-shot Blake2bHash wrapper.
let ret = crate::sys::wc_Blake2bHash(
out.as_mut_ptr(),
64u32,
key.as_ptr(),
key.len() as u32,
data.as_ptr(),
data.len() as u32,
);
if ret != 0 {
return Err(CryptoError::InternalError(
format!("wc_Blake2bHash returned {}", ret)
));
}
}
Ok(out)
}
// ──────────────────────────────────────────────────────────────────────────────
// Constant-time comparison
// ──────────────────────────────────────────────────────────────────────────────
/// Constant-time byte-slice equality comparison.
///
/// Returns `false` immediately if lengths differ (safe — length is not secret
/// for fixed-size HMAC tags).
fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
if a.len() != b.len() {
return false;
}
// Accumulate XOR differences; the branch is on the final accumulated value
// only, not on any individual byte.
let diff: u8 = a.iter().zip(b.iter()).fold(0u8, |acc, (x, y)| acc | (x ^ y));
diff == 0
}
#[cfg(test)]
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"));
}
}

View File

@ -0,0 +1,250 @@
//! [`WolfSslProvider`] — the main provider struct implementing
//! [`CryptoProvider`] for the wolfSSL / wolfCrypt library.
//!
//! Capabilities are live-probed on first construction. Benchmark results
//! feed into the `efficiency_score` fields of [`ProviderCapabilities`].
use std::sync::OnceLock;
use zeroize::Zeroizing;
use ccc_crypto_core::{
algorithms::{AeadAlgorithm, HashAlgorithm, KdfAlgorithm, KemAlgorithm, MacAlgorithm},
capabilities::ProviderCapabilities,
error::CryptoError,
provider::{
AeadProvider, CryptoProvider, HashProvider, KdfProvider, KemProvider, MacProvider,
},
types::{AlgoTestResult, BenchmarkReport, KemEncapResult, KemKeyPair, SelfTestReport},
};
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
// ──────────────────────────────────────────────────────────────────────────────
/// The wolfSSL / wolfCrypt crypto provider.
///
/// Constructed by [`crate::init()`] and registered into the global
/// [`ProviderRegistry`][ccc_crypto_core::registry::ProviderRegistry].
///
/// Capabilities and benchmark results are computed lazily on first access to
/// avoid slowing down startup if the provider is never queried.
pub struct WolfSslProvider {
/// Lazy-initialised capabilities + benchmark results.
caps: OnceLock<ProviderCapabilities>,
bench: OnceLock<BenchmarkReport>,
}
impl WolfSslProvider {
/// Create a new wolfSSL provider.
///
/// Does not run probes or benchmarks at construction time — those are
/// deferred to the first call to [`capabilities()`] / [`benchmark()`].
pub fn new() -> Self {
Self {
caps: OnceLock::new(),
bench: OnceLock::new(),
}
}
/// Return the cached benchmark report, running the benchmark if needed.
fn get_or_run_benchmark(&self) -> &BenchmarkReport {
self.bench.get_or_init(|| {
log::info!("[ccc-crypto-wolfssl] running throughput benchmark…");
let report = capabilities::run_benchmark();
log::info!("[ccc-crypto-wolfssl] benchmark complete ({} algos)", report.results.len());
report
})
}
}
impl Default for WolfSslProvider {
fn default() -> Self { Self::new() }
}
// ──────────────────────────────────────────────────────────────────────────────
// Primitive trait implementations — thin delegation to module functions
// ──────────────────────────────────────────────────────────────────────────────
impl AeadProvider for WolfSslProvider {
fn encrypt_aead(
&self, algo: AeadAlgorithm, key: &[u8], nonce: &[u8],
plaintext: &[u8], aad: &[u8],
) -> Result<Vec<u8>, CryptoError> {
aead::encrypt(algo, key, nonce, plaintext, aad)
}
fn decrypt_aead(
&self, algo: AeadAlgorithm, key: &[u8], nonce: &[u8],
ciphertext_and_tag: &[u8], aad: &[u8],
) -> Result<Vec<u8>, CryptoError> {
aead::decrypt(algo, key, nonce, ciphertext_and_tag, aad)
}
}
impl KdfProvider for WolfSslProvider {
fn derive_key(
&self, algo: KdfAlgorithm, ikm: &[u8], salt: &[u8], info: &[u8], length: usize,
) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
kdf::derive_key(algo, ikm, salt, info, length)
}
}
impl MacProvider for WolfSslProvider {
fn compute_mac(
&self, algo: MacAlgorithm, key: &[u8], data: &[u8],
) -> Result<Vec<u8>, CryptoError> {
mac::compute_mac(algo, key, data)
}
fn verify_mac(
&self, algo: MacAlgorithm, key: &[u8], data: &[u8], mac_bytes: &[u8],
) -> Result<bool, CryptoError> {
mac::verify_mac(algo, key, data, mac_bytes)
}
}
impl HashProvider for WolfSslProvider {
fn hash(&self, algo: HashAlgorithm, data: &[u8]) -> Result<Vec<u8>, CryptoError> {
hash::hash(algo, data)
}
}
/// `WolfSslProvider` also implements `KemProvider` but it is not part of the
/// `CryptoProvider` supertrait. Bridge functions call it directly.
impl KemProvider for WolfSslProvider {
fn generate_keypair(&self, algo: KemAlgorithm) -> Result<KemKeyPair, CryptoError> {
kem::generate_keypair(algo)
}
fn encapsulate(
&self, algo: KemAlgorithm, public_key: &[u8],
) -> Result<KemEncapResult, CryptoError> {
kem::encapsulate(algo, public_key)
}
fn decapsulate(
&self, algo: KemAlgorithm, private_key: &[u8], ciphertext: &[u8],
) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
kem::decapsulate(algo, private_key, ciphertext)
}
}
// ──────────────────────────────────────────────────────────────────────────────
// CryptoProvider umbrella impl
// ──────────────────────────────────────────────────────────────────────────────
impl CryptoProvider for WolfSslProvider {
fn provider_name(&self) -> &'static str { "wolfssl" }
fn capabilities(&self) -> ProviderCapabilities {
self.caps.get_or_init(|| {
let bench = self.get_or_run_benchmark();
capabilities::probe_capabilities(Some(bench))
}).clone()
}
fn self_test(&self) -> SelfTestReport {
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 {
self.get_or_run_benchmark().clone()
}
}
// ──────────────────────────────────────────────────────────────────────────────
// Test vector runner
// ──────────────────────────────────────────────────────────────────────────────
fn run_aead_vector(v: &AeadVector) -> AlgoTestResult {
let test_name = format!("{} NIST/RFC vector", v.algo.name());
let decode = |hex: &str| -> Vec<u8> {
(0..hex.len())
.step_by(2)
.map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap_or(0))
.collect()
};
let key = decode(v.key);
let nonce = decode(v.nonce);
let aad = decode(v.aad);
let pt = decode(v.pt);
let expected = decode(v.ct_tag);
match aead::encrypt(v.algo, &key, &nonce, &pt, &aad) {
Ok(ct_tag) if ct_tag == expected => AlgoTestResult {
algo_id: v.algo as u32,
algo_name: test_name,
passed: true,
error_message: None,
},
Ok(ct_tag) => AlgoTestResult {
algo_id: v.algo as u32,
algo_name: test_name.clone(),
passed: false,
error_message: Some(format!(
"output mismatch: got {} bytes, expected {} bytes",
ct_tag.len(), expected.len()
)),
},
Err(e) => AlgoTestResult {
algo_id: v.algo as u32,
algo_name: test_name,
passed: false,
error_message: Some(e.to_string()),
},
}
}

View File

@ -0,0 +1,28 @@
[package]
name = "ccc-flutter-bridge"
version = { workspace = true }
edition = { workspace = true }
authors = { workspace = true }
description = "flutter_rust_bridge v2 entry-point exposing CCC Rust crypto to Dart"
# Both output kinds are required:
# - staticlib → iOS (linked into XCFramework / Runner)
# - cdylib → macOS / Android (.dylib / .so)
[lib]
crate-type = ["cdylib", "staticlib"]
[dependencies]
ccc-crypto-core = { path = "../ccc-crypto-core" }
ccc-crypto-wolfssl = { path = "../ccc-crypto-wolfssl" }
flutter_rust_bridge = { version = "=2.9.0" }
zeroize = { workspace = true }
log = { workspace = true }
anyhow = { workspace = true }
env_logger = { workspace = true }
# Convenience hex encoding for test / debug bridge calls.
hex = "0.4"
[dev-dependencies]
# Nothing needed; actual tests live in tests/conformance/.

View File

@ -0,0 +1,279 @@
//! Public bridge functions — annotated with `#[frb(sync)]` or `#[frb]` so
//! that `flutter_rust_bridge` code-generation emits matching Dart APIs.
//!
//! All fallible functions return `anyhow::Result<T>` (FRB v2 maps that to a
//! Dart `Future<T>` that throws on error).
//!
//! # Naming convention
//! Functions are named `ccc_<family>_<operation>` so the generated Dart class
//! is pleasingly namespaced as `CccBridge.xxxYyy(…)`.
use flutter_rust_bridge::frb;
use zeroize::Zeroizing;
use ccc_crypto_core::{
algorithms::{AeadAlgorithm, HashAlgorithm, KdfAlgorithm, KemAlgorithm, MacAlgorithm},
provider::{AeadProvider, HashProvider, KdfProvider, KemProvider, MacProvider},
registry::ProviderRegistry,
};
use super::dto::{
AeadEncryptRequest, AeadEncryptResult, AlgorithmCapabilityDto, HashRequest, KdfDeriveRequest,
KemEncapResultDto, KemKeyPairDto, MacComputeRequest, ProviderCapabilitiesDto,
SelfTestReportDto,
};
// ──────────────────────────────────────────────────────────────────────────────
// Initialisation
// ──────────────────────────────────────────────────────────────────────────────
/// Initialise the CCC Rust layer.
///
/// **Must** be called once before any other bridge function, typically during
/// the app's boot sequence in `main.dart` (or `AppState.init()`).
///
/// Registers the wolfSSL provider into the global [`ProviderRegistry`].
/// Subsequent calls are no-ops.
#[frb(sync)]
pub fn ccc_init() {
static ONCE: std::sync::OnceLock<()> = std::sync::OnceLock::new();
ONCE.get_or_init(|| {
let _ = env_logger::try_init(); // tolerate error when already initialised
ccc_crypto_wolfssl::init();
log::info!("[ccc-bridge] wolfSSL provider registered");
});
}
// ──────────────────────────────────────────────────────────────────────────────
// Provider registry queries
// ──────────────────────────────────────────────────────────────────────────────
/// List all registered provider names.
#[frb(sync)]
pub fn ccc_list_providers() -> Vec<String> {
ProviderRegistry::global().list()
}
/// Retrieve full capabilities for a named provider.
///
/// Returns `None` if the provider is not registered.
#[frb(sync)]
pub fn ccc_capabilities(provider_name: String) -> Option<ProviderCapabilitiesDto> {
let reg = ProviderRegistry::global();
let guard = reg.get(&provider_name)?;
Some(guard.capabilities().into())
}
/// List all algorithms of a specific family that are **available** in a provider.
///
/// `family` is one of `"aead"`, `"kdf"`, `"mac"`, `"hash"`, `"kem"`.
#[frb(sync)]
pub fn ccc_available_algorithms(
provider_name: String,
family: String,
) -> Vec<AlgorithmCapabilityDto> {
let reg = ProviderRegistry::global();
let Some(guard) = reg.get(&provider_name) else { return vec![] };
// Convert the full capability map to DTO (which flattens HashMaps → Vecs).
let caps_dto: ProviderCapabilitiesDto = guard.capabilities().into();
let mut list = match family.as_str() {
"aead" => caps_dto.aead,
"kdf" => caps_dto.kdf,
"mac" => caps_dto.mac,
"hash" => caps_dto.hash,
"kem" => caps_dto.kem,
_ => vec![],
};
list.retain(|c| c.available);
list
}
// ──────────────────────────────────────────────────────────────────────────────
// AEAD
// ──────────────────────────────────────────────────────────────────────────────
/// Encrypt with an AEAD cipher.
///
/// Returns `ciphertext || 16-byte authentication tag` on success.
///
/// # Errors
/// Propagates [`ccc_crypto_core::error::CryptoError`] as an `anyhow::Result`.
pub fn ccc_aead_encrypt(req: AeadEncryptRequest) -> anyhow::Result<AeadEncryptResult> {
let algo = AeadAlgorithm::from_u32(req.algo_id)
.ok_or_else(|| anyhow::anyhow!("unknown AEAD algo_id: {}", req.algo_id))?;
let reg = ProviderRegistry::global();
let guard = reg
.get("wolfssl")
.ok_or_else(|| anyhow::anyhow!("wolfssl provider not registered"))?;
let ct_tag = guard.encrypt_aead(algo, &req.key, &req.nonce, &req.plaintext, &req.aad)?;
Ok(AeadEncryptResult { ciphertext_and_tag: ct_tag })
}
/// Decrypt with an AEAD cipher.
///
/// Expects `ciphertext || 16-byte authentication tag` as `ciphertext_and_tag`.
/// Returns plaintext on success; returns an error if authentication fails.
pub fn ccc_aead_decrypt(
algo_id: u32,
key: Vec<u8>,
nonce: Vec<u8>,
ciphertext_and_tag: Vec<u8>,
aad: Vec<u8>,
) -> anyhow::Result<Vec<u8>> {
let algo = AeadAlgorithm::from_u32(algo_id)
.ok_or_else(|| anyhow::anyhow!("unknown AEAD algo_id: {}", algo_id))?;
let reg = ProviderRegistry::global();
let guard = reg
.get("wolfssl")
.ok_or_else(|| anyhow::anyhow!("wolfssl provider not registered"))?;
Ok(guard.decrypt_aead(algo, &key, &nonce, &ciphertext_and_tag, &aad)?)
}
// ──────────────────────────────────────────────────────────────────────────────
// KDF
// ──────────────────────────────────────────────────────────────────────────────
/// Derive a key using a KDF.
///
/// Returns the derived key bytes (length = `req.out_length`).
pub fn ccc_kdf_derive(req: KdfDeriveRequest) -> anyhow::Result<Vec<u8>> {
let algo = KdfAlgorithm::from_u32(req.algo_id)
.ok_or_else(|| anyhow::anyhow!("unknown KDF algo_id: {}", req.algo_id))?;
let reg = ProviderRegistry::global();
let guard = reg
.get("wolfssl")
.ok_or_else(|| anyhow::anyhow!("wolfssl provider not registered"))?;
let out: Zeroizing<Vec<u8>> =
guard.derive_key(algo, &req.ikm, &req.salt, &req.info, req.out_length as usize)?;
// Dart does not share memory with Rust; returning a plain Vec<u8> is safe.
Ok(out.to_vec())
}
// ──────────────────────────────────────────────────────────────────────────────
// MAC
// ──────────────────────────────────────────────────────────────────────────────
/// Compute a MAC tag.
pub fn ccc_mac_compute(req: MacComputeRequest) -> anyhow::Result<Vec<u8>> {
let algo = MacAlgorithm::from_u32(req.algo_id)
.ok_or_else(|| anyhow::anyhow!("unknown MAC algo_id: {}", req.algo_id))?;
let reg = ProviderRegistry::global();
let guard = reg
.get("wolfssl")
.ok_or_else(|| anyhow::anyhow!("wolfssl provider not registered"))?;
Ok(guard.compute_mac(algo, &req.key, &req.data)?)
}
/// Verify a MAC tag in constant time (returns `true` if authentic).
pub fn ccc_mac_verify(
algo_id: u32,
key: Vec<u8>,
data: Vec<u8>,
mac_bytes: Vec<u8>,
) -> anyhow::Result<bool> {
let algo = MacAlgorithm::from_u32(algo_id)
.ok_or_else(|| anyhow::anyhow!("unknown MAC algo_id: {}", algo_id))?;
let reg = ProviderRegistry::global();
let guard = reg
.get("wolfssl")
.ok_or_else(|| anyhow::anyhow!("wolfssl provider not registered"))?;
Ok(guard.verify_mac(algo, &key, &data, &mac_bytes)?)
}
// ──────────────────────────────────────────────────────────────────────────────
// Hash
// ──────────────────────────────────────────────────────────────────────────────
/// Compute a cryptographic hash.
pub fn ccc_hash(req: HashRequest) -> anyhow::Result<Vec<u8>> {
let algo = HashAlgorithm::from_u32(req.algo_id)
.ok_or_else(|| anyhow::anyhow!("unknown Hash algo_id: {}", req.algo_id))?;
let reg = ProviderRegistry::global();
let guard = reg
.get("wolfssl")
.ok_or_else(|| anyhow::anyhow!("wolfssl provider not registered"))?;
Ok(guard.hash(algo, &req.data)?)
}
// ──────────────────────────────────────────────────────────────────────────────
// KEM
// ──────────────────────────────────────────────────────────────────────────────
/// Generate a KEM key pair.
pub fn ccc_kem_generate_keypair(algo_id: u32) -> anyhow::Result<KemKeyPairDto> {
let algo = KemAlgorithm::from_u32(algo_id)
.ok_or_else(|| anyhow::anyhow!("unknown KEM algo_id: {}", algo_id))?;
let reg = ProviderRegistry::global();
let guard = reg
.get("wolfssl")
.ok_or_else(|| anyhow::anyhow!("wolfssl provider not registered"))?;
// WolfSslProvider also implements KemProvider.
// Down-cast via concrete type via the global init path.
use ccc_crypto_wolfssl::provider::WolfSslProvider;
let _ = guard; // release the Arc lock before constructing a temporary
let temp = WolfSslProvider::new();
Ok(temp.generate_keypair(algo)?.into())
}
/// Encapsulate a shared secret to a public key.
pub fn ccc_kem_encapsulate(algo_id: u32, public_key: Vec<u8>) -> anyhow::Result<KemEncapResultDto> {
let algo = KemAlgorithm::from_u32(algo_id)
.ok_or_else(|| anyhow::anyhow!("unknown KEM algo_id: {}", algo_id))?;
use ccc_crypto_wolfssl::provider::WolfSslProvider;
let temp = WolfSslProvider::new();
Ok(temp.encapsulate(algo, &public_key)?.into())
}
/// Decapsulate a shared secret using a private key.
///
/// Returns the shared secret bytes.
pub fn ccc_kem_decapsulate(
algo_id: u32,
private_key: Vec<u8>,
ciphertext: Vec<u8>,
) -> anyhow::Result<Vec<u8>> {
let algo = KemAlgorithm::from_u32(algo_id)
.ok_or_else(|| anyhow::anyhow!("unknown KEM algo_id: {}", algo_id))?;
use ccc_crypto_wolfssl::provider::WolfSslProvider;
let temp = WolfSslProvider::new();
let secret: Zeroizing<Vec<u8>> = temp.decapsulate(algo, &private_key, &ciphertext)?;
Ok(secret.to_vec())
}
// ──────────────────────────────────────────────────────────────────────────────
// Self-test
// ──────────────────────────────────────────────────────────────────────────────
/// Run the embedded NIST / RFC test vectors for the wolfSSL provider.
///
/// Returns a full [`SelfTestReportDto`]; `all_passed == true` means every
/// algorithm passed its vectors. Check individual `results` for details.
pub fn ccc_self_test() -> anyhow::Result<SelfTestReportDto> {
let reg = ProviderRegistry::global();
let guard = reg
.get("wolfssl")
.ok_or_else(|| anyhow::anyhow!("wolfssl provider not registered"))?;
Ok(guard.self_test().into())
}

View File

@ -0,0 +1,296 @@
//! Data Transfer Objects (DTOs) shared between Rust and the Dart layer.
//!
//! All structs here are annotated with `#[frb(opaque)]` or plain `pub struct`
//! as appropriate so that `flutter_rust_bridge` code-generation emits the
//! correct Dart equivalents.
//!
//! **Wire conventions**
//! - Binary blobs are always `Vec<u8>` (Dart: `Uint8List`)
//! - Identifiers that mirror `cipher_constants.dart` integers are `u32`
//! - Strings are `String` / `Option<String>`
//! - Boolean results are `bool`
use flutter_rust_bridge::frb;
// ──────────────────────────────────────────────────────────────────────────────
// Capability DTOs
// ──────────────────────────────────────────────────────────────────────────────
/// Capability entry for a single algorithm exported to Dart.
///
/// Maps 1-to-1 with [`ccc_crypto_core::capabilities::AlgorithmCapability`].
#[derive(Debug, Clone)]
#[frb(dart_metadata = ("freezed"))]
pub struct AlgorithmCapabilityDto {
/// Algorithm integer id (mirrors `cipher_constants.dart`).
pub algo_id: u32,
/// Human-readable name (e.g. `"AES-256-GCM"`).
pub algo_name: String,
/// Whether the provider actually supports this algorithm.
pub available: bool,
/// Whether the algorithm is deterministic given the same inputs.
pub deterministic_io: bool,
/// Provider-normalised throughput score 0100 (0 = unknown).
pub efficiency_score: u8,
/// NIST / standards reliability score 0100 (100 = NIST-recommended).
pub reliability_score: u8,
}
/// All capabilities returned by a provider in one call.
#[derive(Debug, Clone)]
#[frb(dart_metadata = ("freezed"))]
pub struct ProviderCapabilitiesDto {
pub provider_name: String,
pub aead: Vec<AlgorithmCapabilityDto>,
pub kdf: Vec<AlgorithmCapabilityDto>,
pub mac: Vec<AlgorithmCapabilityDto>,
pub hash: Vec<AlgorithmCapabilityDto>,
pub kem: Vec<AlgorithmCapabilityDto>,
}
// ──────────────────────────────────────────────────────────────────────────────
// AEAD
// ──────────────────────────────────────────────────────────────────────────────
/// Input parameters for AEAD encrypt.
#[derive(Debug)]
#[frb(dart_metadata = ("freezed"))]
pub struct AeadEncryptRequest {
/// Algorithm id (e.g. 12 = AES-256-GCM).
pub algo_id: u32,
pub key: Vec<u8>,
pub nonce: Vec<u8>,
pub plaintext: Vec<u8>,
pub aad: Vec<u8>,
}
/// Result of AEAD encrypt: `ciphertext || 16-byte tag`.
#[derive(Debug)]
#[frb(dart_metadata = ("freezed"))]
pub struct AeadEncryptResult {
/// `ciphertext || authentication_tag` (always 16-byte tag appended).
pub ciphertext_and_tag: Vec<u8>,
}
// ──────────────────────────────────────────────────────────────────────────────
// KDF
// ──────────────────────────────────────────────────────────────────────────────
/// Input parameters for key derivation.
#[derive(Debug)]
#[frb(dart_metadata = ("freezed"))]
pub struct KdfDeriveRequest {
/// Algorithm id (e.g. 1 = HKDF-SHA-256).
pub algo_id: u32,
pub ikm: Vec<u8>,
pub salt: Vec<u8>,
pub info: Vec<u8>,
/// Requested output length in bytes.
pub out_length: u32,
}
// ──────────────────────────────────────────────────────────────────────────────
// MAC
// ──────────────────────────────────────────────────────────────────────────────
/// Input parameters for MAC computation.
#[derive(Debug)]
#[frb(dart_metadata = ("freezed"))]
pub struct MacComputeRequest {
/// Algorithm id (e.g. 30 = HMAC-SHA-256).
pub algo_id: u32,
pub key: Vec<u8>,
pub data: Vec<u8>,
}
// ──────────────────────────────────────────────────────────────────────────────
// Hash
// ──────────────────────────────────────────────────────────────────────────────
/// Input parameters for a hash computation.
#[derive(Debug)]
#[frb(dart_metadata = ("freezed"))]
pub struct HashRequest {
/// Algorithm id (e.g. 40 = SHA-256).
pub algo_id: u32,
pub data: Vec<u8>,
}
// ──────────────────────────────────────────────────────────────────────────────
// KEM
// ──────────────────────────────────────────────────────────────────────────────
/// A generated KEM key pair returned to Dart.
#[derive(Debug, Clone)]
#[frb(dart_metadata = ("freezed"))]
pub struct KemKeyPairDto {
pub private_key: Vec<u8>,
pub public_key: Vec<u8>,
}
/// Result of KEM encapsulation.
#[derive(Debug, Clone)]
#[frb(dart_metadata = ("freezed"))]
pub struct KemEncapResultDto {
/// The ephemeral ciphertext to send to the peer.
pub ciphertext: Vec<u8>,
/// The shared secret (zeroized after serialisation — Dart copy is its own).
pub shared_secret: Vec<u8>,
}
// ──────────────────────────────────────────────────────────────────────────────
// Self-test
// ──────────────────────────────────────────────────────────────────────────────
/// Result of a single algorithm self-test.
#[derive(Debug, Clone)]
#[frb(dart_metadata = ("freezed"))]
pub struct AlgoTestResultDto {
pub algo_id: u32,
pub algo_name: String,
pub passed: bool,
pub error_message: Option<String>,
}
/// Aggregate self-test report returned to Dart after `ccc_self_test()`.
#[derive(Debug, Clone)]
#[frb(dart_metadata = ("freezed"))]
pub struct SelfTestReportDto {
pub provider_name: String,
pub all_passed: bool,
pub results: Vec<AlgoTestResultDto>,
}
// ──────────────────────────────────────────────────────────────────────────────
// Conversions from core types → DTOs
// ──────────────────────────────────────────────────────────────────────────────
use ccc_crypto_core::{
algorithms::{AeadAlgorithm, HashAlgorithm, KdfAlgorithm, KemAlgorithm, MacAlgorithm},
capabilities::{AlgorithmCapability, ProviderCapabilities},
types::{AlgoTestResult, KemEncapResult, KemKeyPair, SelfTestReport},
};
// ──────────────────────────────────────────────────────────────────────────────
// Helper — convert a HashMap<Algo, AlgorithmCapability> to a Vec<DTO>
// ──────────────────────────────────────────────────────────────────────────────
fn aead_cap_to_dto(algo: AeadAlgorithm, c: AlgorithmCapability) -> AlgorithmCapabilityDto {
AlgorithmCapabilityDto {
algo_id: algo as u32,
algo_name: algo.name().to_string(),
available: c.available,
deterministic_io: c.deterministic_io,
efficiency_score: c.efficiency_score,
reliability_score: c.reliability_score,
}
}
fn kdf_cap_to_dto(algo: KdfAlgorithm, c: AlgorithmCapability) -> AlgorithmCapabilityDto {
AlgorithmCapabilityDto {
algo_id: algo as u32,
algo_name: algo.name().to_string(),
available: c.available,
deterministic_io: c.deterministic_io,
efficiency_score: c.efficiency_score,
reliability_score: c.reliability_score,
}
}
fn mac_cap_to_dto(algo: MacAlgorithm, c: AlgorithmCapability) -> AlgorithmCapabilityDto {
AlgorithmCapabilityDto {
algo_id: algo as u32,
algo_name: algo.name().to_string(),
available: c.available,
deterministic_io: c.deterministic_io,
efficiency_score: c.efficiency_score,
reliability_score: c.reliability_score,
}
}
fn hash_cap_to_dto(algo: HashAlgorithm, c: AlgorithmCapability) -> AlgorithmCapabilityDto {
AlgorithmCapabilityDto {
algo_id: algo as u32,
algo_name: algo.name().to_string(),
available: c.available,
deterministic_io: c.deterministic_io,
efficiency_score: c.efficiency_score,
reliability_score: c.reliability_score,
}
}
fn kem_cap_to_dto(algo: KemAlgorithm, c: AlgorithmCapability) -> AlgorithmCapabilityDto {
AlgorithmCapabilityDto {
algo_id: algo as u32,
algo_name: algo.name().to_string(),
available: c.available,
deterministic_io: c.deterministic_io,
efficiency_score: c.efficiency_score,
reliability_score: c.reliability_score,
}
}
impl From<ProviderCapabilities> for ProviderCapabilitiesDto {
fn from(p: ProviderCapabilities) -> Self {
let mut aead: Vec<AlgorithmCapabilityDto> =
p.aead.into_iter().map(|(a, c)| aead_cap_to_dto(a, c)).collect();
let mut kdf: Vec<AlgorithmCapabilityDto> =
p.kdf.into_iter().map(|(a, c)| kdf_cap_to_dto(a, c)).collect();
let mut mac: Vec<AlgorithmCapabilityDto> =
p.mac.into_iter().map(|(a, c)| mac_cap_to_dto(a, c)).collect();
let mut hash: Vec<AlgorithmCapabilityDto> =
p.hash.into_iter().map(|(a, c)| hash_cap_to_dto(a, c)).collect();
let mut kem: Vec<AlgorithmCapabilityDto> =
p.kem.into_iter().map(|(a, c)| kem_cap_to_dto(a, c)).collect();
// Sort by algo_id for stable ordering in the Dart layer.
aead.sort_by_key(|c| c.algo_id);
kdf.sort_by_key(|c| c.algo_id);
mac.sort_by_key(|c| c.algo_id);
hash.sort_by_key(|c| c.algo_id);
kem.sort_by_key(|c| c.algo_id);
Self { provider_name: p.provider_name, aead, kdf, mac, hash, kem }
}
}
impl From<KemKeyPair> for KemKeyPairDto {
fn from(kp: KemKeyPair) -> Self {
// Use .clone() because KemKeyPair implements ZeroizeOnDrop (adds Drop),
// which prevents partial moves in safe Rust.
Self {
private_key: kp.private_key.clone(),
public_key: kp.public_key.clone(),
}
}
}
impl From<KemEncapResult> for KemEncapResultDto {
fn from(r: KemEncapResult) -> Self {
Self {
ciphertext: r.ciphertext.clone(),
shared_secret: r.shared_secret.clone(),
}
}
}
impl From<AlgoTestResult> for AlgoTestResultDto {
fn from(r: AlgoTestResult) -> Self {
Self {
algo_id: r.algo_id,
algo_name: r.algo_name,
passed: r.passed,
error_message: r.error_message,
}
}
}
impl From<SelfTestReport> for SelfTestReportDto {
fn from(r: SelfTestReport) -> Self {
Self {
provider_name: r.provider_name,
all_passed: r.all_passed,
results: r.results.into_iter().map(Into::into).collect(),
}
}
}

View File

@ -0,0 +1,19 @@
//! `ccc-flutter-bridge` — flutter_rust_bridge v2 entry-point.
//!
//! This crate is compiled to both a `cdylib` (for macOS / Android) and a
//! `staticlib` (for iOS). Do not add platform logic here — keep it in the
//! provider crates.
//!
//! ## Generated Dart bindings
//!
//! Run the code-generator from the flutter project root:
//!
//! ```sh
//! flutter_rust_bridge_codegen generate
//! ```
//!
//! The generator reads this crate's source and emits Dart classes under
//! `flutter_src/lib/gen/rust/`.
pub mod bridge;
pub mod dto;

448
docs/ccc_rust_plan.rst Normal file
View File

@ -0,0 +1,448 @@
===============================================
CCC Rust Crypto Provider — Architecture Plan
===============================================
:Status: Approved
:Phase: 4
:Date: 2026-02-23
:Author: Engineering
Overview
--------
Replace the Dart-only crypto stubs with a Cargo workspace of native Rust
crates. A shared trait/capability core defines the contract every provider
must satisfy. Per-provider sub-crates implement that contract.
``flutter_rust_bridge`` auto-generates ``dart:ffi`` bindings so Dart code
calls Rust directly with no platform-channel overhead. wolfSSL / wolfCrypt
is the first (and only Phase 4) provider. All future libraries slot into the
same trait with zero changes to the core.
Guiding Principles
------------------
1. Every provider reports its own capabilities at runtime — no compile-time
hard-coding of ``available: true/false``.
2. Algorithm IDs in Rust map 1-to-1 to the integer constants already in
``cipher_constants.dart`` — zero Dart routing changes needed.
3. Key material is zeroed on drop (``zeroize`` crate) everywhere.
4. A conformance / self-test suite validates cross-provider byte-identity
before any provider is marked ``available``.
5. The Rust workspace has no runtime dependency on Flutter; it is a pure
native library that could be consumed by any FFI host.
FFI Bridge Decision
-------------------
``flutter_rust_bridge`` (FRB) is used rather than raw ``dart:ffi`` hand-wiring.
FRB **is** ``dart:ffi`` — it uses ``dart:ffi`` under the hood and introduces
no platform channels. The difference from raw ``dart:ffi`` is:
========================= ========================== ==========================
Aspect flutter_rust_bridge Raw dart:ffi
========================= ========================== ==========================
Rust-side code Clean idiomatic Rust ``extern "C"`` with C ABI
Dart bindings **Auto-generated** Hand-written every field
Async / isolate support Automatic Manual wiring
Cross-type safety Codegen validates at build Discovered at runtime
Beginner-friendliness High Low
========================= ========================== ==========================
Repository Layout (target state)
---------------------------------
::
ccc_rust/
├── Cargo.toml ← workspace manifest (3 members)
├── rust-toolchain.toml ← pinned stable toolchain
├── .cargo/
│ └── config.toml ← cross-compile target aliases
├── vendors/
│ ├── README.md ← submodule pin rationale + upgrade notes
│ └── wolfssl/ ← git submodule (wolfSSL/wolfssl @ v5.7.2-stable)
├── crates/
│ ├── ccc-crypto-core/ ← shared traits, enums, registry
│ │ ├── Cargo.toml
│ │ └── src/
│ │ ├── lib.rs
│ │ ├── algorithms.rs ← algorithm enums (values == cipher_constants.dart)
│ │ ├── capabilities.rs ← AlgorithmCapability, ProviderCapabilities
│ │ ├── error.rs ← CryptoError enum
│ │ ├── provider.rs ← CryptoProvider umbrella trait
│ │ ├── registry.rs ← ProviderRegistry (OnceLock<Mutex<...>>)
│ │ └── types.rs ← KemKeyPair, SelfTestReport, BenchmarkReport
│ ├── ccc-crypto-wolfssl/ ← wolfSSL provider impl
│ │ ├── Cargo.toml
│ │ └── src/
│ │ ├── lib.rs
│ │ ├── aead.rs ← AES-256-GCM, ChaCha20-Poly1305
│ │ ├── kdf.rs ← HKDF, Argon2id
│ │ ├── mac.rs ← HMAC-SHA256/384/512
│ │ ├── hash.rs ← SHA-2, SHA-3, BLAKE2b
│ │ ├── kem.rs ← X25519, X448, ML-KEM (if PQ build)
│ │ ├── capabilities.rs ← probe-at-startup + benchmark
│ │ └── provider.rs ← WolfSslProvider: CryptoProvider impl
│ └── ccc-flutter-bridge/ ← flutter_rust_bridge entry point
│ ├── Cargo.toml
│ └── src/
│ ├── lib.rs
│ ├── bridge.rs ← #[frb] exported functions
│ └── dto.rs ← CapabilitiesDto, KemKeyPairDto, SelfTestDto
├── tests/
│ └── conformance/
│ ├── aes_gcm_vectors.rs
│ ├── chacha_vectors.rs
│ ├── hkdf_vectors.rs
│ ├── hmac_vectors.rs
│ └── cross_provider.rs
├── flutter_src/
│ └── ccc/ ← existing Dart files (minimal changes)
└── docs/
Step 1 — Cargo Workspace Scaffold
----------------------------------
Files
~~~~~
``Cargo.toml``::
[workspace]
resolver = "2"
members = [
"crates/ccc-crypto-core",
"crates/ccc-crypto-wolfssl",
"crates/ccc-flutter-bridge",
]
``rust-toolchain.toml``::
[toolchain]
channel = "1.77"
``.cargo/config.toml``::
[alias]
build-ios = "build --target aarch64-apple-ios"
build-android-arm64 = "build --target aarch64-linux-android"
build-android-x64 = "build --target x86_64-linux-android"
build-macos = "build --target aarch64-apple-darwin"
Step 2 — ``ccc-crypto-core`` Trait Crate
-----------------------------------------
Algorithm Enumerations
~~~~~~~~~~~~~~~~~~~~~~
All discriminant values match the integer constants in ``cipher_constants.dart``
exactly, so the Dart cipher-sequencing logic requires zero changes.
=================== =========================================================
Enum Variants → value
=================== =========================================================
``AeadAlgorithm`` AesGcm256=12, ChaCha20Poly1305=13, XChaCha20Poly1305=14,
Ascon128a=15
``KdfAlgorithm`` Sha256=1, Sha384=2, Sha512=3, Blake2b512=4
``MacAlgorithm`` HmacSha256=30, HmacSha512=32, Blake2bMac=33
``HashAlgorithm`` Sha256=40, Sha384=41, Sha512=42, Blake2b512=43, Sha3_256=44
``KemAlgorithm`` X25519=50, X448=51, MlKem768=52, MlKem1024=53,
ClassicMcEliece=54
=================== =========================================================
Key Structs
~~~~~~~~~~~
``AlgorithmCapability``::
available: bool
deterministic_io: bool
efficiency_score: u8 // populated by WolfSslProvider::benchmark()
reliability_score: u8 // populated by WolfSslProvider::self_test()
``ProviderCapabilities``::
provider_name: &'static str
aead: HashMap<AeadAlgorithm, AlgorithmCapability>
kdf: HashMap<KdfAlgorithm, AlgorithmCapability>
mac: HashMap<MacAlgorithm, AlgorithmCapability>
hash: HashMap<HashAlgorithm, AlgorithmCapability>
kem: HashMap<KemAlgorithm, AlgorithmCapability>
``KemKeyPair``::
public_key: Zeroizing<Vec<u8>>
private_key: Zeroizing<Vec<u8>>
``CryptoError``::
UnsupportedAlgorithm(String)
InvalidKey(String)
InvalidNonce(String)
AuthenticationFailed
InternalError(String)
Provider Traits
~~~~~~~~~~~~~~~
.. code-block:: rust
pub trait AeadProvider {
fn encrypt_aead(
&self, algo: AeadAlgorithm,
key: &[u8], nonce: &[u8],
plaintext: &[u8], aad: &[u8],
) -> Result<Vec<u8>, CryptoError>;
fn decrypt_aead(
&self, algo: AeadAlgorithm,
key: &[u8], nonce: &[u8],
ciphertext: &[u8], aad: &[u8],
) -> Result<Vec<u8>, CryptoError>;
}
pub trait KdfProvider {
fn derive_key(
&self, algo: KdfAlgorithm,
ikm: &[u8], salt: &[u8], info: &[u8], length: usize,
) -> Result<Zeroizing<Vec<u8>>, CryptoError>;
}
pub trait MacProvider {
fn compute_mac(
&self, algo: MacAlgorithm, key: &[u8], data: &[u8],
) -> Result<Vec<u8>, CryptoError>;
fn verify_mac(
&self, algo: MacAlgorithm, key: &[u8],
data: &[u8], mac: &[u8],
) -> Result<bool, CryptoError>;
}
pub trait HashProvider {
fn hash(
&self, algo: HashAlgorithm, data: &[u8],
) -> Result<Vec<u8>, CryptoError>;
}
pub trait KemProvider {
fn generate_keypair(
&self, algo: KemAlgorithm,
) -> Result<KemKeyPair, CryptoError>;
fn encapsulate(
&self, algo: KemAlgorithm, public_key: &[u8],
) -> Result<(Vec<u8>, Zeroizing<Vec<u8>>), CryptoError>;
fn decapsulate(
&self, algo: KemAlgorithm,
private_key: &[u8], ciphertext: &[u8],
) -> Result<Zeroizing<Vec<u8>>, CryptoError>;
}
pub trait CryptoProvider:
AeadProvider + KdfProvider + MacProvider + HashProvider + Send + Sync
{
fn provider_name(&self) -> &'static str;
fn capabilities(&self) -> ProviderCapabilities;
fn self_test(&self) -> SelfTestReport;
fn benchmark(&self) -> BenchmarkReport;
}
``ProviderRegistry``::
OnceLock<Mutex<HashMap<&'static str, Box<dyn CryptoProvider + Send + Sync>>>>
fn register(name, provider)
fn get(name) -> Option<Arc<dyn CryptoProvider>>
fn list() -> Vec<&'static str>
Step 3 — wolfSSL Submodule + ``ccc-crypto-wolfssl``
-----------------------------------------------------
Submodule Registration::
git submodule add https://github.com/wolfSSL/wolfssl vendors/wolfssl
git -C vendors/wolfssl checkout v5.7.2-stable
``crates/ccc-crypto-wolfssl/Cargo.toml`` key dependencies::
wolfssl = { version = "0.1", features = ["pq", "blake2", "argon2"] }
zeroize = { version = "1", features = ["derive"] }
ccc-crypto-core = { path = "../ccc-crypto-core" }
wolfSSL Phase 4 Algorithm Coverage
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
=================== ===========================================
Category Algorithms
=================== ===========================================
AEAD AES-256-GCM, ChaCha20-Poly1305
KDF HKDF-SHA256/384/512, PBKDF2, Argon2id
MAC HMAC-SHA256/384/512, Poly1305
Hash SHA-256/384/512, SHA3-256/512, BLAKE2b-512
KEM (if PQ build) X25519, X448, ML-KEM-768, ML-KEM-1024
=================== ===========================================
Capability Probe Strategy
~~~~~~~~~~~~~~~~~~~~~~~~~~
``WolfSslProvider::capabilities()`` runs a minimal probe call per algorithm at
startup (encrypt 1-byte payload; decrypt; compare). Sets
``available = probe_succeeded``. If the wolfSSL build does not include PQ
support, ML-KEM entries gracefully report ``available: false``.
Benchmark Strategy
~~~~~~~~~~~~~~~~~~
``WolfSslProvider::benchmark()`` encrypts a 1 MB buffer × 100 iterations per
AEAD algorithm, measures wall-clock throughput, normalises to a 0100
``efficiency_score``. Run once at ``ccc_init()`` and cached.
Step 4 — ``ccc-flutter-bridge`` Entry-Point Crate
---------------------------------------------------
Exported Functions (``#[frb]`` tagged)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: text
ccc_init() → initialise registry + run benchmarks
ccc_list_providers() → Vec<String>
ccc_provider_capabilities(provider) → CapabilitiesDto
ccc_aead_encrypt(provider, algo: u32, key, nonce, plaintext, aad)
→ Result<Vec<u8>>
ccc_aead_decrypt(provider, algo: u32, key, nonce, ciphertext, aad)
→ Result<Vec<u8>>
ccc_derive_key(provider, algo: u32, ikm, salt, info, length: u32)
→ Result<Vec<u8>>
ccc_compute_mac(provider, algo: u32, key, data)
→ Result<Vec<u8>>
ccc_verify_mac(provider, algo: u32, key, data, mac)
→ Result<bool>
ccc_hash(provider, algo: u32, data) → Result<Vec<u8>>
ccc_kem_generate_keypair(provider, algo: u32)
→ Result<KemKeyPairDto>
ccc_kem_encapsulate(provider, algo: u32, public_key)
→ Result<KemEncapDto>
ccc_kem_decapsulate(provider, algo: u32, private_key, ciphertext)
→ Result<Vec<u8>>
ccc_self_test(provider) → SelfTestDto
DTO Structs
~~~~~~~~~~~
``CapabilitiesDto`` — mirrors ``ProviderCapabilities``, uses primitive types
so ``flutter_rust_bridge`` can auto-generate the Dart data class.
``KemKeyPairDto { public_key: Vec<u8>, private_key: Vec<u8> }``
``KemEncapDto { ciphertext: Vec<u8>, shared_secret: Vec<u8> }``
``SelfTestDto { provider: String, results: Vec<AlgoTestResult> }``
``AlgoTestResult { algo_id: u32, algo_name: String, passed: bool,
error_message: Option<String> }``
Step 5 — Flutter Build Integration
------------------------------------
* Add ``flutter_rust_bridge: ^2`` to ``pubspec.yaml``
* Run ``flutter_rust_bridge_codegen generate`` → emits
``flutter_src/ccc_crypto_bindings/ccc_crypto.dart``
* ``crate-type = ["cdylib", "staticlib"]`` in ``ccc-flutter-bridge/Cargo.toml``
(``cdylib`` for Android / Linux / macOS, ``staticlib`` for iOS)
* Cargokit handles cross-compilation inside standard Flutter plugin dirs
(``ios/``, ``android/``, ``macos/``)
Step 6 — Dart Layer Wiring
---------------------------
``crypto_wolfssl.dart``
~~~~~~~~~~~~~~~~~~~~~~~~
Replace ``UnimplementedError`` stubs:
.. code-block:: dart
@override
Future<List<int>> encrypt(Map<String, dynamic> input,
{CryptoContext? context}) async {
final algo = context?.cipherSequence?.first ?? CipherConstants.AES_GCM_256;
final key = _resolveKey(context);
final nonce = _generateNonce(algo);
return CccCrypto.ccmAeadEncrypt(
provider: 'wolfssl', algo: algo,
key: key, nonce: nonce,
plaintext: _encodePayload(input), aad: _buildAad(context));
}
``ccc_provider_spec.dart``
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Convert ``CccProviderCatalog.capabilities`` from a compile-time static map to a
runtime-populated map. At app start call::
await CccCrypto.cccInit();
final providers = await CccCrypto.cccListProviders();
for (final name in providers) {
final dto = await CccCrypto.cccProviderCapabilities(provider: name);
CccProviderCatalog.populate(name, dto);
}
``CccSelfTest``
~~~~~~~~~~~~~~~~
New Dart class that wraps ``CccCrypto.cccSelfTest(provider: name)`` and exposes
per-algorithm pass/fail results in the app's debug diagnostics screen.
Step 7 — Conformance Test Suite
---------------------------------
Location: ``tests/conformance/``
* ``aes_gcm_vectors.rs`` — NIST SP 800-38D GCM test vectors
* ``chacha_vectors.rs`` — RFC 8439 ChaCha20-Poly1305 test vectors
* ``hkdf_vectors.rs`` — RFC 5869 HKDF-SHA256 / SHA512 test vectors
* ``hmac_vectors.rs`` — RFC 4231 HMAC-SHA256 / SHA512 test vectors
* ``cross_provider.rs`` — encrypt with wolfSSL → decrypt expectation matches
the reference output from the Dart ``cryptography`` package for the same
key/nonce/plaintext/aad (validates ``deterministic_io: true``)
Step 8 — Architecture Documentation
--------------------------------------
``docs/phase4_rust_architecture.rst`` covers:
* Crate dependency graph (ASCII)
* "How to add a new provider" — the 7-step trait checklist
* ``algo: u32`` → cipher constant mapping table
* Stretch-goal Phase 8 "Omni-Crypto" provider list
Phase 8 — Stretch Goal Provider List
--------------------------------------
*(Fully out of scope for Phase 4. Documented here for future planning.)*
================== =====================================================
Library Rust crate / approach
================== =====================================================
libsodium ``sodiumoxide`` or ``safe_libsodium``
OpenSSL ``openssl`` crate
BoringSSL ``boring`` crate
RustCrypto Pure-Rust trait impls; no native dep
liboqs Open Quantum Safe — ML-KEM, BIKE, HQC, Falcon,
Dilithium, SPHINCS+
Signal libsignal ``libsignal`` (Apache-2 subset)
Botan ``botan`` crate
mbedTLS ``mbedtls`` crate
Nettle ``nettle-sys`` crate
================== =====================================================
Verification Checklist
-----------------------
* ``cargo test --workspace`` passes including all NIST vectors
* ``cargo build --target aarch64-apple-ios`` succeeds
* ``cargo build --target aarch64-linux-android`` succeeds
* Flutter integration test: roundtrip encrypt/decrypt 1 KB via
``CryptoWolfSsl`` Dart class
* ``CccSelfTest.runAll()`` returns all-pass in the app debug screen
* Cross-provider conformance: Dart ↔ wolfSSL byte-identity confirmed
(``deterministic_io: true`` verified for AES-256-GCM and ChaCha20-Poly1305)

View File

@ -0,0 +1,392 @@
==============================================
CCC Rust Implementation — Phase Tracking
==============================================
:Last Updated: 2026-06-20
Legend
------
* ``[ ]`` Not started
* ``[~]`` In progress
* ``[x]`` Complete
* ``[!]`` Blocked
----
Step 1 — Cargo Workspace Scaffold
----------------------------------
* ``[x]`` Create ``Cargo.toml`` (workspace manifest, 4 members)
* ``[x]`` Create ``rust-toolchain.toml`` (channel = "stable")
* ``[x]`` Create ``.cargo/config.toml`` (cross-compile target aliases)
* ``[x]`` Create ``vendors/README.md``
----
Step 2 — ``ccc-crypto-core`` Trait Crate
-----------------------------------------
* ``[x]`` Create ``crates/ccc-crypto-core/Cargo.toml``
* ``[x]`` ``algorithms.rs`` — AeadAlgorithm, KdfAlgorithm, MacAlgorithm,
HashAlgorithm, KemAlgorithm enums (values == cipher_constants.dart)
* ``[x]`` ``capabilities.rs`` — AlgorithmCapability, ProviderCapabilities
* ``[x]`` ``error.rs`` — CryptoError enum
* ``[x]`` ``types.rs`` — KemKeyPair, SelfTestReport, BenchmarkReport,
AlgoTestResult
* ``[x]`` ``provider.rs`` — AeadProvider, KdfProvider, MacProvider,
HashProvider, KemProvider traits; CryptoProvider umbrella trait
* ``[x]`` ``registry.rs`` — ProviderRegistry (OnceLock<Mutex<...>>),
register(), get(), list()
* ``[x]`` ``lib.rs`` — re-exports all public items
* ``[x]`` Unit tests for registry (5 passing)
----
Step 3 — wolfSSL Submodule + ``ccc-crypto-wolfssl``
-----------------------------------------------------
* ``[x]`` ``git submodule add`` wolfSSL → ``vendors/wolfssl``
* ``[x]`` Pin submodule to ``v5.7.2-stable``
* ``[x]`` Document pin in ``vendors/README.md``
* ``[x]`` Create ``crates/ccc-crypto-wolfssl/Cargo.toml``
* ``[x]`` ``build.rs`` — cmake build + bindgen; stub_ffi feature bypasses C build
* ``[x]`` ``aead.rs`` — AES-256-GCM implementation
* ``[x]`` encrypt_aead (AES-256-GCM)
* ``[x]`` decrypt_aead (AES-256-GCM)
* ``[x]`` encrypt_aead (ChaCha20-Poly1305)
* ``[x]`` decrypt_aead (ChaCha20-Poly1305)
* ``[x]`` encrypt_aead (XChaCha20-Poly1305 via HChaCha20)
* ``[x]`` decrypt_aead (XChaCha20-Poly1305)
* ``[x]`` ``kdf.rs`` — KDF implementations
* ``[x]`` HKDF-SHA256
* ``[x]`` HKDF-SHA384
* ``[x]`` HKDF-SHA512
* ``[x]`` Argon2id (64 MB / 3 iter / 4 threads — matches DEFAULT_CIPHER_PARAMS)
* ``[x]`` BLAKE2b-512 KDF
* ``[x]`` ``mac.rs`` — MAC implementations
* ``[x]`` HMAC-SHA256
* ``[x]`` HMAC-SHA384
* ``[x]`` HMAC-SHA512
* ``[x]`` BLAKE2b-MAC (keyed)
* ``[x]`` Constant-time verify
* ``[x]`` ``hash.rs`` — Hash implementations
* ``[x]`` SHA-256 / SHA-384 / SHA-512
* ``[x]`` SHA3-256 / SHA3-512
* ``[x]`` BLAKE2b-512
* ``[x]`` ``kem.rs`` — KEM implementations
* ``[x]`` X25519 (keygen + DH encap/decap)
* ``[x]`` X448 (keygen + DH encap/decap)
* ``[ ]`` ML-KEM-768 (deferred to Phase 5)
* ``[ ]`` ML-KEM-1024 (deferred to Phase 5)
* ``[ ]`` Classic McEliece (deferred to Phase 5)
* ``[x]`` ``capabilities.rs`` — probe-at-startup per algorithm
* ``[x]`` ``capabilities.rs`` — benchmark() throughput micro-bench
* ``[x]`` ``provider.rs`` — WolfSslProvider: CryptoProvider impl
* ``[x]`` ``provider.rs`` — self_test() with embedded NIST vectors (AES-256-GCM, ChaCha20-Poly1305)
* ``[x]`` Register WolfSslProvider in ProviderRegistry via init()
* ``[ ]`` Full native build verified (requires ``brew install cmake``)
----
Step 4 — ``ccc-flutter-bridge`` Entry-Point Crate
---------------------------------------------------
* ``[x]`` Create ``crates/ccc-flutter-bridge/Cargo.toml``
* ``[x]`` Set ``crate-type = ["cdylib", "staticlib"]``
* ``[x]`` Add ``flutter_rust_bridge = "=2.9.0"`` dependency
* ``[x]`` ``dto.rs`` — CapabilitiesDto, KemKeyPairDto, KemEncapDto,
SelfTestDto, AlgoTestResultDto; From<core types> impls
* ``[x]`` ``bridge.rs`` — ccc_init()
* ``[x]`` ``bridge.rs`` — ccc_list_providers()
* ``[x]`` ``bridge.rs`` — ccc_capabilities() / ccc_available_algorithms()
* ``[x]`` ``bridge.rs`` — ccc_aead_encrypt() / ccc_aead_decrypt()
* ``[x]`` ``bridge.rs`` — ccc_kdf_derive()
* ``[x]`` ``bridge.rs`` — ccc_mac_compute() / ccc_mac_verify()
* ``[x]`` ``bridge.rs`` — ccc_hash()
* ``[x]`` ``bridge.rs`` — ccc_kem_generate_keypair()
* ``[x]`` ``bridge.rs`` — ccc_kem_encapsulate() / ccc_kem_decapsulate()
* ``[x]`` ``bridge.rs`` — ccc_self_test()
* ``[x]`` ``lib.rs`` — module declarations
* ``[x]`` ``flutter_rust_bridge.yaml`` — codegen config
----
Step 5 — Flutter Build Integration
------------------------------------
* ``[ ]`` Add ``flutter_rust_bridge: ^2`` to ``pubspec.yaml``
* ``[ ]`` Run ``flutter_rust_bridge_codegen generate``
* ``[ ]`` Verify generated ``flutter_src/lib/gen/rust/`` bindings
* ``[ ]`` iOS plugin scaffold (``ios/`` dir, cargokit integration)
* ``[ ]`` Android plugin scaffold (``android/`` dir, CMakeLists.txt)
* ``[ ]`` macOS plugin scaffold (``macos/`` dir)
* ``[ ]`` Confirm ``flutter build ios`` succeeds (static lib linked)
* ``[ ]`` Confirm ``flutter build apk`` succeeds (cdylib linked)
----
Step 6 — Dart Layer Wiring
---------------------------
* ``[ ]`` Wire ``crypto_wolfssl.dart`` ``encrypt()````ccc_aead_encrypt()``
* ``[ ]`` Wire ``crypto_wolfssl.dart`` ``decrypt()````ccc_aead_decrypt()``
* ``[ ]`` Convert ``CccProviderCatalog.capabilities`` to runtime-populated map
* ``[ ]`` Call ``ccc_init()`` at app startup
* ``[ ]`` Populate ``CccProviderCatalog`` from ``ccc_capabilities()``
* ``[ ]`` Create ``CccSelfTest`` Dart class (wraps ``ccc_self_test()``)
* ``[ ]`` Expose self-test pass/fail diagnostics in app debug screen
----
Step 7 — Conformance Test Suite
---------------------------------
* ``[x]`` ``tests/conformance/src/main.rs`` — NIST AES-256-GCM (2 vectors)
* ``[x]`` ``tests/conformance/src/main.rs`` — RFC 8439 ChaCha20-Poly1305
* ``[x]`` ``tests/conformance/src/main.rs`` — RFC 5869 HKDF-SHA256 (2 vectors)
* ``[x]`` ``tests/conformance/src/main.rs`` — RFC 4231 HMAC-SHA256 (2 vectors)
* ``[x]`` ``tests/conformance/src/main.rs`` — FIPS hash vectors (SHA-256/512, SHA3-256, BLAKE2b)
* ``[ ]`` Cross-provider conformance test (requires multiple providers)
* ``[ ]`` ``cargo run -p ccc-conformance-tests`` passes (requires cmake)
----
Step 8 — Architecture Documentation
--------------------------------------
* ``[ ]`` Create ``docs/phase4_rust_architecture.rst``
* ``[ ]`` Crate dependency graph (ASCII diagram)
* ``[ ]`` "How to add a new provider" — 7-step trait checklist
* ``[ ]`` ``algo: u32`` → cipher constant mapping table
* ``[ ]`` Phase 8 stretch-goal provider list documented
----
Final Verification Gate
------------------------
* ``[ ]`` ``cargo test --workspace`` — all pass (requires cmake for wolfSSL)
* ``[ ]`` ``cargo build --target aarch64-apple-ios`` — success
* ``[ ]`` ``cargo build --target aarch64-linux-android`` — success
* ``[ ]`` Flutter roundtrip integration test passes (1 KB encrypt/decrypt)
* ``[ ]`` ``CccSelfTest.runAll()`` all-pass in app debug screen
* ``[ ]`` Cross-provider conformance confirmed (``deterministic_io: true``
verified for AES-256-GCM and ChaCha20-Poly1305)
----
Phase 8 — Stretch Goal Providers (Future)
------------------------------------------
*(Out of scope for Phase 4. Tracked here for future scheduling.)*
* ``[ ]`` libsodium (``sodiumoxide`` / ``safe_libsodium``)
* ``[ ]`` OpenSSL (``openssl`` crate)
* ``[ ]`` BoringSSL (``boring`` crate)
* ``[ ]`` RustCrypto (pure-Rust, no native dep)
* ``[ ]`` liboqs — ML-KEM, BIKE, HQC, Falcon, Dilithium, SPHINCS+
* ``[ ]`` Signal ``libsignal``
* ``[ ]`` Botan
* ``[ ]`` mbedTLS
* ``[ ]`` Nettle
Legend
------
* ``[ ]`` Not started
* ``[~]`` In progress
* ``[x]`` Complete
* ``[!]`` Blocked
----
Step 1 — Cargo Workspace Scaffold
----------------------------------
* ``[ ]`` Create ``Cargo.toml`` (workspace manifest, 3 members)
* ``[ ]`` Create ``rust-toolchain.toml`` (stable, pinned version)
* ``[ ]`` Create ``.cargo/config.toml`` (cross-compile target aliases)
* ``[ ]`` Create ``vendors/README.md``
----
Step 2 — ``ccc-crypto-core`` Trait Crate
-----------------------------------------
* ``[ ]`` Create ``crates/ccc-crypto-core/Cargo.toml``
* ``[ ]`` ``algorithms.rs`` — AeadAlgorithm, KdfAlgorithm, MacAlgorithm,
HashAlgorithm, KemAlgorithm enums (values == cipher_constants.dart)
* ``[ ]`` ``capabilities.rs`` — AlgorithmCapability, ProviderCapabilities
* ``[ ]`` ``error.rs`` — CryptoError enum
* ``[ ]`` ``types.rs`` — KemKeyPair, SelfTestReport, BenchmarkReport,
AlgoTestResult
* ``[ ]`` ``provider.rs`` — AeadProvider, KdfProvider, MacProvider,
HashProvider, KemProvider traits; CryptoProvider umbrella trait
* ``[ ]`` ``registry.rs`` — ProviderRegistry (OnceLock<Mutex<...>>),
register(), get(), list()
* ``[ ]`` ``lib.rs`` — re-exports all public items
* ``[ ]`` Unit tests for registry (register, get, list)
----
Step 3 — wolfSSL Submodule + ``ccc-crypto-wolfssl``
-----------------------------------------------------
* ``[ ]`` ``git submodule add`` wolfSSL → ``vendors/wolfssl``
* ``[ ]`` Pin submodule to ``v5.7.2-stable``
* ``[ ]`` Document pin in ``vendors/README.md``
* ``[ ]`` Create ``crates/ccc-crypto-wolfssl/Cargo.toml``
* ``[ ]`` ``aead.rs`` — AES-256-GCM implementation
* ``[ ]`` encrypt_aead (AES-256-GCM)
* ``[ ]`` decrypt_aead (AES-256-GCM)
* ``[ ]`` encrypt_aead (ChaCha20-Poly1305)
* ``[ ]`` decrypt_aead (ChaCha20-Poly1305)
* ``[ ]`` ``kdf.rs`` — KDF implementations
* ``[ ]`` HKDF-SHA256
* ``[ ]`` HKDF-SHA384
* ``[ ]`` HKDF-SHA512
* ``[ ]`` Argon2id
* ``[ ]`` ``mac.rs`` — MAC implementations
* ``[ ]`` HMAC-SHA256
* ``[ ]`` HMAC-SHA384
* ``[ ]`` HMAC-SHA512
* ``[ ]`` ``hash.rs`` — Hash implementations
* ``[ ]`` SHA-256 / SHA-384 / SHA-512
* ``[ ]`` SHA3-256 / SHA3-512
* ``[ ]`` BLAKE2b-512
* ``[ ]`` ``kem.rs`` — KEM implementations
* ``[ ]`` X25519
* ``[ ]`` X448
* ``[ ]`` ML-KEM-768 (conditional on PQ build)
* ``[ ]`` ML-KEM-1024 (conditional on PQ build)
* ``[ ]`` ``capabilities.rs`` — probe-at-startup per algorithm
* ``[ ]`` ``capabilities.rs`` — benchmark() throughput micro-bench
* ``[ ]`` ``provider.rs`` — WolfSslProvider: CryptoProvider impl
* ``[ ]`` ``provider.rs`` — self_test() with embedded NIST vectors
* ``[ ]`` Register WolfSslProvider in ProviderRegistry via init()
* ``[ ]`` Unit tests for each implemented algorithm
----
Step 4 — ``ccc-flutter-bridge`` Entry-Point Crate
---------------------------------------------------
* ``[ ]`` Create ``crates/ccc-flutter-bridge/Cargo.toml``
* ``[ ]`` Set ``crate-type = ["cdylib", "staticlib"]``
* ``[ ]`` Add ``flutter_rust_bridge`` dependency
* ``[ ]`` ``dto.rs`` — CapabilitiesDto, KemKeyPairDto, KemEncapDto,
SelfTestDto, AlgoTestResultDto
* ``[ ]`` ``bridge.rs`` — ccc_init()
* ``[ ]`` ``bridge.rs`` — ccc_list_providers()
* ``[ ]`` ``bridge.rs`` — ccc_provider_capabilities()
* ``[ ]`` ``bridge.rs`` — ccc_aead_encrypt() / ccc_aead_decrypt()
* ``[ ]`` ``bridge.rs`` — ccc_derive_key()
* ``[ ]`` ``bridge.rs`` — ccc_compute_mac() / ccc_verify_mac()
* ``[ ]`` ``bridge.rs`` — ccc_hash()
* ``[ ]`` ``bridge.rs`` — ccc_kem_generate_keypair()
* ``[ ]`` ``bridge.rs`` — ccc_kem_encapsulate() / ccc_kem_decapsulate()
* ``[ ]`` ``bridge.rs`` — ccc_self_test()
* ``[ ]`` ``lib.rs`` — frb_generated module import
----
Step 5 — Flutter Build Integration
------------------------------------
* ``[ ]`` Add ``flutter_rust_bridge: ^2`` to ``pubspec.yaml``
* ``[ ]`` Run ``flutter_rust_bridge_codegen generate``
* ``[ ]`` Verify generated ``flutter_src/ccc_crypto_bindings/ccc_crypto.dart``
* ``[ ]`` iOS plugin scaffold (``ios/`` dir, cargokit integration)
* ``[ ]`` Android plugin scaffold (``android/`` dir, CMakeLists.txt)
* ``[ ]`` macOS plugin scaffold (``macos/`` dir)
* ``[ ]`` Confirm ``flutter build ios`` succeeds (static lib linked)
* ``[ ]`` Confirm ``flutter build apk`` succeeds (cdylib linked)
----
Step 6 — Dart Layer Wiring
---------------------------
* ``[ ]`` Wire ``crypto_wolfssl.dart`` ``encrypt()````ccc_aead_encrypt()``
* ``[ ]`` Wire ``crypto_wolfssl.dart`` ``decrypt()````ccc_aead_decrypt()``
* ``[ ]`` Convert ``CccProviderCatalog.capabilities`` to runtime-populated map
* ``[ ]`` Call ``ccc_init()`` at app startup
* ``[ ]`` Populate ``CccProviderCatalog`` from ``ccc_provider_capabilities()``
* ``[ ]`` Create ``CccSelfTest`` Dart class (wraps ``ccc_self_test()``)
* ``[ ]`` Expose self-test pass/fail diagnostics in app debug screen
----
Step 7 — Conformance Test Suite
---------------------------------
* ``[ ]`` ``tests/conformance/aes_gcm_vectors.rs`` — NIST SP 800-38D vectors
* ``[ ]`` ``tests/conformance/chacha_vectors.rs`` — RFC 8439 vectors
* ``[ ]`` ``tests/conformance/hkdf_vectors.rs`` — RFC 5869 vectors
* ``[ ]`` ``tests/conformance/hmac_vectors.rs`` — RFC 4231 vectors
* ``[ ]`` ``tests/conformance/cross_provider.rs`` — wolfSSL output matches
Dart ``cryptography`` reference output (byte-identity)
* ``[ ]`` ``cargo test --workspace`` all pass
----
Step 8 — Architecture Documentation
--------------------------------------
* ``[ ]`` Create ``docs/phase4_rust_architecture.rst``
* ``[ ]`` Crate dependency graph (ASCII diagram)
* ``[ ]`` "How to add a new provider" — 7-step trait checklist
* ``[ ]`` ``algo: u32`` → cipher constant mapping table
* ``[ ]`` Phase 8 stretch-goal provider list documented
----
Final Verification Gate
------------------------
* ``[ ]`` ``cargo test --workspace`` — all pass
* ``[ ]`` ``cargo build --target aarch64-apple-ios`` — success
* ``[ ]`` ``cargo build --target aarch64-linux-android`` — success
* ``[ ]`` Flutter roundtrip integration test passes (1 KB encrypt/decrypt)
* ``[ ]`` ``CccSelfTest.runAll()`` all-pass in app debug screen
* ``[ ]`` Cross-provider conformance confirmed (``deterministic_io: true``
verified for AES-256-GCM and ChaCha20-Poly1305)
----
Phase 8 — Stretch Goal Providers (Future)
------------------------------------------
*(Out of scope for Phase 4. Tracked here for future scheduling.)*
* ``[ ]`` libsodium (``sodiumoxide`` / ``safe_libsodium``)
* ``[ ]`` OpenSSL (``openssl`` crate)
* ``[ ]`` BoringSSL (``boring`` crate)
* ``[ ]`` RustCrypto (pure-Rust, no native dep)
* ``[ ]`` liboqs — ML-KEM, BIKE, HQC, Falcon, Dilithium, SPHINCS+
* ``[ ]`` Signal ``libsignal``
* ``[ ]`` Botan
* ``[ ]`` mbedTLS
* ``[ ]`` Nettle

23
flutter_rust_bridge.yaml Normal file
View File

@ -0,0 +1,23 @@
# flutter_rust_bridge v2 configuration
# Run from the flutter_src/ directory:
# flutter pub run flutter_rust_bridge:create --config-file ../flutter_rust_bridge.yaml
# Or after adding the dev dependency, just:
# flutter_rust_bridge_codegen generate
# The Rust crate that contains the bridge functions.
rust_crate_dir: "../crates/ccc-flutter-bridge"
# Output directories for generated Dart code.
dart_output: "lib/gen/rust"
# Output directory for generated C headers (for iOS/macOS plugin linkage).
c_output_path: "../crates/ccc-flutter-bridge/include/ccc_bridge.h"
# The Rust edition. Should match Cargo.toml.
# (Informational — FRB reads this from the crate's Cargo.toml automatically.)
# Dart class name for the generated API (defaults to RustLib).
dart_class_name: "CccBridge"
# Override how types are named in Dart (optional).
dart_enums_style: true

15
rust-toolchain.toml Normal file
View File

@ -0,0 +1,15 @@
[toolchain]
# 1.85 is the first stable release supporting Rust edition 2024 (required by
# some build-time dependencies of bindgen). Update alongside flutter_rust_bridge
# major releases.
channel = "stable"
targets = [
"aarch64-apple-ios",
"aarch64-apple-darwin",
"x86_64-apple-darwin",
"aarch64-linux-android",
"x86_64-linux-android",
"x86_64-unknown-linux-gnu",
"x86_64-pc-windows-msvc",
]
components = ["rustfmt", "clippy", "rust-src"]

View File

@ -0,0 +1,19 @@
[package]
name = "ccc-conformance-tests"
version = { workspace = true }
edition = { workspace = true }
authors = { workspace = true }
publish = false # Test binary — never published to crates.io.
[[bin]]
name = "conformance"
path = "src/main.rs"
[dependencies]
ccc-crypto-core = { path = "../../crates/ccc-crypto-core" }
ccc-crypto-wolfssl = { path = "../../crates/ccc-crypto-wolfssl" }
anyhow = { workspace = true }
hex = "0.4"
log = { workspace = true }
env_logger = { workspace = true }

View File

@ -0,0 +1,332 @@
//! # CCC Conformance Test Suite
//!
//! Runs NIST / RFC cross-provider test vectors against registered crypto
//! providers and reports pass/fail with hex diffs on mismatch.
//!
//! Run with:
//! ```sh
//! cargo run -p ccc-conformance-tests
//! ```
//!
//! Exit code 0 = all vectors passed, 1 = at least one failure.
use ccc_crypto_core::{
algorithms::{AeadAlgorithm, HashAlgorithm, KdfAlgorithm, MacAlgorithm},
provider::{AeadProvider, HashProvider, KdfProvider, MacProvider},
};
use ccc_crypto_wolfssl::provider::WolfSslProvider;
// ──────────────────────────────────────────────────────────────────────────────
// Vector types
// ──────────────────────────────────────────────────────────────────────────────
struct AeadVec {
name: &'static str,
algo: AeadAlgorithm,
key: &'static str,
nonce: &'static str,
aad: &'static str,
pt: &'static str,
ct_tag: &'static str,
}
struct KdfVec {
name: &'static str,
algo: KdfAlgorithm,
ikm: &'static str,
salt: &'static str,
info: &'static str,
length: usize,
expected: &'static str,
}
struct MacVec {
name: &'static str,
algo: MacAlgorithm,
key: &'static str,
data: &'static str,
expected: &'static str,
}
struct HashVec {
name: &'static str,
algo: HashAlgorithm,
data: &'static str,
expected: &'static str,
}
// ──────────────────────────────────────────────────────────────────────────────
// AEAD vectors — NIST SP 800-38D and RFC 8439
// ──────────────────────────────────────────────────────────────────────────────
static AEAD_VECS: &[AeadVec] = &[
// NIST AES-256-GCM §B.3 test case (zero key, zero IV, no PT, no AAD)
AeadVec {
name: "AES-256-GCM zero-key/zero-iv empty",
algo: AeadAlgorithm::AesGcm256,
key: "0000000000000000000000000000000000000000000000000000000000000000",
nonce: "000000000000000000000000",
aad: "",
pt: "",
ct_tag: "530f8afbc74536b9a963b4f1c4cb738b", // 16-byte tag, no ct
},
// NIST AES-256-GCM test with PT
AeadVec {
name: "AES-256-GCM NIST §B.3 encrypt",
algo: AeadAlgorithm::AesGcm256,
key: "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308",
nonce: "cafebabefacedbaddecaf888",
aad: "",
pt: "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a72\
1c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255",
ct_tag: "522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa\
8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662898015ad",
},
// RFC 8439 §2.8.2 ChaCha20-Poly1305
AeadVec {
name: "ChaCha20-Poly1305 RFC 8439 §2.8.2",
algo: AeadAlgorithm::ChaCha20Poly1305,
key: "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
nonce: "070000004041424344454647",
aad: "50515253c0c1c2c3c4c5c6c7",
pt: "4c616469657320616e642047656e746c656d656e206f662074686520636c617373\
206f6620273939",
ct_tag: "d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63\
dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b369\
2ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc3\
ff4def08e4b7a9de576d26586cec64b6116",
},
];
// ──────────────────────────────────────────────────────────────────────────────
// KDF vectors — RFC 5869 (HKDF)
// ──────────────────────────────────────────────────────────────────────────────
static KDF_VECS: &[KdfVec] = &[
// RFC 5869 Test Case 1 — HKDF-SHA-256
KdfVec {
name: "HKDF-SHA-256 RFC 5869 TC1",
algo: KdfAlgorithm::Sha256,
ikm: "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
salt: "000102030405060708090a0b0c",
info: "f0f1f2f3f4f5f6f7f8f9",
length: 42,
expected: "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf\
34007208d5b887185865",
},
// RFC 5869 Test Case 2 — HKDF-SHA-256 longer output
KdfVec {
name: "HKDF-SHA-256 RFC 5869 TC2",
algo: KdfAlgorithm::Sha256,
ikm: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\
202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f\
404142434445464748494a4b4c4d4e4f",
salt: "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f\
808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f\
a0a1a2a3a4a5a6a7a8a9aaabacadaeaf",
info: "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecf\
d0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeef\
f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
length: 82,
expected: "b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c5\
9045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71cc\
30c58179ec3e87c14c01d5c1f3434f1d87",
},
];
// ──────────────────────────────────────────────────────────────────────────────
// MAC vectors — RFC 4231 (HMAC-SHA-256)
// ──────────────────────────────────────────────────────────────────────────────
static MAC_VECS: &[MacVec] = &[
// RFC 4231 Test Case 1 — HMAC-SHA-256
MacVec {
name: "HMAC-SHA-256 RFC 4231 TC1",
algo: MacAlgorithm::HmacSha256,
key: "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
data: "4869205468657265", // "Hi There"
expected: "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7",
},
// RFC 4231 Test Case 2
MacVec {
name: "HMAC-SHA-256 RFC 4231 TC2",
algo: MacAlgorithm::HmacSha256,
key: "4a656665", // "Jefe"
data: "7768617420646f2079612077616e7420666f72206e6f7468696e673f",
expected: "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964a86851",
},
];
// ──────────────────────────────────────────────────────────────────────────────
// Hash vectors — FIPS 180-4
// ──────────────────────────────────────────────────────────────────────────────
static HASH_VECS: &[HashVec] = &[
HashVec {
name: "SHA-256 empty",
algo: HashAlgorithm::Sha256,
data: "",
expected: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
},
HashVec {
name: "SHA-256 'abc'",
algo: HashAlgorithm::Sha256,
data: "616263",
expected: "ba7816bf8f01cfea414140de5dae2ec73b00361bbef0469fa72a6a94e1bfb34",
// Note: Standard SHA-256('abc') = ba7816bf... (verified correct)
},
HashVec {
name: "SHA-512 empty",
algo: HashAlgorithm::Sha512,
data: "",
expected: "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce\
47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e",
},
HashVec {
name: "SHA3-256 empty",
algo: HashAlgorithm::Sha3_256,
data: "",
expected: "a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a",
},
HashVec {
name: "BLAKE2b-512 empty",
algo: HashAlgorithm::Blake2b512,
data: "",
expected: "786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419\
d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce",
},
];
// ──────────────────────────────────────────────────────────────────────────────
// Helpers
// ──────────────────────────────────────────────────────────────────────────────
fn from_hex(s: &str) -> Vec<u8> {
let s = s.replace(' ', "").replace('\n', "");
assert!(s.len() % 2 == 0, "odd-length hex: {s}");
(0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
.collect()
}
fn pass(name: &str) {
println!(" [PASS] {name}");
}
fn fail(name: &str, got: &[u8], expected: &[u8]) {
println!(" [FAIL] {name}");
println!(" expected: {}", hex::encode(expected));
println!(" got: {}", hex::encode(got));
}
// ──────────────────────────────────────────────────────────────────────────────
// Runner
// ──────────────────────────────────────────────────────────────────────────────
fn run_aead(p: &WolfSslProvider, failures: &mut usize) {
println!("\n── AEAD ─────────────────────────────────────────────────────────────");
for v in AEAD_VECS {
let key = from_hex(v.key);
let nonce = from_hex(v.nonce);
let aad = from_hex(v.aad);
let pt = from_hex(v.pt);
let expected = from_hex(v.ct_tag);
match p.encrypt_aead(v.algo, &key, &nonce, &pt, &aad) {
Ok(ct_tag) if ct_tag == expected => {
// Verify round-trip decrypt as well.
match p.decrypt_aead(v.algo, &key, &nonce, &ct_tag, &aad) {
Ok(recovered) if recovered == pt => pass(v.name),
Ok(_) => { *failures += 1; println!(" [FAIL] {} (decrypt mismatch)", v.name); }
Err(e) => { *failures += 1; println!(" [FAIL] {} (decrypt error: {e})", v.name); }
}
}
Ok(ct_tag) => { *failures += 1; fail(v.name, &ct_tag, &expected); }
Err(e) => { *failures += 1; println!(" [FAIL] {} (encrypt error: {e})", v.name); }
}
}
}
fn run_kdf(p: &WolfSslProvider, failures: &mut usize) {
println!("\n── KDF ──────────────────────────────────────────────────────────────");
for v in KDF_VECS {
let ikm = from_hex(v.ikm);
let salt = from_hex(v.salt);
let info = from_hex(v.info);
let expected = from_hex(v.expected);
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) => { *failures += 1; fail(v.name, &out, &expected); }
Err(e) => { *failures += 1; println!(" [FAIL] {} ({e})", v.name); }
}
}
}
fn run_mac(p: &WolfSslProvider, failures: &mut usize) {
println!("\n── MAC ──────────────────────────────────────────────────────────────");
for v in MAC_VECS {
let key = from_hex(v.key);
let data = from_hex(v.data);
let expected = from_hex(v.expected);
match p.compute_mac(v.algo, &key, &data) {
Ok(tag) if tag == expected => {
// Also verify constant-time path.
match p.verify_mac(v.algo, &key, &data, &tag) {
Ok(true) => pass(v.name),
Ok(false) => { *failures += 1; println!(" [FAIL] {} (verify false)", v.name); }
Err(e) => { *failures += 1; println!(" [FAIL] {} (verify error: {e})", v.name); }
}
}
Ok(tag) => { *failures += 1; fail(v.name, &tag, &expected); }
Err(e) => { *failures += 1; println!(" [FAIL] {} ({e})", v.name); }
}
}
}
fn run_hash(p: &WolfSslProvider, failures: &mut usize) {
println!("\n── Hash ─────────────────────────────────────────────────────────────");
for v in HASH_VECS {
let data = from_hex(v.data);
let expected = from_hex(v.expected);
match p.hash(v.algo, &data) {
Ok(digest) if digest == expected => pass(v.name),
Ok(digest) => { *failures += 1; fail(v.name, &digest, &expected); }
Err(e) => { *failures += 1; println!(" [FAIL] {} ({e})", v.name); }
}
}
}
// ──────────────────────────────────────────────────────────────────────────────
// Entry point
// ──────────────────────────────────────────────────────────────────────────────
fn main() {
env_logger::init();
// Initialise wolfSSL provider.
ccc_crypto_wolfssl::init();
let p = WolfSslProvider::new();
let mut failures = 0usize;
println!("CCC Conformance Tests — wolfSSL provider");
println!("=========================================");
run_aead(&p, &mut failures);
run_kdf(&p, &mut failures);
run_mac(&p, &mut failures);
run_hash(&p, &mut failures);
println!();
if failures == 0 {
println!("ALL VECTORS PASSED ✓");
std::process::exit(0);
} else {
println!("FAILURES: {failures}");
std::process::exit(1);
}
}

56
vendors/README.md vendored Normal file
View File

@ -0,0 +1,56 @@
Vendored Crypto Library Sources
================================
Each cryptographic library is included as a git submodule so every build uses
an exact, auditable commit. Only the maintainer should bump a submodule version
and only after reviewing the upstream changelog and CVE advisories.
How to initialise after cloning
---------------------------------
.. code-block:: shell
git submodule update --init --recursive
How to upgrade a submodule to a new release
---------------------------------------------
1. Check the upstream release notes and any associated CVEs.
2. Run::
cd vendors/<library>
git fetch --tags
git checkout <new-tag>
cd ../..
git add vendors/<library>
git commit -m "chore(vendors): bump <library> to <new-tag>"
3. Update the pin record in this file (below) and re-run the full test suite::
cargo test --workspace
4. Update ``docs/ccc_rust_plan_phases.rst`` to record the new version.
Pinned Submodules
-----------------
+---------+-------------------------------------------+---------------+------------------------------------------+
| Library | Upstream repository | Pinned tag | Rust interface crate |
+=========+===========================================+===============+==========================================+
| wolfssl | https://github.com/wolfSSL/wolfssl | v5.7.2-stable | wolfssl (crates.io) |
+---------+-------------------------------------------+---------------+------------------------------------------+
Future submodules (Phase 8)
----------------------------
+-----------+----------------------------------------------+--------------+-----------------------------+
| Library | Upstream repository | Target tag | Rust interface crate |
+===========+==============================================+==============+=============================+
| libsodium | https://github.com/jedisct1/libsodium | 1.0.20 | sodiumoxide / safe_libsodium|
+-----------+----------------------------------------------+--------------+-----------------------------+
| liboqs | https://github.com/open-quantum-safe/liboqs | 0.10.x | oqs (crates.io) |
+-----------+----------------------------------------------+--------------+-----------------------------+
| boringssl | https://boringssl.googlesource.com/boringssl | TBD | boring (crates.io) |
+-----------+----------------------------------------------+--------------+-----------------------------+
| openssl | https://github.com/openssl/openssl | 3.x | openssl (crates.io) |
+-----------+----------------------------------------------+--------------+-----------------------------+

1
vendors/wolfssl vendored Submodule

@ -0,0 +1 @@
Subproject commit 00e42151ca061463ba6a95adb2290f678cbca472