diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2020-04-21 12:19:53 +0200 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2020-04-21 12:23:27 +0200 |
commit | 004e65b7e0fc324a325fe23480dc2abd95ecedd7 (patch) | |
tree | f3e6f5f3e7d6bf9986cb6ad6505a934bdf038b1b /openpgp/src/serialize/stream.rs | |
parent | 51bebd7b013637a55a90cdedcce4f7123d09d75a (diff) |
openpgp: Add a filter applying ASCII Armor to the streaming stack.
- Previously, when creating an armored message using the
armor::Writer and the streaming serialization interface, one had
to finalize both the writer stack and the armor writer, which is
unergonomic, not obvious, and error prone.
- This filter properly finalizes the armor writer when the writer
stack is finalized.
- Fixes #453.
Diffstat (limited to 'openpgp/src/serialize/stream.rs')
-rw-r--r-- | openpgp/src/serialize/stream.rs | 207 |
1 files changed, 203 insertions, 4 deletions
diff --git a/openpgp/src/serialize/stream.rs b/openpgp/src/serialize/stream.rs index 4e1fe13c..60c52339 100644 --- a/openpgp/src/serialize/stream.rs +++ b/openpgp/src/serialize/stream.rs @@ -14,6 +14,7 @@ //! desired message structure. There are a number of filters that can //! be freely combined: //! +//! - [`Armorer`] applies ASCII-Armor to the stream, //! - [`Encryptor`] encrypts data fed into it, //! - [`Compressor`] compresses data, //! - [`Padder`] pads data, @@ -26,6 +27,7 @@ //! [`io::Write`]: https://doc.rust-lang.org/nightly/std/io/trait.Write.html //! [`Message::new`]: struct.Message.html#method.new //! [`Message`]: struct.Message.html +//! [`Armorer`]: struct.Armorer.html //! [`Encryptor`]: struct.Encryptor.html //! [`Compressor`]: struct.Compressor.html //! [`Padder`]: padding/struct.Padder.html @@ -67,9 +69,9 @@ //! //! This example demonstrates how to create the most common OpenPGP //! message structure (see [Section 11.3 of RFC 4880]). The plaintext -//! is first signed, then compressed, and finally encrypted. Our -//! example pads the plaintext instead of compressing it, but the -//! resulting message structure is the same. +//! is first signed, then compressed, encrypted, and finally ASCII +//! armored. Our example pads the plaintext instead of compressing +//! it, but the resulting message structure is the same. //! //! ``` //! # f().unwrap(); fn f() -> sequoia_openpgp::Result<()> { @@ -78,7 +80,7 @@ //! use openpgp::policy::StandardPolicy; //! use openpgp::cert::prelude::*; //! use openpgp::serialize::stream::{ -//! Message, Encryptor, Signer, LiteralWriter, +//! Message, Armorer, Encryptor, Signer, LiteralWriter, //! padding::{Padder, padme}, //! }; //! # use openpgp::parse::Parse; @@ -106,6 +108,7 @@ //! //! # let mut sink = vec![]; //! let message = Message::new(&mut sink); +//! let message = Armorer::new(message).build()?; //! let message = Encryptor::for_recipients(message, recipients).build()?; //! // Reduce metadata leakage by concealing the message size. //! let message = Padder::new(message, padme)?; @@ -124,6 +127,7 @@ use std::io::{self, Write}; use std::time::SystemTime; use crate::{ + armor, crypto, Error, Fingerprint, @@ -325,6 +329,201 @@ impl<'a> From<&'a mut dyn io::Write> for Message<'a> { } +/// Applies ASCII Armor to the message. +/// +/// ASCII armored data (see [Section 6 of RFC 4880]) is a OpenPGP data +/// stream that has been base64-encoded and decorated with a header, +/// footer, and optional headers representing key-value pairs. It can +/// be safely transmitted over protocols that can only transmit +/// printable characters, and can handled by end users (e.g. copied +/// and pasted). +/// +/// [Section 6 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-6 +pub struct Armorer<'a> { + kind: armor::Kind, + headers: Vec<(String, String)>, + inner: Message<'a>, +} + +impl<'a> Armorer<'a> { + /// Creates a new armoring filter. + /// + /// By default, the type of the armored data is set to + /// [`armor::Kind`]`::Message`. To change it, use + /// [`Armorer::kind`]. To add headers to the armor, use + /// [`Armorer::add_header`]. + /// + /// [`armor::Kind`]: ../../armor/enum.Kind.html + /// [`Armorer::kind`]: #method.kind + /// [`Armorer::add_header`]: #method.add_header + /// + /// # Examples + /// + /// ``` + /// # f().unwrap(); fn f() -> sequoia_openpgp::Result<()> { + /// use std::io::Write; + /// use sequoia_openpgp as openpgp; + /// use openpgp::serialize::stream::{Message, Armorer, LiteralWriter}; + /// + /// let mut sink = vec![]; + /// { + /// let message = Message::new(&mut sink); + /// let message = Armorer::new(message) + /// // Customize the `Armorer` here. + /// .build()?; + /// let mut message = LiteralWriter::new(message).build()?; + /// message.write_all(b"Hello world.")?; + /// message.finalize()?; + /// } + /// assert_eq!("-----BEGIN PGP MESSAGE-----\n\ + /// \n\ + /// yxJiAAAAAABIZWxsbyB3b3JsZC4=\n\ + /// =6nHv\n\ + /// -----END PGP MESSAGE-----\n", + /// std::str::from_utf8(&sink)?); + /// # Ok(()) } + pub fn new(inner: Message<'a>) -> Self { + Self { + kind: armor::Kind::Message, + headers: Vec::with_capacity(0), + inner, + } + } + + /// Changes the kind of armoring. + /// + /// The armor header and footer changes depending on the type of + /// wrapped data. See [`armor::Kind`] for the possible values. + /// + /// [`armor::Kind`]: ../../armor/enum.Kind.html + /// + /// # Examples + /// + /// ``` + /// # f().unwrap(); fn f() -> sequoia_openpgp::Result<()> { + /// use std::io::Write; + /// use sequoia_openpgp as openpgp; + /// use openpgp::armor; + /// use openpgp::serialize::stream::{Message, Armorer, Signer}; + /// # use sequoia_openpgp::policy::StandardPolicy; + /// # use openpgp::{Result, Cert}; + /// # use openpgp::packet::prelude::*; + /// # use openpgp::crypto::KeyPair; + /// # use openpgp::parse::Parse; + /// # use openpgp::parse::stream::*; + /// # let p = &StandardPolicy::new(); + /// # let cert = Cert::from_bytes(&include_bytes!( + /// # "../../tests/data/keys/testy-new-private.pgp")[..])?; + /// # let signing_keypair + /// # = cert.keys().secret() + /// # .with_policy(p, None).alive().revoked(false).for_signing() + /// # .nth(0).unwrap() + /// # .key().clone().into_keypair()?; + /// + /// let mut sink = vec![]; + /// { + /// let message = Message::new(&mut sink); + /// let message = Armorer::new(message) + /// .kind(armor::Kind::Signature) + /// .build()?; + /// let mut signer = Signer::new(message, signing_keypair) + /// .detached() + /// .build()?; + /// + /// // Write the data directly to the `Signer`. + /// signer.write_all(b"Make it so, number one!")?; + /// // In reality, just io::copy() the file to be signed. + /// signer.finalize()?; + /// } + /// + /// assert!(std::str::from_utf8(&sink)? + /// .starts_with("-----BEGIN PGP SIGNATURE-----\n")); + /// # Ok(()) } + pub fn kind(mut self, kind: armor::Kind) -> Self { + self.kind = kind; + self + } + + /// Adds a header to the armor block. + /// + /// There are a number of defined armor header keys (see [Section + /// 6 of RFC 4880]), but in practice, any key may be used, as + /// implementations should simply ignore unknown keys. + /// + /// [Section 6 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-6 + /// + /// # Examples + /// + /// ``` + /// # f().unwrap(); fn f() -> sequoia_openpgp::Result<()> { + /// use std::io::Write; + /// use sequoia_openpgp as openpgp; + /// use openpgp::serialize::stream::{Message, Armorer, LiteralWriter}; + /// + /// let mut sink = vec![]; + /// { + /// let message = Message::new(&mut sink); + /// let message = Armorer::new(message) + /// .add_header("Comment", "No comment.") + /// .build()?; + /// let mut message = LiteralWriter::new(message).build()?; + /// message.write_all(b"Hello world.")?; + /// message.finalize()?; + /// } + /// assert_eq!("-----BEGIN PGP MESSAGE-----\n\ + /// Comment: No comment.\n\ + /// \n\ + /// yxJiAAAAAABIZWxsbyB3b3JsZC4=\n\ + /// =6nHv\n\ + /// -----END PGP MESSAGE-----\n", + /// std::str::from_utf8(&sink)?); + /// # Ok(()) } + pub fn add_header<K, V>(mut self, key: K, value: V) -> Self + where K: AsRef<str>, + V: AsRef<str>, + { + self.headers.push((key.as_ref().to_string(), + value.as_ref().to_string())); + self + } + + /// Builds the armor writer, returning the writer stack. + /// + /// # Examples + /// + /// ``` + /// # f().unwrap(); fn f() -> sequoia_openpgp::Result<()> { + /// use sequoia_openpgp as openpgp; + /// use openpgp::serialize::stream::{Message, Armorer, LiteralWriter}; + /// + /// # let mut sink = vec![]; + /// let message = Message::new(&mut sink); + /// let message = Armorer::new(message) + /// // Customize the `Armorer` here. + /// .build()?; + /// # Ok(()) } + pub fn build(self) -> Result<Message<'a>> { + let level = self.inner.as_ref().cookie_ref().level; + writer::Armorer::new( + self.inner, + Cookie::new(level + 1), + self.kind, + self.headers, + ) + } +} + +impl<'a> fmt::Debug for Armorer<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Armorer") + .field("inner", &self.inner) + .field("kind", &self.kind) + .field("headers", &self.headers) + .finish() + } +} + + /// Writes an arbitrary packet. /// /// This writer can be used to construct arbitrary OpenPGP packets. |