Apache MINA sshd client: test & fix password authentication
Add tests for password and keyboard-interactive authentication. Implement password authentication; the default provided by sshd is non-interactive, which is not useful for JGit. Make sure the CredentialsProvider gets reset on successive password retrieval attempts. Otherwise it might always return the same non- accepted password from a secure storage. (That one was discovered by actually trying this via EGit; the JGit tests don't catch this.) Change the default order of authentication mechanisms to prefer password over keyboard-interactive. This is a mitigation for upstream bug SSHD-866.[1] Also include a fix for upstream bug SSHD-867.[2] [1] https://issues.apache.org/jira/projects/SSHD/issues/SSHD-866 [2] https://issues.apache.org/jira/projects/SSHD/issues/SSHD-867 Bug: 520927 Change-Id: I423e548f06d3b51531016cf08938c8bd7acaa2a9 Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
This commit is contained in:
parent
1316d43e51
commit
00b235f0b8
|
@ -22,10 +22,12 @@ Import-Package: org.apache.sshd.common;version="[2.0.0,2.1.0)",
|
|||
org.apache.sshd.server;version="[2.0.0,2.1.0)",
|
||||
org.apache.sshd.server.auth;version="[2.0.0,2.1.0)",
|
||||
org.apache.sshd.server.auth.gss;version="[2.0.0,2.1.0)",
|
||||
org.apache.sshd.server.auth.keyboard;version="[2.0.0,2.1.0)",
|
||||
org.apache.sshd.server.auth.password;version="[2.0.0,2.1.0)",
|
||||
org.apache.sshd.server.command;version="[2.0.0,2.1.0)",
|
||||
org.apache.sshd.server.session;version="[2.0.0,2.1.0)",
|
||||
org.apache.sshd.server.shell;version="[2.0.0,2.1.0)",
|
||||
org.apache.sshd.server.subsystem;version="2.0.0",
|
||||
org.apache.sshd.server.subsystem;version="[2.0.0,2.1.0)",
|
||||
org.apache.sshd.server.subsystem.sftp;version="[2.0.0,2.1.0)",
|
||||
org.eclipse.jgit.annotations;version="[5.2.0,5.3.0)",
|
||||
org.eclipse.jgit.lib;version="[5.2.0,5.3.0)",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
|
@ -72,6 +73,7 @@
|
|||
import org.apache.sshd.server.auth.gss.GSSAuthenticator;
|
||||
import org.apache.sshd.server.auth.gss.UserAuthGSS;
|
||||
import org.apache.sshd.server.auth.gss.UserAuthGSSFactory;
|
||||
import org.apache.sshd.server.auth.keyboard.DefaultKeyboardInteractiveAuthenticator;
|
||||
import org.apache.sshd.server.command.AbstractCommandSupport;
|
||||
import org.apache.sshd.server.command.Command;
|
||||
import org.apache.sshd.server.session.ServerSession;
|
||||
|
@ -184,14 +186,18 @@ protected Boolean doAuth(Buffer buffer, boolean initial)
|
|||
|
||||
private List<NamedFactory<UserAuth>> getAuthFactories() {
|
||||
List<NamedFactory<UserAuth>> authentications = new ArrayList<>();
|
||||
authentications.add(
|
||||
ServerAuthenticationManager.DEFAULT_USER_AUTH_PUBLIC_KEY_FACTORY);
|
||||
authentications.add(new UserAuthGSSFactory() {
|
||||
@Override
|
||||
public UserAuth create() {
|
||||
return new FakeUserAuthGSS();
|
||||
}
|
||||
});
|
||||
authentications.add(
|
||||
ServerAuthenticationManager.DEFAULT_USER_AUTH_PUBLIC_KEY_FACTORY);
|
||||
authentications.add(
|
||||
ServerAuthenticationManager.DEFAULT_USER_AUTH_KB_INTERACTIVE_FACTORY);
|
||||
authentications.add(
|
||||
ServerAuthenticationManager.DEFAULT_USER_AUTH_PASSWORD_FACTORY);
|
||||
return authentications;
|
||||
}
|
||||
|
||||
|
@ -280,6 +286,30 @@ public void addHostKey(@NonNull Path key, boolean inFront)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable password authentication. The server will accept the test user's
|
||||
* name, converted to all upper-case, as password.
|
||||
*/
|
||||
public void enablePasswordAuthentication() {
|
||||
server.setPasswordAuthenticator((user, pwd, session) -> {
|
||||
return testUser.equals(user)
|
||||
&& testUser.toUpperCase(Locale.ROOT).equals(pwd);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable keyboard-interactive authentication. The server will accept the
|
||||
* test user's name, converted to all upper-case, as password.
|
||||
*/
|
||||
public void enableKeyboardInteractiveAuthentication() {
|
||||
server.setPasswordAuthenticator((user, pwd, session) -> {
|
||||
return testUser.equals(user)
|
||||
&& testUser.toUpperCase(Locale.ROOT).equals(pwd);
|
||||
});
|
||||
server.setKeyboardInteractiveAuthenticator(
|
||||
DefaultKeyboardInteractiveAuthenticator.INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the test server, listening on a random port.
|
||||
*
|
||||
|
|
|
@ -44,6 +44,7 @@ knownHostsUnknownKeyPrompt=Accept and store this key, and continue connecting?
|
|||
knownHostsUnknownKeyType=Cannot read server key from known hosts file {0}; line {1}
|
||||
knownHostsUserAskCreationMsg=File {0} does not exist.
|
||||
knownHostsUserAskCreationPrompt=Create file {0} ?
|
||||
passwordPrompt=Password
|
||||
proxyCannotAuthenticate=Cannot authenticate to proxy {0}
|
||||
proxyHttpFailure=HTTP Proxy connection to {0} failed with code {1}: {2}
|
||||
proxyHttpInvalidUserName=HTTP proxy connection {0} with invalid user name; must not contain colons: {1}
|
||||
|
|
|
@ -60,6 +60,22 @@ public class JGitHostConfigEntry extends HostConfigEntry {
|
|||
|
||||
private Map<String, List<String>> multiValuedOptions;
|
||||
|
||||
@Override
|
||||
public String getProperty(String name, String defaultValue) {
|
||||
// Upstream bug fix (SSHD-867): if there are _no_ properties at all, the
|
||||
// super implementation returns always null even if a default value is
|
||||
// given.
|
||||
//
|
||||
// See https://issues.apache.org/jira/projects/SSHD/issues/SSHD-867
|
||||
//
|
||||
// TODO: remove this override once we're based on sshd > 2.1.0
|
||||
Map<String, String> properties = getProperties();
|
||||
if (properties == null || properties.isEmpty()) {
|
||||
return defaultValue;
|
||||
}
|
||||
return super.getProperty(name, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the multi-valued options.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
|
||||
* and other copyright owners as documented in the project's IP log.
|
||||
*
|
||||
* This program and the accompanying materials are made available
|
||||
* under the terms of the Eclipse Distribution License v1.0 which
|
||||
* accompanies this distribution, is reproduced below, and is
|
||||
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or
|
||||
* without modification, are permitted provided that the following
|
||||
* conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following
|
||||
* disclaimer in the documentation and/or other materials provided
|
||||
* with the distribution.
|
||||
*
|
||||
* - Neither the name of the Eclipse Foundation, Inc. nor the
|
||||
* names of its contributors may be used to endorse or promote
|
||||
* products derived from this software without specific prior
|
||||
* written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package org.eclipse.jgit.internal.transport.sshd;
|
||||
|
||||
import org.apache.sshd.client.auth.AbstractUserAuthFactory;
|
||||
import org.apache.sshd.client.auth.UserAuth;
|
||||
import org.apache.sshd.client.auth.password.UserAuthPasswordFactory;
|
||||
|
||||
/**
|
||||
* A customized {@link UserAuthPasswordFactory} that creates instance of
|
||||
* {@link JGitPasswordAuthentication}.
|
||||
*/
|
||||
public class JGitPasswordAuthFactory extends AbstractUserAuthFactory {
|
||||
|
||||
/** The singleton {@link JGitPasswordAuthFactory}. */
|
||||
public static final JGitPasswordAuthFactory INSTANCE = new JGitPasswordAuthFactory();
|
||||
|
||||
private JGitPasswordAuthFactory() {
|
||||
super(UserAuthPasswordFactory.NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserAuth create() {
|
||||
return new JGitPasswordAuthentication();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
|
||||
* and other copyright owners as documented in the project's IP log.
|
||||
*
|
||||
* This program and the accompanying materials are made available
|
||||
* under the terms of the Eclipse Distribution License v1.0 which
|
||||
* accompanies this distribution, is reproduced below, and is
|
||||
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or
|
||||
* without modification, are permitted provided that the following
|
||||
* conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* - Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following
|
||||
* disclaimer in the documentation and/or other materials provided
|
||||
* with the distribution.
|
||||
*
|
||||
* - Neither the name of the Eclipse Foundation, Inc. nor the
|
||||
* names of its contributors may be used to endorse or promote
|
||||
* products derived from this software without specific prior
|
||||
* written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package org.eclipse.jgit.internal.transport.sshd;
|
||||
|
||||
import java.util.concurrent.CancellationException;
|
||||
|
||||
import org.apache.sshd.client.ClientAuthenticationManager;
|
||||
import org.apache.sshd.client.auth.keyboard.UserInteraction;
|
||||
import org.apache.sshd.client.auth.password.UserAuthPassword;
|
||||
import org.apache.sshd.client.session.ClientSession;
|
||||
|
||||
/**
|
||||
* A password authentication handler that uses the {@link JGitUserInteraction}
|
||||
* to ask the user for the password. It also respects the
|
||||
* {@code NumberOfPasswordPrompts} ssh config.
|
||||
*/
|
||||
public class JGitPasswordAuthentication extends UserAuthPassword {
|
||||
|
||||
private int maxAttempts;
|
||||
|
||||
private int attempts;
|
||||
|
||||
@Override
|
||||
public void init(ClientSession session, String service) throws Exception {
|
||||
super.init(session, service);
|
||||
maxAttempts = Math.max(1,
|
||||
session.getIntProperty(
|
||||
ClientAuthenticationManager.PASSWORD_PROMPTS,
|
||||
ClientAuthenticationManager.DEFAULT_PASSWORD_PROMPTS));
|
||||
attempts = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean sendAuthDataRequest(ClientSession session, String service)
|
||||
throws Exception {
|
||||
if (++attempts > maxAttempts) {
|
||||
return false;
|
||||
}
|
||||
UserInteraction interaction = session.getUserInteraction();
|
||||
if (!interaction.isInteractionAllowed(session)) {
|
||||
return false;
|
||||
}
|
||||
String password = getPassword(session, interaction);
|
||||
if (password == null) {
|
||||
throw new CancellationException();
|
||||
}
|
||||
// sendPassword takes a buffer as first argument, but actually doesn't
|
||||
// use it and creates its own buffer...
|
||||
sendPassword(null, session, password, password);
|
||||
return true;
|
||||
}
|
||||
|
||||
private String getPassword(ClientSession session,
|
||||
UserInteraction interaction) {
|
||||
String[] results = interaction.interactive(session, null, null, "", //$NON-NLS-1$
|
||||
new String[] { SshdText.get().passwordPrompt },
|
||||
new boolean[] { false });
|
||||
return (results == null || results.length == 0) ? null : results[0];
|
||||
}
|
||||
}
|
|
@ -105,23 +105,6 @@ public HostConfigEntry resolveEffectiveHost(String host, int port,
|
|||
String username) throws IOException {
|
||||
HostEntry entry = configFile.lookup(host, port, username);
|
||||
JGitHostConfigEntry config = new JGitHostConfigEntry();
|
||||
String hostName = entry.getValue(SshConstants.HOST_NAME);
|
||||
if (hostName == null || hostName.isEmpty()) {
|
||||
hostName = host;
|
||||
}
|
||||
config.setHostName(hostName);
|
||||
config.setHost(SshdSocketAddress.isIPv6Address(hostName) ? "" : hostName); //$NON-NLS-1$
|
||||
String user = username != null && !username.isEmpty() ? username
|
||||
: entry.getValue(SshConstants.USER);
|
||||
if (user == null || user.isEmpty()) {
|
||||
user = configFile.getLocalUserName();
|
||||
}
|
||||
config.setUsername(user);
|
||||
int p = port >= 0 ? port : positive(entry.getValue(SshConstants.PORT));
|
||||
config.setPort(p >= 0 ? p : SshConstants.SSH_DEFAULT_PORT);
|
||||
config.setIdentities(entry.getValues(SshConstants.IDENTITY_FILE));
|
||||
config.setIdentitiesOnly(
|
||||
flag(entry.getValue(SshConstants.IDENTITIES_ONLY)));
|
||||
// Apache MINA conflates all keys, even multi-valued ones, in one map
|
||||
// and puts multiple values separated by commas in one string. See
|
||||
// the javadoc on HostConfigEntry.
|
||||
|
@ -135,6 +118,28 @@ public HostConfigEntry resolveEffectiveHost(String host, int port,
|
|||
config.setProperties(allOptions);
|
||||
// The following is an extension from JGitHostConfigEntry
|
||||
config.setMultiValuedOptions(entry.getMultiValuedOptions());
|
||||
// Also make sure the underlying properties are set
|
||||
String hostName = entry.getValue(SshConstants.HOST_NAME);
|
||||
if (hostName == null || hostName.isEmpty()) {
|
||||
hostName = host;
|
||||
}
|
||||
config.setHostName(hostName);
|
||||
config.setProperty(SshConstants.HOST_NAME, hostName);
|
||||
config.setHost(SshdSocketAddress.isIPv6Address(hostName) ? "" : hostName); //$NON-NLS-1$
|
||||
String user = username != null && !username.isEmpty() ? username
|
||||
: entry.getValue(SshConstants.USER);
|
||||
if (user == null || user.isEmpty()) {
|
||||
user = configFile.getLocalUserName();
|
||||
}
|
||||
config.setUsername(user);
|
||||
config.setProperty(SshConstants.USER, user);
|
||||
int p = port >= 0 ? port : positive(entry.getValue(SshConstants.PORT));
|
||||
config.setPort(p >= 0 ? p : SshConstants.SSH_DEFAULT_PORT);
|
||||
config.setProperty(SshConstants.PORT,
|
||||
Integer.toString(config.getPort()));
|
||||
config.setIdentities(entry.getValues(SshConstants.IDENTITY_FILE));
|
||||
config.setIdentitiesOnly(
|
||||
flag(entry.getValue(SshConstants.IDENTITIES_ONLY)));
|
||||
return config;
|
||||
}
|
||||
|
||||
|
|
|
@ -45,9 +45,13 @@
|
|||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.apache.sshd.client.auth.keyboard.UserInteraction;
|
||||
import org.apache.sshd.client.session.ClientSession;
|
||||
import org.apache.sshd.common.session.Session;
|
||||
import org.apache.sshd.common.session.SessionListener;
|
||||
import org.eclipse.jgit.transport.CredentialItem;
|
||||
import org.eclipse.jgit.transport.CredentialsProvider;
|
||||
import org.eclipse.jgit.transport.SshConstants;
|
||||
|
@ -61,6 +65,12 @@ public class JGitUserInteraction implements UserInteraction {
|
|||
|
||||
private final CredentialsProvider provider;
|
||||
|
||||
/**
|
||||
* We need to reset the JGit credentials provider if we have repeated
|
||||
* attempts.
|
||||
*/
|
||||
private final Map<Session, SessionListener> ongoing = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Creates a new {@link JGitUserInteraction} for interactive password input
|
||||
* based on the given {@link CredentialsProvider}.
|
||||
|
@ -74,13 +84,13 @@ public JGitUserInteraction(CredentialsProvider provider) {
|
|||
|
||||
@Override
|
||||
public boolean isInteractionAllowed(ClientSession session) {
|
||||
return provider.isInteractive();
|
||||
return provider != null && provider.isInteractive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] interactive(ClientSession session, String name,
|
||||
String instruction, String lang, String[] prompt, boolean[] echo) {
|
||||
// This is keyboard-interactive authentication
|
||||
// This is keyboard-interactive or password authentication
|
||||
List<CredentialItem> items = new ArrayList<>();
|
||||
int numberOfHiddenInputs = 0;
|
||||
for (int i = 0; i < prompt.length; i++) {
|
||||
|
@ -120,6 +130,19 @@ public String[] interactive(ClientSession session, String name,
|
|||
}
|
||||
URIish uri = toURI(session.getUsername(),
|
||||
(InetSocketAddress) session.getConnectAddress());
|
||||
// Reset the provider for this URI if it's not the first attempt and we
|
||||
// have hidden inputs. Otherwise add a session listener that will remove
|
||||
// itself once authenticated.
|
||||
if (numberOfHiddenInputs > 0) {
|
||||
SessionListener listener = ongoing.get(session);
|
||||
if (listener != null) {
|
||||
provider.reset(uri);
|
||||
} else {
|
||||
listener = new SessionAuthMarker(ongoing);
|
||||
ongoing.put(session, listener);
|
||||
session.addSessionListener(listener);
|
||||
}
|
||||
}
|
||||
if (provider.get(uri, items)) {
|
||||
return items.stream().map(i -> {
|
||||
if (i instanceof CredentialItem.Password) {
|
||||
|
@ -166,4 +189,31 @@ public static URIish toURI(String userName, InetSocketAddress remote) {
|
|||
.setPort(port) //
|
||||
.setUser(userName);
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link SessionListener} that removes itself from the session when
|
||||
* authentication is done or the session is closed.
|
||||
*/
|
||||
private static class SessionAuthMarker implements SessionListener {
|
||||
|
||||
private final Map<Session, SessionListener> registered;
|
||||
|
||||
public SessionAuthMarker(Map<Session, SessionListener> registered) {
|
||||
this.registered = registered;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sessionEvent(Session session, SessionListener.Event event) {
|
||||
if (event == SessionListener.Event.Authenticated) {
|
||||
session.removeSessionListener(this);
|
||||
registered.remove(session, this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sessionClosed(Session session) {
|
||||
session.removeSessionListener(this);
|
||||
registered.remove(session, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,6 +56,7 @@ public static SshdText get() {
|
|||
/***/ public String knownHostsUnknownKeyType;
|
||||
/***/ public String knownHostsUserAskCreationMsg;
|
||||
/***/ public String knownHostsUserAskCreationPrompt;
|
||||
/***/ public String passwordPrompt;
|
||||
/***/ public String proxyCannotAuthenticate;
|
||||
/***/ public String proxyHttpFailure;
|
||||
/***/ public String proxyHttpInvalidUserName;
|
||||
|
|
|
@ -63,7 +63,6 @@
|
|||
import org.apache.sshd.client.SshClient;
|
||||
import org.apache.sshd.client.auth.UserAuth;
|
||||
import org.apache.sshd.client.auth.keyboard.UserAuthKeyboardInteractiveFactory;
|
||||
import org.apache.sshd.client.auth.password.UserAuthPasswordFactory;
|
||||
import org.apache.sshd.client.config.hosts.HostConfigEntryResolver;
|
||||
import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
|
||||
import org.apache.sshd.common.NamedFactory;
|
||||
|
@ -75,6 +74,7 @@
|
|||
import org.eclipse.jgit.errors.TransportException;
|
||||
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.JGitSshClient;
|
||||
import org.eclipse.jgit.internal.transport.sshd.JGitSshConfig;
|
||||
|
@ -465,21 +465,23 @@ private FilePasswordProvider createFilePasswordProvider(
|
|||
|
||||
/**
|
||||
* Gets the user authentication mechanisms (or rather, factories for them).
|
||||
* By default this returns gssapi-with-mic, public-key,
|
||||
* keyboard-interactive, and password, in that order. The order is only
|
||||
* significant if the ssh config does <em>not</em> set
|
||||
* {@code PreferredAuthentications}; if it is set, the order defined there
|
||||
* will be taken.
|
||||
* By default this returns gssapi-with-mic, public-key, password, and
|
||||
* keyboard-interactive, in that order. The order is only significant if the
|
||||
* ssh config does <em>not</em> set {@code PreferredAuthentications}; if it
|
||||
* is set, the order defined there will be taken.
|
||||
*
|
||||
* @return the non-empty list of factories.
|
||||
*/
|
||||
@NonNull
|
||||
private List<NamedFactory<UserAuth>> getUserAuthFactories() {
|
||||
// About the order of password and keyboard-interactive, see upstream
|
||||
// bug https://issues.apache.org/jira/projects/SSHD/issues/SSHD-866 .
|
||||
// Password auth doesn't have this problem.
|
||||
return Collections.unmodifiableList(
|
||||
Arrays.asList(GssApiWithMicAuthFactory.INSTANCE,
|
||||
JGitPublicKeyAuthFactory.INSTANCE,
|
||||
UserAuthKeyboardInteractiveFactory.INSTANCE,
|
||||
UserAuthPasswordFactory.INSTANCE));
|
||||
JGitPasswordAuthFactory.INSTANCE,
|
||||
UserAuthKeyboardInteractiveFactory.INSTANCE));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.eclipse.jgit.api.errors.TransportException;
|
||||
import org.eclipse.jgit.transport.CredentialItem;
|
||||
|
@ -668,6 +669,137 @@ public void testEcDsaHostKey() throws Exception {
|
|||
"IdentityFile " + privateKey1.getAbsolutePath());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPasswordAuth() throws Exception {
|
||||
server.enablePasswordAuthentication();
|
||||
TestCredentialsProvider provider = new TestCredentialsProvider(
|
||||
TEST_USER.toUpperCase(Locale.ROOT));
|
||||
cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
|
||||
"Host git", //
|
||||
"HostName localhost", //
|
||||
"Port " + testPort, //
|
||||
"User " + TEST_USER, //
|
||||
"PreferredAuthentications password");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPasswordAuthSeveralTimes() throws Exception {
|
||||
server.enablePasswordAuthentication();
|
||||
TestCredentialsProvider provider = new TestCredentialsProvider(
|
||||
"wrongpass", "wrongpass", TEST_USER.toUpperCase(Locale.ROOT));
|
||||
cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
|
||||
"Host git", //
|
||||
"HostName localhost", //
|
||||
"Port " + testPort, //
|
||||
"User " + TEST_USER, //
|
||||
"PreferredAuthentications password");
|
||||
}
|
||||
|
||||
@Test(expected = TransportException.class)
|
||||
public void testPasswordAuthWrongPassword() throws Exception {
|
||||
server.enablePasswordAuthentication();
|
||||
TestCredentialsProvider provider = new TestCredentialsProvider(
|
||||
"wrongpass");
|
||||
cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
|
||||
"Host git", //
|
||||
"HostName localhost", //
|
||||
"Port " + testPort, //
|
||||
"User " + TEST_USER, //
|
||||
"PreferredAuthentications password");
|
||||
}
|
||||
|
||||
@Test(expected = TransportException.class)
|
||||
public void testPasswordAuthNoPassword() throws Exception {
|
||||
server.enablePasswordAuthentication();
|
||||
TestCredentialsProvider provider = new TestCredentialsProvider();
|
||||
cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
|
||||
"Host git", //
|
||||
"HostName localhost", //
|
||||
"Port " + testPort, //
|
||||
"User " + TEST_USER, //
|
||||
"PreferredAuthentications password");
|
||||
}
|
||||
|
||||
@Test(expected = TransportException.class)
|
||||
public void testPasswordAuthCorrectPasswordTooLate() throws Exception {
|
||||
server.enablePasswordAuthentication();
|
||||
TestCredentialsProvider provider = new TestCredentialsProvider(
|
||||
"wrongpass", "wrongpass", "wrongpass",
|
||||
TEST_USER.toUpperCase(Locale.ROOT));
|
||||
cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
|
||||
"Host git", //
|
||||
"HostName localhost", //
|
||||
"Port " + testPort, //
|
||||
"User " + TEST_USER, //
|
||||
"PreferredAuthentications password");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeyboardInteractiveAuth() throws Exception {
|
||||
server.enableKeyboardInteractiveAuthentication();
|
||||
TestCredentialsProvider provider = new TestCredentialsProvider(
|
||||
TEST_USER.toUpperCase(Locale.ROOT));
|
||||
cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
|
||||
"Host git", //
|
||||
"HostName localhost", //
|
||||
"Port " + testPort, //
|
||||
"User " + TEST_USER, //
|
||||
"PreferredAuthentications keyboard-interactive");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeyboardInteractiveAuthSeveralTimes() throws Exception {
|
||||
server.enableKeyboardInteractiveAuthentication();
|
||||
TestCredentialsProvider provider = new TestCredentialsProvider(
|
||||
"wrongpass", "wrongpass", TEST_USER.toUpperCase(Locale.ROOT));
|
||||
cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
|
||||
"Host git", //
|
||||
"HostName localhost", //
|
||||
"Port " + testPort, //
|
||||
"User " + TEST_USER, //
|
||||
"PreferredAuthentications keyboard-interactive");
|
||||
}
|
||||
|
||||
@Test(expected = TransportException.class)
|
||||
public void testKeyboardInteractiveAuthWrongPassword() throws Exception {
|
||||
server.enableKeyboardInteractiveAuthentication();
|
||||
TestCredentialsProvider provider = new TestCredentialsProvider(
|
||||
"wrongpass");
|
||||
cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
|
||||
"Host git", //
|
||||
"HostName localhost", //
|
||||
"Port " + testPort, //
|
||||
"User " + TEST_USER, //
|
||||
"PreferredAuthentications keyboard-interactive");
|
||||
}
|
||||
|
||||
@Test(expected = TransportException.class)
|
||||
public void testKeyboardInteractiveAuthNoPassword() throws Exception {
|
||||
server.enableKeyboardInteractiveAuthentication();
|
||||
TestCredentialsProvider provider = new TestCredentialsProvider();
|
||||
cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
|
||||
"Host git", //
|
||||
"HostName localhost", //
|
||||
"Port " + testPort, //
|
||||
"User " + TEST_USER, //
|
||||
"PreferredAuthentications keyboard-interactive");
|
||||
}
|
||||
|
||||
@Test(expected = TransportException.class)
|
||||
public void testKeyboardInteractiveAuthCorrectPasswordTooLate()
|
||||
throws Exception {
|
||||
server.enableKeyboardInteractiveAuthentication();
|
||||
TestCredentialsProvider provider = new TestCredentialsProvider(
|
||||
"wrongpass", "wrongpass", "wrongpass",
|
||||
TEST_USER.toUpperCase(Locale.ROOT));
|
||||
cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
|
||||
"Host git", //
|
||||
"HostName localhost", //
|
||||
"Port " + testPort, //
|
||||
"User " + TEST_USER, //
|
||||
"PreferredAuthentications keyboard-interactive");
|
||||
}
|
||||
|
||||
@Theory
|
||||
public void testSshKeys(String keyName) throws Exception {
|
||||
// JSch fails on ECDSA 384/521 keys. Compare
|
||||
|
|
Loading…
Reference in New Issue