use anyhow::Context as _;
use std::fs;
use std::io;
use std::path::PathBuf;
use std::time::SystemTime;
use tempfile::NamedTempFile;
use sequoia_openpgp as openpgp;
use crate::openpgp::armor;
use crate::openpgp::{Packet, Result};
use crate::openpgp::packet::prelude::*;
use crate::openpgp::packet::signature::subpacket::NotationData;
use crate::openpgp::parse::{
Parse,
PacketParserResult,
};
use crate::openpgp::serialize::Serialize;
use crate::openpgp::serialize::stream::{
Message, Armorer, Signer, LiteralWriter,
};
use crate::openpgp::policy::Policy;
use crate::openpgp::types::SignatureType;
use crate::{
create_or_stdout,
create_or_stdout_pgp,
};
pub fn sign(policy: &dyn Policy,
input: &mut (dyn io::Read + Sync + Send),
output_path: Option<&str>,
secrets: Vec<openpgp::Cert>, detached: bool, binary: bool,
append: bool, notarize: bool, time: Option<SystemTime>,
notations: &[(bool, NotationData)],
force: bool)
-> Result<()> {
match (detached, append|notarize) {
(_, false) | (true, true) =>
sign_data(policy, input, output_path, secrets, detached, binary,
append, time, notations, force),
(false, true) =>
sign_message(policy, input, output_path, secrets, binary, notarize,
time, notations, force),
}
}
fn sign_data(policy: &dyn Policy,
input: &mut dyn io::Read, output_path: Option<&str>,
secrets: Vec<openpgp::Cert>, detached: bool, binary: bool,
append: bool, time: Option<SystemTime>,
notations: &[(bool, NotationData)],
force: bool)
-> Result<()> {
let (mut output, prepend_sigs, tmp_path):
(Box<dyn io::Write + Sync + Send>, Vec<Signature>, Option<PathBuf>) =
if detached && append && output_path.is_some() {
let mut sigs = Vec::new();
let mut ppr =
openpgp::parse::PacketParser::from_file(output_path.unwrap())?;
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").into()),
}
}
let tmp_file = NamedTempFile::new_in(
PathBuf::from(output_path.unwrap()).parent()
.unwrap_or(&PathBuf::from(".")))?;
let tmp_path = tmp_file.path().into();
(Box::new(tmp_file), sigs, Some(tmp_path))
} else {
(create_or_stdout(output_path, force)?, Vec::new(), None)
};
let mut keypairs = super::get_signing_keys(&secrets, policy, time)?;
if keypairs.is_empty() {
return Err(anyhow::anyhow!("No signing keys found"));
}
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(SignatureType::Binary);
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, keypairs.pop().unwrap(), builder);
for s in keypairs {
signer = signer.add_signer(s);
if let Some(time) = time {
signer = signer.creation_time(time);
}
}
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")?
};
io::copy(input, &mut writer)
.context("Failed to sign")?;
writer.finalize()
.context("Failed to sign")?;
if let Some(path) = tmp_path {
fs::rename(path,
output_path.expect("must be Some if tmp_path is Some"))?;
}
Ok(())
}
fn sign_message(policy: &dyn Policy,
input: &mut (dyn io::Read + Sync + Send),
output_path: Option<&str>,
secrets: Vec<openpgp::Cert>, binary: bool, notarize: bool,
time: Option<SystemTime>,
notations: &[(bool, NotationData)],
force: bool)
-> Result<()> {
let mut output =
create_or_stdout_pgp(output_path, force,
binary,
armor::Kind::Message)?;
sign_message_(policy, input, &mut output, secrets, notarize, time, notations)?;
output.finalize()?;
Ok(())
}
fn sign_message_(policy: &dyn Policy,
input: &mut (dyn io::Read + Sync + Send),
output: &mut (dyn io::Write + Sync + Send),
secrets: Vec<openpgp::Cert>, notarize: bool,
time: Option<SystemTime>,
notations: &[(bool, NotationData)])
-> Result<()>
{
let mut keypairs = super::get_signing_keys(&secrets, policy, time)?;
if keypairs.is_empty() {
return Err(anyhow::anyhow!("No signing keys found"));
}
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").into());
}
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(SignatureType::Binary);
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, keypairs.pop().unwrap(), builder);
for s in keypairs.drain(..) {
signer = signer.add_signer(s);
if let Some(time) = time {
signer = signer.creation_time(time);
}
}
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")?;
io::copy(&mut pp, &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").into());
}
} 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(())
}