diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2022-03-04 18:20:16 +0100 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2022-03-22 11:40:50 +0100 |
commit | 60358f223356a918de66cd79306945220271e6f1 (patch) | |
tree | d932dde23319bdaacb80ea1ea238922b588b89f2 | |
parent | 55f4826df1ae6491bfc214dabb7160ac8a24b070 (diff) |
extract test vector on decryption
-rw-r--r-- | openpgp/examples/test-vectors-dump-seipv2.rs | 138 | ||||
-rw-r--r-- | openpgp/src/crypto/aead.rs | 19 | ||||
-rw-r--r-- | openpgp/src/packet/skesk.rs | 9 |
3 files changed, 157 insertions, 9 deletions
diff --git a/openpgp/examples/test-vectors-dump-seipv2.rs b/openpgp/examples/test-vectors-dump-seipv2.rs new file mode 100644 index 00000000..24ea5d6c --- /dev/null +++ b/openpgp/examples/test-vectors-dump-seipv2.rs @@ -0,0 +1,138 @@ +/// Decrypts asymmetrically-encrypted OpenPGP messages using the +/// openpgp crate, Sequoia's low-level API. + +use std::io::{self, Read, Write}; + +use anyhow::Context; + +use sequoia_openpgp as openpgp; + +use openpgp::packet::prelude::*; +use openpgp::crypto::{S2K, SessionKey}; +use openpgp::types::*; +use openpgp::parse::{ + Parse, + PacketParser, + stream::{ + DecryptionHelper, + DecryptorBuilder, + VerificationHelper, + MessageStructure, + }, +}; +use openpgp::policy::StandardPolicy as P; +use openpgp::fmt::hex::dump_rfc; +use openpgp::{ + Result, + serialize::MarshalInto, +}; + +pub fn main() -> openpgp::Result<()> { + let p = &P::new(); + + let mut m = Vec::new(); + io::stdin().read_to_end(&mut m)?; + + // Now, create a decryptor with a helper using the given Certs. + let mut decryptor = + DecryptorBuilder::from_reader(io::Cursor::new(&m))? + .mapping(true) + .with_policy(p, None, Helper::default())?; + + // Finally, stream the decrypted data to stdout. + io::copy(&mut decryptor, &mut io::sink()) + .context("Decryption failed")?; + + eprintln!("### Complete AEAD-EAX encrypted packet sequence\n"); + eprintln!("~~~"); + io::stderr().write_all(&m)?; + eprintln!("~~~"); + Ok(()) +} + +/// This helper provides secrets for the decryption, fetches public +/// keys for the signature verification and implements the +/// verification policy. +#[derive(Default)] +struct Helper { + bytes: Vec<u8>, + seip: Option<SEIP2>, +} + +impl DecryptionHelper for Helper { + fn decrypt<D>(&mut self, + _pkesks: &[openpgp::packet::PKESK], + skesks: &[openpgp::packet::SKESK], + _sym_algo: Option<SymmetricAlgorithm>, + mut decrypt: D) + -> openpgp::Result<Option<openpgp::Fingerprint>> + where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool + { + eprintln!("### Sample Parameters\n"); + for skesk in skesks { + let skesk5 = if let SKESK::V5(v5) = skesk { v5 } else { panic!() }; + if let S2K::Iterated { hash, salt, hash_bytes } = skesk5.s2k() { + eprintln!("S2K:\n\n Iterated and Salted S2K\n"); + assert_eq!(*hash, HashAlgorithm::SHA256); + assert_eq!(*hash_bytes, 65011712); + eprintln!("Iterations:\n\n 65011712 (255), SHA2-256\n"); + dump_rfc("Salt", salt); + } else { + panic!("unexpected {:?}", skesk); + }; + + eprintln!("### Sample symmetric-key encrypted session key packet (v5)\n"); + let b = Packet::from(skesk5.clone()).to_vec()?; + dump_rfc("Packet header", &b[..2]); + dump_rfc("Version, algorithms, S2K fields", &b[2..2 + 0x12]); + dump_rfc("Nonce", skesk5.aead_iv()); + dump_rfc("Encrypted session key and AEAD tag", skesk5.esk()); + + eprintln!("### Starting AEAD-EAX decryption of the session key\n"); + let (algo, session_key) = skesk.decrypt(&"password".into())?; + + eprintln!("### Sample v2 SEIPD packet\n"); + let b = &self.bytes; + let seip = self.seip.as_ref().unwrap(); + dump_rfc("Packet header", &b[..2]); + dump_rfc("Version, AES-128, EAX, Chunk size octet", &b[2..2 + 4]); + dump_rfc("Salt", seip.salt()); // XXX + let tag_size = seip.aead().digest_size()?; + dump_rfc("Chunk #0 encrypted data", + &b[2 + 4 + seip.salt().len()..b.len() - 2 * tag_size]); + dump_rfc("Chunk #0 authentication tag", + &b[b.len() - 2 * tag_size..b.len() - 1 * tag_size]); + dump_rfc("Final (zero-sized chunk #1) authentication tag", + &b[b.len() - 1 * tag_size..b.len() - 0 * tag_size]); + + let r = decrypt(algo, &session_key); + assert!(r); + break; + } + + Ok(None) + } +} + +impl VerificationHelper for Helper { + fn get_certs(&mut self, _ids: &[openpgp::KeyHandle]) + -> openpgp::Result<Vec<openpgp::Cert>> { + Ok(Vec::new()) // Feed the Certs to the verifier here. + } + fn check(&mut self, _structure: MessageStructure) + -> openpgp::Result<()> { + Ok(()) // Implement your verification policy here. + } + fn inspect(&mut self, pp: &PacketParser<'_>) -> Result<()> { + match &pp.packet { + Packet::SEIP(SEIP::V2(seip)) => { + let b: Vec<u8> = + pp.map().unwrap().iter().flat_map(|e| e.as_bytes().iter().cloned()).collect(); + self.bytes = b; + self.seip = Some(seip.clone()); + }, + _ => (), + } + Ok(()) + } +} diff --git a/openpgp/src/crypto/aead.rs b/openpgp/src/crypto/aead.rs index 05560135..b82c86df 100644 --- a/openpgp/src/crypto/aead.rs +++ b/openpgp/src/crypto/aead.rs @@ -254,10 +254,9 @@ impl SEIPv2Schedule { format!("Invalid AEAD chunk size: {}", chunk_size)).into()); } - eprintln!("## Sample AEAD-{:?} encryption and decryption\n\n", aead); + eprintln!("### Decryption of data - dump_rfc("Session key", session_key); - dump_rfc("Salt", salt); +Starting AEAD-{:?} decryption of data, using the session key.\n", aead); // Derive the message key and initialization vector. let key_size = sym_algo.key_size()?; @@ -301,6 +300,7 @@ impl Schedule for SEIPv2Schedule { nonce[..self.iv.len()].copy_from_slice(&self.iv); nonce[self.iv.len()..].copy_from_slice(&index_be); + eprintln!("Chunk #{}:\n", index); dump_rfc("Nonce", &nonce); dump_rfc("Additional authenticated data", &self.ad); @@ -324,6 +324,7 @@ impl Schedule for SEIPv2Schedule { nonce[..self.iv.len()].copy_from_slice(&self.iv); nonce[self.iv.len()..].copy_from_slice(&index_be); + eprintln!("Authenticating final tag:\n"); dump_rfc("Final nonce", &nonce); dump_rfc("Final additional authenticated data", &ad); @@ -515,13 +516,17 @@ impl<'a, S: Schedule> Decryptor<'a, S> { &mut plaintext[pos..pos + to_decrypt] }; - self.schedule.dump_rfc("Encrypted data chunk", &chunk[..to_decrypt]); aead.decrypt(buffer, &chunk[..to_decrypt]); + if buffer.len() == 0x25 { + eprintln!("Decrypted chunk #0.\n"); + self.schedule.dump_rfc("Literal data packet with the string contents `Hello, world!`", + &buffer[..0x15]); + self.schedule.dump_rfc("Padding packet", + &buffer[0x15..]); + } // Check digest. aead.digest(&mut digest); - self.schedule.dump_rfc("Read authentication tag", &chunk[to_decrypt..]); - self.schedule.dump_rfc("Computed authentication tag", &digest[..]); if secure_cmp(&digest[..], &chunk[to_decrypt..]) != Ordering::Equal && ! DANGER_DISABLE_AUTHENTICATION { @@ -567,8 +572,6 @@ impl<'a, S: Schedule> Decryptor<'a, S> { aead.digest(&mut digest); let final_digest = self.source.data(final_digest_size)?; - self.schedule.dump_rfc("Read authentication tag", final_digest); - self.schedule.dump_rfc("Computed authentication tag", &digest[..]); if final_digest.len() != final_digest_size || secure_cmp(&digest[..], final_digest) != Ordering::Equal && ! DANGER_DISABLE_AUTHENTICATION diff --git a/openpgp/src/packet/skesk.rs b/openpgp/src/packet/skesk.rs index 585f8144..348ede9f 100644 --- a/openpgp/src/packet/skesk.rs +++ b/openpgp/src/packet/skesk.rs @@ -27,6 +27,7 @@ use crate::types::{ }; use crate::packet::{self, SKESK}; use crate::Packet; +use crate::fmt::hex::dump_rfc; impl SKESK { /// Derives the key inside this SKESK from `password`. Returns a @@ -388,7 +389,6 @@ impl SKESK5 { session_key.len(), payload_algo.key_size()?)).into()); } - use crate::fmt::hex::dump_rfc; eprintln!("### Sample Parameters\n"); eprintln!("S2K: {:?}\n", &s2k); if let crate::crypto::S2K::Iterated { salt, .. } = &s2k { @@ -446,6 +446,7 @@ impl SKESK5 { -> Result<(SymmetricAlgorithm, SessionKey)> { let key = self.s2k().derive_key(password, self.symmetric_algo().key_size()?)?; + dump_rfc("The derived key is", &key); let mut kek: SessionKey = vec![0; self.symmetric_algo().key_size()?].into(); @@ -453,12 +454,17 @@ impl SKESK5 { 5 /* Version. */, self.symmetric_algo().into(), self.aead_algo.into()]; + dump_rfc("HKDF info", &ad); hkdf_sha256(&key, None, &ad, &mut kek); + dump_rfc("HKDF output", &kek); + // Use the derived key to decrypt the ESK. let mut cipher = self.aead_algo.context( self.symmetric_algo(), &kek, self.aead_iv(), CipherOp::Decrypt)?; + dump_rfc("Authenticated Data", &ad); + dump_rfc("Nonce", self.aead_iv()); // Split off the authentication tag. let digest_len = self.aead_algo.digest_size()?; @@ -473,6 +479,7 @@ impl SKESK5 { cipher.digest(&mut digest); if &digest[..] == stored_digest { + dump_rfc("Decrypted session key", &plain); Ok((SymmetricAlgorithm::Unencrypted, plain)) } else { Err(Error::ManipulatedMessage.into()) |