Merge branch 'stable-5.11'
* stable-5.11:
Refactor CommitCommand to improve readability
CommitCommand: fix formatting
CommitCommand: remove unncessary comment
Ensure post-commit hook is called after index lock was released
sshd: try all configured signature algorithms for a key
sshd: modernize ssh config file parsing
sshd: implement ssh config PubkeyAcceptedAlgorithms
Change-Id: Ic3235ffd84c9d7537a1fe5ff4f216578e6e26724
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
diff --git a/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF
index 5030f2c..2a11652 100644
--- a/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF
@@ -14,6 +14,7 @@
org.apache.sshd.common.helpers;version="[2.6.0,2.7.0)",
org.apache.sshd.common.keyprovider;version="[2.6.0,2.7.0)",
org.apache.sshd.common.session;version="[2.6.0,2.7.0)",
+ org.apache.sshd.common.signature;version="[2.6.0,2.7.0)",
org.apache.sshd.common.util.net;version="[2.6.0,2.7.0)",
org.apache.sshd.common.util.security;version="[2.6.0,2.7.0)",
org.apache.sshd.core;version="[2.6.0,2.7.0)",
diff --git a/org.eclipse.jgit.ssh.apache.test/build.properties b/org.eclipse.jgit.ssh.apache.test/build.properties
index 9ffa0ca..406c5a7 100644
--- a/org.eclipse.jgit.ssh.apache.test/build.properties
+++ b/org.eclipse.jgit.ssh.apache.test/build.properties
@@ -3,3 +3,5 @@
bin.includes = META-INF/,\
.,\
plugin.properties
+additional.bundles = org.apache.log4j,\
+ org.slf4j.binding.log4j12
diff --git a/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/ApacheSshTest.java b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/ApacheSshTest.java
index 97f97f9..c56d230 100644
--- a/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/ApacheSshTest.java
+++ b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/ApacheSshTest.java
@@ -47,7 +47,9 @@
import org.eclipse.jgit.api.errors.TransportException;
import org.eclipse.jgit.junit.ssh.SshTestBase;
import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.transport.RemoteSession;
import org.eclipse.jgit.transport.SshSessionFactory;
+import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.util.FS;
import org.junit.Test;
import org.junit.experimental.theories.Theories;
@@ -232,6 +234,61 @@
}
/**
+ * Creates a simple SSH server without git setup.
+ *
+ * @param user
+ * to accept
+ * @param userKey
+ * public key of that user at this server
+ * @return the {@link SshServer}, not yet started
+ * @throws Exception
+ */
+ private SshServer createServer(String user, File userKey) throws Exception {
+ SshServer srv = SshServer.setUpDefaultServer();
+ // Give the server its own host key
+ KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
+ generator.initialize(2048);
+ KeyPair proxyHostKey = generator.generateKeyPair();
+ srv.setKeyPairProvider(
+ session -> Collections.singletonList(proxyHostKey));
+ // Allow (only) publickey authentication
+ srv.setUserAuthFactories(Collections.singletonList(
+ ServerAuthenticationManager.DEFAULT_USER_AUTH_PUBLIC_KEY_FACTORY));
+ // Install the user's public key
+ PublicKey userProxyKey = AuthorizedKeyEntry
+ .readAuthorizedKeys(userKey.toPath()).get(0)
+ .resolvePublicKey(null, PublicKeyEntryResolver.IGNORING);
+ srv.setPublickeyAuthenticator(
+ (userName, publicKey, session) -> user.equals(userName)
+ && KeyUtils.compareKeys(userProxyKey, publicKey));
+ return srv;
+ }
+
+ /**
+ * Writes the server's host key to our knownhosts file.
+ *
+ * @param srv to register
+ * @throws Exception
+ */
+ private void registerServer(SshServer srv) throws Exception {
+ // Add the proxy's host key to knownhosts
+ try (BufferedWriter writer = Files.newBufferedWriter(
+ knownHosts.toPath(), StandardCharsets.US_ASCII,
+ StandardOpenOption.WRITE, StandardOpenOption.APPEND)) {
+ writer.append('\n');
+ KnownHostHashValue.appendHostPattern(writer, "localhost",
+ srv.getPort());
+ writer.append(',');
+ KnownHostHashValue.appendHostPattern(writer, "127.0.0.1",
+ srv.getPort());
+ writer.append(' ');
+ PublicKeyEntry.appendPublicKeyEntry(writer,
+ srv.getKeyPairProvider().loadKeys(null).iterator().next().getPublic());
+ writer.append('\n');
+ }
+ }
+
+ /**
* Creates a simple proxy server. Accepts only publickey authentication from
* the given user with the given key, allows all forwardings. Adds the
* proxy's host key to {@link #knownHosts}.
@@ -247,23 +304,7 @@
*/
private SshServer createProxy(String user, File userKey,
SshdSocketAddress[] report) throws Exception {
- SshServer proxy = SshServer.setUpDefaultServer();
- // Give the server its own host key
- KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
- generator.initialize(2048);
- KeyPair proxyHostKey = generator.generateKeyPair();
- proxy.setKeyPairProvider(
- session -> Collections.singletonList(proxyHostKey));
- // Allow (only) publickey authentication
- proxy.setUserAuthFactories(Collections.singletonList(
- ServerAuthenticationManager.DEFAULT_USER_AUTH_PUBLIC_KEY_FACTORY));
- // Install the user's public key
- PublicKey userProxyKey = AuthorizedKeyEntry
- .readAuthorizedKeys(userKey.toPath()).get(0)
- .resolvePublicKey(null, PublicKeyEntryResolver.IGNORING);
- proxy.setPublickeyAuthenticator(
- (userName, publicKey, session) -> user.equals(userName)
- && KeyUtils.compareKeys(userProxyKey, publicKey));
+ SshServer proxy = createServer(user, userKey);
// Allow forwarding
proxy.setForwardingFilter(new StaticDecisionForwardingFilter(true) {
@@ -275,21 +316,7 @@
}
});
proxy.start();
- // Add the proxy's host key to knownhosts
- try (BufferedWriter writer = Files.newBufferedWriter(
- knownHosts.toPath(), StandardCharsets.US_ASCII,
- StandardOpenOption.WRITE, StandardOpenOption.APPEND)) {
- writer.append('\n');
- KnownHostHashValue.appendHostPattern(writer, "localhost",
- proxy.getPort());
- writer.append(',');
- KnownHostHashValue.appendHostPattern(writer, "127.0.0.1",
- proxy.getPort());
- writer.append(' ');
- PublicKeyEntry.appendPublicKeyEntry(writer,
- proxyHostKey.getPublic());
- writer.append('\n');
- }
+ registerServer(proxy);
return proxy;
}
@@ -606,4 +633,73 @@
}
}
}
+
+ /**
+ * Tests that one can log in to an old server that doesn't handle
+ * rsa-sha2-512 if one puts ssh-rsa first in the client's list of public key
+ * signature algorithms.
+ *
+ * @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=572056">bug
+ * 572056</a>
+ * @throws Exception
+ * on failure
+ */
+ @Test
+ public void testConnectAuthSshRsaPubkeyAcceptedAlgorithms()
+ throws Exception {
+ try (SshServer oldServer = createServer(TEST_USER, publicKey1)) {
+ oldServer.setSignatureFactoriesNames("ssh-rsa");
+ oldServer.start();
+ registerServer(oldServer);
+ installConfig("Host server", //
+ "HostName localhost", //
+ "Port " + oldServer.getPort(), //
+ "User " + TEST_USER, //
+ "IdentityFile " + privateKey1.getAbsolutePath(), //
+ "PubkeyAcceptedAlgorithms ^ssh-rsa");
+ RemoteSession session = getSessionFactory().getSession(
+ new URIish("ssh://server/doesntmatter"), null, FS.DETECTED,
+ 10000);
+ assertNotNull(session);
+ session.disconnect();
+ }
+ }
+
+ /**
+ * Tests that one can log in to an old server that knows only the ssh-rsa
+ * signature algorithm. The client has by default the list of signature
+ * algorithms for RSA as "rsa-sha2-512,rsa-sha2-256,ssh-rsa". It should try
+ * all three with the single key configured, and finally succeed.
+ * <p>
+ * The re-ordering mechanism (see
+ * {@link #testConnectAuthSshRsaPubkeyAcceptedAlgorithms()}) is still
+ * important; servers may impose a penalty (back-off delay) for subsequent
+ * attempts with signature algorithms unknown to the server. So a user
+ * connecting to such a server and noticing delays may still want to put
+ * ssh-rsa first in the list for that host.
+ * </p>
+ *
+ * @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=572056">bug
+ * 572056</a>
+ * @throws Exception
+ * on failure
+ */
+ @Test
+ public void testConnectAuthSshRsa() throws Exception {
+ try (SshServer oldServer = createServer(TEST_USER, publicKey1)) {
+ oldServer.setSignatureFactoriesNames("ssh-rsa");
+ oldServer.start();
+ registerServer(oldServer);
+ installConfig("Host server", //
+ "HostName localhost", //
+ "Port " + oldServer.getPort(), //
+ "User " + TEST_USER, //
+ "IdentityFile " + privateKey1.getAbsolutePath());
+ RemoteSession session = getSessionFactory().getSession(
+ new URIish("ssh://server/doesntmatter"), null, FS.DETECTED,
+ 10000);
+ assertNotNull(session);
+ session.disconnect();
+ }
+ }
}
diff --git a/org.eclipse.jgit.ssh.apache/resources/org/eclipse/jgit/internal/transport/sshd/SshdText.properties b/org.eclipse.jgit.ssh.apache/resources/org/eclipse/jgit/internal/transport/sshd/SshdText.properties
index f810fd4..16b5738 100644
--- a/org.eclipse.jgit.ssh.apache/resources/org/eclipse/jgit/internal/transport/sshd/SshdText.properties
+++ b/org.eclipse.jgit.ssh.apache/resources/org/eclipse/jgit/internal/transport/sshd/SshdText.properties
@@ -5,8 +5,7 @@
configInvalidPattern=Invalid pattern in ssh config key {0}: {1}
configInvalidPositive=Ssh config entry {0} must be a strictly positive number but is ''{1}''
configInvalidProxyJump=Ssh config, host ''{0}'': Cannot parse ProxyJump ''{1}''
-configNoKnownHostKeyAlgorithms=No implementations for any of the algorithms ''{0}'' given in HostKeyAlgorithms in the ssh config; using the default.
-configNoRemainingHostKeyAlgorithms=Ssh config removed all host key algorithms: HostKeyAlgorithms ''{0}''
+configNoKnownAlgorithms=Ssh config ''{0}'' ''{1}'' resulted in empty list (none known, or all known removed); using default.
configProxyJumpNotSsh=Non-ssh URI in ProxyJump ssh config
configProxyJumpWithPath=ProxyJump ssh config: jump host specification must not have a path
ftpCloseFailed=Closing the SFTP channel failed
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java
index 66713ba..8183a92 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java
@@ -21,6 +21,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
@@ -45,6 +46,7 @@
import org.eclipse.jgit.internal.transport.sshd.proxy.StatefulProxyConnector;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.SshConstants;
+import org.eclipse.jgit.util.StringUtils;
/**
* A {@link org.apache.sshd.client.session.ClientSession ClientSession} that can
@@ -201,48 +203,23 @@
@Override
protected String resolveAvailableSignaturesProposal(
FactoryManager manager) {
- Set<String> defaultSignatures = new LinkedHashSet<>();
- defaultSignatures.addAll(getSignatureFactoriesNames());
+ List<String> defaultSignatures = getSignatureFactoriesNames();
HostConfigEntry config = resolveAttribute(
JGitSshClient.HOST_CONFIG_ENTRY);
- String hostKeyAlgorithms = config
+ String algorithms = config
.getProperty(SshConstants.HOST_KEY_ALGORITHMS);
- if (hostKeyAlgorithms != null && !hostKeyAlgorithms.isEmpty()) {
- char first = hostKeyAlgorithms.charAt(0);
- switch (first) {
- case '+':
- // Additions make not much sense -- it's either in
- // defaultSignatures already, or we have no implementation for
- // it. No point in proposing it.
- return String.join(",", defaultSignatures); //$NON-NLS-1$
- case '-':
- // This takes wildcard patterns!
- removeFromList(defaultSignatures,
- SshConstants.HOST_KEY_ALGORITHMS,
- hostKeyAlgorithms.substring(1));
- if (defaultSignatures.isEmpty()) {
- // Too bad: user config error. Warn here, and then fail
- // later.
- log.warn(format(
- SshdText.get().configNoRemainingHostKeyAlgorithms,
- hostKeyAlgorithms));
+ if (!StringUtils.isEmptyOrNull(algorithms)) {
+ List<String> result = modifyAlgorithmList(defaultSignatures,
+ algorithms, SshConstants.HOST_KEY_ALGORITHMS);
+ if (!result.isEmpty()) {
+ if (log.isDebugEnabled()) {
+ log.debug(SshConstants.HOST_KEY_ALGORITHMS + ' ' + result);
}
- return String.join(",", defaultSignatures); //$NON-NLS-1$
- default:
- // Default is overridden -- only accept the ones for which we do
- // have an implementation.
- List<String> newNames = filteredList(defaultSignatures,
- hostKeyAlgorithms);
- if (newNames.isEmpty()) {
- log.warn(format(
- SshdText.get().configNoKnownHostKeyAlgorithms,
- hostKeyAlgorithms));
- // Use the default instead.
- } else {
- return String.join(",", newNames); //$NON-NLS-1$
- }
- break;
+ return String.join(",", result); //$NON-NLS-1$
}
+ log.warn(format(SshdText.get().configNoKnownAlgorithms,
+ SshConstants.HOST_KEY_ALGORITHMS,
+ algorithms));
}
// No HostKeyAlgorithms; using default -- change order to put existing
// keys first.
@@ -262,11 +239,67 @@
}
}
reordered.addAll(defaultSignatures);
+ if (log.isDebugEnabled()) {
+ log.debug(SshConstants.HOST_KEY_ALGORITHMS + ' ' + reordered);
+ }
return String.join(",", reordered); //$NON-NLS-1$
}
+ if (log.isDebugEnabled()) {
+ log.debug(
+ SshConstants.HOST_KEY_ALGORITHMS + ' ' + defaultSignatures);
+ }
return String.join(",", defaultSignatures); //$NON-NLS-1$
}
+ /**
+ * Modifies a given algorithm list according to a list from the ssh config,
+ * including remove ('-') and reordering ('^') operators. Addition ('+') is
+ * not handled since we have no way of adding dynamically implementations,
+ * and the defaultList is supposed to contain all known implementations
+ * already.
+ *
+ * @param defaultList
+ * to modify
+ * @param fromConfig
+ * telling how to modify the {@code defaultList}, must not be
+ * {@code null} or empty
+ * @param overrideKey
+ * ssh config key; used for logging
+ * @return the modified list or {@code null} if {@code overrideKey} is not
+ * set
+ */
+ public List<String> modifyAlgorithmList(List<String> defaultList,
+ String fromConfig, String overrideKey) {
+ Set<String> defaults = new LinkedHashSet<>();
+ defaults.addAll(defaultList);
+ switch (fromConfig.charAt(0)) {
+ case '+':
+ // Additions make not much sense -- it's either in
+ // defaultList already, or we have no implementation for
+ // it. No point in proposing it.
+ return defaultList;
+ case '-':
+ // This takes wildcard patterns!
+ removeFromList(defaults, overrideKey, fromConfig.substring(1));
+ return new ArrayList<>(defaults);
+ case '^':
+ // Specified entries go to the front of the default list
+ List<String> allSignatures = filteredList(defaults,
+ fromConfig.substring(1));
+ Set<String> atFront = new HashSet<>(allSignatures);
+ for (String sig : defaults) {
+ if (!atFront.contains(sig)) {
+ allSignatures.add(sig);
+ }
+ }
+ return allSignatures;
+ default:
+ // Default is overridden -- only accept the ones for which we do
+ // have an implementation.
+ return filteredList(defaults, fromConfig);
+ }
+ }
+
private void removeFromList(Set<String> current, String key,
String patterns) {
for (String toRemove : patterns.split("\\s*,\\s*")) { //$NON-NLS-1$
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthFactory.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthFactory.java
new file mode 100644
index 0000000..0e3e24d
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthFactory.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2018, 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.internal.transport.sshd;
+
+import java.io.IOException;
+
+import org.apache.sshd.client.auth.pubkey.UserAuthPublicKey;
+import org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyFactory;
+import org.apache.sshd.client.session.ClientSession;
+
+/**
+ * A customized authentication factory for public key user authentication.
+ */
+public class JGitPublicKeyAuthFactory extends UserAuthPublicKeyFactory {
+
+ /** The singleton {@link JGitPublicKeyAuthFactory}. */
+ public static final JGitPublicKeyAuthFactory FACTORY = new JGitPublicKeyAuthFactory();
+
+ private JGitPublicKeyAuthFactory() {
+ super();
+ }
+
+ @Override
+ public UserAuthPublicKey createUserAuth(ClientSession session)
+ throws IOException {
+ return new JGitPublicKeyAuthentication(getSignatureFactories());
+ }
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthentication.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthentication.java
new file mode 100644
index 0000000..297b456
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthentication.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2018, 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.internal.transport.sshd;
+
+import java.io.IOException;
+import java.security.PublicKey;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.sshd.client.auth.pubkey.UserAuthPublicKey;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.RuntimeSshException;
+import org.apache.sshd.common.SshConstants;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.signature.Signature;
+import org.apache.sshd.common.signature.SignatureFactoriesHolder;
+import org.apache.sshd.common.util.buffer.Buffer;
+
+/**
+ * Custom {@link UserAuthPublicKey} implementation fixing SSHD-1105: if there
+ * are several signature algorithms applicable for a public key type, we must
+ * try them all, in the correct order.
+ *
+ * @see <a href="https://issues.apache.org/jira/browse/SSHD-1105">SSHD-1105</a>
+ * @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=572056">Bug
+ * 572056</a>
+ */
+public class JGitPublicKeyAuthentication extends UserAuthPublicKey {
+
+ private final List<String> algorithms = new LinkedList<>();
+
+ JGitPublicKeyAuthentication(List<NamedFactory<Signature>> factories) {
+ super(factories);
+ }
+
+ @Override
+ protected boolean sendAuthDataRequest(ClientSession session, String service)
+ throws Exception {
+ if (current == null) {
+ algorithms.clear();
+ }
+ String currentAlgorithm = null;
+ if (current != null && !algorithms.isEmpty()) {
+ currentAlgorithm = algorithms.remove(0);
+ }
+ if (currentAlgorithm == null) {
+ try {
+ if (keys == null || !keys.hasNext()) {
+ if (log.isDebugEnabled()) {
+ log.debug(
+ "sendAuthDataRequest({})[{}] no more keys to send", //$NON-NLS-1$
+ session, service);
+ }
+ return false;
+ }
+ current = keys.next();
+ algorithms.clear();
+ } catch (Error e) { // Copied from superclass
+ warn("sendAuthDataRequest({})[{}] failed ({}) to get next key: {}", //$NON-NLS-1$
+ session, service, e.getClass().getSimpleName(),
+ e.getMessage(), e);
+ throw new RuntimeSshException(e);
+ }
+ }
+ PublicKey key;
+ try {
+ key = current.getPublicKey();
+ } catch (Error e) { // Copied from superclass
+ warn("sendAuthDataRequest({})[{}] failed ({}) to retrieve public key: {}", //$NON-NLS-1$
+ session, service, e.getClass().getSimpleName(),
+ e.getMessage(), e);
+ throw new RuntimeSshException(e);
+ }
+ if (currentAlgorithm == null) {
+ String keyType = KeyUtils.getKeyType(key);
+ Set<String> aliases = new HashSet<>(
+ KeyUtils.getAllEquivalentKeyTypes(keyType));
+ aliases.add(keyType);
+ List<NamedFactory<Signature>> existingFactories;
+ if (current instanceof SignatureFactoriesHolder) {
+ existingFactories = ((SignatureFactoriesHolder) current)
+ .getSignatureFactories();
+ } else {
+ existingFactories = getSignatureFactories();
+ }
+ if (existingFactories != null) {
+ // Select the factories by name and in order
+ existingFactories.forEach(f -> {
+ if (aliases.contains(f.getName())) {
+ algorithms.add(f.getName());
+ }
+ });
+ }
+ currentAlgorithm = algorithms.isEmpty() ? keyType
+ : algorithms.remove(0);
+ }
+ String name = getName();
+ if (log.isDebugEnabled()) {
+ log.debug(
+ "sendAuthDataRequest({})[{}] send SSH_MSG_USERAUTH_REQUEST request {} type={} - fingerprint={}", //$NON-NLS-1$
+ session, service, name, currentAlgorithm,
+ KeyUtils.getFingerPrint(key));
+ }
+
+ Buffer buffer = session
+ .createBuffer(SshConstants.SSH_MSG_USERAUTH_REQUEST);
+ buffer.putString(session.getUsername());
+ buffer.putString(service);
+ buffer.putString(name);
+ buffer.putBoolean(false);
+ buffer.putString(currentAlgorithm);
+ buffer.putPublicKey(key);
+ session.writePacket(buffer);
+ return true;
+ }
+
+ @Override
+ protected void releaseKeys() throws IOException {
+ algorithms.clear();
+ current = null;
+ super.releaseKeys();
+ }
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java
index 74455dc..071e197 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java
@@ -267,6 +267,24 @@
session.setUsername(username);
session.setConnectAddress(address);
session.setHostConfigEntry(hostConfig);
+ // Set signature algorithms for public key authentication
+ String pubkeyAlgos = hostConfig
+ .getProperty(SshConstants.PUBKEY_ACCEPTED_ALGORITHMS);
+ if (!StringUtils.isEmptyOrNull(pubkeyAlgos)) {
+ List<String> signatures = getSignatureFactoriesNames();
+ signatures = session.modifyAlgorithmList(signatures, pubkeyAlgos,
+ SshConstants.PUBKEY_ACCEPTED_ALGORITHMS);
+ if (!signatures.isEmpty()) {
+ if (log.isDebugEnabled()) {
+ log.debug(SshConstants.PUBKEY_ACCEPTED_ALGORITHMS + ' '
+ + signatures);
+ }
+ session.setSignatureFactoriesNames(signatures);
+ } else {
+ log.warn(format(SshdText.get().configNoKnownAlgorithms,
+ SshConstants.PUBKEY_ACCEPTED_ALGORITHMS, pubkeyAlgos));
+ }
+ }
if (session.getCredentialsProvider() == null) {
session.setCredentialsProvider(getCredentialsProvider());
}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java
index 13bb3eb..4c4ff59 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java
@@ -25,8 +25,7 @@
/***/ public String configInvalidPattern;
/***/ public String configInvalidPositive;
/***/ public String configInvalidProxyJump;
- /***/ public String configNoKnownHostKeyAlgorithms;
- /***/ public String configNoRemainingHostKeyAlgorithms;
+ /***/ public String configNoKnownAlgorithms;
/***/ public String configProxyJumpNotSsh;
/***/ public String configProxyJumpWithPath;
/***/ public String ftpCloseFailed;
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java
index 357994d..cad959c 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java
@@ -32,10 +32,9 @@
import org.apache.sshd.client.SshClient;
import org.apache.sshd.client.auth.UserAuthFactory;
import org.apache.sshd.client.auth.keyboard.UserAuthKeyboardInteractiveFactory;
-import org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyFactory;
import org.apache.sshd.client.config.hosts.HostConfigEntryResolver;
-import org.apache.sshd.common.SshException;
import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.SshException;
import org.apache.sshd.common.compression.BuiltinCompressions;
import org.apache.sshd.common.config.keys.FilePasswordProvider;
import org.apache.sshd.common.config.keys.loader.openssh.kdf.BCryptKdfOptions;
@@ -49,6 +48,7 @@
import org.eclipse.jgit.internal.transport.sshd.CachingKeyPairProvider;
import org.eclipse.jgit.internal.transport.sshd.GssApiWithMicAuthFactory;
import org.eclipse.jgit.internal.transport.sshd.JGitPasswordAuthFactory;
+import org.eclipse.jgit.internal.transport.sshd.JGitPublicKeyAuthFactory;
import org.eclipse.jgit.internal.transport.sshd.JGitServerKeyVerifier;
import org.eclipse.jgit.internal.transport.sshd.JGitSshClient;
import org.eclipse.jgit.internal.transport.sshd.JGitSshConfig;
@@ -577,7 +577,7 @@
// Password auth doesn't have this problem.
return Collections.unmodifiableList(
Arrays.asList(GssApiWithMicAuthFactory.INSTANCE,
- UserAuthPublicKeyFactory.INSTANCE,
+ JGitPublicKeyAuthFactory.FACTORY,
JGitPasswordAuthFactory.INSTANCE,
UserAuthKeyboardInteractiveFactory.INSTANCE));
}
diff --git a/org.eclipse.jgit.ssh.jsch.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java b/org.eclipse.jgit.ssh.jsch.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java
index af09f49..4c7e99e 100644
--- a/org.eclipse.jgit.ssh.jsch.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java
+++ b/org.eclipse.jgit.ssh.jsch.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java
@@ -467,4 +467,34 @@
new File(new File(home, ".ssh"), localhost + "_id_dsa"),
h.getIdentityFile());
}
+
+ @Test
+ public void testPubKeyAcceptedAlgorithms() throws Exception {
+ config("Host=orcz\n\tPubkeyAcceptedAlgorithms ^ssh-rsa");
+ Host h = osc.lookup("orcz");
+ Config c = h.getConfig();
+ assertEquals("^ssh-rsa",
+ c.getValue(SshConstants.PUBKEY_ACCEPTED_ALGORITHMS));
+ assertEquals("^ssh-rsa", c.getValue("PubkeyAcceptedKeyTypes"));
+ }
+
+ @Test
+ public void testPubKeyAcceptedKeyTypes() throws Exception {
+ config("Host=orcz\n\tPubkeyAcceptedKeyTypes ^ssh-rsa");
+ Host h = osc.lookup("orcz");
+ Config c = h.getConfig();
+ assertEquals("^ssh-rsa",
+ c.getValue(SshConstants.PUBKEY_ACCEPTED_ALGORITHMS));
+ assertEquals("^ssh-rsa", c.getValue("PubkeyAcceptedKeyTypes"));
+ }
+
+ @Test
+ public void testEolComments() throws Exception {
+ config("#Comment\nHost=orcz #Comment\n\tPubkeyAcceptedAlgorithms ^ssh-rsa # Comment\n#Comment");
+ Host h = osc.lookup("orcz");
+ assertNotNull(h);
+ Config c = h.getConfig();
+ assertEquals("^ssh-rsa",
+ c.getValue(SshConstants.PUBKEY_ACCEPTED_ALGORITHMS));
+ }
}
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
index 33087d7..feef397 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -530,6 +530,7 @@
peerDidNotSupplyACompleteObjectGraph=peer did not supply a complete object graph
personIdentEmailNonNull=E-mail address of PersonIdent must not be null.
personIdentNameNonNull=Name of PersonIdent must not be null.
+postCommitHookFailed=Execution of post-commit hook failed: {0}.
prefixRemote=remote:
problemWithResolvingPushRefSpecsLocally=Problem with resolving push ref specs locally: {0}
progressMonUploading=Uploading {0}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
index 31f6a31..7ec36af 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
@@ -20,6 +20,7 @@
import java.util.List;
import org.eclipse.jgit.api.errors.AbortedByHookException;
+import org.eclipse.jgit.api.errors.CanceledException;
import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
import org.eclipse.jgit.api.errors.EmptyCommitException;
import org.eclipse.jgit.api.errors.GitAPIException;
@@ -36,6 +37,8 @@
import org.eclipse.jgit.dircache.DirCacheBuilder;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.dircache.DirCacheIterator;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.UnmergedPathException;
import org.eclipse.jgit.hooks.CommitMsgHook;
import org.eclipse.jgit.hooks.Hooks;
@@ -67,6 +70,8 @@
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
import org.eclipse.jgit.util.ChangeIdUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* A class used to execute a {@code Commit} command. It has setters for all
@@ -78,6 +83,9 @@
* >Git documentation about Commit</a>
*/
public class CommitCommand extends GitCommand<RevCommit> {
+ private static final Logger log = LoggerFactory
+ .getLogger(CommitCommand.class);
+
private PersonIdent author;
private PersonIdent committer;
@@ -173,8 +181,7 @@
if (all && !repo.isBare()) {
try (Git git = new Git(repo)) {
- git.add()
- .addFilepattern(".") //$NON-NLS-1$
+ git.add().addFilepattern(".") //$NON-NLS-1$
.setUpdate(true).call();
} catch (NoFilepatternException e) {
// should really not happen
@@ -212,7 +219,7 @@
.setCommitMessage(message).call();
}
- // lock the index
+ RevCommit revCommit;
DirCache index = repo.lockDirCache();
try (ObjectInserter odi = repo.newObjectInserter()) {
if (!only.isEmpty())
@@ -226,100 +233,37 @@
if (insertChangeId)
insertChangeId(indexTreeId);
- // Check for empty commits
- if (headId != null && !allowEmpty.booleanValue()) {
- RevCommit headCommit = rw.parseCommit(headId);
- headCommit.getTree();
- if (indexTreeId.equals(headCommit.getTree())) {
- throw new EmptyCommitException(
- JGitText.get().emptyCommit);
- }
- }
+ checkIfEmpty(rw, headId, indexTreeId);
// Create a Commit object, populate it and write it
CommitBuilder commit = new CommitBuilder();
commit.setCommitter(committer);
commit.setAuthor(author);
commit.setMessage(message);
-
commit.setParentIds(parents);
commit.setTreeId(indexTreeId);
if (signCommit.booleanValue()) {
- if (gpgSigner == null) {
- throw new ServiceUnavailableException(
- JGitText.get().signingServiceUnavailable);
- }
- if (gpgSigner instanceof GpgObjectSigner) {
- ((GpgObjectSigner) gpgSigner).signObject(commit,
- signingKey, committer, credentialsProvider,
- gpgConfig);
- } else {
- if (gpgConfig.getKeyFormat() != GpgFormat.OPENPGP) {
- throw new UnsupportedSigningFormatException(JGitText
- .get().onlyOpenPgpSupportedForSigning);
- }
- gpgSigner.sign(commit, signingKey, committer,
- credentialsProvider);
- }
+ sign(commit);
}
ObjectId commitId = odi.insert(commit);
odi.flush();
+ revCommit = rw.parseCommit(commitId);
- RevCommit revCommit = rw.parseCommit(commitId);
- RefUpdate ru = repo.updateRef(Constants.HEAD);
- ru.setNewObjectId(commitId);
- if (!useDefaultReflogMessage) {
- ru.setRefLogMessage(reflogComment, false);
- } else {
- String prefix = amend ? "commit (amend): " //$NON-NLS-1$
- : parents.isEmpty() ? "commit (initial): " //$NON-NLS-1$
- : "commit: "; //$NON-NLS-1$
- ru.setRefLogMessage(prefix + revCommit.getShortMessage(),
- false);
- }
- if (headId != null)
- ru.setExpectedOldObjectId(headId);
- else
- ru.setExpectedOldObjectId(ObjectId.zeroId());
- Result rc = ru.forceUpdate();
- switch (rc) {
- case NEW:
- case FORCED:
- case FAST_FORWARD: {
- setCallable(false);
- if (state == RepositoryState.MERGING_RESOLVED
- || isMergeDuringRebase(state)) {
- // Commit was successful. Now delete the files
- // used for merge commits
- repo.writeMergeCommitMsg(null);
- repo.writeMergeHeads(null);
- } else if (state == RepositoryState.CHERRY_PICKING_RESOLVED) {
- repo.writeMergeCommitMsg(null);
- repo.writeCherryPickHead(null);
- } else if (state == RepositoryState.REVERTING_RESOLVED) {
- repo.writeMergeCommitMsg(null);
- repo.writeRevertHead(null);
- }
- Hooks.postCommit(repo,
- hookOutRedirect.get(PostCommitHook.NAME),
- hookErrRedirect.get(PostCommitHook.NAME)).call();
-
- return revCommit;
- }
- case REJECTED:
- case LOCK_FAILURE:
- throw new ConcurrentRefUpdateException(
- JGitText.get().couldNotLockHEAD, ru.getRef(), rc);
- default:
- throw new JGitInternalException(MessageFormat.format(
- JGitText.get().updatingRefFailed, Constants.HEAD,
- commitId.toString(), rc));
- }
+ updateRef(state, headId, revCommit, commitId);
} finally {
index.unlock();
}
+ try {
+ Hooks.postCommit(repo, hookOutRedirect.get(PostCommitHook.NAME),
+ hookErrRedirect.get(PostCommitHook.NAME)).call();
+ } catch (Exception e) {
+ log.error(MessageFormat.format(
+ JGitText.get().postCommitHookFailed, e.getMessage()),
+ e);
+ }
+ return revCommit;
} catch (UnmergedPathException e) {
throw new UnmergedPathsException(e);
} catch (IOException e) {
@@ -328,6 +272,89 @@
}
}
+ private void checkIfEmpty(RevWalk rw, ObjectId headId, ObjectId indexTreeId)
+ throws EmptyCommitException, MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ if (headId != null && !allowEmpty.booleanValue()) {
+ RevCommit headCommit = rw.parseCommit(headId);
+ headCommit.getTree();
+ if (indexTreeId.equals(headCommit.getTree())) {
+ throw new EmptyCommitException(JGitText.get().emptyCommit);
+ }
+ }
+ }
+
+ private void sign(CommitBuilder commit) throws ServiceUnavailableException,
+ CanceledException, UnsupportedSigningFormatException {
+ if (gpgSigner == null) {
+ throw new ServiceUnavailableException(
+ JGitText.get().signingServiceUnavailable);
+ }
+ if (gpgSigner instanceof GpgObjectSigner) {
+ ((GpgObjectSigner) gpgSigner).signObject(commit,
+ signingKey, committer, credentialsProvider,
+ gpgConfig);
+ } else {
+ if (gpgConfig.getKeyFormat() != GpgFormat.OPENPGP) {
+ throw new UnsupportedSigningFormatException(JGitText
+ .get().onlyOpenPgpSupportedForSigning);
+ }
+ gpgSigner.sign(commit, signingKey, committer,
+ credentialsProvider);
+ }
+ }
+
+ private void updateRef(RepositoryState state, ObjectId headId,
+ RevCommit revCommit, ObjectId commitId)
+ throws ConcurrentRefUpdateException, IOException {
+ RefUpdate ru = repo.updateRef(Constants.HEAD);
+ ru.setNewObjectId(commitId);
+ if (!useDefaultReflogMessage) {
+ ru.setRefLogMessage(reflogComment, false);
+ } else {
+ String prefix = amend ? "commit (amend): " //$NON-NLS-1$
+ : parents.isEmpty() ? "commit (initial): " //$NON-NLS-1$
+ : "commit: "; //$NON-NLS-1$
+ ru.setRefLogMessage(prefix + revCommit.getShortMessage(),
+ false);
+ }
+ if (headId != null) {
+ ru.setExpectedOldObjectId(headId);
+ } else {
+ ru.setExpectedOldObjectId(ObjectId.zeroId());
+ }
+ Result rc = ru.forceUpdate();
+ switch (rc) {
+ case NEW:
+ case FORCED:
+ case FAST_FORWARD: {
+ setCallable(false);
+ if (state == RepositoryState.MERGING_RESOLVED
+ || isMergeDuringRebase(state)) {
+ // Commit was successful. Now delete the files
+ // used for merge commits
+ repo.writeMergeCommitMsg(null);
+ repo.writeMergeHeads(null);
+ } else if (state == RepositoryState.CHERRY_PICKING_RESOLVED) {
+ repo.writeMergeCommitMsg(null);
+ repo.writeCherryPickHead(null);
+ } else if (state == RepositoryState.REVERTING_RESOLVED) {
+ repo.writeMergeCommitMsg(null);
+ repo.writeRevertHead(null);
+ }
+ break;
+ }
+ case REJECTED:
+ case LOCK_FAILURE:
+ throw new ConcurrentRefUpdateException(
+ JGitText.get().couldNotLockHEAD, ru.getRef(), rc);
+ default:
+ throw new JGitInternalException(MessageFormat.format(
+ JGitText.get().updatingRefFailed, Constants.HEAD,
+ commitId.toString(), rc));
+ }
+ }
+
private void insertChangeId(ObjectId treeId) {
ObjectId firstParentId = null;
if (!parents.isEmpty())
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
index 3eef49b..09fe03e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -558,6 +558,7 @@
/***/ public String peerDidNotSupplyACompleteObjectGraph;
/***/ public String personIdentEmailNonNull;
/***/ public String personIdentNameNonNull;
+ /***/ public String postCommitHookFailed;
/***/ public String prefixRemote;
/***/ public String problemWithResolvingPushRefSpecsLocally;
/***/ public String progressMonUploading;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java
index 98c63cd..c514270 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java
@@ -23,7 +23,6 @@
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
@@ -224,8 +223,17 @@
entries.put(DEFAULT_NAME, defaults);
while ((line = reader.readLine()) != null) {
+ // OpenSsh ignores trailing comments on a line. Anything after the
+ // first # on a line is trimmed away (yes, even if the hash is
+ // inside quotes).
+ //
+ // See https://github.com/openssh/openssh-portable/commit/2bcbf679
+ int i = line.indexOf('#');
+ if (i >= 0) {
+ line = line.substring(0, i);
+ }
line = line.trim();
- if (line.isEmpty() || line.startsWith("#")) { //$NON-NLS-1$
+ if (line.isEmpty()) {
continue;
}
String[] parts = line.split("[ \t]*[= \t]", 2); //$NON-NLS-1$
@@ -484,12 +492,30 @@
LIST_KEYS.add(SshConstants.USER_KNOWN_HOSTS_FILE);
}
+ /**
+ * OpenSSH has renamed some config keys. This maps old names to new
+ * names.
+ */
+ private static final Map<String, String> ALIASES = new TreeMap<>(
+ String.CASE_INSENSITIVE_ORDER);
+
+ static {
+ // See https://github.com/openssh/openssh-portable/commit/ee9c0da80
+ ALIASES.put("PubkeyAcceptedKeyTypes", //$NON-NLS-1$
+ SshConstants.PUBKEY_ACCEPTED_ALGORITHMS);
+ }
+
private Map<String, String> options;
private Map<String, List<String>> multiOptions;
private Map<String, List<String>> listOptions;
+ private static String toKey(String key) {
+ String k = ALIASES.get(key);
+ return k != null ? k : key;
+ }
+
/**
* Retrieves the value of a single-valued key, or the first if the key
* has multiple values. Keys are case-insensitive, so
@@ -501,15 +527,15 @@
*/
@Override
public String getValue(String key) {
- String result = options != null ? options.get(key) : null;
+ String k = toKey(key);
+ String result = options != null ? options.get(k) : null;
if (result == null) {
// Let's be lenient and return at least the first value from
// a list-valued or multi-valued key.
- List<String> values = listOptions != null ? listOptions.get(key)
+ List<String> values = listOptions != null ? listOptions.get(k)
: null;
if (values == null) {
- values = multiOptions != null ? multiOptions.get(key)
- : null;
+ values = multiOptions != null ? multiOptions.get(k) : null;
}
if (values != null && !values.isEmpty()) {
result = values.get(0);
@@ -529,10 +555,11 @@
*/
@Override
public List<String> getValues(String key) {
- List<String> values = listOptions != null ? listOptions.get(key)
+ String k = toKey(key);
+ List<String> values = listOptions != null ? listOptions.get(k)
: null;
if (values == null) {
- values = multiOptions != null ? multiOptions.get(key) : null;
+ values = multiOptions != null ? multiOptions.get(k) : null;
}
if (values == null || values.isEmpty()) {
return new ArrayList<>();
@@ -551,34 +578,35 @@
* to set or add
*/
public void setValue(String key, String value) {
+ String k = toKey(key);
if (value == null) {
if (multiOptions != null) {
- multiOptions.remove(key);
+ multiOptions.remove(k);
}
if (listOptions != null) {
- listOptions.remove(key);
+ listOptions.remove(k);
}
if (options != null) {
- options.remove(key);
+ options.remove(k);
}
return;
}
- if (MULTI_KEYS.contains(key)) {
+ if (MULTI_KEYS.contains(k)) {
if (multiOptions == null) {
multiOptions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
}
- List<String> values = multiOptions.get(key);
+ List<String> values = multiOptions.get(k);
if (values == null) {
values = new ArrayList<>(4);
- multiOptions.put(key, values);
+ multiOptions.put(k, values);
}
values.add(value);
} else {
if (options == null) {
options = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
}
- if (!options.containsKey(key)) {
- options.put(key, value);
+ if (!options.containsKey(k)) {
+ options.put(k, value);
}
}
}
@@ -595,20 +623,21 @@
if (values.isEmpty()) {
return;
}
+ String k = toKey(key);
// Check multi-valued keys first; because of the replacement
// strategy, they must take precedence over list-valued keys
// which always follow the "first occurrence wins" strategy.
//
// Note that SendEnv is a multi-valued list-valued key. (It's
// rather immaterial for JGit, though.)
- if (MULTI_KEYS.contains(key)) {
+ if (MULTI_KEYS.contains(k)) {
if (multiOptions == null) {
multiOptions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
}
- List<String> items = multiOptions.get(key);
+ List<String> items = multiOptions.get(k);
if (items == null) {
items = new ArrayList<>(values);
- multiOptions.put(key, items);
+ multiOptions.put(k, items);
} else {
items.addAll(values);
}
@@ -616,8 +645,8 @@
if (listOptions == null) {
listOptions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
}
- if (!listOptions.containsKey(key)) {
- listOptions.put(key, values);
+ if (!listOptions.containsKey(k)) {
+ listOptions.put(k, values);
}
}
}
@@ -630,7 +659,7 @@
* @return {@code true} if the key is a list-valued key.
*/
public static boolean isListKey(String key) {
- return LIST_KEYS.contains(key.toUpperCase(Locale.ROOT));
+ return LIST_KEYS.contains(toKey(key));
}
void merge(HostEntry entry) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java
index fff2938..be55cd1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java
@@ -114,6 +114,14 @@
/** Key in an ssh config file. */
public static final String PREFERRED_AUTHENTICATIONS = "PreferredAuthentications";
+ /**
+ * Key in an ssh config file; defines signature algorithms for public key
+ * authentication as a comma-separated list.
+ *
+ * @since 5.11
+ */
+ public static final String PUBKEY_ACCEPTED_ALGORITHMS = "PubkeyAcceptedAlgorithms";
+
/** Key in an ssh config file. */
public static final String PROXY_COMMAND = "ProxyCommand";