aboutsummaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit.ssh.apache
diff options
context:
space:
mode:
authorMatthias Sohn <matthias.sohn@sap.com>2021-05-12 08:59:07 +0200
committerMatthias Sohn <matthias.sohn@sap.com>2021-05-12 08:59:07 +0200
commit1aa3cf7f416658a4fafd592732ba70f5a1aee19e (patch)
tree275b1176d04d102832641292a765d1a8f3e1b3a2 /org.eclipse.jgit.ssh.apache
parentb6d4844b5d60f63a46f2b8b42189f70de653b385 (diff)
parentfe3034d5b90a8a1f2592a40a670705fbd7305158 (diff)
downloadjgit-1aa3cf7f416658a4fafd592732ba70f5a1aee19e.tar.gz
jgit-1aa3cf7f416658a4fafd592732ba70f5a1aee19e.zip
Merge branch 'master' into next
* master: (34 commits) Remove texts which were added by mistake in 00386272 Fix formatting which was broken in 00386272 LockFile: create OutputStream only when needed Add a cgit interoperability test for LockFile Add TemporaryBuffer.toString(int limit) LockFile: create OutputStream only when needed Prepare 5.12.0-SNAPSHOT builds JGit v5.12.0.202105051250-m2 Update jetty to 9.4.40.v20210413 [releng] Update eclipse-jarsigner-plugin to 1.3.1 Implement ours/theirs content conflict resolution ssh: ensure list is modifiable before using Iterator.remove(). Update orbit to S20210406213021 and add 4.20-staging target Fix typo in test method name Allow file mode conflicts in virtual base commit on recursive merge. sshd: don't lock the known_hosts files on reading Allow info messages in UsernamePasswordCredentialsProvider ssh config: do environment variable replacement sshd: implement server-sig-algs SSH extension (client side) Upgrade ecj to 3.25.0 ... Change-Id: Ibc39a9c4e431d15b67ab4a307241f47a7f3740a9
Diffstat (limited to 'org.eclipse.jgit.ssh.apache')
-rw-r--r--org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF2
-rw-r--r--org.eclipse.jgit.ssh.apache/resources/org/eclipse/jgit/internal/transport/sshd/SshdText.properties7
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java107
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitKexExtensionHandler.java163
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthFactory.java35
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthentication.java224
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java18
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyDatabase.java31
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java7
-rw-r--r--org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java8
10 files changed, 535 insertions, 67 deletions
diff --git a/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF b/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF
index faa52b26ac..89b5133afc 100644
--- a/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF
@@ -59,6 +59,8 @@ Import-Package: net.i2p.crypto.eddsa;version="[0.3.0,0.4.0)",
org.apache.sshd.common.helpers;version="[2.6.0,2.7.0)",
org.apache.sshd.common.io;version="[2.6.0,2.7.0)",
org.apache.sshd.common.kex;version="[2.6.0,2.7.0)",
+ org.apache.sshd.common.kex.extension;version="[2.6.0,2.7.0)",
+ org.apache.sshd.common.kex.extension.parser;version="[2.6.0,2.7.0)",
org.apache.sshd.common.keyprovider;version="[2.6.0,2.7.0)",
org.apache.sshd.common.mac;version="[2.6.0,2.7.0)",
org.apache.sshd.common.random;version="[2.6.0,2.7.0)",
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 f810fd40e4..5bc0867674 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 @@ configInvalidPath=Invalid path in ssh config key {0}: {1}
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
@@ -25,7 +24,6 @@ keyEncryptedPrompt=Passphrase
keyEncryptedRetry=Encrypted key ''{0}'' could not be decrypted. Enter the passphrase again.
keyLoadFailed=Could not load key ''{0}''
knownHostsCouldNotUpdate=Could not update known hosts file {0}
-knownHostsFileLockedRead=Could not read known hosts file (locked) {0}
knownHostsFileLockedUpdate=Could not update known hosts file (locked) {0}
knownHostsFileReadFailed=Failed to read known hosts file {0}
knownHostsInvalidLine=Known hosts file {0} contains invalid line {1}
@@ -77,6 +75,9 @@ proxySocksPasswordTooLong=Password for proxy {0} must be at most 255 bytes long,
proxySocksUnexpectedMessage=Unexpected message received from SOCKS5 proxy {0}; client state {1}: {2}
proxySocksUnexpectedVersion=Expected SOCKS version 5, got {0}
proxySocksUsernameTooLong=User name for proxy {0} must be at most 255 bytes long, is {1} bytes: {2}
+pubkeyAuthWrongCommand=Public key authentication received unknown SSH command {0} from {1} ({2})
+pubkeyAuthWrongKey=Public key authentication received wrong key; sent {0}, got back {1} from {2} ({3})
+pubkeyAuthWrongSignatureAlgorithm=Public key authentication requested signature type {0} but got back {1} from {2} ({3})
serverIdNotReceived=No server identification received within {0} bytes
serverIdTooLong=Server identification is longer than 255 characters (including line ending): {0}
serverIdWithNul=Server identification contains a NUL character: {0}
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 66713ba632..8183a92b9f 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.security.PublicKey;
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.fnmatch.FileNameMatcher;
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 @@ public class JGitClientSession extends ClientSessionImpl {
@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));
- }
- 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$
+ 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);
}
- 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 @@ public class JGitClientSession extends ClientSessionImpl {
}
}
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/JGitKexExtensionHandler.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitKexExtensionHandler.java
new file mode 100644
index 0000000000..9446aaa7d6
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitKexExtensionHandler.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 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.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.sshd.common.AttributeRepository.AttributeKey;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.kex.KexProposalOption;
+import org.apache.sshd.common.kex.extension.KexExtensionHandler;
+import org.apache.sshd.common.kex.extension.KexExtensions;
+import org.apache.sshd.common.kex.extension.parser.ServerSignatureAlgorithms;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.signature.Signature;
+import org.apache.sshd.common.util.logging.AbstractLoggingBean;
+import org.eclipse.jgit.util.StringUtils;
+
+/**
+ * Do not use the DefaultClientKexExtensionHandler from sshd; it doesn't work
+ * properly because of misconceptions. See SSHD-1141.
+ *
+ * @see <a href="https://issues.apache.org/jira/browse/SSHD-1141">SSHD-1141</a>
+ */
+public class JGitKexExtensionHandler extends AbstractLoggingBean
+ implements KexExtensionHandler {
+
+ /** Singleton instance. */
+ public static final JGitKexExtensionHandler INSTANCE = new JGitKexExtensionHandler();
+
+ /**
+ * Session {@link AttributeKey} used to store whether the extension
+ * indicator was already sent.
+ */
+ private static final AttributeKey<Boolean> CLIENT_PROPOSAL_MADE = new AttributeKey<>();
+
+ /**
+ * Session {@link AttributeKey} storing the algorithms announced by the
+ * server as known.
+ */
+ public static final AttributeKey<Set<String>> SERVER_ALGORITHMS = new AttributeKey<>();
+
+ private JGitKexExtensionHandler() {
+ // No public instantiation for singleton
+ }
+
+ @Override
+ public boolean isKexExtensionsAvailable(Session session,
+ AvailabilityPhase phase) throws IOException {
+ return !AvailabilityPhase.PREKEX.equals(phase);
+ }
+
+ @Override
+ public void handleKexInitProposal(Session session, boolean initiator,
+ Map<KexProposalOption, String> proposal) throws IOException {
+ // If it's the very first time, we may add the marker telling the server
+ // that we are ready to handle SSH_MSG_EXT_INFO
+ if (session == null || session.isServerSession() || !initiator) {
+ return;
+ }
+ if (session.getAttribute(CLIENT_PROPOSAL_MADE) != null) {
+ return;
+ }
+ String kexAlgorithms = proposal.get(KexProposalOption.SERVERKEYS);
+ if (StringUtils.isEmptyOrNull(kexAlgorithms)) {
+ return;
+ }
+ List<String> algorithms = new ArrayList<>();
+ // We're a client. We mustn't send the server extension, and we should
+ // send the client extension only once.
+ for (String algo : kexAlgorithms.split(",")) { //$NON-NLS-1$
+ if (KexExtensions.CLIENT_KEX_EXTENSION.equalsIgnoreCase(algo)
+ || KexExtensions.SERVER_KEX_EXTENSION
+ .equalsIgnoreCase(algo)) {
+ continue;
+ }
+ algorithms.add(algo);
+ }
+ // Tell the server that we want to receive SSH2_MSG_EXT_INFO
+ algorithms.add(KexExtensions.CLIENT_KEX_EXTENSION);
+ if (log.isDebugEnabled()) {
+ log.debug(
+ "handleKexInitProposal({}): proposing HostKeyAlgorithms {}", //$NON-NLS-1$
+ session, algorithms);
+ }
+ proposal.put(KexProposalOption.SERVERKEYS,
+ String.join(",", algorithms)); //$NON-NLS-1$
+ session.setAttribute(CLIENT_PROPOSAL_MADE, Boolean.TRUE);
+ }
+
+ @Override
+ public boolean handleKexExtensionRequest(Session session, int index,
+ int count, String name, byte[] data) throws IOException {
+ if (ServerSignatureAlgorithms.NAME.equals(name)) {
+ handleServerSignatureAlgorithms(session,
+ ServerSignatureAlgorithms.INSTANCE.parseExtension(data));
+ }
+ return true;
+ }
+
+ /**
+ * Perform updates after a server-sig-algs extension has been received.
+ *
+ * @param session
+ * the message was received for
+ * @param serverAlgorithms
+ * signature algorithm names announced by the server
+ */
+ protected void handleServerSignatureAlgorithms(Session session,
+ Collection<String> serverAlgorithms) {
+ if (log.isDebugEnabled()) {
+ log.debug("handleServerSignatureAlgorithms({}): {}", session, //$NON-NLS-1$
+ serverAlgorithms);
+ }
+ // Client determines order; server says what it supports. Re-order
+ // such that supported ones are at the front, in client order,
+ // followed by unsupported ones, also in client order.
+ if (serverAlgorithms != null && !serverAlgorithms.isEmpty()) {
+ List<NamedFactory<Signature>> clientAlgorithms = new ArrayList<>(
+ session.getSignatureFactories());
+ if (log.isDebugEnabled()) {
+ log.debug(
+ "handleServerSignatureAlgorithms({}): PubkeyAcceptedAlgorithms before: {}", //$NON-NLS-1$
+ session, clientAlgorithms);
+ }
+ List<NamedFactory<Signature>> unknown = new ArrayList<>();
+ Set<String> known = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
+ known.addAll(serverAlgorithms);
+ for (Iterator<NamedFactory<Signature>> iter = clientAlgorithms
+ .iterator(); iter.hasNext();) {
+ NamedFactory<Signature> algo = iter.next();
+ if (!known.contains(algo.getName())) {
+ unknown.add(algo);
+ iter.remove();
+ }
+ }
+ // Re-add the unknown ones at the end. Per RFC 8308, some
+ // servers may not announce _all_ their supported algorithms,
+ // and a client may use unknown algorithms.
+ clientAlgorithms.addAll(unknown);
+ if (log.isDebugEnabled()) {
+ log.debug(
+ "handleServerSignatureAlgorithms({}): PubkeyAcceptedAlgorithms after: {}", //$NON-NLS-1$
+ session, clientAlgorithms);
+ }
+ session.setAttribute(SERVER_ALGORITHMS, known);
+ session.setSignatureFactories(clientAlgorithms);
+ }
+ }
+}
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 0000000000..0e3e24dcff
--- /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 0000000000..6755094420
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthentication.java
@@ -0,0 +1,224 @@
+/*
+ * 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.security.spec.InvalidKeySpecException;
+import java.text.MessageFormat;
+import java.util.Deque;
+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.NamedResource;
+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 Deque<String> currentAlgorithms = new LinkedList<>();
+
+ private String chosenAlgorithm;
+
+ JGitPublicKeyAuthentication(List<NamedFactory<Signature>> factories) {
+ super(factories);
+ }
+
+ @Override
+ protected boolean sendAuthDataRequest(ClientSession session, String service)
+ throws Exception {
+ if (current == null) {
+ currentAlgorithms.clear();
+ chosenAlgorithm = null;
+ }
+ String currentAlgorithm = null;
+ if (current != null && !currentAlgorithms.isEmpty()) {
+ currentAlgorithm = currentAlgorithms.poll();
+ if (chosenAlgorithm != null) {
+ Set<String> knownServerAlgorithms = session.getAttribute(
+ JGitKexExtensionHandler.SERVER_ALGORITHMS);
+ if (knownServerAlgorithms != null
+ && knownServerAlgorithms.contains(chosenAlgorithm)) {
+ // We've tried key 'current' with 'chosenAlgorithm', but it
+ // failed. However, the server had told us it supported
+ // 'chosenAlgorithm'. Thus it makes no sense to continue
+ // with this key and other signature algorithms. Skip to the
+ // next key, if any.
+ currentAlgorithm = null;
+ }
+ }
+ }
+ 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);
+ }
+ current = null;
+ return false;
+ }
+ current = keys.next();
+ currentAlgorithms.clear();
+ chosenAlgorithm = null;
+ } catch (Error e) { // Copied from superclass
+ throw new RuntimeSshException(e);
+ }
+ }
+ PublicKey key;
+ try {
+ key = current.getPublicKey();
+ } catch (Error e) { // Copied from superclass
+ 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) {
+ if (log.isDebugEnabled()) {
+ log.debug(
+ "sendAuthDataRequest({})[{}] selecting from PubKeyAcceptedAlgorithms {}", //$NON-NLS-1$
+ session, service,
+ NamedResource.getNames(existingFactories));
+ }
+ // Select the factories by name and in order
+ existingFactories.forEach(f -> {
+ if (aliases.contains(f.getName())) {
+ currentAlgorithms.add(f.getName());
+ }
+ });
+ }
+ currentAlgorithm = currentAlgorithms.isEmpty() ? keyType
+ : currentAlgorithms.poll();
+ }
+ 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));
+ }
+
+ chosenAlgorithm = currentAlgorithm;
+ 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 boolean processAuthDataRequest(ClientSession session,
+ String service, Buffer buffer) throws Exception {
+ String name = getName();
+ int cmd = buffer.getUByte();
+ if (cmd != SshConstants.SSH_MSG_USERAUTH_PK_OK) {
+ throw new IllegalStateException(MessageFormat.format(
+ SshdText.get().pubkeyAuthWrongCommand,
+ SshConstants.getCommandMessageName(cmd),
+ session.getConnectAddress(), session.getServerVersion()));
+ }
+ PublicKey key;
+ try {
+ key = current.getPublicKey();
+ } catch (Error e) { // Copied from superclass
+ throw new RuntimeSshException(e);
+ }
+ String rspKeyAlgorithm = buffer.getString();
+ PublicKey rspKey = buffer.getPublicKey();
+ if (log.isDebugEnabled()) {
+ log.debug(
+ "processAuthDataRequest({})[{}][{}] SSH_MSG_USERAUTH_PK_OK type={}, fingerprint={}", //$NON-NLS-1$
+ session, service, name, rspKeyAlgorithm,
+ KeyUtils.getFingerPrint(rspKey));
+ }
+ if (!KeyUtils.compareKeys(rspKey, key)) {
+ throw new InvalidKeySpecException(MessageFormat.format(
+ SshdText.get().pubkeyAuthWrongKey,
+ KeyUtils.getFingerPrint(key),
+ KeyUtils.getFingerPrint(rspKey),
+ session.getConnectAddress(), session.getServerVersion()));
+ }
+ if (!chosenAlgorithm.equalsIgnoreCase(rspKeyAlgorithm)) {
+ // 'algo' SHOULD be the same as 'chosenAlgorithm', which is the one
+ // we sent above. See https://tools.ietf.org/html/rfc4252#page-9 .
+ //
+ // However, at least Github (SSH-2.0-babeld-383743ad) servers seem
+ // to return the key type, not the algorithm name.
+ //
+ // So we don't check but just log the inconsistency. We sign using
+ // 'chosenAlgorithm' in any case, so we don't really care what the
+ // server says here.
+ log.warn(MessageFormat.format(
+ SshdText.get().pubkeyAuthWrongSignatureAlgorithm,
+ chosenAlgorithm, rspKeyAlgorithm, session.getConnectAddress(),
+ session.getServerVersion()));
+ }
+ String username = session.getUsername();
+ Buffer out = session
+ .createBuffer(SshConstants.SSH_MSG_USERAUTH_REQUEST);
+ out.putString(username);
+ out.putString(service);
+ out.putString(name);
+ out.putBoolean(true);
+ out.putString(chosenAlgorithm);
+ out.putPublicKey(key);
+ if (log.isDebugEnabled()) {
+ log.debug(
+ "processAuthDataRequest({})[{}][{}]: signing with algorithm {}", //$NON-NLS-1$
+ session, service, name, chosenAlgorithm);
+ }
+ appendSignature(session, service, name, username, chosenAlgorithm, key,
+ out);
+ session.writePacket(out);
+ return true;
+ }
+
+ @Override
+ protected void releaseKeys() throws IOException {
+ currentAlgorithms.clear();
+ current = null;
+ chosenAlgorithm = 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 74455dc808..071e1979d3 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 @@ public class JGitSshClient extends SshClient {
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/OpenSshServerKeyDatabase.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyDatabase.java
index 47e09b75d7..1a530b7743 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyDatabase.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyDatabase.java
@@ -21,6 +21,7 @@ import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
+import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;
@@ -561,29 +562,17 @@ public class OpenSshServerKeyDatabase
@Override
public List<HostEntryPair> get() {
Path path = getPath();
- try {
- if (checkReloadRequired()) {
- if (!Files.exists(path)) {
- // Has disappeared.
- resetReloadAttributes();
- return Collections.emptyList();
- }
- LockFile lock = new LockFile(path.toFile());
- if (lock.lock()) {
- try {
- entries = reload(getPath());
- } finally {
- lock.unlock();
- }
- } else {
- LOG.warn(format(SshdText.get().knownHostsFileLockedRead,
- path));
+ synchronized (this) {
+ try {
+ if (checkReloadRequired()) {
+ entries = reload(getPath());
}
+ } catch (IOException e) {
+ LOG.warn(format(SshdText.get().knownHostsFileReadFailed,
+ path));
}
- } catch (IOException e) {
- LOG.warn(format(SshdText.get().knownHostsFileReadFailed, path));
+ return Collections.unmodifiableList(entries);
}
- return Collections.unmodifiableList(entries);
}
private List<HostEntryPair> reload(Path path) throws IOException {
@@ -616,7 +605,7 @@ public class OpenSshServerKeyDatabase
}
}
return newEntries;
- } catch (FileNotFoundException e) {
+ } catch (FileNotFoundException | NoSuchFileException e) {
resetReloadAttributes();
return Collections.emptyList();
}
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 13bb3ebe75..73c2288ccc 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 final class SshdText extends TranslationBundle {
/***/ 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;
@@ -45,7 +44,6 @@ public final class SshdText extends TranslationBundle {
/***/ public String keyEncryptedRetry;
/***/ public String keyLoadFailed;
/***/ public String knownHostsCouldNotUpdate;
- /***/ public String knownHostsFileLockedRead;
/***/ public String knownHostsFileLockedUpdate;
/***/ public String knownHostsFileReadFailed;
/***/ public String knownHostsInvalidLine;
@@ -89,6 +87,9 @@ public final class SshdText extends TranslationBundle {
/***/ public String proxySocksUnexpectedMessage;
/***/ public String proxySocksUnexpectedVersion;
/***/ public String proxySocksUsernameTooLong;
+ /***/ public String pubkeyAuthWrongCommand;
+ /***/ public String pubkeyAuthWrongKey;
+ /***/ public String pubkeyAuthWrongSignatureAlgorithm;
/***/ public String serverIdNotReceived;
/***/ public String serverIdTooLong;
/***/ public String serverIdWithNul;
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 357994d431..2d7e0c7c77 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.ClientBuilder;
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;
@@ -48,7 +47,9 @@ import org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile;
import org.eclipse.jgit.internal.transport.sshd.AuthenticationCanceledException;
import org.eclipse.jgit.internal.transport.sshd.CachingKeyPairProvider;
import org.eclipse.jgit.internal.transport.sshd.GssApiWithMicAuthFactory;
+import org.eclipse.jgit.internal.transport.sshd.JGitKexExtensionHandler;
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;
@@ -216,6 +217,7 @@ public class SshdSessionFactory extends SshSessionFactory implements Closeable {
new JGitUserInteraction(credentialsProvider));
client.setUserAuthFactories(getUserAuthFactories());
client.setKeyIdentityProvider(defaultKeysProvider);
+ client.setKexExtensionHandler(JGitKexExtensionHandler.INSTANCE);
// JGit-specific things:
JGitSshClient jgitClient = (JGitSshClient) client;
jgitClient.setKeyCache(getKeyCache());
@@ -577,7 +579,7 @@ public class SshdSessionFactory extends SshSessionFactory implements Closeable {
// Password auth doesn't have this problem.
return Collections.unmodifiableList(
Arrays.asList(GssApiWithMicAuthFactory.INSTANCE,
- UserAuthPublicKeyFactory.INSTANCE,
+ JGitPublicKeyAuthFactory.FACTORY,
JGitPasswordAuthFactory.INSTANCE,
UserAuthKeyboardInteractiveFactory.INSTANCE));
}