diff --git a/crypto/src/encdec/tests.rs b/crypto/src/encdec/tests.rs index 1258ace1..0d3fd5ef 100644 --- a/crypto/src/encdec/tests.rs +++ b/crypto/src/encdec/tests.rs @@ -1,14 +1,299 @@ use crate::{decrypt_data, encrypt_data}; const PASSWORD: &[u8] = "test_password".as_bytes(); +const LONG_PASSWORD: &[u8] = "very_long_password_with_many_characters_for_testing_purposes_123456789".as_bytes(); +const EMPTY_PASSWORD: &[u8] = b""; #[test_case::test_case("hello world".as_bytes())] #[test_case::test_case(&[])] #[test_case::test_case(&[1, 2, 3])] #[test_case::test_case(&[3, 2, 1])] -fn test(input: &[u8]) -> Result<(), crate::Error> { +fn test_basic_encrypt_decrypt_roundtrip(input: &[u8]) -> Result<(), crate::Error> { let encrypted = encrypt_data(PASSWORD, input)?; let decrypted = decrypt_data(PASSWORD, &encrypted)?; assert_eq!(input, decrypted, "input is not equal output"); Ok(()) } + +#[test] +fn test_encrypt_decrypt_with_different_passwords() -> Result<(), crate::Error> { + let data = b"sensitive data"; + let password1 = b"password1"; + let password2 = b"password2"; + + let encrypted = encrypt_data(password1, data)?; + + // Decrypting with correct password should work + let decrypted = decrypt_data(password1, &encrypted)?; + assert_eq!(data, decrypted.as_slice()); + + // Decrypting with wrong password should fail + let result = decrypt_data(password2, &encrypted); + assert!(result.is_err(), "Decryption with wrong password should fail"); + + Ok(()) +} + +#[test] +fn test_encrypt_decrypt_empty_data() -> Result<(), crate::Error> { + let empty_data = b""; + let encrypted = encrypt_data(PASSWORD, empty_data)?; + let decrypted = decrypt_data(PASSWORD, &encrypted)?; + assert_eq!(empty_data, decrypted.as_slice()); + Ok(()) +} + +#[test] +fn test_encrypt_decrypt_large_data() -> Result<(), crate::Error> { + // Test with 1MB of data + let large_data = vec![0xAB; 1024 * 1024]; + let encrypted = encrypt_data(PASSWORD, &large_data)?; + let decrypted = decrypt_data(PASSWORD, &encrypted)?; + assert_eq!(large_data, decrypted); + Ok(()) +} + +#[test] +fn test_encrypt_decrypt_with_empty_password() -> Result<(), crate::Error> { + let data = b"test data"; + let encrypted = encrypt_data(EMPTY_PASSWORD, data)?; + let decrypted = decrypt_data(EMPTY_PASSWORD, &encrypted)?; + assert_eq!(data, decrypted.as_slice()); + Ok(()) +} + +#[test] +fn test_encrypt_decrypt_with_long_password() -> Result<(), crate::Error> { + let data = b"test data with long password"; + let encrypted = encrypt_data(LONG_PASSWORD, data)?; + let decrypted = decrypt_data(LONG_PASSWORD, &encrypted)?; + assert_eq!(data, decrypted.as_slice()); + Ok(()) +} + +#[test] +fn test_encrypt_decrypt_binary_data() -> Result<(), crate::Error> { + // Test with various binary patterns + let binary_patterns = [ + vec![0x00; 100], // All zeros + vec![0xFF; 100], // All ones + (0..=255u8).cycle().take(1000).collect::>(), // Sequential pattern + vec![0xAA, 0x55].repeat(500), // Alternating pattern + ]; + + for pattern in &binary_patterns { + let encrypted = encrypt_data(PASSWORD, pattern)?; + let decrypted = decrypt_data(PASSWORD, &encrypted)?; + assert_eq!(pattern, &decrypted, "Binary pattern mismatch"); + } + Ok(()) +} + +#[test] +fn test_encrypt_decrypt_unicode_data() -> Result<(), crate::Error> { + let unicode_strings = [ + "Hello, 世界! 🌍", + "Тест на русском языке", + "العربية اختبار", + "🚀🔐💻🌟⭐", + "Mixed: ASCII + 中文 + العربية + 🎉", + ]; + + for text in &unicode_strings { + let data = text.as_bytes(); + let encrypted = encrypt_data(PASSWORD, data)?; + let decrypted = decrypt_data(PASSWORD, &encrypted)?; + assert_eq!(data, decrypted.as_slice(), "Unicode data mismatch for: {}", text); + } + Ok(()) +} + +#[test] +fn test_decrypt_with_corrupted_data() { + let data = b"test data"; + let encrypted = encrypt_data(PASSWORD, data).expect("Encryption should succeed"); + + // Test various corruption scenarios + let corruption_tests = [ + (0, "Corrupt first byte"), + (encrypted.len() - 1, "Corrupt last byte"), + (encrypted.len() / 2, "Corrupt middle byte"), + ]; + + for (corrupt_index, description) in &corruption_tests { + let mut corrupted = encrypted.clone(); + corrupted[*corrupt_index] ^= 0xFF; // Flip all bits + + let result = decrypt_data(PASSWORD, &corrupted); + assert!(result.is_err(), "{} should cause decryption to fail", description); + } +} + +#[test] +fn test_decrypt_with_truncated_data() { + let data = b"test data for truncation"; + let encrypted = encrypt_data(PASSWORD, data).expect("Encryption should succeed"); + + // Test truncation at various lengths + let truncation_lengths = [ + 0, // Empty data + 10, // Very short + 32, // Salt length + 44, // Just before nonce + encrypted.len() - 1, // Missing last byte + ]; + + for &length in &truncation_lengths { + let truncated = &encrypted[..length.min(encrypted.len())]; + let result = decrypt_data(PASSWORD, truncated); + assert!(result.is_err(), "Truncated data (length {}) should cause decryption to fail", length); + } +} + +#[test] +fn test_decrypt_with_invalid_header() { + let data = b"test data"; + let mut encrypted = encrypt_data(PASSWORD, data).expect("Encryption should succeed"); + + // Corrupt the algorithm ID (byte 32) + if encrypted.len() > 32 { + encrypted[32] = 0xFF; // Invalid algorithm ID + let result = decrypt_data(PASSWORD, &encrypted); + assert!(result.is_err(), "Invalid algorithm ID should cause decryption to fail"); + } +} + +#[test] +fn test_encryption_produces_different_outputs() -> Result<(), crate::Error> { + let data = b"same data"; + + // Encrypt the same data multiple times + let encrypted1 = encrypt_data(PASSWORD, data)?; + let encrypted2 = encrypt_data(PASSWORD, data)?; + + // Encrypted outputs should be different due to random salt and nonce + assert_ne!(encrypted1, encrypted2, "Encryption should produce different outputs for same input"); + + // But both should decrypt to the same original data + let decrypted1 = decrypt_data(PASSWORD, &encrypted1)?; + let decrypted2 = decrypt_data(PASSWORD, &encrypted2)?; + assert_eq!(decrypted1, decrypted2); + assert_eq!(data, decrypted1.as_slice()); + + Ok(()) +} + +#[test] +fn test_encrypted_data_structure() -> Result<(), crate::Error> { + let data = b"test data"; + let encrypted = encrypt_data(PASSWORD, data)?; + + // Encrypted data should be longer than original (due to salt, nonce, tag) + assert!(encrypted.len() > data.len(), "Encrypted data should be longer than original"); + + // Should have at least: 32 bytes salt + 1 byte ID + 12 bytes nonce + data + 16 bytes tag + let min_expected_length = 32 + 1 + 12 + data.len() + 16; + assert!(encrypted.len() >= min_expected_length, + "Encrypted data length {} should be at least {}", encrypted.len(), min_expected_length); + + Ok(()) +} + +#[test] +fn test_password_variations() -> Result<(), crate::Error> { + let data = b"test data"; + + let password_variations = [ + b"a".as_slice(), // Single character + b"12345".as_slice(), // Numeric + b"!@#$%^&*()".as_slice(), // Special characters + b"\x00\x01\x02\x03".as_slice(), // Binary password + "密码测试".as_bytes(), // Unicode password + &[0xFF; 64], // Long binary password + ]; + + for password in &password_variations { + let encrypted = encrypt_data(password, data)?; + let decrypted = decrypt_data(password, &encrypted)?; + assert_eq!(data, decrypted.as_slice(), "Failed with password: {:?}", password); + } + + Ok(()) +} + +#[test] +fn test_deterministic_with_same_salt_and_nonce() { + // Note: This test is more for understanding the behavior + // In real implementation, salt and nonce should be random + let data = b"test data"; + + let encrypted1 = encrypt_data(PASSWORD, data).expect("Encryption should succeed"); + let encrypted2 = encrypt_data(PASSWORD, data).expect("Encryption should succeed"); + + // Due to random salt and nonce, outputs should be different + assert_ne!(encrypted1, encrypted2, "Encryption should use random salt/nonce"); +} + +#[test] +fn test_cross_platform_compatibility() -> Result<(), crate::Error> { + // Test data that might behave differently on different platforms + let test_cases = [ + vec![0x00, 0x01, 0x02, 0x03], // Low values + vec![0xFC, 0xFD, 0xFE, 0xFF], // High values + (0..256u16).map(|x| (x % 256) as u8).collect::>(), // Full byte range + ]; + + for test_data in &test_cases { + let encrypted = encrypt_data(PASSWORD, test_data)?; + let decrypted = decrypt_data(PASSWORD, &encrypted)?; + assert_eq!(test_data, &decrypted, "Cross-platform compatibility failed"); + } + + Ok(()) +} + +#[test] +fn test_memory_safety_with_large_passwords() -> Result<(), crate::Error> { + let data = b"test data"; + + // Test with very large passwords + let large_passwords = [ + vec![b'a'; 1024], // 1KB password + vec![b'x'; 10 * 1024], // 10KB password + (0..=255u8).cycle().take(5000).collect::>(), // 5KB varied password + ]; + + for password in &large_passwords { + let encrypted = encrypt_data(password, data)?; + let decrypted = decrypt_data(password, &encrypted)?; + assert_eq!(data, decrypted.as_slice(), "Failed with large password of size {}", password.len()); + } + + Ok(()) +} + +#[test] +fn test_concurrent_encryption_safety() -> Result<(), crate::Error> { + use std::sync::Arc; + use std::thread; + + let data = Arc::new(b"concurrent test data".to_vec()); + let password = Arc::new(b"concurrent_password".to_vec()); + + let handles: Vec<_> = (0..10).map(|i| { + let data = Arc::clone(&data); + let password = Arc::clone(&password); + + thread::spawn(move || { + let encrypted = encrypt_data(&password, &data).expect("Encryption should succeed"); + let decrypted = decrypt_data(&password, &encrypted).expect("Decryption should succeed"); + assert_eq!(**data, decrypted, "Thread {} failed", i); + }) + }).collect(); + + for handle in handles { + handle.join().expect("Thread should complete successfully"); + } + + Ok(()) +} diff --git a/crypto/src/jwt/tests.rs b/crypto/src/jwt/tests.rs index 627e8802..04cd0222 100644 --- a/crypto/src/jwt/tests.rs +++ b/crypto/src/jwt/tests.rs @@ -1,19 +1,337 @@ use time::OffsetDateTime; +use serde_json::json; use super::{decode::decode, encode::encode}; #[test] -fn test() { - let claims = serde_json::json!({ +fn test_jwt_encode_decode_basic() { + let claims = json!({ "exp": OffsetDateTime::now_utc().unix_timestamp() + 1000, - "aaa": 1, - "bbb": "bbb" + "sub": "user123", + "iat": OffsetDateTime::now_utc().unix_timestamp(), + "role": "admin" }); - let jwt_token = encode(b"aaaa", &claims).unwrap_or_default(); - let new_claims = match decode(&jwt_token, b"aaaa") { - Ok(res) => Some(res.claims), - Err(_errr) => None, - }; - assert_eq!(new_claims, Some(claims)); + let secret = b"test_secret_key"; + let jwt_token = encode(secret, &claims).expect("Failed to encode JWT"); + + let decoded = decode(&jwt_token, secret).expect("Failed to decode JWT"); + assert_eq!(decoded.claims, claims); +} + +#[test] +fn test_jwt_encode_decode_with_complex_claims() { + let claims = json!({ + "exp": OffsetDateTime::now_utc().unix_timestamp() + 3600, + "sub": "user456", + "iat": OffsetDateTime::now_utc().unix_timestamp(), + "permissions": ["read", "write", "delete"], + "metadata": { + "department": "engineering", + "level": 5, + "active": true + }, + "custom_field": null + }); + + let secret = b"complex_secret_key_123"; + let jwt_token = encode(secret, &claims).expect("Failed to encode complex JWT"); + + let decoded = decode(&jwt_token, secret).expect("Failed to decode complex JWT"); + assert_eq!(decoded.claims, claims); +} + +#[test] +fn test_jwt_decode_with_wrong_secret() { + let claims = json!({ + "exp": OffsetDateTime::now_utc().unix_timestamp() + 1000, + "sub": "user123" + }); + + let correct_secret = b"correct_secret"; + let wrong_secret = b"wrong_secret"; + + let jwt_token = encode(correct_secret, &claims).expect("Failed to encode JWT"); + + // Decoding with wrong secret should fail + let result = decode(&jwt_token, wrong_secret); + assert!(result.is_err(), "Decoding with wrong secret should fail"); +} + +#[test] +fn test_jwt_decode_invalid_token_format() { + let secret = b"test_secret"; + + // Test various invalid token formats + let invalid_tokens = [ + "", // Empty token + "invalid", // Not a JWT format + "header.payload", // Missing signature + "header.payload.signature.extra", // Too many parts + "invalid.header.signature", // Invalid base64 + "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.invalid.signature", // Invalid payload + ]; + + for invalid_token in &invalid_tokens { + let result = decode(invalid_token, secret); + assert!(result.is_err(), "Invalid token '{}' should fail to decode", invalid_token); + } +} + +#[test] +fn test_jwt_with_expired_token() { + let expired_claims = json!({ + "exp": OffsetDateTime::now_utc().unix_timestamp() - 1000, // Expired 1000 seconds ago + "sub": "user123", + "iat": OffsetDateTime::now_utc().unix_timestamp() - 2000 + }); + + let secret = b"test_secret"; + let jwt_token = encode(secret, &expired_claims).expect("Failed to encode expired JWT"); + + // Decoding expired token should fail + let result = decode(&jwt_token, secret); + assert!(result.is_err(), "Expired token should fail to decode"); +} + +#[test] +fn test_jwt_with_future_issued_at() { + let future_claims = json!({ + "exp": OffsetDateTime::now_utc().unix_timestamp() + 3600, + "sub": "user123", + "iat": OffsetDateTime::now_utc().unix_timestamp() + 1000 // Issued in future + }); + + let secret = b"test_secret"; + let jwt_token = encode(secret, &future_claims).expect("Failed to encode future JWT"); + + // Note: The current JWT implementation may not validate iat by default + // This test documents the current behavior - future iat tokens may still decode successfully + let result = decode(&jwt_token, secret); + // For now, we just verify the token can be decoded, but in a production system + // you might want to add custom validation for iat claims + assert!(result.is_ok(), "Token decoding should succeed, but iat validation should be handled separately"); +} + +#[test] +fn test_jwt_with_empty_claims() { + let empty_claims = json!({ + "exp": OffsetDateTime::now_utc().unix_timestamp() + 1000, // Add required exp claim + }); + let secret = b"test_secret"; + + let jwt_token = encode(secret, &empty_claims).expect("Failed to encode empty claims JWT"); + let decoded = decode(&jwt_token, secret).expect("Failed to decode empty claims JWT"); + + assert_eq!(decoded.claims, empty_claims); +} + +#[test] +fn test_jwt_with_different_secret_lengths() { + let claims = json!({ + "exp": OffsetDateTime::now_utc().unix_timestamp() + 1000, + "sub": "user123" + }); + + // Test with various secret lengths + let secrets = [ + b"a".as_slice(), // Very short + b"short_key".as_slice(), // Short + b"medium_length_secret_key".as_slice(), // Medium + b"very_long_secret_key_with_many_characters_for_testing_purposes".as_slice(), // Long + ]; + + for secret in &secrets { + let jwt_token = encode(secret, &claims) + .expect(&format!("Failed to encode JWT with secret length {}", secret.len())); + + let decoded = decode(&jwt_token, secret) + .expect(&format!("Failed to decode JWT with secret length {}", secret.len())); + + assert_eq!(decoded.claims, claims); + } +} + +#[test] +fn test_jwt_with_special_characters_in_claims() { + let claims = json!({ + "exp": OffsetDateTime::now_utc().unix_timestamp() + 1000, + "sub": "user@example.com", + "name": "John Doe", + "description": "User with special chars: !@#$%^&*()_+-=[]{}|;':\",./<>?", + "unicode": "测试用户 🚀 émojis", + "newlines": "line1\nline2\r\nline3", + "quotes": "He said \"Hello\" and she replied 'Hi'" + }); + + let secret = b"test_secret"; + let jwt_token = encode(secret, &claims).expect("Failed to encode JWT with special characters"); + + let decoded = decode(&jwt_token, secret).expect("Failed to decode JWT with special characters"); + assert_eq!(decoded.claims, claims); +} + +#[test] +fn test_jwt_with_large_payload() { + // Create a large payload to test size limits + let large_data = "x".repeat(10000); // 10KB of data + let claims = json!({ + "exp": OffsetDateTime::now_utc().unix_timestamp() + 1000, + "sub": "user123", + "large_field": large_data + }); + + let secret = b"test_secret"; + let jwt_token = encode(secret, &claims).expect("Failed to encode large JWT"); + + let decoded = decode(&jwt_token, secret).expect("Failed to decode large JWT"); + assert_eq!(decoded.claims, claims); +} + +#[test] +fn test_jwt_token_structure() { + let claims = json!({ + "exp": OffsetDateTime::now_utc().unix_timestamp() + 1000, + "sub": "user123" + }); + + let secret = b"test_secret"; + let jwt_token = encode(secret, &claims).expect("Failed to encode JWT"); + + // JWT should have exactly 3 parts separated by dots + let parts: Vec<&str> = jwt_token.split('.').collect(); + assert_eq!(parts.len(), 3, "JWT should have exactly 3 parts"); + + // Each part should be non-empty + for (i, part) in parts.iter().enumerate() { + assert!(!part.is_empty(), "JWT part {} should not be empty", i); + } +} + +#[test] +fn test_jwt_deterministic_encoding() { + let claims = json!({ + "exp": 1234567890, // Fixed timestamp for deterministic test + "sub": "user123", + "iat": 1234567800 + }); + + let secret = b"test_secret"; + + // Encode the same claims multiple times + let token1 = encode(secret, &claims).expect("Failed to encode JWT 1"); + let token2 = encode(secret, &claims).expect("Failed to encode JWT 2"); + + // Tokens should be identical for same input + assert_eq!(token1, token2, "JWT encoding should be deterministic"); +} + +#[test] +fn test_jwt_cross_compatibility() { + let claims = json!({ + "exp": OffsetDateTime::now_utc().unix_timestamp() + 1000, + "sub": "user123" + }); + + let secret1 = b"secret1"; + let secret2 = b"secret2"; + + // Encode with secret1 + let token1 = encode(secret1, &claims).expect("Failed to encode with secret1"); + + // Decode with secret1 should work + let decoded1 = decode(&token1, secret1).expect("Failed to decode with correct secret"); + assert_eq!(decoded1.claims, claims); + + // Decode with secret2 should fail + let result2 = decode(&token1, secret2); + assert!(result2.is_err(), "Decoding with different secret should fail"); +} + +#[test] +fn test_jwt_header_algorithm() { + let claims = json!({ + "exp": OffsetDateTime::now_utc().unix_timestamp() + 1000, + "sub": "user123" + }); + + let secret = b"test_secret"; + let jwt_token = encode(secret, &claims).expect("Failed to encode JWT"); + + let decoded = decode(&jwt_token, secret).expect("Failed to decode JWT"); + + // Verify the algorithm in header is HS512 + assert_eq!(decoded.header.alg, jsonwebtoken::Algorithm::HS512); + assert_eq!(decoded.header.typ, Some("JWT".to_string())); +} + +#[test] +fn test_jwt_claims_validation() { + let now = OffsetDateTime::now_utc().unix_timestamp(); + + let valid_claims = json!({ + "exp": now + 3600, // Expires in 1 hour + "iat": now - 60, // Issued 1 minute ago + "nbf": now - 30, // Not before 30 seconds ago + "sub": "user123" + }); + + let secret = b"test_secret"; + let jwt_token = encode(secret, &valid_claims).expect("Failed to encode valid JWT"); + + let decoded = decode(&jwt_token, secret).expect("Failed to decode valid JWT"); + assert_eq!(decoded.claims, valid_claims); +} + +#[test] +fn test_jwt_with_numeric_claims() { + let claims = json!({ + "exp": OffsetDateTime::now_utc().unix_timestamp() + 1000, + "sub": "user123", + "age": 25, + "score": 95.5, + "count": 0, + "negative": -10 + }); + + let secret = b"test_secret"; + let jwt_token = encode(secret, &claims).expect("Failed to encode numeric JWT"); + + let decoded = decode(&jwt_token, secret).expect("Failed to decode numeric JWT"); + assert_eq!(decoded.claims, claims); +} + +#[test] +fn test_jwt_with_boolean_claims() { + let claims = json!({ + "exp": OffsetDateTime::now_utc().unix_timestamp() + 1000, + "sub": "user123", + "is_admin": true, + "is_active": false, + "email_verified": true + }); + + let secret = b"test_secret"; + let jwt_token = encode(secret, &claims).expect("Failed to encode boolean JWT"); + + let decoded = decode(&jwt_token, secret).expect("Failed to decode boolean JWT"); + assert_eq!(decoded.claims, claims); +} + +#[test] +fn test_jwt_with_array_claims() { + let claims = json!({ + "exp": OffsetDateTime::now_utc().unix_timestamp() + 1000, + "sub": "user123", + "roles": ["admin", "user", "moderator"], + "permissions": [1, 2, 3, 4, 5], + "tags": [], + "mixed_array": ["string", 123, true, null] + }); + + let secret = b"test_secret"; + let jwt_token = encode(secret, &claims).expect("Failed to encode array JWT"); + + let decoded = decode(&jwt_token, secret).expect("Failed to decode array JWT"); + assert_eq!(decoded.claims, claims); }