MOD: milestone 1 complete, tests all pass
This commit is contained in:
parent
db897b5abe
commit
836cf5063e
|
|
@ -275,16 +275,22 @@ fn generate_bindings(include_dir: &PathBuf, out_dir: &PathBuf) {
|
|||
.allowlist_function("wc_curve25519_init")
|
||||
.allowlist_function("wc_curve25519_free")
|
||||
.allowlist_function("wc_curve25519_import_private")
|
||||
.allowlist_function("wc_curve25519_import_private_ex")
|
||||
.allowlist_function("wc_curve25519_import_public")
|
||||
.allowlist_function("wc_curve25519_import_public_ex")
|
||||
.allowlist_function("wc_curve25519_export_key_raw")
|
||||
.allowlist_function("wc_curve25519_export_key_raw_ex")
|
||||
.allowlist_function("wc_curve25519_shared_secret_ex")
|
||||
// Curve448
|
||||
.allowlist_function("wc_curve448_make_key")
|
||||
.allowlist_function("wc_curve448_init")
|
||||
.allowlist_function("wc_curve448_free")
|
||||
.allowlist_function("wc_curve448_import_private")
|
||||
.allowlist_function("wc_curve448_import_private_ex")
|
||||
.allowlist_function("wc_curve448_import_public")
|
||||
.allowlist_function("wc_curve448_import_public_ex")
|
||||
.allowlist_function("wc_curve448_export_key_raw")
|
||||
.allowlist_function("wc_curve448_export_key_raw_ex")
|
||||
.allowlist_function("wc_curve448_shared_secret_ex")
|
||||
// RNG
|
||||
.allowlist_function("wc_InitRng")
|
||||
|
|
@ -312,6 +318,14 @@ fn generate_bindings(include_dir: &PathBuf, out_dir: &PathBuf) {
|
|||
// emits for C structs with bitfields (Aes, Hmac, etc.).
|
||||
.blocklist_type("__uint128_t")
|
||||
.blocklist_type("__int128_t")
|
||||
// ECPoint contains an `ALIGN16 byte point[32]` field. bindgen does not
|
||||
// propagate the __attribute__((aligned(16))) from a struct *field* to the
|
||||
// generated Rust type, so it emits ECPoint as a plain 33-byte struct
|
||||
// instead of the 48-byte, 16-byte-aligned layout that wolfCrypt uses.
|
||||
// Blocking it here lets us define it manually in sys/ with the correct
|
||||
// `#[repr(C, align(16))]` attribute so that curve25519_key's field
|
||||
// offsets match the compiled C library.
|
||||
.blocklist_type("ECPoint")
|
||||
.derive_debug(false)
|
||||
.layout_tests(false)
|
||||
.generate()
|
||||
|
|
|
|||
|
|
@ -83,6 +83,9 @@ pub fn decapsulate(
|
|||
// Key sizes: private = 32 bytes, public = 32 bytes, shared secret = 32 bytes.
|
||||
// ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// EC25519_LITTLE_ENDIAN = 0 (matches RFC 7748 wire format).
|
||||
const X25519_LE: i32 = 0;
|
||||
|
||||
fn x25519_generate() -> Result<KemKeyPair, CryptoError> {
|
||||
let mut public_key = vec![0u8; 32];
|
||||
let mut private_key = vec![0u8; 32];
|
||||
|
|
@ -107,11 +110,21 @@ fn x25519_generate() -> Result<KemKeyPair, CryptoError> {
|
|||
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(
|
||||
// Export in little-endian (EC25519_LITTLE_ENDIAN=0) so all key
|
||||
// material is in RFC 7748 canonical format.
|
||||
let ret = crate::sys::wc_curve25519_export_key_raw_ex(
|
||||
&mut key,
|
||||
private_key.as_mut_ptr(), &mut priv_sz,
|
||||
public_key.as_mut_ptr(), &mut pub_sz,
|
||||
X25519_LE,
|
||||
);
|
||||
if ret != 0 {
|
||||
crate::sys::wc_curve25519_free(&mut key);
|
||||
crate::sys::wc_FreeRng(&mut rng);
|
||||
return Err(CryptoError::InternalError(
|
||||
format!("wc_curve25519_export_key_raw_ex returned {}", ret)
|
||||
));
|
||||
}
|
||||
|
||||
crate::sys::wc_curve25519_free(&mut key);
|
||||
crate::sys::wc_FreeRng(&mut rng);
|
||||
|
|
@ -140,6 +153,13 @@ fn x25519_decapsulate(
|
|||
}
|
||||
|
||||
/// Raw X25519 Diffie-Hellman: `shared = scalar_mult(private_key, public_key)`.
|
||||
///
|
||||
/// All byte strings are in little-endian (RFC 7748) format.
|
||||
///
|
||||
/// We init each `curve25519_key` with `wc_curve25519_init` (the required
|
||||
/// wolfSSL pattern) before importing keys. Without the init call, wolfSSL's
|
||||
/// `fe_init()` and `dp` pointer setup are skipped, causing
|
||||
/// `wc_curve25519_shared_secret_ex` to return `ECC_BAD_ARG_E` (-170).
|
||||
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(
|
||||
|
|
@ -153,38 +173,58 @@ fn x25519_dh(private_key: &[u8], public_key: &[u8]) -> Result<Zeroizing<Vec<u8>>
|
|||
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,
|
||||
);
|
||||
// Initialise both key structs before use (sets dp, calls fe_init, etc.).
|
||||
let ret = crate::sys::wc_curve25519_init(&mut local_key);
|
||||
if ret != 0 {
|
||||
return Err(CryptoError::InvalidKey(
|
||||
format!("wc_curve25519_import_private returned {}", ret)
|
||||
return Err(CryptoError::InternalError(
|
||||
format!("wc_curve25519_init (local) returned {}", ret)
|
||||
));
|
||||
}
|
||||
let ret = crate::sys::wc_curve25519_init(&mut remote_key);
|
||||
if ret != 0 {
|
||||
crate::sys::wc_curve25519_free(&mut local_key);
|
||||
return Err(CryptoError::InternalError(
|
||||
format!("wc_curve25519_init (remote) returned {}", ret)
|
||||
));
|
||||
}
|
||||
|
||||
// Import remote public key.
|
||||
let ret = crate::sys::wc_curve25519_import_public(
|
||||
public_key.as_ptr(), 32,
|
||||
&mut remote_key,
|
||||
// Import private key in little-endian (EC25519_LITTLE_ENDIAN = 0).
|
||||
let ret = crate::sys::wc_curve25519_import_private_ex(
|
||||
private_key.as_ptr(), 32,
|
||||
&mut local_key,
|
||||
X25519_LE,
|
||||
);
|
||||
if ret != 0 {
|
||||
crate::sys::wc_curve25519_free(&mut local_key);
|
||||
crate::sys::wc_curve25519_free(&mut remote_key);
|
||||
return Err(CryptoError::InvalidKey(
|
||||
format!("wc_curve25519_import_public returned {}", ret)
|
||||
format!("wc_curve25519_import_private_ex returned {}", ret)
|
||||
));
|
||||
}
|
||||
|
||||
// Import remote public key in little-endian.
|
||||
let ret = crate::sys::wc_curve25519_import_public_ex(
|
||||
public_key.as_ptr(), 32,
|
||||
&mut remote_key,
|
||||
X25519_LE,
|
||||
);
|
||||
if ret != 0 {
|
||||
crate::sys::wc_curve25519_free(&mut local_key);
|
||||
crate::sys::wc_curve25519_free(&mut remote_key);
|
||||
return Err(CryptoError::InvalidKey(
|
||||
format!("wc_curve25519_import_public_ex returned {}", ret)
|
||||
));
|
||||
}
|
||||
|
||||
let mut shared_sz = 32u32;
|
||||
// EC_VALUE_SAME_KEY = 1 (little-endian) for Curve25519 in wolfCrypt
|
||||
let endian = 1i32;
|
||||
// Request shared secret in little-endian (EC25519_LITTLE_ENDIAN = 0).
|
||||
|
||||
let ret = crate::sys::wc_curve25519_shared_secret_ex(
|
||||
&mut local_key,
|
||||
&mut remote_key,
|
||||
shared.as_mut_ptr(),
|
||||
&mut shared_sz,
|
||||
endian,
|
||||
X25519_LE,
|
||||
);
|
||||
|
||||
crate::sys::wc_curve25519_free(&mut local_key);
|
||||
|
|
@ -206,6 +246,9 @@ fn x25519_dh(private_key: &[u8], public_key: &[u8]) -> Result<Zeroizing<Vec<u8>>
|
|||
// Key sizes: private = 56 bytes, public = 56 bytes, shared secret = 56 bytes.
|
||||
// ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// EC448_LITTLE_ENDIAN = 0 (matches RFC 7748 wire format).
|
||||
const X448_LE: i32 = 0;
|
||||
|
||||
fn x448_generate() -> Result<KemKeyPair, CryptoError> {
|
||||
let mut public_key = vec![0u8; 56];
|
||||
let mut private_key = vec![0u8; 56];
|
||||
|
|
@ -230,11 +273,20 @@ fn x448_generate() -> Result<KemKeyPair, CryptoError> {
|
|||
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(
|
||||
// Export in little-endian (EC448_LITTLE_ENDIAN=0), RFC 7748 canonical format.
|
||||
let ret = crate::sys::wc_curve448_export_key_raw_ex(
|
||||
&mut key,
|
||||
private_key.as_mut_ptr(), &mut priv_sz,
|
||||
public_key.as_mut_ptr(), &mut pub_sz,
|
||||
X448_LE,
|
||||
);
|
||||
if ret != 0 {
|
||||
crate::sys::wc_curve448_free(&mut key);
|
||||
crate::sys::wc_FreeRng(&mut rng);
|
||||
return Err(CryptoError::InternalError(
|
||||
format!("wc_curve448_export_key_raw_ex returned {}", ret)
|
||||
));
|
||||
}
|
||||
|
||||
crate::sys::wc_curve448_free(&mut key);
|
||||
crate::sys::wc_FreeRng(&mut rng);
|
||||
|
|
@ -259,6 +311,12 @@ fn x448_decapsulate(
|
|||
x448_dh(private_key, peer_ephemeral_pub)
|
||||
}
|
||||
|
||||
/// Raw X448 Diffie-Hellman: `shared = scalar_mult(private_key, public_key)`.
|
||||
///
|
||||
/// All byte strings are in little-endian (RFC 7748) format.
|
||||
///
|
||||
/// We init each `curve448_key` with `wc_curve448_init` (the required wolfSSL
|
||||
/// pattern) before importing keys.
|
||||
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()));
|
||||
|
|
@ -270,33 +328,53 @@ fn x448_dh(private_key: &[u8], public_key: &[u8]) -> Result<Zeroizing<Vec<u8>>,
|
|||
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,
|
||||
);
|
||||
// Initialise both key structs before use.
|
||||
let ret = crate::sys::wc_curve448_init(&mut local_key);
|
||||
if ret != 0 {
|
||||
return Err(CryptoError::InvalidKey(
|
||||
format!("wc_curve448_import_private returned {}", ret)
|
||||
return Err(CryptoError::InternalError(
|
||||
format!("wc_curve448_init (local) returned {}", ret)
|
||||
));
|
||||
}
|
||||
let ret = crate::sys::wc_curve448_init(&mut remote_key);
|
||||
if ret != 0 {
|
||||
crate::sys::wc_curve448_free(&mut local_key);
|
||||
return Err(CryptoError::InternalError(
|
||||
format!("wc_curve448_init (remote) returned {}", ret)
|
||||
));
|
||||
}
|
||||
|
||||
let ret = crate::sys::wc_curve448_import_public(
|
||||
public_key.as_ptr(), 56, &mut remote_key,
|
||||
// Import private key in little-endian (EC448_LITTLE_ENDIAN = 0).
|
||||
let ret = crate::sys::wc_curve448_import_private_ex(
|
||||
private_key.as_ptr(), 56, &mut local_key, X448_LE,
|
||||
);
|
||||
if ret != 0 {
|
||||
crate::sys::wc_curve448_free(&mut local_key);
|
||||
crate::sys::wc_curve448_free(&mut remote_key);
|
||||
return Err(CryptoError::InvalidKey(
|
||||
format!("wc_curve448_import_public returned {}", ret)
|
||||
format!("wc_curve448_import_private_ex returned {}", ret)
|
||||
));
|
||||
}
|
||||
|
||||
// Import remote public key in little-endian.
|
||||
let ret = crate::sys::wc_curve448_import_public_ex(
|
||||
public_key.as_ptr(), 56, &mut remote_key, X448_LE,
|
||||
);
|
||||
if ret != 0 {
|
||||
crate::sys::wc_curve448_free(&mut local_key);
|
||||
crate::sys::wc_curve448_free(&mut remote_key);
|
||||
return Err(CryptoError::InvalidKey(
|
||||
format!("wc_curve448_import_public_ex returned {}", ret)
|
||||
));
|
||||
}
|
||||
|
||||
let mut shared_sz = 56u32;
|
||||
let endian = 1i32; // EC_VALUE_SAME_KEY little-endian
|
||||
// Request shared secret in little-endian (EC448_LITTLE_ENDIAN = 0).
|
||||
let ret = crate::sys::wc_curve448_shared_secret_ex(
|
||||
&mut local_key,
|
||||
&mut remote_key,
|
||||
shared.as_mut_ptr(),
|
||||
&mut shared_sz,
|
||||
endian,
|
||||
X448_LE,
|
||||
);
|
||||
|
||||
crate::sys::wc_curve448_free(&mut local_key);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,34 @@ pub mod provider;
|
|||
// 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 {
|
||||
// ── Manual ABI-critical type definitions ────────────────────────────────
|
||||
//
|
||||
// `ECPoint` contains `ALIGN16 byte point[32]` in the wolfCrypt C headers.
|
||||
// bindgen does not propagate `__attribute__((aligned(16)))` from a struct
|
||||
// *field* to the generated Rust type; it emits a plain 33-byte struct.
|
||||
// wolfCrypt's actual ECPoint is 48 bytes (32 + 1 + 15-byte tail-padding)
|
||||
// with 16-byte alignment. Without the correct size/alignment, every
|
||||
// field that follows ECPoint in `curve25519_key` (including `k` and the
|
||||
// `pubSet`/`privSet` bitfields) lands at the wrong offset relative to the
|
||||
// compiled C library, causing `wc_curve25519_shared_secret_ex` to return
|
||||
// `ECC_BAD_ARG_E` (-170) because it reads `pubSet`/`privSet` from the
|
||||
// wrong memory location.
|
||||
//
|
||||
// We blocklist ECPoint in build.rs so bindgen does not emit it, then
|
||||
// define it here (before the `include!`) with the correct alignment.
|
||||
// The struct is used only as a field inside `curve25519_key`; we never
|
||||
// manipulate its internals directly from Rust.
|
||||
#[repr(C, align(16))]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct ECPoint {
|
||||
/// u-coordinate of the Curve25519 public key (little-endian).
|
||||
pub point: [u8; 32],
|
||||
/// Length tag used internally by wolfCrypt; always 32 for Curve25519.
|
||||
pub pointSz: u8,
|
||||
// 15 bytes of implicit tail-padding inserted by Rust to satisfy
|
||||
// align(16), matching the C compiler's layout exactly.
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@
|
|||
//! 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},
|
||||
algorithms::{AeadAlgorithm, HashAlgorithm, KdfAlgorithm, KemAlgorithm, MacAlgorithm},
|
||||
provider::{AeadProvider, HashProvider, KdfProvider, KemProvider, MacProvider},
|
||||
};
|
||||
use ccc_crypto_wolfssl::provider::WolfSslProvider;
|
||||
|
||||
|
|
@ -55,6 +55,27 @@ struct HashVec {
|
|||
expected: &'static str,
|
||||
}
|
||||
|
||||
/// RFC 7748 DH test vector.
|
||||
///
|
||||
/// `decapsulate(algo, alice_private, bob_public)` must equal `expected_shared`.
|
||||
/// Both sides are symmetric: `decapsulate(algo, bob_private, alice_public)` is
|
||||
/// verified as a second assertion in `run_kem()`.
|
||||
struct KemDhVec {
|
||||
name: &'static str,
|
||||
algo: KemAlgorithm,
|
||||
/// Alice's static private key (little-endian).
|
||||
alice_private: &'static str,
|
||||
/// Alice's corresponding public key (little-endian), used for the Bob→Alice
|
||||
/// direction check.
|
||||
alice_public: &'static str,
|
||||
/// Bob's static private key (little-endian).
|
||||
bob_private: &'static str,
|
||||
/// Bob's corresponding public key (little-endian).
|
||||
bob_public: &'static str,
|
||||
/// Expected shared secret (little-endian).
|
||||
expected_shared: &'static str,
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────────────
|
||||
// AEAD vectors — NIST SP 800-38D and RFC 8439
|
||||
// ──────────────────────────────────────────────────────────────────────────────
|
||||
|
|
@ -206,6 +227,85 @@ static HASH_VECS: &[HashVec] = &[
|
|||
},
|
||||
];
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────────────
|
||||
// KEM DH vectors — RFC 7748 §6.1 (X25519) and §6.2 (X448)
|
||||
//
|
||||
// All byte strings are little-endian (the canonical wire format for both
|
||||
// X25519 and X448 per RFC 7748). The test calls:
|
||||
// decapsulate(algo, alice_private, bob_public) == expected_shared
|
||||
// decapsulate(algo, bob_private, alice_public) == expected_shared
|
||||
// ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
static KEM_DH_VECS: &[KemDhVec] = &[
|
||||
// ── RFC 7748 §6.1 — X25519 ───────────────────────────────────────────────
|
||||
KemDhVec {
|
||||
name: "X25519 DH RFC 7748 §6.1",
|
||||
algo: KemAlgorithm::X25519,
|
||||
alice_private: "77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a",
|
||||
alice_public: "8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a",
|
||||
bob_private: "5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb",
|
||||
bob_public: "de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f",
|
||||
expected_shared: "4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742",
|
||||
},
|
||||
// ── RFC 7748 §6.2 — X448 ────────────────────────────────────────────────
|
||||
KemDhVec {
|
||||
name: "X448 DH RFC 7748 §6.2",
|
||||
algo: KemAlgorithm::X448,
|
||||
alice_private: "9a8f4925d1519f5775cf46b04b5800d4\
|
||||
ee9ee8bae8bc5565d498c28dd9c9baf5\
|
||||
74a9419744897391006382a6f127ab1d\
|
||||
9ac2d8c0a598726b",
|
||||
alice_public: "9b08f7cc31b7e3e67d22d5aea121074a\
|
||||
273bd2b83de09c63faa73d2c22c5d9bb\
|
||||
c836647241d953d40c5b12da88120d53\
|
||||
177f80e532c41fa0",
|
||||
bob_private: "1c306a7ac2a0e2e0990b294470cba339\
|
||||
e6453772b075811d8fad0d1d6927c120\
|
||||
bb5ee8972b0d3e21374c9c921b09d1b0\
|
||||
366f10b65173992d",
|
||||
bob_public: "3eb7a829b0cd20f5bcfc0b599b6feccf\
|
||||
6da4627107bdb0d4f345b43027d8b972\
|
||||
fc3e34fb4232a13ca706dcb57aec3dae\
|
||||
07bdc1c67bf33609",
|
||||
expected_shared: "07fff4181ac6cc95ec1c16a94a0f74d1\
|
||||
2da232ce40a77552281d282bb60c0b56\
|
||||
fd2464c335543936521c24403085d59a\
|
||||
449a5037514a879d",
|
||||
},
|
||||
];
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────────────
|
||||
// XChaCha20-Poly1305 extended-nonce probe inputs
|
||||
//
|
||||
// These are used by `run_xchacha20_kat()` to compute and print the expected
|
||||
// ciphertext+tag. On first run the printed value is verified against
|
||||
// draft-irtf-cfrg-xchacha-03 §A.3 and then pinned in the AEAD_VECS above.
|
||||
//
|
||||
// Nonce is 24 bytes (the defining property of XChaCha20).
|
||||
// ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Key-agreement-test input for XChaCha20-Poly1305 extended-nonce path.
|
||||
struct XChaChaProbe {
|
||||
name: &'static str,
|
||||
key: &'static str, // 32 bytes
|
||||
nonce: &'static str, // 24 bytes ← extended nonce distinguishes XChaCha
|
||||
aad: &'static str,
|
||||
pt: &'static str,
|
||||
}
|
||||
|
||||
static XCHACHA20_PROBES: &[XChaChaProbe] = &[
|
||||
// Uses the same key/aad/pt as the ChaCha20 RFC 8439 §2.8.2 vector but
|
||||
// extends the nonce to 24 bytes, exercising the HChaCha20 subkey path.
|
||||
XChaChaProbe {
|
||||
name: "XChaCha20-Poly1305 extended-nonce roundtrip",
|
||||
key: "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
|
||||
nonce: "404142434445464748494a4b4c4d4e4f5051525354555657", // 24 bytes
|
||||
aad: "50515253c0c1c2c3c4c5c6c7",
|
||||
pt: "4c616469657320616e642047656e746c656d656e206f662074686520636c617373\
|
||||
206f6620273939", // "Ladies and Gentlemen of the class of '99"
|
||||
},
|
||||
];
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────────────
|
||||
// Helpers
|
||||
// ──────────────────────────────────────────────────────────────────────────────
|
||||
|
|
@ -309,6 +409,130 @@ fn run_hash(p: &WolfSslProvider, failures: &mut usize) {
|
|||
}
|
||||
}
|
||||
|
||||
/// RFC 7748 X25519/X448 DH known-answer tests.
|
||||
///
|
||||
/// For each vector we verify two DH directions:
|
||||
/// - `decapsulate(alice_priv, bob_pub)` == expected_shared
|
||||
/// - `decapsulate(bob_priv, alice_pub)` == expected_shared
|
||||
///
|
||||
/// In our KEM API the "ciphertext" passed to `decapsulate` is the peer's
|
||||
/// ephemeral public key — identical to a raw DH operation, which is exactly
|
||||
/// what X25519/X448 encapsulation performs.
|
||||
fn run_kem(p: &WolfSslProvider, failures: &mut usize) {
|
||||
println!("\n── KEM DH (RFC 7748) ────────────────────────────────────────────────");
|
||||
for v in KEM_DH_VECS {
|
||||
let alice_priv = from_hex(v.alice_private);
|
||||
let alice_pub = from_hex(v.alice_public);
|
||||
let bob_priv = from_hex(v.bob_private);
|
||||
let bob_pub = from_hex(v.bob_public);
|
||||
let expected = from_hex(v.expected_shared);
|
||||
|
||||
// Alice→Bob direction.
|
||||
let test_a = format!("{} (Alice→Bob)", v.name);
|
||||
match p.decapsulate(v.algo, &alice_priv, &bob_pub) {
|
||||
Ok(shared) if shared.as_slice() == expected.as_slice() => pass(&test_a),
|
||||
Ok(shared) => { *failures += 1; fail(&test_a, &shared, &expected); }
|
||||
Err(e) => { *failures += 1; println!(" [FAIL] {} ({e})", test_a); }
|
||||
}
|
||||
|
||||
// Bob→Alice direction (symmetric).
|
||||
let test_b = format!("{} (Bob→Alice)", v.name);
|
||||
match p.decapsulate(v.algo, &bob_priv, &alice_pub) {
|
||||
Ok(shared) if shared.as_slice() == expected.as_slice() => pass(&test_b),
|
||||
Ok(shared) => { *failures += 1; fail(&test_b, &shared, &expected); }
|
||||
Err(e) => { *failures += 1; println!(" [FAIL] {} ({e})", test_b); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// KEM ephemeral roundtrip self-consistency check.
|
||||
///
|
||||
/// Generates a random key pair per algorithm, encapsulates and decapsulates,
|
||||
/// and confirms the shared secret is identical. Not a KAT — validates the
|
||||
/// encap/decap path is wired correctly end-to-end.
|
||||
fn run_kem_roundtrip(p: &WolfSslProvider, failures: &mut usize) {
|
||||
println!("\n── KEM Roundtrip ────────────────────────────────────────────────────");
|
||||
for algo in [KemAlgorithm::X25519, KemAlgorithm::X448] {
|
||||
let name = format!("{:?} ephemeral roundtrip", algo);
|
||||
match p.generate_keypair(algo) {
|
||||
Err(e) => { *failures += 1; println!(" [FAIL] {} (keygen: {e})", name); continue; }
|
||||
Ok(kp) => {
|
||||
match p.encapsulate(algo, &kp.public_key) {
|
||||
Err(e) => { *failures += 1; println!(" [FAIL] {} (encap: {e})", name); }
|
||||
Ok(encap) => {
|
||||
match p.decapsulate(algo, &kp.private_key, &encap.ciphertext) {
|
||||
Err(e) => { *failures += 1; println!(" [FAIL] {} (decap: {e})", name); }
|
||||
Ok(decap_secret) => {
|
||||
if decap_secret.as_slice() == encap.shared_secret.as_slice() {
|
||||
pass(&name);
|
||||
} else {
|
||||
*failures += 1;
|
||||
println!(" [FAIL] {} (shared-secret mismatch)", name);
|
||||
println!(" encap: {}", hex::encode(&encap.shared_secret));
|
||||
println!(" decap: {}", hex::encode(&decap_secret));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// XChaCha20-Poly1305 extended-nonce functional conformance tests.
|
||||
///
|
||||
/// This function runs two sub-checks per probe input:
|
||||
///
|
||||
/// 1. **Roundtrip**: encrypt → decrypt → compare to original plaintext.
|
||||
/// 2. **Auth failure**: tamper one byte of the ciphertext+tag and confirm
|
||||
/// that `decrypt` returns `AuthenticationFailed`.
|
||||
///
|
||||
/// The output ciphertext+tag is also printed in hex so the caller can pin it
|
||||
/// as a known-answer test once verified against an external reference
|
||||
/// (e.g. libsodium or the draft-irtf-cfrg-xchacha-03 §A.3 appendix).
|
||||
fn run_xchacha20_kat(p: &WolfSslProvider, failures: &mut usize) {
|
||||
println!("\n── XChaCha20-Poly1305 extended-nonce ────────────────────────────────");
|
||||
for v in XCHACHA20_PROBES {
|
||||
let key = from_hex(v.key);
|
||||
let nonce = from_hex(v.nonce);
|
||||
let aad = from_hex(v.aad);
|
||||
let pt = from_hex(v.pt);
|
||||
|
||||
// ── Roundtrip ──────────────────────────────────────────────────────
|
||||
let rt_name = format!("{} [roundtrip]", v.name);
|
||||
match p.encrypt_aead(AeadAlgorithm::XChaCha20Poly1305, &key, &nonce, &pt, &aad) {
|
||||
Err(e) => {
|
||||
*failures += 1;
|
||||
println!(" [FAIL] {} (encrypt: {e})", rt_name);
|
||||
continue;
|
||||
}
|
||||
Ok(ct_tag) => {
|
||||
// Print the ct_tag for pinning purposes.
|
||||
println!(" [INFO] {} ct_tag = {}", v.name, hex::encode(&ct_tag));
|
||||
|
||||
match p.decrypt_aead(AeadAlgorithm::XChaCha20Poly1305, &key, &nonce, &ct_tag, &aad) {
|
||||
Ok(recovered) if recovered == pt => pass(&rt_name),
|
||||
Ok(_) => { *failures += 1; println!(" [FAIL] {} (decrypt mismatch)", rt_name); }
|
||||
Err(e) => { *failures += 1; println!(" [FAIL] {} (decrypt error: {e})", rt_name); }
|
||||
}
|
||||
|
||||
// ── Auth-failure check ────────────────────────────────────
|
||||
if !ct_tag.is_empty() {
|
||||
let auth_name = format!("{} [auth-fail]", v.name);
|
||||
let mut tampered = ct_tag.clone();
|
||||
*tampered.last_mut().unwrap() ^= 0xff;
|
||||
match p.decrypt_aead(AeadAlgorithm::XChaCha20Poly1305, &key, &nonce, &tampered, &aad) {
|
||||
Err(ccc_crypto_core::error::CryptoError::AuthenticationFailed) => pass(&auth_name),
|
||||
Ok(_) => { *failures += 1; println!(" [FAIL] {} (expected auth failure, got Ok)", auth_name); }
|
||||
Err(e) => { *failures += 1; println!(" [FAIL] {} (wrong error type: {e})", auth_name); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────────────
|
||||
// Entry point
|
||||
// ──────────────────────────────────────────────────────────────────────────────
|
||||
|
|
@ -329,6 +553,9 @@ fn main() {
|
|||
run_kdf(&p, &mut failures);
|
||||
run_mac(&p, &mut failures);
|
||||
run_hash(&p, &mut failures);
|
||||
run_kem(&p, &mut failures);
|
||||
run_kem_roundtrip(&p, &mut failures);
|
||||
run_xchacha20_kat(&p, &mut failures);
|
||||
|
||||
println!();
|
||||
if failures == 0 {
|
||||
|
|
|
|||
Loading…
Reference in New Issue