From 1215757f2d73083a64b5d8c99d842a2dc28e79ff Mon Sep 17 00:00:00 2001 From: Nora Widdecke Date: Wed, 29 Jul 2020 18:28:10 +0200 Subject: tool: Add merge signatures command. --- sq/src/commands/merge_signatures.rs | 156 ++++++++++++++++++++++++++++++++++++ sq/src/commands/mod.rs | 2 + sq/src/sq.rs | 6 ++ sq/src/sq_cli.rs | 13 +++ 4 files changed, 177 insertions(+) create mode 100644 sq/src/commands/merge_signatures.rs diff --git a/sq/src/commands/merge_signatures.rs b/sq/src/commands/merge_signatures.rs new file mode 100644 index 00000000..4f785bc5 --- /dev/null +++ b/sq/src/commands/merge_signatures.rs @@ -0,0 +1,156 @@ +use anyhow::Context as _; +use std::io; + +extern crate sequoia_openpgp as openpgp; +use crate::create_or_stdout; +use crate::openpgp::packet::Literal; +use crate::openpgp::packet::Tag; +use crate::openpgp::parse::{PacketParser, PacketParserResult, Parse}; +use crate::openpgp::serialize::stream::{LiteralWriter, Message}; +use crate::openpgp::serialize::Serialize; +use crate::openpgp::{Packet, Result}; + +pub fn merge_signatures( + input1: &mut dyn io::Read, + input2: &mut dyn io::Read, + output_path: Option<&str>, +) -> Result<()> { + let parser1 = + PacketParser::from_reader(input1).context("Failed to build parser")?; + let parser2 = + PacketParser::from_reader(input2).context("Failed to build parser")?; + let mut output = create_or_stdout(output_path, false)?; + + let mut sink = Message::new(&mut output); + + let (ops1, post_ops_parser1) = read_while_by_tag(parser1, Tag::OnePassSig)?; + let (ops2, post_ops_parser2) = read_while_by_tag(parser2, Tag::OnePassSig)?; + + let ops1 = ops1 + .into_iter() + .map(ops_with_last_false) + .collect::>>()?; + + write_packets(ops1, &mut sink)?; + write_packets(ops2, &mut sink)?; + + let (sink_new, post_literal_parser1, post_literal_parser2) = + compare_and_write_literal(sink, post_ops_parser1, post_ops_parser2)?; + sink = sink_new; + + let (sigs2, _) = read_while_by_tag(post_literal_parser2, Tag::Signature)?; + let (sigs1, _) = read_while_by_tag(post_literal_parser1, Tag::Signature)?; + write_packets(sigs2, &mut sink)?; + write_packets(sigs1, &mut sink)?; + + sink.finalize().context("Failed to write data")?; + Ok(()) +} + +fn ops_with_last_false(p: Packet) -> Result { + if let Packet::OnePassSig(mut ops) = p { + ops.set_last(false); + Ok(Packet::OnePassSig(ops)) + } else { + Err(anyhow::anyhow!("Not a OnePassSig packet")) + } +} + +fn write_packets(packets: Vec, mut sink: &mut Message) -> Result<()> { + for packet in packets { + packet.serialize(&mut sink)?; + } + Ok(()) +} + +fn compare_and_write_literal<'a, 'b, 'c>( + sink: Message<'a>, + ppr1: PacketParserResult<'b>, + ppr2: PacketParserResult<'c>, +) -> Result<(Message<'a>, PacketParserResult<'b>, PacketParserResult<'c>)> { + // We want to compare the bodies of the literal packets, by comparing their digests. + // Digests are only known after reading the packets, so: + // First, move both parsers past the literal packet, copy out the body of one of them. + // Second, compare the packets which now include the correct hashes, + // normalize to ignore metadata. + let (mut lp1, ppr1) = read_while_by_tag(ppr1, Tag::Literal)?; + let lp1 = lp1.remove(0); + + let (sink, lp2, ppr2) = write_literal_(sink, ppr2)?; + + let lp1 = normalize_literal(lp1)?; + let lp2 = normalize_literal(lp2)?; + eprintln!("lp1: {:?}", lp1); + eprintln!("lp2: {:?}", lp2); + + if lp1 == lp2 { + Ok((sink, ppr1, ppr2)) + } else { + Err(anyhow::anyhow!("Literal Packets differ, aborting!")) + } +} + +// Clear date and filename. +fn normalize_literal(p: Packet) -> Result { + if let Packet::Literal(mut l) = p { + l.set_date(None)?; + l.set_filename(&[])?; + Ok(l) + } else { + Err(anyhow::anyhow!("Not a literal packet")) + } +} + +fn write_literal_<'a, 'b>( + mut sink: Message<'a>, + ppr: PacketParserResult<'b>, +) -> Result<(Message<'a>, Packet, PacketParserResult<'b>)> { + if let PacketParserResult::Some(mut pp) = ppr { + // Assemble a new Literal packet. + // Cannot use packet.serialize because that does not include the body. + if let Packet::Literal(l) = pp.packet.clone() { + // Create a literal writer to wrap the data in a literal + // message packet. + let mut literal = LiteralWriter::new(sink) + .format(l.format()) + .build() + .context("Failed to create literal writer")?; + // Do not add any metadata as it is unprotected anyway. + + // Just copy all the data. + io::copy(&mut pp, &mut literal).context("Failed to copy data")?; + + // Pop the literal writer. + sink = literal + .finalize_one() + .context("Failed to write literal packet")? + .unwrap(); + } + + let foo = pp.recurse()?; //TODO rename + Ok((sink, foo.0, foo.1)) + } else { + Err(anyhow::anyhow!("Unexpected end of file")) + } +} + +fn read_while_by_tag( + mut ppr: PacketParserResult, + tag: Tag, +) -> Result<(Vec, PacketParserResult)> { + let mut result = vec![]; + + while let PacketParserResult::Some(pp) = ppr { + let next_tag_matches = pp.header().ctb().tag() == tag; + if !next_tag_matches { + return Ok((result, PacketParserResult::Some(pp))); + } + + // Start parsing the next packet, recursing. + let (packet, next_ppr) = pp.recurse()?; + ppr = next_ppr; + result.push(packet); + } + + Ok((result, ppr)) +} diff --git a/sq/src/commands/mod.rs b/sq/src/commands/mod.rs index 06110976..59a9914d 100644 --- a/sq/src/commands/mod.rs +++ b/sq/src/commands/mod.rs @@ -38,6 +38,8 @@ pub use self::dump::dump; mod inspect; pub use self::inspect::inspect; pub mod key; +pub mod merge_signatures; +pub use self::merge_signatures::merge_signatures; /// Returns suitable signing keys from a given list of Certs. fn get_signing_keys(certs: &[openpgp::Cert], p: &dyn Policy, diff --git a/sq/src/sq.rs b/sq/src/sq.rs index f93c0370..61970a9c 100644 --- a/sq/src/sq.rs +++ b/sq/src/sq.rs @@ -283,6 +283,12 @@ fn main() -> Result<()> { m.is_present("use-expired-subkey"), )?; }, + ("merge-signatures", Some(m)) => { + let mut input1 = open_or_stdin(m.value_of("input1"))?; + let mut input2 = open_or_stdin(m.value_of("input2"))?; + let output = m.value_of("output"); + commands::merge_signatures(&mut input1, &mut input2, output)?; + }, ("sign", Some(m)) => { let mut input = open_or_stdin(m.value_of("input"))?; let output = m.value_of("output"); diff --git a/sq/src/sq_cli.rs b/sq/src/sq_cli.rs index afdc1b36..50d99e7c 100644 --- a/sq/src/sq_cli.rs +++ b/sq/src/sq_cli.rs @@ -152,6 +152,19 @@ pub fn build() -> App<'static, 'static> { to using the one that expired last")) ) + .subcommand(SubCommand::with_name("merge-signatures") + .display_order(31) + .about("Merges two signatures") + .arg(Arg::with_name("input1").value_name("FILE") + .help("Sets the first input file to use")) + .arg(Arg::with_name("input2").value_name("FILE") + .help("Sets the second input file to use")) + .arg(Arg::with_name("output").value_name("FILE") + .long("output") + .short("o") + .help("Sets the output file to use")) + ) + .subcommand(SubCommand::with_name("sign") .display_order(25) .about("Signs a message") -- cgit v1.2.3