diff options
author | Neal H. Walfield <neal@pep.foundation> | 2018-02-27 15:23:03 +0100 |
---|---|---|
committer | Neal H. Walfield <neal@pep.foundation> | 2018-02-28 10:13:32 +0100 |
commit | e304deb0fc7a92801cf3ba58aafeb14ce2301aed (patch) | |
tree | d16da7986cc44ddff49feca093dc0303fac01a10 /openpgp/src | |
parent | 79a68d5b59f5d11cbf3123509c82f45a09768720 (diff) |
openpgp: Add support for SEIP and MDC packets.
- Note: due to the way that we handle indeterminate length packets,
if the SEIP packet has an indeterminate length, then only the
first packet in the SEIP container will be parsed.
Diffstat (limited to 'openpgp/src')
-rw-r--r-- | openpgp/src/lib.rs | 35 | ||||
-rw-r--r-- | openpgp/src/packet.rs | 4 | ||||
-rw-r--r-- | openpgp/src/parse/hashed_reader.rs | 221 | ||||
-rw-r--r-- | openpgp/src/parse/parse.rs | 345 | ||||
-rw-r--r-- | openpgp/src/s2k.rs | 3 | ||||
-rw-r--r-- | openpgp/src/serialize/serialize.rs | 37 | ||||
-rw-r--r-- | openpgp/src/symmetric.rs | 301 |
7 files changed, 941 insertions, 5 deletions
diff --git a/openpgp/src/lib.rs b/openpgp/src/lib.rs index 868ba8b0..551f2922 100644 --- a/openpgp/src/lib.rs +++ b/openpgp/src/lib.rs @@ -87,6 +87,9 @@ pub type Result<T> = ::std::result::Result<T, failure::Error>; #[derive(Fail, Debug)] /// Errors returned by this module. pub enum Error { + #[fail(display = "Invalid operation: {}", _0)] + InvalidOperation(String), + /// A malformed packet. #[fail(display = "Malformed packet: {}", _0)] MalformedPacket(String), @@ -109,6 +112,9 @@ pub enum Error { #[fail(display = "Invalid password")] InvalidPassword, + #[fail(display = "Invalid session key: {}", _0)] + InvalidSessionKey(String), + #[fail(display = "{}", _0)] Io(#[cause] io::Error), } @@ -777,6 +783,31 @@ pub struct SKESK { // The encrypted session key. pub esk: Vec<u8>, } + +/// Holds an encrypted data packet. +/// +/// An encrypted data packet is a container. See [Section 5.13 of RFC +/// 4880] for details. +/// +/// [Section 5.13 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.13 +#[derive(PartialEq, Clone, Debug)] +pub struct SEIP { + pub common: PacketCommon, + pub version: u8, +} + +/// Holds an MDC packet. +/// +/// A modification detection code packet. This packet appears after a +/// SEIP packet. See [Section 5.14 of RFC 4880] for details. +/// +/// [Section 5.14 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.14 +#[derive(PartialEq, Clone, Debug)] +pub struct MDC { + pub common: PacketCommon, + pub computed_hash: [u8; 20], + pub hash: [u8; 20], +} /// The OpenPGP packets that Sequoia understands. /// @@ -807,6 +838,8 @@ pub enum Packet { Literal(Literal), CompressedData(CompressedData), SKESK(SKESK), + SEIP(SEIP), + MDC(MDC), } impl Packet { @@ -828,6 +861,8 @@ impl Packet { &Packet::Literal(_) => Tag::Literal, &Packet::CompressedData(_) => Tag::CompressedData, &Packet::SKESK(_) => Tag::SKESK, + &Packet::SEIP(_) => Tag::SEIP, + &Packet::MDC(_) => Tag::MDC, } } } diff --git a/openpgp/src/packet.rs b/openpgp/src/packet.rs index e075aa40..eed3ecd0 100644 --- a/openpgp/src/packet.rs +++ b/openpgp/src/packet.rs @@ -20,6 +20,8 @@ impl<'a> Deref for Packet { &Packet::Literal(ref packet) => &packet.common, &Packet::CompressedData(ref packet) => &packet.common, &Packet::SKESK(ref packet) => &packet.common, + &Packet::SEIP(ref packet) => &packet.common, + &Packet::MDC(ref packet) => &packet.common, } } } @@ -38,6 +40,8 @@ impl<'a> DerefMut for Packet { &mut Packet::Literal(ref mut packet) => &mut packet.common, &mut Packet::CompressedData(ref mut packet) => &mut packet.common, &mut Packet::SKESK(ref mut packet) => &mut packet.common, + &mut Packet::SEIP(ref mut packet) => &mut packet.common, + &mut Packet::MDC(ref mut packet) => &mut packet.common, } } } diff --git a/openpgp/src/parse/hashed_reader.rs b/openpgp/src/parse/hashed_reader.rs new file mode 100644 index 00000000..3c9b72d1 --- /dev/null +++ b/openpgp/src/parse/hashed_reader.rs @@ -0,0 +1,221 @@ +use std::io; +use std::cmp; +use std::mem; + +use nettle::Hash; + +use buffered_reader::BufferedReader; +use buffered_reader::buffered_reader_generic_read_impl; + +use HashAlgo; +use parse::{BufferedReaderState, HashesFor}; +use hash::hash_context; + +#[derive(Debug)] +pub struct HashedReader<R: BufferedReader<BufferedReaderState>> { + reader: R, + cookie: BufferedReaderState, +} + +impl<R: BufferedReader<BufferedReaderState>> HashedReader<R> { + /// Instantiates a new hashed reader. `hashes_for` is the hash's + /// purpose. `algos` is a list of algorithms for which we should + /// compute the hash. + pub fn new(reader: R, hashes_for: HashesFor, algos: Vec<HashAlgo>) + -> Self { + let mut cookie = BufferedReaderState::default(); + for algo in &algos { + cookie.hashes.push((*algo, hash_context(*algo))); + } + cookie.hashes_for = hashes_for; + + HashedReader { + reader: reader, + cookie: cookie, + } + } +} + +impl BufferedReaderState { + fn hash_update(&mut self, data: &[u8]) { + for &mut (algo, ref mut h) in &mut self.hashes { + if false { + eprintln!("{:?} hashing {} bytes: {}.", algo, + data.len(), ::to_hex(data, true)); + } + h.update(data); + } + } +} + +impl<T: BufferedReader<BufferedReaderState>> io::Read for HashedReader<T> { + fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> { + return buffered_reader_generic_read_impl(self, buf); + } +} + +// Wrap a BufferedReader so that any data that is consumed is added to +// the hash. +impl<R: BufferedReader<BufferedReaderState>> + BufferedReader<BufferedReaderState> for HashedReader<R> { + fn buffer(&self) -> &[u8] { + self.reader.buffer() + } + + fn data(&mut self, amount: usize) -> Result<&[u8], io::Error> { + self.reader.data(amount) + } + + fn data_hard(&mut self, amount: usize) -> Result<&[u8], io::Error> { + self.reader.data_hard(amount) + } + + fn consume(&mut self, amount: usize) -> &[u8] { + // We need to take the state rather than get a mutable + // reference to it, because self.reader.buffer() requires a + // reference as well. + let mut state = self.cookie_set(BufferedReaderState::default()); + + { + // The inner buffered reader must return at least `amount` + // bytes, because the caller can't `consume(amount)` if + // the internal buffer doesn't have at least that many + // bytes. + let data = self.reader.buffer(); + assert!(data.len() >= amount); + state.hash_update(&data[..amount]); + } + + self.cookie_set(state); + + self.reader.consume(amount) + } + + fn data_consume(&mut self, amount: usize) -> Result<&[u8], io::Error> { + // See consume() for an explanation of these acrobatics. + + let mut state = self.cookie_set(BufferedReaderState::default()); + + let got = { + let data = self.reader.data(amount)?; + let data = &data[..cmp::min(data.len(), amount)]; + state.hash_update(data); + data.len() + }; + + self.cookie_set(state); + + if let Ok(data) = self.reader.data_consume(amount) { + assert!(data.len() >= got); + Ok(data) + } else { + panic!("data_consume returned less than data!"); + } + } + + fn data_consume_hard(&mut self, amount: usize) -> Result<&[u8], io::Error> { + // See consume() for an explanation of these acrobatics. + + let mut state = self.cookie_set(BufferedReaderState::default()); + + { + let data = self.reader.data_hard(amount)?; + assert!(data.len() >= amount); + state.hash_update(&data[..amount]); + } + + self.cookie_set(state); + + let result = self.reader.data_consume(amount); + assert!(result.is_ok()); + result + } + + fn get_mut(&mut self) -> Option<&mut BufferedReader<BufferedReaderState>> { + Some(&mut self.reader) + } + + fn get_ref(&self) -> Option<&BufferedReader<BufferedReaderState>> { + Some(&self.reader) + } + + fn into_inner<'b>(self: Box<Self>) + -> Option<Box<BufferedReader<BufferedReaderState> + 'b>> + where Self: 'b { + Some(Box::new(self.reader)) + } + + fn cookie_set(&mut self, cookie: BufferedReaderState) -> BufferedReaderState { + mem::replace(&mut self.cookie, cookie) + } + + fn cookie_ref(&self) -> &BufferedReaderState { + &self.cookie + } + + fn cookie_mut(&mut self) -> &mut BufferedReaderState { + &mut self.cookie + } +} + +#[cfg(test)] +mod test { + use super::*; + + use buffered_reader::BufferedReader; + use buffered_reader::BufferedReaderGeneric; + + #[test] + fn hash_test_1() { + struct Test<'a> { + data: &'a [u8], + algos: Vec<HashAlgo>, + expected: Vec<&'a str>, + }; + + let tests = [ + Test { + data: &b"foobar\n"[..], + algos: vec![ HashAlgo::SHA1 ], + expected: vec![ "988881adc9fc3655077dc2d4d757d480b5ea0e11" ], + }, + Test { + data: &b"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789\n"[..], + algos: vec![ HashAlgo::SHA1, HashAlgo::SHA224, + HashAlgo::SHA256, HashAlgo::SHA384, + HashAlgo::SHA512 ], + expected: vec![ + "1d12c55b3a85daab4776a1df41a8f30ada099e11", + "a4c1bde77c682a0e9e30c6afdd1ece2397ffeec61dde2a0eaa23191e", + "151a1d51a1870dc244f07f4844f46ee65fae19a8efeb60b203a074aff899e27d", + "5bea68c8c696bbed95e152d61c446ad0e05bf68f7df39cbfeae568bee6f6691c840fb1d5dd2599737b08dbb33eed344b", + "5fa032487774082af5cc833c2db5f943e31cc75cd2bfaa7d9bbd0ccabf5403b6dbcb484254727a524588f20e9ef336d8ce8533332c5ac1b9d50af3003a0da8d8", + ], + }, + ]; + + for test in tests.iter() { + let reader + = BufferedReaderGeneric::with_cookie( + test.data, None, Default::default()); + let mut reader + = HashedReader::new(reader, HashesFor::MDC, test.algos.clone()); + + assert_eq!(reader.steal_eof().unwrap(), test.data); + + let cookie = reader.cookie_mut(); + + let mut hashes = mem::replace(&mut cookie.hashes, vec![]); + for (i, &mut (algo, ref mut hash)) in hashes.iter_mut().enumerate() { + assert_eq!(algo, test.algos[i]); + + let mut digest = vec![0u8; hash.digest_size()]; + hash.digest(&mut digest); + + assert_eq!(digest, + &::from_hex(test.expected[i], true).unwrap()[..], + "{}: Algo: {:?}", i, algo); + } + } + } +} diff --git a/openpgp/src/parse/parse.rs b/openpgp/src/parse/parse.rs index 1e6156da..551d3b10 100644 --- a/openpgp/src/parse/parse.rs +++ b/openpgp/src/parse/parse.rs @@ -1,12 +1,20 @@ use std; use std::io; +use std::io::prelude::*; use std::cmp; use std::str; +use std::mem; use std::path::Path; use std::fs::File; use ::buffered_reader::*; use Error; +use HashAlgo; +use symmetric::{symmetric_block_size, Decryptor, BufferedReaderDecryptor}; + +use buffered_reader::BufferedReaderGeneric; +use buffered_reader::BufferedReaderEOF; + use super::*; @@ -20,6 +28,9 @@ pub mod key; mod message_parser; pub use self::message_parser::MessageParser; +mod hashed_reader; +pub use self::hashed_reader::HashedReader; + // Whether to trace execution by default (on stderr). const TRACE : bool = false; @@ -263,6 +274,7 @@ impl Unknown { }), reader: Box::new(bio), content_was_read: false, + decrypted: false, recursion_depth: recursion_depth as u8, settings: PacketParserSettings::default(), }); @@ -343,6 +355,7 @@ impl Signature { }), reader: Box::new(bio), content_was_read: false, + decrypted: true, recursion_depth: recursion_depth as u8, settings: PacketParserSettings::default(), }); @@ -423,6 +436,7 @@ impl Key { }, reader: Box::new(bio), content_was_read: false, + decrypted: true, recursion_depth: recursion_depth as u8, settings: PacketParserSettings::default(), }); @@ -444,6 +458,7 @@ impl UserID { }), reader: Box::new(bio), content_was_read: false, + decrypted: true, recursion_depth: recursion_depth as u8, settings: PacketParserSettings::default(), }); @@ -465,6 +480,7 @@ impl UserAttribute { }), reader: Box::new(bio), content_was_read: false, + decrypted: true, recursion_depth: recursion_depth as u8, settings: PacketParserSettings::default(), }); @@ -500,6 +516,7 @@ impl Literal { }), reader: Box::new(bio), content_was_read: false, + decrypted: true, recursion_depth: recursion_depth as u8, settings: PacketParserSettings::default(), }); @@ -634,6 +651,7 @@ impl CompressedData { }), reader: bio, content_was_read: false, + decrypted: true, recursion_depth: recursion_depth as u8, settings: PacketParserSettings::default(), }); @@ -721,6 +739,99 @@ impl SKESK { }), reader: Box::new(bio), content_was_read: false, + decrypted: true, + recursion_depth: recursion_depth as u8, + settings: PacketParserSettings::default() + }); + } +} + +impl SEIP { + /// Parses the body of a SEIP packet. + pub fn parse<'a, R: BufferedReader<BufferedReaderState> + 'a> + (mut bio: R, recursion_depth: usize) + -> Result<PacketParser<'a>> { + let version = bio.data(1)?[0]; + if version != 1 { + return Unknown::parse(bio, recursion_depth, Tag::SEIP); + } + bio.consume(1); + + return Ok(PacketParser { + packet: Packet::SEIP(SEIP { + common: PacketCommon { + children: None, + body: None, + }, + version: version, + }), + reader: Box::new(bio), + content_was_read: false, + decrypted: false, + recursion_depth: recursion_depth as u8, + settings: PacketParserSettings::default(), + }); + } +} + +impl MDC { + /// Parses the body of an MDC packet. + pub fn parse<'a, R: BufferedReader<BufferedReaderState> + 'a> + (mut bio: R, recursion_depth: usize) + -> Result<PacketParser<'a>> { + // Find the HashedReader pushed by the containing SEIP packet. + // In a well-formed message, this will be the outer most + // HashedReader on the BufferedReader stack: we pushed it + // there when we started decrypting the SEIP packet, and an + // MDC packet is the last packet in a SEIP container. + // Nevertheless, we take some basic precautions to check + // whether it is really the matching HashedReader. + + let mut hash = None; + { + let mut r : Option<&mut BufferedReader<BufferedReaderState>> + = Some(&mut bio); + while let Some(bio) = r { + { + let state = bio.cookie_mut(); + if state.hashes_for == HashesFor::MDC { + if state.hashes.len() > 0 { + let (a, h) = state.hashes.pop().unwrap(); + assert_eq!(a, HashAlgo::SHA1); + hash = Some(h); + } + + // If the outer most HashedReader is not the + // matching HashedReader, then the message is + // malformed. + break; + } + } + + r = bio.get_mut(); + } + } + + let mut computed_hash : [u8; 20] = Default::default(); + if let Some(mut hash) = hash { + hash.digest(&mut computed_hash); + } + + let mut hash : [u8; 20] = Default::default(); + hash.copy_from_slice(&bio.data_consume_hard(20)?[..]); + + return Ok(PacketParser { + packet: Packet::MDC(MDC { + common: PacketCommon { + children: None, + body: None, + }, + computed_hash: computed_hash, + hash: hash, + }), + reader: Box::new(bio), + content_was_read: false, + decrypted: true, recursion_depth: recursion_depth as u8, settings: PacketParserSettings::default(), }); @@ -984,7 +1095,17 @@ impl <'a> PacketParserBuilder<BufferedReaderMemory<'a, BufferedReaderState>> { } } -#[derive(Clone, Copy)] +use nettle::Hash; + +/// What the hash in the BUfferedReaderState is for. Currently, it +/// can only be for an MDC packet, but eventually we'll use it to +/// check whether the hash is for a signature packet. +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum HashesFor { + Nothing, + MDC, +} + pub struct BufferedReaderState { // The top-level buffered reader is 0. // The limitor for a top-level packet is 1. @@ -995,12 +1116,21 @@ pub struct BufferedReaderState { // Thus, the filters that control the input for a packet at // recursion depth n have level n + 1. level: usize, + + hashes_for: HashesFor, + hashes: Vec<(HashAlgo, Box<Hash>)>, } impl fmt::Debug for BufferedReaderState { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let algos = self.hashes.iter() + .map(|&(algo, _)| algo) + .collect::<Vec<HashAlgo>>(); + f.debug_struct("BufferedReaderState") .field("level", &self.level) + .field("hashes_for", &self.hashes_for) + .field("hashes", &algos) .finish() } } @@ -1009,6 +1139,8 @@ impl Default for BufferedReaderState { fn default() -> Self { BufferedReaderState { level: 0, + hashes_for: HashesFor::Nothing, + hashes: vec![], } } } @@ -1016,7 +1148,9 @@ impl Default for BufferedReaderState { impl BufferedReaderState { fn new(recursion_depth: usize) -> BufferedReaderState { BufferedReaderState { - level: recursion_depth + 1 + level: recursion_depth + 1, + hashes_for: HashesFor::Nothing, + hashes: vec![], } } } @@ -1095,6 +1229,9 @@ pub struct PacketParser<'a> { // can't recurse, because we're missing some of the packet! content_was_read: bool, + // Whether the content has been decrypted. + decrypted: bool, + // The `PacketParser`'s settings settings: PacketParserSettings, } @@ -1244,6 +1381,10 @@ impl <'a> PacketParser<'a> { CompressedData::parse(bio, recursion_depth)?, Tag::SKESK => SKESK::parse(bio, recursion_depth)?, + Tag::SEIP => + SEIP::parse(bio, recursion_depth)?, + Tag::MDC => + MDC::parse(bio, recursion_depth)?, _ => Unknown::parse(bio, recursion_depth, tag)?, }; @@ -1401,6 +1542,13 @@ impl <'a> PacketParser<'a> { while reader.cookie_ref().level == recursion_depth as usize + 1 { reader.drop_eof().unwrap(); + if settings.trace { + eprintln!("{}PacketParser::next: dropping: {:?}", + indent(recursion_depth), reader); + eprintln!("{} cookie: {:?}", + indent(recursion_depth), + reader.cookie_ref()); + } reader = reader.into_inner().unwrap(); pops += 1; } @@ -1457,7 +1605,7 @@ impl <'a> PacketParser<'a> { match self.packet { // Packets that recurse. - Packet::CompressedData(_) => { + Packet::CompressedData(_) | Packet::SEIP(_) if self.decrypted => { if self.recursion_depth >= self.settings.max_recursion_depth { if self.settings.trace { @@ -1504,12 +1652,15 @@ impl <'a> PacketParser<'a> { } } }, + // decrypted should always be true. + Packet::CompressedData(_) => unreachable!(), // Packets that don't recurse. Packet::Unknown(_) | Packet::Signature(_) | Packet::PublicKey(_) | Packet::PublicSubkey(_) | Packet::SecretKey(_) | Packet::SecretSubkey(_) | Packet::UserID(_) | Packet::UserAttribute(_) - | Packet::Literal(_) | Packet::SKESK(_) => { + | Packet::Literal(_) | Packet::SKESK(_) + | Packet::SEIP(_) | Packet::MDC(_) => { // Drop through. if self.settings.trace { eprintln!("{}PacketParser::recurse(): A {:?} packet is \ @@ -1777,6 +1928,192 @@ fn packet_parser_reader_interface() { assert!(packet.body.is_none()); } +impl<'a> PacketParser<'a> { + // Tries to decrypt the current packet. + // + // On success, this function pushes one or more readers onto the + // `PacketParser`'s reader stack, and sets the packet's + // `decrypted` flag. + // + // If this function is called on a packet that does not contain + // encrypted data, or some of the data was already read, then it + // returns `Error::InvalidOperation`. + pub fn decrypt(&mut self, algo: u8, key: &[u8]) + -> Result<()> + { + if self.content_was_read { + return Err(Error::InvalidOperation( + format!("Packet's content has already been read.")).into()); + } + if self.decrypted { + return Err(Error::InvalidOperation( + format!("Packet not encrypted.")).into()); + } + + if let Packet::SEIP(_) = self.packet { + // Get the first blocksize plus two bytes and check + // whether we can decrypt them using the provided key. + // Don't actually comsume them in case we can't. + let bl = symmetric_block_size(algo)?; + + { + let mut dec = Decryptor::new( + algo, key, &self.reader.data_hard(bl + 2)?[..bl + 2])?; + let mut header = vec![ 0u8; bl + 2 ]; + dec.read(&mut header)?; + + if !(header[bl - 2] == header[bl] + && header[bl - 1] == header[bl + 1]) { + return Err(Error::InvalidSessionKey( + format!("Last two 16-bit quantities don't match: {}", + ::to_hex(&header[..], false))) + .into()); + } + } + + // Ok, we can decrypt the data. Push a Decryptor and a + // HashedReader on the `BufferedReader` stack. + + let reader = mem::replace( + &mut self.reader, + Box::new(BufferedReaderEOF::with_cookie(Default::default()))); + + // This can't fail, because we create a decryptor above + // with the same parameters. + let mut reader = BufferedReaderDecryptor::with_cookie( + algo, key, reader, BufferedReaderState::default()).unwrap(); + reader.cookie_mut().level = self.recursion_depth as usize + 1; + + eprintln!("Adding decryptor at level {}", + reader.cookie_ref().level); + + // And the hasher. + let mut reader = HashedReader::new( + reader, HashesFor::MDC, vec![HashAlgo::SHA1]); + reader.cookie_mut().level = self.recursion_depth as usize + 1; + + eprintln!("Adding hashed reader at level {}", + reader.cookie_ref().level); + + self.reader = Box::new(reader); + + // Consume the header. This shouldn't fail, because it + // worked when reading the header. + self.reader.data_consume_hard(bl + 2).unwrap(); + + self.decrypted = true; + + Ok(()) + } else { + Err(Error::InvalidOperation( + format!("Can't decrypt {:?} packets.", + self.packet.tag())).into()) + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + use std::path::PathBuf; + fn path_to(artifact: &str) -> PathBuf { + [env!("CARGO_MANIFEST_DIR") |