use anyhow::Context as _;
use std::fs;
use std::io;
use std::path::PathBuf;
use tempfile::NamedTempFile;
use buffered_reader::BufferedReader;
use sequoia_openpgp as openpgp;
use openpgp::armor;
use openpgp::crypto;
use openpgp::{Packet, Result};
use openpgp::packet::prelude::*;
use openpgp::packet::signature::subpacket::NotationData;
use openpgp::parse::{
Cookie,
Parse,
PacketParserResult,
};
use openpgp::serialize::Serialize;
use openpgp::serialize::stream::{
Message, Armorer, Signer, LiteralWriter,
};
use openpgp::types::SignatureType;
use crate::Sq;
use crate::cli;
use crate::cli::sign::Mode;
use crate::cli::types::FileOrStdin;
use crate::cli::types::FileOrStdout;
use crate::sq::TrustThreshold;
mod merge_signatures;
use merge_signatures::merge_signatures;
pub fn dispatch(sq: Sq, command: cli::sign::Command) -> Result<()> {
tracer!(TRACE, "sign::dispatch");
let mut input = command.input.open("the data to sign")?;
let output = &command.output;
let detached = &command.detached;
let binary = command.binary;
let append = command.append;
let notarize = command.notarize;
if notarize {
return Err(anyhow::anyhow!("Notarizing messages is not supported."));
}
let signers =
sq.resolve_certs_or_fail(&command.signers, TrustThreshold::Full)?;
let signers = sq.get_signing_keys(&signers, None)?;
if signers.is_empty() && command.merge.is_none() {
return Err(anyhow::anyhow!("No signing keys found"));
}
let notations = command.signature_notations.parse()?;
if let Some(merge) = command.merge {
let output = output.create_pgp_safe(
&sq,
binary,
armor::Kind::Message,
)?;
let data: FileOrStdin = merge.into();
let mut input2 = data.open("OpenPGP signatures")?;
return merge_signatures(&mut input, &mut input2, output);
}
if command.cleartext {
let output = output.create_safe(&sq)?;
clearsign(sq, input, output, signers, ¬ations)?;
} else {
sign(sq,
&mut input,
detached.as_ref().unwrap_or(output),
signers,
command.mode,
detached.is_some(),
binary,
append,
notarize,
¬ations)?;
}
Ok(())
}
pub fn sign<'a, 'store, 'rstore>(
sq: Sq<'store, 'rstore>,
input: &'a mut (dyn BufferedReader<Cookie> + Sync + Send),
output: &'a FileOrStdout,
signers: Vec<(openpgp::Cert, Box<dyn crypto::Signer + Send + Sync>)>,
mode: Mode,
detached: bool,
binary: bool,
append: bool,
notarize: bool,
notations: &'a [(bool, NotationData)])
-> Result<()>
{
match (detached, append|notarize) {
(_, false) | (true, true) =>
sign_data(sq,
input,
output,
signers,
mode,
detached,
binary,
append,
notations),
(false, true) =>
sign_message(sq,
input,
output,
signers,
mode,
binary,
notarize,
notations),
}
}
fn sign_data<'a, 'store, 'rstore>(
sq: Sq<'store, 'rstore>,
input: &'a mut (dyn BufferedReader<Cookie> + Sync + Send),
output_path: &'a FileOrStdout,
mut signers: Vec<(openpgp::Cert, Box<dyn crypto::Signer + Send + Sync>)>,
mode: Mode,
detached: bool,
binary: bool,
append: bool,
notations: &'a [(bool, NotationData)])
-> Result<()>
{
let (mut output, prepend_sigs, tmp_path):
(Box<dyn io::Write + Sync + Send>, Vec<Signature>, Option<PathBuf>) =
if detached && append && output_path.path().is_some() {
let output_path = output_path.path().unwrap();
let mut sigs = Vec::new();
let mut ppr =
openpgp::parse::PacketParser::from_file(output_path)?;
while let PacketParserResult::Some(pp) = ppr {
let (packet, ppr_tmp) = pp.recurse()?;
ppr = ppr_tmp;
match packet {
Packet::Signature(sig) => sigs.push(sig),
p => return Err(
anyhow::anyhow!(
format!("{} in detached signature", p.tag()))
.context("Invalid detached signature")),
}
}
let tmp_file = NamedTempFile::new_in(
PathBuf::from(output_path).parent()
.unwrap_or(&PathBuf::from(".")))?;
let tmp_path = tmp_file.path().into();
(Box::new(tmp_file), sigs, Some(tmp_path))
} else {
(output_path.create_safe(&sq)?, Vec::new(), None)
};
let mut message = Message::new(&mut output);
if ! binary {
message = Armorer::new(message)
.kind(if detached {
armor::Kind::Signature
} else {
armor::Kind::Message
})
.build()?;
}
for sig in prepend_sigs.into_iter() {
Packet::Signature(sig).serialize(&mut message)?;
}
let mut builder = SignatureBuilder::new(mode.into());
for (critical, n) in notations.iter() {
builder = builder.add_notation(
n.name(),
n.value(),
Some(n.flags().clone()),
*critical)?;
}
let mut signer = Signer::with_template(
message,
signers.pop().unwrap().1,
builder)?;
signer = signer.creation_time(sq.time);
for s in signers {
signer = signer.add_signer(s.1)?;
}
if detached {
signer = signer.detached();
}
let signer = signer.build().context("Failed to create signer")?;
let mut writer = if detached {
signer
} else {
LiteralWriter::new(signer).build()
.context("Failed to create literal writer")?
};
input.copy(&mut writer)
.context("Failed to sign")?;
writer.finalize()
.context("Failed to sign")?;
if let Some(path) = tmp_path {
fs::rename(
path,
output_path.path().expect("must be Some if tmp_path is Some"),
)?;
}
Ok(())
}
fn sign_message<'a, 'store, 'rstore>(
sq: Sq<'store, 'rstore>,
input: &'a mut (dyn BufferedReader<Cookie> + Sync + Send),
output: &'a FileOrStdout,
signers: Vec<(openpgp::Cert, Box<dyn crypto::Signer + Send + Sync>)>,
mode: Mode,
binary: bool,
notarize: bool,
notations: &'a [(bool, NotationData)])
-> Result<()>
{
let mut output = output.create_pgp_safe(
&sq,
binary,
armor::Kind::Message,
)?;
sign_message_(sq,
input,
signers,
mode,
notarize,
notations,
&mut output)?;
output.finalize()?;
Ok(())
}
fn sign_message_<'a, 'store, 'rstore>(
sq: Sq<'store, 'rstore>,
input: &'a mut (dyn BufferedReader<Cookie> + Sync + Send),
mut signers: Vec<(openpgp::Cert, Box<dyn crypto::Signer + Send + Sync>)>,
mode: Mode,
notarize: bool,
notations: &'a [(bool, NotationData)],
output: &mut (dyn io::Write + Sync + Send))
-> Result<()>
{
let mut sink = Message::new(output);
let mut ppr
= openpgp::parse::PacketParser::from_reader(input)
.context("Failed to build parser")?;
let mut seen_signature = false;
#[derive(PartialEq, Eq, Debug)]
enum State {
InFirstSigGroup,
AfterFirstSigGroup,
Signing {
signature_count: isize,
},
Done,
}
let mut state =
if ! notarize {
State::InFirstSigGroup
} else {
State::AfterFirstSigGroup
};
while let PacketParserResult::Some(mut pp) = ppr {
if let Err(err) = pp.possible_message() {
return Err(err.context("Malformed OpenPGP message"));
}
match pp.packet {
Packet::PKESK(_) | Packet::SKESK(_) =>
return Err(anyhow::anyhow!(
"Signing encrypted data is not implemented")),
Packet::Literal(_) =>
if let State::InFirstSigGroup = state {
state = State::AfterFirstSigGroup;
},
Packet::CompressedData(_) if seen_signature =>
return Err(anyhow::anyhow!(
"Signing a compress-then-sign message is not implemented")),
_ => (),
}
match state {
State::AfterFirstSigGroup => {
let mut builder = SignatureBuilder::new(mode.into());
for (critical, n) in notations.iter() {
builder = builder.add_notation(
n.name(),
n.value(),
Some(n.flags().clone()),
*critical)?;
}
let mut signer = Signer::with_template(
sink, signers.pop().unwrap().1, builder)?;
signer = signer.creation_time(sq.time);
for s in signers.drain(..) {
signer = signer.add_signer(s.1)?;
}
sink = signer.build().context("Failed to create signer")?;
state = State::Signing { signature_count: 0, };
},
State::Signing { signature_count } if signature_count == 0 => {
sink = sink.finalize_one()
.context("Failed to sign data")?
.unwrap();
state = State::Done;
},
_ => (),
}
if let Packet::Literal(_) = pp.packet {
let l = if let Packet::Literal(l) = pp.packet.clone() {
l
} else {
unreachable!()
};
let mut literal = LiteralWriter::new(sink).format(l.format());
if let Some(f) = l.filename() {
literal = literal.filename(f)?;
}
if let Some(d) = l.date() {
literal = literal.date(d)?;
}
let mut literal = literal.build()
.context("Failed to create literal writer")?;
pp.copy(&mut literal)
.context("Failed to sign data")?;
sink = literal.finalize_one()
.context("Failed to sign data")?
.unwrap();
}
let (packet, ppr_tmp) = if seen_signature {
pp.next()
} else {
pp.recurse()
}.context("Parsing failed")?;
ppr = ppr_tmp;
match packet {
Packet::OnePassSig(mut ops) => {
let was_last = ops.last();
match state {
State::InFirstSigGroup => {
ops.set_last(false);
if was_last {
state = State::AfterFirstSigGroup;
}
},
State::Signing { ref mut signature_count } =>
*signature_count += 1,
_ => (),
}
Packet::OnePassSig(ops).serialize(&mut sink)?;
seen_signature = true;
},
Packet::Signature(sig) => {
Packet::Signature(sig).serialize(&mut sink)
.context("Failed to serialize")?;
if let State::Signing { ref mut signature_count } = state {
*signature_count -= 1;
}
},
_ => (),
}
}
if let PacketParserResult::EOF(eof) = ppr {
if let Err(err) = eof.is_message() {
return Err(err.context("Malformed OpenPGP message"));
}
} else {
unreachable!()
}
match state {
State::Signing { signature_count } => {
assert_eq!(signature_count, 0);
sink.finalize()
.context("Failed to sign data")?;
},
State::Done => (),
_ => panic!("Unexpected state: {:?}", state),
}
Ok(())
}
pub fn clearsign(sq: Sq,
mut input: impl BufferedReader<Cookie> + Sync + Send,
mut output: impl io::Write + Sync + Send,
mut signers: Vec<(openpgp::Cert, Box<dyn crypto::Signer + Send + Sync>)>,
notations: &[(bool, NotationData)])
-> Result<()>
{
let mut builder = SignatureBuilder::new(SignatureType::Text);
for (critical, n) in notations.iter() {
builder = builder.add_notation(
n.name(),
n.value(),
Some(n.flags().clone()),
*critical)?;
}
let message = Message::new(&mut output);
let mut signer = Signer::with_template(
message, signers.pop().unwrap().1, builder)?
.cleartext();
signer = signer.creation_time(sq.time);
for s in signers {
signer = signer.add_signer(s.1)?;
}
let mut message = signer.build().context("Failed to create signer")?;
input.copy(&mut message)
.context("Failed to sign")?;
message.finalize()
.context("Failed to sign")?;
Ok(())
}