diff options
Diffstat (limited to 'sq/src/commands/merge_signatures.rs')
-rw-r--r-- | sq/src/commands/merge_signatures.rs | 156 |
1 files changed, 156 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..d7d38bb7 --- /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 + Send + Sync), + input2: &mut (dyn io::Read + Send + Sync), + 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)) +} |