summaryrefslogtreecommitdiffstats
path: root/openpgp/src/packet/signature.rs
diff options
context:
space:
mode:
Diffstat (limited to 'openpgp/src/packet/signature.rs')
-rw-r--r--openpgp/src/packet/signature.rs288
1 files changed, 287 insertions, 1 deletions
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 = [