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<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
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(secret), fields(path = ?path.as_ref())))]
48
71
    pub async fn load(path: impl AsRef<Path>, secret: Secret) -> Result<Self, Error> {
49
56
        Self::load_inner(path, secret, true).await
50
    }
51

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

            
75
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(secret), fields(path = ?path.as_ref(), validate_items = validate_items)))]
76
13
    async fn load_inner(
77
        path: impl AsRef<Path>,
78
        secret: Secret,
79
        validate_items: bool,
80
    ) -> Result<Self, Error> {
81
30
        #[cfg(feature = "tracing")]
82
        tracing::debug!("Trying to load keyring file at {:?}", path.as_ref());
83
17
        if validate_items {
84
57
            LockedKeyring::load(path).await?.unlock(secret).await
85
        } else {
86
            #[allow(unsafe_code)]
87
            unsafe {
88
14
                LockedKeyring::load(path)
89
10
                    .await?
90
4
                    .unlock_unchecked(secret)
91
8
                    .await
92
            }
93
        }
94
    }
95

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

            
109
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(file, secret), fields(path = ?path.as_ref())))]
110
7
    async fn migrate(
111
        file: &mut fs::File,
112
        path: impl AsRef<Path>,
113
        secret: Secret,
114
    ) -> Result<Self, Error> {
115
27
        let metadata = file.metadata().await?;
116
6
        let mut content = Vec::with_capacity(metadata.len() as usize);
117
24
        file.read_to_end(&mut content).await?;
118

            
119
18
        match api::Keyring::try_from(content.as_slice()) {
120
4
            Ok(keyring) => Ok(Self {
121
4
                keyring: Arc::new(RwLock::new(keyring)),
122
4
                path: Some(path.as_ref().to_path_buf()),
123
2
                mtime: Default::default(),
124
2
                key: Default::default(),
125
4
                secret: Mutex::new(Arc::new(secret)),
126
            }),
127
12
            Err(Error::VersionMismatch(Some(version)))
128
6
                if version[0] == api::LEGACY_MAJOR_VERSION =>
129
            {
130
12
                #[cfg(feature = "tracing")]
131
                tracing::debug!("Migrating from legacy keyring format");
132

            
133
12
                let legacy_keyring = api::LegacyKeyring::try_from(content.as_slice())?;
134
12
                let mut keyring = api::Keyring::new()?;
135
12
                let key = keyring.derive_key(&secret)?;
136

            
137
16
                let decrypted_items = legacy_keyring.decrypt_items(&secret)?;
138

            
139
                #[cfg(feature = "tracing")]
140
16
                let _migrate_span =
141
                    tracing::debug_span!("migrate_items", item_count = decrypted_items.len());
142

            
143
18
                for item in decrypted_items {
144
12
                    let encrypted_item = item.encrypt(&key)?;
145
6
                    keyring.items.push(encrypted_item);
146
                }
147

            
148
6
                Ok(Self {
149
6
                    keyring: Arc::new(RwLock::new(keyring)),
150
12
                    path: Some(path.as_ref().to_path_buf()),
151
6
                    mtime: Default::default(),
152
6
                    key: Default::default(),
153
12
                    secret: Mutex::new(Arc::new(secret)),
154
                })
155
            }
156
            Err(err) => Err(err),
157
        }
158
    }
159

            
160
    /// Helper for opening/creating keyrings with explicit paths.
161
    ///
162
    /// Handles v0 -> v1 migration automatically.
