summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2020-11-24 13:03:48 +0100
committerJustus Winter <justus@sequoia-pgp.org>2020-11-24 13:08:06 +0100
commit03598e4da99dbaf4c9f978e8f6f57ce3f6720b48 (patch)
tree18a7372a6061f713142143cc40d39910a9dc628b
parent3c73fe5152557048c5ed8f10efeeec4f080b356b (diff)
openpgp: Handle truncated armor prefixes.
- Fixes #618.
-rw-r--r--openpgp/src/armor.rs51
-rw-r--r--openpgp/tests/data/armor/test-3.with-headers-quoted-stripped.asc7
2 files changed, 56 insertions, 2 deletions
diff --git a/openpgp/src/armor.rs b/openpgp/src/armor.rs
index a09b6769..4865511e 100644
--- a/openpgp/src/armor.rs
+++ b/openpgp/src/armor.rs
@@ -836,6 +836,9 @@ impl<'a> IoReader<'a> {
// Read the key-value headers.
let mut n = 0;
+ // Sometimes, we find a truncated prefix. In these cases, the
+ // length is not prefix.len(), but this.
+ let mut prefix_len = None;
let mut lines = 0;
loop {
// Skip any known prefix on lines.
@@ -844,7 +847,8 @@ impl<'a> IoReader<'a> {
// consume it here. So at every point in this loop where
// the control flow wraps around, we need to make sure
// that we buffer the prefix in addition to the line.
- self.source.consume(prefix.len());
+ self.source.consume(
+ prefix_len.take().unwrap_or_else(|| prefix.len()));
self.source.consume(n);
@@ -910,11 +914,24 @@ impl<'a> IoReader<'a> {
// consumed in the next iteration.
let next_prefix =
&self.source.data_hard(n + prefix.len())?[n..n + prefix.len()];
- if prefix != next_prefix {
+
+ // Sometimes, we find a truncated prefix.
+ let l = common_prefix(&prefix, next_prefix);
+ let full_prefix = l == prefix.len();
+ if ! (full_prefix
+ // Truncation is okay if the rest of the prefix
+ // contains only whitespace.
+ || prefix[l..].iter().all(|c| c.is_ascii_whitespace()))
+ {
return Err(
Error::new(ErrorKind::InvalidInput,
"Inconsistent quoting of armored data"));
}
+ if ! full_prefix {
+ // Make sure to only consume the truncated prefix in
+ // the next loop iteration.
+ prefix_len = Some(l);
+ }
}
self.source.consume(n);
@@ -925,6 +942,11 @@ impl<'a> IoReader<'a> {
}
}
+/// Computes the length of the common prefix.
+fn common_prefix<A: AsRef<[u8]>, B: AsRef<[u8]>>(a: A, b: B) -> usize {
+ a.as_ref().iter().zip(b.as_ref().iter()).take_while(|(a, b)| a == b).count()
+}
+
// Remove whitespace, etc. from the base64 data.
//
// This function returns the filtered base64 data (i.e., stripped of
@@ -1717,6 +1739,19 @@ mod test {
}
#[test]
+ fn dearmor_quoted_stripped() {
+ let mut r = Reader::new(
+ Cursor::new(
+ &include_bytes!("../tests/data/armor/test-3.with-headers-quoted-stripped.asc")[..]
+ ),
+ ReaderMode::VeryTolerant);
+ let mut buf = [0; 5];
+ let e = r.read(&mut buf);
+ assert!(r.kind() == Some(Kind::File));
+ assert!(e.is_ok());
+ }
+
+ #[test]
fn dearmor_quoted_a_lot() {
let mut r = Reader::new(
Cursor::new(
@@ -1850,4 +1885,16 @@ mod test {
// `data` is malformed, expect an error.
reader.read_to_end(&mut buf).unwrap_err();
}
+
+ #[test]
+ fn common_prefix() {
+ use super::common_prefix as cp;
+ assert_eq!(cp("", ""), 0);
+ assert_eq!(cp("a", ""), 0);
+ assert_eq!(cp("", "a"), 0);
+ assert_eq!(cp("a", "a"), 1);
+ assert_eq!(cp("aa", "a"), 1);
+ assert_eq!(cp("a", "aa"), 1);
+ assert_eq!(cp("ac", "ab"), 1);
+ }
}
diff --git a/openpgp/tests/data/armor/test-3.with-headers-quoted-stripped.asc b/openpgp/tests/data/armor/test-3.with-headers-quoted-stripped.asc
new file mode 100644
index 00000000..47ceaf40
--- /dev/null
+++ b/openpgp/tests/data/armor/test-3.with-headers-quoted-stripped.asc
@@ -0,0 +1,7 @@
+> -----BEGIN PGP ARMORED FILE-----
+> Comment: Some Header
+> Comment: Another one
+>
+> NdWx
+> =oSKZ
+> -----END PGP ARMORED FILE-----