Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# assure line feeds don't interfere with our working copy hash
*.sh text eol=lf
justfile text eol=lf
/tests/tools/src/signature/fixtures/ssh-* text eol=lf

# have GitHub include fixture-making scripts when it counts code
**/tests/fixtures/**/*.sh -linguist-vendored
Expand Down
4 changes: 2 additions & 2 deletions gix-object/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ path = "./benches/edit_tree.rs"

[features]
## Enable commit and annotated-tag signing and signature verification with external programs.
signature = ["dep:gix-command", "dep:gix-tempfile"]
signature = ["dep:gix-command", "dep:gix-path", "dep:gix-tempfile"]
## Enable support for the SHA-1 hash by enabling the respective feature in the `gix-hash` crate.
sha1 = ["gix-hash/sha1"]
## Enable support for the SHA-256 hash by enabling the respective feature in the `gix-hash` crate.
Expand All @@ -51,6 +51,7 @@ gix-actor = { version = "^0.42.0", path = "../gix-actor" }
gix-date = { version = "^0.16.0", path = "../gix-date" }
gix-utils = { version = "^0.3.6", path = "../gix-utils" }
gix-command = { version = "^0.10.1", path = "../gix-command", optional = true }
gix-path = { version = "^0.12.6", path = "../gix-path", optional = true }
gix-tempfile = { version = "^24.0.0", path = "../gix-tempfile", optional = true }

itoa = "1.0.17"
Expand All @@ -71,7 +72,6 @@ gix-object = { path = ".", features = ["signature", "sha1", "sha256"] }
gix-hash = { path = "../gix-hash", features = ["bstr"] }
gix-testtools = { path = "../tests/tools", default-features = false }
gix-odb = { path = "../gix-odb" }
gix-path = { path = "../gix-path" }
termtree = "1.0.0"
criterion = "0.8.2"
pretty_assertions = "1.0.0"
Expand Down
6 changes: 6 additions & 0 deletions gix-object/src/signature/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ pub mod sign;
#[cfg(feature = "signature")]
pub mod verify;

#[cfg(feature = "signature")]
fn ssh_path_argument(path: &std::path::Path) -> std::path::PathBuf {
// The mixed `C:/…` form works with native Windows and Git for Windows' MSYS OpenSSH.
gix_path::from_bstring(gix_path::to_unix_separators_on_windows(gix_path::into_bstr(path)).into_owned())
}

/// A borrowed armored signature and its detected format.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SignatureRef<'a> {
Expand Down
3 changes: 2 additions & 1 deletion gix-object/src/signature/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ fn sign_ssh(payload: &[u8], options: &Options) -> Result<BString, Error> {
// Unlike literal keys, resolved key paths can be passed directly to `ssh-keygen -f`.
None => (options.signing_key.clone(), false),
};
let key = super::ssh_path_argument(std::path::Path::new(&key));
let mut payload_file = secure_temporary_file()?;
write_temporary(&mut payload_file, payload)?;
let payload_path = temporary_path(&mut payload_file)?;
Expand All @@ -177,7 +178,7 @@ fn sign_ssh(payload: &[u8], options: &Options) -> Result<BString, Error> {
command = command.arg("-U");
}
let output = command
.arg(&payload_path)
.arg(super::ssh_path_argument(&payload_path))
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
Expand Down
4 changes: 3 additions & 1 deletion gix-object/src/signature/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,9 @@ impl SignedData<'_> {
.map_err(|err| Error::CommitTime(Box::new(err)))?;
let verify_time = format!("-Overify-time={verify_time}");
let mut signature_file = signature_file(signature)?;
let signature_path = signature_path(&mut signature_file)?;
let signature_path = super::ssh_path_argument(&signature_path(&mut signature_file)?);
let allowed_signers = super::ssh_path_argument(&allowed_signers);
let revocation_file = revocation_file.map(|path| super::ssh_path_argument(&path));
// defensive, as we rely on English when parsing output.
environment.extend([("LANG".into(), "C".into()), ("LC_ALL".into(), "C".into())]);
let common = (
Expand Down
13 changes: 12 additions & 1 deletion gix-path/src/env/auxiliary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,18 @@ mod tests {
///
/// Tests are expected to run with a full Git for Windows installation (not MinGit).
const SHOULD_FIND: &[&str] = &[
"sh", "bash", "dash", "diff", "tar", "less", "sed", "awk", "perl", "cygpath",
"sh",
"bash",
"dash",
"diff",
"tar",
"less",
"sed",
"awk",
"perl",
"cygpath",
"gpg",
"ssh-keygen",
];

/// Shouldn't find anything nonexistent, or only in PATH or in `bin`s we don't mean to search.
Expand Down
9 changes: 8 additions & 1 deletion gix/src/commit/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,14 @@ fn signature_program(
Format::Ssh => (config.trusted_path(gpg::Ssh::PROGRAM)?, &gpg::Ssh::PROGRAM),
};
Ok(program
.unwrap_or_else(|| gix_path::from_bstr(default.default_value_or_panic()).into_owned())
.unwrap_or_else(|| {
let default = gix_path::from_bstr(default.default_value_or_panic()).into_owned();
#[cfg(windows)]
if let Some(program) = default.to_str().and_then(gix_path::env::installation_program) {
return program;
}
default
})
.into_os_string())
}

Expand Down
13 changes: 9 additions & 4 deletions gix/tests/gix/commit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,6 @@ mod signature {

#[test]
fn sign_write_and_verify_an_ssh_commit() -> crate::Result {
if !signature::program_available("ssh-keygen") {
return Ok(());
}
let (_key_home, key) = signature::ssh_private_key()?;
let options = gix::open::Options::isolated().config_overrides([
User::NAME.validated_assignment_fmt(&"Gitoxide Signing Fixture")?,
Expand All @@ -169,7 +166,15 @@ mod signature {
.with_object_memory();
let mut signing_options = repo.commit_signing_options()?;
assert_eq!(signing_options.format, gix::commit::sign::Format::Ssh);
assert_eq!(signing_options.program, "ssh-keygen");
let expected_program = if cfg!(windows) {
gix::path::env::installation_program("ssh-keygen").unwrap_or_else(|| "ssh-keygen".into())
} else {
"ssh-keygen".into()
};
assert_eq!(signing_options.program, expected_program.into_os_string());
if !signature::program_available(&signing_options.program) {
return Ok(());
}
assert_eq!(signing_options.signing_key, key);
assert!(signing_options.program_arguments.is_empty());
signing_options.program_arguments.push("-q".into());
Expand Down
3 changes: 2 additions & 1 deletion tests/tools/src/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
//! These public test identities provide no security and must never be used outside tests.

use std::{
ffi::OsStr,
path::{Path, PathBuf},
process::{Command, Stdio},
};
Expand Down Expand Up @@ -58,7 +59,7 @@ fn msys_path(path: &str) -> String {
}

/// Return whether signing `program` can be launched.
pub fn program_available(program: &str) -> bool {
pub fn program_available(program: impl AsRef<OsStr>) -> bool {
Command::new(program)
.arg("--version")
.stdout(Stdio::null())
Expand Down
Loading