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:
Thomas Wolf 2018-11-16 17:00:25 +01:00 committed by Matthias Sohn
parent 1316d43e51
commit 00b235f0b8
11 changed files with 435 additions and 30 deletions

View File

@ -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)",

View File

@ -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.
*

View File

@ -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}

View File

@ -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.
*

View File

@ -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();
}
}

View File

@ -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];
}
}

View File

@ -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;
}

View File

@ -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);
}
}
}

View File

@ -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;

View File

@ -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));
}
/**

View File

@ -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