This commit is contained in:
2025-04-19 18:59:47 +02:00
parent 3c65e57676
commit 8d707e61a2
21 changed files with 2170 additions and 1 deletions

55
src/api/keypair.rs Normal file
View File

@@ -0,0 +1,55 @@
//! Public API for keypair operations.
use crate::core::keypair;
use crate::core::error::CryptoError;
/// Initializes a new keypair for signing and verification.
///
/// # Returns
///
/// * `Ok(())` if the keypair was initialized successfully.
/// * `Err(CryptoError::KeypairAlreadyInitialized)` if a keypair was already initialized.
pub fn new() -> Result<(), CryptoError> {
keypair::keypair_new()
}
/// Gets the public key of the initialized keypair.
///
/// # Returns
///
/// * `Ok(Vec<u8>)` containing the public key bytes.
/// * `Err(CryptoError::KeypairNotInitialized)` if no keypair has been initialized.
pub fn pub_key() -> Result<Vec<u8>, CryptoError> {
keypair::keypair_pub_key()
}
/// Signs a message using the initialized keypair.
///
/// # Arguments
///
/// * `message` - The message to sign.
///
/// # Returns
///
/// * `Ok(Vec<u8>)` containing the signature bytes.
/// * `Err(CryptoError::KeypairNotInitialized)` if no keypair has been initialized.
pub fn sign(message: &[u8]) -> Result<Vec<u8>, CryptoError> {
keypair::keypair_sign(message)
}
/// Verifies a signature against a message.
///
/// # Arguments
///
/// * `message` - The message that was signed.
/// * `signature` - The signature to verify.
///
/// # Returns
///
/// * `Ok(true)` if the signature is valid.
/// * `Ok(false)` if the signature is invalid.
/// * `Err(CryptoError::KeypairNotInitialized)` if no keypair has been initialized.
/// * `Err(CryptoError::SignatureFormatError)` if the signature format is invalid.
pub fn verify(message: &[u8], signature: &[u8]) -> Result<bool, CryptoError> {
keypair::keypair_verify(message, signature)
}

9
src/api/mod.rs Normal file
View File

@@ -0,0 +1,9 @@
//! Public API for cryptographic operations.
pub mod keypair;
pub mod symmetric;
// Re-export commonly used items for external users
// (Keeping this even though it's currently unused, as it's good practice for public APIs)
#[allow(unused_imports)]
pub use crate::core::error::CryptoError;

49
src/api/symmetric.rs Normal file
View File

