diff options
Diffstat (limited to 'openpgp/src/packet/skesk.rs')
-rw-r--r-- | openpgp/src/packet/skesk.rs | 374 |
1 files changed, 165 insertions, 209 deletions
diff --git a/openpgp/src/packet/skesk.rs b/openpgp/src/packet/skesk.rs index 6dab9d8a..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,74 +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<Box<[u8]>>, - /// 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_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 - } - } -} - -impl Eq for SKESK5 {} - -impl std::hash::Hash for SKESK5 { - fn hash<H: std::hash::Hasher>(&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); - } + aead_iv: Box<[u8]>, } +assert_send_and_sync!(SKESK6); -impl Deref for SKESK5 { +impl Deref for SKESK6 { type Target = SKESK4; fn deref(&self) -> &Self::Target { @@ -383,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> { - 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<Self> { - 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, }) } @@ -455,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<SessionKey>, but then - // SKESK::decrypt must return an - // Result<(Option<SymmetricAlgorithm>, _)> and - // DecryptionHelper::decrypt and PacketParser::decrypt must be - // adapted as well. pub fn decrypt(&self, password: &Password) - -> Result<(SymmetricAlgorithm, SessionKey)> { + -> Result<SessionKey> { 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. @@ -526,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<Box<[u8]>> { - ::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<SKESK5> for super::SKESK { - fn from(p: SKESK5) -> Self { - super::SKESK::V5(p) +impl From<SKESK6> for super::SKESK { + fn from(p: SKESK6) -> Self { + super::SKESK::V6(p) } } -impl From<SKESK5> for Packet { - fn from(s: SKESK5) -> Self { - Packet::SKESK(SKESK::V5(s)) +impl From<SKESK6> 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::<u8>::arbitrary(g).into(), - digest.into_boxed_slice()) + iv.into(), + esk.into()) .unwrap() } } @@ -595,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_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(p: SKESK) -> bool { + 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 sample_skesk5_packet() { - // This sample packet is from RFC4880bis-05, section A.3. + 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 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<Packet> = - 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 |