Compare commits
No commits in common. "3c3fd39ab862fa1086d2391c9f74f195555ebc02" and "919d4bcc95161ee8e9aa83ad9a3474f407faa9bc" have entirely different histories.
3c3fd39ab8
...
919d4bcc95
|
|
@ -1,5 +1,5 @@
|
|||
# https://taskfile.dev
|
||||
version: "3"
|
||||
silent: true # This makes all tasks silent by default
|
||||
|
||||
vars:
|
||||
APPLE_TARGETS: "aarch64-apple-ios aarch64-apple-darwin x86_64-apple-darwin"
|
||||
|
|
@ -201,22 +201,6 @@ tasks:
|
|||
- task: release
|
||||
- task: build:apple
|
||||
|
||||
# ── Publish ─────────────────────────────────────────────────────────────
|
||||
|
||||
release:
|
||||
desc: "Push trunk and sync lum_ccc_fplugin Cargo.lock to the new commit"
|
||||
summary: |
|
||||
Runs in order:
|
||||
1. git push gitea trunk (lum_ccc_rust)
|
||||
2. dev-sync.sh --update (lum_ccc_fplugin) — updates Cargo.lock + clears build cache
|
||||
Set FPLUGIN_DIR env var to override the default fplugin path.
|
||||
vars:
|
||||
FPLUGIN_DIR:
|
||||
sh: echo "${FPLUGIN_DIR:-/Volumes/LUM/source/letusmsg_proj/app/lum_ccc_fplugin}"
|
||||
cmds:
|
||||
- git push gitea trunk
|
||||
- "{{.FPLUGIN_DIR}}/scripts/dev-sync.sh --update"
|
||||
|
||||
# ── Utility ─────────────────────────────────────────────────────────────
|
||||
|
||||
clean:
|
||||
|
|
@ -13,11 +13,6 @@
|
|||
//! * `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`.
|
||||
//! * `ANDROID_NDK_ROOT` (or `ANDROID_NDK_HOME`) – Required when targeting
|
||||
//! Android. Set to the NDK root directory so that CMake can locate the
|
||||
//! Android toolchain. Cargokit/Gradle sets this automatically; when
|
||||
//! invoking `cargo` directly make sure the variable is exported in your
|
||||
//! shell session.
|
||||
|
||||
use std::{
|
||||
env,
|
||||
|
|
@ -104,31 +99,6 @@ fn build_wolfssl_cmake(source_dir: &Path, _out_dir: &Path) -> (PathBuf, PathBuf)
|
|||
|
||||
let mut cfg = cmake::Config::new(source_dir);
|
||||
|
||||
// ── Android NDK ───────────────────────────────────────────────────────────
|
||||
// When cross-compiling for Android the cmake crate already sets
|
||||
// CMAKE_SYSTEM_NAME=Android and the clang compiler paths. The only
|
||||
// missing piece that Android-Determine.cmake checks first is
|
||||
// CMAKE_ANDROID_NDK — once it is set all other NDK detection succeeds.
|
||||
//
|
||||
// We read it from ANDROID_NDK_ROOT (standard SDK Manager layout) or the
|
||||
// legacy ANDROID_NDK_HOME. Cargokit/Gradle sets ANDROID_NDK_ROOT, and the
|
||||
// same variable must be visible in the shell when using cargo directly.
|
||||
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
|
||||
if target_os == "android" {
|
||||
let ndk_root = env::var("ANDROID_NDK_ROOT")
|
||||
.or_else(|_| env::var("ANDROID_NDK_HOME"))
|
||||
.unwrap_or_else(|_| {
|
||||
panic!(
|
||||
"\n\nAndroid target requires ANDROID_NDK_ROOT (or ANDROID_NDK_HOME) \
|
||||
to be set.\nSet it to the NDK directory, e.g.:\n \
|
||||
export ANDROID_NDK_ROOT=$HOME/apps/android-sdk/ndk/28.x.x\n"
|
||||
)
|
||||
});
|
||||
cfg.define("CMAKE_ANDROID_NDK", &ndk_root);
|
||||
println!("cargo:rerun-if-env-changed=ANDROID_NDK_ROOT");
|
||||
println!("cargo:rerun-if-env-changed=ANDROID_NDK_HOME");
|
||||
}
|
||||
|
||||
// ── Core algorithm selection ─────────────────────────────────────────────
|
||||
// wolfSSL cmake uses add_option() with "yes"/"no" string values (not ON/OFF).
|
||||
// Option names must exactly match the `add_option("NAME" ...)` calls in
|
||||
|
|
|
|||
|
|
@ -343,18 +343,16 @@ fn xchacha20_poly1305_encrypt(
|
|||
unsafe {
|
||||
// wc_XChaCha20Poly1305_Encrypt(dst, dst_space, src, src_len,
|
||||
// ad, ad_len, nonce, nonce_len, key, key_len)
|
||||
//
|
||||
// NOTE: wolfSSL's wc_XChaCha20Poly1305_Init unconditionally rejects
|
||||
// a NULL `ad` pointer (BAD_FUNC_ARG / -173), unlike AES-GCM and
|
||||
// ChaCha20-Poly1305 which allow NULL when ad_len == 0. Always pass
|
||||
// aad.as_ptr() — Rust guarantees a non-null dangling pointer for
|
||||
// empty slices.
|
||||
let ret = crate::sys::wc_XChaCha20Poly1305_Encrypt(
|
||||
dst.as_mut_ptr(),
|
||||
dst.len(),
|
||||
plaintext.as_ptr(),
|
||||
plaintext.len(),
|
||||
aad.as_ptr(),
|
||||
if aad.is_empty() {
|
||||
std::ptr::null()
|
||||
} else {
|
||||
aad.as_ptr()
|
||||
},
|
||||
aad.len(),
|
||||
nonce.as_ptr(),
|
||||
nonce.len(),
|
||||
|
|
@ -389,14 +387,16 @@ fn xchacha20_poly1305_decrypt(
|
|||
unsafe {
|
||||
// wc_XChaCha20Poly1305_Decrypt(dst, dst_space, src, src_len,
|
||||
// ad, ad_len, nonce, nonce_len, key, key_len)
|
||||
//
|
||||
// NOTE: See encrypt above — wolfSSL rejects NULL ad for XChaCha20.
|
||||
let ret = crate::sys::wc_XChaCha20Poly1305_Decrypt(
|
||||
dst.as_mut_ptr(),
|
||||
dst.len(),
|
||||
ciphertext_and_tag.as_ptr(),
|
||||
ciphertext_and_tag.len(),
|
||||
aad.as_ptr(),
|
||||
if aad.is_empty() {
|
||||
std::ptr::null()
|
||||
} else {
|
||||
aad.as_ptr()
|
||||
},
|
||||
aad.len(),
|
||||
nonce.as_ptr(),
|
||||
nonce.len(),
|
||||
|
|
@ -410,280 +410,3 @@ fn xchacha20_poly1305_decrypt(
|
|||
|
||||
Ok(dst)
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────────────
|
||||
// Tests
|
||||
// ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use ccc_crypto_core::algorithms::AeadAlgorithm;
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
/// Encrypt then decrypt and assert the plaintext round-trips.
|
||||
fn roundtrip(algo: AeadAlgorithm, key: &[u8], nonce: &[u8], pt: &[u8], aad: &[u8]) {
|
||||
let ct = encrypt(algo, key, nonce, pt, aad)
|
||||
.unwrap_or_else(|e| panic!("{} encrypt failed: {e}", algo.name()));
|
||||
|
||||
// ciphertext must be plaintext + 16-byte tag
|
||||
assert_eq!(
|
||||
ct.len(),
|
||||
pt.len() + TAG_LEN,
|
||||
"{}: unexpected ciphertext length",
|
||||
algo.name()
|
||||
);
|
||||
|
||||
let recovered = decrypt(algo, key, nonce, &ct, aad)
|
||||
.unwrap_or_else(|e| panic!("{} decrypt failed: {e}", algo.name()));
|
||||
assert_eq!(recovered, pt, "{}: plaintext mismatch after roundtrip", algo.name());
|
||||
}
|
||||
|
||||
// ── AES-256-GCM ─────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn aes_gcm_roundtrip_with_aad() {
|
||||
roundtrip(
|
||||
AeadAlgorithm::AesGcm256,
|
||||
&[0x01; 32],
|
||||
&[0x02; 12],
|
||||
b"aes-gcm payload with associated data",
|
||||
b"some associated data",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aes_gcm_roundtrip_empty_aad() {
|
||||
roundtrip(
|
||||
AeadAlgorithm::AesGcm256,
|
||||
&[0x11; 32],
|
||||
&[0x22; 12],
|
||||
b"aes-gcm payload no aad",
|
||||
&[],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aes_gcm_roundtrip_empty_plaintext() {
|
||||
roundtrip(
|
||||
AeadAlgorithm::AesGcm256,
|
||||
&[0x31; 32],
|
||||
&[0x32; 12],
|
||||
&[],
|
||||
b"aad only, no plaintext",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aes_gcm_roundtrip_empty_both() {
|
||||
roundtrip(
|
||||
AeadAlgorithm::AesGcm256,
|
||||
&[0x41; 32],
|
||||
&[0x42; 12],
|
||||
&[],
|
||||
&[],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aes_gcm_tampered_tag_fails() {
|
||||
let key = [0x51u8; 32];
|
||||
let nonce = [0x52u8; 12];
|
||||
let pt = b"integrity check";
|
||||
let mut ct = encrypt(AeadAlgorithm::AesGcm256, &key, &nonce, pt, &[]).unwrap();
|
||||
// Flip last byte of the tag
|
||||
let last = ct.len() - 1;
|
||||
ct[last] ^= 0xFF;
|
||||
assert!(decrypt(AeadAlgorithm::AesGcm256, &key, &nonce, &ct, &[]).is_err());
|
||||
}
|
||||
|
||||
// ── ChaCha20-Poly1305 ───────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn chacha20_roundtrip_with_aad() {
|
||||
roundtrip(
|
||||
AeadAlgorithm::ChaCha20Poly1305,
|
||||
&[0x61; 32],
|
||||
&[0x62; 12],
|
||||
b"chacha20 payload with associated data",
|
||||
b"chacha20 aad",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chacha20_roundtrip_empty_aad() {
|
||||
roundtrip(
|
||||
AeadAlgorithm::ChaCha20Poly1305,
|
||||
&[0x71; 32],
|
||||
&[0x72; 12],
|
||||
b"chacha20 payload no aad",
|
||||
&[],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chacha20_roundtrip_empty_plaintext() {
|
||||
roundtrip(
|
||||
AeadAlgorithm::ChaCha20Poly1305,
|
||||
&[0x81; 32],
|
||||
&[0x82; 12],
|
||||
&[],
|
||||
b"chacha20 aad only",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chacha20_roundtrip_empty_both() {
|
||||
roundtrip(
|
||||
AeadAlgorithm::ChaCha20Poly1305,
|
||||
&[0x91; 32],
|
||||
&[0x92; 12],
|
||||
&[],
|
||||
&[],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chacha20_tampered_tag_fails() {
|
||||
let key = [0xA1u8; 32];
|
||||
let nonce = [0xA2u8; 12];
|
||||
let pt = b"integrity check chacha";
|
||||
let mut ct = encrypt(AeadAlgorithm::ChaCha20Poly1305, &key, &nonce, pt, &[]).unwrap();
|
||||
let last = ct.len() - 1;
|
||||
ct[last] ^= 0xFF;
|
||||
assert!(decrypt(AeadAlgorithm::ChaCha20Poly1305, &key, &nonce, &ct, &[]).is_err());
|
||||
}
|
||||
|
||||
// ── XChaCha20-Poly1305 ──────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn xchacha20_roundtrip_with_aad() {
|
||||
roundtrip(
|
||||
AeadAlgorithm::XChaCha20Poly1305,
|
||||
&[0xB1; 32],
|
||||
&[0xB2; 24],
|
||||
b"xchacha20 payload with associated data",
|
||||
b"xchacha20 aad",
|
||||
);
|
||||
}
|
||||
|
||||
/// Regression: wolfSSL's `wc_XChaCha20Poly1305_Init` unconditionally
|
||||
/// rejects a NULL `ad` pointer (BAD_FUNC_ARG / -173), even when
|
||||
/// `ad_len == 0`. Must pass a non-null dangling pointer for empty AAD.
|
||||
#[test]
|
||||
fn xchacha20_roundtrip_empty_aad() {
|
||||
roundtrip(
|
||||
AeadAlgorithm::XChaCha20Poly1305,
|
||||
&[0xC1; 32],
|
||||
&[0xC2; 24],
|
||||
b"xchacha20 payload no aad",
|
||||
&[],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xchacha20_roundtrip_empty_plaintext() {
|
||||
roundtrip(
|
||||
AeadAlgorithm::XChaCha20Poly1305,
|
||||
&[0xD1; 32],
|
||||
&[0xD2; 24],
|
||||
&[],
|
||||
b"xchacha20 aad only",
|
||||
);
|
||||
}
|
||||
|
||||
/// Regression: both AAD and plaintext empty — exercises the NULL-pointer
|
||||
/// code paths simultaneously.
|
||||
#[test]
|
||||
fn xchacha20_roundtrip_empty_both() {
|
||||
roundtrip(
|
||||
AeadAlgorithm::XChaCha20Poly1305,
|
||||
&[0xE1; 32],
|
||||
&[0xE2; 24],
|
||||
&[],
|
||||
&[],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xchacha20_tampered_tag_fails() {
|
||||
let key = [0xF1u8; 32];
|
||||
let nonce = [0xF2u8; 24];
|
||||
let pt = b"integrity check xchacha";
|
||||
let mut ct = encrypt(AeadAlgorithm::XChaCha20Poly1305, &key, &nonce, pt, &[]).unwrap();
|
||||
let last = ct.len() - 1;
|
||||
ct[last] ^= 0xFF;
|
||||
assert!(decrypt(AeadAlgorithm::XChaCha20Poly1305, &key, &nonce, &ct, &[]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xchacha20_wrong_aad_fails() {
|
||||
let key = [0xF3u8; 32];
|
||||
let nonce = [0xF4u8; 24];
|
||||
let pt = b"aad mismatch test";
|
||||
let ct = encrypt(AeadAlgorithm::XChaCha20Poly1305, &key, &nonce, pt, b"correct").unwrap();
|
||||
assert!(decrypt(AeadAlgorithm::XChaCha20Poly1305, &key, &nonce, &ct, b"wrong").is_err());
|
||||
}
|
||||
|
||||
// ── Key / nonce validation ──────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn aes_gcm_wrong_key_len_rejected() {
|
||||
assert!(encrypt(AeadAlgorithm::AesGcm256, &[0; 16], &[0; 12], b"x", &[]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aes_gcm_wrong_nonce_len_rejected() {
|
||||
assert!(encrypt(AeadAlgorithm::AesGcm256, &[0; 32], &[0; 8], b"x", &[]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chacha20_wrong_key_len_rejected() {
|
||||
assert!(encrypt(AeadAlgorithm::ChaCha20Poly1305, &[0; 16], &[0; 12], b"x", &[]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chacha20_wrong_nonce_len_rejected() {
|
||||
assert!(encrypt(AeadAlgorithm::ChaCha20Poly1305, &[0; 32], &[0; 8], b"x", &[]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xchacha20_wrong_key_len_rejected() {
|
||||
assert!(encrypt(AeadAlgorithm::XChaCha20Poly1305, &[0; 16], &[0; 24], b"x", &[]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xchacha20_wrong_nonce_len_rejected() {
|
||||
assert!(encrypt(AeadAlgorithm::XChaCha20Poly1305, &[0; 32], &[0; 12], b"x", &[]).is_err());
|
||||
}
|
||||
|
||||
// ── Large payload ───────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn xchacha20_large_payload_roundtrip() {
|
||||
let key = [0xAAu8; 32];
|
||||
let nonce = [0xBBu8; 24];
|
||||
let pt = vec![0x42u8; 64 * 1024]; // 64 KiB — exercises the 16k chunked loop
|
||||
roundtrip(AeadAlgorithm::XChaCha20Poly1305, &key, &nonce, &pt, b"large");
|
||||
}
|
||||
|
||||
// ── Cross-algorithm isolation ───────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn ciphertext_not_interchangeable_between_algorithms() {
|
||||
let key = [0xDD; 32];
|
||||
let nonce_12 = [0xEE; 12];
|
||||
let nonce_24 = [0xEE; 24];
|
||||
let pt = b"cross-algo test";
|
||||
|
||||
let ct_aes = encrypt(AeadAlgorithm::AesGcm256, &key, &nonce_12, pt, &[]).unwrap();
|
||||
let ct_chacha = encrypt(AeadAlgorithm::ChaCha20Poly1305, &key, &nonce_12, pt, &[]).unwrap();
|
||||
let ct_xchacha = encrypt(AeadAlgorithm::XChaCha20Poly1305, &key, &nonce_24, pt, &[]).unwrap();
|
||||
|
||||
// Each ciphertext must fail to decrypt under a different algorithm
|
||||
assert!(decrypt(AeadAlgorithm::ChaCha20Poly1305, &key, &nonce_12, &ct_aes, &[]).is_err());
|
||||
assert!(decrypt(AeadAlgorithm::AesGcm256, &key, &nonce_12, &ct_chacha, &[]).is_err());
|
||||
assert!(decrypt(AeadAlgorithm::AesGcm256, &key, &nonce_24, &ct_xchacha, &[]).is_err());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue