1
use std::{
2
    collections::HashMap,
3
    path::{Path, PathBuf},
4
    sync::Arc,
5
};
6

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

            
20
use crate::{
21
    AsAttributes, Key, Secret,
22
    file::{Error, InvalidItemError, LockedItem, LockedKeyring, UnlockedItem, api},
23
};
24

            
25
/// Definition for batch item creation: (label, attributes, secret, replace)
26
pub type ItemDefinition = (String, HashMap<String, String>, Secret, bool);
27

            
28
/// File backed keyring.
29
#[derive(Debug)]
30
pub struct UnlockedKeyring {
31
    pub(super) keyring: Arc<RwLock<api::Keyring>>,
32
    pub(super) path: Option<PathBuf>,
33
    /// Times are stored before reading the file to detect
34
    /// file changes before writing
35
    pub(super) mtime: Mutex<Option<std::time::SystemTime>>,
36
    pub(super) key: Mutex<Option<Arc<Key>>>,
37
    pub(super) secret: Mutex<Option<Arc<Secret>>>,
38
}
39

            
40
impl UnlockedKeyring {
41
    /// Load from a keyring file.
42
    ///
43
    /// # Arguments
44
    ///
45
    /// * `path` - The path to the file backend.
46
    /// * `secret` - The service key, usually retrieved from the Secrets portal.
47
    ///   Pass `None` for unencrypted keyrings.
48
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(secret), fields(path = ?path.as_ref())))]
49
78
    pub async fn load(path: impl AsRef<Path>, secret: Option<Secret>) -> Result<Self, Error> {
50
52
        Self::load_inner(path, secret, true).await
51
    }
52

            
53
    /// Load from a keyring file without validating the secret.
54
    ///
55
    /// # Arguments
56
    ///
57
    /// * `path` - The path to the file backend.
58
    /// * `secret` - The service key, usually retrieved from the Secrets portal.
59
    ///
60
    /// # Safety
61
    ///
62
    /// This method skips validation and doesn't verify that the secret can
63
    /// decrypt all items in the keyring. Use only for recovery scenarios where
64
    /// you need to access a partially corrupted keyring. The keyring may
65
    /// contain items that cannot be decrypted with the provided secret, and
66
    /// writing new items may use a different secret than existing items.
67
    #[allow(unsafe_code)]
68
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(secret), fields(path = ?path.as_ref())))]
69
2
    pub async unsafe fn load_unchecked(
70
        path: impl AsRef<Path>,
71
        secret: Secret,
72
    ) -> Result<Self, Error> {
73
6
        Self::load_inner(path, Some(secret), false).await
74
    }
75

            
76
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(secret), fields(path = ?path.as_ref(), validate_items = validate_items)))]
77
15
    async fn load_inner(
78
        path: impl AsRef<Path>,
79
        secret: Option<Secret>,
80
        validate_items: bool,
81
    ) -> Result<Self, Error> {
82
        #[cfg(feature = "tracing")]
83
        tracing::debug!("Trying to load keyring file at {:?}", path.as_ref());
84
47
        let locked = LockedKeyring::load(path).await?;
85
13
        match secret {
86
41
            Some(secret) if validate_items => locked.unlock(secret).await,
87
2
            Some(secret) => {
88
                #[allow(unsafe_code)]
89
                unsafe {
90
4
                    locked.unlock_unchecked(secret).await
91
                }
92
            }
93
4
            None => locked.unlock_unencrypted().await,
94
        }
95
    }
96

            
97
    /// Creates a temporary backend, that is never stored on disk.
98
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(secret)))]
99
155
    pub async fn temporary(secret: Secret) -> Result<Self, Error> {
100
64
        let keyring = api::Keyring::new()?;
101
28
        Ok(Self {
102
64
            keyring: Arc::new(RwLock::new(keyring)),
103
37
            path: None,
104
27
            mtime: Default::default(),
105
37
            key: Default::default(),
106
63
            secret: Mutex::new(Some(Arc::new(secret))),
107
        })
108
    }
109

            
110
    /// Creates a temporary unencrypted backend, that is never stored on disk.
111
6
    pub async fn temporary_unencrypted() -> Result<Self, Error> {
112
4
        let keyring = api::Keyring::new()?;
113
2
        Ok(Self {
114
4
            keyring: Arc::new(RwLock::new(keyring)),
115
2
            path: None,
116
2
            mtime: Default::default(),
117
2
            key: Default::default(),
118
2
            secret: Mutex::new(None),
119
        })
120
    }
121

            
122
    /// Load a v0 (legacy gnome-keyring) file and migrate it to v1 format.
123
    ///
124
    /// The migrated keyring will be written to `target_path` when
125
    /// [`write()`](Self::write) is called.
126
    ///
127
    /// # Arguments
128
    ///
129
    /// * `source` - Path to the legacy v0 keyring file.
130
    /// * `target_path` - Where the v1 keyring should be stored.
131
    /// * `secret` - The encryption secret, or `None` for unencrypted keyrings.
132
    pub async fn load_from_v0(
133
        source: impl AsRef<Path>,
134
        target_path: impl Into<PathBuf>,
135
        secret: Option<Secret>,
136
    ) -> Result<Self, Error> {
137
        let mut file = fs::File::open(source.as_ref()).await?;
138
        Self::migrate(&mut file, target_path.into(), secret).await
139
    }
140

            
141
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(file, secret), fields(path = ?path.as_ref())))]
142
6
    async fn migrate(
143
        file: &mut fs::File,
144
        path: impl AsRef<Path>,
145
        secret: Option<Secret>,
146
    ) -> Result<Self, Error> {
147
18
        let metadata = file.metadata().await?;
148
6
        let mut content = Vec::with_capacity(metadata.len() as usize);
149
24
        file.read_to_end(&mut content).await?;
150

            
151
18
        match api::Keyring::try_from(content.as_slice()) {
152
4
            Ok(keyring) => Ok(Self {
153
4
                keyring: Arc::new(RwLock::new(keyring)),
154
4
                path: Some(path.as_ref().to_path_buf()),
155
2
                mtime: Default::default(),
156
2
                key: Default::default(),
157
4
                secret: Mutex::new(secret.map(Arc::new)),
158
            }),
159
12
            Err(Error::VersionMismatch(Some(version)))
160
6
                if version[0] == api::LEGACY_MAJOR_VERSION =>
161
            {
162
                #[cfg(feature = "tracing")]
163
                tracing::debug!("Migrating from legacy keyring format");
164

            
165
12
                let legacy_keyring = api::LegacyKeyring::try_from(content.as_slice())?;
166
12
                let mut keyring = api::Keyring::new()?;
167

            
168
24
                let key = secret.as_ref().map(|s| keyring.derive_key(s)).transpose()?;
169
16
                let decrypted_items = legacy_keyring
170
28
                    .decrypt_items(&secret.clone().unwrap_or_else(|| Secret::from(vec![])))?;
171

            
172
                #[cfg(feature = "tracing")]
173
                let _migrate_span =
174
                    tracing::debug_span!("migrate_items", item_count = decrypted_items.len());
175

            
176
18
                for item in decrypted_items {
177
12
                    let encrypted_item = item.encrypt(key.as_ref())?;
178
6
                    keyring.items.push(encrypted_item);
179
                }
180

            
181
6
                Ok(Self {
182
6
                    keyring: Arc::new(RwLock::new(keyring)),
183
12
                    path: Some(path.as_ref().to_path_buf()),
184
6
                    mtime: Default::default(),
185
6
                    key: Default::default(),
186
12
                    secret: Mutex::new(secret.map(Arc::new)),
187
                })
188
            }
189
            Err(err) => Err(err),
190
        }
191
    }
192

            
193
    /// Helper for opening/creating keyrings with explicit paths.
194
    ///
195
    /// Handles v0 -> v1 migration automatically.
196
20
    async fn open_with_paths(
197
        v1_path: PathBuf,
198
        v0_path: PathBuf,
199
        secret: Option<Secret>,
200
    ) -> Result<Self, Error> {
201
39
        if v1_path.exists() {
202
            #[cfg(feature = "tracing")]
203
            tracing::debug!("Loading v1 keyring file");
204
6
            return Self::load(v1_path, secret).await;
205
        }
206

            
207
35
        if v0_path.exists() {
208
            #[cfg(feature = "tracing")]
209
            tracing::debug!("Trying to load keyring file at {:?}", v0_path);
210
18
            match fs::File::open(&v0_path).await {
211
                Err(err) => Err(err.into()),
212
12
                Ok(mut file) => Self::migrate(&mut file, v1_path, secret).await,
213
            }
214
        } else {
215
            #[cfg(feature = "tracing")]
216
            tracing::debug!("Creating new keyring");
217
16
            Ok(Self {
218
35
                keyring: Arc::new(RwLock::new(api::Keyring::new()?)),
219
18
                path: Some(v1_path),
220
16
                mtime: Default::default(),
221
19
                key: Default::default(),
222
35
                secret: Mutex::new(secret.map(Arc::new)),
223
            })
224
        }
225
    }
226

            
227
    /// Open a keyring with given name from the default directory.
228
    ///
229
    /// This function will automatically migrate the keyring to the
230
    /// latest format.
231
    ///
232
    /// # Arguments
233
    ///
234
    /// * `name` - The name of the keyring.
235
    /// * `secret` - The service key, usually retrieved from the Secrets portal.
236
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(secret)))]
237
    pub async fn open(name: &str, secret: Option<Secret>) -> Result<Self, Error> {
238
        let v1_path = api::Keyring::path(name, api::MAJOR_VERSION)?;
239
        let v0_path = api::Keyring::path(name, api::LEGACY_MAJOR_VERSION)?;
240
        Self::open_with_paths(v1_path, v0_path, secret).await
241
    }
242

            
243
    /// Open or create a keyring at a specific data directory.
244
    ///
245
    /// This is useful for tests and cases where you want explicit control over
246
    /// where keyrings are stored, avoiding the default XDG_DATA_HOME location.
247
    ///
248
    /// This function will automatically migrate the keyring to the latest
249
    /// format.
250
    ///
251
    /// # Arguments
252
    ///
253
    /// * `data_dir` - Base data directory (keyrings stored in
254
    ///   `data_dir/keyrings/v1/`)
255
    /// * `name` - The name of the keyring.
256
    /// * `secret` - The service key, usually retrieved from the Secrets portal.
257
    ///
258
    /// # Example
259
    ///
260
    /// ```no_run
261
    /// # use oo7::{Secret, file::UnlockedKeyring};
262
    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
263
    /// let temp_dir = tempfile::tempdir()?;
264
    /// let keyring = UnlockedKeyring::open_at(
265
    ///     temp_dir.path(),
266
    ///     "test-keyring",
267
    ///     Some(Secret::from("password")),
268
    /// )
269
    /// .await?;
270
    /// keyring
271
    ///     .create_item("item", &[("attr", "value")], Secret::text("secret"), false)
272
    ///     .await?;
273
    /// keyring.write().await?; // Writes to temp_dir/keyrings/v1/test-keyring.keyring
274
    /// //
275
    /// # Ok(())
276
    /// # }
277
    /// ```
278
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(secret), fields(data_dir = ?data_dir.as_ref())))]
279
21
    pub async fn open_at(
280
        data_dir: impl AsRef<Path>,
281
        name: &str,
282
        secret: Option<Secret>,
283
    ) -> Result<Self, Error> {
284
22
        let v1_path = api::Keyring::path_at(&data_dir, name, api::MAJOR_VERSION);
285
24
        let v0_path = api::Keyring::path_at(&data_dir, name, api::LEGACY_MAJOR_VERSION);
286
26
        Self::open_with_paths(v1_path, v0_path, secret).await
287
    }
288

            
289
    /// Lock the keyring.
290
8
    pub fn lock(self) -> LockedKeyring {
291
        LockedKeyring {
292
8
            keyring: self.keyring,
293
8
            path: self.path,
294
8
            mtime: self.mtime,
295
        }
296
    }
297

            
298
    /// Lock an item using the keyring's key.
299
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, item)))]
300
40
    pub async fn lock_item(&self, item: UnlockedItem) -> Result<LockedItem, Error> {
301
16
        let key = self.derive_key().await?;
302
8
        item.lock(key.as_deref())
303
    }
304

            
305
    /// Unlock an item using the keyring's key.
306
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, item)))]
307
45
    pub async fn unlock_item(&self, item: LockedItem) -> Result<UnlockedItem, Error> {
308
16
        let key = self.derive_key().await?;
309
8
        item.unlock(key.as_deref())
310
    }
311

            
312
    /// Get the encryption key for this keyring.
313
    ///
314
    /// Returns `None` for unencrypted keyrings.
315
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))]
316
62
    pub async fn key(&self) -> Result<Option<Arc<Key>>, crate::crypto::Error> {
317
32
        self.derive_key().await
318
    }
319

            
320
    /// Return the associated file if any.
321
30
    pub fn path(&self) -> Option<&std::path::Path> {
322
26
        self.path.as_deref()
323
    }
324

            
325
    /// Get the modification timestamp
326
112
    pub async fn modified_time(&self) -> std::time::Duration {
327
56
        self.keyring.read().await.modified_time()
328
    }
329

            
330
    /// Retrieve the number of items
331
    ///
332
    /// This function will not trigger a key derivation and can therefore be
333
    /// faster than [`items().len()`](Self::items).
334
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))]
335
16
    pub async fn n_items(&self) -> usize {
336
12
        self.keyring.read().await.items.len()
337
    }
338

            
339
    /// Retrieve all items including those that cannot be decrypted.
340
    ///
341
    /// Returns a [`Vec`] where each element is either an [`UnlockedItem`] or an
342
    /// [`InvalidItemError`] for items that failed to decrypt.
343
    ///
344
    /// Use this method when you need to know about or handle decryption
345
    /// failures. For most use cases, [`items()`](Self::items) is more
346
    /// convenient as it only returns successfully decrypted items.
347
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))]
348
147
    pub async fn all_items(&self) -> Result<Vec<Result<UnlockedItem, InvalidItemError>>, Error> {
349
95
        let key = self.derive_key().await?;
350
72
        let keyring = self.keyring.read().await;
351

            
352
        #[cfg(feature = "tracing")]
353
        let _span = tracing::debug_span!("decrypt_all", total_items = keyring.items.len());
354

            
355
110
        Ok(keyring
356
            .items
357
34
            .iter()
358
46
            .map(|e| {
359
10
                (*e).clone().decrypt(key.as_deref()).map_err(|err| {
360
2
                    InvalidItemError::new(
361
2
                        err,
362
8
                        e.hashed_attributes.keys().map(|x| x.to_string()).collect(),
363
                    )
364
                })
365
            })
366
34
            .collect())
367
    }
368

            
369
    /// Retrieve the list of available [`UnlockedItem`]s.
370
    ///
371
    /// Items that cannot be decrypted are silently skipped. Use
372
    /// [`all_items()`](Self::all_items) if you need access to decryption
373
    /// errors.
374
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))]
375
151
    pub async fn items(&self) -> Result<Vec<UnlockedItem>, Error> {
376
97
        Ok(self.all_items().await?.into_iter().flatten().collect())
377
    }
378

            
379
    /// Search items matching the attributes.
380
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, attributes)))]
381
24
    pub async fn search_items(
382
        &self,
383
        attributes: &impl AsAttributes,
384
    ) -> Result<Vec<UnlockedItem>, Error> {
385
48
        let key = self.derive_key().await?;
386
48
        let keyring = self.keyring.read().await;
387
48
        let results = keyring.search_items(attributes, key.as_deref())?;
388

            
389
        #[cfg(feature = "tracing")]
390
        tracing::debug!("Found {} matching items", results.len());
391

            
392
24
        Ok(results)
393
    }
394

            
395
    /// Find the first item matching the attributes.
396
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, attributes)))]
397
2
    pub async fn lookup_item(
398
        &self,
399
        attributes: &impl AsAttributes,
400
    ) -> Result<Option<UnlockedItem>, Error> {
401
4
        let key = self.derive_key().await?;
402
4
        let keyring = self.keyring.read().await;
403

            
404
4
        keyring.lookup_item(attributes, key.as_deref())
405
    }
406

            
407
    /// Find the index in the list of items of the first item matching the
408
    /// attributes.
409
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, attributes)))]
410
4
    pub async fn lookup_item_index(
411
        &self,
412
        attributes: &impl AsAttributes,
413
    ) -> Result<Option<usize>, Error> {
414
8
        let key = self.derive_key().await?;
415
8
        let keyring = self.keyring.read().await;
416

            
417
8
        Ok(keyring.lookup_item_index(attributes, key.as_deref()))
418
    }
419

            
420
    /// Delete an item.
421
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, attributes)))]
422
112
    pub async fn delete(&self, attributes: &impl AsAttributes) -> Result<(), Error> {
423
        #[cfg(feature = "tracing")]
424
        let items_before = { self.keyring.read().await.items.len() };
425

            
426
        {
427
26
            let key = self.derive_key().await?;
428
49
            let mut keyring = self.keyring.write().await;
429
54
            keyring.remove_items(attributes, key.as_deref())?;
430
        };
431

            
432
38
        self.write().await?;
433

            
434
        #[cfg(feature = "tracing")]
435
        {
436
            let items_after = self.keyring.read().await.items.len();
437
            let deleted_count = items_before.saturating_sub(items_after);
438
            tracing::info!("Deleted {} items", deleted_count);
439
        }
440

            
441
25
        Ok(())
442
    }
443

            
444
    /// Create a new item
445
    ///
446
    /// # Arguments
447
    ///
448
    /// * `label` - A user visible label of the item.
449
    /// * `attributes` - A map of key/value attributes, used to find the item
450
    ///   later.
451
    /// * `secret` - The secret to store.
452
    /// * `replace` - Whether to replace the value if the `attributes` matches
453
    ///   an existing `secret`.
454
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, secret, attributes), fields(replace = replace)))]
455
56
    pub async fn create_item(
456
        &self,
457
        label: &str,
458
        attributes: &impl AsAttributes,
459
        secret: impl Into<Secret>,
460
        replace: bool,
461
    ) -> Result<UnlockedItem, Error> {
462
        let item = {
463
129
            let key = self.derive_key().await?;
464
112
            let mut keyring = self.keyring.write().await;
465
85
            if replace {
466
55
                keyring.remove_items(attributes, key.as_deref())?;
467
            }
468
60
            let item = UnlockedItem::new(label, attributes, secret);
469
111
            let encrypted_item = item.encrypt(key.as_deref())?;
470
111
            keyring.items.push(encrypted_item);
471
54
            item
472
        };
473
208
        match self.write().await {
474
            Err(e) => {
475
                #[cfg(feature = "tracing")]
476
                tracing::error!("Failed to write keyring after item creation");
477
                Err(e)
478
            }
479
            Ok(_) => {
480
                #[cfg(feature = "tracing")]
481
                tracing::info!("Successfully created item");
482
56
                Ok(item)
483
            }
484
        }
485
    }
486

            
487
    /// Replaces item at the given index.
488
    ///
489
    /// The `index` refers to the index of the [`Vec`] returned by
490
    /// [`items()`](Self::items). If the index does not exist, the functions
491
    /// returns an error.
492
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, item), fields(index = index)))]
493
20
    pub async fn replace_item_index(&self, index: usize, item: &UnlockedItem) -> Result<(), Error> {
494
        {
495
8
            let key = self.derive_key().await?;
496
8
            let mut keyring = self.keyring.write().await;
497

            
498
8
            if let Some(item_store) = keyring.items.get_mut(index) {
499
8
                *item_store = item.encrypt(key.as_deref())?;
500
            } else {
501
2
                return Err(Error::InvalidItemIndex(index));
502
            }
503
        }
504
8
        self.write().await
505
    }
506

            
507
    /// Deletes item at the given index.
508
    ///
509
    /// The `index` refers to the index of the [`Vec`] returned by
510
    /// [`items()`](Self::items). If the index does not exist, the functions
511
    /// returns an error.
512
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self), fields(index = index)))]
513
10
    pub async fn delete_item_index(&self, index: usize) -> Result<(), Error> {
514
        {
515
4
            let mut keyring = self.keyring.write().await;
516

            
517
4
            if index < keyring.items.len() {
518
4
                keyring.items.remove(index);
519
            } else {
520
2
                return Err(Error::InvalidItemIndex(index));
521
            }
522
        }
523
4
        self.write().await
524
    }
525

            
526
    /// Create multiple items in a single operation to avoid re-writing the file
527
    /// multiple times.
528
    ///
529
    /// This is more efficient than calling `create_item()` multiple times.
530
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, items), fields(item_count = items.len())))]
531
24
    pub async fn create_items(&self, items: Vec<ItemDefinition>) -> Result<(), Error> {
532
8
        let key = self.derive_key().await?;
533
8
        let mut mtime = self.mtime.lock().await;
534
8
        let mut keyring = self.keyring.write().await;
535

            
536
        #[cfg(feature = "tracing")]
537
        let _span = tracing::debug_span!("bulk_create", items_to_create = items.len());
538

            
539
16
        for (label, attributes, secret, replace) in items {
540
6
            if replace {
541
4
                keyring.remove_items(&attributes, key.as_deref())?;
542
            }
543
4
            let item = UnlockedItem::new(label, &attributes, secret);
544
8
            let encrypted_item = item.encrypt(key.as_deref())?;
545
8
            keyring.items.push(encrypted_item);
546
        }
547

            
548
        #[cfg(feature = "tracing")]
549
        tracing::debug!("Writing keyring back to the file");
550
8
        if let Some(ref path) = self.path {
551
12
            keyring.dump(path, *mtime).await?;
552
            // Update mtime after successful write
553
12
            if let Ok(modified) = fs::metadata(path).await?.modified() {
554
8
                *mtime = Some(modified);
555
            }
556
        }
557
4
        Ok(())
558
    }
559

            
560
    /// Write the changes to the keyring file.
561
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))]
562
128
    pub async fn write(&self) -> Result<(), Error> {
563
65
        let mut mtime = self.mtime.lock().await;
564
        {
565
58
            let mut keyring = self.keyring.write().await;
566

            
567
50
            if let Some(ref path) = self.path {
568
62
                keyring.dump(path, *mtime).await?;
569
            }
570
        };
571
33
        let Some(ref path) = self.path else {
572
22
            return Ok(());
573
        };
574

            
575
86
        if let Ok(modified) = fs::metadata(path).await?.modified() {
576
47
            *mtime = Some(modified);
577
        }
578
22
        Ok(())
579
    }
580

            
581
    /// Return key, derive and store it first if not initialized.
582
    ///
583
    /// Returns `None` when no secret is set (unencrypted keyring).
584
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))]
585
226
    async fn derive_key(&self) -> Result<Option<Arc<Key>>, crate::crypto::Error> {
586
76
        let keyring = Arc::clone(&self.keyring);
587
76
        let secret_lock = self.secret.lock().await;
588
76
        let secret = match secret_lock.as_ref() {
589
69
            Some(secret) => Arc::clone(secret),
590
12
            None => return Ok(None),
591
        };
592
30
        drop(secret_lock);
593

            
594
40
        let mut key_lock = self.key.lock().await;
595
112
        if key_lock.is_none() {
596
            #[cfg(feature = "async-std")]
597
            let key = blocking::unblock(move || {
598
                async_io::block_on(async { keyring.read().await.derive_key(&secret) })
599
            })
600
            .await?;
601
            #[cfg(feature = "tokio")]
602
            let key = {
603
                tokio::task::spawn_blocking(move || keyring.blocking_read().derive_key(&secret))
604
                    .await
605
                    .unwrap()?
606
            };
607

            
608
80
            *key_lock = Some(Arc::new(key));
609
        }
610

            
611
86
        Ok(key_lock.clone())
612
    }
613

            
614
    /// Change keyring secret
615
    ///
616
    /// # Arguments
617
    ///
618
    /// * `secret` - The new secret to store.
