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 <[email protected]> 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:[email protected]">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()); } }
