Skip to main content

openpgp_cert_d/
certd.rs

1use std::{
2    borrow::Cow,
3    convert::TryInto,
4    env,
5    fs::{self, File},
6    io::{self, Read, Write},
7    path::{Path, PathBuf},
8};
9
10use fd_lock::RwLock;
11use tempfile::NamedTempFile;
12use walkdir::WalkDir;
13
14use crate::SPECIAL_NAMES;
15use crate::{pgp, InternalError};
16use crate::{Error, Result, Tag};
17
18const PATH_PREFIX_LEN: usize = 2;
19
20const TRACE: bool = false;
21
22/// The data type returned by the merge callback.
23///
24/// See, for instance, [`CertD::insert`].
25pub enum MergeResult<'a> {
26    /// Keep the on-disk version.
27    Keep,
28
29    /// Use the specified version.
30    ///
31    /// This is usually a merged version of the on-disk version and the
32    /// new version of the certificate.
33    DataRef(&'a [u8]),
34
35    /// Use the specified version.
36    ///
37    /// This is usually a merged version of the on-disk version and the
38    /// new version of the certificate.
39    Data(Vec<u8>),
40}
41
42impl<'a> From<&'a [u8]> for MergeResult<'a> {
43    fn from(data: &'a [u8]) -> Self {
44        MergeResult::DataRef(data)
45    }
46}
47
48impl From<Vec<u8>> for MergeResult<'_> {
49    fn from(data: Vec<u8>) -> Self {
50        MergeResult::Data(data)
51    }
52}
53
54// Used by `CertD::tag`.
55//
56// One tag for each directory.
57struct CertDTag([Tag; 256]);
58
59impl CertDTag {
60    fn null() -> Self {
61        Self([Tag(676149182_1608123167); 256])
62    }
63
64    fn compress(&self) -> Tag {
65        // This is pretty naive.  A hash would be better.  So would a
66        // few more bits.
67        let mut composite: u64 = 0;
68        for (i, tag) in self.0.iter().enumerate() {
69            let mut tag = tag.0;
70
71            tag = tag.rotate_right(i as u32);
72
73            composite ^= tag
74        }
75
76        Tag(composite)
77    }
78}
79
80/// A certificate store.
81///
82/// This is a handle to an on-disk certificate store that can be used
83/// to lookup and insert certificates.
84///
85/// A certificate store contains certificates.  Its main role is to
86/// hold certificates indexed by their fingerprint.  (Note:
87/// certificates are not indexed by their subkey fingerprints.)  But,
88/// it can also store certificates under [special names].  Currently,
89/// the specification defines one special name, `trust-root`, which
90/// holds the user's local trust root.  Non-standard special names are
91/// possible.  These MUST start with an underscore, which SHOULD be
92/// immediately followed by the vendor's name, e.g.,
93/// `_sequoia_some_special.pgp`.
94///
95///   [special names]: https://www.ietf.org/archive/id/draft-nwjw-openpgp-cert-d-00.html#name-special-names
96#[derive(Debug)]
97pub struct CertD {
98    base: PathBuf,
99}
100
101impl CertD {
102    /// Opens the default certificate store.
103    ///
104    /// If not explicitly requested otherwise, an application SHOULD
105    /// use the [default store].  To use a store with a different
106    /// location, use [`CertD::with_base_dir`].
107    ///
108    /// [default store]: https://www.ietf.org/archive/id/draft-nwjw-openpgp-cert-d-00.html#name-default-stores-location
109    pub fn new() -> Result<CertD> {
110        CertD::with_base_dir(Self::user_configured_store_path()?)
111    }
112
113    /// Returns the location of the user-configured store.
114    ///
115    /// If set, this is the value of the environment variable
116    /// `PGP_CERT_D`.  Otherwise, it is the default store's path as
117    /// returned by [`CertD::default_store_path`].
118    pub fn user_configured_store_path() -> Result<PathBuf> {
119        if let Some(path) = env::var_os("PGP_CERT_D") {
120            Ok(PathBuf::from(path))
121        } else {
122            CertD::default_store_path()
123        }
124    }
125
126    /// Returns the location of the default store.
127    ///
128    /// [The location of the default store] is platform specific.
129    /// This returns an error on unsupported platforms.
130    ///
131    /// [The location of the default store]: https://www.ietf.org/archive/id/draft-nwjw-openpgp-cert-d-00.html#platform-specifics
132    pub fn default_store_path() -> Result<PathBuf> {
133        Ok(dirs::data_dir()
134           .ok_or(Error::UnsupportedPlatform(
135               "Default store's path".into()))?
136           .join("pgp.cert.d"))
137    }
138
139    /// Opens a store with an explicit location.
140    ///
141    /// Note: Most applications should use the [default store], which
142    /// is shared.  The default store can be opened using
143    /// [`CertD::new`].
144    ///
145    ///   [default store]: https://www.ietf.org/archive/id/draft-nwjw-openpgp-cert-d-00.html#name-default-stores-location
146    pub fn with_base_dir<P: AsRef<Path>>(base: P) -> Result<CertD> {
147        Ok(CertD {
148            base: base.as_ref().into(),
149        })
150    }
151
152    /// Get the this Certd's base path.
153    pub fn base_dir(&self) -> &Path {
154        &self.base
155    }
156
157    /// Computes the certificate directory's tag.
158    ///
159    /// Modulo collisions in the hashing algorithm, this tag will
160    /// change whenever a certificate indexed by its fingerprint is
161    /// added, updated, or removed.  The tag is designed to not change
162    /// when a certificate indexed by a special name, or some other
163    /// unrelated data in the certd directory is added, updated, or
164    /// removed.
165    ///
166    /// The tag should be stable in the sense that you can serialize
167    /// it to disk, read it in again later, and compare it to the
168    /// current value.  However, how the tag is computed may change.
169    /// In this case, you may observe a spurious update.
170    pub fn tag(&self) -> Tag {
171        let revert_to_readdir = Some(4);
172
173        platform! {
174            unix => self.tag_probe_unix(revert_to_readdir),
175            windows => self.tag_probe_std(revert_to_readdir),
176        }
177    }
178
179    /// Never call this function directly!
180    ///
181    /// This function is not part of the semver contract; It is only
182    /// exported to facilitate testing and benchmarking.
183    ///
184    /// This implements a variant of [`CertD::tag`], which uses
185    /// `readdir`, and is implemented using functionality from Rust's
186    /// standard library.
187    #[doc(hidden)]
188    pub fn tag_readdir_std(&self) -> Tag {
189        tracer!(TRACE, "CertD::tag_readdir_std");
190
191        let mut composite = CertDTag::null();
192
193        let dir = std::fs::read_dir(&self.base);
194        if let Ok(dir) = dir {
195            'entry: for e in dir {
196                let e = if let Ok(e) = e {
197                    e
198                } else {
199                    continue;
200                };
201
202                // Calling file_type is normally free.
203                //
204                //   https://doc.rust-lang.org/std/fs/struct.DirEntry.html#method.file_type
205                //
206                // As we are only interested in directories, filter out
207                // anything as early as possible.
208                if let Ok(file_type) = e.file_type() {
209                    if file_type.is_dir() {
210                        // Check!
211                    } else {
212                        continue;
213                    }
214                } else {
215                    continue;
216                }
217
218                let filename = e.file_name();
219                t!("Examining {:?}", filename);
220                let filename: &[u8] = platform! {
221                    unix => {
222                        use std::os::unix::ffi::OsStrExt;
223                        filename.as_bytes()
224                    },
225                    windows => {
226                        if let Some(filename) = filename.to_str() {
227                            filename.as_bytes()
228                        } else {
229                            t!("Can't convert to a str.");
230                            continue;
231                        }
232                    }
233                };
234
235                if filename.len() != 2 {
236                    t!("Wrong length.");
237                    continue;
238                }
239
240                let mut nibbles: [u8; 2] = [0; 2];
241
242                for i in 0..2usize {
243                    let v = filename[i];
244                    nibbles[i] = match v {
245                        b'0'..=b'9' => v - b'0',
246                        b'a'..=b'f' => 10 + v - b'a',
247                        _ => {
248                            t!("{}: contains non-lower-hex characters.",
249                               String::from_utf8_lossy(filename));
250                            continue 'entry;
251                        }
252                    };
253                }
254                let i = ((nibbles[0] << 4) + nibbles[1]) as usize;
255
256                // On Windows this is free.  On Unix this requires a
257                // system call.
258                let metadata = if let Ok(metadata) = e.metadata() {
259                    metadata
260                } else {
261                    t!("{:02x}: Can't read meta-data.", i);
262                    continue;
263                };
264
265                let tag = if let Ok(tag) = Tag::try_from(metadata) {
266                    tag
267                } else {
268                    t!("Can't compute tag.");
269                    continue;
270                };
271
272                t!("{:02x} => Tag({:x})", i, tag.0);
273
274                composite.0[i] = tag;
275            }
276        }
277
278        composite.compress()
279    }
280
281    /// Never call this function directly!
282    ///
283    /// This function is not part of the semver contract; It is only
284    /// exported to facilitate testing and benchmarking.
285    ///
286    /// This implements a variant of [`CertD::tag`], which uses
287    /// `readdir`, and is specialized for Unix platforms.
288    #[cfg(unix)]
289    #[doc(hidden)]
290    pub fn tag_readdir_unix(&self) -> Tag {
291        use crate::unixdir::Dir;
292
293        tracer!(TRACE, "CertD::tag_readdir_unix");
294
295        let mut composite = CertDTag::null();
296
297        let dir = Dir::open(&self.base);
298
299        if let Ok(mut dir) = dir {
300            'entry: while let Some(e) = dir.readdir() {
301                let file_type = e.file_type();
302                if file_type.is_dir() || file_type.is_unknown() {
303                    // Plausible.
304                } else {
305                    continue;
306                }
307
308                let filename = e.file_name();
309                t!("Examining {}", String::from_utf8_lossy(filename));
310                if filename.len() != 2 {
311                    t!("Wrong length.");
312                    continue;
313                }
314
315                let mut nibbles: [u8; 2] = [0; 2];
316                for i in 0..2usize {
317                    let v = filename[i];
318                    nibbles[i] = match v {
319                        b'0'..=b'9' => v - b'0',
320                        b'a'..=b'f' => 10 + v - b'a',
321                        _ => {
322                            t!("{}: contains non-lower-hex characters.",
323                               String::from_utf8_lossy(filename));
324                            continue 'entry;
325                        }
326                    };
327                }
328                let i = ((nibbles[0] << 4) + nibbles[1]) as usize;
329
330                let metadata = if let Ok(metadata) = e.metadata() {
331                    metadata
332                } else {
333                    t!("{:02x}: Can't read meta-data.", i);
334                    continue;
335                };
336
337                // Double check as the type in the directory entry is
338                // not definitive (it could be unknown).
339                if ! metadata.is_dir() {
340                    t!("{:02x}: Not a directory.");
341                    continue;
342                }
343
344                let tag = Tag::from(metadata);
345                t!("{:02x} => Tag({:x})", i, tag.0);
346
347                composite.0[i] = tag;
348            }
349        }
350
351        composite.compress()
352    }
353
354    /// Never call this function directly!
355    ///
356    /// This function is not part of the semver contract; It is only
357    /// exported to facilitate testing and benchmarking.
358    ///
359    /// This implements a variant of [`CertD::tag`], which `stats` all
360    /// of the expected subdirectories using the Rust standard
361    /// library.
362    #[doc(hidden)]
363    pub fn tag_probe_std(&self, revert_to_readir: Option<usize>) -> Tag {
364        tracer!(TRACE, "CertD::tag_probe_std");
365
366        const FILENAMES: [&str; 256] = [
367            "00", "01", "02", "03", "04", "05", "06", "07",
368            "08", "09", "0a", "0b", "0c", "0d", "0e", "0f",
369            "10", "11", "12", "13", "14", "15", "16", "17",
370            "18", "19", "1a", "1b", "1c", "1d", "1e", "1f",
371            "20", "21", "22", "23", "24", "25", "26", "27",
372            "28", "29", "2a", "2b", "2c", "2d", "2e", "2f",
373            "30", "31", "32", "33", "34", "35", "36", "37",
374            "38", "39", "3a", "3b", "3c", "3d", "3e", "3f",
375            "40", "41", "42", "43", "44", "45", "46", "47",
376            "48", "49", "4a", "4b", "4c", "4d", "4e", "4f",
377            "50", "51", "52", "53", "54", "55", "56", "57",
378            "58", "59", "5a", "5b", "5c", "5d", "5e", "5f",
379            "60", "61", "62", "63", "64", "65", "66", "67",
380            "68", "69", "6a", "6b", "6c", "6d", "6e", "6f",
381            "70", "71", "72", "73", "74", "75", "76", "77",
382            "78", "79", "7a", "7b", "7c", "7d", "7e", "7f",
383            "80", "81", "82", "83", "84", "85", "86", "87",
384            "88", "89", "8a", "8b", "8c", "8d", "8e", "8f",
385            "90", "91", "92", "93", "94", "95", "96", "97",
386            "98", "99", "9a", "9b", "9c", "9d", "9e", "9f",
387            "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7",
388            "a8", "a9", "aa", "ab", "ac", "ad", "ae", "af",
389            "b0", "b1", "b2", "b3", "b4", "b5", "b6", "b7",
390            "b8", "b9", "ba", "bb", "bc", "bd", "be", "bf",
391            "c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7",
392            "c8", "c9", "ca", "cb", "cc", "cd", "ce", "cf",
393            "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7",
394            "d8", "d9", "da", "db", "dc", "dd", "de", "df",
395            "e0", "e1", "e2", "e3", "e4", "e5", "e6", "e7",
396            "e8", "e9", "ea", "eb", "ec", "ed", "ee", "ef",
397            "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7",
398            "f8", "f9", "fa", "fb", "fc", "fd", "fe", "ff",
399        ];
400
401        let mut misses = 0;
402        let mut hits = 0;
403
404        let mut composite = CertDTag::null();
405
406        let base = self.base_dir();
407
408        for (i, filename) in FILENAMES.iter().enumerate() {
409            let mut path = base.to_path_buf();
410            path.push(filename);
411
412            let metadata = if let Ok(metadata) = std::fs::metadata(path) {
413                hits += 1;
414                metadata
415            } else {
416                t!("{:02x}: No such file or directory.", i);
417
418                misses += 1;
419
420                if let Some(revert_to_readir) = revert_to_readir {
421                    if revert_to_readir == misses && hits <= 1 {
422                        t!("Too many misses; switching to readir implementation.");
423                        return self.tag_readdir_std();
424                    }
425                }
426
427                continue;
428            };
429
430            if ! metadata.file_type().is_dir() {
431                t!("{:02x}: Not a directory.");
432                continue;
433            }
434
435            let tag = match Tag::try_from(&metadata) {
436                Ok(tag) => tag,
437                Err(err) => {
438                    t!("{:02x}: Can't compute tag: {}.", i, err);
439                    continue;
440                }
441            };
442
443            t!("{:02x} => Tag({:x})", i, tag.0);
444
445            composite.0[i] = tag;
446        }
447
448        composite.compress()
449    }
450
451    /// Never call this function directly!
452    ///
453    /// This function is not part of the semver contract; It is only
454    /// exported to facilitate testing and benchmarking.
455    ///
456    /// This implements a variant of [`CertD::tag`], which `stats` all
457    /// of the expected subdirectories, and is specialized for Unix
458    /// platforms.
459    #[doc(hidden)]
460    #[cfg(unix)]
461    pub fn tag_probe_unix(&self, revert_to_readir: Option<usize>) -> Tag {
462        use crate::unixdir::Dir;
463
464        tracer!(TRACE, "CertD::tag_probe_unix");
465
466        const FILENAMES: [&str; 256] = [
467            "00\0", "01\0", "02\0", "03\0", "04\0", "05\0", "06\0", "07\0",
468            "08\0", "09\0", "0a\0", "0b\0", "0c\0", "0d\0", "0e\0", "0f\0",
469            "10\0", "11\0", "12\0", "13\0", "14\0", "15\0", "16\0", "17\0",
470            "18\0", "19\0", "1a\0", "1b\0", "1c\0", "1d\0", "1e\0", "1f\0",
471            "20\0", "21\0", "22\0", "23\0", "24\0", "25\0", "26\0", "27\0",
472            "28\0", "29\0", "2a\0", "2b\0", "2c\0", "2d\0", "2e\0", "2f\0",
473            "30\0", "31\0", "32\0", "33\0", "34\0", "35\0", "36\0", "37\0",
474            "38\0", "39\0", "3a\0", "3b\0", "3c\0", "3d\0", "3e\0", "3f\0",
475            "40\0", "41\0", "42\0", "43\0", "44\0", "45\0", "46\0", "47\0",
476            "48\0", "49\0", "4a\0", "4b\0", "4c\0", "4d\0", "4e\0", "4f\0",
477            "50\0", "51\0", "52\0", "53\0", "54\0", "55\0", "56\0", "57\0",
478            "58\0", "59\0", "5a\0", "5b\0", "5c\0", "5d\0", "5e\0", "5f\0",
479            "60\0", "61\0", "62\0", "63\0", "64\0", "65\0", "66\0", "67\0",
480            "68\0", "69\0", "6a\0", "6b\0", "6c\0", "6d\0", "6e\0", "6f\0",
481            "70\0", "71\0", "72\0", "73\0", "74\0", "75\0", "76\0", "77\0",
482            "78\0", "79\0", "7a\0", "7b\0", "7c\0", "7d\0", "7e\0", "7f\0",
483            "80\0", "81\0", "82\0", "83\0", "84\0", "85\0", "86\0", "87\0",
484            "88\0", "89\0", "8a\0", "8b\0", "8c\0", "8d\0", "8e\0", "8f\0",
485            "90\0", "91\0", "92\0", "93\0", "94\0", "95\0", "96\0", "97\0",
486            "98\0", "99\0", "9a\0", "9b\0", "9c\0", "9d\0", "9e\0", "9f\0",
487            "a0\0", "a1\0", "a2\0", "a3\0", "a4\0", "a5\0", "a6\0", "a7\0",
488            "a8\0", "a9\0", "aa\0", "ab\0", "ac\0", "ad\0", "ae\0", "af\0",
489            "b0\0", "b1\0", "b2\0", "b3\0", "b4\0", "b5\0", "b6\0", "b7\0",
490            "b8\0", "b9\0", "ba\0", "bb\0", "bc\0", "bd\0", "be\0", "bf\0",
491            "c0\0", "c1\0", "c2\0", "c3\0", "c4\0", "c5\0", "c6\0", "c7\0",
492            "c8\0", "c9\0", "ca\0", "cb\0", "cc\0", "cd\0", "ce\0", "cf\0",
493            "d0\0", "d1\0", "d2\0", "d3\0", "d4\0", "d5\0", "d6\0", "d7\0",
494            "d8\0", "d9\0", "da\0", "db\0", "dc\0", "dd\0", "de\0", "df\0",
495            "e0\0", "e1\0", "e2\0", "e3\0", "e4\0", "e5\0", "e6\0", "e7\0",
496            "e8\0", "e9\0", "ea\0", "eb\0", "ec\0", "ed\0", "ee\0", "ef\0",
497            "f0\0", "f1\0", "f2\0", "f3\0", "f4\0", "f5\0", "f6\0", "f7\0",
498            "f8\0", "f9\0", "fa\0", "fb\0", "fc\0", "fd\0", "fe\0", "ff\0",
499        ];
500
501        let mut misses = 0;
502        let mut hits = 0;
503
504        let mut composite = CertDTag::null();
505
506        let base = self.base_dir();
507        let dir = Dir::open(base);
508
509        if let Ok(mut dir) = dir {
510            for (i, filename) in FILENAMES.iter().enumerate() {
511                let metadata = if let Ok(metadata) = dir.fstat(filename.as_bytes()) {
512                    hits += 1;
513                    metadata
514                } else {
515                    t!("{:02x}: No such file or directory.", i);
516
517                    misses += 1;
518
519                    if let Some(revert_to_readir) = revert_to_readir {
520                        if revert_to_readir == misses && hits <= 1 {
521                            t!("Too many misses; switching to readdir implementation.");
522                            return self.tag_readdir_unix();
523                        }
524                    }
525
526                    continue;
527                };
528
529                if ! metadata.is_dir() {
530                    t!("{:02x}: Not a directory.");
531                    continue;
532                }
533
534                let tag = Tag::from(&metadata);
535                t!("{:02x} => Tag({:x})", i, tag.0);
536
537                composite.0[i] = tag
538            }
539        }
540
541        composite.compress()
542    }
543
544    /// Turns a fingerprint into a path in the store.
545    ///
546    /// [The transformation from a fingerprint to a path] is defined
547    /// by the standard.
548    ///
549    ///   [The transformation from a fingerprint to a path]: https://www.ietf.org/archive/id/draft-nwjw-openpgp-cert-d-00.html#section-3.2.1
550    pub fn get_path_by_fingerprint(&self, fingerprint: &str) -> Result<PathBuf> {
551        if ! [pgp::FINGERPRINT_LEN_CHARS_V4,
552              pgp::FINGERPRINT_LEN_CHARS_V6].contains(&fingerprint.len()) {
553            return Err(Error::BadName);
554        }
555        if fingerprint.chars().any(|c| !c.is_ascii_hexdigit()) {
556            return Err(Error::BadName);
557        }
558        let fingerprint = fingerprint.to_ascii_lowercase();
559        Ok(self.base.join(&fingerprint[..2]).join(&fingerprint[2..]))
560    }
561
562    /// Turns a path in the store into a fingerprint, if it conforms to the cert-d
563    /// specification.
564    fn get_fingerprint_by_path(
565        &self,
566        path: &Path,
567    ) -> std::result::Result<String, InternalError> {
568        let path = if path.is_absolute() {
569            path.strip_prefix(&self.base)
570                .map_err(|_| InternalError::PathNotInStore)?
571        } else {
572            path
573        };
574        if !self.base.join(path).is_file() {
575            return Err(InternalError::BadFingerprintPath);
576        }
577        if path.components().count() != 2 {
578            return Err(InternalError::BadFingerprintPath);
579        }
580        let components =
581            path.components().map(|c| c.as_os_str()).collect::<Vec<_>>();
582        if components.iter().any(|c| !c.is_ascii()) {
583            return Err(InternalError::BadFingerprintPath);
584        }
585        let head = components[0].to_string_lossy();
586        if head.len() != PATH_PREFIX_LEN {
587            return Err(InternalError::BadFingerprintPath);
588        }
589        let tail = components[1].to_string_lossy();
590        if tail.len() != pgp::FINGERPRINT_LEN_CHARS_V4 - PATH_PREFIX_LEN
591            && tail.len() != pgp::FINGERPRINT_LEN_CHARS_V6 - PATH_PREFIX_LEN
592        {
593            return Err(InternalError::BadFingerprintPath);
594        }
595        Ok(head.to_string() + &tail)
596    }
597
598    /// Turns a special name into a path in the store.
599    ///
600    /// The specification currently defines one [special name].
601    /// Non-standard special names are allowed, but they must MUST
602    /// start with an underscore, which SHOULD be immediately followed
603    /// by the vendor's name, e.g., `_sequoia_some_special.pgp`.
604    /// Other names cause this function to return [`Error::BadName`].
605    ///
606    ///   [special name]: https://www.ietf.org/archive/id/draft-nwjw-openpgp-cert-d-00.html#name-special-names
607    pub fn get_path_by_special(&self, special: &str) -> Result<PathBuf> {
608        Self::get_relative_path_by_special(special).map(|special| {
609            self.base.join(special)
610        })
611    }
612
613    /// Returns whether the special name is valid.
614    ///
615    /// The specification currently defines one [special name].
616    /// Non-standard special names are allowed, but they must MUST
617    /// start with an underscore, which SHOULD be immediately followed
618    /// by the vendor's name, e.g., `_sequoia_some_special.pgp`.
619    /// Other names cause this function to return [`Error::BadName`].
620    ///
621    ///   [special name]: https://www.ietf.org/archive/id/draft-nwjw-openpgp-cert-d-00.html#name-special-names
622    pub fn is_special(special: &str) -> Result<()> {
623        Self::get_relative_path_by_special(special).map(|_| ())
624    }
625
626    // The meat behind `CertD::get_path_by_special` and
627    // `CertD::is_special`.  See them for documentation.
628    fn get_relative_path_by_special(special: &str) -> Result<PathBuf> {
629        if let Some('_') = special.chars().next() {
630            let special = PathBuf::from(special);
631            if special.components().count() != 1 {
632                Err(Error::BadName)
633            } else {
634                Ok(special)
635            }
636        } else if SPECIAL_NAMES.binary_search(&special).is_ok() {
637            Ok(PathBuf::from(special))
638        } else {
639            Err(Error::BadName)
640        }
641    }
642
643    /// Looks up a certificate in the store by name, i.e., by
644    /// fingerprint or by special name.
645    ///
646    /// The specification currently defines one [special name].
647    /// Non-standard special names are allowed, but they must MUST
648    /// start with an underscore, which SHOULD be immediately followed
649    /// by the vendor's name, e.g., `_sequoia_some_special.pgp`.
650    /// Other names cause this function to return [`Error::BadName`].
651    ///
652    ///   [special name]: https://www.ietf.org/archive/id/draft-nwjw-openpgp-cert-d-00.html#name-special-names
653    ///
654    /// If the certificate exists, this function computes the tag,
655    /// reads in the certificate data, and returns `Ok(Some((tag,
656    /// cert_data)))`.  See [`Tag`] for how the tag can be used to
657    /// cache lookups.
658    ///
659    /// If the certificate does not exist, this function returns `Ok(None)`.
660    ///
661    /// If an I/O error occurs, or the name was invalid, this function returns
662    /// an [`Error`].
663    ///
664    /// [`CertD::get_file`] is often more efficient if you don't
665    /// necessarily need the tag or the file's contents.
666    pub fn get(&self, name: &str) -> Result<Option<(Tag, Vec<u8>)>> {
667        if let Some(mut fp) = self.get_file(name)? {
668            let tag = Tag::try_from(&fp)?;
669            let mut buf = Vec::new();
670            fp.read_to_end(&mut buf)?;
671            Ok(Some((tag, buf)))
672        } else {
673            Ok(None)
674        }
675    }
676
677    /// Looks up a certificate in the store by name, i.e., by
678    /// fingerprint or by special name.
679    ///
680    /// The specification currently defines one [special name].
681    /// Non-standard special names are allowed, but they must MUST
682    /// start with an underscore, which SHOULD be immediately followed
683    /// by the vendor's name, e.g., `_sequoia_some_special.pgp`.
684    /// Other names cause this function to return [`Error::BadName`].
685    ///
686    ///   [special name]: https://www.ietf.org/archive/id/draft-nwjw-openpgp-cert-d-00.html#name-special-names
687    ///
688    /// If the certificate exists, this function returns
689    /// `Ok(Some([std::fs::File]))`.  If the certificate does not
690    /// exist, it returns `Ok(None)`.  If an I/O error occurs, or the
691    /// name was invalid, this function returns an [`Error`].
692    ///
693    /// You can get the file's [`Tag`] by doing:
694    /// `Tag::try_from(&file)`.  See [`Tag`] for how the tag can be
695    /// used to cache lookups.
696    pub fn get_file(&self, name: &str) -> Result<Option<File>> {
697        let path = self.get_path(name)?;
698        match fs::File::open(path) {
699            Ok(f) => Ok(Some(f)),
700            Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(None),
701            Err(e) => Err(e.into()),
702        }
703    }
704
705    /// Conditionally looks up a certificate in the store by an name,
706    /// i.e. a fingerprint or a special name.
707    ///
708    /// The specification currently defines one [special name].
709    /// Non-standard special names are allowed, but they must MUST
710    /// start with an underscore, which SHOULD be immediately followed
711    /// by the vendor's name, e.g., `_sequoia_some_special.pgp`.
712    /// Other names cause this function to return [`Error::BadName`].
713    ///
714    ///   [special name]: https://www.ietf.org/archive/id/draft-nwjw-openpgp-cert-d-00.html#name-special-names
715    ///
716    /// If the certificate has changed, i.e., the provided tag does
717    /// not match the current tag, this function returns
718    /// `Ok(Some((cert, tag)))`.  The tag can be used in subsequent
719    /// calls to this function.
720    ///
721    /// If the certificate has not changed, i.e., the provided tag
722    /// matches the current tag, or the certificate does not exist,
723    /// this function returns `Ok(None)`.
724    ///
725    /// If an I/O error occurs, or the name was invalid, this function returns
726    /// an [`Error`].
727    pub fn get_if_changed(
728        &self,
729        since: Tag,
730        name: &str,
731    ) -> Result<Option<(Tag, Vec<u8>)>> {
732        let path = self.get_path(name)?;
733        match fs::File::open(path) {
734            Ok(mut f) => {
735                let tag = f.metadata()?.try_into()?;
736                if since == tag {
737                    Ok(None) // Not modified.
738                } else {
739                    let mut buf = Vec::new();
740                    f.read_to_end(&mut buf)?;
741                    Ok(Some((tag, buf)))
742                }
743            }
744            Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(None),
745            Err(e) => Err(e.into()),
746        }
747    }
748
749    /// Get the path to a certificate by fingerprint or special name.
750    ///
751    /// The specification currently defines one [special name].
752    /// Non-standard special names are allowed, but they must MUST
753    /// start with an underscore, which SHOULD be immediately followed
754    /// by the vendor's name, e.g., `_sequoia_some_special.pgp`.
755    /// Other names cause this function to return [`Error::BadName`].
756    ///
757    ///   [special name]: https://www.ietf.org/archive/id/draft-nwjw-openpgp-cert-d-00.html#name-special-names
758    pub fn get_path(&self, name: &str) -> Result<PathBuf> {
759        // Try to convert the name to a path, first as a fingerprint and
760        // if that fails as a special name.
761        // If the errors get more insightful than just Error::BadName, prefer
762        // returning the one from fingerprint_to_path.
763        self.get_path_by_fingerprint(name)
764            .or_else(|_| self.get_path_by_special(name))
765    }
766
767    /// Inserts or updates a cert.
768    ///
769    /// Requires the fingerprint and a callback function.  The
770    /// callback is passed `data`, and an `Option<Vec<u8>>`, which
771    /// contains the existing cert data, if any.  The callback is
772    /// expected to merge the two copies of the certificate together.
773    /// The returned data is written to the store.  Note: The callback
774    /// may decide to omit (parts of) the existing data, but this
775    /// should be done with great care as not to lose any vital
776    /// information.
777    ///
778    /// The new [`Tag`] is returned, and if `return_inserted` is true,
779    /// the data written to the store is also returned.
780    ///
781    /// This function locks store, which may block the current thread.
782    /// Use [`CertD::try_insert`] to avoid blocking.
783    pub fn insert<'a, D, M>(&self, fingerprint: &str, data: D,
784                            return_inserted: bool, merge: M)
785        -> Result<(Tag, Option<Vec<u8>>)>
786    where
787        M: FnOnce(D, Option<&[u8]>) -> Result<MergeResult<'a>>,
788    {
789        self.insert_extended(
790            fingerprint, data, return_inserted,
791            |d| Ok(d),
792            merge,
793            |r| Ok(r))
794    }
795
796    /// Inserts or updates a cert, extended version.
797    ///
798    /// This function is like [`CertD::insert`], but after obtaining
799    /// the lock on the cert-d, and before doing anything else, it
800    /// calls `pre`.  Similarly, just before dropping the lock, it
801    /// calls `post`.
802    ///
803    /// If the `pre` callback returns an error, the operation is
804    /// aborted, and the error is propagated to the caller.  The
805    /// `post` callback can also transform the result.
806    ///
807    /// The caller can use this functionality to get the cert-d's tag
808    /// prior to and after an insert operation, for instance.
809    pub fn insert_extended<'a, D, PRE, M, POST>(
810        &self, fingerprint: &str, data: D,
811        return_inserted: bool, pre: PRE, merge: M, post: POST)
812        -> Result<(Tag, Option<Vec<u8>>)>
813    where
814        PRE: FnOnce(D) -> Result<D>,
815        M: FnOnce(D, Option<&[u8]>) -> Result<MergeResult<'a>>,
816        POST: FnOnce((Tag, Option<Vec<u8>>)) -> Result<(Tag, Option<Vec<u8>>)>,
817    {
818        let blocking = true;
819        self.insert_impl(
820            fingerprint, true, data, return_inserted,
821            pre, merge, post,
822            blocking)
823    }
824
825    /// Inserts or updates a cert, non-blocking variant.
826    ///
827    /// Requires the fingerprint and a callback function.  The
828    /// callback is passed `data`, and an `Option<Vec<u8>>`, which
829    /// contains the existing cert data, if any.  The callback is
830    /// expected to merge the two copies of the certificate together.
831    /// The returned data is written to the store.  Note: The callback
832    /// may decide to omit (parts of) the existing data, but this
833    /// should be done with great care as not to lose any vital
834    /// information.
835    ///
836    /// The new [`Tag`] is returned, and if `return_inserted` is true,
837    /// the data written to the store is also returned.
838    ///
839    /// This function attempts to lock the store.  If the store is
840    /// already locked, then it instead returns [`Error::IoError`]
841    /// with an [`std::io::ErrorKind::WouldBlock`].
842    pub fn try_insert<'a, D, M>(&self, fingerprint: &str, data: D,
843                                return_inserted: bool, merge: M)
844        -> Result<(Tag, Option<Vec<u8>>)>
845    where
846        M: FnOnce(D, Option<&[u8]>) -> Result<MergeResult<'a>>,
847    {
848        self.try_insert_extended(
849            fingerprint, data, return_inserted,
850            |d| Ok(d),
851            merge,
852            |r| Ok(r))
853    }
854
855    /// Inserts or updates a cert, extended version.
856    ///
857    /// This function is like [`CertD::try_insert`], but after
858    /// obtaining the lock on the cert-d, and before doing anything
859    /// else, it calls `pre`.  Similarly, just before dropping the
860    /// lock, it calls `post`.
861    ///
862    /// If the `pre` callback returns an error, the operation is
863    /// aborted, and the error is propagated to the caller.  The
864    /// `post` callback can also transform the result.
865    ///
866    /// The caller can use this functionality to get the cert-d's tag
867    /// prior to and after an insert operation, for instance.
868    pub fn try_insert_extended<'a, D, PRE, M, POST>(
869        &self, fingerprint: &str, data: D,
870        return_inserted: bool, pre: PRE, merge: M, post: POST)
871        -> Result<(Tag, Option<Vec<u8>>)>
872    where
873        PRE: FnOnce(D) -> Result<D>,
874        M: FnOnce(D, Option<&[u8]>) -> Result<MergeResult<'a>>,
875        POST: FnOnce((Tag, Option<Vec<u8>>)) -> Result<(Tag, Option<Vec<u8>>)>,
876    {
877        let blocking = false;
878        self.insert_impl(
879            fingerprint, true, data, return_inserted,
880            pre, merge, post, blocking)
881    }
882
883    /// Inserts or updates a cert.
884    ///
885    /// Requires the new certificate data, and a callback function.
886    /// The fingerprint is extracted from `data`.  The callback is
887    /// passed `data`, and an `Option<Vec<u8>>`, which contains the
888    /// existing cert data, if any.  The callback is expected to merge
889    /// the two copies of the certificate together.  The returned data
890    /// is written to the store.  Note: The callback may decide to
891    /// omit (parts of) the existing data, but this should be done
892    /// with great care as not to lose any vital information.
893    ///
894    /// The new [`Tag`] is returned, and if `return_inserted` is true,
895    /// the data written to the store is also returned.
896    ///
897    /// This function locks store, which may block the current thread.
898    /// Use [`CertD::try_insert_data`] to avoid blocking.
899    pub fn insert_data<'a, M>(&self, data: &'a [u8],
900                              return_inserted: bool, merge: M)
901        -> Result<(Tag, Option<Vec<u8>>)>
902    where
903        M: FnOnce(&'a [u8], Option<&[u8]>) -> Result<MergeResult<'a>>,
904    {
905        self.insert_data_extended(
906            data, return_inserted,
907            |d| Ok(d),
908            merge,
909            |r| Ok(r))
910    }
911
912    /// Inserts or updates a cert, extended version.
913    ///
914    /// This function is like [`CertD::insert_data`], but after
915    /// obtaining the lock on the cert-d, and before doing anything
916    /// else, it calls `pre`.  Similarly, just before dropping the
917    /// lock, it calls `post`.
918    ///
919    /// If the `pre` callback returns an error, the operation is
920    /// aborted, and the error is propagated to the caller.  The
921    /// `post` callback can also transform the result.
922    pub fn insert_data_extended<'a, PRE, M, POST>(
923        &self, data: &'a [u8],
924        return_inserted: bool,
925        pre: PRE, merge: M, post: POST)
926        -> Result<(Tag, Option<Vec<u8>>)>
927    where
928        PRE: FnOnce(&'a [u8]) -> Result<&'a [u8]>,
929        M: FnOnce(&'a [u8], Option<&[u8]>) -> Result<MergeResult<'a>>,
930        POST: FnOnce((Tag, Option<Vec<u8>>)) -> Result<(Tag, Option<Vec<u8>>)>,
931    {
932        let blocking = true;
933        let fingerprint = pgp::fingerprint(data)?;
934        self.insert_impl(
935            &fingerprint, true, data, return_inserted,
936            pre, merge, post,
937            blocking)
938    }
939
940    /// Inserts or updates a cert, non-blocking variant.
941    ///
942    /// Requires the new certificate data, and a callback function.
943    /// The fingerprint is extracted from `data`.  The callback is
944    /// passed `data`, and an `Option<Vec<u8>>`, which contains the
945    /// existing cert data, if any.  The callback is expected to merge
946    /// the two copies of the certificate together.  The returned data
947    /// is written to the store.  Note: The callback may decide to
948    /// omit (parts of) the existing data, but this should be done
949    /// with great care as not to lose any vital information.
950    ///
951    /// The new [`Tag`] is returned, and if `return_inserted` is true,
952    /// the data written to the store is also returned.
953    ///
954    /// This function attempts to lock the store.  If the store is
955    /// already locked, then it instead returns [`Error::IoError`]
956    /// with an [`std::io::ErrorKind::WouldBlock`].
957    pub fn try_insert_data<'a, M>(&self, data: &'a [u8],
958                                  return_inserted: bool, merge: M)
959        -> Result<(Tag, Option<Vec<u8>>)>
960    where
961        M: FnOnce(&'a [u8], Option<&[u8]>) -> Result<MergeResult<'a>>,
962    {
963        self.try_insert_data_extended(
964            data, return_inserted,
965            |d| Ok(d),
966            merge,
967            |r| Ok(r))
968    }
969
970    /// Inserts or updates a cert, non-blocking variant, extended
971    /// version.
972    ///
973    /// This function is like [`CertD::try_insert_data`], but after
974    /// obtaining the lock on the cert-d, and before doing anything
975    /// else, it calls `pre`.  Similarly, just before dropping the
976    /// lock, it calls `post`.
977    ///
978    /// If the `pre` callback returns an error, the operation is
979    /// aborted, and the error is propagated to the caller.  The
980    /// `post` callback can also transform the result.
981    pub fn try_insert_data_extended<'a, PRE, M, POST>(
982        &self, data: &'a [u8],
983        return_inserted: bool,
984        pre: PRE, merge: M, post: POST)
985        -> Result<(Tag, Option<Vec<u8>>)>
986    where
987        PRE: FnOnce(&'a [u8]) -> Result<&'a [u8]>,
988        M: FnOnce(&'a [u8], Option<&[u8]>) -> Result<MergeResult<'a>>,
989        POST: FnOnce((Tag, Option<Vec<u8>>)) -> Result<(Tag, Option<Vec<u8>>)>,
990    {
991        let blocking = false;
992        pgp::plausible_tsk_or_tpk(data)?;
993        let fingerprint = pgp::fingerprint(data)?;
994        self.insert_impl(
995            &fingerprint, true, data, return_inserted,
996            pre, merge, post,
997            blocking)
998    }
999
1000    /// Inserts or updates the cert or key stored under a special name.
1001    ///
1002    /// Requires the special name, the cert or key in binary format
1003    /// and a callback function.  The callback is invoked with an
1004    /// `Option<Vec<u8>>` of the existing data (if any), and is
1005    /// expected to merge the two copies together.  The returned
1006    /// `Vec<u8>` is written to the store under the special name.
1007    /// (Note: The function may decide to omit (parts of) the existing
1008    /// data, but this should be done with great care as not to lose
1009    /// any vital information.)  The new [`Tag`] is returned, and if
1010    /// `return_inserted` is true, the data written to the store is
1011    /// also returned.  Otherwise, `None` is returned.
1012    ///
1013    /// This function locks store, which may block the current thread.
1014    /// Use [`CertD::try_insert_special`] to avoid blocking.
1015    ///
1016    /// The specification currently defines one [special name].
1017    /// Non-standard special names are allowed, but they must MUST
1018    /// start with an underscore, which SHOULD be immediately followed
1019    /// by the vendor's name, e.g., `_sequoia_some_special.pgp`.
1020    /// Other names cause this function to return [`Error::BadName`].
1021    ///
1022    ///   [special name]: https://www.ietf.org/archive/id/draft-nwjw-openpgp-cert-d-00.html#name-special-names
1023    pub fn insert_special<'a, D, M>(
1024        &self,
1025        special_name: &str,
1026        data: D,
1027        return_inserted: bool,
1028        merge: M,
1029    ) -> Result<(Tag, Option<Vec<u8>>)>
1030    where
1031        M: FnOnce(D, Option<&[u8]>) -> Result<MergeResult<'a>>,
1032    {
1033        self.insert_special_extended(
1034            special_name, data, return_inserted,
1035            |d| Ok(d),
1036            merge,
1037            |r| Ok(r))
1038    }
1039
1040    /// Inserts or updates the cert or key stored under a special
1041    /// name, extended version.
1042    ///
1043    /// This function is like [`CertD::insert_special`], but after
1044    /// obtaining the lock on the cert-d, and before doing anything
1045    /// else, it calls `pre`.  Similarly, just before dropping the
1046    /// lock, it calls `post`.
1047    ///
1048    /// If the `pre` callback returns an error, the operation is
1049    /// aborted, and the error is propagated to the caller.  The
1050    /// `post` callback can also transform the result.
1051    ///
1052    /// The caller can use this functionality to get the cert-d's tag
1053    /// prior to and after an insert operation, for instance.
1054    pub fn insert_special_extended<'a, D, PRE, M, POST>(
1055        &self,
1056        special_name: &str,
1057        data: D,
1058        return_inserted: bool,
1059        pre: PRE, merge: M, post: POST
1060    ) -> Result<(Tag, Option<Vec<u8>>)>
1061    where
1062        PRE: FnOnce(D) -> Result<D>,
1063        M: FnOnce(D, Option<&[u8]>) -> Result<MergeResult<'a>>,
1064        POST: FnOnce((Tag, Option<Vec<u8>>)) -> Result<(Tag, Option<Vec<u8>>)>,
1065    {
1066        let blocking = true;
1067        self.insert_impl(
1068            special_name, false, data, return_inserted,
1069            pre,
1070            merge,
1071            post,
1072            blocking)
1073    }
1074
1075    /// Inserts or updates the cert or key stored under a special
1076    /// name, non-blocking variant.
1077    ///
1078    /// Requires the special name, the cert or key in binary format
1079    /// and a callback function.  The callback is invoked with an
1080    /// `Option<Vec<u8>>` of the existing data (if any), and is
1081    /// expected to merge the two copies together.  The returned
1082    /// `Vec<u8>` is written to the store under the special name.
1083    /// (Note: The function may decide to omit (parts of) the existing
1084    /// data, but this should be done with great care as not to lose
1085    /// any vital information.)  The new [`Tag`] is returned, and if
1086    /// `return_inserted` is true, the data written to the store is
1087    /// also returned.  Otherwise, `None` is returned.
1088    ///
1089    /// This function attempts to lock the store.  If the store is
1090    /// already locked, then it instead returns [`Error::IoError`]
1091    /// with an [`std::io::ErrorKind::WouldBlock`].
1092    ///
1093    /// The specification currently defines one [special name].
1094    /// Non-standard special names are allowed, but they must MUST
1095    /// start with an underscore, which SHOULD be immediately followed
1096    /// by the vendor's name, e.g., `_sequoia_some_special.pgp`.
1097    /// Other names cause this function to return [`Error::BadName`].
1098    ///
1099    ///   [special name]: https://www.ietf.org/archive/id/draft-nwjw-openpgp-cert-d-00.html#name-special-names
1100    pub fn try_insert_special<'a, D, M>(
1101        &self,
1102        special_name: &str,
1103        data: D,
1104        return_inserted: bool,
1105        merge: M,
1106    ) -> Result<(Tag, Option<Vec<u8>>)>
1107    where
1108        M: FnOnce(D, Option<&[u8]>) -> Result<MergeResult<'a>>,
1109    {
1110        self.try_insert_special_extended(
1111            special_name, data, return_inserted,
1112            |d| Ok(d),
1113            merge,
1114            |r| Ok(r))
1115    }
1116
1117    /// Inserts or updates the cert or key stored under a special
1118    /// name, non-blocking variant, extended version.
1119    ///
1120    /// This function is like [`CertD::try_insert_special`], but after
1121    /// obtaining the lock on the cert-d, and before doing anything
1122    /// else, it calls `pre`.  Similarly, just before dropping the
1123    /// lock, it calls `post`.
1124    ///
1125    /// If the `pre` callback returns an error, the operation is
1126    /// aborted, and the error is propagated to the caller.  The
1127    /// `post` callback can also transform the result.
1128    ///
1129    /// The caller can use this functionality to get the cert-d's tag
1130    /// prior to and after an insert operation, for instance.
1131    pub fn try_insert_special_extended<'a, D, PRE, M, POST>(
1132        &self,
1133        special_name: &str,
1134        data: D,
1135        return_inserted: bool,
1136        pre: PRE, merge: M, post: POST
1137    ) -> Result<(Tag, Option<Vec<u8>>)>
1138    where
1139        PRE: FnOnce(D) -> Result<D>,
1140        M: FnOnce(D, Option<&[u8]>) -> Result<MergeResult<'a>>,
1141        POST: FnOnce((Tag, Option<Vec<u8>>)) -> Result<(Tag, Option<Vec<u8>>)>,
1142    {
1143        let blocking = false;
1144        self.insert_impl(
1145            special_name, false, data, return_inserted,
1146            pre,
1147            merge,
1148            post,
1149            blocking)
1150    }
1151
1152    fn insert_impl<'a, D, PRE, M, POST>(
1153        &self,
1154        name: &str, name_is_fingerprint: bool,
1155        data: D,
1156        return_inserted: bool,
1157        pre: PRE,
1158        merge: M,
1159        post: POST,
1160        blocking: bool,
1161    ) -> Result<(Tag, Option<Vec<u8>>)>
1162    where
1163        PRE: FnOnce(D) -> Result<D>,
1164        M: FnOnce(D, Option<&[u8]>) -> Result<MergeResult<'a>>,
1165        POST: FnOnce((Tag, Option<Vec<u8>>)) -> Result<(Tag, Option<Vec<u8>>)>,
1166    {
1167        let name = if name_is_fingerprint {
1168            pgp::canonicalize_fingerprint(name)?
1169        } else {
1170            Cow::Borrowed(name)
1171        };
1172
1173        let target_path = self.get_path(&name)?;
1174
1175        let mut lf = RwLock::new(self.idempotent_create_lockfile()?);
1176        // Lock exclusively
1177        let lock = if blocking {
1178            lf.write()?
1179        } else {
1180            lf.try_write()?
1181        };
1182
1183        // Let the caller examine the cert-d with the lock.
1184        let data = pre(data)?;
1185
1186        // Make sure the directory exists.
1187        fs::create_dir_all(target_path.parent().expect("at least one leg"))?;
1188
1189        let old_cert = self.get(&name)?.map(|(_, cert)| cert);
1190        let old_cert = old_cert.as_deref();
1191        let merge_result = merge(data, old_cert)?;
1192        let new_cert = match merge_result {
1193            MergeResult::Keep => old_cert.unwrap_or(&[]),
1194            MergeResult::DataRef(data) => data,
1195            MergeResult::Data(ref data) => data,
1196        };
1197
1198        pgp::plausible_tsk_or_tpk(new_cert)?;
1199
1200        if name_is_fingerprint {
1201            let fingerprint = pgp::fingerprint(new_cert)?;
1202            if fingerprint != name {
1203                return Err(Error::BadData(pgp::Error::WrongCertificate(
1204                    name.to_string(), fingerprint.to_string())));
1205            }
1206        }
1207
1208        if let MergeResult::Keep = merge_result {
1209            // There's nothing to do.
1210        } else {
1211            let mut tmp = NamedTempFile::new_in(&self.base)?;
1212            tmp.write_all(new_cert.as_ref())?;
1213            tmp.persist(&target_path).map_err(|e| e.error)?;
1214        }
1215
1216        let tag = fs::File::open(&target_path)?.metadata()?.try_into()?;
1217
1218        let cert = if return_inserted {
1219            Some(new_cert.to_vec())
1220        } else {
1221            None
1222        };
1223
1224        // Let the caller examine the result with the cert-d lock.
1225        let (tag, cert) = post((tag, cert))?;
1226
1227        drop(lock);
1228
1229        Ok((tag, cert))
1230    }
1231
1232    /// Iterates over the certs in the store returning their fingerprints.
1233    ///
1234    /// Note: this only considers certificates that are stored under
1235    /// their fingerprint; it does not include certificates stored
1236    /// under a special name.
1237    pub fn fingerprints(&self) -> impl Iterator<Item = Result<String>> + '_ {
1238        WalkDir::new(&self.base)
1239            // take only subdirs of depth 2
1240            .max_depth(2)
1241            .min_depth(2)
1242            .into_iter()
1243            // Convert the paths to fingerprints. The store is a shared
1244            // directory, writable by anyone, so there may be files that don't
1245            // correspond to a fingerprint. Filter them out.
1246            .filter_map(move |e| match e {
1247                Ok(entry) => match self.get_fingerprint_by_path(entry.path()) {
1248                    Ok(fingerprint) => Some(Ok(fingerprint)),
1249                    Err(_) => None,
1250                },
1251                Err(err) => {
1252                    if let Some(std::io::ErrorKind::NotFound)
1253                        = err.io_error().map(|err| err.kind())
1254                    {
1255                        // Ignore file not found.
1256                        None
1257                    } else {
1258                        Some(Err(err.into()))
1259                    }
1260                }
1261            })
1262    }
1263
1264    /// Iterates over the certs in the store.
1265    ///
1266    /// Iterates over the certs in the store returning the
1267    /// fingerprint, and a file handle for each cert.
1268    ///
1269    /// Note: this only considers certificates that are stored under
1270    /// their fingerprint; it does not include certificates stored
1271    /// under a special name.
1272    pub fn iter_files(
1273        &self,
1274    ) -> impl Iterator<Item = Result<(String, File)>> + '_ {
1275        // Helper function analogous to get, with the fingerprint included in
1276        // the output.
1277        let get_with_fingerprint = move |fingerprint: &str| -> Result<(String, File)> {
1278            match self.get_file(fingerprint)? {
1279                None => Err(Error::IoError(io::Error::new(
1280                    io::ErrorKind::Other,
1281                    // The file was found when fingerprints() walked over the
1282                    // directory, but wasn't found for reading now.
1283                    format!("The file for {} disappeared.", fingerprint),
1284                ))),
1285                Some(file) => Ok((fingerprint.to_owned(), file)),
1286            }
1287        };
1288
1289        self.fingerprints()
1290            .map(move |fingerprint_result| {
1291                fingerprint_result.and_then(|fingerprint| {
1292                    get_with_fingerprint(&fingerprint)
1293                })
1294            })
1295    }
1296
1297    /// Iterates over the certs in the store.
1298    ///
1299    /// Iterates over the certs in the store returning the
1300    /// fingerprint, the tag, and the data for each cert.
1301    ///
1302    /// Note: this only considers certificates that are stored under
1303    /// their fingerprint; it does not include certificates stored
1304    /// under a special name.
1305    pub fn iter(
1306        &self,
1307    ) -> impl Iterator<Item = Result<(String, Tag, Vec<u8>)>> + '_ {
1308        self.iter_files()
1309            .map(|r| {
1310                let (fingerprint, mut fp) = r?;
1311                let tag = Tag::try_from(&fp)?;
1312
1313                let mut data = Vec::new();
1314                fp.read_to_end(&mut data)?;
1315
1316                Ok((fingerprint, tag, data))
1317            })
1318    }
1319
1320    fn idempotent_create_lockfile(&self) -> Result<std::fs::File> {
1321        let lock_path = self.base.join("writelock");
1322        // Open the lockfile for writing, and create it if it does not exist yet.
1323        std::fs::OpenOptions::new()
1324            .write(true)
1325            .create(true)
1326            .truncate(true)
1327            .open(lock_path)
1328            .map_err(Into::into)
1329    }
1330}
1331
1332#[cfg(test)]
1333mod tests {
1334    use super::*;
1335    use assert_fs::prelude::*;
1336    use predicates::prelude::*;
1337
1338    use crate::TRUST_ROOT;
1339
1340    fn test_base() -> assert_fs::TempDir {
1341        let base = assert_fs::TempDir::new().unwrap();
1342        match std::env::var_os("CERTD_TEST_PERSIST") {
1343            Some(_) => {
1344                eprintln!("Test base dir: {}", &base.path().to_string_lossy());
1345                base.into_persistent()
1346            }
1347            None => base,
1348        }
1349    }
1350
1351    struct Testdata<'a> {
1352        data: &'a [u8],
1353        fingerprint: &'a str,
1354    }
1355
1356    impl Testdata<'_> {
1357        fn path(&self) -> String {
1358            [&self.fingerprint[..2], &self.fingerprint[2..]].join("/")
1359        }
1360
1361        fn add_to_certd(&self, base: &assert_fs::TempDir) {
1362            base.child(self.path()).write_binary(self.data).unwrap();
1363        }
1364    }
1365
1366    static ALICE: Testdata = Testdata {
1367        fingerprint: "eb85bb5fa33a75e15e944e63f231550c4f47e38e",
1368        data: include_bytes!("../../testdata/alice.pgp"),
1369    };
1370
1371    static BOB: Testdata = Testdata {
1372        fingerprint: "d1a66e1a23b182c9980f788cfbfcc82a015e7330",
1373        data: include_bytes!("../../testdata/bob.pgp"),
1374    };
1375
1376    static TESTY: Testdata = Testdata {
1377        fingerprint: "39d100ab67d5bd8c04010205fb3751f1587daef1",
1378        data: include_bytes!("../../testdata/testy-new.pgp"),
1379    };
1380
1381    fn setup_testdir(
1382        testdata: &[&Testdata],
1383    ) -> Result<(assert_fs::TempDir, CertD)> {
1384        let base = test_base();
1385        for t in testdata.iter() {
1386            t.add_to_certd(&base);
1387        }
1388
1389        let trust_root_data = include_bytes!("../../testdata/sender.pgp");
1390        base.child("trust-root")
1391            .write_binary(trust_root_data)
1392            .unwrap();
1393
1394        let certd = CertD::with_base_dir(&base)?;
1395        Ok((base, certd))
1396    }
1397
1398    #[test]
1399    fn get_fingerprint() -> std::result::Result<(), Box<dyn std::error::Error>> {
1400        let data = include_bytes!("../../testdata/testy-new.pgp");
1401
1402        let base = test_base();
1403        base.child("39/d100ab67d5bd8c04010205fb3751f1587daef1")
1404            .write_binary(data)
1405            .unwrap();
1406
1407        let certd = CertD::with_base_dir(&base)?;
1408
1409        let (tag, cert) = certd
1410            .get("39d100ab67d5bd8c04010205fb3751f1587daef1")?
1411            .unwrap();
1412        assert_eq!(cert, data);
1413
1414        assert!(certd
1415            .get_if_changed(tag, "39d100ab67d5bd8c04010205fb3751f1587daef1")?
1416            .is_none());
1417
1418        let mut fp = certd
1419            .get_file("39d100ab67d5bd8c04010205fb3751f1587daef1")?
1420            .unwrap();
1421        let tag = Tag::try_from(&fp)?;
1422        let mut data = Vec::new();
1423        fp.read_to_end(&mut data)?;
1424        assert_eq!(cert, data);
1425
1426        assert!(certd
1427            .get_if_changed(tag, "39d100ab67d5bd8c04010205fb3751f1587daef1")?
1428            .is_none());
1429
1430        base.close().unwrap();
1431        Ok(())
1432    }
1433
1434    #[test]
1435    fn get_special() -> std::result::Result<(), Box<dyn std::error::Error>> {
1436        let data = include_bytes!("../../testdata/sender.pgp");
1437
1438        let base = test_base();
1439        base.child("trust-root").write_binary(data).unwrap();
1440
1441        let certd = CertD::with_base_dir(&base)?;
1442
1443        let (tag, cert) = certd.get(TRUST_ROOT)?.unwrap();
1444        assert_eq!(cert, data);
1445
1446        assert!(certd.get_if_changed(tag, TRUST_ROOT)?.is_none());
1447
1448        base.close().unwrap();
1449        Ok(())
1450    }
1451
1452    #[test]
1453    fn get_not_found() -> Result<()> {
1454        let base = test_base();
1455        let certd = CertD::with_base_dir(&base)?;
1456        let result = certd.get("39d100ab67d5bd8c04010205fb3751f1587daef1");
1457        assert!(matches!(result, Ok(None)));
1458        Ok(())
1459    }
1460
1461    #[test]
1462    fn insert_locked() -> Result<()> {
1463        let data = include_bytes!("../../testdata/testy-new.pgp");
1464        let base = test_base();
1465
1466        let file = base.child("39/d100ab67d5bd8c04010205fb3751f1587daef1");
1467        file.assert(predicate::path::missing());
1468
1469        let certd = CertD::with_base_dir(&base)?;
1470
1471        // Lock the lockfile before we try to insert
1472        let mut lf = RwLock::new(certd.idempotent_create_lockfile()?);
1473        // Lock exclusively
1474        let _lock = lf.write()?;
1475
1476        let result = certd.try_insert_data(
1477            data, false,
1478            |new: &[u8], old: Option<&[u8]>| {
1479                assert!(old.is_none());
1480                Ok(MergeResult::DataRef(new))
1481            });
1482
1483        match result.unwrap_err() {
1484            Error::IoError(e) if e.kind() == io::ErrorKind::WouldBlock => {
1485                Ok(())
1486            }
1487            e => Err(e),
1488        }
1489    }
1490
1491    #[test]
1492    fn insert_special_locked() -> Result<()> {
1493        let data = include_bytes!("../../testdata/sender.pgp");
1494        let base = test_base();
1495
1496        let file = base.child("trust-root");
1497        file.assert(predicate::path::missing());
1498
1499        let certd = CertD::with_base_dir(&base)?;
1500
1501        // Lock the lockfile before we try to insert
1502        let mut lock = RwLock::new(certd.idempotent_create_lockfile()?);
1503        // Lock exclusively
1504        let _lock = lock.write()?;
1505
1506        let result = certd.try_insert_special(
1507            TRUST_ROOT,
1508            &data[..],
1509            false,
1510            |new: &[u8], old: Option<&[u8]>| {
1511                assert!(old.is_none());
1512                Ok(MergeResult::DataRef(new))
1513            },
1514        );
1515
1516        match result.unwrap_err() {
1517            Error::IoError(e) if e.kind() == io::ErrorKind::WouldBlock => {
1518                Ok(())
1519            }
1520            e => Err(e),
1521        }
1522    }
1523
1524    #[test]
1525    fn insert_new() -> Result<()> {
1526        let data = include_bytes!("../../testdata/testy-new.pgp");
1527        let data = &data[..];
1528        let base = test_base();
1529
1530        let file = base.child("39/d100ab67d5bd8c04010205fb3751f1587daef1");
1531        file.assert(predicate::path::missing());
1532
1533        let certd = CertD::with_base_dir(&base)?;
1534
1535        let (_, inserted) = certd.insert_data(
1536            data,
1537            true,
1538            |new: &[u8], old: Option<&[u8]>| {
1539                assert!(old.is_none());
1540                Ok(MergeResult::DataRef(new))
1541            })?;
1542        file.assert(data);
1543        assert_eq!(inserted.as_deref(), Some(data));
1544
1545        Ok(())
1546    }
1547
1548    #[test]
1549    fn insert_special_new() -> Result<()> {
1550        let data = include_bytes!("../../testdata/sender.pgp");
1551        let data = &data[..];
1552        let base = test_base();
1553
1554        let file = base.child("trust-root");
1555        file.assert(predicate::path::missing());
1556
1557        let certd = CertD::with_base_dir(&base)?;
1558
1559        let (_, inserted) = certd.insert_special(
1560            "trust-root",
1561            data,
1562            true,
1563            |new: &[u8], old: Option<&[u8]>| {
1564                assert!(old.is_none());
1565                Ok(MergeResult::DataRef(new))
1566            },
1567        )?;
1568        file.assert(data);
1569        assert_eq!(inserted.as_deref(), Some(data));
1570
1571        Ok(())
1572    }
1573
1574    #[test]
1575    fn insert_update() -> std::result::Result<(), Box<dyn std::error::Error>> {
1576        let data = include_bytes!("../../testdata/testy-new.pgp");
1577        let data = &data[..];
1578        let base = test_base();
1579
1580        let file = base.child("39/d100ab67d5bd8c04010205fb3751f1587daef1");
1581        file.touch().unwrap();
1582        file.assert(predicate::str::is_empty());
1583
1584        let certd = CertD::with_base_dir(&base)?;
1585
1586        let (_, inserted) = certd.insert_data(
1587            data,
1588            true,
1589            |new: &[u8], old: Option<&[u8]>| {
1590                assert!(old.is_some());
1591                Ok(MergeResult::DataRef(new))
1592            })?;
1593        file.assert(data);
1594        assert_eq!(inserted.as_deref(), Some(data));
1595
1596        Ok(())
1597    }
1598
1599    #[test]
1600    fn insert_special_update(
1601    ) -> std::result::Result<(), Box<dyn std::error::Error>> {
1602        let data = include_bytes!("../../testdata/sender.pgp");
1603        let data = &data[..];
1604        let base = test_base();
1605
1606        let file = base.child("trust-root");
1607        file.touch().unwrap();
1608        file.assert(predicate::str::is_empty());
1609
1610        let certd = CertD::with_base_dir(&base)?;
1611
1612        let (_, inserted) = certd.insert_special(
1613            TRUST_ROOT,
1614            data,
1615            true,
1616            |new: &[u8], old: Option<&[u8]>| {
1617                assert!(old.is_some());
1618                Ok(MergeResult::DataRef(new))
1619            },
1620        )?;
1621        file.assert(data);
1622        assert_eq!(inserted.as_deref(), Some(data));
1623
1624        Ok(())
1625    }
1626
1627    #[test]
1628    fn insert_get() -> std::result::Result<(), Box<dyn std::error::Error>> {
1629        let data = include_bytes!("../../testdata/testy-new.pgp");
1630        let data = &data[..];
1631        let base = test_base();
1632
1633        let certd = CertD::with_base_dir(&base)?;
1634
1635        certd.insert_data(
1636            data,
1637            false,
1638            |new: &[u8], old: Option<&[u8]>| {
1639                assert!(old.is_none());
1640                Ok(MergeResult::DataRef(new))
1641            })?;
1642        let (_, cert) = certd
1643            .get("39d100ab67d5bd8c04010205fb3751f1587daef1")?
1644            .unwrap();
1645        assert_eq!(cert, data);
1646
1647        Ok(())
1648    }
1649
1650    #[test]
1651    fn get_path_by_fingerprint() -> Result<()> {
1652        let base = test_base();
1653        let certd = CertD::with_base_dir(&base)?;
1654
1655        let expected = base
1656            .path()
1657            .join("39")
1658            .join("d100ab67d5bd8c04010205fb3751f1587daef1");
1659
1660        let fingerprint = "39d100ab67d5bd8c04010205fb3751f1587daef1";
1661        assert_eq!(certd.get_path_by_fingerprint(fingerprint)?, expected);
1662
1663        let fingerprint = "39D100AB67D5BD8C04010205FB3751F1587DAEF1";
1664        assert_eq!(certd.get_path_by_fingerprint(fingerprint)?, expected);
1665
1666        let fingerprint = "39D100ab67D5bD8C04010205FB3751f1587DAeF1";
1667        assert_eq!(certd.get_path_by_fingerprint(fingerprint)?, expected);
1668
1669        Ok(())
1670    }
1671
1672    #[test]
1673    fn get_path_by_fingerprint_negative() -> Result<()> {
1674        let base = test_base();
1675        let certd = CertD::with_base_dir(&base)?;
1676
1677        // empty
1678        let fingerprint = "";
1679        let result = certd.get_path_by_fingerprint(fingerprint);
1680        assert!(matches!(result.unwrap_err(), Error::BadName));
1681
1682        // too short
1683        let fingerprint = "39d100ab67d5bd8c04010205fb3751f1587daef";
1684        let result = certd.get_path_by_fingerprint(fingerprint);
1685        assert!(matches!(result.unwrap_err(), Error::BadName));
1686
1687        // not ascii hex
1688        let fingerprint = "peter";
1689        let result = certd.get_path_by_fingerprint(fingerprint);
1690        assert!(matches!(result.unwrap_err(), Error::BadName));
1691        Ok(())
1692    }
1693
1694    #[test]
1695    fn get_path_by_special() -> Result<()> {
1696        let base = test_base();
1697        let certd = CertD::with_base_dir(&base)?;
1698
1699        let expected = base.path().join(TRUST_ROOT);
1700
1701        let name = "trust-root";
1702        assert_eq!(certd.get_path_by_special(name)?, expected);
1703
1704        let name = "_sequoia";
1705        assert_eq!(certd.get_path_by_special(name)?,
1706                   base.path().join(name));
1707
1708        let name = "_sequoia_foo";
1709        assert_eq!(certd.get_path_by_special(name)?,
1710                   base.path().join(name));
1711
1712        Ok(())
1713    }
1714
1715    #[test]
1716    fn get_path_by_special_negative() -> Result<()> {
1717        let base = test_base();
1718        let certd = CertD::with_base_dir(&base)?;
1719
1720        // empty
1721        let name = "";
1722        let result = certd.get_path_by_special(name);
1723        assert!(matches!(result.unwrap_err(), Error::BadName));
1724
1725        // unknown
1726        let name = "mySpecialName";
1727        let result = certd.get_path_by_special(name);
1728        assert!(matches!(result.unwrap_err(), Error::BadName));
1729
1730        // Case matters.
1731        let name = "TRUST-ROOT";
1732        let result = certd.get_path_by_special(name);
1733        assert!(matches!(result.unwrap_err(), Error::BadName));
1734
1735        let name = "TrUsT-RooT";
1736        let result = certd.get_path_by_special(name);
1737        assert!(matches!(result.unwrap_err(), Error::BadName));
1738
1739        // Directories are not currently allowed.
1740        let name = "_sequoia_foo/bar";
1741        let result = certd.get_path_by_special(name);
1742        assert!(matches!(result.unwrap_err(), Error::BadName));
1743
1744        Ok(())
1745    }
1746
1747    #[test]
1748    fn is_special() -> Result<()> {
1749        assert!(CertD::is_special("trust-root").is_ok());
1750        assert!(CertD::is_special("TRUST-ROOT").is_err());
1751
1752        assert!(CertD::is_special("_special_foo_bar").is_ok());
1753        assert!(CertD::is_special("special_foo_bar").is_err());
1754
1755        assert!(CertD::is_special("8f17777118a33dda9ba48e62aacb3243630052d9").is_err());
1756
1757        Ok(())
1758    }
1759
1760    #[test]
1761    fn fingerprints() -> Result<()> {
1762        use std::collections::HashSet;
1763
1764        let (_base, certd) = setup_testdir(&[&ALICE, &BOB, &TESTY])?;
1765
1766        let iter_fingerprint = certd.fingerprints();
1767        let fingerprints = iter_fingerprint.collect::<Result<HashSet<_>>>()?;
1768        let expected: HashSet<_> =
1769            [ALICE.fingerprint, BOB.fingerprint, TESTY.fingerprint]
1770                .iter()
1771                .map(|&s| s.to_owned())
1772                .collect();
1773        assert_eq!(expected, fingerprints);
1774
1775        Ok(())
1776    }
1777
1778    #[test]
1779    fn fingerprints_empty() -> Result<()> {
1780        use std::collections::HashSet;
1781
1782        let base = test_base();
1783        let certd = CertD::with_base_dir(&base)?;
1784
1785        let iter_fingerprint = certd.fingerprints();
1786        let fingerprints = iter_fingerprint.collect::<Result<HashSet<_>>>()?;
1787        assert!(fingerprints.is_empty());
1788
1789        Ok(())
1790    }
1791
1792    #[test]
1793    fn fingerprints_junk() -> Result<()> {
1794        use std::collections::HashSet;
1795
1796        let (base, certd) = setup_testdir(&[&ALICE, &BOB, &TESTY])?;
1797        base.child("some_file").write_str("some_text").unwrap();
1798        base.child("aa/some_file").write_str("some_text").unwrap();
1799        base.child("aa/aa/some_file")
1800            .write_str("some_text")
1801            .unwrap();
1802
1803        let iter_fingerprint = certd.fingerprints();
1804        let fingerprints = iter_fingerprint.collect::<Result<HashSet<_>>>()?;
1805        let expected: HashSet<_> =
1806            [ALICE.fingerprint, BOB.fingerprint, TESTY.fingerprint]
1807                .iter()
1808                .map(|&s| s.to_owned())
1809                .collect();
1810        assert_eq!(expected, fingerprints);
1811
1812        Ok(())
1813    }
1814
1815    #[test]
1816    fn iter() -> Result<()> {
1817        use std::collections::HashSet;
1818
1819        let (_base, certd) = setup_testdir(&[&ALICE, &BOB, &TESTY])?;
1820
1821        let mut expected: HashSet<_> = [&ALICE, &BOB, &TESTY]
1822            .iter()
1823            .map(|&s| {
1824                (
1825                    s.fingerprint.to_owned(),
1826                    certd.get(s.fingerprint).unwrap().unwrap().0,
1827                    s.data.to_vec(),
1828                )
1829            })
1830            .collect();
1831
1832        for item in certd.iter() {
1833            let item = item?;
1834            assert!(expected.contains(&item));
1835            expected.remove(&item);
1836        }
1837        assert!(expected.is_empty());
1838
1839        Ok(())
1840    }
1841
1842    #[test]
1843    fn iter_files() -> Result<()> {
1844        use std::collections::HashSet;
1845
1846        let (_base, certd) = setup_testdir(&[&ALICE, &BOB, &TESTY])?;
1847
1848        let mut expected: HashSet<_> = [&ALICE, &BOB, &TESTY]
1849            .iter()
1850            .map(|&s| {
1851                (
1852                    s.fingerprint.to_owned(),
1853                    certd.get(s.fingerprint).unwrap().unwrap().0,
1854                    s.data.to_vec().into_boxed_slice(),
1855                )
1856            })
1857            .collect();
1858
1859        for item in certd.iter_files() {
1860            let (fingerprint, mut fp) = item?;
1861            let tag = Tag::try_from(&fp)?;
1862
1863            let mut cert = Vec::new();
1864            fp.read_to_end(&mut cert)?;
1865
1866            let item = (fingerprint, tag, cert.into());
1867
1868            assert!(expected.contains(&item));
1869            expected.remove(&item);
1870        }
1871        assert!(expected.is_empty());
1872
1873        Ok(())
1874    }
1875
1876    #[test]
1877    fn base_path() -> Result<()> {
1878        let base = assert_fs::TempDir::new().unwrap();
1879        let certd = CertD::with_base_dir(&base)?;
1880
1881        assert_eq!(certd.base_dir(), base.path());
1882        Ok(())
1883    }
1884
1885    #[test]
1886    fn default_store_path() {
1887        assert!(CertD::default_store_path().is_ok(),
1888                "The default store's path is not defined for this platform.");
1889    }
1890
1891    #[test]
1892    fn certd_does_not_exist() -> Result<()> {
1893        let mut base = assert_fs::TempDir::new().unwrap().path().to_path_buf();
1894        base.push("asdflkj");
1895
1896        // std::fs::try_exists would be better, but it is still
1897        // experimental.
1898        assert!(std::fs::metadata(&base).is_err());
1899
1900        let certd = CertD::with_base_dir(&base)?;
1901
1902        // fingerprints shouldn't fail even if the cert directory did
1903        // not exist.
1904        let fingerprints = certd.fingerprints().collect::<Result<Vec<_>>>()?;
1905        assert_eq!(fingerprints.len(), 0);
1906
1907        Ok(())
1908    }
1909
1910    #[test]
1911    fn certd_tag() -> Result<()> {
1912        let (_base, certd) = setup_testdir(&[&ALICE])?;
1913
1914        let certd_tag = || -> Tag {
1915            let tag = certd.tag();
1916
1917            let tag_readdir_std = certd.tag_readdir_std();
1918            assert_eq!(tag, tag_readdir_std);
1919
1920            #[cfg(unix)]
1921            {
1922                let tag_readdir_unix = certd.tag_readdir_unix();
1923                assert_eq!(tag, tag_readdir_unix);
1924            }
1925
1926            let tag_probe_std = certd.tag_probe_std(None);
1927            assert_eq!(tag, tag_probe_std);
1928
1929            #[cfg(unix)]
1930            {
1931                let tag_probe_unix = certd.tag_probe_unix(None);
1932                assert_eq!(tag, tag_probe_unix);
1933            }
1934
1935            tag
1936        };
1937
1938        let iter_fingerprint = certd.fingerprints();
1939        let fingerprints = iter_fingerprint.collect::<Result<Vec<_>>>()?;
1940        assert_eq!(fingerprints.len(), 1);
1941
1942        let tag0 = certd_tag();
1943        eprintln!("tag0: {:x}", tag0.0);
1944
1945        // Insert a new certificate.  This should change the tag.
1946        eprintln!("Inserting BOB");
1947        certd.insert_data(
1948            &BOB.data,
1949            false,
1950            |new: &[u8], old: Option<&[u8]>| {
1951                assert!(old.is_none());
1952                Ok(MergeResult::DataRef(new))
1953            })?;
1954
1955        let tag1 = certd_tag();
1956        eprintln!("tag1: {:x}", tag1.0);
1957
1958        assert_ne!(tag0, tag1);
1959
1960        // Adding a special shouldn't change the certd's tag.
1961        eprintln!("Inserting special _bob");
1962        certd.insert_special(
1963            "_bob",
1964            BOB.data,
1965            false,
1966            |new: &[u8], old: Option<&[u8]>| {
1967                assert!(old.is_none());
1968                Ok(MergeResult::DataRef(new))
1969            })?;
1970
1971        let tag2 = certd_tag();
1972        eprintln!("tag2: {:x}", tag2.0);
1973        assert_eq!(tag1, tag2);
1974
1975        // Insert a new certificate.  This should change the tag.
1976        eprintln!("Inserting TESTY");
1977        certd.insert_data(
1978            TESTY.data,
1979            false,
1980            |new: &[u8], old: Option<&[u8]>| {
1981                assert!(old.is_none());
1982                Ok(MergeResult::DataRef(new))
1983            })?;
1984
1985        let tag3 = certd_tag();
1986        eprintln!("tag3: {:x}", tag3.0);
1987        assert_ne!(tag2, tag3);
1988
1989        Ok(())
1990    }
1991}