summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNora Widdecke <nora@sequoia-pgp.org>2020-07-29 18:28:10 +0200
committerJustus Winter <justus@sequoia-pgp.org>2020-12-17 14:08:25 +0100
commit1215757f2d73083a64b5d8c99d842a2dc28e79ff (patch)
tree9878cc8a716b7405e29a31659662923e20a2637c
parent7d10b8245cb42d0482a51a3037861a26fe2a39f6 (diff)
tool: Add merge signatures command.nora/merge_signatures
-rw-r--r--sq/src/commands/merge_signatures.rs156
-rw-r--r--sq/src/commands/mod.rs2
-rw-r--r--sq/src/sq.rs6
-rw-r--r--sq/src/sq_cli.rs13
4 files changed, 177 insertions, 0 deletions
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::<Result<Vec<_>>>()?;
+
+ 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<Packet> {
+ 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<Packet>, 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<Literal> {
+ 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<Packet>, 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")