@@ -0,0 +1,49 @@
//! Public API for symmetric encryption operations.
use crate::core::symmetric;
use crate::core::error::CryptoError;
/// Generates a random 32-byte symmetric key.
///
/// # Returns
///
/// A 32-byte array containing the random key.
pub fn generate_key() -> [u8; 32] {
symmetric::generate_symmetric_key()
}
/// Encrypts data using ChaCha20Poly1305.
///
/// A random nonce is generated internally and appended to the ciphertext.
///
/// # Arguments
///
/// * `key` - The encryption key (should be 32 bytes).
/// * `message` - The message to encrypt.
///
/// # Returns
///
/// * `Ok(Vec<u8>)` containing the ciphertext with the nonce appended.
/// * `Err(CryptoError::InvalidKeyLength)` if the key length is invalid.
/// * `Err(CryptoError::EncryptionFailed)` if encryption fails.
pub fn encrypt(key: &[u8], message: &[u8]) -> Result<Vec<u8>, CryptoError> {
symmetric::encrypt_symmetric(key, message)
}
/// Decrypts data using ChaCha20Poly1305.
///
/// The nonce is extracted from the end of the ciphertext.
///
/// # Arguments
///
/// * `key` - The decryption key (should be 32 bytes).
/// * `ciphertext` - The ciphertext with the nonce appended.
///
/// # Returns
///
/// * `Ok(Vec<u8>)` containing the decrypted message.
/// * `Err(CryptoError::InvalidKeyLength)` if the key length is invalid.
/// * `Err(CryptoError::DecryptionFailed)` if decryption fails or the ciphertext is too short.
pub fn decrypt(key: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, CryptoError> {
symmetric::decrypt_symmetric(key, ciphertext)
}

55
src/core/error.rs Normal file
View File

@@ -0,0 +1,55 @@
//! Error types for cryptographic operations.
/// Errors that can occur during cryptographic operations.
#[derive(Debug)]
pub enum CryptoError {
/// The keypair has not been initialized.
KeypairNotInitialized,
/// The keypair has already been initialized.
KeypairAlreadyInitialized,
/// Signature verification failed.
#[allow(dead_code)]
SignatureVerificationFailed,
/// The signature format is invalid.
SignatureFormatError,
/// Encryption operation failed.
EncryptionFailed,
/// Decryption operation failed.
DecryptionFailed,
/// The key length is invalid.
InvalidKeyLength,
/// Other error with description.
#[allow(dead_code)]
Other(String),
}
impl std::fmt::Display for CryptoError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CryptoError::KeypairNotInitialized => write!(f, "Keypair not initialized"),
CryptoError::KeypairAlreadyInitialized => write!(f, "Keypair already initialized"),
CryptoError::SignatureVerificationFailed => write!(f, "Signature verification failed"),
CryptoError::SignatureFormatError => write!(f, "Invalid signature format"),
CryptoError::EncryptionFailed => write!(f, "Encryption failed"),
CryptoError::DecryptionFailed => write!(f, "Decryption failed"),
CryptoError::InvalidKeyLength => write!(f, "Invalid key length"),
CryptoError::Other(s) => write!(f, "Crypto error: {}", s),
}
}
}
impl std::error::Error for CryptoError {}
/// Converts a CryptoError to an i32 status code for WebAssembly.
pub fn error_to_status_code(err: CryptoError) -> i32 {
match err {
CryptoError::KeypairNotInitialized => -1,
CryptoError::KeypairAlreadyInitialized => -2,
CryptoError::SignatureVerificationFailed => -3,
CryptoError::SignatureFormatError => -4,
CryptoError::EncryptionFailed => -5,
CryptoError::DecryptionFailed => -6,
CryptoError::InvalidKeyLength => -7,
CryptoError::Other(_) => -99,
}
}

87
src/core/keypair.rs Normal file
View File

@@ -0,0 +1,87 @@
//! Core implementation of keypair functionality.
use k256::ecdsa::{SigningKey, VerifyingKey, signature::{Signer, Verifier}, Signature};
use once_cell::sync::OnceCell;
use rand::rngs::OsRng;
use super::error::CryptoError;
/// A keypair for signing and verifying messages.
#[derive(Debug)]
pub struct KeyPair {
pub verifying_key: VerifyingKey,
pub signing_key: SigningKey,
}
/// Global keypair instance.
pub static KEYPAIR: OnceCell<KeyPair> = OnceCell::new();
/// Initializes the global keypair.
///
/// # Returns
///
/// * `Ok(())` if the keypair was initialized successfully.
/// * `Err(CryptoError::KeypairAlreadyInitialized)` if the keypair was already initialized.
pub fn keypair_new() -> Result<(), CryptoError> {
let signing_key = SigningKey::random(&mut OsRng);
let verifying_key = VerifyingKey::from(&signing_key);
let keypair = KeyPair { verifying_key, signing_key };
KEYPAIR.set(keypair).map_err(|_| CryptoError::KeypairAlreadyInitialized)
}
/// Gets the public key bytes.
///
/// # Returns
///
/// * `Ok(Vec<u8>)` containing the public key bytes.
/// * `Err(CryptoError::KeypairNotInitialized)` if the keypair has not been initialized.
pub fn keypair_pub_key() -> Result<Vec<u8>, CryptoError> {
KEYPAIR.get()
.ok_or(CryptoError::KeypairNotInitialized)
.map(|kp| kp.verifying_key.to_sec1_bytes().to_vec())
}
/// Signs a message.
///
/// # Arguments
///
/// * `message` - The message to sign.
///
/// # Returns
///
/// * `Ok(Vec<u8>)` containing the signature bytes.
/// * `Err(CryptoError::KeypairNotInitialized)` if the keypair has not been initialized.
pub fn keypair_sign(message: &[u8]) -> Result<Vec<u8>, CryptoError> {
KEYPAIR.get()
.ok_or(CryptoError::KeypairNotInitialized)
.map(|kp| {
let signature: Signature = kp.signing_key.sign(message);
signature.to_bytes().to_vec()
})
}
/// Verifies a message signature.
///
/// # Arguments
///
/// * `message` - The message that was signed.
/// * `signature_bytes` - The signature to verify.
///
/// # Returns
///
/// * `Ok(true)` if the signature is valid.
/// * `Ok(false)` if the signature is invalid.
/// * `Err(CryptoError::KeypairNotInitialized)` if the keypair has not been initialized.
/// * `Err(CryptoError::SignatureFormatError)` if the signature format is invalid.
pub fn keypair_verify(message: &[u8], signature_bytes: &[u8]) -> Result<bool, CryptoError> {
let keypair = KEYPAIR.get().ok_or(CryptoError::KeypairNotInitialized)?;
let signature = Signature::from_bytes(signature_bytes.into())
.map_err(|_| CryptoError::SignatureFormatError)?;
match keypair.verifying_key.verify(message, &signature) {
Ok(_) => Ok(true),
Err(_) => Ok(false), // Verification failed, but operation was successful
}
}

