From 22fe5f5fa08b7d98a05a9394320d7984083cd62f Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Thu, 18 Oct 2018 16:40:56 +0200 Subject: guide: New crate. - Our previous guide published on our web site quickly bitrotted away. This guide, however, is tested as part of Sequoia's test suite, making sure that it stays in sync. --- guide/Cargo.toml | 13 ++ guide/build.rs | 70 ++++++ guide/src/chapter_00.md | 95 ++++++++ guide/src/chapter_01.md | 569 ++++++++++++++++++++++++++++++++++++++++++++++++ guide/src/chapter_02.md | 218 +++++++++++++++++++ guide/src/lib.rs | 22 ++ 6 files changed, 987 insertions(+) create mode 100644 guide/Cargo.toml create mode 100644 guide/build.rs create mode 100644 guide/src/chapter_00.md create mode 100644 guide/src/chapter_01.md create mode 100644 guide/src/chapter_02.md create mode 100644 guide/src/lib.rs (limited to 'guide') 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 ", + "Kai Michaelis ", + "Neal H. Walfield ", +] +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 { +# 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> { +# // Return public keys for signature verification here. +# Ok(Vec::new()) +# } +# +# fn check(&mut self, _sigs: Vec>) +# -> 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> +# { +# // 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 { + 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> { +# // Return public keys for signature verification here. +# Ok(Vec::new()) +# } +# +# fn check(&mut self, _sigs: Vec>) +# -> 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> +# { +# // 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 { +# 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> { +# // Return public keys for signature verification here. +# Ok(Vec::new()) +# } +# +# fn check(&mut self, _sigs: Vec>) +# -> 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> +# { +# // 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 { +# 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> { + // Return public keys for signature verification here. + Ok(Vec::new()) + } + + fn check(&mut self, _sigs: Vec>) + -> 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> + { + // 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 +[`Message`]s, only those accepted by [this grammar] are. Sequoia +contains a parser that parses packets and verifies the message +structure using this grammar: + +[this grammar]: https://tools.ietf.org/html/rfc4880#section-11.3 +[`Message`]: ../../sequoia_openpgp/struct.Message.html + +```rust +extern crate sequoia_openpgp as openpgp; + +const MESSAGE: &str = + "-----BEGIN PGP MESSAGE----- + + xA0DAAoW5saJekzviSQByxBiAAAAAADYtdiv2KfZgtipwnUEABYKACcFglwJHYoW + IQRnpIdTo4Cms7fffcXmxol6TO+JJAkQ5saJekzviSQAAIJ6APwK6FxtHXn8txDl + tBFsIXlOSLOs4BvArlZzZSMomIyFLAEAwCLJUChMICDxWXRlHxORqU5x6hlO3DdW + sl/1DAbnRgI= + =AqoO + -----END PGP MESSAGE-----"; + +fn main() { + let message = openpgp::Message::from_bytes(MESSAGE.as_bytes()).unwrap(); + + assert_eq!(message.body().and_then(|literal| literal.body()), + Some("صداقة".as_bytes())); +} +``` + +# Parsing packets into packet piles + +[`PacketPile`]s are unstructured sequences of OpenPGP packets. Packet +piles can be inspected, manipulated, validated using a formal grammar +and thereby turned into [`Message`]s or [`TPK`]s using +[`Message::from_packet_pile`] or [`TPK::from_packet_pile`], or just +turned into a vector of [`Packet`]s: + +[`PacketPile`]: ../../sequoia_openpgp/struct.PacketPile.html +[`Packet`]: ../../sequoia_openpgp/enum.Packet.html +[`TPK::from_packet_pile`]: ../../sequoia_openpgp/struct.TPK.html#method.from_packet_pile +[`Message::from_packet_pile`]: ../../sequoia_openpgp/struct.Message.html#method.from_packet_pile + +```rust +extern crate sequoia_openpgp as openpgp; + +const MESSAGE: &str = + "-----BEGIN PGP MESSAGE----- + + xA0DAAoW5saJekzviSQByxBiAAAAAADYtdiv2KfZgtipwnUEABYKACcFglwJHYoW + IQRnpIdTo4Cms7fffcXmxol6TO+JJAkQ5saJekzviSQAAIJ6APwK6FxtHXn8txDl + tBFsIXlOSLOs4BvArlZzZSMomIyFLAEAwCLJUChMICDxWXRlHxORqU5x6hlO3DdW + sl/1DAbnRgI= + =AqoO + -----END PGP MESSAGE-----"; + +fn main() { + let pile = openpgp::PacketPile::from_bytes(MESSAGE.as_bytes()).unwrap(); + + // For simplicity, turn the pile into a vector of packets. + let packets: Vec = pile.into_children().collect(); + + // There are three packets in that message. + assert_eq!(packets.len(), 3); + + // First, we expect an one pass signature packet. + if let openpgp::Packet::OnePassSig(ref ops) = packets[0] { + assert_eq!(ops.issuer().to_string(), "E6C6 897A 4CEF 8924"); + } else { + panic!("expected one pass signature packet"); + } + + // The second packet is the literal data packet. + if let openpgp::Packet::Literal(ref literal) = packets[1] { + assert_eq!(literal.body(), Some("صداقة".as_bytes())); + } else { + panic!("expected literal data packet"); + } + + // Finally, we expect the signature itself. + if let openpgp::Packet::Signature(ref signature) = packets[2] { + assert_eq!(signature.issuer_fingerprint().unwrap().to_string(), + "67A4 8753 A380 A6B3 B7DF 7DC5 E6C6 897A 4CEF 8924"); + } else { + panic!("expected signature packet"); + } +} +``` + +# Streaming packet parsing + +Both the [`Message`]parser and the [`PacketPile`]parser build a tree +structure in memory, and more importantly, they buffer the bodies of +literal data packets. Both properties can be undesirable if a large +number of packets is parsed, or the data contained in the message +large. This problem is exacerbated by the fact that OpenPGP messages +can be compressed, so that processing even small messages can lead to +an unbounded amount of memory being allocated. + +To alleviate this problem, Sequoia features streaming interfaces that +implement [`io::Read`] and [`io::Write`]. These interfaces allow +processing of OpenPGP packets in constant space. + +[`io::Read`]: https://doc.rust-lang.org/std/io/trait.Read.html +[`io::Write`]: https://doc.rust-lang.org/std/io/trait.Write.html + +The core of Sequoia is our [`PacketParser`], upon which all higher +level interfaces are built. It is the most flexible interface for +processing OpenPGP packets, and it is the foundation for our streaming +interfaces. Most of the time, it is not necessary to use this +interface, but nevertheless, our parser is exposed as part of our API +and can be used to quickly process large amounts of OpenPGP packets, +e.g. for collecting statistics about the SKS keyserver dump. For a +complete example, see [`openpgp/examples/statistics.rs`]. + +[`PacketParser`]: ../../sequoia_openpgp/parse/struct.PacketParser.html +[`openpgp/examples/statistics.rs`]: https://gitlab.com/sequoia-pgp/sequoia/blob/master/openpgp/examples/statistics.rs + +```rust +use std::io::Read; + +extern crate sequoia_openpgp as openpgp; +use openpgp::parse::*; + +const MESSAGE: &str = + "-----BEGIN PGP MESSAGE----- + + yMACA0JaaDYxQVkmU1nHKJOZA6l4wQTAABAAAAgACCAAUGaaCalNNxCUkepFQEtY + hKSO3zFBWSZTWTYaxwsA6l5AAMAAAAggADCATUZBKSNSCUkcxQVkmU1k2GscLAOp + eQADAAAAIIAAwgE1GQSkjUglJHMUFZJlNZNhrHCwDqXkAAwAAACCAAMIBNRkEpI1 + IJSRzFBWSZTWUmfJVgAWotAANkAAAggAFBmgClRjNkhJTMqEqoN9JCSnC7kinChI + H89bU4A= + =eySo + -----END PGP MESSAGE-----"; + +fn main() { + let mut bytes_read = 0; + let mut buf = vec![0; 1024 * 1024]; + + let mut ppr = PacketParser::from_bytes(MESSAGE.as_bytes()).unwrap(); + while let PacketParserResult::Some(mut pp) = ppr { + // Match on the kind of packet here while it is in the parser. + if let openpgp::Packet::Literal(_) = pp.packet { + // Stream the content of the literal packet. + while let Ok(_) = pp.read_exact(&mut buf) { + bytes_read += buf.len(); + } + } + + // Start parsing the next packet. + ppr = pp.recurse().unwrap().1; + } + + assert_eq!(bytes_read, 128 * 1024 * 1024); // 128 megabytes +} +``` diff --git a/guide/src/lib.rs b/guide/src/lib.rs new file mode 100644 index 00000000..e1e33403 --- /dev/null +++ b/guide/src/lib.rs @@ -0,0 +1,22 @@ +#![doc=" +Climbing Sequoias. + +This is our guided tour through the Sequoia OpenPGP library. + +Please note that this guide as well as Sequoia is work in progress. +But, fear not! This guide is part of the API documentation, and the +code fragments are tested as part of Sequoias test suite. This makes +sure that this guide is always up-to-date. + +For more inspiration, see our various [examples] and our +command-line frontends [sq] and [sqv]. + +[examples]: https://gitlab.com/sequoia-pgp/sequoia/tree/master/openpgp/examples +[sq]: https://gitlab.com/sequoia-pgp/sequoia/tree/master/tool +[sqv]: https://gitlab.com/sequoia-pgp/sequoia/tree/master/sqv + +Happy climbing! + +"] + +include!(concat!(env!("OUT_DIR"), "/src/lib.rs")); -- cgit v1.2.3