Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sign git commits with SSH keys #1149

Closed
skyfaller opened this issue Feb 25, 2022 · 18 comments · Fixed by #2175
Closed

Sign git commits with SSH keys #1149

skyfaller opened this issue Feb 25, 2022 · 18 comments · Fixed by #2175
Milestone

Comments

@skyfaller
Copy link

skyfaller commented Feb 25, 2022

Is your feature request related to a problem? Please describe.
I would like to be able to sign git commits with SSH keys from gitui.

This is a new feature that seems to only work properly with OpenSSH 8.8 and above, which can make it challenging to test on more stable OSes; but if you are able to use it, it seems painless compared to GPG. Because it is new, it also has poor support on GitHub and other code forges, but I expect that will change.

It may also seem premature to consider, given that GPG signing isn't supported in gitui yet; on the other hand, now may be a good time to consider the user interface to make it easy to support signing with SSH keys as well as GPG keys in the future.

@skyfaller
Copy link
Author

GitHub now supports verifying commits using SSH keys: https://github.blog/changelog/2022-08-23-ssh-commit-verification-now-supported/

@extrawurst
Copy link
Owner

this feature needs to be supported by upstream libgit2 first

@dnaka91
Copy link

dnaka91 commented Oct 24, 2022

I did try to implement GPG signing in the past as part of #97, and it turned out to be very difficult, due to not having a native Rust crate for the GPG part. Basically, it complicated builds on Windows due to some extra C dependencies and licensing might be problematic too.

So I recently tried out to implement SSH signing instead, as I discovered the relatively new ssh-key crate from the RustCrypto group. After a little forth and back with them, they added an implementation for SSH signing to it, and with the current 0.5.0-rc.0, it's very easy to sign commits.

The workflow is exactly the same as with GPG signing, so we wouldn't have to wait for libgit2 to integrate that.

Here is a very minimal example:

# Cargo.toml

[package]
name = "gitsign"
version = "0.1.0"
edition = "2021"

[dependencies]
anyhow = "1.0.66"
dirs = "4.0.0"
git2 = "0.15.0"
ssh-key = { version = "0.5.0-rc.0", features = ["ed25519", "encryption"] }
// src/main.rs

use std::{env, fs};

use anyhow::Result;
use git2::Repository;
use ssh_key::{HashAlg, LineEnding, PrivateKey};

fn main() -> Result<()> {
    // Create a new repo at ./tmp
    let dir = env::current_dir()?.join("tmp");
    fs::remove_dir_all(&dir).ok();
    fs::create_dir_all(&dir)?;

    let repo = Repository::init(&dir)?;

    // Prepare an empty commit
    let mut index = repo.index()?;
    let tree = index.write_tree()?;
    let tree = repo.find_tree(tree)?;

    let signature = repo.signature()?;

    let content = repo.commit_create_buffer(&signature, &signature, "Initial commit", &tree, &[])?;
    let content = String::from_utf8(content.to_vec())?;

    // Sign with SSH key
    let ssh_dir = dirs::home_dir().unwrap().join(".ssh");

    let key = fs::read(ssh_dir.join("id_ed25519"))?;
    let key = PrivateKey::from_openssh(key)?.decrypt("<YOUR PRIVATE KEY PASSWORD>")?;

    let sig = key
        .sign("git", HashAlg::Sha256, content.as_bytes())?
        .to_pem(LineEnding::default())?;

    // Perform actual commit
    let commit = repo.commit_signed(&content, &sig, None)?;
    let commit = repo.find_commit(commit)?;
    repo.branch("main", &commit, true)?;

    Ok(())
}

After running it, you can go to the ./tmp folder and verify the signature with git log --show-signature.

@extrawurst with that, what do you think about implementing SSH signing first, before continuing on the GPG signing path, which seems rather stuck?

There is just one problem: Getting the private key. The Git configuration allows different ways of specifying the key. It can be:

  • A public key handle, which means Git will talk with the SSH agent (through ssh-keygen which does all the signing). This seems the most preferred setup.
  • Direct path to the private key. This one we could support as a first implementation.
  • A program which will respond with the public key handle to use, and then Git will talk to the SSH agent.
  • No key config at all, which means it'll interactively ask for the right handle or pick the first key returned by the SSH agent (based on the config).

Given all these possible options, it might even be easier to just do the same as Git and shell out to ssh-keygen instead of natively implementing it 🤔

@extrawurst
Copy link
Owner

what do you think about implementing SSH signing first

