This is an automated email from the ASF dual-hosted git repository. lgoldstein pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/mina-sshd.git
commit a36319a68eca9243e30728e968b9f7a08d077ada Author: Lyor Goldstein <[email protected]> AuthorDate: Wed Feb 13 14:16:25 2019 +0200 [SSHD-896] Expose KEX related information via SessionContext --- CHANGES.md | 3 ++ .../apache/sshd/cli/client/SftpCommandMain.java | 33 +++++++++++++++++++++- .../apache/sshd/common/kex/KexProposalOption.java | 0 .../java/org/apache/sshd/common/kex/KexState.java | 0 .../apache/sshd/common/session/SessionContext.java | 33 ++++++++++++++++++++++ .../org/apache/sshd/common/session/Session.java | 11 -------- .../common/session/helpers/AbstractSession.java | 28 ++++++++++++++++-- 7 files changed, 94 insertions(+), 14 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 0c43e89..3c76f48 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,6 +13,9 @@ where the code decides to disconnect the session due to various protocol or conf * `ScpFileOpener#getMatchingFilesToSend` has been modified to accept a `Path` as the base directory and also return an `Iterable<Path>`. +* The SFTP command line client provides a `kex` command that displays the KEX parameters of the +current sesssion - client/server proposals and what has been negotiated. + ## Behavioral changes and enhancements * [SSHD-882](https://issues.apache.org/jira/browse/SSHD-882) - Provide hooks to allow users to register a consumer diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java index 922601d..26d471d 100644 --- a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java +++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java @@ -103,6 +103,7 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel { new PwdCommandExecutor(), new InfoCommandExecutor(), new SessionCommandExecutor(), + new KexCommandExecutor(), new ClientCommandExecutor(), new VersionCommandExecutor(), new CdCommandExecutor(), @@ -418,8 +419,38 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel { appendInfoValue(stdout, "Local address", ioSession.getLocalAddress()).println(); appendInfoValue(stdout, "Remote address", ioSession.getRemoteAddress()).println(); + appendInfoValue(stdout, "Client version", session.getClientVersion()).println(); + appendInfoValue(stdout, "Server version", session.getServerVersion()).println(); + return false; + } + } + + private class KexCommandExecutor implements SftpCommandExecutor { + KexCommandExecutor() { + super(); + } + + @Override + public String getName() { + return "kex"; + } + + @Override + public boolean executeCommand( + String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) + throws Exception { + ValidateUtils.checkTrue(GenericUtils.isEmpty(args), "Unexpected arguments: %s", args); + SftpClient sftp = getClient(); + ClientSession session = sftp.getSession(); + + Map<KexProposalOption, String> clientProposals = session.getClientKexProposals(); + Map<KexProposalOption, String> serverProposals = session.getServerKexProposals(); + Map<KexProposalOption, String> negotiated = session.getKexNegotiationResult(); for (KexProposalOption option : KexProposalOption.VALUES) { - appendInfoValue(stdout, option.getDescription(), session.getNegotiatedKexParameter(option)).println(); + String description = option.getDescription(); + appendInfoValue(stdout, description + "[client]", clientProposals.get(option)).println(); + appendInfoValue(stdout, description + "[server]", serverProposals.get(option)).println(); + appendInfoValue(stdout, description + "[negotiated]", negotiated.get(option)).println(); } return false; diff --git a/sshd-core/src/main/java/org/apache/sshd/common/kex/KexProposalOption.java b/sshd-common/src/main/java/org/apache/sshd/common/kex/KexProposalOption.java similarity index 100% rename from sshd-core/src/main/java/org/apache/sshd/common/kex/KexProposalOption.java rename to sshd-common/src/main/java/org/apache/sshd/common/kex/KexProposalOption.java diff --git a/sshd-core/src/main/java/org/apache/sshd/common/kex/KexState.java b/sshd-common/src/main/java/org/apache/sshd/common/kex/KexState.java similarity index 100% rename from sshd-core/src/main/java/org/apache/sshd/common/kex/KexState.java rename to sshd-common/src/main/java/org/apache/sshd/common/kex/KexState.java diff --git a/sshd-common/src/main/java/org/apache/sshd/common/session/SessionContext.java b/sshd-common/src/main/java/org/apache/sshd/common/session/SessionContext.java index c937079..d19dd9c 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/session/SessionContext.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/session/SessionContext.java @@ -19,9 +19,13 @@ package org.apache.sshd.common.session; +import java.util.Map; + import org.apache.sshd.common.AttributeStore; import org.apache.sshd.common.PropertyResolver; import org.apache.sshd.common.auth.UsernameHolder; +import org.apache.sshd.common.kex.KexProposalOption; +import org.apache.sshd.common.kex.KexState; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.net.ConnectionEndpointsIndicator; @@ -73,6 +77,13 @@ public interface SessionContext String getClientVersion(); /** + * @return An <U>un-modifiable</U> map of the latest KEX client proposal options + * May be empty if KEX not yet completed or re-keying in progress + * @see #getKexState() + */ + Map<KexProposalOption, String> getClientKexProposals(); + + /** * Retrieve the server version for this session. * * @return the server version - may be {@code null}/empty if versions not yet exchanged @@ -80,6 +91,28 @@ public interface SessionContext String getServerVersion(); /** + * @return An <U>un-modifiable</U> map of the latest KEX client proposal options. + * May be empty if KEX not yet completed or re-keying in progress + * @see #getKexState() + */ + Map<KexProposalOption, String> getServerKexProposals(); + + KexState getKexState(); + + Map<KexProposalOption, String> getKexNegotiationResult(); + + /** + * Retrieve one of the negotiated values during the KEX stage + * + * @param paramType The request {@link KexProposalOption} value + * - ignored if {@code null} + * @return The negotiated parameter value - {@code null} if invalid + * parameter or no negotiated value. + * @see #getKexState() + */ + String getNegotiatedKexParameter(KexProposalOption paramType); + + /** * @return {@code true} if session has successfully completed the authentication phase */ boolean isAuthenticated(); diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/Session.java b/sshd-core/src/main/java/org/apache/sshd/common/session/Session.java index 3c7f26e..7943e6a 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/session/Session.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/session/Session.java @@ -40,7 +40,6 @@ import org.apache.sshd.common.io.IoSession; import org.apache.sshd.common.io.IoWriteFuture; import org.apache.sshd.common.io.PacketWriter; import org.apache.sshd.common.kex.KexFactoryManager; -import org.apache.sshd.common.kex.KexProposalOption; import org.apache.sshd.common.kex.KeyExchange; import org.apache.sshd.common.mac.MacInformation; import org.apache.sshd.common.util.buffer.Buffer; @@ -77,16 +76,6 @@ public interface Session } /** - * Retrieve one of the negotiated values during the KEX stage - * - * @param paramType The request {@link KexProposalOption} value - * - ignored if {@code null} - * @return The negotiated parameter value - {@code null} if invalid - * parameter or no negotiated value - */ - String getNegotiatedKexParameter(KexProposalOption paramType); - - /** * Retrieves current cipher information - <B>Note:</B> may change if * key re-exchange executed * 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 8e1c947..ef345fe 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 @@ -132,8 +132,12 @@ public abstract class AbstractSession extends SessionHelper { protected String clientVersion; // if empty then means not-initialized protected final Map<KexProposalOption, String> serverProposal = new EnumMap<>(KexProposalOption.class); + protected final Map<KexProposalOption, String> unmodServerProposal = Collections.unmodifiableMap(serverProposal); protected final Map<KexProposalOption, String> clientProposal = new EnumMap<>(KexProposalOption.class); + protected final Map<KexProposalOption, String> unmodClientProposal = Collections.unmodifiableMap(clientProposal); protected final Map<KexProposalOption, String> negotiationResult = new EnumMap<>(KexProposalOption.class); + protected final Map<KexProposalOption, String> unmodNegotiationResult = Collections.unmodifiableMap(negotiationResult); + protected KeyExchange kex; protected Boolean firstKexPacketFollows; protected final AtomicReference<KexState> kexState = new AtomicReference<>(KexState.UNKNOWN); @@ -226,22 +230,42 @@ public abstract class AbstractSession extends SessionHelper { } @Override + public Map<KexProposalOption, String> getServerKexProposals() { + return unmodServerProposal; + } + + @Override public String getClientVersion() { return clientVersion; } @Override + public Map<KexProposalOption, String> getClientKexProposals() { + return unmodClientProposal; + } + + @Override public KeyExchange getKex() { return kex; } @Override + public KexState getKexState() { + return kexState.get(); + } + + @Override public byte[] getSessionId() { // return a clone to avoid anyone changing the internal value return NumberUtils.isEmpty(sessionId) ? sessionId : sessionId.clone(); } @Override + public Map<KexProposalOption, String> getKexNegotiationResult() { + return unmodNegotiationResult; + } + + @Override public String getNegotiatedKexParameter(KexProposalOption paramType) { if (paramType == null) { return null; @@ -1395,8 +1419,8 @@ public abstract class AbstractSession extends SessionHelper { * @return The negotiated options {@link Map} */ protected Map<KexProposalOption, String> negotiate() { - Map<KexProposalOption, String> c2sOptions = Collections.unmodifiableMap(clientProposal); - Map<KexProposalOption, String> s2cOptions = Collections.unmodifiableMap(serverProposal); + Map<KexProposalOption, String> c2sOptions = getClientKexProposals(); + Map<KexProposalOption, String> s2cOptions = getServerKexProposals(); signalNegotiationStart(c2sOptions, s2cOptions); Map<KexProposalOption, String> guess = new EnumMap<>(KexProposalOption.class);
