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

Support smimesign program for signing git commits using S/MIME #59

Open
JBlaufussTR opened this issue Nov 7, 2024 · 14 comments
Open

Support smimesign program for signing git commits using S/MIME #59

JBlaufussTR opened this issue Nov 7, 2024 · 14 comments
Assignees
Labels
enhancement New feature or request

Comments

@JBlaufussTR
Copy link

Description

Support smimesign program for signing commits using S/MIME. The smimesign tool was developed by GitHub itself to support S/MIME commit signing, and works with command-line git with something like this .gitconfig:

[user]
	name = My Name
	email = [email protected]
	signingkey = 00000000000000000000000000000000000dummy
[commit]
	gpgsign = true
[gpg]
	format = x509
[gpg "x509"]
	program = smimesign

Motivation

My workplace is in the process of implementing mandatory git commit signing using corporate-issued S/MIME certificates. They have automatically rolled this out to workstations by automatically updating our .gitconfig files to use the smimesign program. Even the latest Eclipse 2024-09 (4.33.0) using EGit 7.0.0.202409031743-r org.eclipse.egit gets errors with this configuration when making a commit (which works with command-line git). Here is the error popup:

Missing Signing Key

Unable to find a GPG key for signing with key ID `00000000000000000000000000000000000dummy`. Configure the GPG key with committer email address, set git config user.signingKey, or disable commit signing.

The configuration works without issue with command-line git on my machine. Only Eclipse has issues with it.

Alternatives considered

  • Leave Eclipse and use command-line git for any operation that makes a commit.
  • Use IntelliJ, which leverages command-line git instead of its own implementaiton

Additional context

No response

@JBlaufussTR JBlaufussTR added the enhancement New feature or request label Nov 7, 2024
@JBlaufussTR JBlaufussTR changed the title Support smimesign program for signing commits using S/MIME Support smimesign program for signing git commits using S/MIME Nov 7, 2024
@tomaswolf
Copy link
Contributor

EGit supports S/MIME (X.509) signatures using an external GPG since 7.0. This was tested with gpgsm.

I suppose the problem is that EGit first calls <program> --list-secret-keys --with-colons --batch --no-tty <key_spec> to figure out whether there is a key at all, and if so, whether it is a signing key, and smimesign doesn't understand that.

smimesign has the simpler --list-keys option to list available signing keys. Apparently the assumption with smimesign is that any key returned by --list-keys is a signing key. Unfortunately, the command line is different. It also produces output for a human (not easily machine-readable).

EGit also includes the --batch --no-tty options in the call for actually signing to ensure that the program does not prompt for passphrases in a terminal (that would never get to the user when invoked from Eclipse). EGit also includes --output - to force output of the signature to stdout, which might be redundant.

So there seem to be three things to do:

  • Check whether --batch --no-tty is really necessary with gpg/gpgsm. If it is, use it only for gpg/gpgsm.
  • Check whether --output - is really necessary (probably not).
  • Figure out a better way to list keys to determine whether there is a signing key. (In GPG, not all keys are signing keys).

There is of course a third alternative: get smimesign updated to understand --list-secret-keys --with-colons --batch --no-tty and to understand -bsau <keySpec> --batch --no-tty --status-fd 2 --output -. :-)

@tomaswolf
Copy link
Contributor

smimesign --verify also doesn't seem to be fully gpgsm-compatible in the status lines it produces. gpgsm gives me a VALIDSIG line; I don't see that being generated in the smimesign code. VALIDSIG is kind of important because it would report the signature creation time.

So there we have another thing to check: how to deal with a missing signature creation time in validation. Just fill in the git commit/tag timestamp?

@JBlaufussTR
Copy link
Author

@tomaswolf Thank you for looking into this. I just created an issue in their repo as well, in case they have the ability to add support for the command line options you said are missing, though EGit looks a lot more active.

@eric-milles
Copy link

@tomaswolf Is there any chance of adding an "External S/MIME Sign" option to this drop-down? smimesign is a nice compact download; GPG is quite a large suite of tools. Our organization (TR aka Thomson Reuters) has hundreds of java developers and eclipse has been our primary IDE for many years. This latest requirement for X.509 signed commits means we cannot make use of EGit integration for commits.

image

Otherwise, is there a possibility of probing the External GPG program to determine if it is smimesign and passing different options?

