summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--openpgp/Cargo.toml1
-rw-r--r--openpgp/src/lib.rs74
-rw-r--r--openpgp/src/parse/key.rs92
-rw-r--r--openpgp/src/parse/parse.rs1
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: