1
#[cfg(feature = "async-std")]
2
use std::io;
3
use std::{
4
    path::{Path, PathBuf},
5
    sync::Arc,
6
};
7

            
8
#[cfg(feature = "async-std")]
9
use async_fs as fs;
10
#[cfg(feature = "async-std")]
11
use async_lock::{Mutex, RwLock};
12
#[cfg(feature = "async-std")]
13
use futures_lite::AsyncReadExt;
14
#[cfg(feature = "tokio")]
15
use tokio::{
16
    fs,
17
    io::{self, AsyncReadExt},
18
    sync::{Mutex, RwLock},
19
};
20

            
21
use super::{Error, Item, LockedItem, UnlockedKeyring, api};
22
use crate::{Secret, file::InvalidItemError};
23

            
24
/// A locked keyring that requires a secret to unlock.
25
#[derive(Debug)]
26
pub struct LockedKeyring {
27
    pub(super) keyring: Arc<RwLock<api::Keyring>>,
28
    pub(super) path: Option<PathBuf>,
29
    pub(super) mtime: Mutex<Option<std::time::SystemTime>>,
30
}
31

            
32
impl LockedKeyring {
33
    /// Validate that a secret can decrypt the items in this keyring.
34
    ///
35
    /// For empty keyrings, this always returns `true` since there are no items
36
    /// to validate against.
37
    ///
38
    /// # Arguments
39
    ///
40
    /// * `secret` - The secret to validate.
41
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, secret)))]
42
8
    pub async fn validate_secret(&self, secret: &Secret) -> Result<bool, Error> {
43
4
        let keyring = self.keyring.read().await;
44
4
        Ok(keyring.validate_secret(secret)?)
45
    }
46

            
47
    /// Return the associated file if any.
48
2
    pub fn path(&self) -> Option<&std::path::Path> {
49
2
        self.path.as_deref()
50
    }
51

            
52
    /// Get the modification timestamp
53
8
    pub async fn modified_time(&self) -> std::time::Duration {
54
4
        self.keyring.read().await.modified_time()
55
    }
56

            
57
    /// Retrieve the list of available [`LockedItem`]s without decrypting them.
58
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))]
59
8
    pub async fn items(&self) -> Result<Vec<Result<Item, InvalidItemError>>, Error> {
60
4
        let keyring = self.keyring.read().await;
61

            
62
6
        Ok(keyring
63
            .items
64
2
            .iter()
65
4
            .map(|encrypted_item| {
66
2
                Ok(Item::Locked(LockedItem {
67
2
                    inner: encrypted_item.clone(),
68
                }))
69
            })
70
2
            .collect())
71
    }
72

            
73
    /// Unlocks a keyring and validates it
74
16
    pub async fn unlock(self, secret: Secret) -> Result<UnlockedKeyring, Error> {
75
8
        self.unlock_inner(secret, true).await
76
    }
77

            
78
    /// Unlocks a keyring without validating it
79
    ///
80
    /// # Safety
81
    ///
82
    /// The method doesn't validate that the secret can decrypt all the items in
83
    /// the keyring.
84
8
    pub async unsafe fn unlock_unchecked(self, secret: Secret) -> Result<UnlockedKeyring, Error> {
85
4
        self.unlock_inner(secret, false).await
86
    }
87

            
88
4
    async fn unlock_inner(
89
        self,
90
        secret: Secret,
91
        validate_items: bool,
92
    ) -> Result<UnlockedKeyring, Error> {
93
10
        let key = if validate_items {
94
8
            let inner_keyring = self.keyring.read().await;
95

            
96
8
            let key = inner_keyring.derive_key(&secret)?;
97

            
98
4
            let mut n_broken_items = 0;
99
4
            let mut n_valid_items = 0;
100
8
            for encrypted_item in &inner_keyring.items {
101
16
                if encrypted_item.clone().decrypt(&key).is_err() {
102
4
                    n_broken_items += 1;
103
                } else {
104
4
                    n_valid_items += 1;
105
                }
106
            }
107

            
108
4
            drop(inner_keyring);
109

            
110
4
            if n_valid_items == 0 && n_broken_items != 0 {
111
4
                #[cfg(feature = "tracing")]
112
                tracing::error!("Keyring cannot be decrypted. Invalid secret.");
113
4
                return Err(Error::IncorrectSecret);
114
4
            } else if n_broken_items > n_valid_items {
115
                #[cfg(feature = "tracing")]
116
                {
117
4
                    tracing::warn!(
118
                        "The file contains {n_broken_items} broken items and {n_valid_items} valid ones."
119
                    );
120
4
                    tracing::info!(
121
                        "Please switch to `UnlockedKeyring::load_unchecked` to load the keyring without the secret validation.
122
                        `Keyring::delete_broken_items` can be used to remove them or alternatively with `oo7-cli --repair`."
123
                    );
124
                }
125
2
                return Err(Error::PartiallyCorruptedKeyring {
126
2
                    valid_items: n_valid_items,
127
2
                    broken_items: n_broken_items,
128
                });
129
            }
130
8
            Some(Arc::new(key))
131
        } else {
132
2
            None
133
        };
134

            
135
4
        Ok(UnlockedKeyring {
136
4
            keyring: self.keyring,
137
4
            path: self.path,
138
4
            mtime: self.mtime,
139
4
            key: Mutex::new(key),
140
8
            secret: Mutex::new(Arc::new(secret)),
141
        })
142
    }
143

            
144
    /// Load a keyring from a file path.
145
32
    pub async fn load(path: impl AsRef<Path>) -> Result<Self, Error> {
146
16
        let path = path.as_ref();
147
32
        let (mtime, keyring) = match fs::File::open(&path).await {
148
6
            Err(err) if err.kind() == io::ErrorKind::NotFound => {
149
4
                #[cfg(feature = "tracing")]
150
                tracing::debug!("Keyring file not found, creating a new one");
151
4
                (None, api::Keyring::new())
152
            }
153
            Err(err) => return Err(err.into()),
154
8
            Ok(mut file) => {
155
16
                #[cfg(feature = "tracing")]
156
                tracing::debug!("Keyring file found, loading its content");
157
24
                let mtime = file.metadata().await?.modified().ok();
158

            
159
8
                let mut content = Vec::new();
160
32
                file.read_to_end(&mut content).await?;
161

            
162
10
                let keyring = api::Keyring::try_from(content.as_slice())?;
163

            
164
8
                (mtime, keyring)
165
            }
166
        };
167

            
168
8
        Ok(Self {
169
16
            keyring: Arc::new(RwLock::new(keyring)),
170
16
            path: Some(path.to_path_buf()),
171
8
            mtime: Mutex::new(mtime),
172
        })
173
    }
174

            
175
    /// Open a named keyring.
176
    pub async fn open(name: &str) -> Result<Self, Error> {
177
        let v1_path = api::Keyring::path(name, api::MAJOR_VERSION)?;
178
        Self::load(v1_path).await
179
    }
180
}