sequoia_openpgp/
fingerprint.rs

1use std::borrow::Borrow;
2use std::fmt;
3
4#[cfg(test)]
5use quickcheck::{Arbitrary, Gen};
6
7use crate::Error;
8use crate::KeyHandle;
9use crate::KeyID;
10use crate::Result;
11
12/// A long identifier for certificates and keys.
13///
14/// A `Fingerprint` uniquely identifies a public key.
15///
16/// Currently, Sequoia supports *version 6* fingerprints and Key IDs,
17/// and *version 4* fingerprints and Key IDs.  *Version 3*
18/// fingerprints and Key IDs were deprecated by [RFC 4880] in 2007.
19///
20/// Essentially, a fingerprint is a hash over the key's public key
21/// packet.  For details, see [Section 5.5.4 of RFC 9580].
22///
23/// Fingerprints are used, for example, to reference the issuing key
24/// of a signature in its [`IssuerFingerprint`] subpacket.  As a
25/// general rule of thumb, you should prefer using fingerprints over
26/// KeyIDs because the latter are vulnerable to [birthday attack]s.
27///
28/// See also [`KeyID`] and [`KeyHandle`].
29///
30///   [RFC 4880]: https://tools.ietf.org/html/rfc4880
31///   [Section 5.5.4 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5.4
32///   [`IssuerFingerprint`]: crate::packet::signature::subpacket::SubpacketValue::IssuerFingerprint
33///   [birthday attack]: https://nullprogram.com/blog/2019/07/22/
34///   [`KeyID`]: crate::KeyID
35///   [`KeyHandle`]: crate::KeyHandle
36///
37/// # Examples
38///
39/// ```rust
40/// # fn main() -> sequoia_openpgp::Result<()> {
41/// # use sequoia_openpgp as openpgp;
42/// use openpgp::Fingerprint;
43///
44/// let fp: Fingerprint =
45///     "0123 4567 89AB CDEF 0123 4567 89AB CDEF 0123 4567".parse()?;
46///
47/// assert_eq!("0123456789ABCDEF0123456789ABCDEF01234567", fp.to_hex());
48/// # Ok(()) }
49/// ```
50#[non_exhaustive]
51#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
52pub enum Fingerprint {
53    /// Fingerprint of v6 certificates and keys.
54    V6([u8; 32]),
55
56    /// Fingerprint of v4 certificates and keys.
57    V4([u8; 20]),
58
59    /// Fingerprint of unknown version or shape.
60    Unknown {
61        /// Version of the fingerprint, if known.
62        version: Option<u8>,
63
64        /// Raw bytes of the fingerprint.
65        bytes: Box<[u8]>,
66    },
67}
68assert_send_and_sync!(Fingerprint);
69
70impl fmt::Display for Fingerprint {
71    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
72        write!(f, "{:X}", self)
73    }
74}
75
76impl fmt::Debug for Fingerprint {
77    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
78        match self {
79            Fingerprint::V4(_) =>
80                write!(f, "Fingerprint::V4({})", self),
81            Fingerprint::V6(_) =>
82                write!(f, "Fingerprint::V6({})", self),
83            Fingerprint::Unknown { version, .. } =>
84                write!(f, "Fingerprint::Unknown {{ {:?}, {} }}", version, self),
85        }
86    }
87}
88
89impl fmt::UpperHex for Fingerprint {
90    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
91        self.write_to_fmt(f, true)
92    }
93}
94
95impl fmt::LowerHex for Fingerprint {
96    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
97        self.write_to_fmt(f, false)
98    }
99}
100
101impl std::str::FromStr for Fingerprint {
102    type Err = anyhow::Error;
103
104    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
105        if s.chars().filter(|c| ! c.is_whitespace()).count() % 2 == 1 {
106            return Err(crate::Error::InvalidArgument(
107                "Odd number of nibbles".into()).into());
108        }
109
110        Self::from_bytes_intern(None, &crate::fmt::hex::decode_pretty(s)?)
111    }
112}
113
114impl Fingerprint {
115    /// Creates a `Fingerprint` from a byte slice in big endian
116    /// representation.
117    ///
118    /// # Examples
119    ///
120    /// ```rust
121    /// # fn main() -> sequoia_openpgp::Result<()> {
122    /// # use sequoia_openpgp as openpgp;
123    /// use openpgp::Fingerprint;
124    ///
125    /// let fp: Fingerprint =
126    ///     "0123 4567 89AB CDEF 0123 4567 89AB CDEF 0123 4567".parse()?;
127    /// let bytes =
128    ///     [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23,
129    ///      0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67];
130    ///
131    /// assert_eq!(Fingerprint::from_bytes(4, &bytes)?, fp);
132    /// # Ok(()) }
133    /// ```
134    pub fn from_bytes(version: u8, raw: &[u8]) -> Result<Fingerprint> {
135        Self::from_bytes_intern(Some(version), raw)
136    }
137
138    /// Like [`Fingerprint::from_bytes`], but with optional version.
139    pub(crate) fn from_bytes_intern(mut version: Option<u8>, raw: &[u8])
140                                    -> Result<Fingerprint>
141    {
142        // Apply some heuristics if no explicit version is known.
143        if version.is_none() && raw.len() == 32 {
144            version = Some(6);
145        } else if version.is_none() && raw.len() == 20 {
146            version = Some(4);
147        }
148
149        match version {
150            Some(6) => if raw.len() == 32 {
151                let mut fp: [u8; 32] = Default::default();
152                fp.copy_from_slice(raw);
153                Ok(Fingerprint::V6(fp))
154            } else {
155                Err(Error::InvalidArgument(format!(
156                    "a v6 fingerprint consists of 32 bytes, got {}",
157                    raw.len())).into())
158            },
159
160            Some(4) => if raw.len() == 20 {
161                let mut fp : [u8; 20] = Default::default();
162                fp.copy_from_slice(raw);
163                Ok(Fingerprint::V4(fp))
164            } else {
165                Err(Error::InvalidArgument(format!(
166                    "a v4 fingerprint consists of 20 bytes, got {}",
167                    raw.len())).into())
168            },
169
170            _ => Ok(Fingerprint::Unknown {
171                version,
172                bytes: raw.to_vec().into_boxed_slice(),
173            }),
174        }
175    }
176
177    /// Returns the raw fingerprint as a byte slice in big endian
178    /// representation.
179    ///
180    /// # Examples
181    ///
182    /// ```rust
183    /// # fn main() -> sequoia_openpgp::Result<()> {
184    /// # use sequoia_openpgp as openpgp;
185    /// use openpgp::Fingerprint;
186    ///
187    /// let fp: Fingerprint =
188    ///     "0123 4567 89AB CDEF 0123 4567 89AB CDEF 0123 4567".parse()?;
189    ///
190    /// assert_eq!(fp.as_bytes(),
191    ///            [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23,
192    ///             0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67]);
193    /// # Ok(()) }
194    /// ```
195    pub fn as_bytes(&self) -> &[u8] {
196        match self {
197            Fingerprint::V4(ref fp) => fp,
198            Fingerprint::V6(fp) => fp,
199            Fingerprint::Unknown { bytes, .. } => bytes,
200        }
201    }
202
203    /// Converts this fingerprint to its canonical hexadecimal
204    /// representation.
205    ///
206    /// This representation is always uppercase and without spaces and
207    /// is suitable for stable key identifiers.
208    ///
209    /// The output of this function is exactly the same as formatting
210    /// this object with the `:X` format specifier.
211    ///
212    /// ```rust
213    /// # fn main() -> sequoia_openpgp::Result<()> {
214    /// # use sequoia_openpgp as openpgp;
215    /// use openpgp::Fingerprint;
216    ///
217    /// let fp: Fingerprint =
218    ///     "0123 4567 89AB CDEF 0123 4567 89AB CDEF 0123 4567".parse()?;
219    ///
220    /// assert_eq!("0123456789ABCDEF0123456789ABCDEF01234567", fp.to_hex());
221    /// assert_eq!(format!("{:X}", fp), fp.to_hex());
222    /// # Ok(()) }
223    /// ```
224    pub fn to_hex(&self) -> String {
225        use std::fmt::Write;
226
227        let mut output = String::with_capacity(
228            // Each byte results in two hex characters.
229            self.as_bytes().len() * 2);
230
231        // We write to String that never fails but the Write API
232        // returns Results.
233        write!(output, "{:X}", self).unwrap();
234
235        output
236    }
237
238    /// Converts this fingerprint to its hexadecimal representation
239    /// with spaces.
240    ///
241    /// This representation is always uppercase and with spaces
242    /// grouping the hexadecimal digits into groups of four with a
243    /// double space in the middle.  It is only suitable for manual
244    /// comparison of fingerprints.
245    ///
246    /// Note: The spaces will hinder other kind of use cases.  For
247    /// example, it is harder to select the whole fingerprint for
248    /// copying, and it has to be quoted when used as a command line
249    /// argument.  Only use this form for displaying a fingerprint
250    /// with the intent of manual comparisons.
251    ///
252    /// See also [`Fingerprint::to_icao`].
253    ///
254    ///   [`Fingerprint::to_icao`]: Fingerprint::to_icao()
255    ///
256    /// ```rust
257    /// # fn main() -> sequoia_openpgp::Result<()> {
258    /// # use sequoia_openpgp as openpgp;
259    /// let fp: openpgp::Fingerprint =
260    ///     "0123 4567 89AB CDEF 0123 4567 89AB CDEF 0123 4567".parse()?;
261    ///
262    /// assert_eq!("0123 4567 89AB CDEF 0123  4567 89AB CDEF 0123 4567",
263    ///            fp.to_spaced_hex());
264    /// # Ok(()) }
265    /// ```
266    pub fn to_spaced_hex(&self) -> String {
267        use std::fmt::Write;
268
269        let raw_len = self.as_bytes().len();
270        let mut output = String::with_capacity(
271            // Each byte results in two hex characters.
272            raw_len * 2
273            +
274            // Every 2 bytes of output, we insert a space.
275            raw_len / 2
276            // After half of the groups, there is another space.
277            + 1);
278
279        // We write to String that never fails but the Write API
280        // returns Results.
281        write!(output, "{:#X}", self).unwrap();
282
283        output
284    }
285
286    /// Parses the hexadecimal representation of an OpenPGP
287    /// fingerprint.
288    ///
289    /// This function is the reverse of `to_hex`. It also accepts
290    /// other variants of the fingerprint notation including
291    /// lower-case letters, spaces and optional leading `0x`.
292    ///
293    /// ```rust
294    /// # fn main() -> sequoia_openpgp::Result<()> {
295    /// # use sequoia_openpgp as openpgp;
296    /// use openpgp::Fingerprint;
297    ///
298    /// let fp =
299    ///     Fingerprint::from_hex("0123456789ABCDEF0123456789ABCDEF01234567")?;
300    ///
301    /// assert_eq!("0123456789ABCDEF0123456789ABCDEF01234567", fp.to_hex());
302    ///
303    /// let fp =
304    ///     Fingerprint::from_hex("0123 4567 89ab cdef 0123 4567 89ab cdef 0123 4567")?;
305    ///
306    /// assert_eq!("0123456789ABCDEF0123456789ABCDEF01234567", fp.to_hex());
307    /// # Ok(()) }
308    /// ```
309    pub fn from_hex(s: &str) -> std::result::Result<Self, anyhow::Error> {
310        std::str::FromStr::from_str(s)
311    }
312
313    /// Common code for the above functions.
314    fn write_to_fmt(&self, f: &mut fmt::Formatter, upper_case: bool) -> fmt::Result {
315        use std::fmt::Write;
316
317        let raw = self.as_bytes();
318
319        // We currently only handle V4 fingerprints, which look like:
320        //
321        //   8F17 7771 18A3 3DDA 9BA4  8E62 AACB 3243 6300 52D9
322        //
323        // Since we have no idea how to format an invalid fingerprint,
324        // just format it like a V4 fingerprint and hope for the best.
325
326        // XXX: v5 fingerprints have no human-readable formatting by
327        // choice.
328        let a_letter = if upper_case { b'A' } else { b'a' };
329        let pretty = f.alternate();
330
331        for (i, b) in raw.iter().enumerate() {
332            if pretty && i > 0 && i % 2 == 0 {
333                f.write_char(' ')?;
334            }
335
336            if pretty && i > 0 && i * 2 == raw.len() {
337                f.write_char(' ')?;
338            }
339
340            let top = b >> 4;
341            let bottom = b & 0xFu8;
342
343            if top < 10u8 {
344                f.write_char((b'0' + top) as char)?;
345            } else {
346                f.write_char((a_letter + (top - 10u8)) as char)?;
347            }
348
349            if bottom < 10u8 {
350                f.write_char((b'0' + bottom) as char)?;
351            } else {
352                f.write_char((a_letter + (bottom - 10u8)) as char)?;
353            }
354        }
355
356        Ok(())
357    }
358
359    /// Converts the hex representation of the `Fingerprint` to a
360    /// phrase in the [ICAO spelling alphabet].
361    ///
362    ///   [ICAO spelling alphabet]: https://en.wikipedia.org/wiki/ICAO_spelling_alphabet
363    ///
364    /// # Examples
365    ///
366    /// ```rust
367    /// # fn main() -> sequoia_openpgp::Result<()> {
368    /// # use sequoia_openpgp as openpgp;
369    /// use openpgp::Fingerprint;
370    ///
371    /// let fp: Fingerprint =
372    ///     "01AB 4567 89AB CDEF 0123 4567 89AB CDEF 0123 4567".parse()?;
373    ///
374    /// assert!(fp.to_icao().starts_with("Zero One Alfa Bravo"));
375    ///
376    /// # let expected = "\
377    /// # Zero One Alfa Bravo Four Five Six Seven Eight Niner Alfa Bravo \
378    /// # Charlie Delta Echo Foxtrot Zero One Two Three Four Five Six Seven \
379    /// # Eight Niner Alfa Bravo Charlie Delta Echo Foxtrot Zero One Two \
380    /// # Three Four Five Six Seven";
381    /// # assert_eq!(fp.to_icao(), expected);
382    /// #
383    /// # Ok(()) }
384    /// ```
385    pub fn to_icao(&self) -> String {
386        let mut ret = String::default();
387
388        for ch in self.to_hex().chars() {
389            let word = match ch {
390                '0' => "Zero",
391                '1' => "One",
392                '2' => "Two",
393                '3' => "Three",
394                '4' => "Four",
395                '5' => "Five",
396                '6' => "Six",
397                '7' => "Seven",
398                '8' => "Eight",
399                '9' => "Niner",
400                'A' => "Alfa",
401                'B' => "Bravo",
402                'C' => "Charlie",
403                'D' => "Delta",
404                'E' => "Echo",
405                'F' => "Foxtrot",
406                _ => { continue; }
407            };
408
409            if !ret.is_empty() {
410                ret.push(' ');
411            }
412            ret.push_str(word);
413        }
414
415        ret
416    }
417
418    /// Returns whether `self` and `other` could be aliases of each
419    /// other.
420    ///
421    /// `KeyHandle`'s `PartialEq` implementation cannot assert that a
422    /// `Fingerprint` and a `KeyID` are equal, because distinct
423    /// fingerprints may have the same `KeyID`, and `PartialEq` must
424    /// be [transitive], i.e.,
425    ///
426    /// ```text
427    /// a == b and b == c implies a == c.
428    /// ```
429    ///
430    /// [transitive]: std::cmp::PartialEq
431    ///
432    /// That is, if `fpr1` and `fpr2` are distinct fingerprints with the
433    /// same key ID then:
434    ///
435    /// ```text
436    /// fpr1 == keyid and fpr2 == keyid, but fpr1 != fpr2.
437    /// ```
438    ///
439    /// This definition of equality makes searching for a given
440    /// `KeyHandle` using `PartialEq` awkward.  This function fills
441    /// that gap.  It answers the question: given a `KeyHandle` and a
442    /// `Fingerprint`, could they be aliases?  That is, it implements
443    /// the desired, non-transitive equality relation:
444    ///
445    /// ```
446    /// # fn main() -> sequoia_openpgp::Result<()> {
447    /// # use sequoia_openpgp as openpgp;
448    /// # use openpgp::Fingerprint;
449    /// # use openpgp::KeyID;
450    /// # use openpgp::KeyHandle;
451    /// #
452    /// # let fpr1: Fingerprint
453    /// #     = "8F17 7771 18A3 3DDA 9BA4  8E62 AACB 3243 6300 52D9"
454    /// #       .parse::<Fingerprint>()?;
455    /// #
456    /// # let fpr2: Fingerprint
457    /// #     = "0123 4567 8901 2345 6789  0123 AACB 3243 6300 52D9"
458    /// #       .parse::<Fingerprint>()?;
459    /// #
460    /// # let keyid: KeyID = "AACB 3243 6300 52D9".parse::<KeyID>()?;
461    /// #
462    /// // fpr1 and fpr2 are different fingerprints with the same KeyID.
463    /// assert_ne!(fpr1, fpr2);
464    /// assert!(fpr1.aliases(KeyHandle::from(&keyid)));
465    /// assert!(fpr2.aliases(KeyHandle::from(&keyid)));
466    /// assert!(! fpr1.aliases(KeyHandle::from(&fpr2)));
467    /// # Ok(()) }
468    /// ```
469    pub fn aliases<H>(&self, other: H) -> bool
470        where H: Borrow<KeyHandle>
471    {
472        let other = other.borrow();
473
474        match (self, other) {
475            (f, KeyHandle::Fingerprint(o)) => {
476                f == o
477            },
478            (Fingerprint::V4(f), KeyHandle::KeyID(KeyID::Long(o))) => {
479                // Avoid a heap allocation by embedding our
480                // knowledge of how a v4 key ID is derived from a
481                // v4 fingerprint:
482                //
483                // A v4 key ID are the 8 right-most octets of a v4
484                // fingerprint.
485                &f[12..] == o
486            },
487
488            (Fingerprint::V6(f), KeyHandle::KeyID(KeyID::Long(o))) => {
489                // A v6 key ID are the 8 left-most octets of a v6
490                // fingerprint.
491                &f[..8] == o
492            },
493
494            (f, KeyHandle::KeyID(o)) => {
495                &KeyID::from(f) == o
496            },
497        }
498    }
499}
500
501#[cfg(test)]
502impl Fingerprint {
503    pub(crate) fn arbitrary_v4(g: &mut Gen) -> Self {
504        let mut fp = [0; 20];
505        fp.iter_mut().for_each(|p| *p = Arbitrary::arbitrary(g));
506        Fingerprint::V4(fp)
507    }
508
509    pub(crate) fn arbitrary_v6(g: &mut Gen) -> Self {
510        let mut fp = [0; 32];
511        fp.iter_mut().for_each(|p| *p = Arbitrary::arbitrary(g));
512        Fingerprint::V6(fp)
513    }
514}
515
516 #[cfg(test)]
517impl Arbitrary for Fingerprint {
518    fn arbitrary(g: &mut Gen) -> Self {
519        if Arbitrary::arbitrary(g) {
520            Self::arbitrary_v4(g)
521        } else {
522            Self::arbitrary_v6(g)
523        }
524    }
525}
526
527#[cfg(test)]
528mod tests {
529    use super::*;
530
531    #[test]
532    fn v4_hex_formatting() {
533        let fp = "0123 4567 89AB CDEF 0123 4567 89AB CDEF 0123 4567"
534            .parse::<Fingerprint>().unwrap();
535        assert!(matches!(&fp, Fingerprint::V4(_)));
536        assert_eq!(format!("{:X}", fp), "0123456789ABCDEF0123456789ABCDEF01234567");
537        assert_eq!(format!("{:x}", fp), "0123456789abcdef0123456789abcdef01234567");
538    }
539
540    #[test]
541    fn v5_hex_formatting() -> crate::Result<()> {
542        let fp = "0123 4567 89AB CDEF 0123 4567 89AB CDEF \
543                  0123 4567 89AB CDEF 0123 4567 89AB CDEF"
544            .parse::<Fingerprint>()?;
545        assert!(matches!(&fp, Fingerprint::V6(_)));
546        assert_eq!(format!("{:X}", fp), "0123456789ABCDEF0123456789ABCDEF\
547                                         0123456789ABCDEF0123456789ABCDEF");
548        assert_eq!(format!("{:x}", fp), "0123456789abcdef0123456789abcdef\
549                                         0123456789abcdef0123456789abcdef");
550        Ok(())
551    }
552
553    #[test]
554    fn aliases() -> crate::Result<()> {
555        // fp1 and fp15 have the same key ID, but are different
556        // fingerprints.
557        let fp1 = "280C0AB0B94D1302CAAEB71DA299CDCD3884EBEA"
558            .parse::<Fingerprint>()?;
559        let fp15 = "1234567890ABCDEF12345678A299CDCD3884EBEA"
560            .parse::<Fingerprint>()?;
561        let fp2 = "F8D921C01EE93B65D4C6FEB7B456A7DB5E4274D0"
562            .parse::<Fingerprint>()?;
563
564        let keyid1 = KeyID::from(&fp1);
565        let keyid15 = KeyID::from(&fp15);
566        let keyid2 = KeyID::from(&fp2);
567
568        eprintln!("fp1: {:?}", fp1);
569        eprintln!("keyid1: {:?}", keyid1);
570        eprintln!("fp15: {:?}", fp15);
571        eprintln!("keyid15: {:?}", keyid15);
572        eprintln!("fp2: {:?}", fp2);
573        eprintln!("keyid2: {:?}", keyid2);
574
575        assert_ne!(fp1, fp15);
576        assert_eq!(keyid1, keyid15);
577
578        // Compare fingerprints to fingerprints.
579        assert!(fp1.aliases(KeyHandle::from(&fp1)));
580        assert!(! fp1.aliases(KeyHandle::from(&fp15)));
581        assert!(! fp1.aliases(KeyHandle::from(&fp2)));
582
583        assert!(! fp15.aliases(KeyHandle::from(&fp1)));
584        assert!(fp15.aliases(KeyHandle::from(&fp15)));
585        assert!(! fp15.aliases(KeyHandle::from(&fp2)));
586
587        assert!(! fp2.aliases(KeyHandle::from(&fp1)));
588        assert!(! fp2.aliases(KeyHandle::from(&fp15)));
589        assert!(fp2.aliases(KeyHandle::from(&fp2)));
590
591        // Compare fingerprints to key IDs.
592        assert!(fp1.aliases(KeyHandle::from(&keyid1)));
593        assert!(fp1.aliases(KeyHandle::from(&keyid15)));
594        assert!(! fp1.aliases(KeyHandle::from(&keyid2)));
595
596        assert!(fp15.aliases(KeyHandle::from(&keyid1)));
597        assert!(fp15.aliases(KeyHandle::from(&keyid15)));
598        assert!(! fp15.aliases(KeyHandle::from(&keyid2)));
599
600        assert!(! fp2.aliases(KeyHandle::from(&keyid1)));
601        assert!(! fp2.aliases(KeyHandle::from(&keyid15)));
602        assert!(fp2.aliases(KeyHandle::from(&keyid2)));
603
604        Ok(())
605    }
606}