hardware_enclave/
error.rs1#[non_exhaustive]
5#[derive(Debug, thiserror::Error)]
6pub enum Error {
7 #[error("hardware security module not available")]
9 NotAvailable,
10 #[error("key not found: {label}")]
12 KeyNotFound { label: String },
13 #[error("duplicate key label: {label}")]
15 DuplicateLabel { label: String },
16 #[error("invalid key label: {reason}")]
18 InvalidLabel { reason: String },
19 #[error("signing failed: {detail}")]
21 SignFailed { detail: String },
22 #[error("encryption failed: {detail}")]
24 EncryptFailed { detail: String },
25 #[error("decryption failed: {detail}")]
27 DecryptFailed { detail: String },
28 #[error("authentication denied for '{label}'")]
30 AuthDenied { label: String },
31 #[error("authentication required for '{label}': {detail}")]
33 AuthRequired { label: String, detail: String },
34 #[error("user cancelled authentication for '{label}'")]
36 UserCancelled { label: String },
37 #[error("key operation failed — {operation}: {detail}")]
39 KeyOperation { operation: String, detail: String },
40 #[error("tamper detected: {path}")]
42 TamperDetected { path: String },
43 #[error("feature '{feature}' requires a code-signed binary")]
48 RequiresSigning { feature: String },
49 #[error("access policy '{policy}' is not supported by the current backend")]
54 PolicyNotSupported { policy: String },
55 #[error("user presence is not available on this platform")]
60 PresenceNotAvailable,
61 #[error("not implemented: {feature}")]
63 NotImplemented { feature: String },
64 #[error("access policy mismatch: {detail}")]
69 PolicyMismatch { detail: String },
70 #[error("config error: {0}")]
72 Config(String),
73 #[error("I/O error: {0}")]
75 Io(#[from] std::io::Error),
76 #[error("memory error: {0}")]
78 Memory(String),
79}
80
81pub type Result<T> = std::result::Result<T, Error>;
83
84impl From<crate::internal::core::Error> for Error {
85 #[allow(unreachable_patterns)]
86 fn from(e: crate::internal::core::Error) -> Self {
87 use crate::internal::core::Error as CE;
88 match e {
89 CE::NotAvailable => Error::NotAvailable,
90 CE::KeyNotFound { label } => Error::KeyNotFound { label },
91 CE::DuplicateLabel { label } => Error::DuplicateLabel { label },
92 CE::InvalidLabel { reason } => Error::InvalidLabel { reason },
93 CE::SignFailed { detail } => Error::SignFailed { detail },
94 CE::EncryptFailed { detail } => Error::EncryptFailed { detail },
95 CE::DecryptFailed { detail } => Error::DecryptFailed { detail },
96 CE::KeychainAuthDenied { label } => Error::AuthDenied { label },
97 CE::KeychainInteractionRequired { label } => Error::AuthRequired {
98 label,
99 detail: "screen may be locked; unlock and retry".into(),
100 },
101 CE::KeychainNoWindowServer { label } => Error::AuthRequired {
102 label,
103 detail: "no GUI session; restart agent via launchd".into(),
104 },
105 CE::UserCancelled { label } => Error::UserCancelled { label },
106 CE::KeyOperation { operation, detail } => Error::KeyOperation { operation, detail },
107 CE::GenerateFailed { detail } => Error::KeyOperation {
108 operation: "generate".into(),
109 detail,
110 },
111 CE::Config(s) | CE::Serialization(s) => Error::Config(s),
112 CE::Io(e) => Error::Io(e),
113 other => Error::KeyOperation {
119 operation: "unknown".into(),
120 detail: other.to_string(),
121 },
122 }
123 }
124}
125
126impl From<crate::internal::app_storage::StorageError> for Error {
127 #[allow(unreachable_patterns)]
128 fn from(e: crate::internal::app_storage::StorageError) -> Self {
129 use crate::internal::app_storage::StorageError as SE;
130 match e {
131 SE::NotAvailable => Error::NotAvailable,
132 SE::EncryptionFailed(s) => Error::EncryptFailed { detail: s },
133 SE::DecryptionFailed(s) => Error::DecryptFailed { detail: s },
134 SE::SigningFailed(s) => Error::SignFailed { detail: s },
135 SE::KeyInitFailed(s) => Error::KeyOperation {
136 operation: "init".into(),
137 detail: s,
138 },
139 SE::KeyNotFound(s) => Error::KeyNotFound { label: s },
140 SE::PolicyMismatch(s) => Error::PolicyMismatch { detail: s },
141 SE::PlatformError(s) => Error::KeyOperation {
142 operation: "platform".into(),
143 detail: s,
144 },
145 other => Error::KeyOperation {
150 operation: "unknown".into(),
151 detail: other.to_string(),
152 },
153 }
154 }
155}
156
157#[cfg(test)]
158#[allow(clippy::unwrap_used, clippy::panic)]
159mod tests {
160 use super::*;
161 use crate::internal::app_storage::StorageError;
162
163 #[test]
164 fn from_storage_error_policy_mismatch_preserves_detail() {
165 let e: Error = StorageError::PolicyMismatch("None vs BiometricOnly".into()).into();
166 match e {
167 Error::PolicyMismatch { detail } => {
168 assert!(detail.contains("BiometricOnly"));
169 }
170 other => panic!("expected PolicyMismatch, got {other:?}"),
171 }
172 }
173
174 #[test]
175 fn from_storage_error_all_variants_convert() {
176 let variants: Vec<StorageError> = vec![
178 StorageError::NotAvailable,
179 StorageError::EncryptionFailed("e".into()),
180 StorageError::DecryptionFailed("d".into()),
181 StorageError::SigningFailed("s".into()),
182 StorageError::KeyInitFailed("k".into()),
183 StorageError::KeyNotFound("n".into()),
184 StorageError::PolicyMismatch("p".into()),
185 StorageError::PlatformError("pl".into()),
186 ];
187 for v in variants {
188 drop(Error::from(v));
189 }
190 }
191}