summaryrefslogtreecommitdiffstats
path: root/openpgp
diff options
context:
space:
mode:
authorJustus Winter <justus@pep-project.org>2017-12-12 17:16:00 +0100
committerJustus Winter <justus@pep-project.org>2017-12-13 13:55:20 +0100
commitc86ad83ae31d44aeab3317e5e05d6d63e428f0e0 (patch)
tree12d1aac77f256e733104e98faf9143152eacd573 /openpgp
parentc0cab61441df7a0334f817c2cc4817a0910e1193 (diff)
Split up Sequoia.
- Split up into six crates: buffered-reader, openpgp, sequoia-core, sequoia-ffi, sequoia-net, and sequoia-store. - Adjust imports accordingly.
Diffstat (limited to 'openpgp')
-rw-r--r--openpgp/Cargo.toml11
-rw-r--r--openpgp/src/armor.rs723
-rw-r--r--openpgp/src/build/build.rs1
-rw-r--r--openpgp/src/build/mod.rs2
-rw-r--r--openpgp/src/lib.rs531
-rw-r--r--openpgp/src/packets.rs0
-rw-r--r--openpgp/src/parse/compressed-data-algo-1.ascbin0 -> 2282 bytes
-rw-r--r--openpgp/src/parse/compressed-data-algo-2.ascbin0 -> 2288 bytes
-rw-r--r--openpgp/src/parse/compressed-data-algo-3.ascbin0 -> 2216 bytes
-rw-r--r--openpgp/src/parse/compression-quine.gpgbin0 -> 182 bytes
-rw-r--r--openpgp/src/parse/compression-quine.txt3
-rw-r--r--openpgp/src/parse/literal-mode-b.asc1
-rw-r--r--openpgp/src/parse/literal-mode-t-partial-body.asc99
-rw-r--r--openpgp/src/parse/literal-mode-t-partial-body.txt99
-rw-r--r--openpgp/src/parse/mod.rs3
-rw-r--r--openpgp/src/parse/parse.rs983
-rw-r--r--openpgp/src/parse/public-key.ascbin0 -> 25148 bytes
-rw-r--r--openpgp/src/parse/sig.ascbin0 -> 310 bytes
-rw-r--r--openpgp/src/parse/signed.gpgbin0 -> 456 bytes
-rw-r--r--openpgp/src/partial_body.rs300
-rw-r--r--openpgp/src/serialize/mod.rs2
-rw-r--r--openpgp/src/serialize/serialize.rs1
-rw-r--r--openpgp/src/subpackets.rs228
-rw-r--r--openpgp/src/tpk.rs273
-rw-r--r--openpgp/src/types.rs21
-rw-r--r--openpgp/tests/data/armor/test-0.asc4
-rw-r--r--openpgp/tests/data/armor/test-0.bad-crc.asc4
-rw-r--r--openpgp/tests/data/armor/test-0.bin0
-rw-r--r--openpgp/tests/data/armor/test-1.asc5
-rw-r--r--openpgp/tests/data/armor/test-1.bin1
-rw-r--r--openpgp/tests/data/armor/test-1.no-crc.asc4
-rw-r--r--openpgp/tests/data/armor/test-2.asc5
-rw-r--r--openpgp/tests/data/armor/test-2.bad-footer.asc5
-rw-r--r--openpgp/tests/data/armor/test-2.bin1
-rw-r--r--openpgp/tests/data/armor/test-3.asc5
-rw-r--r--openpgp/tests/data/armor/test-3.bin1
-rw-r--r--openpgp/tests/data/armor/test-3.with-headers.asc7
-rw-r--r--openpgp/tests/data/armor/test-47.asc5
-rw-r--r--openpgp/tests/data/armor/test-47.bin1
-rw-r--r--openpgp/tests/data/armor/test-48.asc5
-rw-r--r--openpgp/tests/data/armor/test-48.bin1
-rw-r--r--openpgp/tests/data/armor/test-49.asc6
-rw-r--r--openpgp/tests/data/armor/test-49.bin1
-rw-r--r--openpgp/tests/data/armor/test-50.asc6
-rw-r--r--openpgp/tests/data/armor/test-50.bin1
-rw-r--r--openpgp/tests/data/armor/test-51.asc6
-rw-r--r--openpgp/tests/data/armor/test-51.bin1
-rw-r--r--openpgp/tests/data/keys/testy-broken-no-pk.pgpbin0 -> 966 bytes
-rw-r--r--openpgp/tests/data/keys/testy-broken-no-sig-on-subkey.pgpbin0 -> 925 bytes
-rw-r--r--openpgp/tests/data/keys/testy-broken-no-uid.pgpbin0 -> 857 bytes
-rw-r--r--openpgp/tests/data/keys/testy-no-subkey.pgpbin0 -> 653 bytes
-rw-r--r--openpgp/tests/data/keys/testy.pgpbin0 -> 1238 bytes
52 files changed, 3356 insertions, 0 deletions
diff --git a/openpgp/Cargo.toml b/openpgp/Cargo.toml
new file mode 100644
index 00000000..3189a5b1
--- /dev/null
+++ b/openpgp/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "openpgp"
+version = "0.1.0"
+authors = ["Neal H. Walfield <neal@gnu.org>"]
+
+[dependencies]
+buffered-reader = { path = "../buffered-reader" }
+base64 = "0.8.0"
+nom = "3.2.0"
+num = "0.1.40"
+num-derive = "0.1.41"
diff --git a/openpgp/src/armor.rs b/openpgp/src/armor.rs
new file mode 100644
index 00000000..79ed1349
--- /dev/null
+++ b/openpgp/src/armor.rs
@@ -0,0 +1,723 @@
+//! Handling ASCII Armor (see [RFC 4880, section
+//! 6](https://tools.ietf.org/html/rfc4880#section-6)).
+//!
+//! # Scope
+//!
+//! This implements a subset of the ASCII Armor specification. Not
+//! supported features are:
+//!
+//! - Multipart messages
+//! - Headers
+//!
+//! The former is likely no longer useful today, and the latter seems
+//! to be of questionable value because the data is not authenticated.
+//! Reading armored data with headers is supported, but they are
+//! merely swallowed.
+//!
+//! # Memory allocations
+//!
+//! Both the reader and the writer allocate memory in the order of the
+//! size of chunks read or written.
+//!
+//! # Example
+//!
+//! ```rust,no_run
+//! use std::fs::File;
+//! use openpgp::armor::{Reader, Kind};
+//!
+//! let mut file = File::open("somefile.asc").unwrap();
+//! let mut r = Reader::new(&mut file, Kind::File);
+//! ```
+
+extern crate base64;
+use std::io::{Read, Write};
+use std::io::{Error, ErrorKind};
+use std::cmp::min;
+
+/// The encoded output stream must be represented in lines of no more
+/// than 76 characters each (see (see [RFC 4880, section
+/// 6.3](https://tools.ietf.org/html/rfc4880#section-6.3). GnuPG uses
+/// 64.
+const LINE_LENGTH: usize = 64;
+
+const LINE_ENDING: &str = "\n";
+
+/// Specifies the type of data that is to be encoded (see [RFC 4880,
+/// section 6.2](https://tools.ietf.org/html/rfc4880#section-6.2)).
+#[derive(Copy, Clone, PartialEq)]
+pub enum Kind {
+ /// A generic OpenPGP message.
+ Message,
+ /// A transferable public key.
+ PublicKey,
+ /// A transferable secret key.
+ PrivateKey,
+ /// Alias for PrivateKey.
+ SecretKey,
+ /// A detached signature.
+ Signature,
+ /// A generic file. This is a GnuPG extension.
+ File,
+ /// When reading an Armored file, accept any type.
+ Any,
+}
+
+impl Kind {
+ fn detect(blurb: &[u8]) -> Option<Self> {
+ if blurb.len() < 16 || ! blurb.starts_with(b"-----BEGIN PGP ") {
+ return None;
+ }
+
+ match &blurb[15..17] {
+ b"ME" => Some(Kind::Message),
+ b"PU" => Some(Kind::PublicKey),
+ b"PR" => Some(Kind::SecretKey),
+ b"SI" => Some(Kind::Signature),
+ b"AR" => Some(Kind::File),
+ _ => None,
+ }
+ }
+
+ fn blurb(&self) -> &str {
+ match self {
+ &Kind::Message => "MESSAGE",
+ &Kind::PublicKey => "PUBLIC KEY BLOCK",
+ &Kind::PrivateKey => "PRIVATE KEY BLOCK",
+ &Kind::SecretKey => "PRIVATE KEY BLOCK",
+ &Kind::Signature => "SIGNATURE",
+ &Kind::File => "ARMORED FILE",
+ &Kind::Any => unreachable!(),
+ }
+ }
+
+ fn begin(&self) -> String {
+ format!("-----BEGIN PGP {}-----", self.blurb())
+ }
+
+ fn begin_len(&self) -> usize {
+ 20 + self.blurb().len()
+ }
+
+ fn end(&self) -> String {
+ format!("-----END PGP {}-----", self.blurb())
+ }
+}
+
+/// A filter that applies ASCII Armor to the data written to it.
+pub struct Writer<'a, W: 'a + Write> {
+ sink: &'a mut W,
+ kind: Kind,
+ stash: Vec<u8>,
+ column: usize,
+ crc: CRC,
+ initialized: bool,
+ finalized: bool,
+}
+
+impl<'a, W: Write> Writer<'a, W> {
+ /// Construct a new filter for the given type of data.
+ pub fn new(inner: &'a mut W, kind: Kind) -> Self {
+ assert!(kind != Kind::Any);
+ Writer {
+ sink: inner,
+ kind: kind,
+ stash: Vec::<u8>::with_capacity(2),
+ column: 0,
+ crc: CRC::new(),
+ initialized: false,
+ finalized: false,
+ }
+ }
+
+ /// Write the header if not already done.
+ fn initialize(&mut self) -> Result<(), Error> {
+ if self.initialized { return Ok(()) }
+
+ write!(self.sink, "{}{}{}", self.kind.begin(),
+ LINE_ENDING, LINE_ENDING)?;
+
+ self.initialized = true;
+ Ok(())
+ }
+
+ /// Write the footer. No more data can be written after this
+ /// call. If this is not called explicitly, the header is written
+ /// once the writer is dropped.
+ pub fn finalize(&mut self) -> Result<(), Error> {
+ self.initialize()?;
+ if self.finalized {
+ return Err(Error::new(ErrorKind::BrokenPipe, "Writer is finalized."));
+ }
+
+ // Write any stashed bytes and pad.
+ if self.stash.len() > 0 {
+ self.sink.write_all(base64::encode_config(&self.stash,
+ base64::STANDARD).as_bytes())?;
+ self.column += 4;
+ }
+ self.linebreak()?;
+ if self.column > 0 {
+ write!(self.sink, "{}", LINE_ENDING)?;
+ }
+
+ let crc = self.crc.finalize();
+ let bytes: [u8; 3] = [
+ (crc >> 16) as u8,
+ (crc >> 8) as u8,
+ (crc >> 0) as u8,
+ ];
+
+ // CRC and footer.
+ write!(self.sink, "={}{}{}{}",
+ base64::encode_config(&bytes, base64::STANDARD_NO_PAD),
+ LINE_ENDING, self.kind.end(), LINE_ENDING)?;
+
+ self.finalized = true;
+ Ok(())
+ }
+
+ /// Insert a line break if necessary.
+ fn linebreak(&mut self) -> Result<(), Error> {
+ assert!(self.column <= LINE_LENGTH);
+ if self.column == LINE_LENGTH {
+ write!(self.sink, "{}", LINE_ENDING)?;
+ self.column = 0;
+ }
+ Ok(())
+ }
+}
+
+impl<'a, W: Write> Write for Writer<'a, W> {
+ fn write(&mut self, buf: &[u8]) -> Result<usize, Error> {
+ self.initialize()?;
+ if self.finalized {
+ return Err(Error::new(ErrorKind::BrokenPipe, "Writer is finalized."));
+ }
+
+ // Update CRC on the unencoded data.
+ self.crc.update(buf);
+
+ let mut input = buf;
+ let mut written = 0;
+
+ // First of all, if there are stashed bytes, fill the stash
+ // and encode it.
+ assert!(self.stash.len() < 3);
+ if self.stash.len() > 0 {
+ while self.stash.len() < 3 {
+ if input.len() == 0 {
+ /* We exhausted the input. Return now, any
+ * stashed bytes are encoded when finalizing the
+ * writer. */
+ return Ok(written);
+ }
+ self.stash.push(input[0]);
+ input = &input[1..];
+ written += 1;
+ }
+
+ self.sink.write_all(base64::encode_config(&self.stash,
+ base64::STANDARD_NO_PAD).as_bytes())?;
+ self.column += 4;
+ self.linebreak()?;
+ self.stash.clear();
+ }
+
+ // Ensure that a multiple of 3 bytes are encoded, stash the
+ // rest from the end of input.
+ while input.len() % 3 > 0 {
+ self.stash.push(input[input.len()-1]);
+ input = &input[..input.len()-1];
+ written += 1;
+ }
+ // We popped values from the end of the input, fix the order.
+ self.stash.reverse();
+ assert!(self.stash.len() < 3);
+
+ // We know that we have a multiple of 3 bytes, encode them and write them out.
+ assert!(input.len() % 3 == 0);
+ let encoded = base64::encode_config(input, base64::STANDARD_NO_PAD);
+ written += input.len();
+ let mut enc = encoded.as_bytes();
+ while enc.len() > 0 {
+ let n = min(LINE_LENGTH - self.column, enc.len());
+ self.sink.write_all(&enc[..n])?;
+ enc = &enc[n..];
+ self.column += n;
+ self.linebreak()?;
+ }
+
+ assert_eq!(written, buf.len());
+ Ok(written)
+ }
+
+ fn flush(&mut self) -> Result<(), Error> {
+ self.sink.flush()
+ }
+}
+
+impl<'a, W: Write> Drop for Writer<'a, W> {
+ fn drop(&mut self) {
+ let _ = self.finalize();
+ }
+}
+
+/// A filter that strips ASCII Armor from a stream of data.
+pub struct Reader<'a, R: 'a + Read> {
+ source: &'a mut R,
+ kind: Kind,
+ stash: Vec<u8>,
+ crc: CRC,
+ expect_crc: Option<u32>,
+ initialized: bool,
+ finalized: bool,
+}
+
+impl<'a, R: Read> Reader<'a, R> {
+ /// Construct a new filter for the given type of data.
+ pub fn new(inner: &'a mut R, kind: Kind) -> Self {
+ Reader {
+ source: inner,
+ kind: kind,
+ stash: Vec::<u8>::with_capacity(2),
+ crc: CRC::new(),
+ expect_crc: None,
+ initialized: false,
+ finalized: false,
+ }
+ }
+
+ /// Return the kind of data this reader is for. Useful in
+ /// combination with 'Kind::Any'.
+ pub fn kind(&self) -> Kind {
+ self.kind
+ }
+
+ /// Consume the header if not already done.
+ fn initialize(&mut self) -> Result<(), Error> {
+ if self.initialized { return Ok(()) }
+
+ let buf = if self.kind == Kind::Any {
+ let peek = 17;
+ let mut buf: Vec<u8> = vec![0; peek];
+ self.source.read_exact(&mut buf)?;
+
+ if let Some(k) = Kind::detect(&buf) {
+ self.kind = k;
+ } else {
+ return Err(Error::new(ErrorKind::InvalidInput, "Invalid ASCII Armor header."));
+ }
+
+ buf.resize(self.kind.begin_len(), 0);
+ self.source.read_exact(&mut buf[peek..])?;
+ buf
+ } else {
+ let mut buf: Vec<u8> = vec![0; self.kind.begin_len()];
+ self.source.read_exact(&mut buf)?;
+ buf
+ };
+
+ if buf != self.kind.begin().into_bytes() {
+ return Err(Error::new(ErrorKind::InvalidInput, "Invalid ASCII Armor header."));
+ }
+ self.linebreak()?;
+
+ while self.line()? != 0 {
+ /* Swallow headers. */
+ }
+
+ self.initialized = true;
+ Ok(())
+ }
+
+ /// Consume the footer. No more data can be read after this
+ /// call.
+ fn finalize(&mut self, buf: &[u8]) -> Result<(), Error> {
+ if self.finalized {
+ return Err(Error::new(ErrorKind::BrokenPipe, "Reader is finalized."));
+ }
+
+ let mut rest = Vec::new();
+ self.source.read_to_end(&mut rest)?;
+
+ let mut footer = Vec::new();
+ footer.extend(buf);
+ footer.extend(&rest);
+ let mut off = 0;
+
+ /* Look for CRC. The CRC is optional. */
+ if footer.len() >= 6 && footer[0] == '=' as u8 {
+ /* Found. */
+ let crc = match base64::decode_config(&footer[1..5], base64::MIME) {
+ Ok(d) => d,
+ Err(e) => return Err(Error::new(ErrorKind::InvalidInput, e)),
+ };
+ self.expect_crc = Some((crc[0] as u32) << 16
+ | (crc[1] as u32) << 8
+ | crc[2] as u32);
+
+ /* Update offset, skip whitespace. */
+ off += 5;
+ while off < footer.len() && is_ascii_whitespace(footer[off]) {
+ off += 1;
+ }
+ }
+
+ if ! footer[off..].starts_with(&self.kind.end().into_bytes()) {
+ return Err(Error::new(ErrorKind::InvalidInput, "Invalid ASCII Armor footer."));
+ }
+
+ self.finalized = true;
+ Ok(())
+ }
+
+ /// Consume a linebreak.
+ fn linebreak(&mut self) -> Result<(), Error> {
+ if self.line()? != 0 {
+ return Err(Error::new(ErrorKind::InvalidInput, "Expected newline."));
+ }
+ Ok(())
+ }
+
+ /// Consume a line, returning the number of non-whitespace bytes.
+ fn line(&mut self) -> Result<usize, Error> {
+ let mut buf = [0; 1];
+ let mut c = 0;
+
+ loop {
+ self.source.read_exact(&mut buf)?;
+ if buf[0] == '\r' as u8 {
+ self.source.read_exact(&mut buf)?;
+ }
+
+ if buf[0] != '\n' as u8 {
+ c += 1;
+ } else {
+ break;
+ }
+ }
+
+ Ok(c)