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, Item, 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
    pub async fn load(path: impl AsRef<Path>, secret: Secret) -> Result<Self, Error> {
49
        Self::load_inner(path, secret, true).await
50
    }
51

            
52
    /// Load from a keyring file.
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
    /// The secret is not validated to be the correct one to decrypt the keyring
62
    /// items. Allowing the API user to write new items with a different
63
    /// secret on top of previously added items with a different secret.
64
    ///
65
    /// As it is not a supported behaviour, this API is mostly meant for
66
    /// recovering broken keyrings.
67
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(secret), fields(path = ?path.as_ref())))]
68
    pub async unsafe fn load_unchecked(
69
        path: impl AsRef<Path>,
70
        secret: Secret,
71
    ) -> Result<Self, Error> {
72
        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
    async fn load_inner(
77
        path: impl AsRef<Path>,
78
        secret: Secret,
79
        validate_items: bool,
80
    ) -> Result<Self, Error> {
81
        #[cfg(feature = "tracing")]
82
        tracing::debug!("Trying to load keyring file at {:?}", path.as_ref());
83
        if validate_items {
84
            LockedKeyring::load(path).await?.unlock(secret).await
85
        } else {
86
            unsafe {
87
                LockedKeyring::load(path)
88
                    .await?
89
                    .unlock_unchecked(secret)
90
                    .await
91
            }
92
        }
93
    }
94

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

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

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

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

            
136
6
                let decrypted_items = legacy_keyring.decrypt_items(&secret)?;
137

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

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

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

            
159
    /// Open a keyring with given name from the default directory.
160
    ///
161
    /// This function will automatically migrate the keyring to the
162
    /// latest format.
163
    ///
164
    /// # Arguments
165
    ///
166
    /// * `name` - The name of the keyring.
167
    /// * `secret` - The service key, usually retrieved from the Secrets portal.
168
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(secret)))]
169
12
    pub async fn open(name: &str, secret: Secret) -> Result<Self, Error> {
170
4
        let v1_path = api::Keyring::path(name, api::MAJOR_VERSION)?;
171
4
        if v1_path.exists() {
172
            #[cfg(feature = "tracing")]
173
            tracing::debug!("Loading v1 keyring file");
174
            return Self::load(v1_path, secret).await;
175
        }
176

            
177
4
        let v0_path = api::Keyring::path(name, api::LEGACY_MAJOR_VERSION)?;
178
4
        if v0_path.exists() {
179
4
            #[cfg(feature = "tracing")]
180
            tracing::debug!("Trying to load keyring file at {:?}", v0_path);
181
6
            match fs::File::open(&v0_path).await {
182
                Err(err) => Err(err.into()),
183
4
                Ok(mut file) => Self::migrate(&mut file, v1_path, secret).await,
184
            }
185
        } else {
186
4
            #[cfg(feature = "tracing")]
187
            tracing::debug!("Creating new keyring");
188
2
            Ok(Self {
189
4
                keyring: Arc::new(RwLock::new(api::Keyring::new())),
190
2
                path: Some(v1_path),
191
2
                mtime: Default::default(),
192
2
                key: Default::default(),
193
4
                secret: Mutex::new(Arc::new(secret)),
194
            })
195
        }
196
    }
197

            
198
    /// Lock the keyring.
199
2
    pub fn lock(self) -> LockedKeyring {
200
        LockedKeyring {
201
2
            keyring: self.keyring,
202
2
            path: self.path,
203
2
            mtime: self.mtime,
204
        }
205
    }
206

            
207
    /// Lock an item using the keyring's key.
208
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, item)))]
209
10
    pub async fn lock_item(&self, item: UnlockedItem) -> Result<LockedItem, Error> {
210
4
        let key = self.derive_key().await?;
211
2
        item.lock(&key)
212
    }
213

            
214
    /// Unlock an item using the keyring's key.
215
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, item)))]
216
10
    pub async fn unlock_item(&self, item: LockedItem) -> Result<UnlockedItem, Error> {
217
4
        let key = self.derive_key().await?;
218
2
        item.unlock(&key)
219
    }
220

            
221
    /// Get the encryption key for this keyring.
222
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))]
223
10
    pub async fn key(&self) -> Result<Arc<Key>, crate::crypto::Error> {
224
6
        self.derive_key().await
225
    }
226

            
227
    /// Return the associated file if any.
228
4
    pub fn path(&self) -> Option<&std::path::Path> {
229
4
        self.path.as_deref()
230
    }
231

            
232
    /// Get the modification timestamp
233
16
    pub async fn modified_time(&self) -> std::time::Duration {
234
8
        self.keyring.read().await.modified_time()
235
    }