The release notes for 7.0 mention SignerFactory SPI. Is this the intended extension point for adding smimesign support?

@tomaswolf
Copy link
Contributor

"External GPG" uses the configured gpg.program.

Supporting smimesign, which would be configured explicitly as gpg.x509.program, which the "External GPG" setting will use, boils down to the two things I mentioned above:

  • Check whether --batch --no-tty is really necessary with gpg/gpgsm. If it is, use it only for gpg/gpgsm.
  • Check whether --output - is really necessary (probably not).

The third point (listing keys) is a non-issue for x509 signing. The user has configured at least gpg.format = x509 (and, for smimesign, also gpg.x509.program = smimesign), so it's the user's responsibility that a key for signing is available.

Plus on signature verification, smimesign does not faithfully produce all the status lines gpgsm does, so we need to have some way to use the commit datetime if there is no signature datetime reported.

@tomaswolf
Copy link
Contributor

tomaswolf commented Dec 20, 2024

@eric-milles : I don't quite understand "This latest requirement for X.509 signed commits"... which requirement? Openpgp signatures are still possible, and EGit can now also do SSH signatures. EGit does not require x509 signatures. If you have been using EGit previously, there is nothing new in that area.

That said, I've pushed Gerrit change 1206209. It makes EGit use only the same command-line options as C git. With that, smimesign should then just work, if configured in the git config.

I hope that a large organization such as yours can thoroughly test this on different platforms (Mac OS, Windows, Linux) with both smimesign and gpgsm. (We have no automated integration tests with gpg/smimesign, and I don't have smimesign in my development environment, so I cannot test manually.)

Install EGit from the temporary update site in Eclipse; then test smimesign and gpgsm, also with passphrase-protected keys. Please report here any problems (or also that it works).

Please use the new temporary update site instead.

@tomaswolf tomaswolf self-assigned this Dec 20, 2024
@JBlaufussTR
Copy link
Author

JBlaufussTR commented Dec 20, 2024

I don't quite understand "This latest requirement for X.509 signed commits"... which requirement? Openpgp signatures are still possible, and EGit can now also do SSH signatures. EGit does not require x509 signatures. If you have been using EGit previously, there is nothing new in that area.

To clarify, I think he's referring to the requirements of internal corporate policies.

Install EGit from the temporary update site in Eclipse; then test smimesign and gpgsm, also with passphrase-protected keys. Please report here any problems (or also that it works).

I gave it a try, and it works if I select External GPG and if I override the program in the .gitconfig with the full path:
image.

I'm still getting the same error as in my original report when I have Bouncy Castle Library selected (but I suppose that's expected, I was not aware of that setting until @eric-milles mentioned it).

When using External GPG and the .gitconfig from my original report (without the full path), I am getting the following error, even though smimesign is located on my $PATH at /usr/local/bin, and even when I set the GPG Executable like this:

image

image

org.eclipse.jgit.api.errors.JGitInternalException: Exception caught during execution of commit command
	at org.eclipse.jgit.api.CommitCommand.call(CommitCommand.java:304)
	at org.eclipse.egit.core.op.CommitOperation.commit(CommitOperation.java:256)
	at org.eclipse.egit.core.op.CommitOperation$1.run(CommitOperation.java:208)
	at org.eclipse.core.internal.resources.Workspace.run(Workspace.java:2451)
	at org.eclipse.core.internal.resources.Workspace.run(Workspace.java:2476)
	at org.eclipse.egit.core.op.CommitOperation.execute(CommitOperation.java:217)
	at org.eclipse.egit.ui.internal.commit.CommitJob.run(CommitJob.java:135)
	at org.eclipse.core.internal.jobs.Worker.run(Worker.java:63)
Caused by: java.io.IOException: External program failed (Cannot run program "smimesign": error=2, No such file or directory): smimesign -bsau 00000000000000000000000000000000000dummy --status-fd 2
	at org.eclipse.egit.core.internal.signing.ExternalProcessRunner.run(ExternalProcessRunner.java:105)
	at org.eclipse.egit.core.internal.signing.ExternalGpgSigner.sign(ExternalGpgSigner.java:205)
	at org.eclipse.jgit.lib.Signer.signObject(Signer.java:73)
	at org.eclipse.jgit.api.CommitCommand.sign(CommitCommand.java:335)
	at org.eclipse.jgit.api.CommitCommand.call(CommitCommand.java:281)
	... 7 more
