summaryrefslogtreecommitdiffstats
path: root/tool
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2018-12-18 14:52:43 +0100
committerJustus Winter <justus@sequoia-pgp.org>2018-12-18 14:52:43 +0100
commit4c9346dce753e7fcefd543d2e91e59753c00f816 (patch)
treeb68ade7e12a5e86f52cac2ed32f904390c465341 /tool
parentcdeea6688be0e690d28cda946d51f8cb0e137452 (diff)
tool: Move the signing code to a new module.
Diffstat (limited to 'tool')
-rw-r--r--tool/src/commands/mod.rs305
-rw-r--r--tool/src/commands/sign.rs312
2 files changed, 316 insertions, 301 deletions
diff --git a/tool/src/commands/mod.rs b/tool/src/commands/mod.rs
index f3a6d044..dc7b624d 100644
--- a/tool/src/commands/mod.rs
+++ b/tool/src/commands/mod.rs
@@ -1,19 +1,15 @@
use failure::{self, ResultExt};
use std::cmp::Ordering;
use std::collections::{HashMap, HashSet};
-use std::fs::{self, File};
+use std::fs::File;
use std::io::{self, Write};
-use std::path::PathBuf;
use time;
use rpassword;
-use tempfile::NamedTempFile;
extern crate sequoia_openpgp as openpgp;
use sequoia_core::Context;
-use openpgp::armor;
use openpgp::constants::DataFormat;
-use openpgp::{Packet, TPK, KeyID, Error, Result};
-use openpgp::packet::Signature;
+use openpgp::{TPK, KeyID, Result};
use openpgp::parse::{
Parse,
PacketParserResult,
@@ -21,16 +17,15 @@ use openpgp::parse::{
use openpgp::parse::stream::{
Verifier, DetachedVerifier, VerificationResult, VerificationHelper,
};
-use openpgp::serialize::Serialize;
use openpgp::serialize::stream::{
Message, Signer, LiteralWriter, Encryptor, EncryptionMode,
};
extern crate sequoia_store as store;
-use super::create_or_stdout;
-
mod decrypt;
pub use self::decrypt::decrypt;
+mod sign;
+pub use self::sign::sign;
mod dump;
pub use self::dump::dump;
@@ -94,298 +89,6 @@ pub fn encrypt(store: &mut store::Store,
Ok(())
}
-pub fn sign(input: &mut io::Read, output_path: Option<&str>,
- secrets: Vec<openpgp::TPK>, detached: bool, binary: bool,
- append: bool, notarize: bool, force: bool)
- -> Result<()> {
- match (detached, append|notarize) {
- (_, false) | (true, true) =>
- sign_data(input, output_path, secrets, detached, binary, append,
- force),
- (false, true) =>
- sign_message(input, output_path, secrets, binary, notarize, force),
- }
-}
-
-fn sign_data(input: &mut io::Read, output_path: Option<&str>,
- secrets: Vec<openpgp::TPK>, detached: bool, binary: bool,
- append: bool, force: bool)
- -> Result<()> {
- let (mut output, prepend_sigs, tmp_path):
- (Box<io::Write>, Vec<Signature>, Option<PathBuf>) =
- if detached && append && output_path.is_some() {
- // First, read the existing signatures.
- let mut sigs = Vec::new();
- let mut ppr =
- openpgp::parse::PacketParser::from_file(output_path.unwrap())?;
-
- while let PacketParserResult::Some(mut pp) = ppr {
- let (packet, ppr_tmp) = pp.recurse()?;
- ppr = ppr_tmp;
-
- match packet {
- Packet::Signature(sig) => sigs.push(sig),
- p => return Err(
- failure::err_msg(
- format!("{} in detached signature", p.tag()))
- .context("Invalid detached signature").into()),
- }
- }
-
- // Then, create a temporary file to write to. If we are
- // successful with adding our signature(s), we rename the
- // file replacing the old one.
- 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 output = if ! binary {
- Box::new(armor::Writer::new(&mut output,
- if detached {
- armor::Kind::Signature
- } else {
- armor::Kind::Message
- },
- &[])?)
- } else {
- output
- };
-
- // When extending a detached signature, prepend any existing
- // signatures first.
- for sig in prepend_sigs {
- sig.serialize(&mut output)?;
- }
-
- // Stream an OpenPGP message.
- let sink = Message::new(output);
-
- // Build a vector of references to hand to Signer.
- let keys: Vec<&openpgp::TPK> = secrets.iter().collect();
- let signer = if detached {
- Signer::detached(sink, &keys)
- } else {
- Signer::new(sink, &keys)
- }.context("Failed to create signer")?;
-
- let mut writer = if detached {
- // Detached signatures do not need a literal data packet, just
- // hash the data as is.
- signer
- } else {
- // We want to wrap the data in a literal data packet.
- LiteralWriter::new(signer, DataFormat::Binary, None, None)
- .context("Failed to create literal writer")?
- };
-
- // Finally, copy stdin to our writer stack to sign the data.
- io::copy(input, &mut writer)
- .context("Failed to sign")?;
-
- writer.finalize()
- .context("Failed to sign")?;
-
- if let Some(path) = tmp_path {
- // Atomically replace the old file.
- fs::rename(path,
- output_path.expect("must be Some if tmp_path is Some"))?;
- }
- Ok(())
-}
-
-fn sign_message(input: &mut io::Read, output_path: Option<&str>,
- secrets: Vec<openpgp::TPK>, binary: bool, notarize: bool,
- force: bool)
- -> Result<()> {
- let mut output = create_or_stdout(output_path, force)?;
- let output = if ! binary {
- Box::new(armor::Writer::new(&mut output,
- armor::Kind::Message,
- &[])?)
- } else {
- output
- };
-
- let mut sink = Message::new(output);
- // Build a vector of references to hand to Signer.
- let keys: Vec<&openpgp::TPK> = secrets.iter().collect();
-
- // Create a parser for the message to be notarized.
- let mut ppr
- = openpgp::parse::PacketParser::from_reader(input)
- .context("Failed to build parser")?;
-
- // Once we see a signature, we can no longer strip compression.
- let mut seen_signature = false;
- #[derive(PartialEq, Eq, Debug)]
- enum State {
- InFirstSigGroup,
- AfterFirstSigGroup,
- Signing {
- // Counts how many signatures are being notarized. If
- // this drops to zero, we pop the signer from the stack.
- signature_count: isize,
- },
- Done,
- };
- let mut state =
- if ! notarize {
- State::InFirstSigGroup
- } else {
- // Pretend we have passed the first signature group so
- // that we put our signature first.
- State::AfterFirstSigGroup
- };
-
- while let PacketParserResult::Some(mut pp) = ppr {
- if ! pp.possible_message() {
- return Err(Error::MalformedMessage(
- "Malformed OpenPGP message".into()).into());
- }
-
- match pp.packet {
- Packet::PKESK(_) | Packet::SKESK(_) =>
- return Err(failure::err_msg(
- "Signing encrypted data is not implemented")),
-
- Packet::Literal(_) =>
- if let State::InFirstSigGroup = state {
- // Cope with messages that have no signatures, or
- // with a ops packet without the last flag.
- state = State::AfterFirstSigGroup;
- },
-
- // To implement this, we'd need to stream the
- // compressed data packet inclusive framing, but
- // currently the partial body filter transparently
- // removes the framing.
- //
- // If you do implement this, there is a half-disabled test
- // in tests/sq-sign.rs.
- Packet::CompressedData(_) if seen_signature =>
- return Err(failure::err_msg(
- "Signing a compress-then-sign message is not implemented")),
-
- _ => (),
- }
-
- match state {
- State::AfterFirstSigGroup => {
- // After the first signature group, we push the signer
- // onto the writer stack.
- sink = Signer::new(sink, &keys)
- .context("Failed to create signer")?;
- state = State::Signing { signature_count: 0, };
- },
-
- State::Signing { signature_count } if signature_count == 0 => {
- // All signatures that are being notarized are
- // written, pop the signer from the writer stack.
- 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!()
- };
- // Create a literal writer to wrap the data in a literal
- // message packet.
- let mut literal =
- LiteralWriter::new(sink, l.format(), l.filename(),
- l.date().map(|d| *d))
- .context("Failed to create literal writer")?;
-
- // Finally, just copy all the data.
- io::copy(&mut pp, &mut literal)
- .context("Failed to sign data")?;
-
- // Pop the literal writer.
- sink = literal.finalize_one()
- .context("Failed to sign data")?
- .unwrap();
- }
-
- let (packet, ppr_tmp) = if seen_signature {
- // Once we see a signature, we can no longer strip
- // compression.
- 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 => {
- // We want to append our signature here, hence
- // we set last to false.
- ops.set_last(false);
-
- if was_last {
- // The signature group ends here.
- state = State::AfterFirstSigGroup;
- }
- },
-
- State::Signing { ref mut signature_count } =>
- *signature_count += 1,
-
- _ => (),
- }
-
- ops.serialize(&mut sink)?;
- seen_signature = true;
- },
-
- Packet::Signature(ref sig) => {
- 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 ! eof.is_message() {
- return Err(Error::MalformedMessage(
- "Malformed OpenPGP message".into()).into());
- }
- } else {
- unreachable!()
- }
-
- match state {
- State::Signing { signature_count } => {
- assert_eq!(signature_count, 0);
- sink.finalize_one()
- .context("Failed to sign data")?
- .unwrap();
- },
- State::Done => (),
- _ => panic!("Unexpected state: {:?}", state),
- }
-
- Ok(())
-}
-
struct VHelper<'a> {
ctx: &'a Context,
store: &'a mut store::Store,
diff --git a/tool/src/commands/sign.rs b/tool/src/commands/sign.rs
new file mode 100644
index 00000000..00d2de33
--- /dev/null
+++ b/tool/src/commands/sign.rs
@@ -0,0 +1,312 @@
+use failure::{self, ResultExt};
+use std::fs;
+use std::io;
+use std::path::PathBuf;
+use tempfile::NamedTempFile;
+
+extern crate sequoia_openpgp as openpgp;
+use openpgp::armor;
+use openpgp::constants::DataFormat;
+use openpgp::{Packet, Error, Result};
+use openpgp::packet::Signature;
+use openpgp::parse::{
+ Parse,
+ PacketParserResult,
+};
+use openpgp::serialize::Serialize;
+use openpgp::serialize::stream::{
+ Message, Signer, LiteralWriter,
+};
+use create_or_stdout;
+
+pub fn sign(input: &mut io::Read, output_path: Option<&str>,
+ secrets: Vec<openpgp::TPK>, detached: bool, binary: bool,
+ append: bool, notarize: bool, force: bool)
+ -> Result<()> {
+ match (detached, append|notarize) {
+ (_, false) | (true, true) =>
+ sign_data(input, output_path, secrets, detached, binary, append,
+ force),
+ (false, true) =>
+ sign_message(input, output_path, secrets, binary, notarize, force),
+ }
+}
+
+fn sign_data(input: &mut io::Read, output_path: Option<&str>,
+ secrets: Vec<openpgp::TPK>, detached: bool, binary: bool,
+ append: bool, force: bool)
+ -> Result<()> {
+ let (mut output, prepend_sigs, tmp_path):
+ (Box<io::Write>, Vec<Signature>, Option<PathBuf>) =
+ if detached && append && output_path.is_some() {
+ // First, read the existing signatures.
+ let mut sigs = Vec::new();
+ let mut ppr =
+ openpgp::parse::PacketParser::from_file(output_path.unwrap())?;
+
+ while let PacketParserResult::Some(mut pp) = ppr {
+ let (packet, ppr_tmp) = pp.recurse()?;
+ ppr = ppr_tmp;
+
+ match packet {
+ Packet::Signature(sig) => sigs.push(sig),
+ p => return Err(
+ failure::err_msg(
+ format!("{} in detached signature", p.tag()))
+ .context("Invalid detached signature").into()),
+ }
+ }
+
+ // Then, create a temporary file to write to. If we are
+ // successful with adding our signature(s), we rename the
+ // file replacing the old one.
+ 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 output = if ! binary {
+ Box::new(armor::Writer::new(&mut output,
+ if detached {
+ armor::Kind::Signature
+ } else {
+ armor::Kind::Message
+ },
+ &[])?)
+ } else {
+ output
+ };
+
+ // When extending a detached signature, prepend any existing
+ // signatures first.
+ for sig in prepend_sigs {
+ sig.serialize(&mut output)?;
+ }
+
+ // Stream an OpenPGP message.
+ let sink = Message::new(output);
+
+ // Build a vector of references to hand to Signer.
+ let keys: Vec<&openpgp::TPK> = secrets.iter().collect();
+ let signer = if detached {
+ Signer::detached(sink, &keys)
+ } else {
+ Signer::new(sink, &keys)
+ }.context("Failed to create signer")?;
+
+ let mut writer = if detached {
+ // Detached signatures do not need a literal data packet, just
+ // hash the data as is.
+ signer
+ } else {
+ // We want to wrap the data in a literal data packet.
+ LiteralWriter::new(signer, DataFormat::Binary, None, None)
+ .context("Failed to create literal writer")?
+ };
+
+ // Finally, copy stdin to our writer stack to sign the data.
+ io::copy(input, &mut writer)
+ .context("Failed to sign")?;
+
+ writer.finalize()
+ .context("Failed to sign")?;
+
+ if let Some(path) = tmp_path {
+ // Atomically replace the old file.
+ fs::rename(path,
+ output_path.expect("must be Some if tmp_path is Some"))?;
+ }
+ Ok(())
+}
+
+fn sign_message(input: &mut io::Read, output_path: Option<&str>,
+ secrets: Vec<openpgp::TPK>, binary: bool, notarize: bool,
+ force: bool)
+ -> Result<()> {
+ let mut output = create_or_stdout(output_path, force)?;
+ let output = if ! binary {
+ Box::new(armor::Writer::new(&mut output,
+ armor::Kind::Message,
+ &[])?)
+ } else {
+ output
+ };
+
+ let mut sink = Message::new(output);
+ // Build a vector of references to hand to Signer.
+ let keys: Vec<&openpgp::TPK> = secrets.iter().collect();
+
+ // Create a parser for the message to be notarized.
+ let mut ppr
+ = openpgp::parse::PacketParser::from_reader(input)
+ .context("Failed to build parser")?;
+
+ // Once we see a signature, we can no longer strip compression.
+ let mut seen_signature = false;
+ #[derive(PartialEq, Eq, Debug)]
+ enum State {
+ InFirstSigGroup,
+ AfterFirstSigGroup,
+ Signing {
+ // Counts how many signatures are being notarized. If
+ // this drops to zero, we pop the signer from the stack.
+ signature_count: isize,
+ },
+ Done,
+ };
+ let mut state =
+ if ! notarize {
+ State::InFirstSigGroup
+ } else {
+ // Pretend we have passed the first signature group so
+ // that we put our signature first.
+ State::AfterFirstSigGroup
+ };
+
+ while let PacketParserResult::Some(mut pp) = ppr {
+ if ! pp.possible_message() {
+ return Err(Error::MalformedMessage(
+ "Malformed OpenPGP message".into()).into());
+ }
+
+ match pp.packet {
+ Packet::PKESK(_) | Packet::SKESK(_) =>
+ return Err(failure::err_msg(
+ "Signing encrypted data is not implemented")),
+
+ Packet::Literal(_) =>
+ if let State::InFirstSigGroup = state {
+ // Cope with messages that have no signatures, or
+ // with a ops packet without the last flag.
+ state = State::AfterFirstSigGroup;
+ },
+
+ // To implement this, we'd need to stream the
+ // compressed data packet inclusive framing, but
+ // currently the partial body filter transparently
+ // removes the framing.
+ //
+ // If you do implement this, there is a half-disabled test
+ // in tests/sq-sign.rs.
+ Packet::CompressedData(_) if seen_signature =>
+ return Err(failure::err_msg(
+ "Signing a compress-then-sign message is not implemented")),
+
+ _ => (),
+ }
+
+ match state {
+ State::AfterFirstSigGroup => {
+ // After the first signature group, we push the signer
+ // onto the writer stack.
+ sink = Signer::new(sink, &keys)
+ .context("Failed to create signer")?;
+ state = State::Signing { signature_count: 0, };
+ },
+
+ State::Signing { signature_count } if signature_count == 0 => {
+ // All signatures that are being notarized are
+ // written, pop the signer from the writer stack.
+ 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!()
+ };
+ // Create a literal writer to wrap the data in a literal
+ // message packet.
+ let mut literal =
+ LiteralWriter::new(sink, l.format(), l.filename(),
+ l.date().map(|d| *d))
+ .context("Failed to create literal writer")?;
+
+ // Finally, just copy all the data.
+ io::copy(&mut pp, &mut literal)
+ .context("Failed to sign data")?;
+
+ // Pop the literal writer.
+ sink = literal.finalize_one()
+ .context("Failed to sign data")?
+ .unwrap();
+ }
+
+ let (packet, ppr_tmp) = if seen_signature {
+ // Once we see a signature, we can no longer strip
+ // compression.
+ 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 => {
+ // We want to append our signature here, hence
+ // we set last to false.
+ ops.set_last(false);
+
+ if was_last {
+ // The signature group ends here.
+ state = State::AfterFirstSigGroup;
+ }
+ },
+
+ State::Signing { ref mut signature_count } =>
+ *signature_count += 1,
+
+ _ => (),
+ }
+
+ ops.serialize(&mut sink)?;
+ seen_signature = true;
+ },
+
+ Packet::Signature(ref sig) => {
+ 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 ! eof.is_message() {
+ return Err(Error::MalformedMessage(
+ "Malformed OpenPGP message".into()).into());
+ }
+ } else {
+ unreachable!()
+ }
+
+ match state {
+ State::Signing { signature_count } => {
+ assert_eq!(signature_count, 0);
+ sink.finalize_one()
+ .context("Failed to sign data")?
+ .unwrap();
+ },
+ State::Done => (),
+ _ => panic!("Unexpected state: {:?}", state),
+ }
+
+ Ok(())
+}