You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by tw...@apache.org on 2026/09/26 14:47:07 UTC
(mina-sshd) 02/09: Merge branch 'master' into dev_3.0
This is an automated email from the ASF dual-hosted git repository.
tomaswolf pushed a commit to branch rc-3.0.0-M6
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git
commit 6b551fe9af7e4aff6bffb3e9beb4641d5db7223a
Merge: 8738fd87f 1c97396ff
Author: Thomas Wolf <tw...@apache.org>
AuthorDate: Fri Sep 25 22:42:06 2026 +0200
Merge branch 'master' into dev_3.0
docs/changes/2.19.0.md | 11 +-
pom.xml | 2 +-
.../apache/sshd/common/RuntimeSshException.java | 2 +-
.../bouncycastle/BouncyCastleKEMAccessor.java | 2 +-
.../common/util/security/bouncycastle/MLKEM.java | 14 +-
.../auth/keyboard/UserAuthKeyboardInteractive.java | 22 +-
.../common/channel/ChannelPipedInputStream.java | 7 +-
.../org/apache/sshd/common/forward/SocksProxy.java | 258 +++++++++++-----
.../auth/keyboard/UserAuthKeyboardInteractive.java | 11 +-
.../password/PasswordChangeRequiredException.java | 2 +-
...AuthorizedKeyEntriesPublickeyAuthenticator.java | 139 ++++++++-
.../server/auth/pubkey/PublickeyAuthenticator.java | 12 +-
.../sshd/server/auth/pubkey/UserAuthPublicKey.java | 25 ++
.../sshd/server/session/ServerUserAuthService.java | 49 ++-
.../src/test/java/org/apache/sshd/ProxyTest.java | 90 ++++++
.../java/org/apache/sshd/client/ClientTest.java | 2 +-
.../sshd/client/auth/pubkey/MultiAuthTest.java | 24 ++
.../common/auth/PublicKeyAuthenticationTest.java | 48 +++
.../channel/ChannelPipedInputStreamTest.java | 36 +++
...orizedKeyEntriesPublickeyAuthenticatorTest.java | 83 +++++
.../server/auth/CertificateReuseMultiAuthTest.java | 112 +++++++
.../sshd/server/auth/PublicKeyMultiAuthTest.java | 124 ++++++++
.../auth/ServerUserAuthServiceAsyncTest.java | 333 +++++++++++++++++++++
.../apache/sshd/git/pgm/EmbeddedCommandRunner.java | 3 +-
.../org/apache/sshd/git/pgm/GitPgmCommandTest.java | 87 +++++-
.../org/apache/sshd/ldap/LdapNetworkConnector.java | 75 ++++-
.../sshd/ldap/LdapPasswordAuthenticator.java | 6 +-
.../org/apache/sshd/ldap/LdapEncodingTest.java | 47 +++
.../sshd/ldap/LdapPasswordAuthenticatorTest.java | 7 +
.../sshd/ldap/LdapPublickeyAuthenticatorTest.java | 11 +-
.../apache/sshd/scp/common/helpers/ScpIoUtils.java | 11 +
.../org/apache/sshd/scp/server/ScpCommand.java | 2 +-
.../org/apache/sshd/scp/common/ScpIoUtilsTest.java | 88 ++++++
.../sshd/sftp/client/impl/DefaultSftpClient.java | 180 ++++++-----
.../org/apache/sshd/sftp/common/SftpConstants.java | 3 +
.../sftp/server/AbstractSftpSubsystemHelper.java | 113 ++++---
36 files changed, 1763 insertions(+), 278 deletions(-)
diff --cc pom.xml
index a72f56046,135b933c0..94c3bc0c0
--- a/pom.xml
+++ b/pom.xml
@@@ -81,20 -80,24 +81,20 @@@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- <project.build.outputTimestamp>2026-06-30T21:47:32Z</project.build.outputTimestamp>
+ <project.build.outputTimestamp>2026-06-30T22:29:09Z</project.build.outputTimestamp>
<java.sdk.version>8</java.sdk.version>
- <javac.source>${java.sdk.version}</javac.source>
- <project.build.java.source>${javac.source}</project.build.java.source>
- <maven.compiler.source>${javac.source}</maven.compiler.source>
+ <javac.release>${java.sdk.version}</javac.release>
+ <maven.compiler.release>${java.sdk.version}</maven.compiler.release>
<ant.version>1.10.15</ant.version>
- <ant.build.javac.source>${javac.source}</ant.build.javac.source>
<build-helper-maven-plugin.version>3.6.1</build-helper-maven-plugin.version>
- <javac.target>${javac.source}</javac.target>
- <required.java.version>[${javac.target},)</required.java.version>
- <project.build.java.target>${javac.target}</project.build.java.target>
- <maven.compiler.target>${javac.target}</maven.compiler.target>
- <ant.build.javac.target>${javac.target}</ant.build.javac.target>
+ <required.java.version>[${java.sdk.version},)</required.java.version>
<groovy.version>4.0.17</groovy.version>
- <bouncycastle.version>1.84</bouncycastle.version>
+ <bouncycastle.version>1.86</bouncycastle.version>
+ <!-- BC FIPS has version numbers 2.x.y. -->
+ <bouncycastle.upper.bound>3</bouncycastle.upper.bound>
<!-- NOTE: upgrading slf4j beyond this version causes
Execution verify-style of goal org.apache.maven.plugins:maven-checkstyle-plugin:3.1.2:check failed.: NullPointerException
diff --cc sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleKEMAccessor.java
index 209a23541,0ba631b7b..a65c24eb7
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleKEMAccessor.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleKEMAccessor.java
@@@ -16,36 -16,24 +16,36 @@@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.sshd.deprecated;
+package org.apache.sshd.common.util.security.bouncycastle;
- import org.bouncycastle.pqc.crypto.mlkem.MLKEMParameters;
-import java.io.IOException;
++import org.bouncycastle.crypto.params.MLKEMParameters;
-import org.apache.sshd.common.util.buffer.Buffer;
+final class BouncyCastleKEMAccessor {
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-@FunctionalInterface
-public interface UserAuth {
+ static final BouncyCastleKEMAccessor INSTANCE = new BouncyCastleKEMAccessor();
+
+ private BouncyCastleKEMAccessor() {
+ super();
+ }
- enum Result {
- Success,
- Failure,
- Continued
+ public boolean isSupported() {
+ try {
+ return Inner.isSupported();
+ } catch (Throwable t) {
+ return false;
+ }
}
- Result next(Buffer buffer) throws IOException;
+ private static final class Inner {
+ private Inner() {
+ super();
+ }
+
+ static boolean isSupported() {
+ // Just something that forces class loading.
+ return MLKEMParameters.ml_kem_768 != null;
+ }
+
+ }
}
diff --cc sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/MLKEM.java
index 3dad32c07,f44b7769d..a05ce2920
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/MLKEM.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/MLKEM.java
@@@ -23,16 -23,15 +23,16 @@@ import java.util.Objects
import org.apache.sshd.common.OptionalFeature;
import org.apache.sshd.common.random.JceRandom;
+import org.apache.sshd.common.util.security.KEM;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.SecretWithEncapsulation;
- import org.bouncycastle.pqc.crypto.mlkem.MLKEMExtractor;
- import org.bouncycastle.pqc.crypto.mlkem.MLKEMGenerator;
- import org.bouncycastle.pqc.crypto.mlkem.MLKEMKeyGenerationParameters;
- import org.bouncycastle.pqc.crypto.mlkem.MLKEMKeyPairGenerator;
- import org.bouncycastle.pqc.crypto.mlkem.MLKEMParameters;
- import org.bouncycastle.pqc.crypto.mlkem.MLKEMPrivateKeyParameters;
- import org.bouncycastle.pqc.crypto.mlkem.MLKEMPublicKeyParameters;
+ import org.bouncycastle.crypto.generators.MLKEMKeyPairGenerator;
+ import org.bouncycastle.crypto.kems.MLKEMExtractor;
+ import org.bouncycastle.crypto.kems.MLKEMGenerator;
+ import org.bouncycastle.crypto.params.MLKEMKeyGenerationParameters;
+ import org.bouncycastle.crypto.params.MLKEMParameters;
+ import org.bouncycastle.crypto.params.MLKEMPrivateKeyParameters;
+ import org.bouncycastle.crypto.params.MLKEMPublicKeyParameters;
/**
* An implementation of the mlkem768 key encapsulation method (KEM), formerly known as Kyber, using Bouncy Castle. But
diff --cc sshd-core/src/main/java/org/apache/sshd/common/forward/SocksProxy.java
index a779aa65f,96edb2ced..caf7f4974
--- a/sshd-core/src/main/java/org/apache/sshd/common/forward/SocksProxy.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/forward/SocksProxy.java
@@@ -115,30 -113,12 +113,40 @@@ public class SocksProxy extends Abstrac
}
}
- protected void sendReply(Buffer message, boolean success, LocalWindow window, long windowSize) {
- protected int getUByte(Buffer buffer) {
- return buffer.getUByte();
- }
-
- protected int getUShort(Buffer buffer) {
- return (getUByte(buffer) << Byte.SIZE) + getUByte(buffer);
++ protected void sendReply(Buffer message, boolean success, Runnable completion, LocalWindow window, long windowSize) {
+ try {
- session.writeBuffer(message);
++ session.writeBuffer(message).addListener(future -> {
++ if (future.isWritten()) {
++ if (success) {
++ // Open the channel window: we're ready to accept data now.
++ try {
++ window.release(windowSize);
++ } catch (IOException e) {
++ log.error("Could not open channel window to {} on channel {}: {}", windowSize, channel, e);
++ service.unregisterChannel(channel);
++ channel.close(true);
++ session.close(true);
++ }
++ }
++ if (completion != null) {
++ completion.run();
++ }
++ if (success) {
++ session.resumeRead();
++ }
++ } else {
++ session.close(true);
++ }
++ });
+ } catch (IOException e) {
+ log.error("Failed ({}) to send channel open response for {}: {}", e.getClass().getSimpleName(), channel,
+ e.getMessage());
+ if (success) {
+ service.unregisterChannel(channel);
+ channel.close(true);
+ }
+ throw new IllegalStateException("Failed to send packet", e);
+ }
- if (success) {
- session.resumeRead();
- // Open the channel window: we're ready to accept data now.
- try {
- window.release(windowSize);
- } catch (IOException e) {
- log.error("Could not open channel window to {} on channel {}: {}", windowSize, channel, e);
- service.unregisterChannel(channel);
- channel.close(true);
- throw new IllegalStateException("Failed to open window", e);
- }
- }
}
}
@@@ -155,12 -135,14 +163,14 @@@
protected void onMessage(Buffer buffer) throws IOException {
if (channel == null) {
int cmd = buffer.getUByte();
- if (cmd != 1) {
+ if (cmd != SocksConstants.Socks4.CMD_CONNECT) {
throw new IllegalStateException("Unsupported socks command: " + cmd);
}
- int port = getUShort(buffer);
- String host = Integer.toString(getUByte(buffer)) + "."
- + Integer.toString(getUByte(buffer)) + "."
- + Integer.toString(getUByte(buffer)) + "."
- + Integer.toString(getUByte(buffer));
+ int port = buffer.getUShort();
- String host = Integer.toString(buffer.getUByte()) + "." + Integer.toString(buffer.getUByte()) + "."
- + Integer.toString(buffer.getUByte()) + "." + Integer.toString(buffer.getUByte());
++ String host = Integer.toString(buffer.getUShort()) + "."
++ + Integer.toString(buffer.getUShort()) + "."
++ + Integer.toString(buffer.getUShort()) + "."
++ + Integer.toString(buffer.getUShort());
String userId = getNTString(buffer);
// Socks4a
if (host.startsWith("0.0.0.")) {
@@@ -175,36 -157,39 +185,30 @@@
channel.setStreaming(Streaming.Async);
session.suspendRead();
service.registerChannel(channel);
- channel.open().addListener(this::onChannelOpened);
+ // Open the channel, but don't accept input data yet!
+ LocalWindow window = channel.getLocalWindow();
- long windowSize = window.getSize();
- window.consume(windowSize);
++ long windowSize = window.consumeAll();
+ channel.open().addListener(f -> onChannelOpened(f, window, windowSize));
} else {
super.onMessage(buffer);
}
}
@SuppressWarnings("synthetic-access")
- protected void onChannelOpened(OpenFuture future) {
- session.resumeRead();
+ protected void onChannelOpened(OpenFuture future, LocalWindow window, long windowSize) {
- session.resumeRead();
Buffer buffer = new ByteArrayBuffer(Long.SIZE, false);
buffer.putByte((byte) 0x00);
Throwable t = future.getException();
if (t != null) {
service.unregisterChannel(channel);
channel.close(true);
- buffer.putByte((byte) 0x5b);
+ buffer.putByte(SocksConstants.Socks4.REPLY_FAILURE);
} else {
- buffer.putByte((byte) 0x5a);
- }
- buffer.putByte((byte) 0x00);
- buffer.putByte((byte) 0x00);
- buffer.putByte((byte) 0x00);
- buffer.putByte((byte) 0x00);
- buffer.putByte((byte) 0x00);
- buffer.putByte((byte) 0x00);
- try {
- session.writeBuffer(buffer);
- } catch (IOException e) {
- // TODO Auto-generated catch block
- log.error("Failed ({}) to send channel open packet for {}: {}", e.getClass().getSimpleName(), channel,
- e.getMessage());
- throw new IllegalStateException("Failed to send packet", e);
+ buffer.putByte(SocksConstants.Socks4.REPLY_SUCCESS);
}
- buffer.putByte((byte) 0x00);
- buffer.putByte((byte) 0x00);
- buffer.putByte((byte) 0x00);
- buffer.putByte((byte) 0x00);
- buffer.putByte((byte) 0x00);
- buffer.putByte((byte) 0x00);
- sendReply(buffer, t == null, window, windowSize);
++ buffer.putInt(0);
++ buffer.putShort(0);
++ sendReply(buffer, t == null, () -> session.close(false), window, windowSize);
}
protected String getNTString(Buffer buffer) {
@@@ -220,8 -213,8 +232,9 @@@
* @see <A HREF="https://en.wikipedia.org/wiki/SOCKS#SOCKS5">SOCKS5</A>
*/
public class Socks5 extends Proxy {
+
- private byte[] authMethods;
+ private Socks5State state = Socks5State.GREETING;
+ private Buffer pending = new ByteArrayBuffer();
public Socks5(IoSession session) {
super(session);
@@@ -229,56 -222,115 +242,112 @@@
@SuppressWarnings("synthetic-access")
@Override
- protected void onMessage(Buffer buffer) throws IOException {
- boolean debugEnabled = log.isDebugEnabled();
- if (authMethods == null) {
- int nbAuthMethods = buffer.getUByte();
- authMethods = new byte[nbAuthMethods];
- buffer.getRawBytes(authMethods);
+ protected synchronized void onMessage(Buffer buffer) throws IOException {
+ if (state == Socks5State.FORWARDING && pending.available() == 0) {
+ super.onMessage(buffer);
+ return;
+ }
+ pending.putBuffer(buffer);
+ processPending();
+ }
+
+ protected void processPending() throws IOException {
+ if (state == Socks5State.OPENING_CHANNEL || state == Socks5State.CLOSED) {
+ return;
+ }
+
+ if (state == Socks5State.FORWARDING) {
- forwardPending();
++ forwardPending(true);
+ return;
+ }
+
+ if (state == Socks5State.GREETING) {
+ if (pending.available() < 1) {
+ return;
+ }
+ int authMethodsCount = pending.rawByte(pending.rpos()) & 0xFF;
+ if (pending.available() < authMethodsCount + 1) {
+ return;
+ }
+
+ pending.getUByte();
boolean foundNoAuth = false;
- for (int i = 0; i < nbAuthMethods; i++) {
- foundNoAuth |= authMethods[i] == 0;
- for (int index = 0; index < authMethodsCount; index++) {
++ for (int i = 0; i < authMethodsCount; i++) {
+ foundNoAuth |= pending.getUByte() == 0;
}
- buffer = new ByteArrayBuffer(Byte.SIZE, false);
- buffer.putByte(SocksConstants.Socks5.VERSION);
- buffer.putByte((byte) (foundNoAuth ? 0x00 : 0xFF));
- session.writeBuffer(buffer);
+ pending.compact();
+ Buffer response = new ByteArrayBuffer(2, false);
- response.putByte((byte) 0x05);
++ response.putByte(SocksConstants.Socks5.VERSION);
+ response.putByte((byte) (foundNoAuth ? 0x00 : 0xFF));
+ session.writeBuffer(response);
if (!foundNoAuth) {
throw new IllegalStateException("Received socks5 greeting without NoAuth method");
- } else if (debugEnabled) {
+ } else if (log.isDebugEnabled()) {
log.debug("Received socks5 greeting");
}
- } else if (channel == null) {
- int version = buffer.getUByte();
+ state = Socks5State.CONNECT_REQUEST;
+ }
+
+ if (state == Socks5State.CONNECT_REQUEST) {
+ if (pending.available() < 4) {
+ return;
+ }
+ int offset = pending.rpos();
+ int version = pending.rawByte(offset) & 0xFF;
- if (version != 0x05) {
+ if (version != SocksConstants.Socks5.VERSION) {
throw new IllegalStateException("Unexpected version: " + version);
}
- int cmd = buffer.getUByte();
+ int cmd = pending.rawByte(offset + 1) & 0xFF;
- if (cmd != 1) { // establish a TCP/IP stream connection
+ if (cmd != SocksConstants.Socks5.CMD_CONNECT) { // establish a TCP/IP stream connection
throw new IllegalStateException("Unsupported socks command: " + cmd);
}
- int res = buffer.getUByte();
- if (res != 0 && debugEnabled) {
+ int res = pending.rawByte(offset + 2) & 0xFF;
- if (res != 0) {
- if (log.isDebugEnabled()) {
- log.debug("No zero reserved value: {}", res);
- }
++ if (res != 0 && log.isDebugEnabled()) {
+ log.debug("No zero reserved value: {}", res);
}
-
- int type = buffer.getUByte();
- String host;
+ int type = pending.rawByte(offset + 3) & 0xFF;
+ int addressLength;
- if (type == 0x01) {
+ if (type == SocksConstants.Socks5.ADDRESS_IPV4) {
- host = Integer.toString(buffer.getUByte()) + "." + Integer.toString(buffer.getUByte()) + "."
- + Integer.toString(buffer.getUByte()) + "." + Integer.toString(buffer.getUByte());
+ addressLength = 4;
- } else if (type == 0x03) {
+ } else if (type == SocksConstants.Socks5.ADDRESS_FQDN) {
- host = getBLString(buffer);
+ if (pending.available() < 5) {
+ return;
+ }
+ addressLength = pending.rawByte(offset + 4) & 0xFF;
- } else if (type == 0x04) {
+ } else if (type == SocksConstants.Socks5.ADDRESS_IPV6) {
- host = Integer.toHexString(buffer.getUShort()) + ":" + Integer.toHexString(buffer.getUShort()) + ":"
- + Integer.toHexString(buffer.getUShort()) + ":" + Integer.toHexString(buffer.getUShort()) + ":"
- + Integer.toHexString(buffer.getUShort()) + ":" + Integer.toHexString(buffer.getUShort()) + ":"
- + Integer.toHexString(buffer.getUShort()) + ":" + Integer.toHexString(buffer.getUShort());
+ addressLength = 16;
} else {
throw new IllegalStateException("Unsupported address type: " + type);
}
- int port = buffer.getUShort();
- if (debugEnabled) {
- int requestLength = 4 + (type == 0x03 ? 1 : 0) + addressLength + 2;
++ int requestLength = 4 + (type == SocksConstants.Socks5.ADDRESS_FQDN ? 1 : 0) + addressLength + 2;
+ if (pending.available() < requestLength) {
+ return;
+ }
-
- getUByte(pending); // version already checked above
- getUByte(pending); // command already checked above
- getUByte(pending); // reserved byte
- getUByte(pending); // address type
- String host;
- if (type == 0x01) {
- host = Integer.toString(getUByte(pending)) + "."
- + Integer.toString(getUByte(pending)) + "."
- + Integer.toString(getUByte(pending)) + "."
- + Integer.toString(getUByte(pending));
- } else if (type == 0x03) {
++ pending.getUByte(); // version already checked above
++ pending.getUByte(); // command already checked above
++ pending.getUByte(); // reserved byte
++ pending.getUByte(); // address type
++ String host = null;
++ if (type == SocksConstants.Socks5.ADDRESS_IPV4) {
++ host = Integer.toString(pending.getUByte()) + "."
++ + Integer.toString(pending.getUByte()) + "."
++ + Integer.toString(pending.getUByte()) + "."
++ + Integer.toString(pending.getUByte());
++ } else if (type == SocksConstants.Socks5.ADDRESS_FQDN) {
+ host = getBLString(pending);
- } else {
++ } else if (type == SocksConstants.Socks5.ADDRESS_IPV6) {
+ StringBuilder address = new StringBuilder();
- for (int index = 0; index < 8; index++) {
- if (index > 0) {
++ for (int i = 0; i < 8; i++) {
++ if (i > 0) {
+ address.append(':');
+ }
- address.append(Integer.toHexString(getUShort(pending)));
++ address.append(Integer.toHexString(pending.getUShort()));
+ }
+ host = address.toString();
+ }
- int port = getUShort(pending);
++ int port = pending.getUShort();
+ pending.compact();
+ if (log.isDebugEnabled()) {
log.debug("Received socks5 connection request to {}:{}", host, port);
}
SshdSocketAddress remote = new SshdSocketAddress(host, port);
@@@ -286,48 -338,74 +355,73 @@@
channel.setStreaming(Streaming.Async);
session.suspendRead();
service.registerChannel(channel);
+ state = Socks5State.OPENING_CHANNEL;
- channel.open().addListener(this::onChannelOpened);
+ // Open the channel, but don't accept input data yet!
+ LocalWindow window = channel.getLocalWindow();
+ long windowSize = window.consumeAll();
+ channel.open().addListener(f -> onChannelOpened(f, window, windowSize));
- } else {
- if (debugEnabled) {
- log.debug("Received socks5 connection message");
- }
- super.onMessage(buffer);
}
}
@SuppressWarnings("synthetic-access")
- protected void onChannelOpened(OpenFuture future, LocalWindow window, long windowSize) {
- Buffer response = new ByteArrayBuffer(2);
- protected synchronized void onChannelOpened(OpenFuture future) {
++ protected synchronized void onChannelOpened(OpenFuture future, LocalWindow window, long windowSize) {
++ Buffer response = new ByteArrayBuffer(10, false);
+ response.putByte(SocksConstants.Socks5.VERSION);
Throwable t = future.getException();
++ Runnable completion;
if (t != null) {
service.unregisterChannel(channel);
channel.close(true);
+ pending.clear(true);
+ state = Socks5State.CLOSED;
- sendReply((byte) 0x01, () -> session.close(false));
- return;
- }
-
- state = Socks5State.FORWARDING;
- sendReply((byte) 0x00, this::forwardPending);
- }
-
- protected void sendReply(byte status, Runnable completion) {
- Buffer response = new ByteArrayBuffer(10, false);
- // VER, REP, RSV, ATYP=IPv4, BND.ADDR=0.0.0.0, BND.PORT=0
- response.putRawBytes(new byte[] { 0x05, status, 0x00, 0x01, 0, 0, 0, 0, 0, 0 });
- try {
- session.writeBuffer(response).addListener(future -> {
- if (future.isWritten()) {
- completion.run();
- } else {
- session.close(true);
- }
- });
- } catch (IOException e) {
- log.error("Failed ({}) to send channel open response for {}: {}", e.getClass().getSimpleName(), channel,
- e.getMessage());
- session.close(true);
+ response.putByte(SocksConstants.Socks5.REPLY_FAILURE);
++ completion = () -> session.close(false);
+ } else {
++ state = Socks5State.FORWARDING;
+ response.putByte(SocksConstants.Socks5.REPLY_SUCCESS);
++ completion = () -> forwardPending(false);
}
+ response.putByte((byte) 0x00); // reserved
- SocketAddress bound = session.getAcceptanceAddress();
- if (bound instanceof InetSocketAddress) {
- byte[] ip = ((InetSocketAddress) bound).getAddress().getAddress();
- if (ip.length == 4) {
- response.putByte(SocksConstants.Socks5.ADDRESS_IPV4);
- } else {
- response.putByte(SocksConstants.Socks5.ADDRESS_IPV6);
- }
- response.putRawBytes(ip);
- response.putShort(((InetSocketAddress) bound).getPort());
- } else {
- // Just fill in some dummy values.
- response.putByte(SocksConstants.Socks5.ADDRESS_IPV4);
- response.putLong(0);
- response.putShort(0);
++ response.putByte(SocksConstants.Socks5.ADDRESS_IPV4);
++ response.putInt(0);
++ response.putShort(0);
++ sendReply(response, t == null, completion, window, windowSize);
+ }
+
- protected synchronized void forwardPending() {
++ protected synchronized void forwardPending(boolean suspend) {
+ if (state != Socks5State.FORWARDING) {
+ return;
+ }
+ Buffer payload = pending;
+ pending = new ByteArrayBuffer();
- if (payload.available() == 0) {
- session.resumeRead();
++ int payloadSize = payload.available();
++ if (payloadSize == 0) {
+ return;
+ }
- int payloadSize = payload.available();
- session.suspendRead();
++ if (suspend) {
++ session.suspendRead();
++ }
+ try {
- ThreadUtils.runAsInternal(channel.getAsyncIn(), out -> out.writeBuffer(payload).addListener(future -> {
- if (future.getException() == null) {
++ channel.getAsyncIn().writeBuffer(payload).addListener(future -> {
++ Throwable t = future.getException();
++ if (t == null) {
+ if (log.isDebugEnabled()) {
+ log.debug("Forwarded {} queued socks5 bytes", payloadSize);
+ }
- session.resumeRead();
++ if (suspend) {
++ session.resumeRead();
++ }
+ } else {
++ log.error("Failed ({}) to forward {} queued bytes for {}: {}", t.getClass().getSimpleName(),
++ payloadSize, channel, t.getMessage());
+ session.close(true);
+ }
- }));
++ });
+ } catch (IOException e) {
+ log.error("Failed ({}) to forward {} queued bytes for {}: {}", e.getClass().getSimpleName(), payloadSize,
+ channel, e.getMessage());
+ session.close(true);
}
- sendReply(response, t == null, window, windowSize);
}
protected String getBLString(Buffer buffer) {
diff --cc sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/PublickeyAuthenticator.java
index 703c5887d,2ef3dfc37..dcdfa0ec3
--- a/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/PublickeyAuthenticator.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/PublickeyAuthenticator.java
@@@ -45,13 -44,12 +44,12 @@@ public interface PublickeyAuthenticato
* certificate authority, and that the given user name is listed in the certificate's principals.
* </p>
*
-- * @param username the username
-- * @param key the key
-- * @param session the server session
-- * @return {@code true} if the key may be used; {@code false} otherwise
- * @throws AsyncAuthException If the authentication is performed asynchronously
++ * @param username the username
++ * @param key the key
++ * @param session the server session
++ * @return {@code true} if the key may be used; {@code false} otherwise
*/
- boolean authenticate(String username, PublicKey key, ServerSession session) throws AsyncAuthException;
+ boolean authenticate(String username, PublicKey key, ServerSession session);
/**
* @param id Some kind of mnemonic identifier for the authenticator - used also in
diff --cc sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/UserAuthPublicKey.java
index aa3aacccc,18cd5990b..cbc0e99e3
--- a/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/UserAuthPublicKey.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/pubkey/UserAuthPublicKey.java
@@@ -196,10 -214,32 +214,17 @@@ public class UserAuthPublicKey extends
return Boolean.TRUE;
}
+ protected boolean alreadyAuthenticated(PublicKey key, List<PublicKey> previouslyAuthenticatedKeys) {
+ if (GenericUtils.isEmpty(previouslyAuthenticatedKeys)) {
+ return false;
+ }
+ return previouslyAuthenticatedKeys.stream().anyMatch(k -> KeyUtils.compareKeys(key, k));
+ }
+
protected void verifyCertificateSignature(ServerSession session, OpenSshCertificate cert) throws Exception {
- PublicKey signatureKey = cert.getCaPubKey();
- String keyAlg = KeyUtils.getKeyType(signatureKey);
- String keyId = cert.getId();
-
- String sigAlg = cert.getSignatureAlgorithm();
- if (!keyAlg.equals(KeyUtils.getCanonicalKeyType(sigAlg))) {
+ if (!OpenSshCertificate.verifySignature(cert, SignatureFactoriesManager.resolveSignatureFactories(this, session))) {
throw new CertificateException(
- "Found invalid signature alg " + sigAlg + " for key ID=" + keyId + " using a " + keyAlg + " CA key");
- }
-
- Signature verif = ValidateUtils.checkNotNull(
- NamedFactory.create(SignatureFactoriesManager.resolveSignatureFactories(this, session), sigAlg),
- "No CA verifier located for algorithm=%s of key ID=%s", sigAlg, keyId);
- verif.initVerifier(session, signatureKey);
- verif.update(session, cert.getMessage());
-
- if (!verif.verify(session, cert.getSignature())) {
- throw new CertificateException("CA signature verification failed for key type=" + keyAlg + " of key ID=" + keyId);
+ "CA signature verification failed for key type=" + cert.getKeyType() + " of key ID=" + cert.getId());
}
}