diff --git a/build.gradle b/build.gradle
index 9bd5ecdd6e..330cc9545c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -35,7 +35,7 @@ tasks.named('updateDaemonJvm') {
}
// Define target Java version to compatible.
-def javaVersion = 11;
+def javaVersion = 17;
// OmegaT distribution package meta data.
def shortDescription = 'The free translation memory tool'
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 2ecb9457f7..da97cc82f9 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -18,7 +18,7 @@ lucene_gosen = "5.5.1"
checkstyle = "8.45.1"
guava = "32.1.2-jre"
annotations = "23.0.0"
-jgit = "6.10.0.202406032230-r"
+jgit = "7.0.0.202409031743-r"
svnkit = "1.10.11"
ivy = "2.5.2"
jackson = "2.16.1"
@@ -53,6 +53,7 @@ flatlaf="3.5.1"
assertj_swing_junit = "4.0.0-beta-1"
morfologik = "2.1.9"
jaxb = "2.3.0"
+jgit_gpg_signer = "0.2.3"
[libraries]
slf4j-api = {group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j"}
@@ -125,6 +126,7 @@ jgit-ssh = {group = "org.eclipse.jgit", name = "org.eclipse.jgit.ssh.apache", ve
jgit-agent = {group = "org.eclipse.jgit", name = "org.eclipse.jgit.ssh.apache.agent", version.ref = "jgit"}
jgit-http = {group = "org.eclipse.jgit", name = "org.eclipse.jgit.http.apache", version.ref = "jgit"}
jgit-bc = {group = "org.eclipse.jgit", name = "org.eclipse.jgit.gpg.bc", version.ref = "jgit"}
+jgit-gpg-signer = {group = "tokyo.northside", name = "jgit-gpg-signer", version.ref = "jgit_gpg_signer"}
bc-prov = {group = "org.bouncycastle", name = "bcprov-jdk18on", version.ref = "bc"}
bc-pg = {group = "org.bouncycastle", name = "bcpg-jdk18on", version.ref = "bc"}
bc-pkix = {group = "org.bouncycastle", name = "bcpkix-jdk18on", version.ref = "bc"}
@@ -179,7 +181,7 @@ caffeine = ["caffeine", "caffeine-jcache"]
languagetool-tests = ["languagetool-server", "languagetool-be", "languagetool-fr", "languagetool-en",
"languagetool-ja", "languagetool-pl"]
lucene = ["lucene-core", "lucene-analyzers-common", "lucene-analyzers-kuromoji", "lucene-analyzers-smartcn", "lucene-analyzers-stempel"]
-jgit = ["jgit", "jgit-agent", "jgit-http", "jgit-ssh"]
+jgit = ["jgit", "jgit-agent", "jgit-http", "jgit-ssh", "jgit-gpg-signer"]
dictionary = ["trie4j", "dsl4j", "stardict4j", "jsoup"]
xmlunit = ["xmlunit-core", "xmlunit-assertj", "assertj"]
diff --git a/src/org/omegat/core/team2/impl/GITExternalGpgSigner.java b/src/org/omegat/core/team2/impl/GITExternalGpgSigner.java
deleted file mode 100644
index 37d0779d0f..0000000000
--- a/src/org/omegat/core/team2/impl/GITExternalGpgSigner.java
+++ /dev/null
@@ -1,465 +0,0 @@
-/**************************************************************************
- OmegaT - Computer Assisted Translation (CAT) tool
- with fuzzy matching, translation memory, keyword search,
- glossaries, and translation leveraging into updated projects.
-
- Copyright (C) 2021-2024 Hiroshi Miura, Thomas Wolf and others.
- This is ported from EGit (Apache-2.0)
- Home page: https://www.omegat.org/
- Support center: https://omegat.org/support
-
- This file is part of OmegaT.
-
- OmegaT is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- OmegaT is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see
- * Implementors should obtain the payload for signing from the specified - * commit via {@link CommitBuilder#build()} and create a proper - * {@link GpgSignature}. The generated signature must be set on the - * specified {@code commit} (see - * {@link CommitBuilder#setGpgSignature(GpgSignature)}). - *
- *- * Any existing signature on the commit must be discarded prior obtaining - * the payload via {@link CommitBuilder#build()}. - *
- * - * @param commit - * the commit to sign (must not benull
and must be
- * complete to allow proper calculation of payload)
- * @param gpgSigningKey
- * the signing key to locate (passed as is to the GPG signing
- * tool as is; eg., value of user.signingkey
)
- * @param committer
- * the signing identity (to help with key lookup in case signing
- * key is not specified)
- * @param credentialsProvider
- * provider to use when querying for signing key credentials (eg.
- * passphrase)
- * @throws CanceledException
- * when signing was canceled (eg., user aborted when entering
- * passphrase)
- */
- @Override
- public void sign(final CommitBuilder commit, final String gpgSigningKey, final PersonIdent committer,
- final CredentialsProvider credentialsProvider) throws CanceledException {
- signObject(commit, gpgSigningKey, committer, credentialsProvider);
- }
-
- private void signObject(final ObjectBuilder object, final String gpgSigningKey,
- final PersonIdent committer, final CredentialsProvider credentialsProvider)
- throws CanceledException {
- // Ignore the CredentialsProvider. We let GPG handle all this.
- try {
- String keySpec = gpgSigningKey;
- if (StringUtils.isEmptyOrNull(keySpec)) {
- // fallback
- if (committer == null) {
- throw new CanceledException("Cannot determine signature key");
- }
- keySpec = '<' + committer.getEmailAddress() + '>';
- }
- // git config gpg.program
- // Use this custom program instead of "gpg" found on $PATH when
- // making or verifying a PGP signature.
- GpgConfig config = new GpgConfig(new Config());
- object.setGpgSignature(new GpgSignature(signWithGpg(config, object.build(), keySpec)));
- } catch (IOException e) {
- throw new JGitInternalException(e.getMessage(), e);
- }
- }
-
- /**
- * Indicates if a signing key is available for the specified committer
- * and/or signing key.
- *
- * @param gpgSigningKey
- * the signing key to locate (passed as is to the GPG signing
- * tool as is; e.g., value of user.signingkey
)
- * @param committer
- * the signing identity (to help with key lookup in case signing
- * key is not specified)
- * @param credentialsProvider
- * provider to use when querying for signing key credentials (eg.
- * passphrase)
- * @return true
if a signing key is available,
- * false
otherwise
- * @throws CanceledException
- * when signing was canceled (eg., user aborted when entering
- * passphrase)
- */
- @Override
- public boolean canLocateSigningKey(final String gpgSigningKey, final PersonIdent committer,
- final CredentialsProvider credentialsProvider) throws CanceledException {
- // Ignore the CredentialsProvider. We let GPG handle all this.
- String program = FROM_PATH.getGpg();
- if (StringUtils.isEmptyOrNull(program)) {
- return false;
- }
- String keySpec = gpgSigningKey;
- if (StringUtils.isEmptyOrNull(keySpec)) {
- keySpec = '<' + committer.getEmailAddress() + '>';
- }
- ProcessBuilder process = new ProcessBuilder();
- // For the output format, see
- // https://github.com/gpg/gnupg/blob/master/doc/DETAILS
- process.command(program, "--locate-keys", //$NON-NLS-1$
- "--with-colons", //$NON-NLS-1$
- "--batch", //$NON-NLS-1$
- "--no-tty", //$NON-NLS-1$
- keySpec);
- gpgEnvironment(process);
- try {
- boolean[] result = { false };
- runProcess(process, null, b -> {
- try (BufferedReader r = new BufferedReader(
- new InputStreamReader(b.openInputStream(), StandardCharsets.UTF_8))) {
- // --with-colons always writes UTF-8
- boolean keyFound = false;
- String line;
- while ((line = r.readLine()) != null) {
- if (line.startsWith("pub:") //$NON-NLS-1$
- || line.startsWith("sub:")) { //$NON-NLS-1$
- String[] fields = line.split(":"); //$NON-NLS-1$
- if (fields.length > 11 && fields[11].indexOf('s') >= 0) {
- // It's a signing key.
- keyFound = true;
- break;
- }
- }
- }
- result[0] = keyFound;
- }
- }, null);
- if (!result[0]) {
- if (!StringUtils.isEmptyOrNull(gpgSigningKey)) {
- LOGGER.atWarn().setMessageRB(ExternalGpgSigner_noKeyFound).addArgument(gpgSigningKey)
- .log();
- }
- }
- return result[0];
- } catch (IOException e) {
- Log.log(e);
- return false;
- }
- }
-
- private byte[] signWithGpg(GpgConfig config, byte[] data, String keySpec)
- throws IOException, CanceledException {
- // Sign an object with an external GPG executable. GPG handles
- // passphrase entry, including gpg-agent and native keychain
- // integration.
- String program = config.getProgram();
- if (StringUtils.isEmptyOrNull(program)) {
- program = FROM_PATH.getGpg();
- if (StringUtils.isEmptyOrNull(program)) {
- throw new IOException(OStrings.getString(ExternalGpgSigner_gpgNotFound));
- }
- }
- ProcessBuilder process = new ProcessBuilder();
- process.command(program,
- // Detached signature, sign, armor, user
- "-bsau", //$NON-NLS-1$
- keySpec,
- // No extra output
- "--batch", //$NON-NLS-1$
- "--no-tty", //$NON-NLS-1$
- // Write extra status messages to stderr
- "--status-fd", //$NON-NLS-1$
- "2", //$NON-NLS-1$
- // Force output of the signature to stdout
- "--output", //$NON-NLS-1$
- "-"); //$NON-NLS-1$
- gpgEnvironment(process);
- try (ByteArrayInputStream dataIn = new ByteArrayInputStream(data)) {
- class Holder {
- byte[] rawData;
- }
- Holder result = new Holder();
- runProcess(process, dataIn, (TemporaryBuffer b) -> {
- // Sanity check: do we have a signature?
- GpgSignatureVerifierFactory factory = GpgSignatureVerifierFactory.getDefault();
- boolean isValid;
- if (factory == null) {
- byte[] fromGpg = b.toByteArray(SIGNATURE_START.length);
- isValid = Arrays.equals(fromGpg, SIGNATURE_START);
- if (isValid) {
- result.rawData = b.toByteArray();
- }
- } else {
- byte[] fromGpg = b.toByteArray();
- GpgSignatureVerifier verifier = factory.getVerifier();
- try {
- SignatureVerification verification = verifier.verify(config, data, fromGpg);
- isValid = verification != null && verification.getVerified();
- if (isValid) {
- result.rawData = fromGpg;
- }
- } catch (JGitInternalException e) {
- throw new IOException(e.getLocalizedMessage(), e);
- } finally {
- verifier.clear();
- }
- }
- if (!isValid) {
- throw new IOException(OStrings.getString(ExternalGpgSigner_noSignature, toString(b)));
- }
- }, (TemporaryBuffer e) -> {
- // Error handling: parse stderr to figure out whether we have a
- // cancellation. Unfortunately, GPG does record cancellation not
- // via a [GNUPG:] stable status but by printing "gpg: signing
- // failed: Operation cancelled". Since we don't know whether
- // this string is stable (or may even be localized), we check
- // for a "[GNUPG:] PINENTRY_LAUNCHED" line followed by the next
- // [GNUPG:] line being "[GNUPG:] FAILURE sign".
- //
- // The [GNUPG:] strings are part of GPG's public API. See
- // https://github.com/gpg/gnupg/blob/master/doc/DETAILS
- try (BufferedReader r = new BufferedReader(
- new InputStreamReader(e.openInputStream(), StandardCharsets.UTF_8))) {
- String line;
- boolean pinentry = false;
- while ((line = r.readLine()) != null) {
- if (!pinentry && line.startsWith("[GNUPG:] PINENTRY_LAUNCHED")) {
- pinentry = true;
- } else if (pinentry) {
- if (line.startsWith("[GNUPG:] FAILURE sign")) {
- throw new CanceledException(
- OStrings.getString(ExternalGpgSigner_signingCanceled));
- }
- if (line.startsWith("[GNUPG:]")) {
- pinentry = false;
- }
- }
- }
- } catch (IOException ex) {
- // Swallow it here; runProcess will raise one anyway.
- }
- });
- return result.rawData;
- }
- }
-
- private void gpgEnvironment(ProcessBuilder process) {
- try {
- Map