SSH.java 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  1. package de.tudarmstadt.informatik.hostage.protocol;
  2. import java.io.IOException;
  3. import java.math.BigInteger;
  4. import java.nio.ByteBuffer;
  5. import java.security.SecureRandom;
  6. import java.util.ArrayList;
  7. import java.util.List;
  8. import de.tudarmstadt.informatik.hostage.commons.HelperUtils;
  9. import de.tudarmstadt.informatik.hostage.ssh.crypto.KeyMaterial;
  10. import de.tudarmstadt.informatik.hostage.ssh.crypto.PEMDecoder;
  11. import de.tudarmstadt.informatik.hostage.ssh.crypto.cipher.CBCMode;
  12. import de.tudarmstadt.informatik.hostage.ssh.crypto.cipher.DESede;
  13. import de.tudarmstadt.informatik.hostage.ssh.crypto.dh.DhExchange;
  14. import de.tudarmstadt.informatik.hostage.ssh.crypto.digest.MAC;
  15. import de.tudarmstadt.informatik.hostage.ssh.signature.DSAPrivateKey;
  16. import de.tudarmstadt.informatik.hostage.ssh.signature.DSASHA1Verify;
  17. import de.tudarmstadt.informatik.hostage.ssh.signature.DSASignature;
  18. import de.tudarmstadt.informatik.hostage.ssh.util.TypesReader;
  19. import de.tudarmstadt.informatik.hostage.ssh.util.TypesWriter;
  20. import de.tudarmstadt.informatik.hostage.wrapper.Packet;
  21. /**
  22. * SSH protocol.
  23. * @author Wulf Pfeiffer
  24. */
  25. public class SSH implements Protocol {
  26. /**
  27. * Represents the states of the protocol.
  28. */
  29. private enum STATE {
  30. NONE,
  31. SERVER_VERSION,
  32. CLIENT_VERSION,
  33. KEX_INIT,
  34. NEW_KEYS,
  35. USERAUTH,
  36. CONNECTION,
  37. CHANNEL,
  38. TERMINAL_CMD,
  39. TERMINAL_ENTER,
  40. CLOSED
  41. }
  42. /**
  43. * Denotes in which state the protocol is right now.
  44. */
  45. private STATE state = STATE.NONE;
  46. private boolean useEncryption = false;
  47. @Override
  48. public int getPort() {
  49. return 22;
  50. }
  51. @Override
  52. public TALK_FIRST whoTalksFirst() {
  53. return TALK_FIRST.SERVER;
  54. }
  55. @Override
  56. public List<Packet> processMessage(Packet packet) {
  57. List<Packet> response = new ArrayList<Packet>();
  58. byte[] request = null;
  59. if(packet != null) {
  60. request = packet.getMessage();
  61. if(useEncryption) {
  62. request = decryptBytes(request);
  63. }
  64. }
  65. switch(state) {
  66. case NONE:
  67. response.add(new Packet(serverVersion + serverType + "\r\n"));
  68. state = STATE.SERVER_VERSION;
  69. break;
  70. case SERVER_VERSION:
  71. extractType(request);
  72. extractPayload(request);
  73. response.add(kexInit());
  74. state = STATE.CLIENT_VERSION;
  75. break;
  76. case CLIENT_VERSION:
  77. extractPubKey(request);
  78. response.add(dhKexReply());
  79. state = STATE.KEX_INIT;
  80. break;
  81. case KEX_INIT:
  82. response.add(newKeys());
  83. useEncryption = true;
  84. state = STATE.NEW_KEYS;
  85. break;
  86. case NEW_KEYS:
  87. response.add(serviceReply(request));
  88. state = STATE.USERAUTH;
  89. break;
  90. case USERAUTH:
  91. response.add(connectionReply(request));
  92. state = STATE.CONNECTION;
  93. break;
  94. case CONNECTION:
  95. response.add(channelOpenReply(request));
  96. state = STATE.CHANNEL;
  97. break;
  98. case CHANNEL:
  99. response.add(channelSuccessReply(request));
  100. response.add(terminalPrefix());
  101. state = STATE.TERMINAL_CMD;
  102. break;
  103. case TERMINAL_CMD:
  104. response.add(terminalReply(request));
  105. break;
  106. case CLOSED:
  107. break;
  108. default:
  109. state = STATE.CLOSED;
  110. break;
  111. }
  112. return response;
  113. }
  114. @Override
  115. public boolean isClosed() {
  116. return (state == STATE.CLOSED);
  117. }
  118. @Override
  119. public boolean isSecure() {
  120. return false;
  121. }
  122. @Override
  123. public Class<byte[]> getType() {
  124. return byte[].class;
  125. }
  126. @Override
  127. public String toString() {
  128. return "SSH";
  129. }
  130. /**
  131. * Wraps the packets with packet length and padding.
  132. * @param packet content that is wrapped.
  133. * @return wrapped packet.
  134. */
  135. private Packet wrapPacket(byte[] packet) {
  136. int packetLength = 5 + packet.length; //4 byte packet length, 1 byte padding length, payload length
  137. int paddingLengthCBS = cipherBlockSize - (packetLength % cipherBlockSize);
  138. int paddingLength8 = 8 - (packetLength % 8);
  139. int paddingLength = paddingLengthCBS > paddingLength8 ? paddingLengthCBS : paddingLength8;
  140. if(paddingLength < 4) paddingLength += cipherBlockSize;
  141. packetLength = packetLength + paddingLength - 4; //add padding string length to packet length
  142. byte[] packetLen = ByteBuffer.allocate(4).putInt(packetLength).array();
  143. byte[] paddingLen = {(byte) paddingLength};
  144. byte[] paddingString = HelperUtils.randomBytes(paddingLength);
  145. byte[] response = HelperUtils.concat(packetLen, paddingLen, packet, paddingString);
  146. if(useEncryption) {
  147. byte[] mac = getMac(response);
  148. byte[] responseEnc = encryptBytes(response);
  149. response = HelperUtils.concat(responseEnc,mac);
  150. }
  151. packetNumber++;
  152. return new Packet(response);
  153. }
  154. /**
  155. * Encrypts a request with triple DES.
  156. * @param request that is encrypted.
  157. * @return encrypted request.
  158. */
  159. private byte[] encryptBytes(byte[] bytes) {
  160. byte[] responseEnc = new byte[bytes.length];
  161. for(int i = 0; i < bytes.length; i+=8) {
  162. cbcEnc.transformBlock(bytes, i, responseEnc, i);
  163. }
  164. return responseEnc;
  165. }
  166. /**
  167. * Decrypts a request with triple DES.
  168. * @param request that is decrypted.
  169. * @return decrypted request.
  170. */
  171. private byte[] decryptBytes(byte[] request) {
  172. byte[] message = new byte[request.length - ((request.length % 8 == 0) ? 0 : 20)];
  173. for(int i = 0; i < message.length; i += 8) { // -12 wegen MAC
  174. cbcDec.transformBlock(request, i, message, i);
  175. }
  176. return message;
  177. }
  178. /**
  179. * Creates the SHA1 Mac with the given bytes.
  180. * @param bytes that are used for the Mac.
  181. * @return Mac.
  182. */
  183. private byte[] getMac(byte[] bytes) {
  184. byte[] mac = new byte[20];
  185. macEnc.initMac(packetNumber);
  186. macEnc.update(bytes, 0, bytes.length);
  187. macEnc.getMac(mac, 0);
  188. return mac;
  189. }
  190. /**
  191. * Builds the Kex Init packet that contains all the allowed algorithms by the server.
  192. * @return Kex Init packet.
  193. */
  194. private Packet kexInit() {
  195. TypesWriter tw = new TypesWriter();
  196. tw.writeByte(0x14);
  197. tw.writeBytes(HelperUtils.randomBytes(16)); //cookie
  198. tw.writeString(kex_alg);
  199. tw.writeString(server_alg);
  200. tw.writeString(encrypt_alg_c);
  201. tw.writeString(encrypt_alg_s);
  202. tw.writeString(mac_alg_c);
  203. tw.writeString(mac_alg_s);
  204. tw.writeString(comp_alg_c);
  205. tw.writeString(comp_alg_s);
  206. tw.writeBytes(new byte[]{0x00, 0x00, 0x00, 0x00}); //language client to server
  207. tw.writeBytes(new byte[]{0x00, 0x00, 0x00, 0x00}); //language server to client
  208. tw.writeByte(0x00); //no guess from server
  209. tw.writeBytes(new byte[]{0x00, 0x00, 0x00, 0x00}); //reserved
  210. byte[] response = tw.getBytes();
  211. I_S = response;
  212. return wrapPacket(response);
  213. }
  214. /**
  215. * Builds the Diffie-Hellman Kex Reply, containing the host key,f and the signature.
  216. * @return Diffie-Hellman Kex Reply packet.
  217. */
  218. private Packet dhKexReply() {
  219. byte[] response = null;
  220. try {
  221. SecureRandom rnd = new SecureRandom();
  222. DhExchange dhx = new DhExchange();
  223. dhx.serverInit(1, rnd);
  224. dhx.setE(new BigInteger(e));
  225. f = dhx.getF();
  226. DSAPrivateKey dsa = (DSAPrivateKey) PEMDecoder.decode(dsa_pem, null);
  227. K_S = DSASHA1Verify.encodeSSHDSAPublicKey(dsa.getPublicKey());
  228. h = dhx.calculateH(V_C, V_S, I_C, I_S, K_S);
  229. k = dhx.getK();
  230. DSASignature ds = DSASHA1Verify.generateSignature(h, dsa, rnd);
  231. sig = DSASHA1Verify.encodeSSHDSASignature(ds);
  232. TypesWriter tw = new TypesWriter();
  233. tw.writeByte(31);
  234. tw.writeString(K_S, 0, K_S.length);
  235. tw.writeMPInt(f);
  236. tw.writeString(sig, 0, sig.length);
  237. response = tw.getBytes();
  238. //init for decryption and encryption
  239. KeyMaterial km = KeyMaterial.create("SHA1", h, k, h, 24, 8, 20, 24, 8, 20); // alg, h, k, keylength, blocklength, maclength, keylength, blocklength, maclength
  240. enc = new DESede();
  241. dec = new DESede();
  242. enc.init(true, km.enc_key_server_to_client);
  243. dec.init(false, km.enc_key_client_to_server);
  244. cbcEnc = new CBCMode(enc, km.initial_iv_server_to_client, true);
  245. cbcDec = new CBCMode(dec, km.initial_iv_client_to_server, false);
  246. macEnc = new MAC("hmac-sha1", km.integrity_key_server_to_client);
  247. macDec = new MAC("hmac-sha1", km.integrity_key_client_to_server);
  248. } catch (Exception e) {
  249. e.printStackTrace();
  250. }
  251. return wrapPacket(response);
  252. }
  253. /**
  254. * New Keys response.
  255. * @return New Keys response.
  256. */
  257. private Packet newKeys() {
  258. byte[] msgCode = {0x15};
  259. return wrapPacket(msgCode);
  260. }
  261. /**
  262. * Service ssh-userauth reply.
  263. * @param request from the client.
  264. * @return Service reply.
  265. */
  266. private Packet serviceReply(byte[] request) {
  267. byte[] message;
  268. if(request[5] == 0x15) { //if newkeys request is included in the same packet
  269. message = new byte[request.length - 16]; //remove it
  270. System.arraycopy(request, 16, message, 0, request.length-16);
  271. } else {
  272. message = request;
  273. }
  274. if (message[5] != 0x05 && !(HelperUtils.byteToStr(message).contains("ssh-userauth"))) {
  275. return disconnectReply(7); // disconnect because its not servicerequest ssh-userauth
  276. }
  277. TypesWriter tw = new TypesWriter();
  278. tw.writeByte(0x06);
  279. tw.writeString("ssh-userauth");
  280. return wrapPacket(tw.getBytes());
  281. }
  282. /**
  283. * Userauth ssh-connection reply.
  284. * @param request from the client.
  285. * @return ssh-connection reply.
  286. */
  287. private Packet connectionReply(byte[] request) {
  288. if (request[5] != 0x32 && !(HelperUtils.byteToStr(request).contains("ssh-connection"))) {
  289. return disconnectReply(14);// disconnect because its not servicerequest ssh-connect
  290. }
  291. try {
  292. TypesReader tr = new TypesReader(request, 6);
  293. userName = tr.readString();
  294. terminalPrefix = "[" + userName + "@" + serverName + "]$";
  295. } catch (IOException e) {
  296. e.printStackTrace();
  297. }
  298. byte[] msgcode = {0x34};
  299. return wrapPacket(msgcode);
  300. }
  301. /**
  302. * Channel Open Reply.
  303. * @param request from client.
  304. * @return Channel Open Reply.
  305. */
  306. private Packet channelOpenReply(byte[] request) {
  307. if (!(HelperUtils.byteToStr(request).contains("session"))) {
  308. return disconnectReply(2); // if contains "session" ok else disc
  309. }
  310. TypesReader tr = new TypesReader(request, 6);
  311. TypesWriter tw = new TypesWriter();
  312. try {
  313. tr.readString();
  314. recipientChannel = tr.readUINT32();
  315. int senderChannel = recipientChannel;
  316. int initialWindowSize = tr.readUINT32();
  317. int maximumPacketSize = tr.readUINT32();
  318. tw.writeByte(0x5b); //msgcode
  319. tw.writeUINT32(recipientChannel);
  320. tw.writeUINT32(senderChannel);
  321. tw.writeUINT32(initialWindowSize);
  322. tw.writeUINT32(maximumPacketSize);
  323. } catch (IOException e) {
  324. e.printStackTrace();
  325. }
  326. return wrapPacket(tw.getBytes());
  327. }
  328. /**
  329. * Channel Success Reply.
  330. * @param request from client.
  331. * @return Channel Success Reply.
  332. */
  333. private Packet channelSuccessReply(byte[] request) {
  334. if(!(HelperUtils.byteToStr(request)).contains("pty-req")) {
  335. return disconnectReply(2);
  336. }
  337. TypesWriter tw = new TypesWriter();
  338. tw.writeByte(0x63); //msgcode
  339. tw.writeUINT32(recipientChannel);
  340. return wrapPacket(tw.getBytes());
  341. }
  342. /**
  343. * Returns the terminal prefix for the client.
  344. * @return terminal prefix.
  345. */
  346. private Packet terminalPrefix() {
  347. TypesWriter tw = new TypesWriter();
  348. tw.writeByte(0x5e);
  349. tw.writeUINT32(recipientChannel);
  350. tw.writeString(terminalPrefix);
  351. return wrapPacket(tw.getBytes());
  352. }
  353. /**
  354. * Computes the reply for the client input.
  355. * @param request client input.
  356. * @return input reply.
  357. */
  358. private Packet terminalReply(byte[] request) {
  359. //TODO
  360. TypesReader tr = new TypesReader(request, 6);
  361. String msg = "";
  362. System.out.println(HelperUtils.bytesToHexString(request));
  363. try {
  364. tr.readUINT32();
  365. msg = tr.readString();
  366. System.out.println(msg);
  367. if(msg.contains("\r")) {
  368. System.out.println("hi");
  369. if(cmd.toString().contains("exit")) {
  370. state = STATE.CLOSED; // ugly style
  371. return disconnectReply(2);
  372. }
  373. msg = "\r\nbash: " + cmd + " :command not found\r\n" + terminalPrefix;
  374. cmd = new StringBuffer();
  375. } else if(msg.contains(new String(new char[]{'\u007F'})) && cmd.length() > 0) {
  376. cmd = cmd.delete(cmd.length()-1, cmd.length());
  377. } else {
  378. cmd.append(msg);
  379. }
  380. } catch (IOException e) {
  381. e.printStackTrace();
  382. }
  383. TypesWriter tw = new TypesWriter();
  384. tw.writeByte(0x5e); //msgcode
  385. tw.writeUINT32(recipientChannel);
  386. tw.writeString(msg);
  387. return wrapPacket(tw.getBytes());
  388. }
  389. /**
  390. * Disconnect Reply using the given number as reason code.
  391. * @param reasonCode for disconnect reply. Must be between 1 and 15, default is 2.
  392. * @return Disconnect Reply.
  393. */
  394. private Packet disconnectReply(int reasonCode) {
  395. TypesWriter tw = new TypesWriter();
  396. tw.writeByte(0x01);
  397. switch (reasonCode) {
  398. case 1:
  399. tw.writeUINT32(1);
  400. tw.writeString("SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT");
  401. break;
  402. case 7:
  403. tw.writeUINT32(7);
  404. tw.writeString("SSH_DISCONNECT_SERVICE_NOT_AVAILABLE");
  405. break;
  406. case 14:
  407. tw.writeUINT32(14);
  408. tw.writeString("SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE");
  409. break;
  410. default:
  411. tw.writeUINT32(2);
  412. tw.writeString("SSH_DISCONNECT_PROTOCOL_ERROR");
  413. break;
  414. }
  415. return wrapPacket(tw.getBytes());
  416. }
  417. /**
  418. * Extracts the type of the client
  419. * @param request containing the clients type
  420. */
  421. private void extractType(byte[] request) {
  422. int length = 0;
  423. for(int i = 0; i < request.length; i++, length++) {
  424. if(request[i] == 0x0d) break; //find the end of the type: '\r'
  425. }
  426. V_C = new byte[length];
  427. System.arraycopy(request, 0, V_C, 0, length);
  428. }
  429. /**
  430. * Extracts the payload of a packet and writes it in I_C.
  431. * @param request packet of which the payload is extracted.
  432. */
  433. private void extractPayload(byte[] request) {
  434. int pos = 0;
  435. if(request[5] != 0x14) {
  436. pos = 1;
  437. for(int i = 0; i < request.length; i++, pos++) {
  438. if(request[i] == 0xa) break;
  439. }
  440. }
  441. int packetLength = byteToInt(new byte[]{request[pos], request[1+pos], request[2+pos], request[3+pos]});
  442. int paddingLength = byteToInt(new byte[]{request[4+pos]});
  443. byte[] payload = new byte[packetLength - paddingLength - 1];
  444. for(int i = 5; i < packetLength - paddingLength - 1; i++) {
  445. payload[i-5] = request[i+pos];
  446. }
  447. I_C = payload;
  448. }
  449. /**
  450. * Extracts the public key from the DH Kex Request
  451. * @param request containing the clients public key
  452. */
  453. private void extractPubKey(byte[] request) {
  454. e = new byte[byteToInt(new byte[] {request[6], request[7], request[8], request[9]})];
  455. for(int i = 0; i < e.length; i++) {
  456. e[i] = request[i+10];
  457. }
  458. }
  459. /**
  460. * Converts a byte[] to int
  461. * @param bytes that are converted
  462. * @return converted byte[] as int
  463. */
  464. private static int byteToInt(byte[] bytes) {
  465. int ret = 0;
  466. for (int i=0; i < bytes.length; i++) {
  467. ret <<= 8;
  468. ret |= bytes[i] & 0xFF;
  469. }
  470. return ret;
  471. }
  472. //server infos
  473. private String serverVersion = ProtocolSettings.getSshVersion();
  474. private String serverType = ProtocolSettings.getSshType();
  475. private String serverName = ProtocolSettings.getSshName();
  476. private int packetNumber = 0;
  477. int recipientChannel;
  478. String userName;
  479. String terminalPrefix;
  480. StringBuffer cmd = new StringBuffer();
  481. //SSH Parameters for Kex etc.
  482. private byte[] V_S = (serverVersion + serverType).getBytes();
  483. private byte[] V_C;
  484. private byte[] I_S;
  485. private byte[] I_C;
  486. private byte[] e;
  487. private BigInteger f;
  488. private byte[] h;
  489. private BigInteger k;
  490. private byte[] K_S;
  491. private byte[] sig;
  492. //allowed algorithms for kexinit
  493. private String kex_alg = "diffie-hellman-group1-sha1";
  494. private String server_alg = "ssh-dss";
  495. private String encrypt_alg_c = "3des-cbc";
  496. private String encrypt_alg_s = "3des-cbc";
  497. private String mac_alg_c = "hmac-sha1";
  498. private String mac_alg_s = "hmac-sha1";
  499. private String comp_alg_c = "none";
  500. private String comp_alg_s = "none";
  501. private int cipherBlockSize = 16;
  502. //for en- and decryption
  503. DESede enc;
  504. DESede dec;
  505. CBCMode cbcEnc;
  506. CBCMode cbcDec;
  507. MAC macEnc;
  508. MAC macDec;
  509. //dsa private key
  510. private final char[] dsa_pem = ("-----BEGIN DSA PRIVATE KEY-----\n" +
  511. "MIIBugIBAAKBgQCDZ9R2vfCPwjv5vKF1igIv9drrZ7G0dhMkGT9AZTjgI34Qm4w0\n" +
  512. "0iWeCqO7SmqiaMIjbRIm91MeDed4ObAq4sAkqRE/2P4mTbzFx5KhEczRRiDoqQBX\n" +
  513. "xYa0yWKJpeZ94SGM6DEPuBTxKo0T4uMjbq2FzHL2FXT1/WoNCmRU6gFSiwIVAMK4\n" +
  514. "Epz3JiwDUbkSpLOjIqtEhJmVAoGAL6zlXRI4Q8iwvSDh0vDf1j9a5Aaaq+93LTjK\n" +
  515. "SwL4nvUWBl2Aa0vqu05ZS5rOD1I+/naLMg0fNgFJRhA03sl+12MI3a2HXJWXRSdj\n" +
  516. "m1Vq9cUXqiYrX6+iGfEaA/y9UO4ZPF6if6eLypXB8VuqjtjDCiMMsM6+qQki7L71\n" +
  517. "yN4M75ICgYAcFXUhN2zRug3JvwmGxW8gMgHquSiBnbx1582KGh2B/ukE/kOrbKYD\n" +
  518. "HUkBzolcm4x1Odq5apowlriFxY6zMQP615plIK4x9NaU6dvc/HoTkjzT5EYSMN39\n" +
  519. "eAGufJ0jrtIpKL4lP8o8yrAHfmbR7bjecWc0viTH0+OWlyVsex/bZAIUEKn310Li\n" +
  520. "v62Zs4hlDvhwvx8MQ+A=\n" +
  521. "-----END DSA PRIVATE KEY-----").toCharArray();
  522. }