diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2023-06-26 13:27:02 +0200 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2023-06-26 13:27:02 +0200 |
commit | c4397071a3b72f64ba73e770cdbe72255abddbc6 (patch) | |
tree | 599072ea7caa1b3e6fb938dda40ec07734a30917 | |
parent | 0ccfa1491a17dd24b4dc82c1d9a40d2e48aac027 (diff) | |
parent | 55e665a1894f7d27f31a34a474e4ce77d02c7768 (diff) |
Merge branch 'crypto-refresh-seip-v2' into crypto-refresh
23 files changed, 986 insertions, 334 deletions
diff --git a/openpgp/src/cert/parser/mod.rs b/openpgp/src/cert/parser/mod.rs index d376c0c1..aea9c101 100644 --- a/openpgp/src/cert/parser/mod.rs +++ b/openpgp/src/cert/parser/mod.rs @@ -757,11 +757,19 @@ impl<'a> CertParser<'a> { // returns None. fn parse(&mut self, p: Packet) -> Result<Option<Cert>> { tracer!(TRACE, "CertParser::parse", 0); - if let Packet::Marker(_) = p { - // Ignore Marker Packet. RFC4880, section 5.8: - // - // Such a packet MUST be ignored when received. - return Ok(None); + match p { + Packet::Marker(_) => { + // Ignore Marker Packet. RFC4880, section 5.8: + // + // Such a packet MUST be ignored when received. + return Ok(None); + }, + Packet::Padding(_) => { + // "[Padding packets] MUST be ignored when received.", + // section 5.15 of RFC XXXX. + return Ok(None); + }, + _ => {}, } if !self.packets.is_empty() { diff --git a/openpgp/src/crypto/aead.rs b/openpgp/src/crypto/aead.rs index b6fcc578..dd0f8ed5 100644 --- a/openpgp/src/crypto/aead.rs +++ b/openpgp/src/crypto/aead.rs @@ -17,6 +17,7 @@ use crate::Result; use crate::crypto::SessionKey; use crate::seal; use crate::parse::Cookie; +use crate::crypto::backend::{Backend, interface::Kdf}; /// Minimum AEAD chunk size. /// @@ -232,6 +233,87 @@ impl Schedule for AEDv1Schedule { } } +const SEIP2AD_PREFIX_LEN: usize = 5; +pub(crate) struct SEIPv2Schedule { + nonce: Box<[u8]>, + ad: [u8; SEIP2AD_PREFIX_LEN], + nonce_len: usize, +} + +impl SEIPv2Schedule { + pub(crate) fn new(session_key: &SessionKey, + sym_algo: SymmetricAlgorithm, + aead: AEADAlgorithm, + chunk_size: usize, + salt: &[u8]) -> Result<(SessionKey, Self)> + { + if !(MIN_CHUNK_SIZE..=MAX_CHUNK_SIZE).contains(&chunk_size) { + return Err(Error::InvalidArgument( + format!("Invalid AEAD chunk size: {}", chunk_size)).into()); + } + + // Derive the message key and initialization vector. + let key_size = sym_algo.key_size()?; + // The NONCE size is NONCE_LEN - 8 bytes taken from the KDF. + let nonce_size = aead.nonce_size()? - 8; + let mut key_nonce: SessionKey = + vec![0; key_size + nonce_size].into(); + let ad = [ + 0xd2, // Tag. + 2, // Version. + sym_algo.into(), + aead.into(), + chunk_size.trailing_zeros() as u8 - 6, + ]; + Backend::hkdf_sha256(session_key, Some(salt), &ad, &mut key_nonce)?; + let key = Vec::from(&key_nonce[..key_size]).into(); + let nonce = Vec::from(&key_nonce[key_size..]).into(); + + Ok((key, Self { + nonce, + ad, + nonce_len: aead.nonce_size()?, + })) + } +} + +impl Schedule for SEIPv2Schedule { + fn next_chunk<F, R>(&self, index: u64, mut fun: F) -> R + where + F: FnMut(&[u8], &[u8]) -> R, + { + // The nonce is the NONCE (NONCE_LEN - 8 bytes taken from the + // KDF) concatenated with the chunk index. + let index_be: [u8; 8] = index.to_be_bytes(); + let mut nonce_store = [0u8; MAX_NONCE_LEN]; + let nonce = &mut nonce_store[..self.nonce_len]; + nonce[..self.nonce.len()].copy_from_slice(&self.nonce); + nonce[self.nonce.len()..].copy_from_slice(&index_be); + + fun(nonce, &self.ad) + } + + fn final_chunk<F, R>(&self, index: u64, length: u64, mut fun: F) -> R + where + F: FnMut(&[u8], &[u8]) -> R, + { + // Prepare the associated data. + let mut ad = [0u8; SEIP2AD_PREFIX_LEN + 8]; + ad[..SEIP2AD_PREFIX_LEN].copy_from_slice(&self.ad); + write_be_u64(&mut ad[SEIP2AD_PREFIX_LEN..], length); + + // The nonce is the NONCE (NONCE_LEN - 8 bytes taken from the + // KDF) concatenated with the chunk index. + let index_be: [u8; 8] = index.to_be_bytes(); + let mut nonce_store = [0u8; MAX_NONCE_LEN]; + let nonce = &mut nonce_store[..self.nonce_len]; + nonce[..self.nonce.len()].copy_from_slice(&self.nonce); + nonce[self.nonce.len()..].copy_from_slice(&index_be); + + fun(nonce, &ad) + } +} + /// A `Read`er for decrypting AEAD-encrypted data. pub struct Decryptor<'a, S: Schedule> { // The encrypted data. diff --git a/openpgp/src/message/grammar.lalrpop b/openpgp/src/message/grammar.lalrpop index f98b3786..03b5776f 100644 --- a/openpgp/src/message/grammar.lalrpop +++ b/openpgp/src/message/grammar.lalrpop @@ -21,6 +21,10 @@ Seipv1Part: () = { SEIPv1 OPAQUE_CONTENT POP, } +Seipv2Part: () = { + SEIPv2 Message POP, +} + AedPart: () = { AED Message POP, } @@ -33,6 +37,7 @@ EncryptedPart: () = { EncryptionContainer: () = { Seipv1Part, + Seipv2Part, AedPart, }; @@ -69,6 +74,7 @@ extern { SKESK => lexer::Token::SKESK, PKESK => lexer::Token::PKESK, SEIPv1 => lexer::Token::SEIPv1, + SEIPv2 => lexer::Token::SEIPv2, MDC => lexer::Token::MDC, AED => lexer::Token::AED, OPS => lexer::Token::OPS, diff --git a/openpgp/src/message/lexer.rs b/openpgp/src/message/lexer.rs index 7289d748..e938c4e0 100644 --- a/openpgp/src/message/lexer.rs +++ b/openpgp/src/message/lexer.rs @@ -27,6 +27,8 @@ pub enum Token { PKESK, /// A version 1 SEIP packet. SEIPv1, + /// A version 2 SEIP packet. + SEIPv2, /// An MDC packet. MDC, /// An AED packet. diff --git a/openpgp/src/message/mod.rs b/openpgp/src/message/mod.rs index ec715380..32895dee 100644 --- a/openpgp/src/message/mod.rs +++ b/openpgp/src/message/mod.rs @@ -205,6 +205,7 @@ impl MessageValidator { Tag::SKESK => Token::SKESK, Tag::PKESK => Token::PKESK, Tag::SEIP if version == Some(1) => Token::SEIPv1, + Tag::SEIP if version == Some(2) => Token::SEIPv2, Tag::MDC => Token::MDC, Tag::AED => Token::AED, Tag::OnePassSig => Token::OPS, @@ -214,6 +215,11 @@ impl MessageValidator { // section 5.8 of RFC4880. return; }, + Tag::Padding => { + // "[Padding packets] MUST be ignored when received.", + // section 5.15 of RFC XXXX. + return; + }, _ => { // Unknown token. self.error = Some(MessageParserError::OpenPGP( diff --git a/openpgp/src/packet/any.rs b/openpgp/src/packet/any.rs index 8af73eaf..3238bf86 100644 --- a/openpgp/src/packet/any.rs +++ b/openpgp/src/packet/any.rs @@ -22,6 +22,7 @@ use crate::packet::{ SEIP, MDC, AED, + Padding, }; /// Convenient downcasting from Packets to Packet Bodies. @@ -136,6 +137,7 @@ impl_downcasts!( SEIP, MDC, AED, + Padding, ); diff --git a/openpgp/src/packet/container.rs b/openpgp/src/packet/container.rs index fd8c0f2f..7bfc20ba 100644 --- a/openpgp/src/packet/container.rs +++ b/openpgp/src/packet/container.rs @@ -12,7 +12,10 @@ use xxhash_rust::xxh3::Xxh3; use crate::{ Packet, - packet::Iter, + packet::{ + Iter, + SEIP, + }, }; /// A packet's body holds either unprocessed bytes, processed bytes, @@ -400,7 +403,8 @@ impl Packet { use std::ops::Deref; match self { Packet::CompressedData(p) => Some(p.deref()), - Packet::SEIP(p) => Some(p.deref()), + Packet::SEIP(SEIP::V1(p)) => Some(p.deref()), + Packet::SEIP(SEIP::V2(p)) => Some(p.deref()), Packet::AED(p) => Some(p.deref()), Packet::Literal(p) => Some(p.container_ref()), Packet::Unknown(p) => Some(p.container_ref()), @@ -413,7 +417,8 @@ impl Packet { use std::ops::DerefMut; match self { Packet::CompressedData(p) => Some(p.deref_mut()), - Packet::SEIP(p) => Some(p.deref_mut()), + Packet::SEIP(SEIP::V1(p)) => Some(p.deref_mut()), + Packet::SEIP(SEIP::V2(p)) => Some(p.deref_mut()), Packet::AED(p) => Some(p.deref_mut()), Packet::Literal(p) => Some(p.container_mut()), Packet::Unknown(p) => Some(p.container_mut()), diff --git a/openpgp/src/packet/header/mod.rs b/openpgp/src/packet/header/mod.rs index 61eab916..df4b9785 100644 --- a/openpgp/src/packet/header/mod.rs +++ b/openpgp/src/packet/header/mod.rs @@ -207,6 +207,7 @@ impl Header { Tag::Marker => l == 3, Tag::Reserved => true, + Tag::Padding => true, }; if ! valid { diff --git a/openpgp/src/packet/mod.rs b/openpgp/src/packet/mod.rs index f448e39b..c004155a 100644 --- a/openpgp/src/packet/mod.rs +++ b/openpgp/src/packet/mod.rs @@ -217,6 +217,8 @@ pub mod pkesk; mod mdc; pub use self::mdc::MDC; pub mod aed; +mod padding; +pub use self::padding::Padding; /// Enumeration of packet types. /// @@ -285,6 +287,8 @@ pub enum Packet { MDC(MDC), /// AEAD Encrypted Data Packet. AED(AED), + /// Padding packet. + Padding(Padding), } assert_send_and_sync!(Packet); @@ -360,6 +364,7 @@ impl Packet { Packet::SEIP(_) => Tag::SEIP, Packet::MDC(_) => Tag::MDC, Packet::AED(_) => Tag::AED, + Packet::Padding(_) => Tag::Padding, } } @@ -402,6 +407,7 @@ impl Packet { Packet::SEIP(p) => Some(p.version()), Packet::MDC(_) => None, Packet::AED(p) => Some(p.version()), + Packet::Padding(_) => None, } } @@ -443,6 +449,7 @@ impl Packet { Packet::MDC(x) => Hash::hash(&x, state), Packet::AED(x) => Hash::hash(&x, state), Packet::Unknown(x) => Hash::hash(&x, state), + Packet::Padding(x) => Padding::hash(x, state), } } } @@ -469,10 +476,12 @@ 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::SEIP(ref packet) => &packet.common, + Packet::SKESK(SKESK::V6(ref packet)) => &packet.skesk4.common, + Packet::SEIP(SEIP::V1(packet)) => &packet.common, + Packet::SEIP(SEIP::V2(packet)) => &packet.common, Packet::MDC(ref packet) => &packet.common, Packet::AED(ref packet) => &packet.common, + Packet::Padding(packet) => &packet.common, } } } @@ -496,10 +505,12 @@ 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::SEIP(ref mut packet) => &mut packet.common, + Packet::SKESK(SKESK::V6(ref mut packet)) => &mut packet.skesk4.common, + Packet::SEIP(SEIP::V1(packet)) => &mut packet.common, + Packet::SEIP(SEIP::V2(packet)) => &mut packet.common, Packet::MDC(ref mut packet) => &mut packet.common, Packet::AED(ref mut packet) => &mut packet.common, + Packet::Padding(packet) => &mut packet.common, } } } @@ -527,6 +538,7 @@ impl fmt::Debug for Packet { SEIP(v) => write!(f, "SEIP({:?})", v), MDC(v) => write!(f, "MDC({:?})", v), AED(v) => write!(f, "AED({:?})", v), + Padding(v) => write!(f, "Padding({:?})", v), } } @@ -553,7 +565,7 @@ impl Arbitrary for Packet { fn arbitrary(g: &mut Gen) -> Self { use crate::arbitrary_helper::gen_arbitrary_from_range; - match gen_arbitrary_from_range(0..15, g) { + match gen_arbitrary_from_range(0..16, g) { 0 => Signature::arbitrary(g).into(), 1 => OnePassSig::arbitrary(g).into(), 2 => Key::<key::PublicParts, key::UnspecifiedRole>::arbitrary(g) @@ -572,7 +584,8 @@ impl Arbitrary for Packet { 11 => CompressedData::arbitrary(g).into(), 12 => PKESK::arbitrary(g).into(), 13 => SKESK::arbitrary(g).into(), - 14 => loop { + 14 => Padding::arbitrary(g).into(), + 15 => loop { let mut u = Unknown::new( Tag::arbitrary(g), anyhow::anyhow!("Arbitrary::arbitrary")); u.set_body(Arbitrary::arbitrary(g)); @@ -1233,10 +1246,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); @@ -1245,7 +1258,7 @@ impl SKESK { pub fn version(&self) -> u8 { match self { SKESK::V4(_) => 4, - SKESK::V5(_) => 5, + SKESK::V6(_) => 6, } } } @@ -2057,6 +2070,9 @@ impl<P: key::KeyParts, R: key::KeyRole> DerefMut for Key<P, R> { pub enum SEIP { /// SEIP packet version 1. V1(self::seip::SEIP1), + + /// SEIP packet version 2. + V2(self::seip::SEIP2), } assert_send_and_sync!(SEIP); @@ -2065,6 +2081,7 @@ impl SEIP { pub fn version(&self) -> u8 { match self { SEIP::V1(_) => 1, + SEIP::V2(_) => 2, } } } @@ -2075,26 +2092,6 @@ impl From<SEIP> for Packet { } } -// Trivial forwarder for singleton enum. -impl Deref for SEIP { - type Target = self::seip::SEIP1; - - fn deref(&self) -> &Self::Target { - match self { - SEIP::V1(ref p) => p, - } - } -} - -// Trivial forwarder for singleton enum. -impl DerefMut for SEIP { - fn deref_mut(&mut self) -> &mut Self::Target { - match self { - SEIP::V1(ref mut p) => p, - } - } -} - /// Holds an AEAD encrypted data packet. /// /// An AEAD packet holds encrypted data. It is contains additional diff --git a/openpgp/src/packet/padding.rs b/openpgp/src/packet/padding.rs new file mode 100644 index 00000000..6b7fab5f --- /dev/null +++ b/openpgp/src/packet/padding.rs @@ -0,0 +1,92 @@ +use std::fmt; + +#[cfg(test)] +use quickcheck::{Arbitrary, Gen}; + +use crate::packet; +use crate::Packet; + +/// Holds a Padding packet. +/// +/// Padding packets are used to obscure the size of cryptographic +/// artifacts. +/// +/// See [Section 5.15 of RFC XXX] for details. +/// +/// [Section 5.15 of RFC XXX]: https://openpgp-wg.gitlab.io/rfc4880bis/#name-padding-packet-tag-21 +// IMPORTANT: If you add fields to this struct, you need to explicitly +// IMPORTANT: implement PartialEq, Eq, and Hash. +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct Padding { + pub(crate) common: packet::Common, + value: Vec<u8>, +} + +assert_send_and_sync!(Padding); + +impl From<Vec<u8>> for Padding { + fn from(u: Vec<u8>) -> Self { + Padding { + common: Default::default(), + value: u, + } + } +} + +impl fmt::Display for Padding { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let padding = String::from_utf8_lossy(&self.value[..]); + write!(f, "{}", padding) + } +} + +impl fmt::Debug for Padding { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Padding {{ {} bytes }}", self.value.len()) + } +} + +impl Padding { + /// Creates a new Padding packet of the given size. + /// + /// Note that this is the net size, packet framing (CTB and packet + /// length) will come on top. + pub fn new(size: usize) -> Padding { + let mut v = vec![0; size]; + crate::crypto::random(&mut v); + v.into() + } + + /// Gets the padding packet's value. + pub(crate) fn value(&self) -> &[u8] { + self.value.as_slice() + } +} + +impl From<Padding> for Packet { + fn from(s: Padding) -> Self { + Packet::Padding(s) + } +} + +#[cfg(test)] +impl Arbitrary for Padding { + fn arbitrary(g: &mut Gen) -> Self { + Vec::<u8>::arbitrary(g).into() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::parse::Parse; + use crate::serialize::MarshalInto; + + quickcheck! { + fn roundtrip(p: Padding) -> bool { + let q = Padding::from_bytes(&p.to_vec().unwrap()).unwrap(); + assert_eq!(p, q); + true + } + } +} diff --git a/openpgp/src/packet/prelude.rs b/openpgp/src/packet/prelude.rs index c433dda9..bda7a7e2 100644 --- a/openpgp/src/packet/prelude.rs +++ b/openpgp/src/packet/prelude.rs @@ -34,6 +34,7 @@ pub use crate::packet::{ OnePassSig, PKESK, Packet, + Padding, SEIP, SKESK, Signature, @@ -50,12 +51,15 @@ pub use crate::packet::{ one_pass_sig::OnePassSig3, one_pass_sig::OnePassSig6, pkesk::PKESK3, - seip::SEIP1, + seip::{ + SEIP1, + SEIP2, + }, signature, signature::Signature4, signature::Signature6, signature::SignatureBuilder, skesk::SKESK4, - skesk::SKESK5, + skesk::SKESK6, user_attribute, }; diff --git a/openpgp/src/packet/seip.rs b/openpgp/src/packet/seip.rs index b808acf2..e74db749 100644 --- a/openpgp/src/packet/seip.rs +++ b/openpgp/src/packet/seip.rs @@ -8,6 +8,9 @@ use crate::packet; use crate::Packet; +mod v2; +pub use v2::*; + /// Holds an encrypted data packet. /// /// An encrypted data packet is a container. See [Section 5.13 of RFC diff --git a/openpgp/src/packet/seip/v2.rs b/openpgp/src/packet/seip/v2.rs new file mode 100644 index 00000000..85d02289 --- /dev/null +++ b/openpgp/src/packet/seip/v2.rs @@ -0,0 +1,161 @@ +//! Symmetrically Encrypted Integrity Protected data packets version 2. +//! +//! An encrypted data packet is a container. See [XXX] for details. + +use crate::{ + Error, + packet::{ + self, + Packet, + SEIP, + }, + Result, + types::{ + AEADAlgorithm, + SymmetricAlgorithm, + }, +}; + +/// Holds an encrypted data packet. +/// +/// An encrypted data packet is a container. See [XXX] for details. +/// +/// # A note on equality +/// +/// An unprocessed (encrypted) `SEIP2` packet is never considered equal +/// to a processed (decrypted) one. Likewise, a processed (decrypted) +/// packet is never considered equal to a structured (parsed) one. +// IMPORTANT: If you add fields to this struct, you need to explicitly +// IMPORTANT: implement PartialEq, Eq, and Hash. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct SEIP2 { + /// CTB packet header fields. + pub(crate) common: packet::Common, + + /// Symmetric algorithm. + sym_algo: SymmetricAlgorithm, + /// AEAD algorithm. + aead: AEADAlgorithm, + /// Chunk size. + chunk_size: u64, + /// Salt. + salt: [u8; 32], + + /// This is a container packet. + container: packet::Container, +} + +assert_send_and_sync!(SEIP2); + +impl std::ops::Deref for SEIP2 { + type Target = packet::Container; + fn deref(&self) -> &Self::Target { + &self.container + } +} + +impl std::ops::DerefMut for SEIP2 { + fn deref_mut(&mut self) -> &mut Self::Target { |