10
src/core/mod.rs Normal file
View File

@@ -0,0 +1,10 @@
//! Core cryptographic functionality.
pub mod error;
pub mod keypair;
pub mod symmetric;
// Re-export commonly used items for internal use
// (Keeping this even though it's currently unused, as it's good practice for internal modules)
#[allow(unused_imports)]
pub use error::CryptoError;

90
src/core/symmetric.rs Normal file
View File

@@ -0,0 +1,90 @@
//! Core implementation of symmetric encryption functionality.
use chacha20poly1305::{ChaCha20Poly1305, KeyInit, Nonce};
use chacha20poly1305::aead::Aead;
use rand::{rngs::OsRng, RngCore};
use super::error::CryptoError;
/// The size of the nonce in bytes.
const NONCE_SIZE: usize = 12;
/// Generates a random 32-byte symmetric key.
///
/// # Returns
///
/// A 32-byte array containing the random key.
pub fn generate_symmetric_key() -> [u8; 32] {
let mut key = [0u8; 32];
OsRng.fill_bytes(&mut key);
key
}
/// Encrypts data using ChaCha20Poly1305 with an internally generated nonce.
///
/// The nonce is appended to the ciphertext so it can be extracted during decryption.
///
/// # Arguments
///
/// * `key` - The encryption key (should be 32 bytes).
/// * `message` - The message to encrypt.
///
/// # Returns
///
/// * `Ok(Vec<u8>)` containing the ciphertext with the nonce appended.
/// * `Err(CryptoError::InvalidKeyLength)` if the key length is invalid.
/// * `Err(CryptoError::EncryptionFailed)` if encryption fails.
pub fn encrypt_symmetric(key: &[u8], message: &[u8]) -> Result<Vec<u8>, CryptoError> {
// Create cipher
let cipher = ChaCha20Poly1305::new_from_slice(key)
.map_err(|_| CryptoError::InvalidKeyLength)?;
// Generate random nonce
let mut nonce_bytes = [0u8; NONCE_SIZE];
OsRng.fill_bytes(&mut nonce_bytes);
let nonce = Nonce::from_slice(&nonce_bytes);
// Encrypt message
let ciphertext = cipher.encrypt(nonce, message)
.map_err(|_| CryptoError::EncryptionFailed)?;
// Append nonce to ciphertext
let mut result = ciphertext;
result.extend_from_slice(&nonce_bytes);
Ok(result)
}
/// Decrypts data using ChaCha20Poly1305, extracting the nonce from the ciphertext.
///
/// # Arguments
///
/// * `key` - The decryption key (should be 32 bytes).
/// * `ciphertext_with_nonce` - The ciphertext with the nonce appended.
///
/// # Returns
///
/// * `Ok(Vec<u8>)` containing the decrypted message.
/// * `Err(CryptoError::InvalidKeyLength)` if the key length is invalid.
/// * `Err(CryptoError::DecryptionFailed)` if decryption fails or the ciphertext is too short.
pub fn decrypt_symmetric(key: &[u8], ciphertext_with_nonce: &[u8]) -> Result<Vec<u8>, CryptoError> {
// Check if ciphertext is long enough to contain a nonce
if ciphertext_with_nonce.len() <= NONCE_SIZE {
return Err(CryptoError::DecryptionFailed);
}
// Extract nonce from the end of ciphertext
let ciphertext_len = ciphertext_with_nonce.len() - NONCE_SIZE;
let ciphertext = &ciphertext_with_nonce[0..ciphertext_len];
let nonce_bytes = &ciphertext_with_nonce[ciphertext_len..];
// Create cipher
let cipher = ChaCha20Poly1305::new_from_slice(key)
.map_err(|_| CryptoError::InvalidKeyLength)?;
let nonce = Nonce::from_slice(nonce_bytes);
// Decrypt message
cipher.decrypt(nonce, ciphertext)
.map_err(|_| CryptoError::DecryptionFailed)
}

