/ core / src / error.rs
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  }