diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2021-04-28 10:33:59 +0200 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2021-04-28 11:39:47 +0200 |
commit | d1202ff498b234dd4da0c1ff76769e68d53bf8b4 (patch) | |
tree | 565a8523d3cdd82a7e0b4290eb2d24619cf5de35 /openpgp/src/serialize/stream.rs | |
parent | 4bc90bb02f64b59201c13567af9ce464c8243679 (diff) |
openpgp: Implement Encryptor::with_session_key.
- Fixes #390.
Diffstat (limited to 'openpgp/src/serialize/stream.rs')
-rw-r--r-- | openpgp/src/serialize/stream.rs | 191 |
1 files changed, 186 insertions, 5 deletions
diff --git a/openpgp/src/serialize/stream.rs b/openpgp/src/serialize/stream.rs index 7b713107..6ebf1925 100644 --- a/openpgp/src/serialize/stream.rs +++ b/openpgp/src/serialize/stream.rs @@ -2297,6 +2297,7 @@ impl<'a> Recipient<'a> { /// ``` pub struct Encryptor<'a> { inner: writer::BoxStack<'a, Cookie>, + session_key: Option<SessionKey>, recipients: Vec<Recipient<'a>>, passwords: Vec<Password>, sym_algo: SymmetricAlgorithm, @@ -2366,8 +2367,9 @@ impl<'a> Encryptor<'a> { where R: IntoIterator, R::Item: Into<Recipient<'a>>, { - Self { + Self { inner: inner.into(), + session_key: None, recipients: recipients.into_iter().map(|r| r.into()).collect(), passwords: Vec::new(), sym_algo: Default::default(), @@ -2409,6 +2411,7 @@ impl<'a> Encryptor<'a> { { Self { inner: inner.into(), + session_key: None, recipients: Vec::new(), passwords: passwords.into_iter().map(|p| p.into()).collect(), sym_algo: Default::default(), @@ -2418,6 +2421,174 @@ impl<'a> Encryptor<'a> { } } + /// Creates a new encryptor for the given algorithm and session + /// key. + /// + /// Usually, the encryptor creates a session key and decrypts it + /// for the given recipients and passwords. Using this function, + /// the session key can be supplied instead. There are two main + /// use cases for this: + /// + /// - Replying to an encrypted message usually requires the + /// encryption (sub)keys for every recipient. If even one key + /// is not available, it is not possible to encrypt the new + /// session key. Rather than falling back to replying + /// unencrypted, one can reuse the original message's session + /// key that was encrypted for every recipient and reuse the + /// original [`PKESK`](crate::packet::PKESK)s. + /// + /// - Using the encryptor if the session key is transmitted or + /// derived using a scheme not supported by Sequoia. + /// + /// To add more passwords, use [`Encryptor::add_passwords`]. To + /// add recipients, use [`Encryptor::add_recipients`]. + /// + /// # Examples + /// + /// This example demonstrates how to fall back to the original + /// message's session key in order to encrypt a reply. + /// + /// ``` + /// # fn main() -> sequoia_openpgp::Result<()> { + /// # use std::io::{self, Write}; + /// # use sequoia_openpgp as openpgp; + /// # use openpgp::{KeyHandle, KeyID, Fingerprint, Result}; + /// # use openpgp::cert::prelude::*; + /// # use openpgp::packet::prelude::*; + /// # use openpgp::crypto::{KeyPair, SessionKey}; + /// # use openpgp::types::SymmetricAlgorithm; + /// # use openpgp::parse::{Parse, stream::*}; + /// # use openpgp::serialize::{Serialize, stream::*}; + /// # use openpgp::policy::{Policy, StandardPolicy}; + /// # let p = &StandardPolicy::new(); + /// # + /// // Generate two keys. + /// let (alice, _) = CertBuilder::general_purpose( + /// None, Some("Alice Lovelace <alice@example.org>")).generate()?; + /// let (bob, _) = CertBuilder::general_purpose( + /// None, Some("Bob Babbage <bob@example.org>")).generate()?; + /// + /// // Encrypt a message for both keys. + /// let recipients = vec![&alice, &bob].into_iter().flat_map(|cert| { + /// cert.keys().with_policy(p, None).supported().alive().revoked(false) + /// .for_transport_encryption() + /// }); + /// + /// let mut original = vec![]; + /// let message = Message::new(&mut original); + /// let message = Encryptor::for_recipients(message, recipients).build()?; + /// let mut w = LiteralWriter::new(message).build()?; + /// w.write_all(b"Original message")?; + /// w.finalize()?; + /// + /// // Decrypt original message using Alice's key. + /// let mut decryptor = DecryptorBuilder::from_bytes(&original)? + /// .with_policy(p, None, Helper::new(alice))?; + /// io::copy(&mut decryptor, &mut io::sink())?; + /// let (algo, sk, pkesks) = decryptor.into_helper().recycling_bin.unwrap(); + /// + /// // Compose the reply using the same session key. + /// let mut reply = vec![]; + /// let mut message = Message::new(&mut reply); + /// for p in pkesks { // Emit the stashed PKESK packets. + /// Packet::from(p).serialize(&mut message)?; + /// } + /// let message = Encryptor::with_session_key(message, algo, sk)?.build()?; + /// let mut w = LiteralWriter::new(message).build()?; + /// w.write_all(b"Encrypted reply")?; + /// w.finalize()?; + /// + /// // Check that Bob can decrypt it. + /// let mut decryptor = DecryptorBuilder::from_bytes(&reply)? + /// .with_policy(p, None, Helper::new(bob))?; + /// io::copy(&mut decryptor, &mut io::sink())?; + /// + /// /// Decrypts the message preserving algo, session key, and PKESKs. + /// struct Helper { + /// key: Cert, + /// recycling_bin: Option<(SymmetricAlgorithm, SessionKey, Vec<PKESK>)>, + /// } + /// + /// # impl Helper { + /// # fn new(key: Cert) -> Self { + /// # Helper { key, recycling_bin: None, } + /// # } + /// # } + /// # + /// impl DecryptionHelper for Helper { + /// fn decrypt<D>(&mut self, pkesks: &[PKESK], _skesks: &[SKESK], + /// sym_algo: Option<SymmetricAlgorithm>, mut decrypt: D) + /// -> Result<Option<Fingerprint>> + /// where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool + /// { + /// let p = &StandardPolicy::new(); + /// let mut encryption_context = None; + /// + /// for pkesk in pkesks { // Try each PKESK until we succeed. + /// for ka in self.key.keys().with_policy(p, None) + /// .supported().unencrypted_secret() + /// .key_handle(pkesk.recipient()) + /// .for_storage_encryption().for_transport_encryption() + /// { + /// let mut pair = ka.key().clone().into_keypair().unwrap(); + /// if pkesk.decrypt(&mut pair, sym_algo) + /// .map(|(algo, session_key)| { + /// let success = decrypt(algo, &session_key); + /// if success { + /// // Copy algor, session key, and PKESKs. + /// encryption_context = + /// Some((algo, session_key.clone(), + /// pkesks.iter().cloned().collect())); + /// } + /// success + /// }) + /// .unwrap_or(false) + /// { + /// break; // Decryption successful. + /// } + /// } + /// } + /// + /// self.recycling_bin = encryption_context; // Store for the reply. + /// Ok(Some(self.key.fingerprint())) + /// } + /// } + /// + /// impl VerificationHelper for Helper { + /// // ... + /// # fn get_certs(&mut self, _ids: &[KeyHandle]) -> Result<Vec<Cert>> { + /// # Ok(Vec::new()) // Lookup certificates here. + /// # } + /// # fn check(&mut self, structure: MessageStructure) -> Result<()> { + /// # Ok(()) // Implement your verification policy here. + /// # } + /// } + /// # Ok(()) } + /// ``` + pub fn with_session_key(inner: Message<'a>, + sym_algo: SymmetricAlgorithm, + session_key: SessionKey) + -> Result<Self> + { + let sym_key_size = sym_algo.key_size()?; + if session_key.len() != sym_key_size { + return Err(Error::InvalidArgument( + format!("{} requires a {} bit key, but session key has {}", + sym_algo, sym_key_size, session_key.len())).into()); + } + + Ok(Self { + inner: inner.into(), + session_key: Some(session_key), + recipients: Vec::new(), + passwords: Vec::with_capacity(0), + sym_algo, + aead_algo: Default::default(), + hash: HashAlgorithm::SHA1.context().unwrap(), + cookie: Default::default(), // Will be fixed in build. + }) + } + /// Adds recipients. /// /// The resulting message can be encrypted by any recipient and @@ -2691,9 +2862,12 @@ impl<'a> Encryptor<'a> { /// # Ok(()) } /// ``` pub fn build(mut self) -> Result<Message<'a>> { - if self.recipients.len() + self.passwords.len() == 0 { + if self.recipients.len() + self.passwords.len() == 0 + && self.session_key.is_none() + { return Err(Error::InvalidOperation( - "Neither recipients nor passwords given".into()).into()); + "Neither recipients, passwords, nor session key given".into() + ).into()); } struct AEADParameters { @@ -2717,8 +2891,15 @@ impl<'a> Encryptor<'a> { let mut inner = self.inner; let level = inner.as_ref().cookie_ref().level + 1; - // Generate a session key. - let sk = SessionKey::new(self.sym_algo.key_size()?); + // Reuse existing session key or generate a new one. + let sym_key_size = self.sym_algo.key_size()?; + let sk = self.session_key.take() + .unwrap_or_else(|| SessionKey::new(sym_key_size)); + if sk.len() != sym_key_size { + return Err(Error::InvalidOperation( + format!("{} requires a {} bit key, but session key has {}", + self.sym_algo, sym_key_size, sk.len())).into()); + } // Write the PKESK packet(s). for recipient in self.recipients.iter() { |