123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622 |
- package de.tudarmstadt.informatik.hostage.protocol;
- import java.io.IOException;
- import java.math.BigInteger;
- import java.nio.ByteBuffer;
- import java.security.SecureRandom;
- import java.util.ArrayList;
- import java.util.List;
- import de.tudarmstadt.informatik.hostage.commons.HelperUtils;
- import de.tudarmstadt.informatik.hostage.ssh.crypto.KeyMaterial;
- import de.tudarmstadt.informatik.hostage.ssh.crypto.PEMDecoder;
- import de.tudarmstadt.informatik.hostage.ssh.crypto.cipher.CBCMode;
- import de.tudarmstadt.informatik.hostage.ssh.crypto.cipher.DESede;
- import de.tudarmstadt.informatik.hostage.ssh.crypto.dh.DhExchange;
- import de.tudarmstadt.informatik.hostage.ssh.crypto.digest.MAC;
- import de.tudarmstadt.informatik.hostage.ssh.signature.DSAPrivateKey;
- import de.tudarmstadt.informatik.hostage.ssh.signature.DSASHA1Verify;
- import de.tudarmstadt.informatik.hostage.ssh.signature.DSASignature;
- import de.tudarmstadt.informatik.hostage.ssh.util.TypesReader;
- import de.tudarmstadt.informatik.hostage.ssh.util.TypesWriter;
- import de.tudarmstadt.informatik.hostage.wrapper.Packet;
- /**
- * SSH protocol.
- * Implementation of RFC documents 4250, 4251, 4252, 4253, 4254.
- * It can handle the following requests: Server Protocol, Key Exchange Init, Diffie-Hellman Key Exchange Init, New Keys,
- * Service Request, Connection Request, Channel Open Request, Channel Request.
- * @author Wulf Pfeiffer
- */
- public class SSH implements Protocol {
- /**
- * Represents the states of the protocol.
- */
- private enum STATE {
- NONE, SERVER_VERSION, CLIENT_VERSION, KEX_INIT, NEW_KEYS, USERAUTH, CONNECTION, CHANNEL, TERMINAL_CMD, TERMINAL_ENTER, CLOSED
- }
- /**
- * Denotes in which state the protocol is right now.
- */
- private STATE state = STATE.NONE;
- private boolean useEncryption = false;
- @Override
- public int getDefaultPort() {
- return 22;
- }
- @Override
- public TALK_FIRST whoTalksFirst() {
- return TALK_FIRST.SERVER;
- }
- @Override
- public List<Packet> processMessage(Packet requestPacket) {
- List<Packet> responsePackets = new ArrayList<Packet>();
- byte[] request = null;
- if (requestPacket != null) {
- request = requestPacket.getMessage();
- if (useEncryption) {
- request = decryptBytes(request);
- }
- }
- switch (state) {
- case NONE:
- responsePackets.add(new Packet(serverVersion + serverType + "\r\n"));
- state = STATE.SERVER_VERSION;
- break;
- case SERVER_VERSION:
- extractType(request);
- extractPayload(request);
- responsePackets.add(kexInit());
- state = STATE.CLIENT_VERSION;
- break;
- case CLIENT_VERSION:
- extractPubKey(request);
- responsePackets.add(dhKexReply());
- state = STATE.KEX_INIT;
- break;
- case KEX_INIT:
- responsePackets.add(newKeys());
- useEncryption = true;
- state = STATE.NEW_KEYS;
- break;
- case NEW_KEYS:
- responsePackets.add(serviceReply(request));
- state = STATE.USERAUTH;
- break;
- case USERAUTH:
- responsePackets.add(connectionReply(request));
- state = STATE.CONNECTION;
- break;
- case CONNECTION:
- responsePackets.add(channelOpenReply(request));
- state = STATE.CHANNEL;
- break;
- case CHANNEL:
- responsePackets.add(channelSuccessReply(request));
- responsePackets.add(terminalPrefix());
- state = STATE.TERMINAL_CMD;
- break;
- case TERMINAL_CMD:
- responsePackets.add(terminalReply(request));
- break;
- case CLOSED:
- break;
- default:
- state = STATE.CLOSED;
- break;
- }
- return responsePackets;
- }
- @Override
- public boolean isClosed() {
- return (state == STATE.CLOSED);
- }
- @Override
- public boolean isSecure() {
- return false;
- }
- @Override
- public Class<byte[]> getType() {
- return byte[].class;
- }
- @Override
- public String toString() {
- return "SSH";
- }
- /**
- * Wraps the packets with packet length and padding.
- *
- * @param response
- * content that is wrapped.
- * @return wrapped packet.
- */
- private Packet wrapPacket(byte[] response) {
- int packetLength = 5 + response.length; // 4 byte packet length, 1 byte padding length, payload length
- int paddingLengthCBS = cipherBlockSize
- - (packetLength % cipherBlockSize);
- int paddingLength8 = 8 - (packetLength % 8);
- int paddingLength = paddingLengthCBS > paddingLength8 ? paddingLengthCBS
- : paddingLength8;
- if (paddingLength < 4)
- paddingLength += cipherBlockSize;
- packetLength = packetLength + paddingLength - 4; // add padding string length to packet length
- byte[] packetLen = ByteBuffer.allocate(4).putInt(packetLength).array();
- byte[] paddingLen = { (byte) paddingLength };
- byte[] paddingString = HelperUtils.randomBytes(paddingLength);
- byte[] wrappedResponse = HelperUtils.concat(packetLen, paddingLen, response,
- paddingString);
- if (useEncryption) {
- byte[] mac = createMac(wrappedResponse);
- byte[] responseEnc = encryptBytes(wrappedResponse);
- wrappedResponse = HelperUtils.concat(responseEnc, mac);
- }
- packetNumber++;
- return new Packet(wrappedResponse);
- }
- /**
- * Encrypts a request with triple DES.
- *
- * @param request
- * that is encrypted.
- * @return encrypted request.
- */
- private byte[] encryptBytes(byte[] bytes) {
- byte[] responseEncrypted = new byte[bytes.length];
- for (int i = 0; i < bytes.length; i += 8) {
- cbcEncryption.transformBlock(bytes, i, responseEncrypted, i);
- }
- return responseEncrypted;
- }
- /**
- * Decrypts a request with triple DES.
- *
- * @param request
- * that is decrypted.
- * @return decrypted request.
- */
- private byte[] decryptBytes(byte[] request) {
- byte[] decryptedRequest = new byte[request.length
- - ((request.length % 8 == 0) ? 0 : 20)];
- for (int i = 0; i < decryptedRequest.length; i += 8) { // -12 wegen MAC
- cbcDecryption.transformBlock(request, i, decryptedRequest, i);
- }
- return decryptedRequest;
- }
- /**
- * Creates the SHA1 Mac with the given bytes.
- *
- * @param bytes
- * that are used for the Mac.
- * @return Mac.
- */
- private byte[] createMac(byte[] bytes) {
- byte[] mac = new byte[20];
- macEncryption.initMac(packetNumber);
- macEncryption.update(bytes, 0, bytes.length);
- macEncryption.getMac(mac, 0);
- return mac;
- }
- /**
- * Builds the Kex Init packet that contains all the allowed algorithms by
- * the server.
- *
- * @return Kex Init packet.
- */
- private Packet kexInit() {
- TypesWriter tw = new TypesWriter();
- tw.writeByte(0x14);
- tw.writeBytes(HelperUtils.randomBytes(16)); // cookie
- tw.writeString(KEX_ALG);
- tw.writeString(SERVER_ALG);
- tw.writeString(ENCRYPT_ALG_C);
- tw.writeString(ENCRYPT_ALG_S);
- tw.writeString(MAC_ALG_C);
- tw.writeString(MAC_ALG_S);
- tw.writeString(COMP_ALG_C);
- tw.writeString(COMP_ALG_S);
- tw.writeBytes(new byte[] { 0x00, 0x00, 0x00, 0x00 }); // language client to server
- tw.writeBytes(new byte[] { 0x00, 0x00, 0x00, 0x00 }); // language server to client
- tw.writeByte(0x00); // no guess from server
- tw.writeBytes(new byte[] { 0x00, 0x00, 0x00, 0x00 }); // reserved
- byte[] response = tw.getBytes();
- I_S = response;
- return wrapPacket(response);
- }
- /**
- * Builds the Diffie-Hellman Kex Reply, containing the host key,f and the
- * signature.
- *
- * @return Diffie-Hellman Kex Reply packet.
- */
- private Packet dhKexReply() {
- byte[] response = null;
- try {
- DhExchange dhx = new DhExchange();
- dhx.serverInit(1, random);
- dhx.setE(new BigInteger(e));
- f = dhx.getF();
- DSAPrivateKey dsa = (DSAPrivateKey) PEMDecoder
- .decode(dsaPem, null);
- K_S = DSASHA1Verify.encodeSSHDSAPublicKey(dsa.getPublicKey());
- h = dhx.calculateH(V_C, V_S, I_C, I_S, K_S);
- k = dhx.getK();
- DSASignature ds = DSASHA1Verify.generateSignature(h, dsa, random);
- signature = DSASHA1Verify.encodeSSHDSASignature(ds);
- TypesWriter tw = new TypesWriter();
- tw.writeByte(31);
- tw.writeString(K_S, 0, K_S.length);
- tw.writeMPInt(f);
- tw.writeString(signature, 0, signature.length);
- response = tw.getBytes();
-
- // init for decryption and encryption
- KeyMaterial km = KeyMaterial.create("SHA1", h, k, h, 24, 8, 20, 24,
- 8, 20); // alg, h, k, keylength, blocklength, maclength, keylength, blocklength, maclength
- desEncryption = new DESede();
- desDecryption = new DESede();
- desEncryption.init(true, km.enc_key_server_to_client);
- desDecryption.init(false, km.enc_key_client_to_server);
- cbcEncryption = new CBCMode(desEncryption, km.initial_iv_server_to_client, true);
- cbcDecryption = new CBCMode(desDecryption, km.initial_iv_client_to_server, false);
- macEncryption = new MAC("hmac-sha1", km.integrity_key_server_to_client);
- } catch (Exception e) {
- e.printStackTrace();
- }
- return wrapPacket(response);
- }
- /**
- * New Keys response.
- *
- * @return New Keys response.
- */
- private Packet newKeys() {
- byte[] msgCode = { 0x15 };
- return wrapPacket(msgCode);
- }
- /**
- * Service ssh-userauth reply.
- *
- * @param request
- * from the client.
- * @return Service reply.
- */
- private Packet serviceReply(byte[] request) {
- byte[] message;
- if (request[5] == 0x15) { // if newkeys request is included in the same packet
- message = new byte[request.length - 16]; // remove it
- System.arraycopy(request, 16, message, 0, request.length - 16);
- } else {
- message = request;
- }
- if (message[5] != 0x05
- && !(HelperUtils.byteToStr(message).contains("ssh-userauth"))) {
- return disconnectReply(7); // disconnect because its not servicerequest ssh-userauth
- }
- TypesWriter tw = new TypesWriter();
- tw.writeByte(0x06);
- tw.writeString("ssh-userauth");
- return wrapPacket(tw.getBytes());
- }
- /**
- * Userauth ssh-connection reply.
- *
- * @param request
- * from the client.
- * @return ssh-connection reply.
- */
- private Packet connectionReply(byte[] request) {
- if (request[5] != 0x32
- && !(HelperUtils.byteToStr(request).contains("ssh-connection"))) {
- return disconnectReply(14);// disconnect because its not servicerequest ssh-connect
- }
- try {
- TypesReader tr = new TypesReader(request, 6);
- userName = tr.readString();
- terminalPrefix = "[" + userName + "@" + serverName + "]$";
- } catch (IOException e) {
- e.printStackTrace();
- }
- byte[] msgcode = { 0x34 };
- return wrapPacket(msgcode);
- }
- /**
- * Channel Open Reply.
- *
- * @param request
- * from client.
- * @return Channel Open Reply.
- */
- private Packet channelOpenReply(byte[] request) {
- if (!(HelperUtils.byteToStr(request).contains("session"))) {
- return disconnectReply(2); // if contains "session" ok else disc
- }
- TypesReader tr = new TypesReader(request, 6);
- TypesWriter tw = new TypesWriter();
- try {
- tr.readString();
- recipientChannel = tr.readUINT32();
- int senderChannel = recipientChannel;
- int initialWindowSize = tr.readUINT32();
- int maximumPacketSize = tr.readUINT32();
- tw.writeByte(0x5b); // msgcode
- tw.writeUINT32(recipientChannel);
- tw.writeUINT32(senderChannel);
- tw.writeUINT32(initialWindowSize);
- tw.writeUINT32(maximumPacketSize);
- } catch (IOException e) {
- e.printStackTrace();
- }
- return wrapPacket(tw.getBytes());
- }
- /**
- * Channel Success Reply.
- *
- * @param request
- * from client.
- * @return Channel Success Reply.
- */
- private Packet channelSuccessReply(byte[] request) {
- if (!(HelperUtils.byteToStr(request)).contains("pty-req")) {
- return disconnectReply(2);
- }
- TypesWriter tw = new TypesWriter();
- tw.writeByte(0x63); // msgcode
- tw.writeUINT32(recipientChannel);
- return wrapPacket(tw.getBytes());
- }
- /**
- * Returns the terminal prefix for the client.
- *
- * @return terminal prefix.
- */
- private Packet terminalPrefix() {
- TypesWriter tw = new TypesWriter();
- tw.writeByte(0x5e);
- tw.writeUINT32(recipientChannel);
- tw.writeString(terminalPrefix);
-
- return wrapPacket(tw.getBytes());
- }
- /**
- * Computes the reply for the client input.
- *
- * @param request
- * client input.
- * @return input reply.
- */
- private Packet terminalReply(byte[] request) {
- TypesReader tr = new TypesReader(request, 6);
- String messsage = "";
- System.out.println(HelperUtils.bytesToHexString(request));
- try {
- tr.readUINT32();
- messsage = tr.readString();
- System.out.println(messsage);
- if (messsage.contains("\r")) {
- System.out.println("hi");
- if (command.toString().contains("exit")) {
- state = STATE.CLOSED; // ugly style
- return disconnectReply(2);
- }
- messsage = "\r\nbash: " + command + " :command not found\r\n"
- + terminalPrefix;
- command = new StringBuffer();
- } else if (messsage.contains(new String(new char[] { '\u007F' }))
- && command.length() > 0) {
- command = command.delete(command.length() - 1, command.length());
- } else {
- command.append(messsage);
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- TypesWriter tw = new TypesWriter();
- tw.writeByte(0x5e); // msgcode
- tw.writeUINT32(recipientChannel);
- tw.writeString(messsage);
- return wrapPacket(tw.getBytes());
- }
- /**
- * Disconnect Reply using the given number as reason code.
- *
- * @param reasonCode
- * for disconnect reply. Must be between 1 and 15, default is 2.
- * @return Disconnect Reply.
- */
- private Packet disconnectReply(int reasonCode) {
- TypesWriter tw = new TypesWriter();
- tw.writeByte(0x01);
- switch (reasonCode) {
- case 1:
- tw.writeUINT32(1);
- tw.writeString("SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT");
- break;
- case 7:
- tw.writeUINT32(7);
- tw.writeString("SSH_DISCONNECT_SERVICE_NOT_AVAILABLE");
- break;
- case 14:
- tw.writeUINT32(14);
- tw.writeString("SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE");
- break;
- default:
- tw.writeUINT32(2);
- tw.writeString("SSH_DISCONNECT_PROTOCOL_ERROR");
- break;
- }
- return wrapPacket(tw.getBytes());
- }
- /**
- * Extracts the type of the client
- *
- * @param request
- * containing the clients type
- */
- private void extractType(byte[] request) {
- int length = 0;
- for (int i = 0; i < request.length; i++, length++) {
- if (request[i] == 0x0d)
- break; // find the end of the type: '\r'
- }
- V_C = new byte[length];
- System.arraycopy(request, 0, V_C, 0, length);
- }
- /**
- * Extracts the payload of a packet and writes it in I_C.
- *
- * @param request
- * packet of which the payload is extracted.
- */
- private void extractPayload(byte[] request) {
- int position = 0;
- if (request[5] != 0x14) {
- position = 1;
- for (int i = 0; i < request.length; i++, position++) {
- if (request[i] == 0xa)
- break;
- }
- }
- int packetLength = byteToInt(new byte[] { request[position], request[1 + position], request[2 + position], request[3 + position] });
- int paddingLength = byteToInt(new byte[] { request[4 + position] });
- byte[] payload = new byte[packetLength - paddingLength - 1];
- for (int i = 5; i < packetLength - paddingLength - 1; i++) {
- payload[i - 5] = request[i + position];
- }
- I_C = payload;
- }
- /**
- * Extracts the public key from the DH Kex Request
- *
- * @param request
- * containing the clients public key
- */
- private void extractPubKey(byte[] request) {
- e = new byte[byteToInt(new byte[] { request[6], request[7], request[8],
- request[9] })];
- for (int i = 0; i < e.length; i++) {
- e[i] = request[i + 10];
- }
- }
- /**
- * Converts a byte[] to int
- *
- * @param bytes
- * that are converted
- * @return converted byte[] as int
- */
- private static int byteToInt(byte[] bytes) {
- int convertedInteger = 0;
- for (int i = 0; i < bytes.length; i++) {
- convertedInteger <<= 8;
- convertedInteger |= bytes[i] & 0xFF;
- }
- return convertedInteger;
- }
- // version stuff
- private static String[][][] possibleSshTypes = {
- { { "3." }, { "4", "5", "6", "7", "8", "9" } },
- { { "4." }, { "0", "1", "2", "3", "4", "5", "6", "7", "9" } },
- { { "5." }, { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" } },
- { { "6." }, { "0", "1", "2", "3", "4" } } };
- private static String initSshType() {
- SecureRandom rnd = new SecureRandom();
- int majorVersion = rnd.nextInt(possibleSshTypes.length);
- return "OpenSSH_"
- + possibleSshTypes[majorVersion][0][0]
- + possibleSshTypes[majorVersion][1][rnd
- .nextInt(possibleSshTypes[majorVersion][1].length)];
- }
- // server infos
- private static String serverVersion = "SSH-2.0-";
- private static String serverType = initSshType();
- private static String serverName = HelperUtils.getRandomString(16, false);
- private int packetNumber = 0;
- private int recipientChannel;
- private String userName;
- private String terminalPrefix;
- private StringBuffer command = new StringBuffer();
- private SecureRandom random = new SecureRandom();
- // SSH Parameters for Kex etc.
- private byte[] V_S = (serverVersion + serverType).getBytes();
- private byte[] V_C;
- private byte[] I_S;
- private byte[] I_C;
- private byte[] e;
- private BigInteger f;
- private byte[] h;
- private BigInteger k;
- private byte[] K_S;
- private byte[] signature;
- // allowed algorithms for kexinit
- private static final String KEX_ALG = "diffie-hellman-group1-sha1";
- private static final String SERVER_ALG = "ssh-dss";
- private static final String ENCRYPT_ALG_C = "3des-cbc";
- private static final String ENCRYPT_ALG_S = "3des-cbc";
- private static final String MAC_ALG_C = "hmac-sha1";
- private static final String MAC_ALG_S = "hmac-sha1";
- private static final String COMP_ALG_C = "none";
- private static final String COMP_ALG_S = "none";
- private int cipherBlockSize = 16;
- // for en- and decryption
- private DESede desEncryption;
- private DESede desDecryption;
- private CBCMode cbcEncryption;
- private CBCMode cbcDecryption;
- private MAC macEncryption;
- // private MAC macDec;
- // dsa private key
- private final char[] dsaPem = ("-----BEGIN DSA PRIVATE KEY-----\n"
- + "MIIBugIBAAKBgQCDZ9R2vfCPwjv5vKF1igIv9drrZ7G0dhMkGT9AZTjgI34Qm4w0\n"
- + "0iWeCqO7SmqiaMIjbRIm91MeDed4ObAq4sAkqRE/2P4mTbzFx5KhEczRRiDoqQBX\n"
- + "xYa0yWKJpeZ94SGM6DEPuBTxKo0T4uMjbq2FzHL2FXT1/WoNCmRU6gFSiwIVAMK4\n"
- + "Epz3JiwDUbkSpLOjIqtEhJmVAoGAL6zlXRI4Q8iwvSDh0vDf1j9a5Aaaq+93LTjK\n"
- + "SwL4nvUWBl2Aa0vqu05ZS5rOD1I+/naLMg0fNgFJRhA03sl+12MI3a2HXJWXRSdj\n"
- + "m1Vq9cUXqiYrX6+iGfEaA/y9UO4ZPF6if6eLypXB8VuqjtjDCiMMsM6+qQki7L71\n"
- + "yN4M75ICgYAcFXUhN2zRug3JvwmGxW8gMgHquSiBnbx1582KGh2B/ukE/kOrbKYD\n"
- + "HUkBzolcm4x1Odq5apowlriFxY6zMQP615plIK4x9NaU6dvc/HoTkjzT5EYSMN39\n"
- + "eAGufJ0jrtIpKL4lP8o8yrAHfmbR7bjecWc0viTH0+OWlyVsex/bZAIUEKn310Li\n"
- + "v62Zs4hlDvhwvx8MQ+A=\n" + "-----END DSA PRIVATE KEY-----")
- .toCharArray();
- }
|