feat: update sign command to support signing multiple .zst files

This commit is contained in:
2026-02-28 15:19:17 -06:00
parent 1f22d22887
commit d954ca8660
3 changed files with 101 additions and 28 deletions
+3 -2
View File
@@ -102,8 +102,9 @@ pub enum Commands {
List,
/// Create a detached minisign signature for a .zst file
Sign {
/// Path to the .zst file to sign
file: PathBuf,
/// One or more .zst files to sign
#[arg(value_name = "FILE", required = true, num_args = 1..)]
files: Vec<PathBuf>,
},
/// Repository management
Repo {
+4 -2
View File
@@ -1591,13 +1591,15 @@ pub fn run(cli: Cli) -> Result<()> {
let db_path = config.db_dir.join("packages.db");
db::list_packages(&db_path)?;
}
Commands::Sign { file } => {
let sig_path = signing::sign_zst_file_detached(&cli.rootfs, &file)?;
Commands::Sign { files } => {
let sig_paths = signing::sign_zst_files_detached(&cli.rootfs, &files)?;
for sig_path in sig_paths {
ui::success(format!(
"Created detached signature: {}",
sig_path.display()
));
}
}
Commands::Repo { command } => match command {
RepoCommands::Create { dir } => {
let cfg = config::Config::for_rootfs(&cli.rootfs);
+88 -18
View File
@@ -1,7 +1,7 @@
//! Minisign-based detached signature helpers.
use anyhow::{Context, Result};
use minisign::{PublicKey, SecretKeyBox, SignatureBox};
use minisign::{PublicKey, SecretKey, SecretKeyBox, SignatureBox};
use std::fs;
use std::path::{Path, PathBuf};
@@ -155,17 +155,12 @@ fn detached_sig_path(input: &Path) -> PathBuf {
PathBuf::from(format!("{}.sig", input.display()))
}
fn sign_detached_with_key_paths(input: &Path, sig_path: &Path, keys: &KeyLocations) -> Result<()> {
if !input.exists() {
anyhow::bail!("File not found: {}", input.display());
}
if !is_zst_file(input) {
anyhow::bail!(
"Signing command currently only supports .zst files: {}",
input.display()
);
struct SigningMaterial {
secret_key: SecretKey,
public_key: Option<PublicKey>,
}
fn load_signing_material(keys: &KeyLocations) -> Result<SigningMaterial> {
let signing_key_path = keys.signing_key.as_ref().with_context(
|| "No minisign signing key found in /usr/share/depot/keys/sign (checked rootfs and host)",
)?;
@@ -204,11 +199,32 @@ fn sign_detached_with_key_paths(input: &Path, sig_path: &Path, keys: &KeyLocatio
None
};
Ok(SigningMaterial {
secret_key,
public_key,
})
}
fn sign_detached_with_material(
input: &Path,
sig_path: &Path,
signing_material: &SigningMaterial,
) -> Result<()> {
if !input.exists() {
anyhow::bail!("File not found: {}", input.display());
}
if !is_zst_file(input) {
anyhow::bail!(
"Signing command currently only supports .zst files: {}",
input.display()
);
}
let file =
fs::File::open(input).with_context(|| format!("Failed to open {}", input.display()))?;
let sig = minisign::sign(
public_key.as_ref(),
&secret_key,
signing_material.public_key.as_ref(),
&signing_material.secret_key,
file,
None,
Some(&format!("depot signature for {}", input.display())),
@@ -273,12 +289,23 @@ pub fn verify_zst_file_detached_with_public_key(
verify_detached_with_key_paths(input, sig_path, &keys)
}
/// Sign a `.zst` file with a detached minisign signature written to `<file>.sig`.
pub fn sign_zst_file_detached(rootfs: &Path, input: &Path) -> Result<PathBuf> {
/// Sign one or more `.zst` files with detached minisign signatures written to `<file>.sig`.
pub fn sign_zst_files_detached(rootfs: &Path, inputs: &[PathBuf]) -> Result<Vec<PathBuf>> {
if inputs.is_empty() {
anyhow::bail!("No input files provided for signing");
}
let keys = locate_keys(rootfs)?;
let signing_material = load_signing_material(&keys)?;
let mut sig_paths = Vec::with_capacity(inputs.len());
for input in inputs {
let sig_path = detached_sig_path(input);
sign_detached_with_key_paths(input, &sig_path, &keys)?;
Ok(sig_path)
sign_detached_with_material(input, &sig_path, &signing_material)?;
sig_paths.push(sig_path);
}
Ok(sig_paths)
}
/// Attempt to sign a `.zst` file using discovered keys; skip if no signing key exists.
@@ -294,8 +321,9 @@ pub fn auto_sign_zst_file_detached(rootfs: &Path, input: &Path) -> Result<Option
);
return Ok(None);
}
let signing_material = load_signing_material(&keys)?;
let sig_path = detached_sig_path(input);
sign_detached_with_key_paths(input, &sig_path, &keys)?;
sign_detached_with_material(input, &sig_path, &signing_material)?;
Ok(Some(sig_path))
}
@@ -347,8 +375,9 @@ mod tests {
public_key: Some(pub_path.clone()),
signing_key: Some(sign_path.clone()),
};
let signing_material = load_signing_material(&keys)?;
let sig_path = PathBuf::from(format!("{}.sig", file.display()));
sign_detached_with_key_paths(&file, &sig_path, &keys)?;
sign_detached_with_material(&file, &sig_path, &signing_material)?;
assert!(sig_path.exists());
let pk = PublicKey::from_file(&pub_path)?;
@@ -364,6 +393,47 @@ mod tests {
Ok(())
}
#[test]
fn sign_zst_files_detached_signs_multiple_files() -> Result<()> {
let rootfs = tempfile::tempdir()?;
let first = rootfs.path().join("first.depot.pkg.tar.zst");
let second = rootfs.path().join("second.depot.pkg.tar.zst");
let mut first_file = fs::File::create(&first)?;
first_file.write_all(b"first payload")?;
first_file.flush()?;
let mut second_file = fs::File::create(&second)?;
second_file.write_all(b"second payload")?;
second_file.flush()?;
let (pub_path, _) = write_test_keys(rootfs.path())?;
let inputs = vec![first.clone(), second.clone()];
let sig_paths = sign_zst_files_detached(rootfs.path(), &inputs)?;
assert_eq!(sig_paths.len(), 2);
assert_eq!(
sig_paths[0],
PathBuf::from(format!("{}.sig", first.display()))
);
assert_eq!(
sig_paths[1],
PathBuf::from(format!("{}.sig", second.display()))
);
assert!(sig_paths[0].exists());
assert!(sig_paths[1].exists());
let pk = PublicKey::from_file(pub_path)?;
for (input, sig_path) in inputs.iter().zip(sig_paths.iter()) {
let sig_box = SignatureBox::from_file(sig_path)?;
let mut reader = fs::File::open(input)?;
minisign::verify(&pk, &sig_box, &mut reader, true, false, false)
.context("signature verification should succeed")?;
}
Ok(())
}
#[test]
fn list_trusted_public_keys_finds_multiple_pub_files() -> Result<()> {
let rootfs = tempfile::tempdir()?;