summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2022-03-04 18:20:16 +0100
committerJustus Winter <justus@sequoia-pgp.org>2022-03-22 11:40:50 +0100
commit60358f223356a918de66cd79306945220271e6f1 (patch)
treed932dde23319bdaacb80ea1ea238922b588b89f2
parent55f4826df1ae6491bfc214dabb7160ac8a24b070 (diff)
extract test vector on decryption
-rw-r--r--openpgp/examples/test-vectors-dump-seipv2.rs138
-rw-r--r--openpgp/src/crypto/aead.rs19
-rw-r--r--openpgp/src/packet/skesk.rs9
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())