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
84#[cfg(any(feature = "signing", feature = "encryption"))]
86impl From<crate::internal::core::Error> for Error {
87 #[allow(unreachable_patterns)]
88 fn from(e: crate::internal::core::Error) -> Self {
89 use crate::internal::core::Error as CE;
90 match e {
91 CE::NotAvailable => Error::NotAvailable,
92 CE::KeyNotFound { label } => Error::KeyNotFound { label },
93 CE::DuplicateLabel { label } => Error::DuplicateLabel { label },
94 CE::InvalidLabel { reason } => Error::InvalidLabel { reason },
95 CE::SignFailed { detail } => Error::SignFailed { detail },
96 CE::EncryptFailed { detail } => Error::EncryptFailed { detail },
97 CE::DecryptFailed { detail } => Error::DecryptFailed { detail },
98 CE::KeychainAuthDenied { label } => Error::AuthDenied { label },
99 CE::KeychainInteractionRequired { label } => Error::AuthRequired {
100 label,
101 detail: "screen may be locked; unlock and retry".into(),
102 },
103 CE::KeychainNoWindowServer { label } => Error::AuthRequired {
104 label,
105 detail: "no GUI session; restart agent via launchd".into(),
106 },
107 CE::UserCancelled { label } => Error::UserCancelled { label },
108 CE::KeyOperation { operation, detail } => Error::KeyOperation { operation, detail },
109 CE::GenerateFailed { detail } => Error::KeyOperation {
110 operation: "generate".into(),
111 detail,
112 },
113 CE::Config(s) | CE::Serialization(s) => Error::Config(s),
114 CE::Io(e) => Error::Io(e),
115 other => Error::KeyOperation {
121 operation: "unknown".into(),
122 detail: other.to_string(),
123 },
124 }
125 }
126}
127
128#[cfg(any(feature = "signing", feature = "encryption"))]
129impl From<crate::internal::app_storage::StorageError> for Error {
130 #[allow(unreachable_patterns)]
131 fn from(e: crate::internal::app_storage::StorageError) -> Self {
132 use crate::internal::app_storage::StorageError as SE;
133 match e {
134 SE::NotAvailable => Error::NotAvailable,
135 SE::EncryptionFailed(s) => Error::EncryptFailed { detail: s },
136 SE::DecryptionFailed(s) => Error::DecryptFailed { detail: s },
137 SE::SigningFailed(s) => Error::SignFailed { detail: s },
138 SE::KeyInitFailed(s) => Error::KeyOperation {
139 operation: "init".into(),
140 detail: s,
141 },
142 SE::KeyNotFound(s) => Error::KeyNotFound { label: s },
143 SE::PolicyMismatch(s) => Error::PolicyMismatch { detail: s },
144 SE::PlatformError(s) => Error::KeyOperation {
145 operation: "platform".into(),
146 detail: s,
147 },
148 other => Error::KeyOperation {
153 operation: "unknown".into(),
154 detail: other.to_string(),
155 },
156 }
157 }
158}
159
160#[cfg(all(test, any(feature = "signing", feature = "encryption")))]
161#[allow(clippy::unwrap_used, clippy::panic)]
162mod tests {
163 use super::*;
164 use crate::internal::app_storage::StorageError;
165
166 #[test]
167 fn from_storage_error_policy_mismatch_preserves_detail() {
168 let e: Error = StorageError::PolicyMismatch("None vs BiometricOnly".into()).into();
169 match e {
170 Error::PolicyMismatch { detail } => {
171 assert!(detail.contains("BiometricOnly"));
172 }
173 other => panic!("expected PolicyMismatch, got {other:?}"),
174 }
175 }
176
177 #[test]
178 fn from_storage_error_all_variants_convert() {
179 let variants: Vec<StorageError> = vec![
181 StorageError::NotAvailable,
182 StorageError::EncryptionFailed("e".into()),
183 StorageError::DecryptionFailed("d".into()),
184 StorageError::SigningFailed("s".into()),
185 StorageError::KeyInitFailed("k".into()),
186 StorageError::KeyNotFound("n".into()),
187 StorageError::PolicyMismatch("p".into()),
188 StorageError::PlatformError("pl".into()),
189 ];
190 for v in variants {
191 drop(Error::from(v));
192 }
193 }
194}