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}