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:49 UTC
[mina-sshd] 10/15: [SSHD-1114] Added callbacks for client-side
public key authentication progress
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 a94f45622ace7cad2d6dd78ef8980ff7e87858a3
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Thu Dec 31 23:18:04 2020 +0200
[SSHD-1114] Added callbacks for client-side public key authentication progress
---
CHANGES.md | 1 +
docs/event-listeners.md | 6 ++
.../sshd/client/auth/pubkey/PublicKeyIdentity.java | 6 +-
.../apache/sshd/util/test/JUnitTestSupport.java | 13 +++
.../main/java/org/apache/sshd/agent/SshAgent.java | 10 +++
.../sshd/agent/common/AbstractAgentProxy.java | 1 +
.../org/apache/sshd/agent/local/AgentImpl.java | 6 ++
.../sshd/client/ClientAuthenticationManager.java | 5 ++
.../java/org/apache/sshd/client/SshClient.java | 14 +++-
.../sshd/client/auth/pubkey/KeyAgentIdentity.java | 20 +++--
.../sshd/client/auth/pubkey/KeyPairIdentity.java | 12 +--
.../pubkey/PublicKeyAuthenticationReporter.java | 92 ++++++++++++++++++++++
.../sshd/client/auth/pubkey/UserAuthPublicKey.java | 63 +++++++++++----
.../sshd/client/session/AbstractClientSession.java | 14 ++++
.../client/ClientAuthenticationManagerTest.java | 11 +++
.../sshd/common/auth/AuthenticationTest.java | 85 +++++++++++++++++++-
16 files changed, 326 insertions(+), 33 deletions(-)
diff --git a/CHANGES.md b/CHANGES.md
index 75ae5f7..c3aebd2 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -27,3 +27,4 @@
* [SSHD-1085](https://issues.apache.org/jira/browse/SSHD-1085) Added more notifications related to channel state change for detecting channel closing or closed earlier.
* [SSHD-1109](https://issues.apache.org/jira/browse/SSHD-1109) Replace log4j with logback as the slf4j logger implementation for tests
* [SSHD-1114](https://issues.apache.org/jira/browse/SSHD-1114) Added callbacks for client-side password authentication progress
+* [SSHD-1114](https://issues.apache.org/jira/browse/SSHD-1114) Added callbacks for client-side public key authentication progress
diff --git a/docs/event-listeners.md b/docs/event-listeners.md
index 3f9dcf6..50869ff 100644
--- a/docs/event-listeners.md
+++ b/docs/event-listeners.md
@@ -199,3 +199,9 @@ in [RFC 4254 - section 6.7](https://tools.ietf.org/html/rfc4254#section-6.7)
Used to inform about the progress of the client-side password based authentication as described in [RFC-4252 section 8](https://tools.ietf.org/html/rfc4252#section-8).
Can be registered globally on the `SshClient` and also for a specific `ClientSession` after it is established but before its `auth()` method is called - thus
overriding any globally registered instance.
+
+### `PublicKeyAuthenticationReporter`
+
+Used to inform about the progress of the client-side public key authentication as described in [RFC-4252 section 7](https://tools.ietf.org/html/rfc4252#section-7).
+Can be registered globally on the `SshClient` and also for a specific `ClientSession` after it is established but before its `auth()` method is called - thus
+overriding any globally registered instance.
diff --git a/sshd-common/src/main/java/org/apache/sshd/client/auth/pubkey/PublicKeyIdentity.java b/sshd-common/src/main/java/org/apache/sshd/client/auth/pubkey/PublicKeyIdentity.java
index e794986..f0f3634 100644
--- a/sshd-common/src/main/java/org/apache/sshd/client/auth/pubkey/PublicKeyIdentity.java
+++ b/sshd-common/src/main/java/org/apache/sshd/client/auth/pubkey/PublicKeyIdentity.java
@@ -18,7 +18,7 @@
*/
package org.apache.sshd.client.auth.pubkey;
-import java.security.PublicKey;
+import java.security.KeyPair;
import java.util.Map;
import org.apache.sshd.common.session.SessionContext;
@@ -30,9 +30,9 @@ import org.apache.sshd.common.session.SessionContext;
*/
public interface PublicKeyIdentity {
/**
- * @return The {@link PublicKey} identity value
+ * @return The {@link KeyPair} identity value
*/
- PublicKey getPublicKey();
+ KeyPair getKeyIdentity();
/**
* Proves the public key identity by signing the given data
diff --git a/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java b/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java
index 98d870f..ff3a329 100644
--- a/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java
+++ b/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java
@@ -455,6 +455,19 @@ public abstract class JUnitTestSupport extends Assert {
assertArrayEquals(message + "[encoded-data]", expected.getEncoded(), actual.getEncoded());
}
+ public static <T extends Key> void assertKeyListEquals(
+ String message, List<? extends T> expected, List<? extends T> actual) {
+ int numKeys = GenericUtils.size(expected);
+ assertEquals(message + "[size]", numKeys, GenericUtils.size(actual));
+ if (numKeys <= 0) {
+ return;
+ }
+
+ for (int index = 0; index < numKeys; index++) {
+ assertKeyEquals(message + "[#" + index + "]", expected.get(index), actual.get(index));
+ }
+ }
+
public static <T extends Key> void assertKeyEquals(String message, T expected, T actual) {
if (expected == actual) {
return;
diff --git a/sshd-core/src/main/java/org/apache/sshd/agent/SshAgent.java b/sshd-core/src/main/java/org/apache/sshd/agent/SshAgent.java
index 1c25f3a..700c69c 100644
--- a/sshd-core/src/main/java/org/apache/sshd/agent/SshAgent.java
+++ b/sshd-core/src/main/java/org/apache/sshd/agent/SshAgent.java
@@ -47,6 +47,16 @@ public interface SshAgent extends java.nio.channels.Channel {
*/
Map.Entry<String, byte[]> sign(SessionContext session, PublicKey key, String algo, byte[] data) throws IOException;
+ /**
+ * Used for reporting client-side public key authentication via agent
+ *
+ * @param key The {@link PublicKey} that is going to be used
+ * @return The {@link KeyPair} identity for it - if available - {@code null} otherwise
+ */
+ default KeyPair resolveLocalIdentity(PublicKey key) {
+ return null;
+ }
+
void addIdentity(KeyPair key, String comment) throws IOException;
void removeIdentity(PublicKey key) throws IOException;
diff --git a/sshd-core/src/main/java/org/apache/sshd/agent/common/AbstractAgentProxy.java b/sshd-core/src/main/java/org/apache/sshd/agent/common/AbstractAgentProxy.java
index 3add243..da38c72 100644
--- a/sshd-core/src/main/java/org/apache/sshd/agent/common/AbstractAgentProxy.java
+++ b/sshd-core/src/main/java/org/apache/sshd/agent/common/AbstractAgentProxy.java
@@ -83,6 +83,7 @@ public abstract class AbstractAgentProxy extends AbstractLoggingBean implements
}
int nbIdentities = buffer.getInt();
+ // TODO make the maximum a Property
if ((nbIdentities < 0) || (nbIdentities > 1024)) {
throw new SshException("Illogical identities count: " + nbIdentities);
}
diff --git a/sshd-core/src/main/java/org/apache/sshd/agent/local/AgentImpl.java b/sshd-core/src/main/java/org/apache/sshd/agent/local/AgentImpl.java
index 2b80370..bb50ce6 100644
--- a/sshd-core/src/main/java/org/apache/sshd/agent/local/AgentImpl.java
+++ b/sshd-core/src/main/java/org/apache/sshd/agent/local/AgentImpl.java
@@ -105,6 +105,12 @@ public class AgentImpl implements SshAgent {
}
@Override
+ public KeyPair resolveLocalIdentity(PublicKey key) {
+ Map.Entry<KeyPair, String> pp = getKeyPair(keys, key);
+ return (pp == null) ? null : pp.getKey();
+ }
+
+ @Override
public void removeIdentity(PublicKey key) throws IOException {
if (!isOpen()) {
throw new SshException("Agent closed");
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/ClientAuthenticationManager.java b/sshd-core/src/main/java/org/apache/sshd/client/ClientAuthenticationManager.java
index 14bca04..b6c6706 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/ClientAuthenticationManager.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/ClientAuthenticationManager.java
@@ -30,6 +30,7 @@ import org.apache.sshd.client.auth.UserAuthFactory;
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.keyverifier.ServerKeyVerifier;
import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.common.auth.UserAuthFactoriesManager;
@@ -111,6 +112,10 @@ public interface ClientAuthenticationManager
void setPasswordAuthenticationReporter(PasswordAuthenticationReporter reporter);
+ PublicKeyAuthenticationReporter getPublicKeyAuthenticationReporter();
+
+ void setPublicKeyAuthenticationReporter(PublicKeyAuthenticationReporter reporter);
+
@Override
default void setUserAuthFactoriesNames(Collection<String> names) {
BuiltinUserAuthFactories.ParseResult result = BuiltinUserAuthFactories.parseFactoriesList(names);
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
index 4182bf8..2d07cfa 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
@@ -49,6 +49,7 @@ 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.password.UserAuthPasswordFactory;
+import org.apache.sshd.client.auth.pubkey.PublicKeyAuthenticationReporter;
import org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyFactory;
import org.apache.sshd.client.config.hosts.HostConfigEntry;
import org.apache.sshd.client.config.hosts.HostConfigEntryResolver;
@@ -178,10 +179,11 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
private HostConfigEntryResolver hostConfigEntryResolver;
private ClientIdentityLoader clientIdentityLoader;
private KeyIdentityProvider keyIdentityProvider;
+ private PublicKeyAuthenticationReporter publicKeyAuthenticationReporter;
private FilePasswordProvider filePasswordProvider;
private PasswordIdentityProvider passwordIdentityProvider;
- private UserInteraction userInteraction;
private PasswordAuthenticationReporter passwordAuthenticationReporter;
+ private UserInteraction userInteraction;
private final List<Object> identities = new CopyOnWriteArrayList<>();
private final AuthenticationIdentitiesProvider identitiesProvider;
@@ -359,6 +361,16 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
}
@Override
+ public PublicKeyAuthenticationReporter getPublicKeyAuthenticationReporter() {
+ return publicKeyAuthenticationReporter;
+ }
+
+ @Override
+ public void setPublicKeyAuthenticationReporter(PublicKeyAuthenticationReporter reporter) {
+ this.publicKeyAuthenticationReporter = reporter;
+ }
+
+ @Override
protected void checkConfig() {
super.checkConfig();
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/KeyAgentIdentity.java b/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/KeyAgentIdentity.java
index 53b37b2..2cec320 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/KeyAgentIdentity.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/KeyAgentIdentity.java
@@ -18,6 +18,7 @@
*/
package org.apache.sshd.client.auth.pubkey;
+import java.security.KeyPair;
import java.security.PublicKey;
import java.util.Map;
import java.util.Objects;
@@ -33,18 +34,23 @@ import org.apache.sshd.common.session.SessionContext;
*/
public class KeyAgentIdentity implements PublicKeyIdentity {
private final SshAgent agent;
- private final PublicKey key;
+ private final KeyPair keyPair;
+ private KeyPair resolvedPair;
private final String comment;
public KeyAgentIdentity(SshAgent agent, PublicKey key, String comment) {
this.agent = Objects.requireNonNull(agent, "No signing agent");
- this.key = Objects.requireNonNull(key, "No public key");
+ this.keyPair = new KeyPair(Objects.requireNonNull(key, "No public key"), null);
this.comment = comment;
}
@Override
- public PublicKey getPublicKey() {
- return key;
+ public KeyPair getKeyIdentity() {
+ if (resolvedPair == null) {
+ resolvedPair = agent.resolveLocalIdentity(keyPair.getPublic());
+ }
+
+ return (resolvedPair == null) ? keyPair : resolvedPair;
}
public String getComment() {
@@ -53,12 +59,14 @@ public class KeyAgentIdentity implements PublicKeyIdentity {
@Override
public Map.Entry<String, byte[]> sign(SessionContext session, String algo, byte[] data) throws Exception {
- return agent.sign(session, getPublicKey(), algo, data);
+ KeyPair kp = getKeyIdentity();
+ return agent.sign(session, kp.getPublic(), algo, data);
}
@Override
public String toString() {
- PublicKey pubKey = getPublicKey();
+ KeyPair kp = getKeyIdentity();
+ PublicKey pubKey = kp.getPublic();
return getClass().getSimpleName() + "[" + KeyUtils.getKeyType(pubKey) + "]"
+ " fingerprint=" + KeyUtils.getFingerPrint(pubKey)
+ ", comment=" + getComment();
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/KeyPairIdentity.java b/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/KeyPairIdentity.java
index b90c369..6dfcf72 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/KeyPairIdentity.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/KeyPairIdentity.java
@@ -43,7 +43,7 @@ import org.apache.sshd.common.util.ValidateUtils;
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public class KeyPairIdentity implements PublicKeyIdentity, SignatureFactoriesHolder {
- protected final KeyPair pair;
+ private final KeyPair pair;
private final List<NamedFactory<Signature>> signatureFactories;
public KeyPairIdentity(SignatureFactoriesManager primary, SignatureFactoriesManager secondary, KeyPair pair) {
@@ -55,8 +55,8 @@ public class KeyPairIdentity implements PublicKeyIdentity, SignatureFactoriesHol
}
@Override
- public PublicKey getPublicKey() {
- return pair.getPublic();
+ public KeyPair getKeyIdentity() {
+ return pair;
}
@Override
@@ -68,7 +68,8 @@ public class KeyPairIdentity implements PublicKeyIdentity, SignatureFactoriesHol
public Map.Entry<String, byte[]> sign(SessionContext session, String algo, byte[] data) throws Exception {
NamedFactory<? extends Signature> factory;
if (GenericUtils.isEmpty(algo)) {
- algo = KeyUtils.getKeyType(getPublicKey());
+ KeyPair kp = getKeyIdentity();
+ algo = KeyUtils.getKeyType(kp.getPublic());
// SSHD-1104 check if the key type is aliased
factory = SignatureFactory.resolveSignatureFactory(algo, getSignatureFactories());
} else {
@@ -86,7 +87,8 @@ public class KeyPairIdentity implements PublicKeyIdentity, SignatureFactoriesHol
@Override
public String toString() {
- PublicKey pubKey = getPublicKey();
+ KeyPair kp = getKeyIdentity();
+ PublicKey pubKey = kp.getPublic();
return getClass().getSimpleName()
+ " type=" + KeyUtils.getKeyType(pubKey)
+ ", factories=" + getSignatureFactoriesNameList()
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/PublicKeyAuthenticationReporter.java b/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/PublicKeyAuthenticationReporter.java
new file mode 100644
index 0000000..b5900b8
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/PublicKeyAuthenticationReporter.java
@@ -0,0 +1,92 @@
+/*
+ * 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.client.auth.pubkey;
+
+import java.security.KeyPair;
+import java.util.List;
+
+import org.apache.sshd.client.session.ClientSession;
+
+/**
+ * Provides report about the client side public key authentication progress
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc4252#section-7">RFC-4252 section 7</a>
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface PublicKeyAuthenticationReporter {
+ /**
+ * Sending the initial request to use public key authentication
+ *
+ * @param session The {@link ClientSession}
+ * @param service The requesting service name
+ * @param identity The {@link KeyPair} identity being attempted - <B>Note:</B> for agent based authentications the
+ * private key may be {@code null}
+ * @param signature The type of signature that is being used
+ * @throws Exception If failed to handle the callback - <B>Note:</B> may cause session close
+ */
+ default void signalAuthenticationAttempt(
+ ClientSession session, String service, KeyPair identity, String signature)
+ throws Exception {
+ // ignored
+ }
+
+ /**
+ * Sending the signed response to the server's challenge
+ *
+ * @param session The {@link ClientSession}
+ * @param service The requesting service name
+ * @param identity The {@link KeyPair} identity being attempted - <B>Note:</B> for agent based authentications the
+ * private key may be {@code null}
+ * @param signature The type of signature that is being used
+ * @param signed The generated signature data
+ * @throws Exception If failed to handle the callback - <B>Note:</B> may cause session close
+ */
+ default void signalSignatureAttempt(
+ ClientSession session, String service, KeyPair identity, String signature, byte[] signed)
+ throws Exception {
+ // ignored
+ }
+
+ /**
+ * @param session The {@link ClientSession}
+ * @param service The requesting service name
+ * @param identity The {@link KeyPair} identity being attempted - <B>Note:</B> for agent based authentications the
+ * private key may be {@code null}
+ * @throws Exception If failed to handle the callback - <B>Note:</B> may cause session close
+ */
+ default void signalAuthenticationSuccess(ClientSession session, String service, KeyPair identity) throws Exception {
+ // ignored
+ }
+
+ /**
+ * @param session The {@link ClientSession}
+ * @param service The requesting service name
+ * @param identity The {@link KeyPair} identity being attempted - <B>Note:</B> for agent based authentications
+ * the private key may be {@code null}
+ * @param partial {@code true} if some partial authentication success so far
+ * @param serverMethods The {@link List} of authentication methods that can continue
+ * @throws Exception If failed to handle the callback - <B>Note:</B> may cause session close
+ */
+ default void signalAuthenticationFailure(
+ ClientSession session, String service, KeyPair identity, boolean partial, List<String> serverMethods)
+ throws Exception {
+ // ignored
+ }
+}
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/UserAuthPublicKey.java b/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/UserAuthPublicKey.java
index 28e575e..16fab44 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/UserAuthPublicKey.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/UserAuthPublicKey.java
@@ -20,6 +20,7 @@ package org.apache.sshd.client.auth.pubkey;
import java.io.Closeable;
import java.io.IOException;
+import java.security.KeyPair;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.util.Collection;
@@ -111,16 +112,17 @@ public class UserAuthPublicKey extends AbstractUserAuth implements SignatureFact
log.trace("sendAuthDataRequest({})[{}] current key details: {}", session, service, current);
}
- PublicKey key;
+ KeyPair keyPair;
try {
- key = current.getPublicKey();
+ keyPair = current.getKeyIdentity();
} catch (Error e) {
- warn("sendAuthDataRequest({})[{}] failed ({}) to retrieve public key: {}",
+ warn("sendAuthDataRequest({})[{}] failed ({}) to retrieve key identity: {}",
session, service, e.getClass().getSimpleName(), e.getMessage(), e);
throw new RuntimeSshException(e);
}
- String keyType = KeyUtils.getKeyType(key);
+ PublicKey pubKey = keyPair.getPublic();
+ String keyType = KeyUtils.getKeyType(pubKey);
NamedFactory<? extends Signature> factory;
// SSHD-1104 check if the key type is aliased
if (current instanceof SignatureFactoriesHolder) {
@@ -134,7 +136,12 @@ public class UserAuthPublicKey extends AbstractUserAuth implements SignatureFact
String name = getName();
if (debugEnabled) {
log.debug("sendAuthDataRequest({})[{}] send SSH_MSG_USERAUTH_REQUEST request {} type={} - fingerprint={}",
- session, service, name, algo, KeyUtils.getFingerPrint(key));
+ session, service, name, algo, KeyUtils.getFingerPrint(pubKey));
+ }
+
+ PublicKeyAuthenticationReporter reporter = session.getPublicKeyAuthenticationReporter();
+ if (reporter != null) {
+ reporter.signalAuthenticationAttempt(session, service, keyPair, algo);
}
Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_USERAUTH_REQUEST);
@@ -143,7 +150,7 @@ public class UserAuthPublicKey extends AbstractUserAuth implements SignatureFact
buffer.putString(name);
buffer.putBoolean(false);
buffer.putString(algo);
- buffer.putPublicKey(key);
+ buffer.putPublicKey(pubKey);
session.writePacket(buffer);
return true;
}
@@ -161,17 +168,18 @@ public class UserAuthPublicKey extends AbstractUserAuth implements SignatureFact
/*
* Make sure the server echo-ed the same key we sent as sanctioned by RFC4252 section 7
*/
- PublicKey key;
+ KeyPair keyPair;
boolean debugEnabled = log.isDebugEnabled();
try {
- key = current.getPublicKey();
+ keyPair = current.getKeyIdentity();
} catch (Error e) {
- warn("processAuthDataRequest({})[{}][{}] failed ({}) to retrieve public key: {}",
+ warn("processAuthDataRequest({})[{}][{}] failed ({}) to retrieve key identity: {}",
session, service, name, e.getClass().getSimpleName(), e.getMessage(), e);
throw new RuntimeSshException(e);
}
- String curKeyType = KeyUtils.getKeyType(key);
+ PublicKey pubKey = keyPair.getPublic();
+ String curKeyType = KeyUtils.getKeyType(pubKey);
String rspKeyType = buffer.getString();
Collection<String> aliases = KeyUtils.getAllEquivalentKeyTypes(curKeyType);
String algo;
@@ -193,10 +201,10 @@ public class UserAuthPublicKey extends AbstractUserAuth implements SignatureFact
}
PublicKey rspKey = buffer.getPublicKey();
- if (!KeyUtils.compareKeys(rspKey, key)) {
+ if (!KeyUtils.compareKeys(rspKey, pubKey)) {
throw new InvalidKeySpecException(
"processAuthDataRequest(" + session + ")[" + service + "][" + name + "]"
- + " mismatched " + algo + " keys: expected=" + KeyUtils.getFingerPrint(key)
+ + " mismatched " + algo + " keys: expected=" + KeyUtils.getFingerPrint(pubKey)
+ ", actual=" + KeyUtils.getFingerPrint(rspKey));
}
@@ -216,14 +224,19 @@ public class UserAuthPublicKey extends AbstractUserAuth implements SignatureFact
buffer.putString(name);
buffer.putBoolean(true);
buffer.putString(algo);
- buffer.putPublicKey(key);
- appendSignature(session, service, name, username, algo, key, buffer);
+ buffer.putPublicKey(pubKey);
+
+ byte[] sig = appendSignature(session, service, name, username, algo, pubKey, buffer);
+ PublicKeyAuthenticationReporter reporter = session.getPublicKeyAuthenticationReporter();
+ if (reporter != null) {
+ reporter.signalSignatureAttempt(session, service, keyPair, algo, sig);
+ }
session.writePacket(buffer);
return true;
}
- protected void appendSignature(
+ protected byte[] appendSignature(
ClientSession session, String service, String name, String username, String algo, PublicKey key, Buffer buffer)
throws Exception {
byte[] id = session.getSessionId();
@@ -265,6 +278,26 @@ public class UserAuthPublicKey extends AbstractUserAuth implements SignatureFact
bs.putString(algo);
bs.putBytes(sig);
buffer.putBytes(bs.array(), bs.rpos(), bs.available());
+ return sig;
+ }
+
+ @Override
+ public void signalAuthMethodSuccess(ClientSession session, String service, Buffer buffer) throws Exception {
+ PublicKeyAuthenticationReporter reporter = session.getPublicKeyAuthenticationReporter();
+ if (reporter != null) {
+ reporter.signalAuthenticationSuccess(session, service, (current == null) ? null : current.getKeyIdentity());
+ }
+ }
+
+ @Override
+ public void signalAuthMethodFailure(
+ ClientSession session, String service, boolean partial, List<String> serverMethods, Buffer buffer)
+ throws Exception {
+ PublicKeyAuthenticationReporter reporter = session.getPublicKeyAuthenticationReporter();
+ if (reporter != null) {
+ KeyPair identity = (current == null) ? null : current.getKeyIdentity();
+ reporter.signalAuthenticationFailure(session, service, identity, partial, serverMethods);
+ }
}
@Override
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java b/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java
index 4c5eff4..490bbfb 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java
@@ -35,6 +35,7 @@ import org.apache.sshd.client.auth.UserAuthFactory;
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.channel.ChannelDirectTcpip;
import org.apache.sshd.client.channel.ChannelExec;
import org.apache.sshd.client.channel.ChannelShell;
@@ -93,6 +94,7 @@ public abstract class AbstractClientSession extends AbstractSession implements C
private PasswordIdentityProvider passwordIdentityProvider;
private PasswordAuthenticationReporter passwordAuthenticationReporter;
private KeyIdentityProvider keyIdentityProvider;
+ private PublicKeyAuthenticationReporter publicKeyAuthenticationReporter;
private List<UserAuthFactory> userAuthFactories;
private SocketAddress connectAddress;
private ClientProxyConnector proxyConnector;
@@ -215,6 +217,18 @@ public abstract class AbstractClientSession extends AbstractSession implements C
}
@Override
+ public PublicKeyAuthenticationReporter getPublicKeyAuthenticationReporter() {
+ ClientFactoryManager manager = getFactoryManager();
+ return resolveEffectiveProvider(PublicKeyAuthenticationReporter.class, publicKeyAuthenticationReporter,
+ manager.getPublicKeyAuthenticationReporter());
+ }
+
+ @Override
+ public void setPublicKeyAuthenticationReporter(PublicKeyAuthenticationReporter reporter) {
+ this.publicKeyAuthenticationReporter = reporter;
+ }
+
+ @Override
public ClientProxyConnector getClientProxyConnector() {
ClientFactoryManager manager = getFactoryManager();
return resolveEffectiveProvider(ClientProxyConnector.class, proxyConnector, manager.getClientProxyConnector());
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/ClientAuthenticationManagerTest.java b/sshd-core/src/test/java/org/apache/sshd/client/ClientAuthenticationManagerTest.java
index 40df790..c1062e6 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/ClientAuthenticationManagerTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/ClientAuthenticationManagerTest.java
@@ -33,6 +33,7 @@ import org.apache.sshd.client.auth.UserAuthFactory;
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.keyverifier.ServerKeyVerifier;
import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.client.session.ClientSessionImpl;
@@ -91,6 +92,16 @@ public class ClientAuthenticationManagerTest extends BaseTestSupport {
}
@Override
+ public PublicKeyAuthenticationReporter getPublicKeyAuthenticationReporter() {
+ return null;
+ }
+
+ @Override
+ public void setPublicKeyAuthenticationReporter(PublicKeyAuthenticationReporter reporter) {
+ throw new UnsupportedOperationException("setPublicKeyAuthenticationReporter(" + reporter + ")");
+ }
+
+ @Override
public UserInteraction getUserInteraction() {
return null;
}
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 2dbe763..c75d07d 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
@@ -42,6 +42,7 @@ 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;
@@ -996,13 +997,21 @@ public class AuthenticationTest extends BaseTestSupport {
public void testPasswordAuthenticationReporter() throws Exception {
String goodPassword = getCurrentTestName();
String badPassword = getClass().getSimpleName();
- List<String> actual = new ArrayList<>();
+ 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 {
- actual.add(oldPassword);
+ reported.add(oldPassword);
}
@Override
@@ -1035,7 +1044,77 @@ public class AuthenticationTest extends BaseTestSupport {
}
}
- assertListEquals("Attempted passwords", Arrays.asList(badPassword, goodPassword), actual);
+ 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 {