236

            
237
    /// Retrieve the number of items
238
    ///
239
    /// This function will not trigger a key derivation and can therefore be
240
    /// faster than [`items().len()`](Self::items).
241
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))]
242
    pub async fn n_items(&self) -> usize {
243
        self.keyring.read().await.items.len()
244
    }
245

            
246
    /// Retrieve the list of available [`UnlockedItem`]s.
247
    ///
248
    /// If items cannot be decrypted, [`InvalidItemError`]s are returned for
249
    /// them instead of [`UnlockedItem`]s.
250
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))]
251
19
    pub async fn items(&self) -> Result<Vec<Result<Item, InvalidItemError>>, Error> {
252
13
        let key = self.derive_key().await?;
253
8
        let keyring = self.keyring.read().await;
254

            
255
        #[cfg(feature = "tracing")]
256
12
        let _span = tracing::debug_span!("decrypt", total_items = keyring.items.len());
257

            
258
12
        Ok(keyring
259
            .items
260
4
            .iter()
261
6
            .map(|e| {
262
4
                (*e).clone()
263
4
                    .decrypt(&key)
264
2
                    .map_err(|err| {
265
                        InvalidItemError::new(
266
                            err,
267
                            e.hashed_attributes.keys().map(|x| x.to_string()).collect(),
268
                        )
269
                    })
270
2
                    .map(Item::Unlocked)
271
            })
272
4
            .collect())
273
    }
274

            
275
    /// Search items matching the attributes.
276
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, attributes)))]
277
    pub async fn search_items(&self, attributes: &impl AsAttributes) -> Result<Vec<Item>, Error> {
278
        let key = self.derive_key().await?;
279
        let keyring = self.keyring.read().await;
280
        let results = keyring
281
            .search_items(attributes, &key)?
282
            .into_iter()
283
            .map(Item::Unlocked)
284
            .collect::<Vec<Item>>();
285

            
286
        #[cfg(feature = "tracing")]
287
        tracing::debug!("Found {} matching items", results.len());
288

            
289
        Ok(results)
290
    }
291

            
292
    /// Find the first item matching the attributes.
293
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, attributes)))]
294
    pub async fn lookup_item(&self, attributes: &impl AsAttributes) -> Result<Option<Item>, Error> {
295
        let key = self.derive_key().await?;
296
        let keyring = self.keyring.read().await;
297

            
298
        keyring
299
            .lookup_item(attributes, &key)
300
            .map(|maybe_item| maybe_item.map(Item::Unlocked))
301
    }
302

            
303
    /// Find the index in the list of items of the first item matching the
304
    /// attributes.
305
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, attributes)))]
306
    pub async fn lookup_item_index(
307
        &self,
308
        attributes: &impl AsAttributes,
309
    ) -> Result<Option<usize>, Error> {
310
        let key = self.derive_key().await?;
311
        let keyring = self.keyring.read().await;
312

            
313
        Ok(keyring.lookup_item_index(attributes, &key))
314
    }
315

            
316
    /// Delete an item.
317
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, attributes)))]
318
8
    pub async fn delete(&self, attributes: &impl AsAttributes) -> Result<(), Error> {
319
        #[cfg(feature = "tracing")]
320
4
        let items_before = { self.keyring.read().await.items.len() };
321

            
322
        {
323
2
            let key = self.derive_key().await?;
324
4
            let mut keyring = self.keyring.write().await;
325
4
            keyring.remove_items(attributes, &key)?;
326
        };
327

            
328
2
        self.write().await?;
329

            
330
        #[cfg(feature = "tracing")]
331
        {
332
2
            let items_after = self.keyring.read().await.items.len();
333
2
            let deleted_count = items_before.saturating_sub(items_after);
334
2
            tracing::info!("Deleted {} items", deleted_count);
335
        }
336

            
337
2
        Ok(())
338
    }
339

            
340
    /// Create a new item
341
    ///
342
    /// # Arguments
343
    ///
344
    /// * `label` - A user visible label of the item.
345
    /// * `attributes` - A map of key/value attributes, used to find the item
346
    ///   later.
347
    /// * `secret` - The secret to store.
348
    /// * `replace` - Whether to replace the value if the `attributes` matches
349
    ///   an existing `secret`.
