diff options
Diffstat (limited to 'sq/src/commands/dump.rs')
-rw-r--r-- | sq/src/commands/dump.rs | 946 |
1 files changed, 946 insertions, 0 deletions
diff --git a/sq/src/commands/dump.rs b/sq/src/commands/dump.rs new file mode 100644 index 00000000..6f35f56d --- /dev/null +++ b/sq/src/commands/dump.rs @@ -0,0 +1,946 @@ +use std::io::{self, Read}; + +use sequoia_openpgp as openpgp; +use self::openpgp::types::{Duration, Timestamp, SymmetricAlgorithm}; +use self::openpgp::fmt::hex; +use self::openpgp::crypto::mpi; +use self::openpgp::{Packet, Result}; +use self::openpgp::packet::prelude::*; +use self::openpgp::packet::header::CTB; +use self::openpgp::packet::{Header, header::BodyLength, Signature}; +use self::openpgp::packet::signature::subpacket::{Subpacket, SubpacketValue}; +use self::openpgp::crypto::{SessionKey, S2K}; +use self::openpgp::parse::{map::Map, Parse, PacketParserResult}; + +#[derive(Debug)] +pub enum Kind { + Message { + encrypted: bool, + }, + Keyring, + Cert, + Unknown, +} + +/// Converts sequoia_openpgp types for rendering. +pub trait Convert<T> { + /// Performs the conversion. + fn convert(self) -> T; +} + +impl Convert<chrono::Duration> for std::time::Duration { + fn convert(self) -> chrono::Duration { + chrono::Duration::seconds(self.as_secs() as i64) + } +} + +impl Convert<chrono::Duration> for Duration { + fn convert(self) -> chrono::Duration { + chrono::Duration::seconds(self.as_secs() as i64) + } +} + +impl Convert<chrono::DateTime<chrono::offset::Utc>> for std::time::SystemTime { + fn convert(self) -> chrono::DateTime<chrono::offset::Utc> { + chrono::DateTime::<chrono::offset::Utc>::from(self) + } +} + +impl Convert<chrono::DateTime<chrono::offset::Utc>> for Timestamp { + fn convert(self) -> chrono::DateTime<chrono::offset::Utc> { + std::time::SystemTime::from(self).convert() + } +} + +pub fn dump<W>(input: &mut dyn io::Read, output: &mut dyn io::Write, + mpis: bool, hex: bool, sk: Option<&SessionKey>, + width: W) + -> Result<Kind> + where W: Into<Option<usize>> +{ + let mut ppr + = self::openpgp::parse::PacketParserBuilder::from_reader(input)? + .map(hex).build()?; + let mut message_encrypted = false; + let width = width.into().unwrap_or(80); + let mut dumper = PacketDumper::new(width, mpis); + + while let PacketParserResult::Some(mut pp) = ppr { + let additional_fields = match pp.packet { + Packet::Literal(_) => { + let mut prefix = vec![0; 40]; + let n = pp.read(&mut prefix)?; + Some(vec![ + format!("Content: {:?}{}", + String::from_utf8_lossy(&prefix[..n]), + if n == prefix.len() { "..." } else { "" }), + ]) + }, + Packet::SEIP(_) if sk.is_none() => { + message_encrypted = true; + Some(vec!["No session key supplied".into()]) + } + Packet::SEIP(_) if sk.is_some() => { + message_encrypted = true; + let sk = sk.as_ref().unwrap(); + let mut decrypted_with = None; + for algo in 1..20 { + let algo = SymmetricAlgorithm::from(algo); + if let Ok(size) = algo.key_size() { + if size != sk.len() { continue; } + } else { + continue; + } + + if let Ok(_) = pp.decrypt(algo, sk) { + decrypted_with = Some(algo); + break; + } + } + let mut fields = Vec::new(); + fields.push(format!("Session key: {}", hex::encode(sk))); + if let Some(algo) = decrypted_with { + fields.push(format!("Symmetric algo: {}", algo)); + fields.push("Decryption successful".into()); + } else { + fields.push("Decryption failed".into()); + } + Some(fields) + }, + Packet::AED(_) if sk.is_none() => { + message_encrypted = true; + Some(vec!["No session key supplied".into()]) + } + Packet::AED(_) if sk.is_some() => { + message_encrypted = true; + let sk = sk.as_ref().unwrap(); + let algo = if let Packet::AED(ref aed) = pp.packet { + aed.symmetric_algo() + } else { + unreachable!() + }; + + let _ = pp.decrypt(algo, sk); + + let mut fields = Vec::new(); + fields.push(format!("Session key: {}", hex::encode(sk))); + if pp.encrypted() { + fields.push("Decryption failed".into()); + } else { + fields.push("Decryption successful".into()); + } + Some(fields) + }, + _ => None, + }; + + let header = pp.header().clone(); + let map = pp.take_map(); + + let recursion_depth = pp.recursion_depth(); + let packet = pp.packet.clone(); + + dumper.packet(output, recursion_depth as usize, + header, packet, map, additional_fields)?; + + let (_, ppr_) = match pp.recurse() { + Ok(v) => Ok(v), + Err(e) => { + let _ = dumper.flush(output); + Err(e) + }, + }?; + ppr = ppr_; + } + + dumper.flush(output)?; + + if let PacketParserResult::EOF(eof) = ppr { + if eof.is_message().is_ok() { + Ok(Kind::Message { + encrypted: message_encrypted, + }) + } else if eof.is_cert().is_ok() { + Ok(Kind::Cert) + } else if eof.is_keyring().is_ok() { + Ok(Kind::Keyring) + } else { + Ok(Kind::Unknown) + } + } else { + unreachable!() + } +} + +struct Node { + header: Header, + packet: Packet, + map: Option<Map>, + additional_fields: Option<Vec<String>>, + children: Vec<Node>, +} + +impl Node { + fn new(header: Header, packet: Packet, map: Option<Map>, + additional_fields: Option<Vec<String>>) -> Self { + Node { + header: header, + packet: packet, + map: map, + additional_fields: additional_fields, + children: Vec::new(), + } + } + + fn append(&mut self, depth: usize, node: Node) { + if depth == 0 { + self.children.push(node); + } else { + self.children.iter_mut().last().unwrap().append(depth - 1, node); + } + } +} + +pub struct PacketDumper { + width: usize, + mpis: bool, + root: Option<Node>, +} + +impl PacketDumper { + pub fn new(width: usize, mpis: bool) -> Self { + PacketDumper { + width: width, + mpis: mpis, + root: None, + } + } + + pub fn packet(&mut self, output: &mut dyn io::Write, depth: usize, + header: Header, p: Packet, map: Option<Map>, + additional_fields: Option<Vec<String>>) + -> Result<()> { + let node = Node::new(header, p, map, additional_fields); + if self.root.is_none() { + assert_eq!(depth, 0); + self.root = Some(node); + } else { + if depth == 0 { + let root = self.root.take().unwrap(); + self.dump_tree(output, "", &root)?; + self.root = Some(node); + } else { + self.root.as_mut().unwrap().append(depth - 1, node); + } + } + Ok(()) + } + + pub fn flush(&self, output: &mut dyn io::Write) -> Result<()> { + if let Some(root) = self.root.as_ref() { + self.dump_tree(output, "", &root)?; + } + Ok(()) + } + + fn dump_tree(&self, output: &mut dyn io::Write, indent: &str, node: &Node) + -> Result<()> { + let indent_node = + format!("{}{} ", indent, + if node.children.is_empty() { " " } else { "│" }); + self.dump_packet(output, &indent_node, Some(&node.header), &node.packet, + node.map.as_ref(), node.additional_fields.as_ref())?; + if node.children.is_empty() { + return Ok(()); + } + + let last = node.children.len() - 1; + for (i, child) in node.children.iter().enumerate() { + let is_last = i == last; + write!(output, "{}{}── ", indent, + if is_last { "└" } else { "├" })?; + let indent_child = + format!("{}{} ", indent, + if is_last { " " } else { "│" }); + self.dump_tree(output, &indent_child, child)?; + } + Ok(()) + } + + fn dump_packet(&self, output: &mut dyn io::Write, i: &str, + header: Option<&Header>, p: &Packet, map: Option<&Map>, + additional_fields: Option<&Vec<String>>) + -> Result<()> { + use self::openpgp::Packet::*; + + if let Some(tag) = p.kind() { + write!(output, "{}", tag)?; + } else { + write!(output, "Unknown or Unsupported Packet")?; + } + + if let Some(h) = header { + write!(output, ", {} CTB, {}{}", + if let CTB::Old(_) = h.ctb() { "old" } else { "new" }, + if let Some(map) = map { + format!("{} header bytes + ", + map.iter().take(2).map(|f| f.as_bytes().len()) + .sum::<usize>()) + } else { + // XXX: Mapping is disabled. No can do for + // now. Once we save the header in + // packet::Common, we can use this instead of + // relying on the map. + "".into() + }, + match h.length() { + BodyLength::Full(n) => + format!("{} bytes", n), + BodyLength::Partial(n) => + format!("partial length, {} bytes in first chunk", n), + BodyLength::Indeterminate => + "indeterminate length".into(), + })?; + } + writeln!(output)?; + + fn dump_key<P, R>(pd: &PacketDumper, + output: &mut dyn io::Write, i: &str, + k: &Key<P, R>) + -> Result<()> + where P: key::KeyParts, + R: key::KeyRole, + { + writeln!(output, "{} Version: {}", i, k.version())?; + writeln!(output, "{} Creation time: {}", i, + k.creation_time().convert())?; + writeln!(output, "{} Pk algo: {}", i, k.pk_algo())?; + if let Some(bits) = k.mpis().bits() { + writeln!(output, "{} Pk size: {} bits", i, bits)?; + } + writeln!(output, "{} Fingerprint: {}", i, k.fingerprint())?; + writeln!(output, "{} KeyID: {}", i, k.keyid())?; + if pd.mpis { + writeln!(output, "{}", i)?; + writeln!(output, "{} Public Key:", i)?; + + let ii = format!("{} ", i); + match k.mpis() { + mpi::PublicKey::RSA { e, n } => + pd.dump_mpis(output, &ii, + &[e.value(), n.value()], + &["e", "n"])?, + mpi::PublicKey::DSA { p, q, g, y } => + pd.dump_mpis(output, &ii, + &[p.value(), q.value(), g.value(), + y.value()], + &["p", "q", "g", "y"])?, + mpi::PublicKey::ElGamal { p, g, y } => + pd.dump_mpis(output, &ii, + &[p.value(), g.value(), y.value()], + &["p", "g", "y"])?, + mpi::PublicKey::EdDSA { curve, q } => { + writeln!(output, "{} Curve: {}", ii, curve)?; + pd.dump_mpis(output, &ii, &[q.value()], &["q"])?; + }, + mpi::PublicKey::ECDSA { curve, q } => { + writeln!(output, "{} Curve: {}", ii, curve)?; + pd.dump_mpis(output, &ii, &[q.value()], &["q"])?; + }, + mpi::PublicKey::ECDH { curve, q, hash, sym } => { + writeln!(output, "{} Curve: {}", ii, curve)?; + writeln!(output, "{} Hash algo: {}", ii, hash)?; + writeln!(output, "{} Symmetric algo: {}", ii, + sym)?; + pd.dump_mpis(output, &ii, &[q.value()], &["q"])?; + }, + mpi::PublicKey::Unknown { mpis, rest } => { + let keys: Vec<String> = + (0..mpis.len()).map( + |i| format!("mpi{}", i)).collect(); + pd.dump_mpis( + output, &ii, + &mpis.iter().map(|m| { + m.value().iter().as_slice() + }).collect::<Vec<_>>()[..], + &keys.iter().map(|k| k.as_str()) + .collect::<Vec<_>>()[..], + )?; + + pd.dump_mpis(output, &ii, &[&rest[..]], &["rest"])?; + }, + mpi::PublicKey::__Nonexhaustive => unreachable!(), + } + } + + if let Some(secrets) = k.optional_secret() { + writeln!(output, "{}", i)?; + writeln!(output, "{} Secret Key:", i)?; + + let ii = format!("{} ", i); + match secrets { + SecretKeyMaterial::Unencrypted(ref u) => { + writeln!(output, "{}", i)?; + writeln!(output, "{} Unencrypted", ii)?; + if pd.mpis { + u.map(|mpis| -> Result<()> { + match mpis + { + mpi::SecretKeyMaterial::RSA { d, p, q, u } => + pd.dump_mpis(output, &ii, + &[d.value(), p.value(), + q.value(), u.value()], + &["d", "p", "q", "u"])?, + mpi::SecretKeyMaterial::DSA { x } => + pd.dump_mpis(output, &ii, &[x.value()], + &["x"])?, + mpi::SecretKeyMaterial::ElGamal { x } => + pd.dump_mpis(output, &ii, &[x.value()], + &["x"])?, + mpi::SecretKeyMaterial::EdDSA { scalar } => + pd.dump_mpis(output, &ii, + &[scalar.value()], + &["scalar"])?, + mpi::SecretKeyMaterial::ECDSA { scalar } => + pd.dump_mpis(output, &ii, + &[scalar.value()], + &["scalar"])?, + mpi::SecretKeyMaterial::ECDH { scalar } => + pd.dump_mpis(output, &ii, + &[scalar.value()], + &["scalar"])?, + mpi::SecretKeyMaterial::Unknown { mpis, rest } => { + let keys: Vec<String> = + (0..mpis.len()).map( + |i| format!("mpi{}", i)).collect(); + pd.dump_mpis( + output, &ii, + &mpis.iter().map(|m| { + m.value().iter().as_slice() + }).collect::<Vec<_>>()[..], + &keys.iter().map(|k| k.as_str()) + .collect::<Vec<_>>()[..], + )?; + + pd.dump_mpis(output, &ii, &[rest], + &["rest"])?; + }, + mpi::SecretKeyMaterial::__Nonexhaustive => + unreachable!(), + } + Ok(()) + })?; + } + } + SecretKeyMaterial::Encrypted(ref e) => { + writeln!(output, "{}", i)?; + writeln!(output, "{} Encrypted", ii)?; + write!(output, "{} S2K: ", ii)?; + pd.dump_s2k(output, &ii, e.s2k())?; + writeln!(output, "{} Sym. algo: {}", ii, + e.algo())?; + if pd.mpis { + if let Ok(ciphertext) = e.ciphertext() { + pd.dump_mpis(output, &ii, &[ciphertext], + &["ciphertext"])?; + } + } + }, + } + } + + Ok(()) + } + + match p { + Unknown(ref u) => { + writeln!(output, "{} Tag: {}", i, u.tag())?; + writeln!(output, "{} Error: {}", i, u.error())?; + }, + + PublicKey(ref k) => dump_key(self, output, i, k)?, + PublicSubkey(ref k) => dump_key(self, output, i, k)?, + SecretKey(ref k) => dump_key(self, output, i, k)?, + SecretSubkey(ref k) => dump_key(self, output, i, k)?, + + Signature(ref s) => { + writeln!(output, "{} Version: {}", i, s.version())?; + writeln!(output, "{} Type: {}", i, s.typ())?; + writeln!(output, "{} Pk algo: {}", i, s.pk_algo())?; + writeln!(output, "{} Hash algo: {}", i, s.hash_algo())?; + if s.hashed_area().iter().count() > 0 { + writeln!(output, "{} Hashed area:", i)?; + for pkt in s.hashed_area().iter() { + self.dump_subpacket(output, i, pkt, s)?; + } + } + if s.unhashed_area().iter().count() > 0 { + writeln!(output, "{} Unhashed area:", i)?; + for pkt in s.unhashed_area().iter() { + self.dump_subpacket(output, i, pkt, s)?; + } + } + writeln!(output, "{} Digest prefix: {}", i, + hex::encode(s.digest_prefix()))?; + write!(output, "{} Level: {} ", i, s.level())?; + match s.level() { + 0 => writeln!(output, "(signature over data)")?, + 1 => writeln!(output, "(notarization over signatures \ + level 0 and data)")?, + n => writeln!(output, "(notarization over signatures \ + level <= {} and data)", n - 1)?, + } + if self.mpis { + writeln!(output, "{}", i)?; + writeln!(output, "{} Signature:", i)?; + + let ii = format!("{} ", i); + match s.mpis() { + mpi::Signature::RSA { s } => + self.dump_mpis(output, &ii, + &[s.value()], + &["s"])?, + mpi::Signature::DSA { r, s } => + self.dump_mpis(output, &ii, + &[r.value(), s.value()], + &["r", "s"])?, + mpi::Signature::ElGamal { r, s } => + self.dump_mpis(output, &ii, + &[r.value(), s.value()], + &["r", "s"])?, + mpi::Signature::EdDSA { r, s } => + self.dump_mpis(output, &ii, + &[r.value(), s.value()], + &["r", "s"])?, + mpi::Signature::ECDSA { r, s } => + self.dump_mpis(output, &ii, + &[r.value(), s.value()], + &["r", "s"])?, + mpi::Signature::Unknown { mpis, rest } => { + let keys: Vec<String> = + (0..mpis.len()).map( + |i| format!("mpi{}", i)).collect(); + self.dump_mpis( + output, &ii, + &mpis.iter().map(|m| { + m.value().iter().as_slice() + }).collect::<Vec<_>>()[..], + &keys.iter().map(|k| k.as_str()) + .collect::<Vec<_>>()[..], + )?; + + self.dump_mpis(output, &ii, &[&rest[..]], &["rest"])?; + }, + mpi::Signature::__Nonexhaustive => unreachable!(), + + } + } + }, + + OnePassSig(ref o) => { + writeln!(output, "{} Version: {}", i, o.version())?; + writeln!(output, "{} Type: {}", i, o.typ())?; + writeln!(output, "{} Pk algo: {}", i, o.pk_algo())?; + writeln!(output, "{} Hash algo: {}", i, o.hash_algo())?; + writeln!(output, "{} Issuer: {}", i, o.issuer())?; + writeln!(output, "{} Last: {}", i, o.last())?; + }, + + Trust(ref p) => { + writeln!(output, "{} Value: {}", i, hex::encode(p.value()))?; + }, + + UserID(ref u) => { + writeln!(output, "{} Value: {}", i, + String::from_utf8_lossy(u.value()))?; + }, + + UserAttribute(ref u) => { + use self::openpgp::packet::user_attribute::{Subpacket, Image}; + + for subpacket in u.subpackets() { + match subpacket { + Ok(Subpacket::Image(image)) => match image { + Image::JPEG(data) => + writeln!(output, "{} JPEG: {} bytes", i, + data.len())?, + Image::Private(n, data) => + writeln!(output, + "{} Private image({}): {} bytes", i, + n, data.len())?, + Image::Unknown(n, data) => + writeln!(output, + "{} Unknown image({}): {} bytes", i, + n, data.len())?, + }, + Ok(Subpacket::Unknown(n, data)) => + writeln!(output, + "{} Unknown subpacket({}): {} bytes", i, + n, data.len())?, + Err(e) => + writeln!(output, + "{} Invalid subpacket encoding: {}", i, + e)?, + } + } + }, + + Marker(_) => { + }, + + Literal(ref l) => { + writeln!(output, "{} Format: {}", i, l.format())?; + if let Some(filename) = l.filename() { + writeln!(output, "{} Filename: {}", i, + String::from_utf8_lossy(filename))?; + } + if let Some(timestamp) = l.date() { + writeln!(output, "{} Timestamp: {}", i, + timestamp.convert())?; + } + }, + + CompressedData(ref c) => { + writeln!(output, "{} Algorithm: {}", i, c.algo())?; + }, + + PKESK(ref p) => { + writeln!(output, "{} Version: {}", i, p.version())?; + writeln!(output, "{} Recipient: {}", i, p.recipient())?; + writeln!(output, "{} Pk algo: {}", i, p.pk_algo())?; + if self.mpis { + writeln!(output, "{}", i)?; + writeln!(output, "{} Encrypted session key:", i)?; + + let ii = format!("{} ", i); + match p.esk() { + mpi::Ciphertext::RSA { c } => + self.dump_mpis(output, &ii, + &[c.value()], + &["c"])?, + mpi::Ciphertext::ElGamal { e, c } => + self.dump_mpis(output, &ii, + &[e.value(), c.value()], + &["e", "c"])?, + mpi::Ciphertext::ECDH { e, key } => + self.dump_mpis(output, &ii, + &[e.value(), key], + &["e", "key"])?, + mpi::Ciphertext::Unknown { mpis, rest } => { + let keys: Vec<String> = + (0..mpis.len()).map( + |i| format!("mpi{}", i)).collect(); + self.dump_mpis( + output, &ii, + &mpis.iter().map(|m| { + m.value().iter().as_slice() + }).collect::<Vec<_>>()[..], + &keys.iter().map(|k| k.as_str()) + .collect::<Vec<_>>()[..], + )?; + + self.dump_mpis(output, &ii, &[rest], &["rest"])?; + }, + mpi::Ciphertext::__Nonexhaustive => unreachable!(), + } + } + }, + + SKESK(ref s) => { + writeln!(output, "{} Version: {}", i, s.version())?; + match s { + self::openpgp::packet::SKESK::V4(ref s) => { + writeln!(output, "{} Symmetric algo: {}", i, + s.symmetric_algo())?; + write!(output, "{} S2K: ", i)?; + self.dump_s2k(output, i, s.s2k())?; + if let Ok(Some(esk)) = s.esk() { + writeln!(output, "{} ESK: {}", i, + hex::encode(esk))?; + } + }, + + self::openpgp::packet::SKESK::V5(ref s) => { + writeln!(output, "{} Symmetric algo: {}", i, + s.symmetric_algo())?; + writeln!(output, "{} AEAD: {}", i, + s.aead_algo())?; + write!(output, "{} S2K: ", i)?; + self.dump_s2k(output, i, s.s2k())?; + if let Ok(iv) = s.aead_iv() { + writeln!(output, "{} IV: {}", i, + hex::encode(iv))?; + } + if let Ok(Some(esk)) = s.esk() { + writeln!(output, "{} ESK: {}", i, + hex::encode(esk))?; + } + writeln!(output, "{} Digest: {}", i, + hex::encode(s.aead_digest()))?; + }, + + self::openpgp::packet::SKESK::__Nonexhaustive => + unreachable!(), + } + }, + + SEIP(ref s) => { + writeln!(output, "{} Version: {}", i, s.version())?; + }, + + MDC(ref m) => { + writeln!(output, "{} Digest: {}", + i, hex::encode(m.digest()))?; + writeln!(output, "{} Computed digest: {}", + i, hex::encode(m.computed_digest()))?; + }, + + AED(ref a) => { + writeln!(output, "{} Version: {}", i, a.version())?; + writeln!(output, "{} Symmetric algo: {}", i, a.symmetric_algo())?; + writeln!(output, "{} AEAD: {}", i, a.aead())?; + writeln!(output, "{} Chunk size: {}", i, a.chunk_size())?; + writeln!(output, "{} IV: {}", i, hex::encode(a.iv()))?; + }, + + __Nonexhaustive => unreachable!(), + } + + if let Some(fields) = additional_fields { + for field in fields { + writeln!(output, "{} {}", i, field)?; + } + } + + if let Some(map) = map { + writeln!(output, "{}", i)?; + let mut hd = hex::Dumper::new(output, self.indentation_for_hexdump( + i, map.iter() + .map(|f| if f.name() == "body" { 16 } else { f.name().len() }) + .max() + .expect("we always have one entry"))); + + for field in map.iter() { + if field.name() == "body" { + hd.write_ascii(field.as_bytes())?; + } else { + hd.write(field.as_bytes(), field.name())?; |