You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2021/01/02 07:14:51 UTC
[mina-sshd] 12/15: [SSHD-1114] Split AuthenticationTest class into
several classes
This is an automated email from the ASF dual-hosted git repository.
lgoldstein pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git
commit 6f0513c18876476cc5602f1489597e27d7552fa6
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Fri Jan 1 06:58:35 2021 +0200
[SSHD-1114] Split AuthenticationTest class into several classes
---
.../sshd/common/auth/AuthenticationTest.java | 1046 +-------------------
.../common/auth/AuthenticationTestSupport.java | 105 ++
.../common/auth/HostBasedAuthenticationTest.java | 150 +++
.../KeyboardInteractiveAuthenticationTest.java | 233 +++++
.../common/auth/PasswordAuthenticationTest.java | 440 ++++++++
.../common/auth/PublicKeyAuthenticationTest.java | 327 ++++++
.../sshd/util/test/server/TestServerSession.java | 39 +
7 files changed, 1295 insertions(+), 1045 deletions(-)
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/auth/AuthenticationTest.java b/sshd-core/src/test/java/org/apache/sshd/common/auth/AuthenticationTest.java
index beb3141..d3aece6 100644
--- a/sshd-core/src/test/java/org/apache/sshd/common/auth/AuthenticationTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/common/auth/AuthenticationTest.java
@@ -19,556 +19,33 @@
package org.apache.sshd.common.auth;
import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.PublicKey;
-import java.security.spec.InvalidKeySpecException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Consumer;
import org.apache.sshd.client.SshClient;
-import org.apache.sshd.client.auth.hostbased.HostBasedAuthenticationReporter;
-import org.apache.sshd.client.auth.hostbased.HostKeyIdentityProvider;
-import org.apache.sshd.client.auth.keyboard.UserInteraction;
-import org.apache.sshd.client.auth.password.PasswordAuthenticationReporter;
-import org.apache.sshd.client.auth.password.PasswordIdentityProvider;
-import org.apache.sshd.client.auth.pubkey.PublicKeyAuthenticationReporter;
import org.apache.sshd.client.future.AuthFuture;
import org.apache.sshd.client.session.ClientSession;
-import org.apache.sshd.common.AttributeRepository;
-import org.apache.sshd.common.NamedFactory;
-import org.apache.sshd.common.NamedResource;
-import org.apache.sshd.common.SshConstants;
-import org.apache.sshd.common.SshException;
-import org.apache.sshd.common.config.keys.FilePasswordProvider;
import org.apache.sshd.common.config.keys.KeyUtils;
-import org.apache.sshd.common.io.IoSession;
-import org.apache.sshd.common.io.IoWriteFuture;
import org.apache.sshd.common.kex.KeyExchange;
-import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.common.session.Session;
-import org.apache.sshd.common.session.SessionContext;
import org.apache.sshd.common.session.SessionListener;
-import org.apache.sshd.common.signature.BuiltinSignatures;
-import org.apache.sshd.common.signature.Signature;
-import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.MapEntryUtils.NavigableMapBuilder;
-import org.apache.sshd.common.util.ValidateUtils;
-import org.apache.sshd.common.util.buffer.Buffer;
-import org.apache.sshd.common.util.io.resource.URLResource;
-import org.apache.sshd.common.util.net.SshdSocketAddress;
-import org.apache.sshd.common.util.security.SecurityUtils;
-import org.apache.sshd.core.CoreModuleProperties;
-import org.apache.sshd.server.ServerFactoryManager;
-import org.apache.sshd.server.SshServer;
-import org.apache.sshd.server.auth.hostbased.HostBasedAuthenticator;
-import org.apache.sshd.server.auth.keyboard.DefaultKeyboardInteractiveAuthenticator;
-import org.apache.sshd.server.auth.keyboard.InteractiveChallenge;
import org.apache.sshd.server.auth.keyboard.KeyboardInteractiveAuthenticator;
-import org.apache.sshd.server.auth.keyboard.PromptEntry;
-import org.apache.sshd.server.auth.keyboard.UserAuthKeyboardInteractiveFactory;
import org.apache.sshd.server.auth.password.PasswordAuthenticator;
-import org.apache.sshd.server.auth.password.PasswordChangeRequiredException;
-import org.apache.sshd.server.auth.password.RejectAllPasswordAuthenticator;
-import org.apache.sshd.server.auth.password.UserAuthPasswordFactory;
import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
-import org.apache.sshd.server.auth.pubkey.RejectAllPublickeyAuthenticator;
-import org.apache.sshd.server.session.ServerSession;
-import org.apache.sshd.server.session.ServerSessionImpl;
-import org.apache.sshd.server.session.SessionFactory;
-import org.apache.sshd.util.test.BaseTestSupport;
import org.apache.sshd.util.test.CommonTestSupportUtils;
-import org.apache.sshd.util.test.CoreTestSupportUtils;
-import org.junit.After;
-import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class AuthenticationTest extends BaseTestSupport {
-
- private static final AttributeRepository.AttributeKey<Boolean> PASSWORD_ATTR = new AttributeRepository.AttributeKey<>();
-
- private SshServer sshd;
- private int port;
-
+public class AuthenticationTest extends AuthenticationTestSupport {
public AuthenticationTest() {
super();
}
- @Before
- public void setUp() throws Exception {
- sshd = setupTestServer();
- sshd.setSessionFactory(new SessionFactory(sshd) {
- @Override
- protected ServerSessionImpl doCreateSession(IoSession ioSession) throws Exception {
- return new TestSession(getServer(), ioSession);
- }
- });
- sshd.start();
- port = sshd.getPort();
- }
-
- @After
- public void tearDown() throws Exception {
- if (sshd != null) {
- sshd.stop(true);
- }
- }
-
- @Test
- public void testWrongPassword() throws Exception {
- try (SshClient client = setupTestClient()) {
- client.start();
- try (ClientSession s = client.connect("user", TEST_LOCALHOST, port)
- .verify(CONNECT_TIMEOUT)
- .getSession()) {
- s.addPasswordIdentity("bad password");
- assertAuthenticationResult(getCurrentTestName(), s.auth(), false);
- }
- }
- }
-
- @Test
- public void testChangeUser() throws Exception {
- try (SshClient client = setupTestClient()) {
- client.start();
-
- try (ClientSession s = client.connect(null, TEST_LOCALHOST, port)
- .verify(CONNECT_TIMEOUT)
- .getSession()) {
- Collection<ClientSession.ClientSessionEvent> mask
- = EnumSet.of(ClientSession.ClientSessionEvent.CLOSED, ClientSession.ClientSessionEvent.WAIT_AUTH);
- Collection<ClientSession.ClientSessionEvent> result = s.waitFor(mask, DEFAULT_TIMEOUT);
- assertFalse("Timeout while waiting on session events",
- result.contains(ClientSession.ClientSessionEvent.TIMEOUT));
-
- String password = "the-password";
- for (String username : new String[] { "user1", "user2" }) {
- try {
- assertAuthenticationResult(username, authPassword(s, username, password), false);
- } finally {
- s.removePasswordIdentity(password);
- }
- }
-
- // Note that WAIT_AUTH flag should be false, but since the internal
- // authentication future is not updated, it's still returned
- result = s.waitFor(EnumSet.of(ClientSession.ClientSessionEvent.CLOSED), DEFAULT_TIMEOUT);
- assertTrue("Mismatched client session close mask: " + result, result.containsAll(mask));
- } finally {
- client.stop();
- }
- }
- }
-
- @Test // see SSHD-196
- public void testChangePassword() throws Exception {
- PasswordAuthenticator delegate = sshd.getPasswordAuthenticator();
- AtomicInteger attemptsCount = new AtomicInteger(0);
- AtomicInteger changesCount = new AtomicInteger(0);
- sshd.setPasswordAuthenticator(new PasswordAuthenticator() {
- @Override
- public boolean authenticate(String username, String password, ServerSession session) {
- if (attemptsCount.incrementAndGet() == 1) {
- throw new PasswordChangeRequiredException(
- attemptsCount.toString(),
- getCurrentTestName(), CoreModuleProperties.WELCOME_BANNER_LANGUAGE.getRequiredDefault());
- }
-
- return delegate.authenticate(username, password, session);
- }
-
- @Override
- public boolean handleClientPasswordChangeRequest(
- ServerSession session, String username, String oldPassword, String newPassword) {
- if (changesCount.incrementAndGet() == 1) {
- assertNotEquals("Non-different passwords", oldPassword, newPassword);
- return authenticate(username, newPassword, session);
- } else {
- return PasswordAuthenticator.super.handleClientPasswordChangeRequest(
- session, username, oldPassword, newPassword);
- }
- }
- });
- CoreModuleProperties.AUTH_METHODS.set(sshd, UserAuthPasswordFactory.NAME);
-
- try (SshClient client = setupTestClient()) {
- AtomicInteger updatesCount = new AtomicInteger(0);
- client.setUserInteraction(new UserInteraction() {
- @Override
- public boolean isInteractionAllowed(ClientSession session) {
- return true;
- }
-
- @Override
- public String[] interactive(
- ClientSession session, String name, String instruction,
- String lang, String[] prompt, boolean[] echo) {
- throw new UnsupportedOperationException("Unexpected call");
- }
-
- @Override
- public String getUpdatedPassword(ClientSession session, String prompt, String lang) {
- assertEquals("Mismatched prompt", getCurrentTestName(), prompt);
- assertEquals("Mismatched language",
- CoreModuleProperties.WELCOME_BANNER_LANGUAGE.getRequiredDefault(), lang);
- assertEquals("Unexpected repeated call", 1, updatesCount.incrementAndGet());
- return getCurrentTestName();
- }
- });
-
- AtomicInteger sentCount = new AtomicInteger(0);
- client.setUserAuthFactories(Collections.singletonList(
- new org.apache.sshd.client.auth.password.UserAuthPasswordFactory() {
- @Override
- public org.apache.sshd.client.auth.password.UserAuthPassword createUserAuth(ClientSession session)
- throws IOException {
- return new org.apache.sshd.client.auth.password.UserAuthPassword() {
- @Override
- protected IoWriteFuture sendPassword(
- Buffer buffer, ClientSession session, String oldPassword, String newPassword)
- throws Exception {
- int count = sentCount.incrementAndGet();
- // 1st one is the original one (which is denied by the server)
- // 2nd one is the updated one retrieved from the user interaction
- if (count == 2) {
- return super.sendPassword(buffer, session, getClass().getName(), newPassword);
- } else {
- return super.sendPassword(buffer, session, oldPassword, newPassword);
- }
- }
- };
- }
- }));
- CoreModuleProperties.AUTH_METHODS.set(client, UserAuthPasswordFactory.NAME);
-
- client.start();
-
- try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
- .verify(CONNECT_TIMEOUT)
- .getSession()) {
- s.addPasswordIdentity(getCurrentTestName());
- s.auth().verify(AUTH_TIMEOUT);
- assertEquals("No password change request generated", 2, attemptsCount.get());
- assertEquals("No password change handled", 1, changesCount.get());
- assertEquals("No user interaction invoked", 1, updatesCount.get());
- } finally {
- client.stop();
- }
- }
- }
-
- @Test
- public void testAuthPasswordOnly() throws Exception {
- try (SshClient client = setupTestClient()) {
- sshd.setPasswordAuthenticator(RejectAllPasswordAuthenticator.INSTANCE);
-
- client.start();
- try (ClientSession s = client.connect(null, TEST_LOCALHOST, port)
- .verify(CONNECT_TIMEOUT)
- .getSession()) {
- Collection<ClientSession.ClientSessionEvent> result = s.waitFor(
- EnumSet.of(ClientSession.ClientSessionEvent.CLOSED, ClientSession.ClientSessionEvent.WAIT_AUTH),
- DEFAULT_TIMEOUT);
- assertFalse("Timeout while waiting for session", result.contains(ClientSession.ClientSessionEvent.TIMEOUT));
-
- String password = getCurrentTestName();
- try {
- assertAuthenticationResult(getCurrentTestName(),
- authPassword(s, getCurrentTestName(), password), false);
- } finally {
- s.removePasswordIdentity(password);
- }
- } finally {
- client.stop();
- }
- }
- }
-
- @Test
- public void testAuthKeyPassword() throws Exception {
- try (SshClient client = setupTestClient()) {
- sshd.setPublickeyAuthenticator(RejectAllPublickeyAuthenticator.INSTANCE);
- sshd.setKeyboardInteractiveAuthenticator(KeyboardInteractiveAuthenticator.NONE);
-
- client.start();
-
- try (ClientSession s = client.connect(null, TEST_LOCALHOST, port)
- .verify(CONNECT_TIMEOUT)
- .getSession()) {
- Collection<ClientSession.ClientSessionEvent> result = s.waitFor(
- EnumSet.of(ClientSession.ClientSessionEvent.CLOSED, ClientSession.ClientSessionEvent.WAIT_AUTH),
- DEFAULT_TIMEOUT);
- assertFalse("Timeout while waiting for session", result.contains(ClientSession.ClientSessionEvent.TIMEOUT));
-
- KeyPairProvider provider = createTestHostKeyProvider();
- KeyPair pair = provider.loadKey(s, CommonTestSupportUtils.DEFAULT_TEST_HOST_KEY_TYPE);
- try {
- assertAuthenticationResult(UserAuthMethodFactory.PUBLIC_KEY,
- authPublicKey(s, getCurrentTestName(), pair), false);
- } finally {
- s.removePublicKeyIdentity(pair);
- }
-
- String password = getCurrentTestName();
- try {
- assertAuthenticationResult(UserAuthMethodFactory.PASSWORD,
- authPassword(s, getCurrentTestName(), password), true);
- } finally {
- s.removePasswordIdentity(password);
- }
- } finally {
- client.stop();
- }
- }
- }
-
- @Test // see SSHD-612
- public void testAuthDefaultKeyInteractive() throws Exception {
- try (SshClient client = setupTestClient()) {
- sshd.setPublickeyAuthenticator(RejectAllPublickeyAuthenticator.INSTANCE);
- sshd.setKeyboardInteractiveAuthenticator(new DefaultKeyboardInteractiveAuthenticator() {
- @Override
- public InteractiveChallenge generateChallenge(
- ServerSession session, String username, String lang, String subMethods)
- throws Exception {
- assertEquals("Mismatched user language",
- CoreModuleProperties.INTERACTIVE_LANGUAGE_TAG.getRequired(client),
- lang);
- assertEquals("Mismatched client sub-methods",
- CoreModuleProperties.INTERACTIVE_SUBMETHODS.getRequired(client),
- subMethods);
-
- InteractiveChallenge challenge = super.generateChallenge(session, username, lang, subMethods);
- assertEquals("Mismatched interaction name", getInteractionName(session), challenge.getInteractionName());
- assertEquals("Mismatched interaction instruction", getInteractionInstruction(session),
- challenge.getInteractionInstruction());
- assertEquals("Mismatched language tag", getInteractionLanguage(session), challenge.getLanguageTag());
-
- List<PromptEntry> entries = challenge.getPrompts();
- assertEquals("Mismatched prompts count", 1, GenericUtils.size(entries));
-
- PromptEntry entry = entries.get(0);
- assertEquals("Mismatched prompt", getInteractionPrompt(session), entry.getPrompt());
- assertEquals("Mismatched echo", isInteractionPromptEchoEnabled(session), entry.isEcho());
-
- return challenge;
- }
-
- @Override
- public boolean authenticate(
- ServerSession session, String username, List<String> responses)
- throws Exception {
- return super.authenticate(session, username, responses);
- }
-
- });
- client.start();
-
- try (ClientSession s = client.connect(null, TEST_LOCALHOST, port)
- .verify(CONNECT_TIMEOUT)
- .getSession()) {
- Collection<ClientSession.ClientSessionEvent> result = s.waitFor(
- EnumSet.of(ClientSession.ClientSessionEvent.CLOSED, ClientSession.ClientSessionEvent.WAIT_AUTH),
- DEFAULT_TIMEOUT);
- assertFalse("Timeout while waiting for session", result.contains(ClientSession.ClientSessionEvent.TIMEOUT));
-
- KeyPairProvider provider = createTestHostKeyProvider();
- KeyPair pair = provider.loadKey(s, CommonTestSupportUtils.DEFAULT_TEST_HOST_KEY_TYPE);
- try {
- assertAuthenticationResult(UserAuthMethodFactory.PUBLIC_KEY,
- authPublicKey(s, getCurrentTestName(), pair), false);
- } finally {
- s.removePublicKeyIdentity(pair);
- }
-
- try {
- assertAuthenticationResult(UserAuthMethodFactory.KB_INTERACTIVE,
- authInteractive(s, getCurrentTestName(), getCurrentTestName()), true);
- } finally {
- s.setUserInteraction(null);
- }
- } finally {
- client.stop();
- }
- }
- }
-
- @Test // see SSHD-563
- public void testAuthMultiChallengeKeyInteractive() throws Exception {
- Class<?> anchor = getClass();
- InteractiveChallenge challenge = new InteractiveChallenge();
- challenge.setInteractionName(getCurrentTestName());
- challenge.setInteractionInstruction(anchor.getPackage().getName());
- challenge.setLanguageTag(Locale.getDefault().getLanguage());
-
- Map<String, String> rspMap = NavigableMapBuilder.<String, String> builder(String.CASE_INSENSITIVE_ORDER)
- .put("class", anchor.getSimpleName())
- .put("package", anchor.getPackage().getName())
- .put("test", getCurrentTestName())
- .build();
- for (String prompt : rspMap.keySet()) {
- challenge.addPrompt(prompt, (GenericUtils.size(challenge.getPrompts()) & 0x1) != 0);
- }
-
- CoreModuleProperties.AUTH_METHODS.set(sshd, UserAuthKeyboardInteractiveFactory.NAME);
- AtomicInteger genCount = new AtomicInteger(0);
- AtomicInteger authCount = new AtomicInteger(0);
- sshd.setKeyboardInteractiveAuthenticator(new KeyboardInteractiveAuthenticator() {
- @Override
- public InteractiveChallenge generateChallenge(
- ServerSession session, String username, String lang, String subMethods)
- throws Exception {
- assertEquals("Unexpected challenge call", 1, genCount.incrementAndGet());
- return challenge;
- }
-
- @Override
- public boolean authenticate(
- ServerSession session, String username, List<String> responses)
- throws Exception {
- assertEquals("Unexpected authenticate call", 1, authCount.incrementAndGet());
- assertEquals("Mismatched number of responses", GenericUtils.size(rspMap), GenericUtils.size(responses));
-
- int index = 0;
- // Cannot use forEach because the index is not effectively final
- for (Map.Entry<String, String> re : rspMap.entrySet()) {
- String prompt = re.getKey();
- String expected = re.getValue();
- String actual = responses.get(index);
- assertEquals("Mismatched response for prompt=" + prompt, expected, actual);
- index++;
- }
- return true;
- }
- });
- CoreModuleProperties.AUTH_METHODS.set(sshd, UserAuthKeyboardInteractiveFactory.NAME);
-
- try (SshClient client = setupTestClient()) {
- AtomicInteger interactiveCount = new AtomicInteger(0);
- client.setUserInteraction(new UserInteraction() {
- @Override
- public boolean isInteractionAllowed(ClientSession session) {
- return true;
- }
-
- @Override
- public String[] interactive(
- ClientSession session, String name, String instruction,
- String lang, String[] prompt, boolean[] echo) {
- assertEquals("Unexpected multiple calls", 1, interactiveCount.incrementAndGet());
- assertEquals("Mismatched name", challenge.getInteractionName(), name);
- assertEquals("Mismatched instruction", challenge.getInteractionInstruction(), instruction);
- assertEquals("Mismatched language", challenge.getLanguageTag(), lang);
-
- List<PromptEntry> entries = challenge.getPrompts();
- assertEquals("Mismatched prompts count", GenericUtils.size(entries), GenericUtils.length(prompt));
-
- String[] responses = new String[prompt.length];
- for (int index = 0; index < prompt.length; index++) {
- PromptEntry e = entries.get(index);
- String key = e.getPrompt();
- assertEquals("Mismatched prompt at index=" + index, key, prompt[index]);
- assertEquals("Mismatched echo at index=" + index, e.isEcho(), echo[index]);
- responses[index] = ValidateUtils.checkNotNull(rspMap.get(key), "No value for prompt=%s", key);
- }
-
- return responses;
- }
-
- @Override
- public String getUpdatedPassword(ClientSession session, String prompt, String lang) {
- throw new UnsupportedOperationException("Unexpected call");
- }
- });
- CoreModuleProperties.AUTH_METHODS.set(client, UserAuthKeyboardInteractiveFactory.NAME);
-
- client.start();
-
- try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
- .verify(CONNECT_TIMEOUT)
- .getSession()) {
- s.auth().verify(AUTH_TIMEOUT);
- assertEquals("Bad generated challenge count", 1, genCount.get());
- assertEquals("Bad authentication count", 1, authCount.get());
- assertEquals("Bad interactive count", 1, interactiveCount.get());
- } finally {
- client.stop();
- }
- }
- }
-
- @Test // see SSHD-196
- public void testAuthPasswordChangeRequest() throws Exception {
- PasswordAuthenticator delegate = Objects.requireNonNull(sshd.getPasswordAuthenticator(), "No password authenticator");
- AtomicInteger attemptsCount = new AtomicInteger(0);
- sshd.setPasswordAuthenticator((username, password, session) -> {
- if (attemptsCount.incrementAndGet() == 1) {
- throw new PasswordChangeRequiredException(
- attemptsCount.toString(),
- getCurrentTestName(), CoreModuleProperties.WELCOME_BANNER_LANGUAGE.getRequiredDefault());
- }
-
- return delegate.authenticate(username, password, session);
- });
- CoreModuleProperties.AUTH_METHODS.set(sshd, UserAuthPasswordFactory.NAME);
-
- try (SshClient client = setupTestClient()) {
- AtomicInteger updatesCount = new AtomicInteger(0);
- client.setUserInteraction(new UserInteraction() {
- @Override
- public boolean isInteractionAllowed(ClientSession session) {
- return true;
- }
-
- @Override
- public String[] interactive(
- ClientSession session, String name, String instruction,
- String lang, String[] prompt, boolean[] echo) {
- throw new UnsupportedOperationException("Unexpected call");
- }
-
- @Override
- public String getUpdatedPassword(ClientSession session, String prompt, String lang) {
- assertEquals("Mismatched prompt", getCurrentTestName(), prompt);
- assertEquals("Mismatched language",
- CoreModuleProperties.WELCOME_BANNER_LANGUAGE.getRequiredDefault(), lang);
- assertEquals("Unexpected repeated call", 1, updatesCount.incrementAndGet());
- return getCurrentTestName();
- }
- });
- CoreModuleProperties.AUTH_METHODS.set(client, UserAuthPasswordFactory.NAME);
-
- client.start();
-
- try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
- .verify(CONNECT_TIMEOUT)
- .getSession()) {
- s.addPasswordIdentity(getCurrentTestName());
- s.auth().verify(AUTH_TIMEOUT);
- assertEquals("No password change request generated", 2, attemptsCount.get());
- assertEquals("No user interaction invoked", 1, updatesCount.get());
- } finally {
- client.stop();
- }
- }
- }
-
@Test // see SSHD-600
public void testAuthExceptionPropagation() throws Exception {
try (SshClient client = setupTestClient()) {
@@ -619,257 +96,6 @@ public class AuthenticationTest extends BaseTestSupport {
}
}
- @Test
- public void testPasswordIdentityProviderPropagation() throws Exception {
- try (SshClient client = setupTestClient()) {
- List<String> passwords = Collections.singletonList(getCurrentTestName());
- AtomicInteger loadCount = new AtomicInteger(0);
- PasswordIdentityProvider provider = () -> {
- loadCount.incrementAndGet();
- outputDebugMessage("loadPasswords - count=%s", loadCount);
- return passwords;
- };
- client.setPasswordIdentityProvider(provider);
-
- client.start();
- try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
- .verify(CONNECT_TIMEOUT)
- .getSession()) {
- s.auth().verify(AUTH_TIMEOUT);
- assertEquals("Mismatched load passwords count", 1, loadCount.get());
- assertSame("Mismatched passwords identity provider", provider, s.getPasswordIdentityProvider());
- } finally {
- client.stop();
- }
- }
- }
-
- @Test // see SSHD-618
- public void testPublicKeyAuthDifferentThanKex() throws Exception {
- KeyPairProvider serverKeys = KeyPairProvider.wrap(
- CommonTestSupportUtils.generateKeyPair(KeyUtils.RSA_ALGORITHM, 1024),
- CommonTestSupportUtils.generateKeyPair(KeyUtils.DSS_ALGORITHM, 512),
- CommonTestSupportUtils.generateKeyPair(KeyUtils.EC_ALGORITHM, 256));
- sshd.setKeyPairProvider(serverKeys);
- sshd.setKeyboardInteractiveAuthenticator(KeyboardInteractiveAuthenticator.NONE);
- sshd.setPasswordAuthenticator(RejectAllPasswordAuthenticator.INSTANCE);
-
- KeyPair clientIdentity = CommonTestSupportUtils.generateKeyPair(KeyUtils.EC_ALGORITHM, 256);
- sshd.setPublickeyAuthenticator((username, key, session) -> {
- String keyType = KeyUtils.getKeyType(key);
- String expType = KeyUtils.getKeyType(clientIdentity);
- assertEquals("Mismatched client key types", expType, keyType);
- assertKeyEquals("Mismatched authentication public keys", clientIdentity.getPublic(), key);
- return true;
- });
-
- // since we need to use RSA
- CoreTestSupportUtils.setupFullSignaturesSupport(sshd);
- try (SshClient client = setupTestClient()) {
- // force server to use only RSA
- NamedFactory<Signature> kexSignature = BuiltinSignatures.rsa;
- client.setSignatureFactories(Collections.singletonList(kexSignature));
- client.setServerKeyVerifier((sshClientSession, remoteAddress, serverKey) -> {
- String keyType = KeyUtils.getKeyType(serverKey);
- String expType = kexSignature.getName();
- assertEquals("Mismatched server key type", expType, keyType);
-
- KeyPair kp;
- try {
- kp = ValidateUtils.checkNotNull(serverKeys.loadKey(null, keyType), "No server key for type=%s", keyType);
- } catch (IOException | GeneralSecurityException e) {
- throw new RuntimeException(
- "Unexpected " + e.getClass().getSimpleName() + ")"
- + " keys loading exception: " + e.getMessage(),
- e);
- }
- assertKeyEquals("Mismatched server public keys", kp.getPublic(), serverKey);
- return true;
- });
-
- // allow only EC keys for public key authentication
- org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyFactory factory
- = new org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyFactory();
- factory.setSignatureFactories(
- Arrays.asList(
- BuiltinSignatures.nistp256, BuiltinSignatures.nistp384, BuiltinSignatures.nistp521));
- client.setUserAuthFactories(Collections.singletonList(factory));
-
- client.start();
- try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
- .verify(CONNECT_TIMEOUT)
- .getSession()) {
- s.addPublicKeyIdentity(clientIdentity);
- s.auth().verify(AUTH_TIMEOUT);
- } finally {
- client.stop();
- }
- }
- }
-
- @Test // see SSHD-624
- public void testMismatchedUserAuthPkOkData() throws Exception {
- AtomicInteger challengeCounter = new AtomicInteger(0);
- sshd.setUserAuthFactories(Collections.singletonList(
- new org.apache.sshd.server.auth.pubkey.UserAuthPublicKeyFactory() {
- @Override
- public org.apache.sshd.server.auth.pubkey.UserAuthPublicKey createUserAuth(ServerSession session)
- throws IOException {
- return new org.apache.sshd.server.auth.pubkey.UserAuthPublicKey() {
- @Override
- protected void sendPublicKeyResponse(
- ServerSession session, String username, String alg, PublicKey key,
- byte[] keyBlob, int offset, int blobLen, Buffer buffer)
- throws Exception {
- int count = challengeCounter.incrementAndGet();
- outputDebugMessage("sendPublicKeyChallenge(%s)[%s]: count=%d", session, alg, count);
- if (count == 1) {
- // send wrong key type
- super.sendPublicKeyResponse(session, username,
- KeyPairProvider.SSH_DSS, key, keyBlob, offset, blobLen, buffer);
- } else if (count == 2) {
- // send another key
- KeyPair otherPair = org.apache.sshd.util.test.CommonTestSupportUtils
- .generateKeyPair(KeyUtils.RSA_ALGORITHM, 1024);
- PublicKey otherKey = otherPair.getPublic();
- Buffer buf = session.createBuffer(SshConstants.SSH_MSG_USERAUTH_PK_OK,
- blobLen + alg.length() + Long.SIZE);
- buf.putString(alg);
- buf.putPublicKey(otherKey);
- session.writePacket(buf);
- } else {
- super.sendPublicKeyResponse(session, username, alg, key, keyBlob, offset, blobLen, buffer);
- }
- }
- };
- }
-
- }));
-
- try (SshClient client = setupTestClient()) {
- KeyPair clientIdentity = CommonTestSupportUtils.generateKeyPair(
- CommonTestSupportUtils.DEFAULT_TEST_HOST_KEY_PROVIDER_ALGORITHM,
- CommonTestSupportUtils.DEFAULT_TEST_HOST_KEY_SIZE);
- client.start();
-
- try {
- for (int index = 1; index <= 4; index++) {
- try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
- .verify(CONNECT_TIMEOUT)
- .getSession()) {
- s.addPublicKeyIdentity(clientIdentity);
- s.auth().verify(AUTH_TIMEOUT);
- assertEquals("Mismatched number of challenges", 3, challengeCounter.get());
- break;
- } catch (SshException e) { // expected
- outputDebugMessage("%s on retry #%d: %s", e.getClass().getSimpleName(), index, e.getMessage());
-
- Throwable t = e.getCause();
- assertObjectInstanceOf("Unexpected failure cause at retry #" + index, InvalidKeySpecException.class, t);
- }
- }
- } finally {
- client.stop();
- }
- }
- }
-
- @Test // see SSHD-620
- public void testHostBasedAuthentication() throws Exception {
- AtomicInteger invocationCount = new AtomicInteger(0);
- testHostBasedAuthentication(
- (
- session, username, clientHostKey, clientHostName, clientUsername,
- certificates) -> invocationCount.incrementAndGet() > 0,
- session -> {
- /* ignored */ });
- assertEquals("Mismatched authenticator invocation count", 1, invocationCount.get());
- }
-
- @Test // see SSHD-1114
- public void testHostBasedAuthenticationReporter() throws Exception {
- AtomicReference<String> hostnameClientHolder = new AtomicReference<>();
- AtomicReference<String> usernameClientHolder = new AtomicReference<>();
- AtomicReference<PublicKey> keyClientHolder = new AtomicReference<>();
- HostBasedAuthenticator authenticator
- = (session, username, clientHostKey, clientHostName, clientUsername, certificates) -> {
- return Objects.equals(clientHostName, hostnameClientHolder.get())
- && Objects.equals(clientUsername, usernameClientHolder.get())
- && KeyUtils.compareKeys(clientHostKey, keyClientHolder.get());
- };
-
- HostBasedAuthenticationReporter reporter = new HostBasedAuthenticationReporter() {
- @Override
- public void signalAuthenticationAttempt(
- ClientSession session, String service, KeyPair identity, String hostname, String username, byte[] signature)
- throws Exception {
- hostnameClientHolder.set(hostname);
- usernameClientHolder.set(username);
- keyClientHolder.set(identity.getPublic());
- }
-
- @Override
- public void signalAuthenticationSuccess(
- ClientSession session, String service, KeyPair identity, String hostname, String username)
- throws Exception {
- assertEquals("Host", hostname, hostnameClientHolder.get());
- assertEquals("User", username, usernameClientHolder.get());
- assertKeyEquals("Identity", identity.getPublic(), keyClientHolder.get());
- }
-
- @Override
- public void signalAuthenticationFailure(
- ClientSession session, String service, KeyPair identity,
- String hostname, String username, boolean partial, List<String> serverMethods)
- throws Exception {
- fail("Unexpected failure signalled");
- }
- };
-
- testHostBasedAuthentication(authenticator, session -> session.setHostBasedAuthenticationReporter(reporter));
- }
-
- private void testHostBasedAuthentication(
- HostBasedAuthenticator delegate, Consumer<? super ClientSession> preAuthInitializer)
- throws Exception {
- String hostClientUser = getClass().getSimpleName();
- String hostClientName = SshdSocketAddress.toAddressString(SshdSocketAddress.getFirstExternalNetwork4Address());
- KeyPair hostClientKey = CommonTestSupportUtils.generateKeyPair(
- CommonTestSupportUtils.DEFAULT_TEST_HOST_KEY_PROVIDER_ALGORITHM,
- CommonTestSupportUtils.DEFAULT_TEST_HOST_KEY_SIZE);
- sshd.setHostBasedAuthenticator((session, username, clientHostKey, clientHostName, clientUsername, certificates) -> {
- return hostClientUser.equals(clientUsername)
- && hostClientName.equals(clientHostName)
- && KeyUtils.compareKeys(hostClientKey.getPublic(), clientHostKey)
- && delegate.authenticate(session, username, clientHostKey, clientHostName, clientUsername, certificates);
- });
- sshd.setPasswordAuthenticator(RejectAllPasswordAuthenticator.INSTANCE);
- sshd.setKeyboardInteractiveAuthenticator(KeyboardInteractiveAuthenticator.NONE);
- sshd.setPublickeyAuthenticator(RejectAllPublickeyAuthenticator.INSTANCE);
- sshd.setUserAuthFactories(
- Collections.singletonList(
- org.apache.sshd.server.auth.hostbased.UserAuthHostBasedFactory.INSTANCE));
-
- try (SshClient client = setupTestClient()) {
- org.apache.sshd.client.auth.hostbased.UserAuthHostBasedFactory factory
- = new org.apache.sshd.client.auth.hostbased.UserAuthHostBasedFactory();
- // TODO factory.setClientHostname(CLIENT_HOSTNAME);
- factory.setClientUsername(hostClientUser);
- factory.setClientHostKeys(HostKeyIdentityProvider.wrap(hostClientKey));
-
- client.setUserAuthFactories(Collections.singletonList(factory));
- client.start();
- try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
- .verify(CONNECT_TIMEOUT)
- .getSession()) {
- preAuthInitializer.accept(session);
- session.auth().verify(AUTH_TIMEOUT);
- } finally {
- client.stop();
- }
- }
- }
-
@Test // see SSHD-625
public void testRuntimeErrorsInAuthenticators() throws Exception {
Error thrown = new OutOfMemoryError(getCurrentTestName());
@@ -925,97 +151,6 @@ public class AuthenticationTest extends BaseTestSupport {
}
}
- @Test // see SSHD-714
- public void testPasswordIdentityWithSpacesPrefixOrSuffix() throws Exception {
- sshd.setPasswordAuthenticator((username, password, session) -> {
- return (username != null) && (!username.trim().isEmpty())
- && (password != null) && (!password.isEmpty())
- && ((password.charAt(0) == ' ') || (password.charAt(password.length() - 1) == ' '));
- });
-
- try (SshClient client = setupTestClient()) {
- client.start();
-
- try {
- for (String password : new String[] {
- " ", " ", " " + getCurrentTestName(), getCurrentTestName() + " "
- }) {
- try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
- .verify(CONNECT_TIMEOUT)
- .getSession()) {
- s.addPasswordIdentity(password);
-
- AuthFuture auth = s.auth();
- assertTrue("No authentication result in time for password='" + password + "'",
- auth.await(AUTH_TIMEOUT));
- assertTrue("Failed to authenticate with password='" + password + "'", auth.isSuccess());
- }
- }
- } finally {
- client.stop();
- }
- }
- }
-
- @Test // see SSHD-862
- public void testSessionContextPropagatedToKeyFilePasswordProvider() throws Exception {
- try (SshClient client = setupTestClient()) {
- client.start();
-
- try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
- .verify(CONNECT_TIMEOUT)
- .getSession()) {
- String keyLocation = "super-secret-passphrase-ec256-key";
- FilePasswordProvider passwordProvider = new FilePasswordProvider() {
- @Override
- @SuppressWarnings("synthetic-access")
- public String getPassword(
- SessionContext session, NamedResource resourceKey, int retryIndex)
- throws IOException {
- assertSame("Mismatched session context", s, session);
- assertEquals("Mismatched retry index", 0, retryIndex);
-
- String name = resourceKey.getName();
- int pos = name.lastIndexOf('/');
- if (pos >= 0) {
- name = name.substring(pos + 1);
- }
- assertEquals("Mismatched location", keyLocation, name);
-
- Boolean passwordRequested = session.getAttribute(PASSWORD_ATTR);
- assertNull("Password already requested", passwordRequested);
- session.setAttribute(PASSWORD_ATTR, Boolean.TRUE);
- return "super secret passphrase";
- }
- };
- s.setKeyIdentityProvider(new KeyIdentityProvider() {
- @Override
- public Iterable<KeyPair> loadKeys(SessionContext session) throws IOException, GeneralSecurityException {
- assertSame("Mismatched session context", s, session);
- URL location = getClass().getResource(keyLocation);
- assertNotNull("Missing key file " + keyLocation, location);
-
- URLResource resourceKey = new URLResource(location);
- Iterable<KeyPair> ids;
- try (InputStream keyData = resourceKey.openInputStream()) {
- ids = SecurityUtils.loadKeyPairIdentities(session, resourceKey, keyData, passwordProvider);
- }
- KeyPair kp = GenericUtils.head(ids);
- assertNotNull("No identity loaded from " + resourceKey, kp);
- return Collections.singletonList(kp);
- }
- });
- s.auth().verify(AUTH_TIMEOUT);
-
- Boolean passwordRequested = s.getAttribute(PASSWORD_ATTR);
- assertNotNull("Password provider not invoked", passwordRequested);
- assertTrue("Password not requested", passwordRequested.booleanValue());
- } finally {
- client.stop();
- }
- }
- }
-
@Test // see SSHD-1040
public void testServerKeyAvailableAfterAuth() throws Exception {
KeyPairProvider keyPairProvider = sshd.getKeyPairProvider();
@@ -1050,183 +185,4 @@ public class AuthenticationTest extends BaseTestSupport {
fail("No matching server key found for " + actualKey);
}
-
- @Test // see SSHD-1114
- public void testPasswordAuthenticationReporter() throws Exception {
- String goodPassword = getCurrentTestName();
- String badPassword = getClass().getSimpleName();
- List<String> attempted = new ArrayList<>();
- sshd.setPasswordAuthenticator((user, password, session) -> {
- attempted.add(password);
- return goodPassword.equals(password);
- });
- sshd.setKeyboardInteractiveAuthenticator(KeyboardInteractiveAuthenticator.NONE);
- sshd.setPublickeyAuthenticator(RejectAllPublickeyAuthenticator.INSTANCE);
-
- List<String> reported = new ArrayList<>();
- PasswordAuthenticationReporter reporter = new PasswordAuthenticationReporter() {
- @Override
- public void signalAuthenticationAttempt(
- ClientSession session, String service, String oldPassword, boolean modified, String newPassword)
- throws Exception {
- reported.add(oldPassword);
- }
-
- @Override
- public void signalAuthenticationSuccess(ClientSession session, String service, String password)
- throws Exception {
- assertEquals("Mismatched succesful password", goodPassword, password);
- }
-
- @Override
- public void signalAuthenticationFailure(
- ClientSession session, String service, String password, boolean partial, List<String> serverMethods)
- throws Exception {
- assertEquals("Mismatched failed password", badPassword, password);
- }
- };
-
- try (SshClient client = setupTestClient()) {
- client.setUserAuthFactories(
- Collections.singletonList(new org.apache.sshd.client.auth.password.UserAuthPasswordFactory()));
- client.start();
-
- try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
- .verify(CONNECT_TIMEOUT).getSession()) {
- session.addPasswordIdentity(badPassword);
- session.addPasswordIdentity(goodPassword);
- session.setPasswordAuthenticationReporter(reporter);
- session.auth().verify(AUTH_TIMEOUT);
- } finally {
- client.stop();
- }
- }
-
- List<String> expected = Arrays.asList(badPassword, goodPassword);
- assertListEquals("Attempted passwords", expected, attempted);
- assertListEquals("Reported passwords", expected, reported);
- }
-
- @Test // see SSHD-1114
- public void testPublicKeyAuthenticationReporter() throws Exception {
- KeyPair goodIdentity = CommonTestSupportUtils.generateKeyPair(KeyUtils.EC_ALGORITHM, 256);
- KeyPair badIdentity = CommonTestSupportUtils.generateKeyPair(KeyUtils.EC_ALGORITHM, 256);
- List<PublicKey> attempted = new ArrayList<>();
- sshd.setPublickeyAuthenticator((username, key, session) -> {
- attempted.add(key);
- return KeyUtils.compareKeys(goodIdentity.getPublic(), key);
- });
- sshd.setPasswordAuthenticator(RejectAllPasswordAuthenticator.INSTANCE);
- sshd.setKeyboardInteractiveAuthenticator(KeyboardInteractiveAuthenticator.NONE);
-
- List<PublicKey> reported = new ArrayList<>();
- List<PublicKey> signed = new ArrayList<>();
- PublicKeyAuthenticationReporter reporter = new PublicKeyAuthenticationReporter() {
- @Override
- public void signalAuthenticationAttempt(
- ClientSession session, String service, KeyPair identity, String signature)
- throws Exception {
- reported.add(identity.getPublic());
- }
-
- @Override
- public void signalSignatureAttempt(
- ClientSession session, String service, KeyPair identity, String signature, byte[] sigData)
- throws Exception {
- signed.add(identity.getPublic());
- }
-
- @Override
- public void signalAuthenticationSuccess(ClientSession session, String service, KeyPair identity)
- throws Exception {
- assertTrue("Mismatched success identity", KeyUtils.compareKeys(goodIdentity.getPublic(), identity.getPublic()));
- }
-
- @Override
- public void signalAuthenticationFailure(
- ClientSession session, String service, KeyPair identity, boolean partial, List<String> serverMethods)
- throws Exception {
- assertTrue("Mismatched failed identity", KeyUtils.compareKeys(badIdentity.getPublic(), identity.getPublic()));
- }
- };
-
- try (SshClient client = setupTestClient()) {
- client.setUserAuthFactories(
- Collections.singletonList(new org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyFactory()));
- client.start();
-
- try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
- .verify(CONNECT_TIMEOUT).getSession()) {
- session.addPublicKeyIdentity(badIdentity);
- session.addPublicKeyIdentity(goodIdentity);
- session.setPublicKeyAuthenticationReporter(reporter);
- session.auth().verify(AUTH_TIMEOUT);
- } finally {
- client.stop();
- }
- }
-
- List<PublicKey> expected = Arrays.asList(badIdentity.getPublic(), goodIdentity.getPublic());
- // The server public key authenticator is called twice with the good identity
- int numAttempted = attempted.size();
- assertKeyListEquals("Attempted", expected, (numAttempted > 0) ? attempted.subList(0, numAttempted - 1) : attempted);
- assertKeyListEquals("Reported", expected, reported);
- // The signing is attempted only if the initial public key is accepted
- assertKeyListEquals("Signed", Collections.singletonList(goodIdentity.getPublic()), signed);
- }
-
- private static void assertAuthenticationResult(String message, AuthFuture future, boolean expected) throws IOException {
- assertTrue(message + ": failed to get result on time", future.await(AUTH_TIMEOUT));
- assertEquals(message + ": mismatched authentication result", expected, future.isSuccess());
- }
-
- private static AuthFuture authPassword(ClientSession s, String user, String pswd) throws IOException {
- s.setUsername(user);
- s.addPasswordIdentity(pswd);
- return s.auth();
- }
-
- private static AuthFuture authInteractive(ClientSession s, String user, String pswd) throws IOException {
- s.setUsername(user);
- final String[] response = { pswd };
- s.setUserInteraction(new UserInteraction() {
- @Override
- public boolean isInteractionAllowed(ClientSession session) {
- return true;
- }
-
- @Override
- public String[] interactive(
- ClientSession session, String name, String instruction,
- String lang, String[] prompt, boolean[] echo) {
- assertSame("Mismatched session instance", s, session);
- assertEquals("Mismatched prompt size", 1, GenericUtils.length(prompt));
- assertTrue("Mismatched prompt: " + prompt[0], prompt[0].toLowerCase().contains("password"));
- return response;
- }
-
- @Override
- public String getUpdatedPassword(ClientSession session, String prompt, String lang) {
- throw new UnsupportedOperationException("Unexpected password update request");
- }
- });
- return s.auth();
- }
-
- private static AuthFuture authPublicKey(ClientSession s, String user, KeyPair pair) throws IOException {
- s.setUsername(user);
- s.addPublicKeyIdentity(pair);
- return s.auth();
- }
-
- public static class TestSession extends ServerSessionImpl {
- public TestSession(ServerFactoryManager server, IoSession ioSession) throws Exception {
- super(server, ioSession);
- }
-
- @Override
- public void handleMessage(Buffer buffer) throws Exception {
- super.handleMessage(buffer); // debug breakpoint
- }
- }
}
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/auth/AuthenticationTestSupport.java b/sshd-core/src/test/java/org/apache/sshd/common/auth/AuthenticationTestSupport.java
new file mode 100644
index 0000000..3fab5de
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/common/auth/AuthenticationTestSupport.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.auth;
+
+import java.io.IOException;
+import java.security.KeyPair;
+
+import org.apache.sshd.client.auth.keyboard.UserInteraction;
+import org.apache.sshd.client.future.AuthFuture;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.AttributeRepository;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.util.test.BaseTestSupport;
+import org.junit.After;
+import org.junit.Before;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AuthenticationTestSupport extends BaseTestSupport {
+ protected static final AttributeRepository.AttributeKey<Boolean> PASSWORD_ATTR = new AttributeRepository.AttributeKey<>();
+
+ protected SshServer sshd;
+ protected int port;
+
+ protected AuthenticationTestSupport() {
+ super();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ sshd = setupTestServer();
+ sshd.start();
+ port = sshd.getPort();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (sshd != null) {
+ sshd.stop(true);
+ }
+ }
+
+ protected static void assertAuthenticationResult(String message, AuthFuture future, boolean expected) throws IOException {
+ assertTrue(message + ": failed to get result on time", future.await(AUTH_TIMEOUT));
+ assertEquals(message + ": mismatched authentication result", expected, future.isSuccess());
+ }
+
+ protected static AuthFuture authPassword(ClientSession s, String user, String pswd) throws IOException {
+ s.setUsername(user);
+ s.addPasswordIdentity(pswd);
+ return s.auth();
+ }
+
+ protected static AuthFuture authInteractive(ClientSession s, String user, String pswd) throws IOException {
+ s.setUsername(user);
+ final String[] response = { pswd };
+ s.setUserInteraction(new UserInteraction() {
+ @Override
+ public boolean isInteractionAllowed(ClientSession session) {
+ return true;
+ }
+
+ @Override
+ public String[] interactive(
+ ClientSession session, String name, String instruction,
+ String lang, String[] prompt, boolean[] echo) {
+ assertSame("Mismatched session instance", s, session);
+ assertEquals("Mismatched prompt size", 1, GenericUtils.length(prompt));
+ assertTrue("Mismatched prompt: " + prompt[0], prompt[0].toLowerCase().contains("password"));
+ return response;
+ }
+
+ @Override
+ public String getUpdatedPassword(ClientSession session, String prompt, String lang) {
+ throw new UnsupportedOperationException("Unexpected password update request");
+ }
+ });
+ return s.auth();
+ }
+
+ protected static AuthFuture authPublicKey(ClientSession s, String user, KeyPair pair) throws IOException {
+ s.setUsername(user);
+ s.addPublicKeyIdentity(pair);
+ return s.auth();
+ }
+}
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/auth/HostBasedAuthenticationTest.java b/sshd-core/src/test/java/org/apache/sshd/common/auth/HostBasedAuthenticationTest.java
new file mode 100644
index 0000000..7568bc8
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/common/auth/HostBasedAuthenticationTest.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.auth;
+
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.auth.hostbased.HostBasedAuthenticationReporter;
+import org.apache.sshd.client.auth.hostbased.HostKeyIdentityProvider;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.util.net.SshdSocketAddress;
+import org.apache.sshd.server.auth.hostbased.HostBasedAuthenticator;
+import org.apache.sshd.server.auth.keyboard.KeyboardInteractiveAuthenticator;
+import org.apache.sshd.server.auth.password.RejectAllPasswordAuthenticator;
+import org.apache.sshd.server.auth.pubkey.RejectAllPublickeyAuthenticator;
+import org.apache.sshd.util.test.CommonTestSupportUtils;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class HostBasedAuthenticationTest extends AuthenticationTestSupport {
+ public HostBasedAuthenticationTest() {
+ super();
+ }
+
+ @Test // see SSHD-620
+ public void testHostBasedAuthentication() throws Exception {
+ AtomicInteger invocationCount = new AtomicInteger(0);
+ testHostBasedAuthentication(
+ (
+ session, username, clientHostKey, clientHostName, clientUsername,
+ certificates) -> invocationCount.incrementAndGet() > 0,
+ session -> {
+ /* ignored */ });
+ assertEquals("Mismatched authenticator invocation count", 1, invocationCount.get());
+ }
+
+ @Test // see SSHD-1114
+ public void testHostBasedAuthenticationReporter() throws Exception {
+ AtomicReference<String> hostnameClientHolder = new AtomicReference<>();
+ AtomicReference<String> usernameClientHolder = new AtomicReference<>();
+ AtomicReference<PublicKey> keyClientHolder = new AtomicReference<>();
+ HostBasedAuthenticator authenticator
+ = (session, username, clientHostKey, clientHostName, clientUsername, certificates) -> {
+ return Objects.equals(clientHostName, hostnameClientHolder.get())
+ && Objects.equals(clientUsername, usernameClientHolder.get())
+ && KeyUtils.compareKeys(clientHostKey, keyClientHolder.get());
+ };
+
+ HostBasedAuthenticationReporter reporter = new HostBasedAuthenticationReporter() {
+ @Override
+ public void signalAuthenticationAttempt(
+ ClientSession session, String service, KeyPair identity, String hostname, String username, byte[] signature)
+ throws Exception {
+ hostnameClientHolder.set(hostname);
+ usernameClientHolder.set(username);
+ keyClientHolder.set(identity.getPublic());
+ }
+
+ @Override
+ public void signalAuthenticationSuccess(
+ ClientSession session, String service, KeyPair identity, String hostname, String username)
+ throws Exception {
+ assertEquals("Host", hostname, hostnameClientHolder.get());
+ assertEquals("User", username, usernameClientHolder.get());
+ assertKeyEquals("Identity", identity.getPublic(), keyClientHolder.get());
+ }
+
+ @Override
+ public void signalAuthenticationFailure(
+ ClientSession session, String service, KeyPair identity,
+ String hostname, String username, boolean partial, List<String> serverMethods)
+ throws Exception {
+ fail("Unexpected failure signalled");
+ }
+ };
+
+ testHostBasedAuthentication(authenticator, session -> session.setHostBasedAuthenticationReporter(reporter));
+ }
+
+ private void testHostBasedAuthentication(
+ HostBasedAuthenticator delegate, Consumer<? super ClientSession> preAuthInitializer)
+ throws Exception {
+ String hostClientUser = getClass().getSimpleName();
+ String hostClientName = SshdSocketAddress.toAddressString(SshdSocketAddress.getFirstExternalNetwork4Address());
+ KeyPair hostClientKey = CommonTestSupportUtils.generateKeyPair(
+ CommonTestSupportUtils.DEFAULT_TEST_HOST_KEY_PROVIDER_ALGORITHM,
+ CommonTestSupportUtils.DEFAULT_TEST_HOST_KEY_SIZE);
+ sshd.setHostBasedAuthenticator((session, username, clientHostKey, clientHostName, clientUsername, certificates) -> {
+ return hostClientUser.equals(clientUsername)
+ && hostClientName.equals(clientHostName)
+ && KeyUtils.compareKeys(hostClientKey.getPublic(), clientHostKey)
+ && delegate.authenticate(session, username, clientHostKey, clientHostName, clientUsername, certificates);
+ });
+ sshd.setPasswordAuthenticator(RejectAllPasswordAuthenticator.INSTANCE);
+ sshd.setKeyboardInteractiveAuthenticator(KeyboardInteractiveAuthenticator.NONE);
+ sshd.setPublickeyAuthenticator(RejectAllPublickeyAuthenticator.INSTANCE);
+ sshd.setUserAuthFactories(
+ Collections.singletonList(
+ org.apache.sshd.server.auth.hostbased.UserAuthHostBasedFactory.INSTANCE));
+
+ try (SshClient client = setupTestClient()) {
+ org.apache.sshd.client.auth.hostbased.UserAuthHostBasedFactory factory
+ = new org.apache.sshd.client.auth.hostbased.UserAuthHostBasedFactory();
+ // TODO factory.setClientHostname(CLIENT_HOSTNAME);
+ factory.setClientUsername(hostClientUser);
+ factory.setClientHostKeys(HostKeyIdentityProvider.wrap(hostClientKey));
+
+ client.setUserAuthFactories(Collections.singletonList(factory));
+ client.start();
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
+ .verify(CONNECT_TIMEOUT)
+ .getSession()) {
+ preAuthInitializer.accept(session);
+ session.auth().verify(AUTH_TIMEOUT);
+ } finally {
+ client.stop();
+ }
+ }
+ }
+}
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/auth/KeyboardInteractiveAuthenticationTest.java b/sshd-core/src/test/java/org/apache/sshd/common/auth/KeyboardInteractiveAuthenticationTest.java
new file mode 100644
index 0000000..feeabf3
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/common/auth/KeyboardInteractiveAuthenticationTest.java
@@ -0,0 +1,233 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.auth;
+
+import java.security.KeyPair;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.auth.keyboard.UserInteraction;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.MapEntryUtils.NavigableMapBuilder;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.core.CoreModuleProperties;
+import org.apache.sshd.server.auth.keyboard.DefaultKeyboardInteractiveAuthenticator;
+import org.apache.sshd.server.auth.keyboard.InteractiveChallenge;
+import org.apache.sshd.server.auth.keyboard.KeyboardInteractiveAuthenticator;
+import org.apache.sshd.server.auth.keyboard.PromptEntry;
+import org.apache.sshd.server.auth.keyboard.UserAuthKeyboardInteractiveFactory;
+import org.apache.sshd.server.auth.pubkey.RejectAllPublickeyAuthenticator;
+import org.apache.sshd.server.session.ServerSession;
+import org.apache.sshd.util.test.CommonTestSupportUtils;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class KeyboardInteractiveAuthenticationTest extends AuthenticationTestSupport {
+ public KeyboardInteractiveAuthenticationTest() {
+ super();
+ }
+
+ @Test // see SSHD-612
+ public void testAuthDefaultKeyInteractive() throws Exception {
+ try (SshClient client = setupTestClient()) {
+ sshd.setPublickeyAuthenticator(RejectAllPublickeyAuthenticator.INSTANCE);
+ sshd.setKeyboardInteractiveAuthenticator(new DefaultKeyboardInteractiveAuthenticator() {
+ @Override
+ public InteractiveChallenge generateChallenge(
+ ServerSession session, String username, String lang, String subMethods)
+ throws Exception {
+ assertEquals("Mismatched user language",
+ CoreModuleProperties.INTERACTIVE_LANGUAGE_TAG.getRequired(client),
+ lang);
+ assertEquals("Mismatched client sub-methods",
+ CoreModuleProperties.INTERACTIVE_SUBMETHODS.getRequired(client),
+ subMethods);
+
+ InteractiveChallenge challenge = super.generateChallenge(session, username, lang, subMethods);
+ assertEquals("Mismatched interaction name", getInteractionName(session), challenge.getInteractionName());
+ assertEquals("Mismatched interaction instruction", getInteractionInstruction(session),
+ challenge.getInteractionInstruction());
+ assertEquals("Mismatched language tag", getInteractionLanguage(session), challenge.getLanguageTag());
+
+ List<PromptEntry> entries = challenge.getPrompts();
+ assertEquals("Mismatched prompts count", 1, GenericUtils.size(entries));
+
+ PromptEntry entry = entries.get(0);
+ assertEquals("Mismatched prompt", getInteractionPrompt(session), entry.getPrompt());
+ assertEquals("Mismatched echo", isInteractionPromptEchoEnabled(session), entry.isEcho());
+
+ return challenge;
+ }
+
+ @Override
+ public boolean authenticate(
+ ServerSession session, String username, List<String> responses)
+ throws Exception {
+ return super.authenticate(session, username, responses);
+ }
+
+ });
+ client.start();
+
+ try (ClientSession s = client.connect(null, TEST_LOCALHOST, port)
+ .verify(CONNECT_TIMEOUT)
+ .getSession()) {
+ Collection<ClientSession.ClientSessionEvent> result = s.waitFor(
+ EnumSet.of(ClientSession.ClientSessionEvent.CLOSED, ClientSession.ClientSessionEvent.WAIT_AUTH),
+ DEFAULT_TIMEOUT);
+ assertFalse("Timeout while waiting for session", result.contains(ClientSession.ClientSessionEvent.TIMEOUT));
+
+ KeyPairProvider provider = createTestHostKeyProvider();
+ KeyPair pair = provider.loadKey(s, CommonTestSupportUtils.DEFAULT_TEST_HOST_KEY_TYPE);
+ try {
+ assertAuthenticationResult(UserAuthMethodFactory.PUBLIC_KEY,
+ authPublicKey(s, getCurrentTestName(), pair), false);
+ } finally {
+ s.removePublicKeyIdentity(pair);
+ }
+
+ try {
+ assertAuthenticationResult(UserAuthMethodFactory.KB_INTERACTIVE,
+ authInteractive(s, getCurrentTestName(), getCurrentTestName()), true);
+ } finally {
+ s.setUserInteraction(null);
+ }
+ } finally {
+ client.stop();
+ }
+ }
+ }
+
+ @Test // see SSHD-563
+ public void testAuthMultiChallengeKeyInteractive() throws Exception {
+ Class<?> anchor = getClass();
+ InteractiveChallenge challenge = new InteractiveChallenge();
+ challenge.setInteractionName(getCurrentTestName());
+ challenge.setInteractionInstruction(anchor.getPackage().getName());
+ challenge.setLanguageTag(Locale.getDefault().getLanguage());
+
+ Map<String, String> rspMap = NavigableMapBuilder.<String, String> builder(String.CASE_INSENSITIVE_ORDER)
+ .put("class", anchor.getSimpleName())
+ .put("package", anchor.getPackage().getName())
+ .put("test", getCurrentTestName())
+ .build();
+ for (String prompt : rspMap.keySet()) {
+ challenge.addPrompt(prompt, (GenericUtils.size(challenge.getPrompts()) & 0x1) != 0);
+ }
+
+ CoreModuleProperties.AUTH_METHODS.set(sshd, UserAuthKeyboardInteractiveFactory.NAME);
+ AtomicInteger genCount = new AtomicInteger(0);
+ AtomicInteger authCount = new AtomicInteger(0);
+ sshd.setKeyboardInteractiveAuthenticator(new KeyboardInteractiveAuthenticator() {
+ @Override
+ public InteractiveChallenge generateChallenge(
+ ServerSession session, String username, String lang, String subMethods)
+ throws Exception {
+ assertEquals("Unexpected challenge call", 1, genCount.incrementAndGet());
+ return challenge;
+ }
+
+ @Override
+ public boolean authenticate(
+ ServerSession session, String username, List<String> responses)
+ throws Exception {
+ assertEquals("Unexpected authenticate call", 1, authCount.incrementAndGet());
+ assertEquals("Mismatched number of responses", GenericUtils.size(rspMap), GenericUtils.size(responses));
+
+ int index = 0;
+ // Cannot use forEach because the index is not effectively final
+ for (Map.Entry<String, String> re : rspMap.entrySet()) {
+ String prompt = re.getKey();
+ String expected = re.getValue();
+ String actual = responses.get(index);
+ assertEquals("Mismatched response for prompt=" + prompt, expected, actual);
+ index++;
+ }
+ return true;
+ }
+ });
+ CoreModuleProperties.AUTH_METHODS.set(sshd, UserAuthKeyboardInteractiveFactory.NAME);
+
+ try (SshClient client = setupTestClient()) {
+ AtomicInteger interactiveCount = new AtomicInteger(0);
+ client.setUserInteraction(new UserInteraction() {
+ @Override
+ public boolean isInteractionAllowed(ClientSession session) {
+ return true;
+ }
+
+ @Override
+ public String[] interactive(
+ ClientSession session, String name, String instruction,
+ String lang, String[] prompt, boolean[] echo) {
+ assertEquals("Unexpected multiple calls", 1, interactiveCount.incrementAndGet());
+ assertEquals("Mismatched name", challenge.getInteractionName(), name);
+ assertEquals("Mismatched instruction", challenge.getInteractionInstruction(), instruction);
+ assertEquals("Mismatched language", challenge.getLanguageTag(), lang);
+
+ List<PromptEntry> entries = challenge.getPrompts();
+ assertEquals("Mismatched prompts count", GenericUtils.size(entries), GenericUtils.length(prompt));
+
+ String[] responses = new String[prompt.length];
+ for (int index = 0; index < prompt.length; index++) {
+ PromptEntry e = entries.get(index);
+ String key = e.getPrompt();
+ assertEquals("Mismatched prompt at index=" + index, key, prompt[index]);
+ assertEquals("Mismatched echo at index=" + index, e.isEcho(), echo[index]);
+ responses[index] = ValidateUtils.checkNotNull(rspMap.get(key), "No value for prompt=%s", key);
+ }
+
+ return responses;
+ }
+
+ @Override
+ public String getUpdatedPassword(ClientSession session, String prompt, String lang) {
+ throw new UnsupportedOperationException("Unexpected call");
+ }
+ });
+ CoreModuleProperties.AUTH_METHODS.set(client, UserAuthKeyboardInteractiveFactory.NAME);
+
+ client.start();
+
+ try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
+ .verify(CONNECT_TIMEOUT)
+ .getSession()) {
+ s.auth().verify(AUTH_TIMEOUT);
+ assertEquals("Bad generated challenge count", 1, genCount.get());
+ assertEquals("Bad authentication count", 1, authCount.get());
+ assertEquals("Bad interactive count", 1, interactiveCount.get());
+ } finally {
+ client.stop();
+ }
+ }
+ }
+}
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/auth/PasswordAuthenticationTest.java b/sshd-core/src/test/java/org/apache/sshd/common/auth/PasswordAuthenticationTest.java
new file mode 100644
index 0000000..628338d
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/common/auth/PasswordAuthenticationTest.java
@@ -0,0 +1,440 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.auth;
+
+import java.io.IOException;
+import java.security.KeyPair;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.auth.keyboard.UserInteraction;
+import org.apache.sshd.client.auth.password.PasswordAuthenticationReporter;
+import org.apache.sshd.client.auth.password.PasswordIdentityProvider;
+import org.apache.sshd.client.future.AuthFuture;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.io.IoWriteFuture;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.core.CoreModuleProperties;
+import org.apache.sshd.server.auth.keyboard.KeyboardInteractiveAuthenticator;
+import org.apache.sshd.server.auth.password.PasswordAuthenticator;
+import org.apache.sshd.server.auth.password.PasswordChangeRequiredException;
+import org.apache.sshd.server.auth.password.RejectAllPasswordAuthenticator;
+import org.apache.sshd.server.auth.password.UserAuthPasswordFactory;
+import org.apache.sshd.server.auth.pubkey.RejectAllPublickeyAuthenticator;
+import org.apache.sshd.server.session.ServerSession;
+import org.apache.sshd.util.test.CommonTestSupportUtils;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class PasswordAuthenticationTest extends AuthenticationTestSupport {
+ public PasswordAuthenticationTest() {
+ super();
+ }
+
+ @Test
+ public void testWrongPassword() throws Exception {
+ try (SshClient client = setupTestClient()) {
+ client.start();
+ try (ClientSession s = client.connect("user", TEST_LOCALHOST, port)
+ .verify(CONNECT_TIMEOUT)
+ .getSession()) {
+ s.addPasswordIdentity("bad password");
+ assertAuthenticationResult(getCurrentTestName(), s.auth(), false);
+ }
+ }
+ }
+
+ @Test
+ public void testChangeUser() throws Exception {
+ try (SshClient client = setupTestClient()) {
+ client.start();
+
+ try (ClientSession s = client.connect(null, TEST_LOCALHOST, port)
+ .verify(CONNECT_TIMEOUT)
+ .getSession()) {
+ Collection<ClientSession.ClientSessionEvent> mask
+ = EnumSet.of(ClientSession.ClientSessionEvent.CLOSED, ClientSession.ClientSessionEvent.WAIT_AUTH);
+ Collection<ClientSession.ClientSessionEvent> result = s.waitFor(mask, DEFAULT_TIMEOUT);
+ assertFalse("Timeout while waiting on session events",
+ result.contains(ClientSession.ClientSessionEvent.TIMEOUT));
+
+ String password = "the-password";
+ for (String username : new String[] { "user1", "user2" }) {
+ try {
+ assertAuthenticationResult(username, authPassword(s, username, password), false);
+ } finally {
+ s.removePasswordIdentity(password);
+ }
+ }
+
+ // Note that WAIT_AUTH flag should be false, but since the internal
+ // authentication future is not updated, it's still returned
+ result = s.waitFor(EnumSet.of(ClientSession.ClientSessionEvent.CLOSED), DEFAULT_TIMEOUT);
+ assertTrue("Mismatched client session close mask: " + result, result.containsAll(mask));
+ } finally {
+ client.stop();
+ }
+ }
+ }
+
+ @Test // see SSHD-196
+ public void testChangePassword() throws Exception {
+ PasswordAuthenticator delegate = sshd.getPasswordAuthenticator();
+ AtomicInteger attemptsCount = new AtomicInteger(0);
+ AtomicInteger changesCount = new AtomicInteger(0);
+ sshd.setPasswordAuthenticator(new PasswordAuthenticator() {
+ @Override
+ public boolean authenticate(String username, String password, ServerSession session) {
+ if (attemptsCount.incrementAndGet() == 1) {
+ throw new PasswordChangeRequiredException(
+ attemptsCount.toString(),
+ getCurrentTestName(), CoreModuleProperties.WELCOME_BANNER_LANGUAGE.getRequiredDefault());
+ }
+
+ return delegate.authenticate(username, password, session);
+ }
+
+ @Override
+ public boolean handleClientPasswordChangeRequest(
+ ServerSession session, String username, String oldPassword, String newPassword) {
+ if (changesCount.incrementAndGet() == 1) {
+ assertNotEquals("Non-different passwords", oldPassword, newPassword);
+ return authenticate(username, newPassword, session);
+ } else {
+ return PasswordAuthenticator.super.handleClientPasswordChangeRequest(
+ session, username, oldPassword, newPassword);
+ }
+ }
+ });
+ CoreModuleProperties.AUTH_METHODS.set(sshd, UserAuthPasswordFactory.NAME);
+
+ try (SshClient client = setupTestClient()) {
+ AtomicInteger updatesCount = new AtomicInteger(0);
+ client.setUserInteraction(new UserInteraction() {
+ @Override
+ public boolean isInteractionAllowed(ClientSession session) {
+ return true;
+ }
+
+ @Override
+ public String[] interactive(
+ ClientSession session, String name, String instruction,
+ String lang, String[] prompt, boolean[] echo) {
+ throw new UnsupportedOperationException("Unexpected call");
+ }
+
+ @Override
+ public String getUpdatedPassword(ClientSession session, String prompt, String lang) {
+ assertEquals("Mismatched prompt", getCurrentTestName(), prompt);
+ assertEquals("Mismatched language",
+ CoreModuleProperties.WELCOME_BANNER_LANGUAGE.getRequiredDefault(), lang);
+ assertEquals("Unexpected repeated call", 1, updatesCount.incrementAndGet());
+ return getCurrentTestName();
+ }
+ });
+
+ AtomicInteger sentCount = new AtomicInteger(0);
+ client.setUserAuthFactories(Collections.singletonList(
+ new org.apache.sshd.client.auth.password.UserAuthPasswordFactory() {
+ @Override
+ public org.apache.sshd.client.auth.password.UserAuthPassword createUserAuth(ClientSession session)
+ throws IOException {
+ return new org.apache.sshd.client.auth.password.UserAuthPassword() {
+ @Override
+ protected IoWriteFuture sendPassword(
+ Buffer buffer, ClientSession session, String oldPassword, String newPassword)
+ throws Exception {
+ int count = sentCount.incrementAndGet();
+ // 1st one is the original one (which is denied by the server)
+ // 2nd one is the updated one retrieved from the user interaction
+ if (count == 2) {
+ return super.sendPassword(buffer, session, getClass().getName(), newPassword);
+ } else {
+ return super.sendPassword(buffer, session, oldPassword, newPassword);
+ }
+ }
+ };
+ }
+ }));
+ CoreModuleProperties.AUTH_METHODS.set(client, UserAuthPasswordFactory.NAME);
+
+ client.start();
+
+ try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
+ .verify(CONNECT_TIMEOUT)
+ .getSession()) {
+ s.addPasswordIdentity(getCurrentTestName());
+ s.auth().verify(AUTH_TIMEOUT);
+ assertEquals("No password change request generated", 2, attemptsCount.get());
+ assertEquals("No password change handled", 1, changesCount.get());
+ assertEquals("No user interaction invoked", 1, updatesCount.get());
+ } finally {
+ client.stop();
+ }
+ }
+ }
+
+ @Test
+ public void testAuthPasswordOnly() throws Exception {
+ try (SshClient client = setupTestClient()) {
+ sshd.setPasswordAuthenticator(RejectAllPasswordAuthenticator.INSTANCE);
+
+ client.start();
+ try (ClientSession s = client.connect(null, TEST_LOCALHOST, port)
+ .verify(CONNECT_TIMEOUT)
+ .getSession()) {
+ Collection<ClientSession.ClientSessionEvent> result = s.waitFor(
+ EnumSet.of(ClientSession.ClientSessionEvent.CLOSED, ClientSession.ClientSessionEvent.WAIT_AUTH),
+ DEFAULT_TIMEOUT);
+ assertFalse("Timeout while waiting for session", result.contains(ClientSession.ClientSessionEvent.TIMEOUT));
+
+ String password = getCurrentTestName();
+ try {
+ assertAuthenticationResult(getCurrentTestName(),
+ authPassword(s, getCurrentTestName(), password), false);
+ } finally {
+ s.removePasswordIdentity(password);
+ }
+ } finally {
+ client.stop();
+ }
+ }
+ }
+
+ @Test
+ public void testAuthKeyPassword() throws Exception {
+ try (SshClient client = setupTestClient()) {
+ sshd.setPublickeyAuthenticator(RejectAllPublickeyAuthenticator.INSTANCE);
+ sshd.setKeyboardInteractiveAuthenticator(KeyboardInteractiveAuthenticator.NONE);
+
+ client.start();
+
+ try (ClientSession s = client.connect(null, TEST_LOCALHOST, port)
+ .verify(CONNECT_TIMEOUT)
+ .getSession()) {
+ Collection<ClientSession.ClientSessionEvent> result = s.waitFor(
+ EnumSet.of(ClientSession.ClientSessionEvent.CLOSED, ClientSession.ClientSessionEvent.WAIT_AUTH),
+ DEFAULT_TIMEOUT);
+ assertFalse("Timeout while waiting for session", result.contains(ClientSession.ClientSessionEvent.TIMEOUT));
+
+ KeyPairProvider provider = createTestHostKeyProvider();
+ KeyPair pair = provider.loadKey(s, CommonTestSupportUtils.DEFAULT_TEST_HOST_KEY_TYPE);
+ try {
+ assertAuthenticationResult(UserAuthMethodFactory.PUBLIC_KEY,
+ authPublicKey(s, getCurrentTestName(), pair), false);
+ } finally {
+ s.removePublicKeyIdentity(pair);
+ }
+
+ String password = getCurrentTestName();
+ try {
+ assertAuthenticationResult(UserAuthMethodFactory.PASSWORD,
+ authPassword(s, getCurrentTestName(), password), true);
+ } finally {
+ s.removePasswordIdentity(password);
+ }
+ } finally {
+ client.stop();
+ }
+ }
+ }
+
+ @Test // see SSHD-196
+ public void testAuthPasswordChangeRequest() throws Exception {
+ PasswordAuthenticator delegate = Objects.requireNonNull(sshd.getPasswordAuthenticator(), "No password authenticator");
+ AtomicInteger attemptsCount = new AtomicInteger(0);
+ sshd.setPasswordAuthenticator((username, password, session) -> {
+ if (attemptsCount.incrementAndGet() == 1) {
+ throw new PasswordChangeRequiredException(
+ attemptsCount.toString(),
+ getCurrentTestName(), CoreModuleProperties.WELCOME_BANNER_LANGUAGE.getRequiredDefault());
+ }
+
+ return delegate.authenticate(username, password, session);
+ });
+ CoreModuleProperties.AUTH_METHODS.set(sshd, UserAuthPasswordFactory.NAME);
+
+ try (SshClient client = setupTestClient()) {
+ AtomicInteger updatesCount = new AtomicInteger(0);
+ client.setUserInteraction(new UserInteraction() {
+ @Override
+ public boolean isInteractionAllowed(ClientSession session) {
+ return true;
+ }
+
+ @Override
+ public String[] interactive(
+ ClientSession session, String name, String instruction,
+ String lang, String[] prompt, boolean[] echo) {
+ throw new UnsupportedOperationException("Unexpected call");
+ }
+
+ @Override
+ public String getUpdatedPassword(ClientSession session, String prompt, String lang) {
+ assertEquals("Mismatched prompt", getCurrentTestName(), prompt);
+ assertEquals("Mismatched language",
+ CoreModuleProperties.WELCOME_BANNER_LANGUAGE.getRequiredDefault(), lang);
+ assertEquals("Unexpected repeated call", 1, updatesCount.incrementAndGet());
+ return getCurrentTestName();
+ }
+ });
+ CoreModuleProperties.AUTH_METHODS.set(client, UserAuthPasswordFactory.NAME);
+
+ client.start();
+
+ try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
+ .verify(CONNECT_TIMEOUT)
+ .getSession()) {
+ s.addPasswordIdentity(getCurrentTestName());
+ s.auth().verify(AUTH_TIMEOUT);
+ assertEquals("No password change request generated", 2, attemptsCount.get());
+ assertEquals("No user interaction invoked", 1, updatesCount.get());
+ } finally {
+ client.stop();
+ }
+ }
+ }
+
+ @Test
+ public void testPasswordIdentityProviderPropagation() throws Exception {
+ try (SshClient client = setupTestClient()) {
+ List<String> passwords = Collections.singletonList(getCurrentTestName());
+ AtomicInteger loadCount = new AtomicInteger(0);
+ PasswordIdentityProvider provider = () -> {
+ loadCount.incrementAndGet();
+ outputDebugMessage("loadPasswords - count=%s", loadCount);
+ return passwords;
+ };
+ client.setPasswordIdentityProvider(provider);
+
+ client.start();
+ try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
+ .verify(CONNECT_TIMEOUT)
+ .getSession()) {
+ s.auth().verify(AUTH_TIMEOUT);
+ assertEquals("Mismatched load passwords count", 1, loadCount.get());
+ assertSame("Mismatched passwords identity provider", provider, s.getPasswordIdentityProvider());
+ } finally {
+ client.stop();
+ }
+ }
+ }
+
+ @Test // see SSHD-714
+ public void testPasswordIdentityWithSpacesPrefixOrSuffix() throws Exception {
+ sshd.setPasswordAuthenticator((username, password, session) -> {
+ return (username != null) && (!username.trim().isEmpty())
+ && (password != null) && (!password.isEmpty())
+ && ((password.charAt(0) == ' ') || (password.charAt(password.length() - 1) == ' '));
+ });
+
+ try (SshClient client = setupTestClient()) {
+ client.start();
+
+ try {
+ for (String password : new String[] {
+ " ", " ", " " + getCurrentTestName(), getCurrentTestName() + " "
+ }) {
+ try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
+ .verify(CONNECT_TIMEOUT)
+ .getSession()) {
+ s.addPasswordIdentity(password);
+
+ AuthFuture auth = s.auth();
+ assertTrue("No authentication result in time for password='" + password + "'",
+ auth.await(AUTH_TIMEOUT));
+ assertTrue("Failed to authenticate with password='" + password + "'", auth.isSuccess());
+ }
+ }
+ } finally {
+ client.stop();
+ }
+ }
+ }
+
+ @Test // see SSHD-1114
+ public void testPasswordAuthenticationReporter() throws Exception {
+ String goodPassword = getCurrentTestName();
+ String badPassword = getClass().getSimpleName();
+ List<String> attempted = new ArrayList<>();
+ sshd.setPasswordAuthenticator((user, password, session) -> {
+ attempted.add(password);
+ return goodPassword.equals(password);
+ });
+ sshd.setKeyboardInteractiveAuthenticator(KeyboardInteractiveAuthenticator.NONE);
+ sshd.setPublickeyAuthenticator(RejectAllPublickeyAuthenticator.INSTANCE);
+
+ List<String> reported = new ArrayList<>();
+ PasswordAuthenticationReporter reporter = new PasswordAuthenticationReporter() {
+ @Override
+ public void signalAuthenticationAttempt(
+ ClientSession session, String service, String oldPassword, boolean modified, String newPassword)
+ throws Exception {
+ reported.add(oldPassword);
+ }
+
+ @Override
+ public void signalAuthenticationSuccess(ClientSession session, String service, String password)
+ throws Exception {
+ assertEquals("Mismatched succesful password", goodPassword, password);
+ }
+
+ @Override
+ public void signalAuthenticationFailure(
+ ClientSession session, String service, String password, boolean partial, List<String> serverMethods)
+ throws Exception {
+ assertEquals("Mismatched failed password", badPassword, password);
+ }
+ };
+
+ try (SshClient client = setupTestClient()) {
+ client.setUserAuthFactories(
+ Collections.singletonList(new org.apache.sshd.client.auth.password.UserAuthPasswordFactory()));
+ client.start();
+
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
+ .verify(CONNECT_TIMEOUT).getSession()) {
+ session.addPasswordIdentity(badPassword);
+ session.addPasswordIdentity(goodPassword);
+ session.setPasswordAuthenticationReporter(reporter);
+ session.auth().verify(AUTH_TIMEOUT);
+ } finally {
+ client.stop();
+ }
+ }
+
+ List<String> expected = Arrays.asList(badPassword, goodPassword);
+ assertListEquals("Attempted passwords", expected, attempted);
+ assertListEquals("Reported passwords", expected, reported);
+ }
+}
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/auth/PublicKeyAuthenticationTest.java b/sshd-core/src/test/java/org/apache/sshd/common/auth/PublicKeyAuthenticationTest.java
new file mode 100644
index 0000000..300b77e
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/common/auth/PublicKeyAuthenticationTest.java
@@ -0,0 +1,327 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.auth;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.auth.pubkey.PublicKeyAuthenticationReporter;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.SshConstants;
+import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.session.SessionContext;
+import org.apache.sshd.common.signature.BuiltinSignatures;
+import org.apache.sshd.common.signature.Signature;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.io.resource.URLResource;
+import org.apache.sshd.common.util.security.SecurityUtils;
+import org.apache.sshd.server.auth.keyboard.KeyboardInteractiveAuthenticator;
+import org.apache.sshd.server.auth.password.RejectAllPasswordAuthenticator;
+import org.apache.sshd.server.session.ServerSession;
+import org.apache.sshd.util.test.CommonTestSupportUtils;
+import org.apache.sshd.util.test.CoreTestSupportUtils;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class PublicKeyAuthenticationTest extends AuthenticationTestSupport {
+ public PublicKeyAuthenticationTest() {
+ super();
+ }
+
+ @Test // see SSHD-618
+ public void testPublicKeyAuthDifferentThanKex() throws Exception {
+ KeyPairProvider serverKeys = KeyPairProvider.wrap(
+ CommonTestSupportUtils.generateKeyPair(KeyUtils.RSA_ALGORITHM, 1024),
+ CommonTestSupportUtils.generateKeyPair(KeyUtils.DSS_ALGORITHM, 512),
+ CommonTestSupportUtils.generateKeyPair(KeyUtils.EC_ALGORITHM, 256));
+ sshd.setKeyPairProvider(serverKeys);
+ sshd.setKeyboardInteractiveAuthenticator(KeyboardInteractiveAuthenticator.NONE);
+ sshd.setPasswordAuthenticator(RejectAllPasswordAuthenticator.INSTANCE);
+
+ KeyPair clientIdentity = CommonTestSupportUtils.generateKeyPair(KeyUtils.EC_ALGORITHM, 256);
+ sshd.setPublickeyAuthenticator((username, key, session) -> {
+ String keyType = KeyUtils.getKeyType(key);
+ String expType = KeyUtils.getKeyType(clientIdentity);
+ assertEquals("Mismatched client key types", expType, keyType);
+ assertKeyEquals("Mismatched authentication public keys", clientIdentity.getPublic(), key);
+ return true;
+ });
+
+ // since we need to use RSA
+ CoreTestSupportUtils.setupFullSignaturesSupport(sshd);
+ try (SshClient client = setupTestClient()) {
+ // force server to use only RSA
+ NamedFactory<Signature> kexSignature = BuiltinSignatures.rsa;
+ client.setSignatureFactories(Collections.singletonList(kexSignature));
+ client.setServerKeyVerifier((sshClientSession, remoteAddress, serverKey) -> {
+ String keyType = KeyUtils.getKeyType(serverKey);
+ String expType = kexSignature.getName();
+ assertEquals("Mismatched server key type", expType, keyType);
+
+ KeyPair kp;
+ try {
+ kp = ValidateUtils.checkNotNull(serverKeys.loadKey(null, keyType), "No server key for type=%s", keyType);
+ } catch (IOException | GeneralSecurityException e) {
+ throw new RuntimeException(
+ "Unexpected " + e.getClass().getSimpleName() + ")"
+ + " keys loading exception: " + e.getMessage(),
+ e);
+ }
+ assertKeyEquals("Mismatched server public keys", kp.getPublic(), serverKey);
+ return true;
+ });
+
+ // allow only EC keys for public key authentication
+ org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyFactory factory
+ = new org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyFactory();
+ factory.setSignatureFactories(
+ Arrays.asList(
+ BuiltinSignatures.nistp256, BuiltinSignatures.nistp384, BuiltinSignatures.nistp521));
+ client.setUserAuthFactories(Collections.singletonList(factory));
+
+ client.start();
+ try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
+ .verify(CONNECT_TIMEOUT)
+ .getSession()) {
+ s.addPublicKeyIdentity(clientIdentity);
+ s.auth().verify(AUTH_TIMEOUT);
+ } finally {
+ client.stop();
+ }
+ }
+ }
+
+ @Test // see SSHD-624
+ public void testMismatchedUserAuthPkOkData() throws Exception {
+ AtomicInteger challengeCounter = new AtomicInteger(0);
+ sshd.setUserAuthFactories(Collections.singletonList(
+ new org.apache.sshd.server.auth.pubkey.UserAuthPublicKeyFactory() {
+ @Override
+ public org.apache.sshd.server.auth.pubkey.UserAuthPublicKey createUserAuth(ServerSession session)
+ throws IOException {
+ return new org.apache.sshd.server.auth.pubkey.UserAuthPublicKey() {
+ @Override
+ protected void sendPublicKeyResponse(
+ ServerSession session, String username, String alg, PublicKey key,
+ byte[] keyBlob, int offset, int blobLen, Buffer buffer)
+ throws Exception {
+ int count = challengeCounter.incrementAndGet();
+ outputDebugMessage("sendPublicKeyChallenge(%s)[%s]: count=%d", session, alg, count);
+ if (count == 1) {
+ // send wrong key type
+ super.sendPublicKeyResponse(session, username,
+ KeyPairProvider.SSH_DSS, key, keyBlob, offset, blobLen, buffer);
+ } else if (count == 2) {
+ // send another key
+ KeyPair otherPair = org.apache.sshd.util.test.CommonTestSupportUtils
+ .generateKeyPair(KeyUtils.RSA_ALGORITHM, 1024);
+ PublicKey otherKey = otherPair.getPublic();
+ Buffer buf = session.createBuffer(SshConstants.SSH_MSG_USERAUTH_PK_OK,
+ blobLen + alg.length() + Long.SIZE);
+ buf.putString(alg);
+ buf.putPublicKey(otherKey);
+ session.writePacket(buf);
+ } else {
+ super.sendPublicKeyResponse(session, username, alg, key, keyBlob, offset, blobLen, buffer);
+ }
+ }
+ };
+ }
+
+ }));
+
+ try (SshClient client = setupTestClient()) {
+ KeyPair clientIdentity = CommonTestSupportUtils.generateKeyPair(
+ CommonTestSupportUtils.DEFAULT_TEST_HOST_KEY_PROVIDER_ALGORITHM,
+ CommonTestSupportUtils.DEFAULT_TEST_HOST_KEY_SIZE);
+ client.start();
+
+ try {
+ for (int index = 1; index <= 4; index++) {
+ try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
+ .verify(CONNECT_TIMEOUT)
+ .getSession()) {
+ s.addPublicKeyIdentity(clientIdentity);
+ s.auth().verify(AUTH_TIMEOUT);
+ assertEquals("Mismatched number of challenges", 3, challengeCounter.get());
+ break;
+ } catch (SshException e) { // expected
+ outputDebugMessage("%s on retry #%d: %s", e.getClass().getSimpleName(), index, e.getMessage());
+
+ Throwable t = e.getCause();
+ assertObjectInstanceOf("Unexpected failure cause at retry #" + index, InvalidKeySpecException.class, t);
+ }
+ }
+ } finally {
+ client.stop();
+ }
+ }
+ }
+
+ @Test // see SSHD-862
+ public void testSessionContextPropagatedToKeyFilePasswordProvider() throws Exception {
+ try (SshClient client = setupTestClient()) {
+ client.start();
+
+ try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
+ .verify(CONNECT_TIMEOUT)
+ .getSession()) {
+ String keyLocation = "super-secret-passphrase-ec256-key";
+ FilePasswordProvider passwordProvider = new FilePasswordProvider() {
+ @Override
+ public String getPassword(
+ SessionContext session, NamedResource resourceKey, int retryIndex)
+ throws IOException {
+ assertSame("Mismatched session context", s, session);
+ assertEquals("Mismatched retry index", 0, retryIndex);
+
+ String name = resourceKey.getName();
+ int pos = name.lastIndexOf('/');
+ if (pos >= 0) {
+ name = name.substring(pos + 1);
+ }
+ assertEquals("Mismatched location", keyLocation, name);
+
+ Boolean passwordRequested = session.getAttribute(PASSWORD_ATTR);
+ assertNull("Password already requested", passwordRequested);
+ session.setAttribute(PASSWORD_ATTR, Boolean.TRUE);
+ return "super secret passphrase";
+ }
+ };
+ s.setKeyIdentityProvider(new KeyIdentityProvider() {
+ @Override
+ public Iterable<KeyPair> loadKeys(SessionContext session) throws IOException, GeneralSecurityException {
+ assertSame("Mismatched session context", s, session);
+ URL location = getClass().getResource(keyLocation);
+ assertNotNull("Missing key file " + keyLocation, location);
+
+ URLResource resourceKey = new URLResource(location);
+ Iterable<KeyPair> ids;
+ try (InputStream keyData = resourceKey.openInputStream()) {
+ ids = SecurityUtils.loadKeyPairIdentities(session, resourceKey, keyData, passwordProvider);
+ }
+ KeyPair kp = GenericUtils.head(ids);
+ assertNotNull("No identity loaded from " + resourceKey, kp);
+ return Collections.singletonList(kp);
+ }
+ });
+ s.auth().verify(AUTH_TIMEOUT);
+
+ Boolean passwordRequested = s.getAttribute(PASSWORD_ATTR);
+ assertNotNull("Password provider not invoked", passwordRequested);
+ assertTrue("Password not requested", passwordRequested.booleanValue());
+ } finally {
+ client.stop();
+ }
+ }
+ }
+
+ @Test // see SSHD-1114
+ public void testPublicKeyAuthenticationReporter() throws Exception {
+ KeyPair goodIdentity = CommonTestSupportUtils.generateKeyPair(KeyUtils.EC_ALGORITHM, 256);
+ KeyPair badIdentity = CommonTestSupportUtils.generateKeyPair(KeyUtils.EC_ALGORITHM, 256);
+ List<PublicKey> attempted = new ArrayList<>();
+ sshd.setPublickeyAuthenticator((username, key, session) -> {
+ attempted.add(key);
+ return KeyUtils.compareKeys(goodIdentity.getPublic(), key);
+ });
+ sshd.setPasswordAuthenticator(RejectAllPasswordAuthenticator.INSTANCE);
+ sshd.setKeyboardInteractiveAuthenticator(KeyboardInteractiveAuthenticator.NONE);
+
+ List<PublicKey> reported = new ArrayList<>();
+ List<PublicKey> signed = new ArrayList<>();
+ PublicKeyAuthenticationReporter reporter = new PublicKeyAuthenticationReporter() {
+ @Override
+ public void signalAuthenticationAttempt(
+ ClientSession session, String service, KeyPair identity, String signature)
+ throws Exception {
+ reported.add(identity.getPublic());
+ }
+
+ @Override
+ public void signalSignatureAttempt(
+ ClientSession session, String service, KeyPair identity, String signature, byte[] sigData)
+ throws Exception {
+ signed.add(identity.getPublic());
+ }
+
+ @Override
+ public void signalAuthenticationSuccess(ClientSession session, String service, KeyPair identity)
+ throws Exception {
+ assertTrue("Mismatched success identity", KeyUtils.compareKeys(goodIdentity.getPublic(), identity.getPublic()));
+ }
+
+ @Override
+ public void signalAuthenticationFailure(
+ ClientSession session, String service, KeyPair identity, boolean partial, List<String> serverMethods)
+ throws Exception {
+ assertTrue("Mismatched failed identity", KeyUtils.compareKeys(badIdentity.getPublic(), identity.getPublic()));
+ }
+ };
+
+ try (SshClient client = setupTestClient()) {
+ client.setUserAuthFactories(
+ Collections.singletonList(new org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyFactory()));
+ client.start();
+
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
+ .verify(CONNECT_TIMEOUT).getSession()) {
+ session.addPublicKeyIdentity(badIdentity);
+ session.addPublicKeyIdentity(goodIdentity);
+ session.setPublicKeyAuthenticationReporter(reporter);
+ session.auth().verify(AUTH_TIMEOUT);
+ } finally {
+ client.stop();
+ }
+ }
+
+ List<PublicKey> expected = Arrays.asList(badIdentity.getPublic(), goodIdentity.getPublic());
+ // The server public key authenticator is called twice with the good identity
+ int numAttempted = attempted.size();
+ assertKeyListEquals("Attempted", expected, (numAttempted > 0) ? attempted.subList(0, numAttempted - 1) : attempted);
+ assertKeyListEquals("Reported", expected, reported);
+ // The signing is attempted only if the initial public key is accepted
+ assertKeyListEquals("Signed", Collections.singletonList(goodIdentity.getPublic()), signed);
+ }
+}
diff --git a/sshd-core/src/test/java/org/apache/sshd/util/test/server/TestServerSession.java b/sshd-core/src/test/java/org/apache/sshd/util/test/server/TestServerSession.java
new file mode 100644
index 0000000..652092b
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/util/test/server/TestServerSession.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.util.test.server;
+
+import org.apache.sshd.common.io.IoSession;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.server.ServerFactoryManager;
+import org.apache.sshd.server.session.ServerSessionImpl;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class TestServerSession extends ServerSessionImpl {
+ public TestServerSession(ServerFactoryManager server, IoSession ioSession) throws Exception {
+ super(server, ioSession);
+ }
+
+ @Override
+ public void handleMessage(Buffer buffer) throws Exception {
+ super.handleMessage(buffer); // debug breakpoint
+ }
+}