keyring_core/error.rs
1/*!
2
3Platform-independent error model.
4
5There is an escape hatch here for surfacing platform-specific
6error information returned by the platform-specific storage provider,
7but the concrete objects returned must be `Send` so they can be
8moved from one thread to another. (Since most platform errors
9are integer error codes, this requirement
10is not much of a burden on the platform-specific store providers.)
11 */
12
13use crate::Entry;
14
15pub type PlatformError = Box<dyn std::error::Error + Send + Sync>;
16
17/// Each variant of the `Error` enum provides a summary of the error.
18/// More details, if relevant, are contained in the associated value,
19/// which may be platform-specific.
20///
21/// This enum is non-exhaustive so that more values can be added to it
22/// without a SemVer break. Clients should always have default handling
23/// for variants they don't understand.
24#[non_exhaustive]
25#[derive(Debug)]
26pub enum Error {
27 /// This indicates runtime failure in the underlying platform storage
28 /// system. The details of the failure can be retrieved from the
29 /// attached platform error.
30 PlatformFailure(PlatformError),
31 /// This indicates that the underlying secure storage holding saved
32 /// items could not be accessed. Typically, this is because of access
33 /// rules in the platform; for example, it might be that the credential
34 /// store is locked. The underlying platform error will typically give
35 /// the reason.
36 NoStorageAccess(PlatformError),
37 /// This indicates that there is no underlying credential entry in the
38 /// platform for this entry. Either one was never set, or it was
39 /// deleted.
40 NoEntry,
41 /// This indicates that the retrieved password blob was not a UTF-8
42 /// string. The underlying bytes are available for examination in the
43 /// attached value.
44 BadEncoding(Vec<u8>),
45 /// This indicates that the retrieved secret blob was not formatted as
46 /// expected by the store. (Some stores perform encryption or other
47 /// transformations when storing secrets.) The raw data of the
48 /// retrieved blob are attached, as is an underlying error indicating
49 /// what went wrong.
50 BadDataFormat(Vec<u8>, PlatformError),
51 /// This indicates that the store itself was not formatted as expected.
52 /// The attached value describes the problem.
53 BadStoreFormat(String),
54 /// This indicates that one of the entry's credential attributes
55 /// exceeded a length limit in the underlying platform. The attached
56 /// values give the name of the attribute and the platform length limit
57 /// that was exceeded.
58 TooLong(String, u32),
59 /// This indicates that one of the parameters passed to the operation
60 /// was invalid. The attached value gives the parameter and describes
61 /// the problem.
62 Invalid(String, String),
63 /// This indicates that there is more than one credential found in the
64 /// store that matches the entry. Its value is a vector of entries
65 /// wrapping the matching credentials.
66 Ambiguous(Vec<Entry>),
67 /// This indicates that there was no default credential builder to use;
68 /// the client must set one before creating entries.
69 NoDefaultStore,
70 /// This indicates that the requested operation is unsupported by the
71 /// store handling the request. The value describes why the requested
72 /// operation could not be performed.
73 NotSupportedByStore(String),
74}
75
76pub type Result<T> = std::result::Result<T, Error>;
77
78impl std::fmt::Display for Error {
79 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
80 match self {
81 Error::PlatformFailure(err) => write!(f, "Platform failure: {err}"),
82 Error::NoStorageAccess(err) => {
83 write!(f, "Couldn't access platform storage: {err}")
84 }
85 Error::NoEntry => write!(f, "No matching credential found"),
86 Error::BadEncoding(_) => write!(f, "Password data is not valid UTF-8"),
87 Error::BadDataFormat(_, err) => {
88 write!(f, "Secret data is malformed: {err:?}")
89 }
90 Error::BadStoreFormat(reason) => write!(f, "Store data is malformed: {reason}"),
91 Error::TooLong(name, len) => write!(
92 f,
93 "Value of '{name}' is longer than the platform limit of {len} chars"
94 ),
95 Error::Invalid(attr, reason) => {
96 write!(f, "Value of '{attr}' is invalid: {reason}")
97 }
98 Error::Ambiguous(items) => {
99 write!(
100 f,
101 "Entry is matched by {} credentials: {items:?}",
102 items.len(),
103 )
104 }
105 Error::NoDefaultStore => {
106 write!(
107 f,
108 "No default store has been set, so cannot search or create entries"
109 )
110 }
111 Error::NotSupportedByStore(issue) => write!(f, "Unsupported: {issue}"),
112 }
113 }
114}
115
116impl std::error::Error for Error {
117 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
118 match self {
119 Error::PlatformFailure(err) => Some(err.as_ref()),
120 Error::NoStorageAccess(err) => Some(err.as_ref()),
121 Error::BadDataFormat(_, err) => Some(err.as_ref()),
122 _ => None,
123 }
124 }
125}
126
127/// Try to interpret a byte vector as a password string
128pub fn decode_password(bytes: Vec<u8>) -> Result<String> {
129 String::from_utf8(bytes).map_err(|err| Error::BadEncoding(err.into_bytes()))
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 #[test]
137 fn test_bad_password() {
138 // malformed sequences here taken from:
139 // https://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt
140 for bytes in [b"\x80".to_vec(), b"\xbf".to_vec(), b"\xed\xa0\xa0".to_vec()] {
141 match decode_password(bytes.clone()) {
142 Err(Error::BadEncoding(str)) => assert_eq!(str, bytes),
143 Err(other) => panic!("Bad password ({bytes:?}) decode gave wrong error: {other}"),
144 Ok(s) => panic!("Bad password ({bytes:?}) decode gave results: {s:?}"),
145 }
146 }
147 }
148}