Coverage for src / kdbxtool / exceptions.py: 91%
66 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-12-19 21:22 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2025-12-19 21:22 +0000
1"""Custom exception hierarchy for kdbxtool.
3This module provides a rich exception hierarchy for better error handling
4and user feedback. All exceptions inherit from KdbxError.
6Exception Hierarchy:
7 KdbxError (base)
8 ├── FormatError
9 │ ├── InvalidSignatureError
10 │ ├── UnsupportedVersionError
11 │ └── CorruptedDataError
12 ├── CryptoError
13 │ ├── DecryptionError
14 │ ├── AuthenticationError
15 │ ├── KdfError
16 │ ├── UnknownCipherError
17 │ └── TwofishNotAvailableError
18 ├── CredentialError
19 │ ├── InvalidPasswordError
20 │ ├── InvalidKeyFileError
21 │ ├── MissingCredentialsError
22 │ └── YubiKeyError
23 │ ├── YubiKeyNotFoundError
24 │ ├── YubiKeySlotError
25 │ ├── YubiKeyTimeoutError
26 │ └── YubiKeyNotAvailableError
27 └── DatabaseError
28 ├── EntryNotFoundError
29 └── GroupNotFoundError
31Security Note:
32 Exception messages are designed to avoid leaking sensitive information.
33 They provide enough context for debugging without exposing secrets.
34"""
36from __future__ import annotations
39class KdbxError(Exception):
40 """Base exception for all kdbxtool errors.
42 All exceptions raised by kdbxtool inherit from this class,
43 making it easy to catch all library-specific errors.
44 """
47# --- Format Errors ---
50class FormatError(KdbxError):
51 """Error in KDBX file format or structure.
53 Raised when the file doesn't conform to the KDBX specification.
54 """
57class InvalidSignatureError(FormatError):
58 """Invalid KDBX file signature (magic bytes).
60 The file doesn't start with the expected KDBX magic bytes,
61 indicating it's not a valid KeePass database file.
62 """
65class UnsupportedVersionError(FormatError):
66 """Unsupported KDBX version.
68 The file uses a KDBX version that this library doesn't support.
69 """
71 def __init__(self, version_major: int, version_minor: int) -> None:
72 self.version_major = version_major
73 self.version_minor = version_minor
74 super().__init__(f"Unsupported KDBX version: {version_major}.{version_minor}")
77class CorruptedDataError(FormatError):
78 """Database file is corrupted or truncated.
80 The file structure is invalid, possibly due to incomplete download,
81 disk corruption, or other data integrity issues.
82 """
85# --- Crypto Errors ---
88class CryptoError(KdbxError):
89 """Error in cryptographic operations.
91 Base class for all cryptographic errors including encryption,
92 decryption, and key derivation.
93 """
96class DecryptionError(CryptoError):
97 """Failed to decrypt database content.
99 This typically indicates wrong credentials (password/keyfile),
100 but the message is kept generic to avoid confirming which
101 credential component is incorrect.
102 """
104 def __init__(self, message: str = "Decryption failed") -> None:
105 super().__init__(message)
108class AuthenticationError(CryptoError):
109 """HMAC or integrity verification failed.
111 The database's authentication code doesn't match, indicating
112 either wrong credentials or data tampering.
113 """
115 def __init__(
116 self, message: str = "Authentication failed - wrong credentials or corrupted data"
117 ) -> None:
118 super().__init__(message)
121class KdfError(CryptoError):
122 """Error in key derivation function.
124 Problems with KDF parameters, unsupported KDF types,
125 or KDF computation failures.
126 """
129class UnknownCipherError(CryptoError):
130 """Unknown or unsupported cipher algorithm.
132 The database uses a cipher that this library doesn't recognize.
133 """
135 def __init__(self, cipher_uuid: bytes) -> None:
136 self.cipher_uuid = cipher_uuid
137 super().__init__(f"Unknown cipher: {cipher_uuid.hex()}")
140class TwofishNotAvailableError(CryptoError):
141 """Twofish cipher requested but oxifish package not installed.
143 The database uses Twofish encryption, which requires the optional
144 oxifish package. Install it with: pip install kdbxtool[twofish]
145 """
147 def __init__(self) -> None:
148 super().__init__(
149 "Twofish cipher requires the oxifish package. "
150 "Install with: pip install kdbxtool[twofish]"
151 )
154# --- Credential Errors ---
157class CredentialError(KdbxError):
158 """Error with database credentials.
160 Base class for credential-related errors. Messages are kept
161 generic to avoid information disclosure about which credential
162 component is incorrect.
163 """
166class InvalidPasswordError(CredentialError):
167 """Invalid or missing password.
169 Note: This is only raised when we can definitively determine
170 the password is wrong without revealing information about
171 other credential components.
172 """
174 def __init__(self, message: str = "Invalid password") -> None:
175 super().__init__(message)
178class InvalidKeyFileError(CredentialError):
179 """Invalid or missing keyfile.
181 The keyfile is malformed, has wrong format, or failed
182 hash verification.
183 """
185 def __init__(self, message: str = "Invalid keyfile") -> None:
186 super().__init__(message)
189class MissingCredentialsError(CredentialError):
190 """No credentials provided.
192 At least one credential (password or keyfile) is required
193 to open or create a database.
194 """
196 def __init__(self) -> None:
197 super().__init__("At least one credential (password or keyfile) is required")
200# --- YubiKey Errors ---
203class YubiKeyError(CredentialError):
204 """Error communicating with YubiKey.
206 Base class for YubiKey-related errors. These occur during
207 challenge-response authentication with a hardware YubiKey.
208 """
211class YubiKeyNotFoundError(YubiKeyError):
212 """No YubiKey detected.
214 No YubiKey device was found connected to the system.
215 Ensure the YubiKey is properly inserted.
216 """
218 def __init__(self, message: str | None = None) -> None:
219 super().__init__(message or "No YubiKey device found. Ensure it is connected.")
222class YubiKeySlotError(YubiKeyError):
223 """YubiKey slot not configured for HMAC-SHA1.
225 The specified slot on the YubiKey is not configured for
226 HMAC-SHA1 challenge-response authentication.
227 """
229 def __init__(self, slot: int) -> None:
230 self.slot = slot
231 super().__init__(f"YubiKey slot {slot} is not configured for HMAC-SHA1 challenge-response")
234class YubiKeyTimeoutError(YubiKeyError):
235 """YubiKey operation timed out.
237 The YubiKey operation timed out, typically because touch
238 was required but not received within the timeout period.
239 """
241 def __init__(self, timeout_seconds: float = 15.0) -> None:
242 self.timeout_seconds = timeout_seconds
243 super().__init__(
244 f"YubiKey operation timed out after {timeout_seconds}s. "
245 "Touch may be required - try again and press the YubiKey button."
246 )
249class YubiKeyNotAvailableError(YubiKeyError):
250 """YubiKey support requested but yubikey-manager not installed.
252 The yubikey-manager package is required for YubiKey challenge-response
253 authentication. Install it with: pip install kdbxtool[yubikey]
254 """
256 def __init__(self) -> None:
257 super().__init__(
258 "YubiKey support requires the yubikey-manager package. "
259 "Install with: pip install kdbxtool[yubikey]"
260 )
263# --- Database Errors ---
266class DatabaseError(KdbxError):
267 """Error in database operations.
269 Base class for errors that occur during database manipulation
270 after successful decryption.
271 """
274class EntryNotFoundError(DatabaseError):
275 """Entry not found in database.
277 The requested entry doesn't exist or was not found
278 in the specified location.
279 """
281 def __init__(self, message: str = "Entry not found") -> None:
282 super().__init__(message)
285class GroupNotFoundError(DatabaseError):
286 """Group not found in database.
288 The requested group doesn't exist or was not found
289 in the database hierarchy.
290 """
292 def __init__(self, message: str = "Group not found") -> None:
293 super().__init__(message)
296class InvalidXmlError(DatabaseError):
297 """Invalid or malformed XML payload.
299 The decrypted XML content doesn't conform to the expected
300 KDBX XML schema.
301 """
303 def __init__(self, message: str = "Invalid KDBX XML structure") -> None:
304 super().__init__(message)
307class Kdbx3UpgradeRequired(DatabaseError):
308 """KDBX3 database requires explicit upgrade confirmation.
310 When saving a KDBX3 database to its original file, explicit
311 confirmation is required since the save will upgrade it to KDBX4.
312 Use save(allow_upgrade=True) to confirm the upgrade.
313 """
315 def __init__(self) -> None:
316 super().__init__(
317 "Saving a KDBX3 database will upgrade it to KDBX4 format. "
318 "Use save(allow_upgrade=True) to confirm, or save to a different file."
319 )
322class MergeError(DatabaseError):
323 """Error during database merge operation.
325 Raised when a merge operation fails due to incompatible databases,
326 invalid state, or other merge-specific issues.
327 """
329 def __init__(self, message: str = "Merge operation failed") -> None:
330 super().__init__(message)