package de.tudarmstadt.informatik.hostage.protocol; import java.math.BigInteger; import java.nio.ByteBuffer; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.MessageDigest; import java.security.PublicKey; import java.security.Signature; import java.security.interfaces.DSAPublicKey; import java.util.ArrayList; import java.util.List; import java.util.Random; import javax.crypto.KeyAgreement; import javax.crypto.interfaces.DHPublicKey; import javax.crypto.spec.DHParameterSpec; import javax.crypto.spec.DHPublicKeySpec; import de.tudarmstadt.informatik.hostage.wrapper.ByteArray; public final class SSH implements Protocol { private enum STATE { NONE, SRVR_VERSION, CLNT_VERSION, KEX_INIT, DH_KEX_REP, CLOSED } private STATE connectionState = STATE.NONE; private String serverVersion = "SSH-2.0-"; private String serverType = "OpenSSH_6.0p1 Debian-4"; private final byte[] pDH = { (byte)0x00, (byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF, (byte)0xC9,(byte)0x0F,(byte)0xDA,(byte)0xA2,(byte)0x21,(byte)0x68,(byte)0xC2,(byte)0x34, (byte)0xC4,(byte)0xC6,(byte)0x62,(byte)0x8B,(byte)0x80,(byte)0xDC,(byte)0x1C,(byte)0xD1, (byte)0x29,(byte)0x02,(byte)0x4E,(byte)0x08,(byte)0x8A,(byte)0x67,(byte)0xCC,(byte)0x74, (byte)0x02,(byte)0x0B,(byte)0xBE,(byte)0xA6,(byte)0x3B,(byte)0x13,(byte)0x9B,(byte)0x22, (byte)0x51,(byte)0x4A,(byte)0x08,(byte)0x79,(byte)0x8E,(byte)0x34,(byte)0x04,(byte)0xDD, (byte)0xEF,(byte)0x95,(byte)0x19,(byte)0xB3,(byte)0xCD,(byte)0x3A,(byte)0x43,(byte)0x1B, (byte)0x30,(byte)0x2B,(byte)0x0A,(byte)0x6D,(byte)0xF2,(byte)0x5F,(byte)0x14,(byte)0x37, (byte)0x4F,(byte)0xE1,(byte)0x35,(byte)0x6D,(byte)0x6D,(byte)0x51,(byte)0xC2,(byte)0x45, (byte)0xE4,(byte)0x85,(byte)0xB5,(byte)0x76,(byte)0x62,(byte)0x5E,(byte)0x7E,(byte)0xC6, (byte)0xF4,(byte)0x4C,(byte)0x42,(byte)0xE9,(byte)0xA6,(byte)0x37,(byte)0xED,(byte)0x6B, (byte)0x0B,(byte)0xFF,(byte)0x5C,(byte)0xB6,(byte)0xF4,(byte)0x06,(byte)0xB7,(byte)0xED, (byte)0xEE,(byte)0x38,(byte)0x6B,(byte)0xFB,(byte)0x5A,(byte)0x89,(byte)0x9F,(byte)0xA5, (byte)0xAE,(byte)0x9F,(byte)0x24,(byte)0x11,(byte)0x7C,(byte)0x4B,(byte)0x1F,(byte)0xE6, (byte)0x49,(byte)0x28,(byte)0x66,(byte)0x51,(byte)0xEC,(byte)0xE6,(byte)0x53,(byte)0x81, (byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF }; private final byte[] gDH = {0x02}; private byte[] V_S = serverType.getBytes(); private byte[] V_C; private byte[] I_S; private byte[] I_C; private byte[] eDH; private byte[] fDH; private byte[] kDH; private byte[] hDH; private byte[] K_S; private byte[] sigH; private KeyPair dsa; private String kex_alg = "diffie-hellman-group1-sha1"; private String server_alg = "ssh-dss"; private String encrypt_alg_c = "aes128-ctr"; private String encrypt_alg_s = "aes128-ctr"; private String mac_alg_c = "hmac-sha1"; private String mac_alg_s = "hmac-sha1"; private String comp_alg_c = "none"; private String comp_alg_s = "none"; private int cipherBlockSize = 16; private STATE state = STATE.NONE; @Override public int getPort() { return 22; } @Override public TALK_FIRST whoTalksFirst() { return TALK_FIRST.SERVER; } @Override public List processMessage(ByteArray message) { List response = new ArrayList(); byte[] request = null; if(message != null) request = message.get(); switch(connectionState) { case NONE: response.add(new ByteArray(serverVersion + serverType + "\r\n")); response.add(new ByteArray(kexInit())); connectionState = STATE.SRVR_VERSION; break; case SRVR_VERSION: if(request != null && request.length >= 8) { extractType(request); connectionState = STATE.CLNT_VERSION; } break; case CLNT_VERSION: if(request != null && request.length > 5 && request[5] == 0x14) { extractCookie(request); connectionState = STATE.KEX_INIT; } break; case KEX_INIT: if(request.length > 5 && request[5] == 0x1e) { extractPubKey(request); response.add(new ByteArray(dhKexReply())); connectionState = STATE.DH_KEX_REP; } break; case DH_KEX_REP: connectionState = STATE.CLOSED; break; case CLOSED: break; default: connectionState = STATE.CLOSED; break; } return response; } @Override public boolean isClosed() { return (state == STATE.CLOSED); } @Override public boolean isSecure() { return false; } @Override public Class getType() { return ByteArray.class; } @Override public String toString() { return "SSH"; } private byte[] wrapPckt(byte[] payload) { int pcktLen = 5 + payload.length; //4 byte packet length, 1 byte padding length, payload length int paddLenCBS = cipherBlockSize - (pcktLen % cipherBlockSize); int paddLen8 = 8 - (pcktLen % 8); int paddingLen = paddLenCBS > paddLen8 ? paddLenCBS : paddLen8; if(paddingLen < 4) paddingLen += cipherBlockSize; pcktLen = pcktLen + paddingLen - 4; //add padding string length to packet length byte[] pcktLength = ByteBuffer.allocate(4).putInt(pcktLen).array(); byte[] paddingLength = {(byte) paddingLen}; byte[] paddingString = new byte[paddingLen]; for(int i = 0; i < paddingLen; i++) { paddingString[i] = 0x00; } return concat(pcktLength, paddingLength, payload, paddingString); } private byte[] kexInit() { byte[] msgCode = {0x14}; I_S = randomBytes(16); byte[] kexLength = ByteBuffer.allocate(4).putInt(kex_alg.getBytes().length).array(); byte[] serverLength = ByteBuffer.allocate(4).putInt(server_alg.getBytes().length).array(); byte[] encrypt_c_Length = ByteBuffer.allocate(4).putInt(encrypt_alg_c.getBytes().length).array(); byte[] encrypt_s_Length = ByteBuffer.allocate(4).putInt(encrypt_alg_s.getBytes().length).array(); byte[] mac_c_Length = ByteBuffer.allocate(4).putInt(mac_alg_c.getBytes().length).array(); byte[] mac_s_Length = ByteBuffer.allocate(4).putInt(mac_alg_s.getBytes().length).array(); byte[] comp_c_Length = ByteBuffer.allocate(4).putInt(comp_alg_c.getBytes().length).array(); byte[] comp_s_Length = ByteBuffer.allocate(4).putInt(comp_alg_s.getBytes().length).array(); byte[] language_c_s = {0x00, 0x00, 0x00, 0x00}; byte[] language_s_c = {0x00, 0x00, 0x00, 0x00}; byte[] kexFirsPckt = {0x00}; byte[] reserved = {0x00, 0x00, 0x00, 0x00}; byte[] response = concat(msgCode, I_S, kexLength, kex_alg.getBytes(), serverLength, server_alg.getBytes(), encrypt_c_Length, encrypt_alg_c.getBytes(), encrypt_s_Length, encrypt_alg_s.getBytes(), mac_c_Length, mac_alg_c.getBytes(), mac_s_Length, mac_alg_s.getBytes(), comp_c_Length, comp_alg_c.getBytes(), comp_s_Length, comp_alg_s.getBytes(), language_c_s, language_s_c, kexFirsPckt, reserved); return wrapPckt(response); } private byte[] dhKexReply() { generateDHKeys(); generateHostKey(); generateSha1Hash(); generateSignature(); byte[] msgCode = {0x1f}; byte[] hostKeyLength = ByteBuffer.allocate(4).putInt(K_S.length).array(); byte[] fDHLength = ByteBuffer.allocate(4).putInt(fDH.length).array(); byte[] signatureLength = ByteBuffer.allocate(4).putInt(sigH.length).array(); byte[] server_algLength = ByteBuffer.allocate(4).putInt(server_alg.getBytes().length).array(); byte[] payloadLength = ByteBuffer.allocate(4).putInt(server_algLength.length + signatureLength.length + sigH.length + server_alg.getBytes().length).array(); byte[] response = concat(msgCode, hostKeyLength, K_S, fDHLength, fDH, payloadLength, server_algLength, server_alg.getBytes(), signatureLength, sigH); return wrapPckt(response); } private byte[] newKeys() { byte[] msgCode = {0x15}; return wrapPckt(msgCode); } private void generateDHKeys() { try { KeyPairGenerator myKpairGen = KeyPairGenerator.getInstance("DH"); KeyAgreement myKeyAgree = KeyAgreement.getInstance("DH"); BigInteger p = new BigInteger(pDH); BigInteger g = new BigInteger(gDH); BigInteger e = new BigInteger(eDH); DHParameterSpec dhParamSpec = new DHParameterSpec(p, g); myKpairGen.initialize(dhParamSpec); KeyPair myKpair = myKpairGen.generateKeyPair(); myKeyAgree.init(myKpair.getPrivate()); BigInteger f = ((DHPublicKey) (myKpair.getPublic())).getY(); fDH = f.toByteArray(); KeyFactory myKeyFac = KeyFactory.getInstance("DH"); DHPublicKeySpec keySpec = new DHPublicKeySpec(e, p, g); PublicKey yourPubKey = myKeyFac.generatePublic(keySpec); myKeyAgree.doPhase(yourPubKey, true); byte[] mySharedSecret = myKeyAgree.generateSecret(); kDH = mySharedSecret; } catch (Exception e) { e.printStackTrace(); } } private void generateHostKey() { try { KeyPairGenerator generator = KeyPairGenerator.getInstance("DSA"); dsa = generator.generateKeyPair(); byte[] string = "ssh-dss".getBytes(); byte[] stringLength = ByteBuffer.allocate(4).putInt(string.length).array(); byte[] p = ((DSAPublicKey) dsa.getPublic()).getParams().getP().toByteArray(); if(p[0] != 0x00) p = concat(new byte[]{0x00}, p); byte[] pLength = ByteBuffer.allocate(4).putInt(p.length).array(); byte[] q = ((DSAPublicKey) dsa.getPublic()).getParams().getQ().toByteArray(); if(q[0] != 0x00) q = concat(new byte[]{0x00}, q); byte[] qLength = ByteBuffer.allocate(4).putInt(q.length).array(); byte[] g = ((DSAPublicKey) dsa.getPublic()).getParams().getG().toByteArray(); if(g[0] != 0x00) g = concat(new byte[]{0x00}, g); byte[] gLength = ByteBuffer.allocate(4).putInt(g.length).array(); byte[] y = ((DSAPublicKey) dsa.getPublic()).getY().toByteArray(); if(y[0] != 0x00) y = concat(new byte[]{0x00}, y); byte[] yLength = ByteBuffer.allocate(4).putInt(y.length).array(); K_S = concat(stringLength, string, pLength, p, qLength, q, gLength, g, yLength, y); } catch (Exception e) { e.printStackTrace(); } } private void generateSha1Hash() { try { MessageDigest sha = MessageDigest.getInstance("SHA-1"); sha.update(V_C); sha.update(V_S); sha.update(I_C); sha.update(I_S); sha.update(K_S); sha.update(eDH); sha.update(fDH); sha.update(kDH); hDH = sha.digest(); } catch (Exception e) { e.printStackTrace(); } } private void generateSignature() { try { Signature sig = Signature.getInstance("SHA1withDSA"); sig.initVerify(dsa.getPublic()); sig.initSign(dsa.getPrivate()); sig.update(hDH); sigH = doStuff(sig.sign()); } catch (Exception e) { e.printStackTrace(); } } private void extractType(byte[] request) { V_C = new byte[request.length - 10]; for(int i = 0; i < V_C.length; i++) { if(request[i] == 0x0d) break; V_C[i] = request[i+8]; } } private void extractCookie(byte[] request) { I_C = new byte[16]; for(int i = 0; i < I_C.length; i++) { I_C[i] = request[i+6]; } } private void extractPubKey(byte[] request) { eDH = new byte[byteToInt(new byte[] {request[6], request[7], request[8], request[9]})]; for(int i = 0; i < eDH.length; i++) { eDH[i] = request[i+10]; } } private static int byteToInt(byte[] bytes) { int ret = 0; for (int i=0; i < bytes.length; i++) { ret <<= 8; ret |= (int)bytes[i] & 0xFF; } return ret; } private byte[] randomBytes(int size) { byte[] bytes = new byte[size]; Random rdm = new Random(); rdm.nextBytes(bytes); return bytes; } private byte[] concat(byte[]...bytes) { int newSize = 0; for(byte[] b: bytes) newSize += b.length; //get total new size byte[] dst = new byte[newSize]; //create new array with new size int currentPos = 0; int newPos; for(byte[] b:bytes) { //for each elem b out of bytes newPos = b.length; //get b.length and new position System.arraycopy(b, 0, dst, currentPos, newPos); //copy b in dst from currentPos to newPos currentPos += newPos; //increase currentPos to newPos + currentPos } return dst; } private byte[] doStuff(byte[] sig) { // sig is in ASN.1 // SEQUENCE::={ r INTEGER, s INTEGER } int len = 0; int index = 3; len = sig[index++] & 0xff; byte[] r = new byte[len]; System.arraycopy(sig, index, r, 0, r.length); index = index + len + 1; len = sig[index++] & 0xff; byte[] s = new byte[len]; System.arraycopy(sig, index, s, 0, s.length); byte[] result = new byte[40]; // result must be 40 bytes, but length of r and s may not be 20 bytes System.arraycopy(r, (r.length > 20) ? 1 : 0, result, (r.length > 20) ? 0 : 20 - r.length, (r.length > 20) ? 20 : r.length); System.arraycopy(s, (s.length > 20) ? 1 : 0, result, (s.length > 20) ? 20 : 40 - s.length, (s.length > 20) ? 20 : s.length); return result; } }