Caused by: java.io.IOException: Cannot run program "smimesign": error=2, No such file or directory
	at java.base/java.lang.ProcessBuilder.start(ProcessBuilder.java:1170)
	at java.base/java.lang.ProcessBuilder.start(ProcessBuilder.java:1089)
	at org.eclipse.jgit.util.FS.runProcess(FS.java:2126)
	at org.eclipse.jgit.util.FS.execute(FS.java:2259)
	at org.eclipse.egit.core.internal.signing.ExternalProcessRunner.run(ExternalProcessRunner.java:56)
	... 11 more
Caused by: java.io.IOException: error=2, No such file or directory
	at java.base/java.lang.ProcessImpl.forkAndExec(Native Method)
	at java.base/java.lang.ProcessImpl.<init>(ProcessImpl.java:295)
	at java.base/java.lang.ProcessImpl.start(ProcessImpl.java:225)
	at java.base/java.lang.ProcessBuilder.start(ProcessBuilder.java:1126)
	... 15 more

I'm on Mac OS X, and it looks like I might have some $PATH issues to debug to get this working a bit more smoothly (the Eclipse Terminal also starts up with a different $PATH than my typical terminal shell, until I add --login like described here).

I'll work on testing this a little more after the holidays.

Thank you for your help!

@tomaswolf
Copy link
Contributor

You probably have defined

gpg.x509.program = smimesign

Try

gpg.x509.program = /usr/local/bin/smimesign

EGit uses the program as specified in the git config. The executable defined in the UI is taken only as fallback if no program is defined in the git config.

I could try improving this a bit. One idea might be to call the program not directly but via the shell (with --login).

@tomaswolf
Copy link
Contributor

Or wait -- you wrote you already had the full path in the git config. Hmmm. Then I don't see why the exception message says Cannot run program "smimesign". It must have picked up plain "smimesign" (without the path) somewhere.

@tomaswolf
Copy link
Contributor

tomaswolf commented Dec 21, 2024

I've pushed a follow-up commit that explicitly looks up the program on $PATH.

Use the new temporary update site for testing, please.

@eric-milles
Copy link

eric-milles commented Dec 22, 2024

I was able to set the External GPG Executable to an adapter batch script:

@ECHO OFF
java %USERPROFILE%\SigningAdapter.java %*
REM -Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=y
import java.io.*;
import java.util.*;

public class SigningAdapter {
  public static void main(String[] args) {
    if (Arrays.asList(args).contains("--list-secret-keys")) {
      System.out.println("crs:a:b:c:d:e:f:g:h:i:j:ks");
    } else { // -bsau, <ID>, --batch, --no-tty, --status-fd, 2, --output, -
      try { // TODO: put args into list and drop "--batch", etc. with remove
        var b = new ProcessBuilder("smimesign.exe", args[0], args[1], args[4], args[5]);
        var p = b.inheritIO().start();
        System.exit(p.waitFor());
      } catch (Exception e) {
        System.err.println(e.getMessage());
      }
    }
  }
}

Given this and no "gpg.x509.program" property, a commit can be signed via smimesign. I had hoped I could name it "smimesign.bat" and co-locate it with "smimesign.exe" so the config for command-line git is left alone. However the windows pathspec says exe recognized before bat. In any case, this did work for the happy path case. Once a commit is signed, is there any way to verify that is the case in the UI?

NOTE: This is vanilla Eclipse 4.33 (2024-09). I did not see the update site you posted before trying this.

@tomaswolf
Copy link
Contributor

Try the update site; you should not need this wrapper. It should just work out of the box with the following two git configs:

[gpg]
    format = x509
[gpg "x509"]
    program = /path/to/smimesign # Or just smimesign if it's on $PATH

(Plus the user.signingKey, if needed.)

@tomaswolf
Copy link
Contributor

tomaswolf commented Dec 22, 2024

On Windows, it would be

[gpg]
    format = x509
[gpg "x509"]
   program = C:/path/to/smimesign.exe # Or just smimesign.exe if it's on %PATH%

@tomaswolf
Copy link
Contributor

Once a commit is signed, is there any way to verify that is the case in the UI?

Yes, in the history view. The lower left pane shows the commit, which includes the signature info if the preference at Preferences->Version Control->Git->History ("Verify Signatures") is switched on.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants