feat: update sign command to support signing multiple .zst files
This commit is contained in:
+3
-2
@@ -102,8 +102,9 @@ pub enum Commands {
|
|||||||
List,
|
List,
|
||||||
/// Create a detached minisign signature for a .zst file
|
/// Create a detached minisign signature for a .zst file
|
||||||
Sign {
|
Sign {
|
||||||
/// Path to the .zst file to sign
|
/// One or more .zst files to sign
|
||||||
file: PathBuf,
|
#[arg(value_name = "FILE", required = true, num_args = 1..)]
|
||||||
|
files: Vec<PathBuf>,
|
||||||
},
|
},
|
||||||
/// Repository management
|
/// Repository management
|
||||||
Repo {
|
Repo {
|
||||||
|
|||||||
+4
-2
@@ -1591,13 +1591,15 @@ pub fn run(cli: Cli) -> Result<()> {
|
|||||||
let db_path = config.db_dir.join("packages.db");
|
let db_path = config.db_dir.join("packages.db");
|
||||||
db::list_packages(&db_path)?;
|
db::list_packages(&db_path)?;
|
||||||
}
|
}
|
||||||
Commands::Sign { file } => {
|
Commands::Sign { files } => {
|
||||||
let sig_path = signing::sign_zst_file_detached(&cli.rootfs, &file)?;
|
let sig_paths = signing::sign_zst_files_detached(&cli.rootfs, &files)?;
|
||||||
|
for sig_path in sig_paths {
|
||||||
ui::success(format!(
|
ui::success(format!(
|
||||||
"Created detached signature: {}",
|
"Created detached signature: {}",
|
||||||
sig_path.display()
|
sig_path.display()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Commands::Repo { command } => match command {
|
Commands::Repo { command } => match command {
|
||||||
RepoCommands::Create { dir } => {
|
RepoCommands::Create { dir } => {
|
||||||
let cfg = config::Config::for_rootfs(&cli.rootfs);
|
let cfg = config::Config::for_rootfs(&cli.rootfs);
|
||||||
|
|||||||
+89
-19
@@ -1,7 +1,7 @@
|
|||||||
//! Minisign-based detached signature helpers.
|
//! Minisign-based detached signature helpers.
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use minisign::{PublicKey, SecretKeyBox, SignatureBox};
|
use minisign::{PublicKey, SecretKey, SecretKeyBox, SignatureBox};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
@@ -155,17 +155,12 @@ fn detached_sig_path(input: &Path) -> PathBuf {
|
|||||||
PathBuf::from(format!("{}.sig", input.display()))
|
PathBuf::from(format!("{}.sig", input.display()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sign_detached_with_key_paths(input: &Path, sig_path: &Path, keys: &KeyLocations) -> Result<()> {
|
struct SigningMaterial {
|
||||||
if !input.exists() {
|
secret_key: SecretKey,
|
||||||
anyhow::bail!("File not found: {}", input.display());
|
public_key: Option<PublicKey>,
|
||||||
}
|
}
|
||||||
if !is_zst_file(input) {
|
|
||||||
anyhow::bail!(
|
|
||||||
"Signing command currently only supports .zst files: {}",
|
|
||||||
input.display()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
fn load_signing_material(keys: &KeyLocations) -> Result<SigningMaterial> {
|
||||||
let signing_key_path = keys.signing_key.as_ref().with_context(
|
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)",
|
|| "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
|
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 =
|
let file =
|
||||||
fs::File::open(input).with_context(|| format!("Failed to open {}", input.display()))?;
|
fs::File::open(input).with_context(|| format!("Failed to open {}", input.display()))?;
|
||||||
let sig = minisign::sign(
|
let sig = minisign::sign(
|
||||||
public_key.as_ref(),
|
signing_material.public_key.as_ref(),
|
||||||
&secret_key,
|
&signing_material.secret_key,
|
||||||
file,
|
file,
|
||||||
None,
|
None,
|
||||||
Some(&format!("depot signature for {}", input.display())),
|
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)
|
verify_detached_with_key_paths(input, sig_path, &keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sign a `.zst` file with a detached minisign signature written to `<file>.sig`.
|
/// Sign one or more `.zst` files with detached minisign signatures written to `<file>.sig`.
|
||||||
pub fn sign_zst_file_detached(rootfs: &Path, input: &Path) -> Result<PathBuf> {
|
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 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);
|
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(sig_path)
|
sig_paths.push(sig_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(sig_paths)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempt to sign a `.zst` file using discovered keys; skip if no signing key exists.
|
/// 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);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
let signing_material = load_signing_material(&keys)?;
|
||||||
let sig_path = detached_sig_path(input);
|
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))
|
Ok(Some(sig_path))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,8 +375,9 @@ mod tests {
|
|||||||
public_key: Some(pub_path.clone()),
|
public_key: Some(pub_path.clone()),
|
||||||
signing_key: Some(sign_path.clone()),
|
signing_key: Some(sign_path.clone()),
|
||||||
};
|
};
|
||||||
|
let signing_material = load_signing_material(&keys)?;
|
||||||
let sig_path = PathBuf::from(format!("{}.sig", file.display()));
|
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());
|
assert!(sig_path.exists());
|
||||||
|
|
||||||
let pk = PublicKey::from_file(&pub_path)?;
|
let pk = PublicKey::from_file(&pub_path)?;
|
||||||
@@ -364,6 +393,47 @@ mod tests {
|
|||||||
Ok(())
|
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]
|
#[test]
|
||||||
fn list_trusted_public_keys_finds_multiple_pub_files() -> Result<()> {
|
fn list_trusted_public_keys_finds_multiple_pub_files() -> Result<()> {
|
||||||
let rootfs = tempfile::tempdir()?;
|
let rootfs = tempfile::tempdir()?;
|
||||||
|
|||||||
Reference in New Issue
Block a user