NEW: first draft of crates
This commit is contained in:
parent
df1de9d99d
commit
3c9320ffa5
|
|
@ -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"
|
||||
|
|
@ -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/
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
[submodule "vendors/wolfssl"]
|
||||
path = vendors/wolfssl
|
||||
url = https://github.com/wolfSSL/wolfssl
|
||||
|
|
@ -11,5 +11,10 @@
|
|||
"**/var/**": true,
|
||||
"**/bin/**": true
|
||||
},
|
||||
"foldOnOpen.targets": ["AllBlockComments"]
|
||||
"foldOnOpen.targets": [
|
||||
"AllBlockComments"
|
||||
],
|
||||
"chat.tools.terminal.autoApprove": {
|
||||
"cargo search": true
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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" }
|
||||
|
|
@ -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.
|
||||
|
|
@ -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 10–19
|
||||
// ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// 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 1–4
|
||||
// ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// 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 30–35
|
||||
// ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// 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 40–49
|
||||
// ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// 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 50–59 (new range, not yet in cipher_constants.dart)
|
||||
// ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Key encapsulation mechanism algorithms.
|
||||
///
|
||||
/// Values 50–59 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",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 0–100 (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 0–100.
|
||||
///
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
}
|
||||
|
|
@ -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};
|
||||
|
|
@ -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` (0–100).
|
||||
fn benchmark(&self) -> BenchmarkReport;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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 0–100 (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>,
|
||||
}
|
||||
|
|
@ -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 = []
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
|
|
@ -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 0–100 `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` (0–100) 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 0–100 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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
|
@ -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 1–64 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 1–64 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"));
|
||||
}
|
||||
}
|
||||
|
|
@ -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()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -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/.
|
||||
|
|
@ -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())
|
||||
}
|
||||
|
|
@ -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 0–100 (0 = unknown).
|
||||
pub efficiency_score: u8,
|
||||
/// NIST / standards reliability score 0–100 (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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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 0–100
|
||||
``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)
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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"]
|
||||
|
|
@ -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 }
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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) |
|
||||
+-----------+----------------------------------------------+--------------+-----------------------------+
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 00e42151ca061463ba6a95adb2290f678cbca472
|
||||
Loading…
Reference in New Issue