├── .gitignore ├── README.md ├── plaininput └── src ├── client └── ForwardClient.java ├── communication ├── handshake │ ├── AsymmetricCrypto.java │ ├── HandleCertificate.java │ ├── Handshake.java │ ├── HandshakeCrypto.java │ ├── HandshakeMessage.java │ └── aCertificate.java ├── session │ ├── IV.java │ ├── SessionDecrypter.java │ ├── SessionEncrypter.java │ └── SessionKey.java └── threads │ ├── ForwardServerClientThread.java │ └── ForwardThread.java ├── meta ├── Arguments.java ├── Common.java ├── Logger.java └── tests │ ├── HandshakeCryptoTester.java │ ├── TestSessionCrypto.java │ └── VPNTester.java └── server └── ForwardServer.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea/* 3 | out/* 4 | .DS_STORE 5 | CAPrivateKey.* 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VPN Project 2 | 3 | Virtual Private Network software built with Java using OpenSSL. 4 | 5 | ## How to run the VPN 6 | 7 | In order to run the VPN you need to run the ForwardServer class. 8 | 9 | You should run the ForwardServer class with specific program arguments: 10 | 11 | `--handshakeport=2206 --usercert=certs/server.pem --cacert=certs/CA.pem --key=certs/serverprivatekey.pem` 12 | 13 | These are program arguments for the client: 14 | 15 | `--handshakehost=localhost --handshakeport=2206 --targethost=localhost --targetport=1337 --usercert=certs/client.pem --cacert=certs/CA.pem --key=certs/clientprivatekey.der` 16 | 17 | ## How to test it 18 | 19 | Open a netcat listening to the target port specified by the client. 20 | 21 | Connect a netcat to the client's forward port that it specifies in the logs of its program after the handshake is complete. 22 | 23 | You should then be able to write text between the two netcats. 24 | 25 | Test the encryption is working by looking at the ForwardThread file and uncomment the block of code specified in the file. 26 | 27 | ## Certificates 28 | 29 | This program requires that the server's and client's certificates have been signed by the same CA. 30 | 31 | Generate the CA certificate and its private key: 32 | 33 | `openssl req -new -x509 -newkey rsa:2048 -keyout CAprivatekey.pem -out CA.pem` 34 | 35 | Generate the CSR for the client/server: 36 | 37 | `openssl req -out client.csr -new -newkey rsa:2048 -keyout clientprivatekey.pem` 38 | 39 | Sign the CSR with the CA: 40 | 41 | `openssl x509 -req -in client.csr -CA CA.pem -CAkey CAprivatekey.pem -CAcreateserial -out client.pem` 42 | 43 | We also need to convert .pem formatted key to a .der: 44 | 45 | `openssl pkcs8 -nocrypt -topk8 -inform PEM -in clientprivatekey.pem -outform DER -out clientprivatekey.der` 46 | 47 | ## How does it work? 48 | 49 | Below is a detailed set of steps breaking down how, using this project, a client and server create a secure communication channel. 50 | 51 | The step numbers are referenced in the code at the points where each task is completed. 52 | 53 | 1. ClientHello 54 | 1. the first message the client sends to the server 55 | 2. MessageType = ClientHello 56 | 3. Certificate = client’s x509 certificate (as a string) 57 | 2. Server Verifies Client certificate 58 | 3. ServerHello (if verified) 59 | 1. MessageType = ServerHello 60 | 2. Certificate = server’s x509 certificate 61 | 4. Client verifies server certificate 62 | 5. Client requests port forwarding to target host & port 63 | 1. MessageType = forward 64 | 2. TargetHost = server.kth.se (target server) 65 | 3. TargetPort = 6789 (port of server) 66 | 6. If server agrees on target, sets up session: 67 | 1. Generate Session Key & IV 68 | 2. Encrypt Session & IV with client's public key (taken from client's cert) 69 | 3. Create socket endpoint, and port number for session communication 70 | 7. Server sends client session message 71 | 1. MessageType = session 72 | 2. SessionKey = AES key encrypted with client’s Public Key (as a string) 73 | 3. SessionIV = AES CTR IV, encrypted with … 74 | 4. ServerHost = name of host to which client should connect (address of VPN server) 75 | 5. ServerPort = TCP port which client should connect (VPN Server’s port it has decided on) 76 | 8. Client receives session message and knows information for a session, handshake is complete 77 | 9. VPN client connects with user 78 | 1. VPN client decides on internal communication host and port for the user 79 | 2. User creates TCP connection with client host and client port 80 | 3. Now data that user sends goes through VPN client, gets encrypted, gets sent to VPN server, gets decrypted and sent to target. 81 | 10. Setup of secure communication channel is now complete 82 | -------------------------------------------------------------------------------- /plaininput: -------------------------------------------------------------------------------- 1 | HulaHoop -------------------------------------------------------------------------------- /src/client/ForwardClient.java: -------------------------------------------------------------------------------- 1 | package client; /** 2 | * Port forwarding client. Forward data 3 | * between two TCP ports. Based on Nakov TCP Socket Forward Server 4 | * and adapted for IK2206. 5 | * 6 | * See original copyright notice below. 7 | * (c) 2018 Peter Sjodin, KTH 8 | */ 9 | 10 | /** 11 | * Nakov TCP Socket Forward Server - freeware 12 | * Version 1.0 - March, 2002 13 | * (c) 2001 by Svetlin Nakov - http://www.nakov.com 14 | */ 15 | 16 | 17 | import communication.handshake.*; 18 | import communication.session.IV; 19 | import communication.session.SessionKey; 20 | import communication.threads.ForwardServerClientThread; 21 | import meta.Arguments; 22 | import meta.Common; 23 | 24 | import java.io.IOException; 25 | import java.net.InetAddress; 26 | import java.net.ServerSocket; 27 | import java.net.Socket; 28 | import java.net.UnknownHostException; 29 | import java.security.PrivateKey; 30 | import java.security.cert.X509Certificate; 31 | 32 | public class ForwardClient { 33 | private static final boolean ENABLE_LOGGING = true; 34 | public static final int DEFAULTSERVERPORT = 2206; 35 | public static final String DEFAULTSERVERHOST = "localhost"; 36 | public static final String PROGRAMNAME = "client.ForwardClient"; 37 | 38 | private static Arguments arguments; 39 | private static int serverPort; 40 | private static String serverHost; 41 | 42 | /** 43 | * Program entry point. Reads arguments and encrypt 44 | * the forward server 45 | */ 46 | public static void main(String[] args) { 47 | try { 48 | arguments = new Arguments(); 49 | arguments.setDefault("handshakeport", Integer.toString(DEFAULTSERVERPORT)); 50 | arguments.setDefault("handshakehost", DEFAULTSERVERHOST); 51 | arguments.loadArguments(args); 52 | 53 | if (arguments.get("targetport") == null || arguments.get("targethost") == null) { 54 | throw new IllegalArgumentException("Target not specified"); 55 | } 56 | 57 | } catch(IllegalArgumentException ex) { 58 | System.out.println(ex); 59 | usage(); 60 | System.exit(1); 61 | } 62 | 63 | try { 64 | startForwardClient(); 65 | } catch(Exception e) { 66 | e.printStackTrace(); 67 | } 68 | } 69 | 70 | static public void startForwardClient() throws Exception { 71 | doHandshake(); 72 | setUpSession(); 73 | 74 | // Wait for client. Accept one connection. 75 | ForwardServerClientThread forwardThread; 76 | ServerSocket listensocket; 77 | 78 | try { 79 | /* Create a new socket. This is to where the user should connect. 80 | * client.ForwardClient sets up port forwarding between this socket 81 | * and the ServerHost/ServerPort learned from the handshake */ 82 | listensocket = new ServerSocket(); 83 | /* Let the system pick a port number */ 84 | listensocket.bind(null); 85 | /* Tell the user, so the user knows where to connect */ 86 | tellUser(listensocket); 87 | 88 | Socket clientSocket = listensocket.accept(); 89 | String clientHostPort = clientSocket.getInetAddress().getHostAddress() + ":" + clientSocket.getPort(); 90 | log("Accepted client from " + clientHostPort); 91 | 92 | forwardThread = new ForwardServerClientThread(clientSocket, serverHost, serverPort); 93 | forwardThread.start(); 94 | } catch (IOException e) { 95 | e.printStackTrace(); 96 | System.out.println(e); 97 | throw e; 98 | } 99 | } 100 | 101 | private static void doHandshake() throws Exception { 102 | /* Connect to forward server server */ 103 | System.out.println("Connecting to " + arguments.get("handshakehost") + ":" + Integer.parseInt(arguments.get("handshakeport"))); 104 | Socket socket = new Socket(arguments.get("handshakehost"), Integer.parseInt(arguments.get("handshakeport"))); 105 | 106 | /* This is where the handshake should take place */ 107 | 108 | //Step 1 Client Hello 109 | HandshakeMessage clientHello = new HandshakeMessage(); 110 | 111 | clientHello.putParameter(Common.MESSAGE_TYPE, Common.CLIENT_HELLO); 112 | clientHello.putParameter(Common.CERTIFICATE, aCertificate.encodeCert(aCertificate.pathToCert(arguments.get("usercert")))); 113 | clientHello.send(socket); 114 | 115 | //Step 3 receiving serverHello 116 | HandshakeMessage serverHello = new HandshakeMessage(); 117 | serverHello.recv(socket); 118 | 119 | //verify that it is in fact a serverHello 120 | if (!serverHello.getParameter(Common.MESSAGE_TYPE).equals(Common.SERVER_HELLO)) { 121 | System.err.println("Received invalid handshake type!"); 122 | socket.close(); 123 | throw new Error(); 124 | } 125 | 126 | //Step 4 verifies serveHello certificate is signed by our CA 127 | String serverCertString = serverHello.getParameter(Common.CERTIFICATE); 128 | X509Certificate serverCert = aCertificate.stringToCert(serverCertString); 129 | 130 | //verify certificate is signed by our CA 131 | HandleCertificate handleCertificate = new HandleCertificate(arguments.get("cacert")); 132 | 133 | if (!handleCertificate.verify(serverCert)) { 134 | System.err.println("SERVER CA FAILED VERIFICATION"); 135 | socket.close(); 136 | throw new Error(); 137 | } else { 138 | // Logger.log("Successful verification of server certificate..."); 139 | } 140 | 141 | //step 5 client requests connection to target 142 | HandshakeMessage forwardMessage = new HandshakeMessage(); 143 | forwardMessage.putParameter(Common.MESSAGE_TYPE, Common.FORWARD_MSG); 144 | forwardMessage.putParameter(Common.TARGET_HOST, arguments.get("targethost")); 145 | forwardMessage.putParameter(Common.TARGET_PORT, arguments.get("targetport")); 146 | 147 | forwardMessage.send(socket); 148 | 149 | //step 8 receive session information from server 150 | HandshakeMessage sessionMessage = new HandshakeMessage(); 151 | sessionMessage.recv(socket); 152 | 153 | if (!sessionMessage.getParameter(Common.MESSAGE_TYPE).equals(Common.SESSION_MSG)) { 154 | System.err.println("Received invalid handshake type! Should be session"); 155 | socket.close(); 156 | throw new Error(); 157 | } 158 | 159 | //decrypt session message to get session key and iv 160 | PrivateKey clientPrivKey = AsymmetricCrypto.getPrivateKeyFromKeyFile(arguments.get("key")); 161 | 162 | String sessionKeyDecrypted = AsymmetricCrypto.decrypt(sessionMessage.getParameter(Common.SESSION_KEY), clientPrivKey); 163 | String sessionIVDecrypted = AsymmetricCrypto.decrypt(sessionMessage.getParameter(Common.SESSION_IV), clientPrivKey); 164 | 165 | Handshake.sessionKey = new SessionKey(sessionKeyDecrypted); 166 | Handshake.iv = new IV(sessionIVDecrypted); 167 | 168 | //need to do shit here 169 | log("Handshake successful!"); 170 | 171 | socket.close(); 172 | } 173 | 174 | private static void setUpSession() { 175 | /* 176 | * Fake the handshake result with static parameters. 177 | */ 178 | 179 | /* This is to where the client.ForwardClient should connect. 180 | * The server.ForwardServer creates a socket 181 | * dynamically and communicates the address (hostname and port number) 182 | * to client.ForwardClient during the communication.asdf.handshake (ServerHost, ServerPort parameters). 183 | * Here, we use a static address instead. 184 | */ 185 | serverHost = Handshake.serverHost; 186 | serverPort = Handshake.serverPort; 187 | } 188 | 189 | /* 190 | * Let user know that we are waiting 191 | */ 192 | private static void tellUser(ServerSocket listensocket) throws UnknownHostException { 193 | System.out.println("Client forwarder to target " + arguments.get("targethost") + ":" + arguments.get("targetport")); 194 | System.out.println("Waiting for incoming connections at " + 195 | InetAddress.getLocalHost().getHostAddress() + ":" + listensocket.getLocalPort()); 196 | } 197 | 198 | /* 199 | * Set up client forwarder. 200 | * Run handshake negotiation, then set up a listening socket and wait for user. 201 | * When user has connected, start port forwarder thread. 202 | */ 203 | 204 | /** 205 | * Prints given log message on the standard output if logging is enabled, 206 | * otherwise ignores it 207 | */ 208 | public static void log(String aMessage) 209 | { 210 | if (ENABLE_LOGGING) 211 | System.out.println(aMessage); 212 | } 213 | 214 | static void usage() { 215 | String indent = ""; 216 | System.err.println(indent + "Usage: " + PROGRAMNAME + " options"); 217 | System.err.println(indent + "Where options are:"); 218 | indent += " "; 219 | System.err.println(indent + "--targethost="); 220 | System.err.println(indent + "--targetport="); 221 | System.err.println(indent + "--handshakehost="); 222 | System.err.println(indent + "--handshakeport="); 223 | System.err.println(indent + "--usercert="); 224 | System.err.println(indent + "--cacert="); 225 | System.err.println(indent + "--key="); 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /src/communication/handshake/AsymmetricCrypto.java: -------------------------------------------------------------------------------- 1 | package communication.handshake; 2 | 3 | import javax.crypto.Cipher; 4 | import java.io.FileInputStream; 5 | import java.io.IOException; 6 | import java.nio.charset.StandardCharsets; 7 | import java.nio.file.Files; 8 | import java.nio.file.Path; 9 | import java.nio.file.Paths; 10 | import java.security.*; 11 | import java.security.cert.CertificateFactory; 12 | import java.security.spec.InvalidKeySpecException; 13 | import java.security.spec.PKCS8EncodedKeySpec; 14 | import java.util.Base64; 15 | 16 | public class AsymmetricCrypto { 17 | public static String encrypt(String plaintext, Key key) throws Exception { 18 | //byte[] encoded = Base64.getEncoder().encode(plaintext.getBytes(StandardCharsets.UTF_8)); 19 | 20 | Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); 21 | cipher.init(Cipher.ENCRYPT_MODE, key); 22 | 23 | return new String(Base64.getEncoder().encode(cipher.doFinal(plaintext.getBytes()))); 24 | } 25 | 26 | public static String decrypt(String ciphertext, Key key) throws Exception { 27 | byte[] decoded = Base64.getDecoder().decode(ciphertext.getBytes(StandardCharsets.UTF_8)); 28 | 29 | Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); 30 | cipher.init(Cipher.DECRYPT_MODE, key); 31 | 32 | return new String(cipher.doFinal(decoded)); 33 | } 34 | 35 | public static PublicKey getPublicKeyFromCertFile(String certfile) { 36 | try { 37 | CertificateFactory fact = CertificateFactory.getInstance("X.509"); 38 | FileInputStream CAIs = new FileInputStream(certfile); 39 | return fact.generateCertificate(CAIs).getPublicKey(); 40 | } catch (Exception e) { 41 | e.printStackTrace(); 42 | return null; 43 | } 44 | } 45 | 46 | public static PrivateKey getPrivateKeyFromKeyFile(String keyfile) { 47 | try { 48 | Path path = Paths.get(keyfile); 49 | byte[] privKeyBytes = Files.readAllBytes(path); 50 | PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privKeyBytes); 51 | KeyFactory keyFactory = KeyFactory.getInstance("RSA"); 52 | return keyFactory.generatePrivate(keySpec); 53 | } catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) { 54 | e.printStackTrace(); 55 | return null; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/communication/handshake/HandleCertificate.java: -------------------------------------------------------------------------------- 1 | package communication.handshake; 2 | 3 | import java.security.cert.*; 4 | 5 | public class HandleCertificate { 6 | private X509Certificate CACert; 7 | private X509Certificate userCert; 8 | 9 | //have to pass CA file 10 | public HandleCertificate(String CACertPath) throws Exception { 11 | CACert = aCertificate.pathToCert(CACertPath); 12 | } 13 | 14 | public boolean verify(X509Certificate userCert) { 15 | this.userCert = userCert; 16 | return (isCAVerified() && isUserVerified()); 17 | } 18 | 19 | private boolean isCAVerified() { 20 | //System.out.println("\nVerifying CA's certificate:"); 21 | 22 | return isDateValid(CACert); 23 | } 24 | 25 | private boolean isUserVerified() { 26 | // System.out.println("\nVerifying user's certificate:"); 27 | 28 | if (!isDateValid(CACert)) 29 | return false; 30 | 31 | //Verifies that user certificate was signed using the private key 32 | //that corresponds to the CA's public key 33 | try { 34 | userCert.verify(CACert.getPublicKey()); 35 | // System.out.println("-> User certificate is signed by CA"); 36 | } catch (Exception e) { 37 | System.err.println("ERROR: User certificate not signed by CA"); 38 | return false; 39 | } 40 | 41 | return true; 42 | } 43 | 44 | private boolean isDateValid(X509Certificate certificate) { 45 | //checking date & expiration of cert 46 | try { 47 | certificate.checkValidity(); 48 | // System.out.println("-> The certificate has not expired & is valid"); 49 | return true; 50 | } catch (CertificateExpiredException | CertificateNotYetValidException e) { 51 | System.err.println("ERROR: Certificate's dates are not valid"); 52 | return false; 53 | } 54 | } 55 | 56 | // public void printCACertDN () { 57 | // System.out.println("Here is the CA's DN"); 58 | // System.out.println(CACert.getSubjectDN()); 59 | // } 60 | // 61 | // public void printUserCertDN () { 62 | // System.out.println("Here is the User's DN"); 63 | // System.out.println(userCert.getSubjectDN()); 64 | // } 65 | } -------------------------------------------------------------------------------- /src/communication/handshake/Handshake.java: -------------------------------------------------------------------------------- 1 | package communication.handshake; 2 | 3 | import communication.session.IV; 4 | import communication.session.SessionKey; 5 | 6 | public class Handshake { 7 | /* Where the client forwarder forwards data from */ 8 | public static final String serverHost = "localhost"; 9 | public static final int serverPort = 4412; 10 | 11 | /* The final destination */ 12 | public static String targetHost = "localhost"; 13 | public static int targetPort = 6789; 14 | 15 | public static SessionKey sessionKey; 16 | public static IV iv; 17 | 18 | public static String getServerHost() { 19 | return serverHost; 20 | } 21 | 22 | public static int getServerPort() { 23 | return serverPort; 24 | } 25 | 26 | public static String getTargetHost() { 27 | return targetHost; 28 | } 29 | 30 | public static void setTargetHost(String targetHost) { 31 | Handshake.targetHost = targetHost; 32 | } 33 | 34 | public static int getTargetPort() { 35 | return targetPort; 36 | } 37 | 38 | public static void setTargetPort(int targetPort) { 39 | Handshake.targetPort = targetPort; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/communication/handshake/HandshakeCrypto.java: -------------------------------------------------------------------------------- 1 | package communication.handshake; 2 | 3 | import javax.crypto.BadPaddingException; 4 | import javax.crypto.Cipher; 5 | import javax.crypto.IllegalBlockSizeException; 6 | import javax.crypto.NoSuchPaddingException; 7 | import java.io.FileInputStream; 8 | import java.io.IOException; 9 | import java.nio.file.Files; 10 | import java.nio.file.Path; 11 | import java.nio.file.Paths; 12 | import java.security.*; 13 | import java.security.cert.CertificateFactory; 14 | import java.security.spec.InvalidKeySpecException; 15 | import java.security.spec.PKCS8EncodedKeySpec; 16 | 17 | public class HandshakeCrypto { 18 | // public static byte[] encrypt(byte[] plaintext, Key key) { 19 | // try { 20 | // Cipher cipher = Cipher.getInstance("RSA"); 21 | // cipher.init(Cipher.ENCRYPT_MODE, key); 22 | // return cipher.doFinal(plaintext); 23 | // } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e) { 24 | // e.printStackTrace(); 25 | // return null; 26 | // } 27 | // } 28 | // 29 | // public static byte[] decrypt(byte[] ciphertext, Key key) { 30 | // try { 31 | // Cipher cipher = Cipher.getInstance("RSA"); 32 | // cipher.init(Cipher.DECRYPT_MODE, key); 33 | // return cipher.doFinal(ciphertext); 34 | // } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e) { 35 | // e.printStackTrace(); 36 | // return null; 37 | // } 38 | // } 39 | 40 | // public static PublicKey getPublicKeyFromCertFile(String certfile) { 41 | // try { 42 | // CertificateFactory fact = CertificateFactory.getInstance("X.509"); 43 | // FileInputStream CAIs = new FileInputStream(certfile); 44 | // return fact.generateCertificate(CAIs).getPublicKey(); 45 | // } catch (Exception e) { 46 | // e.printStackTrace(); 47 | // return null; 48 | // } 49 | // } 50 | // 51 | // public static PrivateKey getPrivateKeyFromKeyFile(String keyfile) { 52 | // try { 53 | // Path path = Paths.get(keyfile); 54 | // byte[] privKeyBytes = Files.readAllBytes(path); 55 | // PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privKeyBytes); 56 | // KeyFactory keyFactory = KeyFactory.getInstance("RSA"); 57 | // return keyFactory.generatePrivate(keySpec); 58 | // } catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) { 59 | // e.printStackTrace(); 60 | // return null; 61 | // } 62 | // } 63 | } 64 | -------------------------------------------------------------------------------- /src/communication/handshake/HandshakeMessage.java: -------------------------------------------------------------------------------- 1 | package communication.handshake;/* 2 | * Handshake message encoding/decoding and transmission 3 | * for IK2206 project. 4 | * 5 | */ 6 | 7 | import java.io.IOException; 8 | import java.io.ByteArrayOutputStream; 9 | import java.io.ByteArrayInputStream; 10 | 11 | import java.nio.charset.StandardCharsets; 12 | import java.net.InetAddress; 13 | import java.net.Socket; 14 | import java.util.Properties; 15 | 16 | /* 17 | * A Handshake message is represented as a set of parameters -- pairs. 18 | * Extends Properties class. 19 | */ 20 | 21 | public class HandshakeMessage extends Properties { 22 | 23 | /* 24 | * Get the value of a parameter 25 | */ 26 | public String getParameter(String param) { 27 | return this.getProperty(param); 28 | } 29 | 30 | /* 31 | * Assign a parameter 32 | */ 33 | public void putParameter(String param, String value) { 34 | this.put(param, value); 35 | } 36 | 37 | /* 38 | * Send a handshake message out on a socket 39 | * 40 | * Use the built-in encoding of Properties as XML: 41 | * - Encode the message in XML 42 | * - Convert XML to a byte array, and write the byte array to the socket 43 | * 44 | * Prepend the byte array with an integer string with the length of the string. 45 | * The integer string is terminated by a whitespace. 46 | */ 47 | public void send(Socket socket) throws IOException { 48 | ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream(); 49 | String comment = "From " + InetAddress.getLocalHost() + ":" + socket.getLocalPort() + 50 | " to " + socket.getInetAddress().getHostAddress() + ":" + socket.getPort(); 51 | this.storeToXML(byteOutputStream, comment); 52 | byte[] bytes = byteOutputStream.toByteArray(); 53 | socket.getOutputStream().write(String.format("%d ", bytes.length).getBytes(StandardCharsets.UTF_8)); 54 | socket.getOutputStream().write(bytes); 55 | socket.getOutputStream().flush(); 56 | 57 | } 58 | 59 | /* 60 | * Receive a handshake message on a socket 61 | * 62 | * First read a string with an integer followed by whitespace, 63 | * which gives the size of the message in bytes. Then read the XML data 64 | * and convert it to a HandshakeMessage. 65 | */ 66 | public void recv(Socket socket) throws IOException { 67 | int length = 0; 68 | 69 | for (int n = socket.getInputStream().read(); !Character.isWhitespace(n); n = socket.getInputStream().read()) { 70 | length = length*10 + Character.getNumericValue(n); 71 | } 72 | 73 | byte[] data = new byte[length]; 74 | int nread = 0; 75 | 76 | while (nread < length) { 77 | nread += socket.getInputStream().read(data, nread, length-nread); 78 | } 79 | this.loadFromXML(new ByteArrayInputStream(data)); 80 | } 81 | 82 | }; 83 | -------------------------------------------------------------------------------- /src/communication/handshake/aCertificate.java: -------------------------------------------------------------------------------- 1 | package communication.handshake; 2 | 3 | import java.io.ByteArrayInputStream; 4 | import java.io.FileInputStream; 5 | import java.io.InputStream; 6 | import java.io.StringWriter; 7 | import java.security.cert.CertificateFactory; 8 | import java.security.cert.X509Certificate; 9 | import java.util.Base64; 10 | 11 | public class aCertificate { 12 | //takes a path and converts it to a cert object 13 | public static X509Certificate pathToCert(String certPath) throws Exception { 14 | CertificateFactory fact = CertificateFactory.getInstance("X.509"); 15 | 16 | FileInputStream CAIs = new FileInputStream(certPath); 17 | return (X509Certificate) fact.generateCertificate(CAIs); 18 | } 19 | 20 | //takes a string and converts it to a cert object 21 | //string has to have line breaks! 22 | public static X509Certificate stringToCert(String certString) throws Exception { 23 | CertificateFactory fact = CertificateFactory.getInstance("X.509"); 24 | InputStream is = new ByteArrayInputStream(certString.getBytes()); 25 | return (X509Certificate) fact.generateCertificate(is); 26 | } 27 | 28 | //takes a cert object and converts it to a string 29 | public static String encodeCert(X509Certificate cert) throws Exception { 30 | String LINE_SEPARATOR = System.getProperty("line.separator"); 31 | String BEGIN_CERT = "-----BEGIN CERTIFICATE-----"; 32 | String END_CERT = "-----END CERTIFICATE-----"; 33 | 34 | Base64.Encoder encoder = Base64.getMimeEncoder(64, LINE_SEPARATOR.getBytes()); 35 | 36 | byte[] rawCrtText = cert.getEncoded(); 37 | String encodedCertText = new String(encoder.encode(rawCrtText)); 38 | return BEGIN_CERT + LINE_SEPARATOR + encodedCertText + LINE_SEPARATOR + END_CERT; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/communication/session/IV.java: -------------------------------------------------------------------------------- 1 | package communication.session; 2 | 3 | import javax.crypto.Cipher; 4 | import javax.crypto.spec.IvParameterSpec; 5 | import java.security.SecureRandom; 6 | import java.util.Base64; 7 | 8 | public class IV { 9 | private IvParameterSpec ivParameterSpec; 10 | private byte[] iv; 11 | 12 | public IV() throws Exception { 13 | createIV(); 14 | } 15 | 16 | public IV(String ivString) { 17 | iv = ivString.getBytes(); 18 | decodeIV(); 19 | } 20 | 21 | private void createIV() throws Exception{ 22 | Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding"); 23 | 24 | //Generate unique counter value for encryption using CTR 25 | SecureRandom randomSecureRandom = new SecureRandom(); 26 | iv = new byte[cipher.getBlockSize()]; 27 | randomSecureRandom.nextBytes(iv); 28 | ivParameterSpec = new IvParameterSpec(iv); 29 | } 30 | 31 | public String encodeIV() { return Base64.getEncoder().encodeToString(iv); } 32 | 33 | private void decodeIV() { 34 | ivParameterSpec = new IvParameterSpec(Base64.getDecoder().decode(iv)); 35 | } 36 | 37 | public IvParameterSpec getIvParameterSpec() { 38 | return ivParameterSpec; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/communication/session/SessionDecrypter.java: -------------------------------------------------------------------------------- 1 | package communication.session; 2 | 3 | import javax.crypto.Cipher; 4 | import javax.crypto.CipherInputStream; 5 | import java.io.InputStream; 6 | 7 | public class SessionDecrypter { 8 | private SessionKey sessionKey; 9 | private IV iv; 10 | 11 | public SessionDecrypter(SessionKey key, IV iv) { 12 | sessionKey = key; 13 | this.iv = iv; 14 | } 15 | 16 | public CipherInputStream openCipherInputStream (InputStream input) { 17 | try { 18 | Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding"); 19 | cipher.init(Cipher.DECRYPT_MODE, sessionKey.getSecretKey(), iv.getIvParameterSpec()); 20 | return new CipherInputStream(input, cipher); 21 | } catch (Exception e) { 22 | e.printStackTrace(); 23 | } 24 | return null; 25 | } 26 | 27 | public SessionKey getSessionKey() { 28 | return sessionKey; 29 | } 30 | 31 | public void setSessionKey(SessionKey sessionKey) { 32 | this.sessionKey = sessionKey; 33 | } 34 | 35 | public IV getIv() { 36 | return iv; 37 | } 38 | 39 | public void setIv(IV iv) { 40 | this.iv = iv; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/communication/session/SessionEncrypter.java: -------------------------------------------------------------------------------- 1 | package communication.session; 2 | 3 | import javax.crypto.Cipher; 4 | import javax.crypto.CipherOutputStream; 5 | import java.io.OutputStream; 6 | 7 | public class SessionEncrypter { 8 | private SessionKey sessionKey; 9 | private IV iv; 10 | 11 | public SessionEncrypter(SessionKey sessionKey, IV iv) { 12 | this.sessionKey = sessionKey; 13 | this.iv = iv; 14 | } 15 | 16 | public String encodeKey() { 17 | return sessionKey.encodeKey(); 18 | } 19 | 20 | public String encodeIV() { return iv.encodeIV(); } 21 | 22 | public CipherOutputStream openCipherOutputStream(OutputStream output) throws Exception { 23 | Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding"); 24 | 25 | cipher.init(Cipher.ENCRYPT_MODE, sessionKey.getSecretKey(), iv.getIvParameterSpec()); 26 | 27 | return new CipherOutputStream(output,cipher); 28 | } 29 | } -------------------------------------------------------------------------------- /src/communication/session/SessionKey.java: -------------------------------------------------------------------------------- 1 | package communication.session; 2 | 3 | import javax.crypto.KeyGenerator; 4 | import javax.crypto.SecretKey; 5 | import javax.crypto.spec.SecretKeySpec; 6 | import java.security.NoSuchAlgorithmException; 7 | import java.util.Base64; 8 | 9 | public class SessionKey { 10 | private SecretKey secretKey; 11 | 12 | public SessionKey(Integer keyLength) { 13 | createSecretKey(keyLength); 14 | } 15 | 16 | public SessionKey (String encodedKey) { 17 | decodeKey(encodedKey); 18 | } 19 | 20 | public SecretKey getSecretKey() { 21 | return secretKey; 22 | } 23 | 24 | public String encodeKey() { 25 | String encodedKey = Base64.getEncoder().encodeToString(secretKey.getEncoded()); 26 | return encodedKey; 27 | } 28 | 29 | private void createSecretKey(Integer keyLength) { 30 | KeyGenerator keyGenerator = null; 31 | 32 | try { 33 | keyGenerator = KeyGenerator.getInstance("AES"); 34 | } catch (NoSuchAlgorithmException e) { 35 | e.printStackTrace(); 36 | } 37 | 38 | keyGenerator.init(keyLength); 39 | 40 | secretKey = keyGenerator.generateKey(); 41 | } 42 | 43 | private void decodeKey(String encodedKey) { 44 | byte[] decodedKey = Base64.getDecoder().decode(encodedKey); 45 | secretKey = new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES"); 46 | } 47 | } -------------------------------------------------------------------------------- /src/communication/threads/ForwardServerClientThread.java: -------------------------------------------------------------------------------- 1 | package communication.threads; /** 2 | * ForwardServerClientThread handles the clients of Nakov Forward Server. It 3 | * connects two sockets and starts the TCP forwarding between given client 4 | * and its assigned server. After the forwarding is failed and the two threads 5 | * are stopped, closes the sockets. 6 | * 7 | */ 8 | 9 | /** 10 | * Modifications for IK2206: 11 | * - Server pool removed 12 | * - Two variants - client connects to listening socket or client is already connected 13 | * 14 | * Peter Sjodin, KTH 15 | */ 16 | 17 | import client.ForwardClient; 18 | import meta.Logger; 19 | 20 | import java.net.Socket; 21 | import java.net.ServerSocket; 22 | import java.io.IOException; 23 | import java.io.InputStream; 24 | import java.io.OutputStream; 25 | 26 | public class ForwardServerClientThread extends Thread 27 | { 28 | private ForwardClient mForwardClient = null; 29 | private Socket mClientSocket = null; 30 | private Socket mServerSocket = null; 31 | private ServerSocket mListenSocket = null; 32 | private boolean mBothConnectionsAreAlive = false; 33 | private String mClientHostPort; 34 | private String mServerHostPort; 35 | private int mServerPort; 36 | private String mServerHost; 37 | 38 | /** 39 | * Creates a client thread for handling clients of NakovForwardServer. 40 | * A client socket should be connected and passed to this constructor. 41 | * A server socket is created later by encrypt() method. 42 | */ 43 | public ForwardServerClientThread(Socket aClientSocket, String serverhost, int serverport) 44 | { 45 | mClientSocket = aClientSocket; 46 | mServerPort = serverport; 47 | mServerHost = serverhost; 48 | } 49 | 50 | /** 51 | * Creates a client thread for handling clients of NakovForwardServer. 52 | * Wait for client to connect on client listening socket. 53 | * A server socket is created later by encrypt() method. 54 | */ 55 | public ForwardServerClientThread(ServerSocket listensocket, String serverhost, int serverport) throws IOException 56 | { 57 | mListenSocket = listensocket; 58 | //mServerHost = listensocket.getInetAddress().getHostAddress(); 59 | mServerPort = serverport; 60 | mServerHost = serverhost; 61 | } 62 | 63 | public ServerSocket getListenSocket() { 64 | return mListenSocket; 65 | } 66 | 67 | /** 68 | * Obtains a destination server socket to some of the servers in the list. 69 | * Starts two threads for forwarding : "client in <--> dest server out" and 70 | * "dest server in <--> client out", waits until one of these threads stop 71 | * due to read/write failure or connection closure. Closes opened connections. 72 | * 73 | * If there is a listen socket, first wait for incoming connection 74 | * on the listen socket. 75 | */ 76 | public void run() 77 | { 78 | try { 79 | 80 | // Wait for incoming connection on listen socket, if there is one 81 | if (mListenSocket != null) { 82 | mClientSocket = mListenSocket.accept(); 83 | mClientHostPort = mClientSocket.getInetAddress().getHostAddress() + ":" + mClientSocket.getPort(); 84 | Logger.log("Accepted from " + mServerPort + " <--> " + mClientHostPort + " started."); 85 | 86 | } 87 | else { 88 | mClientHostPort = mClientSocket.getInetAddress().getHostAddress() + ":" + mClientSocket.getPort(); 89 | } 90 | 91 | try { 92 | mServerSocket = new Socket(mServerHost, mServerPort); 93 | } catch (Exception e) { 94 | System.out.println("Connection failed to " + mServerHost + ":" + mServerPort); 95 | e.printStackTrace(); 96 | // Prints what exception has been thrown 97 | System.out.println(e); 98 | } 99 | 100 | // Obtain input and output streams of server and client 101 | InputStream clientIn = mClientSocket.getInputStream(); 102 | OutputStream clientOut = mClientSocket.getOutputStream(); 103 | InputStream serverIn = mServerSocket.getInputStream(); 104 | OutputStream serverOut = mServerSocket.getOutputStream(); 105 | 106 | mServerHostPort = mServerHost + ":" + mServerPort; 107 | Logger.log("TCP Forwarding " + mClientHostPort + " <--> " + mServerHostPort + " started."); 108 | 109 | // Start forwarding of socket data between server and client 110 | ForwardThread clientForward = new ForwardThread(this, clientIn, serverOut); 111 | ForwardThread serverForward = new ForwardThread(this, serverIn, clientOut); 112 | mBothConnectionsAreAlive = true; 113 | clientForward.start(); 114 | serverForward.start(); 115 | 116 | } catch (IOException ioe) { 117 | ioe.printStackTrace(); 118 | } 119 | } 120 | 121 | /** 122 | * connectionBroken() method is called by forwarding child communication.asdf.threads to notify 123 | * this thread (their parent thread) that one of the connections (server or client) 124 | * is broken (a read/write failure occured). This method disconnects both server 125 | * and client sockets causing both communication.asdf.threads to stop forwarding. 126 | */ 127 | public synchronized void connectionBroken() 128 | { 129 | if (mBothConnectionsAreAlive) { 130 | // One of the connections is broken. Close the other connection and stop forwarding 131 | // Closing these socket connections will close their input/output streams 132 | // and that way will stop the communication.asdf.threads that read from these streams 133 | try { mServerSocket.close(); } catch (IOException e) {} 134 | try { mClientSocket.close(); } catch (IOException e) {} 135 | 136 | mBothConnectionsAreAlive = false; 137 | 138 | Logger.log("TCP Forwarding " + mClientHostPort + " <--> " + mServerHostPort + " stopped."); 139 | } 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /src/communication/threads/ForwardThread.java: -------------------------------------------------------------------------------- 1 | package communication.threads; /** 2 | * ForwardThread handles the TCP forwarding between a socket input stream (source) 3 | * and a socket output stream (destination). It reads the input stream and forwards 4 | * everything to the output stream. If some of the streams fails, the forwarding 5 | * is stopped and the parent thread is notified to close all its connections. 6 | */ 7 | 8 | import communication.handshake.Handshake; 9 | import communication.session.SessionDecrypter; 10 | import communication.session.SessionEncrypter; 11 | 12 | import javax.crypto.CipherOutputStream; 13 | import java.io.InputStream; 14 | import java.io.OutputStream; 15 | 16 | public class ForwardThread extends Thread { 17 | private static final int READ_BUFFER_SIZE = 8192; 18 | InputStream mInputStream = null; 19 | OutputStream mOutputStream = null; 20 | ForwardServerClientThread mParent = null; 21 | SessionEncrypter sessionEncrypter; 22 | SessionDecrypter sessionDecrypter; 23 | 24 | /** 25 | * Creates a new traffic forward thread specifying its input stream, 26 | * output stream and parent thread 27 | */ 28 | public ForwardThread(ForwardServerClientThread aParent, InputStream aInputStream, OutputStream aOutputStream) 29 | { 30 | mInputStream = aInputStream; 31 | mOutputStream = aOutputStream; 32 | mParent = aParent; 33 | sessionEncrypter = new SessionEncrypter(Handshake.sessionKey, Handshake.iv); 34 | sessionDecrypter = new SessionDecrypter(Handshake.sessionKey, Handshake.iv); 35 | } 36 | 37 | /** 38 | * Runs the thread. Until it is possible, reads the input stream and puts read 39 | * data in the output stream. If reading can not be done (due to exception or 40 | * when the stream is at his end) or writing is failed, exits the thread. 41 | */ 42 | public void run() { 43 | byte[] buffer = new byte[READ_BUFFER_SIZE]; 44 | try { 45 | while (true) { 46 | /** 47 | * If we can call getListenSocket then we know we are being called by a server 48 | * so we should encrypt what is being sent. Otherwise we know it is the client and we should decrypt. 49 | * 50 | * To test that the encryption is working uncommment the block of code in the else and comment the default code. 51 | * You should be see that any plaintext you send appears as gibberish on the recieving side. 52 | */ 53 | if (mParent.getListenSocket() != null) { 54 | int bytesRead = mInputStream.read(buffer); 55 | if (bytesRead == -1) 56 | break; // End of stream is reached --> exit the thread 57 | 58 | CipherOutputStream cipherOutputStream = sessionEncrypter.openCipherOutputStream(mOutputStream); 59 | cipherOutputStream.write(buffer, 0, bytesRead); 60 | // } else { 61 | // CipherInputStream cipherInputStream = sessionDecrypter.openCipherInputStream(mInputStream); 62 | // int bytesRead = cipherInputStream.read(buffer); 63 | // if (bytesRead == -1) 64 | // break; // End of stream is reached --> exit the thread 65 | // mOutputStream.write(buffer, 0, bytesRead); 66 | // mOutputStream.flush(); 67 | // } 68 | //UNCOMMENT THIS TO TEST THAT ENCRYPTION IS WORKING 69 | } else { 70 | int bytesRead = mInputStream.read(buffer); 71 | if (bytesRead == -1) 72 | break; 73 | 74 | mOutputStream.write(buffer, 0, bytesRead); 75 | } 76 | } 77 | } catch (Exception e) { 78 | // Read/write failed --> connection is broken --> exit the thread 79 | e.printStackTrace(); 80 | } 81 | 82 | // Notify parent thread that the connection is broken and forwarding should stop 83 | mParent.connectionBroken(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/meta/Arguments.java: -------------------------------------------------------------------------------- 1 | package meta; 2 | 3 | import java.util.Properties; 4 | 5 | /** 6 | * Process command line arguments of the form "--argument=value". 7 | * Store arguments as in meta.Arguments object (derived from Properties). 8 | * 9 | */ 10 | 11 | public class Arguments extends Properties { 12 | public void setDefault(String arg, String value) { 13 | this.setProperty(arg, value); 14 | } 15 | public void loadArguments(String args[]) throws IllegalArgumentException { 16 | for(String argument : args) { 17 | if(!argument.startsWith("--")) { 18 | throw new IllegalArgumentException("Argument does not start with \"--\""); 19 | } 20 | 21 | String[] keyValue = argument.substring(2).split("=", 2); 22 | 23 | if(keyValue.length != 2 || keyValue[1].length() < 1) { 24 | throw new IllegalArgumentException("Argument \"" + keyValue[0] + "\"%s needs a value"); 25 | } 26 | this.setProperty(keyValue[0], keyValue[1]); 27 | } 28 | } 29 | 30 | public String get(String arg) { 31 | return this.getProperty(arg); 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /src/meta/Common.java: -------------------------------------------------------------------------------- 1 | package meta; 2 | 3 | public class Common { 4 | public static final int KEY_LENGTH = 128; 5 | 6 | //PARAMETERS FOR HANDSHAKE MESSAGES 7 | public static final String MESSAGE_TYPE = "MessageType"; 8 | public static final String CLIENT_HELLO = "ClientHello"; 9 | public static final String SERVER_HELLO = "ServerHello"; 10 | public static final String FORWARD_MSG = "Forward"; 11 | public static final String CERTIFICATE = "Certificate"; 12 | public static final String TARGET_HOST = "TargetHost"; 13 | public static final String TARGET_PORT = "TargetPort"; 14 | public static final String SESSION_MSG = "Session"; 15 | public static final String SESSION_KEY = "SessionKey"; 16 | public static final String SESSION_IV = "SessionIV"; 17 | 18 | //PATHS TO CERT FILES 19 | public static final String CA_PATH = "certs/new/CA.pem"; 20 | public static final String SERVER_CERT_PATH = "certs/new/server.pem"; 21 | public static final String CLIENT_CERT_PATH = "certs/new/client.pem"; 22 | public static final String CLIENT_PRIV_KEY_PATH = "certs/new/clientprivatekey.der"; 23 | } 24 | -------------------------------------------------------------------------------- /src/meta/Logger.java: -------------------------------------------------------------------------------- 1 | package meta; 2 | 3 | public class Logger { 4 | private static final boolean ENABLE_LOGGING = true; 5 | public static void log(String message) { 6 | if (ENABLE_LOGGING) { 7 | System.out.println(message); 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/meta/tests/HandshakeCryptoTester.java: -------------------------------------------------------------------------------- 1 | package meta.tests; 2 | 3 | public class HandshakeCryptoTester { 4 | static String PRIVATEKEYFILE = "certs/CAPrivateKey.der"; 5 | static String CERTFILE = "certs/CA.pem"; 6 | static String PLAINTEXT = "Time flies like an arrow. Fruit flies like a banana."; 7 | static String ENCODING = "UTF-8"; 8 | 9 | /* For converting between strings and byte arrays */ 10 | static public void run() throws Exception { 11 | // /* Extract key pair */ 12 | // PublicKey publickey = HandshakeCrypto.getPublicKeyFromCertFile(CERTFILE); 13 | // PrivateKey privatekey = HandshakeCrypto.getPrivateKeyFromKeyFile(PRIVATEKEYFILE); 14 | // 15 | // /* Encode string as bytes */ 16 | // byte[] plaininputbytes = PLAINTEXT.getBytes(ENCODING); 17 | // /* Encrypt it */ 18 | // byte[] cipher = HandshakeCrypto.encrypt(plaininputbytes, publickey); 19 | // /* Then decrypt back */ 20 | // byte[] plainoutputbytes = HandshakeCrypto.decrypt(cipher, privatekey); 21 | // 22 | // /* Decode bytes into string */ 23 | // String plainoutput = new String(plainoutputbytes, ENCODING); 24 | // 25 | // if (plainoutput.equals(PLAINTEXT)) { 26 | // System.out.println("Pass. Input and output strings are the same: \"" + PLAINTEXT + "\""); 27 | // } else { 28 | // System.out.println("Fail. Expected \"" + PLAINTEXT + "\", but got \"" + plainoutput + "\'"); 29 | // } 30 | } 31 | } -------------------------------------------------------------------------------- /src/meta/tests/TestSessionCrypto.java: -------------------------------------------------------------------------------- 1 | package meta.tests; 2 | 3 | import communication.session.SessionDecrypter; 4 | import communication.session.SessionEncrypter; 5 | 6 | import java.io.*; 7 | import javax.crypto.*; 8 | 9 | public class TestSessionCrypto { 10 | static String PLAININPUT = "plaininput"; 11 | static String PLAINOUTPUT = "plainoutput"; 12 | static String CIPHER = "cipher"; 13 | static Integer KEYLENGTH = 128; 14 | 15 | public void run() throws Exception { 16 | int b; 17 | /* 18 | // Create encrypter instance for a given key length 19 | SessionEncrypter sessionEncrypter = new SessionEncrypter(KEYLENGTH); 20 | 21 | // Attach output file to encrypter, and open input file 22 | try ( 23 | CipherOutputStream cryptoOut = sessionEncrypter.openCipherOutputStream(new FileOutputStream(CIPHER)); 24 | FileInputStream plainIn = new FileInputStream(PLAININPUT); 25 | ) { 26 | // Copy data byte by byte from plain input to crypto output via encrypter 27 | while ((b = plainIn.read()) != -1) { 28 | cryptoOut.write(b); 29 | } 30 | } 31 | 32 | // Now ciphertext is in cipher output file. Decrypt it back to plaintext. 33 | 34 | // Create decrypter instance using cipher parameters from encrypter 35 | SessionDecrypter sessionDecrypter = new SessionDecrypter(sessionEncrypter.encodeKey(), sessionEncrypter.encodeIV()); 36 | 37 | // Attach input file to decrypter, and open output file 38 | try ( 39 | CipherInputStream cryptoIn = sessionDecrypter.openCipherInputStream(new FileInputStream(CIPHER)); 40 | FileOutputStream plainOut = new FileOutputStream(PLAINOUTPUT); 41 | ) { 42 | // Copy data byte by byte from cipher input to plain output via decrypter 43 | while ((b = cryptoIn.read()) != -1) { 44 | plainOut.write(b); 45 | } 46 | } 47 | */ 48 | System.out.format("Encryption and decryption done. Check that \"%s\" and \"%s\" are identical!\n", PLAININPUT, PLAINOUTPUT); 49 | } 50 | } -------------------------------------------------------------------------------- /src/meta/tests/VPNTester.java: -------------------------------------------------------------------------------- 1 | package meta.tests; 2 | 3 | import communication.handshake.aCertificate; 4 | 5 | import java.security.cert.X509Certificate; 6 | 7 | public class VPNTester { 8 | public void main(String[] args) { 9 | testaCertificate(); 10 | /*test();*/ 11 | } 12 | 13 | private void testaCertificate() { 14 | String testS = "-----BEGIN CERTIFICATE-----\n" + 15 | "MIIDjjCCAnYCCQDjsWS7GJ4tMjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC\n" + 16 | "U0UxEjAQBgNVBAgMCVN0b2NraG9sbTEOMAwGA1UEBwwFS2lzdGExDDAKBgNVBAoM\n" + 17 | "A0tUaDEMMAoGA1UECwwDSUNUMRowGAYDVQQDDBFJbnRlcm5ldCBTZWN1cml0eTEd\n" + 18 | "MBsGCSqGSIb3DQEJARYObG91aXNjYkBrdGguc2UwHhcNMTgxMTI3MjI0ODQwWhcN\n" + 19 | "MjEwODIzMjI0ODQwWjCBiDELMAkGA1UEBhMCU0UxEjAQBgNVBAgMCVN0b2NraG9s\n" + 20 | "bTEOMAwGA1UEBwwFS2lzdGExDDAKBgNVBAoMA0tUaDEMMAoGA1UECwwDSUNUMRow\n" + 21 | "GAYDVQQDDBFJbnRlcm5ldCBTZWN1cml0eTEdMBsGCSqGSIb3DQEJARYObG91aXNj\n" + 22 | "YkBrdGguc2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCgmCBuI2g8\n" + 23 | "19D3S4PLmvOpw1b8+nCEYm1ts5wlp4whL5DT81Y4rE+iPSY8IkExm7/BQk7PR1Yg\n" + 24 | "rWbI/Wi0NDYauTZyArW4TO0beL46/wgd0PHVB8OB89JkQbpgGDhYQAP2obGpgURe\n" + 25 | "hJVO608T3ZksEZm2pNLC1OprywG86f8Ojows8WYlU74p6j0MrqmBOYxJPbq3OLs5\n" + 26 | "IgnnJXtdAkW9RdlCmZ2XElYwAbQrytVtvCtsSh1bLz4jcqycUUO7m9+YVD5+PKfO\n" + 27 | "0hnttgfyDphIYexgliXGSeM+sz61Nz8kTqYL3X6CutObDHGJt8D8mcZViM2XDUkA\n" + 28 | "qpOno48SvFMNAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAI/RqXFBxFRID85oQZe7\n" + 29 | "6qPeok+DaP9LG8NvnpdcXlweJ2bFJzuLXfNq0CmdGNgASQOv5HpMPz5tfZxm3mYt\n" + 30 | "Xhxy6Jwu1iukvtAiJenRQFjaeSgg11ORZZ9DjuxUUZaKIi+eYMZV0fSniGOBW8DJ\n" + 31 | "bCZX85Wrl8JAdG2O0UUiq43t7fiCAIe7mVcfhYKIKsRqUl7er5O9bJsiviskrmBV\n" + 32 | "v6b7GEniLaoyUzMM4zxEpcyIFumDavPQcMrPcHbTmGzTatlBCRMppR8EseJbjkcJ\n" + 33 | "xyUTDOon+O3w/SnmxVYNcvxoQdaBa4QY2Vik/8gL9zogJlUEFqGUKIICJ6MdSJy9\n" + 34 | "mL4=\n" + 35 | "-----END CERTIFICATE-----\n"; 36 | try { 37 | X509Certificate cert = aCertificate.stringToCert(testS); 38 | 39 | System.out.println(cert.getIssuerDN()); 40 | 41 | System.out.println(aCertificate.stringToCert(aCertificate.encodeCert(cert)).getIssuerDN()); 42 | } catch (Exception e) { 43 | e.printStackTrace(); 44 | } 45 | 46 | } 47 | /* 48 | private static void test() { 49 | System.out.println(" - Test 1: - "); 50 | testSessionKey(); 51 | System.out.println(); 52 | 53 | System.out.println(" - Test 2: - "); 54 | testSessionCrypto(); 55 | System.out.println(); 56 | 57 | System.out.println(" - Test 3: - "); 58 | testVerifyCertificate(); 59 | System.out.println(); 60 | 61 | System.out.println(" - Test 4: - "); 62 | testHandshakeCrypto(); 63 | } 64 | 65 | private static void testVerifyCertificate() { 66 | HandleCertificate vC = new HandleCertificate("certs/CA.pem", "certs/user.pem"); 67 | vC.verify(); 68 | } 69 | 70 | private static void testSessionKey() { 71 | SessionKey key1 = new SessionKey(128); 72 | SessionKey key2 = new SessionKey(key1.encodeKey()); 73 | 74 | if (key1.getSecretKey().equals(key2.getSecretKey())) { 75 | System.out.println("The keys match"); 76 | } else { 77 | System.out.println("The keys do not match"); 78 | } 79 | } 80 | 81 | private static void testSessionCrypto() { 82 | TestSessionCrypto testSessionCrypto = new TestSessionCrypto(); 83 | 84 | try { 85 | testSessionCrypto.encrypt(); 86 | } catch (Exception e) { 87 | e.printStackTrace(); 88 | } 89 | } 90 | 91 | private static void testHandshakeCrypto() { 92 | try { 93 | HandshakeCryptoTester.encrypt(); 94 | } catch (Exception e) { 95 | e.printStackTrace(); 96 | } 97 | 98 | }*/ 99 | } 100 | -------------------------------------------------------------------------------- /src/server/ForwardServer.java: -------------------------------------------------------------------------------- 1 | package server; /** 2 | * Port forwarding server. Forward data 3 | * between two TCP ports. Based on Nakov TCP Socket Forward Server 4 | * and adapted for IK2206. 5 | * 6 | * Original copyright notice below. 7 | * (c) 2018 Peter Sjodin, KTH 8 | */ 9 | 10 | /** 11 | * Nakov TCP Socket Forward Server - freeware 12 | * Version 1.0 - March, 2002 13 | * (c) 2001 by Svetlin Nakov - http://www.nakov.com 14 | */ 15 | 16 | import communication.handshake.*; 17 | import communication.session.IV; 18 | import communication.session.SessionKey; 19 | import meta.Arguments; 20 | import meta.Common; 21 | import meta.Logger; 22 | import communication.threads.ForwardServerClientThread; 23 | 24 | import java.lang.Integer; 25 | import java.net.ServerSocket; 26 | import java.net.Socket; 27 | import java.net.InetSocketAddress; 28 | import java.net.UnknownHostException; 29 | import java.io.IOException; 30 | import java.security.cert.X509Certificate; 31 | 32 | public class ForwardServer 33 | { 34 | private static final boolean ENABLE_LOGGING = true; 35 | public static final int DEFAULTSERVERPORT = 2206; 36 | public static final String DEFAULTSERVERHOST = "localhost"; 37 | public static final String PROGRAMNAME = "server.ForwardServer"; 38 | private static Arguments arguments; 39 | 40 | private ServerSocket handshakeSocket; 41 | 42 | private ServerSocket listenSocket; 43 | private String targetHost; 44 | private int targetPort; 45 | 46 | /** 47 | * Program entry point. Reads settings, starts check-alive thread and 48 | * the forward server 49 | */ 50 | public static void main(String[] args) throws Exception { 51 | arguments = new Arguments(); 52 | arguments.setDefault("handshakeport", Integer.toString(DEFAULTSERVERPORT)); 53 | arguments.setDefault("handshakehost", DEFAULTSERVERHOST); 54 | arguments.loadArguments(args); 55 | 56 | ForwardServer srv = new ForwardServer(); 57 | try { 58 | srv.startForwardServer(); 59 | } catch (Exception e) { 60 | e.printStackTrace(); 61 | } 62 | } 63 | 64 | /** 65 | * Starts the forward server - binds on a given port and starts serving 66 | */ 67 | public void startForwardServer() throws Exception { 68 | // Bind server on given TCP port 69 | int port = Integer.parseInt(arguments.get("handshakeport")); 70 | String address = arguments.get("handshakehost"); 71 | 72 | try { 73 | handshakeSocket = new ServerSocket(port); 74 | } catch (IOException ioe) { 75 | throw new IOException("Unable to bind to port " + port); 76 | } 77 | 78 | log("Forward Server started at address " + address + " on TCP port " + port); 79 | log("Waiting for connections..."); 80 | 81 | // Accept client connections and process them until stopped 82 | while(true) { 83 | ForwardServerClientThread forwardThread; 84 | 85 | try { 86 | doHandshake(); 87 | setUpSession(); 88 | //This creates communication channel between server and target 89 | //pass in session key and iv to forward server 90 | forwardThread = new ForwardServerClientThread(this.listenSocket, this.targetHost, this.targetPort); 91 | forwardThread.start(); 92 | } catch (IOException e) { 93 | throw e; 94 | } 95 | } 96 | } 97 | 98 | /** 99 | * Do communication.asdf.handshake negotiation with client to authenticate, learn 100 | * target host/port, etc. 101 | */ 102 | private void doHandshake() throws UnknownHostException, IOException, Exception { 103 | Socket clientSocket = handshakeSocket.accept(); 104 | String clientHostPort = clientSocket.getInetAddress().getHostAddress() + ":" + clientSocket.getPort(); 105 | Logger.log("Incoming handshake connection from " + clientHostPort); 106 | 107 | //Step 2, get ClientHello and verify 108 | 109 | HandshakeMessage clientHello = new HandshakeMessage(); 110 | clientHello.recv(clientSocket); 111 | 112 | if (!clientHello.getParameter("MessageType").equals("ClientHello")) { 113 | System.err.println("Received invalid handshake type!"); 114 | clientSocket.close(); 115 | throw new Error(); 116 | } 117 | 118 | //get client certificate from message 119 | String clientCertString = clientHello.getParameter("Certificate"); 120 | X509Certificate clientCert = aCertificate.stringToCert(clientCertString); 121 | 122 | //verify certificate is signed by our CA 123 | HandleCertificate handleCertificate = new HandleCertificate(arguments.get("cacert")); 124 | 125 | if (!handleCertificate.verify(clientCert)) { 126 | System.err.println("CLIENT CA FAILED VERIFICATION"); 127 | clientSocket.close(); 128 | throw new Error(); 129 | } else { 130 | //Logger.log("Successful verification of client certificate..."); 131 | } 132 | 133 | // Client is verified, proceed to sending ServerHello 134 | HandshakeMessage serverHello = new HandshakeMessage(); 135 | serverHello.putParameter(Common.MESSAGE_TYPE, Common.SERVER_HELLO); 136 | serverHello.putParameter(Common.CERTIFICATE, aCertificate.encodeCert(aCertificate.pathToCert(arguments.get("usercert")))); 137 | serverHello.send(clientSocket); 138 | 139 | //step 6 server receives ForwardMessage from client and sets up session 140 | HandshakeMessage forwardMessage = new HandshakeMessage(); 141 | forwardMessage.recv(clientSocket); 142 | 143 | if (!forwardMessage.getParameter(Common.MESSAGE_TYPE).equals(Common.FORWARD_MSG)) { 144 | System.err.println("Received invalid message type! Should be forward message"); 145 | clientSocket.close(); 146 | throw new Error(); 147 | } 148 | 149 | //Set client's desired target as handshake target 150 | Handshake.setTargetHost(forwardMessage.getParameter(Common.TARGET_HOST)); 151 | Handshake.setTargetPort(Integer.parseInt(forwardMessage.getParameter(Common.TARGET_PORT))); 152 | 153 | //step 6.1 generate session key and iv 154 | SessionKey sessionKey = new SessionKey(Common.KEY_LENGTH); 155 | IV iv = new IV(); 156 | 157 | Handshake.sessionKey = sessionKey; 158 | Handshake.iv = iv; 159 | 160 | //step 6.2 encrypt secretKey and IV with client's public key 161 | String encryptedSessionKey = AsymmetricCrypto.encrypt(sessionKey.encodeKey(), clientCert.getPublicKey()); 162 | String encryptedIV = AsymmetricCrypto.encrypt(iv.encodeIV(), clientCert.getPublicKey()); 163 | 164 | //step 7 Server sends client session message 165 | HandshakeMessage sessionMsg = new HandshakeMessage(); 166 | sessionMsg.putParameter(Common.MESSAGE_TYPE, Common.SESSION_MSG); 167 | sessionMsg.putParameter(Common.SESSION_KEY, encryptedSessionKey); 168 | sessionMsg.putParameter(Common.SESSION_IV, encryptedIV); 169 | 170 | sessionMsg.send(clientSocket); 171 | 172 | clientSocket.close(); 173 | log("Handshake successful!"); 174 | 175 | //end of handshake 176 | } 177 | 178 | private void setUpSession() throws UnknownHostException, IOException, Exception { 179 | /* 180 | * Fake the handshake result with static parameters. 181 | */ 182 | 183 | /* listenSocket is a new socket where the ForwardServer waits for the 184 | * client to connect. The ForwardServer creates this socket and communicates 185 | * the socket's address to the ForwardClient during the handshake, so that the 186 | * ForwardClient knows to where it should connect (ServerHost/ServerPort parameters). 187 | * Here, we use a static address instead (serverHost/serverPort). 188 | * (This may give "Address already in use" errors, but that's OK for now.) 189 | */ 190 | 191 | /* 192 | listenSocket is the session communication channel 193 | */ 194 | listenSocket = new ServerSocket(); 195 | listenSocket.bind(new InetSocketAddress(Handshake.serverHost, Handshake.serverPort)); 196 | 197 | /* The final destination. The ForwardServer sets up port forwarding 198 | * between the listensocket (ie., ServerHost/ServerPort) and the target. 199 | */ 200 | targetHost = Handshake.targetHost; 201 | targetPort = Handshake.targetPort; 202 | } 203 | 204 | /** 205 | * Prints given log message on the standart output if logging is enabled, 206 | * otherwise ignores it 207 | */ 208 | public void log(String aMessage) { 209 | if (ENABLE_LOGGING) 210 | System.out.println(aMessage); 211 | } 212 | 213 | static void usage() { 214 | String indent = ""; 215 | System.err.println(indent + "Usage: " + PROGRAMNAME + " options"); 216 | System.err.println(indent + "Where options are:"); 217 | indent += " "; 218 | System.err.println(indent + "--serverhost="); 219 | System.err.println(indent + "--serverport="); 220 | System.err.println(indent + "--usercert="); 221 | System.err.println(indent + "--cacert="); 222 | System.err.println(indent + "--key="); 223 | } 224 | } --------------------------------------------------------------------------------