72
src/lib.rs Normal file
View File

@@ -0,0 +1,72 @@
//! WebAssembly module for cryptographic operations.
use wasm_bindgen::prelude::*;
use web_sys::console;
// Import modules
mod api;
mod core;
mod tests;
// Re-export for internal use
use api::keypair;
use api::symmetric;
use core::error::error_to_status_code;
// This is like the `main` function, except for JavaScript.
#[wasm_bindgen(start)]
pub fn main_js() -> Result<(), JsValue> {
// This provides better error messages in debug mode.
// It's disabled in release mode so it doesn't bloat up the file size.
#[cfg(debug_assertions)]
console_error_panic_hook::set_once();
console::log_1(&JsValue::from_str("Crypto module initialized"));
Ok(())
}
// --- WebAssembly Exports ---
#[wasm_bindgen]
pub fn keypair_new() -> i32 {
match keypair::new() {
Ok(_) => 0, // Success
Err(e) => error_to_status_code(e),
}
}
#[wasm_bindgen]
pub fn keypair_pub_key() -> Result<Vec<u8>, JsValue> {
keypair::pub_key()
.map_err(|e| JsValue::from_str(&e.to_string()))
}
#[wasm_bindgen]
pub fn keypair_sign(message: &[u8]) -> Result<Vec<u8>, JsValue> {
keypair::sign(message)
.map_err(|e| JsValue::from_str(&e.to_string()))
}
#[wasm_bindgen]
pub fn keypair_verify(message: &[u8], signature: &[u8]) -> Result<bool, JsValue> {
keypair::verify(message, signature)
.map_err(|e| JsValue::from_str(&e.to_string()))
}
#[wasm_bindgen]
pub fn generate_symmetric_key() -> Vec<u8> {
symmetric::generate_key().to_vec()
}
#[wasm_bindgen]
pub fn encrypt_symmetric(key: &[u8], message: &[u8]) -> Result<Vec<u8>, JsValue> {
symmetric::encrypt(key, message)
.map_err(|e| JsValue::from_str(&e.to_string()))
}
#[wasm_bindgen]
pub fn decrypt_symmetric(key: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, JsValue> {
symmetric::decrypt(key, ciphertext)
.map_err(|e| JsValue::from_str(&e.to_string()))
}

297
src/mod.rs Normal file
View File

@@ -0,0 +1,297 @@
// src/core/mod.rs - Core cryptographic logic (independent of WASM interface)
use k256::ecdsa::{SigningKey, VerifyingKey, signature::{Signer, Verifier}, Signature};
use rand::rngs::OsRng;
use chacha20poly1305::{ChaCha20Poly1305, Nonce, KeyInit};
use chacha20poly1305::aead::Aead;
use once_cell::sync::OnceCell;
use rand::RngCore;
// Define errors for clarity
#[derive(Debug)]
pub enum CryptoError {
KeypairNotInitialized,
KeypairAlreadyInitialized,
SignatureVerificationFailed,
SignatureFormatError,
EncryptionFailed,
DecryptionFailed,
InvalidKeyLength,
Other(String),
}
impl std::fmt::Display for CryptoError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CryptoError::KeypairNotInitialized => write!(f, "Keypair not initialized"),
CryptoError::KeypairAlreadyInitialized => write!(f, "Keypair already initialized"),
CryptoError::SignatureVerificationFailed => write!(f, "Signature verification failed"),
CryptoError::SignatureFormatError => write!(f, "Invalid signature format"),
CryptoError::EncryptionFailed => write!(f, "Encryption failed"),
CryptoError::DecryptionFailed => write!(f, "Decryption failed"),
CryptoError::InvalidKeyLength => write!(f, "Invalid key length"),
CryptoError::Other(s) => write!(f, "Crypto error: {}", s),
}
}
}
impl std::error::Error for CryptoError {}
// --- Core KeyPair Logic ---
#[derive(Debug)] // Added Debug for OnceCell initialization
struct KeyPair {
verifying_key: VerifyingKey,
signing_key: SigningKey,
}
static KEYPAIR: OnceCell<KeyPair> = OnceCell::new();
/// Initializes the global keypair. Returns error if already initialized.
pub fn keypair_new_internal() -> Result<(), CryptoError> {
let signing_key = SigningKey::random(&mut OsRng);
let verifying_key = VerifyingKey::from(&signing_key);
let keypair = KeyPair { verifying_key, signing_key };
KEYPAIR.set(keypair).map_err(|_| CryptoError::KeypairAlreadyInitialized)
}
/// Gets the public key bytes. Returns error if not initialized.
pub fn keypair_pub_key_internal() -> Result<Vec<u8>, CryptoError> {
KEYPAIR.get()
.ok_or(CryptoError::KeypairNotInitialized)
.map(|kp| kp.verifying_key.to_sec1_bytes().to_vec())
}
/// Signs a message. Returns error if not initialized.
pub fn keypair_sign_internal(message: &[u8]) -> Result<Vec<u8>, CryptoError> {
KEYPAIR.get()
.ok_or(CryptoError::KeypairNotInitialized)
.map(|kp| {
let signature: Signature = kp.signing_key.sign(message);
signature.to_bytes().to_vec()
})
}
/// Verifies a message signature. Returns Ok(true/false) or error if not initialized or sig format invalid.
pub fn keypair_verify_internal(message: &[u8], signature_bytes: &[u8]) -> Result<bool, CryptoError> {
let keypair = KEYPAIR.get().ok_or(CryptoError::KeypairNotInitialized)?;
let signature = Signature::from_bytes(signature_bytes.into())
.map_err(|_| CryptoError::SignatureFormatError)?;
match keypair.verifying_key.verify(message, &signature) {
Ok(_) => Ok(true),
Err(_) => Ok(false), // Verification failed, but operation was successful
}
}
// --- Core Symmetric Crypto Logic ---
/// Generates a random 12-byte nonce.
pub fn generate_nonce_internal() -> [u8; 12] {
let mut nonce = [0u8; 12];
OsRng.fill_bytes(&mut nonce);
nonce
}
/// Generates a random 32-byte symmetric key.
pub fn generate_symmetric_key_internal() -> [u8; 32] {
let mut key = [0u8; 32];
OsRng.fill_bytes(&mut key);
key
}
/// Encrypts data using ChaCha20Poly1305.
pub fn encrypt_symmetric_internal(key: &[u8], message: &[u8], nonce_bytes: &[u8]) -> Result<Vec<u8>, CryptoError> {
let cipher = ChaCha20Poly1305::new_from_slice(key)
.map_err(|_| CryptoError::InvalidKeyLength)?;
let nonce = Nonce::from_slice(nonce_bytes); // Assumes nonce_bytes is correct length (12)
cipher.encrypt(nonce, message)
.map_err(|_| CryptoError::EncryptionFailed)
}
/// Decrypts data using ChaCha20Poly1305.
pub fn decrypt_symmetric_internal(key: &[u8], ciphertext: &[u8], nonce_bytes: &[u8]) -> Result<Vec<u8>, CryptoError> {
let cipher = ChaCha20Poly1305::new_from_slice(key)
.map_err(|_| CryptoError::InvalidKeyLength)?;
let nonce = Nonce::from_slice(nonce_bytes); // Assumes nonce_bytes is correct length (12)
cipher.decrypt(nonce, ciphertext)
.map_err(|_| CryptoError::DecryptionFailed)
}
// Helper to convert CryptoError to an i32 status code for C ABI
pub fn error_to_status_code(err: CryptoError) -> i32 {
match err {
CryptoError::KeypairNotInitialized => -1,
CryptoError::KeypairAlreadyInitialized => -2,
CryptoError::SignatureVerificationFailed => -3, // Note: verify_internal returns Ok(false) for this
CryptoError::SignatureFormatError => -4,
CryptoError::EncryptionFailed => -5,
CryptoError::DecryptionFailed => -6,
CryptoError::InvalidKeyLength => -7,
CryptoError::Other(_) => -99,
}
}
// Helper for C ABI functions needing output buffers
// Copies data, writes actual length, returns 0 on success or negative error code
// Assumes out_ptr and out_len_ptr are valid
pub unsafe fn copy_to_output(data: &[u8], out_ptr: *mut u8, out_len_ptr: *mut u32) -> i32 {
let len = data.len();
std::ptr::write(out_len_ptr, len as u32);
std::ptr::copy_nonoverlapping(data.as_ptr(), out_ptr, len);
0 // Success
}
#[cfg(test)]
mod tests {
use super::*;
// Helper to ensure keypair is initialized for tests that need it.
// Panics if initialization fails (which it shouldn't unless tests run weirdly).
fn ensure_keypair_initialized() {
// Use try_init which doesn't panic if already initialized
let _ = keypair_new_internal();
assert!(KEYPAIR.get().is_some(), "KEYPAIR should be initialized");
}
#[test]
fn test_keypair_generation_and_retrieval() {
// Resetting global state in tests is hard.
// We'll test that we *can* initialize and get the key.
// Note: This relies on test execution order or isolation if KEYPAIR could be set elsewhere.
// For simple cases, calling new might return Err if already set by another test,
// but get should still work.
let _ = keypair_new_internal(); // Ignore error if already initialized by another test
let pub_key = keypair_pub_key_internal().expect("Should be able to get pub key after init");
assert!(!pub_key.is_empty(), "Public key should not be empty");
// Basic check for SEC1 format (0x02, 0x03, or 0x04 prefix)
assert!(pub_key.len() == 33 || pub_key.len() == 65, "Public key length is incorrect");
assert!(pub_key[0] == 0x02 || pub_key[0] == 0x03 || pub_key[0] == 0x04, "Invalid SEC1 format start byte");
}
#[test]
fn test_sign_verify_valid() {
ensure_keypair_initialized();
let message = b"this is a test message";
let signature = keypair_sign_internal(message).expect("Signing failed");
assert!(!signature.is_empty(), "Signature should not be empty");
let is_valid = keypair_verify_internal(message, &signature).expect("Verification failed");
assert!(is_valid, "Signature should be valid");
}
#[test]
fn test_verify_invalid_signature() {
ensure_keypair_initialized();
let message = b"another test message";
let mut invalid_signature = keypair_sign_internal(message).expect("Signing failed");
// Tamper with the signature
invalid_signature[0] = invalid_signature[0].wrapping_add(1);
let is_valid = keypair_verify_internal(message, &invalid_signature).expect("Verification process failed");
assert!(!is_valid, "Tampered signature should be invalid");
}
#[test]
fn test_verify_wrong_message() {
ensure_keypair_initialized();
let message = b"original message";
let wrong_message = b"different message";
let signature = keypair_sign_internal(message).expect("Signing failed");
let is_valid = keypair_verify_internal(wrong_message, &signature).expect("Verification process failed");
assert!(!is_valid, "Signature should be invalid for a different message");
}
#[test]
fn test_generate_nonce() {
let nonce = generate_nonce_internal();
assert_eq!(nonce.len(), 12, "Nonce length should be 12 bytes");
// Check if it's not all zeros (highly unlikely for random)
assert!(nonce.iter().any(|&byte| byte != 0), "Nonce should not be all zeros");
}
#[test]
fn test_generate_symmetric_key() {
let key = generate_symmetric_key_internal();
assert_eq!(key.len(), 32, "Symmetric key length should be 32 bytes");
// Check if it's not all zeros (highly unlikely for random)
assert!(key.iter().any(|&byte| byte != 0), "Key should not be all zeros");
}
#[test]
fn test_encrypt_decrypt_symmetric() {
let key = generate_symmetric_key_internal();
let nonce = generate_nonce_internal();
let message = b"super secret data";
let ciphertext = encrypt_symmetric_internal(&key, message, &nonce)
.expect("Encryption failed");
assert_ne!(message, ciphertext.as_slice(), "Ciphertext should be different from message");
let decrypted_message = decrypt_symmetric_internal(&key, &ciphertext, &nonce)
.expect("Decryption failed");
assert_eq!(message, decrypted_message.as_slice(), "Decrypted message should match original");
}
#[test]
fn test_decrypt_wrong_key() {
let key1 = generate_symmetric_key_internal();
let key2 = generate_symmetric_key_internal(); // Different key
let nonce = generate_nonce_internal();
let message = b"data for key1";
let ciphertext = encrypt_symmetric_internal(&key1, message, &nonce)
.expect("Encryption failed");
let result = decrypt_symmetric_internal(&key2, &ciphertext, &nonce);
assert!(result.is_err(), "Decryption should fail with the wrong key");
assert!(matches!(result.unwrap_err(), CryptoError::DecryptionFailed), "Error should be DecryptionFailed");
}
#[test]
fn test_decrypt_wrong_nonce() {
let key = generate_symmetric_key_internal();
let nonce1 = generate_nonce_internal();
let nonce2 = generate_nonce_internal(); // Different nonce
let message = b"data with nonce1";
let ciphertext = encrypt_symmetric_internal(&key, message, &nonce1)
.expect("Encryption failed");
let result = decrypt_symmetric_internal(&key, &ciphertext, &nonce2);
assert!(result.is_err(), "Decryption should fail with the wrong nonce");
assert!(matches!(result.unwrap_err(), CryptoError::DecryptionFailed), "Error should be DecryptionFailed");
}
#[test]
fn test_decrypt_tampered_ciphertext() {
let key = generate_symmetric_key_internal();
let nonce = generate_nonce_internal();
let message = b"important data";
let mut ciphertext = encrypt_symmetric_internal(&key, message, &nonce)
.expect("Encryption failed");
// Tamper with ciphertext (e.g., flip a bit)
if !ciphertext.is_empty() {
ciphertext[0] ^= 0x01;
} else {
panic!("Ciphertext is empty, cannot tamper");
}
let result = decrypt_symmetric_internal(&key, &ciphertext, &nonce);
assert!(result.is_err(), "Decryption should fail with tampered ciphertext");
assert!(matches!(result.unwrap_err(), CryptoError::DecryptionFailed), "Error should be DecryptionFailed");
}
}

