summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2022-01-20 17:38:29 +0100
committerJustus Winter <justus@sequoia-pgp.org>2022-03-22 11:40:50 +0100
commit7e108d056bc6887aa774c73ad6dedc07e99baeb2 (patch)
tree25ccaa5effa0bcc57657162c289ec4550b6b7800
parente8652195b7c56fcef63eefe637c631b6389acb4f (diff)
XXX create test vectors
-rw-r--r--openpgp/examples/test-vectors-dump-s2k.rs20
-rw-r--r--openpgp/examples/test-vectors-seipv2.rs101
-rw-r--r--openpgp/src/crypto/aead.rs36
-rw-r--r--openpgp/src/fmt.rs15
-rw-r--r--openpgp/src/packet/skesk.rs16
-rw-r--r--openpgp/src/serialize/stream.rs1
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