📄 ssh2transport.java
字号:
/****************************************************************************** * * Copyright (c) 1999-2003 AppGate Network Security AB. All Rights Reserved. * * This file contains Original Code and/or Modifications of Original Code as * defined in and that are subject to the MindTerm Public Source License, * Version 2.0, (the 'License'). You may not use this file except in compliance * with the License. * * You should have received a copy of the MindTerm Public Source License * along with this software; see the file LICENSE. If not, write to * AppGate Network Security AB, Otterhallegatan 2, SE-41118 Goteborg, SWEDEN * *****************************************************************************/package com.mindbright.ssh2;import java.io.InputStream;import java.io.OutputStream;import java.net.Socket;import java.io.IOException;import java.io.FileOutputStream;import com.mindbright.jca.security.MessageDigest;import com.mindbright.jca.security.SecureRandom;import com.mindbright.jca.security.InvalidKeyException;import com.mindbright.jce.crypto.Cipher;import com.mindbright.jce.crypto.Mac;import com.mindbright.jce.crypto.spec.SecretKeySpec;import com.mindbright.jce.crypto.spec.IvParameterSpec;import com.mindbright.jce.crypto.ShortBufferException;import com.mindbright.util.SecureRandomAndPad;import com.mindbright.util.Queue;import com.mindbright.util.Log;/** * This class implements the transport layer of the secure shell version 2 * (ssh2) protocol stack. It handles the initial negotiation of algorithms for * key exchange, host key type, encryption, message authentication and * compression. It also handles server authentication (through the provided * <code>SSH2TransportEventHandler</code>). * <p> * To create a <code>SSH2Transport</code> instance a TCP connection to the ssh2 * server must first be established using a <code>java.net.Socket</code>. This * socket is passed to the constructor. The constructor is passive in that it * doesn't start any communication. To start the protocol engine and begin * communcation with the server the method <code>boot</code> must be called. In * this method the version information echange is done and two threads are * started which handles the protocol engine and all communication with the * server. * <p> * The operation of the transport layer can be controlled and monitored with * instances of the classes <code>SSH2Preferences</code> and * <code>SSH2TransportEventHandler</code> which are provided to the constructor. * <p> * After initial negotiation the chosen key exchange algorithm is handled by a * subclass of the abstract class <code>SSH2KeyExchanger</code>. When the key * exchange is complete, keys for encryption and message authentication can be * derived. The communciation at this point is secured with the selected * cipher/mac algorithms. The actual encryption, message authentication and * compression is handled in the class <code>SSH2TransportPDU</code> which is * the data container implementing the specific formatting defined in the * protocol. * <p> * Before the upper layers (i.e. the user authentication and connection layers) * of the protocol can be started the key exchange stage must be completed. This * must be checked by calling the blocking method * <code>waitForKEXComplete</code>. When the key exchange is complete a secure * connection to an authenticated server has been established. The function of * the transport layer at this point is the multiplexing of protocol data units * (referred to as PDU or packet) between the server and the upper layers which * are implemented in the classes <code>SSH2UserAuth</code> and * <code>SSH2Connection</code>. * * @see SSH2TransportEventHandler * @see SSH2Preferences * @see SSH2UserAuth * @see SSH2Connection */public final class SSH2Transport { private class KeepAliveThread implements Runnable { private volatile int interval; private volatile boolean keepRunning; protected KeepAliveThread(int interval) { this.interval = interval; this.keepRunning = true; Thread heartbeat = new Thread(this, "SSH2TransportKeepAlive"); heartbeat.setDaemon(true); heartbeat.setPriority(Thread.MIN_PRIORITY); heartbeat.start(); } protected synchronized void setInterval(int interval) { if(interval < 1) { stop(); } else { this.interval = interval; } } public void run() { int inactive = 0; while (keepRunning) { try { Thread.sleep(1000); } catch (InterruptedException e) { /* ignore */ } if (activity) { inactive = 0; activity = false; } else { if (++inactive >= interval) { sendIgnore("heartbeat".getBytes()); Thread.yield(); inactive = 0; activity = false; } } } } protected void stop() { keepRunning = false; } public boolean isRunning() { return keepRunning; } } /** * Context for transport tranceiver/receiver. Holds the encryption * and compression states. */ public static class TranceiverContext { protected Mac mac; protected Cipher cipher; protected SSH2Compressor compressor; public int getCipherBlockSize() { return cipher.getBlockSize(); } public void init(byte[] cKey, byte[] iv, byte[] mKey, int compLevel, boolean transmitter) throws SSH2Exception { try { if(cipher != null) { cipher.init(transmitter ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, new SecretKeySpec(cKey, cipher.getAlgorithm()), new IvParameterSpec(iv)); } if(mac != null) { mac.init(new SecretKeySpec(mKey, mac.getAlgorithm())); } if(compressor != null) { compressor.init((transmitter ? SSH2Compressor.COMPRESS_MODE : SSH2Compressor.UNCOMPRESS_MODE), compLevel); } } catch (InvalidKeyException e) { throw new SSH2FatalException("Invalid key in TranceiverContext.init"); } } } private final static boolean DEBUG_ALL_TX = false; private final static boolean DEBUG_ALL_RX = false; private boolean weAreAServer; private String clientVersion; private String serverVersion; private SSH2Preferences ourPrefs; private SSH2Preferences peerPrefs; private SSH2TransportEventHandler eventHandler; private SSH2KeyExchanger keyExchanger; private volatile SSH2UserAuth userAuth; private volatile SSH2Connection connection; private Log tpLog; protected Socket tpSocket; protected InputStream tpIn; protected OutputStream tpOut; private Thread transmitter; private Thread receiver; private Queue txQueue; private SecureRandomAndPad tpRand; private KeepAliveThread heartbeat; private byte[] sessionId; private volatile boolean keyExchangeInProgress; private boolean keyExchangeOk; private Object keyExchangeMonitor; private SSH2TransportPDU clientKEXINITPkt; private SSH2TransportPDU serverKEXINITPkt; private byte[] serverPublicKeyBlob; private TranceiverContext rxContext; private TranceiverContext txContext; private int rxSeqNum; private int txSeqNum; private int rxNumPacketsSinceKEX; private int txNumPacketsSinceKEX; private int rxNumBytesSinceKEX; private int txNumBytesSinceKEX; private final static int PACKETS_BEFORE_REKEY = 2147483647; private final static int BYTES_BEFORE_REKEY= 1073741824; private Object disconnectMonitor; private volatile boolean isConnected; private volatile boolean isTxUp; private volatile boolean isRxUp; private String disconnectMessage; private boolean activity; // Incompatibility flags (peer's incompatibility of course :-) // public boolean incompatibleSignature; public boolean incompatibleServiceAccept; public boolean incompatiblePublicKeyAuth; public boolean incompatibleHMACKeyLength; public boolean incompatiblePublicKeyUserId; public boolean incompatibleChannelOpenFail; public boolean incompatibleRijndael; public boolean incompatibleCantReKey; public boolean incompatibleBuggyChannelClose; /** * This is the basic constructor used when default preferences is ok and no * logging or event handling is needed. * * @param tpSocket the connection to the ssh2 server * @param rand the source of randomness for keys and padding */ public SSH2Transport(Socket tpSocket, SecureRandomAndPad rand) { this(tpSocket, new SSH2Preferences(), rand); } /** * This is the basic constructor used when no logging or event handling is * needed. * * @param tpSocket the connection to the ssh2 server * @param prefs the protocol preferences * @param rand the source of randomness for keys and padding */ public SSH2Transport(Socket tpSocket, SSH2Preferences prefs, SecureRandomAndPad rand) { this(tpSocket, prefs, null, rand); } /** * This is the constructor used when an event handler is needed but no * logging. * * @param tpSocket the connection to the ssh2 server * @param prefs the protocol preferences * @param eventHandler the event handler which receives callbacks * @param rand the source of randomness for keys and padding */ public SSH2Transport(Socket tpSocket, SSH2Preferences prefs, SSH2TransportEventHandler eventHandler, SecureRandomAndPad rand) { this(tpSocket, prefs, eventHandler, rand, new Log(prefs.getIntPreference(SSH2Preferences.LOG_LEVEL))); String logFile = prefs.getPreference(SSH2Preferences.LOG_FILE); if(logFile != null) { try { boolean append = "true".equals(prefs. getPreference(SSH2Preferences.LOG_APPEND)); FileOutputStream log = new FileOutputStream(logFile, append); tpLog.setLogOutputStream(log); } catch (IOException e) { tpLog.error("SSH2Transport", "<init>", "could't open log file: " + e.getMessage()); } } } /** * This is the constructor used when both an event handler and logging is * needed. * * @param tpSocket the connection to the ssh2 server * @param prefs the protocol preferences * @param eventHandler the event handler which receives callbacks * @param rand the source of randomness for keys and padding * @param log the log handler which receives all logs */ public SSH2Transport(Socket tpSocket, SSH2Preferences prefs, SSH2TransportEventHandler eventHandler, SecureRandomAndPad rand, Log log) { this.disconnectMonitor = new Object(); this.keyExchangeMonitor = new Object(); this.isConnected = false; this.isTxUp = false; this.isRxUp = false; this.ourPrefs = prefs; this.eventHandler = (eventHandler != null ? eventHandler : new SSH2TransportEventAdapter()); this.tpSocket = tpSocket; this.tpRand = rand; this.tpLog = log; // !!! REMOVE SSH2TransportPDU.pktDefaultSize = ourPrefs.getIntPreference(SSH2Preferences.DEFAULT_PKT_SZ); SSH2TransportPDUPool.POOL_SIZE = ourPrefs.getIntPreference(SSH2Preferences.PKT_POOL_SZ); try { setSocketOptions(SSH2Preferences.SOCK_OPT_TRANSPORT, tpSocket); this.rxContext = SSH2TransportPDU.createTranceiverContext("none", "none", "none"); this.txContext = SSH2TransportPDU.createTranceiverContext("none", "none", "none"); this.tpIn = tpSocket.getInputStream(); this.tpOut = tpSocket.getOutputStream(); } catch (Exception e) { // !!! TODO: pathological, fixit!!! } } /** * Starts the protocol engine and begins communication with the server. It * completes the version negotiation and starts two threads which handles * the protocol engine and all communication with the server. The key * exchange is started here. * * @exception SSH2Exception if a fatal error occurs such as an I/O error or * a protocol mismatch. */ public void boot() throws SSH2Exception { synchronized(disconnectMonitor) { if(isConnected) { throw new SSH2FatalException("Already booted"); } isConnected = true; } try { negotiateVersion(); } catch (IOException e) { throw new SSH2FatalException("I/O error in version negotiation", e); } transmitter = new Thread(new Runnable() { public void run() { transportTransmitLoop(); } }, "SSH2TransportTX"); txQueue = new Queue(ourPrefs.getIntPreference(SSH2Preferences.QUEUE_DEPTH), ourPrefs.getIntPreference(SSH2Preferences.QUEUE_HIWATER)); transmitter.start(); // Note we start the receiver AFTER we do startKeyExchange() to avoid // race with startKeyExchange() in receiver // startKeyExchange(); receiver = new Thread(new Runnable() { public void run() { transportReceiveLoop(); } }, "SSH2TransportRX"); receiver.start(); } public void setSocketOptions(String desc, Socket sock) throws IOException { String prefix = SSH2Preferences.SOCK_OPT + desc; String val = ourPrefs.getPreference(prefix + "." + SSH2Preferences.SO_TCP_NODELAY); if(val != null) { sock.setTcpNoDelay(Boolean.valueOf(val).booleanValue()); } /* TODO more socket options goes here... */ } /** * Gets the session identifier calculated at key exchange as defined in the * protool spec. * * @return the session identifier as a byte array. */ public byte[] getSessionId() { byte[] id = sessionId; if(!incompatiblePublicKeyUserId) { SSH2DataBuffer buf = new SSH2DataBuffer(sessionId.length + 4); buf.writeString(sessionId); id = buf.readRestRaw(); } return id; } /** * Gets the PDU containing the key exchange initialization (KEXINIT) sent by * the client. * * @return the PDU containing the KEXINIT packet */ public SSH2TransportPDU getClientKEXINITPDU() { return clientKEXINITPkt; } /** * Gets the PDU containing the key exchange initialization (KEXINIT) sent by * the server. * * @return the PDU containing the KEXINIT packet. */ public SSH2TransportPDU getServerKEXINITPDU() { return serverKEXINITPkt; } /** * Gets the client's version string * * @return the client's version string */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -