summaryrefslogtreecommitdiffstats
path: root/autocrypt
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2020-02-06 17:46:56 +0100
committerJustus Winter <justus@sequoia-pgp.org>2020-02-06 18:01:33 +0100
commit6953b4f9902f5f524e92a15518d0132fb662704a (patch)
treea02b4e656196a674146709ad42f32f4b295dcac3 /autocrypt
parent2bd001a81fa53e4f3cbc972f6354c8e3e510d1c7 (diff)
autocrypt: New crate.
- Move the autocrypt-related functionality to a new crate. - Fixes #424.
Diffstat (limited to 'autocrypt')
-rw-r--r--autocrypt/Cargo.toml25
-rw-r--r--autocrypt/README.md12
-rw-r--r--autocrypt/src/cert.rs83
-rw-r--r--autocrypt/src/lib.rs1111
-rw-r--r--autocrypt/src/serialize.rs25
-rw-r--r--autocrypt/tests/data/README.txt2
-rw-r--r--autocrypt/tests/data/setup-message.txt135
-rw-r--r--autocrypt/tests/data/testy-private.pgpbin0 -> 2540 bytes
-rw-r--r--autocrypt/tests/data/testy.pgpbin0 -> 1238 bytes
9 files changed, 1393 insertions, 0 deletions
diff --git a/autocrypt/Cargo.toml b/autocrypt/Cargo.toml
new file mode 100644
index 00000000..dc91fd2f
--- /dev/null
+++ b/autocrypt/Cargo.toml
@@ -0,0 +1,25 @@
+[package]
+name = "sequoia-autocrypt"
+description = "Autocrypt support"
+version = "0.13.0"
+authors = [
+ "Justus Winter <justus@sequoia-pgp.org>",
+ "Kai Michaelis <kai@sequoia-pgp.org>",
+ "Neal H. Walfield <neal@sequoia-pgp.org>",
+]
+documentation = "https://docs.sequoia-pgp.org/0.13.0/sequoia_autocrypt"
+homepage = "https://sequoia-pgp.org/"
+repository = "https://gitlab.com/sequoia-pgp/sequoia"
+readme = "README.md"
+keywords = ["autocrypt", "mua", "opportunistic", "mail", "encryption"]
+categories = ["cryptography", "authentication", "email"]
+license = "GPL-2.0-or-later"
+edition = "2018"
+
+[badges]
+gitlab = { repository = "sequoia-pgp/sequoia" }
+maintenance = { status = "actively-developed" }
+
+[dependencies]
+sequoia-openpgp = { path = "../openpgp", version = "0.13" }
+base64 = "0.10.1"
diff --git a/autocrypt/README.md b/autocrypt/README.md
new file mode 100644
index 00000000..3a502ea3
--- /dev/null
+++ b/autocrypt/README.md
@@ -0,0 +1,12 @@
+# sequoia-autocrypt
+
+This module deals with Autocrypt encoded data (see the [Autocrypt
+Spec]).
+
+ [Autocrypt Spec]: https://autocrypt.org/level1.html#openpgp-based-key-data
+
+# Scope
+
+This implements low-level functionality like encoding and decoding of
+Autocrypt headers and setup messages. Note: Autocrypt is more than
+just headers; it requires tight integration with the MUA.
diff --git a/autocrypt/src/cert.rs b/autocrypt/src/cert.rs
new file mode 100644
index 00000000..772a3f6e
--- /dev/null
+++ b/autocrypt/src/cert.rs
@@ -0,0 +1,83 @@
+use sequoia_openpgp as openpgp;
+use openpgp::packet;
+use openpgp::cert::{
+ CertBuilder,
+ CipherSuite,
+};
+use openpgp::types::{
+ KeyFlags,
+};
+
+use super::{
+ Autocrypt,
+};
+
+/// Generates a key compliant to
+/// [Autocrypt](https://autocrypt.org/).
+///
+/// If no version is given the latest one is used.
+///
+/// The autocrypt specification requires a UserID. However,
+/// because it can be useful to add the UserID later, it is
+/// permitted to be none.
+pub fn cert_builder<'a, V, U>(version: V, userid: Option<U>)
+ -> CertBuilder
+ where V: Into<Option<Autocrypt>>,
+ U: Into<packet::UserID>
+{
+ let builder = CertBuilder::new()
+ .set_cipher_suite(match version.into().unwrap_or_default() {
+ Autocrypt::V1 => CipherSuite::RSA3k,
+ Autocrypt::V1_1 => CipherSuite::Cv25519,
+ })
+ .set_primary_key_flags(
+ KeyFlags::default()
+ .set_certification(true)
+ .set_signing(true))
+ .add_subkey(
+ KeyFlags::default()
+ .set_transport_encryption(true)
+ .set_storage_encryption(true),
+ None);
+
+ if let Some(userid) = userid {
+ builder.add_userid(userid.into())
+ } else {
+ builder
+ }
+}
+
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use openpgp::types::PublicKeyAlgorithm;
+
+ #[test]
+ fn autocrypt_v1() {
+ let (cert1, _) = cert_builder(Autocrypt::V1, Some("Foo"))
+ .generate().unwrap();
+ assert_eq!(cert1.primary_key().pk_algo(),
+ PublicKeyAlgorithm::RSAEncryptSign);
+ assert_eq!(cert1.keys().subkeys().next().unwrap().key().pk_algo(),
+ PublicKeyAlgorithm::RSAEncryptSign);
+ assert_eq!(cert1.userids().count(), 1);
+ }
+
+ #[test]
+ fn autocrypt_v1_1() {
+ let (cert1, _) = cert_builder(Autocrypt::V1_1, Some("Foo"))
+ .generate().unwrap();
+ assert_eq!(cert1.primary_key().pk_algo(),
+ PublicKeyAlgorithm::EdDSA);
+ assert_eq!(cert1.keys().subkeys().next().unwrap().key().pk_algo(),
+ PublicKeyAlgorithm::ECDH);
+ match cert1.keys().subkeys().next().unwrap().key().mpis() {
+ openpgp::crypto::mpis::PublicKey::ECDH {
+ curve: openpgp::types::Curve::Cv25519, ..
+ } => (),
+ m => panic!("unexpected mpi: {:?}", m),
+ }
+ assert_eq!(cert1.userids().count(), 1);
+ }
+}
diff --git a/autocrypt/src/lib.rs b/autocrypt/src/lib.rs
new file mode 100644
index 00000000..95e6b297
--- /dev/null
+++ b/autocrypt/src/lib.rs
@@ -0,0 +1,1111 @@
+//! Autocrypt.
+//!
+//! This module deals with Autocrypt encoded data (see the [Autocrypt
+//! Spec]).
+//!
+//! [Autocrypt Spec]: https://autocrypt.org/level1.html#openpgp-based-key-data
+//!
+//! # Scope
+//!
+//! This implements low-level functionality like encoding and decoding
+//! of Autocrypt headers and setup messages. Note: Autocrypt is more
+//! than just headers; it requires tight integration with the MUA.
+
+use base64;
+
+use std::io;
+use std::io::prelude::*;
+use std::io::BufReader;
+use std::path::Path;
+use std::fs::File;
+use std::str;
+
+use sequoia_openpgp as openpgp;
+use openpgp::armor;
+use openpgp::Error;
+pub use openpgp::Result;
+use openpgp::Packet;
+use openpgp::packet::SKESK;
+use openpgp::Cert;
+use openpgp::cert::components::Amalgamation;
+use openpgp::parse::{
+ Parse,
+ PacketParserResult, PacketParser,
+};
+use openpgp::serialize::Serialize;
+use openpgp::serialize::stream::{
+ Message, LiteralWriter, Encryptor,
+};
+use openpgp::crypto::Password;
+use openpgp::policy::Policy;
+
+mod cert;
+pub use cert::cert_builder;
+mod serialize;
+
+/// Version of Autocrypt to use. `Autocrypt::default()` always returns the
+/// latest version.
+pub enum Autocrypt {
+ /// Autocrypt <= 1.0.1
+ V1,
+ /// Autocrypt version 1.1 (January 2019)
+ V1_1,
+}
+
+impl Default for Autocrypt {
+ fn default() -> Self { Autocrypt::V1_1 }
+}
+
+/// An autocrypt header attribute.
+#[derive(Debug, PartialEq)]
+pub struct Attribute {
+ /// Whether the attribute is critical.
+ pub critical: bool,
+ /// The attribute's name.
+ ///
+ /// If the attribute is not critical, the leading underscore has
+ /// been stripped.
+ pub key: String,
+ /// The attribute's value.
+ pub value: String,
+}
+
+/// Whether the data comes from an "Autocrypt" or "Autocrypt-Gossip"
+/// header.
+#[derive(Debug, PartialEq)]
+pub enum AutocryptHeaderType {
+ /// An "Autocrypt" header.
+ Sender,
+ /// An "Autocrypt-Gossip" header.
+ Gossip,
+}
+
+/// A parsed Autocrypt header.
+#[derive(Debug, PartialEq)]
+pub struct AutocryptHeader {
+ /// Whether this is an "Autocrypt" or "Autocrypt-Gossip" header.
+ pub header_type: AutocryptHeaderType,
+
+ /// The parsed key data.
+ pub key: Option<Cert>,
+
+ /// All attributes.
+ pub attributes: Vec<Attribute>,
+}
+
+impl AutocryptHeader {
+ fn empty(header_type: AutocryptHeaderType) -> Self {
+ AutocryptHeader {
+ header_type: header_type,
+ key: None,
+ attributes: Vec::new(),
+ }
+ }
+
+ /// Creates a new "Autocrypt" header.
+ pub fn new_sender<'a, P>(policy: &dyn Policy,
+ cert: &Cert, addr: &str, prefer_encrypt: P)
+ -> Result<Self>
+ where P: Into<Option<&'a str>>
+ {
+ // Minimize Cert.
+ let mut acc = Vec::new();
+
+ // The primary key and the most recent selfsig.
+ let primary = cert.primary_key().bundle();
+ acc.push(primary.key().clone().mark_role_primary().into());
+ primary.self_signatures().iter().take(1)
+ .for_each(|s| acc.push(s.clone().into()));
+
+ // The subkeys and the most recent selfsig.
+ for skb in cert.keys().subkeys() {
+ // Skip if revoked.
+ if ! skb.self_revocations().is_empty()
+ || ! skb.other_revocations().is_empty()
+ {
+ continue;
+ }
+
+ let k = skb.key().clone();
+ acc.push(k.into());
+ skb.self_signatures().iter().take(1)
+ .for_each(|s| acc.push(s.clone().into()));
+ }
+
+ // The UserIDs matching ADDR.
+ for uidb in cert.userids().with_policy(policy, None) {
+ // XXX: Fix match once we have the rfc2822-name-addr.
+ if let Ok(Some(a)) = uidb.userid().email() {
+ if &a == addr {
+ acc.push(uidb.userid().clone().into());
+ acc.push(uidb.binding_signature().clone().into());
+ } else {
+ // Address is not matching.
+ continue;
+ }
+ } else {
+ // Malformed UserID.
+ continue;
+ }
+ }
+
+ let cleaned_cert = Cert::from_packet_pile(acc.into())?;
+
+ Ok(AutocryptHeader {
+ header_type: AutocryptHeaderType::Sender,
+ key: Some(cleaned_cert),
+ attributes: vec![
+ Attribute {
+ critical: true,
+ key: "addr".into(),
+ value: addr.into(),
+ },
+ Attribute {
+ critical: true,
+ key: "prefer-encrypt".into(),
+ value: prefer_encrypt.into()
+ .unwrap_or("nopreference").into(),
+ },
+ ],
+ })
+ }
+
+ /// Looks up an attribute.
+ pub fn get(&self, key: &str) -> Option<&Attribute> {
+ for a in &self.attributes {
+ if a.key == key {
+ return Some(a);
+ }
+ }
+
+ None
+ }
+}
+
+/// A set of parsed Autocrypt headers.
+#[derive(Debug, PartialEq)]
+pub struct AutocryptHeaders {
+ /// The value in the from header.
+ pub from: Option<String>,
+
+ /// Any autocrypt headers.
+ pub headers: Vec<AutocryptHeader>,
+}
+
+impl AutocryptHeaders {
+ fn empty() -> Self {
+ AutocryptHeaders {
+ from: None,
+ headers: Vec::new(),
+ }
+ }
+
+ fn from_lines<I: Iterator<Item = io::Result<String>>>(mut lines: I)
+ -> Result<Self>
+ {
+ let mut headers = AutocryptHeaders::empty();
+
+ let mut next_line = lines.next();
+ while let Some(line) = next_line {
+ // Return any error.
+ let mut line = line?;
+
+ if line == "" {
+ // End of headers.
+ break;
+ }
+
+ next_line = lines.next();
+
+ // If the line is folded (a line break was inserted in
+ // front of whitespace (either a space (0x20) or a
+ // horizontal tab (0x09)), then unfold it.
+ //
+ // See https://tools.ietf.org/html/rfc5322#section-2.2.3
+ while let Some(Ok(nl)) = next_line {
+ if nl.len() > 0 && (&nl[0..1] == " " || &nl[0..1] == "\t") {
+ line.push_str(&nl[..]);
+ next_line = lines.next();
+ } else {
+ // Put it back.
+ next_line = Some(Ok(nl));
+ break;
+ }
+ }
+
+ const AUTOCRYPT : &str = "Autocrypt: ";
+ const FROM : &str = "From: ";
+
+ if line.starts_with(FROM) {
+ headers.from
+ = Some(line[FROM.len()..].trim_matches(' ').into());
+ } else if line.starts_with(AUTOCRYPT) {
+ let ac_value = &line[AUTOCRYPT.len()..];
+
+ let mut header = AutocryptHeader::empty(
+ AutocryptHeaderType::Sender);
+
+ for pair in ac_value.split(';') {
+ let pair = pair
+ .splitn(2, |c| c == '=')
+ .collect::<Vec<&str>>();
+
+ let (key, value) : (String, String) = if pair.len() == 1 {
+ // No value...
+ (pair[0].trim_matches(' ').into(), "".into())
+ } else {
+ (pair[0].trim_matches(' ').into(),
+ pair[1].trim_matches(' ').into())
+ };
+
+ if key == "keydata" {
+ if let Ok(decoded) = base64::decode(
+ &value.replace(" ", "")[..]) {
+ if let Ok(cert) = Cert::from_bytes(&decoded[..]) {
+ header.key = Some(cert);
+ }
+ }
+ }
+
+ let critical = key.len() >= 1 && &key[0..1] == "_";
+ header.attributes.push(Attribute {
+ critical: critical,
+ key: if critical {
+ key[1..].to_string()
+ } else {
+ key
+ },
+ value: value,
+ });
+ }
+
+ headers.headers.push(header);
+ }
+ }
+
+ return Ok(headers)
+ }
+
+ /// Parses an autocrypt header.
+ ///
+ /// `data` should be all of a mail's headers.
+ pub fn from_bytes(data: &[u8]) -> Result<Self> {
+ let lines = BufReader::new(io::Cursor::new(data)).lines();
+ Self::from_lines(lines)
+ }
+
+ /// Parses an autocrypt header.
+ ///
+ /// `path` should name a file containing a single mail. If the
+ /// file is in mbox format, then only the first mail is
+ /// considered.
+ pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
+ Self::from_reader(File::open(path)?)
+ }
+
+ /// Parses an autocrypt header.
+ ///
+ /// `reader` contain a single mail. If it contains multiple
+ /// emails, then only the first mail is considered.
+ pub fn from_reader<R: io::Read>(reader: R) -> Result<Self> {
+ Self::from_lines(BufReader::new(reader).lines())
+ }
+}
+
+/// Holds an Autocrypt Setup Message.
+///
+/// An [Autocrypt Setup Message] is used to transfer a private key from
+/// one device to another.
+///
+/// [Autocrypt Setup Message]:
+/// https://autocrypt.org/level1.html#autocrypt-setup-message
+#[derive(Debug, PartialEq)]
+pub struct AutocryptSetupMessage {
+ prefer_encrypt: Option<String>,
+ passcode_format: Option<String>,
+ passcode: Option<Password>,
+ // We only emit a "Passcode-Begin" header if this is set. Note:
+ // we don't check if this actually matches the start of the
+ // passcode.
+ passcode_begin: Option<String>,
+
+ cert: Cert,
+}
+
+impl AutocryptSetupMessage {
+ /// Creates a new Autocrypt Setup Message for the specified `Cert`.
+ ///
+ /// You can set the `prefer_encrypt` setting, which defaults to
+ /// "nopreference", using `set_prefer_encrypt`.
+ ///
+ /// Note: this generates a random passcode. To retrieve the
+ /// passcode, use the `passcode` method.
+ ///
+ /// To decode an Autocrypt Setup Message, use the `from_bytes` or
+ /// `from_reader` methods.
+ pub fn new(cert: Cert) -> Self {
+ AutocryptSetupMessage {
+ prefer_encrypt: None,
+ passcode: None,
+ passcode_format: None,
+ passcode_begin: None,
+ cert: cert,
+ }
+ }
+
+ /// Sets the prefer encrypt header.
+ pub fn set_prefer_encrypt(mut self, value: &str) -> Self {
+ self.prefer_encrypt = Some(value.into());
+ self
+ }
+
+ /// Returns the prefer encrypt header.
+ pub fn prefer_encrypt(&self) -> Option<&str> {
+ self.prefer_encrypt.as_ref().map(|v| &v[..])
+ }
+
+
+ /// Sets the "Passcode-Format" header.
+ pub fn set_passcode_format(mut self, value: &str) -> Self {
+ self.passcode_format = Some(value.into());
+ self
+ }
+
+ /// Returns the "Passcode-Format" header.
+ pub fn passcode_format(&self) -> Option<&str> {
+ self.passcode_format.as_ref().map(|v| &v[..])
+ }
+
+
+ /// Sets the passcode.
+ pub fn set_passcode(mut self, passcode: Password) -> Self {
+ self.passcode = Some(passcode);
+ self
+ }
+
+ /// Returns the ASM's passcode.
+ ///
+ /// If the passcode has not yet been generated, this returns
+ /// `None`.
+ pub fn passcode(&self) -> Option<&Password> {
+ self.passcode.as_ref()
+ }
+
+
+ /// Sets the "Passcode-Begin" header.
+ pub fn set_passcode_begin(mut self, value: &str) -> Self {
+ self.passcode_begin = Some(value.into());
+ self
+ }
+
+ /// Returns the "Passcode-Begin" header.
+ pub fn passcode_begin(&self) -> Option<&str> {
+ self.passcode_begin.as_ref().map(|v| &v[..])
+ }
+
+
+ // Generates a new passcode in "numeric9x4" format.
+ fn passcode_gen() -> Password {
+ use openpgp::crypto::mem;
+ // Generate a random passcode.
+
+ // The passcode consists of 36 digits, which encode
+ // approximately 119 bits of information. 120 bits = 15
+ // bytes.
+ let mut p_as_vec = mem::Protected::from(vec![0; 15]);
+ openpgp::crypto::random(&mut p_as_vec[..]);
+
+ // Turn it into a 128-bit number.
+ let mut p_as_u128 = 0u128;
+ for v in p_as_vec.iter() {
+ p_as_u128 = (p_as_u128 << 8) + *v as u128;
+ }
+
+ // Turn it into ASCII.
+ let mut p : Vec<u8> = Vec::new();
+ for i in 0..36 {
+ if i > 0 && i % 4 == 0 {
+ p.push('-' as u8);
+ }
+
+ p.push(('0' as u8) + ((p_as_u128 as u8) % 10));
+ p_as_u128 = p_as_u128 / 10;
+ }
+
+ p.into()
+ }
+
+ /// If there is no passcode, generates one.
+ fn passcode_ensure(&mut self) {
+ if self.passcode.is_some() {
+ return;
+ }
+
+ let passcode = Self::passcode_gen();
+ self.passcode_format = Some("numeric9x4".into());
+ self.passcode_begin = passcode.map(|p| {
+ Some(str::from_utf8(&p[..2]).unwrap().into())
+ });
+ self.passcode = Some(passcode);
+ }
+
+ /// Generates the Autocrypt Setup Message.
+ ///
+ /// The message is written to `w`.
+ pub fn serialize<W: io::Write>(&mut self, w: &mut W) -> Result<()> {
+ // The outer message is an ASCII-armored encoded message
+ // containing a single SK-ESK and a single SEIP packet. The
+ // SEIP packet contains a literal data packet whose content is
+ // the inner message.
+
+ self.passcode_ensure();
+
+ let mut headers : Vec<(&str, &str)> = Vec::new();
+ if let Some(ref format) = self.passcode_format {
+ headers.push(
+ (&"Passphrase-Format"[..], &format[..]));
+ }
+ if let Some(ref begin) = self.passcode_begin {
+ headers.push(
+ (&"Passphrase-Begin"[..], &begin[..]));
+ }
+
+ let mut armor_writer =
+ armor::Writer::new(w, armor::Kind::Message, &headers[..])?;
+
+ {
+ // Passphrase-Format header with value numeric9x4
+ let m = Message::new(&mut armor_writer);
+ let w = Encryptor::with_password(m, self.passcode.clone().unwrap())
+ .build()?;
+
+ let mut w = LiteralWriter::new(w).build()?;
+
+ // The inner message is an ASCII-armored encoded Cert.
+ let mut w = armor::Writer::new(
+ &mut w, armor::Kind::SecretKey,
+ &[ (&"Autocrypt-Prefer-Encrypt"[..],
+ self.prefer_encrypt().unwrap_or(&"nopreference"[..])) ])?;
+
+ self.cert.as_tsk().serialize(&mut w)?;
+ w.finalize()?;
+ }
+ armor_writer.finalize()?;
+ Ok(())
+ }
+
+
+ /// Parses the autocrypt setup message in `r`.
+ ///
+ /// `passcode` is the passcode used to protect the message.
+ pub fn from_bytes<'a>(bytes: &'a [u8])
+ -> Result<AutocryptSetupMessageParser<'a>>
+ {
+ Self::from_reader(bytes)
+ }
+
+ /// Parses the autocrypt setup message in `r`.
+ ///
+ /// `passcode` is the passcode used to protect the message.
+ pub fn from_reader<'a, R: io::Read + 'a>(r: R)
+ -> Result<AutocryptSetupMessageParser<'a>> {
+ // The outer message uses ASCII-armor. It includes a password
+ // hint. Hence, we need to parse it aggressively.
+ let mut r = armor::Reader::new(
+ r, armor::ReaderMode::Tolerant(Some(armor::Kind::Message)));
+
+ // Note, it is essential that we call r.headers here so that
+ // we can return any error now and not in
+ // AutocryptSetupMessageParser::header.
+ let (format, begin) = {
+ let headers = r.headers()?;
+
+ let format = headers.iter()
+ .filter_map(|(k, v)| {
+ if k == "Passphrase-Format" { Some(v) } else { None }
+ })
+ .collect::<Vec<&String>>();
+ let format = if format.len() > 0 {
+ // If there are multiple headers, then just silently take
+ // the first one.
+ Some(format[0].clone())
+ } else {
+ None
+ };
+
+ let begin = headers.iter()
+ .filter_map(|(k, v)| {
+ if k == "Passphrase-Begin" { Some(v) } else { None }
+ })
+ .collect::<Vec<&String>>();
+ let begin = if begin.len() > 0 {
+ // If there are multiple headers, then just silently take
+ // the first one.
+ Some(begin[0].clone())
+ } else {
+ None
+ };
+
+ (format, begin)
+ };
+
+ // Get the first packet, which is the SK-ESK packet.
+
+ let mut ppr = PacketParser::from_reader(r)?;
+
+ // The outer message consists of exactly three packets: a
+ // SK-ESK and a SEIP packet, which contains a Literal data
+ // packet.
+
+ let pp = if let PacketParserResult::Some(pp) = ppr {
+ pp
+ } else {
+ return Err(
+ Error::MalformedMessage(
+ "Premature EOF: expected an SK-ESK, encountered EOF".into())
+ .into());
+ };
+
+ let (packet, ppr_) = pp.next()?;
+ ppr = ppr_;
+
+ let skesk = match packet {
+ Packet::SKESK(skesk) => skesk,
+ p => return Err(
+ Error::MalformedMessage(
+ format!("Expected a SKESK packet, found a {}", p.tag())
+ .into())
+ .into()),
+ };
+
+ let pp = match ppr {
+ PacketParserResult::EOF(_) =>
+ return Err(
+ Error::MalformedMessage(
+ "Pre-mature EOF after reading SK-ESK packet".into())
+ .into()),
+ PacketParserResult::Some(pp) => {
+ match pp.packet {
+ Packet::SEIP(_) => (),
+ ref p => return Err(
+ Error::MalformedMessage(
+ format!("Expected a SEIP packet, found a {}",
+ p.tag())
+ .into())
+ .into()),
+ }
+
+ pp
+ }
+ };
+
+ Ok(AutocryptSetupMessageParser {
+ passcode_format: format,
+ passcode_begin: begin,
+ skesk: skesk,
+ pp: pp,
+ passcode: None,
+ })
+ }
+
+ /// Returns the Cert consuming the `AutocryptSetupMessage` in the
+ /// process.
+ pub fn into_cert(self) -> Cert {
+ self.cert
+ }
+}
+
+/// A Parser for an `AutocryptSetupMessage`.
+pub struct AutocryptSetupMessageParser<'a> {
+ passcode_format: Option<String>,
+ passcode_begin: Option<String>,
+ skesk: SKESK,
+ pp: PacketParser<'a>,
+ passcode: Option<Password>,
+}
+
+impl<'a> AutocryptSetupMessageParser<'a> {
+ /// Returns the "Passcode-Format" header.
+ pub fn passcode_format(&self) -> Option<&str> {
+ self.passcode_format.as_ref().map(|v| &v[..])
+ }
+
+ /// Returns the "Passcode-Begin" header.
+ pub fn passcode_begin(&self) -> Option<&str> {
+ self.passcode_begin.as_ref().map(|v| &v[..])
+ }
+
+ /// Tries to decrypt the message.
+ ///
+ /// On success, follow up with
+ /// `AutocryptSetupMessageParser::parse()` to extract the
+ /// `AutocryptSetupMessage`.
+ pub fn decrypt(&mut self, passcode: &Password) -> Result<()> {
+ if self.pp.decrypted() {
+ return Err(
+ Error::InvalidOperation("Already decrypted".into()).into());
+ }
+
+ let (algo, key) = self.skesk.decrypt(passcode)?;
+ self.pp.decrypt(algo, &key)?;
+
+ self.passcode = Some(passcode.clone());
+
+ Ok(())
+ }
+
+ /// Finishes parsing the `AutocryptSetupMessage`.
+ ///
+ /// Before calling this, you must decrypt the payload using
+ /// `decrypt`.
+ ///
+ /// If the payload has not been decrypted, returns
+ /// `Error::InvalidOperation`.
+ ///
+ /// If the payload is malformed, returns
+ /// `Error::MalformedMessage`.
+ pub fn parse(self) -> Result<AutocryptSetupMessage> {
+ if !self.pp.decrypted() {
+ return Err(
+ Error::Inval