Skip to content

Commit

Permalink
Add functions to remove signatures
Browse files Browse the repository at this point in the history
  • Loading branch information
Kijewski committed Oct 31, 2023
1 parent 0b02fa5 commit 9faefd5
Show file tree
Hide file tree
Showing 16 changed files with 428 additions and 172 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,10 @@ jobs:
tar czf Cargo.lock.tgz Cargo.lock
zipsign sign tar Cargo.lock.tgz priv.key
zipsign verify tar Cargo.lock.tgz pub.key
zipsign unsign tar Cargo.lock.tgz
# Windows doesn't have a "zip" command
jar -cfM Cargo.lock.zip Cargo.lock
zipsign sign zip Cargo.lock.zip priv.key
zipsign verify zip Cargo.lock.zip pub.key
zipsign unsign zip Cargo.lock.zip
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ Options:
* `-o`, `--output <OUTPUT>`: Signed file to generate (if omitted, the input is overwritten)
* `-c`, `--context <CONTEXT>`: Arbitrary string used to salt the input, defaults to file name of `<INPUT>`
* `-f`, `--force`: Overwrite output file if it exists
* `-f`, `--force`: Overwrite output file if it exists
Arguments:
Expand All @@ -115,6 +115,24 @@ Arguments:
* `<INPUT>`: Signed `.zip` or `.tar.gz` file
* `<KEYS>...`: One or more files containing verifying keys

### Remove signatures

Usage: `zipsign unsign [zip|tar] [-o <OUTPUT>] <INPUT>`

Subcommands:

* `zip`: Removed signatures from `.zip` file
* `tar`: Removed signatures from `.tar.gz` file

Arguments:

* `<INPUT>`: Signed `.zip` or `.tar.gz` file

Options:

* `-o`, `--output <OUTPUT>`: Unsigned file to generate (if omitted, the input is overwritten)
* `-f`, `--force`: Overwrite output file if it exists

### How does it work?

