diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2020-11-24 13:03:48 +0100 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2020-11-24 13:08:06 +0100 |
commit | 03598e4da99dbaf4c9f978e8f6f57ce3f6720b48 (patch) | |
tree | 18a7372a6061f713142143cc40d39910a9dc628b | |
parent | 3c73fe5152557048c5ed8f10efeeec4f080b356b (diff) |
openpgp: Handle truncated armor prefixes.
- Fixes #618.
-rw-r--r-- | openpgp/src/armor.rs | 51 | ||||
-rw-r--r-- | openpgp/tests/data/armor/test-3.with-headers-quoted-stripped.asc | 7 |
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----- |