282 lines
12 KiB
Rust
282 lines
12 KiB
Rust
//! 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},
|
|
registry::ProviderRegistry,
|
|
};
|
|
// Provider traits are imported for use in future dispatch methods.
|
|
#[allow(unused_imports)]
|
|
use ccc_crypto_core::provider::{AeadProvider, HashProvider, KdfProvider, KemProvider, MacProvider};
|
|
|
|
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())
|
|
}
|