diff options
author | Neal H. Walfield <neal@pep.foundation> | 2018-11-26 16:37:11 +0100 |
---|---|---|
committer | Neal H. Walfield <neal@pep.foundation> | 2018-11-26 16:53:49 +0100 |
commit | b67341afdc1890769630eaa38f07a63539d1ea30 (patch) | |
tree | badd4c13313d62706caed17efa88c74143617914 | |
parent | c070935c07d997ec0d1c0718d18bb638d643a2d9 (diff) |
openpgp: Improve OpenPGP message detection heuristic.
- Also, split out the code.
-rw-r--r-- | openpgp/src/packet/ctb.rs | 2 | ||||
-rw-r--r-- | openpgp/src/parse/packet_parser_builder.rs | 17 | ||||
-rw-r--r-- | openpgp/src/parse/parse.rs | 80 |
3 files changed, 83 insertions, 16 deletions
diff --git a/openpgp/src/packet/ctb.rs b/openpgp/src/packet/ctb.rs index c48931f2..3223ca6f 100644 --- a/openpgp/src/packet/ctb.rs +++ b/openpgp/src/packet/ctb.rs @@ -225,7 +225,7 @@ impl CTB { ptag, if ptag == '-' as u8 { " (ptag is a dash, perhaps this is an \ - ASCII-armored encoded message)" + ASCII-armor encoded message)" } else { "" })).into()); diff --git a/openpgp/src/parse/packet_parser_builder.rs b/openpgp/src/parse/packet_parser_builder.rs index faec4765..289574e8 100644 --- a/openpgp/src/parse/packet_parser_builder.rs +++ b/openpgp/src/parse/packet_parser_builder.rs @@ -15,6 +15,7 @@ use parse::PacketParserSettings; use parse::ParserResult; use parse::Cookie; use armor; +use packet; /// A builder for configuring a `PacketParser`. /// @@ -126,21 +127,7 @@ impl<'a> PacketParserBuilder<'a> { { let state = PacketParserState::new(self.settings); - // If the first byte does not have the high-bit set, it is - // definitely not a binary OpenPGP message. Since an - // ASCII-armor message never has a high-bit set, there is a - // good chance that the message is armored. - // - // This heuristic doesn't work if the message is - // ASCII-armored, but the armored data is preceded by - // non-ASCII UTF-8, which sets the high bit. In such cases we - // will fail to push an armor decoder. - let armored = { - let data = self.bio.data(1)?; - data.len() > 0 && ((data[0] as i8) > 0) - }; - - if armored { + if let Err(_) = packet::Header::plausible(&mut self.bio) { self.bio = Box::new(BufferedReaderGeneric::with_cookie( armor::Reader::from_buffered_reader(self.bio, None), None, diff --git a/openpgp/src/parse/parse.rs b/openpgp/src/parse/parse.rs index 1dea32b5..396d8c53 100644 --- a/openpgp/src/parse/parse.rs +++ b/openpgp/src/parse/parse.rs @@ -778,6 +778,86 @@ impl Header { }; return Ok(Header { ctb: ctb, length: length }); } + + /// Returns whether the byte stream starts with a plausible + /// OpenPGP header. + /// + /// This is a heuristic. + /// + /// This function does not consume any data from `reader`. + pub fn plausible<R: BufferedReader<C>, C>(reader: &mut R) + -> Result<()> + { + use packet::Tag; + + // If the first byte does not have the high-bit set, it is + // definitely not a binary OpenPGP message. Since an + // ASCII-armor message never has a high-bit set, there is a + // good chance that the message is armored. + // + // This very simply heuristic fails if the message is + // ASCII-armored, but the armored data is preceded by + // non-ASCII UTF-8, the first character of which sets the high + // bit. As such, we also check that the tag and length are + // reasonable. + + + // The shortest non-empty valid message is two bytes long: a + // UserID packet with 0 bytes. The longest header is 6 bytes: + // a new-style CTB with a 5-byte length. + let mut data = reader.data(6)?; + if data.len() > 6 { + data = &data[..6]; + } + + if data.len() == 0 { + // An empty message is a valid OpenPGP message. + return Ok(()); + } + + let header = Self::from_reader(data)?; + + // We allow almost all known packets. But, no valid message + // will start with a PublicSubkey, for instance. + match header.ctb.tag { + Tag::PKESK | Tag::SKESK | + Tag::PublicKey | Tag::PublicSubkey | + Tag::SecretKey | Tag::SecretSubkey | + Tag::UserID | + Tag::Signature => { + // Check to make sure the length is sane. Keys, PKESK / + // SKESK and Signatures should be at most a few kilobytes. + // But, allow up to a few megabytes. + let length = match header.length { + BodyLength::Full(l) => l /* Exact length. */, + BodyLength::Partial(l) => l /* Minimum length. */, + BodyLength::Indeterminate => + return Err(Error::MalformedMessage( + format!("Indeterminate body length invalid for {}", + header.ctb.tag).into()).into()), + }; + + if length > 8 * 1024 * 1024 { + return Err(Error::MalformedMessage( + format!("{} too long ({} bytes)", + header.ctb.tag, length).into()).into()); + } + }, + + Tag::Reserved | Tag::Marker | Tag::Trust + | Tag::MDC | Tag::Unknown(_) => + return Err(Error::MalformedMessage( + format!("OpenPGP messages don't start with {}", + header.ctb.tag).into()).into()), + + Tag::OnePassSig | Tag::CompressedData + | Tag::SED | Tag::Literal | Tag::UserAttribute + | Tag::SEIP | Tag::AED | Tag::Private(_) => (), + } + + // Not complete junk... + Ok(()) + } } impl Unknown { |