1use thiserror::Error;
4
5pub trait AuthsErrorInfo {
11 fn error_code(&self) -> &'static str;
18
19 fn suggestion(&self) -> Option<&'static str>;
24}
25
26#[derive(Debug, Error)]
28#[non_exhaustive]
29pub enum AgentError {
30 #[error("Key not found")]
32 KeyNotFound,
33
34 #[error("Incorrect passphrase")]
36 IncorrectPassphrase,
37
38 #[error("Missing Passphrase")]
40 MissingPassphrase,
41
42 #[error("Security error: {0}")]
44 SecurityError(String),
45
46 #[error("Crypto error: {0}")]
48 CryptoError(String),
49
50 #[error("Key deserialization error: {0}")]
52 KeyDeserializationError(String),
53
54 #[error("Signing failed: {0}")]
56 SigningFailed(String),
57
58 #[error("Protocol error: {0}")]
60 Proto(String),
61
62 #[error("IO error: {0}")]
64 IO(#[from] std::io::Error),
65
66 #[error("git error: {0}")]
68 GitError(String),
69
70 #[error("Invalid input: {0}")]
72 InvalidInput(String),
73
74 #[error("Mutex lock poisoned: {0}")]
76 MutexError(String),
77
78 #[error("Storage error: {0}")]
80 StorageError(String),
81
82 #[error("User input cancelled")]
84 UserInputCancelled,
85
86 #[error("Keychain backend unavailable: {backend} - {reason}")]
89 BackendUnavailable {
90 backend: &'static str,
92 reason: String,
94 },
95
96 #[error("Storage is locked, authentication required")]
98 StorageLocked,
99
100 #[error("Failed to initialize keychain backend: {backend} - {error}")]
102 BackendInitFailed {
103 backend: &'static str,
105 error: String,
107 },
108
109 #[error("Credential too large for backend (max {max_bytes} bytes, got {actual_bytes})")]
111 CredentialTooLarge {
112 max_bytes: usize,
114 actual_bytes: usize,
116 },
117
118 #[error("Agent is locked. Unlock with 'auths agent unlock' or restart the agent.")]
120 AgentLocked,
121
122 #[error("Passphrase too weak: {0}")]
124 WeakPassphrase(String),
125
126 #[error("HSM PIN is locked — reset required")]
129 HsmPinLocked,
130
131 #[error("HSM device removed")]
133 HsmDeviceRemoved,
134
135 #[error("HSM session expired")]
137 HsmSessionExpired,
138
139 #[error("HSM does not support mechanism: {0}")]
141 HsmUnsupportedMechanism(String),
142}
143
144impl AuthsErrorInfo for AgentError {
145 fn error_code(&self) -> &'static str {
146 match self {
147 Self::KeyNotFound => "AUTHS_KEY_NOT_FOUND",
148 Self::IncorrectPassphrase => "AUTHS_INCORRECT_PASSPHRASE",
149 Self::MissingPassphrase => "AUTHS_MISSING_PASSPHRASE",
150 Self::SecurityError(_) => "AUTHS_SECURITY_ERROR",
151 Self::CryptoError(_) => "AUTHS_CRYPTO_ERROR",
152 Self::KeyDeserializationError(_) => "AUTHS_KEY_DESERIALIZATION_ERROR",
153 Self::SigningFailed(_) => "AUTHS_SIGNING_FAILED",
154 Self::Proto(_) => "AUTHS_PROTOCOL_ERROR",
155 Self::IO(_) => "AUTHS_IO_ERROR",
156 Self::GitError(_) => "AUTHS_GIT_ERROR",
157 Self::InvalidInput(_) => "AUTHS_INVALID_INPUT",
158 Self::MutexError(_) => "AUTHS_MUTEX_ERROR",
159 Self::StorageError(_) => "AUTHS_STORAGE_ERROR",
160 Self::UserInputCancelled => "AUTHS_USER_CANCELLED",
161 Self::BackendUnavailable { .. } => "AUTHS_BACKEND_UNAVAILABLE",
162 Self::StorageLocked => "AUTHS_STORAGE_LOCKED",
163 Self::BackendInitFailed { .. } => "AUTHS_BACKEND_INIT_FAILED",
164 Self::CredentialTooLarge { .. } => "AUTHS_CREDENTIAL_TOO_LARGE",
165 Self::AgentLocked => "AUTHS_AGENT_LOCKED",
166 Self::WeakPassphrase(_) => "AUTHS_WEAK_PASSPHRASE",
167 Self::HsmPinLocked => "AUTHS_HSM_PIN_LOCKED",
168 Self::HsmDeviceRemoved => "AUTHS_HSM_DEVICE_REMOVED",
169 Self::HsmSessionExpired => "AUTHS_HSM_SESSION_EXPIRED",
170 Self::HsmUnsupportedMechanism(_) => "AUTHS_HSM_UNSUPPORTED_MECHANISM",
171 }
172 }
173
174 fn suggestion(&self) -> Option<&'static str> {
175 match self {
176 Self::KeyNotFound => Some("Run `auths key list` to see available keys"),
177 Self::IncorrectPassphrase => Some("Check your passphrase and try again"),
178 Self::MissingPassphrase => {
179 Some("Provide a passphrase with --passphrase or set AUTHS_PASSPHRASE")
180 }
181 Self::BackendUnavailable { .. } => {
182 Some("Run `auths doctor` to diagnose keychain issues")
183 }
184 Self::StorageLocked => Some("Authenticate with your platform keychain"),
185 Self::BackendInitFailed { .. } => {
186 Some("Run `auths doctor` to diagnose keychain issues")
187 }
188 Self::GitError(_) => Some("Ensure you're in a Git repository"),
189 Self::AgentLocked => {
190 Some("Run `auths agent unlock` or restart with `auths agent start`")
191 }
192 Self::UserInputCancelled => {
193 Some("Run the command again and provide the required input")
194 }
195 Self::StorageError(_) => Some("Check file permissions and disk space"),
196 Self::SecurityError(_)
198 | Self::CryptoError(_)
199 | Self::KeyDeserializationError(_)
200 | Self::SigningFailed(_)
201 | Self::Proto(_)
202 | Self::IO(_)
203 | Self::InvalidInput(_)
204 | Self::MutexError(_)
205 | Self::CredentialTooLarge { .. } => None,
206 Self::WeakPassphrase(_) => {
207 Some("Use at least 12 characters with uppercase, lowercase, and a digit or symbol")
208 }
209 Self::HsmPinLocked => Some("Reset the HSM PIN using your HSM vendor's admin tools"),
210 Self::HsmDeviceRemoved => Some("Reconnect the HSM device and try again"),
211 Self::HsmSessionExpired => Some("Retry the operation — a new session will be opened"),
212 Self::HsmUnsupportedMechanism(_) => {
213 Some("Check that your HSM supports Ed25519 (CKM_EDDSA)")
214 }
215 }
216 }
217}
218
219#[derive(Debug, Error)]
221#[non_exhaustive]
222pub enum TrustError {
223 #[error("I/O error: {0}")]
225 Io(#[from] std::io::Error),
226 #[error("{0}")]
228 InvalidData(String),
229 #[error("not found: {0}")]
231 NotFound(String),
232 #[error("serialization error: {0}")]
234 Serialization(#[from] serde_json::Error),
235 #[error("already exists: {0}")]
237 AlreadyExists(String),
238 #[error("lock acquisition failed: {0}")]
240 Lock(String),
241 #[error("policy rejected: {0}")]
243 PolicyRejected(String),
244}
245
246impl AuthsErrorInfo for TrustError {
247 fn error_code(&self) -> &'static str {
248 match self {
249 Self::Io(_) => "AUTHS_TRUST_IO_ERROR",
250 Self::InvalidData(_) => "AUTHS_TRUST_INVALID_DATA",
251 Self::NotFound(_) => "AUTHS_TRUST_NOT_FOUND",
252 Self::Serialization(_) => "AUTHS_TRUST_SERIALIZATION_ERROR",
253 Self::AlreadyExists(_) => "AUTHS_TRUST_ALREADY_EXISTS",
254 Self::Lock(_) => "AUTHS_TRUST_LOCK_FAILED",
255 Self::PolicyRejected(_) => "AUTHS_TRUST_POLICY_REJECTED",
256 }
257 }
258
259 fn suggestion(&self) -> Option<&'static str> {
260 match self {
261 Self::NotFound(_) => Some("Run `auths trust list` to see pinned identities"),
262 Self::PolicyRejected(_) => Some("Run `auths trust add` to pin this identity"),
263 Self::Lock(_) => Some("Check file permissions and try again"),
264 Self::Io(_) => Some("Check disk space and file permissions"),
265 Self::AlreadyExists(_) => Some("Run `auths trust list` to see existing entries"),
266 Self::InvalidData(_) | Self::Serialization(_) => None,
267 }
268 }
269}
270
271impl From<AgentError> for ssh_agent_lib::error::AgentError {
272 fn from(err: AgentError) -> Self {
273 match err {
274 AgentError::KeyNotFound => Self::Failure,
275 AgentError::IncorrectPassphrase => Self::Failure,
276 _ => Self::Failure,
277 }
278 }
279}