coven_crypto/custody/
error.rs1use zeroize::Zeroize;
4
5#[derive(Debug, thiserror::Error)]
7pub enum KeyError {
8 #[error("invalid device identity entry")]
10 DeviceIdEncoding,
11 #[error("store is closed")]
13 StoreClosed,
14 #[error("custody file failed: {0}")]
16 File(coven_foundation::files::FileError),
17 #[error("custody cryptography failed: {0}")]
19 Crypto(#[from] crate::CryptoError),
20 #[error("invalid custody material: {0}")]
22 Material(#[from] crate::MaterialError),
23 #[error("wrong passphrase or damaged custody file")]
25 PassphraseAuthentication,
26 #[error("invalid passphrase file header")]
28 PassphraseHeader,
29 #[error("Argon2id parameters are outside the accepted bounds")]
31 PassphraseParameters,
32 #[error("key custody resources unavailable: {0}")]
34 Unavailable(#[source] Box<dyn std::error::Error + Send + Sync>),
35 #[error("keychain failed: {0}")]
37 Keychain(#[source] KeychainError),
38 #[error("keychain is unavailable in this session: {0}")]
44 KeychainTemporarilyUnavailable(#[source] KeychainError),
45 #[error(
50 "the app lacks its keychain-access-groups entitlement \
51 (errSecMissingEntitlement, OSStatus -34018): {0}"
52 )]
53 MissingKeychainEntitlement(#[source] KeychainError),
54 #[error("keyring service requires a nonempty name without NUL")]
56 InvalidServiceName,
57 #[error("saved restore codes need iCloud Keychain or Block Store")]
60 Unsupported,
61 #[error("Block Store already holds its 16 entries for this app")]
64 SavedEntriesFull,
65 #[error("the saved restore code doesn't fit in a Block Store entry")]
68 SavedEntryTooLarge,
69 #[error("the person left the store; their saved entries stay deleted")]
71 Left,
72 #[error("invalid store id in saved restore-code entry")]
75 RestoreCodeStoreId,
76 #[error(transparent)]
79 Shutdown(#[from] coven_foundation::task::Shutdown),
80 #[error(transparent)]
83 Bug(#[from] coven_foundation::task::Bug),
84}
85
86impl coven_foundation::task::CarriesBug for KeyError {
87 fn bug(&self) -> Option<&coven_foundation::task::Bug> {
88 match self {
89 Self::Bug(bug) => Some(bug),
90 Self::File(error) => error.bug(),
91 _ => None,
92 }
93 }
94}
95
96impl From<coven_foundation::files::FileError> for KeyError {
97 fn from(error: coven_foundation::files::FileError) -> Self {
98 match error {
99 coven_foundation::files::FileError::Bug(bug) => Self::Bug(bug),
100 error => Self::File(error),
101 }
102 }
103}
104
105impl From<coven_foundation::task::TaskFailed> for KeyError {
106 fn from(failed: coven_foundation::task::TaskFailed) -> Self {
107 failed.into_error()
108 }
109}
110
111pub struct KeychainError(keyring_core::Error);
114
115#[cfg(any(target_os = "macos", target_os = "ios"))]
117const ERR_SEC_INTERACTION_NOT_ALLOWED: i32 = -25308;
118#[cfg(any(target_os = "macos", target_os = "ios"))]
120const ERR_SEC_MISSING_ENTITLEMENT: i32 = -34018;
121
122impl From<KeychainError> for KeyError {
123 fn from(error: KeychainError) -> Self {
124 #[cfg(any(target_os = "macos", target_os = "ios"))]
125 match error.apple_status() {
126 Some(ERR_SEC_INTERACTION_NOT_ALLOWED) => {
127 return Self::KeychainTemporarilyUnavailable(error)
128 }
129 Some(ERR_SEC_MISSING_ENTITLEMENT) => return Self::MissingKeychainEntitlement(error),
130 _ => {}
131 }
132 match &error.0 {
133 keyring_core::Error::NoStorageAccess(_) => Self::KeychainTemporarilyUnavailable(error),
134 _ => Self::Keychain(error),
135 }
136 }
137}
138
139impl KeychainError {
140 #[cfg(any(target_os = "macos", target_os = "ios"))]
146 fn apple_status(&self) -> Option<i32> {
147 let keyring_core::Error::PlatformFailure(cause) = &self.0 else {
148 return None;
149 };
150 cause.downcast_ref::<security_framework::base::Error>().map(|status| status.code())
151 }
152}
153
154impl From<keyring_core::Error> for KeychainError {
155 fn from(mut error: keyring_core::Error) -> Self {
156 match &mut error {
159 keyring_core::Error::BadEncoding(bytes)
160 | keyring_core::Error::BadDataFormat(bytes, _) => bytes.zeroize(),
161 _ => {}
162 }
163 Self(error)
164 }
165}
166
167impl std::fmt::Debug for KeychainError {
168 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169 #[cfg(any(target_os = "macos", target_os = "ios"))]
171 if let Some(status) = self.apple_status() {
172 return write!(f, "PlatformFailure(OSStatus {status})");
173 }
174 let kind = match &self.0 {
175 keyring_core::Error::PlatformFailure(_) => "PlatformFailure",
176 keyring_core::Error::NoStorageAccess(_) => "NoStorageAccess",
177 keyring_core::Error::NoEntry => "NoEntry",
178 keyring_core::Error::BadEncoding(_) => "BadEncoding",
179 keyring_core::Error::BadDataFormat(_, _) => "BadDataFormat",
180 keyring_core::Error::BadStoreFormat(_) => "BadStoreFormat",
181 keyring_core::Error::TooLong(_, _) => "TooLong",
182 keyring_core::Error::Invalid(_, _) => "Invalid",
183 keyring_core::Error::Ambiguous(_) => "Ambiguous",
184 keyring_core::Error::NoDefaultStore => "NoDefaultStore",
185 keyring_core::Error::NotSupportedByStore(_) => "NotSupportedByStore",
186 _ => "NativeError",
187 };
188 f.write_str(kind)
189 }
190}
191
192impl std::fmt::Display for KeychainError {
193 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194 std::fmt::Debug::fmt(self, f)
195 }
196}
197
198impl std::error::Error for KeychainError {
199 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
200 Some(&self.0)
201 }
202}
203
204#[cfg(test)]
205#[path = "error_tests.rs"]
206mod tests;