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 {
116 operation: "unknown".into(),
117 detail: other.to_string(),
118 },
119 }
120 }
121}
122
123impl From<crate::internal::app_storage::StorageError> for Error {
124 #[allow(unreachable_patterns)]
125 fn from(e: crate::internal::app_storage::StorageError) -> Self {
126 use crate::internal::app_storage::StorageError as SE;
127 match e {
128 SE::NotAvailable => Error::NotAvailable,
129 SE::EncryptionFailed(s) => Error::EncryptFailed { detail: s },
130 SE::DecryptionFailed(s) => Error::DecryptFailed { detail: s },
131 SE::SigningFailed(s) => Error::SignFailed { detail: s },
132 SE::KeyInitFailed(s) => Error::KeyOperation {
133 operation: "init".into(),
134 detail: s,
135 },
136 SE::KeyNotFound(s) => Error::KeyNotFound { label: s },
137 SE::PolicyMismatch(s) => Error::PolicyMismatch { detail: s },
138 SE::PlatformError(s) => Error::KeyOperation {
139 operation: "platform".into(),
140 detail: s,
141 },
142 other => Error::KeyOperation {
145 operation: "unknown".into(),
146 detail: other.to_string(),
147 },
148 }
149 }
150}
151
152#[cfg(test)]
153#[allow(clippy::unwrap_used, clippy::panic)]
154mod tests {
155 use super::*;
156 use crate::internal::app_storage::StorageError;
157
158 #[test]
159 fn from_storage_error_policy_mismatch_preserves_detail() {
160 let e: Error = StorageError::PolicyMismatch("None vs BiometricOnly".into()).into();
161 match e {
162 Error::PolicyMismatch { detail } => {
163 assert!(detail.contains("BiometricOnly"));
164 }
165 other => panic!("expected PolicyMismatch, got {other:?}"),
166 }
167 }
168
169 #[test]
170 fn from_storage_error_all_variants_convert() {
171 let variants: Vec<StorageError> = vec![
173 StorageError::NotAvailable,
174 StorageError::EncryptionFailed("e".into()),
175 StorageError::DecryptionFailed("d".into()),
176 StorageError::SigningFailed("s".into()),
177 StorageError::KeyInitFailed("k".into()),
178 StorageError::KeyNotFound("n".into()),
179 StorageError::PolicyMismatch("p".into()),
180 StorageError::PlatformError("pl".into()),
181 ];
182 for v in variants {
183 drop(Error::from(v));
184 }
185 }
186}