SSH.java 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. package de.tudarmstadt.informatik.hostage.protocol;
  2. import java.math.BigInteger;
  3. import java.nio.ByteBuffer;
  4. import java.security.KeyFactory;
  5. import java.security.KeyPair;
  6. import java.security.KeyPairGenerator;
  7. import java.security.MessageDigest;
  8. import java.security.PublicKey;
  9. import java.security.Signature;
  10. import java.security.interfaces.DSAPublicKey;
  11. import java.util.ArrayList;
  12. import java.util.List;
  13. import java.util.Random;
  14. import javax.crypto.KeyAgreement;
  15. import javax.crypto.interfaces.DHPublicKey;
  16. import javax.crypto.spec.DHParameterSpec;
  17. import javax.crypto.spec.DHPublicKeySpec;
  18. import de.tudarmstadt.informatik.hostage.commons.HelperUtils;
  19. import de.tudarmstadt.informatik.hostage.wrapper.ByteArray;
  20. /**
  21. * SSH protocol
  22. * @author Wulf Pfeiffer
  23. */
  24. public final class SSH implements Protocol<ByteArray> {
  25. /**
  26. * Represents the states of the protocol
  27. */
  28. private enum STATE {
  29. NONE,
  30. SERVER_VERSION,
  31. CLIENT_VERSION,
  32. KEX_INIT,
  33. DH_KEX_REP,
  34. CLOSED
  35. }
  36. /**
  37. * Denotes in which state the protocol is right now
  38. */
  39. private STATE connectionState = STATE.NONE;
  40. private String serverVersion = "SSH-2.0-";
  41. private String serverType = "OpenSSH_6.0p1 Debian-4";
  42. //Diffie-Hellman-Group-1 p and g
  43. private final byte[] p = {
  44. (byte)0x00,
  45. (byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,
  46. (byte)0xC9,(byte)0x0F,(byte)0xDA,(byte)0xA2,(byte)0x21,(byte)0x68,(byte)0xC2,(byte)0x34,
  47. (byte)0xC4,(byte)0xC6,(byte)0x62,(byte)0x8B,(byte)0x80,(byte)0xDC,(byte)0x1C,(byte)0xD1,
  48. (byte)0x29,(byte)0x02,(byte)0x4E,(byte)0x08,(byte)0x8A,(byte)0x67,(byte)0xCC,(byte)0x74,
  49. (byte)0x02,(byte)0x0B,(byte)0xBE,(byte)0xA6,(byte)0x3B,(byte)0x13,(byte)0x9B,(byte)0x22,
  50. (byte)0x51,(byte)0x4A,(byte)0x08,(byte)0x79,(byte)0x8E,(byte)0x34,(byte)0x04,(byte)0xDD,
  51. (byte)0xEF,(byte)0x95,(byte)0x19,(byte)0xB3,(byte)0xCD,(byte)0x3A,(byte)0x43,(byte)0x1B,
  52. (byte)0x30,(byte)0x2B,(byte)0x0A,(byte)0x6D,(byte)0xF2,(byte)0x5F,(byte)0x14,(byte)0x37,
  53. (byte)0x4F,(byte)0xE1,(byte)0x35,(byte)0x6D,(byte)0x6D,(byte)0x51,(byte)0xC2,(byte)0x45,
  54. (byte)0xE4,(byte)0x85,(byte)0xB5,(byte)0x76,(byte)0x62,(byte)0x5E,(byte)0x7E,(byte)0xC6,
  55. (byte)0xF4,(byte)0x4C,(byte)0x42,(byte)0xE9,(byte)0xA6,(byte)0x37,(byte)0xED,(byte)0x6B,
  56. (byte)0x0B,(byte)0xFF,(byte)0x5C,(byte)0xB6,(byte)0xF4,(byte)0x06,(byte)0xB7,(byte)0xED,
  57. (byte)0xEE,(byte)0x38,(byte)0x6B,(byte)0xFB,(byte)0x5A,(byte)0x89,(byte)0x9F,(byte)0xA5,
  58. (byte)0xAE,(byte)0x9F,(byte)0x24,(byte)0x11,(byte)0x7C,(byte)0x4B,(byte)0x1F,(byte)0xE6,
  59. (byte)0x49,(byte)0x28,(byte)0x66,(byte)0x51,(byte)0xEC,(byte)0xE6,(byte)0x53,(byte)0x81,
  60. (byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF
  61. };
  62. private final byte[] g = {0x02};
  63. private byte[] V_S = serverType.getBytes();
  64. private byte[] V_C;
  65. private byte[] I_S;
  66. private byte[] I_C;
  67. private byte[] e;
  68. private byte[] f;
  69. private byte[] k;
  70. private byte[] h;
  71. private byte[] K_S;
  72. private byte[] sig;
  73. private KeyPair dsa;
  74. private String kex_alg = "diffie-hellman-group1-sha1";
  75. private String server_alg = "ssh-dss";
  76. private String encrypt_alg_c = "aes128-ctr";
  77. private String encrypt_alg_s = "aes128-ctr";
  78. private String mac_alg_c = "hmac-sha1";
  79. private String mac_alg_s = "hmac-sha1";
  80. private String comp_alg_c = "none";
  81. private String comp_alg_s = "none";
  82. private int cipherBlockSize = 16;
  83. private STATE state = STATE.NONE;
  84. @Override
  85. public int getPort() {
  86. return 22;
  87. }
  88. @Override
  89. public TALK_FIRST whoTalksFirst() {
  90. return TALK_FIRST.SERVER;
  91. }
  92. @Override
  93. public List<ByteArray> processMessage(ByteArray message) {
  94. List<ByteArray> response = new ArrayList<ByteArray>();
  95. byte[] request = null;
  96. if(message != null) {
  97. request = message.get();
  98. System.out.println("AWDLWDAO: "+message.size());
  99. }
  100. switch(connectionState) {
  101. case NONE:
  102. response.add(new ByteArray(serverVersion + serverType + "\r\n"));
  103. connectionState = STATE.SERVER_VERSION;
  104. System.out.println("NONE");
  105. break;
  106. case SERVER_VERSION:
  107. extractType(request);
  108. extractCookie(request);
  109. response.add(new ByteArray(kexInit()));
  110. connectionState = STATE.KEX_INIT;
  111. System.out.println("SERVER");
  112. break;
  113. case CLIENT_VERSION:
  114. connectionState = STATE.KEX_INIT;
  115. System.out.println("CLIENT");
  116. break;
  117. case KEX_INIT:
  118. extractPubKey(request);
  119. response.add(new ByteArray(dhKexReply()));
  120. connectionState = STATE.DH_KEX_REP;
  121. System.out.println("KEX");
  122. break;
  123. case DH_KEX_REP:
  124. connectionState = STATE.CLOSED;
  125. break;
  126. case CLOSED:
  127. break;
  128. default:
  129. connectionState = STATE.CLOSED;
  130. break;
  131. }
  132. return response;
  133. }
  134. @Override
  135. public boolean isClosed() {
  136. return (state == STATE.CLOSED);
  137. }
  138. @Override
  139. public boolean isSecure() {
  140. return false;
  141. }
  142. @Override
  143. public Class<ByteArray> getType() {
  144. return ByteArray.class;
  145. }
  146. @Override
  147. public String toString() {
  148. return "SSH";
  149. }
  150. /**
  151. * Wraps the packets with packet length and padding
  152. * @param packet content that is wrapped
  153. * @return wrapped packet
  154. */
  155. private byte[] wrapPacket(byte[] packet) {
  156. int packetLength = 5 + packet.length; //4 byte packet length, 1 byte padding length, payload length
  157. int paddingLengthCBS = cipherBlockSize - (packetLength % cipherBlockSize);
  158. int paddingLength8 = 8 - (packetLength % 8);
  159. int paddingLength = paddingLengthCBS > paddingLength8 ? paddingLengthCBS : paddingLength8;
  160. if(paddingLength < 4) paddingLength += cipherBlockSize;
  161. packetLength = packetLength + paddingLength - 4; //add padding string length to packet length
  162. byte[] packetLen = ByteBuffer.allocate(4).putInt(packetLength).array();
  163. byte[] paddingLen = {(byte) paddingLength};
  164. byte[] paddingString = new byte[paddingLength];
  165. for(int i = 0; i < paddingLength; i++) {
  166. paddingString[i] = 0x00;
  167. }
  168. return HelperUtils.concat(packetLen, paddingLen, packet, paddingString);
  169. }
  170. /**
  171. * Builds the Kex Init packet that contains all the allowed algorithms by the server
  172. * @return Kex Init packet
  173. */
  174. private byte[] kexInit() {
  175. byte[] msgCode = {0x14};
  176. I_S = randomBytes(16);
  177. byte[] kexLength = ByteBuffer.allocate(4).putInt(kex_alg.getBytes().length).array();
  178. byte[] serverLength = ByteBuffer.allocate(4).putInt(server_alg.getBytes().length).array();
  179. byte[] encrypt_c_Length = ByteBuffer.allocate(4).putInt(encrypt_alg_c.getBytes().length).array();
  180. byte[] encrypt_s_Length = ByteBuffer.allocate(4).putInt(encrypt_alg_s.getBytes().length).array();
  181. byte[] mac_c_Length = ByteBuffer.allocate(4).putInt(mac_alg_c.getBytes().length).array();
  182. byte[] mac_s_Length = ByteBuffer.allocate(4).putInt(mac_alg_s.getBytes().length).array();
  183. byte[] comp_c_Length = ByteBuffer.allocate(4).putInt(comp_alg_c.getBytes().length).array();
  184. byte[] comp_s_Length = ByteBuffer.allocate(4).putInt(comp_alg_s.getBytes().length).array();
  185. byte[] language_c_s = {0x00, 0x00, 0x00, 0x00};
  186. byte[] language_s_c = {0x00, 0x00, 0x00, 0x00};
  187. byte[] kexFirsPckt = {0x00};
  188. byte[] reserved = {0x00, 0x00, 0x00, 0x00};
  189. byte[] response = HelperUtils.concat(msgCode, I_S, kexLength, kex_alg.getBytes(), serverLength, server_alg.getBytes(),
  190. encrypt_c_Length, encrypt_alg_c.getBytes(), encrypt_s_Length, encrypt_alg_s.getBytes(), mac_c_Length, mac_alg_c.getBytes(),
  191. mac_s_Length, mac_alg_s.getBytes(), comp_c_Length, comp_alg_c.getBytes(), comp_s_Length, comp_alg_s.getBytes(),
  192. language_c_s, language_s_c, kexFirsPckt, reserved);
  193. return wrapPacket(response);
  194. }
  195. /**
  196. * Builds the Diffie-Hellman Kex Reply, containing the host key,f and the signature
  197. * @return Diffie-Hellman Kex Reply packet
  198. */
  199. private byte[] dhKexReply() {
  200. generateDHKeys();
  201. generateHostKey();
  202. generateSha1Hash();
  203. generateSignature();
  204. byte[] msgCode = {0x1f};
  205. byte[] hostKeyLength = ByteBuffer.allocate(4).putInt(K_S.length).array();
  206. byte[] fDHLength = ByteBuffer.allocate(4).putInt(f.length).array();
  207. byte[] signatureLength = ByteBuffer.allocate(4).putInt(sig.length).array();
  208. byte[] server_algLength = ByteBuffer.allocate(4).putInt(server_alg.getBytes().length).array();
  209. byte[] payloadLength = ByteBuffer.allocate(4).putInt(server_algLength.length + signatureLength.length + sig.length + server_alg.getBytes().length).array();
  210. byte[] response = HelperUtils.concat(msgCode, hostKeyLength, K_S,
  211. fDHLength, f, payloadLength, server_algLength, server_alg.getBytes(), signatureLength, sig);
  212. return wrapPacket(response);
  213. }
  214. // private byte[] newKeys() {
  215. // byte[] msgCode = {0x15};
  216. // return wrapPckt(msgCode);
  217. // }
  218. /**
  219. * Generates the required Diffie-Hellman keys with p and g from Oakley Group 1
  220. */
  221. private void generateDHKeys() {
  222. try {
  223. KeyPairGenerator myKpairGen = KeyPairGenerator.getInstance("DH");
  224. KeyAgreement myKeyAgree = KeyAgreement.getInstance("DH");
  225. BigInteger p = new BigInteger(this.p);
  226. BigInteger g = new BigInteger(this.g);
  227. BigInteger e = new BigInteger(this.e);
  228. DHParameterSpec dhParamSpec = new DHParameterSpec(p, g);
  229. myKpairGen.initialize(dhParamSpec);
  230. KeyPair myKpair = myKpairGen.generateKeyPair();
  231. myKeyAgree.init(myKpair.getPrivate());
  232. BigInteger f = ((DHPublicKey) (myKpair.getPublic())).getY();
  233. this.f = f.toByteArray();
  234. KeyFactory myKeyFac = KeyFactory.getInstance("DH");
  235. DHPublicKeySpec keySpec = new DHPublicKeySpec(e, p, g);
  236. PublicKey yourPubKey = myKeyFac.generatePublic(keySpec);
  237. myKeyAgree.doPhase(yourPubKey, true);
  238. byte[] mySharedSecret = myKeyAgree.generateSecret();
  239. k = mySharedSecret;
  240. } catch (Exception e) {
  241. e.printStackTrace();
  242. }
  243. }
  244. /**
  245. * Generates the Host Key based on the DSA algorithm
  246. */
  247. private void generateHostKey() {
  248. try {
  249. KeyPairGenerator generator = KeyPairGenerator.getInstance("DSA");
  250. dsa = generator.generateKeyPair();
  251. byte[] string = "ssh-dss".getBytes();
  252. byte[] stringLength = ByteBuffer.allocate(4).putInt(string.length).array();
  253. byte[] p = ((DSAPublicKey) dsa.getPublic()).getParams().getP().toByteArray();
  254. if(p[0] != 0x00) p = HelperUtils.concat(new byte[]{0x00}, p);
  255. byte[] pLength = ByteBuffer.allocate(4).putInt(p.length).array();
  256. byte[] q = ((DSAPublicKey) dsa.getPublic()).getParams().getQ().toByteArray();
  257. if(q[0] != 0x00) q = HelperUtils.concat(new byte[]{0x00}, q);
  258. byte[] qLength = ByteBuffer.allocate(4).putInt(q.length).array();
  259. byte[] g = ((DSAPublicKey) dsa.getPublic()).getParams().getG().toByteArray();
  260. if(g[0] != 0x00) g = HelperUtils.concat(new byte[]{0x00}, g);
  261. byte[] gLength = ByteBuffer.allocate(4).putInt(g.length).array();
  262. byte[] y = ((DSAPublicKey) dsa.getPublic()).getY().toByteArray();
  263. if(y[0] != 0x00) y = HelperUtils.concat(new byte[]{0x00}, y);
  264. byte[] yLength = ByteBuffer.allocate(4).putInt(y.length).array();
  265. K_S = HelperUtils.concat(stringLength, string, pLength, p, qLength, q, gLength, g, yLength, y);
  266. } catch (Exception e) {
  267. e.printStackTrace();
  268. }
  269. }
  270. /**
  271. * Generates the SHA-1 Hash from several values
  272. */
  273. private void generateSha1Hash() {
  274. try {
  275. MessageDigest sha = MessageDigest.getInstance("SHA-1");
  276. sha.update(V_C);
  277. sha.update(V_S);
  278. sha.update(I_C);
  279. sha.update(I_S);
  280. sha.update(K_S);
  281. sha.update(e);
  282. sha.update(f);
  283. sha.update(k);
  284. h = sha.digest();
  285. } catch (Exception e) {
  286. e.printStackTrace();
  287. }
  288. }
  289. /**
  290. * Generates the signature of the hash using DSA algorithm with SHA-1
  291. */
  292. private void generateSignature() {
  293. try {
  294. Signature sig = Signature.getInstance("SHA1withDSA");
  295. sig.initVerify(dsa.getPublic());
  296. sig.initSign(dsa.getPrivate());
  297. sig.update(h);
  298. this.sig = extractSignature(sig.sign());
  299. } catch (Exception e) {
  300. e.printStackTrace();
  301. }
  302. }
  303. /**
  304. * Extracts the type of the client
  305. * @param request containing the clients type
  306. */
  307. private void extractType(byte[] request) {
  308. int length = 0;
  309. for(int i = 8; i < request.length; i++, length++) { //start at 8 because "SSH-2.0-" is not part of type
  310. if(request[i] == 0x0d) break; //find the end of the type: '\r'
  311. }
  312. V_C = new byte[length];
  313. System.arraycopy(request, 8, V_C, 0, length);
  314. }
  315. /**
  316. * Extracts the cookie from the Kex Init client request
  317. * @param request containing the clients cookie
  318. */
  319. private void extractCookie(byte[] request) {
  320. int pos = 0;
  321. if(request[5] != 0x14) { //if type packet is in front of kex init
  322. pos = 1; //start behind the end of type message
  323. for(int i = 0; i < request.length; i++, pos++) {
  324. if(request[i] == 0x0a) break; //find end of type message: '\n'
  325. }
  326. }
  327. I_C = new byte[16];
  328. System.arraycopy(request, 6+pos, I_C, 0, 16); //srcLen: headersize+position after type packet
  329. }
  330. /**
  331. * Extracts the public key from the DH Kex Request
  332. * @param request containing the clients public key
  333. */
  334. private void extractPubKey(byte[] request) {
  335. e = new byte[byteToInt(new byte[] {request[6], request[7], request[8], request[9]})];
  336. for(int i = 0; i < e.length; i++) {
  337. e[i] = request[i+10];
  338. }
  339. }
  340. /**
  341. * Converts a byte[] to int
  342. * @param bytes that are converted
  343. * @return converted byte[] as int
  344. */
  345. private static int byteToInt(byte[] bytes) {
  346. int ret = 0;
  347. for (int i=0; i < bytes.length; i++) {
  348. ret <<= 8;
  349. ret |= (int)bytes[i] & 0xFF;
  350. }
  351. return ret;
  352. }
  353. /**
  354. * Generates a random byte[] of a specified size
  355. * @param size of the byte[]
  356. * @return random byte[]
  357. */
  358. private byte[] randomBytes(int size) {
  359. byte[] bytes = new byte[size];
  360. Random rdm = new Random();
  361. rdm.nextBytes(bytes);
  362. return bytes;
  363. }
  364. /**
  365. * Extracts r and s from a DSA-signature
  366. * @param signature
  367. * @return r and s as byte[]
  368. */
  369. private byte[] extractSignature(byte[] signature) {
  370. //{ r INTEGER, s INTEGER }
  371. int length = 0;
  372. int index = 3;
  373. length = signature[index++] & 0xff;
  374. byte[] r = new byte[length];
  375. System.arraycopy(signature, index, r, 0, r.length);
  376. index = index + length + 1;
  377. length = signature[index++] & 0xff;
  378. byte[] s = new byte[length];
  379. System.arraycopy(signature, index, s, 0, s.length);
  380. byte[] result = new byte[40];
  381. // result must be 40 bytes, but length of r and s may not be 20 bytes
  382. System.arraycopy(r,
  383. (r.length > 20) ? 1 : 0,
  384. result,
  385. (r.length > 20) ? 0 : 20 - r.length,
  386. (r.length > 20) ? 20 : r.length);
  387. System.arraycopy(s,
  388. (s.length > 20) ? 1 : 0,
  389. result,
  390. (s.length > 20) ? 20 : 40 - s.length,
  391. (s.length > 20) ? 20 : s.length);
  392. return result;
  393. }
  394. public static String bytesToHex(byte[] bytes) {
  395. final char[] hexArray = "0123456789ABCDEF".toCharArray();
  396. char[] hexChars = new char[bytes.length * 2];
  397. int v;
  398. for ( int j = 0; j < bytes.length; j++ ) {
  399. v = bytes[j] & 0xFF;
  400. hexChars[j * 2] = hexArray[v >>> 4];
  401. hexChars[j * 2 + 1] = hexArray[v & 0x0F];
  402. }
  403. return new String(hexChars);
  404. }
  405. }