#[non_exhaustive]
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("hardware security module not available")]
NotAvailable,
#[error("key not found: {label}")]
KeyNotFound { label: String },
#[error("duplicate key label: {label}")]
DuplicateLabel { label: String },
#[error("invalid key label: {reason}")]
InvalidLabel { reason: String },
#[error("signing failed: {detail}")]
SignFailed { detail: String },
#[error("encryption failed: {detail}")]
EncryptFailed { detail: String },
#[error("decryption failed: {detail}")]
DecryptFailed { detail: String },
#[error("authentication denied for '{label}'")]
AuthDenied { label: String },
#[error("authentication required for '{label}': {detail}")]
AuthRequired { label: String, detail: String },
#[error("user cancelled authentication for '{label}'")]
UserCancelled { label: String },
#[error("key operation failed — {operation}: {detail}")]
KeyOperation { operation: String, detail: String },
#[error("tamper detected: {path}")]
TamperDetected { path: String },
#[error("feature '{feature}' requires a code-signed binary")]
RequiresSigning { feature: String },
#[error("access policy '{policy}' is not supported by the current backend")]
PolicyNotSupported { policy: String },
#[error("user presence is not available on this platform")]
PresenceNotAvailable,
#[error("not implemented: {feature}")]
NotImplemented { feature: String },
#[error("access policy mismatch: {detail}")]
PolicyMismatch { detail: String },
#[error("config error: {0}")]
Config(String),
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("memory error: {0}")]
Memory(String),
}
pub type Result<T> = std::result::Result<T, Error>;
#[cfg(any(feature = "signing", feature = "encryption"))]
impl From<crate::internal::core::Error> for Error {
#[allow(unreachable_patterns)]
fn from(e: crate::internal::core::Error) -> Self {
use crate::internal::core::Error as CE;
match e {
CE::NotAvailable => Error::NotAvailable,
CE::KeyNotFound { label } => Error::KeyNotFound { label },
CE::DuplicateLabel { label } => Error::DuplicateLabel { label },
CE::InvalidLabel { reason } => Error::InvalidLabel { reason },
CE::SignFailed { detail } => Error::SignFailed { detail },
CE::EncryptFailed { detail } => Error::EncryptFailed { detail },
CE::DecryptFailed { detail } => Error::DecryptFailed { detail },
CE::KeychainAuthDenied { label } => Error::AuthDenied { label },
CE::KeychainInteractionRequired { label } => Error::AuthRequired {
label,
detail: "screen may be locked; unlock and retry".into(),
},
CE::KeychainNoWindowServer { label } => Error::AuthRequired {
label,
detail: "no GUI session; restart agent via launchd".into(),
},
CE::UserCancelled { label } => Error::UserCancelled { label },
CE::KeyOperation { operation, detail } => Error::KeyOperation { operation, detail },
CE::GenerateFailed { detail } => Error::KeyOperation {
operation: "generate".into(),
detail,
},
CE::Config(s) | CE::Serialization(s) => Error::Config(s),
CE::Io(e) => Error::Io(e),
other => Error::KeyOperation {
operation: "unknown".into(),
detail: other.to_string(),
},
}
}
}
#[cfg(any(feature = "signing", feature = "encryption"))]
impl From<crate::internal::app_storage::StorageError> for Error {
#[allow(unreachable_patterns)]
fn from(e: crate::internal::app_storage::StorageError) -> Self {
use crate::internal::app_storage::StorageError as SE;
match e {
SE::NotAvailable => Error::NotAvailable,
SE::EncryptionFailed(s) => Error::EncryptFailed { detail: s },
SE::DecryptionFailed(s) => Error::DecryptFailed { detail: s },
SE::SigningFailed(s) => Error::SignFailed { detail: s },
SE::KeyInitFailed(s) => Error::KeyOperation {
operation: "init".into(),
detail: s,
},
SE::KeyNotFound(s) => Error::KeyNotFound { label: s },
SE::PolicyMismatch(s) => Error::PolicyMismatch { detail: s },
SE::PlatformError(s) => Error::KeyOperation {
operation: "platform".into(),
detail: s,
},
other => Error::KeyOperation {
operation: "unknown".into(),
detail: other.to_string(),
},
}
}
}
#[cfg(all(test, any(feature = "signing", feature = "encryption")))]
#[allow(clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
use crate::internal::app_storage::StorageError;
#[test]
fn from_storage_error_policy_mismatch_preserves_detail() {
let e: Error = StorageError::PolicyMismatch("None vs BiometricOnly".into()).into();
match e {
Error::PolicyMismatch { detail } => {
assert!(detail.contains("BiometricOnly"));
}
other => panic!("expected PolicyMismatch, got {other:?}"),
}
}
#[test]
fn from_storage_error_all_variants_convert() {
let variants: Vec<StorageError> = vec![
StorageError::NotAvailable,
StorageError::EncryptionFailed("e".into()),
StorageError::DecryptionFailed("d".into()),
StorageError::SigningFailed("s".into()),
StorageError::KeyInitFailed("k".into()),
StorageError::KeyNotFound("n".into()),
StorageError::PolicyMismatch("p".into()),
StorageError::PlatformError("pl".into()),
];
for v in variants {
drop(Error::from(v));
}
}
}