diff options
Diffstat (limited to 'openpgp/src/parse.rs')
-rw-r--r-- | openpgp/src/parse.rs | 221 |
1 files changed, 198 insertions, 23 deletions
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<HashingMode<Box<dyn crypto::hash::Digest>>>, } 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::<Vec<_>>(); 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<PacketParser> { 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::<Vec<_>>()); 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<Self> { - 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<PacketParser> { + 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<Cookie>> + = 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<Self> { + 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 {:?}.", |