All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.vngx.jsch.kex.KeyExchange Maven / Gradle / Ivy

Go to download

**vngx-jsch** (beta) is an updated version of the popular JSch SSH library written in pure Java. It has been updated to Java 6 with all the latest language features and improved code clarity.

The newest version!
/*
 * Copyright (c) 2010-2011 Michael Laudati, N1 Concepts LLC.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The names of the authors may not be used to endorse or promote products
 * derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL N1
 * CONCEPTS LLC OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.vngx.jsch.kex;

import static org.vngx.jsch.constants.TransportLayerProtocol.SSH_MSG_KEXINIT;
import static org.vngx.jsch.constants.TransportLayerProtocol.SSH_MSG_NEWKEYS;

import java.util.concurrent.atomic.AtomicBoolean;

import org.vngx.jsch.Buffer;
import org.vngx.jsch.JSch;
import org.vngx.jsch.Packet;
import org.vngx.jsch.Session;
import org.vngx.jsch.UserInfo;
import org.vngx.jsch.Util;
import org.vngx.jsch.algorithm.AlgorithmManager;
import org.vngx.jsch.algorithm.Algorithms;
import org.vngx.jsch.algorithm.Random;
import org.vngx.jsch.cipher.Cipher;
import org.vngx.jsch.config.SessionConfig;
import org.vngx.jsch.constants.MessageConstants;
import org.vngx.jsch.constants.SSHConstants;
import org.vngx.jsch.exception.JSchException;
import org.vngx.jsch.util.HostKey;
import org.vngx.jsch.util.HostKeyRepository;
import org.vngx.jsch.util.HostKeyRepository.Check;
import org.vngx.jsch.util.Logger;
import org.vngx.jsch.util.Logger.Level;

/**
 * 

Key Exchange is any method in cryptography by which cryptographic keys are * exchanged between users, allowing use of a cryptographic algorithm. If Alice * and Bob wish to exchange encrypted messages, each must be equipped to decrypt * received messages and to encrypt sent messages. The nature of the information * they require to do so depends on the encryption technique they might use. If * they use a code, both will require a copy of the same codebook. If they use a * cipher, they will need appropriate keys. If the cipher is a symmetric key * cipher, both will need a copy of the same key. If an asymmetric key cipher * with the public/private key property, both will need the other's public * key.

* *

Of course, if the DH private parameters for the client and server are * revealed, then the session key is revealed, but these items can be thrown * away after the key exchange completes. It's worth pointing out that these * items should not be allowed to end up on swap space and that they should be * erased from memory as soon as the key exchange completes.

* *

RFC 4253 - The * Secure Shell (SSH) Transport Layer Protocol: Key Exchange

* * @see org.vngx.jsch.kex.KexAlgorithm * @see org.vngx.jsch.kex.KexProposal * * @author Michael Laudati */ public final class KeyExchange { /** Number of bytes in the KEX_INIT 'cookie'. */ final static int KEX_COOKIE_LENGTH = 16; /** Session the key exchange belongs to. */ final Session _session; /** Buffer for sending SSH packets. */ final Buffer _buffer = new Buffer(); /** True if session is currently in process of a key exchange. */ final AtomicBoolean _inKeyExchange = new AtomicBoolean(false); /** Guessed algorithms during key exchange. */ KexProposal _proposal; /** Key exchange algorithm which performs the actual exchange. */ KexAlgorithm _kexAlg; /** Client's SSH_MSG_KEXINIT payload sent to server. */ byte[] I_C; /** Server's SSH_MSG_KEXINIT payload received from server. */ byte[] I_S; /** Host key generated after key exchange and validation. */ HostKey _hostKey; /** * Creates a new instance of {@code KeyExchange} for the specified * {@code session} instance. A key exchange should not be created * until after the session has established a socket connection to the remote * host and exchanged version information. * * @param session to create key exchange for */ public KeyExchange(Session session) { if( session == null ) { throw new IllegalArgumentException("Session cannot be null"); } _session = session; } public byte[] runFirstKex() throws Exception { // Initialize the key exchange for session and check expected response sendKexInit(); if( _session.read(_buffer).getCommand() != SSH_MSG_KEXINIT ) { throw new KexException("Invalid kex protocol, expected SSH_MSG_KEXINIT(20): " + _buffer.getCommand()); } JSch.getLogger().log(Logger.Level.INFO, "SSH_MSG_KEXINIT received"); // Read server response and generate appropriate kex algorithm receiveKexInit(_buffer); // Check host key against known hosts before continuing... checkHost(_kexAlg); // Request new set of keys after initial key exchange sendNewKeys(); // Read SSH_MSG_NEWKEYS response from server if( _session.read(_buffer).getCommand() != SSH_MSG_NEWKEYS ) { throw new KexException("Invalid kex protocol, expected SSH_MSG_NEWKEYS(21): " + _buffer.getCommand()); } JSch.getLogger().log(Logger.Level.INFO, "SSH_MSG_NEWKEYS received"); // Return the session ID (copy of exchange hash H) from first kex return Util.copyOf(_kexAlg.getH(), _kexAlg.getH().length); } public void rekey(Buffer rekeyBuffer) throws Exception { receiveKexInit(rekeyBuffer); } /** * Returns true if in the process of a key exchange. * * @return true if in process of a key exchange */ public boolean inKex() { return _inKeyExchange.get(); } public void kexCompleted() { _inKeyExchange.set(false); } /** *

Sends a key exchange init request message to the server using a local * packet specifying the client's proposals for key exchange.

* *

RFC 4253 - * 7.1. Algorithm Negotiation

* * @throws KexException if any errors occur */ public void sendKexInit() throws KexException { // Check if already in middle of a kex if( _inKeyExchange.getAndSet(true) ) { // Flip state flag entering kex return; // Return if already in process of kex } Buffer kexBuffer = new Buffer(); // Use a separate packet and buffer since Packet kexPacket = new Packet(kexBuffer); // kex may be invoked by user thread try { // Random instance for generating the kex cookie. The 'cookie' MUST // be a random value generated by the sender. Its purpose is to make // it impossible for either side to fully determine the keys and the // session identifier. final Random random = AlgorithmManager.getManager().createAlgorithm(Algorithms.RANDOM, _session); // Construct the KEX INIT message packet // byte SSH_MSG_KEXINIT(20) // byte[16] cookie (random bytes) // string kex_algorithms // string server_host_key_algorithms // string encryption_algorithms_client_to_server // string encryption_algorithms_server_to_client // string mac_algorithms_client_to_server // string mac_algorithms_server_to_client // string compression_algorithms_client_to_server // string compression_algorithms_server_to_client // string languages_client_to_server // string languages_server_to_client // byte boolean first_kex_packet_follows // uint32 0 (reserved for future extension) kexPacket.reset(); kexBuffer.putByte(SSH_MSG_KEXINIT); random.fill(kexBuffer.getArray(), kexBuffer.getIndex(), KEX_COOKIE_LENGTH); kexBuffer.skip(KEX_COOKIE_LENGTH); // Move index forward kexBuffer.putString(_session.getConfig().getString(SessionConfig.KEX_ALGORITHMS)); kexBuffer.putString(_session.getConfig().getString(SessionConfig.KEX_SERVER_HOST_KEY)); kexBuffer.putString(_session.getConfig().getCiphersC2S()); // Checked list of client-to-server ciphers kexBuffer.putString(_session.getConfig().getCiphersS2C()); // Checked list of server-to-client ciphers kexBuffer.putString(_session.getConfig().getString(SessionConfig.KEX_MAC_C2S)); kexBuffer.putString(_session.getConfig().getString(SessionConfig.KEX_MAC_S2C)); kexBuffer.putString(_session.getConfig().getString(SessionConfig.KEX_COMPRESSION_C2S)); kexBuffer.putString(_session.getConfig().getString(SessionConfig.KEX_COMPRESSION_S2C)); kexBuffer.putString(_session.getConfig().getString(SessionConfig.KEX_LANG_C2S)); kexBuffer.putString(_session.getConfig().getString(SessionConfig.KEX_LANG_S2C)); kexBuffer.putByte((byte) 0); // 0 is false, not sending guessed packet kexBuffer.putInt(0); // Set the client's kex algorithm initialization message I_C = new byte[kexBuffer.getIndex()-5]; System.arraycopy(kexBuffer.getArray(), 5, I_C, 0, I_C.length); _session.write(kexPacket); // Send key exchange init message to server JSch.getLogger().log(Logger.Level.INFO, "SSH_MSG_KEXINIT sent"); } catch(Exception e) { throw new KexException("Failed to send SSH_MSG_KEXINIT", e); } finally { kexBuffer.clear(); // Clear buffer to ensure sensitive data is wiped } } /** *

Receives the server's key exchange KEXINIT from the specified * {@code buffer}.

* * @param buffer containing server's SSH_MSG_KEXINIT message * @throws KexException if any errors occur */ private void receiveKexInit(final Buffer buffer) throws KexException { // Read the server's proposal for kex algorithms from buffer // Check packet length for compression and set size of data portion int packetLength = buffer.getInt(); if( packetLength != buffer.getLength() ) { buffer.getByte(); /* padding length*/ // Compressed: (uncompressed data length) - (packet length int + padding length byte) I_S = new byte[buffer.getIndex() - 5]; } else { // Not compressed: (packet length) - (padding length byte )- (padding length) I_S = new byte[packetLength - 1 - buffer.getByte()]; } buffer.getBytes(I_S); // Read in server proposal from buffer // If rekeying was activated by server, then send client's proposal for kex if( !_inKeyExchange.get() ) { sendKexInit(); } // Guess algorithms to use from the client's and server's proposals _proposal = KexProposal.createProposal(I_S, I_C); if( JSch.getLogger().isEnabled(Logger.Level.DEBUG) ) { JSch.getLogger().log(Level.DEBUG, _proposal.toString()); } // If not authorized yet, don't allow 'none' cipher to be used, throw // an exception to prevent auth data from being sent in the clear if( !_session.isAuthenticated() && ( Cipher.CIPHER_NONE.equals(_proposal.getCipherAlgCtoS()) || Cipher.CIPHER_NONE.equals(_proposal.getCipherAlgStoC()) ) ) { throw new KexException("Cipher 'none' cannot be used before authentication has succeeded"); } // Attempt to create kex algorithm to perform kex try { JSch.getLogger().log(Logger.Level.INFO, "Kex method: " + _proposal.getKexAlg()); _kexAlg = AlgorithmManager.getManager().createAlgorithm(_proposal.getKexAlg(), _session); _kexAlg.init(_session, I_C, I_S); // Initialize and return } catch(Exception e) { throw new KexException("Failed to load KexAlgorithm '"+_proposal.getKexAlg()+"'", e); } try { do { if( !_kexAlg.next(_session.read(_buffer)) ) { throw new KexException("Kex failure, host key could not be verified"); } } while( _kexAlg.getState() != KexAlgorithm.STATE_END ); // Do until kex is completed } catch(KexException ke) { throw ke; } catch(Exception e) { throw new KexException("Failed to run KexAlgorithm", e); } } /** *

Sends message to server to end key exchange and start using new keys. * Key exchange ends by each side sending an SSH_MSG_NEWKEYS message. This * message is sent with the old keys and algorithms. All messages sent * after this message MUST use the new keys and algorithms. When this * message is received, the new keys and algorithms MUST be used for * receiving.

* *

The purpose of this message is to ensure that a party is able to * respond with an SSH_MSG_DISCONNECT message that the other party can * understand if something goes wrong with the key exchange.

* *

RFC 4253 - * 7.3. Taking Keys Into Use

* * @throws KexException if any errors occur */ public void sendNewKeys() throws KexException { try { // Send SSH_MSG_NEWKEYS request to server Buffer buffer = new Buffer(500); Packet packet = new Packet(buffer); packet.reset(); buffer.putByte(SSH_MSG_NEWKEYS); _session.write(packet); JSch.getLogger().log(Logger.Level.INFO, "SSH_MSG_NEWKEYS sent"); } catch(Exception e) { throw new KexException("Failed to send SSH_MSG_NEWKEYS request", e); } } /** * Checks if the host received during key exchange is a valid host as * determined by the user and known hosts. * * @param kex instance * @throws JSchException if any errors occur */ private void checkHost(KexAlgorithm kex) throws JSchException { UserInfo _userinfo = _session.getUserInfo(); // Check if host key alias exists and use it, or if it's not present and // not using default port, set the port in host to check String chost = _session.getHost(); if( _session.getHostKeyAlias() != null ) { chost = _session.getHostKeyAlias(); } else if( _session.getPort() != SSHConstants.DEFAULT_SSH_PORT ) { chost = "[" + chost + "]:" + _session.getPort(); } // Check host against known hosts repository HostKeyRepository hkr = JSch.getInstance().getHostKeyRepository(); Check keyCheck; synchronized( hkr ) { keyCheck = hkr.check(chost, kex.K_S); } boolean insert = false; String shkc = _session.getConfig().getString(SessionConfig.STRICT_HOST_KEY_CHECKING); if( ("ask".equals(shkc) || "yes".equals(shkc)) && keyCheck == Check.CHANGED ) { String file = hkr.getKnownHostsRepositoryID() != null ? hkr.getKnownHostsRepositoryID() : SSHConstants.KNOWN_HOSTS; // Notify user host key changed (ask if requested) and throw exception // if user doesn't accept the new key if( _userinfo != null ) { if( "ask".equals(shkc) ) { if( !_userinfo.promptYesNo(String.format(MessageConstants.PROMPT_REPLACE_KEY, kex._hostKeyType.DISPLAY_NAME, Util.getFingerPrint(kex.K_S), file)) ) { throw new JSchException("HostKey has changed (StrictHostKeyChecking:ask): "+chost); } } else { // shkc.equals("yes") _userinfo.showMessage(String.format(MessageConstants.INVALID_SERVER_HOST, kex._hostKeyType.DISPLAY_NAME, Util.getFingerPrint(kex.K_S), file)); throw new JSchException("HostKey has changed (StrictHostKeyChecking:yes): "+chost); } } // Remove the old key from the repository synchronized ( hkr ) { hkr.remove(chost, kex._hostKeyType, null); insert = true; } } if( ("ask".equals(shkc) || "yes".equals(shkc)) && keyCheck != Check.OK && !insert ) { if( "yes".equals(shkc) ) { throw new JSchException("HostKey does not match known hosts (StrictHostKeyChecking:yes): "+chost); } if( _userinfo != null ) { if( !_userinfo.promptYesNo(String.format(MessageConstants.PROMPT_UNKNOWN_KEY, chost, kex._hostKeyType.DISPLAY_NAME, Util.getFingerPrint(kex.K_S))) ) { throw new JSchException("HostKey does not match known hosts (StrictHostKeyChecking:ask): "+chost); } insert = true; } else { if( keyCheck == Check.NOT_INCLUDED ) { throw new JSchException("UnknownHostKey: "+chost+". "+kex._hostKeyType+" key fingerprint is "+Util.getFingerPrint(kex.K_S)); } else { throw new JSchException("HostKey has been changed (StrictHostKeyChecking:ask): " + chost); } } } if( "no".equals(shkc) && keyCheck == Check.NOT_INCLUDED ) { insert = true; } if( keyCheck == Check.OK && JSch.getLogger().isEnabled(Logger.Level.INFO) ) { JSch.getLogger().log(Logger.Level.INFO, "Host '"+chost+"' is known and matches the "+kex._hostKeyType+" host key"); } if( insert && JSch.getLogger().isEnabled(Logger.Level.WARN) ) { JSch.getLogger().log(Logger.Level.WARN, "Permanently added '"+chost+"' ("+kex._hostKeyType+") to the list of known hosts."); } // Create host key instance _hostKey = HostKey.createHostKey(chost, kex.K_S, _session.getConfig().getBoolean(SessionConfig.HASH_KNOWN_HOSTS)); if( insert ) { synchronized( hkr ) { hkr.add(_hostKey, _userinfo); } } } /** * Returns the key exchange proposals agreed upon during the key exchange. * * @return key exchange proposals */ public KexProposal getKexProposal() { return _proposal; } public KexAlgorithm getKexAlgorithm() { return _kexAlg; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy