/** * Copyright 2003-2016 SSHTOOLS Limited. All Rights Reserved. * * For product documentation visit https://www.sshtools.com/ * * This file is part of J2SSH Maverick. * * J2SSH Maverick is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * J2SSH Maverick is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with J2SSH Maverick. If not, see <http://www.gnu.org/licenses/>. */ package com.sshtools.ssh.components.jce; import java.io.IOException; import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import javax.crypto.KeyAgreement; import javax.crypto.interfaces.DHPublicKey; import javax.crypto.spec.DHParameterSpec; import javax.crypto.spec.DHPublicKeySpec; import com.sshtools.ssh.SshException; import com.sshtools.ssh.components.ComponentManager; import com.sshtools.ssh.components.Digest; import com.sshtools.ssh.components.SshKeyExchangeClient; import com.sshtools.ssh2.TransportProtocol; import com.sshtools.util.ByteArrayReader; import com.sshtools.util.ByteArrayWriter; /** * An implementation of the diffie-hellman-group-exchange key exchange mechanism * that uses JCE provider for DH agreement and Digest. * * @author Lee David Painter */ public class DiffieHellmanGroupExchangeSha1 extends SshKeyExchangeClient implements AbstractKeyExchange { /** * Constant for the algorithm name "diffie-hellman-group-exchange-sha1". */ public static final String DIFFIE_HELLMAN_GROUP_EXCHANGE_SHA1 = "diffie-hellman-group-exchange-sha1"; final static int SSH_MSG_KEXDH_GEX_REQUEST_OLD = 30; final static int SSH_MSG_KEXDH_GEX_GROUP = 31; final static int SSH_MSG_KEXDH_GEX_INIT = 32; final static int SSH_MSG_KEXDH_GEX_REPLY = 33; final static int SSH_MSG_KEXDH_GEX_REQUEST = 34; BigInteger g; BigInteger p; static BigInteger ONE = BigInteger.valueOf(1); BigInteger e = null; BigInteger f = null; BigInteger y = null; String clientId; String serverId; byte[] clientKexInit; byte[] serverKexInit; KeyPairGenerator dhKeyPairGen; KeyAgreement dhKeyAgreement; KeyFactory dhKeyFactory; /** * Construct an uninitialized instance. */ public DiffieHellmanGroupExchangeSha1() { this("SHA-1"); } protected DiffieHellmanGroupExchangeSha1(String algorithm) { super(algorithm); } public boolean isKeyExchangeMessage(int messageid) { switch (messageid) { case SSH_MSG_KEXDH_GEX_REQUEST_OLD: case SSH_MSG_KEXDH_GEX_INIT: case SSH_MSG_KEXDH_GEX_GROUP: case SSH_MSG_KEXDH_GEX_REPLY: case SSH_MSG_KEXDH_GEX_REQUEST: return true; default: return false; } } /** * Get the algorithm name for this key exchange * * @return "diffie-hellman-group1-sha1" */ public String getAlgorithm() { return DIFFIE_HELLMAN_GROUP_EXCHANGE_SHA1; } /** * The client requests a modulus from the server indicating the pre- ferred * size. In the following description (C is the client, S is the server; the * modulus p is a large safe prime and g is a genera- tor for a subgroup of * GF(p); min is the minimal size of p in bits that is acceptable to the * client; n is the size of the modulus p in bits that the client would like * to receive from the server; max is the maximal size of p in bits that the * client can accept; V_S is S's version string; V_C is C's version string; * K_S is S's public host key; I_C is C's KEXINIT message and I_S S's * KEXINIT message which have been exchanged before this part begins): * * <pre> * 1. C sends "min || n || max" to S, indicating the minimal accept- * able group size, the preferred size of the group and the maxi- * mal group size in bits the client will accept. * * 2. S finds a group that best matches the client's request, and * sends "p || g" to C. * * 3. C generates a random number x (1 < x < (p-1)/2). It computes e * = g^x mod p, and sends "e" to S. * * 4. S generates a random number y (0 < y < (p-1)/2) and computes f * = g^y mod p. S receives "e". It computes K = e^y mod p, H = * hash(V_C || V_S || I_C || I_S || K_S || min || n || max || p * || g || e || f || K) (these elements are encoded according to * their types; see below), and signature s on H with its private * host key. S sends "K_S || f || s" to C. The signing opera- * tion may involve a second hashing operation. * * Implementation Notes: * * To increase the speed of the key exchange, both client * and server may reduce the size of their private expo- * nents. It should be at least twice as long as the key * material that is generated from the shared secret. For * more details see the paper by van Oorschot and Wiener * [1]. * * 5. C verifies that K_S really is the host key for S (e.g. using * certificates or a local database). C is also allowed to * accept the key without verification; however, doing so will * render the protocol insecure against active attacks (but may * be desirable for practical reasons in the short term in many * environments). C then computes K = f^x mod p, H = hash(V_C || * V_S || I_C || I_S || K_S || min || n || max || p || g || e || * f || K), and verifies the signature s on H. * </pre> * * @param clientIdentification * @param serverIdentification * @param clientKexInit * @param serverKexInit * @throws IOException */ public void performClientExchange(String clientIdentification, String serverIdentification, byte[] clientKexInit, byte[] serverKexInit) throws SshException { try { this.clientId = clientIdentification; this.serverId = serverIdentification; this.clientKexInit = clientKexInit; this.serverKexInit = serverKexInit; try { dhKeyFactory = JCEProvider .getProviderForAlgorithm(JCEAlgorithms.JCE_DH) == null ? KeyFactory .getInstance(JCEAlgorithms.JCE_DH) : KeyFactory .getInstance(JCEAlgorithms.JCE_DH, JCEProvider .getProviderForAlgorithm(JCEAlgorithms.JCE_DH)); dhKeyPairGen = JCEProvider .getProviderForAlgorithm(JCEAlgorithms.JCE_DH) == null ? KeyPairGenerator .getInstance(JCEAlgorithms.JCE_DH) : KeyPairGenerator .getInstance(JCEAlgorithms.JCE_DH, JCEProvider .getProviderForAlgorithm(JCEAlgorithms.JCE_DH)); dhKeyAgreement = JCEProvider .getProviderForAlgorithm(JCEAlgorithms.JCE_DH) == null ? KeyAgreement .getInstance(JCEAlgorithms.JCE_DH) : KeyAgreement .getInstance(JCEAlgorithms.JCE_DH, JCEProvider .getProviderForAlgorithm(JCEAlgorithms.JCE_DH)); } catch (NoSuchAlgorithmException ex) { throw new SshException( "JCE does not support Diffie Hellman key exchange", SshException.JCE_ERROR); } ByteArrayWriter msg = new ByteArrayWriter(); /* * SSH_MSG_KEX_DH_GEX_REQUEST_OLD is used for backwards * compatibility. Instead of sending "min || n || max", the client * only sends "n". Additionally, the hash is calculated using only * "n" instead of "min || n || max". */ boolean disableBackwardsCompatibility = !transport.getContext() .isDHGroupExchangeBackwardsCompatible(); int preferredKeySize = transport.getContext() .getDHGroupExchangeKeySize(); try { msg.write(disableBackwardsCompatibility ? SSH_MSG_KEXDH_GEX_REQUEST : SSH_MSG_KEXDH_GEX_REQUEST_OLD); if (disableBackwardsCompatibility) { // This breaks some old servers, use backwards compatibility msg.writeInt(1024); msg.writeInt(preferredKeySize); msg.writeInt(8192); } else { msg.writeInt(preferredKeySize); } transport.sendMessage(msg.toByteArray(), true); } finally { try { msg.close(); } catch (IOException e) { } } byte[] tmp = transport.nextMessage(); if (tmp[0] != SSH_MSG_KEXDH_GEX_GROUP) { transport.disconnect(TransportProtocol.KEY_EXCHANGE_FAILED, "Expected SSH_MSG_KEX_GEX_GROUP"); throw new SshException( "Key exchange failed: Expected SSH_MSG_KEX_GEX_GROUP [id=" + tmp[0] + "]", SshException.INTERNAL_ERROR); } ByteArrayReader bar = new ByteArrayReader(tmp, 1, tmp.length - 1); try { p = bar.readBigInteger(); g = bar.readBigInteger(); DHParameterSpec dhSkipParamSpec = new DHParameterSpec(p, g); dhKeyPairGen.initialize(dhSkipParamSpec); KeyPair dhKeyPair = dhKeyPairGen.generateKeyPair(); dhKeyAgreement.init(dhKeyPair.getPrivate()); e = ((DHPublicKey) dhKeyPair.getPublic()).getY(); } catch (InvalidKeyException ex) { throw new SshException("Failed to generate DH value", SshException.JCE_ERROR); } catch (InvalidAlgorithmParameterException ex) { throw new SshException("Failed to generate DH value", SshException.JCE_ERROR); } finally { try { bar.close(); } catch (IOException e) { } } // Send DH_INIT message msg.reset(); msg.write(SSH_MSG_KEXDH_GEX_INIT); msg.writeBigInteger(e); transport.sendMessage(msg.toByteArray(), true); // Wait for the reply processing any valid transport messages tmp = transport.nextMessage(); if (tmp[0] != SSH_MSG_KEXDH_GEX_REPLY) { transport.disconnect(TransportProtocol.KEY_EXCHANGE_FAILED, "Expected SSH_MSG_KEXDH_GEX_REPLY"); throw new SshException( "Key exchange failed: Expected SSH_MSG_KEXDH_GEX_REPLY [id=" + tmp[0] + "]", SshException.INTERNAL_ERROR); } bar = new ByteArrayReader(tmp, 1, tmp.length - 1); try { hostKey = bar.readBinaryString(); f = bar.readBigInteger(); signature = bar.readBinaryString(); // Calculate diffie hellman k value DHPublicKeySpec spec = new DHPublicKeySpec(f, p, g); DHPublicKey key = (DHPublicKey) dhKeyFactory .generatePublic(spec); dhKeyAgreement.doPhase(key, true); tmp = dhKeyAgreement.generateSecret(); if ((tmp[0] & 0x80) == 0x80) { byte[] tmp2 = new byte[tmp.length + 1]; System.arraycopy(tmp, 0, tmp2, 1, tmp.length); tmp = tmp2; } // Calculate diffe hellman k value secret = new BigInteger(tmp); // Calculate the exchange hash calculateExchangeHash(disableBackwardsCompatibility, preferredKeySize); } finally { bar.close(); } } catch (Exception ex) { throw new SshException(ex, SshException.INTERNAL_ERROR); } } public String getProvider() { if (dhKeyAgreement != null) return dhKeyAgreement.getProvider().getName(); else return ""; } /** * <p> * Calculates the exchange hash as an SHA1 hash of the following data. * <blockquote> * * <pre> * String the client's version string (CR and NL excluded) * String the server's version string (CR and NL excluded) * String the payload of the client's SSH_MSG_KEXINIT * String the payload of the server's SSH_MSG_KEXINIT * String the host key * UINT32 minimum size in bits of the acceptable group * UINT32 preferred size in bits of the acceptable group * UNIT32 maximum size in bits of the acceptable group * BigInteger p, safe prime * BigInteger g, generator for subgroup * BigInteger e, exchange value sent by the client * BigInteger f, exchange value sent by the server * BigInteger K, the shared secret * </pre> * * </blockquote> * </p> * * @throws IOException */ protected void calculateExchangeHash(boolean disableBackwardsCompatibility, int preferredKeySize) throws SshException { Digest hash = (Digest) ComponentManager.getInstance() .supportedDigests().getInstance(getHashAlgorithm()); // The local software version comments hash.putString(clientId); // The remote software version comments hash.putString(serverId); // The local kex init payload hash.putInt(clientKexInit.length); hash.putBytes(clientKexInit); // The remote kex init payload hash.putInt(serverKexInit.length); hash.putBytes(serverKexInit); // The host key hash.putInt(hostKey.length); hash.putBytes(hostKey); // Maximum size in bits of the acceptable group if (disableBackwardsCompatibility) { hash.putInt(1024); hash.putInt(preferredKeySize); hash.putInt(8192); // This breaks some old servers, use backwards // compatibility } else { hash.putInt(preferredKeySize); } // The safe prime hash.putBigInteger(p); // The generator hash.putBigInteger(g); // The diffie hellman e value hash.putBigInteger(e); // The diffie hellman f value hash.putBigInteger(f); // The diffie hellman k value hash.putBigInteger(secret); // Do the final output exchangeHash = hash.doFinal(); } }