/* * Copyright (C) 2010 Marc A. Paradise * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.bbssh.ssh.kex; import java.io.IOException; import net.rim.device.api.crypto.RandomSource; import org.bbssh.ssh.SSHMessages; import org.bbssh.ssh.packets.SshPacket2; import org.bbssh.util.Tools; /** * Container and converter for the Kex initialization packet, as defined in RFC 4253, section 7.1 */ public class KexInitData { byte[] cookie; String[] kexAlgorithms; String[] serverHostKeyAlgorithms; String[] clientToServerCryptoAlgorithms; String[] serverToClientCryptoAlgorithms; String[] MACClientToServer; String[] MACServerToClient; String[] compressionClientToServer; String[] compressionServerToClient; String[] languagesClientToServer; String[] languagesServerToClient; boolean firstKEXPacketFollowing; int reserved; SshPacket2 data; // @todo hmac-sha1-96, hmac-md5, hmac-md5-96 public static final String[] SUPPORTED_HMAC_ALGORITHMS = new String[] { "hmac-sha1" }; // @todo "ZLIB" public static final String[] SUPPORTED_COMPRESSION = new String[] { "none" }; // @todo "ssh-rsa" public static final String[] SUPPORTED_HOST_KEY_ALGORITHMS = new String[] { "ssh-dss", "ssh-rsa" }; public static final String[] SUPPORTED_KEX_ALGORITHMS = new String[] { "diffie-hellman-group14-sha1", "diffie-hellman-group1-sha1" }; private KexInitData() { } public static KexInitData createInstance() { // @todo - we can support the full range of allowed hashes // now that we've switched over to BB crypto. // Note that in all cases,we must list our preferred choice *first* KexInitData out = new KexInitData(); out.cookie = RandomSource.getBytes(16); out.kexAlgorithms = SUPPORTED_KEX_ALGORITHMS; out.serverHostKeyAlgorithms = SUPPORTED_HOST_KEY_ALGORITHMS; out.clientToServerCryptoAlgorithms = CipherManager.getInstance().getSupportedCiphers(); out.serverToClientCryptoAlgorithms = out.clientToServerCryptoAlgorithms; out.MACClientToServer = SUPPORTED_HMAC_ALGORITHMS; out.MACServerToClient = SUPPORTED_HMAC_ALGORITHMS; out.compressionClientToServer = SUPPORTED_COMPRESSION; out.compressionServerToClient = SUPPORTED_COMPRESSION; out.languagesClientToServer = new String[] {}; out.languagesServerToClient = new String[] {}; // @todo - we must support server sending 'first KEX' TRUE, AND guessing correctly! out.firstKEXPacketFollowing = false; out.reserved = 0; return out; } public static KexInitData createInstanceFromPacket(SshPacket2 packet) { KexInitData out = new KexInitData(); out.cookie = packet.getBytes(16); out.kexAlgorithms = packet.getStringList(); out.serverHostKeyAlgorithms = packet.getStringList(); out.clientToServerCryptoAlgorithms = packet.getStringList(); out.serverToClientCryptoAlgorithms = packet.getStringList(); out.MACClientToServer = packet.getStringList(); out.MACServerToClient = packet.getStringList(); out.compressionClientToServer = packet.getStringList(); out.compressionServerToClient = packet.getStringList(); out.languagesClientToServer = packet.getStringList(); out.languagesServerToClient = packet.getStringList(); out.firstKEXPacketFollowing = packet.getByte() == 1; out.reserved = packet.getInt32(); return out; } public SshPacket2 createOutboundPacket() { data = new SshPacket2(SSHMessages.SSH_MSG_KEXINIT); data.putBytes(cookie); data.putNameList(kexAlgorithms); data.putNameList(serverHostKeyAlgorithms); data.putNameList(clientToServerCryptoAlgorithms); data.putNameList(serverToClientCryptoAlgorithms); data.putNameList(MACClientToServer); data.putNameList(MACServerToClient); data.putNameList(compressionClientToServer); data.putNameList(compressionServerToClient); data.putNameList(languagesClientToServer); data.putNameList(languagesServerToClient); data.putByte(firstKEXPacketFollowing ? (byte) 1 : (byte) 0); data.putInt32(0); return data; } public String[] getMACClientToServer() { return MACClientToServer; } public String[] getMACServerToClient() { return MACServerToClient; } public String[] getClientToServerCryptoAlgorithms() { return clientToServerCryptoAlgorithms; } public String[] getCompressionClientToServer() { return compressionClientToServer; } public String[] getCompressionServerToClient() { return compressionServerToClient; } public byte[] getCookie() { return cookie; } public SshPacket2 getData() { return data; } public boolean isFirstKEXPacketFollowing() { return firstKEXPacketFollowing; } public String[] getKexAlgorithms() { return kexAlgorithms; } public String[] getLanguagesClientToServer() { return languagesClientToServer; } public String[] getLanguagesServerToClient() { return languagesServerToClient; } public int getReserved() { return reserved; } public String[] getServerHostKeyAlgorithms() { return serverHostKeyAlgorithms; } public String[] getServerToClientCryptoAlgorithms() { return serverToClientCryptoAlgorithms; } public static KexAgreement findAgreement(KexInitData s, KexInitData c) throws IOException { KexAgreement a = new KexAgreement(); a.kexAlgorithm = Tools.findFirstMatchingElement(c.getKexAlgorithms(), s.getKexAlgorithms()); a.serverHostKeyAlgorithm = Tools.findFirstMatchingElement(c .getServerHostKeyAlgorithms(), s.getServerHostKeyAlgorithms()); a.clientToServerCryptoAlgorithm = Tools.findFirstMatchingElement(c .getClientToServerCryptoAlgorithms(), s.getClientToServerCryptoAlgorithms()); a.serverToClientCryptoAlgorithm = Tools.findFirstMatchingElement(c .getServerToClientCryptoAlgorithms(), s.getServerToClientCryptoAlgorithms()); a.MACClientToServer = Tools.findFirstMatchingElement(c.getMACClientToServer(), s.getMACClientToServer()); a.MACServerToClient = Tools.findFirstMatchingElement(c.getMACServerToClient(), s.getMACServerToClient()); a.languageClientToServer = Tools.findFirstMatchingElement(c .getLanguagesClientToServer(), s.getLanguagesClientToServer()); a.languageServerToClient = Tools.findFirstMatchingElement(c .getLanguagesServerToClient(), s.getLanguagesServerToClient()); a.compressionClientToServer = Tools.findFirstMatchingElement(c .getCompressionClientToServer(), s.getCompressionClientToServer()); a.compressionServerToClient = Tools.findFirstMatchingElement(c.getCompressionServerToClient(), s .getCompressionServerToClient()); // @todo better handling of errors an exception - perhaps even throw exception // in comparator itself? Not an IO exception, but AgreementFailedException... if (a.kexAlgorithm == null) { throw new IOException("Could not agree upon KEX algorithm"); } if (a.serverHostKeyAlgorithm == null) { throw new IOException("Could not agree upon Server Host Key algorithm"); } if (a.clientToServerCryptoAlgorithm == null) { throw new IOException("Could not agree upon C->S Crypto algorithm"); } if (a.serverToClientCryptoAlgorithm == null) { throw new IOException("Could not agree upon S->C Crypto algorithm"); } if (a.MACClientToServer == null) { throw new IOException("Could not agree upon C->S MAC algorithm"); } if (a.MACServerToClient == null) { throw new IOException("Could not agree upon S->C MAC algorithm"); } // Note that we may have an empty string, but should NEVER have null for these // last two items. if (a.compressionClientToServer == null) { throw new IOException("Could not agree upon C->S Compression algorithm"); } if (a.compressionServerToClient == null) { throw new IOException("Could not agree upon S->C Compression algorithm"); } // Language is entirely optional, so null is valid. /** * if (a.languageClientToServer == null) { throw new IOException("Could not agree upon C->S language"); } if * (a.languageServerToClient == null) { throw new IOException("Could not agree upon S->C language"); } */ return a; } }