The files are signed with one or more private keys using [ed25519ph](https://datatracker.ietf.org/doc/html/rfc8032#section-5.1).
Expand Down
7 changes: 5 additions & 2 deletions api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@ default = ["tar", "zip"]
verify-tar = ["dep:base64"]
verify-zip = []

unsign-tar = ["dep:base64"]
unsign-zip = ["dep:zip"]

sign-tar = ["dep:base64"]
sign-zip = ["dep:zip"]

tar = ["sign-tar", "verify-tar"]
zip = ["sign-zip", "verify-zip"]
tar = ["sign-tar", "unsign-tar", "verify-tar"]
zip = ["sign-zip", "unsign-zip", "verify-zip"]

[package.metadata.docs.rs]
all-features = true
Expand Down
4 changes: 2 additions & 2 deletions api/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub(crate) type SignatureCountLeInt = u16;
/// Followed by base64 encoded signatures string, the current stream position before this block
/// encoded as zero-padded 16 bytes hexadecimal string, and [`GZIP_END`]
/// [`GZIP_END`]
#[cfg(any(feature = "sign-tar", feature = "verify-tar"))]
#[cfg(any(feature = "sign-tar", feature = "unsign-tar", feature = "verify-tar"))]
pub(crate) const GZIP_START: &[u8; 10] = {
const EPOCH: u32 = 978_307_200; // 2001-01-01 00:00:00 Z

Expand All @@ -31,7 +31,7 @@ pub(crate) const GZIP_START: &[u8; 10] = {
};

/// Suffix of the signature block in a signed .tar.gz file
#[cfg(any(feature = "sign-tar", feature = "verify-tar"))]
#[cfg(any(feature = "sign-tar", feature = "unsign-tar", feature = "verify-tar"))]
pub(crate) const GZIP_END: &[u8; 14] = &[
0x00, // deflate: NUL terminator, end of comments
0x01, // deflate: block header (final block, uncompressed)
Expand Down
24 changes: 19 additions & 5 deletions api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#![warn(missing_docs)]
#![warn(non_ascii_idents)]
#![warn(noop_method_call)]
#![warn(rust_2018_idioms)]
#![warn(rust_2021_idioms)]
#![warn(single_use_lifetimes)]
#![warn(trivial_casts)]
#![warn(unreachable_pub)]
Expand All @@ -23,7 +23,12 @@

mod constants;
pub mod sign;
#[cfg(any(feature = "sign-zip", feature = "unsign-zip"))]
mod sign_unsign_zip;
pub mod unsign;
pub mod verify;
#[cfg(any(feature = "verify-tar", feature = "unsign-tar"))]
mod verify_unsign_tar;

use std::fmt;
use std::io::{self, Read};
Expand Down Expand Up @@ -108,6 +113,15 @@ pub enum ZipsignError {
#[cfg_attr(docsrs, doc(cfg(feature = "verify-zip")))]
VerifyZip(#[from] self::verify::VerifyZipError),

/// An error returned by [`copy_and_unsign_tar()`][self::unsign::copy_and_unsign_tar]
#[cfg(feature = "unsign-tar")]
#[cfg_attr(docsrs, doc(cfg(feature = "unsign-tar")))]
UnsignTar(#[from] self::unsign::UnsignTarError),
/// An error returned by [`copy_and_unsign_zip()`][self::unsign::copy_and_unsign_zip]
#[cfg(feature = "unsign-zip")]
#[cfg_attr(docsrs, doc(cfg(feature = "unsign-zip")))]
UnsignZip(#[from] self::unsign::UnsignZipError),

/// An I/O occurred
Io(#[from] io::Error),
}
Expand All @@ -124,7 +138,7 @@ macro_rules! Error {
),+ $(,)? }
) => {
$(#[$meta])+
$vis struct $outer($inner);
$vis struct $outer(Box<$inner>);

#[derive(Debug, thiserror::Error)]
enum $inner { $(
Expand All @@ -138,21 +152,21 @@ macro_rules! Error {
impl std::fmt::Debug for $outer {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(&self.0, f)
std::fmt::Debug::fmt(&*self.0, f)
}
}

impl std::fmt::Display for $outer {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.0, f)
std::fmt::Display::fmt(&*self.0, f)
}
}

impl From<$inner> for $outer {
#[inline]
fn from(value: $inner) -> Self {
Self(value)
Self(Box::new(value))
}
}

Expand Down
4 changes: 2 additions & 2 deletions api/src/sign/tar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ pub fn copy_and_sign_tar<I, O>(
context: Option<&[u8]>,
) -> Result<(), SignTarError>
where
I: ?Sized + Read + Seek,
O: ?Sized + Read + Seek + Write,
I: ?Sized + Seek + Read,
O: ?Sized + Seek + Write,
{
if keys.len() > SignatureCountLeInt::MAX as usize {
return Err(Error::TooManyKeys.into());
Expand Down
49 changes: 7 additions & 42 deletions api/src/sign/zip.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
#![cfg_attr(docsrs, doc(cfg(feature = "sign-zip")))]

use std::io::{BufReader, BufWriter, IoSlice, Read, Seek, SeekFrom, Write};

use zip::result::ZipError;
use zip::{ZipArchive, ZipWriter};
use std::io::{IoSlice, Read, Seek, SeekFrom, Write};

use super::{gather_signature_data, GatherSignatureDataError};
use crate::constants::{SignatureCountLeInt, BUF_LIMIT, HEADER_SIZE};
use crate::sign_unsign_zip::{copy_zip, CopyZipError};
use crate::{Prehash, SigningKey, SIGNATURE_LENGTH};

crate::Error! {
/// An error returned by [`copy_and_sign_zip()`]
pub struct SignZipError(Error) {
#[error("could not read input ZIP")]
InputZip(#[source] ZipError),
#[error("could not read file #{1} inside input ZIP")]
InputZipIndex(#[source] ZipError, usize),
#[error("could not copy ZIP data")]
Copy(#[source] CopyZipError),
#[error("could not write to output, device full?")]
OutputFull,
#[error("could not read output")]
Expand All @@ -24,10 +20,6 @@ crate::Error! {
OutputSeek(#[source] std::io::Error),
#[error("could not write to output")]
OutputWrite(#[source] std::io::Error),
#[error("could not write ZIP file #{1} to output")]
OutputZip(#[source] ZipError, usize),
#[error("could not finish writing output ZIP")]
OutputZipFinish(#[source] ZipError),
#[error("could not gather signature data")]
Sign(#[source] GatherSignatureDataError),
#[error("too many keys")]
Expand Down Expand Up @@ -56,7 +48,7 @@ where

// copy ZIP
write_padding(signature_bytes, output)?;
copy_zip(input, output)?;
copy_zip(input, output).map_err(Error::Copy)?;

// gather signature
let _ = output
Expand All @@ -67,9 +59,8 @@ where

// write signature
output.rewind().map_err(Error::OutputSeek)?;
output
.write_all(&buf)
.map_err(|err| SignZipError(Error::OutputWrite(err)))
output.write_all(&buf).map_err(Error::OutputWrite)?;
Ok(())
}

fn write_padding<O>(mut padding_to_write: usize, output: &mut O) -> Result<(), Error>
Expand All @@ -95,29 +86,3 @@ where
}
Ok(())
}

fn copy_zip<I, O>(input: &mut I, output: &mut O) -> Result<(), Error>
where
I: ?Sized + Read + Seek,
O: ?Sized + Write + Seek,
{
let mut input = ZipArchive::new(BufReader::new(input)).map_err(Error::InputZip)?;
let mut output = ZipWriter::new(BufWriter::new(output));

output.set_raw_comment(input.comment().to_owned());
for idx in 0..input.len() {
let file = input
.by_index_raw(idx)
.map_err(|err| Error::InputZipIndex(err, idx))?;
output
.raw_copy_file(file)
.map_err(|err| Error::OutputZip(err, idx))?;
}
output
.finish()
.map_err(Error::OutputZipFinish)?
.flush()
.map_err(Error::OutputWrite)?;

Ok(())
}
44 changes: 44 additions & 0 deletions api/src/sign_unsign_zip.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use std::io::{BufReader, BufWriter, Read, Seek, Write};

use zip::result::ZipError;
use zip::{ZipArchive, ZipWriter};

#[derive(Debug, thiserror::Error)]
pub(crate) enum CopyZipError {
#[error("could not read input ZIP")]
InputZip(#[source] ZipError),
#[error("could not read file #{1} inside input ZIP")]
InputZipIndex(#[source] ZipError, usize),
#[error("could not write to output")]
OutputWrite(#[source] std::io::Error),
#[error("could not write ZIP file #{1} to output")]
OutputZip(#[source] ZipError, usize),
#[error("could not finish writing output ZIP")]
OutputZipFinish(#[source] ZipError),
}

pub(crate) fn copy_zip<I, O>(input: &mut I, output: &mut O) -> Result<(), CopyZipError>
where
I: ?Sized + Seek + Read,
O: ?Sized + Seek + Write,
{
let mut input = ZipArchive::new(BufReader::new(input)).map_err(CopyZipError::InputZip)?;
let mut output = ZipWriter::new(BufWriter::new(output));

output.set_raw_comment(input.comment().to_owned());
for idx in 0..input.len() {
let file = input
.by_index_raw(idx)
.map_err(|err| CopyZipError::InputZipIndex(err, idx))?;
output
.raw_copy_file(file)
.map_err(|err| CopyZipError::OutputZip(err, idx))?;
}
output
.finish()
.map_err(CopyZipError::OutputZipFinish)?
.flush()
.map_err(CopyZipError::OutputWrite)?;

Ok(())
}
11 changes: 11 additions & 0 deletions api/src/unsign/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//! Functions to remove signatures from a file

#[cfg(feature = "unsign-tar")]
mod tar;
#[cfg(feature = "unsign-zip")]
mod zip;

#[cfg(feature = "unsign-tar")]
pub use self::tar::{copy_and_unsign_tar, UnsignTarError};
#[cfg(feature = "unsign-zip")]
pub use self::zip::{copy_and_unsign_zip, UnsignZipError};
42 changes: 42 additions & 0 deletions api/src/unsign/tar.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#![cfg_attr(docsrs, doc(cfg(feature = "unsign-tar")))]

use std::io::{copy, Read, Seek, Write};

use crate::verify_unsign_tar::{
tar_find_data_start_and_len, tar_read_signatures, TarFindDataStartAndLenError,
TarReadSignaturesError,
};

crate::Error! {
/// An error returned by [`copy_and_unsign_tar()`]
pub struct UnsignTarError(Error) {
#[error("could not copy data")]
Copy(#[source] std::io::Error),
#[error("could not find read signatures in .tar.gz file")]
FindDataStartAndLen(#[source] TarFindDataStartAndLenError),
#[error("could not find read signatures in .tar.gz file")]
ReadSignatures(#[source] TarReadSignaturesError),
#[error("could not seek inside the input")]
Seek(#[source] std::io::Error),
}
}

/// Copy a signed `.tar.gz` file without the signatures
pub fn copy_and_unsign_tar<I, O>(input: &mut I, output: &mut O) -> Result<(), UnsignTarError>
where
I: ?Sized + Seek + Read,
O: ?Sized + Seek + Write,
{
// seek to start of base64 encoded signatures
let (data_start, data_len) =
tar_find_data_start_and_len(input).map_err(Error::FindDataStartAndLen)?;

// read base64 encoded signatures
let _ = tar_read_signatures(data_start, data_len, input).map_err(Error::ReadSignatures)?;

// copy data
input.rewind().map_err(Error::Seek)?;
let _ = copy(&mut input.take(data_start), output).map_err(Error::Copy)?;

Ok(())
}
23 changes: 23 additions & 0 deletions api/src/unsign/zip.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#![cfg_attr(docsrs, doc(cfg(feature = "unsign-zip")))]

use std::io::{Read, Seek, Write};

use crate::sign_unsign_zip::{copy_zip, CopyZipError};

crate::Error! {
/// An error returned by [`copy_and_unsign_zip()`]
pub struct UnsignZipError(Error) {
#[error("could not copy ZIP data")]
Copy(#[source] CopyZipError),
}
}

/// Copy a signed `.zip` file without the signatures
pub fn copy_and_unsign_zip<I, O>(input: &mut I, output: &mut O) -> Result<(), UnsignZipError>
where
I: ?Sized + Read + Seek,
O: ?Sized + Read + Seek + Write,
{
copy_zip(input, output).map_err(Error::Copy)?;
Ok(())
}
Loading

0 comments on commit 9faefd5

Please sign in to comment.