diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2023-04-25 18:35:48 +0200 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2024-03-13 10:59:50 +0100 |
commit | 802c5c18feea6c79557b386127f06d1a01e286c8 (patch) | |
tree | 8af5b38fdbf379a8a84d729a164cd5f780fdb875 | |
parent | 6797f544f742929635c79241b14a626ced43d86a (diff) |
wip pkeskv6
-rw-r--r-- | guide/src/chapter_02.md | 8 | ||||
-rw-r--r-- | ipc/examples/gpg-agent-decrypt.rs | 6 | ||||
-rw-r--r-- | ipc/tests/gpg-agent.rs | 7 | ||||
-rw-r--r-- | openpgp/benches/common/decrypt.rs | 7 | ||||
-rw-r--r-- | openpgp/examples/decrypt-with.rs | 4 | ||||
-rw-r--r-- | openpgp/examples/generate-encrypt-decrypt.rs | 2 | ||||
-rw-r--r-- | openpgp/examples/reply-encrypted.rs | 10 | ||||
-rw-r--r-- | openpgp/src/keyhandle.rs | 10 | ||||
-rw-r--r-- | openpgp/src/packet/mod.rs | 84 | ||||
-rw-r--r-- | openpgp/src/packet/pkesk.rs | 225 | ||||
-rw-r--r-- | openpgp/src/packet/pkesk/v6.rs | 183 | ||||
-rw-r--r-- | openpgp/src/packet/prelude.rs | 5 | ||||
-rw-r--r-- | openpgp/src/packet/skesk.rs | 11 | ||||
-rw-r--r-- | openpgp/src/parse.rs | 95 | ||||
-rw-r--r-- | openpgp/src/parse/stream.rs | 46 | ||||
-rw-r--r-- | openpgp/src/policy.rs | 8 | ||||
-rw-r--r-- | openpgp/src/serialize.rs | 59 | ||||
-rw-r--r-- | openpgp/src/serialize/stream.rs | 40 | ||||
-rw-r--r-- | openpgp/tests/secret-leak-detector/detector.rs | 2 |
19 files changed, 674 insertions, 138 deletions
diff --git a/guide/src/chapter_02.md b/guide/src/chapter_02.md index 493528d7..ffde7ace 100644 --- a/guide/src/chapter_02.md +++ b/guide/src/chapter_02.md @@ -128,7 +128,7 @@ fn main() -> openpgp::Result<()> { # sym_algo: Option<SymmetricAlgorithm>, # mut decrypt: D) # -> openpgp::Result<Option<openpgp::Fingerprint>> -# where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool +# where D: FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool # { # // The encryption key is the first and only subkey. # let key = self.secret.keys().unencrypted_secret() @@ -276,7 +276,7 @@ fn generate() -> openpgp::Result<openpgp::Cert> { # sym_algo: Option<SymmetricAlgorithm>, # mut decrypt: D) # -> openpgp::Result<Option<openpgp::Fingerprint>> -# where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool +# where D: FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool # { # // The encryption key is the first and only subkey. # let key = self.secret.keys().unencrypted_secret() @@ -424,7 +424,7 @@ fn encrypt(policy: &dyn Policy, # sym_algo: Option<SymmetricAlgorithm>, # mut decrypt: D) # -> openpgp::Result<Option<openpgp::Fingerprint>> -# where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool +# where D: FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool # { # // The encryption key is the first and only subkey. # let key = self.secret.keys().unencrypted_secret() @@ -586,7 +586,7 @@ impl<'a> DecryptionHelper for Helper<'a> { sym_algo: Option<SymmetricAlgorithm>, mut decrypt: D) -> openpgp::Result<Option<openpgp::Fingerprint>> - where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool + where D: FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool { // The encryption key is the first and only subkey. let key = self.secret.keys().unencrypted_secret() diff --git a/ipc/examples/gpg-agent-decrypt.rs b/ipc/examples/gpg-agent-decrypt.rs index f0777718..d996fb56 100644 --- a/ipc/examples/gpg-agent-decrypt.rs +++ b/ipc/examples/gpg-agent-decrypt.rs @@ -122,11 +122,13 @@ impl DecryptionHelper for Helper { sym_algo: Option<SymmetricAlgorithm>, mut decrypt: D) -> openpgp::Result<Option<openpgp::Fingerprint>> - where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool + where D: FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool { // Try each PKESK until we succeed. for pkesk in pkesks { - if let Some(pair) = self.keys.get_mut(pkesk.recipient()) { + if let Some(pair) = pkesk.recipient() + .and_then(|r| self.keys.get_mut(&r.into())) + { if pkesk.decrypt(pair, sym_algo) .map(|(algo, session_key)| decrypt(algo, &session_key)) .unwrap_or(false) diff --git a/ipc/tests/gpg-agent.rs b/ipc/tests/gpg-agent.rs index 8ec4482d..d7f8f8c1 100644 --- a/ipc/tests/gpg-agent.rs +++ b/ipc/tests/gpg-agent.rs @@ -396,7 +396,7 @@ fn decrypt(also_try_explicit_async: bool) -> openpgp::Result<()> { sym_algo: Option<SymmetricAlgorithm>, mut decrypt: D) -> openpgp::Result<Option<openpgp::Fingerprint>> - where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool + where D: FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool { // We only gave the cert to GnuPG, the agent doesn't // have the secret. @@ -424,7 +424,10 @@ fn decrypt(also_try_explicit_async: bool) -> openpgp::Result<()> { } for pkesk in pkesks { - if *pkesk.recipient() != keypair.public().keyid() { + if pkesk.recipient() + .map(|h| ! h.aliases(keypair.public().key_handle())) + .unwrap_or(true) + { continue; } diff --git a/openpgp/benches/common/decrypt.rs b/openpgp/benches/common/decrypt.rs index ef8a2db8..dc3cc8ab 100644 --- a/openpgp/benches/common/decrypt.rs +++ b/openpgp/benches/common/decrypt.rs @@ -39,7 +39,7 @@ impl DecryptionHelper for PasswordHelper { mut decrypt: D, ) -> Result<Option<Fingerprint>> where - D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool, + D: FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool, { // Finally, try to decrypt using the SKESKs. for skesk in skesks { @@ -135,7 +135,7 @@ impl DecryptionHelper for CertHelper<'_> { mut decrypt: D, ) -> Result<Option<Fingerprint>> where - D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool, + D: FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool, { let p = &StandardPolicy::new(); @@ -158,7 +158,8 @@ impl DecryptionHelper for CertHelper<'_> { .filter_map(|key| { pkesks .iter() - .find(|pkesk| pkesk.recipient() == &key.keyid()) + .find(|pkesk| pkesk.recipient().map( + |r| r.aliases(&key.key_handle())).unwrap_or(false)) .map(|pkesk| (pkesk, key)) }) .find(|(pkesk, key)| { diff --git a/openpgp/examples/decrypt-with.rs b/openpgp/examples/decrypt-with.rs index 20382d3a..c5cfe17b 100644 --- a/openpgp/examples/decrypt-with.rs +++ b/openpgp/examples/decrypt-with.rs @@ -91,12 +91,12 @@ impl DecryptionHelper for Helper { sym_algo: Option<SymmetricAlgorithm>, mut decrypt: D) -> openpgp::Result<Option<openpgp::Fingerprint>> - where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool + where D: FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool { // Try each PKESK until we succeed. let mut recipient = None; for pkesk in pkesks { - if let Some((fp, pair)) = self.keys.get_mut(pkesk.recipient()) { + if let Some((fp, pair)) = self.keys.get_mut(&KeyID::from(pkesk.recipient())) { if pkesk.decrypt(pair, sym_algo) .map(|(algo, session_key)| decrypt(algo, &session_key)) .unwrap_or(false) diff --git a/openpgp/examples/generate-encrypt-decrypt.rs b/openpgp/examples/generate-encrypt-decrypt.rs index 2e6c6b12..f1e8e38b 100644 --- a/openpgp/examples/generate-encrypt-decrypt.rs +++ b/openpgp/examples/generate-encrypt-decrypt.rs @@ -121,7 +121,7 @@ impl<'a> DecryptionHelper for Helper<'a> { sym_algo: Option<SymmetricAlgorithm>, mut decrypt: D) -> openpgp::Result<Option<openpgp::Fingerprint>> - where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool + where D: FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool { let key = self.secret.keys().unencrypted_secret() .with_policy(self.policy, None) diff --git a/openpgp/examples/reply-encrypted.rs b/openpgp/examples/reply-encrypted.rs index 61d41d11..f711a93b 100644 --- a/openpgp/examples/reply-encrypted.rs +++ b/openpgp/examples/reply-encrypted.rs @@ -88,7 +88,7 @@ pub fn main() -> openpgp::Result<()> { .context("Decryption failed")?; let (algo, sk, pkesks) = decryptor.into_helper().recycling_bin.unwrap(); - eprintln!("- Reusing ({}, {}) with {} PKESK packets", + eprintln!("- Reusing ({:?}, {}) with {} PKESK packets", algo, openpgp::fmt::hex::encode(&sk), pkesks.len()); // Compose a writer stack corresponding to the output format and @@ -105,7 +105,7 @@ pub fn main() -> openpgp::Result<()> { } // We want to encrypt a literal data packet. - let message = Encryptor2::with_session_key(message, algo, sk)? + let message = Encryptor2::with_session_key(message, algo.expect("XXX seipdv2"), sk)? .build().context("Failed to create encryptor")?; let mut message = LiteralWriter::new(message).build() @@ -128,7 +128,7 @@ pub fn main() -> openpgp::Result<()> { /// verification policy. struct Helper { keys: HashMap<KeyID, (Fingerprint, KeyPair)>, - recycling_bin: Option<(SymmetricAlgorithm, SessionKey, Vec<PKESK>)>, + recycling_bin: Option<(Option<SymmetricAlgorithm>, SessionKey, Vec<PKESK>)>, } impl Helper { @@ -161,13 +161,13 @@ impl DecryptionHelper for Helper { sym_algo: Option<SymmetricAlgorithm>, mut decrypt: D) -> openpgp::Result<Option<openpgp::Fingerprint>> - where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool + where D: FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool { // Try each PKESK until we succeed. let mut recipient = None; let mut encryption_context = None; for pkesk in pkesks { - if let Some((fp, pair)) = self.keys.get_mut(pkesk.recipient()) { + if let Some((fp, pair)) = self.keys.get_mut(&KeyID::from(pkesk.recipient())) { if pkesk.decrypt(pair, sym_algo) .map(|(algo, session_key)| { let success = decrypt(algo, &session_key); diff --git a/openpgp/src/keyhandle.rs b/openpgp/src/keyhandle.rs index 32f23536..f1aa2766 100644 --- a/openpgp/src/keyhandle.rs +++ b/openpgp/src/keyhandle.rs @@ -134,6 +134,16 @@ impl From<KeyHandle> for KeyID { } } +impl From<Option<KeyHandle>> for KeyID { + fn from(i: Option<KeyHandle>) -> Self { + match i { + Some(KeyHandle::Fingerprint(i)) => i.into(), + Some(KeyHandle::KeyID(i)) => i, + None => KeyID::wildcard(), + } + } +} + impl From<&KeyHandle> for KeyID { fn from(i: &KeyHandle) -> Self { match i { diff --git a/openpgp/src/packet/mod.rs b/openpgp/src/packet/mod.rs index 81eb23b6..e350c174 100644 --- a/openpgp/src/packet/mod.rs +++ b/openpgp/src/packet/mod.rs @@ -170,13 +170,16 @@ pub mod prelude; use crate::{ crypto::{ + Decryptor, KeyPair, Password, + SessionKey, }, types::{ + HashAlgorithm, SignatureType, + SymmetricAlgorithm, PublicKeyAlgorithm, - HashAlgorithm, }, KeyHandle, }; @@ -478,7 +481,8 @@ impl Deref for Packet { Packet::UserAttribute(ref packet) => &packet.common, Packet::Literal(ref packet) => &packet.common, Packet::CompressedData(ref packet) => &packet.common, - Packet::PKESK(ref packet) => &packet.common, + Packet::PKESK(PKESK::V3(packet)) => &packet.common, + Packet::PKESK(PKESK::V6(packet)) => &packet.common, Packet::SKESK(SKESK::V4(ref packet)) => &packet.common, Packet::SKESK(SKESK::V6(ref packet)) => &packet.skesk4.common, Packet::SEIP(SEIP::V1(packet)) => &packet.common, @@ -508,7 +512,8 @@ impl DerefMut for Packet { Packet::UserAttribute(ref mut packet) => &mut packet.common, Packet::Literal(ref mut packet) => &mut packet.common, Packet::CompressedData(ref mut packet) => &mut packet.common, - Packet::PKESK(ref mut packet) => &mut packet.common, + Packet::PKESK(PKESK::V3(packet)) => &mut packet.common, + Packet::PKESK(PKESK::V6(packet)) => &mut packet.common, Packet::SKESK(SKESK::V4(ref mut packet)) => &mut packet.common, Packet::SKESK(SKESK::V6(ref mut packet)) => &mut packet.skesk4.common, Packet::SEIP(SEIP::V1(packet)) => &mut packet.common, @@ -1234,6 +1239,8 @@ impl<'a> std::convert::TryFrom<&'a Signature> for OnePassSig { pub enum PKESK { /// PKESK packet version 3. V3(self::pkesk::PKESK3), + /// PKESK packet version 6. + V6(self::pkesk::PKESK6), } assert_send_and_sync!(PKESK); @@ -1242,36 +1249,75 @@ impl PKESK { pub fn version(&self) -> u8 { match self { PKESK::V3(_) => 3, + PKESK::V6(_) => 6, } } -} -impl From<PKESK> for Packet { - fn from(p: PKESK) -> Self { - Packet::PKESK(p) + /// Gets the recipient. + pub fn recipient(&self) -> Option<KeyHandle> { + match self { + PKESK::V3(p) => { + let id = p.recipient(); + if id.is_wildcard() { + None + } else { + Some(id.into()) + } + }, + PKESK::V6(p) => p.recipient().map(Into::into), + } } -} - -// Trivial forwarder for singleton enum. -impl Deref for PKESK { - type Target = self::pkesk::PKESK3; - fn deref(&self) -> &Self::Target { + /// Gets the public key algorithm. + pub fn pk_algo(&self) -> PublicKeyAlgorithm { match self { - PKESK::V3(ref p) => p, + PKESK::V3(p) => p.pk_algo(), + PKESK::V6(p) => p.pk_algo(), } } -} -// Trivial forwarder for singleton enum. -impl DerefMut for PKESK { - fn deref_mut(&mut self) -> &mut Self::Target { + /// Gets the encrypted session key. + pub fn esk(&self) -> &crate::crypto::mpi::Ciphertext { + match self { + PKESK::V3(p) => p.esk(), + PKESK::V6(p) => p.esk(), + } + } + /// Decrypts the encrypted session key. + /// + /// If the symmetric algorithm used to encrypt the message is + /// known in advance, it should be given as argument. This allows + /// us to reduce the side-channel leakage of the decryption + /// operation for RSA. + /// + /// Returns the session key and symmetric algorithm used to + /// encrypt the following payload. + /// + /// Returns `None` on errors. This prevents leaking information + /// to an attacker, which could lead to compromise of secret key + /// material with certain algorithms (RSA). See [Section 14 of + /// RFC 4880]. + /// + /// [Section 14 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-14 + pub fn decrypt(&self, decryptor: &mut dyn Decryptor, + sym_algo_hint: Option<SymmetricAlgorithm>) + -> Option<(Option<SymmetricAlgorithm>, SessionKey)> + { match self { - PKESK::V3(ref mut p) => p, + PKESK::V3(p) => p.decrypt(decryptor, sym_algo_hint) + .map(|(s, k)| (Some(s), k)), + PKESK::V6(p) => p.decrypt(decryptor, sym_algo_hint) + .map(|k| (None, k)), } } } +impl From<PKESK> for Packet { + fn from(p: PKESK) -> Self { + Packet::PKESK(p) + } +} + /// Holds a symmetrically encrypted session key. /// /// The session key is used to decrypt the actual ciphertext, which is diff --git a/openpgp/src/packet/pkesk.rs b/openpgp/src/packet/pkesk.rs index c394e7c8..35b90af3 100644 --- a/openpgp/src/packet/pkesk.rs +++ b/openpgp/src/packet/pkesk.rs @@ -21,6 +21,9 @@ use crate::SymmetricAlgorithm; use crate::crypto::SessionKey; use crate::packet; +mod v6; +pub use v6::PKESK6; + /// Holds an asymmetrically encrypted session key. /// /// The session key is needed to decrypt the actual ciphertext. See @@ -67,34 +70,13 @@ impl PKESK3 { where P: key::KeyParts, R: key::KeyRole, { - // XXX: Corner case: for X25519 and X448 we have to prepend - // the cipher octet to the ciphertext instead of encrypting - // it. - - // We need to prefix the cipher specifier to the session key, - // and a two-octet checksum. - let mut psk = Vec::with_capacity(1 + session_key.len() + 2); - psk.push(algo.into()); - psk.extend_from_slice(session_key); - - // XXX: Move the checksumming somewhere else. - - // Compute the sum modulo 65536, i.e. as u16. - let checksum = session_key - .iter() - .cloned() - .map(u16::from) - .fold(0u16, u16::wrapping_add); - - psk.extend_from_slice(&checksum.to_be_bytes()); - - let psk: SessionKey = psk.into(); - let esk = recipient.encrypt(&psk)?; Ok(PKESK3{ common: Default::default(), recipient: recipient.keyid(), pk_algo: recipient.pk_algo(), - esk, + esk: packet::PKESK::encrypt_common( + Some(algo), session_key, + recipient.parts_as_unspecified().role_as_unspecified())?, }) } @@ -155,19 +137,173 @@ impl PKESK3 { sym_algo_hint: Option<SymmetricAlgorithm>) -> Result<(SymmetricAlgorithm, SessionKey)> { - // XXX: Corner case: for X25519 and X448 we have to prepend - // the cipher octet to the ciphertext instead of encrypting - // it. + packet::PKESK::decrypt_common(&self.esk, decryptor, sym_algo_hint, true) + } +} + +/// Returns whether the given `algo` requires checksumming, and +/// whether the cipher octet is prepended to the encrypted session +/// key, or it is prepended to the plain session key and then +/// encrypted. +fn classify_pk_algo(algo: PublicKeyAlgorithm, seipdv1: bool) + -> Result<(bool, bool, bool)> +{ + #[allow(deprecated)] + match algo { + // Classical encryption: plaintext includes the cipher + // octet and is checksummed. + PublicKeyAlgorithm::RSAEncryptSign | + PublicKeyAlgorithm::RSAEncrypt | + PublicKeyAlgorithm::ElGamalEncrypt | + PublicKeyAlgorithm::ElGamalEncryptSign | + PublicKeyAlgorithm::ECDH => + Ok((true, false, seipdv1)), + + // Corner case: for X25519 and X448 we have to prepend + // the cipher octet to the ciphertext instead of + // encrypting it. + PublicKeyAlgorithm::X25519 | + PublicKeyAlgorithm::X448 => + Ok((false, seipdv1, false)), + + a @ PublicKeyAlgorithm::RSASign | + a @ PublicKeyAlgorithm::DSA | + a @ PublicKeyAlgorithm::ECDSA | + a @ PublicKeyAlgorithm::EdDSA | + a @ PublicKeyAlgorithm::Ed25519 | + a @ PublicKeyAlgorithm::Ed448 | + a @ PublicKeyAlgorithm::Private(_) | + a @ PublicKeyAlgorithm::Unknown(_) => + Err(Error::UnsupportedPublicKeyAlgorithm(a).into()), + } +} + + +impl packet::PKESK { + fn encrypt_common(algo: Option<SymmetricAlgorithm>, + session_key: &SessionKey, + recipient: &Key<key::UnspecifiedParts, + key::UnspecifiedRole>) + -> Result<Ciphertext> + { + let (checksummed, unencrypted_cipher_octet, encrypted_cipher_octet) = + classify_pk_algo(recipient.pk_algo(), algo.is_some())?; + + // We may need to prefix the cipher specifier to the session + // key, and we may add a two-octet checksum. + let mut psk = Vec::with_capacity( + encrypted_cipher_octet.then(|| 1).unwrap_or(0) + + session_key.len() + + checksummed.then(|| 2).unwrap_or(0)); + if let Some(algo) = algo { + if encrypted_cipher_octet { + psk.push(algo.into()); + } + } + psk.extend_from_slice(session_key); + + if checksummed { + // Compute the sum modulo 65536, i.e. as u16. + let checksum = session_key + .iter() + .cloned() + .map(u16::from) + .fold(0u16, u16::wrapping_add); + + psk.extend_from_slice(&checksum.to_be_bytes()); + } + + // Make sure it is cleaned up when dropped. + let psk: SessionKey = psk.into(); + let mut esk = recipient.encrypt(&psk)?; + + if let Some(algo) = algo { + if unencrypted_cipher_octet { + match esk { + Ciphertext::X25519 { ref mut key, .. } | + Ciphertext::X448 { ref mut key, .. } => { + let mut new_key = Vec::with_capacity(1 + key.len()); + new_key.push(algo.into()); + new_key.extend_from_slice(key); + *key = new_key.into(); + }, + _ => unreachable!("We only prepend the cipher octet \ + for X25519 and X448"), + }; + } + } + + Ok(esk) + } + + fn decrypt_common(ciphertext: &Ciphertext, + decryptor: &mut dyn Decryptor, + sym_algo_hint: Option<SymmetricAlgorithm>, + seipdv1: bool) + -> Result<(SymmetricAlgorithm, SessionKey)> + { + let (checksummed, unencrypted_cipher_octet, encrypted_cipher_octet) = + classify_pk_algo(decryptor.public().pk_algo(), seipdv1)?; + + //dbg!((checksummed, unencrypted_cipher_octet, encrypted_cipher_octet)); + + let mut sym_algo: Option<SymmetricAlgorithm> = None; + let modified_ciphertext; + let esk; + if unencrypted_cipher_octet { + match ciphertext { + Ciphertext::X25519 { e, key, } => { + sym_algo = + Some((*key.get(0).ok_or_else( + || Error::MalformedPacket("Short ESK".into()))?) + .into()); + modified_ciphertext = Ciphertext::X25519 { + e: e.clone(), + key: key[1..].into(), + }; + esk = &modified_ciphertext; + }, + Ciphertext::X448 { e, key, } => { + sym_algo = + Some((*key.get(0).ok_or_else( + || Error::MalformedPacket("Short ESK".into()))?) + .into()); + modified_ciphertext = Ciphertext::X448 { + e: e.clone(), + key: key[1..].into(), + }; + esk = &modified_ciphertext; + }, + + _ => { + // We only prepend the cipher octet for X25519 and + // X448, yet we're trying to decrypt a ciphertext + // that uses a different algorithm, clearly + // something has gone wrong and will fail when we + // try to decrypt it downstream. + esk = ciphertext; + }, + } + } else { + esk = ciphertext; + } let plaintext_len = if let Some(s) = sym_algo_hint { - Some(1 /* cipher octet */ + s.key_size()? + 2 /* chksum */) + Some(encrypted_cipher_octet.then(|| 1).unwrap_or(0) + + s.key_size()? + + checksummed.then(|| 2).unwrap_or(0)) } else { None }; - let plain = decryptor.decrypt(&self.esk, plaintext_len)?; - // XXX: Move the checksumming somewhere else. - let key_rgn = 1..plain.len().saturating_sub(2); - let sym_algo: SymmetricAlgorithm = plain[0].into(); + let plain = decryptor.decrypt(esk, plaintext_len)?; + let key_rgn = encrypted_cipher_octet.then(|| 1).unwrap_or(0) + ..plain.len().saturating_sub(checksummed.then(|| 2).unwrap_or(0)); + if encrypted_cipher_octet { + sym_algo = Some(plain[0].into()); + } + let sym_algo = sym_algo.or(sym_algo_hint) + .ok_or_else(|| Error::InvalidOperation( |