/* * This file is part of "The Java Telnet Application". * * (c) Matthias L. Jugel, Marcus Mei�ner 1996-2002. All Rights Reserved. * The file was changed by Radek Polak to work as midlet in MIDP 1.0 * * This file has been modified by Karl von Randow for MidpSSH. * * Please visit http://javatelnet.org/ for updates and contact. * * --LICENSE NOTICE-- * 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., 675 Mass Ave, Cambridge, MA 02139, USA. * --LICENSE NOTICE-- */ package ssh; import java.io.IOException; import java.util.Random; import ssh.v1.BigInteger; import ssh.v1.Cipher; import ssh.v1.MD5; import ssh.v1.SshCrypto; import ssh.v1.SshPacket1; import ssh.v2.DHKeyExchange; import ssh.v2.PublicKeyAuthentication; import ssh.v2.SHA1Digest; import ssh.v2.SshCrypto2; import ssh.v2.SshPacket2; import app.Settings; import app.session.SshSession; /** * Secure Shell IO * * @author Marcus Meissner * @version $Id$ */ public class SshIO { private static MD5 md5 = new MD5(); private SshSession sshSession; /** * variables for the connection */ private String idstr = ""; // ("SSH-<protocolmajor>.<protocolminor>-<version>\n") private String idstr_sent = "SSH/MidpSSH\n"; /** * Debug level. This results in additional diagnostic messages on the java * console. */ // private static int debug = 0; /** * State variable for Ssh negotiation reader */ //#ifndef nossh1 private SshCrypto crypto; //#endif //#ifdef ssh2 private SshCrypto2 crypto2; private int remoteId; //#endif String cipher_type;// = "IDEA"; private static java.util.Random rnd = new java.util.Random(); private int remotemajor, remoteminor; private int mymajor, myminor; private int useprotocol; public String login, password; // nobody is to access those fields : better to use pivate, nobody knows :-) private String dataToSend = null; private byte lastPacketSentType; // phase : handleBytes private byte state; private static final byte STATE_INIT = 0; private static final byte STATE_KEYS = 1; private static final byte STATE_SECURE = 2; private static final byte STATE_CONNECTED = 3; private int authmode; public boolean usepublickey = false; private static final byte MODE_PUBLICKEY = 1; private static final byte MODE_PASSWORD = 2; //#ifdef keybrdinteractive private static final byte MODE_KEYBOARD_INTERACTIVE = 3; //#endif // handlePacket // messages // The supported packet types and the corresponding message numbers are // given in the following table. Messages with _MSG_ in their name may // be sent by either side. Messages with _CMSG_ are only sent by the // client, and messages with _SMSG_ only by the server. // // private static final byte SSH_MSG_NONE = 0; private static final byte SSH_MSG_DISCONNECT = 1; private static final byte SSH_SMSG_PUBLIC_KEY = 2; private static final byte SSH_CMSG_SESSION_KEY = 3; private static final byte SSH_CMSG_USER = 4; private static final byte SSH_CMSG_AUTH_PASSWORD = 9; private static final byte SSH_CMSG_REQUEST_PTY = 10; private static final byte SSH_CMSG_EXEC_SHELL = 12; private static final byte SSH_SMSG_SUCCESS = 14; private static final byte SSH_SMSG_FAILURE = 15; private static final byte SSH_CMSG_STDIN_DATA = 16; private static final byte SSH_SMSG_STDOUT_DATA = 17; private static final byte SSH_SMSG_STDERR_DATA = 18; private static final byte SSH_SMSG_EXITSTATUS = 20; private static final byte SSH_MSG_IGNORE = 32; private static final byte SSH_CMSG_EXIT_CONFIRMATION = 33; // private static final byte SSH_MSG_DEBUG = 36; /* SSH v2 stuff */ private static final byte SSH2_MSG_DISCONNECT = 1; private static final byte SSH2_MSG_IGNORE = 2; // private static final byte SSH2_MSG_UNIMPLEMENTED = 3; private static final byte SSH2_MSG_SERVICE_REQUEST = 5; private static final byte SSH2_MSG_SERVICE_ACCEPT = 6; private static final byte SSH2_MSG_KEXINIT = 20; private static final byte SSH2_MSG_NEWKEYS = 21; private static final byte SSH2_MSG_KEXDH_INIT = 30; private static final byte SSH2_MSG_KEXDH_REPLY = 31; private static final byte SSH2_MSG_USERAUTH_REQUEST = 50; private static final byte SSH2_MSG_USERAUTH_FAILURE = 51; private static final byte SSH2_MSG_USERAUTH_SUCCESS = 52; // private static final byte SSH2_MSG_USERAUTH_BANNER = 53; // private static final byte SSH2_MSG_USERAUTH_PK_OK = 60; private static final byte SSH2_MSG_USERAUTH_INFO_REQUEST = 60; private static final byte SSH2_MSG_USERAUTH_INFO_RESPONSE = 61; private static final byte SSH2_MSG_CHANNEL_OPEN = 90; private static final byte SSH2_MSG_CHANNEL_OPEN_CONFIRMATION = 91; // private static final byte SSH2_MSG_CHANNEL_OPEN_FAILURE = 92; // private static final byte SSH2_MSG_CHANNEL_WINDOW_ADJUST = 93; private static final byte SSH2_MSG_CHANNEL_DATA = 94; // private static final byte SSH2_MSG_CHANNEL_EXTENDED_DATA = 95; // private static final byte SSH2_MSG_CHANNEL_EOF = 96; private static final byte SSH2_MSG_CHANNEL_CLOSE = 97; private static final byte SSH2_MSG_CHANNEL_REQUEST = 98; // private static final byte SSH2_MSG_CHANNEL_SUCCESS = 99; // private static final byte SSH2_MSG_CHANNEL_FAILURE = 100; private int outgoingseq = 0; // // encryption types // // private static final int SSH_CIPHER_NONE = 0; // No encryption private static final int SSH_CIPHER_IDEA = 1; // IDEA in CFB mode (patented) private static final int SSH_CIPHER_DES = 2; // DES in CBC mode private static final int SSH_CIPHER_3DES = 3; // Triple-DES in CBC mode // private static final int SSH_CIPHER_TSS = 4; // An experimental stream cipher // private static final int SSH_CIPHER_RC4 = 5; // RC4 (patented) private static final int SSH_CIPHER_BLOWFISH = 6; // Bruce Scheiers blowfish (public // d) // // authentication methods // // private final int SSH_AUTH_RHOSTS = 1; // .rhosts or /etc/hosts.equiv // private final int SSH_AUTH_RSA = 2; // pure RSA authentication // private final int SSH_AUTH_PASSWORD = 3; // password authentication, // implemented ! // private final int SSH_AUTH_RHOSTS_RSA = 4; // .rhosts with RSA host // authentication /** * Initialise SshIO */ public SshIO(SshSession sshSession) { this.sshSession = sshSession; } SshPacket currentpacket; /** write data to our back end */ public void write(byte[] b) throws IOException { sshSession.sendData(b); } protected void sendDisconnect(int reason, String reasonStr) throws IOException { //#ifdef ssh2 if (useprotocol == 2) { SshPacket2 pn = new SshPacket2(SSH2_MSG_DISCONNECT); pn.putInt32(reason); pn.putString(reasonStr); pn.putString("en"); sendPacket2(pn); } //#endif //#ifndef nossh1 if (useprotocol == 1) { SshPacket1 pn = new SshPacket1(SSH_MSG_DISCONNECT); pn.putInt32(reason); pn.putString(reasonStr); pn.putString("en"); sendPacket1(pn); } //#endif } public void sendData(byte[] data, int offset, int length) throws IOException { String str = new String(data, offset, length); // if (debug > 1) System.out.println("SshIO.send(" + str + ")"); if (dataToSend == null) dataToSend = str; else dataToSend += str; if (state == STATE_CONNECTED) { //#ifdef ssh2 if (useprotocol == 2) { SshPacket2 pn = new SshPacket2(SSH2_MSG_CHANNEL_DATA); pn.putInt32(remoteId); pn.putString(dataToSend); sendPacket2(pn); } //#endif //#ifndef nossh1 if (useprotocol == 1) { Send_SSH_CMSG_STDIN_DATA(dataToSend); } //#endif dataToSend = null; } } /** * Read data from the remote host. Blocks until data is available. * * Returns an array of bytes that will be displayed. * */ public byte[] handleSSH(byte buff[], int boffset, int length) throws IOException { String result; int boffsetend = boffset + length; // if (debug > 1) // Telnet.console.append("SshIO.getPacket(" + buff + "," + length + // ")"); PHASE_INIT: if (state == STATE_INIT) { byte b; // of course, byte is a signed entity (-128 -> 127) while (boffset < boffsetend) { b = buff[boffset++]; // both sides MUST send an identification string of the form // "SSH-protoversion-softwareversion comments", // followed by newline character(ascii 10 = '\n' or '\r') idstr += (char) b; if (b == '\n') { if (idstr.startsWith("SSH-")) { remotemajor = Integer.parseInt(idstr.substring(4, 5)); String minorverstr = idstr.substring(6, 8); if (!Character.isDigit(minorverstr.charAt(1))) minorverstr = minorverstr.substring(0, 1); remoteminor = Integer.parseInt(minorverstr); //#ifdef ssh2 if (remotemajor == 2) { mymajor = 2; myminor = 0; useprotocol = 2; } else { //#ifndef nossh1 /* * Check if we have discretion over whether to use ssh1 * or ssh2 */ if (remoteminor == 99 && Settings.sshVersionPreferred == 2) { mymajor = 2; myminor = 0; useprotocol = 2; } else { mymajor = 1; myminor = 5; useprotocol = 1; } //#else if (remoteminor == 99) { mymajor = 2; myminor = 0; useprotocol = 2; } else { return "Server requires SSH1.\r\n".getBytes(); } //#endif } //#else if (remotemajor == 2) { // TODO disconnect return "Server requires SSH2.\r\n".getBytes(); } else { mymajor = 1; myminor = 5; useprotocol = 1; } //#endif // this is how we tell the remote server what protocol // we // use. idstr_sent = "SSH-" + mymajor + "." + myminor + "-" + idstr_sent; write(idstr_sent.getBytes()); state = STATE_KEYS; //#ifdef ssh2 if (useprotocol == 2) { currentpacket = new SshPacket2(); } //#ifndef nossh1 else { currentpacket = new SshPacket1(); } //#endif //#else currentpacket = new SshPacket1(); //#endif break PHASE_INIT; } else { /* Lines sent during init that do not start SSH- should be ignored * http://www.ietf.org/internet-drafts/draft-ietf-secsh-transport-24.txt * section 4.2 */ idstr = ""; } } } if (boffset == boffsetend) return "".getBytes(); return "Init Error\n".getBytes(); } result = ""; while (boffset < boffsetend) { boffset = currentpacket.addPayload(buff, boffset, (boffsetend - boffset)); if (currentpacket.isFinished()) { //#ifdef ssh2 if (useprotocol == 2) { result = result + handlePacket2((SshPacket2) currentpacket); currentpacket = new SshPacket2(crypto2); } //#endif //#ifndef nossh1 if (useprotocol == 1) { result = result + handlePacket1((SshPacket1) currentpacket); currentpacket = new SshPacket1(crypto); } //#endif } } return result.getBytes(); } /** * Given a host key return the finger print string for that key. * * @param host_key * @return */ private String fingerprint(byte[] host_key) { byte[] fprint = md5.digest(host_key); StringBuffer buf = new StringBuffer(); int n = fprint.length; for (int i = 0; i < n; i++) { int j = fprint[i] & 0xff; String hex = Integer.toHexString(j); if (hex.length() == 1) { buf.append('0'); } buf.append(hex); if (i + 1 < n) buf.append(':'); } return buf.toString(); } //#ifdef ssh2 /** * Handle SSH protocol Version 2 * * @param p * the packet we will process here. * @return a array of bytes */ private String handlePacket2(SshPacket2 p) throws IOException { switch (p.getType()) { case SSH2_MSG_KEXINIT: { /* * Described * http://www.ietf.org/rfc/rfc4253.txt * Section 7.1 */ // p.getBytes(16); // cookie // p.getString(); // kex_algorithms // p.getString(); // server_host_key_algorithms // p.getString(); // encryption_algorithms_client_to_server // p.getString(); // encryption_algorithms_server_to_client // p.getString(); // mac_algorithms_client_to_server // p.getString(); // mac_algorithms_server_to_client // p.getString(); // compression_algorithms_client_to_server // p.getString(); // compression_algorithms_server_to_client // p.getString(); // languages_client_to_server // p.getString(); // languages_server_to_client // p.getBytes(1); // first_kex_packet_follows SshPacket2 pn = new SshPacket2(SSH2_MSG_KEXINIT); byte[] kexsend = new byte[16]; Random random = new Random(); for (int i = 0; i < kexsend.length; i++) { kexsend[i] = (byte) random.nextInt(); } String ciphername; pn.putBytes(kexsend); pn.putString("diffie-hellman-group1-sha1"); pn.putString(DHKeyExchange.SSH_DSS); cipher_type = "DES3"; ciphername = "3des-cbc"; pn.putString(ciphername); pn.putString(ciphername); pn.putString("hmac-sha1"); pn.putString("hmac-sha1"); pn.putString("none"); pn.putString("none"); pn.putString(""); pn.putString(""); pn.putByte((byte) 0); pn.putInt32(0); byte[] I_C = pn.getData(); sendPacket2(pn); if (Settings.ssh2StoreKey) { if (Settings.ssh2x == null || Settings.ssh2y == null) { byte[][] keys = DHKeyExchange .generateKeyPairBytes(Settings.ssh2KeySize); Settings.ssh2x = keys[0]; Settings.ssh2y = keys[1]; Settings.saveSettings(); } dhkex = new DHKeyExchange(Settings.ssh2x, Settings.ssh2y); } else { dhkex = new DHKeyExchange(Settings.ssh2KeySize); } dhkex.V_S = idstr.trim().getBytes(); dhkex.V_C = idstr_sent.trim().getBytes(); dhkex.I_S = add20(p.getData()); dhkex.I_C = add20(I_C); pn = new SshPacket2(SSH2_MSG_KEXDH_INIT); pn.putMpInt(dhkex.getE()); sendPacket2(pn); return "Negotiating..."; } case SSH2_MSG_KEXDH_REPLY: { byte[] K_S = p.getByteString(); // System.out.println( "K_S=" + K_S ); byte[] dhserverpub = p.getMpInt(); // result += "DH Server Pub: " + dhserverpub + "\n\r"; byte[] sig_of_h = p.getByteString(); boolean ok = dhkex.next(K_S, dhserverpub, sig_of_h); if (ok) { // TODO handle fingerprint better return "OK\r\n" + dhkex.keyalg + " " + fingerprint(K_S) + "\r\n"; } else { sendDisconnect(3, "Key exchange failed"); return "FAILED\r\n"; } } case SSH2_MSG_NEWKEYS: { // Send response sendPacket2(new SshPacket2(SSH2_MSG_NEWKEYS)); // byte[] session_key = new byte[24]; // crypto = new SshCrypto( cipher_type, session_key ); updateKeys(dhkex); state = STATE_SECURE; SshPacket2 pn = new SshPacket2(SSH2_MSG_SERVICE_REQUEST); pn.putString("ssh-userauth"); sendPacket2(pn); //#ifndef noinstructions //#ifdef removeme if (1 == 1) //#endif return "Requesting authentication\r\n"; //#else break; //#endif } case SSH2_MSG_SERVICE_ACCEPT: if (state < STATE_SECURE) break; return authenticate2(); case SSH2_MSG_USERAUTH_FAILURE: if (state < STATE_SECURE) break; // p.getString(); // methods // p.getByte(); // partialSuccess String message = authenticate2(); if (message != null) { return message; } else { return "Authentication failed.\r\nAvailable methods are: " + p.getString() + "\r\n"; } //#ifdef keybrdinteractive case SSH2_MSG_USERAUTH_INFO_REQUEST: if (state < STATE_SECURE) break; String name = p.getString(); String instruction = p.getString(); p.getString(); // language tag int numPrompts = p.getInt32(); String[] prompts = new String[numPrompts]; boolean[] echos = new boolean[numPrompts]; for (int i = 0; i < numPrompts; i++) { prompts[i] = p.getString(); echos[i] = p.getByte() != 0; } sshSession.prompt(name, instruction, prompts, echos); break; //#endif case SSH2_MSG_USERAUTH_SUCCESS: { // Open channel SshPacket2 pn = new SshPacket2(SSH2_MSG_CHANNEL_OPEN); pn.putString("session"); pn.putInt32(0); pn.putInt32(0x100000); pn.putInt32(0x4000); sendPacket2(pn); //#ifndef noinstructions //#ifdef removeme if (1 == 1) //#endif return "Authentication accepted\r\n"; //#else break; //#endif } case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: { p.getInt32(); // localId remoteId = p.getInt32(); // p.getInt32(); // remoteWindowSize // p.getInt32(); // remotePacketSize // Open PTY SshPacket2 pn = new SshPacket2(SSH2_MSG_CHANNEL_REQUEST); pn.putInt32(remoteId); pn.putString("pty-req"); pn.putByte((byte) 0); // want reply pn.putString(getTerminalID()); pn.putInt32(getTerminalWidth()); pn.putInt32(getTerminalHeight()); pn.putInt32(0); pn.putInt32(0); pn.putString(""); sendPacket2(pn); // Open Shell pn = new SshPacket2(SSH2_MSG_CHANNEL_REQUEST); pn.putInt32(remoteId); pn.putString("shell"); pn.putByte((byte) 0); // want reply sendPacket2(pn); state = STATE_CONNECTED; if (dataToSend != null) { pn = new SshPacket2(SSH2_MSG_CHANNEL_DATA); pn.putInt32(0); pn.putString(dataToSend); sendPacket2(pn); dataToSend = null; } //#ifndef noinstructions //#ifdef removeme if (1 == 1) //#endif return "Shell opened\r\n"; //#else break; //#endif } case SSH2_MSG_CHANNEL_DATA: { p.getInt32(); // localId String data = p.getString(); return data; } case SSH2_MSG_CHANNEL_CLOSE: { sendDisconnect(11, "Finished"); break; } case SSH2_MSG_DISCONNECT: p.getInt32(); // disconnect reason String discreason1 = p.getString(); return "\r\nDisconnected: " + discreason1 + "\r\n"; } return ""; } //#ifdef keybrdinteractive public void sendUserauthInfoResponse(String[] responses) throws IOException { SshPacket2 buf = new SshPacket2(SSH2_MSG_USERAUTH_INFO_RESPONSE); buf.putInt32(responses.length); for (int i = 0; i < responses.length; i++) { buf.putString(responses[i]); } sendPacket2(buf); } //#endif private String authenticate2() throws IOException { SshPacket2 buf = new SshPacket2(SSH2_MSG_USERAUTH_REQUEST); buf.putString(login); buf.putString("ssh-connection"); if (usepublickey && Settings.x != null && authmode < MODE_PUBLICKEY) { /* Try publickey */ authmode = MODE_PUBLICKEY; PublicKeyAuthentication kg = new PublicKeyAuthentication(); buf.putString("publickey"); buf.putByte((byte) 1); buf.putString(DHKeyExchange.SSH_DSS); buf.putString(kg.getPublicKeyBlob()); byte[] sig = kg.sign(session_id, buf.getData()); buf.putString(sig); sendPacket2(buf); //#ifndef noinstructions //#ifdef removeme if (1 == 1) //#endif return "Sent publickey\r\n"; //#else return ""; //#endif } else if (authmode < MODE_PASSWORD) { /* Do password auth */ authmode = MODE_PASSWORD; buf.putString("password"); buf.putByte((byte) 0); buf.putString(password); sendPacket2(buf); //#ifndef noinstructions //#ifdef removeme if (1 == 1) //#endif return "Sent password\r\n"; //#else return ""; //#endif } //#ifdef keybrdinteractive else if (authmode < MODE_KEYBOARD_INTERACTIVE) { /* Attempt keyboard-interactive auth */ authmode = MODE_KEYBOARD_INTERACTIVE; buf.putString("keyboard-interactive"); buf.putString(""); buf.putString(""); sendPacket2(buf); //#ifndef noinstructions //#ifdef removeme if (1 == 1) //#endif return "Start keyboard-interactive\r\n"; //#else return ""; //#endif } //#endif else { return null; } } private void sendPacket2(SshPacket2 packet) throws IOException { write(packet.getPayLoad(crypto2, outgoingseq)); outgoingseq++; lastPacketSentType = packet.getType(); } private DHKeyExchange dhkex; private byte[] session_id; private void updateKeys(DHKeyExchange kex) { byte[] K = kex.K; byte[] H = kex.H; SHA1Digest hash = new SHA1Digest(); if (session_id == null) { session_id = new byte[H.length]; System.arraycopy(H, 0, session_id, 0, H.length); } /* * Initial IV client to server: HASH (K || H || "A" || session_id) * Initial IV server to client: HASH (K || H || "B" || session_id) * Encryption key client to server: HASH (K || H || "C" || session_id) * Encryption key server to client: HASH (K || H || "D" || session_id) * Integrity key client to server: HASH (K || H || "E" || session_id) * Integrity key server to client: HASH (K || H || "F" || session_id) */ SshPacket2 buf = new SshPacket2(); buf.putMpInt(K); buf.putBytes(H); buf.putByte((byte) 0x41); buf.putBytes(session_id); byte[] b = buf.getData(); hash.update(b, 0, b.length); byte[] IVc2s = new byte[hash.getDigestSize()]; hash.doFinal(IVc2s, 0); int j = b.length - session_id.length - 1; b[j]++; hash.update(b, 0, b.length); byte[] IVs2c = new byte[hash.getDigestSize()]; hash.doFinal(IVs2c, 0); b[j]++; hash.update(b, 0, b.length); byte[] Ec2s = new byte[hash.getDigestSize()]; hash.doFinal(Ec2s, 0); b[j]++; hash.update(b, 0, b.length); byte[] Es2c = new byte[hash.getDigestSize()]; hash.doFinal(Es2c, 0); b[j]++; hash.update(b, 0, b.length); byte[] MACc2s = new byte[hash.getDigestSize()]; hash.doFinal(MACc2s, 0); b[j]++; hash.update(b, 0, b.length); byte[] MACs2c = new byte[hash.getDigestSize()]; hash.doFinal(MACs2c, 0); int keySize = 24; while (keySize > Es2c.length) { buf = new SshPacket2(); buf.putMpInt(K); buf.putBytes(H); buf.putBytes(Es2c); b = buf.getData(); hash.update(b, 0, b.length); byte[] foo = new byte[hash.getDigestSize()]; hash.doFinal(foo, 0); byte[] bar = new byte[Es2c.length + foo.length]; System.arraycopy(Es2c, 0, bar, 0, Es2c.length); System.arraycopy(foo, 0, bar, Es2c.length, foo.length); Es2c = bar; } while (keySize > Ec2s.length) { buf = new SshPacket2(); buf.putMpInt(K); buf.putBytes(H); buf.putBytes(Ec2s); b = buf.getData(); hash.update(b, 0, b.length); byte[] foo = new byte[hash.getDigestSize()]; hash.doFinal(foo, 0); byte[] bar = new byte[Ec2s.length + foo.length]; System.arraycopy(Ec2s, 0, bar, 0, Ec2s.length); System.arraycopy(foo, 0, bar, Ec2s.length, foo.length); Ec2s = bar; } crypto2 = new SshCrypto2(IVc2s, IVs2c, Ec2s, Es2c, MACc2s, MACs2c); } private byte[] add20(byte[] in) { byte[] out = new byte[in.length + 1]; out[0] = 20; System.arraycopy(in, 0, out, 1, in.length); return out; } //#endif //#ifndef nossh1 private String handlePacket1(SshPacket1 p) throws IOException { // the // message // to handle // is data // and its // length is // we have to deal with data.... // if (debug > 0) // System.out.println("1 packet to handle, type " + p.getType()); switch (p.getType()) { case SSH_MSG_DISCONNECT: return p.getString(); case SSH_SMSG_PUBLIC_KEY: byte[] anti_spoofing_cookie; // 8 bytes byte[] server_key_public_exponent; // mp-int byte[] server_key_public_modulus; // mp-int byte[] host_key_public_exponent; // mp-int byte[] host_key_public_modulus; // mp-int byte[] supported_ciphers_mask; // 32-bit int anti_spoofing_cookie = p.getBytes(8); p.getBytes(4); // server_key_bits server_key_public_exponent = p.getMpInt(); server_key_public_modulus = p.getMpInt(); p.getBytes(4); // host_key_bits host_key_public_exponent = p.getMpInt(); host_key_public_modulus = p.getMpInt(); p.getBytes(4); // protocol_flags supported_ciphers_mask = p.getBytes(4); p.getBytes(4); // supported_authentications_mask // We have completely received the PUBLIC_KEY // We prepare the answer ... String ret = Send_SSH_CMSG_SESSION_KEY(anti_spoofing_cookie, server_key_public_modulus, host_key_public_modulus, supported_ciphers_mask, server_key_public_exponent, host_key_public_exponent); if (ret != null) return ret; // TODO prompt user to confirm fingerprint byte[] host_key_combined = new byte[host_key_public_exponent.length + host_key_public_modulus.length]; System.arraycopy(host_key_public_modulus, 0, host_key_combined, 0, host_key_public_modulus.length); System.arraycopy(server_key_public_exponent, 0, host_key_combined, host_key_public_modulus.length, server_key_public_exponent.length); String fingerprint = fingerprint(host_key_combined); return fingerprint + "\r\n"; // break; case SSH_SMSG_SUCCESS: // if (debug > 0) // System.out.println("SSH_SMSG_SUCCESS (last packet was " + // lastPacketSentType + ")"); if (lastPacketSentType == SSH_CMSG_SESSION_KEY) { // we have succefully sent the session key !! (at last :-) ) Send_SSH_CMSG_USER(); break; } if (lastPacketSentType == SSH_CMSG_USER) { // authentication is NOT needed for this user Send_SSH_CMSG_REQUEST_PTY(); // request a pseudo-terminal return "Empty password login.\r\n"; } if (lastPacketSentType == SSH_CMSG_AUTH_PASSWORD) {// password // correct !!! // yahoo // if (debug > 0) // System.out.println("login succesful"); // now we have to start the interactive session ... Send_SSH_CMSG_REQUEST_PTY(); // request a pseudo-terminal return "Login & password accepted\r\n"; } if (lastPacketSentType == SSH_CMSG_REQUEST_PTY) {// pty // accepted // !! /* * we can send data with a pty accepted ... no need for a shell. */ state = STATE_CONNECTED; if (dataToSend != null) { Send_SSH_CMSG_STDIN_DATA(dataToSend); dataToSend = null; } Send_SSH_CMSG_EXEC_SHELL(); // we start a shell break; } if (lastPacketSentType == SSH_CMSG_EXEC_SHELL) {// shell is // running // ... /* empty */ } break; case SSH_SMSG_FAILURE: if (lastPacketSentType == SSH_CMSG_AUTH_PASSWORD) {// password // incorrect ??? // System.out.println("failed to log in"); return "Login & password not accepted\r\n"; } if (lastPacketSentType == SSH_CMSG_USER) { // authentication is needed for the given user // (in most cases that's true) Send_SSH_CMSG_AUTH_PASSWORD(); break; } if (lastPacketSentType == SSH_CMSG_REQUEST_PTY) {// pty not // accepted // !! break; } break; case SSH_SMSG_STDOUT_DATA: // receive some data from the server return p.getString(); case SSH_SMSG_STDERR_DATA: // receive some error data from the return "Error : " + p.getString(); case SSH_SMSG_EXITSTATUS: // sent by the server to indicate that // the client program has terminated. // 32-bit int exit status of the command p.getInt32(); Send_SSH_CMSG_EXIT_CONFIRMATION(); // System.out.println("SshIO : Exit status " + value); break; } return ""; } // handlePacket private void sendPacket1(SshPacket1 packet) throws IOException { write(packet.getPayLoad(crypto)); lastPacketSentType = packet.getType(); } // // Send_SSH_CMSG_SESSION_KEY // Create : // the session_id, // the session_key, // the Xored session_key, // the double_encrypted session key // send SSH_CMSG_SESSION_KEY // Turn the encryption on (initialise the block cipher) // private String Send_SSH_CMSG_SESSION_KEY(byte[] anti_spoofing_cookie, byte[] server_key_public_modulus, byte[] host_key_public_modulus, byte[] supported_ciphers_mask, byte[] server_key_public_exponent, byte[] host_key_public_exponent) throws IOException { byte cipher_types; // encryption types byte[] session_key; // mp-int // create the session id // session_id = md5(hostkey->n || servkey->n || cookie) //protocol V // 1.5. (we use this one) // session_id = md5(servkey->n || hostkey->n || cookie) //protocol V // 1.1.(Why is it different ??) // byte[] session_id_byte = new byte[host_key_public_modulus.length + server_key_public_modulus.length + anti_spoofing_cookie.length]; System.arraycopy(host_key_public_modulus, 0, session_id_byte, 0, host_key_public_modulus.length); System.arraycopy(server_key_public_modulus, 0, session_id_byte, host_key_public_modulus.length, server_key_public_modulus.length); System.arraycopy(anti_spoofing_cookie, 0, session_id_byte, host_key_public_modulus.length + server_key_public_modulus.length, anti_spoofing_cookie.length); byte[] hash_md5 = md5.digest(session_id_byte); // SSH_CMSG_SESSION_KEY : Sent by the client // 1 byte cipher_type (must be one of the supported values) // 8 bytes anti_spoofing_cookie (must match data sent by the server) // mp-int double-encrypted session key (uses the session-id) // 32-bit int protocol_flags // if ((supported_ciphers_mask[3] & (byte) (1 << SSH_CIPHER_BLOWFISH)) != 0 && hasCipher("Blowfish")) { cipher_types = (byte) SSH_CIPHER_BLOWFISH; cipher_type = "Blowfish"; } else { if ((supported_ciphers_mask[3] & (1 << SSH_CIPHER_IDEA)) != 0 && hasCipher("IDEA")) { cipher_types = (byte) SSH_CIPHER_IDEA; cipher_type = "IDEA"; } else { if ((supported_ciphers_mask[3] & (1 << SSH_CIPHER_3DES)) != 0 && hasCipher("DES3")) { cipher_types = (byte) SSH_CIPHER_3DES; cipher_type = "DES3"; } else { if ((supported_ciphers_mask[3] & (1 << SSH_CIPHER_DES)) != 0 && hasCipher("DES")) { cipher_types = (byte) SSH_CIPHER_DES; cipher_type = "DES"; } else { // System.err.println("SshIO: remote server does not // supported IDEA, BlowFish or 3DES, support cypher mask // is " + supported_ciphers_mask[3] + ".\n"); return "\rIncompatible ciphers.\r\n"; } } } } // if (debug > 0) // System.out.println("SshIO: Using " + cipher_type + " // blockcipher.\n"); // anti_spoofing_cookie : the same // double_encrypted_session_key : // 32 bytes of random bits // Xor the 16 first bytes with the session-id // encrypt with the server_key_public (small) then the // host_key_public(big) using RSA. // // 32 bytes of random bits byte[] random_bits1 = new byte[16], random_bits2 = new byte[16]; // / java.util.Date date = new java.util.Date(); ////the number of // milliseconds since January 1, 1970, 00:00:00 GMT. // Math.random() a pseudorandom double between 0.0 and 1.0. random_bits2 = random_bits1 = // md5.hash("" + Math.random() * (new java.util.Date()).getDate()); md5.digest(("" + rnd.nextLong() * (new java.util.Date()).getTime()) .getBytes()); // RADEK // - // zase // RANDOM random_bits1 = md5.digest(addArrayOfBytes(md5 .digest((password + login).getBytes()), random_bits1)); random_bits2 = md5.digest(addArrayOfBytes(md5 .digest((password + login).getBytes()), random_bits2)); // SecureRandom random = new java.security.SecureRandom(random_bits1); // //no supported by netscape :-( // random.nextBytes(random_bits1); // random.nextBytes(random_bits2); session_key = addArrayOfBytes(random_bits1, random_bits2); // Xor the 16 first bytes with the session-id byte[] session_keyXored = XORArrayOfBytes(random_bits1, hash_md5); session_keyXored = addArrayOfBytes(session_keyXored, random_bits2); // We encrypt now!! byte[] encrypted_session_key; /* * Karl: according to SSH 1.5 protocol spec we encrypt first with the * key with the shortest modulus. Usually this was the server key but * some servers have bigger keys than the host key! So check here and * swap around if necessary. */ if (server_key_public_modulus.length <= host_key_public_modulus.length) { encrypted_session_key = encrypteRSAPkcs1Twice( session_keyXored, server_key_public_exponent, server_key_public_modulus, host_key_public_exponent, host_key_public_modulus); } else { encrypted_session_key = encrypteRSAPkcs1Twice( session_keyXored, host_key_public_exponent, host_key_public_modulus, server_key_public_exponent, server_key_public_modulus); } // protocol_flags :protocol extension cf. page 18 int protocol_flags = 0; /* currently 0 */ SshPacket1 packet = new SshPacket1(SSH_CMSG_SESSION_KEY); packet.putByte((byte) cipher_types); packet.putBytes(anti_spoofing_cookie); packet.putBytes(encrypted_session_key); packet.putInt32(protocol_flags); sendPacket1(packet); crypto = new SshCrypto(cipher_type, session_key); return null; } static public byte[] encrypteRSAPkcs1Twice( byte[] clearData, byte[] server_key_public_exponent, byte[] server_key_public_modulus, byte[] host_key_public_exponent, byte[] host_key_public_modulus ) { // At each encryption step, a multiple-precision integer is constructed // // the integer is interpreted as a sequence of bytes, msb first; // the number of bytes is the number of bytes needed to represent the // modulus. // // cf PKCS #1: RSA Encryption Standard. Available for anonymous ftp at // ftp.rsa.com. // The sequence of byte is as follows: // The most significant byte is zero. // The next byte contains the value 2 (stands for public-key encrypted // data) // Then, there are non zero random bytes to fill any unused space // a zero byte, // and the data to be encrypted byte[] EncryptionBlock; //what will be encrypted int offset = 0; EncryptionBlock = new byte[server_key_public_modulus.length]; EncryptionBlock[0] = 0; EncryptionBlock[1] = 2; offset = 2; for ( int i = 2; i < ( EncryptionBlock.length - clearData.length - 1 ); i++ ) EncryptionBlock[offset++] = getNotZeroRandomByte(); EncryptionBlock[offset++] = 0; for ( int i = 0; i < clearData.length; i++ ) EncryptionBlock[offset++] = clearData[i]; //EncryptionBlock can be encrypted now ! BigInteger m, e, message; byte[] messageByte; m = new BigInteger( server_key_public_modulus ); e = new BigInteger( server_key_public_exponent ); message = new BigInteger( EncryptionBlock ); // byte[] messageByteOld1 = message.toByteArray(); message = message.modPow( e, m ); //RSA Encryption !! byte[] messageByteTemp = message.toByteArray(); //messageByte holds the // encypted data. //there should be no zeroes a the begining but we have to fix it (JDK // bug !!) messageByte = new byte[server_key_public_modulus.length]; int tempOffset = 0; while ( messageByteTemp[tempOffset] == 0 ) tempOffset++; for ( int i = messageByte.length - messageByteTemp.length + tempOffset; i < messageByte.length; i++ ) messageByte[i] = messageByteTemp[tempOffset++]; // we can't check that the crypted message is OK : no way to decrypt :-( //according to the ssh source !!!!! Not well explained in the // protocol!!! clearData = messageByte; //SECOND ROUND !! offset = 0; EncryptionBlock = new byte[host_key_public_modulus.length]; EncryptionBlock[0] = 0; EncryptionBlock[1] = 2; offset = 2; for ( int i = 2; i < ( EncryptionBlock.length - clearData.length - 1 ); i++ ) EncryptionBlock[offset++] = getNotZeroRandomByte(); //random // !=0 EncryptionBlock[offset++] = 0; for ( int i = 0; i < clearData.length; i++ ) EncryptionBlock[offset++] = clearData[i]; //EncryptionBlock can be encrypted now ! m = new BigInteger( host_key_public_modulus ); e = new BigInteger( host_key_public_exponent ); message = new BigInteger( EncryptionBlock ); message = message.modPow( e, m ); messageByteTemp = message.toByteArray(); //messageByte holds the // encypted data. //there should be no zeroes a the begining but we have to fix it (JDK // bug !!) messageByte = new byte[host_key_public_modulus.length]; tempOffset = 0; while ( messageByteTemp[tempOffset] == 0 ) tempOffset++; for ( int i = messageByte.length - messageByteTemp.length + tempOffset; i < messageByte.length; i++ ) messageByte[i] = messageByteTemp[tempOffset++]; //Second encrypted key : encrypted_session_key //mp-int byte[] encrypted_session_key = new byte[host_key_public_modulus.length + 2]; //encrypted_session_key // is a // mp-int // !!! //the lengh of the mp-int. encrypted_session_key[1] = (byte) ( ( 8 * host_key_public_modulus.length ) & 0xff ); encrypted_session_key[0] = (byte) ( ( ( 8 * host_key_public_modulus.length ) >> 8 ) & 0xff ); //the mp-int for ( int i = 0; i < host_key_public_modulus.length; i++ ) encrypted_session_key[i + 2] = messageByte[i]; return encrypted_session_key; } static public byte[] addArrayOfBytes( byte[] a, byte[] b ) { if ( a == null ) return b; if ( b == null ) return a; byte[] temp = new byte[a.length + b.length]; for ( int i = 0; i < a.length; i++ ) temp[i] = a[i]; for ( int i = 0; i < b.length; i++ ) temp[i + a.length] = b[i]; return temp; } static public byte[] XORArrayOfBytes( byte[] a, byte[] b ) { if ( a == null ) return null; if ( b == null ) return null; if ( a.length != b.length ) return null; byte[] result = new byte[a.length]; for ( int i = 0; i < result.length; i++ ) result[i] = (byte) ( ( ( a[i] & 0xff ) ^ ( b[i] & 0xff ) ) & 0xff );// ^ // xor // operator return result; } private boolean hasCipher(String cipherName) { return (Cipher.getInstance(cipherName) != null); } /** * SSH_CMSG_USER string user login name on server */ private String Send_SSH_CMSG_USER() throws IOException { // if (debug > 0) System.err.println("Send_SSH_CMSG_USER(" + login + // ")"); SshPacket1 p = new SshPacket1(SSH_CMSG_USER); p.putString(login); sendPacket1(p); return ""; } /** * Send_SSH_CMSG_AUTH_PASSWORD string user password */ private String Send_SSH_CMSG_AUTH_PASSWORD() throws IOException { SshPacket1 p = new SshPacket1(SSH_CMSG_AUTH_PASSWORD); p.putString(password); sendPacket1(p); return ""; } /** * Send_SSH_CMSG_EXEC_SHELL (no arguments) Starts a shell (command * interpreter), and enters interactive session mode. */ private String Send_SSH_CMSG_EXEC_SHELL() throws IOException { SshPacket1 packet = new SshPacket1(SSH_CMSG_EXEC_SHELL); sendPacket1(packet); return ""; } /** * Send_SSH_CMSG_STDIN_DATA * */ private String Send_SSH_CMSG_STDIN_DATA(String str) throws IOException { SshPacket1 packet = new SshPacket1(SSH_CMSG_STDIN_DATA); packet.putString(str); sendPacket1(packet); return ""; } /** * Send_SSH_CMSG_REQUEST_PTY string TERM environment variable value (e.g. * vt100) 32-bit int terminal height, rows (e.g., 24) 32-bit int terminal * width, columns (e.g., 80) 32-bit int terminal width, pixels (0 if no * graphics) (e.g., 480) */ private String Send_SSH_CMSG_REQUEST_PTY() throws IOException { SshPacket1 p = new SshPacket1(SSH_CMSG_REQUEST_PTY); p.putString(getTerminalID()); p.putInt32(getTerminalHeight()); // Int32 rows p.putInt32(getTerminalWidth()); // Int32 columns p.putInt32(0); // Int32 x pixels p.putInt32(0); // Int32 y pixels p.putByte((byte) 0); // Int8 terminal modes sendPacket1(p); return ""; } private String Send_SSH_CMSG_EXIT_CONFIRMATION() throws IOException { SshPacket1 packet = new SshPacket1(SSH_CMSG_EXIT_CONFIRMATION); sendPacket1(packet); return ""; } //#endif /** * Send_SSH_NOOP (no arguments) Sends a NOOP packet to keep the connection * alive. */ public String Send_SSH_NOOP() throws IOException { // KARL The specification states that this packet is never sent, however // the OpenSSL source // for keep alives indicates that SSH_MSG_IGNORE (the alternative) // crashes some servers and // advocates SSH_MSG_NONE instead. /* OpenSSL now seems to not like SSH_MSG_NONE http://www.xk72.com/phpBB2/viewtopic.php?t=346 */ //#ifndef nossh1 if (useprotocol == 1) { SshPacket1 packet = new SshPacket1(SSH_MSG_IGNORE); sendPacket1(packet); } //#endif //#ifdef ssh2 if (useprotocol == 2) { SshPacket2 packet = new SshPacket2(SSH2_MSG_IGNORE); packet.putString(""); sendPacket2(packet); } //#endif return ""; } protected String getTerminalID() { return sshSession.getTerminalID(); } protected int getTerminalHeight() { return sshSession.getTerminalHeight(); } protected int getTerminalWidth() { return sshSession.getTerminalWidth(); } static public byte getNotZeroRandomByte() { java.util.Date date = new java.util.Date(); String randomString = String.valueOf( SshIO.rnd.nextLong() * date.getTime() ); // RADEK // date.GetTime() // * // Math.random() byte[] randomBytes = md5.digest( randomString.getBytes() ); int i = 0; while ( i < 20 ) { byte b = 0; if ( i < randomBytes.length ) b = randomBytes[i]; if ( b != 0 ) return b; i++; } return getNotZeroRandomByte(); } }