absolutely welcome. and as usual I am for implementing the first version (MVP) of it as simple as possible and then iterate over it. so I assume a direct path to the key to be used via program param would be the way to go. after that we should look into querying the agent and only afterwards go for the most complicated version where we need to have a UI for the user to choose from key options.

@stale
Copy link

stale bot commented May 21, 2023

This issue has been automatically marked as stale because it has not had any activity half a year. It will be closed in 14 days if no further activity occurs. Thank you for your contributions.

@stale stale bot added the dormant Marked by stale bot on close label May 21, 2023
@matthiasbeyer
Copy link

Still relevant.

@stale stale bot removed the dormant Marked by stale bot on close label May 21, 2023
@extrawurst
Copy link
Owner

libgit2/libgit2#6617 - soon supported by upstream

@extrawurst
Copy link
Owner

Also really interesting read about another tool using libgit2-only without shellout to support ssh signing: https://blog.gitbutler.com/signing-commits-in-git-explained/

@SaadiSave
Copy link

libgit2/libgit2#6617 - soon supported by upstream

Since this is now supported upstream in libgit2, will gitui automatically respect the commit.gpgsign config option for SSH signing?

@igas
Copy link

igas commented Jan 16, 2024

@SaadiSave there was no release of libgit2 after feature was merged yet.

@yanganto
Copy link
Contributor

Hi @dnaka91
I try to solve this with your suggestion, and I realize the third input of commit_signed should not be None. None is for gpgsig, but sshsig or SSHSIG does not work here.

Do you have any idea about this? Many thanks.

@dnaka91
Copy link

dnaka91 commented Jan 23, 2024

Despite this now being ssh signature signing, it's reusing the old header.

You can see in the above link that it's still under the gpgsig header and in the git source code it's the only header used for signatures.

Makes sense because even the settings are still all the same and just swap the underlying signing program (more or less).

@yanganto
Copy link
Contributor

I got it. There is still a problem here.
There is no parents field in commit_signed, so the commit is generated but not after the head of the current branch. Could you help me to fix this? Many thanks.

@yanganto
Copy link
Contributor

Also, I am not sure the None is valid for signature_field, because I will get a bug from gpg-interface.

BUG: gpg-interface.c:284: bad signature '-----BEGIN SSH SIGNATURE-----
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgTjgfFOnWZfMF8CBzrp50Vvs/E6
aHZKmKF2GhZRjHBiUAAAADZ2l0AAAAAAAAAAZzaGEyNTYAAABTAAAAC3NzaC1lZDI1NTE5
AAAAQADCbMrw19YOZSdJxhjCgJvWIRpHT842Wu8xhITmurehYcHtn9pFcibpsx47V0D73f
1zRD4DptJ7v7t9IbKx8Qk=
-----END SSH SIGNATURE-----

'
(END)[1]    24547 abort (core dumped)  git log --show-signature

@dnaka91
Copy link

dnaka91 commented Jan 24, 2024

I noticed a small difference with the regular Git signatures, and that is a trailing newline in the signature. Don't know about gpg-interface so that might be a thing?

Just for fun I tried implementing the same thing with gix instead of git2 and put it all into a repo. Works fine for me with both crates and I can verify it with my Git CLI 🤷

The repo is here: https://github.com/dnaka91/gitsign

Could it be that your Git CLI is a bit dated and doesn't have the SSH signing feature yet?


Edit: I'm using Git 2.43.0 which is the latest version at the time of writing.

@yanganto yanganto mentioned this issue Jan 24, 2024
4 tasks
@yanganto
Copy link
Contributor

yanganto commented Jan 24, 2024

Hi @extrawurst
There are things we need to think about before completing the PR. I have checked this signed commit with the ssh key will show verified tag in GitHub.

  • There will be a duplicate syn crate in the dependency, for 1.0 and 2.0
  • There will be a warning with the crate for RSA SSH key, but it breaks the current CI.
  • The key path is passed from options, and we do not need additional TUI on it, because users do not change the signing key for different commits.

It seems hard to avoid these. Will the defects be accepted?

@jirutka
Copy link
Contributor

jirutka commented May 18, 2024

libgit2/libgit2#6617 - soon supported by upstream

Since SSH signing is now supported by libgit2, can you please reimplement this feature to drop dependency on the ssh-key crate? ssh-key has dozens(!) of transitive dependencies, it increased the gitui binary size by ~1 MiB and caused issues with building in debug profile (for running tests) on Alpine Linux (thread 'opt cgu.0' has overflowed its stack).

@qaqland
Copy link

qaqland commented Jul 5, 2024

thread 'opt cgu.0' has overflowed its stack

got same on Alpine Linux with gitui v0.26.3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
9 participants