Skip to content

wip: "imprint" fn #541

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 85 additions & 49 deletions src/packet/key/public.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
use std::io::BufRead;

use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
use md5::Md5;
use rand::{CryptoRng, Rng};
use rsa::traits::PublicKeyParts;
use sha1_checked::{Digest, Sha1};

use crate::{
crypto::{self, hash::HashAlgorithm, public_key::PublicKeyAlgorithm},
errors::{bail, ensure, ensure_eq, unimplemented_err, unsupported_err, Result},
errors::{bail, ensure, ensure_eq, format_err, unimplemented_err, unsupported_err, Result},
packet::{PacketHeader, Signature, SignatureConfig, SignatureType, Subpacket, SubpacketData},
ser::Serialize,
types::{
Expand Down Expand Up @@ -612,29 +610,38 @@ impl crate::packet::PacketTrait for PublicSubkey {
}
}

impl KeyDetails for PubKeyInner {
fn version(&self) -> KeyVersion {
self.version
}
impl PubKeyInner {
/// An imprint of a public key packet is a generalisation of a fingerprint.
///
/// It is calculated in the same way as the fingerprint, except that it MAY use a
/// digest algorithm other than the one specified for the fingerprint.
///
/// See https://www.ietf.org/archive/id/draft-ietf-openpgp-replacementkey-03.html#name-key-imprints
///
/// Note that imprints are intended as a special purpose tool. For most use cases, the OpenPGP
/// fingerprint is the most appropriate identifier for a certificate or a component key.
///
/// For [HashAlgorithm::Sha1], rPGP uses [sha1_checked](https://crates.io/crates/sha1-checked)
/// with collision detection.
fn imprint(&self, h: HashAlgorithm) -> Result<Vec<u8>> {
// this should only error for unknown hash algorithms or key versions

let mut hasher = h.new_hasher()?;

fn fingerprint(&self) -> Fingerprint {
use crate::ser::Serialize;

match self.version {
KeyVersion::V2 | KeyVersion::V3 => {
let mut h = Md5::new();
self.public_params
.to_writer(&mut h)
.expect("write to hasher");
let digest = h.finalize();
let mut v: Vec<u8> = Vec::new();
self.public_params.to_writer(&mut v).expect("write to vec");

if self.version == KeyVersion::V2 {
Fingerprint::V2(digest.into())
} else {
Fingerprint::V3(digest.into())
}
hasher.update(&v);

Ok(hasher.finalize().to_vec())
}
KeyVersion::V4 => {
hasher.update(&[0x99]);

// A one-octet version number (4).
let mut packet = vec![4, 0, 0, 0, 0];

Expand All @@ -643,60 +650,89 @@ impl KeyDetails for PubKeyInner {

// A one-octet number denoting the public-key algorithm of this key.
packet.push(self.algorithm.into());

self.public_params
.to_writer(&mut packet)
.expect("write to vec");

let mut h = Sha1::new();
h.update([0x99]);
h.write_u16::<BigEndian>(packet.len() as u16)
.expect("write to hasher");
h.update(&packet);

let digest = h.finalize();
hasher.update(&(packet.len() as u16).to_be_bytes());
hasher.update(&packet);

Fingerprint::V4(digest.into())
Ok(hasher.finalize().to_vec())
}
KeyVersion::V5 => unimplemented!("V5 keys"),
KeyVersion::V5 => Err(format_err!("Imprint unsupported for V5 keys")),
KeyVersion::V6 => {
// Serialize public parameters
let mut pp: Vec<u8> = vec![];
self.public_params
.to_writer(&mut pp)
.expect("serialize to Vec<u8>");

// A v6 fingerprint is the 256-bit SHA2-256 hash of:
let mut h = sha2::Sha256::new();

// a.1) 0x9B (1 octet)
h.update([0x9B]);
hasher.update(&[0x9B]);

// length of public parameters
let len = self.public_params.write_len() as u32;

// a.2) four-octet scalar octet count of (b)-(f)
let total_len: u32 = 1 + 4 + 1 + 4 + pp.len() as u32;
h.write_u32::<BigEndian>(total_len)
.expect("write to hasher");
let total_len: u32 = 1 + 4 + 1 + 4 + len;
hasher.update(&total_len.to_be_bytes());

// b) version number = 6 (1 octet);
h.update([0x06]);
hasher.update(&[0x06]);

// c) timestamp of key creation (4 octets);
h.write_u32::<BigEndian>(self.created_at.timestamp() as u32)
.expect("write to hasher");
hasher.update(&(self.created_at.timestamp() as u32).to_be_bytes());

// d) algorithm (1 octet);
h.update([self.algorithm.into()]);
hasher.update(&[self.algorithm.into()]);

// e) four-octet scalar octet count for the following key material;
h.write_u32::<BigEndian>(pp.len() as u32)
.expect("write to hasher");
hasher.update(&len.to_be_bytes());

// f) algorithm-specific fields.
h.update(&pp);
let mut pp: Vec<u8> = vec![];
self.public_params.to_writer(&mut pp).expect("write to vec");
hasher.update(&pp);

let digest = h.finalize();
Ok(hasher.finalize().to_vec())
}
KeyVersion::Other(v) => Err(format_err!("Imprint unsupported for {} keys", v)),
}
}
}

impl KeyDetails for PubKeyInner {
fn version(&self) -> KeyVersion {
self.version
}

Fingerprint::V6(digest.into())
fn fingerprint(&self) -> Fingerprint {
match self.version {
KeyVersion::V2 | KeyVersion::V3 => {
if self.version == KeyVersion::V2 {
Fingerprint::V2(
self.imprint(HashAlgorithm::Md5)
.expect("failure is not an option")
.try_into()
.expect("Md5 is 16 bytes"),
)
} else {
Fingerprint::V3(
self.imprint(HashAlgorithm::Md5)
.expect("failure is not an option")
.try_into()
.expect("Md5 is 16 bytes"),
)
}
}
KeyVersion::V4 => Fingerprint::V4(
self.imprint(HashAlgorithm::Sha1)
.expect("failure is not an option")
.try_into()
.expect("Sha1 is 20 bytes"),
),
KeyVersion::V5 => unimplemented!("V5 keys"),
KeyVersion::V6 => Fingerprint::V6(
self.imprint(HashAlgorithm::Sha256)
.expect("failure is not an option")
.try_into()
.expect("Sha256 is 32 bytes"),
),
KeyVersion::Other(v) => unimplemented!("Unsupported key version {}", v),
}
}
Expand Down
Loading