350
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, secret, attributes), fields(replace = replace)))]
351
4
    pub async fn create_item(
352
        &self,
353
        label: &str,
354
        attributes: &impl AsAttributes,
355
        secret: impl Into<Secret>,
356
        replace: bool,
357
    ) -> Result<Item, Error> {
358
        let item = {
359
12
            let key = self.derive_key().await?;
360
8
            let mut keyring = self.keyring.write().await;
361
6
            if replace {
362
4
                keyring.remove_items(attributes, &key)?;
363
            }
364
4
            let item = UnlockedItem::new(label, attributes, secret);
365
8
            let encrypted_item = item.encrypt(&key)?;
366
8
            keyring.items.push(encrypted_item);
367
4
            item
368
        };
369
16
        match self.write().await {
370
            Err(e) => {
371
                #[cfg(feature = "tracing")]
372
                tracing::error!("Failed to write keyring after item creation");
373
                Err(e)
374
            }
375
            Ok(_) => {
376
8
                #[cfg(feature = "tracing")]
377
                tracing::info!("Successfully created item");
378
4
                Ok(Item::Unlocked(item))
379
            }
380
        }
381
    }
382

            
383
    /// Replaces item at the given index.
384
    ///
385
    /// The `index` refers to the index of the [`Vec`] returned by
386
    /// [`items()`](Self::items). If the index does not exist, the functions
387
    /// returns an error.
388
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, item), fields(index = index)))]
389
    pub async fn replace_item_index(&self, index: usize, item: &UnlockedItem) -> Result<(), Error> {
390
        {
391
            let key = self.derive_key().await?;
392
            let mut keyring = self.keyring.write().await;
393

            
394
            if let Some(item_store) = keyring.items.get_mut(index) {
395
                *item_store = item.encrypt(&key)?;
396
            } else {
397
                return Err(Error::InvalidItemIndex(index));
398
            }
399
        }
400
        self.write().await
401
    }
402

            
403
    /// Deletes item at the given index.
404
    ///
405
    /// The `index` refers to the index of the [`Vec`] returned by
406
    /// [`items()`](Self::items). If the index does not exist, the functions
407
    /// returns an error.
408
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self), fields(index = index)))]
409
    pub async fn delete_item_index(&self, index: usize) -> Result<(), Error> {
410
        {
411
            let mut keyring = self.keyring.write().await;
412

            
413
            if index < keyring.items.len() {
414
                keyring.items.remove(index);
415
            } else {
416
                return Err(Error::InvalidItemIndex(index));
417
            }
418
        }
419
        self.write().await
420
    }
421

            
422
    /// Create multiple items in a single operation to avoid re-writing the file
423
    /// multiple times.
424
    ///
425
    /// This is more efficient than calling `create_item()` multiple times.
426
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, items), fields(item_count = items.len())))]
427
    pub async fn create_items(&self, items: Vec<ItemDefinition>) -> Result<(), Error> {
428
        let key = self.derive_key().await?;
429
        let mut mtime = self.mtime.lock().await;
430
        let mut keyring = self.keyring.write().await;
431

            
432
        #[cfg(feature = "tracing")]
433
        let _span = tracing::debug_span!("bulk_create", items_to_create = items.len());
434

            
435
        for (label, attributes, secret, replace) in items {
436
            if replace {
437
                keyring.remove_items(&attributes, &key)?;
438
            }
439
            let item = UnlockedItem::new(label, &attributes, secret);
440
            let encrypted_item = item.encrypt(&key)?;
441
            keyring.items.push(encrypted_item);
442
        }
443

            
444
        #[cfg(feature = "tracing")]
445
        tracing::debug!("Writing keyring back to the file");
446
        if let Some(ref path) = self.path {
447
            keyring.dump(path, *mtime).await?;
448
            // Update mtime after successful write
449
            if let Ok(modified) = fs::metadata(path).await?.modified() {
450
                *mtime = Some(modified);
451
            }
452
        }
453
        Ok(())
454
    }
455

            
456
    /// Write the changes to the keyring file.
457
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))]
458
10
    pub async fn write(&self) -> Result<(), Error> {
459
4
        let mut mtime = self.mtime.lock().await;
460
        {
461
4
            let mut keyring = self.keyring.write().await;
462

            
463
4
            if let Some(ref path) = self.path {
464
6
                keyring.dump(path, *mtime).await?;
465
            }
466
        };
467
2
        let Some(ref path) = self.path else {
468
2
            return Ok(());
469
        };
470

            
471
8
        if let Ok(modified) = fs::metadata(path).await?.modified() {
472
4
            *mtime = Some(modified);
473
        }
474
2
        Ok(())
475
    }
476

            
477
    /// Return key, derive and store it first if not initialized
478
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))]
479
21
    async fn derive_key(&self) -> Result<Arc<Key>, crate::crypto::Error> {
480
8
        let keyring = Arc::clone(&self.keyring);
481
8
        let secret_lock = self.secret.lock().await;
482
8
        let secret = Arc::clone(&secret_lock);
483
6
        drop(secret_lock);
484

            
485
2
        let mut key_lock = self.key.lock().await;
486
12
        if key_lock.is_none() {
487
            #[cfg(feature = "async-std")]
488
            let key = blocking::unblock(move || {
489
                async_io::block_on(async { keyring.read().await.derive_key(&secret) })
490
            })
491
            .await?;
492
            #[cfg(feature = "tokio")]
493
            let key = {
494
26
                tokio::task::spawn_blocking(move || keyring.blocking_read().derive_key(&secret))
495
17
                    .await
496
                    .unwrap()?
497
            };
498

            
499
8
            *key_lock = Some(Arc::new(key));
500
        }
501

            
502
8
        Ok(Arc::clone(key_lock.as_ref().unwrap()))
503
    }
504

            
505
    /// Change keyring secret
506
    ///
507
    /// # Arguments
508
    ///
509
    /// * `secret` - The new secret to store.
510
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, secret)))]
511
12
    pub async fn change_secret(&self, secret: Secret) -> Result<(), Error> {
512
4
        let keyring = self.keyring.read().await;
513
4
        let key = self.derive_key().await?;
514
4
        let mut items = Vec::with_capacity(keyring.items.len());
515

            
516
        #[cfg(feature = "tracing")]
517
6
        let _decrypt_span =
518
            tracing::debug_span!("decrypt_for_reencrypt", total_items = keyring.items.len());
519

            
520
4
        for item in &keyring.items {
521
2
            items.push(item.clone().decrypt(&key)?);
522
        }
523
2
        drop(keyring);
524

            
525
2
        #[cfg(feature = "tracing")]
526
        tracing::debug!("Updating secret and resetting key");
527

            
528
4
        let mut secret_lock = self.secret.lock().await;
529
2
        *secret_lock = Arc::new(secret);
530
2
        drop(secret_lock);
531

            
532
4
        let mut key_lock = self.key.lock().await;
533
        // Unset the old key
534
2
        *key_lock = None;
535
2
        drop(key_lock);
536

            
537
        // Reset Keyring content before setting the new key
538
4
        let mut keyring = self.keyring.write().await;
539
4
        keyring.reset();
540
2
        drop(keyring);
541

            
542
        // Set new key
543
4
        let key = self.derive_key().await?;
544

            
545
        #[cfg(feature = "tracing")]
546
6
        let _reencrypt_span = tracing::debug_span!("reencrypt", total_items = items.len());
547

            
548
4
        let mut keyring = self.keyring.write().await;
549
6
        for item in items {
550
4
            let encrypted_item = item.encrypt(&key)?;
551
4
            keyring.items.push(encrypted_item);
552
        }
553
2
        drop(keyring);
554

            
555
6
        self.write().await
556
    }
557

            
558
    /// Validate that a secret can decrypt the items in this keyring.
559
    ///
560
    /// For empty keyrings, this always returns `true` since there are no items
561
    /// to validate against.
562
    ///
563
    /// # Arguments
564
    ///
565
    /// * `secret` - The secret to validate.
566
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, secret)))]
567
8
    pub async fn validate_secret(&self, secret: &Secret) -> Result<bool, Error> {
568
4
        let keyring = self.keyring.read().await;
569
4
        Ok(keyring.validate_secret(secret)?)
570
    }
571

            
572
    /// Delete any item that cannot be decrypted with the key associated to the
573
    /// keyring.
574
    ///
575
    /// This can only happen if an item was created using
576
    /// [`Self::load_unchecked`] or prior to 0.4 where we didn't validate
577
    /// the secret when using [`Self::load`] or modified externally.
578
    #[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))]
579
    pub async fn delete_broken_items(&self) -> Result<usize, Error> {
580
        let key = self.derive_key().await?;
581
        let mut keyring = self.keyring.write().await;
582
        let mut broken_items = vec![];
583

            
584
        #[cfg(feature = "tracing")]
585
        let _span = tracing::debug_span!("identify_broken", total_items = keyring.items.len());
586

            
587
        for (index, encrypted_item) in keyring.items.iter().enumerate() {
588
            if !encrypted_item.is_valid(&key) {
589
                broken_items.push(index);
590
            }
591
        }
592
        let n_broken_items = broken_items.len();
593

            
594
        #[cfg(feature = "tracing")]
595
        tracing::info!("Found {} broken items to delete", n_broken_items);
596

            
597
        #[cfg(feature = "tracing")]
598
        let _remove_span = tracing::debug_span!("remove_broken", broken_count = n_broken_items);
599

            
600
        for index in broken_items.into_iter().rev() {
601
            keyring.items.remove(index);
602
        }
603
        drop(keyring);
604

            
605
        self.write().await?;
606
        Ok(n_broken_items)
607
    }
608
}