summaryrefslogtreecommitdiffstats
path: root/guide
diff options
context:
space:
mode:
Diffstat (limited to 'guide')
-rw-r--r--guide/Cargo.toml13
-rw-r--r--guide/build.rs70
-rw-r--r--guide/src/chapter_00.md95
-rw-r--r--guide/src/chapter_01.md569
-rw-r--r--guide/src/chapter_02.md218
-rw-r--r--guide/src/lib.rs22
6 files changed, 987 insertions, 0 deletions
diff --git a/guide/Cargo.toml b/guide/Cargo.toml
new file mode 100644
index 00000000..ee0477db
--- /dev/null
+++ b/guide/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "sequoia-guide"
+version = "0.2.0"
+authors = [
+ "Justus Winter <justus@sequoia-pgp.org>",
+ "Kai Michaelis <kai@sequoia-pgp.org>",
+ "Neal H. Walfield <neal@sequoia-pgp.org>",
+]
+build = "build.rs"
+
+[dependencies]
+sequoia-openpgp = { path = "../openpgp", version = "0.2.0" }
+failure = "0.1.2"
diff --git a/guide/build.rs b/guide/build.rs
new file mode 100644
index 00000000..3d73a984
--- /dev/null
+++ b/guide/build.rs
@@ -0,0 +1,70 @@
+use std::env;
+use std::io::{self, Write, BufRead};
+use std::fs::{self, DirEntry};
+use std::ffi::OsString;
+use std::path::{Path, PathBuf};
+
+// one possible implementation of walking a directory only visiting files
+fn visit_dirs(dir: &Path, cb: &Fn(&DirEntry) -> io::Result<()>)
+ -> io::Result<()> {
+ if dir.is_dir() {
+ for entry in fs::read_dir(dir)? {
+ let entry = entry?;
+ let path = entry.path();
+ if path.is_dir() {
+ visit_dirs(&path, cb)?;
+ } else {
+ cb(&entry)?;
+ }
+ }
+ }
+ Ok(())
+}
+
+fn manifest_dir() -> PathBuf {
+ PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap())
+}
+
+fn out_dir() -> PathBuf {
+ env::var_os("OUT_DIR").unwrap().into()
+}
+
+fn lib_path() -> PathBuf {
+ out_dir().join("src/lib.rs")
+}
+
+fn translate_path(src: &Path) -> PathBuf {
+ let src = src.to_str().unwrap();
+ format!("{}rs", &src[..src.len() - 2]).into()
+}
+
+fn translate2rs(src: &DirEntry) -> io::Result<()> {
+ let path = src.path();
+ if path.extension() != Some(&OsString::from("md")) {
+ return Ok(());
+ }
+
+ let sink_filename = out_dir().join(
+ translate_path(&path).strip_prefix(&manifest_dir()).unwrap());
+
+ eprintln!("{:?} -> {:?}", path, sink_filename);
+ println!("rerun-if-changed={}", path.to_str().unwrap());
+
+ fs::create_dir_all(sink_filename.parent().unwrap())?;
+ let mut sink = fs::File::create(sink_filename)?;
+
+ for line in io::BufReader::new(fs::File::open(&path)?).lines() {
+ writeln!(&mut sink, "//! {}", line?)?;
+ }
+
+ let mut lib =
+ fs::OpenOptions::new().create(true).append(true).open(&lib_path())?;
+ writeln!(&mut lib, "pub mod {};", path.file_stem().unwrap().to_str().unwrap())?;
+
+ Ok(())
+}
+
+fn main() {
+ let _ = fs::remove_file(&lib_path());
+ visit_dirs(&manifest_dir().join("src"), &translate2rs).unwrap();
+}
diff --git a/guide/src/chapter_00.md b/guide/src/chapter_00.md
new file mode 100644
index 00000000..63eed991
--- /dev/null
+++ b/guide/src/chapter_00.md
@@ -0,0 +1,95 @@
+Describes how to create a simple Rust application using Sequoia.
+
+# Build dependencies
+
+First of all, you need Rust, and a few libraries that we depend upon.
+On Debian-like systems, the required packages can be installed using
+the following command. As of this writing, this works fine on Debian
+10 (Buster). You can use Debian 9 (Stretch), but you need to pull
+`rustc`, `cargo`, and `nettle-dev` from testing.
+
+```text
+# apt install git rustc cargo clang make pkg-config nettle-dev libssl-dev capnproto libsqlite3-dev
+```
+
+# Creating a new project
+
+If are starting from scratch, you need to create a new crate:
+
+```text
+$ cargo new --bin example
+ Created binary (application) `example` project
+$ cd example
+```
+
+Now add Sequoia to the `[dependencies]` section in `Cargo.toml`:
+
+```toml
+sequoia-openpgp = "0.2"
+```
+
+If you want to use the bleeding edge, you can instead refer to the
+version in git:
+
+```toml
+sequoia-openpgp = { git = "https://gitlab.com/sequoia-pgp/sequoia.git" }
+```
+
+To build and run your application, do:
+
+```sh
+$ cargo run
+```
+
+On the first run, cargo will download and build Sequoia and all
+dependencies. When finished, nothing really happens because we have
+not populated `main` yet. Let's do that! Open `src/main.rs` with
+your favorite editor, and enter:
+
+```
+#[macro_use] // For armored!
+extern crate sequoia_openpgp as openpgp;
+use std::io;
+
+fn main() {
+ let mut reader = armored!(
+ "-----BEGIN PGP ARMORED FILE-----
+
+ SGVsbG8gd29ybGQhCg==
+ =XLsG
+ -----END PGP ARMORED FILE-----"
+ );
+
+ io::copy(&mut reader, &mut io::stdout()).unwrap();
+}
+```
+
+Running the application now prints a friendly message to stdout.
+
+A word on the `armored` macro. We will use this macro in this guide
+to inline OpenPGP data into the source code. Sequoia includes filters
+for ASCII armored data. You can use these filters to read armored
+data from any `Read`er, or write armored data to any `Write`r. The
+`armored` macro does the same for string literals. In order to use
+this macro, you need to use `#[macro_use]` when importing the
+`openpgp` crate.
+
+# Building the Sequoia tool
+
+Sequoia includes a simple frontend `sq` that can be used to experiment
+with Sequoia and OpenPGP. The documentation for this tool is
+[here](../../sq/index.html). It is also an example of
+how to use various aspects of Sequoia. Clone Sequoia and build the
+tool:
+
+```sh
+$ git clone https://gitlab.com/sequoia-pgp/sequoia.git
+...
+$ cd sequoia
+$ cargo build -p sequoia-tool
+...
+$ target/debug/sq
+sq 0.1.0
+Sequoia is an implementation of OpenPGP. This is a command-line frontend.
+...
+```
diff --git a/guide/src/chapter_01.md b/guide/src/chapter_01.md
new file mode 100644
index 00000000..e81354e5
--- /dev/null
+++ b/guide/src/chapter_01.md
@@ -0,0 +1,569 @@
+Describes key creation, encryption, and decryption.
+
+In this chapter, we will see how to use Sequoia's [low-level API] to
+generate an OpenPGP key, and use it to encrypt and decrypt some data.
+We will construct this program from top to bottom, concatenating the
+fragments yields the [`openpgp/examples/generate-encrypt-decrypt.rs`].
+
+[low-level API]: ../../sequoia_openpgp/index.html
+[`openpgp/examples/generate-encrypt-decrypt.rs`]: https://gitlab.com/sequoia-pgp/sequoia/blob/master/openpgp/examples/generate-encrypt-decrypt.rs
+
+```rust
+use std::io::{self, Write};
+
+extern crate sequoia_openpgp as openpgp;
+use openpgp::serialize::stream::*;
+use openpgp::parse::stream::*;
+
+const MESSAGE: &'static str = "дружба";
+
+fn main() {
+ // Generate a key.
+ let key = generate().unwrap();
+
+ // Encrypt the message.
+ let mut ciphertext = Vec::new();
+ encrypt(&mut ciphertext, MESSAGE, &key).unwrap();
+
+ // Decrypt the message.
+ let mut plaintext = Vec::new();
+ decrypt(&mut plaintext, &ciphertext, &key).unwrap();
+
+ assert_eq!(MESSAGE.as_bytes(), &plaintext[..]);
+}
+#
+# /// Generates an encryption-capable key.
+# fn generate() -> openpgp::Result<openpgp::TPK> {
+# let (tpk, _revocation) = openpgp::tpk::TPKBuilder::default()
+# .add_userid("someone@example.org")
+# .add_encryption_subkey()
+# .generate()?;
+#
+# // Save the revocation certificate somewhere.
+#
+# Ok(tpk)
+# }
+#
+# /// Encrypts the given message.
+# fn encrypt(sink: &mut Write, plaintext: &str, recipient: &openpgp::TPK)
+# -> openpgp::Result<()> {
+# // Start streaming an OpenPGP message.
+# let message = Message::new(sink);
+#
+# // We want to encrypt a literal data packet.
+# let encryptor = Encryptor::new(message,
+# &[], // No symmetric encryption.
+# &[recipient],
+# EncryptionMode::ForTransport)?;
+#
+# // Emit a literal data packet.
+# let mut literal_writer = LiteralWriter::new(
+# encryptor, openpgp::constants::DataFormat::Binary, None, None)?;
+#
+# // Encrypt the data.
+# literal_writer.write_all(plaintext.as_bytes())?;
+#
+# // Finalize the OpenPGP message to make sure that all data is
+# // written.
+# literal_writer.finalize()?;
+#
+# Ok(())
+# }
+#
+# /// Decrypts the given message.
+# fn decrypt(sink: &mut Write, ciphertext: &[u8], recipient: &openpgp::TPK)
+# -> openpgp::Result<()> {
+# // Make a helper that that feeds the recipient's secret key to the
+# // decryptor.
+# let helper = Helper {
+# secret: recipient,
+# };
+#
+# // Now, create a decryptor with a helper using the given TPKs.
+# let mut decryptor = Decryptor::from_bytes(ciphertext, helper)?;
+#
+# // Decrypt the data.
+# io::copy(&mut decryptor, sink)?;
+#
+# Ok(())
+# }
+#
+# struct Helper<'a> {
+# secret: &'a openpgp::TPK,
+# }
+#
+# impl<'a> VerificationHelper for Helper<'a> {
+# fn get_public_keys(&mut self, _ids: &[openpgp::KeyID])
+# -> openpgp::Result<Vec<openpgp::TPK>> {
+# // Return public keys for signature verification here.
+# Ok(Vec::new())
+# }
+#
+# fn check(&mut self, _sigs: Vec<Vec<VerificationResult>>)
+# -> openpgp::Result<()> {
+# // Implement your signature verification policy here.
+# Ok(())
+# }
+# }
+#
+# impl<'a> DecryptionHelper for Helper<'a> {
+# fn get_secret(&mut self,
+# _pkesks: &[&openpgp::packet::PKESK],
+# _skesks: &[&openpgp::packet::SKESK])
+# -> openpgp::Result<Option<Secret>>
+# {
+# // The encryption key is the first and only subkey.
+# let key = self.secret.subkeys().nth(0)
+# .map(|binding| binding.subkey().clone())
+# .unwrap();
+#
+# // The secret key is not encrypted.
+# let secret =
+# if let Some(openpgp::SecretKey::Unencrypted {
+# ref mpis,
+# }) = key.secret() {
+# mpis.clone()
+# } else {
+# unreachable!()
+# };
+#
+# Ok(Some(Secret::Asymmetric {
+# identity: self.secret.fingerprint(),
+# key: key,
+# secret: secret,
+# }))
+# }
+# }
+```
+
+# Key generation
+
+First, we need to generate a new key. This key shall have one user
+id, and one encryption-capable subkey. We use the [`TPKBuilder`] to
+create it:
+
+[`TPKBuilder`]: ../../sequoia_openpgp/tpk/struct.TPKBuilder.html
+
+```rust
+# use std::io::{self, Write};
+#
+# extern crate sequoia_openpgp as openpgp;
+# use openpgp::serialize::stream::*;
+# use openpgp::parse::stream::*;
+#
+# const MESSAGE: &'static str = "дружба";
+#
+# fn main() {
+# // Generate a key.
+# let key = generate().unwrap();
+#
+# // Encrypt the message.
+# let mut ciphertext = Vec::new();
+# encrypt(&mut ciphertext, MESSAGE, &key).unwrap();
+#
+# // Decrypt the message.
+# let mut plaintext = Vec::new();
+# decrypt(&mut plaintext, &ciphertext, &key).unwrap();
+#
+# assert_eq!(MESSAGE.as_bytes(), &plaintext[..]);
+# }
+#
+/// Generates an encryption-capable key.
+fn generate() -> openpgp::Result<openpgp::TPK> {
+ let (tpk, _revocation) = openpgp::tpk::TPKBuilder::default()
+ .add_userid("someone@example.org")
+ .add_encryption_subkey()
+ .generate()?;
+
+ // Save the revocation certificate somewhere.
+
+ Ok(tpk)
+}
+#
+# /// Encrypts the given message.
+# fn encrypt(sink: &mut Write, plaintext: &str, recipient: &openpgp::TPK)
+# -> openpgp::Result<()> {
+# // Start streaming an OpenPGP message.
+# let message = Message::new(sink);
+#
+# // We want to encrypt a literal data packet.
+# let encryptor = Encryptor::new(message,
+# &[], // No symmetric encryption.
+# &[recipient],
+# EncryptionMode::ForTransport)?;
+#
+# // Emit a literal data packet.
+# let mut literal_writer = LiteralWriter::new(
+# encryptor, openpgp::constants::DataFormat::Binary, None, None)?;
+#
+# // Encrypt the data.
+# literal_writer.write_all(plaintext.as_bytes())?;
+#
+# // Finalize the OpenPGP message to make sure that all data is
+# // written.
+# literal_writer.finalize()?;
+#
+# Ok(())
+# }
+#
+# /// Decrypts the given message.
+# fn decrypt(sink: &mut Write, ciphertext: &[u8], recipient: &openpgp::TPK)
+# -> openpgp::Result<()> {
+# // Make a helper that that feeds the recipient's secret key to the
+# // decryptor.
+# let helper = Helper {
+# secret: recipient,
+# };
+#
+# // Now, create a decryptor with a helper using the given TPKs.
+# let mut decryptor = Decryptor::from_bytes(ciphertext, helper)?;
+#
+# // Decrypt the data.
+# io::copy(&mut decryptor, sink)?;
+#
+# Ok(())
+# }
+#
+# struct Helper<'a> {
+# secret: &'a openpgp::TPK,
+# }
+#
+# impl<'a> VerificationHelper for Helper<'a> {
+# fn get_public_keys(&mut self, _ids: &[openpgp::KeyID])
+# -> openpgp::Result<Vec<openpgp::TPK>> {
+# // Return public keys for signature verification here.
+# Ok(Vec::new())
+# }
+#
+# fn check(&mut self, _sigs: Vec<Vec<VerificationResult>>)
+# -> openpgp::Result<()> {
+# // Implement your signature verification policy here.
+# Ok(())
+# }
+# }
+#
+# impl<'a> DecryptionHelper for Helper<'a> {
+# fn get_secret(&mut self,
+# _pkesks: &[&openpgp::packet::PKESK],
+# _skesks: &[&openpgp::packet::SKESK])
+# -> openpgp::Result<Option<Secret>>
+# {
+# // The encryption key is the first and only subkey.
+# let key = self.secret.subkeys().nth(0)
+# .map(|binding| binding.subkey().clone())
+# .unwrap();
+#
+# // The secret key is not encrypted.
+# let secret =
+# if let Some(openpgp::SecretKey::Unencrypted {
+# ref mpis,
+# }) = key.secret() {
+# mpis.clone()
+# } else {
+# unreachable!()
+# };
+#
+# Ok(Some(Secret::Asymmetric {
+# identity: self.secret.fingerprint(),
+# key: key,
+# secret: secret,
+# }))
+# }
+# }
+```
+
+# Encryption
+
+To encrypt a message, we first compose a writer stack corresponding to
+the desired output format and packet structure. The resulting object
+implements [`io::Write`], and we simply write the plaintext to it.
+
+[`io::Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
+
+```rust
+# use std::io::{self, Write};
+#
+# extern crate sequoia_openpgp as openpgp;
+# use openpgp::serialize::stream::*;
+# use openpgp::parse::stream::*;
+#
+# const MESSAGE: &'static str = "дружба";
+#
+# fn main() {
+# // Generate a key.
+# let key = generate().unwrap();
+#
+# // Encrypt the message.
+# let mut ciphertext = Vec::new();
+# encrypt(&mut ciphertext, MESSAGE, &key).unwrap();
+#
+# // Decrypt the message.
+# let mut plaintext = Vec::new();
+# decrypt(&mut plaintext, &ciphertext, &key).unwrap();
+#
+# assert_eq!(MESSAGE.as_bytes(), &plaintext[..]);
+# }
+#
+# /// Generates an encryption-capable key.
+# fn generate() -> openpgp::Result<openpgp::TPK> {
+# let (tpk, _revocation) = openpgp::tpk::TPKBuilder::default()
+# .add_userid("someone@example.org")
+# .add_encryption_subkey()
+# .generate()?;
+#
+# // Save the revocation certificate somewhere.
+#
+# Ok(tpk)
+# }
+#
+/// Encrypts the given message.
+fn encrypt(sink: &mut Write, plaintext: &str, recipient: &openpgp::TPK)
+ -> openpgp::Result<()> {
+ // Start streaming an OpenPGP message.
+ let message = Message::new(sink);
+
+ // We want to encrypt a literal data packet.
+ let encryptor = Encryptor::new(message,
+ &[], // No symmetric encryption.
+ &[recipient],
+ EncryptionMode::ForTransport)?;
+
+ // Emit a literal data packet.
+ let mut literal_writer = LiteralWriter::new(
+ encryptor, openpgp::constants::DataFormat::Binary, None, None)?;
+
+ // Encrypt the data.
+ literal_writer.write_all(plaintext.as_bytes())?;
+
+ // Finalize the OpenPGP message to make sure that all data is
+ // written.
+ literal_writer.finalize()?;
+
+ Ok(())
+}
+#
+# /// Decrypts the given message.
+# fn decrypt(sink: &mut Write, ciphertext: &[u8], recipient: &openpgp::TPK)
+# -> openpgp::Result<()> {
+# // Make a helper that that feeds the recipient's secret key to the
+# // decryptor.
+# let helper = Helper {
+# secret: recipient,
+# };
+#
+# // Now, create a decryptor with a helper using the given TPKs.
+# let mut decryptor = Decryptor::from_bytes(ciphertext, helper)?;
+#
+# // Decrypt the data.
+# io::copy(&mut decryptor, sink)?;
+#
+# Ok(())
+# }
+#
+# struct Helper<'a> {
+# secret: &'a openpgp::TPK,
+# }
+#
+# impl<'a> VerificationHelper for Helper<'a> {
+# fn get_public_keys(&mut self, _ids: &[openpgp::KeyID])
+# -> openpgp::Result<Vec<openpgp::TPK>> {
+# // Return public keys for signature verification here.
+# Ok(Vec::new())
+# }
+#
+# fn check(&mut self, _sigs: Vec<Vec<VerificationResult>>)
+# -> openpgp::Result<()> {
+# // Implement your signature verification policy here.
+# Ok(())
+# }
+# }
+#
+# impl<'a> DecryptionHelper for Helper<'a> {
+# fn get_secret(&mut self,
+# _pkesks: &[&openpgp::packet::PKESK],
+# _skesks: &[&openpgp::packet::SKESK])
+# -> openpgp::Result<Option<Secret>>
+# {
+# // The encryption key is the first and only subkey.
+# let key = self.secret.subkeys().nth(0)
+# .map(|binding| binding.subkey().clone())
+# .unwrap();
+#
+# // The secret key is not encrypted.
+# let secret =
+# if let Some(openpgp::SecretKey::Unencrypted {
+# ref mpis,
+# }) = key.secret() {
+# mpis.clone()
+# } else {
+# unreachable!()
+# };
+#
+# Ok(Some(Secret::Asymmetric {
+# identity: self.secret.fingerprint(),
+# key: key,
+# secret: secret,
+# }))
+# }
+# }
+```
+
+# Decryption
+
+Decryption is more difficult than encryption. When we encrypt, we
+control the packet structure being generated. However, when we
+decrypt, the control flow is determined by the message being
+processed.
+
+To use Sequoia's low-level streaming decryptor, we need to provide an
+object that implements [`VerificationHelper`] and
+[`DecryptionHelper`]. This object provides public and secret keys for
+the signature verification and decryption, and implements the
+signature verification policy.
+
+[`VerificationHelper`]: ../../sequoia_openpgp/parse/stream/trait.VerificationHelper.html
+[`DecryptionHelper`]: ../../sequoia_openpgp/parse/stream/trait.DecryptionHelper.html
+
+To decrypt messages, we create a [`Decryptor`] with our helper.
+Decrypted data can be read from this using [`io::Read`].
+
+[`Decryptor`]: ../../sequoia_openpgp/parse/stream/struct.Decryptor.html
+[`io::Read`]: https://doc.rust-lang.org/std/io/trait.Read.html
+
+```rust
+# use std::io::{self, Write};
+#
+# extern crate sequoia_openpgp as openpgp;
+# use openpgp::serialize::stream::*;
+# use openpgp::parse::stream::*;
+#
+# const MESSAGE: &'static str = "дружба";
+#
+# fn main() {
+# // Generate a key.
+# let key = generate().unwrap();
+#
+# // Encrypt the message.
+# let mut ciphertext = Vec::new();
+# encrypt(&mut ciphertext, MESSAGE, &key).unwrap();
+#
+# // Decrypt the message.
+# let mut plaintext = Vec::new();
+# decrypt(&mut plaintext, &ciphertext, &key).unwrap();
+#
+# assert_eq!(MESSAGE.as_bytes(), &plaintext[..]);
+# }
+#
+# /// Generates an encryption-capable key.
+# fn generate() -> openpgp::Result<openpgp::TPK> {
+# let (tpk, _revocation) = openpgp::tpk::TPKBuilder::default()
+# .add_userid("someone@example.org")
+# .add_encryption_subkey()
+# .generate()?;
+#
+# // Save the revocation certificate somewhere.
+#
+# Ok(tpk)
+# }
+#
+# /// Encrypts the given message.
+# fn encrypt(sink: &mut Write, plaintext: &str, recipient: &openpgp::TPK)
+# -> openpgp::Result<()> {
+# // Start streaming an OpenPGP message.
+# let message = Message::new(sink);
+#
+# // We want to encrypt a literal data packet.
+# let encryptor = Encryptor::new(message,
+# &[], // No symmetric encryption.
+# &[recipient],
+# EncryptionMode::ForTransport)?;
+#
+# // Emit a literal data packet.
+# let mut literal_writer = LiteralWriter::new(
+# encryptor, openpgp::constants::DataFormat::Binary, None, None)?;
+#
+# // Encrypt the data.
+# literal_writer.write_all(plaintext.as_bytes())?;
+#
+# // Finalize the OpenPGP message to make sure that all data is
+# // written.
+# literal_writer.finalize()?;
+#
+# Ok(())
+# }
+#
+/// Decrypts the given message.
+fn decrypt(sink: &mut Write, ciphertext: &[u8], recipient: &openpgp::TPK)
+ -> openpgp::Result<()> {
+ // Make a helper that that feeds the recipient's secret key to the
+ // decryptor.
+ let helper = Helper {
+ secret: recipient,
+ };
+
+ // Now, create a decryptor with a helper using the given TPKs.
+ let mut decryptor = Decryptor::from_bytes(ciphertext, helper)?;
+
+ // Decrypt the data.
+ io::copy(&mut decryptor, sink)?;
+
+ Ok(())
+}
+
+struct Helper<'a> {
+ secret: &'a openpgp::TPK,
+}
+
+impl<'a> VerificationHelper for Helper<'a> {
+ fn get_public_keys(&mut self, _ids: &[openpgp::KeyID])
+ -> openpgp::Result<Vec<openpgp::TPK>> {
+ // Return public keys for signature verification here.
+ Ok(Vec::new())
+ }
+
+ fn check(&mut self, _sigs: Vec<Vec<VerificationResult>>)
+ -> openpgp::Result<()> {
+ // Implement your signature verification policy here.
+ Ok(())
+ }
+}
+
+impl<'a> DecryptionHelper for Helper<'a> {
+ fn get_secret(&mut self,
+ _pkesks: &[&openpgp::packet::PKESK],
+ _skesks: &[&openpgp::packet::SKESK])
+ -> openpgp::Result<Option<Secret>>
+ {
+ // The encryption key is the first and only subkey.
+ let key = self.secret.subkeys().nth(0)
+ .map(|binding| binding.subkey().clone())
+ .unwrap();
+
+ // The secret key is not encrypted.
+ let secret =
+ if let Some(openpgp::SecretKey::Unencrypted {
+ ref mpis,
+ }) = key.secret() {
+ mpis.clone()
+ } else {
+ unreachable!()
+ };
+
+ Ok(Some(Secret::Asymmetric {
+ identity: self.secret.fingerprint(),
+ key: key,
+ secret: secret,
+ }))
+ }
+}
+```
+
+# Further reading
+
+For more examples on how to read a key from a file, and then either
+encrypt or decrypt some messages, see
+[`openpgp/examples/encrypt-for.rs`] and
+[`openpgp/examples/decrypt-with.rs`].
+
+[`openpgp/examples/encrypt-for.rs`]: https://gitlab.com/sequoia-pgp/sequoia/blob/master/openpgp/examples/encrypt-for.rs
+[`openpgp/examples/decrypt-with.rs`]: https://gitlab.com/sequoia-pgp/sequoia/blob/master/openpgp/examples/decrypt-with.rs
diff --git a/guide/src/chapter_02.md b/guide/src/chapter_02.md
new file mode 100644
index 00000000..f39cecd1
--- /dev/null
+++ b/guide/src/chapter_02.md
@@ -0,0 +1,218 @@
+Describes how to use some of Sequoia's parsers.
+
+Sequoia contains and exposes several parsers. In this chapter, we
+will cover some of them, starting from a high level parser, the
+[`TPKParser`] that parses transferable public keys ([`TPK`]s), all
+down to the actual OpenPGP [`PacketParser`].
+
+[`TPKParser`]: ../../sequoia_openpgp/tpk/struct.TPKParser.html
+[`TPK`]: ../../sequoia_openpgp/struct.TPK.html
+[`PacketParser`]: ../../sequoia_openpgp/parse/struct.PacketParser.html
+
+# Parsing TPKs
+
+First, we will start with a string that presumably contains a
+transferable public key, and feed it into the [`TPKParser`]. On
+success, we can use or examine the resulting [`TPK`]:
+
+```rust
+extern crate sequoia_openpgp as openpgp;
+
+const KEY: &str =
+ "-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+ xjMEXAfmvxYJKwYBBAHaRw8BAQdAVNM03IK1KDgDNCbf4XcARhfqzyx425FEJMQ5
+ qF+DrwHNF+G8iM+BzrnPg8+Ezr/PhM6tzrvOt8+CwoQEExYKADYCHgMCmwEFglwH
+ 5r8FiQWfpgAWIQTAh0R4plxUCh9zcrSiLq1hTRF0SgkQoi6tYU0RdEoCFQoAALip
+ AP4sSVgNJogb/v0Qst0+WlmrJ6upG8Ynao5mnRFmfx2LjAEAyGJJBaEBB+x4kOse
+ 9uACwAXFhBRLN9zGgbyySQ3fRwjOMwRcB+a/FgkrBgEEAdpHDwEBB0BXBFWMeVd1
+ nNn/VqTVEgY3wknX/KkKfMWhslFJoyZ4L8LAOAQYFgoAMwKbAgWCXAfmvwWJBZ+m
+ ABYhBMCHRHimXFQKH3NytKIurWFNEXRKCRCiLq1hTRF0SgIVCgB3dqAEGRYKACcF
+ glwH5r8WIQRnpIdTo4Cms7fffcXmxol6TO+JJAkQ5saJekzviSQAAMuvAQDdRfbM
+ u2bDtVqNLIP/0WD/5X0us49r1yXMH+Ilg5NEEQEAuSQ1pY+reS62ETUS0uKYhxxv
+ 7OOsr8YM/ZMQ0exZsw/u+QEAuakAXrR7uFmWyigopQ7qMYfnK5zNfQNykvony5tS
+ HpEBAJs3ZwHq+Q0ziAZNgcvdp0mklx8IXd8x59NjiP1t3mUBzjgEXAfmvxIKKwYB
+ BAGXVQEFAQEHQJuIvcDm3Sh0+ZOE5hj7jCBas2xOCqYiG6+bWWieoxRrAwEICcKB
+ BBgWCgAzApsMBYJcB+a/BYkFn6YAFiEEwIdEeKZcVAofc3K0oi6tYU0RdEoJEKIu
+ rWFNEXRKAgsJAADx4wD/VrXZ7I/hBC37lzhyVEcCaHcorVXVn8ACCiyRmgmNbY4A
+ /1lJmQJoDlpYlx3BAJ6RYuXRJoyU5KpcBf5afBPn8ncB
+ =MHBq
+ -----END PGP PUBLIC KEY BLOCK-----";
+
+fn main() {
+ let tpk = openpgp::TPK::from_bytes(KEY.as_bytes()).unwrap();
+
+ assert_eq!(tpk.fingerprint().to_string(),
+ "C087 4478 A65C 540A 1F73 72B4 A22E AD61 4D11 744A");
+
+ // Iterate over UserIDs.
+ assert_eq!(tpk.userids().count(), 1);
+ assert_eq!(tpk.userids().nth(0).unwrap().userid(),
+ &"Ἀριστοτέλης".into());
+
+ // Iterate over subkeys.
+ assert_eq!(tpk.subkeys().count(), 2);
+ assert_eq!(tpk.subkeys().nth(0).unwrap().subkey().fingerprint().to_string(),
+ "67A4 8753 A380 A6B3 B7DF 7DC5 E6C6 897A 4CEF 8924");
+ assert_eq!(tpk.subkeys().nth(1).unwrap().subkey().fingerprint().to_string(),
+ "185C DAA1 2723 0423 19E4 7F67 108F 2CAF 9034 356D");
+}
+```
+
+# Parsing OpenPGP messages
+
+Not all sequences of OpenPGP packets are in valid OpenPGP