diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2022-01-20 17:38:29 +0100 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2022-03-22 11:40:50 +0100 |
commit | 7e108d056bc6887aa774c73ad6dedc07e99baeb2 (patch) | |
tree | 25ccaa5effa0bcc57657162c289ec4550b6b7800 | |
parent | e8652195b7c56fcef63eefe637c631b6389acb4f (diff) |
XXX create test vectors
-rw-r--r-- | openpgp/examples/test-vectors-dump-s2k.rs | 20 | ||||
-rw-r--r-- | openpgp/examples/test-vectors-seipv2.rs | 101 | ||||
-rw-r--r-- | openpgp/src/crypto/aead.rs | 36 | ||||
-rw-r--r-- | openpgp/src/fmt.rs | 15 | ||||
-rw-r--r-- | openpgp/src/packet/skesk.rs | 16 | ||||
-rw-r--r-- | openpgp/src/serialize/stream.rs | 1 |
6 files changed, 187 insertions, 2 deletions
diff --git a/openpgp/examples/test-vectors-dump-s2k.rs b/openpgp/examples/test-vectors-dump-s2k.rs new file mode 100644 index 00000000..52cf3584 --- /dev/null +++ b/openpgp/examples/test-vectors-dump-s2k.rs @@ -0,0 +1,20 @@ +use sequoia_openpgp::{ + fmt::hex, + Packet, + packet::SKESK, + PacketPile, + Result, + parse::Parse, +}; + +fn main() -> Result<()> { + let pp = PacketPile::from_file("seipv2.txt")?; + if let Some(Packet::SKESK(SKESK::V5(v))) = pp.path_ref(&[0]) { + hex::dump_rfc("s2k derived key", &v.s2k().derive_key(&"password".into(), 16)?); + hex::dump_rfc("ecrypted key", &v.decrypt(&"password".into())?.1); + } else { + panic!() + } + + Ok(()) +} diff --git a/openpgp/examples/test-vectors-seipv2.rs b/openpgp/examples/test-vectors-seipv2.rs new file mode 100644 index 00000000..f8307d27 --- /dev/null +++ b/openpgp/examples/test-vectors-seipv2.rs @@ -0,0 +1,101 @@ +use std::io::{self, Write}; + +use anyhow::Context; + +use sequoia_openpgp as openpgp; + +use crate::openpgp::serialize::stream::Armorer; +use crate::openpgp::crypto::*; +use crate::openpgp::types::*; +use crate::openpgp::parse::Parse; +use openpgp::packet::{Packet, Padding}; +use crate::openpgp::serialize::{ + Serialize, + stream::{ + Message, LiteralWriter, Encryptor, + }, +}; +use crate::openpgp::policy::StandardPolicy as P; + +const MESSAGE: &[u8] = b"Hello, world!"; +const BIG_MESSAGE: [u8; 4096 + 13] = [0; 4096 + 13]; + +const ALICE: &[u8] = b"-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: Alice's OpenPGP certificate +Comment: https://www.ietf.org/id/draft-bre-openpgp-samples-01.html + +mDMEXEcE6RYJKwYBBAHaRw8BAQdArjWwk3FAqyiFbFBKT4TzXcVBqPTB3gmzlC/U +b7O1u120JkFsaWNlIExvdmVsYWNlIDxhbGljZUBvcGVucGdwLmV4YW1wbGU+iJAE +ExYIADgCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AWIQTrhbtfozp14V6UTmPy +MVUMT0fjjgUCXaWfOgAKCRDyMVUMT0fjjukrAPoDnHBSogOmsHOsd9qGsiZpgRnO +dypvbm+QtXZqth9rvwD9HcDC0tC+PHAsO7OTh1S1TC9RiJsvawAfCPaQZoed8gK4 +OARcRwTpEgorBgEEAZdVAQUBAQdAQv8GIa2rSTzgqbXCpDDYMiKRVitCsy203x3s +E9+eviIDAQgHiHgEGBYIACAWIQTrhbtfozp14V6UTmPyMVUMT0fjjgUCXEcE6QIb +DAAKCRDyMVUMT0fjjlnQAQDFHUs6TIcxrNTtEZFjUFm1M0PJ1Dng/cDW4xN80fsn +0QEA22Kr7VkCjeAEC08VSTeV+QFsmz55/lntWkwYWhmvOgE= +=iIGO +-----END PGP PUBLIC KEY BLOCK-----"; + +fn main() -> openpgp::Result<()> { + let p = &P::new(); + let aead_algo = AEADAlgorithm::GCM; + + let cert = openpgp::Cert::from_bytes(ALICE).unwrap(); + // Build a list of recipient subkeys. + let mut recipients = Vec::new(); + // Make sure we add at least one subkey from every + // certificate. + let mut found_one = false; + for key in cert.keys().with_policy(p, None) + .supported().alive().revoked(false) + .for_transport_encryption() + { + recipients.push(key); + found_one = true; + } + + if ! found_one { + return Err(anyhow::anyhow!("No suitable encryption subkey for {}", + cert)); + } + assert_eq!(recipients.len(), 1); + + for (_label, plaintext) in &[ + (format!("{:?}", String::from_utf8_lossy(MESSAGE)), MESSAGE), + (format!("{} zeros", BIG_MESSAGE.len()), &BIG_MESSAGE[..]) + ] { + for sym_algo in &[SymmetricAlgorithm::AES128, + SymmetricAlgorithm::AES192, + SymmetricAlgorithm::AES256] { + let sk = SessionKey::new(sym_algo.key_size()?); + let mut sink = io::stdout(); + let message = Message::new(&mut sink); + let message = Armorer::new(message) + .build()?; + + // We want to encrypt a literal data packet. + let message = Encryptor::with_session_key(message, *sym_algo, sk)? + .add_passwords(vec!["password"]) + .aead_algo(aead_algo) + .build().context("Failed to create encryptor")?; + + let mut message = LiteralWriter::new(message).build() + .context("Failed to create literal writer")?; + + message.write_all(plaintext) + .context("Failed to encrypt")?; + + let mut message = message.finalize_one()?.unwrap(); + Packet::from( + Padding::new(openpgp::serialize::stream::padding::padme( + plaintext.len() as u64) as usize)) + .serialize(&mut message)?; + + message.finalize()?; + + return Ok(()); + } + } + + Ok(()) +} diff --git a/openpgp/src/crypto/aead.rs b/openpgp/src/crypto/aead.rs index a2d70efe..a087d4c1 100644 --- a/openpgp/src/crypto/aead.rs +++ b/openpgp/src/crypto/aead.rs @@ -19,6 +19,7 @@ use crate::crypto::mem::secure_cmp; use crate::seal; use crate::parse::Cookie; use crate::crypto::hkdf_sha256; +use crate::fmt::hex::dump_rfc; /// Minimum AEAD chunk size. /// @@ -133,6 +134,8 @@ pub trait Schedule: Send + Sync { fn final_chunk<F, R>(&self, index: u64, length: u64, fun: F) -> R where F: FnMut(&[u8], &[u8]) -> R; + + fn dump_rfc<B: AsRef<[u8]>>(&self, _: &str, _: B) {} } const AED1AD_PREFIX_LEN: usize = 5; @@ -251,6 +254,11 @@ impl SEIPv2Schedule { format!("Invalid AEAD chunk size: {}", chunk_size)).into()); } + eprintln!("## Sample AEAD-{:?} encryption and decryption\n\n", aead); + + dump_rfc("Session key", session_key); + dump_rfc("Salt", salt); + // Derive the message key and initialization vector. let key_size = sym_algo.key_size()?; // The IV size is NONCE_LEN - 8 bytes taken from the KDF. @@ -264,9 +272,13 @@ impl SEIPv2Schedule { aead.into(), chunk_size.trailing_zeros() as u8 - 6, ]; + dump_rfc("HKDF info", &ad); hkdf_sha256(session_key, Some(salt), &ad, &mut key_iv); + dump_rfc("HKDF output", &key_iv); let key = Vec::from(&key_iv[..key_size]).into(); - let iv = Vec::from(&key_iv[key_size..]).into_boxed_slice(); + let iv = Vec::from(&key_iv[key_size..]).into(); + dump_rfc("Message key", &key); + dump_rfc("Initialization vector", &iv); Ok((key, Self { iv, @@ -289,6 +301,9 @@ impl Schedule for SEIPv2Schedule { nonce[..self.iv.len()].copy_from_slice(&self.iv); nonce[self.iv.len()..].copy_from_slice(&index_be); + dump_rfc("Nonce", &nonce); + dump_rfc("Additional authenticated data", &self.ad); + fun(nonce, &self.ad) } @@ -309,8 +324,15 @@ impl Schedule for SEIPv2Schedule { nonce[..self.iv.len()].copy_from_slice(&self.iv); nonce[self.iv.len()..].copy_from_slice(&index_be); + dump_rfc("Final nonce", &nonce); + dump_rfc("Final additional authenticated data", &ad); + fun(nonce, &ad) } + + fn dump_rfc<B: AsRef<[u8]>>(&self, l: &str, s: B) { + dump_rfc(l, s); + } } /// A `Read`er for decrypting AEAD-encrypted data. @@ -491,10 +513,13 @@ 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]); // 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 { @@ -540,6 +565,8 @@ 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 @@ -769,10 +796,12 @@ impl<W: io::Write, S: Schedule> Encryptor<W, S> { self.chunk_index += 1; crate::vec_truncate(&mut self.buffer, 0); inner.write_all(&self.scratch)?; + self.schedule.dump_rfc("Encrypted data chunk", &self.scratch); // Write digest. aead.digest(&mut self.scratch[..self.digest_size]); inner.write_all(&self.scratch[..self.digest_size])?; + self.schedule.dump_rfc("Authentication tag", &self.scratch[..self.digest_size]); } } @@ -797,10 +826,12 @@ impl<W: io::Write, S: Schedule> Encryptor<W, S> { self.bytes_encrypted += self.scratch.len() as u64; self.chunk_index += 1; inner.write_all(&self.scratch)?; + self.schedule.dump_rfc("Encrypted data chunk", &self.scratch); // Write digest. aead.digest(&mut self.scratch[..self.digest_size]); inner.write_all(&self.scratch[..self.digest_size])?; + self.schedule.dump_rfc("Authentication tag", &self.scratch[..self.digest_size]); } else { // Stash for later. assert!(self.buffer.is_empty()); @@ -832,11 +863,13 @@ impl<W: io::Write, S: Schedule> Encryptor<W, S> { self.chunk_index += 1; crate::vec_truncate(&mut self.buffer, 0); inner.write_all(&self.scratch)?; + self.schedule.dump_rfc("Encrypted data chunk", &self.scratch); // Write digest. unsafe { self.scratch.set_len(self.digest_size) } aead.digest(&mut self.scratch[..self.digest_size]); inner.write_all(&self.scratch[..self.digest_size])?; + self.schedule.dump_rfc("Authentication tag", &self.scratch[..self.digest_size]); } // Write final digest. @@ -852,6 +885,7 @@ impl<W: io::Write, S: Schedule> Encryptor<W, S> { })?; aead.digest(&mut self.scratch[..self.digest_size]); inner.write_all(&self.scratch[..self.digest_size])?; + self.schedule.dump_rfc("Final authentication tag", &self.scratch[..self.digest_size]); Ok(inner) } else { diff --git a/openpgp/src/fmt.rs b/openpgp/src/fmt.rs index 4e04e74f..5a7c4b68 100644 --- a/openpgp/src/fmt.rs +++ b/openpgp/src/fmt.rs @@ -19,6 +19,21 @@ pub mod hex { super::to_hex(buffer.as_ref(), true) } + /// XXX + pub fn dump_rfc<B: AsRef<[u8]>>(l: &str, s: B) { + eprintln!("{}:", l); + for (i, b) in s.as_ref().iter().enumerate() { + if i % 16 == 0 { + eprint!("\n "); + } else if i > 0 { + eprint!(" "); + } + eprint!("{:02x}", b); + } + eprintln!(); + eprintln!(); + } + /// Decodes the given hexadecimal number. pub fn decode<H: AsRef<str>>(hex: H) -> Result<Vec<u8>> { super::from_hex(hex.as_ref(), false) diff --git a/openpgp/src/packet/skesk.rs b/openpgp/src/packet/skesk.rs index 8cd3bb67..b382e5b2 100644 --- a/openpgp/src/packet/skesk.rs +++ b/openpgp/src/packet/skesk.rs @@ -460,6 +460,15 @@ 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 { + dump_rfc("Salt", salt); + } else { + panic!(); + } + // Derive key and make a cipher. let ad = [0xc3, 5, esk_algo.into(), esk_aead.into()]; let key = s2k.derive_key(password, esk_algo.key_size()?)?; @@ -474,13 +483,20 @@ impl SKESK5 { // Prepare associated data. ctx.update(&ad); + eprintln!("### Starting AEAD-EAX decryption of the session key\n"); + dump_rfc("The derived key is", &key); + dump_rfc("Authenticated Data", &ad); + dump_rfc("Nonce", &iv); + // Encrypt the session key with the KEK. let mut esk = vec![0u8; session_key.len()]; ctx.encrypt(&mut esk, session_key); + dump_rfc("AEAD encrypted session key", &esk); // Digest. let mut digest = vec![0u8; esk_aead.digest_size()?]; ctx.digest(&mut digest); + dump_rfc("Authentication tag", &digest); SKESK5::new(esk_algo, esk_aead, s2k, iv.into_boxed_slice(), esk.into(), digest.into_boxed_slice()) diff --git a/openpgp/src/serialize/stream.rs b/openpgp/src/serialize/stream.rs index 2f03bd9c..2be56f23 100644 --- a/openpgp/src/serialize/stream.rs +++ b/openpgp/src/serialize/stream.rs @@ -2820,7 +2820,6 @@ impl<'a> Encryptor<'a> { // Function hidden from the public API due to // https://gitlab.com/sequoia-pgp/sequoia/-/issues/550 // It is used only for tests so that it does not bit-rot. - #[cfg(test)] pub fn aead_algo(mut self, algo: AEADAlgorithm) -> Self { self.aead_algo = Some(algo); self |