|
@@ -1,14 +1,85 @@
|
|
|
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;
|
|
|
|
|
|
-public final class SSH implements Protocol<String> {
|
|
|
+import javax.crypto.KeyAgreement;
|
|
|
+import javax.crypto.interfaces.DHPublicKey;
|
|
|
+import javax.crypto.spec.DHParameterSpec;
|
|
|
+import javax.crypto.spec.DHPublicKeySpec;
|
|
|
|
|
|
- private static enum STATE {
|
|
|
- NONE, OPEN, CLOSED
|
|
|
- };
|
|
|
+import de.tudarmstadt.informatik.hostage.wrapper.ByteArray;
|
|
|
|
|
|
+public final class SSH implements Protocol<ByteArray> {
|
|
|
+
|
|
|
+ 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
|
|
@@ -18,13 +89,50 @@ public final class SSH implements Protocol<String> {
|
|
|
|
|
|
@Override
|
|
|
public TALK_FIRST whoTalksFirst() {
|
|
|
- return TALK_FIRST.CLIENT;
|
|
|
+ return TALK_FIRST.SERVER;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public List<String> processMessage(String message) {
|
|
|
- ArrayList<String> response = new ArrayList<String>();
|
|
|
- response.add("Not implemented yet!");
|
|
|
+ public List<ByteArray> processMessage(ByteArray message) {
|
|
|
+ List<ByteArray> response = new ArrayList<ByteArray>();
|
|
|
+ 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;
|
|
|
}
|
|
|
|
|
@@ -39,13 +147,246 @@ public final class SSH implements Protocol<String> {
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public Class<String> getType() {
|
|
|
- return String.class;
|
|
|
+ public Class<ByteArray> 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;
|
|
|
+ }
|
|
|
+
|
|
|
|
|
|
}
|