View File

@@ -0,0 +1,57 @@
//! Tests for keypair functionality.
#[cfg(test)]
mod tests {
use crate::core::keypair;
// Helper to ensure keypair is initialized for tests that need it.
fn ensure_keypair_initialized() {
// Use try_init which doesn't panic if already initialized
let _ = keypair::keypair_new();
assert!(keypair::KEYPAIR.get().is_some(), "KEYPAIR should be initialized");
}
#[test]
fn test_keypair_generation_and_retrieval() {
let _ = keypair::keypair_new(); // Ignore error if already initialized by another test
let pub_key = keypair::keypair_pub_key().expect("Should be able to get pub key after init");
assert!(!pub_key.is_empty(), "Public key should not be empty");
// Basic check for SEC1 format (0x02, 0x03, or 0x04 prefix)
assert!(pub_key.len() == 33 || pub_key.len() == 65, "Public key length is incorrect");
assert!(pub_key[0] == 0x02 || pub_key[0] == 0x03 || pub_key[0] == 0x04, "Invalid SEC1 format start byte");
}
#[test]
fn test_sign_verify_valid() {
ensure_keypair_initialized();
let message = b"this is a test message";
let signature = keypair::keypair_sign(message).expect("Signing failed");
assert!(!signature.is_empty(), "Signature should not be empty");
let is_valid = keypair::keypair_verify(message, &signature).expect("Verification failed");
assert!(is_valid, "Signature should be valid");
}
#[test]
fn test_verify_invalid_signature() {
ensure_keypair_initialized();
let message = b"another test message";
let mut invalid_signature = keypair::keypair_sign(message).expect("Signing failed");
// Tamper with the signature
invalid_signature[0] = invalid_signature[0].wrapping_add(1);
let is_valid = keypair::keypair_verify(message, &invalid_signature).expect("Verification process failed");
assert!(!is_valid, "Tampered signature should be invalid");
}
#[test]
fn test_verify_wrong_message() {
ensure_keypair_initialized();
let message = b"original message";
let wrong_message = b"different message";
let signature = keypair::keypair_sign(message).expect("Signing failed");
let is_valid = keypair::keypair_verify(wrong_message, &signature).expect("Verification process failed");
assert!(!is_valid, "Signature should be invalid for a different message");
}
}

