Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.sshtools.synergy.ssh.TransportProtocol Maven / Gradle / Ivy
package com.sshtools.synergy.ssh;
/*-
* #%L
* Common API
* %%
* Copyright (C) 2002 - 2024 JADAPTIVE Limited
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* .
* #L%
*/
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.UUID;
import java.util.Vector;
import com.sshtools.common.events.Event;
import com.sshtools.common.events.EventCodes;
import com.sshtools.common.events.EventServiceImplementation;
import com.sshtools.common.logger.Log;
import com.sshtools.common.logger.Log.Level;
import com.sshtools.common.nio.IdleStateListener;
import com.sshtools.common.nio.WriteOperationRequest;
import com.sshtools.common.policy.SignaturePolicy;
import com.sshtools.common.ssh.ConnectionAwareTask;
import com.sshtools.common.ssh.ExecutorOperationQueues;
import com.sshtools.common.ssh.ExecutorOperationSupport;
import com.sshtools.common.ssh.SshException;
import com.sshtools.common.ssh.components.ComponentManager;
import com.sshtools.common.ssh.components.Digest;
import com.sshtools.common.ssh.components.SshCipher;
import com.sshtools.common.ssh.components.SshHmac;
import com.sshtools.common.ssh.components.SshPublicKey;
import com.sshtools.common.ssh.components.jce.ChaCha20Poly1305;
import com.sshtools.common.ssh.compression.SshCompression;
import com.sshtools.common.sshd.SshMessage;
import com.sshtools.common.util.ByteArrayReader;
import com.sshtools.common.util.ByteArrayWriter;
import com.sshtools.common.util.UnsignedInteger64;
import com.sshtools.common.util.Utils;
import com.sshtools.synergy.nio.ConnectRequestFuture;
import com.sshtools.synergy.nio.DisconnectRequestFuture;
import com.sshtools.synergy.nio.ProtocolEngine;
import com.sshtools.synergy.nio.SocketConnection;
import com.sshtools.synergy.nio.SocketWriteCallback;
import com.sshtools.synergy.ssh.components.SshKeyExchange;
/**
* This class implements the SSH Transport Protocol for the SSHD server.
*/
public abstract class TransportProtocol
extends ExecutorOperationSupport
implements ProtocolEngine, IdleStateListener, SshTransport {
/**
* Character set encoding. All input/output strings created by the API are
* created with this encoding. The default is "UTF-8" and it may be changed,
* the results however are unknown.
*/
public static String CHARSET_ENCODING = "UTF-8";
SecureRandom rnd = new SecureRandom();
byte[] incomingSwap;
protected String localIdentification = "SSH-2.0-";
protected StringBuffer remoteIdentification = new StringBuffer();
protected boolean receivedRemoteIdentification = false;
protected boolean sentLocalIdentification = false;
boolean postedIdentification = false;
protected byte[] localkex;
protected byte[] remotekex;
protected byte[] sessionIdentifier;
protected UUID uuid;
protected boolean hasExtensionCapability = false;
protected boolean enableExtensionCapability = true;
LinkedList outgoingQueue = new LinkedList();
LinkedList kexQueue = new LinkedList();
protected Service activeService;
List transportListeners = new ArrayList<>();
List idleListeners = new ArrayList<>();
// Message type numbers
static final int SSH_MSG_DISCONNECT = 1;
static final int SSH_MSG_IGNORE = 2;
static final int SSH_MSG_UNIMPLEMENTED = 3;
static final int SSH_MSG_DEBUG = 4;
protected static final int SSH_MSG_SERVICE_REQUEST = 5;
public static final int SSH_MSG_SERVICE_ACCEPT = 6;
public static final int SSH_MSG_EXT_INFO = 7;
static final int SSH_MSG_KEX_INIT = 20;
static final int SSH_MSG_NEWKEYS = 21;
// Message processing variables
boolean expectPacket = true;
int expectedBytes = 0;
byte[] payloadIncoming;
byte[] packet;
int offsetIncoming = 0;
int numOutgoingBytesSinceKEX;
int numOutgoingPacketsSinceKEX;
int numIncomingBytesSinceKEX;
int numIncomingPacketsSinceKEX;
long lastActivity = System.currentTimeMillis();
long lastIdleEvent = System.currentTimeMillis();
boolean closed = false;
protected boolean completedFirstKeyExchange = false;
protected Date disconnectStarted = null;
private static final String STRICT_KEX_CLIENT = "[email protected] ";
private static final String STRICT_KEX_SERVER = "[email protected] ";
boolean isKexStrict = false;
boolean hasFirstNewKeys = false;
protected void transferState(TransportProtocol extends SshContext> transport) {
transport.localIdentification = localIdentification;
transport.remoteIdentification = remoteIdentification;
transport.receivedRemoteIdentification = true;
transport.sentLocalIdentification = true;
transport.sessionIdentifier = sessionIdentifier;
transport.uuid = uuid;
transport.currentState = currentState;
transport.lastActivity = lastActivity;
transport.outgoingQueue.addAll(outgoingQueue);
transport.kexQueue.addAll(kexQueue);
transport.socketConnection = socketConnection;
transport.postedIdentification = postedIdentification;
transport.onSocketConnect(socketConnection);
receivedRemoteIdentification = false;
currentState = DISCONNECTED;
}
public ConnectRequestFuture getConnectFuture() {
return connectFuture;
}
public DisconnectRequestFuture getDisconnectFuture() {
return disconnectFuture;
}
/**
* Protocol state: Negotation of the protocol version
*/
public final static int NEGOTIATING_PROTOCOL = 1;
/**
* Protocol state: The protocol is performing key exchange
*/
public final static int PERFORMING_KEYEXCHANGE = 2;
/**
* Protocol state: The transport protocol is connected and services can be
* started or may already be active.
*/
public final static int CONNECTED = 3;
/**
* Protocol state: The transport protocol has disconnected.
*
* @see #getLastError()
*/
public final static int DISCONNECTED = 4;
int currentState = TransportProtocol.NEGOTIATING_PROTOCOL;
SshKeyExchange keyExchange;
SshCipher encryption;
SshCipher decryption;
SshHmac outgoingMac;
SshHmac incomingMac;
SshCompression outgoingCompression;
SshCompression incomingCompression;
protected SshPublicKey hostKey;
// C=Client
// S=Server
protected String cipherCS;
protected String cipherSC;
protected String macCS;
protected String macSC;
protected String compressionCS;
protected String compressionSC;
protected String keyExchangeAlgorithm;
protected String publicKey;
String remoteKeyExchanges;
String remotePublicKeys;
String remoteCiphersCS;
String remoteCiphersSC;
String remoteCSMacs;
String remoteSCMacs;
String remoteCSCompressions;
String remoteSCCompressions;
long outgoingSequence = 0;
long incomingSequence = 0;
long outgoingBytes = 0;
long incomingBytes = 0;
Object kexlockIn = new Object();
Object kexlockOut = new Object();
boolean queuedKexInit = false;
boolean sentKexInit = false;
protected Connection con;
/** Disconnect reason: The host is not allowed */
public final static int HOST_NOT_ALLOWED = 1;
/** Disconnect reason: A protocol error occurred */
public final static int PROTOCOL_ERROR = 2;
/** Disconnect reason: Key exchange failed */
public final static int KEY_EXCHANGE_FAILED = 3;
/** Disconnect reason: Reserved */
public final static int RESERVED = 4;
/** Disconnect reason: An error occurred verifying the MAC */
public final static int MAC_ERROR = 5;
/** Disconnect reason: A compression error occurred */
public final static int COMPRESSION_ERROR = 6;
/** Disconnect reason: The requested service is not available */
public final static int SERVICE_NOT_AVAILABLE = 7;
/** Disconnect reason: The protocol version is not supported */
public final static int PROTOCOL_VERSION_NOT_SUPPORTED = 8;
/** Disconnect reason: The host key supplied could not be verified */
public final static int HOST_KEY_NOT_VERIFIABLE = 9;
/** Disconnect reason: The connection was lost */
public final static int CONNECTION_LOST = 10;
/** Disconnect reason: The application disconnected */
public final static int BY_APPLICATION = 11;
/** Disconnect reason: Too many connections, try later */
public final static int TOO_MANY_CONNECTIONS = 12;
/** Disconnect reason: Authentication was cancelled */
public final static int AUTH_CANCELLED_BY_USER = 13;
/** Disconnect reason: No more authentication methods are available */
public final static int NO_MORE_AUTH_METHODS_AVAILABLE = 14;
/** Disconnect reason: The user's name is illegal */
public final static int ILLEGAL_USER_NAME = 15;
private static final Integer ACTIVE_SERVICE_IN = ExecutorOperationQueues.generateUniqueQueue("TransportProtocol.activeService.in");
IgnoreMessage ignoreMessage;
long lastKeepAlive = 0;
protected T sshContext;
protected SocketConnection socketConnection;
public static Object lock = new Object();
Date started = new Date();
ConnectRequestFuture connectFuture;
DisconnectRequestFuture disconnectFuture = new DisconnectRequestFuture();
AuthenticatedFuture authenticatedFuture = new AuthenticatedFuture(this);
/**
* Create a default transport protocol instance in CLIENT_MODE.
*
* @throws IOException
*/
public TransportProtocol(T sshContext, ConnectRequestFuture connectFuture) {
super("transport-protocol");
this.sshContext = sshContext;
this.ignoreMessage = new IgnoreMessage();
this.connectFuture = connectFuture;
this.uuid = UUID.randomUUID();
this.incomingSwap = new byte[sshContext.getMaximumPacketLength()];
}
public SocketConnection getSocketConnection() {
return socketConnection;
}
public void addEventListener(TransportProtocolListener listener) {
if (listener != null)
transportListeners.add(listener);
}
public SocketAddress getRemoteAddress() {
return socketConnection.getRemoteAddress();
}
/**
* Returns the remote port of the connected socket.
*
* @return int
*/
public int getRemotePort() {
return socketConnection.getPort();
}
public T getContext() {
return sshContext;
}
public Connection getConnection() {
return con;
}
protected abstract boolean canConnect(SocketConnection connection);
protected abstract void onConnected();
protected abstract void onDisconnected();
public void onSocketConnect(SocketConnection connection) {
this.socketConnection = connection;
if(Log.isInfoEnabled()) {
Log.info("Connnection created {} on interface {}",
socketConnection.getRemoteAddress().toString(),
socketConnection.getLocalAddress().toString());
}
if (!canConnect(connection)) {
if(Log.isDebugEnabled())
Log.debug("Access denied by TransportProtocol.canConnect");
EventServiceImplementation.getInstance().fireEvent(
(new Event(this, EventCodes.EVENT_CONNECTED, new IOException("Access Denied")))
.addAttribute(
EventCodes.ATTRIBUTE_CONNECTION,
con)
.addAttribute(
EventCodes.ATTRIBUTE_OPERATION_STARTED,
started)
.addAttribute(
EventCodes.ATTRIBUTE_OPERATION_FINISHED,
new Date()));
connection.closeConnection(false);
return;
}
connection.getIdleStates().register(this);
onConnected();
if (!sentLocalIdentification) {
EventServiceImplementation.getInstance().fireEvent(
(new Event(this, EventCodes.EVENT_CONNECTED, true))
.addAttribute(
EventCodes.ATTRIBUTE_CONNECTION,
con)
.addAttribute(
EventCodes.ATTRIBUTE_OPERATION_STARTED,
started)
.addAttribute(
EventCodes.ATTRIBUTE_OPERATION_FINISHED,
new Date()));
this.localIdentification += sshContext.getSoftwareVersionComments() + "\r\n";
// Send our identification String
if(!sshContext.isHttpRedirect()) {
sendLocalIdentification(false, null);
}
}
}
private synchronized void sendLocalIdentification() {
sendLocalIdentification(false, null);
}
private synchronized void sendLocalIdentification(final boolean doHttpRedirect, final String hostname) {
if(!postedIdentification) {
postedIdentification = true;
postMessage(new SshMessage() {
public boolean writeMessageIntoBuffer(ByteBuffer buf) {
try {
if(doHttpRedirect){
String httpRedirect = "HTTP/1.1 302 Moved Location\r\n"
+ "Location: " + sshContext.getHttpRedirectUrl().replace("${hostname}", hostname) + "/\r\n"
+ "Connection: close\r\n"
+ "Content-Type: text/plain\r\n"
+ "Content-Length: " + localIdentification.getBytes("UTF-8").length + "\r\n\r\n";
buf.put(httpRedirect.getBytes("UTF-8"));
}
buf.put(localIdentification.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("UTF-8 is not supported!!");
}
return true;
}
public void messageSent(Long sequenceNo) {
if(Log.isDebugEnabled())
Log.debug("Sent local identification string "
+ localIdentification.trim());
sentLocalIdentification = true;
if (receivedRemoteIdentification && canSendKeyExchangeInit())
sendKeyExchangeInit();
}
});
}
}
protected boolean canSendKeyExchangeInit() {
return true;
}
/**
* Called when the socket channel is reported to be ready for reading.
*
*/
public boolean onSocketRead(ByteBuffer incomingData) {
if(Log.isTraceEnabled())
Log.trace("Processing APPLICATION READ data");
boolean wantsWrite = false;
try {
// What's the protocol's state
switch (currentState) {
case TransportProtocol.NEGOTIATING_PROTOCOL:
negotiateProtocol(incomingData);
break;
case TransportProtocol.PERFORMING_KEYEXCHANGE:
case TransportProtocol.CONNECTED:
wantsWrite = processBinaryPackets(incomingData);
break;
}
} catch (Throwable ex) {
connectFuture.setLastError(ex);
if(Log.isInfoEnabled()) {
Log.info("Read error from {} {}",
con.getRemoteIPAddress(),
ex.getMessage());
}
if(Log.isDebugEnabled())
Log.debug("Connection closed on socket read", ex);
socketConnection.closeConnection();
}
return wantsWrite;
}
/**
* Get if disconnecting has started but not yet complete.
*
* @return disconnecting
*/
public boolean isDisonnecting() {
return isConnected() && disconnectStarted != null;
}
/**
* Determine if the protocol is still connected
*
* @return boolean
*/
public boolean isConnected() {
return (currentState == NEGOTIATING_PROTOCOL
|| currentState == PERFORMING_KEYEXCHANGE || currentState == CONNECTED);
}
/**
* Negotiate the protocol version with the client
*
* @throws IOException
*/
void negotiateProtocol(ByteBuffer applicationData) throws IOException {
if (receivedRemoteIdentification) {
processBinaryPackets(applicationData);
return;
}
char c = 0;
while (applicationData.remaining() > 0) {
c = (char) applicationData.get();
if (c == '\n') {
if(remoteIdentification.toString().startsWith("SSH-")) {
if(startBinaryProtocol()) {
if (sentLocalIdentification) {
if(canSendKeyExchangeInit()) {
sendKeyExchangeInit();
}
// Make sure that any remaining data is
// processed by the binary packet protocol
processBinaryPackets(applicationData);
}
}
return;
}
try {
processNegotiationString(remoteIdentification.toString().trim());
} catch(Throwable t) {
if(Log.isDebugEnabled())
Log.debug("Bad value in negotiation string!", t);
socketConnection.closeConnection();
return;
}
remoteIdentification.setLength(0);
continue;
}
remoteIdentification.append(c);
}
}
protected void processNegotiationString(String value) throws IOException {
}
protected boolean startBinaryProtocol() {
if(Log.isInfoEnabled()) {
Log.info("Connnection {}:{} identifies itself as {}",
con.getRemoteIPAddress(), con.getRemotePort(),
remoteIdentification.toString().trim());
}
sendLocalIdentification(false, null);
// Check the remote client version
String tmp = remoteIdentification.toString();
if (!tmp.startsWith("SSH-2.0-") && !tmp.startsWith("SSH-1.99-")) {
if(Log.isDebugEnabled())
Log.debug("Remote client reported an invalid protocol version!");
socketConnection.closeConnection();
return false;
}
if(Log.isDebugEnabled())
Log.debug("Remote client version OK");
receivedRemoteIdentification = true;
EventServiceImplementation.getInstance().fireEvent(
(new Event(this, EventCodes.EVENT_NEGOTIATED_PROTOCOL, true))
.addAttribute(
EventCodes.ATTRIBUTE_CONNECTION,
con)
.addAttribute(
EventCodes.ATTRIBUTE_OPERATION_STARTED,
started)
.addAttribute(
EventCodes.ATTRIBUTE_OPERATION_FINISHED,
new Date()));
onRemoteIdentificationReceived(tmp);
return true;
}
protected void onRemoteIdentificationReceived(String remoteIdentification) {
}
/**
* Process data into actual SSH messages
*/
boolean processBinaryPackets(ByteBuffer applicationData) {
boolean requiresWriteOperation = false;
boolean hasMessage = false;
try {
while (isConnected()
&& ((expectPacket && (applicationData.remaining() > incomingCipherLength)) || (expectedBytes > 0 && applicationData
.hasRemaining())) && !requiresWriteOperation) {
/**
* Lock the key exchange variables, we do not want to change
* these whilst we are decrypting and authenticating a message.
* We will release this before we process the message to ensure
* other threads do not get locked up sending data.
*/
synchronized (kexlockIn) {
if(decryption!=null && decryption instanceof ChaCha20Poly1305) {
hasMessage = decodeChaCha20Poly1305Format(applicationData);
} else if(incomingMac!=null && incomingMac.isETM()) {
hasMessage = decodeETMPacketFormat(applicationData);
} else {
hasMessage = decodeOriginalPacketFormat(applicationData);
}
}
/**
* Process the message outside of kexlock. This is to ensure
* that any thread attempting to send a message on this
* connection can still send without causing a lockup.
*/
if (hasMessage) {
// Process the message
try {
processMessage(payloadIncoming, incomingSequence++);
} catch (WriteOperationRequest x) {
requiresWriteOperation = true;
} finally {
// Update stats and sequence
if (incomingSequence >= 4294967296L) {
incomingSequence = 0;
}
incomingBytes += payloadIncoming.length;
numIncomingBytesSinceKEX += payloadIncoming.length;
numIncomingPacketsSinceKEX++;
// if done alot of communication then change keys
if (numIncomingBytesSinceKEX >= getContext()
.getKeyExchangeTransferLimit()
|| numIncomingPacketsSinceKEX >= getContext()
.getKeyExchangePacketLimit()) {
sendKeyExchangeInit();
}
// Reset variables for a new message
expectPacket = true;
expectedBytes = 0;
offsetIncoming = 0;
payloadIncoming = null;
hasMessage = false;
}
}
}
if(Log.isTraceEnabled())
Log.trace("Transport protocol "
+ (expectPacket ? "is expecting another packet"
: "still has "
+ expectedBytes
+ " bytes of data to complete packet with "
+ offsetIncoming
+ " bytes already received"
+ " requiresWrite="
+ requiresWriteOperation));
} catch (Throwable ex) {
ex.printStackTrace();
if(Log.isInfoEnabled()) {
Log.info("Transport error {} {}",
con.getRemoteIPAddress(),
ex.getMessage());
}
if(Log.isDebugEnabled())
Log.debug("Connection Error", ex);
if (isConnected())
disconnect(TransportProtocol.PROTOCOL_ERROR,
"The application encountered an error");
requiresWriteOperation = true;
}
return requiresWriteOperation;
}
private boolean decodeChaCha20Poly1305Format(ByteBuffer applicationData) throws IOException {
ChaCha20Poly1305 cipher = (ChaCha20Poly1305) decryption;
if (expectPacket) {
/**
* We need to decrypt the initial binary packet header
* to determine how much data we are expecting
*/
applicationData.get(incomingSwap, 0, 4);
// Work out the message length, payload, padding and
// remaining bytes
msglen = (int) cipher.readPacketLength(incomingSwap, new UnsignedInteger64(incomingSequence));
if (msglen <= 0)
throw new IOException(
"Client sent invalid message length of "
+ msglen + "!");
if ((msglen + 4) < 0
|| (msglen + 4) > sshContext
.getMaximumPacketLength()) {
disconnect(
TransportProtocol.PROTOCOL_ERROR,
"Incoming packet length "
+ msglen
+ ((msglen + 4) < 0 ? " is too small"
: " exceeds maximum supported length of "
+ sshContext
.getMaximumPacketLength()));
throw new IOException("Disconnected");
}
remaining = msglen;
expectedBytes = remaining + incomingMacLength;
expectPacket = false;
offsetIncoming += 4;
}
/**
* If we have more data to get from the network do it now
*/
if (!expectPacket && applicationData.remaining() > 0) {
/**
* Determine how many bytes to process this time
*/
int count = (expectedBytes > applicationData
.remaining() ? applicationData.remaining()
: expectedBytes);
/**
* Now get the data
*/
applicationData.get(incomingSwap, offsetIncoming, count);
/**
* Update our position in the swap buffer and the number
* of expected bytes
*/
expectedBytes -= count;
offsetIncoming += count;
/**
* Only complete the message once we have all the bytes
*/
if (expectedBytes == 0) {
// Record the packet legth
// int packetlen = msglen;
decryption.transform(incomingSwap,
4, incomingSwap,
4, remaining + incomingMacLength);
padlen = (incomingSwap[4] & 0xFF);
payloadIncoming = new byte[msglen - padlen - 1];
// Copy the payload into the final output buffer
System.arraycopy(incomingSwap, 5, payloadIncoming,
0, msglen - padlen - 1);
// Uncompress the message payload if necersary
if (incomingCompression != null) {
payloadIncoming = incomingCompression
.uncompress(payloadIncoming, 0,
payloadIncoming.length);
}
return true;
}
}
return false;
}
private boolean decodeETMPacketFormat(ByteBuffer applicationData) throws IOException {
if (expectPacket) {
/**
* We need to decrypt the initial binary packet header
* to determine how much data we are expecting
*/
applicationData.get(incomingSwap, offsetIncoming,
4);
// Work out the message length, payload, padding and
// remaining bytes
msglen = (int) ByteArrayReader.readInt(incomingSwap,
0);
if (msglen <= 0)
throw new IOException(
"Client sent invalid message length of "
+ msglen + "!");
if ((msglen + 4) < 0
|| (msglen + 4) > sshContext
.getMaximumPacketLength()) {
disconnect(
TransportProtocol.PROTOCOL_ERROR,
"Incoming packet length "
+ msglen
+ ((msglen + 4) < 0 ? " is too small"
: " exceeds maximum supported length of "
+ sshContext
.getMaximumPacketLength()));
throw new IOException("Disconnected");
}
remaining = msglen;
expectedBytes = remaining + incomingMacLength;
expectPacket = false;
offsetIncoming += 4;
}
/**
* If we have more data to get from the network do it now
*/
if (!expectPacket && applicationData.remaining() > 0) {
/**
* Determine how many bytes to process this time
*/
int count = (expectedBytes > applicationData
.remaining() ? applicationData.remaining()
: expectedBytes);
/**
* Now get the data
*/
applicationData
.get(incomingSwap, offsetIncoming, count);
/**
* Update our position in the swap buffer and the number
* of expected bytes
*/
expectedBytes -= count;
offsetIncoming += count;
/**
* Only complete the message once we have all the bytes
*/
if (expectedBytes == 0) {
// Record the packet legth
int packetlen = msglen;
// Verify the message
if (incomingMac != null) {
if (!incomingMac.verify(incomingSequence,
incomingSwap, 0, packetlen+4,
incomingSwap, packetlen+4)) {
throw new IOException(
"Corrupt Mac on input");
}
}
// Decrypt the data now that we have it all
if (decryption != null) {
decryption.transform(incomingSwap,
4, incomingSwap,
4, remaining);
}
padlen = (incomingSwap[4] & 0xFF);
payloadIncoming = new byte[msglen - padlen - 1];
// Copy the payload into the final output buffer
System.arraycopy(incomingSwap, 5, payloadIncoming,
0, msglen - padlen - 1);
// Uncompress the message payload if necersary
if (incomingCompression != null) {
payloadIncoming = incomingCompression
.uncompress(payloadIncoming, 0,
payloadIncoming.length);
}
return true;
}
}
return false;
}
private boolean decodeOriginalPacketFormat(ByteBuffer applicationData) throws IOException {
if (expectPacket) {
/**
* We need to decrypt the initial binary packet header
* to determine how much data we are expecting
*/
applicationData.get(incomingSwap, offsetIncoming,
incomingCipherLength);
if (decryption != null && !decryption.isMAC()) {
decryption.transform(incomingSwap, offsetIncoming,
incomingSwap, offsetIncoming,
incomingCipherLength);
}
// Work out the message length, payload, padding and
// remaining bytes
msglen = (int) ByteArrayReader.readInt(incomingSwap,
offsetIncoming);
if (msglen <= 0)
throw new IOException(
"Client sent invalid message length of "
+ msglen + "!");
if ((msglen + 4) < 0
|| (msglen + 4) > sshContext
.getMaximumPacketLength()) {
disconnect(
TransportProtocol.PROTOCOL_ERROR,
"Incoming packet length "
+ msglen
+ ((msglen + 4) < 0 ? " is too small"
: " exceeds maximum supported length of "
+ sshContext
.getMaximumPacketLength()));
throw new IOException("Disconnected");
}
padlen = (incomingSwap[4] & 0xFF);
remaining = (msglen - (incomingCipherLength - 4));
expectedBytes = remaining + incomingMacLength;
// Create our final storage buffer for the decrpyted
// message
expectPacket = false;
offsetIncoming += incomingCipherLength;
}
/**
* If we have more data to get from the network do it now
*/
if (!expectPacket && applicationData.remaining() > 0) {
/**
* Determine how many bytes to process this time
*/
int count = (expectedBytes > applicationData
.remaining() ? applicationData.remaining()
: expectedBytes);
/**
* Now get the data
*/
applicationData
.get(incomingSwap, offsetIncoming, count);
/**
* Update our position in the swap buffer and the number
* of expected bytes
*/
expectedBytes -= count;
offsetIncoming += count;
/**
* Only complete the message once we have all the bytes
*/
if (expectedBytes == 0) {
// Record the packet legth
int packetlen = msglen + 4;
// Decrypt the data now that we have it all
if (decryption != null) {
if(!decryption.isMAC()) {
decryption.transform(incomingSwap,
incomingCipherLength, incomingSwap,
incomingCipherLength, remaining);
} else {
decryption.transform(incomingSwap, 0, incomingSwap, 0, msglen + 4 + decryption.getMacLength());
padlen = (incomingSwap[4] & 0xFF);
}
}
// Verify the message
if (incomingMac != null) {
if (!incomingMac.verify(incomingSequence,
incomingSwap, 0, packetlen,
incomingSwap, packetlen)) {
throw new IOException(
"Corrupt Mac on input");
}
}
payloadIncoming = new byte[msglen - padlen - 1];
// Copy the payload into the final output buffer
System.arraycopy(incomingSwap, 5, payloadIncoming,
0, msglen - padlen - 1);
// Uncompress the message payload if necersary
if (incomingCompression != null) {
payloadIncoming = incomingCompression
.uncompress(payloadIncoming, 0,
payloadIncoming.length);
}
return true;
}
}
return false;
}
public boolean wantsToWrite() {
synchronized (kexlockOut) {
if (currentState == PERFORMING_KEYEXCHANGE
&& completedFirstKeyExchange) {
return kexQueue.size() > 0;
}
return outgoingQueue.size() > 0 || kexQueue.size() > 0;
}
}
public int getQueueSizes() {
synchronized (kexlockOut) {
return outgoingQueue.size() + kexQueue.size();
}
}
/**
* Called when the selector framework is idle. We take the opportunity to
* send an SSH_MSG_IGNORE message in the hope that we can detect any sockets
* that may have closed.
*/
public boolean idle() {
if (currentState == TransportProtocol.DISCONNECTED)
return true; // Remove from idle state manager
long idleTimeSeconds = (System.currentTimeMillis() - lastActivity) / 1000;
if(!hasCompletedKeyExchange()) {
if(con.getContext().getIdleAuthenticationTimeoutSeconds() > 0 &&
con.getContext().getIdleAuthenticationTimeoutSeconds() < idleTimeSeconds) {
if(Log.isDebugEnabled()) {
Log.debug("Idle time of {} seconds exceeded threshold of {} seconds",
idleTimeSeconds,
con.getContext().getIdleAuthenticationTimeoutSeconds());
}
disconnect(BY_APPLICATION,String.format("Remote exceeded idle timeout of %d seconds for unauthenticated connections",
con.getContext().getIdleAuthenticationTimeoutSeconds()));
return true;
}
}
if(currentState == CONNECTED) {
if(con.getContext().getIdleConnectionTimeoutSeconds() > 0 &&
con.getContext().getIdleConnectionTimeoutSeconds() < idleTimeSeconds) {
if(Log.isDebugEnabled()) {
Log.debug("Idle time of {} seconds exceeded threshold of {} seconds",
idleTimeSeconds,
con.getContext().getIdleConnectionTimeoutSeconds());
}
disconnect(BY_APPLICATION, String.format("Remote exceeded idle timeout of %d seconds for authenticated connections",
con.getContext().getIdleConnectionTimeoutSeconds()));
return true;
}
}
if (currentState == TransportProtocol.CONNECTED
|| currentState == TransportProtocol.PERFORMING_KEYEXCHANGE) {
if(getContext().isSendIgnorePacketOnIdle()) {
if (getContext().getKeepAliveInterval() > 0
&& idleTimeSeconds > getContext().getKeepAliveInterval()) {
long keepAliveSeconds = getContext().getKeepAliveInterval() + 1;
if (lastKeepAlive > 0) {
keepAliveSeconds = (System.currentTimeMillis() - lastKeepAlive) / 1000;
}
if (keepAliveSeconds > getContext().getKeepAliveInterval()) {
postMessage(ignoreMessage);
lastKeepAlive = System.currentTimeMillis();
}
}
}
}
if (activeService!=null && activeService.getIdleTimeoutSeconds() > 0) {
idleTimeSeconds = (System.currentTimeMillis() - lastIdleEvent) / 1000;
if(activeService!=null && idleTimeSeconds >= activeService.getIdleTimeoutSeconds()) {
lastIdleEvent = System.currentTimeMillis();
return activeService.idle();
}
}
return false;
}
/**
* Called when the socket channel is reported to be ready for writing.
*/
public SocketWriteCallback onSocketWrite(ByteBuffer outgoingMessage) {
if(Log.isTraceEnabled())
Log.debug("Processing APPLICATION WRITE event");
final SshMessage msg;
try {
final Long sequenceNo = outgoingSequence;
synchronized (kexlockOut) {
if ((kexQueue.size() > 0 || outgoingQueue.size() > 0)) {
// Get the next message and write into the buffer
if (currentState == PERFORMING_KEYEXCHANGE
&& completedFirstKeyExchange) {
if (kexQueue.size() > 0) {
msg = (SshMessage) kexQueue.getFirst();
if (msg.writeMessageIntoBuffer(outgoingMessage))
kexQueue.removeFirst();
} else {
// Simply return there are no key exchange messages
// to send
// socketConnection.setWriteState(wantsToWrite());
return null;
}
} else {
synchronized (outgoingQueue) {
msg = (SshMessage) outgoingQueue.getFirst();
if (msg.writeMessageIntoBuffer(outgoingMessage)) {
outgoingQueue.removeFirst();
}
}
}
if (currentState != TransportProtocol.NEGOTIATING_PROTOCOL) {
outgoingMessage.flip();
if(encryption!=null && encryption instanceof ChaCha20Poly1305) {
encodeChaCha20Poly1305FormatPacket(outgoingMessage);
} else if(outgoingMac!=null && outgoingMac.isETM()) {
encodeETMFormatPacket(outgoingMessage);
} else {
encodeOriginalFormatPacket(outgoingMessage);
}
numOutgoingBytesSinceKEX += outgoingMessage.position();
numOutgoingPacketsSinceKEX++;
outgoingSequence++;
if (outgoingSequence >= 4294967296L) {
outgoingSequence = 0;
}
}
} else {
msg = null;
}
// if sent lots of bytes or packets then change keys
if (numOutgoingBytesSinceKEX >= getContext()
.getKeyExchangeTransferLimit()
|| numOutgoingPacketsSinceKEX >= getContext()
.getKeyExchangePacketLimit()) {
sendKeyExchangeInit();
}
return new SocketWriteCallback() {
public void completedWrite() {
try {
if (msg != null) {
msg.messageSent(sequenceNo);
}
} catch (SshException e) {
Log.error("Failed during messageSent", e);
disconnect(PROTOCOL_ERROR, "Internal error");
}
}
};
} // End kexlock
} catch (Throwable ex) {
ex.printStackTrace();
if(Log.isInfoEnabled()) {
Log.info("Write error from {} {}",
con.getRemoteIPAddress(),
ex.getMessage());
}
if(Log.isDebugEnabled()) {
Log.debug("Connection closed on socket write", ex);
}
socketConnection.closeConnection();
return null;
}
}
private void encodeChaCha20Poly1305FormatPacket(ByteBuffer outgoingMessage) throws IOException {
ChaCha20Poly1305 cipher = (ChaCha20Poly1305) encryption;
byte[] payload = new byte[outgoingMessage.remaining()];
outgoingMessage.get(payload);
outgoingMessage.clear();
int padding = 4;
int cipherlen = 8;
// Compress the payload if necersary
if (outgoingCompression != null) {
payload = outgoingCompression.compress(payload, 0,
payload.length);
}
// Determine the padding length
padding += ((cipherlen - ((payload.length + 1 + padding) % cipherlen)) % cipherlen);
// Write the packet length field
outgoingMessage.put(cipher.writePacketLength(payload.length + 1 + padding, new UnsignedInteger64(outgoingSequence)));
// Write the padding length
outgoingMessage.put((byte) padding);
// Write the message payload
outgoingMessage.put(payload, 0, payload.length);
outgoingBytes += payload.length + padding + 1 + cipher.getMacLength() + 4;
// Create some random data for the padding
byte[] pad = new byte[padding];
rnd.nextBytes(pad);
// Write the padding
outgoingMessage.put(pad);
outgoingMessage.flip();
// Get the unencrypted packet data
byte[] packet = new byte[outgoingMessage.remaining() + encryption.getMacLength()];
outgoingMessage.get(packet, 0, outgoingMessage.remaining());
cipher.transform(packet, 4, packet, 4, packet.length-4);
// Reset the message
outgoingMessage.clear();
// Write the packet data
outgoingMessage.put(packet);
}
private void encodeETMFormatPacket(ByteBuffer outgoingMessage) throws IOException {
/**
* Wrap the message payload into the binary packet
* format
*/
byte[] payload = new byte[outgoingMessage.remaining()];
outgoingMessage.get(payload);
outgoingMessage.clear();
int padding = 4;
int cipherlen = 8;
// Determine the cipher length
if (encryption != null) {
cipherlen = encryption.getBlockSize();
}
// Compress the payload if necersary
if (outgoingCompression != null) {
payload = outgoingCompression.compress(payload, 0,
payload.length);
}
// Determine the padding length
padding += ((cipherlen - ((payload.length + 1 + padding) % cipherlen)) % cipherlen);
// Write the packet length field
outgoingMessage.putInt(payload.length + 1 + padding);
// Write the padding length
outgoingMessage.put((byte) padding);
// Write the message payload
outgoingMessage.put(payload, 0, payload.length);
outgoingBytes += payload.length + padding + 1;
// Create some random data for the padding
byte[] pad = new byte[padding];
rnd.nextBytes(pad);
// Write the padding
outgoingMessage.put(pad);
outgoingMessage.flip();
// Get the unencrypted packet data
byte[] packet;
if(encryption!=null && encryption.isMAC()) {
packet = new byte[outgoingMessage.remaining() + encryption.getMacLength()];
} else {
packet = new byte[outgoingMessage.remaining()];
}
outgoingMessage.get(packet);
byte[] mac = null;
// Perfrom encrpytion
if (encryption != null) {
encryption.transform(packet, 4, packet, 4, packet.length-4);
}
// Generate the MAC
if (outgoingMac != null) {
mac = new byte[outgoingMac.getMacLength()];
outgoingMac.generate(outgoingSequence, packet, 0,
packet.length, mac, 0);
}
// Reset the message
outgoingMessage.clear();
// Write the packet data
outgoingMessage.put(packet);
// Combine the packet and MAC
if (mac != null && mac.length > 0) {
outgoingMessage.put(mac);
outgoingBytes += mac.length;
}
}
private void encodeOriginalFormatPacket(ByteBuffer outgoingMessage) throws IOException {
/**
* Wrap the message payload into the binary packet
* format
*/
byte[] payload = new byte[outgoingMessage.remaining()];
outgoingMessage.get(payload);
outgoingMessage.clear();
if(Log.isTraceEnabled()) {
Log.raw(Level.TRACE, Utils.bytesToHex(payload, 0, payload.length, 32, true, true), true);
}
int padding = 4;
int cipherlen = 8;
// Determine the cipher length
if (encryption != null) {
cipherlen = encryption.getBlockSize();
}
// Compress the payload if necersary
if (outgoingCompression != null) {
payload = outgoingCompression.compress(payload, 0,
payload.length);
}
// Determine the padding length
if(encryption!=null && encryption.isMAC()) {
padding += ((cipherlen - ((payload.length + 1 + padding) % cipherlen)) % cipherlen);
} else {
padding += ((cipherlen - ((payload.length + 5 + padding) % cipherlen)) % cipherlen);
}
// Write the packet length field
int msglen = payload.length + 1 + padding;
outgoingMessage.putInt(msglen);
// Write the padding length
outgoingMessage.put((byte) padding);
// Write the message payload
outgoingMessage.put(payload, 0, payload.length);
outgoingBytes += payload.length + padding + 5;
// Create some random data for the padding
byte[] pad = new byte[padding];
rnd.nextBytes(pad);
// Write the padding
outgoingMessage.put(pad);
outgoingMessage.flip();
// Get the unencrypted packet data
byte[] packet;
if(encryption!=null && encryption.isMAC()) {
packet = new byte[outgoingMessage.remaining() + encryption.getMacLength()];
} else {
packet = new byte[outgoingMessage.remaining()];
}
outgoingMessage.get(packet, 0, outgoingMessage.remaining());
byte[] mac = null;
// Generate the MAC
if (outgoingMac != null) {
mac = new byte[outgoingMac.getMacLength()];
outgoingMac.generate(outgoingSequence, packet, 0,
packet.length, mac, 0);
}
// Perfrom encrpytion
if (encryption != null) {
if(encryption.isMAC()) {
encryption.transform(packet, 0, packet, 0, msglen+4);
} else {
encryption.transform(packet);
}
}
// Reset the message
outgoingMessage.clear();
// Write the packet data
outgoingMessage.put(packet);
// Combine the packet and MAC
if (mac != null && mac.length > 0) {
outgoingMessage.put(mac);
outgoingBytes += mac.length;
}
}
public int getState() {
return currentState;
}
/**
* Returns the local address to which the remote socket is connected.
*
* @return InetAddress
*/
public SocketAddress getLocalAddress() {
return socketConnection.getLocalAddress();
}
/**
* Returns the local port to which the remote socket is connected.
*
* @return int
*/
public int getLocalPort() {
return socketConnection.getLocalPort();
}
public String getRemoteIdentification() {
return remoteIdentification.toString();
}
public String getUUID() {
return uuid.toString();
}
// protected abstract SocketAddress getConnectionAddress();
/**
* Disconnect from the remote host. No more messages can be sent after this
* method has been called.
*
* @param reason
* @param description
* @throws IOException
*/
public void disconnect(int reason, String description) {
if (description == null)
description = "Failure";
disconnectStarted = new Date();
if(Log.isInfoEnabled()) {
Log.info("Disconnect {} {}",
con.getRemoteIPAddress(),
description);
}
postMessage(new DisconnectMessage(reason, description, true));
}
/**
* Disconnects everything internally
*/
public void onSocketClose() {
synchronized (this) {
if (!closed) {
Connection connection = getConnection();
closed = true;
if(Log.isInfoEnabled()) {
Log.info("Connection closed {}",
con.getRemoteIPAddress());
}
if (disconnectStarted == null)
disconnectStarted = new Date();
if(Log.isDebugEnabled())
Log.debug("Performing internal disconnect {}", getUUID());
setTransportState(TransportProtocol.DISCONNECTED);
disconnectFuture.disconnected();
if (socketConnection != null)
socketConnection.getIdleStates().remove(TransportProtocol.this);
if (activeService != null) {
if(Log.isDebugEnabled())
Log.debug("Stopping the active service");
activeService.stop();
}
if(Log.isDebugEnabled())
Log.debug("Logging off user");
for (Iterator it = transportListeners
.iterator(); it.hasNext();) {
it.next().onDisconnect(TransportProtocol.this);
}
if(Log.isDebugEnabled()) {
Log.debug("Submitting transport cleanup to executor service");
}
if(connection != null) {
/* Connection may be null if a socket connection was made by the protocol never started */
addTask(EVENTS, new ConnectionTaskWrapper(connection, new Runnable() {
public void run() {
synchronized (lock) {
cleanupOperations(new ConnectionAwareTask(con) {
protected void doTask() {
EventServiceImplementation
.getInstance()
.fireEvent(
new Event(
this,
EventCodes.EVENT_DISCONNECTED,
true)
.addAttribute(
EventCodes.ATTRIBUTE_CONNECTION,
con)
.addAttribute(
EventCodes.ATTRIBUTE_OPERATION_STARTED,
disconnectStarted)
.addAttribute(
EventCodes.ATTRIBUTE_OPERATION_FINISHED,
new Date()));
disconnected();
onDisconnected();
}
});
}
}
}));
}
}
}
}
/**
* Gets the secure random number generator for this transport.
*
* @return the secure RND
*/
public SecureRandom getRND() {
return rnd;
}
void setTransportState(int transportState) {
currentState = transportState;
}
protected abstract void initializeKeyExchange(SshKeyExchange keyExchange,
boolean firstPacketFollows,
boolean useFirstPacket) throws IOException, SshException;
int incomingCipherLength = 8;
int incomingMacLength = 0;
int msglen = 0;
int padlen = 0;
int remaining = 0;
byte[] initial;
/**
* Perform key exchange
*
* @param msg
* @throws IOException
*/
@SuppressWarnings("unchecked")
void performKeyExchange(byte[] msg) throws IOException,
WriteOperationRequest {
ByteArrayReader bar = null;
if(!postedIdentification) {
sendLocalIdentification();
}
checkAlgorithms();
try {
// Set the state to performing key exchange now that we have both
// msgs
currentState = TransportProtocol.PERFORMING_KEYEXCHANGE;
// Extract the remote's side kex init taking away the header and
// padding
remotekex = msg;
bar = new ByteArrayReader(remotekex, 0, remotekex.length);
bar.skip(17);
remoteKeyExchanges = checkValidString("key exchange",
bar.readString());
remotePublicKeys = checkValidString("public key",
bar.readString());
remoteCiphersCS = checkValidString("client->server cipher",
bar.readString());
remoteCiphersSC = checkValidString("server->client cipher",
bar.readString());
remoteCSMacs = bar.readString();
remoteSCMacs = bar.readString();
remoteCSCompressions = bar.readString();
remoteSCCompressions = bar.readString();
hasExtensionCapability = remoteKeyExchanges.contains("ext-info-");
if(!completedFirstKeyExchange) {
isKexStrict = remoteKeyExchanges.contains(isServerMode() ? STRICT_KEX_CLIENT : STRICT_KEX_SERVER);
}
// Read language strings and ignore as don't support other languages
String lang = bar.readString();
lang = bar.readString();
boolean firstPacketFollows = (bar.read() != 0);
onKeyExchangeInit();
// Send our kex init (this will only be sent if needed to)
sendKeyExchangeInit();
// Determine the negotiated key exchange
String localKeyExchanges = sshContext.supportedKeyExchanges().list(
sshContext.getPreferredKeyExchange());
String localCiphersCS = sshContext
.supportedCiphersCS()
.list(sshContext
.getPreferredCipherCS());
String localCiphersSC = sshContext
.supportedCiphersSC()
.list(sshContext
.getPreferredCipherSC());
String localPublicKeys = sshContext.getPublicKeys();
String localMacsCS = sshContext
.supportedMacsCS()
.list(sshContext
.getPreferredMacCS());
String localMacsSC = sshContext
.supportedMacsSC()
.list(sshContext
.getPreferredMacSC());
String localCompressionCS = sshContext
.supportedCompressionsCS()
.list(sshContext
.getPreferredCompressionCS());
String localCompressionSC = sshContext
.supportedCompressionsSC()
.list(sshContext
.getPreferredCompressionSC());
if(Log.isDebugEnabled()) {
Log.debug("Remote Key Exchanges: {}", remoteKeyExchanges);
Log.debug("Remote Public Keys: {}", remotePublicKeys);
Log.debug("Remote Ciphers CS: {}", remoteCiphersCS);
Log.debug("Remote Ciphers SC: {}", remoteCiphersSC);
Log.debug("Remote Macs CS: {}", remoteCSMacs);
Log.debug("Remote Macs SC: {}", remoteSCMacs);
Log.debug("Remote Compression CS: {}", remoteCSCompressions);
Log.debug("Remote Compression SC: {}", remoteSCCompressions);
Log.debug("Lang: {}", lang);
Log.debug("First Packet Follows: {}", firstPacketFollows);
Log.debug("Local Key Exchanges: {}", localKeyExchanges);
Log.debug("Local Public Keys: {}", localPublicKeys);
Log.debug("Local Ciphers CS: {}", localCiphersCS);
Log.debug("Local Ciphers SC: {}", localCiphersSC);
Log.debug("Local Macs CS: {}", localMacsCS);
Log.debug("Local Macs SC: {}", localMacsSC);
Log.debug("Local Compression CS: {}", localCompressionCS);
Log.debug("Local Compression SC: {}", localCompressionSC);
}
keyExchangeAlgorithm = selectNegotiatedComponent(
remoteKeyExchanges, localKeyExchanges);
keyExchange = (SshKeyExchange) sshContext
.supportedKeyExchanges().getInstance(keyExchangeAlgorithm);
// Get the server's public key for the preferred algorithm
publicKey = selectNegotiatedComponent(remotePublicKeys,
sshContext.getSupportedPublicKeys());
// Determine if we should use the first packet
boolean useFirstPacket = remoteKeyExchanges.startsWith(sshContext
.getPreferredKeyExchange())
&& remotePublicKeys.startsWith(sshContext
.getPreferredPublicKey());
EventServiceImplementation
.getInstance()
.fireEvent(
(new Event(this,
EventCodes.EVENT_KEY_EXCHANGE_INIT,
true))
.addAttribute(
EventCodes.ATTRIBUTE_CONNECTION,
con)
.addAttribute(
EventCodes.ATTRIBUTE_REMOTE_KEY_EXCHANGES,
remoteKeyExchanges)
.addAttribute(
EventCodes.ATTRIBUTE_LOCAL_KEY_EXCHANGES,
localKeyExchanges)
.addAttribute(
EventCodes.ATTRIBUTE_REMOTE_PUBLICKEYS,
remotePublicKeys)
.addAttribute(
EventCodes.ATTRIBUTE_LOCAL_PUBLICKEYS,
localPublicKeys)
.addAttribute(
EventCodes.ATTRIBUTE_REMOTE_CIPHERS_CS,
remoteCiphersCS)
.addAttribute(
EventCodes.ATTRIBUTE_LOCAL_CIPHERS_CS,
localCiphersCS)
.addAttribute(
EventCodes.ATTRIBUTE_REMOTE_CIPHERS_SC,
remoteCiphersSC)
.addAttribute(
EventCodes.ATTRIBUTE_LOCAL_CIPHERS_SC,
localCiphersSC)
.addAttribute(
EventCodes.ATTRIBUTE_REMOTE_CS_MACS,
remoteCSMacs)
.addAttribute(
EventCodes.ATTRIBUTE_LOCAL_CS_MACS,
localMacsCS)
.addAttribute(
EventCodes.ATTRIBUTE_REMOTE_SC_MACS,
remoteSCMacs)
.addAttribute(
EventCodes.ATTRIBUTE_LOCAL_SC_MACS,
localMacsSC)
.addAttribute(
EventCodes.ATTRIBUTE_REMOTE_CS_COMPRESSIONS,
remoteCSCompressions)
.addAttribute(
EventCodes.ATTRIBUTE_LOCAL_CS_COMPRESSIONS,
localCompressionCS)
.addAttribute(
EventCodes.ATTRIBUTE_REMOTE_SC_COMPRESSIONS,
remoteSCCompressions)
.addAttribute(
EventCodes.ATTRIBUTE_LOCAL_SC_COMPRESSIONS,
localCompressionSC));
// Initialize the key exchange
initializeKeyExchange(keyExchange, firstPacketFollows, useFirstPacket);
cipherCS = selectNegotiatedComponent(
checkValidString("client->server cipher list",
remoteCiphersCS), localCiphersCS);
cipherSC = selectNegotiatedComponent(
checkValidString("server->client cipher list",
remoteCiphersSC), localCiphersSC);
macCS = selectNegotiatedComponent(
checkValidString("client->server hmac list", remoteCSMacs),
localMacsCS);
macSC = selectNegotiatedComponent(
checkValidString("server->client hmac list", remoteSCMacs),
localMacsSC);
compressionCS = selectNegotiatedComponent(
checkValidString("client->server compression list",
remoteCSCompressions),
localCompressionCS);
compressionSC = selectNegotiatedComponent(
checkValidString("server->client compression list",
remoteSCCompressions),
localCompressionSC);
if(Log.isDebugEnabled()) {
Log.debug("Negotiated Key Exchange: {}", keyExchangeAlgorithm);
Log.debug("Negotiated Public Key: {}", publicKey);
Log.debug("Negotiated Cipher CS: {}", cipherCS);
Log.debug("Negotiated Cipher SC: {}", cipherSC);
Log.debug("Negotiated Mac CS: {}", macCS);
Log.debug("Negotiated Mac SC: {}", macSC);
Log.debug("Negotiated Compression CS: {}", compressionCS);
Log.debug("Negotiated Compression SC: {}", compressionSC);
}
keyExchangeInitialized();
} catch (SshException ex) {
if(ex.getCause()!=null) {
ex.getCause().printStackTrace();
}
EventServiceImplementation.getInstance().fireEvent(
new Event(this,
EventCodes.EVENT_KEY_EXCHANGE_FAILURE, true)
.addAttribute(EventCodes.ATTRIBUTE_CONNECTION,
con));
throw new IOException("Unexpected protocol termination: "
+ ex.getMessage());
} finally {
if (bar != null) {
bar.close();
}
}
}
protected abstract String getExtensionNegotiationString();
protected abstract boolean isExtensionNegotiationSupported();
protected abstract void onKeyExchangeInit() throws SshException;
private void checkStrictKex() {
if(isKexStrict && !hasFirstNewKeys) {
disconnect(PROTOCOL_ERROR,
"Strict KEX mode encountered a message that is not permitted at this time");
}
}
private void checkAlgorithms() {
if(Boolean.getBoolean("maverick.isolate")) {
String kex = System.getProperty("maverick.isolatedKex", SshContext.KEX_DIFFIE_HELLMAN_ECDH_NISTP_256);
String cipher = System.getProperty("maverick.isolatedCipher", SshContext.CIPHER_AES128_CTR);
String mac = System.getProperty("maverick.isolatedMac", SshContext.HMAC_SHA1);
String compression = System.getProperty("maverick.isolatedComp", SshContext.COMPRESSION_NONE);
String pk = System.getProperty("maverick.isolatedPublicKey", SshContext.PUBLIC_KEY_SSHRSA);
getContext().supportedKeyExchanges().removeAllBut(kex);
getContext().supportedCiphersCS().removeAllBut(cipher);
getContext().supportedCiphersSC().removeAllBut(cipher);
getContext().supportedMacsCS().removeAllBut(mac);
getContext().supportedMacsSC().removeAllBut(mac);
getContext().supportedCompressionsCS().removeAllBut(compression);
getContext().supportedCompressionsSC().removeAllBut(compression);
getContext().supportedPublicKeys().removeAllBut(pk);
}
}
protected void keyExchangeInitialized() {
}
protected abstract void disconnected();
protected abstract void onNewKeysReceived();
protected abstract boolean processTransportMessage(int msgid, byte[] msg) throws IOException, SshException;
/**
* Process a message. This should be called when reading messages from
* outside of the transport protocol so that the transport protocol can
* parse its own messages.
*
* @param msg
* @return true
if the message was processed by the transport
* and can be discarded, otherwise false
.
* @throws IOException
*/
public void processMessage(byte[] msg, long sequenceNo) throws SshException,
IOException, WriteOperationRequest {
resetIdleState(this);
if (msg.length < 1) {
throw new IOException("Invalid transport protocol message");
}
if(Log.isTraceEnabled()) {
Log.raw(Level.TRACE, Utils.bytesToHex(msg, 32, true, true), true);
}
int msgId = msg[0];
if(Log.isTraceEnabled()) {
Log.debug("Processing transport protocol message id {}", msgId);
}
switch (msgId) {
case SSH_MSG_DISCONNECT: {
ByteArrayReader bar = new ByteArrayReader(msg);
try {
bar.skip(5);
String reason = bar.readString();
if(Log.isDebugEnabled()) {
Log.debug("Recieved SSH_MSG_DISCONNECT {}", reason);
}
addTask(EVENTS, new ConnectionTaskWrapper(con, new Runnable() {
public void run() {
EventServiceImplementation
.getInstance()
.fireEvent(
new Event(
this,
EventCodes.EVENT_REMOTE_DISCONNECTED,
true)
.addAttribute(EventCodes.ATTRIBUTE_REASON, reason)
.addAttribute(
EventCodes.ATTRIBUTE_CONNECTION,
con)
.addAttribute(
EventCodes.ATTRIBUTE_OPERATION_STARTED,
disconnectStarted)
.addAttribute(
EventCodes.ATTRIBUTE_OPERATION_FINISHED,
new Date()));
}
}));
socketConnection.closeConnection();
} finally {
bar.close();
}
break;
}
case SSH_MSG_IGNORE:
checkStrictKex();
if(Log.isDebugEnabled())
Log.debug("Received SSH_MSG_IGNORE");
break;
case SSH_MSG_DEBUG:
checkStrictKex();
if(Log.isDebugEnabled())
Log.debug("Received SSH_MSG_DEBUG");
break;
case SSH_MSG_EXT_INFO:
checkStrictKex();
if(Log.isDebugEnabled())
Log.debug("Received SSH_MSG_EXT_INFO");
processExtensionInfo(msg);
break;
case SSH_MSG_NEWKEYS:
if(Log.isDebugEnabled())
Log.debug("Received SSH_MSG_NEWKEYS");
hasFirstNewKeys = true;
synchronized (keyExchange) {
keyExchange.setReceivedNewKeys(true);
// Put the keys into use
onNewKeysReceived();
}
break;
case SSH_MSG_KEX_INIT: {
if(Log.isDebugEnabled())
Log.debug("Received SSH_MSG_KEX_INIT");
performKeyExchange(msg);
break;
}
case SSH_MSG_UNIMPLEMENTED: {
checkStrictKex();
ByteArrayReader bar = new ByteArrayReader(msg);
try {
bar.skip(1);
if(Log.isDebugEnabled())
Log.debug("Received SSH_MSG_UNIMPLEMENTED for sequence {}", bar.readInt());
} finally {
bar.close();
}
if(Boolean.getBoolean("maverick.failOnUnimplemented")) {
throw new IllegalStateException("SSH_MSG_UNIMPLEMENTED message returned by remote");
}
break;
}
default: {
if(processTransportMessage(msgId, msg)) {
return;
}
// Not a transport protocol message so try key exchange
if (currentState == TransportProtocol.PERFORMING_KEYEXCHANGE) {
if (keyExchange.processMessage(msg)) {
break;
}
}
if(Log.isTraceEnabled()) {
Log.trace("Posting mesage id {} to active service for processing", msgId);
}
addTask(ACTIVE_SERVICE_IN, new ConnectionAwareTask(con) {
protected void doTask() {
try {
if(Log.isTraceEnabled()) {
Log.trace("Processing active service message id {}", msgId);
}
// Not a key exchange message so try the active service
if (activeService != null && activeService.processMessage(msg)) {
return;
}
/**
* If we reached here we have an unimplemented message
*/
if(Log.isDebugEnabled()) {
Log.debug("Unimplemented Message id={}", msg[0]);
}
postMessage(new UnimplementedMessage(sequenceNo));
} catch (IOException | SshException e) {
disconnect(PROTOCOL_ERROR, e.getMessage());
}
}
});
}
}
}
protected abstract void onNewKeysSent();
public void sendNewKeys() {
// We can now put the new keys into use
postMessage(new SshMessage() {
public boolean writeMessageIntoBuffer(ByteBuffer buf) {
buf.put((byte) TransportProtocol.SSH_MSG_NEWKEYS);
return true;
}
public void messageSent(Long sequenceNo) {
// Potentially generate keys????
synchronized (keyExchange) {
if(Log.isDebugEnabled())
Log.debug("Sent SSH_MSG_NEWKEYS");
keyExchange.setSentNewKeys(true);
onNewKeysSent();
}
}
}, true);
}
public T getSshContext() {
return sshContext;
}
protected String selectNegotiatedComponent(String clientlist, String serverlist)
throws IOException {
String originalClient = clientlist;
String originalServer = serverlist;
Vector r = new Vector();
int idx;
String name;
while ((idx = serverlist.indexOf(",")) > -1) {
r.addElement(serverlist.substring(0, idx).trim());
serverlist = serverlist.substring(idx + 1).trim();
}
r.addElement(serverlist);
while ((idx = clientlist.indexOf(",")) > -1) {
name = clientlist.substring(0, idx).trim();
if (r.contains(name)) {
return name;
}
clientlist = clientlist.substring(idx + 1).trim();
}
if (r.contains(clientlist)) {
return clientlist;
}
EventServiceImplementation
.getInstance()
.fireEvent(
(new Event(
this,
EventCodes.EVENT_FAILED_TO_NEGOTIATE_TRANSPORT_COMPONENT,
true))
.addAttribute(
EventCodes.ATTRIBUTE_CONNECTION,
con)
.addAttribute(
EventCodes.ATTRIBUTE_LOCAL_COMPONENT_LIST,
serverlist)
.addAttribute(
EventCodes.ATTRIBUTE_REMOTE_COMPONENT_LIST,
clientlist));
throw new IOException(String.format("Failed to negotiate a transport component from %s and %s", originalClient, originalServer));
}
protected void onKeyExchangeComplete() {
}
protected void completeKeyExchange(SshKeyExchange keyExchange) {
localkex = null;
remotekex = null;
completedFirstKeyExchange = true;
onKeyExchangeComplete();
EventServiceImplementation.getInstance()
.fireEvent(
(new Event(this,
EventCodes.EVENT_KEY_EXCHANGE_COMPLETE,
true))
.addAttribute(
EventCodes.ATTRIBUTE_CONNECTION,
con)
.addAttribute(
EventCodes.ATTRIBUTE_USING_PUBLICKEY,
publicKey)
.addAttribute(
EventCodes.ATTRIBUTE_USING_KEY_EXCHANGE,
keyExchangeAlgorithm)
.addAttribute(
EventCodes.ATTRIBUTE_USING_CS_CIPHER,
cipherCS)
.addAttribute(
EventCodes.ATTRIBUTE_USING_SC_CIPHER,
cipherSC)
.addAttribute(
EventCodes.ATTRIBUTE_USING_CS_MAC,
macCS)
.addAttribute(
EventCodes.ATTRIBUTE_USING_SC_MAC,
macSC)
.addAttribute(
EventCodes.ATTRIBUTE_USING_CS_COMPRESSION,
compressionCS)
.addAttribute(
EventCodes.ATTRIBUTE_USING_SC_COMPRESSION,
compressionSC));
setTransportState(TransportProtocol.CONNECTED);
}
protected void generateNewKeysServerOut() {
synchronized (kexlockOut) {
try {
// The first exchange hash is the session identifier
if (sessionIdentifier == null) {
sessionIdentifier = keyExchange.getExchangeHash();
}
// Generate a new set of context components
encryption = (SshCipher) sshContext.supportedCiphersSC()
.getInstance(cipherSC);
// Create the new keys and initialize all the components
encryption.init(SshCipher.ENCRYPT_MODE, makeSshKey('B', encryption.getBlockSize()),
makeSshKey('D', encryption.getKeyLength()));
if(!encryption.isMAC()) {
outgoingMac = (SshHmac) sshContext.supportedMacsSC()
.getInstance(macSC);
outgoingMac.init(makeSshKey('F', outgoingMac.getMacSize()));
}
outgoingCompression = null;
if (!compressionSC.equals(SshContext.COMPRESSION_NONE)) {
outgoingCompression = (SshCompression) sshContext
.supportedCompressionsSC().getInstance(
compressionSC);
outgoingCompression.init(SshCompression.DEFLATER,
getSshContext().getCompressionLevel());
}
if(isKexStrict) {
if(Log.isDebugEnabled()) {
Log.debug("Resetting OUTGOING sequence from {} to zero for strict transport protocol requirements", outgoingSequence);
}
outgoingSequence = 0L;
}
if(isKexStrict) {
if(Log.isDebugEnabled()) {
Log.debug("Resetting OUTGOING sequence from {} to zero for strict transport protocol requirements", outgoingSequence);
}
outgoingSequence = 0L;
}
if (keyExchange.hasReceivedNewKeys()) {
completeKeyExchange(keyExchange);
}
} catch (Throwable ex) {
if(Log.isDebugEnabled())
Log.debug("Failed to create transport component", ex);
connectFuture.done(false);
if(disconnectStarted != null)
disconnect(
TransportProtocol.PROTOCOL_ERROR,
"Failed to create a transport component! "
+ ex.getMessage());
}
}
}
protected void generateNewKeysServerIn() {
synchronized (kexlockIn) {
try {
// The first exchange hash is the session identifier
if (sessionIdentifier == null) {
sessionIdentifier = keyExchange.getExchangeHash();
}
// Generate a new set of context components
decryption = (SshCipher) sshContext.supportedCiphersCS()
.getInstance(cipherCS);
// Put the incoming components into use
decryption.init(SshCipher.DECRYPT_MODE, makeSshKey('A', decryption.getBlockSize()),
makeSshKey('C', decryption.getKeyLength()));
if(!decryption.isMAC()) {
incomingMac = (SshHmac) sshContext.supportedMacsCS()
.getInstance(macCS);
incomingMac.init(makeSshKey('E', incomingMac.getMacSize()));
incomingMacLength = incomingMac.getMacLength();
} else {
incomingMacLength = decryption.getMacLength();
}
incomingCompression = null;
if (!compressionCS.equals(SshContext.COMPRESSION_NONE)) {
incomingCompression = (SshCompression) sshContext
.supportedCompressionsCS().getInstance(
compressionCS);
incomingCompression.init(SshCompression.INFLATER,
getSshContext().getCompressionLevel());
}
incomingCipherLength = decryption.getBlockSize();
if(isKexStrict) {
if(Log.isDebugEnabled()) {
Log.debug("Resetting INCOMING sequence from {} to zero for strict transport protocol requirements", incomingSequence);
}
incomingSequence = 0L;
}
if (keyExchange.hasSentNewKeys()) {
completeKeyExchange(keyExchange);
}
} catch (Throwable ex) {
if(Log.isDebugEnabled())
Log.debug("Failed to create transport component", ex);
connectFuture.done(false);
if(disconnectStarted != null)
disconnect(
TransportProtocol.PROTOCOL_ERROR,
"Failed to create a transport component! "
+ ex.getMessage());
}
}
}
protected void generateNewKeysClientOut() {
synchronized (kexlockOut) {
try {
// The first exchange hash is the session identifier
if (sessionIdentifier == null) {
sessionIdentifier = keyExchange.getExchangeHash();
}
// Generate a new set of context components
encryption = (SshCipher) sshContext.supportedCiphersSC()
.getInstance(cipherCS);
// Create the new keys and initialize all the components
encryption.init(SshCipher.ENCRYPT_MODE, makeSshKey('A', encryption.getBlockSize()),
makeSshKey('C', encryption.getKeyLength()));
if(!encryption.isMAC()) {
outgoingMac = (SshHmac) sshContext.supportedMacsSC()
.getInstance(macCS);
outgoingMac.init(makeSshKey('E', outgoingMac.getMacSize()));
}
outgoingCompression = null;
if (!compressionSC.equals(SshContext.COMPRESSION_NONE)) {
outgoingCompression = (SshCompression) sshContext
.supportedCompressionsSC().getInstance(
compressionCS);
outgoingCompression.init(SshCompression.DEFLATER,
getSshContext().getCompressionLevel());
}
if(isKexStrict) {
if(Log.isDebugEnabled()) {
Log.debug("Resetting OUTGOING sequence from {} to zero for strict transport protocol requirements", outgoingSequence);
}
outgoingSequence = 0L;
}
if (keyExchange.hasReceivedNewKeys()) {
completeKeyExchange(keyExchange);
}
} catch (Throwable ex) {
if(Log.isErrorEnabled())
Log.error("Failed to create transport component", ex);
connectFuture.done(false);
if(disconnectStarted != null)
disconnect(
TransportProtocol.PROTOCOL_ERROR,
"Failed to create a transport component! "
+ ex.getMessage());
}
}
}
protected void generateNewKeysClientIn() {
synchronized (kexlockIn) {
try {
// The first exchange hash is the session identifier
if (sessionIdentifier == null) {
sessionIdentifier = keyExchange.getExchangeHash();
}
// Generate a new set of context components
decryption = (SshCipher) sshContext.supportedCiphersCS()
.getInstance(cipherSC);
// Put the incoming components into use
decryption.init(SshCipher.DECRYPT_MODE, makeSshKey('B', decryption.getBlockSize()),
makeSshKey('D', decryption.getKeyLength()));
if(!decryption.isMAC()) {
incomingMac = (SshHmac) sshContext.supportedMacsCS()
.getInstance(macSC);
incomingMac.init(makeSshKey('F', incomingMac.getMacSize()));
incomingMacLength = incomingMac.getMacLength();
} else {
incomingMacLength = decryption.getMacLength();
}
incomingCompression = null;
if (!compressionCS.equals(SshContext.COMPRESSION_NONE)) {
incomingCompression = (SshCompression) sshContext
.supportedCompressionsCS().getInstance(
compressionSC);
incomingCompression.init(SshCompression.INFLATER,
getSshContext().getCompressionLevel());
}
incomingCipherLength = decryption.getBlockSize();
if(isKexStrict) {
if(Log.isDebugEnabled()) {
Log.debug("Resetting INCOMING sequence from {} to zero for strict transport protocol requirements", incomingSequence);
}
incomingSequence = 0L;
}
if (keyExchange.hasSentNewKeys()) {
completeKeyExchange(keyExchange);
}
} catch (Throwable ex) {
if(Log.isErrorEnabled())
Log.error("Failed to create transport component", ex);
connectFuture.setLastError(ex);
connectFuture.done(false);
if(disconnectStarted != null)
disconnect(
TransportProtocol.PROTOCOL_ERROR,
"Failed to create a transport component! "
+ ex.getMessage());
}
}
}
void sendKeyExchangeInit() {
try {
synchronized (kexlockOut) {
numIncomingBytesSinceKEX = 0;
numIncomingPacketsSinceKEX = 0;
numOutgoingBytesSinceKEX = 0;
numOutgoingPacketsSinceKEX = 0;
setTransportState(PERFORMING_KEYEXCHANGE);
if (localkex == null) {
try {
localkex = TransportProtocolHelper.generateKexInit(getContext(),
isExtensionNegotiationSupported(),
getExtensionNegotiationString(),
isServerMode() ? STRICT_KEX_SERVER : STRICT_KEX_CLIENT);
kexQueue.clear();
if(Log.isDebugEnabled())
Log.debug("Posting SSH_MSG_KEX_INIT");
postMessage(new SshMessage() {
public boolean writeMessageIntoBuffer(ByteBuffer buf) {
buf.put(localkex);
return true;
}
public void messageSent(Long sequenceNo) {
if(Log.isDebugEnabled())
Log.debug("Sent SSH_MSG_KEX_INIT");
}
}, true);
} catch(SshException e) {
disconnect(BY_APPLICATION, "Internal error");
}
}
}
} catch (IOException ex) {
disconnect(TransportProtocol.PROTOCOL_ERROR,
"Failed to create SSH_MSG_KEX_INIT");
}
}
protected abstract boolean isServerMode();
private void processExtensionInfo(byte[] msg) throws IOException {
ByteArrayReader reader = new ByteArrayReader(msg);
try {
reader.skip(1);
int count = (int) reader.readInt();
if(Log.isDebugEnabled()) {
Log.debug("Server supports {} extensions", count);
}
for(int i=0; i list = kex && completedFirstKeyExchange ? kexQueue
: outgoingQueue;
synchronized (kexlockOut) {
list.addLast(msg);
}
socketConnection.flagWrite();
}
byte[] makeSshKey(char chr, int sizeRequired) throws SshException, IOException {
// Create the first 20 bytes of key data
ByteArrayWriter keydata = new ByteArrayWriter();
try {
byte[] data = new byte[20];
Digest hash = (Digest) ComponentManager.getDefaultInstance()
.supportedDigests()
.getInstance(keyExchange.getHashAlgorithm());
// Put the dh k value
hash.putBigInteger(keyExchange.getSecret());
// Put in the exchange hash
hash.putBytes(keyExchange.getExchangeHash());
// Put in the character
hash.putByte((byte) chr);
// Put in the session identifier
hash.putBytes(sessionIdentifier);
// Create the first 20 bytes
data = hash.doFinal();
keydata.write(data);
while(keydata.size() < sizeRequired) {
// Now do the next 20
hash.reset();
// Put the dh k value in again
hash.putBigInteger(keyExchange.getSecret());
// And the exchange hash
hash.putBytes(keyExchange.getExchangeHash());
// Finally the first 20 bytes of data we created
hash.putBytes(data);
data = hash.doFinal();
// Put it all together
keydata.write(data);
}
// Return it
return keydata.toByteArray();
} finally {
keydata.close();
}
}
class IgnoreMessage implements SshMessage {
SecureRandom rnd = new SecureRandom();
public boolean writeMessageIntoBuffer(ByteBuffer buf) {
byte[] tmp = new byte[Math.min(getContext().getKeepAliveDataMaxLength(), 1024)];
try {
buf.put((byte) SSH_MSG_IGNORE);
int len = (int) (Math.random() * (tmp.length + 1));
rnd.nextBytes(tmp);
buf.putInt(len);
buf.put(tmp, 0, len);
return true;
} finally {
tmp = null;
}
}
public void messageSent(Long sequenceNo) {
if(Log.isDebugEnabled())
Log.debug("Sent SSH_MSG_IGNORE {}", activeService.getIdleLog());
}
}
class UnimplementedMessage implements SshMessage {
long sequenceNo;
UnimplementedMessage(long sequenceNo) {
this.sequenceNo = sequenceNo;
}
public boolean writeMessageIntoBuffer(ByteBuffer buf) {
buf.put((byte) SSH_MSG_UNIMPLEMENTED);
buf.putInt((int) sequenceNo);
return true;
}
public void messageSent(Long sequenceNo) {
if(Log.isDebugEnabled())
Log.debug("Sent SSH_MSG_UNIMPLEMENTED");
}
}
class DisconnectMessage implements SshMessage {
int reason;
String description;
boolean closeProtocol;
DisconnectMessage(int reason, String description, boolean closeProtocol) {
this.reason = reason;
this.description = description;
this.closeProtocol = closeProtocol;
}
public boolean writeMessageIntoBuffer(ByteBuffer buf) {
buf.put((byte) SSH_MSG_DISCONNECT);
buf.putInt(reason);
buf.putInt(description.length());
buf.put(description.getBytes());
buf.putInt(0);
return true;
}
public void messageSent(Long sequenceNo) {
if(Log.isDebugEnabled())
Log.debug("Sent SSH_MSG_DISCONNECT reason=" + reason + " "
+ description);
socketConnection.closeConnection(closeProtocol);
}
}
public byte[] getSessionKey() {
return sessionIdentifier;
}
public static byte[] getBytes(String str, String charset) {
try {
return str.getBytes(charset);
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("System does not support " + charset);
}
}
public void kill() {
socketConnection.closeConnection();
}
public String getHostKeyAlgorithm() {
return publicKey;
}
public SshPublicKey getHostKey() {
return hostKey;
}
public String getKeyExchangeAlgorithm() {
return keyExchangeAlgorithm;
}
public String[] getRemoteKeyExchanges() {
return remoteKeyExchanges.split(",");
}
public String[] getRemotePublicKeys() {
return remotePublicKeys.split(",");
}
public String[] getRemoteCiphersCS() {
return remoteCiphersCS.split(",");
}
public String[] getRemoteCiphersSC() {
return remoteCiphersSC.split(",");
}
public String[] getRemoteMacsCS() {
return remoteCSMacs.split(",");
}
public String[] getRemoteMacsSC() {
return remoteSCMacs.split(",");
}
public String[] getRemoteCompressionsCS() {
return remoteCSCompressions.split(",");
}
public String[] getRemoteCompressionsSC() {
return remoteSCCompressions.split(",");
}
public boolean hasCompletedKeyExchange() {
return completedFirstKeyExchange;
}
public ExecutorOperationSupport> getExecutor() {
return this;
}
public void registerIdleStateListener(IdleStateListener listener) {
idleListeners.add(listener);
}
public void removeIdleStateListener(IdleStateListener listener) {
idleListeners.remove(listener);
}
public void resetIdleState(IdleStateListener listener) {
if(Log.isTraceEnabled()) {
Log.trace("Resetting idle state");
}
lastActivity = System.currentTimeMillis();
if (getContext().getIdleConnectionTimeoutSeconds() > 0
&& socketConnection != null)
socketConnection.getIdleStates().reset(this);
}
public boolean isSelectorThread() {
return Thread.currentThread().equals(getSocketConnection().getSelectorThread());
}
public String getKeyExchangeInUse() {
return keyExchangeAlgorithm;
}
public String getHostKeyInUse() {
return publicKey;
}
public String getLocalIdentification() {
return localIdentification.trim();
}
public AuthenticatedFuture getAuthenticatedFuture() {
return authenticatedFuture;
}
}