163
13
    async fn open_with_paths(
164
        v1_path: PathBuf,
165
        v0_path: PathBuf,
166
        secret: Secret,
167
    ) -> Result<Self, Error> {
168
24
        if v1_path.exists() {
169
4
            #[cfg(feature = "tracing")]
170
            tracing::debug!("Loading v1 keyring file");
171
8
            return Self::load(v1_path, secret).await;
172
        }
173

            
174
24
        if v0_path.exists() {
175
12
            #[cfg(feature = "tracing")]
176
            tracing::debug!("Trying to load keyring file at {:?}", v0_path);
177
25
            match fs::File::open(&v0_path).await {
178
                Err(err) => Err(err.into()),
179
21
                Ok(mut file) => Self::migrate(&mut file, v1_path, secret).await,
180
            }
181
        } else {
182
25
            #[cfg(feature = "tracing")]
183
            tracing::debug!("Creating new keyring");
184
14
            Ok(Self {
185
28
                keyring: Arc::new(RwLock::new(api::Keyring::new()?)),
186
14
                path: Some(v1_path),
187
14
                mtime: Default::default(),
188
14
                key: Default::default(),
189
28
                secret: Mutex::new(Arc::new(secret)),
190
            })
191
        }
192
    }
193

            
194
    /// Open a keyring with given name from the default directory.
195
    ///
196
    /// This function will automatically migrate the keyring to the
197
    /// latest format.
198
    ///
199
    /// # Arguments
200
    ///
201
    /// * `name` - The name of the keyring.
202
    /// * `secret` - The service key, usually retrieved from the Secrets portal.
203
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(secret)))]
204
    pub async fn open(name: &str, secret: Secret) -> Result<Self, Error> {
205
        let v1_path = api::Keyring::path(name, api::MAJOR_VERSION)?;
206
        let v0_path = api::Keyring::path(name, api::LEGACY_MAJOR_VERSION)?;
207
        Self::open_with_paths(v1_path, v0_path, secret).await
208
    }
209

            
210
    /// Open or create a keyring at a specific data directory.
211
    ///
212
    /// This is useful for tests and cases where you want explicit control over
213
    /// where keyrings are stored, avoiding the default XDG_DATA_HOME location.
214
    ///
215
    /// This function will automatically migrate the keyring to the latest
216
    /// format.
217
    ///
218
    /// # Arguments
219
    ///
220
    /// * `data_dir` - Base data directory (keyrings stored in
221
    ///   `data_dir/keyrings/v1/`)
222
    /// * `name` - The name of the keyring.
223
    /// * `secret` - The service key, usually retrieved from the Secrets portal.
224
    ///
225
    /// # Example
226
    ///
227
    /// ```no_run
228
    /// # use oo7::{Secret, file::UnlockedKeyring};
229
    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
230
    /// let temp_dir = tempfile::tempdir()?;
231
    /// let keyring =
232
    ///     UnlockedKeyring::open_at(temp_dir.path(), "test-keyring", Secret::from("password")).await?;
233
    /// keyring
234
    ///     .create_item("item", &[("attr", "value")], Secret::text("secret"), false)
235
    ///     .await?;
236
    /// keyring.write().await?; // Writes to temp_dir/keyrings/v1/test-keyring.keyring
237
    /// //
238
    /// # Ok(())
239
    /// # }
240
    /// ```
241
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(secret), fields(data_dir = ?data_dir.as_ref())))]
242
17
    pub async fn open_at(
243
        data_dir: impl AsRef<Path>,
244
        name: &str,
245
        secret: Secret,
246
    ) -> Result<Self, Error> {
247
17
        let v1_path = api::Keyring::path_at(&data_dir, name, api::MAJOR_VERSION);
248
17
        let v0_path = api::Keyring::path_at(&data_dir, name, api::LEGACY_MAJOR_VERSION);
249
22
        Self::open_with_paths(v1_path, v0_path, secret).await
250
    }
251

            
252
    /// Lock the keyring.
253
8
    pub fn lock(self) -> LockedKeyring {
254
        LockedKeyring {
255
8
            keyring: self.keyring,
256
8
            path: self.path,
257
8
            mtime: self.mtime,
258
        }
259
    }
260

            
261
    /// Lock an item using the keyring's key.
262
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, item)))]
263
40
    pub async fn lock_item(&self, item: UnlockedItem) -> Result<LockedItem, Error> {
264
24
        let key = self.derive_key().await?;
265
8
        item.lock(&key)
266
    }
