diff options
author | Justus Winter <justus@pep-project.org> | 2017-12-12 17:16:00 +0100 |
---|---|---|
committer | Justus Winter <justus@pep-project.org> | 2017-12-13 13:55:20 +0100 |
commit | c86ad83ae31d44aeab3317e5e05d6d63e428f0e0 (patch) | |
tree | 12d1aac77f256e733104e98faf9143152eacd573 /openpgp | |
parent | c0cab61441df7a0334f817c2cc4817a0910e1193 (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')
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) |