diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2023-02-13 16:57:48 +0100 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2023-06-26 13:24:45 +0200 |
commit | 99247e3aed358fb6d073fed3b0503d74dc212184 (patch) | |
tree | a135f571ac0cb8a1d0b95493a21878966ddc6a51 | |
parent | 9dbe8f192d4e04cb87706fc7dd4ddfd4174ac737 (diff) |
openpgp: Add support for v6 signature packets.
todo:
- check salt lengths on verification
- salt length in arbitrary
- streaming signing
-rw-r--r-- | openpgp/src/cert.rs | 22 | ||||
-rw-r--r-- | openpgp/src/crypto/hash.rs | 204 | ||||
-rw-r--r-- | openpgp/src/packet/mod.rs | 18 | ||||
-rw-r--r-- | openpgp/src/packet/prelude.rs | 1 | ||||
-rw-r--r-- | openpgp/src/packet/signature.rs | 155 | ||||
-rw-r--r-- | openpgp/src/packet/signature/v6.rs | 282 | ||||
-rw-r--r-- | openpgp/src/parse.rs | 49 | ||||
-rw-r--r-- | openpgp/src/serialize.rs | 96 | ||||
-rw-r--r-- | openpgp/src/types/mod.rs | 14 | ||||
-rw-r--r-- | openpgp/tests/data/crypto-refresh/v6-minimal-cert.key | 12 | ||||
-rw-r--r-- | openpgp/tests/data/crypto-refresh/v6-minimal-secret.key | 14 |
11 files changed, 835 insertions, 32 deletions
diff --git a/openpgp/src/cert.rs b/openpgp/src/cert.rs index 819b9245..0553b444 100644 --- a/openpgp/src/cert.rs +++ b/openpgp/src/cert.rs @@ -7237,4 +7237,26 @@ Pu1xwz57O4zo1VYf6TqHJzVC3OMvMUM2hhdecMUe5x6GorNaj6g= Ok(()) } + + #[test] + fn v6_minimal_cert() -> Result<()> { + let p = &crate::policy::StandardPolicy::new(); + let t = None; // XXX + let cert = Cert::from_bytes( + crate::tests::file("crypto-refresh/v6-minimal-cert.key"))?; + assert_eq!(cert.userids().count(), 0); + let vcert = cert.with_policy(p, t)?; + assert_eq!(vcert.keys().count(), 2); + assert_eq!(vcert.keys().for_signing().count(), 1); + assert_eq!(vcert.keys().for_transport_encryption().count(), 1); + + let cert = Cert::from_bytes( + crate::tests::file("crypto-refresh/v6-minimal-secret.key")).unwrap(); + assert_eq!(cert.userids().count(), 0); + let vcert = cert.with_policy(p, t)?; + assert_eq!(vcert.keys().count(), 2); + assert_eq!(vcert.keys().for_signing().count(), 1); + assert_eq!(vcert.keys().for_transport_encryption().count(), 1); + Ok(()) + } } diff --git a/openpgp/src/crypto/hash.rs b/openpgp/src/crypto/hash.rs index 765a6211..bb905b63 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, Key6}; use crate::packet::Signature; -use crate::packet::signature::{self, Signature3, Signature4}; +use crate::packet::signature::{self, Signature3, Signature4, Signature6}; use crate::Result; use crate::types::Timestamp; @@ -468,6 +468,7 @@ impl Hash for Signature { match self { Signature::V3(sig) => sig.hash(hash), Signature::V4(sig) => sig.hash(hash), + Signature::V6(sig) => sig.hash(hash), } } } @@ -501,17 +502,17 @@ impl Hash for Signature3 { impl Hash for Signature4 { fn hash(&self, hash: &mut dyn Digest) { - self.fields.hash(hash); + Self::hash_fields(hash, &self.fields); } } -impl Hash for signature::SignatureFields { - fn hash(&self, hash: &mut dyn Digest) { +impl Signature4 { + fn hash_fields(hash: &mut dyn Digest, sig: &signature::SignatureFields) { use crate::serialize::MarshalInto; // XXX: Annoyingly, we have no proper way of handling errors // here. - let hashed_area = self.hashed_area().to_vec() + let hashed_area = sig.hashed_area().to_vec() .unwrap_or_else(|_| Vec::new()); // A version 4 signature packet is laid out as follows: @@ -528,9 +529,9 @@ impl Hash for signature::SignatureFields { // Version. header[0] = 4; - header[1] = self.typ().into(); - header[2] = self.pk_algo().into(); - header[3] = self.hash_algo().into(); + header[1] = sig.typ().into(); + header[2] = sig.pk_algo().into(); + header[3] = sig.hash_algo().into(); // The length of the hashed area, as a 16-bit big endian number. let len = hashed_area.len() as u16; @@ -563,13 +564,91 @@ impl Hash for signature::SignatureFields { } } +impl Hash for Signature6 { + fn hash(&self, hash: &mut dyn Digest) { + Self::hash_fields(hash, &self.fields); + } +} + +impl Signature6 { + fn hash_fields(hash: &mut dyn Digest, sig: &signature::SignatureFields) { + use crate::serialize::MarshalInto; + + // XXX: Annoyingly, we have no proper way of handling errors + // here. + let hashed_area = sig.hashed_area().to_vec() + .unwrap_or_else(|_| Vec::new()); + + // A version 6 signature packet is laid out as follows: + // + // version - 1 byte \ + // type - 1 byte \ + // pk_algo - 1 byte \ + // hash_algo - 1 byte Included in the hash + // hashed_area_len - 4 bytes (big endian)/ + // hashed_area _/ + // ... <- Not included in the hash + + let mut header = [0u8; 8]; + + // Version. + header[0] = 6; + header[1] = sig.typ().into(); + header[2] = sig.pk_algo().into(); + header[3] = sig.hash_algo().into(); + + // The length of the hashed area, as a 16-bit big endian number. + let len = hashed_area.len() as u32; + header[4..8].copy_from_slice(&len.to_be_bytes()); + + hash.update(&header[..]); + hash.update(&hashed_area); + + // A version 6 signature trailer is: + // + // version - 1 byte + // 0xFF (constant) - 1 byte + // amount - 4 bytes (big endian) + // + // The amount field is the amount of hashed from this + // packet (this excludes the message content, and this + // trailer) modulo 2**32. + // + // See https://tools.ietf.org/html/rfc4880#section-5.2.4 + let mut trailer = [0u8; 6]; + + trailer[0] = 6; + trailer[1] = 0xff; + // The signature packet's length, not including the previous + // two bytes and the length modulo 2**32. + let len = header.len() + hashed_area.len(); + trailer[2..6].copy_from_slice(&(len as u32).to_be_bytes()); + + hash.update(&trailer[..]); + } +} + +impl Hash for signature::SignatureBuilder { + fn hash(&self, hash: &mut dyn Digest) { + match self.version { + signature::SBVersion::V4 {} => + Signature4::hash_fields(hash, &self.fields), + signature::SBVersion::V6 { .. } => + Signature6::hash_fields(hash, &self.fields), + } + } +} + /// Hashing-related functionality. /// /// <a id="hashing-functions"></a> -impl signature::SignatureFields { +impl signature::SignatureBuilder { /// Hashes this standalone signature. pub fn hash_standalone(&self, hash: &mut dyn Digest) { + if let Some(salt) = self.prefix_salt() { + hash.update(salt); + } self.hash(hash); } @@ -585,6 +664,9 @@ impl signature::SignatureFields { key: &Key<P, key::PrimaryRole>) where P: key::KeyParts, { + if let Some(salt) = self.prefix_salt() { + hash.update(salt); + } key.hash(hash); self.hash(hash); } @@ -597,6 +679,9 @@ impl signature::SignatureFields { where P: key::KeyParts, Q: key::KeyParts, { + if let Some(salt) = self.prefix_salt() { + hash.update(salt); + } key.hash(hash); subkey.hash(hash); self.hash(hash); @@ -610,6 +695,9 @@ impl signature::SignatureFields { where P: key::KeyParts, Q: key::KeyParts, { + if let Some(salt) = self.prefix_salt() { + hash.update(salt); + } self.hash_subkey_binding(hash, key, subkey); } @@ -620,6 +708,9 @@ impl signature::SignatureFields { userid: &UserID) where P: key::KeyParts, { + if let Some(salt) = self.prefix_salt() { + hash.update(salt); + } key.hash(hash); userid.hash(hash); self.hash(hash); @@ -635,6 +726,9 @@ impl signature::SignatureFields { ua: &UserAttribute) where P: key::KeyParts, { + if let Some(salt) = self.prefix_salt() { + hash.update(salt); + } key.hash(hash); ua.hash(hash); self.hash(hash); @@ -645,12 +739,104 @@ impl signature::SignatureFields { /// /// <a id="hashing-functions"></a> impl Signature { + /// Hashes this standalone signature. + pub fn hash_standalone(&self, hash: &mut dyn Digest) + { + if let Some(salt) = self.salt() { + hash.update(salt); + } + self.hash(hash); + } + + /// Hashes this timestamp signature. + pub fn hash_timestamp(&self, hash: &mut dyn Digest) + { + self.hash_standalone(hash); + } + + /// Hashes this direct key signature over the specified primary + /// key, and the primary key. + pub fn hash_direct_key<P>(&self, hash: &mut dyn Digest, + key: &Key<P, key::PrimaryRole>) + where P: key::KeyParts, + { + if let Some(salt) = self.salt() { + hash.update(salt); + } + key.hash(hash); + self.hash(hash); + } + + /// Hashes this subkey binding over the specified primary key and + /// subkey, the primary key, and the subkey. + pub fn hash_subkey_binding<P, Q>(&self, hash: &mut dyn Digest, + key: &Key<P, key::PrimaryRole>, + subkey: &Key<Q, key::SubordinateRole>) + where P: key::KeyParts, + Q: key::KeyParts, + { + if let Some(salt) = self.salt() { + hash.update(salt); + } + key.hash(hash); + subkey.hash(hash); + self.hash(hash); + } + + /// Hashes this primary key binding over the specified primary key + /// and subkey, the primary key, and the subkey. + pub fn hash_primary_key_binding<P, Q>(&self, hash: &mut dyn Digest, + key: &Key<P, key::PrimaryRole>, + subkey: &Key<Q, key::SubordinateRole>) + where P: key::KeyParts, + Q: key::KeyParts, + { + if let Some(salt) = self.salt() { + hash.update(salt); + } + self.hash_subkey_binding(hash, key, subkey); + } + + /// Hashes this user ID binding over the specified primary key and + /// user ID, the primary key, and the userid. + pub fn hash_userid_binding<P>(&self, hash: &mut dyn Digest, + key: &Key<P, key::PrimaryRole>, + userid: &UserID) + where P: key::KeyParts, + { + if let Some(salt) = self.salt() { + hash.update(salt); + } + key.hash(hash); + userid.hash(hash); + self.hash(hash); + } + + /// Hashes this user attribute binding over the specified primary + /// key and user attribute, the primary key, and the user + /// attribute. + pub fn hash_user_attribute_binding<P>( + &self, + hash: &mut dyn Digest, + key: &Key<P, key::PrimaryRole>, + ua: &UserAttribute) + where P: key::KeyParts, + { + if let Some(salt) = self.salt() { + hash.update(salt); + } + key.hash(hash); + ua.hash(hash); + self.hash(hash); + } + /// Hashes this signature for use in a Third-Party Confirmation /// 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), + Signature::V6(s) => s.hash_for_confirmation(hash), } } } diff --git a/openpgp/src/packet/mod.rs b/openpgp/src/packet/mod.rs index 513c40ad..3d72846c 100644 --- a/openpgp/src/packet/mod.rs +++ b/openpgp/src/packet/mod.rs @@ -993,6 +993,9 @@ pub enum Signature { /// Signature packet version 4. V4(self::signature::Signature4), + + /// Signature packet version 6. + V6(self::signature::Signature6), } assert_send_and_sync!(Signature); @@ -1002,6 +1005,7 @@ impl Signature { match self { Signature::V3(_) => 3, Signature::V4(_) => 4, + Signature::V6(_) => 6, } } } @@ -1012,6 +1016,18 @@ impl From<Signature> for Packet { } } +impl Signature { + /// Gets the salt, if any. + pub fn salt(&self) -> Option<&[u8]> { + match self { + Signature::V3(_) => None, + Signature::V4(_) => None, + Signature::V6(s) => Some(s.salt()), + } + } + +} + // Trivial forwarder for singleton enum. impl Deref for Signature { type Target = signature::Signature4; @@ -1020,6 +1036,7 @@ impl Deref for Signature { match self { Signature::V3(sig) => &sig.intern, Signature::V4(sig) => sig, + Signature::V6(sig) => &sig.common, } } } @@ -1030,6 +1047,7 @@ impl DerefMut for Signature { match self { Signature::V3(ref mut sig) => &mut sig.intern, Signature::V4(ref mut sig) => sig, + Signature::V6(ref mut sig) => &mut sig.common, } } } diff --git a/openpgp/src/packet/prelude.rs b/openpgp/src/packet/prelude.rs index cd69d3dd..5c3d7782 100644 --- a/openpgp/src/packet/prelude.rs +++ b/openpgp/src/packet/prelude.rs @@ -52,6 +52,7 @@ pub use crate::packet::{ seip::SEIP1, signature, signature::Signature4, + signature::Signature6, signature::SignatureBuilder, skesk::SKESK4, skesk::SKESK5, diff --git a/openpgp/src/packet/signature.rs b/openpgp/src/packet/signature.rs index c5f3b019..d09ea346 100644 --- a/openpgp/src/packet/signature.rs +++ b/openpgp/src/packet/signature.rs @@ -154,7 +154,7 @@ use crate::types::Timestamp; #[cfg(test)] /// Like quickcheck::Arbitrary, but bounded. -trait ArbitraryBounded { +pub(crate) trait ArbitraryBounded { /// Generates an arbitrary value, but only recurses if `depth > /// 0`. fn arbitrary_bounded(g: &mut Gen, depth: usize) -> Self; @@ -178,6 +178,8 @@ macro_rules! impl_arbitrary_with_bound { } pub mod subpacket; +mod v6; +pub use v6::Signature6; /// How many seconds to backdate signatures. /// @@ -285,6 +287,20 @@ impl SignatureFields { } } +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub(crate) enum SBVersion { + V4 {}, + V6 { + salt: Vec<u8>, + }, +} + +impl Default for SBVersion { + fn default() -> Self { + SBVersion::V4 {} + } +} + /// A Signature builder. /// /// The `SignatureBuilder` is used to create [`Signature`]s. Although @@ -451,7 +467,8 @@ pub struct SignatureBuilder { reference_time: Option<SystemTime>, overrode_creation_time: bool, original_creation_time: Option<SystemTime>, - fields: SignatureFields, + pub(crate) fields: SignatureFields, + pub(crate) version: SBVersion, } assert_send_and_sync!(SignatureBuilder); @@ -482,7 +499,8 @@ impl SignatureBuilder { pk_algo: PublicKeyAlgorithm::Unknown(0), hash_algo: HashAlgorithm::default(), subpackets: SubpacketAreas::default(), - } + }, + version: SBVersion::default(), } } @@ -1702,7 +1720,24 @@ impl SignatureBuilder { /// # Ok(()) } /// ``` pub fn pre_sign(mut self, signer: &dyn Signer) -> Result<Self> { - self.pk_algo = signer.public().pk_algo(); + let pk = signer.public(); + self.pk_algo = pk.pk_algo(); + + // Set the version. A Key6 will create a Signature6, a key4 + // will create a Signature4. If necessary, generate a salt. + // If the salt has been explicitly set, it is not changed. + self.version = match (self.version, pk.version()) { + (SBVersion::V4 {}, 4) => SBVersion::V4 {}, + (SBVersion::V6 { .. }, 4) => SBVersion::V4 {}, + (SBVersion::V4 {}, 6) => { + let mut salt = vec![0; self.fields.hash_algo().salt_size()?]; + crate::crypto::random(&mut salt); + SBVersion::V6 { salt } + }, + (SBVersion::V6 { salt }, 6) => SBVersion::V6 { salt }, + (_, n) => return Err(Error::InvalidOperation( + format!("Unsupported key version {}", n)).into()), + }; // Set the creation time. if ! self.overrode_creation_time { @@ -1711,31 +1746,83 @@ impl SignatureBuilder { } } - // Make sure we have an issuer packet. - if self.issuers().next().is_none() - && self.issuer_fingerprints().next().is_none() - { - self = self.set_issuer(signer.public().keyid())? - .set_issuer_fingerprint(signer.public().fingerprint())?; - } + match &self.version { + SBVersion::V4 {} => { + // Make sure we have an issuer packet. + if self.issuers().next().is_none() + && self.issuer_fingerprints().next().is_none() + { + self = self.set_issuer(signer.public().keyid())? + .set_issuer_fingerprint(signer.public().fingerprint())?; + } + + // Add a salt to v4 signatures to make the signature + // unpredictable. + let mut salt = [0; 32]; + crate::crypto::random(&mut salt); + self = self.set_notation("salt@notations.sequoia-pgp.org", + salt, None, false)?; + }, + SBVersion::V6 { .. } => { + // Make sure we have an issuer fingerprint packet. + if self.issuer_fingerprints().next().is_none() { + self = self + .set_issuer_fingerprint(signer.public().fingerprint())?; + } - // Add a salt to make the signature unpredictable. - let mut salt = [0; 32]; - crate::crypto::random(&mut salt); - self = self.set_notation("salt@notations.sequoia-pgp.org", - salt, None, false)?; + // In v6 signatures, we have a proper prefix salt. + }, + } self.sort(); Ok(self) } + /// Returns the prefix salt. + /// + /// In OpenPGP v6 signatures, a salt is prefixed to the data + /// stream hashed in the signature. If a v6 signature is + /// generated by using a v6 key, then a salt will be added + /// automatically. If a salt has been set explicitly (see + /// [`SignatureBuilder::set_prefix_salt`]), this function will + /// return the salt. + pub fn prefix_salt(&self) -> Option<&[u8]> { + match &self.version { + SBVersion::V4 {} => None, + SBVersion::V6 { salt } => Some(salt), + } + } + + /// Explicitly sets the prefix salt. + /// + /// In OpenPGP v6 signatures, a salt is prefixed to the data + /// stream hashed in the signature. If a v6 signature is + /// generated by using a v6 key, then a salt will be added + /// automatically. In general, you do not need to call this + /// function. + /// + /// When streaming a signed message, it is useful to explicitly + /// set the salt using this function. + pub fn set_prefix_salt(mut self, new_salt: Vec<u8>) -> (Self, Option<Vec<u8>>) { + let mut old = None; + + self.version = match std::mem::take(&mut self.version) { + SBVersion::V4 {} => SBVersion::V6 { salt: new_salt }, + SBVersion::V6 { salt } => { + old = Some(salt); + SBVersion::V6 { salt: new_salt } + }, + }; + + (self, old) + } + fn sign(self, signer: &mut dyn Signer, digest: Vec<u8>) -> Result<Signature> { let mpis = signer.sign(self.hash_algo, &digest)?; - - Ok(Signature4 { + let v4 = Signature4 { common: Default::default(), fields: self.fields, digest_prefix: [digest[0], digest[1]], @@ -1743,7 +1830,13 @@ impl SignatureBuilder { computed_digest: Some(digest), level: 0, additional_issuers: Vec::with_capacity(0), - }.into()) + }; + + match self.version { + SBVersion::V4 {} => Ok(v4.into()), + SBVersion::V6 { salt } => + Ok(Signature6::from_common(v4, salt)?.into()) + } } } @@ -1752,6 +1845,7 @@ impl From<Signature> for SignatureBuilder { match sig { Signature::V3(sig) => sig.into(), Signature::V4(sig) => sig.into(), + Signature::V6(sig) => sig.into(), } } } @@ -1777,10 +1871,17 @@ impl From<Signature4> for SignatureBuilder { overrode_creation_time: false, original_creation_time: creation_time, fields, + version: Default::default(), } } } +impl From<Signature6> for SignatureBuilder { + fn from(sig: Signature6) -> Self { + SignatureBuilder::from(sig.common) + } +} + /// Holds a v4 Signature packet. /// /// This holds a [version 4] Signature packet. Normally, you won't @@ -2699,6 +2800,13 @@ impl Signature { "Signature has no creation time subpacket".into()).into()); } + // XXX if hash_algo.salt_size().map(|expected| expected != salt_len) + // XXX .unwrap_or(false) + // XXX { + // XXX return php.fail(format!("bad salt length, expected {} got {}", + // XXX ); + // XXX } + let result = key.verify(self.mpis(), self.hash_algo(), digest.as_ref()); if result.is_ok() { // Mark information in this signature as authenticated. @@ -3378,10 +3486,11 @@ impl From<Signature4> for super::Signature { #[cfg(test)] impl ArbitraryBounded for super::Signature { fn arbitrary_bounded(g: &mut Gen, depth: usize) -> Self { - if bool::arbitrary(g) { - Signature3::arbitrary_bounded(g, depth).into() - } else { - Signature4::arbitrary_bounded(g, depth).into() + match u8::arbitrary(g) % 3 { + 0 => Signature3::arbitrary_bounded(g, depth).into(), + 1 => Signature4::arbitrary_bounded(g, depth).into(), + 2 => Signature6::arbitrary_bounded(g, depth).into(), + _ => unreachable!(), } } } diff --git a/openpgp/src/packet/signature/v6.rs b/openpgp/src/packet/signature/v6.rs new file mode 100644 index 00000000..d0993397 --- /dev/null +++ b/openpgp/src/packet/signature/v6.rs @@ -0,0 +1,282 @@ +//! OpenPGP v6 signature implementation. + +use std::cmp::Ordering; +use std::convert::TryFrom; +use std::fmt; +use std::ops::{Deref, DerefMut}; + +use crate::{ + Error, + HashAlgorithm, + Packet, + PublicKeyAlgorithm, + Result, + SignatureType, + crypto::mpi, + packet::{ + Signature, + signature::{ + Signature4, + subpacket::{ + SubpacketArea, + }, + }, + }, +}; + +/// Holds a v6 Signature packet. +/// +/// This holds a [version 6] 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 6]: https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-08.html#name-version-4-and-6-signature-p +/// [`Signature`]: super::Signature +#[derive(Clone)] +pub struct Signature6 { + pub(crate) common: Signature4, + salt: Vec<u8>, +} +assert_send_and_sync!(Signature6); + +impl TryFrom<Signature> for Signature6 { + type Error = anyhow::Error; + + fn try_from(sig: Signature) -> Result<Self> { + match sig { + Signature::V6(sig) => Ok(sig), + sig => Err( + Error::InvalidArgument( + format!( + "Got a v{}, require a v6 signature", + sig.version())) + .into()), + } + } +} + +// Yes, Signature6 derefs to Signature4. This is because Signature +// derefs to Signature4 so this is the only way to add support for v6 +// sigs without breaking the semver. +impl Deref for Signature6 { + type Target = Signature4; + + fn deref(&self) -> &Self::Target { + &self.common + } +} + +impl DerefMut for Signature6 { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.common + } +} + +impl fmt::Debug for Signature6 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Signature6") + .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) |