267

            
268
    /// Unlock an item using the keyring's key.
269
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, item)))]
270
40
    pub async fn unlock_item(&self, item: LockedItem) -> Result<UnlockedItem, Error> {
271
24
        let key = self.derive_key().await?;
272
8
        item.unlock(&key)
273
    }
274

            
275
    /// Get the encryption key for this keyring.
276
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))]
277
67
    pub async fn key(&self) -> Result<Arc<Key>, crate::crypto::Error> {
278
48
        self.derive_key().await
279
    }
280

            
281
    /// Return the associated file if any.
282
26
    pub fn path(&self) -> Option<&std::path::Path> {
283
34
        self.path.as_deref()
284
    }
285

            
286
    /// Get the modification timestamp
287
115
    pub async fn modified_time(&self) -> std::time::Duration {
288
81
        self.keyring.read().await.modified_time()
289
    }
290

            
291
    /// Retrieve the number of items
292
    ///
293
    /// This function will not trigger a key derivation and can therefore be
294
    /// faster than [`items().len()`](Self::items).
295
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))]
296
8
    pub async fn n_items(&self) -> usize {
297
8
        self.keyring.read().await.items.len()
298
    }
299

            
300
    /// Retrieve all items including those that cannot be decrypted.
301
    ///
302
    /// Returns a [`Vec`] where each element is either an [`UnlockedItem`] or an
303
    /// [`InvalidItemError`] for items that failed to decrypt.
304
    ///
305
    /// Use this method when you need to know about or handle decryption
306
    /// failures. For most use cases, [`items()`](Self::items) is more
307
    /// convenient as it only returns successfully decrypted items.
308
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))]
309
167
    pub async fn all_items(&self) -> Result<Vec<Result<UnlockedItem, InvalidItemError>>, Error> {
310
134
        let key = self.derive_key().await?;
311
109
        let keyring = self.keyring.read().await;
312

            
313
        #[cfg(feature = "tracing")]
314
79
        let _span = tracing::debug_span!("decrypt_all", total_items = keyring.items.len());
315

            
316
109
        Ok(keyring
317
            .items
318
33
            .iter()
319
46
            .map(|e| {
320
10
                (*e).clone().decrypt(&key).map_err(|err| {
321
2
                    InvalidItemError::new(
322
2
                        err,
323
8
                        e.hashed_attributes.keys().map(|x| x.to_string()).collect(),
324
                    )
325
                })
326
            })
327
33
            .collect())
328
    }
329

            
330
    /// Retrieve the list of available [`UnlockedItem`]s.
331
    ///
332
    /// Items that cannot be decrypted are silently skipped. Use
333
    /// [`all_items()`](Self::all_items) if you need access to decryption
334
    /// errors.
335
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))]
336
161
    pub async fn items(&self) -> Result<Vec<UnlockedItem>, Error> {
337
132
        Ok(self.all_items().await?.into_iter().flatten().collect())
338
    }
339

            
340
    /// Search items matching the attributes.
341
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, attributes)))]
342
15
    pub async fn search_items(
343
        &self,
344
        attributes: &impl AsAttributes,
345
    ) -> Result<Vec<UnlockedItem>, Error> {
346
30
        let key = self.derive_key().await?;
347
30
        let keyring = self.keyring.read().await;
348
30
        let results = keyring.search_items(attributes, &key)?;
349

            
350
30
        #[cfg(feature = "tracing")]
351
        tracing::debug!("Found {} matching items", results.len());
352

            
353
15
        Ok(results)
354
    }
355

            
356
    /// Find the first item matching the attributes.
357
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, attributes)))]
358
2
    pub async fn lookup_item(
359
        &self,
360
        attributes: &impl AsAttributes,
361
    ) -> Result<Option<UnlockedItem>, Error> {
362
4
        let key = self.derive_key().await?;
363
4
        let keyring = self.keyring.read().await;
364

            
365
6
        keyring.lookup_item(attributes, &key)
366
    }
