Compare commits
10 Commits
1b2ae45b78
...
26986be4ba
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26986be4ba | ||
|
|
0b83536590 | ||
|
|
97c8ac8cb4 | ||
|
|
e7c7e18533 | ||
|
|
93906a0d43 | ||
|
|
11555fa551 | ||
|
|
4ceee46a15 | ||
|
|
8373c8893a | ||
|
|
64db206fbf | ||
|
|
a62138aa30 |
105
0001-Avoid-optional-dependency-on-native-tomcat-APR-libra.patch
Normal file
105
0001-Avoid-optional-dependency-on-native-tomcat-APR-libra.patch
Normal file
@ -0,0 +1,105 @@
|
||||
From accd3e006a05615cf6eed9369d91fbedcc4eab16 Mon Sep 17 00:00:00 2001
|
||||
From: Mat Booth <mat.booth@redhat.com>
|
||||
Date: Thu, 7 Mar 2019 11:27:55 +0000
|
||||
Subject: [PATCH] Avoid optional dependency on native tomcat APR library
|
||||
|
||||
---
|
||||
pom.xml | 5 -----
|
||||
sshd-core/pom.xml | 6 ------
|
||||
.../sshd/agent/local/ProxyAgentFactory.java | 16 +---------------
|
||||
sshd-osgi/pom.xml | 6 ------
|
||||
4 files changed, 1 insertion(+), 32 deletions(-)
|
||||
|
||||
diff --git a/pom.xml b/pom.xml
|
||||
index 867ca88..7c29678 100644
|
||||
--- a/pom.xml
|
||||
+++ b/pom.xml
|
||||
@@ -428,11 +428,6 @@
|
||||
<artifactId>mina-core</artifactId>
|
||||
<version>2.0.23</version>
|
||||
</dependency>
|
||||
- <dependency>
|
||||
- <groupId>tomcat</groupId>
|
||||
- <artifactId>tomcat-apr</artifactId>
|
||||
- <version>5.5.23</version>
|
||||
- </dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>net.i2p.crypto</groupId>
|
||||
diff --git a/sshd-core/pom.xml b/sshd-core/pom.xml
|
||||
index 6171c5c..73a43a7 100644
|
||||
--- a/sshd-core/pom.xml
|
||||
+++ b/sshd-core/pom.xml
|
||||
@@ -43,12 +43,6 @@
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
- <groupId>tomcat</groupId>
|
||||
- <artifactId>tomcat-apr</artifactId>
|
||||
- <optional>true</optional>
|
||||
- </dependency>
|
||||
-
|
||||
- <dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcpg-jdk15on</artifactId>
|
||||
<optional>true</optional>
|
||||
diff --git a/sshd-core/src/main/java/org/apache/sshd/agent/local/ProxyAgentFactory.java b/sshd-core/src/main/java/org/apache/sshd/agent/local/ProxyAgentFactory.java
|
||||
index ab19539..5757e68 100644
|
||||
--- a/sshd-core/src/main/java/org/apache/sshd/agent/local/ProxyAgentFactory.java
|
||||
+++ b/sshd-core/src/main/java/org/apache/sshd/agent/local/ProxyAgentFactory.java
|
||||
@@ -27,8 +27,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import org.apache.sshd.agent.SshAgent;
|
||||
import org.apache.sshd.agent.SshAgentFactory;
|
||||
import org.apache.sshd.agent.SshAgentServer;
|
||||
-import org.apache.sshd.agent.unix.AprLibrary;
|
||||
-import org.apache.sshd.agent.unix.UnixAgentFactory;
|
||||
import org.apache.sshd.common.FactoryManager;
|
||||
import org.apache.sshd.common.PropertyResolver;
|
||||
import org.apache.sshd.common.channel.ChannelFactory;
|
||||
@@ -51,9 +49,7 @@ public class ProxyAgentFactory implements SshAgentFactory {
|
||||
|
||||
@Override
|
||||
public List<ChannelFactory> getChannelForwardingFactories(FactoryManager manager) {
|
||||
- return isPreferredUnixAgent(manager)
|
||||
- ? UnixAgentFactory.DEFAULT_FORWARDING_CHANNELS
|
||||
- : LocalAgentFactory.DEFAULT_FORWARDING_CHANNELS;
|
||||
+ return LocalAgentFactory.DEFAULT_FORWARDING_CHANNELS;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -104,16 +100,6 @@ public class ProxyAgentFactory implements SshAgentFactory {
|
||||
}
|
||||
|
||||
public static boolean isPreferredUnixAgent(PropertyResolver resolver) {
|
||||
- if (CoreModuleProperties.PREFER_UNIX_AGENT.getRequired(resolver)) {
|
||||
- try {
|
||||
- if (AprLibrary.getInstance() != null) {
|
||||
- return true;
|
||||
- }
|
||||
- } catch (Exception ignore) {
|
||||
- // ignored
|
||||
- }
|
||||
- }
|
||||
-
|
||||
return false;
|
||||
}
|
||||
}
|
||||
diff --git a/sshd-osgi/pom.xml b/sshd-osgi/pom.xml
|
||||
index 5395ceb..f456263 100644
|
||||
--- a/sshd-osgi/pom.xml
|
||||
+++ b/sshd-osgi/pom.xml
|
||||
@@ -68,12 +68,6 @@
|
||||
<optional>true</optional>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
- <dependency>
|
||||
- <groupId>tomcat</groupId>
|
||||
- <artifactId>tomcat-apr</artifactId>
|
||||
- <optional>true</optional>
|
||||
- <scope>provided</scope>
|
||||
- </dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
--
|
||||
2.20.1
|
||||
2338
CVE-2023-35887.patch
Normal file
2338
CVE-2023-35887.patch
Normal file
File diff suppressed because it is too large
Load Diff
976
CVE-2023-48795.patch
Normal file
976
CVE-2023-48795.patch
Normal file
@ -0,0 +1,976 @@
|
||||
From 6b0fd46f64bcb75eeeee31d65f10242660aad7c1 Mon Sep 17 00:00:00 2001
|
||||
From: Thomas Wolf <twolf@apache.org>
|
||||
Date: Fri, 29 Dec 2023 17:39:14 +0100
|
||||
Subject: [PATCH 1/3] GH-445: OpenSSH "strict KEX" protocol extension
|
||||
|
||||
Origin: https://github.com/apache/mina-sshd/pull/449
|
||||
|
||||
Implements the OpenSSH "strict KEX" protocol extension.[1] If both
|
||||
parties in a an SSH connection announce support for strict KEX in the
|
||||
initial KEX_INIT message, strict KEX is active; otherwise it isn't.
|
||||
|
||||
With strict KEX active, there must be only KEX-related messages during
|
||||
the initial key exchange (no IGNORE or DEBUG messages are allowed), and
|
||||
the KEX_INIT message must be the first one to have been received after
|
||||
the initial version exchange. If these conditions are violated, the
|
||||
connection is terminated.
|
||||
|
||||
Strict KEX also resets message sequence numbers to zero after each
|
||||
NEW_KEYS message sent or received.
|
||||
|
||||
[1] https://github.com/openssh/openssh-portable/blob/master/PROTOCOL
|
||||
---
|
||||
CHANGES.md | 11 ++
|
||||
docs/standards.md | 35 ++--
|
||||
docs/technical/kex.md | 15 ++
|
||||
.../common/kex/extension/KexExtensions.java | 20 ++-
|
||||
.../session/helpers/AbstractSession.java | 161 ++++++++++++++++--
|
||||
.../session/helpers/AbstractSessionTest.java | 1 +
|
||||
6 files changed, 213 insertions(+), 30 deletions(-)
|
||||
|
||||
diff --git a/docs/technical/kex.md b/docs/technical/kex.md
|
||||
index e5d353a92..a3f5facc1 100644
|
||||
--- a/docs/technical/kex.md
|
||||
+++ b/docs/technical/kex.md
|
||||
@@ -129,3 +129,18 @@ thread is not overrun by producers and actually can finish.
|
||||
Again, "client" and "server" could also be inverted. For instance, a client uploading
|
||||
files via SFTP might have an application thread pumping data through a channel, which
|
||||
might be blocked during KEX.
|
||||
+
|
||||
+### Strict Key Exchange
|
||||
+
|
||||
+"Strict KEX" is an SSH protocol extension introduced in 2023 to harden the protocol against
|
||||
+a particular form of attack. For details, see ["Terrapin attack"](https://www.terrapin-attack.com/)
|
||||
+and [CVE-2023-48795](https://nvd.nist.gov/vuln/detail/CVE-2023-48795). The "strict KEX"
|
||||
+counter-measures are active if both peers indicate support for it at the start of the initial
|
||||
+key exchange. By default, Apache MINA sshd always supports "strict kex" and advertises it, and
|
||||
+thus it will always be active if the other party also supports it.
|
||||
+
|
||||
+If for whatever reason you want to disable using "strict KEX", this can be achieved by setting
|
||||
+a custom session factory on the `SshClient` or `SshServer`. This custom session factory would create
|
||||
+custom sessions subclassed from `ClientSessionImpl`or `ServerSessionImpl` that do not do anything
|
||||
+in method `doStrictKexProposal()` (just return the proposal unchanged).
|
||||
+
|
||||
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/kex/extension/KexExtensions.java b/sshd-common/src/main/java/org/apache/sshd/common/kex/extension/KexExtensions.java
|
||||
index 9fac45c13..f275227e1 100644
|
||||
--- a/sshd-common/src/main/java/org/apache/sshd/common/kex/extension/KexExtensions.java
|
||||
+++ b/sshd-common/src/main/java/org/apache/sshd/common/kex/extension/KexExtensions.java
|
||||
@@ -59,9 +59,23 @@ public final class KexExtensions {
|
||||
public static final String CLIENT_KEX_EXTENSION = "ext-info-c";
|
||||
public static final String SERVER_KEX_EXTENSION = "ext-info-s";
|
||||
|
||||
- @SuppressWarnings("checkstyle:Indentation")
|
||||
- public static final Predicate<String> IS_KEX_EXTENSION_SIGNAL
|
||||
- = n -> CLIENT_KEX_EXTENSION.equalsIgnoreCase(n) || SERVER_KEX_EXTENSION.equalsIgnoreCase(n);
|
||||
+ public static final Predicate<String> IS_KEX_EXTENSION_SIGNAL = //
|
||||
+ n -> CLIENT_KEX_EXTENSION.equalsIgnoreCase(n) || SERVER_KEX_EXTENSION.equalsIgnoreCase(n);
|
||||
+
|
||||
+ /**
|
||||
+ * Reminder:
|
||||
+ *
|
||||
+ * These pseudo-algorithms are only valid in the initial SSH2_MSG_KEXINIT and MUST be ignored if they are present in
|
||||
+ * subsequent SSH2_MSG_KEXINIT packets.
|
||||
+ *
|
||||
+ * <B>Note:</B> these values are <U>appended</U> to the initial proposals and removed if received before proceeding
|
||||
+ * with the standard KEX proposals negotiation.
|
||||
+ *
|
||||
+ * @see <A HREF="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL">OpenSSH PROTOCOL - 1.9 transport:
|
||||
+ * strict key exchange extension</A>
|
||||
+ */
|
||||
+ public static final String STRICT_KEX_CLIENT_EXTENSION = "kex-strict-c-v00@openssh.com";
|
||||
+ public static final String STRICT_KEX_SERVER_EXTENSION = "kex-strict-s-v00@openssh.com";
|
||||
|
||||
/**
|
||||
* A case <U>insensitive</U> map of all the default known {@link KexExtensionParser} where key=the extension name
|
||||
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java
|
||||
index 4bdb39c4c..b05a3ab92 100644
|
||||
--- a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java
|
||||
+++ b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java
|
||||
@@ -27,13 +27,17 @@
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.AbstractMap.SimpleImmutableEntry;
|
||||
+import java.util.ArrayList;
|
||||
+import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.EnumMap;
|
||||
+import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
+import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
@@ -45,6 +49,7 @@
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.LongConsumer;
|
||||
import java.util.logging.Level;
|
||||
+import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.sshd.common.Closeable;
|
||||
import org.apache.sshd.common.Factory;
|
||||
@@ -109,6 +114,7 @@
|
||||
*
|
||||
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
||||
*/
|
||||
+@SuppressWarnings("checkstyle:MethodCount")
|
||||
public abstract class AbstractSession extends SessionHelper {
|
||||
/**
|
||||
* Name of the property where this session is stored in the attributes of the underlying MINA session. See
|
||||
@@ -192,6 +198,22 @@ public abstract class AbstractSession extends SessionHelper {
|
||||
protected final Object decodeLock = new Object();
|
||||
protected final Object requestLock = new Object();
|
||||
|
||||
+ /**
|
||||
+ * "Strict KEX" is a mitigation for the "Terrapin attack". The KEX protocol is modified as follows:
|
||||
+ * <ol>
|
||||
+ * <li>During the initial (unencrypted) KEX, no extra messages not strictly necessary for KEX are allowed. The
|
||||
+ * KEX_INIT message must be the first one after the version identification, and no IGNORE or DEBUG messages are
|
||||
+ * allowed until the KEX is completed. If a party receives such a message, it terminates the connection.</li>
|
||||
+ * <li>Message sequence numbers are reset to zero after a key exchange (initial or later). When the NEW_KEYS message
|
||||
+ * has been sent, the outgoing message number is reset; after a NEW_KEYS message has been received, the incoming
|
||||
+ * message number is reset.</li>
|
||||
+ * </ol>
|
||||
+ * Strict KEX is negotiated in the original KEX proposal; it is active if and only if both parties indicate that
|
||||
+ * they support strict KEX.
|
||||
+ */
|
||||
+ protected boolean strictKex;
|
||||
+ protected long initialKexInitSequenceNumber = -1;
|
||||
+
|
||||
/**
|
||||
* The {@link KeyExchangeMessageHandler} instance also serves as lock protecting {@link #kexState} changes from DONE
|
||||
* to INIT or RUN, and from KEYS to DONE.
|
||||
@@ -550,18 +572,24 @@ protected void doHandleMessage(Buffer buffer) throws Exception {
|
||||
handleDisconnect(buffer);
|
||||
break;
|
||||
case SshConstants.SSH_MSG_IGNORE:
|
||||
+ failStrictKex(cmd);
|
||||
handleIgnore(buffer);
|
||||
break;
|
||||
case SshConstants.SSH_MSG_UNIMPLEMENTED:
|
||||
+ failStrictKex(cmd);
|
||||
handleUnimplemented(buffer);
|
||||
break;
|
||||
case SshConstants.SSH_MSG_DEBUG:
|
||||
+ // Fail after handling -- by default a message will be logged, which might be helpful.
|
||||
handleDebug(buffer);
|
||||
+ failStrictKex(cmd);
|
||||
break;
|
||||
case SshConstants.SSH_MSG_SERVICE_REQUEST:
|
||||
+ failStrictKex(cmd);
|
||||
handleServiceRequest(buffer);
|
||||
break;
|
||||
case SshConstants.SSH_MSG_SERVICE_ACCEPT:
|
||||
+ failStrictKex(cmd);
|
||||
handleServiceAccept(buffer);
|
||||
break;
|
||||
case SshConstants.SSH_MSG_KEXINIT:
|
||||
@@ -571,9 +599,11 @@ protected void doHandleMessage(Buffer buffer) throws Exception {
|
||||
handleNewKeys(cmd, buffer);
|
||||
break;
|
||||
case KexExtensions.SSH_MSG_EXT_INFO:
|
||||
+ failStrictKex(cmd);
|
||||
handleKexExtension(cmd, buffer);
|
||||
break;
|
||||
case KexExtensions.SSH_MSG_NEWCOMPRESS:
|
||||
+ failStrictKex(cmd);
|
||||
handleNewCompression(cmd, buffer);
|
||||
break;
|
||||
default:
|
||||
@@ -589,26 +619,35 @@ protected void doHandleMessage(Buffer buffer) throws Exception {
|
||||
}
|
||||
|
||||
handleKexMessage(cmd, buffer);
|
||||
- } else if (currentService.process(cmd, buffer)) {
|
||||
- resetIdleTimeout();
|
||||
} else {
|
||||
- /*
|
||||
- * According to https://tools.ietf.org/html/rfc4253#section-11.4
|
||||
- *
|
||||
- * An implementation MUST respond to all unrecognized messages with an SSH_MSG_UNIMPLEMENTED message
|
||||
- * in the order in which the messages were received.
|
||||
- */
|
||||
- if (log.isDebugEnabled()) {
|
||||
- log.debug("process({}) Unsupported command: {}",
|
||||
- this, SshConstants.getCommandMessageName(cmd));
|
||||
+ failStrictKex(cmd);
|
||||
+ if (currentService.process(cmd, buffer)) {
|
||||
+ resetIdleTimeout();
|
||||
+ } else {
|
||||
+ /*
|
||||
+ * According to https://tools.ietf.org/html/rfc4253#section-11.4
|
||||
+ *
|
||||
+ * An implementation MUST respond to all unrecognized messages with an SSH_MSG_UNIMPLEMENTED
|
||||
+ * message in the order in which the messages were received.
|
||||
+ */
|
||||
+ if (log.isDebugEnabled()) {
|
||||
+ log.debug("process({}) Unsupported command: {}", this, SshConstants.getCommandMessageName(cmd));
|
||||
+ }
|
||||
+ notImplemented(cmd, buffer);
|
||||
}
|
||||
- notImplemented(cmd, buffer);
|
||||
}
|
||||
break;
|
||||
}
|
||||
checkRekey();
|
||||
}
|
||||
|
||||
+ protected void failStrictKex(int cmd) throws SshException {
|
||||
+ if (!initialKexDone && strictKex) {
|
||||
+ throw new SshException(SshConstants.SSH2_DISCONNECT_KEY_EXCHANGE_FAILED,
|
||||
+ SshConstants.getCommandMessageName(cmd) + " not allowed during initial key exchange in strict KEX");
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
protected boolean handleFirstKexPacketFollows(int cmd, Buffer buffer, boolean followFlag) {
|
||||
if (!followFlag) {
|
||||
return true; // if 1st KEX packet does not follow then process the command
|
||||
@@ -1118,7 +1157,7 @@ protected IoWriteFuture doWritePacket(Buffer buffer) throws IOException {
|
||||
}
|
||||
|
||||
protected int resolveIgnoreBufferDataLength() {
|
||||
- if ((ignorePacketDataLength <= 0)
|
||||
+ if (!initialKexDone || (ignorePacketDataLength <= 0)
|
||||
|| (ignorePacketsFrequency <= 0L)
|
||||
|| (ignorePacketsVariance < 0)) {
|
||||
return 0;
|
||||
@@ -1931,6 +1970,13 @@ protected void prepareNewKeys() throws Exception {
|
||||
* @throws Exception on errors
|
||||
*/
|
||||
protected void setOutputEncoding() throws Exception {
|
||||
+ if (strictKex) {
|
||||
+ if (log.isDebugEnabled()) {
|
||||
+ log.debug("setOutputEncoding({}): strict KEX resets output message sequence number from {} to 0", this, seqo);
|
||||
+ }
|
||||
+ seqo = 0;
|
||||
+ }
|
||||
+
|
||||
outCipher = outSettings.getCipher(seqo);
|
||||
outMac = outSettings.getMac();
|
||||
outCompression = outSettings.getCompression();
|
||||
@@ -1962,6 +2008,13 @@ protected void setOutputEncoding() throws Exception {
|
||||
* @throws Exception on errors
|
||||
*/
|
||||
protected void setInputEncoding() throws Exception {
|
||||
+ if (strictKex) {
|
||||
+ if (log.isDebugEnabled()) {
|
||||
+ log.debug("setInputEncoding({}): strict KEX resets input message sequence number from {} to 0", this, seqi);
|
||||
+ }
|
||||
+ seqi = 0;
|
||||
+ }
|
||||
+
|
||||
inCipher = inSettings.getCipher(seqi);
|
||||
inMac = inSettings.getMac();
|
||||
inCompression = inSettings.getCompression();
|
||||
@@ -2044,6 +2097,25 @@ protected IoWriteFuture notImplemented(int cmd, Buffer buffer) throws Exception
|
||||
return sendNotImplemented(seqi - 1L);
|
||||
}
|
||||
|
||||
+ /**
|
||||
+ * Given a KEX proposal and a {@link KexProposalOption}, removes all occurrences of a value from a comma-separated
|
||||
+ * value list.
|
||||
+ *
|
||||
+ * @param options {@link Map} holding the Kex proposal
|
||||
+ * @param option {@link KexProposalOption} to modify
|
||||
+ * @param toRemove value to remove
|
||||
+ * @return {@code true} if the option contained the value (and it was removed); {@code false} otherwise
|
||||
+ */
|
||||
+ protected boolean removeValue(Map<KexProposalOption, String> options, KexProposalOption option, String toRemove) {
|
||||
+ String val = options.get(option);
|
||||
+ Set<String> algorithms = new LinkedHashSet<>(Arrays.asList(val.split(",")));
|
||||
+ boolean result = algorithms.remove(toRemove);
|
||||
+ if (result) {
|
||||
+ options.put(option, algorithms.stream().collect(Collectors.joining(",")));
|
||||
+ }
|
||||
+ return result;
|
||||
+ }
|
||||
+
|
||||
/**
|
||||
* Compute the negotiated proposals by merging the client and server proposal. The negotiated proposal will also be
|
||||
* stored in the {@link #negotiationResult} property.
|
||||
@@ -2056,11 +2128,43 @@ protected Map<KexProposalOption, String> negotiate() throws Exception {
|
||||
Map<KexProposalOption, String> s2cOptions = getServerKexProposals();
|
||||
signalNegotiationStart(c2sOptions, s2cOptions);
|
||||
|
||||
+ // Make modifiable. Strict KEX flags are to be heeded only in initial KEX, and to be ignored afterwards.
|
||||
+ c2sOptions = new EnumMap<>(c2sOptions);
|
||||
+ s2cOptions = new EnumMap<>(s2cOptions);
|
||||
+ boolean strictKexClient = removeValue(c2sOptions, KexProposalOption.ALGORITHMS,
|
||||
+ KexExtensions.STRICT_KEX_CLIENT_EXTENSION);
|
||||
+ boolean strictKexServer = removeValue(s2cOptions, KexProposalOption.ALGORITHMS,
|
||||
+ KexExtensions.STRICT_KEX_SERVER_EXTENSION);
|
||||
+ if (removeValue(c2sOptions, KexProposalOption.ALGORITHMS, KexExtensions.STRICT_KEX_SERVER_EXTENSION)
|
||||
+ && !initialKexDone) {
|
||||
+ log.warn("negotiate({}) client proposal contains server flag {}; will be ignored", this,
|
||||
+ KexExtensions.STRICT_KEX_SERVER_EXTENSION);
|
||||
+ }
|
||||
+ if (removeValue(s2cOptions, KexProposalOption.ALGORITHMS, KexExtensions.STRICT_KEX_CLIENT_EXTENSION)
|
||||
+ && !initialKexDone) {
|
||||
+ log.warn("negotiate({}) server proposal contains client flag {}; will be ignored", this,
|
||||
+ KexExtensions.STRICT_KEX_CLIENT_EXTENSION);
|
||||
+ }
|
||||
+ // Make unmodifiable again
|
||||
+ c2sOptions = Collections.unmodifiableMap(c2sOptions);
|
||||
+ s2cOptions = Collections.unmodifiableMap(s2cOptions);
|
||||
Map<KexProposalOption, String> guess = new EnumMap<>(KexProposalOption.class);
|
||||
Map<KexProposalOption, String> negotiatedGuess = Collections.unmodifiableMap(guess);
|
||||
try {
|
||||
boolean debugEnabled = log.isDebugEnabled();
|
||||
boolean traceEnabled = log.isTraceEnabled();
|
||||
+ if (!initialKexDone) {
|
||||
+ strictKex = strictKexClient && strictKexServer;
|
||||
+ if (debugEnabled) {
|
||||
+ log.debug("negotiate({}) strict KEX={} client={} server={}", this, strictKex, strictKexClient,
|
||||
+ strictKexServer);
|
||||
+ }
|
||||
+ if (strictKex && initialKexInitSequenceNumber != 1) {
|
||||
+ throw new SshException(SshConstants.SSH2_DISCONNECT_KEY_EXCHANGE_FAILED,
|
||||
+ "Strict KEX negotiated but sequence number of first KEX_INIT received is not 1: "
|
||||
+ + initialKexInitSequenceNumber);
|
||||
+ }
|
||||
+ }
|
||||
SessionDisconnectHandler discHandler = getSessionDisconnectHandler();
|
||||
KexExtensionHandler extHandler = getKexExtensionHandler();
|
||||
for (KexProposalOption paramType : KexProposalOption.VALUES) {
|
||||
@@ -2520,8 +2624,34 @@ protected String resolveSessionKexProposal(String hostKeyTypes) throws IOExcepti
|
||||
}
|
||||
}
|
||||
|
||||
+ protected Map<KexProposalOption, String> doStrictKexProposal(Map<KexProposalOption, String> proposal) {
|
||||
+ String value = proposal.get(KexProposalOption.ALGORITHMS);
|
||||
+ String askForStrictKex = isServerSession()
|
||||
+ ? KexExtensions.STRICT_KEX_SERVER_EXTENSION
|
||||
+ : KexExtensions.STRICT_KEX_CLIENT_EXTENSION;
|
||||
+ if (!initialKexDone) {
|
||||
+ // On the initial KEX, include the strict KEX flag
|
||||
+ if (GenericUtils.isEmpty(value)) {
|
||||
+ value = askForStrictKex;
|
||||
+ } else {
|
||||
+ value += "," + askForStrictKex;
|
||||
+ }
|
||||
+ } else if (!GenericUtils.isEmpty(value)) {
|
||||
+ // On subsequent KEXes, do not include ext-info-c/ext-info-s or the strict KEX flag in the proposal.
|
||||
+ List<String> algorithms = new ArrayList<>(Arrays.asList(value.split(",")));
|
||||
+ String extType = isServerSession() ? KexExtensions.SERVER_KEX_EXTENSION : KexExtensions.CLIENT_KEX_EXTENSION;
|
||||
+ boolean changed = algorithms.remove(extType);
|
||||
+ changed |= algorithms.remove(askForStrictKex);
|
||||
+ if (changed) {
|
||||
+ value = algorithms.stream().collect(Collectors.joining(","));
|
||||
+ }
|
||||
+ }
|
||||
+ proposal.put(KexProposalOption.ALGORITHMS, value);
|
||||
+ return proposal;
|
||||
+ }
|
||||
+
|
||||
protected byte[] sendKexInit() throws Exception {
|
||||
- Map<KexProposalOption, String> proposal = getKexProposal();
|
||||
+ Map<KexProposalOption, String> proposal = doStrictKexProposal(getKexProposal());
|
||||
|
||||
byte[] seed;
|
||||
synchronized (kexState) {
|
||||
@@ -2588,6 +2718,9 @@ protected void setServerKexData(byte[] data) {
|
||||
protected byte[] receiveKexInit(Buffer buffer) throws Exception {
|
||||
Map<KexProposalOption, String> proposal = new EnumMap<>(KexProposalOption.class);
|
||||
|
||||
+ if (!initialKexDone) {
|
||||
+ initialKexInitSequenceNumber = seqi;
|
||||
+ }
|
||||
byte[] seed;
|
||||
synchronized (kexState) {
|
||||
seed = receiveKexInit(buffer, proposal);
|
||||
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/session/helpers/AbstractSessionTest.java b/sshd-core/src/test/java/org/apache/sshd/common/session/helpers/AbstractSessionTest.java
|
||||
index 6fe6e8104..8d74d51b7 100644
|
||||
--- a/sshd-core/src/test/java/org/apache/sshd/common/session/helpers/AbstractSessionTest.java
|
||||
+++ b/sshd-core/src/test/java/org/apache/sshd/common/session/helpers/AbstractSessionTest.java
|
||||
@@ -437,6 +437,7 @@ public static class MySession extends AbstractSession {
|
||||
public MySession() {
|
||||
super(true, org.apache.sshd.util.test.CoreTestSupportUtils.setupTestServer(AbstractSessionTest.class),
|
||||
new MyIoSession());
|
||||
+ initialKexDone = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
From 315739e4e9d1dc7a4ff32ea64936982ed0b73e76 Mon Sep 17 00:00:00 2001
|
||||
From: Thomas Wolf <twolf@apache.org>
|
||||
Date: Mon, 1 Jan 2024 15:00:40 +0100
|
||||
Subject: [PATCH 2/3] GH-445: Unit tests for strict KEX
|
||||
|
||||
Add tests for the restricted message handling if strict KEX is active:
|
||||
|
||||
* Initial KEX fails if KEX_INIT is not the first message
|
||||
* Initial KEX fails if there are spurious messages like DEBUG during KEX
|
||||
* Re-KEX succeeds even if there are spurious messages
|
||||
---
|
||||
.../common/kex/extension/StrictKexTest.java | 264 ++++++++++++++++++
|
||||
1 file changed, 264 insertions(+)
|
||||
create mode 100644 sshd-core/src/test/java/org/apache/sshd/common/kex/extension/StrictKexTest.java
|
||||
|
||||
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/kex/extension/StrictKexTest.java b/sshd-core/src/test/java/org/apache/sshd/common/kex/extension/StrictKexTest.java
|
||||
new file mode 100644
|
||||
index 000000000..6d6c2ce8c
|
||||
--- /dev/null
|
||||
+++ b/sshd-core/src/test/java/org/apache/sshd/common/kex/extension/StrictKexTest.java
|
||||
@@ -0,0 +1,264 @@
|
||||
+/*
|
||||
+ * 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.kex.extension;
|
||||
+
|
||||
+import java.io.IOException;
|
||||
+import java.util.Map;
|
||||
+import java.util.concurrent.atomic.AtomicBoolean;
|
||||
+import java.util.concurrent.atomic.AtomicReference;
|
||||
+
|
||||
+import org.apache.sshd.client.SshClient;
|
||||
+import org.apache.sshd.client.session.ClientSession;
|
||||
+import org.apache.sshd.common.SshConstants;
|
||||
+import org.apache.sshd.common.SshException;
|
||||
+import org.apache.sshd.common.io.IoWriteFuture;
|
||||
+import org.apache.sshd.common.kex.KexProposalOption;
|
||||
+import org.apache.sshd.common.session.Session;
|
||||
+import org.apache.sshd.common.session.SessionListener;
|
||||
+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;
|
||||
+import org.junit.FixMethodOrder;
|
||||
+import org.junit.Test;
|
||||
+import org.junit.runners.MethodSorters;
|
||||
+
|
||||
+/**
|
||||
+ * Tests for message handling during "strict KEX" is active: initial KEX must fail and disconnect if the KEX_INIT
|
||||
+ * message is not first, or if there are spurious extra messages like IGNORE or DEBUG during KEX. Later KEXes must
|
||||
+ * succeed even if there are spurious messages.
|
||||
+ * <p>
|
||||
+ * The other part of "strict KEX" is resetting the message sequence numbers after KEX. This is not tested here but in
|
||||
+ * the {@link StrictKexInteroperabilityTest}, which runs an Apache MINA sshd client against OpenSSH servers that have or
|
||||
+ * do not have the "strict KEX" extension. If the sequence number handling was wrong, those tests would fail.
|
||||
+ * </p>
|
||||
+ *
|
||||
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
||||
+ * @see <A HREF="https://github.com/apache/mina-sshd/issues/445">Terrapin Mitigation: "strict-kex"</A>
|
||||
+ */
|
||||
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
+public class StrictKexTest extends BaseTestSupport {
|
||||
+ private SshServer sshd;
|
||||
+ private SshClient client;
|
||||
+
|
||||
+ public StrictKexTest() {
|
||||
+ super();
|
||||
+ }
|
||||
+
|
||||
+ @Before
|
||||
+ public void setUp() throws Exception {
|
||||
+ sshd = setupTestServer();
|
||||
+ client = setupTestClient();
|
||||
+ }
|
||||
+
|
||||
+ @After
|
||||
+ public void tearDown() throws Exception {
|
||||
+ if (sshd != null) {
|
||||
+ sshd.stop(true);
|
||||
+ }
|
||||
+ if (client != null) {
|
||||
+ client.stop();
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ @Test
|
||||
+ public void connectionClosedIfFirstPacketFromClientNotKexInit() throws Exception {
|
||||
+ testConnectionClosedIfFirstPacketFromPeerNotKexInit(true);
|
||||
+ }
|
||||
+
|
||||
+ @Test
|
||||
+ public void connectionClosedIfFirstPacketFromServerNotKexInit() throws Exception {
|
||||
+ testConnectionClosedIfFirstPacketFromPeerNotKexInit(false);
|
||||
+ }
|
||||
+
|
||||
+ private void testConnectionClosedIfFirstPacketFromPeerNotKexInit(boolean clientInitiates) throws Exception {
|
||||
+ AtomicReference<IoWriteFuture> debugMsg = new AtomicReference<>();
|
||||
+ SessionListener messageInitiator = new SessionListener() {
|
||||
+ @Override // At this stage KEX-INIT not sent yet
|
||||
+ public void sessionNegotiationOptionsCreated(Session session, Map<KexProposalOption, String> proposal) {
|
||||
+ try {
|
||||
+ debugMsg.set(session.sendDebugMessage(true, getCurrentTestName(), null));
|
||||
+ } catch (Exception e) {
|
||||
+ throw new RuntimeException(e);
|
||||
+ }
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
+ if (clientInitiates) {
|
||||
+ client.addSessionListener(messageInitiator);
|
||||
+ } else {
|
||||
+ sshd.addSessionListener(messageInitiator);
|
||||
+ }
|
||||
+
|
||||
+ try (ClientSession session = obtainInitialTestClientSession()) {
|
||||
+ fail("Unexpected session success");
|
||||
+ } catch (SshException e) {
|
||||
+ IoWriteFuture future = debugMsg.get();
|
||||
+ assertNotNull("No SSH_MSG_DEBUG", future);
|
||||
+ assertTrue("SSH_MSG_DEBUG should have been sent", future.isWritten());
|
||||
+ // Due to a race condition in the Nio2 transport when closing a connection due to an exception it's possible
|
||||
+ // that we do _not_ get the expected disconnection code. The race condition may lead to the IoSession being
|
||||
+ // closed in the peer before it has sent the DISCONNECT message. Happens in particular on Windows.
|
||||
+ if (e.getDisconnectCode() == SshConstants.SSH2_DISCONNECT_KEY_EXCHANGE_FAILED) {
|
||||
+ assertTrue("Unexpected disconnect reason: " + e.getMessage(), e.getMessage()
|
||||
+ .startsWith("Strict KEX negotiated but sequence number of first KEX_INIT received is not 1"));
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ @Test
|
||||
+ public void connectionClosedIfSpuriousPacketFromClientInKex() throws Exception {
|
||||
+ testConnectionClosedIfSupriousPacketInKex(true);
|
||||
+ }
|
||||
+
|
||||
+ @Test
|
||||
+ public void connectionClosedIfSpuriousPacketFromServerInKex() throws Exception {
|
||||
+ testConnectionClosedIfSupriousPacketInKex(false);
|
||||
+ }
|
||||
+
|
||||
+ private void testConnectionClosedIfSupriousPacketInKex(boolean clientInitiates) throws Exception {
|
||||
+ AtomicReference<IoWriteFuture> debugMsg = new AtomicReference<>();
|
||||
+ SessionListener messageInitiator = new SessionListener() {
|
||||
+ @Override // At this stage the peer's KEX_INIT has been received
|
||||
+ public void sessionNegotiationEnd(
|
||||
+ Session session, Map<KexProposalOption, String> clientProposal,
|
||||
+ Map<KexProposalOption, String> serverProposal, Map<KexProposalOption, String> negotiatedOptions,
|
||||
+ Throwable reason) {
|
||||
+ try {
|
||||
+ debugMsg.set(session.sendDebugMessage(true, getCurrentTestName(), null));
|
||||
+ } catch (Exception e) {
|
||||
+ throw new RuntimeException(e);
|
||||
+ }
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
+ if (clientInitiates) {
|
||||
+ client.addSessionListener(messageInitiator);
|
||||
+ } else {
|
||||
+ sshd.addSessionListener(messageInitiator);
|
||||
+ }
|
||||
+
|
||||
+ try (ClientSession session = obtainInitialTestClientSession()) {
|
||||
+ fail("Unexpected session success");
|
||||
+ } catch (SshException e) {
|
||||
+ IoWriteFuture future = debugMsg.get();
|
||||
+ assertNotNull("No SSH_MSG_DEBUG", future);
|
||||
+ assertTrue("SSH_MSG_DEBUG should have been sent", future.isWritten());
|
||||
+ if (e.getDisconnectCode() == SshConstants.SSH2_DISCONNECT_KEY_EXCHANGE_FAILED) {
|
||||
+ assertEquals("Unexpected disconnect reason",
|
||||
+ "SSH_MSG_DEBUG not allowed during initial key exchange in strict KEX", e.getMessage());
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ @Test
|
||||
+ public void reKeyAllowsDebugInKexFromClient() throws Exception {
|
||||
+ testReKeyAllowsDebugInKex(true);
|
||||
+ }
|
||||
+
|
||||
+ @Test
|
||||
+ public void reKeyAllowsDebugInKexFromServer() throws Exception {
|
||||
+ testReKeyAllowsDebugInKex(false);
|
||||
+ }
|
||||
+
|
||||
+ private void testReKeyAllowsDebugInKex(boolean clientInitiates) throws Exception {
|
||||
+ AtomicBoolean sendDebug = new AtomicBoolean();
|
||||
+ AtomicReference<IoWriteFuture> debugMsg = new AtomicReference<>();
|
||||
+ SessionListener messageInitiator = new SessionListener() {
|
||||
+ @Override // At this stage the peer's KEX_INIT has been received
|
||||
+ public void sessionNegotiationEnd(
|
||||
+ Session session, Map<KexProposalOption, String> clientProposal,
|
||||
+ Map<KexProposalOption, String> serverProposal, Map<KexProposalOption, String> negotiatedOptions,
|
||||
+ Throwable reason) {
|
||||
+ if (sendDebug.get()) {
|
||||
+ try {
|
||||
+ debugMsg.set(session.sendDebugMessage(true, getCurrentTestName(), null));
|
||||
+ } catch (Exception e) {
|
||||
+ throw new RuntimeException(e);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
+ if (clientInitiates) {
|
||||
+ client.addSessionListener(messageInitiator);
|
||||
+ } else {
|
||||
+ sshd.addSessionListener(messageInitiator);
|
||||
+ }
|
||||
+
|
||||
+ try (ClientSession session = obtainInitialTestClientSession()) {
|
||||
+ assertTrue("Session should be stablished", session.isOpen());
|
||||
+ sendDebug.set(true);
|
||||
+ assertTrue("KEX not done", session.reExchangeKeys().verify(CONNECT_TIMEOUT).isDone());
|
||||
+ IoWriteFuture future = debugMsg.get();
|
||||
+ assertNotNull("No SSH_MSG_DEBUG", future);
|
||||
+ assertTrue("SSH_MSG_DEBUG should have been sent", future.isWritten());
|
||||
+ assertTrue(session.isOpen());
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ @Test
|
||||
+ public void strictKexWorksWithServerFlagInClientProposal() throws Exception {
|
||||
+ testStrictKexWorksWithWrongFlag(true);
|
||||
+ }
|
||||
+
|
||||
+ @Test
|
||||
+ public void strictKexWorksWithClientFlagInServerProposal() throws Exception {
|
||||
+ testStrictKexWorksWithWrongFlag(false);
|
||||
+ }
|
||||
+
|
||||
+ private void testStrictKexWorksWithWrongFlag(boolean clientInitiates) throws Exception {
|
||||
+ SessionListener messageInitiator = new SessionListener() {
|
||||
+ @Override
|
||||
+ public void sessionNegotiationOptionsCreated(Session session, Map<KexProposalOption, String> proposal) {
|
||||
+ // Modify the proposal by including the *wrong* flag. (The framework will also add the correct flag.)
|
||||
+ String value = proposal.get(KexProposalOption.ALGORITHMS);
|
||||
+ String toAdd = clientInitiates
|
||||
+ ? KexExtensions.STRICT_KEX_SERVER_EXTENSION
|
||||
+ : KexExtensions.STRICT_KEX_CLIENT_EXTENSION;
|
||||
+ if (GenericUtils.isEmpty(value)) {
|
||||
+ value = toAdd;
|
||||
+ } else {
|
||||
+ value += ',' + toAdd;
|
||||
+ }
|
||||
+ proposal.put(KexProposalOption.ALGORITHMS, value);
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
+ if (clientInitiates) {
|
||||
+ client.addSessionListener(messageInitiator);
|
||||
+ } else {
|
||||
+ sshd.addSessionListener(messageInitiator);
|
||||
+ }
|
||||
+
|
||||
+ try (ClientSession session = obtainInitialTestClientSession()) {
|
||||
+ assertTrue("Session should be stablished", session.isOpen());
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private ClientSession obtainInitialTestClientSession() throws IOException {
|
||||
+ sshd.start();
|
||||
+ int port = sshd.getPort();
|
||||
+
|
||||
+ client.start();
|
||||
+ return createAuthenticatedClientSession(client, port);
|
||||
+ }
|
||||
+}
|
||||
|
||||
From 7b2c781640a7a78a9455b86593a1f63c9e8cab92 Mon Sep 17 00:00:00 2001
|
||||
From: Thomas Wolf <twolf@apache.org>
|
||||
Date: Tue, 2 Jan 2024 22:35:42 +0100
|
||||
Subject: [PATCH 3/3] GH-445: strict KEX interoperability tests
|
||||
|
||||
Run an Apache MINA sshd client against OpenSSH servers that do have or
|
||||
do not have strict KEX.
|
||||
---
|
||||
.../StrictKexInteroperabilityTest.java | 192 ++++++++++++++++++
|
||||
.../sshd/common/kex/extensions/client/bob_key | 27 +++
|
||||
.../common/kex/extensions/client/bob_key.pub | 1 +
|
||||
.../kex/extensions/client/entrypoint.sh | 6 +
|
||||
sshd-mina/pom.xml | 1 +
|
||||
sshd-netty/pom.xml | 1 +
|
||||
6 files changed, 228 insertions(+)
|
||||
create mode 100644 sshd-core/src/test/java/org/apache/sshd/common/kex/extension/StrictKexInteroperabilityTest.java
|
||||
create mode 100644 sshd-core/src/test/resources/org/apache/sshd/common/kex/extensions/client/bob_key
|
||||
create mode 100644 sshd-core/src/test/resources/org/apache/sshd/common/kex/extensions/client/bob_key.pub
|
||||
create mode 100644 sshd-core/src/test/resources/org/apache/sshd/common/kex/extensions/client/entrypoint.sh
|
||||
|
||||
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/kex/extension/StrictKexInteroperabilityTest.java b/sshd-core/src/test/java/org/apache/sshd/common/kex/extension/StrictKexInteroperabilityTest.java
|
||||
new file mode 100644
|
||||
index 000000000..43e6d8a8e
|
||||
--- /dev/null
|
||||
+++ b/sshd-core/src/test/java/org/apache/sshd/common/kex/extension/StrictKexInteroperabilityTest.java
|
||||
@@ -0,0 +1,192 @@
|
||||
+/*
|
||||
+ * 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.kex.extension;
|
||||
+
|
||||
+import java.io.PipedInputStream;
|
||||
+import java.io.PipedOutputStream;
|
||||
+import java.nio.charset.StandardCharsets;
|
||||
+
|
||||
+import org.apache.sshd.client.ClientFactoryManager;
|
||||
+import org.apache.sshd.client.SshClient;
|
||||
+import org.apache.sshd.client.channel.ChannelShell;
|
||||
+import org.apache.sshd.client.session.ClientSession;
|
||||
+import org.apache.sshd.client.session.ClientSessionImpl;
|
||||
+import org.apache.sshd.client.session.SessionFactory;
|
||||
+import org.apache.sshd.common.channel.StreamingChannel;
|
||||
+import org.apache.sshd.common.io.IoSession;
|
||||
+import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
|
||||
+import org.apache.sshd.util.test.BaseTestSupport;
|
||||
+import org.apache.sshd.util.test.CommonTestSupportUtils;
|
||||
+import org.apache.sshd.util.test.ContainerTestCase;
|
||||
+import org.junit.After;
|
||||
+import org.junit.Before;
|
||||
+import org.junit.Test;
|
||||
+import org.junit.experimental.categories.Category;
|
||||
+import org.slf4j.Logger;
|
||||
+import org.slf4j.LoggerFactory;
|
||||
+import org.testcontainers.containers.GenericContainer;
|
||||
+import org.testcontainers.containers.output.Slf4jLogConsumer;
|
||||
+import org.testcontainers.containers.wait.strategy.Wait;
|
||||
+import org.testcontainers.images.builder.ImageFromDockerfile;
|
||||
+import org.testcontainers.images.builder.dockerfile.DockerfileBuilder;
|
||||
+import org.testcontainers.utility.MountableFile;
|
||||
+
|
||||
+/**
|
||||
+ * Tests to ensure that an Apache MINA sshd client can talk to OpenSSH servers with or without "strict KEX". This
|
||||
+ * implicitly tests the message sequence number handling; if sequence numbers get out of sync or are reset wrongly,
|
||||
+ * subsequent messages cannot be decrypted correctly and there will be exceptions.
|
||||
+ *
|
||||
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
||||
+ * @see <A HREF="https://github.com/apache/mina-sshd/issues/445">Terrapin Mitigation: "strict-kex"</A>
|
||||
+ */
|
||||
+@Category(ContainerTestCase.class)
|
||||
+public class StrictKexInteroperabilityTest extends BaseTestSupport {
|
||||
+
|
||||
+ private static final Logger LOG = LoggerFactory.getLogger(StrictKexInteroperabilityTest.class);
|
||||
+
|
||||
+ private static final String TEST_RESOURCES = "org/apache/sshd/common/kex/extensions/client";
|
||||
+
|
||||
+ private SshClient client;
|
||||
+
|
||||
+ public StrictKexInteroperabilityTest() {
|
||||
+ super();
|
||||
+ }
|
||||
+
|
||||
+ @Before
|
||||
+ public void setUp() throws Exception {
|
||||
+ client = setupTestClient();
|
||||
+ SessionFactory factory = new TestSessionFactory(client);
|
||||
+ client.setSessionFactory(factory);
|
||||
+ }
|
||||
+
|
||||
+ @After
|
||||
+ public void tearDown() throws Exception {
|
||||
+ if (client != null) {
|
||||
+ client.stop();
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private DockerfileBuilder strictKexImage(DockerfileBuilder builder, boolean withStrictKex) {
|
||||
+ if (!withStrictKex) {
|
||||
+ return builder
|
||||
+ // CentOS 7 is EOL and thus unlikely to get the security update for strict KEX.
|
||||
+ .from("centos:7.9.2009") //
|
||||
+ .run("yum install -y openssh-server") // Installs OpenSSH 7.4
|
||||
+ .run("/usr/sbin/sshd-keygen") // Generate multiple host keys
|
||||
+ .run("adduser bob"); // Add a user
|
||||
+ } else {
|
||||
+ return builder
|
||||
+ .from("alpine:20231219") //
|
||||
+ .run("apk --update add openssh-server") // Installs OpenSSH 9.6
|
||||
+ .run("ssh-keygen -A") // Generate multiple host keys
|
||||
+ .run("adduser -D bob") // Add a user
|
||||
+ .run("echo 'bob:passwordBob' | chpasswd"); // Give it a password to unlock the user
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ @Test
|
||||
+ public void testStrictKexOff() throws Exception {
|
||||
+ testStrictKex(false);
|
||||
+ }
|
||||
+
|
||||
+ @Test
|
||||
+ public void testStrictKexOn() throws Exception {
|
||||
+ testStrictKex(true);
|
||||
+ }
|
||||
+
|
||||
+ private void testStrictKex(boolean withStrictKex) throws Exception {
|
||||
+ // This tests that the message sequence numbers are handled correctly. Strict KEX resets them to zero on any
|
||||
+ // KEX, without strict KEX, they're not reset. If sequence numbers get out of sync, received messages are
|
||||
+ // decrypted wrongly and there will be exceptions.
|
||||
+ @SuppressWarnings("resource")
|
||||
+ GenericContainer<?> sshdContainer = new GenericContainer<>(new ImageFromDockerfile()
|
||||
+ .withDockerfileFromBuilder(builder -> strictKexImage(builder, withStrictKex) //
|
||||
+ .run("mkdir -p /home/bob/.ssh") // Create the SSH config directory
|
||||
+ .entryPoint("/entrypoint.sh") //
|
||||
+ .build())) //
|
||||
+ .withCopyFileToContainer(MountableFile.forClasspathResource(TEST_RESOURCES + "/bob_key.pub"),
|
||||
+ "/home/bob/.ssh/authorized_keys")
|
||||
+ // entrypoint must be executable. Spotbugs doesn't like 0777, so use hex
|
||||
+ .withCopyFileToContainer(
|
||||
+ MountableFile.forClasspathResource(TEST_RESOURCES + "/entrypoint.sh", 0x1ff),
|
||||
+ "/entrypoint.sh")
|
||||
+ .waitingFor(Wait.forLogMessage(".*Server listening on :: port 22.*\\n", 1)) //
|
||||
+ .withExposedPorts(22) //
|
||||
+ .withLogConsumer(new Slf4jLogConsumer(LOG));
|
||||
+ sshdContainer.start();
|
||||
+ try {
|
||||
+ FileKeyPairProvider keyPairProvider = CommonTestSupportUtils.createTestKeyPairProvider(TEST_RESOURCES + "/bob_key");
|
||||
+ client.setKeyIdentityProvider(keyPairProvider);
|
||||
+ client.start();
|
||||
+ try (ClientSession session = client.connect("bob", sshdContainer.getHost(), sshdContainer.getMappedPort(22))
|
||||
+ .verify(CONNECT_TIMEOUT).getSession()) {
|
||||
+ session.auth().verify(AUTH_TIMEOUT);
|
||||
+ assertTrue("Should authenticate", session.isAuthenticated());
|
||||
+ assertTrue("Unexpected session type " + session.getClass().getName(), session instanceof TestSession);
|
||||
+ assertEquals("Unexpected strict KEX usage", withStrictKex, ((TestSession) session).usesStrictKex());
|
||||
+ try (ChannelShell channel = session.createShellChannel()) {
|
||||
+ channel.setOut(System.out);
|
||||
+ channel.setErr(System.err);
|
||||
+ channel.setStreaming(StreamingChannel.Streaming.Sync);
|
||||
+ PipedOutputStream pos = new PipedOutputStream();
|
||||
+ PipedInputStream pis = new PipedInputStream(pos);
|
||||
+ channel.setIn(pis);
|
||||
+ assertTrue("Could not open session", channel.open().await(DEFAULT_TIMEOUT));
|
||||
+ LOG.info("writing some data...");
|
||||
+ pos.write("\n\n".getBytes(StandardCharsets.UTF_8));
|
||||
+ assertTrue("Channel should be open", channel.isOpen());
|
||||
+ assertTrue(session.reExchangeKeys().verify(CONNECT_TIMEOUT).isDone());
|
||||
+ assertTrue("Channel should be open", channel.isOpen());
|
||||
+ LOG.info("writing some data...");
|
||||
+ pos.write("\n\n".getBytes(StandardCharsets.UTF_8));
|
||||
+ assertTrue("Channel should be open", channel.isOpen());
|
||||
+ channel.close(true);
|
||||
+ }
|
||||
+ }
|
||||
+ } finally {
|
||||
+ sshdContainer.stop();
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // Subclass ClientSessionImpl to get access to the strictKex flag.
|
||||
+
|
||||
+ private static class TestSessionFactory extends SessionFactory {
|
||||
+
|
||||
+ TestSessionFactory(ClientFactoryManager client) {
|
||||
+ super(client);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ protected ClientSessionImpl doCreateSession(IoSession ioSession) throws Exception {
|
||||
+ return new TestSession(getClient(), ioSession);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private static class TestSession extends ClientSessionImpl {
|
||||
+
|
||||
+ TestSession(ClientFactoryManager client, IoSession ioSession) throws Exception {
|
||||
+ super(client, ioSession);
|
||||
+ }
|
||||
+
|
||||
+ boolean usesStrictKex() {
|
||||
+ return strictKex;
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
diff --git a/sshd-core/src/test/resources/org/apache/sshd/common/kex/extensions/client/bob_key b/sshd-core/src/test/resources/org/apache/sshd/common/kex/extensions/client/bob_key
|
||||
new file mode 100644
|
||||
index 000000000..b5b70aeaa
|
||||
--- /dev/null
|
||||
+++ b/sshd-core/src/test/resources/org/apache/sshd/common/kex/extensions/client/bob_key
|
||||
@@ -0,0 +1,27 @@
|
||||
+-----BEGIN RSA PRIVATE KEY-----
|
||||
+MIIEpAIBAAKCAQEAxY3Hr1SqpJIQ9SbFfGMGweVy8jg2TEH3GC1K0LudQHJwogRi
|
||||
++debdCqUtuSITbpPhjkeZSk9rq198d6RhT6TQmY9J8wLL2/+VXZk/rMVEEjeXQS3
|
||||
+ImRnL2vVmkAunv6LwfDGHIovkhwj3/lqGWphDAKnHyXusPDwQ3N4LFGgxwXvRGqc
|
||||
+lzmP8H+KDWaaPapk1AZCBIoD4JbL8faBtLNU01r+pB3sIKvfsPJ5DxPErThfrPuD
|
||||
+qIbA3axEqFlgX4aVl3yMnSWjfhLhO7xD3YwrtUhannHt8pZQo5FkwCGWDpkG3xs+
|
||||
+qK3ZACrhMFMTvPuDS83jDtEzNd5KYb4KnkOPMQIDAQABAoIBAQCE5GktgrD/39pU
|
||||
+b25tzFehW25FjpbIGZ/UvbMUUwDnd5RZCMZj9yv1qyc7GOSwFOKmEgpmVqXNuZt9
|
||||
+dxFBJuT8x7Xf7Zygnp/icbBivakvuTUMMb3X/t6CwfGAwCgcgHMXVZaPYE275f4k
|
||||
+Dq3Wxv7di3NMusGkeY/GcAipF4gmGKKe7Ck1ifRypF2cDJsgTtsoFUHNNKfnT3gf
|
||||
+OcJsVLRl0osbsxdqU+Tep46+jHrNt8J9n2VeRNRIqGHj0CkNdpLQOs+MjvIO3Hgq
|
||||
+9NUxwIExwaPnBpTLlWwfemCz3JQnlAineMbYBGa1tpAA3Iw56NWcNbiOPyUyffbI
|
||||
+wBC4r1uZAoGBAPESsergFD+ontChEI+h38oM/D9DKCObZR2kz6WArZ54i1dJWOgh
|
||||
+HCsuxgPjxmaddPKghfNhUORdZBynuS5G7n6BfItNilDiFm2KBk12d38OVovUFo1Q
|
||||
+r5akclKf0kFxHt5TzHIrNAv7B4OF0Uk3kuDHM7ITX3qDpTSBLlzPAUUHAoGBANHJ
|
||||
+QIPmuF2q+PXnnSgdEyiETfl/IqUTXQyxda8kRIPJKKHZKPHZePhgJKUq9VP32PrP
|
||||
+AxIBNrS3Netsp+EAApj09hmWUcgJRIU1/wjpVGqUmguYgh8nVFOPDudOJD5ltQ/A
|
||||
+enzQ19IkGroaQB8CBGZsPaBAvqRZ5PLbm+BZEPQHAoGAblaMMGCXY/udlQfjOJpy
|
||||
+f1wqKBpoyMNbKJJCqBGZZaruu+jKVJSy++DQqP8b0+PFnzdxl8+24o8MP0FVNKUq
|
||||
+i6RgiLHY2ORiN4ixEctjLjg1zJIqMEv50g06di7IYUORSVk5fhfgHourCLu66rQQ
|
||||
++eiy9JKBZOXUO4/U1I26mwkCgYAhfuCuLsiBLCtUGAcfwISuk3FfxMzjTpQs0qjX
|
||||
+rhLCd/vk26eN9gs6nR88v/8ryQb8BNGYrljtwdL6I/8qDbZcdcBVlYq5RcGLA3QV
|
||||
+GCxCWDfAYjlkgAMW1GCsze07iUG/ohvskevjwaAC1u4mBUxujhnI3I2T8EZ+AFKD
|
||||
+H7V1QQKBgQDNt+zjSdLtA9AczxDwWmi5SbS+k+nGbi6AQO9i73wky/wxx7FonfWS
|
||||
+2skkOUIst3HBc0Oz+CJTfNFQK6GVqtzTdlZFhMYS0ua1Djd6q6S648+K0cieY4r5
|
||||
+5irivHYVN8t7lBcvbA7E7yD6dHXSHsn6yOLTrV382qRfJTbxG7ZVWA==
|
||||
+-----END RSA PRIVATE KEY-----
|
||||
diff --git a/sshd-core/src/test/resources/org/apache/sshd/common/kex/extensions/client/bob_key.pub b/sshd-core/src/test/resources/org/apache/sshd/common/kex/extensions/client/bob_key.pub
|
||||
new file mode 100644
|
||||
index 000000000..efecd1b08
|
||||
--- /dev/null
|
||||
+++ b/sshd-core/src/test/resources/org/apache/sshd/common/kex/extensions/client/bob_key.pub
|
||||
@@ -0,0 +1 @@
|
||||
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFjcevVKqkkhD1JsV8YwbB5XLyODZMQfcYLUrQu51AcnCiBGL515t0KpS25IhNuk+GOR5lKT2urX3x3pGFPpNCZj0nzAsvb/5VdmT+sxUQSN5dBLciZGcva9WaQC6e/ovB8MYcii+SHCPf+WoZamEMAqcfJe6w8PBDc3gsUaDHBe9EapyXOY/wf4oNZpo9qmTUBkIEigPglsvx9oG0s1TTWv6kHewgq9+w8nkPE8StOF+s+4OohsDdrESoWWBfhpWXfIydJaN+EuE7vEPdjCu1SFqece3yllCjkWTAIZYOmQbfGz6ordkAKuEwUxO8+4NLzeMO0TM13kphvgqeQ48x user01
|
||||
diff --git a/sshd-core/src/test/resources/org/apache/sshd/common/kex/extensions/client/entrypoint.sh b/sshd-core/src/test/resources/org/apache/sshd/common/kex/extensions/client/entrypoint.sh
|
||||
new file mode 100644
|
||||
index 000000000..26489c5f0
|
||||
--- /dev/null
|
||||
+++ b/sshd-core/src/test/resources/org/apache/sshd/common/kex/extensions/client/entrypoint.sh
|
||||
@@ -0,0 +1,6 @@
|
||||
+#!/bin/sh
|
||||
+
|
||||
+chown -R bob /home/bob
|
||||
+chmod 0600 /home/bob/.ssh/*
|
||||
+
|
||||
+/usr/sbin/sshd -D -ddd
|
||||
diff --git a/sshd-mina/pom.xml b/sshd-mina/pom.xml
|
||||
index 967b12930..6d1c4ed6a 100644
|
||||
--- a/sshd-mina/pom.xml
|
||||
+++ b/sshd-mina/pom.xml
|
||||
@@ -124,6 +124,7 @@
|
||||
<exclude>**/SessionReKeyHostKeyExchangeTest.java</exclude>
|
||||
<exclude>**/HostBoundPubKeyAuthTest.java</exclude>
|
||||
<exclude>**/PortForwardingWithOpenSshTest.java</exclude>
|
||||
+ <exclude>**/StrictKexInteroperabilityTest.java</exclude>
|
||||
<!-- reading files from classpath doesn't work correctly w/ reusable test jar -->
|
||||
<exclude>**/OpenSSHCertificateTest.java</exclude>
|
||||
</excludes>
|
||||
diff --git a/sshd-netty/pom.xml b/sshd-netty/pom.xml
|
||||
index 5d774029f..ac34b5094 100644
|
||||
--- a/sshd-netty/pom.xml
|
||||
+++ b/sshd-netty/pom.xml
|
||||
@@ -143,6 +143,7 @@
|
||||
<exclude>**/SessionReKeyHostKeyExchangeTest.java</exclude>
|
||||
<exclude>**/HostBoundPubKeyAuthTest.java</exclude>
|
||||
<exclude>**/PortForwardingWithOpenSshTest.java</exclude>
|
||||
+ <exclude>**/StrictKexInteroperabilityTest.java</exclude>
|
||||
<!-- reading files from classpath doesn't work correctly w/ reusable test jar -->
|
||||
<exclude>**/OpenSSHCertificateTest.java</exclude>
|
||||
</excludes>
|
||||
BIN
apache-sshd-2.9.2-src.tar.gz
Normal file
BIN
apache-sshd-2.9.2-src.tar.gz
Normal file
Binary file not shown.
241
apache-sshd-javadoc.patch
Normal file
241
apache-sshd-javadoc.patch
Normal file
@ -0,0 +1,241 @@
|
||||
--- apache-sshd-2.9.2/sshd-common/src/main/java/org/apache/sshd/client/auth/password/PasswordIdentityProvider.java 2022-11-16 09:50:02.519293210 +0100
|
||||
+++ apache-sshd-2.9.2/sshd-common/src/main/java/org/apache/sshd/client/auth/password/PasswordIdentityProvider.java 2022-11-16 10:29:30.819501234 +0100
|
||||
@@ -36,7 +36,7 @@
|
||||
public interface PasswordIdentityProvider {
|
||||
|
||||
/**
|
||||
- * An "empty" implementation of {@link PasswordIdentityProvider} that returns an empty group of passwords
|
||||
+ * An "empty" implementation of {@link PasswordIdentityProvider} that returns an empty group of passwords
|
||||
*/
|
||||
PasswordIdentityProvider EMPTY_PASSWORDS_PROVIDER = new PasswordIdentityProvider() {
|
||||
@Override
|
||||
--- apache-sshd-2.9.2/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java 2022-11-16 09:50:02.523293237 +0100
|
||||
+++ apache-sshd-2.9.2/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java 2022-11-16 10:21:06.704044979 +0100
|
||||
@@ -754,7 +754,7 @@
|
||||
* @param expected The expected fingerprint if {@code null} or empty then returns a failure with the default
|
||||
* fingerprint.
|
||||
* @param key the {@link PublicKey} - if {@code null} then returns null.
|
||||
- * @return SimpleImmutableEntry<Boolean, String> - key is success indicator, value is actual fingerprint,
|
||||
+ * @return SimpleImmutableEntry<Boolean, String> - key is success indicator, value is actual fingerprint,
|
||||
* {@code null} if no key.
|
||||
* @see #getDefaultFingerPrintFactory()
|
||||
* @see #checkFingerPrint(String, Factory, PublicKey)
|
||||
@@ -768,7 +768,7 @@
|
||||
* fingerprint.
|
||||
* @param f The {@link Factory} to be used to generate the default {@link Digest} for the key
|
||||
* @param key the {@link PublicKey} - if {@code null} then returns null.
|
||||
- * @return SimpleImmutableEntry<Boolean, String> - key is success indicator, value is actual fingerprint,
|
||||
+ * @return SimpleImmutableEntry<Boolean, String> - key is success indicator, value is actual fingerprint,
|
||||
* {@code null} if no key.
|
||||
*/
|
||||
public static SimpleImmutableEntry<Boolean, String> checkFingerPrint(
|
||||
@@ -781,7 +781,7 @@
|
||||
* fingerprint.
|
||||
* @param d The {@link Digest} to be used to generate the default fingerprint for the key
|
||||
* @param key the {@link PublicKey} - if {@code null} then returns null.
|
||||
- * @return SimpleImmutableEntry<Boolean, String> - key is success indicator, value is actual fingerprint,
|
||||
+ * @return SimpleImmutableEntry<Boolean, String> - key is success indicator, value is actual fingerprint,
|
||||
* {@code null} if no key.
|
||||
*/
|
||||
public static SimpleImmutableEntry<Boolean, String> checkFingerPrint(String expected, Digest d, PublicKey key) {
|
||||
--- apache-sshd-2.9.2/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java 2022-11-16 09:50:02.523293237 +0100
|
||||
+++ apache-sshd-2.9.2/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java 2022-11-16 10:27:11.094543153 +0100
|
||||
@@ -63,9 +63,7 @@
|
||||
import org.apache.sshd.common.util.security.SecurityUtils;
|
||||
|
||||
/**
|
||||
- * Basic support for <A HREF=
|
||||
- * "http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.key?rev=1.1&content-type=text/x-cvsweb-markup">OpenSSH
|
||||
- * key file(s)</A>
|
||||
+ * Basic support for <A HREF="http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.key?rev=1.1&content-type=text/x-cvsweb-markup">OpenSSH key file(s)</A>
|
||||
*
|
||||
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
||||
*/
|
||||
--- apache-sshd-2.9.2/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/BufferPublicKeyParser.java 2022-11-16 09:50:02.531293291 +0100
|
||||
+++ apache-sshd-2.9.2/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/BufferPublicKeyParser.java 2022-11-16 10:07:03.290271908 +0100
|
||||
@@ -64,13 +64,13 @@
|
||||
SkED25519BufferPublicKeyParser.INSTANCE));
|
||||
|
||||
/**
|
||||
- * @param keyType The key type - e.g., "ssh-rsa", "ssh-dss"
|
||||
+ * @param keyType The key type - e.g., "ssh-rsa", "ssh-dss"
|
||||
* @return {@code true} if this key type is supported by the parser
|
||||
*/
|
||||
boolean isKeyTypeSupported(String keyType);
|
||||
|
||||
/**
|
||||
- * @param keyType The key type - e.g., "ssh-rsa", "ssh-dss"
|
||||
+ * @param keyType The key type - e.g., "ssh-rsa", "ssh-dss"
|
||||
* @param buffer The {@link Buffer} containing the encoded raw public key
|
||||
* @return The decoded {@link PublicKey}
|
||||
* @throws GeneralSecurityException If failed to generate the key
|
||||
--- apache-sshd-2.9.2/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java 2022-11-16 09:50:02.527293266 +0100
|
||||
+++ apache-sshd-2.9.2/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java 2022-11-16 10:17:14.006452121 +0100
|
||||
@@ -112,10 +112,11 @@
|
||||
* @param with String to replace with
|
||||
* @param max maximum number of values to replace, or <code>-1</code> if no maximum
|
||||
* @return the text with any replacements processed
|
||||
- * @author Arnout J. Kuiper <a href="mailto:ajkuiper@wxs.nl">ajkuiper@wxs.nl</a>
|
||||
- * @author Magesh Umasankar
|
||||
- * @author <a href="mailto:bruce@callenish.com">Bruce Atherton</a>
|
||||
- * @author <a href="mailto:levylambert@tiscali-dsl.de">Antoine Levy-Lambert</a>
|
||||
+ *
|
||||
+ * author Arnout J. Kuiper <a href="mailto:ajkuiper@wxs.nl">ajkuiper@wxs.nl</a>
|
||||
+ * author Magesh Umasankar
|
||||
+ * author <a href="mailto:bruce@callenish.com">Bruce Atherton</a>
|
||||
+ * author <a href="mailto:levylambert@tiscali-dsl.de">Antoine Levy-Lambert</a>
|
||||
*/
|
||||
@SuppressWarnings("PMD.AssignmentInOperand")
|
||||
public static String replace(String text, String repl, String with, int max) {
|
||||
--- apache-sshd-2.9.2/sshd-common/src/main/java/org/apache/sshd/common/util/io/der/DERWriter.java 2022-11-16 09:50:02.531293291 +0100
|
||||
+++ apache-sshd-2.9.2/sshd-common/src/main/java/org/apache/sshd/common/util/io/der/DERWriter.java 2022-11-16 10:09:10.435142161 +0100
|
||||
@@ -76,7 +76,7 @@
|
||||
}
|
||||
|
||||
/**
|
||||
- * The integer is always considered to be positive, so if the first byte is < 0, we pad with a zero to make it
|
||||
+ * The integer is always considered to be positive, so if the first byte is < 0, we pad with a zero to make it
|
||||
* positive
|
||||
*
|
||||
* @param bytes {@link BigInteger} bytes
|
||||
@@ -87,7 +87,7 @@
|
||||
}
|
||||
|
||||
/**
|
||||
- * The integer is always considered to be positive, so if the first byte is < 0, we pad with a zero to make it
|
||||
+ * The integer is always considered to be positive, so if the first byte is < 0, we pad with a zero to make it
|
||||
* positive
|
||||
*
|
||||
* @param bytes {@link BigInteger} bytes
|
||||
--- apache-sshd-2.9.2/sshd-common/src/main/java/org/apache/sshd/common/util/OsUtils.java 2022-11-16 09:50:02.527293266 +0100
|
||||
+++ apache-sshd-2.9.2/sshd-common/src/main/java/org/apache/sshd/common/util/OsUtils.java 2022-11-16 10:28:23.527039819 +0100
|
||||
@@ -165,7 +165,7 @@
|
||||
}
|
||||
|
||||
/**
|
||||
- * Remove {@code Windows} domain and/or group prefix as well as "(User);" suffix
|
||||
+ * Remove {@code Windows} domain and/or group prefix as well as "(User);" suffix
|
||||
*
|
||||
* @param user The original username - ignored if {@code null}/empty
|
||||
* @return The canonical user - unchanged if {@code Unix} O/S
|
||||
--- apache-sshd-2.9.2/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java 2022-11-16 09:50:02.535293319 +0100
|
||||
+++ apache-sshd-2.9.2/sshd-common/src/main/java/org/apache/sshd/common/util/security/SecurityUtils.java 2022-11-16 10:31:13.564205742 +0100
|
||||
@@ -119,7 +119,7 @@
|
||||
/**
|
||||
* The min. key size value used for testing whether Diffie-Hellman Group Exchange is supported or not. According to
|
||||
* <A HREF="https://tools.ietf.org/html/rfc4419">RFC 4419</A> section 3: "Servers and clients SHOULD support
|
||||
- * groups with a modulus length of k bits, where 1024 <= k <= 8192". </code>
|
||||
+ * groups with a modulus length of k bits, where 1024 <= k <= 8192". </code>
|
||||
*
|
||||
* <B>Note: this has been amended by <A HREF="https://tools.ietf.org/html/rfc8270">RFC 8270</A>
|
||||
*/
|
||||
--- apache-sshd-2.9.2/sshd-core/src/main/java/org/apache/sshd/client/session/ClientProxyConnector.java 2022-11-16 09:50:02.571293565 +0100
|
||||
+++ apache-sshd-2.9.2/sshd-core/src/main/java/org/apache/sshd/client/session/ClientProxyConnector.java 2022-11-16 10:28:51.175229400 +0100
|
||||
@@ -23,8 +23,8 @@
|
||||
|
||||
/**
|
||||
* Provides a way to implement proxied connections where some metadata about the client is sent <U>before</U> the actual
|
||||
- * SSH protocol is executed - e.g., the <A HREF=@http://www.haproxy.org/download/1.6/doc/proxy-protocol.txt">PROXY
|
||||
- * protocol</A>. The implementor should use the {@code IoSession#write(Buffer)} method to send any packets with the
|
||||
+ * SSH protocol is executed - e.g., the <A HREF="http://www.haproxy.org/download/1.6/doc/proxy-protocol.txt">PROXY protocol</A>.
|
||||
+ * The implementor should use the {@code IoSession#write(Buffer)} method to send any packets with the
|
||||
* meta-data.
|
||||
*
|
||||
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
||||
--- apache-sshd-2.9.2/sshd-core/src/main/java/org/apache/sshd/common/BaseBuilder.java 2022-11-16 09:50:02.575293593 +0100
|
||||
+++ apache-sshd-2.9.2/sshd-core/src/main/java/org/apache/sshd/common/BaseBuilder.java 2022-11-16 10:04:31.529233186 +0100
|
||||
@@ -67,7 +67,7 @@
|
||||
|
||||
/**
|
||||
* The default {@link BuiltinCiphers} setup in order of preference as specified by
|
||||
- * <A HREF="https://www.freebsd.org/cgi/man.cgi?query=ssh_config&sektion=5">ssh_config(5)</A>
|
||||
+ * <A HREF="https://www.freebsd.org/cgi/man.cgi?query=ssh_config&sektion=5">ssh_config(5)</A>
|
||||
*/
|
||||
public static final List<BuiltinCiphers> DEFAULT_CIPHERS_PREFERENCE = Collections.unmodifiableList(
|
||||
Arrays.asList(
|
||||
@@ -83,7 +83,7 @@
|
||||
|
||||
/**
|
||||
* The default {@link BuiltinDHFactories} setup in order of preference as specified by
|
||||
- * <A HREF="https://www.freebsd.org/cgi/man.cgi?query=ssh_config&sektion=5"> ssh_config(5)</A>
|
||||
+ * <A HREF="https://www.freebsd.org/cgi/man.cgi?query=ssh_config&sektion=5"> ssh_config(5)</A>
|
||||
*/
|
||||
public static final List<BuiltinDHFactories> DEFAULT_KEX_PREFERENCE = Collections.unmodifiableList(
|
||||
Arrays.asList(
|
||||
@@ -104,7 +104,7 @@
|
||||
|
||||
/**
|
||||
* The default {@link BuiltinMacs} setup in order of preference as specified by
|
||||
- * <A HREF="https://www.freebsd.org/cgi/man.cgi?query=ssh_config&sektion=5"> ssh_config(5)</A>
|
||||
+ * <A HREF="https://www.freebsd.org/cgi/man.cgi?query=ssh_config&sektion=5"> ssh_config(5)</A>
|
||||
*/
|
||||
public static final List<BuiltinMacs> DEFAULT_MAC_PREFERENCE = Collections.unmodifiableList(
|
||||
Arrays.asList(
|
||||
--- apache-sshd-2.9.2/sshd-core/src/main/java/org/apache/sshd/common/channel/LocalWindow.java 2022-11-16 09:50:02.575293593 +0100
|
||||
+++ apache-sshd-2.9.2/sshd-core/src/main/java/org/apache/sshd/common/channel/LocalWindow.java 2022-11-16 10:22:11.968492069 +0100
|
||||
@@ -51,8 +51,6 @@
|
||||
/**
|
||||
* Initializes the {@link LocalWindow} with the packet and window sizes from the {@code resolver}.
|
||||
*
|
||||
- * @param size the initial window size
|
||||
- * @param packetSize the peer's advertised maximum packet size
|
||||
* @param resolver {@PropertyResolver} to access properties
|
||||
*/
|
||||
public void init(PropertyResolver resolver) {
|
||||
--- apache-sshd-2.9.2/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/KeyExchangeMessageHandler.java 2022-11-16 09:50:02.579293619 +0100
|
||||
+++ apache-sshd-2.9.2/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/KeyExchangeMessageHandler.java 2022-11-16 10:49:31.567743605 +0100
|
||||
@@ -46,7 +46,7 @@
|
||||
/**
|
||||
* Manages SSH message sending during a key exchange. RFC 4253 specifies that during a key exchange, no high-level
|
||||
* messages are to be sent, but a receiver must be able to deal with messages "in flight" until the peer's
|
||||
- * {@link SshConstants#SSH_MSG_KEX_INIT} message is received.
|
||||
+ * {@link SshConstants#SSH_MSG_KEXINIT} message is received.
|
||||
* <p>
|
||||
* Apache MINA sshd queues up high-level messages that threads try to send while a key exchange is ongoing, and sends
|
||||
* them once the key exchange is done. Sending queued messages may make the peer re-trigger a new key exchange, in which
|
||||
@@ -154,7 +154,7 @@
|
||||
}
|
||||
|
||||
/**
|
||||
- * Initializes the state for a new key exchange. {@link #allPacketsFlushed()} will be {@code false}, and a new
|
||||
+ * Initializes the state for a new key exchange. <code>kexFlushed</code> will be {@code false}, and a new
|
||||
* future to be fulfilled when all queued packets will be flushed once the key exchange is done is set. The
|
||||
* currently set future from an earlier key exchange is returned. The returned future may or may not be fulfilled;
|
||||
* if it isn't, there are still left-over pending packets to write from the previous key exchange, which will be
|
||||
@@ -406,7 +406,7 @@
|
||||
* exchange, flushing is stopped and is to be resumed by another call to this method when the new key exchange is
|
||||
* done.
|
||||
*
|
||||
- * @param flushDone the future obtained from {@link #getFlushedFuture()}; will be fulfilled once all pending packets
|
||||
+ * @param flushDone the future obtained from {@link #terminateKeyExchange()}; will be fulfilled once all pending packets
|
||||
* have been written
|
||||
*/
|
||||
protected void flushQueue(DefaultKeyExchangeFuture flushDone) {
|
||||
--- apache-sshd-2.9.2/sshd-core/src/main/java/org/apache/sshd/common/session/Session.java 2022-11-16 09:50:02.579293619 +0100
|
||||
+++ apache-sshd-2.9.2/sshd-core/src/main/java/org/apache/sshd/common/session/Session.java 2022-11-16 10:02:05.032231651 +0100
|
||||
@@ -224,11 +224,11 @@
|
||||
* {@link Buffer} to the given {@link ReplyHandler}, which may execute in a different thread.
|
||||
*
|
||||
* <dl>
|
||||
- * <dt>want-reply == true && replyHandler != null</dt>
|
||||
+ * <dt>want-reply == true && replyHandler != null</dt>
|
||||
* <dd>The returned future is fulfilled with {@code null} when the request was sent, or with an exception if the
|
||||
* request could not be sent. The {@code replyHandler} is invoked once the reply is received, with the SSH reply
|
||||
* code and the data received.</dd>
|
||||
- * <dt>want-reply == true && replyHandler == null</dt>
|
||||
+ * <dt>want-reply == true && replyHandler == null</dt>
|
||||
* <dd>The returned future is fulfilled with an exception if the request could not be sent, or a failure reply was
|
||||
* received. If a success reply was received, the future is fulfilled with the received data buffer.</dd>
|
||||
* <dt>want-reply == false</dt>
|
||||
--- apache-sshd-2.9.2/sshd-core/src/main/java/org/apache/sshd/server/session/ServerProxyAcceptor.java 2022-11-16 09:50:02.583293646 +0100
|
||||
+++ apache-sshd-2.9.2/sshd-core/src/main/java/org/apache/sshd/server/session/ServerProxyAcceptor.java 2022-11-16 10:33:44.345239622 +0100
|
||||
@@ -23,8 +23,7 @@
|
||||
|
||||
/**
|
||||
* Provides a way to implement proxied connections where some metadata about the client is sent <U>before</U> the actual
|
||||
- * SSH protocol is executed - e.g., the <A HREF=@http://www.haproxy.org/download/1.6/doc/proxy-protocol.txt">PROXY
|
||||
- * protocol</A>.
|
||||
+ * SSH protocol is executed - e.g., the <A HREF="http://www.haproxy.org/download/1.6/doc/proxy-protocol.txt">PROXY protocol</A>.
|
||||
*
|
||||
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
||||
*/
|
||||
88
apache-sshd.spec
Normal file
88
apache-sshd.spec
Normal file
@ -0,0 +1,88 @@
|
||||
Epoch: 1
|
||||
Name: apache-sshd
|
||||
Version: 2.9.2
|
||||
Release: 3
|
||||
Summary: Apache SSHD
|
||||
License: ASL 2.0 and ISC
|
||||
URL: http://mina.apache.org/sshd-project
|
||||
Source0: https://archive.apache.org/dist/mina/sshd/%{version}/apache-sshd-%{version}-src.tar.gz
|
||||
Patch0: 0001-Avoid-optional-dependency-on-native-tomcat-APR-libra.patch
|
||||
Patch1: apache-sshd-javadoc.patch
|
||||
# https://github.com/apache/mina-sshd/commit/c20739b43aab0f7bf2ccad982a6cb37b9d5a8a0b
|
||||
Patch2: CVE-2023-35887.patch
|
||||
Patch3: CVE-2023-48795.patch
|
||||
|
||||
BuildRequires: maven-local mvn(junit:junit) mvn(net.i2p.crypto:eddsa) mvn(org.apache.ant:ant)
|
||||
BuildRequires: mvn(org.apache:apache:pom:) mvn(org.apache.felix:maven-bundle-plugin)
|
||||
BuildRequires: mvn(org.apache.maven:maven-archiver)
|
||||
BuildRequires: mvn(org.apache.maven.plugins:maven-antrun-plugin)
|
||||
BuildRequires: mvn(org.apache.maven.plugins:maven-clean-plugin)
|
||||
BuildRequires: mvn(org.apache.maven.plugins:maven-dependency-plugin)
|
||||
BuildRequires: mvn(org.apache.maven.plugins:maven-remote-resources-plugin)
|
||||
BuildRequires: mvn(org.apache.maven.surefire:surefire-junit47)
|
||||
BuildRequires: mvn(org.bouncycastle:bcpg-jdk15on) mvn(org.bouncycastle:bcpkix-jdk15on)
|
||||
BuildRequires: mvn(org.codehaus.mojo:build-helper-maven-plugin)
|
||||
BuildRequires: mvn(org.codehaus.plexus:plexus-archiver) mvn(org.slf4j:slf4j-api)
|
||||
BuildRequires: mvn(org.slf4j:jcl-over-slf4j)
|
||||
BuildArch: noarch
|
||||
%description
|
||||
Apache SSHD is a 100% pure java library to support the SSH protocols on both
|
||||
the client and server side.
|
||||
|
||||
%package javadoc
|
||||
Summary: API documentation for %{name}
|
||||
%description javadoc
|
||||
This package provides %{name}.
|
||||
|
||||
%prep
|
||||
%autosetup -p1
|
||||
rm -rf sshd-core/src/main/java/org/apache/sshd/agent/unix
|
||||
%pom_remove_dep :spring-framework-bom
|
||||
%pom_remove_dep :testcontainers-bom sshd-sftp sshd-core
|
||||
%pom_disable_module assembly
|
||||
%pom_disable_module sshd-mina
|
||||
%pom_disable_module sshd-netty
|
||||
%pom_disable_module sshd-ldap
|
||||
%pom_disable_module sshd-git
|
||||
%pom_disable_module sshd-contrib
|
||||
%pom_disable_module sshd-spring-sftp
|
||||
%pom_disable_module sshd-cli
|
||||
%pom_disable_module sshd-openpgp
|
||||
%pom_remove_plugin :apache-rat-plugin
|
||||
%pom_remove_plugin :gmavenplus-plugin
|
||||
%pom_remove_plugin :maven-checkstyle-plugin
|
||||
%pom_remove_plugin :maven-enforcer-plugin
|
||||
%pom_remove_plugin :maven-pmd-plugin
|
||||
%pom_remove_plugin :animal-sniffer-maven-plugin
|
||||
%pom_remove_plugin :impsort-maven-plugin
|
||||
%pom_remove_plugin :formatter-maven-plugin . sshd-core
|
||||
%pom_xpath_inject "pom:configuration/pom:instructions" "<_nouses>true</_nouses>" .
|
||||
|
||||
%build
|
||||
%mvn_build -f -- -Dworkspace.root.dir=$(pwd)
|
||||
|
||||
%install
|
||||
%mvn_install
|
||||
|
||||
%files -f .mfiles
|
||||
%doc CHANGES.md
|
||||
%license LICENSE.txt NOTICE.txt assembly/src/main/legal/licenses/jbcrypt.txt
|
||||
|
||||
%files javadoc -f .mfiles-javadoc
|
||||
%license LICENSE.txt NOTICE.txt assembly/src/main/legal/licenses/jbcrypt.txt
|
||||
|
||||
%changelog
|
||||
* Mon Jan 22 2024 wangkai <13474090681@163.com> - 1:2.9.2-3
|
||||
- Fix CVE-2023-48795
|
||||
|
||||
* Thu Jan 11 2024 yaoxin <yao_xin001@hoperun.com> - 1:2.9.2-2
|
||||
- Fix CVE-2023-35887
|
||||
|
||||
* Mon Nov 21 2022 liangqifeng <liangqifeng@ncti-gba.cn> - 1:2.9.2-1
|
||||
- Fix CVE-2022-45047
|
||||
|
||||
* Tue Aug 10 2021 yaoxin <yaoxin30@huawei.com> - 2.2.0-2
|
||||
- Fix CVE-2021-30129
|
||||
|
||||
* Thu Aug 6 2020 Jeffery.Gao <gaojianxing@huawei.com> - 2.2.0-1
|
||||
- Package init
|
||||
4
apache-sshd.yaml
Normal file
4
apache-sshd.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
version_control: NA
|
||||
src_repo: NA
|
||||
tag_prefix: NA
|
||||
seperator: NA
|
||||
Loading…
x
Reference in New Issue
Block a user