diff options
Diffstat (limited to 'openpgp/src/packet/pkesk.rs')
-rw-r--r-- | openpgp/src/packet/pkesk.rs | 225 |
1 files changed, 183 insertions, 42 deletions
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( + "No symmetric algorithm discovered or given".into()))?; let mut key: SessionKey = vec![0u8; sym_algo.key_size()?].into(); if key_rgn.len() != sym_algo.key_size()? { @@ -178,17 +314,18 @@ impl PKESK3 { key.copy_from_slice(&plain[key_rgn]); - let our_checksum - = key.iter().map(|&x| x as usize).sum::<usize>() & 0xffff; - let their_checksum = (plain[plain.len() - 2] as usize) << 8 - | (plain[plain.len() - 1] as usize); + if checksummed { + let our_checksum + = key.iter().map(|&x| x as usize).sum::<usize>() & 0xffff; + let their_checksum = (plain[plain.len() - 2] as usize) << 8 + | (plain[plain.len() - 1] as usize); - if their_checksum == our_checksum { - Ok((sym_algo, key)) - } else { - Err(Error::MalformedPacket("key checksum wrong".to_string()) - .into()) + if their_checksum != our_checksum { + return Err(Error::MalformedPacket( + "key checksum wrong".to_string()).into()); + } } + Ok((sym_algo, key)) } } @@ -207,7 +344,11 @@ impl From<PKESK3> for Packet { #[cfg(test)] impl Arbitrary for super::PKESK { fn arbitrary(g: &mut Gen) -> Self { - PKESK3::arbitrary(g).into() + if bool::arbitrary(g) { + PKESK3::arbitrary(g).into() + } else { + PKESK6::arbitrary(g).into() + } } } |