From dc7a668bea6bc45fdba5bfe2565d1dcc4966251a Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Tue, 14 Feb 2023 19:37:54 +0100 Subject: openpgp: Implement SKESKv6 from the crypto refresh. - Use HKDF in the SKESKv6 to provide key separation. See https://gitlab.com/openpgp-wg/rfc4880bis/-/commit/a42ea9c05479cc8b0424c85b865d796d2250586c --- openpgp/src/packet/mod.rs | 10 +- openpgp/src/packet/prelude.rs | 2 +- openpgp/src/packet/skesk.rs | 375 +++++++++------------ openpgp/src/parse.rs | 67 ++-- openpgp/src/parse/stream.rs | 2 +- openpgp/src/serialize.rs | 42 ++- openpgp/src/serialize/stream.rs | 2 +- .../data/crypto-refresh/v6skesk-aes128-eax.pgp | 7 + .../data/crypto-refresh/v6skesk-aes128-gcm.pgp | 7 + .../data/crypto-refresh/v6skesk-aes128-ocb.pgp | 7 + 10 files changed, 248 insertions(+), 273 deletions(-) create mode 100644 openpgp/tests/data/crypto-refresh/v6skesk-aes128-eax.pgp create mode 100644 openpgp/tests/data/crypto-refresh/v6skesk-aes128-gcm.pgp create mode 100644 openpgp/tests/data/crypto-refresh/v6skesk-aes128-ocb.pgp diff --git a/openpgp/src/packet/mod.rs b/openpgp/src/packet/mod.rs index af927464..6956791c 100644 --- a/openpgp/src/packet/mod.rs +++ b/openpgp/src/packet/mod.rs @@ -480,7 +480,7 @@ impl Deref for Packet { Packet::CompressedData(ref packet) => &packet.common, Packet::PKESK(ref packet) => &packet.common, Packet::SKESK(SKESK::V4(ref packet)) => &packet.common, - Packet::SKESK(SKESK::V5(ref packet)) => &packet.skesk4.common, + Packet::SKESK(SKESK::V6(ref packet)) => &packet.skesk4.common, Packet::SEIP(ref packet) => &packet.common, #[allow(deprecated)] Packet::MDC(ref packet) => &packet.common, @@ -509,7 +509,7 @@ impl DerefMut for Packet { Packet::CompressedData(ref mut packet) => &mut packet.common, Packet::PKESK(ref mut packet) => &mut packet.common, Packet::SKESK(SKESK::V4(ref mut packet)) => &mut packet.common, - Packet::SKESK(SKESK::V5(ref mut packet)) => &mut packet.skesk4.common, + Packet::SKESK(SKESK::V6(ref mut packet)) => &mut packet.skesk4.common, Packet::SEIP(ref mut packet) => &mut packet.common, #[allow(deprecated)] Packet::MDC(ref mut packet) => &mut packet.common, @@ -1292,10 +1292,10 @@ impl DerefMut for PKESK { pub enum SKESK { /// SKESK packet version 4. V4(self::skesk::SKESK4), - /// SKESK packet version 5. + /// SKESK packet version 6. /// /// This feature is [experimental](super#experimental-features). - V5(self::skesk::SKESK5), + V6(self::skesk::SKESK6), } assert_send_and_sync!(SKESK); @@ -1304,7 +1304,7 @@ impl SKESK { pub fn version(&self) -> u8 { match self { SKESK::V4(_) => 4, - SKESK::V5(_) => 5, + SKESK::V6(_) => 6, } } } diff --git a/openpgp/src/packet/prelude.rs b/openpgp/src/packet/prelude.rs index 1e41b16a..c06339c1 100644 --- a/openpgp/src/packet/prelude.rs +++ b/openpgp/src/packet/prelude.rs @@ -57,6 +57,6 @@ pub use crate::packet::{ signature::Signature6, signature::SignatureBuilder, skesk::SKESK4, - skesk::SKESK5, + skesk::SKESK6, user_attribute, }; diff --git a/openpgp/src/packet/skesk.rs b/openpgp/src/packet/skesk.rs index 5331604e..98075970 100644 --- a/openpgp/src/packet/skesk.rs +++ b/openpgp/src/packet/skesk.rs @@ -12,7 +12,13 @@ use std::ops::{Deref, DerefMut}; use quickcheck::{Arbitrary, Gen}; use crate::Result; -use crate::crypto::{self, S2K, Password, SessionKey}; +use crate::crypto::{ + self, + S2K, + Password, + SessionKey, + backend::{Backend, interface::Kdf}, +}; use crate::crypto::aead::CipherOp; use crate::Error; use crate::types::{ @@ -31,7 +37,8 @@ impl SKESK { { match self { SKESK::V4(ref s) => s.decrypt(password), - SKESK::V5(ref s) => s.decrypt(password), + SKESK::V6(ref s) => + Ok((SymmetricAlgorithm::Unencrypted, s.decrypt(password)?)), } } } @@ -42,7 +49,7 @@ impl Arbitrary for SKESK { if bool::arbitrary(g) { SKESK::V4(SKESK4::arbitrary(g)) } else { - SKESK::V5(SKESK5::arbitrary(g)) + SKESK::V6(SKESK6::arbitrary(g)) } } } @@ -60,7 +67,7 @@ pub struct SKESK4 { pub(crate) common: packet::Common, /// Packet version. Must be 4 or 5. /// - /// This struct is also used by SKESK5, hence we have a version + /// This struct is also used by SKESK6, hence we have a version /// field. version: u8, /// Symmetric algorithm used to encrypt the session key. @@ -308,75 +315,18 @@ impl Arbitrary for SKESK4 { /// [Section 5.3 of RFC 4880]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-05#section-5.3 /// /// This feature is [experimental](super::super#experimental-features). -#[derive(Clone, Debug)] -pub struct SKESK5 { +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct SKESK6 { /// Common fields. pub(crate) skesk4: SKESK4, /// AEAD algorithm. aead_algo: AEADAlgorithm, /// Initialization vector for the AEAD algorithm. - /// - /// If we recognized the S2K object during parsing, we can - /// successfully parse the data into S2K, AEAED IV, and - /// ciphertext. However, if we do not recognize the S2K type, we - /// do not know how large its parameters are, so we cannot cleanly - /// parse it, and have to accept that the S2K's body bleeds into - /// the rest of the data. In this case, the raw data is put into - /// the `esk` field, and `aead_iv` is set to `None`. - aead_iv: Option>, - /// Digest for the AEAD algorithm. - aead_digest: Box<[u8]>, -} -assert_send_and_sync!(SKESK5); - -// Because the S2K, IV, and ESK cannot be cleanly separated at parse -// time, we need to carefully compare and hash SKESK5 packets. - -impl PartialEq for SKESK5 { - fn eq(&self, other: &SKESK5) -> bool { - self.skesk4.version == other.skesk4.version - && self.skesk4.sym_algo == other.skesk4.sym_algo - && self.aead_algo == other.aead_algo - && self.aead_digest == other.aead_digest - // Treat S2K, IV, and ESK as opaque blob. - && { - // XXX: This would be nicer without the allocations. - use crate::serialize::MarshalInto; - let mut a = self.skesk4.s2k.to_vec().unwrap(); - let mut b = other.skesk4.s2k.to_vec().unwrap(); - if let Ok(iv) = self.aead_iv() { - a.extend_from_slice(iv); - } - if let Ok(iv) = other.aead_iv() { - b.extend_from_slice(iv); - } - a.extend_from_slice(self.skesk4.raw_esk()); - b.extend_from_slice(other.skesk4.raw_esk()); - a == b - } - } + aead_iv: Box<[u8]>, } +assert_send_and_sync!(SKESK6); -impl Eq for SKESK5 {} - -impl std::hash::Hash for SKESK5 { - fn hash(&self, state: &mut H) { - self.skesk4.version.hash(state); - self.skesk4.sym_algo.hash(state); - self.aead_digest.hash(state); - // Treat S2K, IV, and ESK as opaque blob. - // XXX: This would be nicer without the allocations. - use crate::serialize::MarshalInto; - let mut a = self.skesk4.s2k.to_vec().unwrap(); - if let Some(iv) = self.aead_iv.as_ref() { - a.extend_from_slice(iv); - } - a.extend_from_slice(self.skesk4.raw_esk()); - a.hash(state); - } -} - -impl Deref for SKESK5 { +impl Deref for SKESK6 { type Target = SKESK4; fn deref(&self) -> &Self::Target { @@ -384,49 +334,33 @@ impl Deref for SKESK5 { } } -impl DerefMut for SKESK5 { +impl DerefMut for SKESK6 { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.skesk4 } } -impl SKESK5 { +impl SKESK6 { /// Creates a new SKESK version 5 packet. /// /// The given symmetric algorithm is the one used to encrypt the /// session key. - pub fn new(esk_algo: SymmetricAlgorithm, esk_aead: AEADAlgorithm, - s2k: S2K, iv: Box<[u8]>, esk: Box<[u8]>, digest: Box<[u8]>) + pub fn new(sym_algo: SymmetricAlgorithm, + aead_algo: AEADAlgorithm, + s2k: S2K, + aead_iv: Box<[u8]>, + esk: Box<[u8]>) -> Result { - Self::new_raw(esk_algo, esk_aead, s2k, Ok((iv, esk)), digest) - } - - /// Creates a new SKESK version 5 packet. - /// - /// The given symmetric algorithm is the one used to encrypt the - /// session key. - pub(crate) fn new_raw(esk_algo: SymmetricAlgorithm, esk_aead: AEADAlgorithm, - s2k: S2K, - iv_esk: std::result::Result<(Box<[u8]>, Box<[u8]>), - Box<[u8]>>, - digest: Box<[u8]>) - -> Result { - let (iv, esk) = match iv_esk { - Ok((iv, esk)) => (Some(iv), Ok(Some(esk))), - Err(raw) => (None, Err(raw)), - }; - - Ok(SKESK5{ - skesk4: SKESK4{ + Ok(SKESK6 { + skesk4: SKESK4 { common: Default::default(), - version: 5, - sym_algo: esk_algo, + version: 6, + sym_algo, s2k, - esk, + esk: Ok(Some(esk)), }, - aead_algo: esk_aead, - aead_iv: iv, - aead_digest: digest, + aead_algo, + aead_iv, }) } @@ -456,64 +390,55 @@ impl SKESK5 { } // Derive key and make a cipher. + let ad = [0xc3, 6, esk_algo.into(), esk_aead.into()]; let key = s2k.derive_key(password, esk_algo.key_size()?)?; - let mut iv = vec![0u8; esk_aead.nonce_size()?]; - crypto::random(&mut iv); - let aad = [0xc3, 5, esk_algo.into(), esk_aead.into()]; - let mut ctx = esk_aead.context(esk_algo, &key, &aad, &iv, - CipherOp::Encrypt)?; + + let mut kek: SessionKey = vec![0; esk_algo.key_size()?].into(); + Backend::hkdf_sha256(&key, None, &ad, &mut kek)?; + // Encrypt the session key with the KEK. + let mut iv = vec![0u8; esk_aead.nonce_size()?]; + crypto::random(&mut iv); + let mut ctx = + esk_aead.context(esk_algo, &kek, &ad, &iv, CipherOp::Encrypt)?; let mut esk_digest = vec![0u8; session_key.len() + esk_aead.digest_size()?]; ctx.encrypt_seal(&mut esk_digest, session_key)?; - let digest = esk_digest[session_key.len()..].to_vec(); - let esk = { - crate::vec_truncate(&mut esk_digest, session_key.len()); - esk_digest - }; - - SKESK5::new(esk_algo, esk_aead, s2k, iv.into_boxed_slice(), esk.into(), - digest.into_boxed_slice()) + // Attach digest to the ESK, we model it as one. + SKESK6::new(esk_algo, esk_aead, s2k, iv.into_boxed_slice(), + esk_digest.into()) } - /// Derives the key inside this `SKESK5` from `password`. + /// Derives the key inside this `SKESK6` from `password`. /// /// Returns a tuple containing a placeholder symmetric cipher and - /// the key itself. `SKESK5` packets do not contain the symmetric + /// the key itself. `SKESK6` packets do not contain the symmetric /// cipher algorithm and instead rely on the `AED` packet that /// contains it. - // XXX: This function should return Result, but then - // SKESK::decrypt must return an - // Result<(Option, _)> and - // DecryptionHelper::decrypt and PacketParser::decrypt must be - // adapted as well. pub fn decrypt(&self, password: &Password) - -> Result<(SymmetricAlgorithm, SessionKey)> { + -> Result { let key = self.s2k().derive_key(password, self.symmetric_algo().key_size()?)?; - if let Some(esk) = self.esk()? { - // Use the derived key to decrypt the ESK. - let aad = [0xc3, 5 /* Version. */, self.symmetric_algo().into(), - self.aead_algo.into()]; - let mut cipher = self.aead_algo.context( - self.symmetric_algo(), &key, &aad, self.aead_iv()?, - CipherOp::Decrypt)?; - - let mut plain: SessionKey = vec![0; esk.len()].into(); - let mut chunk = - Vec::with_capacity(esk.len() + self.aead_digest.len()); - chunk.extend_from_slice(esk); - chunk.extend_from_slice(&self.aead_digest); - cipher.decrypt_verify(&mut plain, &chunk)?; - Ok((SymmetricAlgorithm::Unencrypted, plain)) - } else { - Err(Error::MalformedPacket( - "No encrypted session key in v5 SKESK packet".into()) - .into()) - } + let mut kek: SessionKey = + vec![0; self.symmetric_algo().key_size()?].into(); + let ad = [0xc3, + 6 /* Version. */, + self.symmetric_algo().into(), + self.aead_algo.into()]; + Backend::hkdf_sha256(&key, None, &ad, &mut kek)?; + + // Use the derived key to decrypt the ESK. + let mut cipher = self.aead_algo.context( + self.symmetric_algo(), &kek, &ad, self.aead_iv(), + CipherOp::Decrypt)?; + + let mut plain: SessionKey = + vec![0; self.esk().len() - self.aead_algo.digest_size()?].into(); + cipher.decrypt_verify(&mut plain, self.esk())?; + Ok(plain) } /// Gets the AEAD algorithm. @@ -527,66 +452,59 @@ impl SKESK5 { } /// Gets the AEAD initialization vector. - /// - /// If the [`S2K`] mechanism is not supported by Sequoia, this - /// function will fail. Note that the information is not lost, - /// but stored in the packet. If the packet is serialized again, - /// it is written out. - /// - /// [`S2K`]: super::super::crypto::S2K - pub fn aead_iv(&self) -> Result<&[u8]> { - self.aead_iv.as_ref() - .map(|iv| &iv[..]) - .ok_or_else(|| Error::MalformedPacket( - format!("Unknown S2K: {:?}", self.s2k)).into()) + pub fn aead_iv(&self) -> &[u8] { + &self.aead_iv } /// Sets the AEAD initialization vector. - pub fn set_aead_iv(&mut self, iv: Box<[u8]>) -> Option> { - ::std::mem::replace(&mut self.aead_iv, Some(iv)) + pub fn set_aead_iv(&mut self, iv: Box<[u8]>) -> Box<[u8]> { + ::std::mem::replace(&mut self.aead_iv, iv) } - /// Gets the AEAD digest. - pub fn aead_digest(&self) -> &[u8] { - &self.aead_digest + /// Gets the encrypted session key. + pub fn esk(&self) -> &[u8] { + self.skesk4.raw_esk() } - /// Sets the AEAD digest. - pub fn set_aead_digest(&mut self, digest: Box<[u8]>) -> Box<[u8]> { - ::std::mem::replace(&mut self.aead_digest, digest) + /// Sets the encrypted session key. + pub fn set_esk(&mut self, esk: Box<[u8]>) -> Box<[u8]> { + ::std::mem::replace(&mut self.esk, Ok(Some(esk))) + .expect("v6 SKESK can always be parsed") + .expect("v6 SKESK packets always have an ESK") } } -impl From for super::SKESK { - fn from(p: SKESK5) -> Self { - super::SKESK::V5(p) +impl From for super::SKESK { + fn from(p: SKESK6) -> Self { + super::SKESK::V6(p) } } -impl From for Packet { - fn from(s: SKESK5) -> Self { - Packet::SKESK(SKESK::V5(s)) +impl From for Packet { + fn from(s: SKESK6) -> Self { + Packet::SKESK(SKESK::V6(s)) } } #[cfg(test)] -impl Arbitrary for SKESK5 { +impl Arbitrary for SKESK6 { fn arbitrary(g: &mut Gen) -> Self { let algo = AEADAlgorithm::const_default(); let mut iv = vec![0u8; algo.nonce_size().unwrap()]; for b in iv.iter_mut() { *b = u8::arbitrary(g); } - let mut digest = vec![0u8; algo.digest_size().unwrap()]; - for b in digest.iter_mut() { + let esk_len = + (u8::arbitrary(g) % 64) as usize + algo.digest_size().unwrap(); + let mut esk = vec![0u8; esk_len]; + for b in esk.iter_mut() { *b = u8::arbitrary(g); } - SKESK5::new(SymmetricAlgorithm::arbitrary(g), + SKESK6::new(SymmetricAlgorithm::arbitrary(g), algo, S2K::arbitrary(g), - iv.into_boxed_slice(), - Vec::::arbitrary(g).into(), - digest.into_boxed_slice()) + iv.into(), + esk.into()) .unwrap() } } @@ -596,61 +514,98 @@ mod test { use super::*; use crate::PacketPile; use crate::parse::Parse; - use crate::serialize::{Marshal, MarshalInto}; + use crate::serialize::MarshalInto; quickcheck! { - fn roundtrip(p: SKESK) -> bool { + fn roundtrip_v4(p: SKESK4) -> bool { + let p = SKESK::from(p); let q = SKESK::from_bytes(&p.to_vec().unwrap()).unwrap(); assert_eq!(p, q); true } } + quickcheck! { + fn roundtrip_v6(p: SKESK6) -> bool { + let p = SKESK::from(p); + let q = SKESK::from_bytes(&p.to_vec().unwrap()).unwrap(); + assert_eq!(p, q); + true + } + } + + /// This sample packet is from RFCXXX. + #[test] + fn v6skesk_aes128_ocb() -> Result<()> { + sample_skesk6_packet( + SymmetricAlgorithm::AES128, + AEADAlgorithm::OCB, + "crypto-refresh/v6skesk-aes128-ocb.pgp", + b"\xe8\x0d\xe2\x43\xa3\x62\xd9\x3b\ + \x9d\xc6\x07\xed\xe9\x6a\x73\x56", + b"\x28\xe7\x9a\xb8\x23\x97\xd3\xc6\ + \x3d\xe2\x4a\xc2\x17\xd7\xb7\x91") + } + + /// This sample packet is from RFCXXX. #[test] - fn sample_skesk5_packet() { - // This sample packet is from RFC4880bis-05, section A.3. + fn v6skesk_aes128_eax() -> Result<()> { + sample_skesk6_packet( + SymmetricAlgorithm::AES128, + AEADAlgorithm::EAX, + "crypto-refresh/v6skesk-aes128-eax.pgp", + b"\x15\x49\x67\xe5\x90\xaa\x1f\x92\ + \x3e\x1c\x0a\xc6\x4c\x88\xf2\x3d", + b"\x38\x81\xba\xfe\x98\x54\x12\x45\ + \x9b\x86\xc3\x6f\x98\xcb\x9a\x5e") + } + + /// This sample packet is from RFCXXX. + #[test] + fn v6skesk_aes128_gcm() -> Result<()> { + sample_skesk6_packet( + SymmetricAlgorithm::AES128, + AEADAlgorithm::GCM, + "crypto-refresh/v6skesk-aes128-gcm.pgp", + b"\x25\x02\x81\x71\x5b\xba\x78\x28\ + \xef\x71\xef\x64\xc4\x78\x47\x53", + b"\x19\x36\xfc\x85\x68\x98\x02\x74\ + \xbb\x90\x0d\x83\x19\x36\x0c\x77") + } + + fn sample_skesk6_packet(cipher: SymmetricAlgorithm, + aead: AEADAlgorithm, + name: &str, + derived_key: &[u8], + session_key: &[u8]) + -> Result<()> { let password: Password = String::from("password").into(); - let raw = [ - // Packet header: - 0xc3, 0x3e, - - // Version, algorithms, S2K fields: - 0x05, 0x07, 0x01, 0x03, 0x08, 0xcd, 0x5a, 0x9f, - 0x70, 0xfb, 0xe0, 0xbc, 0x65, 0x90, - - // AEAD IV: - 0xbc, 0x66, 0x9e, 0x34, 0xe5, 0x00, 0xdc, 0xae, - 0xdc, 0x5b, 0x32, 0xaa, 0x2d, 0xab, 0x02, 0x35, - - // AEAD encrypted CEK: - 0x9d, 0xee, 0x19, 0xd0, 0x7c, 0x34, 0x46, 0xc4, - 0x31, 0x2a, 0x34, 0xae, 0x19, 0x67, 0xa2, 0xfb, - - // Authentication tag: - 0x7e, 0x92, 0x8e, 0xa5, 0xb4, 0xfa, 0x80, 0x12, - 0xbd, 0x45, 0x6d, 0x17, 0x38, 0xc6, 0x3c, 0x36, - ]; let packets: Vec = - PacketPile::from_bytes(&raw[..]).unwrap().into_children().collect(); - assert_eq!(packets.len(), 1); - if let Packet::SKESK(SKESK::V5(ref s)) = packets[0] { - assert_eq!(&s.s2k().derive_key( - &password, s.symmetric_algo().key_size().unwrap()).unwrap()[..], - &[0xb2, 0x55, 0x69, 0xb9, 0x54, 0x32, 0x45, 0x66, - 0x45, 0x27, 0xc4, 0x97, 0x6e, 0x7a, 0x5d, 0x6e][..]); - - if AEADAlgorithm::EAX.is_supported() { - assert_eq!(&s.decrypt(&password).unwrap().1[..], - &[0x86, 0xf1, 0xef, 0xb8, 0x69, 0x52, 0x32, 0x9f, - 0x24, 0xac, 0xd3, 0xbf, 0xd0, 0xe5, 0x34, 0x6d][..]); + PacketPile::from_bytes( + crate::tests::file(name))? + .into_children().collect(); + assert_eq!(packets.len(), 2); + if let Packet::SKESK(SKESK::V6(ref s)) = packets[0] { + let derived = s.s2k().derive_key( + &password, s.symmetric_algo().key_size()?)?; + eprintln!("derived: {:x?}", &derived[..]); + assert_eq!(&derived[..], derived_key); + + if aead.is_supported() + && aead.supports_symmetric_algo(&cipher) + { + let sk = s.decrypt(&password)?; + eprintln!("sk: {:x?}", &sk[..]); + assert_eq!(&sk[..], session_key); + } else { + eprintln!("{}-{} is not supported, skipping decryption.", + cipher, aead); } } else { - panic!("bad packet"); + panic!("bad packet, expected v6 SKESK: {:?}", packets[0]); } - let mut serialized = Vec::new(); - packets[0].serialize(&mut serialized).unwrap(); - assert_eq!(&raw[..], &serialized[..]); + Ok(()) } /// Tests various S2K methods, with and without encrypted session diff --git a/openpgp/src/parse.rs b/openpgp/src/parse.rs index 62f1e225..935d2412 100644 --- a/openpgp/src/parse.rs +++ b/openpgp/src/parse.rs @@ -3307,7 +3307,7 @@ impl SKESK { let version = php_try!(php.parse_u8("version")); match version { 4 => SKESK4::parse(php), - 5 => SKESK5::parse(php), + 6 => SKESK6::parse(php), _ => php.fail("unknown version"), } } @@ -3342,58 +3342,49 @@ impl SKESK4 { } } -impl SKESK5 { +impl SKESK6 { /// Parses the body of an SK-ESK packet. fn parse(mut php: PacketHeaderParser) -> Result { make_php_try!(php); + + // Octet count of the following 5 fields. + let parameter_len = php_try!(php.parse_u8("parameter_len")); + if parameter_len < 1 + 1 + 1 + 2 /* S2K */ + 12 /* IV */ { + return php.fail("expected at least 16 parameter octets"); + } + let sym_algo: SymmetricAlgorithm = php_try!(php.parse_u8("sym_algo")).into(); let aead_algo: AEADAlgorithm = php_try!(php.parse_u8("aead_algo")).into(); - let s2k = php_try!(S2K::parse_v4(&mut php)); - let s2k_supported = s2k.is_supported(); - let iv_size = php_try!(aead_algo.nonce_size()); - let digest_size = php_try!(aead_algo.digest_size()); - - // The rest of the packet is (potentially) the S2K - // parameters, the AEAD IV, the ESK, and the AEAD - // digest. We don't know the size of the S2K - // parameters if the S2K method is not supported, and - // we don't know the size of the ESK. - let mut esk = php_try!(php.reader.steal_eof() - .map_err(anyhow::Error::from)); - let aead_iv = if s2k_supported && esk.len() >= iv_size { - // We know the S2K method, so the parameters have - // been parsed into the S2K object. So, `esk` - // starts with iv_size bytes of IV. - let mut iv = esk; - esk = iv.split_off(iv_size); - iv - } else { - Vec::with_capacity(0) // A dummy value. - }; - let l = esk.len(); - let aead_digest = esk.split_off(l.saturating_sub(digest_size)); - // Now fix the map. - if s2k_supported { - php.field("aead_iv", iv_size); + // The S2K object's length and the S2K. + let s2k_len = php_try!(php.parse_u8("s2k_len")); + if parameter_len < 1 + 1 + 1 + s2k_len + 12 /* IV */ { + return php.fail("S2K overflows parameter count"); } - php.field("esk", esk.len()); - php.field("aead_digest", aead_digest.len()); - let skesk = php_try!(SKESK5::new_raw( + let s2k = php_try!(S2K::parse_v6(&mut php, s2k_len)); + + // And the IV. + let iv = + if let Some(iv_len) = parameter_len.checked_sub(1 + 1 + 1 + s2k_len) { + php_try!(php.parse_bytes("iv", iv_len as usize)).into() + } else { + return php.fail("IV overflows parameter count"); + }; + + // Finally, the ESK including the AEAD tag. + let esk = php_try!(php.parse_bytes_eof("esk")).into(); + + let skesk = php_try!(SKESK6::new( sym_algo, aead_algo, s2k, - if s2k_supported { - Ok((aead_iv.into(), esk.into())) - } else { - Err(esk.into()) - }, - aead_digest.into_boxed_slice(), + iv, + esk, )); php.ok(skesk.into()) diff --git a/openpgp/src/parse/stream.rs b/openpgp/src/parse/stream.rs index d3647912..b42ae16b 100644 --- a/openpgp/src/parse/stream.rs +++ b/openpgp/src/parse/stream.rs @@ -2448,7 +2448,7 @@ impl<'a, H: VerificationHelper + DecryptionHelper> Decryptor<'a, H> { { let decryption_proxy = |algo, secret: &SessionKey| { // Take the algo from the AED packet over - // the dummy one from the SKESK5 packet. + // the dummy one from the SKESK6 packet. let algo = sym_algo_hint.unwrap_or(algo); let result = pp.decrypt(algo, secret); t!("pp.decrypt({:?}, {:?}) => {:?}", diff --git a/openpgp/src/serialize.rs b/openpgp/src/serialize.rs index 65bac0fe..11133e01 100644 --- a/openpgp/src/serialize.rs +++ b/openpgp/src/serialize.rs @@ -2818,7 +2818,7 @@ impl Marshal for SKESK { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { match self { SKESK::V4(ref s) => s.serialize(o), - SKESK::V5(ref s) => s.serialize(o), + SKESK::V6(ref s) => s.serialize(o), } } } @@ -2827,7 +2827,7 @@ impl NetLength for SKESK { fn net_len(&self) -> usize { match self { SKESK::V4(ref s) => s.net_len(), - SKESK::V5(ref s) => s.net_len(), + SKESK::V6(ref s) => s.net_len(), } } } @@ -2836,7 +2836,7 @@ impl MarshalInto for SKESK { fn serialized_len(&self) -> usize { match self { SKESK::V4(ref s) => s.serialized_len(), - SKESK::V5(ref s) => s.serialized_len(), + SKESK::V6(ref s) => s.serialized_len(), } } @@ -2844,7 +2844,7 @@ impl MarshalInto for SKESK { match self { SKESK::V4(s) => generic_serialize_into(s, MarshalInto::serialized_len(s), buf), - SKESK::V5(s) => + SKESK::V6(s) => generic_serialize_into(s, MarshalInto::serialized_len(s), buf), } } @@ -2880,36 +2880,44 @@ impl MarshalInto for SKESK4 { } } -impl seal::Sealed for SKESK5 {} -impl Marshal for SKESK5 { +impl seal::Sealed for SKESK6 {} +impl Marshal for SKESK6 { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { - write_byte(o, 5)?; // Version. + let s2k_len = self.s2k().serialized_len(); + + write_byte(o, 6)?; // Version. + // Parameter octet count. + write_byte(o, (1 // Symmetric algorithm. + + 1 // AEAD mode. + + 1 // S2K octet count. + + s2k_len + + self.aead_iv().len()) as u8)?; write_byte(o, self.symmetric_algo().into())?; write_byte(o, self.aead_algo().into())?; + // S2K octet count. + write_byte(o, s2k_len as u8)?; self.s2k().serialize(o)?; - if let Ok(iv) = self.aead_iv() { - o.write_all(iv)?; - } - o.write_all(self.raw_esk())?; - o.write_all(self.aead_digest())?; + o.write_all(self.aead_iv())?; + o.write_all(self.esk())?; Ok(()) } } -impl NetLength for SKESK5 { +impl NetLength for SKESK6 { fn net_len(&self) -> usize { 1 // Version. + + 1 // Parameter octet count. + 1 // Cipher algo. + 1 // AEAD algo. + + 1 // S2K octet count. + self.s2k().serialized_len() - + self.aead_iv().map(|iv| iv.len()).unwrap_or(0) - + self.raw_esk().len() - + self.aead_digest().len() + + self.aead_iv().len() + + self.esk().len() } } -impl MarshalInto for SKESK5 { +impl MarshalInto for SKESK6 { fn serialized_len(&self) -> usize { self.net_len() } diff --git a/openpgp/src/serialize/stream.rs b/openpgp/src/serialize/stream.rs index 61adbf55..e5abe9a9 100644 --- a/openpgp/src/serialize/stream.rs +++ b/openpgp/src/serialize/stream.rs @@ -2998,7 +2998,7 @@ impl<'a, 'b> Encryptor2<'a, 'b> { // Write the SKESK packet(s). for password in self.passwords.iter() { if let Some(aead) = aead.as_ref() { - let skesk = SKESK5::with_password(self.sym_algo, + let skesk = SKESK6::with_password(self.sym_algo, self.sym_algo, aead.algo, Default::default(), diff --git a/openpgp/tests/data/crypto-refresh/v6skesk-aes128-eax.pgp b/openpgp/tests/data/crypto-refresh/v6skesk-aes128-eax.pgp new file mode 100644 index 00000000..fdb5b18e --- /dev/null +++ b/openpgp/tests/data/crypto-refresh/v6skesk-aes128-eax.pgp @@ -0,0 +1,7 @@ +-----BEGIN PGP MESSAGE----- + +w0AGHgcBCwMIpa5XnR/F2Cv/aSJPkZmTs1Bvo7WaanPP+MXvxfQcV/tU4cImgV14 +KPX5LEVOtl6+AKtZhsaObnxV0mkCBwEGn/kOOzIZZPOkKRPI3MZhkyUBUifvt+rq +pJ8EwuZ0F11KPSJu1q/LnKmsEiwUcOEcY9TAqyQcapOK1Iv5mlqZuQu6gyXeYQR1 +QCWKt5Wala0FHdqW6xVDHf719eIlXKeCYVRuM5o= +-----END PGP MESSAGE----- diff --git a/openpgp/tests/data/crypto-refresh/v6skesk-aes128-gcm.pgp b/openpgp/tests/data/crypto-refresh/v6skesk-aes128-gcm.pgp new file mode 100644 index 00000000..be593ad7 --- /dev/null +++ b/openpgp/tests/data/crypto-refresh/v6skesk-aes128-gcm.pgp @@ -0,0 +1,7 @@ +-----BEGIN PGP MESSAGE----- + +wzwGGgcDCwMI6dOXhbIHAAj/tC58SD70iERXyzcmubPbn/d25fTZpAlS4kRymIUa +v/91Jt8t1VRBdXmneZ/SaQIHAwb8uUSQvLmLvcnRBsYJAmaUD3LontwhtVlrFXax +Ae0Pn/xvxtZbv9JNzQeQlm5tHoWjAFN4TLHYtqBpnvEhVaeyrWJYUxtXZR/Xd3kS ++pXjXZtAIW9ppMJI2yj/QzHxYykHOZ5v+Q== +-----END PGP MESSAGE----- diff --git a/openpgp/tests/data/crypto-refresh/v6skesk-aes128-ocb.pgp b/openpgp/tests/data/crypto-refresh/v6skesk-aes128-ocb.pgp new file mode 100644 index 00000000..f11e5cf8 --- /dev/null +++ b/openpgp/tests/data/crypto-refresh/v6skesk-aes128-ocb.pgp @@ -0,0 +1,7 @@ +-----BEGIN PGP MESSAGE----- + +wz8GHQcCCwMIVqKY0vXjZFP/z8xcEWZO2520JZDX3EawckG2EsOBLP/76gDyNHsl +ZBEj+IeuYNT9YU4IN9gZ02zSaQIHAgYgpmH3MfyaMDK1YjMmAn46XY21dI6+/wsM +WRDQns3WQf+f04VidYA1vEl1TOG/P/+n2tCjuBBPUTPPQqQQCoPu9MobSAGohGv0 +K82nyM6dZeIS8wHLzZj9yt5pSod61CRzI/boVw== +-----END PGP MESSAGE----- -- cgit v1.2.3