1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
//! Shared OpenPGP Certificate Directory
//!
//! This crate implements a generic [OpenPGP certificate store] that can
//! be shared between implementations.  It also defines a way to root
//! trust, and a way to associate pet names with certificates.
//! Sharing certificates and trust decisions increases security by
//! enabling more applications to take advantage of OpenPGP.  It also
//! improves privacy by reducing the required certificate discoveries
//! that go out to the network.
//!
//! Note that this crate is only concerned with the low-level
//! mechanics of the certificate directory and does not depend on an
//! OpenPGP implementation.  This is the reason it returns opaque
//! [`Data`] when retrieving certificates.  Generally, it has to be
//! combined with an OpenPGP implementation to be useful.
//!
//! [OpenPGP certificate store]: https://datatracker.ietf.org/doc/draft-nwjw-openpgp-cert-d/

use std::time::{Duration, UNIX_EPOCH};

#[macro_use]
mod macros;
mod certd;
pub use certd::CertD;

mod error;
pub use error::*;

mod pgp;

/// Special name of the trust root.
///
/// This is the [special name] under which the [trust root] is stored.
///
/// [special name]: https://www.ietf.org/archive/id/draft-nwjw-openpgp-cert-d-00.html#name-special-names
/// [trust root]: https://www.ietf.org/archive/id/draft-nwjw-openpgp-cert-d-00.html#name-trust-root
pub const TRUST_ROOT: &str = "trust-root";

// SPECIAL_NAMES must be sorted. This allows using
// SPECIAL_NAMES.binary_search(needle).is_ok() (O(log(n))) instead of
// SPECIAL_NAMES.contains(needle) (O(n))).
const SPECIAL_NAMES: &[&str] = &[TRUST_ROOT];

/// Opaque data returned by this crate.
pub type Data = Box<[u8]>;

/// Facilitates caching of derived data.
///
/// Every time you look up a cert in the directory, the operation also
/// returns a tag.  This tag, which can be converted to a `u64`, is an
/// opaque identifier that changes whenever the cert in the directory
/// changes.
///
/// To use it, store the tag with your cached data, and every time you
/// re-do the lookup, compare the returned tag with the stored tag to
/// see if your cached data is still up-to-date.
///
/// # Examples
///
/// This demonstrates how to use the tag to prevent useless
/// recomputations.
///
/// ```
/// # use openpgp_cert_d::*;
/// # fn dosth(certd: &CertD) -> Result<()> {
/// let fp = "eb85bb5fa33a75e15e944e63f231550c4f47e38e";
/// let (tag, cert) = certd.get(fp)?.expect("cert to exist");
/// // ...
/// if let Some((new_cert, new_tag)) = certd.get_if_changed(tag, fp)? {
///     // cert changed...
/// } else {
///     // cert didn't change...
/// }
/// # Ok(()) }
/// ```
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub struct Tag(u64);

impl std::convert::TryFrom<std::fs::Metadata> for Tag {
    type Error = std::io::Error;

    /// Compute a `Tag` from file metadata.
    fn try_from(m: std::fs::Metadata) -> std::io::Result<Self> {
        let d = m
            .modified()?
            .duration_since(UNIX_EPOCH)
            .unwrap_or_else(|_| Duration::new(0, 0));

        let inode = platform! {
            unix => {
                use std::os::unix::fs::MetadataExt;
                m.ino()
            },
            windows => 0,
        };

        // XXX better hashing
        Ok(Tag(inode ^ d.as_secs() ^ d.subsec_nanos() as u64))
    }
}

impl From<u64> for Tag {
    fn from(t: u64) -> Self {
        Tag(t)
    }
}

impl From<Tag> for u64 {
    fn from(t: Tag) -> Self {
        t.0
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn special_names_is_sorted() {
        let mut sn = SPECIAL_NAMES.to_vec();
        sn.sort_unstable();
        assert_eq!(sn, SPECIAL_NAMES);
    }
}