diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a444663..77733ed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -181,13 +181,9 @@ jobs: zipsign gen-key priv.key pub.key tar czf Cargo.lock.tgz Cargo.lock - zipsign sign tar -o Cargo.lock.signed.tgz Cargo.lock.tgz priv.key - rm Cargo.lock.tgz - mv Cargo.lock.signed.tgz Cargo.lock.tgz + zipsign sign tar Cargo.lock.tgz priv.key zipsign verify tar Cargo.lock.tgz pub.key zip Cargo.lock.zip Cargo.lock - zipsign sign zip -o Cargo.lock.signed.zip Cargo.lock.zip priv.key - rm Cargo.lock.zip - mv Cargo.lock.signed.zip Cargo.lock.zip + zipsign sign zip Cargo.lock.zip priv.key zipsign verify zip Cargo.lock.zip pub.key diff --git a/Cargo.lock b/Cargo.lock index a4f9675..e8e9b56 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -62,6 +62,18 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + [[package]] name = "block-buffer" version = "0.10.4" @@ -77,6 +89,15 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -251,6 +272,33 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fastrand" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" + [[package]] name = "fiat-crypto" version = "0.2.1" @@ -306,12 +354,24 @@ version = "0.2.148" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +[[package]] +name = "linux-raw-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" + [[package]] name = "memchr" version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" +[[package]] +name = "normalize-path" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5438dd2b2ff4c6df6e1ce22d825ed2fa93ee2922235cc45186991717f0a892d" + [[package]] name = "once_cell" version = "1.18.0" @@ -391,6 +451,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "rustc_version" version = "0.4.0" @@ -400,6 +469,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.38.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "semver" version = "1.0.18" @@ -479,6 +561,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys", +] + [[package]] name = "thiserror" version = "1.0.48" @@ -644,8 +739,10 @@ version = "0.1.0-a.1" dependencies = [ "clap", "ed25519-dalek", + "normalize-path", "pretty-error-debug", "rand_core", + "tempfile", "thiserror", "zipsign-api", ] diff --git a/Cargo.toml b/Cargo.toml index c73e57b..c599041 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,8 +16,10 @@ default-members = [".", "api"] base64 = "0.21" clap = { version = "4", features = ["derive"] } ed25519-dalek = { version = "2", features = ["digest"] } +normalize-path = "0.2" pretty-error-debug = "0.3" rand_core = { version = "0.6", features = ["getrandom"] } +tempfile = "3" thiserror = "1" zip = { version = "0.6", default-features = false } @@ -30,8 +32,10 @@ features = ["verify-tar", "verify-zip", "sign-tar", "sign-zip"] [dependencies] clap.workspace = true ed25519-dalek = { workspace = true, features = ["rand_core"] } +normalize-path.workspace = true pretty-error-debug.workspace = true rand_core.workspace = true +tempfile.workspace = true thiserror.workspace = true zipsign-api.workspace = true diff --git a/README.md b/README.md index 18a6372..2c24af4 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,7 @@ cargo install --git https://github.com/Kijewski/zipsign Cargo.lock # Sign the ZIP file: - $ zipsign sign zip -o Cargo.lock.signed.zip Cargo.lock.zip priv.key - $ mv Cargo.lock.signed.zip Cargo.lock.zip + $ zipsign sign zip Cargo.lock.zip priv.key $ unzip -l Cargo.lock.zip Cargo.lock @@ -48,8 +47,7 @@ cargo install --git https://github.com/Kijewski/zipsign Cargo.lock # Sign the .tar.gz file: - $ zipsign sign tar -o Cargo.lock.signed.tgz Cargo.lock.tgz priv.key - $ mv Cargo.lock.signed.tgz Cargo.lock.tgz + $ zipsign sign tar Cargo.lock.tgz priv.key $ tar tzf Cargo.lock.tgz Cargo.lock @@ -74,7 +72,7 @@ Options: ### Sign a .zip or .tar.gz file -Usage: `zipsign sign [zip|tar] -o ...` +Usage: `zipsign sign [zip|tar] [-o ] ...` Subcommands: @@ -83,7 +81,7 @@ Subcommands: Options: -* `-o`, `--output `: Signed file to generate +* `-o`, `--output `: Signed file to generate (if omitted, the input is overwritten) * `-c`, `--context `: Arbitrary string used to salt the input, defaults to file name of `` * `-f`, `--force`: Overwrite output file if it exists diff --git a/src/sign.rs b/src/sign.rs index c7cbf27..b66f9f2 100644 --- a/src/sign.rs +++ b/src/sign.rs @@ -1,8 +1,9 @@ -use std::fs::{File, OpenOptions}; +use std::fs::{rename, File}; use std::io::Write; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use clap::{Args, Parser, Subcommand}; +use normalize_path::NormalizePath; use zipsign_api::prehash; use zipsign_api::sign::{ copy_and_sign_tar, copy_and_sign_zip, gather_signature_data, read_signing_keys, @@ -51,9 +52,9 @@ enum ArchiveKind { struct CommonArgs { /// Input file to sign input: PathBuf, - /// Signed file to generate + /// Signed file to generate (if omitted, the input is overwritten) #[arg(long, short = 'o')] - output: PathBuf, + output: Option, /// One or more files containing private keys #[arg(required = true)] keys: Vec, @@ -75,14 +76,16 @@ pub(crate) enum Error { InputOpen(#[source] std::io::Error), #[error("could not read input")] InputRead(#[source] std::io::Error), - #[error("could not open or create output file")] - OutputOpen(#[source] std::io::Error), + #[error("could not rename output file")] + OutputRename(#[source] std::io::Error), #[error("could not write to output")] OutputWrite(#[source] std::io::Error), #[error("could not read signing keys")] ReadSigningKeys(#[from] ReadSigningKeysError), #[error("could not copy and sign the input")] Tar(#[from] SignTarError), + #[error("could not create temporary file in output directory")] + Tempfile(#[source] std::io::Error), #[error("could not copy and sign the input")] Zip(#[from] SignZipError), } @@ -95,28 +98,31 @@ pub(crate) fn main(args: Cli) -> Result<(), Error> { let keys = args.keys.into_iter().map(File::open); let keys = read_signing_keys(keys)?; - let mut input = File::open(&args.input).map_err(Error::InputOpen)?; - let mut output = OpenOptions::new() - .create(true) - .create_new(!args.force) - .read(true) - .write(true) - .truncate(true) - .open(&args.output) - .map_err(Error::OutputOpen)?; + let output_path = args.output.as_deref().unwrap_or(&args.input).normalize(); + let output_dir = output_path.parent().unwrap_or(Path::new(".")); + let mut output_file = tempfile::Builder::new() + .prefix(".zipsign.") + .suffix(".tmp") + .tempfile_in(output_dir) + .map_err(Error::Tempfile)?; + let mut input = File::open(&args.input).map_err(Error::InputOpen)?; match kind { ArchiveKind::Separate => { let prehashed_message = prehash(&mut input).map_err(Error::InputRead)?; let data = gather_signature_data(&keys, &prehashed_message, Some(context))?; - output.write_all(&data).map_err(Error::OutputWrite)?; + output_file.write_all(&data).map_err(Error::OutputWrite)?; }, ArchiveKind::Zip => { - copy_and_sign_zip(&mut input, &mut output, &keys, Some(context))?; + copy_and_sign_zip(&mut input, &mut output_file, &keys, Some(context))?; }, ArchiveKind::Tar => { - copy_and_sign_tar(&mut input, &mut output, &keys, Some(context))?; + copy_and_sign_tar(&mut input, &mut output_file, &keys, Some(context))?; }, } + // drop input so it can be overwritten input=output + drop(input); + + rename(output_file.into_temp_path(), output_path).map_err(Error::OutputRename)?; Ok(()) }