diff options
author | Neal H. Walfield <neal@pep.foundation> | 2022-11-11 09:32:25 +0100 |
---|---|---|
committer | Neal H. Walfield <neal@pep.foundation> | 2022-11-11 21:01:46 +0100 |
commit | 53474e58493cc10a9e021fde5ca1048a86859f2b (patch) | |
tree | 38bf36070d7aa5fb87d0e790809bad1003f5c5f2 | |
parent | 813f3f00a967eea210641103b5e64e0d31299ae6 (diff) |
openpgp: Add support for verifying v3 signatures.
- RFC 4880 explicitly allows the use of v3 signatures, but adds:
> Implementations SHOULD accept V3 signatures. Implementations
> SHOULD generate V4 signatures.
- In practice, rpm-based distributions are generating v3 signatures,
and it will be awhile before we can actually stop supporting them.
https://bugzilla.redhat.com/show_bug.cgi?id=2141686#c20
- Add support for parsing, verifying, and serializing v3
signatures (but not v3 certificates, and not generating v3
signatures!).
-rw-r--r-- | openpgp/NEWS | 2 | ||||
-rw-r--r-- | openpgp/src/crypto/hash.rs | 31 | ||||
-rw-r--r-- | openpgp/src/packet/mod.rs | 6 | ||||
-rw-r--r-- | openpgp/src/packet/signature.rs | 288 | ||||
-rw-r--r-- | openpgp/src/packet/signature/subpacket.rs | 12 | ||||
-rw-r--r-- | openpgp/src/parse.rs | 130 | ||||
-rw-r--r-- | openpgp/src/serialize.rs | 149 | ||||
-rw-r--r-- | openpgp/tests/data/messages/a-cypherpunks-manifesto.txt.dennis-simon-anton-v3.sig | 6 |
8 files changed, 578 insertions, 46 deletions
diff --git a/openpgp/NEWS b/openpgp/NEWS index 9b1cf413..6d17339d 100644 --- a/openpgp/NEWS +++ b/openpgp/NEWS @@ -5,6 +5,8 @@ * Changes in 1.11.0 * New functionality - AsymmetricAlgorithm implements PartialEq, Eq, and Copy. + - Signature3 implements support for parsing, verifying, and + reserializing version 3 signature packages. * Changes in 1.10.0 ** New functionality - Cert::insert_packets2 diff --git a/openpgp/src/crypto/hash.rs b/openpgp/src/crypto/hash.rs index 9af3de07..c4e7c4c8 100644 --- a/openpgp/src/crypto/hash.rs +++ b/openpgp/src/crypto/hash.rs @@ -40,7 +40,7 @@ use crate::packet::UserAttribute; use crate::packet::key; use crate::packet::key::Key4; use crate::packet::Signature; -use crate::packet::signature::{self, Signature4}; +use crate::packet::signature::{self, Signature3, Signature4}; use crate::Result; use crate::types::Timestamp; @@ -411,11 +411,39 @@ impl<P, R> Hash for Key4<P, R> impl Hash for Signature { fn hash(&self, hash: &mut dyn Digest) { match self { + Signature::V3(sig) => sig.hash(hash), Signature::V4(sig) => sig.hash(hash), } } } +impl Hash for Signature3 { + fn hash(&self, hash: &mut dyn Digest) { + // XXX: Annoyingly, we have no proper way of handling errors + // here. + + let mut buffer = [0u8; 5]; + + // Signature type. + buffer[0] = u8::from(self.typ()); + + // Creation time. + let creation_time: u32 = + Timestamp::try_from( + self.signature_creation_time() + .unwrap_or(std::time::UNIX_EPOCH)) + .unwrap_or_else(|_| Timestamp::from(0)) + .into(); + + buffer[1] = (creation_time >> 24) as u8; + buffer[2] = (creation_time >> 16) as u8; + buffer[3] = (creation_time >> 8) as u8; + buffer[4] = (creation_time ) as u8; + + hash.update(&buffer[..]); + } +} + impl Hash for Signature4 { fn hash(&self, hash: &mut dyn Digest) { self.fields.hash(hash); @@ -566,6 +594,7 @@ impl Signature { /// signature. pub fn hash_for_confirmation(&self, hash: &mut dyn Digest) { match self { + Signature::V3(s) => s.hash_for_confirmation(hash), Signature::V4(s) => s.hash_for_confirmation(hash), } } diff --git a/openpgp/src/packet/mod.rs b/openpgp/src/packet/mod.rs index b112eaf2..1a3da9ab 100644 --- a/openpgp/src/packet/mod.rs +++ b/openpgp/src/packet/mod.rs @@ -987,6 +987,9 @@ fn packet_path_iter() { #[non_exhaustive] #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Debug)] pub enum Signature { + /// Signature packet version 3. + V3(self::signature::Signature3), + /// Signature packet version 4. V4(self::signature::Signature4), } @@ -996,6 +999,7 @@ impl Signature { /// Gets the version. pub fn version(&self) -> u8 { match self { + Signature::V3(_) => 3, Signature::V4(_) => 4, } } @@ -1013,6 +1017,7 @@ impl Deref for Signature { fn deref(&self) -> &Self::Target { match self { + Signature::V3(sig) => &sig.intern, Signature::V4(sig) => sig, } } @@ -1022,6 +1027,7 @@ impl Deref for Signature { impl DerefMut for Signature { fn deref_mut(&mut self) -> &mut Self::Target { match self { + Signature::V3(ref mut sig) => &mut sig.intern, Signature::V4(ref mut sig) => sig, } } diff --git a/openpgp/src/packet/signature.rs b/openpgp/src/packet/signature.rs index 782ad526..84910e25 100644 --- a/openpgp/src/packet/signature.rs +++ b/openpgp/src/packet/signature.rs @@ -113,6 +113,7 @@ //! [its documentation]: subpacket::SubpacketAreas use std::cmp::Ordering; +use std::convert::TryFrom; use std::fmt; use std::hash::Hasher; use std::ops::{Deref, DerefMut}; @@ -128,6 +129,7 @@ use crate::crypto::{ hash::{self, Hash, Digest}, Signer, }; +use crate::KeyID; use crate::KeyHandle; use crate::HashAlgorithm; use crate::PublicKeyAlgorithm; @@ -148,6 +150,7 @@ use crate::packet::signature::subpacket::{ SubpacketTag, SubpacketValue, }; +use crate::types::Timestamp; #[cfg(test)] /// Like quickcheck::Arbitrary, but bounded. @@ -1742,6 +1745,7 @@ impl SignatureBuilder { impl From<Signature> for SignatureBuilder { fn from(sig: Signature) -> Self { match sig { + Signature::V3(sig) => sig.into(), Signature::V4(sig) => sig.into(), } } @@ -2010,6 +2014,204 @@ impl Signature4 { } } +impl From<Signature3> for SignatureBuilder { + fn from(sig: Signature3) -> Self { + SignatureBuilder::from(sig.intern) + } +} + +/// Holds a v3 Signature packet. +/// +/// This holds a [version 3] Signature packet. Normally, you won't +/// directly work with this data structure, but with the [`Signature`] +/// 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 3]: https://tools.ietf.org/html/rfc4880#section-5.2 +/// [`Signature`]: super::Signature +/// +/// Note: Per RFC 4880, v3 signatures should not be generated, but +/// they should be accepted. As such, support for version 3 +/// signatures is limited to verifying them, but not generating them. +#[derive(Clone)] +pub struct Signature3 { + pub(crate) intern: Signature4, +} +assert_send_and_sync!(Signature3); + +impl TryFrom<Signature> for Signature3 { + type Error = anyhow::Error; + + fn try_from(sig: Signature) -> Result<Self> { + match sig { + Signature::V3(sig) => Ok(sig), + sig => Err( + Error::InvalidArgument( + format!( + "Got a v{}, require a v3 signature", + sig.version())) + .into()), + } + } +} + +// Yes, Signature3 derefs to Signature4. This is because Signature +// derefs to Signature4 so this is the only way to add support for v3 +// sigs without breaking the semver. +impl Deref for Signature3 { + type Target = Signature4; + + fn deref(&self) -> &Self::Target { + &self.intern + } +} + +impl DerefMut for Signature3 { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.intern + } +} + +impl fmt::Debug for Signature3 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Signature3") + .field("version", &self.version()) + .field("typ", &self.typ()) + .field("pk_algo", &self.pk_algo()) + .field("hash_algo", &self.hash_algo()) + .field("hashed_area", self.hashed_area()) + .field("unhashed_area", self.unhashed_area()) + .field("additional_issuers", &self.additional_issuers) + .field("digest_prefix", + &crate::fmt::to_hex(&self.digest_prefix, false)) + .field( + "computed_digest", + &self + .computed_digest + .as_ref() + .map(|hash| crate::fmt::to_hex(&hash[..], false)), + ) + .field("level", &self.level) + .field("mpis", &self.mpis) + .finish() + } +} + +impl PartialEq for Signature3 { + /// This method tests for self and other values to be equal, and + /// is used by ==. + /// + /// This method compares the serialized version of the two + /// packets. Thus, the computed values are ignored ([`level`], + /// [`computed_digest`]). + /// + /// Note: because this function also compares the unhashed + /// subpacket area, it is possible for a malicious party to take + /// valid signatures, add subpackets to the unhashed area, + /// yielding valid but distinct signatures. If you want to ignore + /// the unhashed area, you should instead use the + /// [`Signature::normalized_eq`] method. + /// + /// [`level`]: Signature3::level() + /// [`computed_digest`]: Signature3::computed_digest() + fn eq(&self, other: &Signature3) -> bool { + self.cmp(other) == Ordering::Equal + } +} + +impl Eq for Signature3 {} + +impl PartialOrd for Signature3 { + fn partial_cmp(&self, other: &Signature3) -> Option<Ordering> { + Some(self.cmp(other)) + } +} + +impl Ord for Signature3 { + fn cmp(&self, other: &Signature3) -> Ordering { + self.intern.cmp(&other.intern) + } +} + +impl std::hash::Hash for Signature3 { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + use std::hash::Hash as StdHash; + StdHash::hash(&self.intern, state); + } +} + +impl Signature3 { + /// Creates a new signature packet. + /// + /// If you want to sign something, consider using the [`SignatureBuilder`] + /// interface. + /// + pub fn new(typ: SignatureType, creation_time: Timestamp, + issuer: KeyID, + pk_algo: PublicKeyAlgorithm, + hash_algo: HashAlgorithm, + digest_prefix: [u8; 2], + mpis: mpi::Signature) -> Self { + let hashed_area = SubpacketArea::new(vec![ + Subpacket::new( + SubpacketValue::SignatureCreationTime(creation_time), + true).expect("fits"), + ]).expect("fits"); + let unhashed_area = SubpacketArea::new(vec![ + Subpacket::new( + SubpacketValue::Issuer(issuer), + false).expect("fits"), + ]).expect("fits"); + + let mut sig = Signature4::new(typ, + pk_algo, hash_algo, + hashed_area, unhashed_area, + digest_prefix, mpis); + sig.version = 3; + + Signature3 { + intern: sig, + } + } + + /// Gets the public key algorithm. + // SigantureFields::pk_algo is private, because we don't want it + // available on SignatureBuilder, which also derefs to + // &SignatureFields. + pub fn pk_algo(&self) -> PublicKeyAlgorithm { + self.fields.pk_algo() + } + + /// Gets the hash prefix. + pub fn digest_prefix(&self) -> &[u8; 2] { + &self.digest_prefix + } + + /// Gets the signature packet's MPIs. + pub fn mpis(&self) -> &mpi::Signature { + &self.mpis + } + + /// Gets the computed hash value. + /// + /// This is set by the [`PacketParser`] when parsing the message. + /// + /// [`PacketParser`]: crate::parse::PacketParser + pub fn computed_digest(&self) -> Option<&[u8]> { + self.computed_digest.as_ref().map(|d| &d[..]) + } + + /// Gets the signature level. + /// + /// A level of 0 indicates that the signature is directly over the + /// data, a level of 1 means that the signature is a notarization + /// over all level 0 signatures and the data, and so on. + pub fn level(&self) -> usize { + self.level + } +} + impl crate::packet::Signature { /// Returns the value of any Issuer and Issuer Fingerprint subpackets. /// @@ -3152,6 +3354,18 @@ impl Signature { } } +impl From<Signature3> for Packet { + fn from(s: Signature3) -> Self { + Packet::Signature(s.into()) + } +} + +impl From<Signature3> for super::Signature { + fn from(s: Signature3) -> Self { + super::Signature::V3(s) + } +} + impl From<Signature4> for Packet { fn from(s: Signature4) -> Self { Packet::Signature(s.into()) @@ -3167,7 +3381,11 @@ impl From<Signature4> for super::Signature { #[cfg(test)] impl ArbitraryBounded for super::Signature { fn arbitrary_bounded(g: &mut Gen, depth: usize) -> Self { - Signature4::arbitrary_bounded(g, depth).into() + if bool::arbitrary(g) { + Signature3::arbitrary_bounded(g, depth).into() + } else { + Signature4::arbitrary_bounded(g, depth).into() + } } } @@ -3222,6 +3440,52 @@ impl ArbitraryBounded for Signature4 { impl_arbitrary_with_bound!(Signature4); #[cfg(test)] +impl ArbitraryBounded for Signature3 { + fn arbitrary_bounded(g: &mut Gen, _depth: usize) -> Self { + use mpi::MPI; + use PublicKeyAlgorithm::*; + + let pk_algo = PublicKeyAlgorithm::arbitrary_for_signing(g); + + #[allow(deprecated)] + let mpis = match pk_algo { + RSAEncryptSign | RSASign => mpi::Signature::RSA { + s: MPI::arbitrary(g), + }, + + DSA => mpi::Signature::DSA { + r: MPI::arbitrary(g), + s: MPI::arbitrary(g), + }, + + EdDSA => mpi::Signature::EdDSA { + r: MPI::arbitrary(g), + s: MPI::arbitrary(g), + }, + + ECDSA => mpi::Signature::ECDSA { + r: MPI::arbitrary(g), + s: MPI::arbitrary(g), + }, + + _ => unreachable!(), + }; + + Signature3::new( + SignatureType::arbitrary(g), + Timestamp::arbitrary(g), + KeyID::arbitrary(g), + pk_algo, + HashAlgorithm::arbitrary(g), + [Arbitrary::arbitrary(g), Arbitrary::arbitrary(g)], + mpis) + } +} + +#[cfg(test)] +impl_arbitrary_with_bound!(Signature3); + +#[cfg(test)] mod test { use super::*; use crate::KeyID; @@ -3491,6 +3755,28 @@ mod test { } #[test] + fn verify_v3_sig() { + if ! PublicKeyAlgorithm::DSA.is_supported() { + return; + } + + let cert = Cert::from_bytes(crate::tests::key( + "dennis-simon-anton-private.pgp")).unwrap(); + let msg = crate::tests::manifesto(); + let p = Packet::from_bytes( + crate::tests::message("a-cypherpunks-manifesto.txt.dennis-simon-anton-v3.sig")) + .unwrap(); + let mut sig = if let Packet::Signature(s) = p { + assert_eq!(s.version(), 3); + s + } else { + panic!("Expected a Signature, got: {:?}", p); + }; + + sig.verify_message(cert.primary_key().key(), msg).unwrap(); + } + + #[test] fn sign_with_short_ed25519_secret_key() { // 20 byte sec key let secret_key = [ diff --git a/openpgp/src/packet/signature/subpacket.rs b/openpgp/src/packet/signature/subpacket.rs index 065111eb..50e56271 100644 --- a/openpgp/src/packet/signature/subpacket.rs +++ b/openpgp/src/packet/signature/subpacket.rs @@ -3730,12 +3730,12 @@ impl TryFrom<Signature> for Signature4 { fn try_from(sig: Signature) -> Result<Self> { match sig { Signature::V4(sig) => Ok(sig), - // XXX: Once there are more signature variants: - //sig => Err( - // Error::InvalidArgument( - // format!("Got a v{}, require a v4 signature", sig.version()) - // .into()) - // .into()), + sig => Err( + Error::InvalidArgument( + format!( + "Got a v{}, require a v4 signature", + sig.version())) + .into()), } } } diff --git a/openpgp/src/parse.rs b/openpgp/src/parse.rs index 79467efe..fe421c71 100644 --- a/openpgp/src/parse.rs +++ b/openpgp/src/parse.rs @@ -205,6 +205,7 @@ use crate::{ Container, Header, }, + packet::signature::Signature3, packet::signature::Signature4, packet::prelude::*, Packet, @@ -1344,6 +1345,7 @@ impl Signature { let version = php_try!(php.parse_u8("version")); match version { + 3 => Signature3::parse(php), 4 => Signature4::parse(php), _ => { t!("Ignoring version {} packet.", version); @@ -1358,48 +1360,14 @@ impl Signature { -> Result<()> { Signature4::plausible(bio, header) } -} -impl Signature4 { - // Parses a signature packet. - fn parse<'a, T: 'a + BufferedReader<Cookie>>(mut php: PacketHeaderParser<T>) - -> Result<PacketParser<'a>> + fn parse_finish(indent: isize, mut pp: PacketParser, + typ: SignatureType, hash_algo: HashAlgorithm) + -> Result<PacketParser> { - let indent = php.recursion_depth(); - tracer!(TRACE, "Signature4::parse", indent); + tracer!(TRACE, "Signature::parse_finish", indent); - make_php_try!(php); - - let typ = php_try!(php.parse_u8("type")); - let pk_algo: PublicKeyAlgorithm = php_try!(php.parse_u8("pk_algo")).into(); - let hash_algo: HashAlgorithm = - php_try!(php.parse_u8("hash_algo")).into(); - let hashed_area_len = php_try!(php.parse_be_u16("hashed_area_len")); - let hashed_area - = php_try!(SubpacketArea::parse(&mut php, - hashed_area_len as usize, - hash_algo)); - let unhashed_area_len = php_try!(php.parse_be_u16("unhashed_area_len")); - let unhashed_area - = php_try!(SubpacketArea::parse(&mut php, - unhashed_area_len as usize, - hash_algo)); - let digest_prefix1 = php_try!(php.parse_u8("digest_prefix1")); - let digest_prefix2 = php_try!(php.parse_u8("digest_prefix2")); - if ! pk_algo.for_signing() { - return php.fail("not a signature algorithm"); - } - let mpis = php_try!( - crypto::mpi::Signature::_parse(pk_algo, &mut php)); - - let typ = typ.into(); let need_hash = HashingMode::for_signature(hash_algo, typ); - let mut pp = php.ok(Packet::Signature(Signature4::new( - typ, pk_algo, hash_algo, - hashed_area, - unhashed_area, - [digest_prefix1, digest_prefix2], - mpis).into()))?; // Locate the corresponding HashedReader and extract the // computed hash. @@ -1480,6 +1448,50 @@ impl Signature4 { Ok(pp) } +} + +impl Signature4 { + // Parses a signature packet. + fn parse<'a, T: 'a + BufferedReader<Cookie>>(mut php: PacketHeaderParser<T>) + -> Result<PacketParser<'a>> + { + let indent = php.recursion_depth(); + tracer!(TRACE, "Signature4::parse", indent); + + make_php_try!(php); + + let typ = php_try!(php.parse_u8("type")); + let pk_algo: PublicKeyAlgorithm = php_try!(php.parse_u8("pk_algo")).into(); + let hash_algo: HashAlgorithm = + php_try!(php.parse_u8("hash_algo")).into(); + let hashed_area_len = php_try!(php.parse_be_u16("hashed_area_len")); + let hashed_area + = php_try!(SubpacketArea::parse(&mut php, + hashed_area_len as usize, + hash_algo)); + let unhashed_area_len = php_try!(php.parse_be_u16("unhashed_area_len")); + let unhashed_area + = php_try!(SubpacketArea::parse(&mut php, + unhashed_area_len as usize, + hash_algo)); + let digest_prefix1 = php_try!(php.parse_u8("digest_prefix1")); + let digest_prefix2 = php_try!(php.parse_u8("digest_prefix2")); + if ! pk_algo.for_signing() { + return php.fail("not a signature algorithm"); + } + let mpis = php_try!( + crypto::mpi::Signature::_parse(pk_algo, &mut php)); + + let typ = typ.into(); + let pp = php.ok(Packet::Signature(Signature4::new( + typ, pk_algo, hash_algo, + hashed_area, + unhashed_area, + [digest_prefix1, digest_prefix2], + mpis).into()))?; + + Signature::parse_finish(indent, pp, typ, hash_algo) + } /// Returns whether the data appears to be a signature (no promises). fn plausible<T: BufferedReader<Cookie>>( @@ -1527,6 +1539,48 @@ impl Signature4 { } } +impl Signature3 { + // Parses a v3 signature packet. + fn parse<'a, T: 'a + BufferedReader<Cookie>>(mut php: PacketHeaderParser<T>) + -> Result<PacketParser<'a>> + { + let indent = php.recursion_depth(); + tracer!(TRACE, "Signature3::parse", indent); + + make_php_try!(php); + + let len = php_try!(php.parse_u8("hashed length")); + if len != 5 { + return php.fail("invalid length \ + (a v3 sig has 5 bytes of hashed data)"); + } + let typ = php_try!(php.parse_u8("type")); + let creation_time: Timestamp + = php_try!(php.parse_be_u32("creation_time")).into(); + let issuer: KeyID + = KeyID::from_bytes(&php_try!(php.parse_bytes("issuer", 8))[..]); + let pk_algo: PublicKeyAlgorithm + = php_try!(php.parse_u8("pk_algo")).into(); + let hash_algo: HashAlgorithm = + php_try!(php.parse_u8("hash_algo")).into(); + let digest_prefix1 = php_try!(php.parse_u8("digest_prefix1")); + let digest_prefix2 = php_try!(php.parse_u8("digest_prefix2")); + if ! pk_algo.for_signing() { + return php.fail("not a signature algorithm"); + } + let mpis = php_try!( + crypto::mpi::Signature::_parse(pk_algo, &mut php)); + + let typ = typ.into(); + let pp = php.ok(Packet::Signature(Signature3::new( + typ, creation_time, issuer, pk_algo, hash_algo, + [digest_prefix1, digest_prefix2], + mpis).into()))?; + + Signature::parse_finish(indent, pp, typ, hash_algo) + } +} + impl_parse_generic_packet!(Signature); #[test] diff --git a/openpgp/src/serialize.rs b/openpgp/src/serialize.rs index 7588fb4b..531b3d35 100644 --- a/openpgp/src/serialize.rs +++ b/openpgp/src/serialize.rs @@ -152,6 +152,7 @@ use crate::packet::signature::subpacket::{ SubpacketArea, Subpacket, SubpacketValue, SubpacketLength }; use crate::packet::prelude::*; +use crate::packet::signature::Signature3; use crate::seal; use crate::types::{ RevocationKey, @@ -1591,12 +1592,14 @@ impl seal::Sealed for Signature {} impl Marshal for Signature { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { match self { + Signature::V3(ref s) => s.serialize(o), Signature::V4(ref s) => s.serialize(o), } } fn export(&self, o: &mut dyn std::io::Write) -> Result<()> { match self { + Signature::V3(ref s) => s.export(o), Signature::V4(ref s) => s.export(o), } } @@ -1605,29 +1608,173 @@ impl Marshal for Signature { impl MarshalInto for Signature { fn serialized_len(&self) -> usize { match self { + Signature::V3(ref s) => s.serialized_len(), Signature::V4(ref s) => s.serialized_len(), } } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { match self { + Signature::V3(ref s) => s.serialize_into(buf), Signature::V4(ref s) => s.serialize_into(buf), } } fn export_into(&self, buf: &mut [u8]) -> Result<usize> { match self { + Signature::V3(ref s) => s.export_into(buf), Signature::V4(ref s) => s.export_into(buf), } } fn export_to_vec(&self) -> Result<Vec<u8>> { match self { + Signature::V3(ref s) => s.export_to_vec(), Signature::V4(ref s) => s.export_to_vec(), } } } +impl NetLength for Signature { + fn net_len(&self) -> usize { + match self { + Signature::V3(sig) => sig.net_len(), + Signature::V4(sig) => sig.net_len(), + } + } +} + +impl seal::Sealed for Signature3 {} +impl Marshal for Signature3 { + /// Writes a serialized version of the specified `Signature` + /// packet to `o`. + /// + /// # Errors + /// + /// Returns [`Error::InvalidArgument`] if `self` does not contain + /// a valid v3 signature. Because v3 signature support was added + /// late in the 1.x release cycle, `Signature3` is just a thin + /// wrapper around a `Signature4`. As such, it is possible to add + /// v4 specific data to a `Signature3`. In general, this isn't a + /// significnat problem as generating v3 is deprecated. + /// + /// [`Error::InvalidArgument`]: Error::InvalidArgument + fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { + use crate::packet::signature::subpacket::SubpacketTag; + + assert_eq!(self.version(), 3); + write_byte(o, self.version())?; + // hashed length. + write_byte(o, 5)?; + write_byte(o, self.typ().into())?; + if let Some(SubpacketValue::SignatureCreationTime(ct)) + = self.hashed_area().subpacket( + SubpacketTag::SignatureCreationTime) + .map(|sp| sp.value()) + { + write_be_u32(o, u32::from(*ct))?; + } else { + return Err(Error::InvalidArgument( + "Invalid v3 signature, missing creation time.".into()). |