diff options
author | Neal H. Walfield <neal@pep.foundation> | 2017-12-13 12:31:37 +0100 |
---|---|---|
committer | Neal H. Walfield <neal@pep.foundation> | 2017-12-13 14:43:19 +0100 |
commit | ce890346ffb882fa8620f5901e87281fa18dc530 (patch) | |
tree | 96bb9bf02f3575a4726d8e222e8a8867fd8e517d /openpgp | |
parent | c86ad83ae31d44aeab3317e5e05d6d63e428f0e0 (diff) |
Add fingerprint support.
- Export a first-class type for representing fingerprints.
- Provide an implementation to compute fingerprints over public
keys.
Diffstat (limited to 'openpgp')
-rw-r--r-- | openpgp/Cargo.toml | 1 | ||||
-rw-r--r-- | openpgp/src/lib.rs | 74 | ||||
-rw-r--r-- | openpgp/src/parse/key.rs | 92 | ||||
-rw-r--r-- | openpgp/src/parse/parse.rs | 1 |
4 files changed, 168 insertions, 0 deletions
diff --git a/openpgp/Cargo.toml b/openpgp/Cargo.toml index 3189a5b1..142509d4 100644 --- a/openpgp/Cargo.toml +++ b/openpgp/Cargo.toml @@ -9,3 +9,4 @@ base64 = "0.8.0" nom = "3.2.0" num = "0.1.40" num-derive = "0.1.41" +sha1 = "*" diff --git a/openpgp/src/lib.rs b/openpgp/src/lib.rs index f5d1b775..a449ed95 100644 --- a/openpgp/src/lib.rs +++ b/openpgp/src/lib.rs @@ -11,6 +11,8 @@ extern crate num_derive; #[macro_use] extern crate nom; +extern crate sha1; + pub mod armor; pub mod parse; pub mod tpk; @@ -529,3 +531,75 @@ impl<'a> Iterator for PacketIter<'a> { return self.child; } } + +pub enum Fingerprint { + V4([u8;20]), + // Used for holding fingerprints that we don't understand. For + // instance, we don't grok v3 fingerprints. And, it is possible + // that the Issuer subpacket contains the wrong number of bytes. + Invalid(Box<[u8]>) +} + +impl Fingerprint { + /// Reads a binary fingerprint. + pub fn from_bytes(raw: &[u8]) -> Fingerprint { + if raw.len() == 20 { + let mut fp : [u8; 20] = Default::default(); + fp.copy_from_slice(raw); + Fingerprint::V4(fp) + } else { + Fingerprint::Invalid(raw.to_vec().into_boxed_slice()) + } + } + + // Converts the fingerprint to its standard representation. + pub fn to_string(&self) -> String { + let raw = match self { + &Fingerprint::V4(ref fp) => &fp[..], + &Fingerprint::Invalid(ref fp) => &fp[..], + }; + + // We currently only handle V4 fingerprints, which look like: + // + // 8F17 7771 18A3 3DDA 9BA4 8E62 AACB 3243 6300 52D9 + // + // Since we have no idea how to format an invalid fingerprint, + // just format it like a V4 fingerprint and hope for the best. + + let mut output = Vec::with_capacity( + // Each byte results in to hex characters. + raw.len() * 2 + // Every 2 bytes of output, we insert a space. + + raw.len() / 2 + // After 5 groups, there is another space. + + raw.len() / 10); + + for (i, b) in raw.iter().enumerate() { + if i > 0 && i % 2 == 0 { + output.push(' ' as u8); + } + + if i > 0 && i % 10 == 0 { + output.push(' ' as u8); + } + + let top = b >> 4; + let bottom = b & 0xFu8; + + if top < 10u8 { + output.push('0' as u8 + top) + } else { + output.push('A' as u8 + (top - 10u8)) + } + + if bottom < 10u8 { + output.push('0' as u8 + bottom) + } else { + output.push('A' as u8 + (bottom - 10u8)) + } + } + + // We know the content is valid UTF-8. + String::from_utf8(output).unwrap() + } +} diff --git a/openpgp/src/parse/key.rs b/openpgp/src/parse/key.rs new file mode 100644 index 00000000..f34214db --- /dev/null +++ b/openpgp/src/parse/key.rs @@ -0,0 +1,92 @@ +use super::*; +use sha1; + +impl Key { + // Computes and returns the key's fingerprint as per Section 12.2 + // of RFC 4880. + pub fn fingerprint(&self) -> Fingerprint { + let mut m = sha1::Sha1::new(); + + // We hash 8 bytes plus the MPIs. But, the len doesn't + // include the tag (1 byte) or the length (2 bytes). + let len = (9 - 3) + self.mpis.len(); + + let mut header : Vec<u8> = Vec::with_capacity(9); + + // Tag. + header.push(0x99); + + // Length (big endian). + header.push(((len >> 8) & 0xFF) as u8); + header.push((len & 0xFF) as u8); + + // Version. + header.push(4); + + // Creation time. + header.push(((self.creation_time >> 24) & 0xFF) as u8); + header.push(((self.creation_time >> 16) & 0xFF) as u8); + header.push(((self.creation_time >> 8) & 0xFF) as u8); + header.push((self.creation_time & 0xFF) as u8); + + // Algorithm. + header.push(self.pk_algo); + + m.update(&header[..]); + + // MPIs. + m.update(&self.mpis[..]); + + Fingerprint::from_bytes(&m.digest().bytes()[..]) + } +} + +#[cfg(test)] +mod fingerprint_test { + use super::*; + + #[test] + fn fingerprint_test () { + use std::path::PathBuf; + use std::fs::File; + use ::buffered_reader::*; + + let path : PathBuf = [env!("CARGO_MANIFEST_DIR"), + "src", "parse", + "public-key.asc"] + .iter().collect(); + let mut f = File::open(&path).expect(&path.to_string_lossy()); + let bio = BufferedReaderGeneric::new(&mut f, None); + let message = Message::deserialize(bio, None).unwrap(); + + // The blob contains a public key and a three subkeys. + let mut pki = 0; + let mut ski = 0; + + let pks = [ "8F17 7771 18A3 3DDA 9BA4 8E62 AACB 3243 6300 52D9" ]; + let sks = [ "C03F A641 1B03 AE12 5764 6118 7223 B566 78E0 2528", + "50E6 D924 308D BF22 3CFB 510A C2B8 1905 6C65 2598", + "2DC5 0AB5 5BE2 F3B0 4C2D 2CF8 A350 6AFB 820A BD08"]; + + for p in message.iter() { + if let &Packet::PublicKey(ref p) = p { + let fp = p.fingerprint().to_string(); + // eprintln!("PK: {:?}", fp); + + assert!(pki < pks.len()); + assert_eq!(fp, pks[pki]); + pki += 1; + } + + if let &Packet::PublicSubkey(ref p) = p { + let fp = p.fingerprint().to_string(); + // eprintln!("SK: {:?}", fp); + + assert!(ski < sks.len()); + assert_eq!(fp, sks[ski]); + ski += 1; + } + } + assert!(pki == pks.len() && ski == sks.len()); + } +} diff --git a/openpgp/src/parse/parse.rs b/openpgp/src/parse/parse.rs index ed9e41b0..cb17849c 100644 --- a/openpgp/src/parse/parse.rs +++ b/openpgp/src/parse/parse.rs @@ -8,6 +8,7 @@ use super::partial_body::BufferedReaderPartialBodyFilter; use super::*; +pub mod key; /// The default amount of acceptable nesting. Typically, we expect a /// message to looking like: |