367

            
368
    /// Find the index in the list of items of the first item matching the
369
    /// attributes.
370
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, attributes)))]
371
4
    pub async fn lookup_item_index(
372
        &self,
373
        attributes: &impl AsAttributes,
374
    ) -> Result<Option<usize>, Error> {
375
8
        let key = self.derive_key().await?;
376
8
        let keyring = self.keyring.read().await;
377

            
378
12
        Ok(keyring.lookup_item_index(attributes, &key))
379
    }
380

            
381
    /// Delete an item.
382
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, attributes)))]
383
110
    pub async fn delete(&self, attributes: &impl AsAttributes) -> Result<(), Error> {
384
        #[cfg(feature = "tracing")]
385
51
        let items_before = { self.keyring.read().await.items.len() };
386

            
387
        {
388
26
            let key = self.derive_key().await?;
389
51
            let mut keyring = self.keyring.write().await;
390
49
            keyring.remove_items(attributes, &key)?;
391
        };
392

            
393
41
        self.write().await?;
394

            
395
        #[cfg(feature = "tracing")]
396
        {
397
28
            let items_after = self.keyring.read().await.items.len();
398
28
            let deleted_count = items_before.saturating_sub(items_after);
399
35
            tracing::info!("Deleted {} items", deleted_count);
400
        }
401

            
402
26
        Ok(())
403
    }
404

            
405
    /// Create a new item
406
    ///
407
    /// # Arguments
408
    ///
409
    /// * `label` - A user visible label of the item.
410
    /// * `attributes` - A map of key/value attributes, used to find the item
411
    ///   later.
412
    /// * `secret` - The secret to store.
413
    /// * `replace` - Whether to replace the value if the `attributes` matches
414
    ///   an existing `secret`.
415
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, secret, attributes), fields(replace = replace)))]
416
48
    pub async fn create_item(
417
        &self,
418
        label: &str,
419
        attributes: &impl AsAttributes,
420
        secret: impl Into<Secret>,
421
        replace: bool,
422
    ) -> Result<UnlockedItem, Error> {
423
        let item = {
424
110
            let key = self.derive_key().await?;
425
98
            let mut keyring = self.keyring.write().await;
426
71
            if replace {
427
46
                keyring.remove_items(attributes, &key)?;
428
            }
429
49
            let item = UnlockedItem::new(label, attributes, secret);
430
101
            let encrypted_item = item.encrypt(&key)?;
431
104
            keyring.items.push(encrypted_item);
432
52
            item
433
        };
434
193
        match self.write().await {
435
            Err(e) => {
436
                #[cfg(feature = "tracing")]
437
                tracing::error!("Failed to write keyring after item creation");
438
                Err(e)
439
            }
440
            Ok(_) => {
441
110
                #[cfg(feature = "tracing")]
442
                tracing::info!("Successfully created item");
443
53
                Ok(item)
444
            }
445
        }
446
    }
447

            
448
    /// Replaces item at the given index.
449
    ///
450
    /// The `index` refers to the index of the [`Vec`] returned by
451
    /// [`items()`](Self::items). If the index does not exist, the functions
452
    /// returns an error.
453
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, item), fields(index = index)))]
454
20
    pub async fn replace_item_index(&self, index: usize, item: &UnlockedItem) -> Result<(), Error> {
455
        {
456
12
            let key = self.derive_key().await?;
457
12
            let mut keyring = self.keyring.write().await;
458

            
459
8
            if let Some(item_store) = keyring.items.get_mut(index) {
460
8
                *item_store = item.encrypt(&key)?;
461
            } else {
462
2
                return Err(Error::InvalidItemIndex(index));
463
            }
464
        }
465
12
        self.write().await
466
    }
467

            
468
    /// Deletes item at the given index.
469
    ///
470
    /// The `index` refers to the index of the [`Vec`] returned by
471
    /// [`items()`](Self::items). If the index does not exist, the functions