7
src/tests/mod.rs Normal file
View File

@@ -0,0 +1,7 @@
//! Test modules for cryptographic functionality.
#[cfg(test)]
pub mod keypair_tests;
#[cfg(test)]
pub mod symmetric_tests;

View File

@@ -0,0 +1,67 @@
//! Tests for symmetric encryption functionality.
#[cfg(test)]
mod tests {
use crate::core::symmetric;
use crate::core::error::CryptoError;
#[test]
fn test_generate_symmetric_key() {
let key = symmetric::generate_symmetric_key();
assert_eq!(key.len(), 32, "Symmetric key length should be 32 bytes");
// Check if it's not all zeros (highly unlikely for random)
assert!(key.iter().any(|&byte| byte != 0), "Key should not be all zeros");
}
#[test]
fn test_encrypt_decrypt_symmetric() {
let key = symmetric::generate_symmetric_key();
let message = b"super secret data";
let ciphertext = symmetric::encrypt_symmetric(&key, message)
.expect("Encryption failed");
assert_ne!(message, ciphertext.as_slice(), "Ciphertext should be different from message");
let decrypted_message = symmetric::decrypt_symmetric(&key, &ciphertext)
.expect("Decryption failed");
assert_eq!(message, decrypted_message.as_slice(), "Decrypted message should match original");
}
#[test]
fn test_decrypt_wrong_key() {
let key1 = symmetric::generate_symmetric_key();
let key2 = symmetric::generate_symmetric_key(); // Different key
let message = b"data for key1";
let ciphertext = symmetric::encrypt_symmetric(&key1, message)
.expect("Encryption failed");
let result = symmetric::decrypt_symmetric(&key2, &ciphertext);
assert!(result.is_err(), "Decryption should fail with the wrong key");
assert!(matches!(result.unwrap_err(), CryptoError::DecryptionFailed), "Error should be DecryptionFailed");
}
#[test]
fn test_decrypt_tampered_ciphertext() {
let key = symmetric::generate_symmetric_key();
let message = b"important data";
let mut ciphertext = symmetric::encrypt_symmetric(&key, message)
.expect("Encryption failed");
// Tamper with ciphertext (e.g., flip a bit)
if !ciphertext.is_empty() {
ciphertext[0] ^= 0x01;
} else {
panic!("Ciphertext is empty, cannot tamper");
}
let result = symmetric::decrypt_symmetric(&key, &ciphertext);
assert!(result.is_err(), "Decryption should fail with tampered ciphertext");
assert!(matches!(result.unwrap_err(), CryptoError::DecryptionFailed), "Error should be DecryptionFailed");
}
}