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