472
    /// returns an error.
473
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self), fields(index = index)))]
474
10
    pub async fn delete_item_index(&self, index: usize) -> Result<(), Error> {
475
        {
476
6
            let mut keyring = self.keyring.write().await;
477

            
478
4
            if index < keyring.items.len() {
479
4
                keyring.items.remove(index);
480
            } else {
481
2
                return Err(Error::InvalidItemIndex(index));
482
            }
483
        }
484
6
        self.write().await
485
    }
486

            
487
    /// Create multiple items in a single operation to avoid re-writing the file
488
    /// multiple times.
489
    ///
490
    /// This is more efficient than calling `create_item()` multiple times.
491
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, items), fields(item_count = items.len())))]
492
24
    pub async fn create_items(&self, items: Vec<ItemDefinition>) -> Result<(), Error> {
493
10
        let key = self.derive_key().await?;
494
10
        let mut mtime = self.mtime.lock().await;
495
10
        let mut keyring = self.keyring.write().await;
496

            
497
        #[cfg(feature = "tracing")]
498
10
        let _span = tracing::debug_span!("bulk_create", items_to_create = items.len());
499

            
500
16
        for (label, attributes, secret, replace) in items {
501
6
            if replace {
502
4
                keyring.remove_items(&attributes, &key)?;
503
            }
504
4
            let item = UnlockedItem::new(label, &attributes, secret);
505
8
            let encrypted_item = item.encrypt(&key)?;
506
8
            keyring.items.push(encrypted_item);
507
        }
508

            
509
4
        #[cfg(feature = "tracing")]
510
        tracing::debug!("Writing keyring back to the file");
511
8
        if let Some(ref path) = self.path {
512
14
            keyring.dump(path, *mtime).await?;
513
            // Update mtime after successful write
514
14
            if let Ok(modified) = fs::metadata(path).await?.modified() {
515
8
                *mtime = Some(modified);
516
            }
517
        }
518
4
        Ok(())
519
    }
520

            
521
    /// Write the changes to the keyring file.
522
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))]
523
131
    pub async fn write(&self) -> Result<(), Error> {
524
96
        let mut mtime = self.mtime.lock().await;
525
        {
526
89
            let mut keyring = self.keyring.write().await;
527

            
528
47
            if let Some(ref path) = self.path {
529
64
                keyring.dump(path, *mtime).await?;
530
            }
531
        };
532
33
        let Some(ref path) = self.path else {
533
25
            return Ok(());
534
        };
535

            
536
88
        if let Ok(modified) = fs::metadata(path).await?.modified() {
537
40
            *mtime = Some(modified);
538
        }
539
18
        Ok(())
540
    }
541

            
542
    /// Return key, derive and store it first if not initialized
543
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))]
544
210
    async fn derive_key(&self) -> Result<Arc<Key>, crate::crypto::Error> {
545
75
        let keyring = Arc::clone(&self.keyring);
546
106
        let secret_lock = self.secret.lock().await;
547
71
        let secret = Arc::clone(&secret_lock);
548
38
        drop(secret_lock);
549

            
550
70
        let mut key_lock = self.key.lock().await;
551
110
        if key_lock.is_none() {
552
            #[cfg(feature = "async-std")]
553
            let key = blocking::unblock(move || {
554
                async_io::block_on(async { keyring.read().await.derive_key(&secret) })
555
            })
556
            .await?;
557
            #[cfg(feature = "tokio")]
558
            let key = {
559
235
                tokio::task::spawn_blocking(move || keyring.blocking_read().derive_key(&secret))
560
172
                    .await
561
                    .unwrap()?
562
            };
563

            
564
72
            *key_lock = Some(Arc::new(key));
565
        }
566

            
567
79
        Ok(Arc::clone(key_lock.as_ref().unwrap()))
568
    }
569

            
570
    /// Change keyring secret
571
    ///
572
    /// # Arguments
573
    ///
574
    /// * `secret` - The new secret to store.
