diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2023-08-21 13:47:05 +0200 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2023-11-09 16:11:21 +0100 |
commit | b59876e066cf6ab87a5d44af2725eee84fc3cb93 (patch) | |
tree | 83e7368f2bc850c29fd9caee0566b14fbe9acfdd | |
parent | 535aa58576b7c5642ec1ca09c046a52884223e7e (diff) |
fix hashing inline-signed and detached v6 signatures.crypto-refresh-seip-v2
- Fixes #1042.
-rw-r--r-- | openpgp/src/parse.rs | 71 | ||||
-rw-r--r-- | openpgp/src/parse/hashed_reader.rs | 147 | ||||
-rw-r--r-- | openpgp/src/parse/stream.rs | 4 | ||||
-rw-r--r-- | openpgp/src/serialize/stream.rs | 64 |
4 files changed, 183 insertions, 103 deletions
diff --git a/openpgp/src/parse.rs b/openpgp/src/parse.rs index fe58c488..c69c986b 100644 --- a/openpgp/src/parse.rs +++ b/openpgp/src/parse.rs @@ -841,14 +841,13 @@ pub(crate) struct SignatureGroup { /// 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<(Vec<u8>, - HashingMode<Box<dyn crypto::hash::Digest>>)>, + 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(|(salt, mode)| (salt, mode.map(|ctx| ctx.algo()))) + .map(|mode| mode.map(|ctx| ctx.algo())) .collect::<Vec<_>>(); f.debug_struct("Cookie") @@ -1400,7 +1399,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); @@ -1419,13 +1418,8 @@ impl Signature { return Ok(pp); } - let need_hash = HashingMode::for_signature(hash_algo, typ); - let need_salt = - if let Packet::Signature(Signature::V6(sig)) = &pp.packet { - sig.salt() - } else { - &[] - }; + let need_hash = HashingMode::for_signature(hash_algo, sig); + t!("Need a {:?}", need_hash); // Locate the corresponding HashedReader and extract the // computed hash. @@ -1465,11 +1459,14 @@ 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( - |(salt, mode)| - if salt == need_salt - && mode.map(|ctx| ctx.algo()) == need_hash + |mode| + if mode.map(|ctx| ctx.algo()) == need_hash { Some(mode.as_ref()) } else { @@ -1552,7 +1549,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) } } @@ -1596,7 +1593,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). @@ -1684,7 +1681,7 @@ impl Signature3 { [digest_prefix1, digest_prefix2], mpis).into()))?; - Signature::parse_finish(indent, pp, typ, hash_algo) + Signature::parse_finish(indent, pp, hash_algo) } } @@ -2099,7 +2096,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(); @@ -2137,16 +2134,14 @@ impl OnePassSig3 { // hash algorithm. if php.state.settings.automatic_hashing && ! cookie.sig_group().hashes.iter() - .any(|(salt, mode)| { + .any(|mode| { mode.map(|ctx| ctx.algo()) == need_hash - && salt.is_empty() }) { if let Ok(ctx) = hash_algo.context() { cookie.sig_group_mut().hashes.push( - (vec![], - HashingMode::for_signature( - Box::new(ctx), typ)) + HashingMode::for_salt_and_type( + Box::new(ctx), &[], typ) ); } } @@ -2182,7 +2177,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: @@ -2203,7 +2198,7 @@ impl OnePassSig3 { assert!(! fake_eof); let mut reader = HashedReader::new( - reader, want_hashes_for, vec![], algos)?; + 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; @@ -2280,7 +2275,8 @@ impl PacketParser<'_> { let hash_algo = ops.hash_algo(); let typ = ops.typ(); let salt = ops.salt(); - let need_hash = HashingMode::for_signature(hash_algo, typ); + 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 @@ -2306,13 +2302,13 @@ impl PacketParser<'_> { // hash algorithm. if ! cookie.sig_group().hashes.iter() .any(|mode| { - mode.1.map(|ctx| ctx.algo()) == need_hash + mode.map(|ctx| ctx.algo()) == need_hash }) { - cookie.sig_group_mut().hashes.push(( - salt.map(|s| s.into()).unwrap_or_default(), - HashingMode::for_signature( - Box::new(hash_algo.context()?), typ))); + cookie.sig_group_mut().hashes.push( + HashingMode::for_salt_and_type( + Box::new(hash_algo.context()?), + salt.unwrap_or(&[]), typ)); } break; } @@ -2399,7 +2395,7 @@ impl OnePassSig6 { // against when we get to the Signature packet. let mut algos = Vec::new(); if hash_algo.is_supported() { - algos.push(HashingMode::for_signature(hash_algo, typ)); + algos.push(HashingMode::for_salt_and_type(hash_algo, &salt, typ)); } // Commit here after potentially pushing a signature group. @@ -2423,7 +2419,7 @@ impl OnePassSig6 { assert!(! fake_eof); let mut reader = HashedReader::new( - reader, want_hashes_for, salt, algos)?; + 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; @@ -3424,9 +3420,9 @@ impl MDC { if !state.sig_group().hashes.is_empty() { let h = state.sig_group_mut().hashes .iter_mut().find_map( - |(_salt, mode)| - if mode.map(|ctx| ctx.algo()) == - HashingMode::Binary(HashAlgorithm::SHA1) + |mode| + if matches!(mode.map(|ctx| ctx.algo()), + HashingMode::Binary(_, HashAlgorithm::SHA1)) { Some(mode.as_mut()) } else { @@ -6066,8 +6062,7 @@ impl<'a> PacketParser<'a> { // And the hasher. let mut reader = HashedReader::new( reader, HashesFor::MDC, - vec![], - 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 51549060..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<T> { /// Hash for a binary signature. /// /// The data is hashed as-is. - Binary(T), + Binary(Vec<u8>, T), /// Hash for a text signature. /// /// The data is hashed with line endings normalized to `\r\n`. - Text(T), + Text(Vec<u8>, T), /// Like Text, but the last character that we hashed was a '\r' /// that we converted to a '\r\n'. - TextLastWasCr(T), + TextLastWasCr(Vec<u8>, T), } impl<T: std::fmt::Debug> std::fmt::Debug for HashingMode<T> { 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<T: PartialEq> PartialEq for HashingMode<T> { 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<T> HashingMode<T> { pub(crate) fn map<U, F: Fn(&T) -> U>(&self, f: F) -> HashingMode<U> { 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<T> HashingMode<T> { -> Result<HashingMode<U>> { 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<D> HashingMode<D> } 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<D> HashingMode<D> } 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"), } @@ -217,19 +253,19 @@ impl<R: BufferedReader<Cookie>> HashedReader<R> { /// purpose. `algos` is a list of algorithms for which we should /// compute the hash. pub fn new(reader: R, hashes_for: HashesFor, - salt: Vec<u8>, algos: Vec<HashingMode<HashAlgorithm>>) -> Result<Self> { let mut cookie = Cookie::default(); for mode in algos { + let salt = mode.salt().to_vec(); let mode = mode.mapf(|algo| { let mut ctx = algo.context()?; ctx.update(&salt); Ok(ctx) })?; - cookie.sig_group_mut().hashes.push((salt.clone(), mode)); + cookie.sig_group_mut().hashes.push(mode); } cookie.hashes_for = hashes_for; @@ -267,13 +303,12 @@ impl Cookie { assert!(ngroups > 1); for h in self.sig_groups[ngroups-2].hashes.iter_mut() { - t!("({:?}): group {} {:?} (salted: {:?} hashing {} stashed bytes)", + t!("({:?}): group {} {:?} hashing {} stashed bytes.", hashes_for, ngroups-2, - h.1.map(|ctx| ctx.algo()), - ! h.0.is_empty(), + h.map(|ctx| ctx.algo()), data.len()); - h.1.update(&stashed_data); + h.update(&stashed_data); } } @@ -298,8 +333,8 @@ impl Cookie { for h in sig_group.hashes.iter_mut() { t!("{:?}: group {} {:?} hashing {} bytes.", - hashes_for, i, h.1.map(|ctx| ctx.algo()), data.len()); - h.1.update(data); + hashes_for, i, h.map(|ctx| ctx.algo()), data.len()); + h.update(data); } } } @@ -333,8 +368,8 @@ impl Cookie { // Hash the data. for h in self.sig_groups[0].hashes.iter_mut() { t!("{:?}: {:?} hashing {} bytes.", - hashes_for, h.1.map(|ctx| ctx.algo()), data.len()); - h.1.update(data); + hashes_for, h.map(|ctx| ctx.algo()), data.len()); + h.update(data); } } } @@ -463,14 +498,14 @@ pub(crate) fn hash_buffered_reader<R>(reader: R, where R: BufferedReader<crate::parse::Cookie>, { let mut reader - = HashedReader::new(reader, HashesFor::Signature, vec![], algos.to_vec())?; + = HashedReader::new(reader, HashesFor::Signature, algos.to_vec())?; // Hash all of the data. reader.drop_eof()?; let hashes = mem::take(&mut reader.cookie_mut().sig_group_mut().hashes); - Ok(hashes.into_iter().map(|h| h.1).collect()) + Ok(hashes) } #[cfg(test)] @@ -518,9 +553,8 @@ mod test { test.data, None, Default::default()); let mut reader = HashedReader::new(reader, HashesFor::MDC, - vec![], test.expected.keys().cloned() - .map(HashingMode::Binary) + .map(|v| HashingMode::Binary(vec![], v)) .collect()).unwrap(); assert_eq!(reader.steal_eof().unwrap(), test.data); @@ -528,7 +562,7 @@ mod test { let cookie = reader.cookie_mut(); let mut hashes = std::mem::take(&mut cookie.sig_group_mut().hashes); - for (_salt, mode) in hashes.iter_mut() { + for mode in hashes.iter_mut() { let hash = mode.as_mut(); let algo = hash.algo(); let mut digest = vec![0u8; hash.digest_size()]; @@ -553,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); } @@ -589,7 +624,7 @@ mod test { hash_buffered_reader( reader, &expected.keys().cloned() - .map(HashingMode::Binary). + .map(|v| HashingMode::Binary(vec![], v)). collect::<Vec<_>>()) .unwrap(); diff --git a/openpgp/src/parse/stream.rs b/openpgp/src/parse/stream.rs index 446c2941..b06b64fd 100644 --- a/openpgp/src/parse/stream.rs +++ b/openpgp/src/parse/stream.rs @@ -2537,7 +2537,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)?; @@ -2545,7 +2545,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/stream.rs b/openpgp/src/serialize/stream.rs index 8dddcc4b..0e13e627 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()), }; |