From c9b949de1f7408ef292e77fb965ef19ee38c1421 Mon Sep 17 00:00:00 2001 From: Mike Mulchrone Date: Sat, 13 Jun 2026 20:00:30 -0400 Subject: [PATCH 1/2] refactor for error handling --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/asymmetric/cas_rsa.rs | 123 +++++------ src/asymmetric/types.rs | 10 +- src/compression/zstd.rs | 18 +- src/error.rs | 62 ++++++ src/hybrid/cas_hybrid.rs | 8 +- src/hybrid/hpke.rs | 33 +-- src/key_exchange/cas_key_exchange.rs | 3 +- src/key_exchange/x25519.rs | 25 +-- src/lib.rs | 2 + src/message/cas_hmac.rs | 8 +- src/message/hmac.rs | 19 +- src/password_hashers/argon2.rs | 49 +++-- src/password_hashers/bcrypt.rs | 17 +- src/password_hashers/scrypt.rs | 35 ++-- src/pqc/ml_kem.rs | 33 ++- src/pqc/slh_dsa.rs | 24 ++- src/signatures/ed25519.rs | 56 +++-- src/sponges/ascon_aead.rs | 56 ++--- src/sponges/cas_ascon_aead.rs | 8 +- src/symmetric/aes.rs | 114 +++++++---- src/symmetric/cas_symmetric_encryption.rs | 24 ++- src/symmetric/chacha20poly1305.rs | 33 ++- tests/asymmetric.rs | 20 +- tests/compression.rs | 4 +- tests/hybrid.rs | 6 +- tests/key_exchange.rs | 4 +- tests/message.rs | 6 +- tests/password_hashers.rs | 36 ++-- tests/pqc.rs | 8 +- tests/signatures.rs | 10 +- tests/sponges.rs | 4 +- tests/symmetric.rs | 238 +++++++++++----------- 34 files changed, 645 insertions(+), 455 deletions(-) create mode 100644 src/error.rs diff --git a/Cargo.lock b/Cargo.lock index a482daa..f1d1a5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -158,7 +158,7 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cas-lib" -version = "0.2.78" +version = "0.2.79" dependencies = [ "aes-gcm", "argon2", diff --git a/Cargo.toml b/Cargo.toml index c1aa299..53fab45 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cas-lib" -version = "0.2.78" +version = "0.2.79" edition = "2021" description = "A function wrapper layer for RustCrypto and Dalek-Cryptography. Intended to be used in FFI situations with a global heap deallactor at the top level project." license = "Apache-2.0" diff --git a/src/asymmetric/cas_rsa.rs b/src/asymmetric/cas_rsa.rs index 16e0b7e..1814ece 100644 --- a/src/asymmetric/cas_rsa.rs +++ b/src/asymmetric/cas_rsa.rs @@ -1,58 +1,65 @@ -use rand::rngs::OsRng; -use rsa::{ - pkcs1::{DecodeRsaPublicKey, EncodeRsaPublicKey}, - pkcs8::{DecodePrivateKey, EncodePrivateKey}, - RsaPublicKey, -}; -use rsa::{Pkcs1v15Sign, RsaPrivateKey}; - -use super::types::{CASRSAEncryption, RSAKeyPairResult}; - -pub struct CASRSA; - -impl CASRSAEncryption for CASRSA { - /// Generates an RSA key pair of the specified size. - /// The key size must be at of a supported kind like 1024, 2048, 4096 bits. - fn generate_rsa_keys(key_size: usize) -> RSAKeyPairResult { - // TODO: do a check for key_size, if it is too small, return an error - let mut rng: OsRng = OsRng; - let private_key: RsaPrivateKey = - RsaPrivateKey::new(&mut rng, key_size).expect("failed to generate a key"); - let public_key: RsaPublicKey = private_key.to_public_key(); - let result = RSAKeyPairResult { - public_key: public_key - .to_pkcs1_pem(rsa::pkcs1::LineEnding::LF) - .unwrap() - .to_string(), - private_key: private_key - .to_pkcs8_pem(rsa::pkcs8::LineEnding::LF) - .unwrap() - .to_string(), - }; - result - } - - /// Sign the given hash with the provided private key of the RSA key pair. - /// The parameter `hash` doesn't necessarily have to be a hash, it can be any data that you want to sign. - fn sign(private_key: String, hash: Vec) -> Vec { - let private_key = RsaPrivateKey::from_pkcs8_pem(&private_key).unwrap(); - let signed_data = private_key - .sign(Pkcs1v15Sign::new_unprefixed(), &hash) - .unwrap(); - signed_data - } - - - /// Verify the signature of the given hash with the provided public key of the RSA key pair. - /// The parameter `hash` doesn't necessarily have to be a hash, it can be any data that you want to verify. - /// Returns true if the signature is valid, false otherwise. - fn verify(public_key: String, hash: Vec, signature: Vec) -> bool { - let public_key = RsaPublicKey::from_pkcs1_pem(&public_key).unwrap(); - let verified = public_key.verify(Pkcs1v15Sign::new_unprefixed(), &hash, &signature); - if verified.is_err() == false { - return true; - } else { - return false; - } - } -} \ No newline at end of file +use rand::rngs::OsRng; +use rsa::{ + pkcs1::{DecodeRsaPublicKey, EncodeRsaPublicKey}, + pkcs8::{DecodePrivateKey, EncodePrivateKey}, + RsaPublicKey, +}; +use rsa::{Pkcs1v15Sign, RsaPrivateKey}; + +use crate::error::{CasError, CasResult}; + +use super::types::{CASRSAEncryption, RSAKeyPairResult}; + +/// The smallest RSA modulus size (in bits) this library will generate. +/// Keys below 2048 bits are considered insecure. +const MIN_RSA_KEY_SIZE: usize = 2048; + +pub struct CASRSA; + +impl CASRSAEncryption for CASRSA { + /// Generates an RSA key pair of the specified size. + /// The key size must be at least 2048 bits; smaller sizes are rejected with + /// [`CasError::InvalidParameters`]. + fn generate_rsa_keys(key_size: usize) -> CasResult { + if key_size < MIN_RSA_KEY_SIZE { + return Err(CasError::InvalidParameters); + } + let mut rng: OsRng = OsRng; + let private_key: RsaPrivateKey = + RsaPrivateKey::new(&mut rng, key_size).map_err(|_| CasError::KeyGenerationFailed)?; + let public_key: RsaPublicKey = private_key.to_public_key(); + Ok(RSAKeyPairResult { + public_key: public_key + .to_pkcs1_pem(rsa::pkcs1::LineEnding::LF) + .map_err(|_| CasError::InvalidPemKey)? + .to_string(), + private_key: private_key + .to_pkcs8_pem(rsa::pkcs8::LineEnding::LF) + .map_err(|_| CasError::InvalidPemKey)? + .to_string(), + }) + } + + /// Sign the given hash with the provided private key of the RSA key pair. + /// The parameter `hash` doesn't necessarily have to be a hash, it can be any data that you want to sign. + fn sign(private_key: String, hash: Vec) -> CasResult> { + let private_key = + RsaPrivateKey::from_pkcs8_pem(&private_key).map_err(|_| CasError::InvalidPemKey)?; + private_key + .sign(Pkcs1v15Sign::new_unprefixed(), &hash) + .map_err(|_| CasError::SigningFailed) + } + + + /// Verify the signature of the given hash with the provided public key of the RSA key pair. + /// The parameter `hash` doesn't necessarily have to be a hash, it can be any data that you want to verify. + /// Returns `Ok(true)` if the signature is valid, `Ok(false)` if it is not, and an + /// error if the public key could not be parsed. + fn verify(public_key: String, hash: Vec, signature: Vec) -> CasResult { + let public_key = + RsaPublicKey::from_pkcs1_pem(&public_key).map_err(|_| CasError::InvalidPemKey)?; + Ok(public_key + .verify(Pkcs1v15Sign::new_unprefixed(), &hash, &signature) + .is_ok()) + } +} diff --git a/src/asymmetric/types.rs b/src/asymmetric/types.rs index d25f7ef..71ad84e 100644 --- a/src/asymmetric/types.rs +++ b/src/asymmetric/types.rs @@ -1,10 +1,12 @@ +use crate::error::CasResult; + pub struct RSAKeyPairResult { pub private_key: String, pub public_key: String, } pub trait CASRSAEncryption { - fn generate_rsa_keys(key_size: usize) -> RSAKeyPairResult; - fn sign(private_key: String, hash: Vec) -> Vec; - fn verify(public_key: String, hash: Vec, signed_text: Vec) -> bool; -} \ No newline at end of file + fn generate_rsa_keys(key_size: usize) -> CasResult; + fn sign(private_key: String, hash: Vec) -> CasResult>; + fn verify(public_key: String, hash: Vec, signed_text: Vec) -> CasResult; +} diff --git a/src/compression/zstd.rs b/src/compression/zstd.rs index 3c98b90..55c55f5 100644 --- a/src/compression/zstd.rs +++ b/src/compression/zstd.rs @@ -1,19 +1,23 @@ use std::io::Cursor; +use crate::error::{CasError, CasResult}; + /// Compresses data using Zstandard compression algorithm. /// The `level` parameter controls the compression level (0-22). /// Higher levels result in better compression but slower performance. -pub fn compress(data_to_compress: Vec, level: i32) -> Vec { +pub fn compress(data_to_compress: Vec, level: i32) -> CasResult> { let cursor = Cursor::new(data_to_compress); let mut compressed_data = Vec::new(); - zstd::stream::copy_encode(cursor, &mut compressed_data, level).unwrap(); - compressed_data + zstd::stream::copy_encode(cursor, &mut compressed_data, level) + .map_err(|_| CasError::CompressionFailed)?; + Ok(compressed_data) } /// Decompresses data using Zstandard decompression algorithm. -pub fn decompress(data_to_decompress: Vec) -> Vec { +pub fn decompress(data_to_decompress: Vec) -> CasResult> { let mut cursor = Cursor::new(data_to_decompress); let mut decompressed_data = Vec::new(); - zstd::stream::copy_decode(&mut cursor, &mut decompressed_data).unwrap(); - decompressed_data -} \ No newline at end of file + zstd::stream::copy_decode(&mut cursor, &mut decompressed_data) + .map_err(|_| CasError::CompressionFailed)?; + Ok(decompressed_data) +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..6574379 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,62 @@ +use std::fmt; + +/// The unified error type returned by all fallible `cas-lib` operations. +/// +/// Every cryptographic operation that can fail returns [`CasResult`] instead of +/// panicking. This is important for FFI consumers: a panic unwinding across the +/// FFI boundary is undefined behavior and typically aborts the host process. A +/// malformed key, a tampered ciphertext, or a failed authentication tag are all +/// recoverable conditions and are reported through this enum. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum CasError { + /// A provided key had an invalid length or could not be parsed. + InvalidKey, + /// A provided nonce/IV had an invalid length. + InvalidNonce, + /// A provided signature had an invalid length or could not be parsed. + InvalidSignature, + /// Input bytes could not be decoded into the expected type. + InvalidInput, + /// PEM/DER decoding or encoding of a key failed. + InvalidPemKey, + /// Invalid algorithm parameters were supplied (e.g. an RSA key size that is + /// too small, or out-of-range password-hashing parameters). + InvalidParameters, + /// AEAD encryption failed. + EncryptionFailed, + /// AEAD decryption failed or the authentication tag did not verify. + DecryptionFailed, + /// A signing operation failed. + SigningFailed, + /// Key generation failed. + KeyGenerationFailed, + /// Password hashing or verification setup failed. + PasswordHashingFailed, + /// Compression or decompression failed. + CompressionFailed, +} + +impl fmt::Display for CasError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let message = match self { + CasError::InvalidKey => "invalid key: wrong length or could not be parsed", + CasError::InvalidNonce => "invalid nonce: wrong length", + CasError::InvalidSignature => "invalid signature: wrong length or could not be parsed", + CasError::InvalidInput => "invalid input: could not be decoded", + CasError::InvalidPemKey => "invalid PEM key: could not be decoded or encoded", + CasError::InvalidParameters => "invalid algorithm parameters", + CasError::EncryptionFailed => "encryption failed", + CasError::DecryptionFailed => "decryption failed or authentication tag mismatch", + CasError::SigningFailed => "signing failed", + CasError::KeyGenerationFailed => "key generation failed", + CasError::PasswordHashingFailed => "password hashing failed", + CasError::CompressionFailed => "compression or decompression failed", + }; + f.write_str(message) + } +} + +impl std::error::Error for CasError {} + +/// The result type returned by all fallible `cas-lib` operations. +pub type CasResult = Result; diff --git a/src/hybrid/cas_hybrid.rs b/src/hybrid/cas_hybrid.rs index d6b8065..28fb362 100644 --- a/src/hybrid/cas_hybrid.rs +++ b/src/hybrid/cas_hybrid.rs @@ -1,6 +1,8 @@ +use crate::error::CasResult; + pub trait CASHybrid { fn generate_key_pair() -> (Vec, Vec, Vec); fn generate_info_str() -> Vec; - fn encrypt(plaintext: Vec, public_key: Vec, info_str: Vec) -> (Vec, Vec, Vec); - fn decrypt(ciphertext: Vec, private_key: Vec, encapped_key: Vec, tag: Vec, info_str: Vec) -> Vec; -} \ No newline at end of file + fn encrypt(plaintext: Vec, public_key: Vec, info_str: Vec) -> CasResult<(Vec, Vec, Vec)>; + fn decrypt(ciphertext: Vec, private_key: Vec, encapped_key: Vec, tag: Vec, info_str: Vec) -> CasResult>; +} diff --git a/src/hybrid/hpke.rs b/src/hybrid/hpke.rs index 8b5e2a3..5895a80 100644 --- a/src/hybrid/hpke.rs +++ b/src/hybrid/hpke.rs @@ -8,6 +8,7 @@ use hpke::{ use rand::{rngs::StdRng, SeedableRng}; use uuid::Uuid; +use crate::error::{CasError, CasResult}; use super::cas_hybrid::CASHybrid; type Kem = X25519HkdfSha256; @@ -38,60 +39,62 @@ impl CASHybrid for CASHPKE { } /// Encrypts data using HPKE with the provided public key and info string. - /// Returns the encapsulated key, ciphertext, and tag. + /// Returns the encapsulated key, ciphertext, and tag, or an error if the + /// public key could not be parsed or encryption failed. fn encrypt( plaintext: Vec, public_key: Vec, info_str: Vec, - ) -> (Vec, Vec, Vec) { + ) -> CasResult<(Vec, Vec, Vec)> { let mut csprng = StdRng::from_entropy(); let public_key = ::PublicKey::from_bytes(public_key.as_slice()) - .expect("could not deserialize server privkey!"); + .map_err(|_| CasError::InvalidKey)?; let (encapped_key, mut sender_ctx) = hpke::setup_sender::( &OpModeS::Base, &public_key, &info_str.as_slice(), &mut csprng, ) - .expect("invalid server pubkey!"); + .map_err(|_| CasError::EncryptionFailed)?; let mut msg_copy = plaintext; let tag = sender_ctx .seal_in_place_detached(&mut msg_copy, b"") - .expect("encryption failed!"); + .map_err(|_| CasError::EncryptionFailed)?; let ciphertext = msg_copy; - ( + Ok(( encapped_key.to_bytes().to_vec(), ciphertext, tag.to_bytes().to_vec(), - ) + )) } /// Decrypts data using HPKE with the provided private key, encapsulated key, tag, and info string. - /// Returns the decrypted plaintext. + /// Returns the decrypted plaintext, or an error if any input could not be + /// parsed or decryption failed. fn decrypt( ciphertext: Vec, private_key: Vec, encapped_key: Vec, tag: Vec, info_str: Vec, - ) -> Vec { + ) -> CasResult> { let server_sk = ::PrivateKey::from_bytes(&private_key) - .expect("could not deserialize server privkey!"); - let tag = AeadTag::::from_bytes(&tag).expect("could not deserialize AEAD tag!"); + .map_err(|_| CasError::InvalidKey)?; + let tag = AeadTag::::from_bytes(&tag).map_err(|_| CasError::InvalidInput)?; let encapped_key = ::EncappedKey::from_bytes(&encapped_key) - .expect("could not deserialize the encapsulated pubkey!"); + .map_err(|_| CasError::InvalidKey)?; let mut receiver_ctx = hpke::setup_receiver::( &OpModeR::Base, &server_sk, &encapped_key, &info_str, ) - .expect("failed to set up receiver!"); + .map_err(|_| CasError::DecryptionFailed)?; let mut ciphertext_copy = ciphertext; receiver_ctx .open_in_place_detached(&mut ciphertext_copy, b"", &tag) - .expect("invalid ciphertext!"); + .map_err(|_| CasError::DecryptionFailed)?; let plaintext = ciphertext_copy; - plaintext + Ok(plaintext) } } diff --git a/src/key_exchange/cas_key_exchange.rs b/src/key_exchange/cas_key_exchange.rs index 6348381..0754498 100644 --- a/src/key_exchange/cas_key_exchange.rs +++ b/src/key_exchange/cas_key_exchange.rs @@ -1,6 +1,7 @@ +use crate::error::CasResult; use super::x25519::X25519SecretPublicKeyResult; pub trait CASKeyExchange { fn generate_secret_and_public_key() -> X25519SecretPublicKeyResult; - fn diffie_hellman(my_secret_key: Vec, users_public_key: Vec) -> Vec; + fn diffie_hellman(my_secret_key: Vec, users_public_key: Vec) -> CasResult>; } diff --git a/src/key_exchange/x25519.rs b/src/key_exchange/x25519.rs index 74e0dd0..3f7f1ed 100644 --- a/src/key_exchange/x25519.rs +++ b/src/key_exchange/x25519.rs @@ -3,6 +3,7 @@ use rand::rngs::OsRng; use x25519_dalek::{PublicKey, StaticSecret}; +use crate::error::{CasError, CasResult}; use super::cas_key_exchange::CASKeyExchange; pub struct X25519SecretPublicKeyResult { @@ -26,17 +27,17 @@ impl CASKeyExchange for X25519 { } /// Performs a Diffie-Hellman key exchange using the provided secret key and user's public key. - /// Returns the shared secret as a vector of bytes. - /// The shared secret is computed as the Diffie-Hellman result of the secret key and the user's public key. + /// Returns the shared secret as a vector of bytes, or an error if either input + /// is not 32 bytes long. /// The secret key and user's public key are expected to be in byte array format. - fn diffie_hellman(my_secret_key: Vec, users_public_key: Vec) -> Vec { - let mut secret_key_box = Box::new([0u8; 32]); - secret_key_box.copy_from_slice(&my_secret_key); - let mut users_public_key_box = Box::new([0u8; 32]); - users_public_key_box.copy_from_slice(&users_public_key); - - let secret_key = StaticSecret::from(*secret_key_box); - let public_key = PublicKey::from(*users_public_key_box); - return secret_key.diffie_hellman(&public_key).as_bytes().to_vec(); + fn diffie_hellman(my_secret_key: Vec, users_public_key: Vec) -> CasResult> { + let secret_key_bytes: [u8; 32] = + my_secret_key.try_into().map_err(|_| CasError::InvalidKey)?; + let public_key_bytes: [u8; 32] = + users_public_key.try_into().map_err(|_| CasError::InvalidKey)?; + + let secret_key = StaticSecret::from(secret_key_bytes); + let public_key = PublicKey::from(public_key_bytes); + Ok(secret_key.diffie_hellman(&public_key).as_bytes().to_vec()) } -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index 466dfef..4525d03 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +pub mod error; + pub mod password_hashers { pub mod argon2; pub mod bcrypt; diff --git a/src/message/cas_hmac.rs b/src/message/cas_hmac.rs index 5849348..9daba5d 100644 --- a/src/message/cas_hmac.rs +++ b/src/message/cas_hmac.rs @@ -1,4 +1,6 @@ +use crate::error::CasResult; + pub trait CASHMAC { - fn sign(key: Vec, message: Vec) -> Vec; - fn verify(key: Vec, message: Vec, signature: Vec) -> bool; -} \ No newline at end of file + fn sign(key: Vec, message: Vec) -> CasResult>; + fn verify(key: Vec, message: Vec, signature: Vec) -> CasResult; +} diff --git a/src/message/hmac.rs b/src/message/hmac.rs index c7fe6c7..9148db7 100644 --- a/src/message/hmac.rs +++ b/src/message/hmac.rs @@ -1,4 +1,5 @@ +use crate::error::{CasError, CasResult}; use super::cas_hmac::CASHMAC; use hmac::{Hmac, Mac}; use sha2::Sha256; @@ -9,21 +10,21 @@ pub struct HMAC; impl CASHMAC for HMAC { /// Signs a message using HMAC with SHA-256. /// Returns the signature as a vector of bytes. - fn sign(key: Vec, message: Vec) -> Vec { - let mut mac = HmacSha256::new_from_slice(&key).unwrap(); + fn sign(key: Vec, message: Vec) -> CasResult> { + let mut mac = HmacSha256::new_from_slice(&key).map_err(|_| CasError::InvalidKey)?; mac.update(&message); - let result = mac.finalize().into_bytes().to_vec(); - result + Ok(mac.finalize().into_bytes().to_vec()) } - + /// Verifies a signature using HMAC with SHA-256. - /// Returns true if the signature is valid, false otherwise. - fn verify(key: Vec, message: Vec, signature: Vec) -> bool { - let mut mac = HmacSha256::new_from_slice(&key).unwrap(); + /// Returns `Ok(true)` if the signature is valid, `Ok(false)` if it is not, and + /// an error if the key could not be used. + fn verify(key: Vec, message: Vec, signature: Vec) -> CasResult { + let mut mac = HmacSha256::new_from_slice(&key).map_err(|_| CasError::InvalidKey)?; mac.update(&message); - return mac.verify_slice(&signature).is_ok(); + Ok(mac.verify_slice(&signature).is_ok()) } diff --git a/src/password_hashers/argon2.rs b/src/password_hashers/argon2.rs index c3ab92e..fb85bbb 100644 --- a/src/password_hashers/argon2.rs +++ b/src/password_hashers/argon2.rs @@ -4,6 +4,8 @@ use argon2::{ }; use rand::RngCore; +use crate::error::{CasError, CasResult}; + pub struct CASArgon; @@ -16,55 +18,64 @@ impl CASArgon { /// - iterations: Number of iterations. /// - parallelism: Degree of parallelism. /// - password_to_hash: The password to be hashed. - pub fn hash_password_parameters(memory_cost: u32, iterations: u32, parallelism: u32, password_to_hash: String) -> String { - let params = Params::new(memory_cost * 1024, iterations, parallelism, None).unwrap(); + pub fn hash_password_parameters(memory_cost: u32, iterations: u32, parallelism: u32, password_to_hash: String) -> CasResult { + let params = Params::new(memory_cost * 1024, iterations, parallelism, None) + .map_err(|_| CasError::InvalidParameters)?; let argon2 = Argon2::new(argon2::Algorithm::Argon2id, argon2::Version::V0x13, params); let salt = SaltString::generate(&mut OsRng); - let hash = argon2.hash_password(password_to_hash.as_bytes(), &salt).unwrap(); - hash.to_string() + let hash = argon2 + .hash_password(password_to_hash.as_bytes(), &salt) + .map_err(|_| CasError::PasswordHashingFailed)?; + Ok(hash.to_string()) } /// Derives a 128-bit AES key from a password using Argon2. /// Returns the derived key as a vector of bytes. - pub fn derive_aes_128_key(password: Vec) -> Vec { + pub fn derive_aes_128_key(password: Vec) -> CasResult> { let mut rng = OsRng; let mut salt: [u8; 16] = [0; 16]; rng.fill_bytes(&mut salt); let mut key = Box::new([0u8; 16]); - Argon2::default().hash_password_into(password.as_ref(), &salt, &mut *key).unwrap(); - key.to_vec() + Argon2::default() + .hash_password_into(password.as_ref(), &salt, &mut *key) + .map_err(|_| CasError::PasswordHashingFailed)?; + Ok(key.to_vec()) } /// Derives a 256-bit AES key from a password using Argon2. /// Returns the derived key as a vector of bytes. - pub fn derive_aes_256_key(password: Vec) -> Vec { + pub fn derive_aes_256_key(password: Vec) -> CasResult> { let mut rng = OsRng; let mut salt: [u8; 16] = [0; 16]; rng.fill_bytes(&mut salt); let mut key = Box::new([0u8; 32]); - Argon2::default().hash_password_into(password.as_ref(), &salt, &mut *key).unwrap(); - key.to_vec() + Argon2::default() + .hash_password_into(password.as_ref(), &salt, &mut *key) + .map_err(|_| CasError::PasswordHashingFailed)?; + Ok(key.to_vec()) } /// Hashes a password using Argon2. /// Returns the hashed password as a string. - pub fn hash_password(password_to_hash: String) -> String { + pub fn hash_password(password_to_hash: String) -> CasResult { let salt = SaltString::generate(&mut OsRng); let argon2 = Argon2::default(); let hashed_password = argon2 .hash_password(password_to_hash.as_bytes(), &salt) - .unwrap() + .map_err(|_| CasError::PasswordHashingFailed)? .to_string(); - return hashed_password; + Ok(hashed_password) } /// Verifies a password against a hashed password using Argon2. - /// Returns true if the password matches the hashed password, false otherwise. - pub fn verify_password(hashed_password: String, password_to_verify: String) -> bool { - let hashed_password = PasswordHash::new(&hashed_password).unwrap(); - return Argon2::default() + /// Returns `Ok(true)` if the password matches, `Ok(false)` if it does not, and + /// an error if the stored hash could not be parsed. + pub fn verify_password(hashed_password: String, password_to_verify: String) -> CasResult { + let hashed_password = + PasswordHash::new(&hashed_password).map_err(|_| CasError::PasswordHashingFailed)?; + Ok(Argon2::default() .verify_password(password_to_verify.as_bytes(), &hashed_password) - .is_ok(); + .is_ok()) } -} \ No newline at end of file +} diff --git a/src/password_hashers/bcrypt.rs b/src/password_hashers/bcrypt.rs index fd17129..b5bf335 100644 --- a/src/password_hashers/bcrypt.rs +++ b/src/password_hashers/bcrypt.rs @@ -2,6 +2,8 @@ use bcrypt::{hash, verify, DEFAULT_COST}; +use crate::error::{CasError, CasResult}; + pub struct CASBCrypt; impl CASBCrypt { @@ -10,19 +12,20 @@ impl CASBCrypt { /// - password_to_hash: The password to be hashed. /// - cost: The cost parameter for bcrypt (default is 12 and max is 31). /// Returns the hashed password as a string. - pub fn hash_password_customized(password_to_hash: String, cost: u32) -> String { - return hash(password_to_hash, cost).unwrap(); + pub fn hash_password_customized(password_to_hash: String, cost: u32) -> CasResult { + hash(password_to_hash, cost).map_err(|_| CasError::PasswordHashingFailed) } /// Hashes a password using bcrypt. /// Returns the hashed password as a string. - pub fn hash_password(password_to_hash: String) -> String { - return hash(password_to_hash, DEFAULT_COST).unwrap(); + pub fn hash_password(password_to_hash: String) -> CasResult { + hash(password_to_hash, DEFAULT_COST).map_err(|_| CasError::PasswordHashingFailed) } /// Verifies a password against a hashed password using bcrypt. - /// Returns true if the password matches the hashed password, false otherwise. - pub fn verify_password(hashed_password: String, password_to_verify: String) -> bool { - return verify(password_to_verify, &hashed_password).unwrap(); + /// Returns `Ok(true)` if the password matches, `Ok(false)` if it does not, and + /// an error if the stored hash could not be parsed. + pub fn verify_password(hashed_password: String, password_to_verify: String) -> CasResult { + verify(password_to_verify, &hashed_password).map_err(|_| CasError::PasswordHashingFailed) } } diff --git a/src/password_hashers/scrypt.rs b/src/password_hashers/scrypt.rs index 86b18ba..cf39e2f 100644 --- a/src/password_hashers/scrypt.rs +++ b/src/password_hashers/scrypt.rs @@ -2,11 +2,14 @@ + use scrypt::{ password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, Scrypt, Params }; +use crate::error::{CasError, CasResult}; + pub struct CASScrypt; impl CASScrypt { @@ -16,28 +19,34 @@ impl CASScrypt { /// - cpu_memory_cost: logâ‚‚ of the Scrypt parameter `N`, the work factor. /// - block_size: `r` parameter: resource usage. /// - parallelism: `p` parameter: parallelization. - pub fn hash_password_customized(password_to_hash: String, cpu_memory_cost: u8, block_size: u32, parallelism: u32) -> String { + pub fn hash_password_customized(password_to_hash: String, cpu_memory_cost: u8, block_size: u32, parallelism: u32) -> CasResult { let salt = SaltString::generate(&mut OsRng); - let params = Params::new(cpu_memory_cost, block_size, parallelism, 32).unwrap(); - return Scrypt.hash_password_customized(password_to_hash.as_bytes(), None, None, params, &salt).unwrap().to_string(); + let params = Params::new(cpu_memory_cost, block_size, parallelism, 32) + .map_err(|_| CasError::InvalidParameters)?; + Ok(Scrypt + .hash_password_customized(password_to_hash.as_bytes(), None, None, params, &salt) + .map_err(|_| CasError::PasswordHashingFailed)? + .to_string()) } /// Hashes a password using Scrypt. /// Returns the hashed password as a string. - pub fn hash_password(password_to_hash: String) -> String { + pub fn hash_password(password_to_hash: String) -> CasResult { let salt = SaltString::generate(&mut OsRng); - return Scrypt + Ok(Scrypt .hash_password(password_to_hash.as_bytes(), &salt) - .unwrap() - .to_string(); + .map_err(|_| CasError::PasswordHashingFailed)? + .to_string()) } /// Verifies a password against a hashed password using Scrypt. - /// Returns true if the password matches the hashed password, false otherwise. - pub fn verify_password(hashed_password: String, password_to_verify: String) -> bool { - let parsed_hash = PasswordHash::new(&hashed_password).unwrap(); - return Scrypt + /// Returns `Ok(true)` if the password matches, `Ok(false)` if it does not, and + /// an error if the stored hash could not be parsed. + pub fn verify_password(hashed_password: String, password_to_verify: String) -> CasResult { + let parsed_hash = + PasswordHash::new(&hashed_password).map_err(|_| CasError::PasswordHashingFailed)?; + Ok(Scrypt .verify_password(password_to_verify.as_bytes(), &parsed_hash) - .is_ok(); + .is_ok()) } -} \ No newline at end of file +} diff --git a/src/pqc/ml_kem.rs b/src/pqc/ml_kem.rs index 72f6102..063e642 100644 --- a/src/pqc/ml_kem.rs +++ b/src/pqc/ml_kem.rs @@ -2,6 +2,7 @@ use ml_kem::kem::{DecapsulationKey, EncapsulationKey, Encapsulate, Decapsulate}; use ml_kem::*; use rand::rngs::OsRng; +use crate::error::{CasError, CasResult}; use crate::pqc::cas_pqc::{MlKemEncapResult, MlKemKeyPair}; /// ML-KEM-1024 (Kyber-1024) byte lengths (public API sanity checks) @@ -9,16 +10,6 @@ const MLKEM1024_PUBLIC_KEY_LEN: usize = 1568; const MLKEM1024_SECRET_KEY_LEN: usize = 3168; const MLKEM1024_CIPHERTEXT_LEN: usize = 1568; -#[derive(Debug)] -pub enum MlKemError { - BadPublicKeyLength, - BadSecretKeyLength, - BadCiphertextLength, - DecodeFailed, -} - -pub type MlKemResult = Result; - /// Generate (secret/decapsulation key, public/encapsulation key) pub fn ml_kem_1024_generate() -> MlKemKeyPair { let mut rng = OsRng; @@ -30,15 +21,15 @@ pub fn ml_kem_1024_generate() -> MlKemKeyPair { } /// Encapsulate to a public key -> (ciphertext, shared_secret) -pub fn ml_kem_1024_encapsulate(public_key: Vec) -> MlKemResult { +pub fn ml_kem_1024_encapsulate(public_key: Vec) -> CasResult { if public_key.len() != MLKEM1024_PUBLIC_KEY_LEN { - return Err(MlKemError::BadPublicKeyLength); + return Err(CasError::InvalidKey); } let ek_bytes: Encoded> = - public_key.as_slice().try_into().map_err(|_| MlKemError::DecodeFailed)?; + public_key.as_slice().try_into().map_err(|_| CasError::InvalidKey)?; let ek = EncapsulationKey::::from_bytes(&ek_bytes); let mut rng = OsRng; - let (ct, ss) = ek.encapsulate(&mut rng).map_err(|_| MlKemError::DecodeFailed)?; + let (ct, ss) = ek.encapsulate(&mut rng).map_err(|_| CasError::EncryptionFailed)?; Ok(MlKemEncapResult { ciphertext: ct.as_slice().to_vec(), shared_secret: ss.as_slice().to_vec(), @@ -46,21 +37,21 @@ pub fn ml_kem_1024_encapsulate(public_key: Vec) -> MlKemResult shared_secret -pub fn ml_kem_1024_decapsulate(secret_key: Vec, ciphertext: Vec) -> MlKemResult> { +pub fn ml_kem_1024_decapsulate(secret_key: Vec, ciphertext: Vec) -> CasResult> { if secret_key.len() != MLKEM1024_SECRET_KEY_LEN { - return Err(MlKemError::BadSecretKeyLength); + return Err(CasError::InvalidKey); } if ciphertext.len() != MLKEM1024_CIPHERTEXT_LEN { - return Err(MlKemError::BadCiphertextLength); + return Err(CasError::InvalidInput); } let dk_bytes: Encoded> = - secret_key.as_slice().try_into().map_err(|_| MlKemError::DecodeFailed)?; + secret_key.as_slice().try_into().map_err(|_| CasError::InvalidKey)?; let dk = DecapsulationKey::::from_bytes(&dk_bytes); let ct: Ciphertext = - ciphertext.as_slice().try_into().map_err(|_| MlKemError::DecodeFailed)?; + ciphertext.as_slice().try_into().map_err(|_| CasError::InvalidInput)?; - let ss = dk.decapsulate(&ct).map_err(|_| MlKemError::DecodeFailed)?; + let ss = dk.decapsulate(&ct).map_err(|_| CasError::DecryptionFailed)?; Ok(ss.to_vec()) -} \ No newline at end of file +} diff --git a/src/pqc/slh_dsa.rs b/src/pqc/slh_dsa.rs index 164546a..6a94501 100644 --- a/src/pqc/slh_dsa.rs +++ b/src/pqc/slh_dsa.rs @@ -2,7 +2,9 @@ use rand; use signature::*; use slh_dsa::*; +use crate::error::{CasError, CasResult}; use crate::pqc::cas_pqc::SlhDsaKeyPair; + pub fn generate_signing_and_verification_key() -> SlhDsaKeyPair { let mut rng = rand::rngs::OsRng; let sk = SigningKey::::new(&mut rng); @@ -14,14 +16,22 @@ pub fn generate_signing_and_verification_key() -> SlhDsaKeyPair { } } -pub fn sign_message(message: Vec, signing_key: Vec) -> Vec { - let key = SigningKey::::try_from(signing_key.as_slice()).unwrap(); +/// Signs a message with an SLH-DSA signing key. +/// Returns an error if the signing key could not be parsed. +pub fn sign_message(message: Vec, signing_key: Vec) -> CasResult> { + let key = SigningKey::::try_from(signing_key.as_slice()) + .map_err(|_| CasError::InvalidKey)?; let signature: Signature = key.sign(&message); - signature.to_bytes().to_vec() + Ok(signature.to_bytes().to_vec()) } -pub fn verify_signature(message: Vec, signature: Vec, verification_key: Vec) -> bool { - let vk = VerifyingKey::::try_from(verification_key.as_slice()).unwrap(); - let sig = Signature::::try_from(signature.as_slice()).unwrap(); - vk.verify(&message, &sig).is_ok() +/// Verifies an SLH-DSA signature. +/// Returns `Ok(true)` if the signature is valid, `Ok(false)` if it is not, and +/// an error if the verification key or signature could not be parsed. +pub fn verify_signature(message: Vec, signature: Vec, verification_key: Vec) -> CasResult { + let vk = VerifyingKey::::try_from(verification_key.as_slice()) + .map_err(|_| CasError::InvalidKey)?; + let sig = Signature::::try_from(signature.as_slice()) + .map_err(|_| CasError::InvalidSignature)?; + Ok(vk.verify(&message, &sig).is_ok()) } diff --git a/src/signatures/ed25519.rs b/src/signatures/ed25519.rs index bd9c136..4b33e5a 100644 --- a/src/signatures/ed25519.rs +++ b/src/signatures/ed25519.rs @@ -7,6 +7,7 @@ use ed25519_dalek::{Signer, SigningKey, VerifyingKey}; use ed25519_dalek::Signature; use rand::rngs::OsRng; +use crate::error::{CasError, CasResult}; use crate::signatures::cas_ed25519::Ed25519ByteKeyPair; use super::cas_ed25519::Ed25519ByteSignature; @@ -24,49 +25,46 @@ pub fn get_ed25519_key_pair() -> Ed25519ByteKeyPair { } /// Signs a message using the provided Ed25519 key pair. -/// Returns the signature and public key as an Ed25519ByteSignature. -pub fn ed25519_sign_with_key_pair(key_pair: Vec, message_to_sign: Vec) -> Ed25519ByteSignature { - let mut key_pair_box = Box::new([0u8; 32]); - key_pair_box.copy_from_slice(&key_pair); +/// Returns the signature and public key as an Ed25519ByteSignature, or an error +/// if the key pair is not 32 bytes long. +pub fn ed25519_sign_with_key_pair(key_pair: Vec, message_to_sign: Vec) -> CasResult { + let key_pair_bytes: [u8; 32] = key_pair.try_into().map_err(|_| CasError::InvalidKey)?; - let keypair = SigningKey::from_bytes(&*key_pair_box); + let keypair = SigningKey::from_bytes(&key_pair_bytes); let signature = keypair.sign(&message_to_sign); let signature_bytes = signature.to_bytes().to_vec(); let public_keypair_vec = keypair.verifying_key().as_bytes().to_vec(); - let result = Ed25519ByteSignature { + Ok(Ed25519ByteSignature { public_key: public_keypair_vec, signature: signature_bytes - }; - result + }) } /// Verifies a signature using the provided Ed25519 key pair. -/// Returns true if the signature is valid, false otherwise. +/// Returns `Ok(true)` if the signature is valid, `Ok(false)` if it is not, and +/// an error if the key pair or signature has an invalid length. /// The key pair is expected to be in byte array format. -pub fn ed25519_verify_with_key_pair(key_pair: Vec, signature: Vec, message: Vec) -> bool { - let mut key_pair_box = Box::new([0u8; 32]); - key_pair_box.copy_from_slice(&key_pair); - let mut signature_box = Box::new([0u8; 64]); - signature_box.copy_from_slice(&signature); +pub fn ed25519_verify_with_key_pair(key_pair: Vec, signature: Vec, message: Vec) -> CasResult { + let key_pair_bytes: [u8; 32] = key_pair.try_into().map_err(|_| CasError::InvalidKey)?; + let signature_bytes: [u8; 64] = signature.try_into().map_err(|_| CasError::InvalidSignature)?; - let keypair = SigningKey::from_bytes(&*key_pair_box); - let signature = Signature::from_bytes(&*signature_box); - return keypair.verify(&message, &signature).is_ok(); + let keypair = SigningKey::from_bytes(&key_pair_bytes); + let signature = Signature::from_bytes(&signature_bytes); + Ok(keypair.verify(&message, &signature).is_ok()) } /// Verifies a signature using the provided public key. -/// Returns true if the signature is valid, false otherwise. +/// Returns `Ok(true)` if the signature is valid, `Ok(false)` if it is not, and +/// an error if the public key or signature has an invalid length or could not be parsed. /// The public key and signature are expected to be in byte array format. -pub fn ed25519_verify_with_public_key(public_key: Vec, signature: Vec, message: Vec) -> bool { - let mut public_key_box = Box::new([0u8; 32]); - public_key_box.copy_from_slice(&public_key); - let mut signature_box = Box::new([0u8; 64]); - signature_box.copy_from_slice(&signature); - +pub fn ed25519_verify_with_public_key(public_key: Vec, signature: Vec, message: Vec) -> CasResult { + let public_key_bytes: [u8; 32] = public_key.try_into().map_err(|_| CasError::InvalidKey)?; + let signature_bytes: [u8; 64] = signature.try_into().map_err(|_| CasError::InvalidSignature)?; - let verifying_key = VerifyingKey::from_bytes(&*public_key_box).unwrap(); - let signature_parsed = Signature::from_bytes(&*signature_box); - return verifying_key + let verifying_key = + VerifyingKey::from_bytes(&public_key_bytes).map_err(|_| CasError::InvalidKey)?; + let signature_parsed = Signature::from_bytes(&signature_bytes); + Ok(verifying_key .verify_strict(&message, &signature_parsed) - .is_ok(); -} \ No newline at end of file + .is_ok()) +} diff --git a/src/sponges/ascon_aead.rs b/src/sponges/ascon_aead.rs index 0a2c096..3e1d120 100644 --- a/src/sponges/ascon_aead.rs +++ b/src/sponges/ascon_aead.rs @@ -1,49 +1,57 @@ use ascon_aead::{AsconAead128 as Ascon128, Key, AsconAead128Nonce, aead::{Aead, KeyInit}}; use rand::{rngs::OsRng, RngCore}; +use crate::error::{CasError, CasResult}; + use super::cas_ascon_aead::{CASAsconAead}; + +const ASCON_KEY_LEN: usize = 16; +const ASCON_NONCE_LEN: usize = 16; + pub struct AsconAead; -impl CASAsconAead for AsconAead { - /// Encrypts with AscondAead - fn encrypt(key: Vec, nonce: Vec, plaintext: Vec) -> Vec { - if key.len() != 16 || nonce.len() != 16 { - panic!("Key and nonce must be 16 bytes long"); - } - let key_array: [u8; 16] = key.try_into().unwrap(); +impl CASAsconAead for AsconAead { + /// Encrypts with AsconAead. + /// Returns an error if the key or nonce is not 16 bytes long, or if encryption fails. + fn encrypt(key: Vec, nonce: Vec, plaintext: Vec) -> CasResult> { + let key_array: [u8; ASCON_KEY_LEN] = + key.try_into().map_err(|_| CasError::InvalidKey)?; + let nonce_array: [u8; ASCON_NONCE_LEN] = + nonce.try_into().map_err(|_| CasError::InvalidNonce)?; let key_generic_array = Key::::from(key_array); - let nonce_array: [u8; 16] = nonce.try_into().unwrap(); let nonce_generic_array = AsconAead128Nonce::from(nonce_array); let cipher = Ascon128::new(&key_generic_array); - let ciphertext = cipher.encrypt(&nonce_generic_array, plaintext.as_ref()).unwrap(); - ciphertext + cipher + .encrypt(&nonce_generic_array, plaintext.as_ref()) + .map_err(|_| CasError::EncryptionFailed) } - /// Decrypts with AscondAead - fn decrypt(key: Vec, nonce: Vec, ciphertext: Vec) -> Vec { - if key.len() != 16 || nonce.len() != 16 { - panic!("Key and nonce must be 16 bytes long"); - } - let key_array: [u8; 16] = key.try_into().unwrap(); + /// Decrypts with AsconAead. + /// Returns an error if the key or nonce is not 16 bytes long, or if decryption fails. + fn decrypt(key: Vec, nonce: Vec, ciphertext: Vec) -> CasResult> { + let key_array: [u8; ASCON_KEY_LEN] = + key.try_into().map_err(|_| CasError::InvalidKey)?; + let nonce_array: [u8; ASCON_NONCE_LEN] = + nonce.try_into().map_err(|_| CasError::InvalidNonce)?; let key_generic_array = Key::::from(key_array); - let nonce_array: [u8; 16] = nonce.try_into().unwrap(); let nonce_generic_array = AsconAead128Nonce::from(nonce_array); let cipher = Ascon128::new(&key_generic_array); - let plaintext = cipher.decrypt(&nonce_generic_array, ciphertext.as_ref()).unwrap(); - plaintext + cipher + .decrypt(&nonce_generic_array, ciphertext.as_ref()) + .map_err(|_| CasError::DecryptionFailed) } - + /// Generates a 16-byte key for Ascon Aead fn generate_key() -> Vec { - let mut key_bytes = [0u8; 16]; + let mut key_bytes = [0u8; ASCON_KEY_LEN]; OsRng.fill_bytes(&mut key_bytes); return Key::::from(key_bytes).to_vec(); } - + /// Generates a Ascon Aead nonce fn generate_nonce() -> Vec { - let mut nonce_bytes = [0u8; 16]; + let mut nonce_bytes = [0u8; ASCON_NONCE_LEN]; OsRng.fill_bytes(&mut nonce_bytes); return AsconAead128Nonce::from(nonce_bytes).to_vec(); } -} \ No newline at end of file +} diff --git a/src/sponges/cas_ascon_aead.rs b/src/sponges/cas_ascon_aead.rs index f41556e..800b24f 100644 --- a/src/sponges/cas_ascon_aead.rs +++ b/src/sponges/cas_ascon_aead.rs @@ -1,6 +1,8 @@ +use crate::error::CasResult; + pub trait CASAsconAead { fn generate_key() -> Vec; fn generate_nonce() -> Vec; - fn encrypt(key: Vec, nonce: Vec, plaintext: Vec) -> Vec; - fn decrypt(key: Vec, nonce: Vec, ciphertext: Vec) -> Vec; -} \ No newline at end of file + fn encrypt(key: Vec, nonce: Vec, plaintext: Vec) -> CasResult>; + fn decrypt(key: Vec, nonce: Vec, ciphertext: Vec) -> CasResult>; +} diff --git a/src/symmetric/aes.rs b/src/symmetric/aes.rs index 77d1d7a..c7455de 100644 --- a/src/symmetric/aes.rs +++ b/src/symmetric/aes.rs @@ -9,16 +9,27 @@ use aes_gcm::{ }; use sha2::Sha256; +use crate::error::{CasError, CasResult}; + use super::cas_symmetric_encryption::{CASAES128Encryption, CASAES256Encryption}; + +const AES_NONCE_LEN: usize = 12; +const AES128_KEY_LEN: usize = 16; +const AES256_KEY_LEN: usize = 32; + pub struct CASAES128; pub struct CASAES256; impl CASAES256Encryption for CASAES256 { - /// Generates an AES256 key from a vector - fn key_from_vec(key_slice: Vec) -> Vec { + /// Generates an AES256 key from a vector. + /// Returns an error if the input is not 32 bytes long. + fn key_from_vec(key_slice: Vec) -> CasResult> { + if key_slice.len() != AES256_KEY_LEN { + return Err(CasError::InvalidKey); + } let key = Key::::from_slice(key_slice.as_slice()); - key.to_vec() + Ok(key.to_vec()) } /// Generates an AES 256 32-bit Key @@ -29,48 +40,67 @@ impl CASAES256Encryption for CASAES256 { /// Encrypts with AES-256-GCM taking an aes_key and aes_nonce - fn encrypt_plaintext(aes_key: Vec, nonce: Vec, plaintext: Vec) -> Vec { + fn encrypt_plaintext(aes_key: Vec, nonce: Vec, plaintext: Vec) -> CasResult> { + if aes_key.len() != AES256_KEY_LEN { + return Err(CasError::InvalidKey); + } + if nonce.len() != AES_NONCE_LEN { + return Err(CasError::InvalidNonce); + } let key = Key::::from_slice(aes_key.as_slice()); let cipher = Aes256Gcm::new(key); let nonce = Nonce::from_slice(nonce.as_slice()); - let ciphertext = cipher.encrypt(nonce, plaintext.as_ref()).unwrap(); - ciphertext + cipher + .encrypt(nonce, plaintext.as_ref()) + .map_err(|_| CasError::EncryptionFailed) } /// Decrypts with AES-256-GCM taking an aes_key and aes_nonce - fn decrypt_ciphertext(aes_key: Vec, nonce: Vec, ciphertext: Vec) -> Vec { + fn decrypt_ciphertext(aes_key: Vec, nonce: Vec, ciphertext: Vec) -> CasResult> { + if aes_key.len() != AES256_KEY_LEN { + return Err(CasError::InvalidKey); + } + if nonce.len() != AES_NONCE_LEN { + return Err(CasError::InvalidNonce); + } let key = Key::::from_slice(aes_key.as_slice()); let cipher = Aes256Gcm::new(key); let nonce = Nonce::from_slice(nonce.as_slice()); - let plaintext = cipher.decrypt(nonce, ciphertext.as_ref()).unwrap(); - plaintext + cipher + .decrypt(nonce, ciphertext.as_ref()) + .map_err(|_| CasError::DecryptionFailed) } /// Creates an AES-256 key 32-byte key from an X25519 Shared Secret - fn key_from_x25519_shared_secret(shared_secret: Vec) -> Vec { + fn key_from_x25519_shared_secret(shared_secret: Vec) -> CasResult> { let hk = Hkdf::::new(None, &shared_secret); let mut aes_key: Box<[u8; 32]> = Box::new([0u8; 32]); - hk.expand(b"aes key", &mut *aes_key).unwrap(); - aes_key.to_vec() + hk.expand(b"aes key", &mut *aes_key) + .map_err(|_| CasError::KeyGenerationFailed)?; + Ok(aes_key.to_vec()) } - + /// Generates an AES nonce fn generate_nonce() -> Vec { let mut os_rng = OsRng; - let mut nonce = [0u8; 12]; + let mut nonce = [0u8; AES_NONCE_LEN]; os_rng.fill_bytes(&mut nonce); - nonce.to_vec() + nonce.to_vec() } } impl CASAES128Encryption for CASAES128 { - /// Generates an AES128 key from a vector - fn key_from_vec(key_slice: Vec) -> Vec { + /// Generates an AES128 key from a vector. + /// Returns an error if the input is not 16 bytes long. + fn key_from_vec(key_slice: Vec) -> CasResult> { + if key_slice.len() != AES128_KEY_LEN { + return Err(CasError::InvalidKey); + } let key = Key::::from_slice(key_slice.as_slice()); - key.to_vec() + Ok(key.to_vec()) } /// Generates an AES-128 16-byte key @@ -79,47 +109,59 @@ impl CASAES128Encryption for CASAES128 { return Aes128Gcm::generate_key(&mut os_rng).to_vec(); } - + /// Encrypts with AES-128-GCM taking an aes_key and aes_nonce - fn encrypt_plaintext(aes_key: Vec, nonce: Vec, plaintext: Vec) -> Vec { + fn encrypt_plaintext(aes_key: Vec, nonce: Vec, plaintext: Vec) -> CasResult> { + if aes_key.len() != AES128_KEY_LEN { + return Err(CasError::InvalidKey); + } + if nonce.len() != AES_NONCE_LEN { + return Err(CasError::InvalidNonce); + } let key = Key::::from_slice(aes_key.as_slice()); let cipher = Aes128Gcm::new(key); let nonce = Nonce::from_slice(nonce.as_slice()); - let ciphertext = cipher.encrypt(nonce, plaintext.as_ref()).unwrap(); - ciphertext + cipher + .encrypt(nonce, plaintext.as_ref()) + .map_err(|_| CasError::EncryptionFailed) } - + /// Decrypts with AES-128-GCM taking an aes_key and aes_nonce - fn decrypt_ciphertext(aes_key: Vec, nonce: Vec, ciphertext: Vec) -> Vec { + fn decrypt_ciphertext(aes_key: Vec, nonce: Vec, ciphertext: Vec) -> CasResult> { + if aes_key.len() != AES128_KEY_LEN { + return Err(CasError::InvalidKey); + } + if nonce.len() != AES_NONCE_LEN { + return Err(CasError::InvalidNonce); + } let key = Key::::from_slice(aes_key.as_slice()); let cipher = Aes128Gcm::new(key); let nonce = Nonce::from_slice(nonce.as_slice()); - let plaintext = cipher.decrypt(nonce, ciphertext.as_ref()).unwrap(); - plaintext + cipher + .decrypt(nonce, ciphertext.as_ref()) + .map_err(|_| CasError::DecryptionFailed) } - + /// Generates an AES-128 16-byte key from an X25519 shared secret - fn key_from_x25519_shared_secret(shared_secret: Vec) -> Vec { + fn key_from_x25519_shared_secret(shared_secret: Vec) -> CasResult> { let hk = Hkdf::::new(None, &shared_secret); let mut aes_key = Box::new([0u8; 16]); - hk.expand(b"aes key", &mut *aes_key).unwrap(); - aes_key.to_vec() + hk.expand(b"aes key", &mut *aes_key) + .map_err(|_| CasError::KeyGenerationFailed)?; + Ok(aes_key.to_vec()) } - - + /// Generates an AES nonce fn generate_nonce() -> Vec { let mut os_rng = OsRng; - let mut nonce = [0u8; 12]; + let mut nonce = [0u8; AES_NONCE_LEN]; os_rng.fill_bytes(&mut nonce); nonce.to_vec() } - - -} \ No newline at end of file +} diff --git a/src/symmetric/cas_symmetric_encryption.rs b/src/symmetric/cas_symmetric_encryption.rs index fecdd90..50262d4 100644 --- a/src/symmetric/cas_symmetric_encryption.rs +++ b/src/symmetric/cas_symmetric_encryption.rs @@ -1,24 +1,26 @@ +use crate::error::CasResult; + pub trait CASAES256Encryption { fn generate_key() -> Vec; - fn encrypt_plaintext(aes_key: Vec, nonce: Vec, plaintext: Vec) -> Vec; - fn decrypt_ciphertext(aes_key: Vec, nonce: Vec, ciphertext: Vec) -> Vec; - fn key_from_x25519_shared_secret(shared_secret: Vec) -> Vec; + fn encrypt_plaintext(aes_key: Vec, nonce: Vec, plaintext: Vec) -> CasResult>; + fn decrypt_ciphertext(aes_key: Vec, nonce: Vec, ciphertext: Vec) -> CasResult>; + fn key_from_x25519_shared_secret(shared_secret: Vec) -> CasResult>; fn generate_nonce() -> Vec; - fn key_from_vec(key_slice: Vec) -> Vec; + fn key_from_vec(key_slice: Vec) -> CasResult>; } pub trait CASAES128Encryption { fn generate_key() -> Vec; - fn encrypt_plaintext(aes_key: Vec, nonce: Vec, plaintext: Vec) -> Vec; - fn decrypt_ciphertext(aes_key: Vec, nonce: Vec, ciphertext: Vec) -> Vec; - fn key_from_x25519_shared_secret(shared_secret: Vec) -> Vec; + fn encrypt_plaintext(aes_key: Vec, nonce: Vec, plaintext: Vec) -> CasResult>; + fn decrypt_ciphertext(aes_key: Vec, nonce: Vec, ciphertext: Vec) -> CasResult>; + fn key_from_x25519_shared_secret(shared_secret: Vec) -> CasResult>; fn generate_nonce() -> Vec; - fn key_from_vec(key_slice: Vec) -> Vec; + fn key_from_vec(key_slice: Vec) -> CasResult>; } pub trait Chacha20Poly1305Encryption { fn generate_key() -> Vec; - fn encrypt_plaintext(aes_key: Vec, nonce: Vec, plaintext: Vec) -> Vec; - fn decrypt_ciphertext(aes_key: Vec, nonce: Vec, ciphertext: Vec) -> Vec; + fn encrypt_plaintext(aes_key: Vec, nonce: Vec, plaintext: Vec) -> CasResult>; + fn decrypt_ciphertext(aes_key: Vec, nonce: Vec, ciphertext: Vec) -> CasResult>; fn generate_nonce() -> Vec; -} \ No newline at end of file +} diff --git a/src/symmetric/chacha20poly1305.rs b/src/symmetric/chacha20poly1305.rs index eb68533..9142023 100644 --- a/src/symmetric/chacha20poly1305.rs +++ b/src/symmetric/chacha20poly1305.rs @@ -3,34 +3,53 @@ use aes_gcm::{aead::Aead}; use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce, KeyInit}; use rand::rngs::OsRng; +use crate::error::{CasError, CasResult}; use crate::symmetric::cas_symmetric_encryption::Chacha20Poly1305Encryption; +const CHACHA20_KEY_LEN: usize = 32; +const CHACHA20_NONCE_LEN: usize = 12; pub struct CASChacha20Poly1305; impl Chacha20Poly1305Encryption for CASChacha20Poly1305 { - + fn generate_key() -> Vec { ChaCha20Poly1305::generate_key(&mut OsRng).to_vec() } - fn encrypt_plaintext(aes_key: Vec, nonce: Vec, plaintext: Vec) -> Vec { + fn encrypt_plaintext(aes_key: Vec, nonce: Vec, plaintext: Vec) -> CasResult> { + if aes_key.len() != CHACHA20_KEY_LEN { + return Err(CasError::InvalidKey); + } + if nonce.len() != CHACHA20_NONCE_LEN { + return Err(CasError::InvalidNonce); + } let key = Key::from_slice(aes_key.as_slice()); let cipher = ChaCha20Poly1305::new(&key); let nonce = Nonce::from_slice(nonce.as_slice()); - cipher.encrypt(nonce, plaintext.as_ref()).expect("encryption failed") + cipher + .encrypt(nonce, plaintext.as_ref()) + .map_err(|_| CasError::EncryptionFailed) } - fn decrypt_ciphertext(aes_key: Vec, nonce: Vec, ciphertext: Vec) -> Vec { + fn decrypt_ciphertext(aes_key: Vec, nonce: Vec, ciphertext: Vec) -> CasResult> { + if aes_key.len() != CHACHA20_KEY_LEN { + return Err(CasError::InvalidKey); + } + if nonce.len() != CHACHA20_NONCE_LEN { + return Err(CasError::InvalidNonce); + } let key = Key::from_slice(aes_key.as_slice()); let cipher = ChaCha20Poly1305::new(&key); let nonce = Nonce::from_slice(nonce.as_slice()); - cipher.decrypt(nonce, ciphertext.as_ref()).expect("decryption failed") + cipher + .decrypt(nonce, ciphertext.as_ref()) + .map_err(|_| CasError::DecryptionFailed) } fn generate_nonce() -> Vec { - let mut nonce = [0u8; 12]; // ChaCha20Poly1305 uses 96-bit (12-byte) nonces + let mut nonce = [0u8; CHACHA20_NONCE_LEN]; // ChaCha20Poly1305 uses 96-bit (12-byte) nonces OsRng.fill_bytes(&mut nonce); nonce.to_vec() } -} \ No newline at end of file +} diff --git a/tests/asymmetric.rs b/tests/asymmetric.rs index 472f24f..5387ea1 100644 --- a/tests/asymmetric.rs +++ b/tests/asymmetric.rs @@ -4,25 +4,31 @@ mod asymmetric { #[test] pub fn generate_rsa_keys() { - let key_pair: RSAKeyPairResult = CASRSA::generate_rsa_keys(2048); + let key_pair: RSAKeyPairResult = CASRSA::generate_rsa_keys(2048).unwrap(); assert!(!key_pair.private_key.is_empty()); assert!(!key_pair.public_key.is_empty()); } + #[test] + pub fn rejects_small_key_size() { + let result = CASRSA::generate_rsa_keys(1024); + assert!(result.is_err(), "RSA key sizes below 2048 bits should be rejected"); + } + #[test] pub fn signature() { - let key_pair: RSAKeyPairResult = CASRSA::generate_rsa_keys(1024); + let key_pair: RSAKeyPairResult = CASRSA::generate_rsa_keys(2048).unwrap(); let document = b"Hello, world!".to_vec(); - let signature = CASRSA::sign(key_pair.private_key, document.clone()); + let signature = CASRSA::sign(key_pair.private_key, document.clone()).unwrap(); assert!(document != signature, "Signature should not be the same as the document"); } #[test] pub fn verification() { - let key_pair: RSAKeyPairResult = CASRSA::generate_rsa_keys(2048); + let key_pair: RSAKeyPairResult = CASRSA::generate_rsa_keys(2048).unwrap(); let document = b"Hello, world!".to_vec(); - let signature = CASRSA::sign(key_pair.private_key.clone(), document.clone()); - let verification = CASRSA::verify(key_pair.public_key, document, signature); + let signature = CASRSA::sign(key_pair.private_key.clone(), document.clone()).unwrap(); + let verification = CASRSA::verify(key_pair.public_key, document, signature).unwrap(); assert!(verification, "Signature should be valid"); } -} \ No newline at end of file +} diff --git a/tests/compression.rs b/tests/compression.rs index 9d74495..020ea15 100644 --- a/tests/compression.rs +++ b/tests/compression.rs @@ -7,12 +7,12 @@ mod compression { let original_data = b"Hello, world! This is a test of the compression and decompression functionality."; // Compress the data - let compressed_data: Vec = compress(original_data.to_vec(), 9); + let compressed_data: Vec = compress(original_data.to_vec(), 9).unwrap(); assert!(!compressed_data.is_empty(), "Compressed data should not be empty"); assert!(compressed_data.len() < original_data.len(), "Compressed data should be smaller than original data"); // Decompress the data - let decompressed_data: Vec = decompress(compressed_data); + let decompressed_data: Vec = decompress(compressed_data).unwrap(); assert_eq!(decompressed_data, original_data.to_vec(), "Decompressed data should match original data"); } } \ No newline at end of file diff --git a/tests/hybrid.rs b/tests/hybrid.rs index 0c56fd3..7c08b9e 100644 --- a/tests/hybrid.rs +++ b/tests/hybrid.rs @@ -19,7 +19,7 @@ mod hybrid { let file_bytes: Vec = std::fs::read(path).unwrap(); let (_private_key, public_key, info_str) = CASHPKE::generate_key_pair(); - let (encapped_key, ciphertext, tag) = CASHPKE::encrypt(file_bytes.clone(), public_key, info_str); + let (encapped_key, ciphertext, tag) = CASHPKE::encrypt(file_bytes.clone(), public_key, info_str).unwrap(); assert!(!encapped_key.is_empty()); assert!(!ciphertext.is_empty()); assert!(!tag.is_empty()); @@ -31,8 +31,8 @@ mod hybrid { let path = Path::new("tests/test.docx"); let file_bytes: Vec = std::fs::read(path).unwrap(); let (private_key, public_key, info_str) = CASHPKE::generate_key_pair(); - let (encapped_key, ciphertext, tag) = CASHPKE::encrypt(file_bytes.clone(), public_key, info_str.clone()); - let decrypted_bytes = CASHPKE::decrypt(ciphertext, private_key, encapped_key, tag, info_str); + let (encapped_key, ciphertext, tag) = CASHPKE::encrypt(file_bytes.clone(), public_key, info_str.clone()).unwrap(); + let decrypted_bytes = CASHPKE::decrypt(ciphertext, private_key, encapped_key, tag, info_str).unwrap(); assert_eq!(file_bytes, decrypted_bytes); } } \ No newline at end of file diff --git a/tests/key_exchange.rs b/tests/key_exchange.rs index dbb3746..82eb935 100644 --- a/tests/key_exchange.rs +++ b/tests/key_exchange.rs @@ -7,8 +7,8 @@ mod key_exchange { let alice: X25519SecretPublicKeyResult = X25519::generate_secret_and_public_key(); let bob: X25519SecretPublicKeyResult = X25519::generate_secret_and_public_key(); - let alice_shared_secret = X25519::diffie_hellman(alice.secret_key.clone(), bob.public_key.clone()); - let bob_shared_secret = X25519::diffie_hellman(bob.secret_key.clone(), alice.public_key.clone()); + let alice_shared_secret = X25519::diffie_hellman(alice.secret_key.clone(), bob.public_key.clone()).unwrap(); + let bob_shared_secret = X25519::diffie_hellman(bob.secret_key.clone(), alice.public_key.clone()).unwrap(); assert_eq!(alice_shared_secret, bob_shared_secret); } } \ No newline at end of file diff --git a/tests/message.rs b/tests/message.rs index 3107df1..c89da70 100644 --- a/tests/message.rs +++ b/tests/message.rs @@ -7,7 +7,7 @@ mod message { let key = vec![1, 2, 3, 4, 5]; let message = vec![6, 7, 8, 9, 10]; // Replace `ConcreteHmacType` with the actual struct that implements CASHMAC - let signature = HMAC::sign(key.clone(), message.clone()); + let signature = HMAC::sign(key.clone(), message.clone()).unwrap(); assert!(!signature.is_empty()); } @@ -15,8 +15,8 @@ mod message { pub fn hmac_verify() { let key = vec![1, 2, 3, 4, 5]; let message = vec![6, 7, 8, 9, 10]; - let signature = HMAC::sign(key.clone(), message.clone()); - let is_valid = HMAC::verify(key, message, signature); + let signature = HMAC::sign(key.clone(), message.clone()).unwrap(); + let is_valid = HMAC::verify(key, message, signature).unwrap(); assert!(is_valid); } } \ No newline at end of file diff --git a/tests/password_hashers.rs b/tests/password_hashers.rs index cb65cde..d9b3b98 100644 --- a/tests/password_hashers.rs +++ b/tests/password_hashers.rs @@ -12,74 +12,74 @@ mod password_hashers { #[test] pub fn argon2_hash_with_parameters() { let password = "BadPassword".to_string(); - let hash = CASArgon::hash_password_parameters(1024, 5, 5, password.clone()); - let verification = CASArgon::verify_password(hash, password); + let hash = CASArgon::hash_password_parameters(1024, 5, 5, password.clone()).unwrap(); + let verification = CASArgon::verify_password(hash, password).unwrap(); assert_eq!(true, verification); } #[test] pub fn argon2_derive_aes_128_and_encrypt() { let password = b"BadPassword".to_vec(); // do not use this as a password. - let key = CASArgon::derive_aes_128_key(password); + let key = CASArgon::derive_aes_128_key(password).unwrap(); let nonce = CASAES128::generate_nonce(); let path = Path::new("tests/test.docx"); let file_bytes: Vec = std::fs::read(path).unwrap(); - let encrypted = CASAES128::encrypt_plaintext(key.clone(), nonce.clone(), file_bytes.clone()); - let decrypted = CASAES128::decrypt_ciphertext(key, nonce, encrypted); + let encrypted = CASAES128::encrypt_plaintext(key.clone(), nonce.clone(), file_bytes.clone()).unwrap(); + let decrypted = CASAES128::decrypt_ciphertext(key, nonce, encrypted).unwrap(); assert_eq!(file_bytes, decrypted); } #[test] pub fn argon2_derive_aes_256_and_encrypt() { let password = b"BadPassword".to_vec(); // do not use this as a password. - let key = CASArgon::derive_aes_256_key(password); + let key = CASArgon::derive_aes_256_key(password).unwrap(); let nonce = CASAES128::generate_nonce(); let path = Path::new("tests/test.docx"); let file_bytes: Vec = std::fs::read(path).unwrap(); - let encrypted = CASAES256::encrypt_plaintext(key.clone(), nonce.clone(), file_bytes.clone()); - let decrypted = CASAES256::decrypt_ciphertext(key, nonce, encrypted); + let encrypted = CASAES256::encrypt_plaintext(key.clone(), nonce.clone(), file_bytes.clone()).unwrap(); + let decrypted = CASAES256::decrypt_ciphertext(key, nonce, encrypted).unwrap(); assert_eq!(file_bytes, decrypted); } #[test] pub fn argon2_hash_password() { let password = "BadPassword".to_string(); - let hash = CASArgon::hash_password(password.clone()); - let verification = CASArgon::verify_password(hash, password); + let hash = CASArgon::hash_password(password.clone()).unwrap(); + let verification = CASArgon::verify_password(hash, password).unwrap(); assert_eq!(true, verification); } #[test] pub fn scrypt_hash_password() { let password = "DoNotUseThisPassword".to_string(); - let hash = CASScrypt::hash_password(password.clone()); - let verification = CASScrypt::verify_password(hash, password); + let hash = CASScrypt::hash_password(password.clone()).unwrap(); + let verification = CASScrypt::verify_password(hash, password).unwrap(); assert_eq!(true, verification); } #[test] pub fn scrypt_hash_password_customized() { let password = "DoNotUseThisPassword".to_string(); - let hash = CASScrypt::hash_password_customized(password.clone(), 17, 8, 1); - let verification = CASScrypt::verify_password(hash, password); + let hash = CASScrypt::hash_password_customized(password.clone(), 17, 8, 1).unwrap(); + let verification = CASScrypt::verify_password(hash, password).unwrap(); assert_eq!(true, verification); } #[test] pub fn bcrypt_hash_password() { let password = "DoNotUseThisPassword".to_string(); - let hash = CASBCrypt::hash_password(password.clone()); - let verification = CASBCrypt::verify_password(hash, password); + let hash = CASBCrypt::hash_password(password.clone()).unwrap(); + let verification = CASBCrypt::verify_password(hash, password).unwrap(); assert_eq!(true, verification); } #[test] pub fn bcrypt_hash_password_customized() { let password = "DoNotUseThisPassword".to_string(); - let hash = CASBCrypt::hash_password_customized(password.clone(), 12); - let verification = CASBCrypt::verify_password(hash, password); + let hash = CASBCrypt::hash_password_customized(password.clone(), 12).unwrap(); + let verification = CASBCrypt::verify_password(hash, password).unwrap(); assert_eq!(true, verification); } diff --git a/tests/pqc.rs b/tests/pqc.rs index ccfaf70..56e0c26 100644 --- a/tests/pqc.rs +++ b/tests/pqc.rs @@ -17,8 +17,8 @@ mod pqc { let hash = ::hash_512(to_hash.clone()); let keys = generate_signing_and_verification_key(); - let signature = sign_message(hash.clone(), keys.signing_key); - let is_valid = verify_signature(hash, signature, keys.verification_key); + let signature = sign_message(hash.clone(), keys.signing_key).unwrap(); + let is_valid = verify_signature(hash, signature, keys.verification_key).unwrap(); assert_eq!(true, is_valid); } @@ -28,8 +28,8 @@ mod pqc { let hash = ::hash_512(to_hash.clone()); let keys = generate_signing_and_verification_key(); - let signature = sign_message(hash, keys.signing_key); - let is_valid = verify_signature(to_hash, signature, keys.verification_key); + let signature = sign_message(hash, keys.signing_key).unwrap(); + let is_valid = verify_signature(to_hash, signature, keys.verification_key).unwrap(); assert_eq!(false, is_valid); } } \ No newline at end of file diff --git a/tests/signatures.rs b/tests/signatures.rs index 22bdb60..2faf153 100644 --- a/tests/signatures.rs +++ b/tests/signatures.rs @@ -13,7 +13,7 @@ mod signatures { pub fn sign_with_key_pair() { let key_pair = get_ed25519_key_pair(); let message_to_sign = b"Hello World Message To Sign".to_vec(); - let signature = ed25519_sign_with_key_pair(key_pair.key_pair, message_to_sign); + let signature = ed25519_sign_with_key_pair(key_pair.key_pair, message_to_sign).unwrap(); assert!(signature.signature != [0; 64], "Array is all zeros"); assert!(signature.public_key != [0; 32], "Array is all zeros"); } @@ -22,8 +22,8 @@ mod signatures { pub fn verify_with_public_key_() { let key_pair = get_ed25519_key_pair(); let message_to_sign = b"Hello World Message To Sign".to_vec(); - let signature = ed25519_sign_with_key_pair(key_pair.key_pair, message_to_sign.clone()); - let verification = ed25519_verify_with_public_key(signature.public_key, signature.signature, message_to_sign); + let signature = ed25519_sign_with_key_pair(key_pair.key_pair, message_to_sign.clone()).unwrap(); + let verification = ed25519_verify_with_public_key(signature.public_key, signature.signature, message_to_sign).unwrap(); assert_eq!(verification, true); } @@ -31,8 +31,8 @@ mod signatures { pub fn verify_with_key_pair() { let key_pair = get_ed25519_key_pair(); let message_to_sign = b"Hello World Message To Sign".to_vec(); - let signature = ed25519_sign_with_key_pair(key_pair.key_pair.clone(), message_to_sign.clone()); - let verification = ed25519_verify_with_key_pair(key_pair.key_pair, signature.signature, message_to_sign); + let signature = ed25519_sign_with_key_pair(key_pair.key_pair.clone(), message_to_sign.clone()).unwrap(); + let verification = ed25519_verify_with_key_pair(key_pair.key_pair, signature.signature, message_to_sign).unwrap(); assert_eq!(verification, true); } } diff --git a/tests/sponges.rs b/tests/sponges.rs index cfd3d0d..579243e 100644 --- a/tests/sponges.rs +++ b/tests/sponges.rs @@ -9,11 +9,11 @@ mod sponges { let file_bytes: Vec = std::fs::read(path).unwrap(); let ascon_nonce = ::generate_nonce(); let ascon_key = ::generate_key(); - let encrypted_bytes = ::encrypt(ascon_key.clone(), ascon_nonce.clone(), file_bytes.clone()); + let encrypted_bytes = ::encrypt(ascon_key.clone(), ascon_nonce.clone(), file_bytes.clone()).unwrap(); let mut file = File::create("encrypted.docx").unwrap(); file.write_all(&encrypted_bytes).unwrap(); - let decrypted_bytes = ::decrypt(ascon_key, ascon_nonce, encrypted_bytes); + let decrypted_bytes = ::decrypt(ascon_key, ascon_nonce, encrypted_bytes).unwrap(); let mut file = File::create("decrypted.docx").unwrap(); file.write_all(&decrypted_bytes).unwrap(); assert_eq!(file_bytes, decrypted_bytes); diff --git a/tests/symmetric.rs b/tests/symmetric.rs index c567987..3095742 100644 --- a/tests/symmetric.rs +++ b/tests/symmetric.rs @@ -1,8 +1,8 @@ -use std::{fs::{File}, io::Write, path::Path}; - -use cas_lib::symmetric::{aes::CASAES256, cas_symmetric_encryption::CASAES256Encryption}; - -#[cfg(test)] +use std::{fs::{File}, io::Write, path::Path}; + +use cas_lib::symmetric::{aes::CASAES256, cas_symmetric_encryption::CASAES256Encryption}; + +#[cfg(test)] mod symmetric { use cas_lib::{key_exchange::{cas_key_exchange::CASKeyExchange, x25519::X25519}, pqc::{cas_pqc::MlKemKeyPair, ml_kem::{ml_kem_1024_decapsulate, ml_kem_1024_encapsulate, ml_kem_1024_generate}}, symmetric::{aes::CASAES128, cas_symmetric_encryption::{CASAES128Encryption, Chacha20Poly1305Encryption}, chacha20poly1305::CASChacha20Poly1305}}; use hkdf::Hkdf; @@ -168,7 +168,8 @@ mod symmetric { case.key.clone(), case.iv.clone(), case.pt.clone(), - ); + ) + .unwrap(); assert_eq!(encrypted, expected); } @@ -186,7 +187,8 @@ mod symmetric { case.key.clone(), case.iv.clone(), case.pt.clone(), - ); + ) + .unwrap(); assert_eq!(encrypted, expected); } @@ -197,121 +199,121 @@ mod symmetric { let path = Path::new("tests/test.docx"); let file_bytes: Vec = std::fs::read(path).unwrap(); let aes_nonce = ::generate_nonce(); - let aes_key = ::generate_key(); - let encrypted_bytes = ::encrypt_plaintext(aes_key.clone(), aes_nonce.clone(), file_bytes.clone()); - let mut file = File::create("encrypted.docx").unwrap(); - file.write_all(&encrypted_bytes).unwrap(); - - let decrypted_bytes = ::decrypt_ciphertext(aes_key, aes_nonce, encrypted_bytes); - let mut file = File::create("decrypted.docx").unwrap(); - file.write_all(&decrypted_bytes).unwrap(); - assert_eq!(file_bytes, decrypted_bytes); - } - - #[test] - fn test_aes256_x25519_diffie_hellman() { - let bob_public = ::generate_secret_and_public_key(); - let alice_public = ::generate_secret_and_public_key(); - - let bob_shared_secret = ::diffie_hellman(bob_public.secret_key, alice_public.public_key); - let alice_shared_secret = ::diffie_hellman(alice_public.secret_key, bob_public.public_key); - - let aes_nonce = ::generate_nonce(); - let path = Path::new("tests/test.docx"); - let file_bytes: Vec = std::fs::read(path).unwrap(); - let alice_key = ::key_from_x25519_shared_secret(bob_shared_secret); - let encrypted_bytes = ::encrypt_plaintext(alice_key, aes_nonce.clone(), file_bytes.clone()); - let mut file = File::create("encrypted.docx").unwrap(); - file.write_all(&encrypted_bytes).unwrap(); - - let bob_key = ::key_from_x25519_shared_secret(alice_shared_secret); - let decrypted_bytes = ::decrypt_ciphertext(bob_key, aes_nonce, encrypted_bytes); - let mut file = File::create("decrypted.docx").unwrap(); - file.write_all(&decrypted_bytes).unwrap(); - assert_eq!(file_bytes, decrypted_bytes); - } - - #[test] - fn test_aes_128() { - let path = Path::new("tests/test.docx"); - let file_bytes: Vec = std::fs::read(path).unwrap(); - let aes_nonce = ::generate_nonce(); - let aes_key = ::generate_key(); - let encrypted_bytes = ::encrypt_plaintext(aes_key.clone(), aes_nonce.clone(), file_bytes.clone()); - let mut file = File::create("encrypted.docx").unwrap(); - file.write_all(&encrypted_bytes).unwrap(); - - - let decrypted_bytes = ::decrypt_ciphertext(aes_key, aes_nonce, encrypted_bytes); - let mut file = File::create("decrypted.docx").unwrap(); - file.write_all(&decrypted_bytes).unwrap(); - assert_eq!(file_bytes, decrypted_bytes); - } - - #[test] - fn test_aes128_x25519_diffie_hellman() { - let bob_public = ::generate_secret_and_public_key(); - let alice_public = ::generate_secret_and_public_key(); - - let bob_shared_secret = ::diffie_hellman(bob_public.secret_key, alice_public.public_key); - let alice_shared_secret = ::diffie_hellman(alice_public.secret_key, bob_public.public_key); - - let aes_nonce = ::generate_nonce(); - let path = Path::new("tests/test.docx"); - let file_bytes: Vec = std::fs::read(path).unwrap(); - let alice_key = ::key_from_x25519_shared_secret(bob_shared_secret); - let encrypted_bytes = ::encrypt_plaintext(alice_key, aes_nonce.clone(), file_bytes.clone()); - let mut file = File::create("encrypted.docx").unwrap(); - file.write_all(&encrypted_bytes).unwrap(); - - - let bob_key = ::key_from_x25519_shared_secret(alice_shared_secret); - let decrypted_bytes = ::decrypt_ciphertext(bob_key, aes_nonce, encrypted_bytes); - let mut file = File::create("decrypted.docx").unwrap(); - file.write_all(&decrypted_bytes).unwrap(); - assert_eq!(file_bytes, decrypted_bytes); - } - - #[test] - fn test_chacha20_poly1305() { - let path = Path::new("tests/test.docx"); - let file_bytes: Vec = std::fs::read(path).unwrap(); - let chacha20_nonce = ::generate_nonce(); - let chacha20_key = ::generate_key(); - let encrypted_bytes = ::encrypt_plaintext(chacha20_key.clone(), chacha20_nonce.clone(), file_bytes.clone()); - let mut file = File::create("encrypted.docx").unwrap(); - file.write_all(&encrypted_bytes).unwrap(); - - let decrypted_bytes = ::decrypt_ciphertext(chacha20_key, chacha20_nonce, encrypted_bytes); - let mut file = File::create("decrypted.docx").unwrap(); - file.write_all(&decrypted_bytes).unwrap(); - assert_eq!(file_bytes, decrypted_bytes); - } - - #[test] + let aes_key = ::generate_key(); + let encrypted_bytes = ::encrypt_plaintext(aes_key.clone(), aes_nonce.clone(), file_bytes.clone()).unwrap(); + let mut file = File::create("encrypted.docx").unwrap(); + file.write_all(&encrypted_bytes).unwrap(); + + let decrypted_bytes = ::decrypt_ciphertext(aes_key, aes_nonce, encrypted_bytes).unwrap(); + let mut file = File::create("decrypted.docx").unwrap(); + file.write_all(&decrypted_bytes).unwrap(); + assert_eq!(file_bytes, decrypted_bytes); + } + + #[test] + fn test_aes256_x25519_diffie_hellman() { + let bob_public = ::generate_secret_and_public_key(); + let alice_public = ::generate_secret_and_public_key(); + + let bob_shared_secret = ::diffie_hellman(bob_public.secret_key, alice_public.public_key).unwrap(); + let alice_shared_secret = ::diffie_hellman(alice_public.secret_key, bob_public.public_key).unwrap(); + + let aes_nonce = ::generate_nonce(); + let path = Path::new("tests/test.docx"); + let file_bytes: Vec = std::fs::read(path).unwrap(); + let alice_key = ::key_from_x25519_shared_secret(bob_shared_secret).unwrap(); + let encrypted_bytes = ::encrypt_plaintext(alice_key, aes_nonce.clone(), file_bytes.clone()).unwrap(); + let mut file = File::create("encrypted.docx").unwrap(); + file.write_all(&encrypted_bytes).unwrap(); + + let bob_key = ::key_from_x25519_shared_secret(alice_shared_secret).unwrap(); + let decrypted_bytes = ::decrypt_ciphertext(bob_key, aes_nonce, encrypted_bytes).unwrap(); + let mut file = File::create("decrypted.docx").unwrap(); + file.write_all(&decrypted_bytes).unwrap(); + assert_eq!(file_bytes, decrypted_bytes); + } + + #[test] + fn test_aes_128() { + let path = Path::new("tests/test.docx"); + let file_bytes: Vec = std::fs::read(path).unwrap(); + let aes_nonce = ::generate_nonce(); + let aes_key = ::generate_key(); + let encrypted_bytes = ::encrypt_plaintext(aes_key.clone(), aes_nonce.clone(), file_bytes.clone()).unwrap(); + let mut file = File::create("encrypted.docx").unwrap(); + file.write_all(&encrypted_bytes).unwrap(); + + + let decrypted_bytes = ::decrypt_ciphertext(aes_key, aes_nonce, encrypted_bytes).unwrap(); + let mut file = File::create("decrypted.docx").unwrap(); + file.write_all(&decrypted_bytes).unwrap(); + assert_eq!(file_bytes, decrypted_bytes); + } + + #[test] + fn test_aes128_x25519_diffie_hellman() { + let bob_public = ::generate_secret_and_public_key(); + let alice_public = ::generate_secret_and_public_key(); + + let bob_shared_secret = ::diffie_hellman(bob_public.secret_key, alice_public.public_key).unwrap(); + let alice_shared_secret = ::diffie_hellman(alice_public.secret_key, bob_public.public_key).unwrap(); + + let aes_nonce = ::generate_nonce(); + let path = Path::new("tests/test.docx"); + let file_bytes: Vec = std::fs::read(path).unwrap(); + let alice_key = ::key_from_x25519_shared_secret(bob_shared_secret).unwrap(); + let encrypted_bytes = ::encrypt_plaintext(alice_key, aes_nonce.clone(), file_bytes.clone()).unwrap(); + let mut file = File::create("encrypted.docx").unwrap(); + file.write_all(&encrypted_bytes).unwrap(); + + + let bob_key = ::key_from_x25519_shared_secret(alice_shared_secret).unwrap(); + let decrypted_bytes = ::decrypt_ciphertext(bob_key, aes_nonce, encrypted_bytes).unwrap(); + let mut file = File::create("decrypted.docx").unwrap(); + file.write_all(&decrypted_bytes).unwrap(); + assert_eq!(file_bytes, decrypted_bytes); + } + + #[test] + fn test_chacha20_poly1305() { + let path = Path::new("tests/test.docx"); + let file_bytes: Vec = std::fs::read(path).unwrap(); + let chacha20_nonce = ::generate_nonce(); + let chacha20_key = ::generate_key(); + let encrypted_bytes = ::encrypt_plaintext(chacha20_key.clone(), chacha20_nonce.clone(), file_bytes.clone()).unwrap(); + let mut file = File::create("encrypted.docx").unwrap(); + file.write_all(&encrypted_bytes).unwrap(); + + let decrypted_bytes = ::decrypt_ciphertext(chacha20_key, chacha20_nonce, encrypted_bytes).unwrap(); + let mut file = File::create("decrypted.docx").unwrap(); + file.write_all(&decrypted_bytes).unwrap(); + assert_eq!(file_bytes, decrypted_bytes); + } + + #[test] pub fn round_trip_mlkem1024_hkdf() { let secret_key_public_key: MlKemKeyPair = ml_kem_1024_generate(); let ct = ml_kem_1024_encapsulate(secret_key_public_key.public_key).expect("encapsulate failed"); let ss_receiver = ml_kem_1024_decapsulate(secret_key_public_key.secret_key, ct.ciphertext).expect("decapsulate failed"); - - let bob_shared_secret = Hkdf::::new(None, &ss_receiver); - let alice_shared_secret = Hkdf::::new(None, &ct.shared_secret); - - let mut aes_key = Box::new([0u8; 32]); - bob_shared_secret.expand(b"aes key", &mut *aes_key).unwrap(); - - let mut aes_key2 = Box::new([0u8; 32]); - alice_shared_secret.expand(b"aes key", &mut *aes_key2).unwrap(); - assert_eq!(aes_key.to_vec(), aes_key2.to_vec()); - - let aes_nonce = ::generate_nonce(); - let path = Path::new("tests/test.docx"); - let file_bytes: Vec = std::fs::read(path).unwrap(); - let encrypted_bytes = ::encrypt_plaintext(aes_key.to_vec(), aes_nonce.clone(), file_bytes.clone()); - let mut file = File::create("encrypted.docx").unwrap(); - file.write_all(&encrypted_bytes).unwrap(); - - let decrypted_bytes = ::decrypt_ciphertext(aes_key2.to_vec(), aes_nonce, encrypted_bytes); + + let bob_shared_secret = Hkdf::::new(None, &ss_receiver); + let alice_shared_secret = Hkdf::::new(None, &ct.shared_secret); + + let mut aes_key = Box::new([0u8; 32]); + bob_shared_secret.expand(b"aes key", &mut *aes_key).unwrap(); + + let mut aes_key2 = Box::new([0u8; 32]); + alice_shared_secret.expand(b"aes key", &mut *aes_key2).unwrap(); + assert_eq!(aes_key.to_vec(), aes_key2.to_vec()); + + let aes_nonce = ::generate_nonce(); + let path = Path::new("tests/test.docx"); + let file_bytes: Vec = std::fs::read(path).unwrap(); + let encrypted_bytes = ::encrypt_plaintext(aes_key.to_vec(), aes_nonce.clone(), file_bytes.clone()).unwrap(); + let mut file = File::create("encrypted.docx").unwrap(); + file.write_all(&encrypted_bytes).unwrap(); + + let decrypted_bytes = ::decrypt_ciphertext(aes_key2.to_vec(), aes_nonce, encrypted_bytes).unwrap(); let mut file = File::create("decrypted.docx").unwrap(); file.write_all(&decrypted_bytes).unwrap(); assert_eq!(file_bytes, decrypted_bytes); From ed97eae654c30e4fd169639e95f9f4ddb5145d38 Mon Sep 17 00:00:00 2001 From: Mike Mulchrone Date: Sat, 13 Jun 2026 20:01:58 -0400 Subject: [PATCH 2/2] delete owasp scan --- .github/workflows/owasp-dc.yml | 66 ---------------------------------- 1 file changed, 66 deletions(-) delete mode 100644 .github/workflows/owasp-dc.yml diff --git a/.github/workflows/owasp-dc.yml b/.github/workflows/owasp-dc.yml deleted file mode 100644 index 41ec02a..0000000 --- a/.github/workflows/owasp-dc.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: OWASP Dependency Scan - -on: - pull_request: - branches: [ "main" ] - push: - branches: [ "main" ] - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -env: - CARGO_TERM_COLOR: always - -jobs: - depscan: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Set up Rust - uses: dtolnay/rust-toolchain@stable - - - name: Generate lockfile when missing - run: | - if [ ! -f Cargo.lock ]; then - cargo generate-lockfile - fi - - - name: Build the project - run: cargo build --release --verbose - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: "24" - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.11" - - - name: Install OWASP scanning tools - run: | - npm install -g @cyclonedx/cdxgen - python -m pip install --upgrade pip - pip install owasp-depscan - - - name: Create reports directory - run: mkdir -p reports - - - name: Generate CycloneDX SBOM - run: cdxgen -t rust -o reports/sbom.json . - - - name: Run OWASP dep-scan - run: depscan --bom reports/sbom.json --reports-dir reports - - - name: Upload dependency scan reports - uses: actions/upload-artifact@v4 - if: always() - with: - name: dependency-scan-reports - path: reports/