575
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, secret)))]
576
36
    pub async fn change_secret(&self, secret: Secret) -> Result<(), Error> {
577
18
        let keyring = self.keyring.read().await;
578
18
        let key = self.derive_key().await?;
579
12
        let mut items = Vec::with_capacity(keyring.items.len());
580

            
581
        #[cfg(feature = "tracing")]
582
16
        let _decrypt_span =
583
            tracing::debug_span!("decrypt_for_reencrypt", total_items = keyring.items.len());
584

            
585
12
        for item in &keyring.items {
586
6
            items.push(item.clone().decrypt(&key)?);
587
        }
588
6
        drop(keyring);
589

            
590
6
        #[cfg(feature = "tracing")]
591
        tracing::debug!("Updating secret and resetting key");
592

            
593
18
        let mut secret_lock = self.secret.lock().await;
594
6
        *secret_lock = Arc::new(secret);
595
6
        drop(secret_lock);
596

            
597
18
        let mut key_lock = self.key.lock().await;
598
        // Unset the old key
599
6
        *key_lock = None;
600
6
        drop(key_lock);
601

            
602
        // Reset Keyring content before setting the new key
603
18
        let mut keyring = self.keyring.write().await;
604
12
        keyring.reset()?;
605
6
        drop(keyring);
606

            
607
        // Set new key
608
18
        let key = self.derive_key().await?;
609

            
610
        #[cfg(feature = "tracing")]
611
16
        let _reencrypt_span = tracing::debug_span!("reencrypt", total_items = items.len());
612

            
613
18
        let mut keyring = self.keyring.write().await;
614
18
        for item in items {
615
12
            let encrypted_item = item.encrypt(&key)?;
616
12
            keyring.items.push(encrypted_item);
617
        }
618
6
        drop(keyring);
619

            
620
24
        self.write().await
621
    }
622

            
623
    /// Validate that a secret can decrypt the items in this keyring.
624
    ///
625
    /// For empty keyrings, this always returns `true` since there are no items
626
    /// to validate against.
627
    ///
628
    /// # Arguments
629
    ///
630
    /// * `secret` - The secret to validate.
631
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, secret)))]
632
16
    pub async fn validate_secret(&self, secret: &Secret) -> Result<bool, Error> {
633
12
        let keyring = self.keyring.read().await;
634
8
        Ok(keyring.validate_secret(secret)?)
635
    }
636

            
637
    /// Delete any item that cannot be decrypted with the key associated to the
638
    /// keyring.
639
    ///
640
    /// This can only happen if an item was created using
641
    /// [`Self::load_unchecked`] or prior to 0.4 where we didn't validate
642
    /// the secret when using [`Self::load`] or modified externally.
643
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))]
644
12
    pub async fn delete_broken_items(&self) -> Result<usize, Error> {
645
6
        let key = self.derive_key().await?;
646
6
        let mut keyring = self.keyring.write().await;
647
2
        let mut broken_items = vec![];
648

            
649
        #[cfg(feature = "tracing")]
650
4
        let _span = tracing::debug_span!("identify_broken", total_items = keyring.items.len());
651

            
652
4
        for (index, encrypted_item) in keyring.items.iter().enumerate() {
653
4
            if !encrypted_item.is_valid(&key) {
654
2
                broken_items.push(index);
655
            }
656
        }
657
2
        let n_broken_items = broken_items.len();
658

            
659
2
        #[cfg(feature = "tracing")]
660
        tracing::info!("Found {} broken items to delete", n_broken_items);
661

            
662
        #[cfg(feature = "tracing")]
663
4
        let _remove_span = tracing::debug_span!("remove_broken", broken_count = n_broken_items);
664

            
665
6
        for index in broken_items.into_iter().rev() {
666
4
            keyring.items.remove(index);
667
        }
668
2
        drop(keyring);
669

            
670
6
        self.write().await?;
671
2
        Ok(n_broken_items)
672
    }
673
}