123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428 |
- 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.commons.HelperUtils;
- import de.tudarmstadt.informatik.hostage.wrapper.ByteArray;
- /**
- * SSH protocol
- * @author Wulf Pfeiffer
- */
- public final class SSH implements Protocol<ByteArray> {
- /**
- * Represents the states of the protocol
- */
- private enum STATE {
- NONE,
- SERVER_VERSION,
- CLIENT_VERSION,
- KEX_INIT,
- CLOSED
- }
-
- /**
- * Denotes in which state the protocol is right now
- */
- private STATE connectionState = STATE.NONE;
-
- private String serverVersion = "SSH-2.0-";
- private String serverType = "OpenSSH_6.0p1 Debian-4";
-
- //Diffie-Hellman-Group-1 p and g
- private final byte[] p = {
- (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[] g = {0x02};
-
- private byte[] V_S = serverType.getBytes();
- private byte[] V_C;
- private byte[] I_S;
- private byte[] I_C;
- private byte[] e;
- private byte[] f;
- private byte[] k;
- private byte[] h;
- private byte[] K_S;
- private byte[] sig;
- 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<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"));
- connectionState = STATE.SERVER_VERSION;
- break;
- case SERVER_VERSION:
- extractType(request);
- extractCookie(request);
- response.add(new ByteArray(kexInit()));
- connectionState = STATE.CLIENT_VERSION;
- break;
- case CLIENT_VERSION:
- extractPubKey(request);
- response.add(new ByteArray(dhKexReply()));
- connectionState = STATE.KEX_INIT;
- break;
- case KEX_INIT:
- 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<ByteArray> getType() {
- return ByteArray.class;
- }
- @Override
- public String toString() {
- return "SSH";
- }
- /**
- * Wraps the packets with packet length and padding
- * @param packet content that is wrapped
- * @return wrapped packet
- */
- private byte[] wrapPacket(byte[] packet) {
- int packetLength = 5 + packet.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 = new byte[paddingLength];
- for(int i = 0; i < paddingLength; i++) {
- paddingString[i] = 0x00;
- }
- return HelperUtils.concat(packetLen, paddingLen, packet, paddingString);
- }
-
- /**
- * Builds the Kex Init packet that contains all the allowed algorithms by the server
- * @return Kex Init packet
- */
- 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 = HelperUtils.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 wrapPacket(response);
- }
-
- /**
- * Builds the Diffie-Hellman Kex Reply, containing the host key,f and the signature
- * @return Diffie-Hellman Kex Reply packet
- */
- 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(f.length).array();
- byte[] signatureLength = ByteBuffer.allocate(4).putInt(sig.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 + sig.length + server_alg.getBytes().length).array();
- byte[] response = HelperUtils.concat(msgCode, hostKeyLength, K_S,
- fDHLength, f, payloadLength, server_algLength, server_alg.getBytes(), signatureLength, sig);
- return wrapPacket(response);
- }
-
- // private byte[] newKeys() {
- // byte[] msgCode = {0x15};
- // return wrapPckt(msgCode);
- // }
-
- /**
- * Generates the required Diffie-Hellman keys with p and g from Oakley Group 1
- */
- private void generateDHKeys() {
- try {
- KeyPairGenerator myKpairGen = KeyPairGenerator.getInstance("DH");
- KeyAgreement myKeyAgree = KeyAgreement.getInstance("DH");
- BigInteger p = new BigInteger(this.p);
- BigInteger g = new BigInteger(this.g);
- BigInteger e = new BigInteger(this.e);
-
- DHParameterSpec dhParamSpec = new DHParameterSpec(p, g);
- myKpairGen.initialize(dhParamSpec);
- KeyPair myKpair = myKpairGen.generateKeyPair();
- myKeyAgree.init(myKpair.getPrivate());
- BigInteger f = ((DHPublicKey) (myKpair.getPublic())).getY();
- this.f = 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();
- k = mySharedSecret;
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- /**
- * Generates the Host Key based on the DSA algorithm
- */
- 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 = HelperUtils.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 = HelperUtils.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 = HelperUtils.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 = HelperUtils.concat(new byte[]{0x00}, y);
- byte[] yLength = ByteBuffer.allocate(4).putInt(y.length).array();
-
- K_S = HelperUtils.concat(stringLength, string, pLength, p, qLength, q, gLength, g, yLength, y);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- /**
- * Generates the SHA-1 Hash from several values
- */
- 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(e);
- sha.update(f);
- sha.update(k);
- h = sha.digest();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- /**
- * Generates the signature of the hash using DSA algorithm with SHA-1
- */
- private void generateSignature() {
- try {
- Signature sig = Signature.getInstance("SHA1withDSA");
- sig.initVerify(dsa.getPublic());
- sig.initSign(dsa.getPrivate());
- sig.update(h);
- this.sig = extractSignature(sig.sign());
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- /**
- * Extracts the type of the client
- * @param request containing the clients type
- */
- private void extractType(byte[] request) {
- int length = 0;
- for(int i = 8; i < request.length; i++, length++) { //start at 8 because "SSH-2.0-" is not part of type
- if(request[i] == 0x0d) break; //find the end of the type: '\r'
- }
- V_C = new byte[length];
- System.arraycopy(request, 8, V_C, 0, length);
- }
-
- /**
- * Extracts the cookie from the Kex Init client request
- * @param request containing the clients cookie
- */
- private void extractCookie(byte[] request) {
- int pos = 0;
- if(request[5] != 0x14) { //if type packet is in front of kex init
- pos = 1; //start behind the end of type message
- for(int i = 0; i < request.length; i++, pos++) {
- if(request[i] == 0x0a) break; //find end of type message: '\n'
- }
- }
- I_C = new byte[16];
- System.arraycopy(request, 6+pos, I_C, 0, 16); //srcLen: headersize+position after type packet
- }
-
- /**
- * 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 ret = 0;
- for (int i=0; i < bytes.length; i++) {
- ret <<= 8;
- ret |= bytes[i] & 0xFF;
- }
- return ret;
- }
-
- /**
- * Generates a random byte[] of a specified size
- * @param size of the byte[]
- * @return random byte[]
- */
- private byte[] randomBytes(int size) {
- byte[] bytes = new byte[size];
- Random rdm = new Random();
- rdm.nextBytes(bytes);
- return bytes;
- }
- /**
- * Extracts r and s from a DSA-signature
- * @param signature
- * @return r and s as byte[]
- */
- private byte[] extractSignature(byte[] signature) {
- //{ r INTEGER, s INTEGER }
- int length = 0;
- int index = 3;
- length = signature[index++] & 0xff;
- byte[] r = new byte[length];
- System.arraycopy(signature, index, r, 0, r.length);
- index = index + length + 1;
- length = signature[index++] & 0xff;
- byte[] s = new byte[length];
- System.arraycopy(signature, 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;
- }
- }
|