619
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, secret)))]
620
36
    pub async fn change_secret(&self, secret: Secret) -> Result<(), Error> {
621
12
        let keyring = self.keyring.read().await;
622
12
        let key = self.derive_key().await?;
623
12
        let mut items = Vec::with_capacity(keyring.items.len());
624

            
625
        #[cfg(feature = "tracing")]
626
        let _decrypt_span =
627
            tracing::debug_span!("decrypt_for_reencrypt", total_items = keyring.items.len());
628

            
629
12
        for item in &keyring.items {
630
6
            items.push(item.clone().decrypt(key.as_deref())?);
631
        }
632
6
        drop(keyring);
633

            
634
        #[cfg(feature = "tracing")]
635
        tracing::debug!("Updating secret and resetting key");
636

            
637
12
        let mut secret_lock = self.secret.lock().await;
638
6
        *secret_lock = Some(Arc::new(secret));
639
6
        drop(secret_lock);
640

            
641
12
        let mut key_lock = self.key.lock().await;
642
        // Unset the old key
643
6
        *key_lock = None;
644
6
        drop(key_lock);
645

            
646
        // Reset Keyring content before setting the new key
647
12
        let mut keyring = self.keyring.write().await;
648
12
        keyring.reset()?;
649
6
        drop(keyring);
650

            
651
        // Set new key
652
13
        let key = self.derive_key().await?;
653

            
654
        #[cfg(feature = "tracing")]
655
        let _reencrypt_span = tracing::debug_span!("reencrypt", total_items = items.len());
656

            
657
12
        let mut keyring = self.keyring.write().await;
658
18
        for item in items {
659
12
            let encrypted_item = item.encrypt(key.as_deref())?;
660
12
            keyring.items.push(encrypted_item);
661
        }
662
6
        drop(keyring);
663

            
664
18
        self.write().await
665
    }
666

            
667
    /// Validate that a secret can decrypt the items in this keyring.
668
    ///
669
    /// For empty keyrings, this always returns `true` since there are no items
670
    /// to validate against.
671
    ///
672
    /// # Arguments
673
    ///
674
    /// * `secret` - The secret to validate.
675
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, secret)))]
676
16
    pub async fn validate_secret(&self, secret: &Secret) -> Result<bool, Error> {
677
8
        let keyring = self.keyring.read().await;
678
8
        Ok(keyring.validate_secret(secret)?)
679
    }
680

            
681
8
    pub async fn validate_unencrypted(&self) -> Result<bool, Error> {
682
4
        let keyring = self.keyring.read().await;
683
4
        Ok(keyring.validate_unencrypted())
684
    }
685

            
686
    /// Delete any item that cannot be decrypted with the key associated to the
687
    /// keyring.
688
    ///
689
    /// This can only happen if an item was created using
690
    /// [`Self::load_unchecked`] or prior to 0.4 where we didn't validate
691
    /// the secret when using [`Self::load`] or modified externally.
692
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))]
693
12
    pub async fn delete_broken_items(&self) -> Result<usize, Error> {
694
4
        let key = self.derive_key().await?;
695
4
        let mut keyring = self.keyring.write().await;
696
2
        let mut broken_items = vec![];
697

            
698
        #[cfg(feature = "tracing")]
699
        let _span = tracing::debug_span!("identify_broken", total_items = keyring.items.len());
700

            
701
4
        for (index, encrypted_item) in keyring.items.iter().enumerate() {
702
4
            if !encrypted_item.is_valid(key.as_deref()) {
703
2
                broken_items.push(index);
704
            }
705
        }
706
2
        let n_broken_items = broken_items.len();
707

            
708
        #[cfg(feature = "tracing")]
709
        tracing::info!("Found {} broken items to delete", n_broken_items);
710

            
711
        #[cfg(feature = "tracing")]
712
        let _remove_span = tracing::debug_span!("remove_broken", broken_count = n_broken_items);
713

            
714
6
        for index in broken_items.into_iter().rev() {
715
4
            keyring.items.remove(index);
716
        }
717
2
        drop(keyring);
718

            
719
4
        self.write().await?;
720
2
        Ok(n_broken_items)
721
    }
722
}