summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNeal H. Walfield <neal@pep.foundation>2018-11-26 16:37:11 +0100
committerNeal H. Walfield <neal@pep.foundation>2018-11-26 16:53:49 +0100
commitb67341afdc1890769630eaa38f07a63539d1ea30 (patch)
treebadd4c13313d62706caed17efa88c74143617914
parentc070935c07d997ec0d1c0718d18bb638d643a2d9 (diff)
openpgp: Improve OpenPGP message detection heuristic.
- Also, split out the code.
-rw-r--r--openpgp/src/packet/ctb.rs2
-rw-r--r--openpgp/src/parse/packet_parser_builder.rs17
-rw-r--r--openpgp/src/parse/parse.rs80
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 {