From c6bcc4141df0a8b3eae5c024a9d59c23efed560b Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Wed, 5 Apr 2023 17:54:06 +0200 Subject: openpgp: Add support for v6 OPS packets, inline-signed messages. todo: - finish facade, at least set_last --- openpgp/src/packet/header/mod.rs | 4 +- openpgp/src/packet/mod.rs | 108 +++++++++++++---- openpgp/src/packet/one_pass_sig.rs | 9 +- openpgp/src/packet/one_pass_sig/v6.rs | 222 ++++++++++++++++++++++++++++++++++ openpgp/src/packet/prelude.rs | 1 + openpgp/src/parse.rs | 221 +++++++++++++++++++++++++++++---- openpgp/src/parse/hashed_reader.rs | 133 +++++++++++++------- openpgp/src/parse/stream.rs | 4 +- openpgp/src/serialize.rs | 57 +++++++++ openpgp/src/serialize/stream.rs | 64 ++++++++-- 10 files changed, 717 insertions(+), 106 deletions(-) create mode 100644 openpgp/src/packet/one_pass_sig/v6.rs diff --git a/openpgp/src/packet/header/mod.rs b/openpgp/src/packet/header/mod.rs index d0df418b..61eab916 100644 --- a/openpgp/src/packet/header/mod.rs +++ b/openpgp/src/packet/header/mod.rs @@ -181,7 +181,9 @@ impl Header { // 10 bytes of fixed header, plus the // encrypted session key. 10 < l && l < 10 * 1024, - Tag::OnePassSig if ! future_compatible => l == 13, + Tag::OnePassSig if ! future_compatible => + l == 13 // v3 + || (6 + 32..6 + 32 + 256).contains(&l), // v6 Tag::OnePassSig => l < 1024, Tag::PublicKey | Tag::PublicSubkey | Tag::SecretKey | Tag::SecretSubkey => diff --git a/openpgp/src/packet/mod.rs b/openpgp/src/packet/mod.rs index 883b2792..21c192f5 100644 --- a/openpgp/src/packet/mod.rs +++ b/openpgp/src/packet/mod.rs @@ -168,9 +168,17 @@ pub use container::Body; pub mod prelude; -use crate::crypto::{ - KeyPair, - Password, +use crate::{ + crypto::{ + KeyPair, + Password, + }, + types::{ + SignatureType, + PublicKeyAlgorithm, + HashAlgorithm, + }, + KeyHandle, }; mod any; @@ -451,7 +459,8 @@ impl Deref for Packet { match self { Packet::Unknown(ref packet) => &packet.common, Packet::Signature(ref packet) => &packet.common, - Packet::OnePassSig(ref packet) => &packet.common, + Packet::OnePassSig(OnePassSig::V3(ref packet)) => &packet.common, + Packet::OnePassSig(OnePassSig::V6(ref packet)) => &packet.common.common, Packet::PublicKey(ref packet) => &packet.common, Packet::PublicSubkey(ref packet) => &packet.common, Packet::SecretKey(ref packet) => &packet.common, @@ -478,7 +487,8 @@ impl DerefMut for Packet { match self { Packet::Unknown(ref mut packet) => &mut packet.common, Packet::Signature(ref mut packet) => &mut packet.common, - Packet::OnePassSig(ref mut packet) => &mut packet.common, + Packet::OnePassSig(OnePassSig::V3(ref mut packet)) => &mut packet.common, + Packet::OnePassSig(OnePassSig::V6(ref mut packet)) => &mut packet.common.common, Packet::PublicKey(ref mut packet) => &mut packet.common, Packet::PublicSubkey(ref mut packet) => &mut packet.common, Packet::SecretKey(ref mut packet) => &mut packet.common, @@ -1091,6 +1101,8 @@ impl DerefMut for Signature { pub enum OnePassSig { /// OnePassSig packet version 3. V3(self::one_pass_sig::OnePassSig3), + /// OnePassSig packet version 6. + V6(self::one_pass_sig::OnePassSig6), } assert_send_and_sync!(OnePassSig); @@ -1099,6 +1111,71 @@ impl OnePassSig { pub fn version(&self) -> u8 { match self { OnePassSig::V3(_) => 3, + OnePassSig::V6(_) => 6, + } + } + + /// Gets the signature type. + pub fn typ(&self) -> SignatureType { + match self { + OnePassSig::V3(p) => p.typ(), + OnePassSig::V6(p) => p.typ(), + } + } + + /// Gets the public key algorithm. + pub fn pk_algo(&self) -> PublicKeyAlgorithm { + match self { + OnePassSig::V3(p) => p.pk_algo(), + OnePassSig::V6(p) => p.pk_algo(), + } + } + + /// Gets the hash algorithm. + pub fn hash_algo(&self) -> HashAlgorithm { + match self { + OnePassSig::V3(p) => p.hash_algo(), + OnePassSig::V6(p) => p.hash_algo(), + } + } + + /// Gets the salt, if any. + pub fn salt(&self) -> Option<&[u8]> { + match self { + OnePassSig::V3(_) => None, + OnePassSig::V6(p) => Some(p.salt()), + } + } + + /// Gets the issuer. + pub fn issuer(&self) -> KeyHandle { + match self { + OnePassSig::V3(p) => p.issuer().into(), + OnePassSig::V6(p) => p.issuer().into(), + } + } + + /// Gets the last flag. + pub fn last(&self) -> bool { + match self { + OnePassSig::V3(p) => p.last(), + OnePassSig::V6(p) => p.last(), + } + } + + /// Sets the last flag. + pub fn set_last(&mut self, last: bool) -> bool { + match self { + OnePassSig::V3(p) => p.set_last(last), + OnePassSig::V6(p) => p.set_last(last), + } + } + + /// Gets the raw value of the last flag. + pub fn last_raw(&self) -> u8 { + match self { + OnePassSig::V3(p) => p.last_raw(), + OnePassSig::V6(p) => p.last_raw(), } } } @@ -1115,28 +1192,9 @@ impl<'a> std::convert::TryFrom<&'a Signature> for OnePassSig { fn try_from(s: &'a Signature) -> Result { match s.version() { 4 => one_pass_sig::OnePassSig3::try_from(s).map(Into::into), + 6 => one_pass_sig::OnePassSig6::try_from(s).map(Into::into), n => Err(Error::InvalidOperation( format!("Unsupported signature version {}", n)).into()), - } - } -} - -// Trivial forwarder for singleton enum. -impl Deref for OnePassSig { - type Target = one_pass_sig::OnePassSig3; - - fn deref(&self) -> &Self::Target { - match self { - OnePassSig::V3(ops) => ops, - } - } -} - -// Trivial forwarder for singleton enum. -impl DerefMut for OnePassSig { - fn deref_mut(&mut self) -> &mut Self::Target { - match self { - OnePassSig::V3(ref mut ops) => ops, } } } diff --git a/openpgp/src/packet/one_pass_sig.rs b/openpgp/src/packet/one_pass_sig.rs index 14bad636..19242e40 100644 --- a/openpgp/src/packet/one_pass_sig.rs +++ b/openpgp/src/packet/one_pass_sig.rs @@ -19,6 +19,9 @@ use crate::HashAlgorithm; use crate::PublicKeyAlgorithm; use crate::SignatureType; +mod v6; +pub use v6::OnePassSig6; + /// Holds a one-pass signature packet. /// /// See [Section 5.4 of RFC 4880] for details. @@ -172,7 +175,11 @@ impl<'a> std::convert::TryFrom<&'a Signature> for OnePassSig3 { #[cfg(test)] impl Arbitrary for super::OnePassSig { fn arbitrary(g: &mut Gen) -> Self { - OnePassSig3::arbitrary(g).into() + if Arbitrary::arbitrary(g) { + OnePassSig3::arbitrary(g).into() + } else { + OnePassSig6::arbitrary(g).into() + } } } diff --git a/openpgp/src/packet/one_pass_sig/v6.rs b/openpgp/src/packet/one_pass_sig/v6.rs new file mode 100644 index 00000000..7fc83b2e --- /dev/null +++ b/openpgp/src/packet/one_pass_sig/v6.rs @@ -0,0 +1,222 @@ +//! OpenPGP v6 signature implementation. + +use std::convert::TryFrom; +use std::fmt; +use std::mem; + +#[cfg(test)] +use quickcheck::{Arbitrary, Gen}; + +use crate::{ + Error, + Fingerprint, + HashAlgorithm, + Packet, + PublicKeyAlgorithm, + Result, + SignatureType, + packet::{ + Signature, + OnePassSig, + one_pass_sig::{ + OnePassSig3, + }, + }, +}; + +/// Holds a v6 OnePassSig packet. +/// +/// This holds a [version 6] OnePassSig packet. Normally, you won't +/// directly work with this data structure, but with the [`OnePassSig`] +/// enum, which is version agnostic. An exception is when you need to +/// do version-specific operations. But currently, there aren't any +/// version-specific methods. +/// +/// [version 6]: https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-08.html#name-one-pass-signature-packets- +/// [`OnePassSig`]: crate::packet::OnePassSig +#[derive(PartialEq, Eq, Hash, Clone)] +pub struct OnePassSig6 { + pub(crate) common: OnePassSig3, + salt: Vec, + issuer: Fingerprint, +} +assert_send_and_sync!(OnePassSig6); + +impl TryFrom for OnePassSig6 { + type Error = anyhow::Error; + + fn try_from(ops: OnePassSig) -> Result { + match ops { + OnePassSig::V6(ops) => Ok(ops), + ops => Err( + Error::InvalidArgument( + format!( + "Got a v{}, require a v6 one-pass signature", + ops.version())) + .into()), + } + } +} + +impl fmt::Debug for OnePassSig6 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("OnePassSig6") + .field("typ", &self.typ()) + .field("hash_algo", &self.hash_algo()) + .field("pk_algo", &self.pk_algo()) + .field("salt", &crate::fmt::hex::encode(self.salt())) + .field("issuer", &self.issuer()) + .field("last", &self.last()) + .finish() + } +} + +impl OnePassSig6 { + /// Returns a new One-Pass Signature packet. + pub fn new(typ: SignatureType, issuer: Fingerprint) -> Self { + OnePassSig6 { + common: OnePassSig3::new(typ), + salt: vec![], + issuer, + } + } + + /// Gets the signature type. + pub fn typ(&self) -> SignatureType { + self.common.typ() + } + + /// Sets the signature type. + pub fn set_type(&mut self, t: SignatureType) -> SignatureType { + self.common.set_type(t) + } + + /// Gets the public key algorithm. + pub fn pk_algo(&self) -> PublicKeyAlgorithm { + self.common.pk_algo() + } + + /// Sets the public key algorithm. + pub fn set_pk_algo(&mut self, algo: PublicKeyAlgorithm) -> PublicKeyAlgorithm { + self.common.set_pk_algo(algo) + } + + /// Gets the hash algorithm. + pub fn hash_algo(&self) -> HashAlgorithm { + self.common.hash_algo() + } + + /// Sets the hash algorithm. + pub fn set_hash_algo(&mut self, algo: HashAlgorithm) -> HashAlgorithm { + self.common.set_hash_algo(algo) + } + + /// Gets the salt. + pub fn salt(&self) -> &[u8] { + &self.salt + } + + /// Sets the salt. + pub fn set_salt(&mut self, salt: Vec) -> Vec { + mem::replace(&mut self.salt, salt) + } + + /// Gets the issuer. + pub fn issuer(&self) -> &Fingerprint { + &self.issuer + } + + /// Sets the issuer. + pub fn set_issuer(&mut self, issuer: Fingerprint) -> Fingerprint { + mem::replace(&mut self.issuer, issuer) + } + + /// Gets the last flag. + pub fn last(&self) -> bool { + self.common.last() + } + + /// Sets the last flag. + pub fn set_last(&mut self, last: bool) -> bool { + self.common.set_last(last) + } + + /// Gets the raw value of the last flag. + pub fn last_raw(&self) -> u8 { + self.common.last_raw() + } + + /// Sets the raw value of the last flag. + pub fn set_last_raw(&mut self, last: u8) -> u8 { + self.common.set_last_raw(last) + } +} + +impl From for OnePassSig { + fn from(s: OnePassSig6) -> Self { + OnePassSig::V6(s) + } +} + +impl From for Packet { + fn from(p: OnePassSig6) -> Self { + OnePassSig::from(p).into() + } +} + +impl<'a> std::convert::TryFrom<&'a Signature> for OnePassSig6 { + type Error = anyhow::Error; + + fn try_from(s: &'a Signature) -> Result { + let s = if let Signature::V6(s) = s { + s + } else { + return Err(Error::InvalidArgument(format!( + "Can not derive a v6 OnePassSig from a v{} Signature", + s.version())).into()); + }; + + let issuer = match s.issuer_fingerprints().next() { + Some(i) => i.clone(), + None => + return Err(Error::InvalidArgument( + "Signature has no issuer fingerprints".into()).into()), + }; + let mut common = OnePassSig3::new(s.typ()); + common.set_hash_algo(s.hash_algo()); + common.set_pk_algo(s.pk_algo()); + Ok(OnePassSig6 { + common, + salt: s.salt().to_vec(), + issuer, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::arbitrary_helper::arbitrary_bounded_vec; + use crate::parse::Parse; + use crate::serialize::MarshalInto; + + impl Arbitrary for OnePassSig6 { + fn arbitrary(g: &mut Gen) -> Self { + let mut ops = OnePassSig6::new(SignatureType::arbitrary(g), + Fingerprint::arbitrary_v6(g)); + ops.set_hash_algo(HashAlgorithm::arbitrary(g)); + ops.set_pk_algo(PublicKeyAlgorithm::arbitrary(g)); + ops.set_last_raw(u8::arbitrary(g)); + ops.set_salt(arbitrary_bounded_vec(g, 256)); + ops + } + } + + quickcheck! { + fn roundtrip(p: OnePassSig6) -> bool { + let q = OnePassSig6::from_bytes(&p.to_vec().unwrap()).unwrap(); + assert_eq!(p, q); + true + } + } +} diff --git a/openpgp/src/packet/prelude.rs b/openpgp/src/packet/prelude.rs index 5c3d7782..c433dda9 100644 --- a/openpgp/src/packet/prelude.rs +++ b/openpgp/src/packet/prelude.rs @@ -48,6 +48,7 @@ pub use crate::packet::{ key::Key6, key::SecretKeyMaterial, one_pass_sig::OnePassSig3, + one_pass_sig::OnePassSig6, pkesk::PKESK3, seip::SEIP1, signature, diff --git a/openpgp/src/parse.rs b/openpgp/src/parse.rs index b168c5f0..e7a72920 100644 --- a/openpgp/src/parse.rs +++ b/openpgp/src/parse.rs @@ -846,12 +846,25 @@ pub(crate) struct SignatureGroup { ops_count: usize, /// The hash contexts. + /// + /// We store a salt and the hash context as tuples. + /// + /// In v6, the hash is salted. We store the salt here so that we + /// can find the right hash context again when we encounter the + /// signature packet. + /// + /// In v4, the hash is not salted. Hence, salt is the zero-length + /// vector. The fact that the hash is not salted allows for an + /// optimization: to verify two signatures using the same hash + /// algorithm, the hash must be computed just once. We implement + /// this optimization for v4 signatures. pub(crate) hashes: Vec>>, } impl fmt::Debug for SignatureGroup { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let algos = self.hashes.iter().map(|mode| mode.map(|ctx| ctx.algo())) + let algos = self.hashes.iter() + .map(|mode| mode.map(|ctx| ctx.algo())) .collect::>(); f.debug_struct("Cookie") @@ -1405,7 +1418,7 @@ impl Signature { /// When parsing an inline-signed message, attaches the digest to /// the signature. fn parse_finish(indent: isize, mut pp: PacketParser, - typ: SignatureType, hash_algo: HashAlgorithm) + hash_algo: HashAlgorithm) -> Result { tracer!(TRACE, "Signature::parse_finish", indent); @@ -1424,7 +1437,7 @@ impl Signature { return Ok(pp); } - let need_hash = HashingMode::for_signature(hash_algo, typ); + let need_hash = HashingMode::for_signature(hash_algo, sig); t!("Need a {:?}", need_hash); if TRACE { pp.reader.dump(&mut std::io::stderr())?; @@ -1470,10 +1483,15 @@ impl Signature { if cookie.hashes_for == HashesFor::Signature || cookie.hashes_for == HashesFor::CleartextSignature { + t!("Have: {:?}", + cookie.sig_group().hashes.iter() + .map(|h| h.map(|h| h.algo())) + .collect::>()); if let Some(hash) = cookie.sig_group().hashes.iter().find_map( |mode| - if mode.map(|ctx| ctx.algo()) == need_hash { + if mode.map(|ctx| ctx.algo()) == need_hash + { Some(mode.as_ref()) } else { None @@ -1555,7 +1573,7 @@ impl Signature6 { mpis)); let pp = php.ok(sig.into())?; - Signature::parse_finish(indent, pp, typ, hash_algo) + Signature::parse_finish(indent, pp, hash_algo) } } @@ -1599,7 +1617,7 @@ impl Signature4 { [digest_prefix1, digest_prefix2], mpis).into()))?; - Signature::parse_finish(indent, pp, typ, hash_algo) + Signature::parse_finish(indent, pp, hash_algo) } /// Returns whether the data appears to be a signature (no promises). @@ -1687,7 +1705,7 @@ impl Signature3 { [digest_prefix1, digest_prefix2], mpis).into()))?; - Signature::parse_finish(indent, pp, typ, hash_algo) + Signature::parse_finish(indent, pp, hash_algo) } } @@ -2082,6 +2100,7 @@ impl OnePassSig { let version = php_try!(php.parse_u8("version")); match version { 3 => OnePassSig3::parse(php), + 6 => OnePassSig6::parse(php), _ => { t!("Ignoring version {} packet", version); @@ -2116,7 +2135,7 @@ impl OnePassSig3 { sig.set_pk_algo(pk_algo.into()); sig.set_issuer(KeyID::from_bytes(&issuer)); sig.set_last_raw(last); - let need_hash = HashingMode::for_signature(hash_algo, typ); + let need_hash = HashingMode::for_salt_and_type(hash_algo, &[], typ); let recursion_depth = php.recursion_depth(); @@ -2160,7 +2179,8 @@ impl OnePassSig3 { { if let Ok(ctx) = hash_algo.context() { cookie.sig_group_mut().hashes.push( - HashingMode::for_signature(Box::new(ctx), typ) + HashingMode::for_salt_and_type( + Box::new(ctx), &[], typ) ); } } @@ -2196,7 +2216,7 @@ impl OnePassSig3 { // it explicitly. let mut algos = Vec::new(); if pp.state.settings.automatic_hashing && hash_algo.is_supported() { - algos.push(need_hash); + algos.push(HashingMode::for_salt_and_type(hash_algo, &[], typ)); } // We can't push the HashedReader on the BufferedReader stack: @@ -2293,7 +2313,9 @@ impl PacketParser<'_> { let hash_algo = ops.hash_algo(); let typ = ops.typ(); - let need_hash = HashingMode::for_signature(hash_algo, typ); + let salt = ops.salt(); + let need_hash = HashingMode::for_salt_and_type( + hash_algo, salt.unwrap_or(&[]), typ); let recursion_depth = self.recursion_depth(); let want_hashes_for = if Cookie::processing_csf_message(&self.reader) { HashesFor::CleartextSignature @@ -2323,8 +2345,9 @@ impl PacketParser<'_> { }) { cookie.sig_group_mut().hashes.push( - HashingMode::for_signature( - Box::new(hash_algo.context()?), typ)); + HashingMode::for_salt_and_type( + Box::new(hash_algo.context()?), + salt.unwrap_or(&[]), typ)); } break; } @@ -2340,7 +2363,7 @@ impl PacketParser<'_> { } #[test] -fn one_pass_sig_parser_test () { +fn one_pass_sig3_parser_test () { use crate::SignatureType; use crate::PublicKeyAlgorithm; @@ -2365,12 +2388,164 @@ fn one_pass_sig_parser_test () { impl_parse_with_buffered_reader!( OnePassSig3, |reader| -> Result { - OnePassSig::from_reader(reader).map(|p| match p { - OnePassSig::V3(p) => p, - // XXX: Once we have a second variant. - // - // p => Err(Error::InvalidOperation( - // format!("Not a OnePassSig::V3 packet: {:?}", p)).into()), + OnePassSig::from_reader(reader).and_then(|p| match p { + OnePassSig::V3(p) => Ok(p), + p => Err(Error::InvalidOperation( + format!("Not a OnePassSig::V3 packet: {:?}", p)).into()), + }) + }); + +impl OnePassSig6 { + #[allow(clippy::blocks_in_if_conditions)] + fn parse(mut php: PacketHeaderParser) -> Result { + let indent = php.recursion_depth(); + tracer!(TRACE, "OnePassSig6", indent); + + make_php_try!(php); + + let typ = php_try!(php.parse_u8("type")); + let hash_algo = php_try!(php.parse_u8("hash_algo")); + let pk_algo = php_try!(php.parse_u8("pk_algo")); + let salt_len = php_try!(php.parse_u8("salt_len")); + let salt = php_try!(php.parse_bytes("salt", salt_len.into())); + let mut issuer = [0u8; 32]; + issuer.copy_from_slice(&php_try!(php.parse_bytes("issuer", 32))); + let last = php_try!(php.parse_u8("last")); + + let hash_algo = hash_algo.into(); + let typ = typ.into(); + let mut sig = OnePassSig6::new(typ, Fingerprint::from_bytes(&issuer)); + sig.set_salt(salt.clone()); + sig.set_hash_algo(hash_algo); + sig.set_pk_algo(pk_algo.into()); + sig.set_last_raw(last); + + let recursion_depth = php.recursion_depth(); + + // Check if we are processing a cleartext signed message. + let want_hashes_for = if Cookie::processing_csf_message(&php.reader) { + HashesFor::CleartextSignature + } else { + HashesFor::Signature + }; + + // Walk up the reader chain to see if there is already a + // hashed reader on level recursion_depth - 1. + let done = { + let mut done = false; + let mut reader : Option<&mut dyn BufferedReader> + = Some(&mut php.reader); + while let Some(r) = reader { + { + let cookie = r.cookie_mut(); + if let Some(br_level) = cookie.level { + if br_level < recursion_depth - 1 { + break; + } + if br_level == recursion_depth - 1 + && cookie.hashes_for == want_hashes_for { + // We found a suitable hashed reader. + if cookie.saw_last { + cookie.sig_group_push(); + cookie.saw_last = false; + cookie.hash_stash = + Some(php.header_bytes.clone()); + } + + // Make sure that it uses the required + // hash algorithm. + if php.state.settings.automatic_hashing + { + if let Ok(ctx) = hash_algo.context() { + cookie.sig_group_mut().hashes.push( + HashingMode::for_salt_and_type( + Box::new(ctx), &salt, typ) + ); + } + } + + // Account for this OPS packet. + cookie.sig_group_mut().ops_count += 1; + + // Keep track of the last flag. + cookie.saw_last = last > 0; + + // We're done. + done = true; + break; + } + } else { + break; + } + } + reader = r.get_mut(); + } + done + }; + // Commit here after potentially pushing a signature group. + let mut pp = php.ok(Packet::OnePassSig(sig.into()))?; + if done { + return Ok(pp); + } + + // We create an empty hashed reader even if we don't support + // the hash algorithm so that we have something to match + // against when we get to the Signature packet. + let mut algos = Vec::new(); + if pp.state.settings.automatic_hashing && hash_algo.is_supported() { + algos.push(HashingMode::for_salt_and_type(hash_algo, &salt, typ)); + } + + // We can't push the HashedReader on the BufferedReader stack: + // when we finish processing this OnePassSig packet, it will + // be popped. Instead, we need to insert it at the next + // higher level. Unfortunately, this isn't possible. But, + // since we're done reading the current packet, we can pop the + // readers associated with it, and then push the HashedReader. + // This is a bit of a layering violation, but I (Neal) can't + // think of a more elegant solution. + + assert!(pp.reader.cookie_ref().level <= Some(recursion_depth)); + let (fake_eof, reader) + = buffered_reader_stack_pop(Box::new(pp.take_reader()), + recursion_depth)?; + // We only pop the buffered readers for the OPS, and we + // (currently) never use a fake eof for OPS packets. + assert!(! fake_eof); + + let mut reader = HashedReader::new( + reader, want_hashes_for, algos)?; + reader.cookie_mut().level = Some(recursion_depth - 1); + // Account for this OPS packet. + reader.cookie_mut().sig_group_mut().ops_count += 1; + // Keep track of the last flag. + reader.cookie_mut().saw_last = last > 0; + + t!("Pushed a hashed reader, level {:?}", reader.cookie_mut().level); + + // We add an empty limitor on top of the hashed reader, + // because when we are done processing a packet, + // PacketParser::finish discards any unread data from the top + // reader. Since the top reader is the HashedReader, this + // discards any following packets. To prevent this, we push a + // Limitor on the reader stack. + let mut reader = buffered_reader::Limitor::with_cookie( + reader, 0, Cookie::default()); + reader.cookie_mut().level = Some(recursion_depth); + + pp.reader = Box::new(reader); + + Ok(pp) + } +} + +impl_parse_with_buffered_reader!( + OnePassSig6, + |reader| -> Result { + OnePassSig::from_reader(reader).and_then(|p| match p { + OnePassSig::V6(p) => Ok(p), + p => Err(Error::InvalidOperation( + format!("Not a OnePassSig::V6 packet: {:?}", p)).into()), }) }); @@ -3317,8 +3492,8 @@ impl MDC { let h = state.sig_group_mut().hashes .iter_mut().find_map( |mode| - if mode.map(|ctx| ctx.algo()) == - HashingMode::Binary(HashAlgorithm::SHA1) + if matches!(mode.map(|ctx| ctx.algo()), + HashingMode::Binary(_, HashAlgorithm::SHA1)) { Some(mode.as_mut()) } else { @@ -5877,7 +6052,7 @@ impl<'a> PacketParser<'a> { // And the hasher. let mut reader = HashedReader::new( reader, HashesFor::MDC, - vec![HashingMode::Binary(HashAlgorithm::SHA1)])?; + vec![HashingMode::Binary(vec![], HashAlgorithm::SHA1)])?; reader.cookie_mut().level = Some(self.recursion_depth()); t!("Pushing HashedReader, level {:?}.", diff --git a/openpgp/src/parse/hashed_reader.rs b/openpgp/src/parse/hashed_reader.rs index 7668fecd..1335c7c0 100644 --- a/openpgp/src/parse/hashed_reader.rs +++ b/openpgp/src/parse/hashed_reader.rs @@ -7,6 +7,8 @@ use buffered_reader::BufferedReader; use buffered_reader::buffered_reader_generic_read_impl; use crate::crypto::hash::Digest; +use crate::fmt::hex; +use crate::packet::Signature; use crate::parse::{Cookie, HashesFor, Hashing}; use crate::Result; use crate::types::HashAlgorithm; @@ -23,25 +25,34 @@ pub(crate) enum HashingMode { /// Hash for a binary signature. /// /// The data is hashed as-is. - Binary(T), + Binary(Vec, T), /// Hash for a text signature. /// /// The data is hashed with line endings normalized to `\r\n`. - Text(T), + Text(Vec, T), /// Like Text, but the last character that we hashed was a '\r' /// that we converted to a '\r\n'. - TextLastWasCr(T), + TextLastWasCr(Vec, T), } impl std::fmt::Debug for HashingMode { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { use self::HashingMode::*; match self { - Binary(t) => write!(f, "Binary({:?})", t), - Text(t) => write!(f, "Text({:?})", t), - TextLastWasCr(t) => write!(f, "Text(last was CR, {:?})", t), + Binary(salt, t) if salt.is_empty() => + write!(f, "Binary({:?})", t), + Binary(salt, t) => + write!(f, "Binary({}, {:?})", hex::encode(salt), t), + Text(salt, t) if salt.is_empty() => + write!(f, "Text({:?})", t), + Text(salt, t) => + write!(f, "Text({}, {:?})", hex::encode(salt), t), + TextLastWasCr(salt, t) if salt.is_empty() => + write!(f, "Text(last was CR, {:?})", t), + TextLastWasCr(salt, t) => + write!(f, "Text(last was CR, {}, {:?})", hex::encode(salt), t), } } } @@ -50,12 +61,17 @@ impl PartialEq for HashingMode { fn eq(&self, other: &Self) -> bool { use self::HashingMode::*; match (self, other) { - (Binary(s), Binary(o)) => s.eq(o), - - (Text(s), Text(o)) => s.eq(o), - (TextLastWasCr(s), Text(o)) => s.eq(o), - (Text(s), TextLastWasCr(o)) => s.eq(o), - (TextLastWasCr(s), TextLastWasCr(o)) => s.eq(o), + (Binary(salt_s, s), Binary(salt_o, o)) => + salt_s == salt_o && s == o, + + (Text(salt_s, s), Text(salt_o, o)) => + salt_s == salt_o && s == o, + (TextLastWasCr(salt_s, s), Text(salt_o, o)) => + salt_s == salt_o && s == o, + (Text(salt_s, s), TextLastWasCr(salt_o, o)) => + salt_s == salt_o && s == o, + (TextLastWasCr(salt_s, s), TextLastWasCr(salt_o, o)) => + salt_s == salt_o && s == o, _ => false, } @@ -66,9 +82,9 @@ impl HashingMode { pub(crate) fn map U>(&self, f: F) -> HashingMode { use self::HashingMode::*; match self { - Binary(t) => Binary(f(t)), - Text(t) => Text(f(t)), - TextLastWasCr(t) => TextLastWasCr(f(t)), + Binary(salt, t) => Binary(salt.clone(), f(t)), + Text(salt, t) => Text(salt.clone(), f(t)), + TextLastWasCr(salt, t) => TextLastWasCr(salt.clone(), f(t)), } } @@ -76,44 +92,62 @@ impl HashingMode { -> Result> { use self::HashingMode::*; match self { - Binary(t) => Ok(Binary(f(t)?)), - Text(t) => Ok(Text(f(t)?)), - TextLastWasCr(t) => Ok(TextLastWasCr(f(t)?)), + Binary(salt, t) => Ok(Binary(salt.clone(), f(t)?)), + Text(salt, t) => Ok(Text(salt.clone(), f(t)?)), + TextLastWasCr(salt, t) => Ok(TextLastWasCr(salt.clone(), f(t)?)), + } + } + + pub(crate) fn salt(&self) -> &[u8] { + use self::HashingMode::*; + match self { + Binary(salt, _t) => salt, + Text(salt, _t) => salt, + TextLastWasCr(salt, _t) => salt, } } pub(crate) fn as_ref(&self) -> &T { use self::HashingMode::*; match self { - Binary(t) => t, - Text(t) => t, - TextLastWasCr(t) => t, + Binary(_salt, t) => t, + Text(_salt, t) => t, + TextLastWasCr(_salt, t) => t, } } pub(crate) fn as_mut(&mut self) -> &mut T { use self::HashingMode::*; match self { - Binary(t) => t, - Text(t) => t, - TextLastWasCr(t) => t, + Binary(_salt, t) => t, + Text(_salt, t) => t, + TextLastWasCr(_salt, t) => t, } } - pub(crate) fn for_signature(t: T, typ: SignatureType) -> Self { + pub(crate) fn for_signature(t: T, s: &Signature) -> Self { + match s { + Signature::V3(s) => Self::for_salt_and_type(t, &[], s.typ()), + Signature::V4(s) => Self::for_salt_and_type(t, &[], s.typ()), + Signature::V6(s) => Self::for_salt_and_type(t, s.salt(), s.typ()), + } + } + pub(crate) fn for_salt_and_type(t: T, salt: &[u8], typ: SignatureType) + -> Self + { if typ == SignatureType::Text { - HashingMode::Text(t) + HashingMode::Text(salt.into(), t) } else { - HashingMode::Binary(t) + HashingMode::Binary(salt.into(), t) } } pub(crate) fn into_inner(self) -> T { use self::HashingMode::*; match self { - Binary(t) => t, - Text(t) => t, - TextLastWasCr(t) => t, + Binary(_salt, t) => t, + Text(_salt, t) => t, + TextLastWasCr(_salt, t) => t, } } } @@ -132,9 +166,9 @@ impl HashingMode } let (h, mut last_was_cr) = match self { - HashingMode::Text(h) => (h, false), - HashingMode::TextLastWasCr(h) => (h, true), - HashingMode::Binary(h) => return h.update(data), + HashingMode::Text(_salt, h) => (h, false), + HashingMode::TextLastWasCr(_salt, h) => (h, true), + HashingMode::Binary(_salt, h) => return h.update(data), }; let mut line = data; @@ -173,19 +207,21 @@ impl HashingMode } match (&mut *self, last_is_cr) { - (&mut HashingMode::Text(_), false) => { + (&mut HashingMode::Text(_, _), false) => { // This is the common case. Getting a crlf that is // split across two chunks is extremely rare. Hence, // the clones used to change the variant are rarely // needed. }, - (&mut HashingMode::Text(ref mut h), true) => { - *self = HashingMode::TextLastWasCr(h.clone()); + (&mut HashingMode::Text(ref mut salt, ref mut h), true) => { + *self = + HashingMode::TextLastWasCr(std::mem::take(salt), h.clone()); } - (&mut HashingMode::TextLastWasCr(ref mut h), false) => { - *self = HashingMode::Text(h.clone()); + (&mut HashingMode::TextLastWasCr(ref mut salt, ref mut h), false) => + { + *self = HashingMode::Text(std::mem::take(salt), h.clone()); }, - (&mut HashingMode::TextLastWasCr(_), true) => (), + (&mut HashingMode::TextLastWasCr(_, _), true) => (), _ => unreachable!("handled above"), } @@ -222,8 +258,10 @@ impl> HashedReader { let mut cookie = Cookie::default(); for mode in algos { + let salt = mode.salt().to_vec(); let mode = mode.mapf(|algo| { - let ctx = algo.context()?; + let mut ctx = algo.context()?; + ctx.update(&salt); Ok(ctx) })?; @@ -265,10 +303,10 @@ impl Cookie { assert!(ngroups > 1); for h in self.sig_groups[ngroups-2].hashes.iter_mut() { - t!("({:?}): group {} {:?} hashing {} stashed bytes: {}", - hashes_for, ngroups-2, h.map(|ctx| ctx.algo()), - stashed_data.len(), - crate::fmt::hex::encode_pretty(&stashed_data)); + t!("({:?}): group {} {:?} hashing {} stashed bytes.", + hashes_for, ngroups-2, + h.map(|ctx| ctx.algo()), + data.len()); h.update(&stashed_data); } @@ -516,7 +554,7 @@ mod test { let mut reader = HashedReader::new(reader, HashesFor::MDC, test.expected.keys().cloned() - .map(HashingMode::Binary) + .map(|v| HashingMode::Binary(vec![], v)) .collect()).unwrap(); assert_eq!(reader.steal_eof().unwrap(), test.data); @@ -549,7 +587,8 @@ mod test { ] { for chunk_size in &[ text.len(), 1 ] { let mut ctx - = HashingMode::Text(HashAlgorithm::SHA256.context()?); + = HashingMode::Text(vec![], + HashAlgorithm::SHA256.context()?); for chunk in text.as_bytes().chunks(*chunk_size) { ctx.update(chunk); } @@ -585,7 +624,7 @@ mod test { hash_buffered_reader( reader, &expected.keys().cloned() - .map(HashingMode::Binary). + .map(|v| HashingMode::Binary(vec![], v)). collect::>()) .unwrap(); diff --git a/openpgp/src/parse/stream.rs b/openpgp/src/parse/stream.rs index e419e14b..3f30d696 100644 --- a/openpgp/src/parse/stream.rs +++ b/openpgp/src/parse/stream.rs @@ -2553,7 +2553,7 @@ impl<'a, H: VerificationHelper + DecryptionHelper> Decryptor<'a, H> { // Compute the necessary hashes. let algos: Vec<_> = sigs.iter().map(|s| { - HashingMode::for_signature(s.hash_algo(), s.typ()) + HashingMode::for_signature(s.hash_algo(), s) }).collect(); let hashes = crate::parse::hashed_reader::hash_buffered_reader(data, &algos)?; @@ -2561,7 +2561,7 @@ impl<'a, H: VerificationHelper + DecryptionHelper> Decryptor<'a, H> { // Attach the digests. for sig in sigs.iter_mut() { let need_hash = - HashingMode::for_signature(sig.hash_algo(), sig.typ()); + HashingMode::for_signature(sig.hash_algo(), sig); // Note: |hashes| < 10, most likely 1. for mode in hashes.iter() .filter(|m| m.map(|c| c.algo()) == need_hash) diff --git a/openpgp/src/serialize.rs b/openpgp/src/serialize.rs index fa33bc95..cead9f46 100644 --- a/openpgp/src/serialize.rs +++ b/openpgp/src/serialize.rs @@ -2054,6 +2054,7 @@ impl Marshal for OnePassSig { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { match self { OnePassSig::V3(ref s) => s.serialize(o), + OnePassSig::V6(ref s) => s.serialize(o), } } } @@ -2062,12 +2063,23 @@ impl MarshalInto for OnePassSig { fn serialized_len(&self) -> usize { match self { OnePassSig::V3(ref s) => s.serialized_len(), + OnePassSig::V6(ref s) => s.serialized_len(), } } fn serialize_into(&self, buf: &mut [u8]) -> Result { match self { OnePassSig::V3(ref s) => s.serialize_into(buf), + OnePassSig::V6(ref s) => s.serialize_into(buf), + } + } +} + +impl NetLength for OnePassSig { + fn net_len(&self) -> usize { + match self { + OnePassSig::V3(ref s) => s.net_len(), + OnePassSig::V6(ref s) => s.net_len(), } } } @@ -2107,6 +2119,51 @@ impl MarshalInto for OnePassSig3 { } } +impl seal::Sealed for OnePassSig6 {} +impl Marshal for OnePassSig6 { + fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { + write_byte(o, 6)?; // Version. + write_byte(o, self.typ().into())?; + write_byte(o, self.hash_algo().into())?; + write_byte(o, self.pk_algo().into())?; + write_byte(o, self.salt().len().try_into().map_err( + |_| Error::InvalidArgument("Salt too large".into()))?)?; + o.write_all(self.salt())?; + if let Fingerprint::V6(bytes) = self.issuer() { + o.write_all(bytes)?; + } else { + return Err(Error::InvalidArgument( + "Need a v6 fingerprint as issuer".into()).into()); + } + write_byte(o, self.last_raw())?; + + Ok(()) + } +} + +impl NetLength for OnePassSig6 { + fn net_len(&self) -> usize { + 1 // Version. + + 1 // Signature type. + + 1 // Hash algorithm + + 1 // PK algorithm. + + 1 // The salt length. + + self.salt().len() // The salt. + + 32 // Issuer fingerprint. + + 1 // Last. + } +} + +impl MarshalInto for OnePassSig6 { + fn serialized_len(&self) -> usize { + self.net_len() + } + + fn serialize_into(&self, buf: &mut [u8]) -> Result { + generic_serialize_into(self, MarshalInto::serialized_len(self), buf) + } +} + impl seal::Sealed for Key {} impl Marshal for Key { fn serialize(&self, o: &mut dyn io::Write) -> Result<()> { diff --git a/openpgp/src/serialize/stream.rs b/openpgp/src/serialize/stream.rs index a11ac3ed..61adbf55 100644 --- a/openpgp/src/serialize/stream.rs +++ b/openpgp/src/serialize/stream.rs @@ -1245,7 +1245,7 @@ impl<'a> Signer<'a> { assert!(!self.signers.is_empty(), "The constructor adds a signer."); assert!(self.inner.is_some(), "The constructor adds an inner writer."); - for (keypair, signer_hash, _signer_salt) in self.signers.iter_mut() { + for (keypair, signer_hash, signer_salt) in self.signers.iter_mut() { // First, compute a suitable hash algorithm, starting with // the one configured using Self::hash_algo. let mut acceptable_hashes = @@ -1273,7 +1273,7 @@ impl<'a> Signer<'a> { } let algo = acceptable_hashes[0]; *signer_hash = algo; - let hash = algo.context()?; + let mut hash = algo.context()?; match keypair.public().version() { 4 => { @@ -1281,11 +1281,32 @@ impl<'a> Signer<'a> { if self.template.typ() == SignatureType::Text || self.mode == SignatureMode::Cleartext { - HashingMode::Text(hash) + HashingMode::Text(vec![], hash) } else { - HashingMode::Binary(hash) + HashingMode::Binary(vec![], hash) }); }, + 6 => { + // Version 6 signatures are salted, and we + // need to include it in the OPS packet. + // Generate and remember the salt here. + let mut salt = vec![0; algo.salt_size()?]; + crate::crypto::random(&mut salt); + + // Add the salted context. + hash.update(&salt); + self.hashes.push( + if self.template.typ() == SignatureType::Text + || self.mode == SignatureMode::Cleartext + { + HashingMode::Text(salt.clone(), hash) + } else { + HashingMode::Binary(salt.clone(), hash) + }); + + // And remember which signer used which salt. + *signer_salt = salt; + }, v => return Err(Error::InvalidOperation( format!("Unsupported Key version {}", v)).into()), } @@ -1296,7 +1317,7 @@ impl<'a> Signer<'a> { // For every key we collected, build and emit a one pass // signature packet. let signers_count = self.signers.len(); - for (i, (keypair, hash_algo, _salt)) in + for (i, (keypair, hash_algo, salt)) in self.signers.iter().enumerate() { let last = i == signers_count - 1; @@ -1312,6 +1333,18 @@ impl<'a> Signer<'a> { Packet::from(ops) .serialize(self.inner.as_mut().unwrap())?; }, + 6 => { + // Version 6 signatures are salted, and we + // need to include it in the OPS packet. + let mut ops = OnePassSig6::new( + self.template.typ(), key.fingerprint()); + ops.set_pk_algo(key.pk_algo()); + ops.set_hash_algo(*hash_algo); + ops.set_salt(salt.clone()); + ops.set_last(last); + Packet::from(ops) + .serialize(self.inner.as_mut().unwrap())?; + }, v => return Err(Error::InvalidOperation( format!("Unsupported Key version {}", v)).into()), } @@ -1381,14 +1414,15 @@ impl<'a> Signer<'a> { // Emit the signatures in reverse, so that the // one-pass-signature and signature packets "bracket" the // message. - for (signer, algo, _signer_salt) in self.signers.iter_mut().rev() { + for (signer, algo, signer_salt) in self.signers.iter_mut().rev() { let (mut sig, hash) = match signer.public().version() { 4 => { // V4 signature. let hash = self.hashes.iter() .find_map(|hash| { - if hash.as_ref().algo() == *algo + if hash.salt().is_empty() + && hash.as_ref().algo() == *algo { Some(hash.clone()) } else { @@ -1402,6 +1436,22 @@ impl<'a> Signer<'a> { (sig, hash) }, + 6 => { + // V6 signature. + let hash = self.hashes.iter() + .find_map(|hash| if signer_salt == hash.salt() { + Some(hash.clone()) + } else { + None + }) + .expect("we put it in there"); + + // Make and hash a signature packet. + let sig = self.template.clone() + .set_prefix_salt(signer_salt.clone()).0; + + (sig, hash) + }, v => return Err(Error::InvalidOperation( format!("Unsupported Key version {}", v)).into()), }; -- cgit v1.2.3