error.rs
1 //! Error types for the Dead Drop core library. 2 //! 3 //! This module defines all error types used throughout the library, 4 //! providing detailed error information while avoiding exposure of 5 //! sensitive data in error messages. 6 //! 7 //! # Design Principles 8 //! 9 //! - **Security First**: Error messages never include sensitive data like keys, 10 //! plaintexts, or partial secrets. Generic messages are used for cryptographic 11 //! failures to prevent information leakage. 12 //! 13 //! - **Categorization**: Errors are grouped by subsystem (crypto, handshake, 14 //! protocol, storage, identity) for easier handling and debugging. 15 //! 16 //! - **Conversion**: Common error types from dependencies are automatically 17 //! converted via `From` implementations, simplifying error propagation. 18 //! 19 //! # Usage 20 //! 21 //! ```rust 22 //! use dead_drop_core::error::{DeadDropError, Result}; 23 //! 24 //! fn validate_key(key: &[u8]) -> Result<()> { 25 //! if key.len() != 32 { 26 //! return Err(DeadDropError::InvalidKey( 27 //! "Key must be 32 bytes".to_string() 28 //! )); 29 //! } 30 //! Ok(()) 31 //! } 32 //! ``` 33 //! 34 //! # Error Handling Best Practices 35 //! 36 //! 1. **Use the `?` operator** to propagate errors up the call stack 37 //! 2. **Match on specific variants** when recovery is possible 38 //! 3. **Log errors appropriately** but never log sensitive data 39 //! 4. **Convert to user-friendly messages** at the UI layer 40 41 use thiserror::Error; 42 43 /// Main error type for Dead Drop operations. 44 /// 45 /// This enum categorizes all errors that can occur during Dead Drop operations. 46 /// Each variant provides context about what went wrong while being careful not 47 /// to leak sensitive information. 48 /// 49 /// # Categories 50 /// 51 /// - **Cryptographic Errors**: Key generation, encryption, decryption, signing 52 /// - **Handshake Errors**: Noise protocol failures, peer verification 53 /// - **Protocol Errors**: Message format, replay detection, expiration 54 /// - **Storage Errors**: Database operations, encryption at rest 55 /// - **Identity Errors**: QR codes, identity initialization 56 /// - **General Errors**: Serialization, input validation 57 /// 58 /// # Security Note 59 /// 60 /// Error messages are intentionally vague for cryptographic operations to 61 /// prevent information leakage that could aid attackers. For example, 62 /// `InvalidSignature` doesn't indicate whether the key or message was wrong. 63 #[derive(Error, Debug)] 64 pub enum DeadDropError { 65 // ==================== Cryptographic Errors ==================== 66 // These errors indicate failures in low-level cryptographic operations. 67 // Messages are kept generic to avoid leaking information to attackers. 68 69 /// Key generation failed. 70 /// 71 /// This typically indicates a problem with the random number generator 72 /// or an internal library error. Should be rare in practice. 73 #[error("Key generation failed: {0}")] 74 KeyGeneration(String), 75 76 /// Encryption operation failed. 77 /// 78 /// Common causes: 79 /// - Plaintext exceeds maximum size limit 80 /// - Internal AEAD cipher error (should be rare) 81 #[error("Encryption failed: {0}")] 82 Encryption(String), 83 84 /// Decryption operation failed. 85 /// 86 /// This error is returned when: 87 /// - Authentication tag verification fails (tampering detected) 88 /// - Wrong key was used 89 /// - Ciphertext was corrupted 90 /// 91 /// # Security Note 92 /// 93 /// The error message intentionally doesn't distinguish between these 94 /// cases to prevent oracle attacks. 95 #[error("Decryption failed: {0}")] 96 Decryption(String), 97 98 /// Signature verification failed. 99 /// 100 /// The Ed25519 signature did not match the message and public key. 101 /// This could indicate tampering or use of the wrong key. 102 /// 103 /// # Security Note 104 /// 105 /// This error is intentionally opaque to prevent distinguishing 106 /// between "wrong key" and "tampered message" scenarios. 107 #[error("Invalid signature")] 108 InvalidSignature, 109 110 /// Key derivation failed. 111 /// 112 /// HKDF or other KDF operation could not complete. This typically 113 /// indicates invalid parameters (e.g., output length too large). 114 #[error("Key derivation failed: {0}")] 115 KeyDerivation(String), 116 117 /// Invalid key format or length. 118 /// 119 /// The provided key bytes could not be parsed as a valid cryptographic 120 /// key. Common causes: 121 /// - Wrong length (e.g., 16 bytes instead of 32) 122 /// - Invalid curve point for Ed25519/X25519 123 #[error("Invalid key: {0}")] 124 InvalidKey(String), 125 126 // ==================== Handshake Errors ==================== 127 // These errors occur during the Noise XX handshake protocol. 128 // They indicate either protocol violations or active attacks. 129 130 /// Noise protocol handshake failed. 131 /// 132 /// The Noise XX handshake could not complete. Possible causes: 133 /// - Invalid handshake message format 134 /// - Decryption failure during handshake 135 /// - Protocol state machine violation 136 #[error("Handshake failed: {0}")] 137 Handshake(String), 138 139 /// Handshake state is invalid for the requested operation. 140 /// 141 /// Operations like `write_message` or `read_message` were called 142 /// in the wrong order for the current handshake state. 143 #[error("Invalid handshake state: {0}")] 144 InvalidHandshakeState(String), 145 146 /// Remote peer identity verification failed. 147 /// 148 /// The peer's static public key could not be verified. This may 149 /// indicate a man-in-the-middle attack. 150 #[error("Peer identity verification failed")] 151 PeerVerificationFailed, 152 153 // ==================== Protocol Errors ==================== 154 // These errors indicate violations of the Dead Drop wire protocol 155 // or application-level security policy violations. 156 157 /// Message format is invalid or malformed. 158 /// 159 /// The message bytes could not be parsed according to the expected 160 /// wire format. This may indicate corruption or version mismatch. 161 #[error("Invalid message format: {0}")] 162 InvalidFormat(String), 163 164 /// Replay attack detected (message ID already seen). 165 /// 166 /// A message with a previously-seen ID was received. This is a 167 /// security violation indicating either a replay attack or a 168 /// duplicate delivery. 169 /// 170 /// # Response 171 /// 172 /// The message should be silently discarded without notifying 173 /// the sender. 174 #[error("Replay attack detected")] 175 ReplayDetected, 176 177 /// Message has expired (timestamp too old). 178 /// 179 /// The message timestamp is outside the acceptable window. 180 /// Messages older than the configured maximum age are rejected 181 /// to prevent delayed replay attacks. 182 #[error("Message expired")] 183 MessageExpired, 184 185 /// Sender is not in contacts. 186 /// 187 /// A message was received from a device whose public key is not 188 /// in the contact list. This may be a legitimate unknown sender 189 /// or an attack. 190 #[error("Unknown contact")] 191 UnknownContact, 192 193 /// Message size exceeds maximum allowed. 194 /// 195 /// The plaintext or ciphertext exceeds the configured size limit. 196 /// This prevents memory exhaustion attacks. 197 #[error("Message too large: {size} bytes (max: {max})")] 198 MessageTooLarge { 199 /// Actual size of the message in bytes. 200 size: usize, 201 /// Maximum allowed size in bytes. 202 max: usize, 203 }, 204 205 /// Invalid message state transition. 206 /// 207 /// The message lifecycle state machine does not allow the 208 /// requested transition. For example, a "delivered" message 209 /// cannot transition back to "pending". 210 #[error("Invalid state transition: {from} -> {to}")] 211 InvalidStateTransition { 212 /// The current state of the message. 213 from: String, 214 /// The requested target state. 215 to: String, 216 }, 217 218 // ==================== Storage Errors ==================== 219 /// Database operation failed 220 #[error("Database error: {0}")] 221 Database(String), 222 223 /// Storage encryption/decryption failed 224 #[error("Storage encryption error: {0}")] 225 StorageEncryption(String), 226 227 /// Data not found in storage 228 #[error("Not found: {0}")] 229 NotFound(String), 230 231 /// Data already exists (duplicate) 232 #[error("Already exists: {0}")] 233 AlreadyExists(String), 234 235 // ==================== Identity Errors ==================== 236 /// Identity has not been initialized 237 #[error("Identity not initialized")] 238 IdentityNotInitialized, 239 240 /// QR code payload is invalid 241 #[error("Invalid QR code: {0}")] 242 InvalidQRCode(String), 243 244 /// QR code has expired 245 #[error("QR code expired")] 246 QRCodeExpired, 247 248 // ==================== General Errors ==================== 249 /// Serialization failed 250 #[error("Serialization error: {0}")] 251 Serialization(String), 252 253 /// Deserialization failed 254 #[error("Deserialization error: {0}")] 255 Deserialization(String), 256 257 /// Invalid input provided 258 #[error("Invalid input: {0}")] 259 InvalidInput(String), 260 261 /// Internal error (should not happen) 262 #[error("Internal error: {0}")] 263 Internal(String), 264 } 265 266 /// Result type alias for Dead Drop operations. 267 pub type Result<T> = std::result::Result<T, DeadDropError>; 268 269 // ============================================================================= 270 // ERROR CONVERSIONS 271 // ============================================================================= 272 // 273 // These `From` implementations allow using the `?` operator to propagate 274 // errors from external crates. They intentionally obscure internal details 275 // to prevent information leakage. 276 277 /// Conversion from ChaCha20-Poly1305 AEAD errors. 278 /// 279 /// The original error is discarded to prevent leaking information about 280 /// why decryption failed (which could aid oracle attacks). 281 impl From<chacha20poly1305::Error> for DeadDropError { 282 fn from(_: chacha20poly1305::Error) -> Self { 283 // Don't expose internal crypto error details - could aid attackers 284 DeadDropError::Decryption("AEAD operation failed".to_string()) 285 } 286 } 287 288 /// Conversion from Ed25519 signature errors. 289 /// 290 /// All signature errors are converted to `InvalidSignature` without 291 /// additional context to prevent distinguishing between key errors 292 /// and signature errors. 293 impl From<ed25519_dalek::SignatureError> for DeadDropError { 294 fn from(_: ed25519_dalek::SignatureError) -> Self { 295 DeadDropError::InvalidSignature 296 } 297 } 298 299 /// Conversion from Snow (Noise protocol) errors. 300 /// 301 /// Handshake errors may contain useful debugging information, so we 302 /// preserve the error message. In production, consider logging these 303 /// but not exposing them to users. 304 impl From<snow::Error> for DeadDropError { 305 fn from(e: snow::Error) -> Self { 306 DeadDropError::Handshake(e.to_string()) 307 } 308 } 309 310 /// Conversion from bincode serialization errors. 311 /// 312 /// Serialization errors are generally safe to expose as they don't 313 /// leak cryptographic secrets. 314 impl From<bincode::Error> for DeadDropError { 315 fn from(e: bincode::Error) -> Self { 316 DeadDropError::Serialization(e.to_string()) 317 } 318 } 319 320 /// Conversion from Base64 decoding errors. 321 /// 322 /// These errors are safe to expose as they only indicate malformed 323 /// input encoding. 324 impl From<base64::DecodeError> for DeadDropError { 325 fn from(e: base64::DecodeError) -> Self { 326 DeadDropError::Deserialization(format!("Base64 decode error: {}", e)) 327 } 328 } 329 330 /// Conversion from slice-to-array conversion errors. 331 /// 332 /// These occur when a byte slice doesn't match the expected fixed size, 333 /// typically when parsing keys or signatures. 334 impl From<std::array::TryFromSliceError> for DeadDropError { 335 fn from(e: std::array::TryFromSliceError) -> Self { 336 DeadDropError::InvalidKey(format!("Invalid key length: {}", e)) 337 } 338 } 339 340 /// Conversion from rusqlite database errors. 341 /// 342 /// Database errors are converted to the generic Database variant. 343 /// The error message is preserved for debugging but should not be 344 /// exposed to users. 345 impl From<rusqlite::Error> for DeadDropError { 346 fn from(e: rusqlite::Error) -> Self { 347 DeadDropError::Database(e.to_string()) 348 } 349 } 350 351 #[cfg(test)] 352 mod tests { 353 use super::*; 354 355 #[test] 356 fn test_error_display() { 357 let err = DeadDropError::KeyGeneration("test error".to_string()); 358 assert_eq!(format!("{}", err), "Key generation failed: test error"); 359 360 let err = DeadDropError::InvalidSignature; 361 assert_eq!(format!("{}", err), "Invalid signature"); 362 363 let err = DeadDropError::MessageTooLarge { 364 size: 1000, 365 max: 500, 366 }; 367 assert_eq!( 368 format!("{}", err), 369 "Message too large: 1000 bytes (max: 500)" 370 ); 371 } 372 373 #[test] 374 fn test_error_debug() { 375 let err = DeadDropError::ReplayDetected; 376 // Debug format should include the variant name 377 let debug_str = format!("{:?}", err); 378 assert!(debug_str.contains("ReplayDetected")); 379 } 380 381 #[test] 382 fn test_result_type() { 383 fn returns_ok() -> Result<i32> { 384 Ok(42) 385 } 386 387 fn returns_err() -> Result<i32> { 388 Err(DeadDropError::InvalidInput("test".to_string())) 389 } 390 391 assert!(returns_ok().is_ok()); 392 assert!(returns_err().is_err()); 393 } 394 395 #[test] 396 fn test_error_conversion_from_bincode() { 397 // Create a bincode error by trying to deserialize invalid data 398 let invalid_data: &[u8] = &[]; 399 let result: std::result::Result<String, _> = bincode::deserialize(invalid_data); 400 if let Err(bincode_err) = result { 401 let dead_drop_err: DeadDropError = bincode_err.into(); 402 assert!(matches!(dead_drop_err, DeadDropError::Serialization(_))); 403 } 404 } 405 406 #[test] 407 fn test_error_conversion_from_base64() { 408 use base64::Engine; 409 let result = base64::engine::general_purpose::STANDARD.decode("not valid base64!!!"); 410 if let Err(base64_err) = result { 411 let dead_drop_err: DeadDropError = base64_err.into(); 412 assert!(matches!(dead_drop_err, DeadDropError::Deserialization(_))); 413 } 414 } 415 }