diff options
Diffstat (limited to 'tool')
-rw-r--r-- | tool/src/commands/mod.rs | 305 | ||||
-rw-r--r-- | tool/src/commands/sign.rs | 312 |
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(()) +} |