aboutsummaryrefslogtreecommitdiffstats
path: root/lib/src/main/java/com/trilead/ssh2/transport
diff options
context:
space:
mode:
Diffstat (limited to 'lib/src/main/java/com/trilead/ssh2/transport')
-rw-r--r--lib/src/main/java/com/trilead/ssh2/transport/ClientServerHello.java250
-rw-r--r--lib/src/main/java/com/trilead/ssh2/transport/KexManager.java1290
-rw-r--r--lib/src/main/java/com/trilead/ssh2/transport/KexParameters.java48
-rw-r--r--lib/src/main/java/com/trilead/ssh2/transport/KexState.java60
-rw-r--r--lib/src/main/java/com/trilead/ssh2/transport/MessageHandler.java28
-rw-r--r--lib/src/main/java/com/trilead/ssh2/transport/NegotiateException.java24
-rw-r--r--lib/src/main/java/com/trilead/ssh2/transport/NegotiatedParameters.java44
-rw-r--r--lib/src/main/java/com/trilead/ssh2/transport/TransportConnection.java686
-rw-r--r--lib/src/main/java/com/trilead/ssh2/transport/TransportManager.java1604
9 files changed, 2017 insertions, 2017 deletions
diff --git a/lib/src/main/java/com/trilead/ssh2/transport/ClientServerHello.java b/lib/src/main/java/com/trilead/ssh2/transport/ClientServerHello.java
index 23726f3..d7a5ee5 100644
--- a/lib/src/main/java/com/trilead/ssh2/transport/ClientServerHello.java
+++ b/lib/src/main/java/com/trilead/ssh2/transport/ClientServerHello.java
@@ -1,125 +1,125 @@
-
-package com.trilead.ssh2.transport;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
-
-import com.trilead.ssh2.Connection;
-
-/**
- * ClientServerHello.
- *
- * @author Christian Plattner, plattner@trilead.com
- * @version $Id: ClientServerHello.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $
- */
-public class ClientServerHello
-{
- String server_line;
- String client_line;
-
- String server_versioncomment;
-
- public final static int readLineRN(InputStream is, byte[] buffer) throws IOException
- {
- int pos = 0;
- boolean need10 = false;
- int len = 0;
- while (true)
- {
- int c = is.read();
- if (c == -1)
- throw new IOException("Premature connection close");
-
- buffer[pos++] = (byte) c;
-
- if (c == 13)
- {
- need10 = true;
- continue;
- }
-
- if (c == 10)
- break;
-
- if (need10 == true)
- throw new IOException("Malformed line sent by the server, the line does not end correctly.");
-
- len++;
- if (pos >= buffer.length)
- throw new IOException("The server sent a too long line.");
- }
-
- return len;
- }
-
- public ClientServerHello(InputStream bi, OutputStream bo) throws IOException
- {
- client_line = "SSH-2.0-" + Connection.identification;
-
- bo.write((client_line + "\r\n").getBytes("ISO-8859-1"));
- bo.flush();
-
- byte[] serverVersion = new byte[512];
-
- for (int i = 0; i < 50; i++)
- {
- int len = readLineRN(bi, serverVersion);
-
- server_line = new String(serverVersion, 0, len, "ISO-8859-1");
-
- if (server_line.startsWith("SSH-"))
- break;
- }
-
- if (server_line.startsWith("SSH-") == false)
- throw new IOException(
- "Malformed server identification string. There was no line starting with 'SSH-' amongst the first 50 lines.");
-
- if (server_line.startsWith("SSH-1.99-"))
- server_versioncomment = server_line.substring(9);
- else if (server_line.startsWith("SSH-2.0-"))
- server_versioncomment = server_line.substring(8);
- else
- throw new IOException("Server uses incompatible protocol, it is not SSH-2 compatible.");
- }
-
- /**
- * @return Returns the client_versioncomment.
- */
- public byte[] getClientString()
- {
- byte[] result;
-
- try
- {
- result = client_line.getBytes("ISO-8859-1");
- }
- catch (UnsupportedEncodingException ign)
- {
- result = client_line.getBytes();
- }
-
- return result;
- }
-
- /**
- * @return Returns the server_versioncomment.
- */
- public byte[] getServerString()
- {
- byte[] result;
-
- try
- {
- result = server_line.getBytes("ISO-8859-1");
- }
- catch (UnsupportedEncodingException ign)
- {
- result = server_line.getBytes();
- }
-
- return result;
- }
-}
+
+package com.trilead.ssh2.transport;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+
+import com.trilead.ssh2.Connection;
+
+/**
+ * ClientServerHello.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: ClientServerHello.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $
+ */
+public class ClientServerHello
+{
+ String server_line;
+ String client_line;
+
+ String server_versioncomment;
+
+ public final static int readLineRN(InputStream is, byte[] buffer) throws IOException
+ {
+ int pos = 0;
+ boolean need10 = false;
+ int len = 0;
+ while (true)
+ {
+ int c = is.read();
+ if (c == -1)
+ throw new IOException("Premature connection close");
+
+ buffer[pos++] = (byte) c;
+
+ if (c == 13)
+ {
+ need10 = true;
+ continue;
+ }
+
+ if (c == 10)
+ break;
+
+ if (need10 == true)
+ throw new IOException("Malformed line sent by the server, the line does not end correctly.");
+
+ len++;
+ if (pos >= buffer.length)
+ throw new IOException("The server sent a too long line.");
+ }
+
+ return len;
+ }
+
+ public ClientServerHello(InputStream bi, OutputStream bo) throws IOException
+ {
+ client_line = "SSH-2.0-" + Connection.identification;
+
+ bo.write((client_line + "\r\n").getBytes("ISO-8859-1"));
+ bo.flush();
+
+ byte[] serverVersion = new byte[512];
+
+ for (int i = 0; i < 50; i++)
+ {
+ int len = readLineRN(bi, serverVersion);
+
+ server_line = new String(serverVersion, 0, len, "ISO-8859-1");
+
+ if (server_line.startsWith("SSH-"))
+ break;
+ }
+
+ if (server_line.startsWith("SSH-") == false)
+ throw new IOException(
+ "Malformed server identification string. There was no line starting with 'SSH-' amongst the first 50 lines.");
+
+ if (server_line.startsWith("SSH-1.99-"))
+ server_versioncomment = server_line.substring(9);
+ else if (server_line.startsWith("SSH-2.0-"))
+ server_versioncomment = server_line.substring(8);
+ else
+ throw new IOException("Server uses incompatible protocol, it is not SSH-2 compatible.");
+ }
+
+ /**
+ * @return Returns the client_versioncomment.
+ */
+ public byte[] getClientString()
+ {
+ byte[] result;
+
+ try
+ {
+ result = client_line.getBytes("ISO-8859-1");
+ }
+ catch (UnsupportedEncodingException ign)
+ {
+ result = client_line.getBytes();
+ }
+
+ return result;
+ }
+
+ /**
+ * @return Returns the server_versioncomment.
+ */
+ public byte[] getServerString()
+ {
+ byte[] result;
+
+ try
+ {
+ result = server_line.getBytes("ISO-8859-1");
+ }
+ catch (UnsupportedEncodingException ign)
+ {
+ result = server_line.getBytes();
+ }
+
+ return result;
+ }
+}
diff --git a/lib/src/main/java/com/trilead/ssh2/transport/KexManager.java b/lib/src/main/java/com/trilead/ssh2/transport/KexManager.java
index 6e0d904..230047e 100644
--- a/lib/src/main/java/com/trilead/ssh2/transport/KexManager.java
+++ b/lib/src/main/java/com/trilead/ssh2/transport/KexManager.java
@@ -1,664 +1,664 @@
-
-package com.trilead.ssh2.transport;
-
-import java.io.IOException;
-import java.security.SecureRandom;
-import java.security.interfaces.DSAPublicKey;
-import java.security.interfaces.ECPublicKey;
-import java.security.interfaces.RSAPublicKey;
-import java.util.Set;
-import java.util.TreeSet;
-
-import com.trilead.ssh2.ConnectionInfo;
-import com.trilead.ssh2.DHGexParameters;
-import com.trilead.ssh2.ServerHostKeyVerifier;
-import com.trilead.ssh2.compression.CompressionFactory;
-import com.trilead.ssh2.compression.ICompressor;
-import com.trilead.ssh2.crypto.CryptoWishList;
-import com.trilead.ssh2.crypto.KeyMaterial;
-import com.trilead.ssh2.crypto.cipher.BlockCipher;
-import com.trilead.ssh2.crypto.cipher.BlockCipherFactory;
-import com.trilead.ssh2.crypto.dh.DhGroupExchange;
+
+package com.trilead.ssh2.transport;
+
+import java.io.IOException;
+import java.security.SecureRandom;
+import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.util.Set;
+import java.util.TreeSet;
+
+import com.trilead.ssh2.ConnectionInfo;
+import com.trilead.ssh2.DHGexParameters;
+import com.trilead.ssh2.ServerHostKeyVerifier;
+import com.trilead.ssh2.compression.CompressionFactory;
+import com.trilead.ssh2.compression.ICompressor;
+import com.trilead.ssh2.crypto.CryptoWishList;
+import com.trilead.ssh2.crypto.KeyMaterial;
+import com.trilead.ssh2.crypto.cipher.BlockCipher;
+import com.trilead.ssh2.crypto.cipher.BlockCipherFactory;
+import com.trilead.ssh2.crypto.dh.DhGroupExchange;
import com.trilead.ssh2.crypto.dh.GenericDhExchange;
-import com.trilead.ssh2.crypto.digest.MAC;
-import com.trilead.ssh2.log.Logger;
-import com.trilead.ssh2.packets.PacketKexDHInit;
-import com.trilead.ssh2.packets.PacketKexDHReply;
-import com.trilead.ssh2.packets.PacketKexDhGexGroup;
-import com.trilead.ssh2.packets.PacketKexDhGexInit;
-import com.trilead.ssh2.packets.PacketKexDhGexReply;
-import com.trilead.ssh2.packets.PacketKexDhGexRequest;
-import com.trilead.ssh2.packets.PacketKexDhGexRequestOld;
-import com.trilead.ssh2.packets.PacketKexInit;
-import com.trilead.ssh2.packets.PacketNewKeys;
-import com.trilead.ssh2.packets.Packets;
-import com.trilead.ssh2.signature.DSASHA1Verify;
-import com.trilead.ssh2.signature.ECDSASHA2Verify;
-import com.trilead.ssh2.signature.RSASHA1Verify;
-
-
-/**
- * KexManager.
- *
- * @author Christian Plattner, plattner@trilead.com
- * @version $Id: KexManager.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
- */
-public class KexManager
-{
- private static final Logger log = Logger.getLogger(KexManager.class);
-
- private static final Set<String> HOSTKEY_ALGS = new TreeSet<String>();
- static {
- HOSTKEY_ALGS.add("ecdsa-sha2-nistp256");
- HOSTKEY_ALGS.add("ecdsa-sha2-nistp384");
- HOSTKEY_ALGS.add("ecdsa-sha2-nistp521");
- HOSTKEY_ALGS.add("ssh-rsa");
- HOSTKEY_ALGS.add("ssh-dsa");
- }
-
- private static final Set<String> KEX_ALGS = new TreeSet<String>();
- static {
+import com.trilead.ssh2.crypto.digest.MAC;
+import com.trilead.ssh2.log.Logger;
+import com.trilead.ssh2.packets.PacketKexDHInit;
+import com.trilead.ssh2.packets.PacketKexDHReply;
+import com.trilead.ssh2.packets.PacketKexDhGexGroup;
+import com.trilead.ssh2.packets.PacketKexDhGexInit;
+import com.trilead.ssh2.packets.PacketKexDhGexReply;
+import com.trilead.ssh2.packets.PacketKexDhGexRequest;
+import com.trilead.ssh2.packets.PacketKexDhGexRequestOld;
+import com.trilead.ssh2.packets.PacketKexInit;
+import com.trilead.ssh2.packets.PacketNewKeys;
+import com.trilead.ssh2.packets.Packets;
+import com.trilead.ssh2.signature.DSASHA1Verify;
+import com.trilead.ssh2.signature.ECDSASHA2Verify;
+import com.trilead.ssh2.signature.RSASHA1Verify;
+
+
+/**
+ * KexManager.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: KexManager.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ */
+public class KexManager
+{
+ private static final Logger log = Logger.getLogger(KexManager.class);
+
+ private static final Set<String> HOSTKEY_ALGS = new TreeSet<String>();
+ static {
+ HOSTKEY_ALGS.add("ecdsa-sha2-nistp256");
+ HOSTKEY_ALGS.add("ecdsa-sha2-nistp384");
+ HOSTKEY_ALGS.add("ecdsa-sha2-nistp521");
+ HOSTKEY_ALGS.add("ssh-rsa");
+ HOSTKEY_ALGS.add("ssh-dsa");
+ }
+
+ private static final Set<String> KEX_ALGS = new TreeSet<String>();
+ static {
KEX_ALGS.add("ecdh-sha2-nistp256");
KEX_ALGS.add("ecdh-sha2-nistp384");
KEX_ALGS.add("ecdh-sha2-nistp521");
- KEX_ALGS.add("diffie-hellman-group-exchange-sha1");
- KEX_ALGS.add("diffie-hellman-group14-sha1");
- KEX_ALGS.add("diffie-hellman-group1-sha1");
- }
-
- KexState kxs;
- int kexCount = 0;
- KeyMaterial km;
- byte[] sessionId;
- ClientServerHello csh;
-
- final Object accessLock = new Object();
- ConnectionInfo lastConnInfo = null;
-
- boolean connectionClosed = false;
-
- boolean ignore_next_kex_packet = false;
-
- final TransportManager tm;
-
- CryptoWishList nextKEXcryptoWishList;
- DHGexParameters nextKEXdhgexParameters;
-
- ServerHostKeyVerifier verifier;
- final String hostname;
- final int port;
- final SecureRandom rnd;
-
- public KexManager(TransportManager tm, ClientServerHello csh, CryptoWishList initialCwl, String hostname, int port,
- ServerHostKeyVerifier keyVerifier, SecureRandom rnd)
- {
- this.tm = tm;
- this.csh = csh;
- this.nextKEXcryptoWishList = initialCwl;
- this.nextKEXdhgexParameters = new DHGexParameters();
- this.hostname = hostname;
- this.port = port;
- this.verifier = keyVerifier;
- this.rnd = rnd;
- }
-
- public ConnectionInfo getOrWaitForConnectionInfo(int minKexCount) throws IOException
- {
- synchronized (accessLock)
- {
- while (true)
- {
- if ((lastConnInfo != null) && (lastConnInfo.keyExchangeCounter >= minKexCount))
- return lastConnInfo;
-
- if (connectionClosed)
- throw (IOException) new IOException("Key exchange was not finished, connection is closed.")
- .initCause(tm.getReasonClosedCause());
-
- try
- {
- accessLock.wait();
- }
- catch (InterruptedException e)
- {
- }
- }
- }
- }
-
- private String getFirstMatch(String[] client, String[] server) throws NegotiateException
- {
- if (client == null || server == null)
- throw new IllegalArgumentException();
-
- if (client.length == 0)
- return null;
-
- for (int i = 0; i < client.length; i++)
- {
- for (int j = 0; j < server.length; j++)
- {
- if (client[i].equals(server[j]))
- return client[i];
- }
- }
- throw new NegotiateException();
- }
-
- private boolean compareFirstOfNameList(String[] a, String[] b)
- {
- if (a == null || b == null)
- throw new IllegalArgumentException();
-
- if ((a.length == 0) && (b.length == 0))
- return true;
-
- if ((a.length == 0) || (b.length == 0))
- return false;
-
- return (a[0].equals(b[0]));
- }
-
- private boolean isGuessOK(KexParameters cpar, KexParameters spar)
- {
- if (cpar == null || spar == null)
- throw new IllegalArgumentException();
-
- if (compareFirstOfNameList(cpar.kex_algorithms, spar.kex_algorithms) == false)
- {
- return false;
- }
-
- if (compareFirstOfNameList(cpar.server_host_key_algorithms, spar.server_host_key_algorithms) == false)
- {
- return false;
- }
-
- /*
- * We do NOT check here if the other algorithms can be agreed on, this
- * is just a check if kex_algorithms and server_host_key_algorithms were
- * guessed right!
- */
-
- return true;
- }
-
- private NegotiatedParameters mergeKexParameters(KexParameters client, KexParameters server)
- {
- NegotiatedParameters np = new NegotiatedParameters();
-
- try
- {
- np.kex_algo = getFirstMatch(client.kex_algorithms, server.kex_algorithms);
-
- log.log(20, "kex_algo=" + np.kex_algo);
-
- np.server_host_key_algo = getFirstMatch(client.server_host_key_algorithms,
- server.server_host_key_algorithms);
-
- log.log(20, "server_host_key_algo=" + np.server_host_key_algo);
-
- np.enc_algo_client_to_server = getFirstMatch(client.encryption_algorithms_client_to_server,
- server.encryption_algorithms_client_to_server);
- np.enc_algo_server_to_client = getFirstMatch(client.encryption_algorithms_server_to_client,
- server.encryption_algorithms_server_to_client);
-
- log.log(20, "enc_algo_client_to_server=" + np.enc_algo_client_to_server);
- log.log(20, "enc_algo_server_to_client=" + np.enc_algo_server_to_client);
-
- np.mac_algo_client_to_server = getFirstMatch(client.mac_algorithms_client_to_server,
- server.mac_algorithms_client_to_server);
- np.mac_algo_server_to_client = getFirstMatch(client.mac_algorithms_server_to_client,
- server.mac_algorithms_server_to_client);
-
- log.log(20, "mac_algo_client_to_server=" + np.mac_algo_client_to_server);
- log.log(20, "mac_algo_server_to_client=" + np.mac_algo_server_to_client);
-
- np.comp_algo_client_to_server = getFirstMatch(client.compression_algorithms_client_to_server,
- server.compression_algorithms_client_to_server);
- np.comp_algo_server_to_client = getFirstMatch(client.compression_algorithms_server_to_client,
- server.compression_algorithms_server_to_client);
-
- log.log(20, "comp_algo_client_to_server=" + np.comp_algo_client_to_server);
- log.log(20, "comp_algo_server_to_client=" + np.comp_algo_server_to_client);
-
- }
- catch (NegotiateException e)
- {
- return null;
- }
-
- try
- {
- np.lang_client_to_server = getFirstMatch(client.languages_client_to_server,
- server.languages_client_to_server);
- }
- catch (NegotiateException e1)
- {
- np.lang_client_to_server = null;
- }
-
- try
- {
- np.lang_server_to_client = getFirstMatch(client.languages_server_to_client,
- server.languages_server_to_client);
- }
- catch (NegotiateException e2)
- {
- np.lang_server_to_client = null;
- }
-
- if (isGuessOK(client, server))
- np.guessOK = true;
-
- return np;
- }
-
- public synchronized void initiateKEX(CryptoWishList cwl, DHGexParameters dhgex) throws IOException
- {
- nextKEXcryptoWishList = cwl;
- nextKEXdhgexParameters = dhgex;
-
- if (kxs == null)
- {
- kxs = new KexState();
-
- kxs.dhgexParameters = nextKEXdhgexParameters;
+ KEX_ALGS.add("diffie-hellman-group-exchange-sha1");
+ KEX_ALGS.add("diffie-hellman-group14-sha1");
+ KEX_ALGS.add("diffie-hellman-group1-sha1");
+ }
+
+ KexState kxs;
+ int kexCount = 0;
+ KeyMaterial km;
+ byte[] sessionId;
+ ClientServerHello csh;
+
+ final Object accessLock = new Object();
+ ConnectionInfo lastConnInfo = null;
+
+ boolean connectionClosed = false;
+
+ boolean ignore_next_kex_packet = false;
+
+ final TransportManager tm;
+
+ CryptoWishList nextKEXcryptoWishList;
+ DHGexParameters nextKEXdhgexParameters;
+
+ ServerHostKeyVerifier verifier;
+ final String hostname;
+ final int port;
+ final SecureRandom rnd;
+
+ public KexManager(TransportManager tm, ClientServerHello csh, CryptoWishList initialCwl, String hostname, int port,
+ ServerHostKeyVerifier keyVerifier, SecureRandom rnd)
+ {
+ this.tm = tm;
+ this.csh = csh;
+ this.nextKEXcryptoWishList = initialCwl;
+ this.nextKEXdhgexParameters = new DHGexParameters();
+ this.hostname = hostname;
+ this.port = port;
+ this.verifier = keyVerifier;
+ this.rnd = rnd;
+ }
+
+ public ConnectionInfo getOrWaitForConnectionInfo(int minKexCount) throws IOException
+ {
+ synchronized (accessLock)
+ {
+ while (true)
+ {
+ if ((lastConnInfo != null) && (lastConnInfo.keyExchangeCounter >= minKexCount))
+ return lastConnInfo;
+
+ if (connectionClosed)
+ throw (IOException) new IOException("Key exchange was not finished, connection is closed.")
+ .initCause(tm.getReasonClosedCause());
+
+ try
+ {
+ accessLock.wait();
+ }
+ catch (InterruptedException e)
+ {
+ }
+ }
+ }
+ }
+
+ private String getFirstMatch(String[] client, String[] server) throws NegotiateException
+ {
+ if (client == null || server == null)
+ throw new IllegalArgumentException();
+
+ if (client.length == 0)
+ return null;
+
+ for (int i = 0; i < client.length; i++)
+ {
+ for (int j = 0; j < server.length; j++)
+ {
+ if (client[i].equals(server[j]))
+ return client[i];
+ }
+ }
+ throw new NegotiateException();
+ }
+
+ private boolean compareFirstOfNameList(String[] a, String[] b)
+ {
+ if (a == null || b == null)
+ throw new IllegalArgumentException();
+
+ if ((a.length == 0) && (b.length == 0))
+ return true;
+
+ if ((a.length == 0) || (b.length == 0))
+ return false;
+
+ return (a[0].equals(b[0]));
+ }
+
+ private boolean isGuessOK(KexParameters cpar, KexParameters spar)
+ {
+ if (cpar == null || spar == null)
+ throw new IllegalArgumentException();
+
+ if (compareFirstOfNameList(cpar.kex_algorithms, spar.kex_algorithms) == false)
+ {
+ return false;
+ }
+
+ if (compareFirstOfNameList(cpar.server_host_key_algorithms, spar.server_host_key_algorithms) == false)
+ {
+ return false;
+ }
+
+ /*
+ * We do NOT check here if the other algorithms can be agreed on, this
+ * is just a check if kex_algorithms and server_host_key_algorithms were
+ * guessed right!
+ */
+
+ return true;
+ }
+
+ private NegotiatedParameters mergeKexParameters(KexParameters client, KexParameters server)
+ {
+ NegotiatedParameters np = new NegotiatedParameters();
+
+ try
+ {
+ np.kex_algo = getFirstMatch(client.kex_algorithms, server.kex_algorithms);
+
+ log.log(20, "kex_algo=" + np.kex_algo);
+
+ np.server_host_key_algo = getFirstMatch(client.server_host_key_algorithms,
+ server.server_host_key_algorithms);
+
+ log.log(20, "server_host_key_algo=" + np.server_host_key_algo);
+
+ np.enc_algo_client_to_server = getFirstMatch(client.encryption_algorithms_client_to_server,
+ server.encryption_algorithms_client_to_server);
+ np.enc_algo_server_to_client = getFirstMatch(client.encryption_algorithms_server_to_client,
+ server.encryption_algorithms_server_to_client);
+
+ log.log(20, "enc_algo_client_to_server=" + np.enc_algo_client_to_server);
+ log.log(20, "enc_algo_server_to_client=" + np.enc_algo_server_to_client);
+
+ np.mac_algo_client_to_server = getFirstMatch(client.mac_algorithms_client_to_server,
+ server.mac_algorithms_client_to_server);
+ np.mac_algo_server_to_client = getFirstMatch(client.mac_algorithms_server_to_client,
+ server.mac_algorithms_server_to_client);
+
+ log.log(20, "mac_algo_client_to_server=" + np.mac_algo_client_to_server);
+ log.log(20, "mac_algo_server_to_client=" + np.mac_algo_server_to_client);
+
+ np.comp_algo_client_to_server = getFirstMatch(client.compression_algorithms_client_to_server,
+ server.compression_algorithms_client_to_server);
+ np.comp_algo_server_to_client = getFirstMatch(client.compression_algorithms_server_to_client,
+ server.compression_algorithms_server_to_client);
+
+ log.log(20, "comp_algo_client_to_server=" + np.comp_algo_client_to_server);
+ log.log(20, "comp_algo_server_to_client=" + np.comp_algo_server_to_client);
+
+ }
+ catch (NegotiateException e)
+ {
+ return null;
+ }
+
+ try
+ {
+ np.lang_client_to_server = getFirstMatch(client.languages_client_to_server,
+ server.languages_client_to_server);
+ }
+ catch (NegotiateException e1)
+ {
+ np.lang_client_to_server = null;
+ }
+
+ try
+ {
+ np.lang_server_to_client = getFirstMatch(client.languages_server_to_client,
+ server.languages_server_to_client);
+ }
+ catch (NegotiateException e2)
+ {
+ np.lang_server_to_client = null;
+ }
+
+ if (isGuessOK(client, server))
+ np.guessOK = true;
+
+ return np;
+ }
+
+ public synchronized void initiateKEX(CryptoWishList cwl, DHGexParameters dhgex) throws IOException
+ {
+ nextKEXcryptoWishList = cwl;
+ nextKEXdhgexParameters = dhgex;
+
+ if (kxs == null)
+ {
+ kxs = new KexState();
+
+ kxs.dhgexParameters = nextKEXdhgexParameters;
PacketKexInit kp = new PacketKexInit(nextKEXcryptoWishList);
- kxs.localKEX = kp;
- tm.sendKexMessage(kp.getPayload());
- }
- }
-
- private boolean establishKeyMaterial()
- {
- try
- {
- int mac_cs_key_len = MAC.getKeyLen(kxs.np.mac_algo_client_to_server);
- int enc_cs_key_len = BlockCipherFactory.getKeySize(kxs.np.enc_algo_client_to_server);
- int enc_cs_block_len = BlockCipherFactory.getBlockSize(kxs.np.enc_algo_client_to_server);
-
- int mac_sc_key_len = MAC.getKeyLen(kxs.np.mac_algo_server_to_client);
- int enc_sc_key_len = BlockCipherFactory.getKeySize(kxs.np.enc_algo_server_to_client);
- int enc_sc_block_len = BlockCipherFactory.getBlockSize(kxs.np.enc_algo_server_to_client);
-
+ kxs.localKEX = kp;
+ tm.sendKexMessage(kp.getPayload());
+ }
+ }
+
+ private boolean establishKeyMaterial()
+ {
+ try
+ {
+ int mac_cs_key_len = MAC.getKeyLen(kxs.np.mac_algo_client_to_server);
+ int enc_cs_key_len = BlockCipherFactory.getKeySize(kxs.np.enc_algo_client_to_server);
+ int enc_cs_block_len = BlockCipherFactory.getBlockSize(kxs.np.enc_algo_client_to_server);
+
+ int mac_sc_key_len = MAC.getKeyLen(kxs.np.mac_algo_server_to_client);
+ int enc_sc_key_len = BlockCipherFactory.getKeySize(kxs.np.enc_algo_server_to_client);
+ int enc_sc_block_len = BlockCipherFactory.getBlockSize(kxs.np.enc_algo_server_to_client);
+
km = KeyMaterial.create(kxs.hashAlgo, kxs.H, kxs.K, sessionId, enc_cs_key_len, enc_cs_block_len, mac_cs_key_len,
- enc_sc_key_len, enc_sc_block_len, mac_sc_key_len);
- }
- catch (IllegalArgumentException e)
- {
- return false;
- }
- return true;
- }
-
- private void finishKex() throws IOException
- {
- if (sessionId == null)
- sessionId = kxs.H;
-
- establishKeyMaterial();
-
- /* Tell the other side that we start using the new material */
-
- PacketNewKeys ign = new PacketNewKeys();
- tm.sendKexMessage(ign.getPayload());
-
- BlockCipher cbc;
- MAC mac;
- ICompressor comp;
-
- try
- {
- cbc = BlockCipherFactory.createCipher(kxs.np.enc_algo_client_to_server, true, km.enc_key_client_to_server,
- km.initial_iv_client_to_server);
-
- mac = new MAC(kxs.np.mac_algo_client_to_server, km.integrity_key_client_to_server);
-
- comp = CompressionFactory.createCompressor(kxs.np.comp_algo_client_to_server);
-
- }
- catch (IllegalArgumentException e1)
- {
- throw new IOException("Fatal error during MAC startup!");
- }
-
- tm.changeSendCipher(cbc, mac);
- tm.changeSendCompression(comp);
- tm.kexFinished();
- }
-
- public static final String[] getDefaultServerHostkeyAlgorithmList()
- {
- return HOSTKEY_ALGS.toArray(new String[HOSTKEY_ALGS.size()]);
- }
-
- public static final void checkServerHostkeyAlgorithmsList(String[] algos)
- {
- for (int i = 0; i < algos.length; i++)
- {
- if (!HOSTKEY_ALGS.contains(algos[i]))
- throw new IllegalArgumentException("Unknown server host key algorithm '" + algos[i] + "'");
- }
- }
-
- public static final String[] getDefaultKexAlgorithmList()
- {
- return KEX_ALGS.toArray(new String[KEX_ALGS.size()]);
- }
-
- public static final void checkKexAlgorithmList(String[] algos)
- {
- for (int i = 0; i < algos.length; i++)
- {
- if (!KEX_ALGS.contains(algos[i]))
- throw new IllegalArgumentException("Unknown kex algorithm '" + algos[i] + "'");
- }
- }
-
- private boolean verifySignature(byte[] sig, byte[] hostkey) throws IOException
- {
- if (kxs.np.server_host_key_algo.startsWith("ecdsa-sha2-"))
- {
- byte[] rs = ECDSASHA2Verify.decodeSSHECDSASignature(sig);
- ECPublicKey epk = ECDSASHA2Verify.decodeSSHECDSAPublicKey(hostkey);
-
- log.log(50, "Verifying ecdsa signature");
-
- return ECDSASHA2Verify.verifySignature(kxs.H, rs, epk);
- }
-
- if (kxs.np.server_host_key_algo.equals("ssh-rsa"))
- {
- byte[] rs = RSASHA1Verify.decodeSSHRSASignature(sig);
- RSAPublicKey rpk = RSASHA1Verify.decodeSSHRSAPublicKey(hostkey);
-
- log.log(50, "Verifying ssh-rsa signature");
-
- return RSASHA1Verify.verifySignature(kxs.H, rs, rpk);
- }
-
- if (kxs.np.server_host_key_algo.equals("ssh-dss"))
- {
- byte[] ds = DSASHA1Verify.decodeSSHDSASignature(sig);
- DSAPublicKey dpk = DSASHA1Verify.decodeSSHDSAPublicKey(hostkey);
-
- log.log(50, "Verifying ssh-dss signature");
-
- return DSASHA1Verify.verifySignature(kxs.H, ds, dpk);
- }
-
- throw new IOException("Unknown server host key algorithm '" + kxs.np.server_host_key_algo + "'");
- }
-
- public synchronized void handleMessage(byte[] msg, int msglen) throws IOException
- {
- PacketKexInit kip;
-
- if (msg == null)
- {
- synchronized (accessLock)
- {
- connectionClosed = true;
- accessLock.notifyAll();
- return;
- }
- }
-
- if ((kxs == null) && (msg[0] != Packets.SSH_MSG_KEXINIT))
- throw new IOException("Unexpected KEX message (type " + msg[0] + ")");
-
- if (ignore_next_kex_packet)
- {
- ignore_next_kex_packet = false;
- return;
- }
-
- if (msg[0] == Packets.SSH_MSG_KEXINIT)
- {
- if ((kxs != null) && (kxs.state != 0))
- throw new IOException("Unexpected SSH_MSG_KEXINIT message during on-going kex exchange!");
-
- if (kxs == null)
- {
- /*
- * Ah, OK, peer wants to do KEX. Let's be nice and play
- * together.
- */
- kxs = new KexState();
- kxs.dhgexParameters = nextKEXdhgexParameters;
+ enc_sc_key_len, enc_sc_block_len, mac_sc_key_len);
+ }
+ catch (IllegalArgumentException e)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ private void finishKex() throws IOException
+ {
+ if (sessionId == null)
+ sessionId = kxs.H;
+
+ establishKeyMaterial();
+
+ /* Tell the other side that we start using the new material */
+
+ PacketNewKeys ign = new PacketNewKeys();
+ tm.sendKexMessage(ign.getPayload());
+
+ BlockCipher cbc;
+ MAC mac;
+ ICompressor comp;
+
+ try
+ {
+ cbc = BlockCipherFactory.createCipher(kxs.np.enc_algo_client_to_server, true, km.enc_key_client_to_server,
+ km.initial_iv_client_to_server);
+
+ mac = new MAC(kxs.np.mac_algo_client_to_server, km.integrity_key_client_to_server);
+
+ comp = CompressionFactory.createCompressor(kxs.np.comp_algo_client_to_server);
+
+ }
+ catch (IllegalArgumentException e1)
+ {
+ throw new IOException("Fatal error during MAC startup!");
+ }
+
+ tm.changeSendCipher(cbc, mac);
+ tm.changeSendCompression(comp);
+ tm.kexFinished();
+ }
+
+ public static final String[] getDefaultServerHostkeyAlgorithmList()
+ {
+ return HOSTKEY_ALGS.toArray(new String[HOSTKEY_ALGS.size()]);
+ }
+
+ public static final void checkServerHostkeyAlgorithmsList(String[] algos)
+ {
+ for (int i = 0; i < algos.length; i++)
+ {
+ if (!HOSTKEY_ALGS.contains(algos[i]))
+ throw new IllegalArgumentException("Unknown server host key algorithm '" + algos[i] + "'");
+ }
+ }
+
+ public static final String[] getDefaultKexAlgorithmList()
+ {
+ return KEX_ALGS.toArray(new String[KEX_ALGS.size()]);
+ }
+
+ public static final void checkKexAlgorithmList(String[] algos)
+ {
+ for (int i = 0; i < algos.length; i++)
+ {
+ if (!KEX_ALGS.contains(algos[i]))
+ throw new IllegalArgumentException("Unknown kex algorithm '" + algos[i] + "'");
+ }
+ }
+
+ private boolean verifySignature(byte[] sig, byte[] hostkey) throws IOException
+ {
+ if (kxs.np.server_host_key_algo.startsWith("ecdsa-sha2-"))
+ {
+ byte[] rs = ECDSASHA2Verify.decodeSSHECDSASignature(sig);
+ ECPublicKey epk = ECDSASHA2Verify.decodeSSHECDSAPublicKey(hostkey);
+
+ log.log(50, "Verifying ecdsa signature");
+
+ return ECDSASHA2Verify.verifySignature(kxs.H, rs, epk);
+ }
+
+ if (kxs.np.server_host_key_algo.equals("ssh-rsa"))
+ {
+ byte[] rs = RSASHA1Verify.decodeSSHRSASignature(sig);
+ RSAPublicKey rpk = RSASHA1Verify.decodeSSHRSAPublicKey(hostkey);
+
+ log.log(50, "Verifying ssh-rsa signature");
+
+ return RSASHA1Verify.verifySignature(kxs.H, rs, rpk);
+ }
+
+ if (kxs.np.server_host_key_algo.equals("ssh-dss"))
+ {
+ byte[] ds = DSASHA1Verify.decodeSSHDSASignature(sig);
+ DSAPublicKey dpk = DSASHA1Verify.decodeSSHDSAPublicKey(hostkey);
+
+ log.log(50, "Verifying ssh-dss signature");
+
+ return DSASHA1Verify.verifySignature(kxs.H, ds, dpk);
+ }
+
+ throw new IOException("Unknown server host key algorithm '" + kxs.np.server_host_key_algo + "'");
+ }
+
+ public synchronized void handleMessage(byte[] msg, int msglen) throws IOException
+ {
+ PacketKexInit kip;
+
+ if (msg == null)
+ {
+ synchronized (accessLock)
+ {
+ connectionClosed = true;
+ accessLock.notifyAll();
+ return;
+ }
+ }
+
+ if ((kxs == null) && (msg[0] != Packets.SSH_MSG_KEXINIT))
+ throw new IOException("Unexpected KEX message (type " + msg[0] + ")");
+
+ if (ignore_next_kex_packet)
+ {
+ ignore_next_kex_packet = false;
+ return;
+ }
+
+ if (msg[0] == Packets.SSH_MSG_KEXINIT)
+ {
+ if ((kxs != null) && (kxs.state != 0))
+ throw new IOException("Unexpected SSH_MSG_KEXINIT message during on-going kex exchange!");
+
+ if (kxs == null)
+ {
+ /*
+ * Ah, OK, peer wants to do KEX. Let's be nice and play
+ * together.
+ */
+ kxs = new KexState();
+ kxs.dhgexParameters = nextKEXdhgexParameters;
kip = new PacketKexInit(nextKEXcryptoWishList);
- kxs.localKEX = kip;
- tm.sendKexMessage(kip.getPayload());
- }
-
- kip = new PacketKexInit(msg, 0, msglen);
- kxs.remoteKEX = kip;
-
- kxs.np = mergeKexParameters(kxs.localKEX.getKexParameters(), kxs.remoteKEX.getKexParameters());
-
- if (kxs.np == null)
- throw new IOException("Cannot negotiate, proposals do not match.");
-
- if (kxs.remoteKEX.isFirst_kex_packet_follows() && (kxs.np.guessOK == false))
- {
- /*
- * Guess was wrong, we need to ignore the next kex packet.
- */
-
- ignore_next_kex_packet = true;
- }
-
- if (kxs.np.kex_algo.equals("diffie-hellman-group-exchange-sha1"))
- {
- if (kxs.dhgexParameters.getMin_group_len() == 0 || csh.server_versioncomment.matches("OpenSSH_2\\.([0-4]\\.|5\\.[0-2]).*"))
- {
- PacketKexDhGexRequestOld dhgexreq = new PacketKexDhGexRequestOld(kxs.dhgexParameters);
- tm.sendKexMessage(dhgexreq.getPayload());
-
- }
- else
- {
- PacketKexDhGexRequest dhgexreq = new PacketKexDhGexRequest(kxs.dhgexParameters);
- tm.sendKexMessage(dhgexreq.getPayload());
- }
+ kxs.localKEX = kip;
+ tm.sendKexMessage(kip.getPayload());
+ }
+
+ kip = new PacketKexInit(msg, 0, msglen);
+ kxs.remoteKEX = kip;
+
+ kxs.np = mergeKexParameters(kxs.localKEX.getKexParameters(), kxs.remoteKEX.getKexParameters());
+
+ if (kxs.np == null)
+ throw new IOException("Cannot negotiate, proposals do not match.");
+
+ if (kxs.remoteKEX.isFirst_kex_packet_follows() && (kxs.np.guessOK == false))
+ {
+ /*
+ * Guess was wrong, we need to ignore the next kex packet.
+ */
+
+ ignore_next_kex_packet = true;
+ }
+
+ if (kxs.np.kex_algo.equals("diffie-hellman-group-exchange-sha1"))
+ {
+ if (kxs.dhgexParameters.getMin_group_len() == 0 || csh.server_versioncomment.matches("OpenSSH_2\\.([0-4]\\.|5\\.[0-2]).*"))
+ {
+ PacketKexDhGexRequestOld dhgexreq = new PacketKexDhGexRequestOld(kxs.dhgexParameters);
+ tm.sendKexMessage(dhgexreq.getPayload());
+
+ }
+ else
+ {
+ PacketKexDhGexRequest dhgexreq = new PacketKexDhGexRequest(kxs.dhgexParameters);
+ tm.sendKexMessage(dhgexreq.getPayload());
+ }
kxs.hashAlgo = "SHA1";
- kxs.state = 1;
- return;
- }
-
- if (kxs.np.kex_algo.equals("diffie-hellman-group1-sha1")
+ kxs.state = 1;
+ return;
+ }
+
+ if (kxs.np.kex_algo.equals("diffie-hellman-group1-sha1")
|| kxs.np.kex_algo.equals("diffie-hellman-group14-sha1")
|| kxs.np.kex_algo.equals("ecdh-sha2-nistp256")
|| kxs.np.kex_algo.equals("ecdh-sha2-nistp384")
|| kxs.np.kex_algo.equals("ecdh-sha2-nistp521")) {
kxs.dhx = GenericDhExchange.getInstance(kxs.np.kex_algo);
-
+
kxs.dhx.init(kxs.np.kex_algo);
kxs.hashAlgo = kxs.dhx.getHashAlgo();
-
- PacketKexDHInit kp = new PacketKexDHInit(kxs.dhx.getE());
- tm.sendKexMessage(kp.getPayload());
- kxs.state = 1;
- return;
- }
-
- throw new IllegalStateException("Unknown KEX method!");
- }
-
- if (msg[0] == Packets.SSH_MSG_NEWKEYS)
- {
- if (km == null)
- throw new IOException("Peer sent SSH_MSG_NEWKEYS, but I have no key material ready!");
-
- BlockCipher cbc;
- MAC mac;
- ICompressor comp;
-
- try
- {
- cbc = BlockCipherFactory.createCipher(kxs.np.enc_algo_server_to_client, false,
- km.enc_key_server_to_client, km.initial_iv_server_to_client);
-
- mac = new MAC(kxs.np.mac_algo_server_to_client, km.integrity_key_server_to_client);
-
- comp = CompressionFactory.createCompressor(kxs.np.comp_algo_server_to_client);
- }
- catch (IllegalArgumentException e1)
- {
- throw new IOException("Fatal error during MAC startup!");
- }
-
- tm.changeRecvCipher(cbc, mac);
- tm.changeRecvCompression(comp);
-
- ConnectionInfo sci = new ConnectionInfo();
-
- kexCount++;
-
- sci.keyExchangeAlgorithm = kxs.np.kex_algo;
- sci.keyExchangeCounter = kexCount;
- sci.clientToServerCryptoAlgorithm = kxs.np.enc_algo_client_to_server;
- sci.serverToClientCryptoAlgorithm = kxs.np.enc_algo_server_to_client;
- sci.clientToServerMACAlgorithm = kxs.np.mac_algo_client_to_server;
- sci.serverToClientMACAlgorithm = kxs.np.mac_algo_server_to_client;
- sci.serverHostKeyAlgorithm = kxs.np.server_host_key_algo;
- sci.serverHostKey = kxs.hostkey;
-
- synchronized (accessLock)
- {
- lastConnInfo = sci;
- accessLock.notifyAll();
- }
-
- kxs = null;
- return;
- }
-
- if ((kxs == null) || (kxs.state == 0))
- throw new IOException("Unexpected Kex submessage!");
-
- if (kxs.np.kex_algo.equals("diffie-hellman-group-exchange-sha1"))
- {
- if (kxs.state == 1)
- {
- PacketKexDhGexGroup dhgexgrp = new PacketKexDhGexGroup(msg, 0, msglen);
- kxs.dhgx = new DhGroupExchange(dhgexgrp.getP(), dhgexgrp.getG());
- kxs.dhgx.init(rnd);
- PacketKexDhGexInit dhgexinit = new PacketKexDhGexInit(kxs.dhgx.getE());
- tm.sendKexMessage(dhgexinit.getPayload());
- kxs.state = 2;
- return;
- }
-
- if (kxs.state == 2)
- {
- PacketKexDhGexReply dhgexrpl = new PacketKexDhGexReply(msg, 0, msglen);
-
- kxs.hostkey = dhgexrpl.getHostKey();
-
- if (verifier != null)
- {
- boolean vres = false;
-
- try
- {
- vres = verifier.verifyServerHostKey(hostname, port, kxs.np.server_host_key_algo, kxs.hostkey);
- }
- catch (Exception e)
- {
- throw (IOException) new IOException(
- "The server hostkey was not accepted by the verifier callback.").initCause(e);
- }
-
- if (vres == false)
- throw new IOException("The server hostkey was not accepted by the verifier callback");
- }
-
- kxs.dhgx.setF(dhgexrpl.getF());
-
- try
- {
- kxs.H = kxs.dhgx.calculateH(csh.getClientString(), csh.getServerString(),
- kxs.localKEX.getPayload(), kxs.remoteKEX.getPayload(), dhgexrpl.getHostKey(),
- kxs.dhgexParameters);
- }
- catch (IllegalArgumentException e)
- {
- throw (IOException) new IOException("KEX error.").initCause(e);
- }
-
- boolean res = verifySignature(dhgexrpl.getSignature(), kxs.hostkey);
-
- if (res == false)
- throw new IOException("Hostkey signature sent by remote is wrong!");
-
- kxs.K = kxs.dhgx.getK();
-
- finishKex();
- kxs.state = -1;
- return;
- }
-
- throw new IllegalStateException("Illegal State in KEX Exchange!");
- }
-
- if (kxs.np.kex_algo.equals("diffie-hellman-group1-sha1")
+
+ PacketKexDHInit kp = new PacketKexDHInit(kxs.dhx.getE());
+ tm.sendKexMessage(kp.getPayload());
+ kxs.state = 1;
+ return;
+ }
+
+ throw new IllegalStateException("Unknown KEX method!");
+ }
+
+ if (msg[0] == Packets.SSH_MSG_NEWKEYS)
+ {
+ if (km == null)
+ throw new IOException("Peer sent SSH_MSG_NEWKEYS, but I have no key material ready!");
+
+ BlockCipher cbc;
+ MAC mac;
+ ICompressor comp;
+
+ try
+ {
+ cbc = BlockCipherFactory.createCipher(kxs.np.enc_algo_server_to_client, false,
+ km.enc_key_server_to_client, km.initial_iv_server_to_client);
+
+ mac = new MAC(kxs.np.mac_algo_server_to_client, km.integrity_key_server_to_client);
+
+ comp = CompressionFactory.createCompressor(kxs.np.comp_algo_server_to_client);
+ }
+ catch (IllegalArgumentException e1)
+ {
+ throw new IOException("Fatal error during MAC startup!");
+ }
+
+ tm.changeRecvCipher(cbc, mac);
+ tm.changeRecvCompression(comp);
+
+ ConnectionInfo sci = new ConnectionInfo();
+
+ kexCount++;
+
+ sci.keyExchangeAlgorithm = kxs.np.kex_algo;
+ sci.keyExchangeCounter = kexCount;
+ sci.clientToServerCryptoAlgorithm = kxs.np.enc_algo_client_to_server;
+ sci.serverToClientCryptoAlgorithm = kxs.np.enc_algo_server_to_client;
+ sci.clientToServerMACAlgorithm = kxs.np.mac_algo_client_to_server;
+ sci.serverToClientMACAlgorithm = kxs.np.mac_algo_server_to_client;
+ sci.serverHostKeyAlgorithm = kxs.np.server_host_key_algo;
+ sci.serverHostKey = kxs.hostkey;
+
+ synchronized (accessLock)
+ {
+ lastConnInfo = sci;
+ accessLock.notifyAll();
+ }
+
+ kxs = null;
+ return;
+ }
+
+ if ((kxs == null) || (kxs.state == 0))
+ throw new IOException("Unexpected Kex submessage!");
+
+ if (kxs.np.kex_algo.equals("diffie-hellman-group-exchange-sha1"))
+ {
+ if (kxs.state == 1)
+ {
+ PacketKexDhGexGroup dhgexgrp = new PacketKexDhGexGroup(msg, 0, msglen);
+ kxs.dhgx = new DhGroupExchange(dhgexgrp.getP(), dhgexgrp.getG());
+ kxs.dhgx.init(rnd);
+ PacketKexDhGexInit dhgexinit = new PacketKexDhGexInit(kxs.dhgx.getE());
+ tm.sendKexMessage(dhgexinit.getPayload());
+ kxs.state = 2;
+ return;
+ }
+
+ if (kxs.state == 2)
+ {
+ PacketKexDhGexReply dhgexrpl = new PacketKexDhGexReply(msg, 0, msglen);
+
+ kxs.hostkey = dhgexrpl.getHostKey();
+
+ if (verifier != null)
+ {
+ boolean vres = false;
+
+ try
+ {
+ vres = verifier.verifyServerHostKey(hostname, port, kxs.np.server_host_key_algo, kxs.hostkey);
+ }
+ catch (Exception e)
+ {
+ throw (IOException) new IOException(
+ "The server hostkey was not accepted by the verifier callback.").initCause(e);
+ }
+
+ if (vres == false)
+ throw new IOException("The server hostkey was not accepted by the verifier callback");
+ }
+
+ kxs.dhgx.setF(dhgexrpl.getF());
+
+ try
+ {
+ kxs.H = kxs.dhgx.calculateH(csh.getClientString(), csh.getServerString(),
+ kxs.localKEX.getPayload(), kxs.remoteKEX.getPayload(), dhgexrpl.getHostKey(),
+ kxs.dhgexParameters);
+ }
+ catch (IllegalArgumentException e)
+ {
+ throw (IOException) new IOException("KEX error.").initCause(e);
+ }
+
+ boolean res = verifySignature(dhgexrpl.getSignature(), kxs.hostkey);
+
+ if (res == false)
+ throw new IOException("Hostkey signature sent by remote is wrong!");
+
+ kxs.K = kxs.dhgx.getK();
+
+ finishKex();
+ kxs.state = -1;
+ return;
+ }
+
+ throw new IllegalStateException("Illegal State in KEX Exchange!");
+ }
+
+ if (kxs.np.kex_algo.equals("diffie-hellman-group1-sha1")
|| kxs.np.kex_algo.equals("diffie-hellman-group14-sha1")
|| kxs.np.kex_algo.equals("ecdh-sha2-nistp256")
|| kxs.np.kex_algo.equals("ecdh-sha2-nistp384")
|| kxs.np.kex_algo.equals("ecdh-sha2-nistp521"))
- {
- if (kxs.state == 1)
- {
-
- PacketKexDHReply dhr = new PacketKexDHReply(msg, 0, msglen);
-
- kxs.hostkey = dhr.getHostKey();
-
- if (verifier != null)
- {
- boolean vres = false;
-
- try
- {
- vres = verifier.verifyServerHostKey(hostname, port, kxs.np.server_host_key_algo, kxs.hostkey);
- }
- catch (Exception e)
- {
- throw (IOException) new IOException(
- "The server hostkey was not accepted by the verifier callback.").initCause(e);
- }
-
- if (vres == false)
- throw new IOException("The server hostkey was not accepted by the verifier callback");
- }
-
- kxs.dhx.setF(dhr.getF());
-
- try
- {
- kxs.H = kxs.dhx.calculateH(csh.getClientString(), csh.getServerString(), kxs.localKEX.getPayload(),
- kxs.remoteKEX.getPayload(), dhr.getHostKey());
- }
- catch (IllegalArgumentException e)
- {
- throw (IOException) new IOException("KEX error.").initCause(e);
- }
-
- boolean res = verifySignature(dhr.getSignature(), kxs.hostkey);
-
- if (res == false)
- throw new IOException("Hostkey signature sent by remote is wrong!");
-
- kxs.K = kxs.dhx.getK();
-
- finishKex();
- kxs.state = -1;
- return;
- }
- }
-
- throw new IllegalStateException("Unkown KEX method! (" + kxs.np.kex_algo + ")");
- }
-}
+ {
+ if (kxs.state == 1)
+ {
+
+ PacketKexDHReply dhr = new PacketKexDHReply(msg, 0, msglen);
+
+ kxs.hostkey = dhr.getHostKey();
+
+ if (verifier != null)
+ {
+ boolean vres = false;
+
+ try
+ {
+ vres = verifier.verifyServerHostKey(hostname, port, kxs.np.server_host_key_algo, kxs.hostkey);
+ }
+ catch (Exception e)
+ {
+ throw (IOException) new IOException(
+ "The server hostkey was not accepted by the verifier callback.").initCause(e);
+ }
+
+ if (vres == false)
+ throw new IOException("The server hostkey was not accepted by the verifier callback");
+ }
+
+ kxs.dhx.setF(dhr.getF());
+
+ try
+ {
+ kxs.H = kxs.dhx.calculateH(csh.getClientString(), csh.getServerString(), kxs.localKEX.getPayload(),
+ kxs.remoteKEX.getPayload(), dhr.getHostKey());
+ }
+ catch (IllegalArgumentException e)
+ {
+ throw (IOException) new IOException("KEX error.").initCause(e);
+ }
+
+ boolean res = verifySignature(dhr.getSignature(), kxs.hostkey);
+
+ if (res == false)
+ throw new IOException("Hostkey signature sent by remote is wrong!");
+
+ kxs.K = kxs.dhx.getK();
+
+ finishKex();
+ kxs.state = -1;
+ return;
+ }
+ }
+
+ throw new IllegalStateException("Unkown KEX method! (" + kxs.np.kex_algo + ")");
+ }
+}
diff --git a/lib/src/main/java/com/trilead/ssh2/transport/KexParameters.java b/lib/src/main/java/com/trilead/ssh2/transport/KexParameters.java
index 6e7e08f..70bcf3e 100644
--- a/lib/src/main/java/com/trilead/ssh2/transport/KexParameters.java
+++ b/lib/src/main/java/com/trilead/ssh2/transport/KexParameters.java
@@ -1,24 +1,24 @@
-package com.trilead.ssh2.transport;
-
-/**
- * KexParameters.
- *
- * @author Christian Plattner, plattner@trilead.com
- * @version $Id: KexParameters.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
- */
-public class KexParameters
-{
- public byte[] cookie;
- public String[] kex_algorithms;
- public String[] server_host_key_algorithms;
- public String[] encryption_algorithms_client_to_server;
- public String[] encryption_algorithms_server_to_client;
- public String[] mac_algorithms_client_to_server;
- public String[] mac_algorithms_server_to_client;
- public String[] compression_algorithms_client_to_server;
- public String[] compression_algorithms_server_to_client;
- public String[] languages_client_to_server;
- public String[] languages_server_to_client;
- public boolean first_kex_packet_follows;
- public int reserved_field1;
-}
+package com.trilead.ssh2.transport;
+
+/**
+ * KexParameters.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: KexParameters.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ */
+public class KexParameters
+{
+ public byte[] cookie;
+ public String[] kex_algorithms;
+ public String[] server_host_key_algorithms;
+ public String[] encryption_algorithms_client_to_server;
+ public String[] encryption_algorithms_server_to_client;
+ public String[] mac_algorithms_client_to_server;
+ public String[] mac_algorithms_server_to_client;
+ public String[] compression_algorithms_client_to_server;
+ public String[] compression_algorithms_server_to_client;
+ public String[] languages_client_to_server;
+ public String[] languages_server_to_client;
+ public boolean first_kex_packet_follows;
+ public int reserved_field1;
+}
diff --git a/lib/src/main/java/com/trilead/ssh2/transport/KexState.java b/lib/src/main/java/com/trilead/ssh2/transport/KexState.java
index d9f1004..8611f3f 100644
--- a/lib/src/main/java/com/trilead/ssh2/transport/KexState.java
+++ b/lib/src/main/java/com/trilead/ssh2/transport/KexState.java
@@ -1,33 +1,33 @@
-package com.trilead.ssh2.transport;
-
-
-import java.math.BigInteger;
-
-import com.trilead.ssh2.DHGexParameters;
-import com.trilead.ssh2.crypto.dh.DhGroupExchange;
+package com.trilead.ssh2.transport;
+
+
+import java.math.BigInteger;
+
+import com.trilead.ssh2.DHGexParameters;
+import com.trilead.ssh2.crypto.dh.DhGroupExchange;
import com.trilead.ssh2.crypto.dh.GenericDhExchange;
-import com.trilead.ssh2.packets.PacketKexInit;
-
-/**
- * KexState.
- *
- * @author Christian Plattner, plattner@trilead.com
- * @version $Id: KexState.java,v 1.1 2007/10/15 12:49:57 cplattne Exp $
- */
-public class KexState
-{
- public PacketKexInit localKEX;
- public PacketKexInit remoteKEX;
- public NegotiatedParameters np;
- public int state = 0;
-
- public BigInteger K;
- public byte[] H;
-
- public byte[] hostkey;
-
+import com.trilead.ssh2.packets.PacketKexInit;
+
+/**
+ * KexState.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: KexState.java,v 1.1 2007/10/15 12:49:57 cplattne Exp $
+ */
+public class KexState
+{
+ public PacketKexInit localKEX;
+ public PacketKexInit remoteKEX;
+ public NegotiatedParameters np;
+ public int state = 0;
+
+ public BigInteger K;
+ public byte[] H;
+
+ public byte[] hostkey;
+
public String hashAlgo;
public GenericDhExchange dhx;
- public DhGroupExchange dhgx;
- public DHGexParameters dhgexParameters;
-}
+ public DhGroupExchange dhgx;
+ public DHGexParameters dhgexParameters;
+}
diff --git a/lib/src/main/java/com/trilead/ssh2/transport/MessageHandler.java b/lib/src/main/java/com/trilead/ssh2/transport/MessageHandler.java
index 1f173e8..039d473 100644
--- a/lib/src/main/java/com/trilead/ssh2/transport/MessageHandler.java
+++ b/lib/src/main/java/com/trilead/ssh2/transport/MessageHandler.java
@@ -1,14 +1,14 @@
-package com.trilead.ssh2.transport;
-
-import java.io.IOException;
-
-/**
- * MessageHandler.
- *
- * @author Christian Plattner, plattner@trilead.com
- * @version $Id: MessageHandler.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
- */
-public interface MessageHandler
-{
- public void handleMessage(byte[] msg, int msglen) throws IOException;
-}
+package com.trilead.ssh2.transport;
+
+import java.io.IOException;
+
+/**
+ * MessageHandler.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: MessageHandler.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ */
+public interface MessageHandler
+{
+ public void handleMessage(byte[] msg, int msglen) throws IOException;
+}
diff --git a/lib/src/main/java/com/trilead/ssh2/transport/NegotiateException.java b/lib/src/main/java/com/trilead/ssh2/transport/NegotiateException.java
index 8562a65..ff53097 100644
--- a/lib/src/main/java/com/trilead/ssh2/transport/NegotiateException.java
+++ b/lib/src/main/java/com/trilead/ssh2/transport/NegotiateException.java
@@ -1,12 +1,12 @@
-package com.trilead.ssh2.transport;
-
-/**
- * NegotiateException.
- *
- * @author Christian Plattner, plattner@trilead.com
- * @version $Id: NegotiateException.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
- */
-public class NegotiateException extends Exception
-{
- private static final long serialVersionUID = 3689910669428143157L;
-}
+package com.trilead.ssh2.transport;
+
+/**
+ * NegotiateException.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: NegotiateException.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ */
+public class NegotiateException extends Exception
+{
+ private static final long serialVersionUID = 3689910669428143157L;
+}
diff --git a/lib/src/main/java/com/trilead/ssh2/transport/NegotiatedParameters.java b/lib/src/main/java/com/trilead/ssh2/transport/NegotiatedParameters.java
index 6f035c0..e9f3a0a 100644
--- a/lib/src/main/java/com/trilead/ssh2/transport/NegotiatedParameters.java
+++ b/lib/src/main/java/com/trilead/ssh2/transport/NegotiatedParameters.java
@@ -1,22 +1,22 @@
-package com.trilead.ssh2.transport;
-
-/**
- * NegotiatedParameters.
- *
- * @author Christian Plattner, plattner@trilead.com
- * @version $Id: NegotiatedParameters.java,v 1.1 2007/10/15 12:49:57 cplattne Exp $
- */
-public class NegotiatedParameters
-{
- public boolean guessOK;
- public String kex_algo;
- public String server_host_key_algo;
- public String enc_algo_client_to_server;
- public String enc_algo_server_to_client;
- public String mac_algo_client_to_server;
- public String mac_algo_server_to_client;
- public String comp_algo_client_to_server;
- public String comp_algo_server_to_client;
- public String lang_client_to_server;
- public String lang_server_to_client;
-}
+package com.trilead.ssh2.transport;
+
+/**
+ * NegotiatedParameters.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: NegotiatedParameters.java,v 1.1 2007/10/15 12:49:57 cplattne Exp $
+ */
+public class NegotiatedParameters
+{
+ public boolean guessOK;
+ public String kex_algo;
+ public String server_host_key_algo;
+ public String enc_algo_client_to_server;
+ public String enc_algo_server_to_client;
+ public String mac_algo_client_to_server;
+ public String mac_algo_server_to_client;
+ public String comp_algo_client_to_server;
+ public String comp_algo_server_to_client;
+ public String lang_client_to_server;
+ public String lang_server_to_client;
+}
diff --git a/lib/src/main/java/com/trilead/ssh2/transport/TransportConnection.java b/lib/src/main/java/com/trilead/ssh2/transport/TransportConnection.java
index 2c04ce4..906c3c9 100644
--- a/lib/src/main/java/com/trilead/ssh2/transport/TransportConnection.java
+++ b/lib/src/main/java/com/trilead/ssh2/transport/TransportConnection.java
@@ -1,343 +1,343 @@
-
-package com.trilead.ssh2.transport;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.security.SecureRandom;
-
-import com.trilead.ssh2.compression.ICompressor;
-import com.trilead.ssh2.crypto.cipher.BlockCipher;
-import com.trilead.ssh2.crypto.cipher.CipherInputStream;
-import com.trilead.ssh2.crypto.cipher.CipherOutputStream;
-import com.trilead.ssh2.crypto.cipher.NullCipher;
-import com.trilead.ssh2.crypto.digest.MAC;
-import com.trilead.ssh2.log.Logger;
-import com.trilead.ssh2.packets.Packets;
-
-
-/**
- * TransportConnection.
- *
- * @author Christian Plattner, plattner@trilead.com
- * @version $Id: TransportConnection.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
- */
-public class TransportConnection
-{
- private static final Logger log = Logger.getLogger(TransportConnection.class);
-
- int send_seq_number = 0;
-
- int recv_seq_number = 0;
-
- CipherInputStream cis;
-
- CipherOutputStream cos;
-
- boolean useRandomPadding = false;
-
- /* Depends on current MAC and CIPHER */
-
- MAC send_mac;
-
- byte[] send_mac_buffer;
-
- int send_padd_blocksize = 8;
-
- MAC recv_mac;
-
- byte[] recv_mac_buffer;
-
- byte[] recv_mac_buffer_cmp;
-
- int recv_padd_blocksize = 8;
-
- ICompressor recv_comp = null;
-
- ICompressor send_comp = null;
-
- boolean can_recv_compress = false;
-
- boolean can_send_compress = false;
-
- byte[] recv_comp_buffer;
-
- byte[] send_comp_buffer;
-
- /* won't change */
-
- final byte[] send_padding_buffer = new byte[256];
-
- final byte[] send_packet_header_buffer = new byte[5];
-
- final byte[] recv_padding_buffer = new byte[256];
-
- final byte[] recv_packet_header_buffer = new byte[5];
-
- boolean recv_packet_header_present = false;
-
- ClientServerHello csh;
-
- final SecureRandom rnd;
-
- public TransportConnection(InputStream is, OutputStream os, SecureRandom rnd)
- {
- this.cis = new CipherInputStream(new NullCipher(), is);
- this.cos = new CipherOutputStream(new NullCipher(), os);
- this.rnd = rnd;
- }
-
- public void changeRecvCipher(BlockCipher bc, MAC mac)
- {
- cis.changeCipher(bc);
- recv_mac = mac;
- recv_mac_buffer = (mac != null) ? new byte[mac.size()] : null;
- recv_mac_buffer_cmp = (mac != null) ? new byte[mac.size()] : null;
- recv_padd_blocksize = bc.getBlockSize();
- if (recv_padd_blocksize < 8)
- recv_padd_blocksize = 8;
- }
-
- public void changeSendCipher(BlockCipher bc, MAC mac)
- {
- if ((bc instanceof NullCipher) == false)
- {
- /* Only use zero byte padding for the first few packets */
- useRandomPadding = true;
- /* Once we start encrypting, there is no way back */
- }
-
- cos.changeCipher(bc);
- send_mac = mac;
- send_mac_buffer = (mac != null) ? new byte[mac.size()] : null;
- send_padd_blocksize = bc.getBlockSize();
- if (send_padd_blocksize < 8)
- send_padd_blocksize = 8;
- }
-
- public void changeRecvCompression(ICompressor comp)
- {
- recv_comp = comp;
-
- if (comp != null) {
- recv_comp_buffer = new byte[comp.getBufferSize()];
- can_recv_compress |= recv_comp.canCompressPreauth();
- }
- }
-
- public void changeSendCompression(ICompressor comp)
- {
- send_comp = comp;
-
- if (comp != null) {
- send_comp_buffer = new byte[comp.getBufferSize()];
- can_send_compress |= send_comp.canCompressPreauth();
- }
- }
-
- public void sendMessage(byte[] message) throws IOException
- {
- sendMessage(message, 0, message.length, 0);
- }
-
- public void sendMessage(byte[] message, int off, int len) throws IOException
- {
- sendMessage(message, off, len, 0);
- }
-
- public int getPacketOverheadEstimate()
- {
- // return an estimate for the paket overhead (for send operations)
- return 5 + 4 + (send_padd_blocksize - 1) + send_mac_buffer.length;
- }
-
- public void sendMessage(byte[] message, int off, int len, int padd) throws IOException
- {
- if (padd < 4)
- padd = 4;
- else if (padd > 64)
- padd = 64;
-
- if (send_comp != null && can_send_compress) {
- if (send_comp_buffer.length < message.length + 1024)
- send_comp_buffer = new byte[message.length + 1024];
- len = send_comp.compress(message, off, len, send_comp_buffer);
- message = send_comp_buffer;
- }
-
- int packet_len = 5 + len + padd; /* Minimum allowed padding is 4 */
-
- int slack = packet_len % send_padd_blocksize;
-
- if (slack != 0)
- {
- packet_len += (send_padd_blocksize - slack);
- }
-
- if (packet_len < 16)
- packet_len = 16;
-
- int padd_len = packet_len - (5 + len);
-
- if (useRandomPadding)
- {
- for (int i = 0; i < padd_len; i = i + 4)
- {
- /*
- * don't waste calls to rnd.nextInt() (by using only 8bit of the
- * output). just believe me: even though we may write here up to 3
- * bytes which won't be used, there is no "buffer overflow" (i.e.,
- * arrayindexoutofbounds). the padding buffer is big enough =) (256
- * bytes, and that is bigger than any current cipher block size + 64).
- */
-
- int r = rnd.nextInt();
- send_padding_buffer[i] = (byte) r;
- send_padding_buffer[i + 1] = (byte) (r >> 8);
- send_padding_buffer[i + 2] = (byte) (r >> 16);
- send_padding_buffer[i + 3] = (byte) (r >> 24);
- }
- }
- else
- {
- /* use zero padding for unencrypted traffic */
- for (int i = 0; i < padd_len; i++)
- send_padding_buffer[i] = 0;
- /* Actually this code is paranoid: we never filled any
- * bytes into the padding buffer so far, therefore it should
- * consist of zeros only.
- */
- }
-
- send_packet_header_buffer[0] = (byte) ((packet_len - 4) >> 24);
- send_packet_header_buffer[1] = (byte) ((packet_len - 4) >> 16);
- send_packet_header_buffer[2] = (byte) ((packet_len - 4) >> 8);
- send_packet_header_buffer[3] = (byte) ((packet_len - 4));
- send_packet_header_buffer[4] = (byte) padd_len;
-
- cos.write(send_packet_header_buffer, 0, 5);
- cos.write(message, off, len);
- cos.write(send_padding_buffer, 0, padd_len);
-
- if (send_mac != null)
- {
- send_mac.initMac(send_seq_number);
- send_mac.update(send_packet_header_buffer, 0, 5);
- send_mac.update(message, off, len);
- send_mac.update(send_padding_buffer, 0, padd_len);
-
- send_mac.getMac(send_mac_buffer, 0);
- cos.writePlain(send_mac_buffer, 0, send_mac_buffer.length);
- }
-
- cos.flush();
-
- if (log.isEnabled())
- {
- log.log(90, "Sent " + Packets.getMessageName(message[off] & 0xff) + " " + len + " bytes payload");
- }
-
- send_seq_number++;
- }
-
- public int peekNextMessageLength() throws IOException
- {
- if (recv_packet_header_present == false)
- {
- cis.read(recv_packet_header_buffer, 0, 5);
- recv_packet_header_present = true;
- }
-
- int packet_length = ((recv_packet_header_buffer[0] & 0xff) << 24)
- | ((recv_packet_header_buffer[1] & 0xff) << 16) | ((recv_packet_header_buffer[2] & 0xff) << 8)
- | ((recv_packet_header_buffer[3] & 0xff));
-
- int padding_length = recv_packet_header_buffer[4] & 0xff;
-
- if (packet_length > 35000 || packet_length < 12)
- throw new IOException("Illegal packet size! (" + packet_length + ")");
-
- int payload_length = packet_length - padding_length - 1;
-
- if (payload_length < 0)
- throw new IOException("Illegal padding_length in packet from remote (" + padding_length + ")");
-
- return payload_length;
- }
-
- public int receiveMessage(byte buffer[], int off, int len) throws IOException
- {
- if (recv_packet_header_present == false)
- {
- cis.read(recv_packet_header_buffer, 0, 5);
- }
- else
- recv_packet_header_present = false;
-
- int packet_length = ((recv_packet_header_buffer[0] & 0xff) << 24)
- | ((recv_packet_header_buffer[1] & 0xff) << 16) | ((recv_packet_header_buffer[2] & 0xff) << 8)
- | ((recv_packet_header_buffer[3] & 0xff));
-
- int padding_length = recv_packet_header_buffer[4] & 0xff;
-
- if (packet_length > 35000 || packet_length < 12)
- throw new IOException("Illegal packet size! (" + packet_length + ")");
-
- int payload_length = packet_length - padding_length - 1;
-
- if (payload_length < 0)
- throw new IOException("Illegal padding_length in packet from remote (" + padding_length + ")");
-
- if (payload_length >= len)
- throw new IOException("Receive buffer too small (" + len + ", need " + payload_length + ")");
-
- cis.read(buffer, off, payload_length);
- cis.read(recv_padding_buffer, 0, padding_length);
-
- if (recv_mac != null)
- {
- cis.readPlain(recv_mac_buffer, 0, recv_mac_buffer.length);
-
- recv_mac.initMac(recv_seq_number);
- recv_mac.update(recv_packet_header_buffer, 0, 5);
- recv_mac.update(buffer, off, payload_length);
- recv_mac.update(recv_padding_buffer, 0, padding_length);
- recv_mac.getMac(recv_mac_buffer_cmp, 0);
-
- for (int i = 0; i < recv_mac_buffer.length; i++)
- {
- if (recv_mac_buffer[i] != recv_mac_buffer_cmp[i])
- throw new IOException("Remote sent corrupt MAC.");
- }
- }
-
- recv_seq_number++;
-
- if (log.isEnabled())
- {
- log.log(90, "Received " + Packets.getMessageName(buffer[off] & 0xff) + " " + payload_length
- + " bytes payload");
- }
-
- if (recv_comp != null && can_recv_compress) {
- int[] uncomp_len = new int[] { payload_length };
- buffer = recv_comp.uncompress(buffer, off, uncomp_len);
-
- if (buffer == null) {
- throw new IOException("Error while inflating remote data");
- } else {
- return uncomp_len[0];
- }
- } else {
- return payload_length;
- }
- }
-
- /**
- *
- */
- public void startCompression() {
- can_recv_compress = true;
- can_send_compress = true;
- }
-}
+
+package com.trilead.ssh2.transport;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.SecureRandom;
+
+import com.trilead.ssh2.compression.ICompressor;
+import com.trilead.ssh2.crypto.cipher.BlockCipher;
+import com.trilead.ssh2.crypto.cipher.CipherInputStream;
+import com.trilead.ssh2.crypto.cipher.CipherOutputStream;
+import com.trilead.ssh2.crypto.cipher.NullCipher;
+import com.trilead.ssh2.crypto.digest.MAC;
+import com.trilead.ssh2.log.Logger;
+import com.trilead.ssh2.packets.Packets;
+
+
+/**
+ * TransportConnection.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: TransportConnection.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ */
+public class TransportConnection
+{
+ private static final Logger log = Logger.getLogger(TransportConnection.class);
+
+ int send_seq_number = 0;
+
+ int recv_seq_number = 0;
+
+ CipherInputStream cis;
+
+ CipherOutputStream cos;
+
+ boolean useRandomPadding = false;
+
+ /* Depends on current MAC and CIPHER */
+
+ MAC send_mac;
+
+ byte[] send_mac_buffer;
+
+ int send_padd_blocksize = 8;
+
+ MAC recv_mac;
+
+ byte[] recv_mac_buffer;
+
+ byte[] recv_mac_buffer_cmp;
+
+ int recv_padd_blocksize = 8;
+
+ ICompressor recv_comp = null;
+
+ ICompressor send_comp = null;
+
+ boolean can_recv_compress = false;
+
+ boolean can_send_compress = false;
+
+ byte[] recv_comp_buffer;
+
+ byte[] send_comp_buffer;
+
+ /* won't change */
+
+ final byte[] send_padding_buffer = new byte[256];
+
+ final byte[] send_packet_header_buffer = new byte[5];
+
+ final byte[] recv_padding_buffer = new byte[256];
+
+ final byte[] recv_packet_header_buffer = new byte[5];
+
+ boolean recv_packet_header_present = false;
+
+ ClientServerHello csh;
+
+ final SecureRandom rnd;
+
+ public TransportConnection(InputStream is, OutputStream os, SecureRandom rnd)
+ {
+ this.cis = new CipherInputStream(new NullCipher(), is);
+ this.cos = new CipherOutputStream(new NullCipher(), os);
+ this.rnd = rnd;
+ }
+
+ public void changeRecvCipher(BlockCipher bc, MAC mac)
+ {
+ cis.changeCipher(bc);
+ recv_mac = mac;
+ recv_mac_buffer = (mac != null) ? new byte[mac.size()] : null;
+ recv_mac_buffer_cmp = (mac != null) ? new byte[mac.size()] : null;
+ recv_padd_blocksize = bc.getBlockSize();
+ if (recv_padd_blocksize < 8)
+ recv_padd_blocksize = 8;
+ }
+
+ public void changeSendCipher(BlockCipher bc, MAC mac)
+ {
+ if ((bc instanceof NullCipher) == false)
+ {
+ /* Only use zero byte padding for the first few packets */
+ useRandomPadding = true;
+ /* Once we start encrypting, there is no way back */
+ }
+
+ cos.changeCipher(bc);
+ send_mac = mac;
+ send_mac_buffer = (mac != null) ? new byte[mac.size()] : null;
+ send_padd_blocksize = bc.getBlockSize();
+ if (send_padd_blocksize < 8)
+ send_padd_blocksize = 8;
+ }
+
+ public void changeRecvCompression(ICompressor comp)
+ {
+ recv_comp = comp;
+
+ if (comp != null) {
+ recv_comp_buffer = new byte[comp.getBufferSize()];
+ can_recv_compress |= recv_comp.canCompressPreauth();
+ }
+ }
+
+ public void changeSendCompression(ICompressor comp)
+ {
+ send_comp = comp;
+
+ if (comp != null) {
+ send_comp_buffer = new byte[comp.getBufferSize()];
+ can_send_compress |= send_comp.canCompressPreauth();
+ }
+ }
+
+ public void sendMessage(byte[] message) throws IOException
+ {
+ sendMessage(message, 0, message.length, 0);
+ }
+
+ public void sendMessage(byte[] message, int off, int len) throws IOException
+ {
+ sendMessage(message, off, len, 0);
+ }
+
+ public int getPacketOverheadEstimate()
+ {
+ // return an estimate for the paket overhead (for send operations)
+ return 5 + 4 + (send_padd_blocksize - 1) + send_mac_buffer.length;
+ }
+
+ public void sendMessage(byte[] message, int off, int len, int padd) throws IOException
+ {
+ if (padd < 4)
+ padd = 4;
+ else if (padd > 64)
+ padd = 64;
+
+ if (send_comp != null && can_send_compress) {
+ if (send_comp_buffer.length < message.length + 1024)
+ send_comp_buffer = new byte[message.length + 1024];
+ len = send_comp.compress(message, off, len, send_comp_buffer);
+ message = send_comp_buffer;
+ }
+
+ int packet_len = 5 + len + padd; /* Minimum allowed padding is 4 */
+
+ int slack = packet_len % send_padd_blocksize;
+
+ if (slack != 0)
+ {
+ packet_len += (send_padd_blocksize - slack);
+ }
+
+ if (packet_len < 16)
+ packet_len = 16;
+
+ int padd_len = packet_len - (5 + len);
+
+ if (useRandomPadding)
+ {
+ for (int i = 0; i < padd_len; i = i + 4)
+ {
+ /*
+ * don't waste calls to rnd.nextInt() (by using only 8bit of the
+ * output). just believe me: even though we may write here up to 3
+ * bytes which won't be used, there is no "buffer overflow" (i.e.,
+ * arrayindexoutofbounds). the padding buffer is big enough =) (256
+ * bytes, and that is bigger than any current cipher block size + 64).
+ */
+
+ int r = rnd.nextInt();
+ send_padding_buffer[i] = (byte) r;
+ send_padding_buffer[i + 1] = (byte) (r >> 8);
+ send_padding_buffer[i + 2] = (byte) (r >> 16);
+ send_padding_buffer[i + 3] = (byte) (r >> 24);
+ }
+ }
+ else
+ {
+ /* use zero padding for unencrypted traffic */
+ for (int i = 0; i < padd_len; i++)
+ send_padding_buffer[i] = 0;
+ /* Actually this code is paranoid: we never filled any
+ * bytes into the padding buffer so far, therefore it should
+ * consist of zeros only.
+ */
+ }
+
+ send_packet_header_buffer[0] = (byte) ((packet_len - 4) >> 24);
+ send_packet_header_buffer[1] = (byte) ((packet_len - 4) >> 16);
+ send_packet_header_buffer[2] = (byte) ((packet_len - 4) >> 8);
+ send_packet_header_buffer[3] = (byte) ((packet_len - 4));
+ send_packet_header_buffer[4] = (byte) padd_len;
+
+ cos.write(send_packet_header_buffer, 0, 5);
+ cos.write(message, off, len);
+ cos.write(send_padding_buffer, 0, padd_len);
+
+ if (send_mac != null)
+ {
+ send_mac.initMac(send_seq_number);
+ send_mac.update(send_packet_header_buffer, 0, 5);
+ send_mac.update(message, off, len);
+ send_mac.update(send_padding_buffer, 0, padd_len);
+
+ send_mac.getMac(send_mac_buffer, 0);
+ cos.writePlain(send_mac_buffer, 0, send_mac_buffer.length);
+ }
+
+ cos.flush();
+
+ if (log.isEnabled())
+ {
+ log.log(90, "Sent " + Packets.getMessageName(message[off] & 0xff) + " " + len + " bytes payload");
+ }
+
+ send_seq_number++;
+ }
+
+ public int peekNextMessageLength() throws IOException
+ {
+ if (recv_packet_header_present == false)
+ {
+ cis.read(recv_packet_header_buffer, 0, 5);
+ recv_packet_header_present = true;
+ }
+
+ int packet_length = ((recv_packet_header_buffer[0] & 0xff) << 24)
+ | ((recv_packet_header_buffer[1] & 0xff) << 16) | ((recv_packet_header_buffer[2] & 0xff) << 8)
+ | ((recv_packet_header_buffer[3] & 0xff));
+
+ int padding_length = recv_packet_header_buffer[4] & 0xff;
+
+ if (packet_length > 35000 || packet_length < 12)
+ throw new IOException("Illegal packet size! (" + packet_length + ")");
+
+ int payload_length = packet_length - padding_length - 1;
+
+ if (payload_length < 0)
+ throw new IOException("Illegal padding_length in packet from remote (" + padding_length + ")");
+
+ return payload_length;
+ }
+
+ public int receiveMessage(byte buffer[], int off, int len) throws IOException
+ {
+ if (recv_packet_header_present == false)
+ {
+ cis.read(recv_packet_header_buffer, 0, 5);
+ }
+ else
+ recv_packet_header_present = false;
+
+ int packet_length = ((recv_packet_header_buffer[0] & 0xff) << 24)
+ | ((recv_packet_header_buffer[1] & 0xff) << 16) | ((recv_packet_header_buffer[2] & 0xff) << 8)
+ | ((recv_packet_header_buffer[3] & 0xff));
+
+ int padding_length = recv_packet_header_buffer[4] & 0xff;
+
+ if (packet_length > 35000 || packet_length < 12)
+ throw new IOException("Illegal packet size! (" + packet_length + ")");
+
+ int payload_length = packet_length - padding_length - 1;
+
+ if (payload_length < 0)
+ throw new IOException("Illegal padding_length in packet from remote (" + padding_length + ")");
+
+ if (payload_length >= len)
+ throw new IOException("Receive buffer too small (" + len + ", need " + payload_length + ")");
+
+ cis.read(buffer, off, payload_length);
+ cis.read(recv_padding_buffer, 0, padding_length);
+
+ if (recv_mac != null)
+ {
+ cis.readPlain(recv_mac_buffer, 0, recv_mac_buffer.length);
+
+ recv_mac.initMac(recv_seq_number);
+ recv_mac.update(recv_packet_header_buffer, 0, 5);
+ recv_mac.update(buffer, off, payload_length);
+ recv_mac.update(recv_padding_buffer, 0, padding_length);
+ recv_mac.getMac(recv_mac_buffer_cmp, 0);
+
+ for (int i = 0; i < recv_mac_buffer.length; i++)
+ {
+ if (recv_mac_buffer[i] != recv_mac_buffer_cmp[i])
+ throw new IOException("Remote sent corrupt MAC.");
+ }
+ }
+
+ recv_seq_number++;
+
+ if (log.isEnabled())
+ {
+ log.log(90, "Received " + Packets.getMessageName(buffer[off] & 0xff) + " " + payload_length
+ + " bytes payload");
+ }
+
+ if (recv_comp != null && can_recv_compress) {
+ int[] uncomp_len = new int[] { payload_length };
+ buffer = recv_comp.uncompress(buffer, off, uncomp_len);
+
+ if (buffer == null) {
+ throw new IOException("Error while inflating remote data");
+ } else {
+ return uncomp_len[0];
+ }
+ } else {
+ return payload_length;
+ }
+ }
+
+ /**
+ *
+ */
+ public void startCompression() {
+ can_recv_compress = true;
+ can_send_compress = true;
+ }
+}
diff --git a/lib/src/main/java/com/trilead/ssh2/transport/TransportManager.java b/lib/src/main/java/com/trilead/ssh2/transport/TransportManager.java
index c81dbdf..2e88126 100644
--- a/lib/src/main/java/com/trilead/ssh2/transport/TransportManager.java
+++ b/lib/src/main/java/com/trilead/ssh2/transport/TransportManager.java
@@ -1,802 +1,802 @@
-
-package com.trilead.ssh2.transport;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.net.UnknownHostException;
-import java.security.SecureRandom;
-import java.util.Vector;
-
-import com.trilead.ssh2.ConnectionInfo;
-import com.trilead.ssh2.ConnectionMonitor;
-import com.trilead.ssh2.DHGexParameters;
-import com.trilead.ssh2.HTTPProxyData;
-import com.trilead.ssh2.HTTPProxyException;
-import com.trilead.ssh2.ProxyData;
-import com.trilead.ssh2.ServerHostKeyVerifier;
-import com.trilead.ssh2.compression.ICompressor;
-import com.trilead.ssh2.crypto.Base64;
-import com.trilead.ssh2.crypto.CryptoWishList;
-import com.trilead.ssh2.crypto.cipher.BlockCipher;
-import com.trilead.ssh2.crypto.digest.MAC;
-import com.trilead.ssh2.log.Logger;
-import com.trilead.ssh2.packets.PacketDisconnect;
-import com.trilead.ssh2.packets.Packets;
-import com.trilead.ssh2.packets.TypesReader;
-import com.trilead.ssh2.util.Tokenizer;
-
-
-/*
- * Yes, the "standard" is a big mess. On one side, the say that arbitary channel
- * packets are allowed during kex exchange, on the other side we need to blindly
- * ignore the next _packet_ if the KEX guess was wrong. Where do we know from that
- * the next packet is not a channel data packet? Yes, we could check if it is in
- * the KEX range. But the standard says nothing about this. The OpenSSH guys
- * block local "normal" traffic during KEX. That's fine - however, they assume
- * that the other side is doing the same. During re-key, if they receive traffic
- * other than KEX, they become horribly irritated and kill the connection. Since
- * we are very likely going to communicate with OpenSSH servers, we have to play
- * the same game - even though we could do better.
- *
- * btw: having stdout and stderr on the same channel, with a shared window, is
- * also a VERY good idea... =(
- */
-
-/**
- * TransportManager.
- *
- * @author Christian Plattner, plattner@trilead.com
- * @version $Id: TransportManager.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $
- */
-public class TransportManager
-{
- private static final Logger log = Logger.getLogger(TransportManager.class);
-
- class HandlerEntry
- {
- MessageHandler mh;
- int low;
- int high;
- }
-
- private final Vector<byte[]> asynchronousQueue = new Vector<byte[]>();
- private Thread asynchronousThread = null;
-
- class AsynchronousWorker extends Thread
- {
- public void run()
- {
- while (true)
- {
- byte[] msg = null;
-
- synchronized (asynchronousQueue)
- {
- if (asynchronousQueue.size() == 0)
- {
- /* After the queue is empty for about 2 seconds, stop this thread */
-
- try
- {
- asynchronousQueue.wait(2000);
- }
- catch (InterruptedException e)
- {
- /* OKOK, if somebody interrupts us, then we may die earlier. */
- }
-
- if (asynchronousQueue.size() == 0)
- {
- asynchronousThread = null;
- return;
- }
- }
-
- msg = asynchronousQueue.remove(0);
- }
-
- /* The following invocation may throw an IOException.
- * There is no point in handling it - it simply means
- * that the connection has a problem and we should stop
- * sending asynchronously messages. We do not need to signal that
- * we have exited (asynchronousThread = null): further
- * messages in the queue cannot be sent by this or any
- * other thread.
- * Other threads will sooner or later (when receiving or
- * sending the next message) get the same IOException and
- * get to the same conclusion.
- */
-
- try
- {
- sendMessage(msg);
- }
- catch (IOException e)
- {
- return;
- }
- }
- }
- }
-
- String hostname;
- int port;
- final Socket sock = new Socket();
-
- Object connectionSemaphore = new Object();
-
- boolean flagKexOngoing = false;
- boolean connectionClosed = false;
-
- Throwable reasonClosedCause = null;
-
- TransportConnection tc;
- KexManager km;
-
- Vector<HandlerEntry> messageHandlers = new Vector<HandlerEntry>();
-
- Thread receiveThread;
-
- Vector connectionMonitors = new Vector();
- boolean monitorsWereInformed = false;
-
- /**
- * There were reports that there are JDKs which use
- * the resolver even though one supplies a dotted IP
- * address in the Socket constructor. That is why we
- * try to generate the InetAdress "by hand".
- *
- * @param host
- * @return the InetAddress
- * @throws UnknownHostException
- */
- private InetAddress createInetAddress(String host) throws UnknownHostException
- {
- /* Check if it is a dotted IP4 address */
-
- InetAddress addr = parseIPv4Address(host);
-
- if (addr != null)
- return addr;
-
- return InetAddress.getByName(host);
- }
-
- private InetAddress parseIPv4Address(String host) throws UnknownHostException
- {
- if (host == null)
- return null;
-
- String[] quad = Tokenizer.parseTokens(host, '.');
-
- if ((quad == null) || (quad.length != 4))
- return null;
-
- byte[] addr = new byte[4];
-
- for (int i = 0; i < 4; i++)
- {
- int part = 0;
-
- if ((quad[i].length() == 0) || (quad[i].length() > 3))
- return null;
-
- for (int k = 0; k < quad[i].length(); k++)
- {
- char c = quad[i].charAt(k);
-
- /* No, Character.isDigit is not the same */
- if ((c < '0') || (c > '9'))
- return null;
-
- part = part * 10 + (c - '0');
- }
-
- if (part > 255) /* 300.1.2.3 is invalid =) */
- return null;
-
- addr[i] = (byte) part;
- }
-
- return InetAddress.getByAddress(host, addr);
- }
-
- public TransportManager(String host, int port) throws IOException
- {
- this.hostname = host;
- this.port = port;
- }
-
- public int getPacketOverheadEstimate()
- {
- return tc.getPacketOverheadEstimate();
- }
-
- public void setTcpNoDelay(boolean state) throws IOException
- {
- sock.setTcpNoDelay(state);
- }
-
- public void setSoTimeout(int timeout) throws IOException
- {
- sock.setSoTimeout(timeout);
- }
-
- public ConnectionInfo getConnectionInfo(int kexNumber) throws IOException
- {
- return km.getOrWaitForConnectionInfo(kexNumber);
- }
-
- public Throwable getReasonClosedCause()
- {
- synchronized (connectionSemaphore)
- {
- return reasonClosedCause;
- }
- }
-
- public byte[] getSessionIdentifier()
- {
- return km.sessionId;
- }
-
- public void close(Throwable cause, boolean useDisconnectPacket)
- {
- if (useDisconnectPacket == false)
- {
- /* OK, hard shutdown - do not aquire the semaphore,
- * perhaps somebody is inside (and waits until the remote
- * side is ready to accept new data). */
-
- try
- {
- sock.close();
- }
- catch (IOException ignore)
- {
- }
-
- /* OK, whoever tried to send data, should now agree that
- * there is no point in further waiting =)
- * It is safe now to aquire the semaphore.
- */
- }
-
- synchronized (connectionSemaphore)
- {
- if (connectionClosed == false)
- {
- if (useDisconnectPacket == true)
- {
- try
- {
- byte[] msg = new PacketDisconnect(Packets.SSH_DISCONNECT_BY_APPLICATION, cause.getMessage(), "")
- .getPayload();
- if (tc != null)
- tc.sendMessage(msg);
- }
- catch (IOException ignore)
- {
- }
-
- try
- {
- sock.close();
- }
- catch (IOException ignore)
- {
- }
- }
-
- connectionClosed = true;
- reasonClosedCause = cause; /* may be null */
- }
- connectionSemaphore.notifyAll();
- }
-
- /* No check if we need to inform the monitors */
-
- Vector monitors = null;
-
- synchronized (this)
- {
- /* Short term lock to protect "connectionMonitors"
- * and "monitorsWereInformed"
- * (they may be modified concurrently)
- */
-
- if (monitorsWereInformed == false)
- {
- monitorsWereInformed = true;
- monitors = (Vector) connectionMonitors.clone();
- }
- }
-
- if (monitors != null)
- {
- for (int i = 0; i < monitors.size(); i++)
- {
- try
- {
- ConnectionMonitor cmon = (ConnectionMonitor) monitors.elementAt(i);
- cmon.connectionLost(reasonClosedCause);
- }
- catch (Exception ignore)
- {
- }
- }
- }
- }
-
- private void establishConnection(ProxyData proxyData, int connectTimeout) throws IOException
- {
- /* See the comment for createInetAddress() */
-
- if (proxyData == null)
- {
- InetAddress addr = createInetAddress(hostname);
- sock.connect(new InetSocketAddress(addr, port), connectTimeout);
- sock.setSoTimeout(0);
- return;
- }
-
- if (proxyData instanceof HTTPProxyData)
- {
- HTTPProxyData pd = (HTTPProxyData) proxyData;
-
- /* At the moment, we only support HTTP proxies */
-
- InetAddress addr = createInetAddress(pd.proxyHost);
- sock.connect(new InetSocketAddress(addr, pd.proxyPort), connectTimeout);
- sock.setSoTimeout(0);
-
- /* OK, now tell the proxy where we actually want to connect to */
-
- StringBuffer sb = new StringBuffer();
-
- sb.append("CONNECT ");
- sb.append(hostname);
- sb.append(':');
- sb.append(port);
- sb.append(" HTTP/1.0\r\n");
-
- if ((pd.proxyUser != null) && (pd.proxyPass != null))
- {
- String credentials = pd.proxyUser + ":" + pd.proxyPass;
- char[] encoded = Base64.encode(credentials.getBytes("ISO-8859-1"));
- sb.append("Proxy-Authorization: Basic ");
- sb.append(encoded);
- sb.append("\r\n");
- }
-
- if (pd.requestHeaderLines != null)
- {
- for (int i = 0; i < pd.requestHeaderLines.length; i++)
- {
- if (pd.requestHeaderLines[i] != null)
- {
- sb.append(pd.requestHeaderLines[i]);
- sb.append("\r\n");
- }
- }
- }
-
- sb.append("\r\n");
-
- OutputStream out = sock.getOutputStream();
-
- out.write(sb.toString().getBytes("ISO-8859-1"));
- out.flush();
-
- /* Now parse the HTTP response */
-
- byte[] buffer = new byte[1024];
- InputStream in = sock.getInputStream();
-
- int len = ClientServerHello.readLineRN(in, buffer);
-
- String httpReponse = new String(buffer, 0, len, "ISO-8859-1");
-
- if (httpReponse.startsWith("HTTP/") == false)
- throw new IOException("The proxy did not send back a valid HTTP response.");
-
- /* "HTTP/1.X XYZ X" => 14 characters minimum */
-
- if ((httpReponse.length() < 14) || (httpReponse.charAt(8) != ' ') || (httpReponse.charAt(12) != ' '))
- throw new IOException("The proxy did not send back a valid HTTP response.");
-
- int errorCode = 0;
-
- try
- {
- errorCode = Integer.parseInt(httpReponse.substring(9, 12));
- }
- catch (NumberFormatException ignore)
- {
- throw new IOException("The proxy did not send back a valid HTTP response.");
- }
-
- if ((errorCode < 0) || (errorCode > 999))
- throw new IOException("The proxy did not send back a valid HTTP response.");
-
- if (errorCode != 200)
- {
- throw new HTTPProxyException(httpReponse.substring(13), errorCode);
- }
-
- /* OK, read until empty line */
-
- while (true)
- {
- len = ClientServerHello.readLineRN(in, buffer);
- if (len == 0)
- break;
- }
- return;
- }
-
- throw new IOException("Unsupported ProxyData");
- }
-
- public void initialize(CryptoWishList cwl, ServerHostKeyVerifier verifier, DHGexParameters dhgex,
- int connectTimeout, SecureRandom rnd, ProxyData proxyData) throws IOException
- {
- /* First, establish the TCP connection to the SSH-2 server */
-
- establishConnection(proxyData, connectTimeout);
-
- /* Parse the server line and say hello - important: this information is later needed for the
- * key exchange (to stop man-in-the-middle attacks) - that is why we wrap it into an object
- * for later use.
- */
-
- ClientServerHello csh = new ClientServerHello(sock.getInputStream(), sock.getOutputStream());
-
- tc = new TransportConnection(sock.getInputStream(), sock.getOutputStream(), rnd);
-
- km = new KexManager(this, csh, cwl, hostname, port, verifier, rnd);
- km.initiateKEX(cwl, dhgex);
-
- receiveThread = new Thread(new Runnable()
- {
- public void run()
- {
- try
- {
- receiveLoop();
- }
- catch (IOException e)
- {
- close(e, false);
-
- if (log.isEnabled())
- log.log(10, "Receive thread: error in receiveLoop: " + e.getMessage());
- }
-
- if (log.isEnabled())
- log.log(50, "Receive thread: back from receiveLoop");
-
- /* Tell all handlers that it is time to say goodbye */
-
- if (km != null)
- {
- try
- {
- km.handleMessage(null, 0);
- }
- catch (IOException e)
- {
- }
- }
-
- for (int i = 0; i < messageHandlers.size(); i++)
- {
- HandlerEntry he = messageHandlers.elementAt(i);
- try
- {
- he.mh.handleMessage(null, 0);
- }
- catch (Exception ignore)
- {
- }
- }
- }
- });
-
- receiveThread.setDaemon(true);
- receiveThread.start();
- }
-
- public void registerMessageHandler(MessageHandler mh, int low, int high)
- {
- HandlerEntry he = new HandlerEntry();
- he.mh = mh;
- he.low = low;
- he.high = high;
-
- synchronized (messageHandlers)
- {
- messageHandlers.addElement(he);
- }
- }
-
- public void removeMessageHandler(MessageHandler mh, int low, int high)
- {
- synchronized (messageHandlers)
- {
- for (int i = 0; i < messageHandlers.size(); i++)
- {
- HandlerEntry he = messageHandlers.elementAt(i);
- if ((he.mh == mh) && (he.low == low) && (he.high == high))
- {
- messageHandlers.removeElementAt(i);
- break;
- }
- }
- }
- }
-
- public void sendKexMessage(byte[] msg) throws IOException
- {
- synchronized (connectionSemaphore)
- {
- if (connectionClosed)
- {
- throw (IOException) new IOException("Sorry, this connection is closed.").initCause(reasonClosedCause);
- }
-
- flagKexOngoing = true;
-
- try
- {
- tc.sendMessage(msg);
- }
- catch (IOException e)
- {
- close(e, false);
- throw e;
- }
- }
- }
-
- public void kexFinished() throws IOException
- {
- synchronized (connectionSemaphore)
- {
- flagKexOngoing = false;
- connectionSemaphore.notifyAll();
- }
- }
-
- public void forceKeyExchange(CryptoWishList cwl, DHGexParameters dhgex) throws IOException
- {
- km.initiateKEX(cwl, dhgex);
- }
-
- public void changeRecvCipher(BlockCipher bc, MAC mac)
- {
- tc.changeRecvCipher(bc, mac);
- }
-
- public void changeSendCipher(BlockCipher bc, MAC mac)
- {
- tc.changeSendCipher(bc, mac);
- }
-
- /**
- * @param comp
- */
- public void changeRecvCompression(ICompressor comp) {
- tc.changeRecvCompression(comp);
- }
-
- /**
- * @param comp
- */
- public void changeSendCompression(ICompressor comp) {
- tc.changeSendCompression(comp);
- }
-
- /**
- *
- */
- public void startCompression() {
- tc.startCompression();
- }
-
- public void sendAsynchronousMessage(byte[] msg) throws IOException
- {
- synchronized (asynchronousQueue)
- {
- asynchronousQueue.addElement(msg);
-
- /* This limit should be flexible enough. We need this, otherwise the peer
- * can flood us with global requests (and other stuff where we have to reply
- * with an asynchronous message) and (if the server just sends data and does not
- * read what we send) this will probably put us in a low memory situation
- * (our send queue would grow and grow and...) */
-
- if (asynchronousQueue.size() > 100)
- throw new IOException("Error: the peer is not consuming our asynchronous replies.");
-
- /* Check if we have an asynchronous sending thread */
-
- if (asynchronousThread == null)
- {
- asynchronousThread = new AsynchronousWorker();
- asynchronousThread.setDaemon(true);
- asynchronousThread.start();
-
- /* The thread will stop after 2 seconds of inactivity (i.e., empty queue) */
- }
- }
- }
-
- public void setConnectionMonitors(Vector monitors)
- {
- synchronized (this)
- {
- connectionMonitors = (Vector) monitors.clone();
- }
- }
-
- public void sendMessage(byte[] msg) throws IOException
- {
- if (Thread.currentThread() == receiveThread)
- throw new IOException("Assertion error: sendMessage may never be invoked by the receiver thread!");
-
- synchronized (connectionSemaphore)
- {
- while (true)
- {
- if (connectionClosed)
- {
- throw (IOException) new IOException("Sorry, this connection is closed.")
- .initCause(reasonClosedCause);
- }
-
- if (flagKexOngoing == false)
- break;
-
- try
- {
- connectionSemaphore.wait();
- }
- catch (InterruptedException e)
- {
- }
- }
-
- try
- {
- tc.sendMessage(msg);
- }
- catch (IOException e)
- {
- close(e, false);
- throw e;
- }
- }
- }
-
- public void receiveLoop() throws IOException
- {
- byte[] msg = new byte[35000];
-
- while (true)
- {
- int msglen = tc.receiveMessage(msg, 0, msg.length);
-
- int type = msg[0] & 0xff;
-
- if (type == Packets.SSH_MSG_IGNORE)
- continue;
-
- if (type == Packets.SSH_MSG_DEBUG)
- {
- if (log.isEnabled())
- {
- TypesReader tr = new TypesReader(msg, 0, msglen);
- tr.readByte();
- tr.readBoolean();
- StringBuffer debugMessageBuffer = new StringBuffer();
- debugMessageBuffer.append(tr.readString("UTF-8"));
-
- for (int i = 0; i < debugMessageBuffer.length(); i++)
- {
- char c = debugMessageBuffer.charAt(i);
-
- if ((c >= 32) && (c <= 126))
- continue;
- debugMessageBuffer.setCharAt(i, '\uFFFD');
- }
-
- log.log(50, "DEBUG Message from remote: '" + debugMessageBuffer.toString() + "'");
- }
- continue;
- }
-
- if (type == Packets.SSH_MSG_UNIMPLEMENTED)
- {
- throw new IOException("Peer sent UNIMPLEMENTED message, that should not happen.");
- }
-
- if (type == Packets.SSH_MSG_DISCONNECT)
- {
- TypesReader tr = new TypesReader(msg, 0, msglen);
- tr.readByte();
- int reason_code = tr.readUINT32();
- StringBuffer reasonBuffer = new StringBuffer();
- reasonBuffer.append(tr.readString("UTF-8"));
-
- /*
- * Do not get fooled by servers that send abnormal long error
- * messages
- */
-
- if (reasonBuffer.length() > 255)
- {
- reasonBuffer.setLength(255);
- reasonBuffer.setCharAt(254, '.');
- reasonBuffer.setCharAt(253, '.');
- reasonBuffer.setCharAt(252, '.');
- }
-
- /*
- * Also, check that the server did not send charcaters that may
- * screw up the receiver -> restrict to reasonable US-ASCII
- * subset -> "printable characters" (ASCII 32 - 126). Replace
- * all others with 0xFFFD (UNICODE replacement character).
- */
-
- for (int i = 0; i < reasonBuffer.length(); i++)
- {
- char c = reasonBuffer.charAt(i);
-
- if ((c >= 32) && (c <= 126))
- continue;
- reasonBuffer.setCharAt(i, '\uFFFD');
- }
-
- throw new IOException("Peer sent DISCONNECT message (reason code " + reason_code + "): "
- + reasonBuffer.toString());
- }
-
- /*
- * Is it a KEX Packet?
- */
-
- if ((type == Packets.SSH_MSG_KEXINIT) || (type == Packets.SSH_MSG_NEWKEYS)
- || ((type >= 30) && (type <= 49)))
- {
- km.handleMessage(msg, msglen);
- continue;
- }
-
- if (type == Packets.SSH_MSG_USERAUTH_SUCCESS) {
- tc.startCompression();
- }
-
- MessageHandler mh = null;
-
- for (int i = 0; i < messageHandlers.size(); i++)
- {
- HandlerEntry he = messageHandlers.elementAt(i);
- if ((he.low <= type) && (type <= he.high))
- {
- mh = he.mh;
- break;
- }
- }
-
- if (mh == null)
- throw new IOException("Unexpected SSH message (type " + type + ")");
-
- mh.handleMessage(msg, msglen);
- }
- }
-}
+
+package com.trilead.ssh2.transport;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.security.SecureRandom;
+import java.util.Vector;
+
+import com.trilead.ssh2.ConnectionInfo;
+import com.trilead.ssh2.ConnectionMonitor;
+import com.trilead.ssh2.DHGexParameters;
+import com.trilead.ssh2.HTTPProxyData;
+import com.trilead.ssh2.HTTPProxyException;
+import com.trilead.ssh2.ProxyData;
+import com.trilead.ssh2.ServerHostKeyVerifier;
+import com.trilead.ssh2.compression.ICompressor;
+import com.trilead.ssh2.crypto.Base64;
+import com.trilead.ssh2.crypto.CryptoWishList;
+import com.trilead.ssh2.crypto.cipher.BlockCipher;
+import com.trilead.ssh2.crypto.digest.MAC;
+import com.trilead.ssh2.log.Logger;
+import com.trilead.ssh2.packets.PacketDisconnect;
+import com.trilead.ssh2.packets.Packets;
+import com.trilead.ssh2.packets.TypesReader;
+import com.trilead.ssh2.util.Tokenizer;
+
+
+/*
+ * Yes, the "standard" is a big mess. On one side, the say that arbitary channel
+ * packets are allowed during kex exchange, on the other side we need to blindly
+ * ignore the next _packet_ if the KEX guess was wrong. Where do we know from that
+ * the next packet is not a channel data packet? Yes, we could check if it is in
+ * the KEX range. But the standard says nothing about this. The OpenSSH guys
+ * block local "normal" traffic during KEX. That's fine - however, they assume
+ * that the other side is doing the same. During re-key, if they receive traffic
+ * other than KEX, they become horribly irritated and kill the connection. Since
+ * we are very likely going to communicate with OpenSSH servers, we have to play
+ * the same game - even though we could do better.
+ *
+ * btw: having stdout and stderr on the same channel, with a shared window, is
+ * also a VERY good idea... =(
+ */
+
+/**
+ * TransportManager.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: TransportManager.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $
+ */
+public class TransportManager
+{
+ private static final Logger log = Logger.getLogger(TransportManager.class);
+
+ class HandlerEntry
+ {
+ MessageHandler mh;
+ int low;
+ int high;
+ }
+
+ private final Vector<byte[]> asynchronousQueue = new Vector<byte[]>();
+ private Thread asynchronousThread = null;
+
+ class AsynchronousWorker extends Thread
+ {
+ public void run()
+ {
+ while (true)
+ {
+ byte[] msg = null;
+
+ synchronized (asynchronousQueue)
+ {
+ if (asynchronousQueue.size() == 0)
+ {
+ /* After the queue is empty for about 2 seconds, stop this thread */
+
+ try
+ {
+ asynchronousQueue.wait(2000);
+ }
+ catch (InterruptedException e)
+ {
+ /* OKOK, if somebody interrupts us, then we may die earlier. */
+ }
+
+ if (asynchronousQueue.size() == 0)
+ {
+ asynchronousThread = null;
+ return;
+ }
+ }
+
+ msg = asynchronousQueue.remove(0);
+ }
+
+ /* The following invocation may throw an IOException.
+ * There is no point in handling it - it simply means
+ * that the connection has a problem and we should stop
+ * sending asynchronously messages. We do not need to signal that
+ * we have exited (asynchronousThread = null): further
+ * messages in the queue cannot be sent by this or any
+ * other thread.
+ * Other threads will sooner or later (when receiving or
+ * sending the next message) get the same IOException and
+ * get to the same conclusion.
+ */
+
+ try
+ {
+ sendMessage(msg);
+ }
+ catch (IOException e)
+ {
+ return;
+ }
+ }
+ }
+ }
+
+ String hostname;
+ int port;
+ final Socket sock = new Socket();
+
+ Object connectionSemaphore = new Object();
+
+ boolean flagKexOngoing = false;
+ boolean connectionClosed = false;
+
+ Throwable reasonClosedCause = null;
+
+ TransportConnection tc;
+ KexManager km;
+
+ Vector<HandlerEntry> messageHandlers = new Vector<HandlerEntry>();
+
+ Thread receiveThread;
+
+ Vector connectionMonitors = new Vector();
+ boolean monitorsWereInformed = false;
+
+ /**
+ * There were reports that there are JDKs which use
+ * the resolver even though one supplies a dotted IP
+ * address in the Socket constructor. That is why we
+ * try to generate the InetAdress "by hand".
+ *
+ * @param host
+ * @return the InetAddress
+ * @throws UnknownHostException
+ */
+ private InetAddress createInetAddress(String host) throws UnknownHostException
+ {
+ /* Check if it is a dotted IP4 address */
+
+ InetAddress addr = parseIPv4Address(host);
+
+ if (addr != null)
+ return addr;
+
+ return InetAddress.getByName(host);
+ }
+
+ private InetAddress parseIPv4Address(String host) throws UnknownHostException
+ {
+ if (host == null)
+ return null;
+
+ String[] quad = Tokenizer.parseTokens(host, '.');
+
+ if ((quad == null) || (quad.length != 4))
+ return null;
+
+ byte[] addr = new byte[4];
+
+ for (int i = 0; i < 4; i++)
+ {
+ int part = 0;
+
+ if ((quad[i].length() == 0) || (quad[i].length() > 3))
+ return null;
+
+ for (int k = 0; k < quad[i].length(); k++)
+ {
+ char c = quad[i].charAt(k);
+
+ /* No, Character.isDigit is not the same */
+ if ((c < '0') || (c > '9'))
+ return null;
+
+ part = part * 10 + (c - '0');
+ }
+
+ if (part > 255) /* 300.1.2.3 is invalid =) */
+ return null;
+
+ addr[i] = (byte) part;
+ }
+
+ return InetAddress.getByAddress(host, addr);
+ }
+
+ public TransportManager(String host, int port) throws IOException
+ {
+ this.hostname = host;
+ this.port = port;
+ }
+
+ public int getPacketOverheadEstimate()
+ {
+ return tc.getPacketOverheadEstimate();
+ }
+
+ public void setTcpNoDelay(boolean state) throws IOException
+ {
+ sock.setTcpNoDelay(state);
+ }
+
+ public void setSoTimeout(int timeout) throws IOException
+ {
+ sock.setSoTimeout(timeout);
+ }
+
+ public ConnectionInfo getConnectionInfo(int kexNumber) throws IOException
+ {
+ return km.getOrWaitForConnectionInfo(kexNumber);
+ }
+
+ public Throwable getReasonClosedCause()
+ {
+ synchronized (connectionSemaphore)
+ {
+ return reasonClosedCause;
+ }
+ }
+
+ public byte[] getSessionIdentifier()
+ {
+ return km.sessionId;
+ }
+
+ public void close(Throwable cause, boolean useDisconnectPacket)
+ {
+ if (useDisconnectPacket == false)
+ {
+ /* OK, hard shutdown - do not aquire the semaphore,
+ * perhaps somebody is inside (and waits until the remote
+ * side is ready to accept new data). */
+
+ try
+ {
+ sock.close();
+ }
+ catch (IOException ignore)
+ {
+ }
+
+ /* OK, whoever tried to send data, should now agree that
+ * there is no point in further waiting =)
+ * It is safe now to aquire the semaphore.
+ */
+ }
+
+ synchronized (connectionSemaphore)
+ {
+ if (connectionClosed == false)
+ {
+ if (useDisconnectPacket == true)
+ {
+ try
+ {
+ byte[] msg = new PacketDisconnect(Packets.SSH_DISCONNECT_BY_APPLICATION, cause.getMessage(), "")
+ .getPayload();
+ if (tc != null)
+ tc.sendMessage(msg);
+ }
+ catch (IOException ignore)
+ {
+ }
+
+ try
+ {
+ sock.close();
+ }
+ catch (IOException ignore)
+ {
+ }
+ }
+
+ connectionClosed = true;
+ reasonClosedCause = cause; /* may be null */
+ }
+ connectionSemaphore.notifyAll();
+ }
+
+ /* No check if we need to inform the monitors */
+
+ Vector monitors = null;
+
+ synchronized (this)
+ {
+ /* Short term lock to protect "connectionMonitors"
+ * and "monitorsWereInformed"
+ * (they may be modified concurrently)
+ */
+
+ if (monitorsWereInformed == false)
+ {
+ monitorsWereInformed = true;
+ monitors = (Vector) connectionMonitors.clone();
+ }
+ }
+
+ if (monitors != null)
+ {
+ for (int i = 0; i < monitors.size(); i++)
+ {
+ try
+ {
+ ConnectionMonitor cmon = (ConnectionMonitor) monitors.elementAt(i);
+ cmon.connectionLost(reasonClosedCause);
+ }
+ catch (Exception ignore)
+ {
+ }
+ }
+ }
+ }
+
+ private void establishConnection(ProxyData proxyData, int connectTimeout) throws IOException
+ {
+ /* See the comment for createInetAddress() */
+
+ if (proxyData == null)
+ {
+ InetAddress addr = createInetAddress(hostname);
+ sock.connect(new InetSocketAddress(addr, port), connectTimeout);
+ sock.setSoTimeout(0);
+ return;
+ }
+
+ if (proxyData instanceof HTTPProxyData)
+ {
+ HTTPProxyData pd = (HTTPProxyData) proxyData;
+
+ /* At the moment, we only support HTTP proxies */
+
+ InetAddress addr = createInetAddress(pd.proxyHost);
+ sock.connect(new InetSocketAddress(addr, pd.proxyPort), connectTimeout);
+ sock.setSoTimeout(0);
+
+ /* OK, now tell the proxy where we actually want to connect to */
+
+ StringBuffer sb = new StringBuffer();
+
+ sb.append("CONNECT ");
+ sb.append(hostname);
+ sb.append(':');
+ sb.append(port);
+ sb.append(" HTTP/1.0\r\n");
+
+ if ((pd.proxyUser != null) && (pd.proxyPass != null))
+ {
+ String credentials = pd.proxyUser + ":" + pd.proxyPass;
+ char[] encoded = Base64.encode(credentials.getBytes("ISO-8859-1"));
+ sb.append("Proxy-Authorization: Basic ");
+ sb.append(encoded);
+ sb.append("\r\n");
+ }
+
+ if (pd.requestHeaderLines != null)
+ {
+ for (int i = 0; i < pd.requestHeaderLines.length; i++)
+ {
+ if (pd.requestHeaderLines[i] != null)
+ {
+ sb.append(pd.requestHeaderLines[i]);
+ sb.append("\r\n");
+ }
+ }
+ }
+
+ sb.append("\r\n");
+
+ OutputStream out = sock.getOutputStream();
+
+ out.write(sb.toString().getBytes("ISO-8859-1"));
+ out.flush();
+
+ /* Now parse the HTTP response */
+
+ byte[] buffer = new byte[1024];
+ InputStream in = sock.getInputStream();
+
+ int len = ClientServerHello.readLineRN(in, buffer);
+
+ String httpReponse = new String(buffer, 0, len, "ISO-8859-1");
+
+ if (httpReponse.startsWith("HTTP/") == false)
+ throw new IOException("The proxy did not send back a valid HTTP response.");
+
+ /* "HTTP/1.X XYZ X" => 14 characters minimum */
+
+ if ((httpReponse.length() < 14) || (httpReponse.charAt(8) != ' ') || (httpReponse.charAt(12) != ' '))
+ throw new IOException("The proxy did not send back a valid HTTP response.");
+
+ int errorCode = 0;
+
+ try
+ {
+ errorCode = Integer.parseInt(httpReponse.substring(9, 12));
+ }
+ catch (NumberFormatException ignore)
+ {
+ throw new IOException("The proxy did not send back a valid HTTP response.");
+ }
+
+ if ((errorCode < 0) || (errorCode > 999))
+ throw new IOException("The proxy did not send back a valid HTTP response.");
+
+ if (errorCode != 200)
+ {
+ throw new HTTPProxyException(httpReponse.substring(13), errorCode);
+ }
+
+ /* OK, read until empty line */
+
+ while (true)
+ {
+ len = ClientServerHello.readLineRN(in, buffer);
+ if (len == 0)
+ break;
+ }
+ return;
+ }
+
+ throw new IOException("Unsupported ProxyData");
+ }
+
+ public void initialize(CryptoWishList cwl, ServerHostKeyVerifier verifier, DHGexParameters dhgex,
+ int connectTimeout, SecureRandom rnd, ProxyData proxyData) throws IOException
+ {
+ /* First, establish the TCP connection to the SSH-2 server */
+
+ establishConnection(proxyData, connectTimeout);
+
+ /* Parse the server line and say hello - important: this information is later needed for the
+ * key exchange (to stop man-in-the-middle attacks) - that is why we wrap it into an object
+ * for later use.
+ */
+
+ ClientServerHello csh = new ClientServerHello(sock.getInputStream(), sock.getOutputStream());
+
+ tc = new TransportConnection(sock.getInputStream(), sock.getOutputStream(), rnd);
+
+ km = new KexManager(this, csh, cwl, hostname, port, verifier, rnd);
+ km.initiateKEX(cwl, dhgex);
+
+ receiveThread = new Thread(new Runnable()
+ {
+ public void run()
+ {
+ try
+ {
+ receiveLoop();
+ }
+ catch (IOException e)
+ {
+ close(e, false);
+
+ if (log.isEnabled())
+ log.log(10, "Receive thread: error in receiveLoop: " + e.getMessage());
+ }
+
+ if (log.isEnabled())
+ log.log(50, "Receive thread: back from receiveLoop");
+
+ /* Tell all handlers that it is time to say goodbye */
+
+ if (km != null)
+ {
+ try
+ {
+ km.handleMessage(null, 0);
+ }
+ catch (IOException e)
+ {
+ }
+ }
+
+ for (int i = 0; i < messageHandlers.size(); i++)
+ {
+ HandlerEntry he = messageHandlers.elementAt(i);
+ try
+ {
+ he.mh.handleMessage(null, 0);
+ }
+ catch (Exception ignore)
+ {
+ }
+ }
+ }
+ });
+
+ receiveThread.setDaemon(true);
+ receiveThread.start();
+ }
+
+ public void registerMessageHandler(MessageHandler mh, int low, int high)
+ {
+ HandlerEntry he = new HandlerEntry();
+ he.mh = mh;
+ he.low = low;
+ he.high = high;
+
+ synchronized (messageHandlers)
+ {
+ messageHandlers.addElement(he);
+ }
+ }
+
+ public void removeMessageHandler(MessageHandler mh, int low, int high)
+ {
+ synchronized (messageHandlers)
+ {
+ for (int i = 0; i < messageHandlers.size(); i++)
+ {
+ HandlerEntry he = messageHandlers.elementAt(i);
+ if ((he.mh == mh) && (he.low == low) && (he.high == high))
+ {
+ messageHandlers.removeElementAt(i);
+ break;
+ }
+ }
+ }
+ }
+
+ public void sendKexMessage(byte[] msg) throws IOException
+ {
+ synchronized (connectionSemaphore)
+ {
+ if (connectionClosed)
+ {
+ throw (IOException) new IOException("Sorry, this connection is closed.").initCause(reasonClosedCause);
+ }
+
+ flagKexOngoing = true;
+
+ try
+ {
+ tc.sendMessage(msg);
+ }
+ catch (IOException e)
+ {
+ close(e, false);
+ throw e;
+ }
+ }
+ }
+
+ public void kexFinished() throws IOException
+ {
+ synchronized (connectionSemaphore)
+ {
+ flagKexOngoing = false;
+ connectionSemaphore.notifyAll();
+ }
+ }
+
+ public void forceKeyExchange(CryptoWishList cwl, DHGexParameters dhgex) throws IOException
+ {
+ km.initiateKEX(cwl, dhgex);
+ }
+
+ public void changeRecvCipher(BlockCipher bc, MAC mac)
+ {
+ tc.changeRecvCipher(bc, mac);
+ }
+
+ public void changeSendCipher(BlockCipher bc, MAC mac)
+ {
+ tc.changeSendCipher(bc, mac);
+ }
+
+ /**
+ * @param comp
+ */
+ public void changeRecvCompression(ICompressor comp) {
+ tc.changeRecvCompression(comp);
+ }
+
+ /**
+ * @param comp
+ */
+ public void changeSendCompression(ICompressor comp) {
+ tc.changeSendCompression(comp);
+ }
+
+ /**
+ *
+ */
+ public void startCompression() {
+ tc.startCompression();
+ }
+
+ public void sendAsynchronousMessage(byte[] msg) throws IOException
+ {
+ synchronized (asynchronousQueue)
+ {
+ asynchronousQueue.addElement(msg);
+
+ /* This limit should be flexible enough. We need this, otherwise the peer
+ * can flood us with global requests (and other stuff where we have to reply
+ * with an asynchronous message) and (if the server just sends data and does not
+ * read what we send) this will probably put us in a low memory situation
+ * (our send queue would grow and grow and...) */
+
+ if (asynchronousQueue.size() > 100)
+ throw new IOException("Error: the peer is not consuming our asynchronous replies.");
+
+ /* Check if we have an asynchronous sending thread */
+
+ if (asynchronousThread == null)
+ {
+ asynchronousThread = new AsynchronousWorker();
+ asynchronousThread.setDaemon(true);
+ asynchronousThread.start();
+
+ /* The thread will stop after 2 seconds of inactivity (i.e., empty queue) */
+ }
+ }
+ }
+
+ public void setConnectionMonitors(Vector monitors)
+ {
+ synchronized (this)
+ {
+ connectionMonitors = (Vector) monitors.clone();
+ }
+ }
+
+ public void sendMessage(byte[] msg) throws IOException
+ {
+ if (Thread.currentThread() == receiveThread)
+ throw new IOException("Assertion error: sendMessage may never be invoked by the receiver thread!");
+
+ synchronized (connectionSemaphore)
+ {
+ while (true)
+ {
+ if (connectionClosed)
+ {
+ throw (IOException) new IOException("Sorry, this connection is closed.")
+ .initCause(reasonClosedCause);
+ }
+
+ if (flagKexOngoing == false)
+ break;
+
+ try
+ {
+ connectionSemaphore.wait();
+ }
+ catch (InterruptedException e)
+ {
+ }
+ }
+
+ try
+ {
+ tc.sendMessage(msg);
+ }
+ catch (IOException e)
+ {
+ close(e, false);
+ throw e;
+ }
+ }
+ }
+
+ public void receiveLoop() throws IOException
+ {
+ byte[] msg = new byte[35000];
+
+ while (true)
+ {
+ int msglen = tc.receiveMessage(msg, 0, msg.length);
+
+ int type = msg[0] & 0xff;
+
+ if (type == Packets.SSH_MSG_IGNORE)
+ continue;
+
+ if (type == Packets.SSH_MSG_DEBUG)
+ {
+ if (log.isEnabled())
+ {
+ TypesReader tr = new TypesReader(msg, 0, msglen);
+ tr.readByte();
+ tr.readBoolean();
+ StringBuffer debugMessageBuffer = new StringBuffer();
+ debugMessageBuffer.append(tr.readString("UTF-8"));
+
+ for (int i = 0; i < debugMessageBuffer.length(); i++)
+ {
+ char c = debugMessageBuffer.charAt(i);
+
+ if ((c >= 32) && (c <= 126))
+ continue;
+ debugMessageBuffer.setCharAt(i, '\uFFFD');
+ }
+
+ log.log(50, "DEBUG Message from remote: '" + debugMessageBuffer.toString() + "'");
+ }
+ continue;
+ }
+
+ if (type == Packets.SSH_MSG_UNIMPLEMENTED)
+ {
+ throw new IOException("Peer sent UNIMPLEMENTED message, that should not happen.");
+ }
+
+ if (type == Packets.SSH_MSG_DISCONNECT)
+ {
+ TypesReader tr = new TypesReader(msg, 0, msglen);
+ tr.readByte();
+ int reason_code = tr.readUINT32();
+ StringBuffer reasonBuffer = new StringBuffer();
+ reasonBuffer.append(tr.readString("UTF-8"));
+
+ /*
+ * Do not get fooled by servers that send abnormal long error
+ * messages
+ */
+
+ if (reasonBuffer.length() > 255)
+ {
+ reasonBuffer.setLength(255);
+ reasonBuffer.setCharAt(254, '.');
+ reasonBuffer.setCharAt(253, '.');
+ reasonBuffer.setCharAt(252, '.');
+ }
+
+ /*
+ * Also, check that the server did not send charcaters that may
+ * screw up the receiver -> restrict to reasonable US-ASCII
+ * subset -> "printable characters" (ASCII 32 - 126). Replace
+ * all others with 0xFFFD (UNICODE replacement character).
+ */
+
+ for (int i = 0; i < reasonBuffer.length(); i++)
+ {
+ char c = reasonBuffer.charAt(i);
+
+ if ((c >= 32) && (c <= 126))
+ continue;
+ reasonBuffer.setCharAt(i, '\uFFFD');
+ }
+
+ throw new IOException("Peer sent DISCONNECT message (reason code " + reason_code + "): "
+ + reasonBuffer.toString());
+ }
+
+ /*
+ * Is it a KEX Packet?
+ */
+
+ if ((type == Packets.SSH_MSG_KEXINIT) || (type == Packets.SSH_MSG_NEWKEYS)
+ || ((type >= 30) && (type <= 49)))
+ {
+ km.handleMessage(msg, msglen);
+ continue;
+ }
+
+ if (type == Packets.SSH_MSG_USERAUTH_SUCCESS) {
+ tc.startCompression();
+ }
+
+ MessageHandler mh = null;
+
+ for (int i = 0; i < messageHandlers.size(); i++)
+ {
+ HandlerEntry he = messageHandlers.elementAt(i);
+ if ((he.low <= type) && (type <= he.high))
+ {
+ mh = he.mh;
+ break;
+ }
+ }
+
+ if (mh == null)
+ throw new IOException("Unexpected SSH message (type " + type + ")");
+
+ mh.handleMessage(msg, msglen);
+ }
+ }
+}