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