Download | Plain Text | No Line Numbers
- /*
- * Copyright (c) 2010, Manuel Mausz. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * - The names of the authors may not be used to endorse or promote products
- * derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
- * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
- * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-
- import java.util.Arrays;
- import java.util.MissingResourceException;
-
- import javax.crypto.*;
- import java.security.*;
- import org.bouncycastle.openssl.PEMReader;
- import org.bouncycastle.util.encoders.Base64;
- import org.bouncycastle.openssl.PasswordFinder;
-
- import java.net.*;
- import java.io.*;
-
- /*
- * Client implementation for Lab#1 of DSLab WS10
- * See angabe.pdf for details
- *
- * This code is not documented at all. This is volitional
- *
- * @author Manuel Mausz (0728348)
- */
- public class Client
- {
- public class ProxyConnection
- extends CommandNetwork
- implements Runnable
- {
- private final Utils.EncObjectInputStream oin;
- private final Utils.EncObjectOutputStream oout;
-
- ProxyConnection(Utils.EncObjectInputStream oin, Utils.EncObjectOutputStream oout,
- throws NoSuchMethodException
- {
- this.oin = oin;
- this.oout = oout;
- this.sharedFolder = sharedFolder;
- this.mainLock = mainLock;
- this.intLock = intLock;
-
- cmdHandler.register("!error", this, "cmdError");
- cmdHandler.register("!output", this, "cmdOutput");
- cmdHandler.register("!download", this, "cmdDownload");
- cmdHandler.register("!upload", this, "cmdUpload");
- cmdHandler.register("!ok", this, "cmdOk");
- cmdHandler.register("unknown", this, "cmdUnknown");
- }
-
- /*------------------------------------------------------------------------*/
-
- public void notifyInteractive()
- {
- synchronized(intLock)
- {
- intLock.notify();
- }
- }
-
- /*------------------------------------------------------------------------*/
-
- throws IOException
- {
- long num;
- if ((num = Utils.parseHeaderNum(args, 0)) < 0)
- return;
-
- String msg;
- for (; num > 0 && (msg = oin.readLine()) != null; --num)
- out.println(msg.substring(1));
- notifyInteractive();
- }
-
- /*------------------------------------------------------------------------*/
-
- throws IOException
- {
- cmdPrintOutput(err, cmd, args);
- }
-
- /*------------------------------------------------------------------------*/
-
- throws IOException
- {
- cmdPrintOutput(out, cmd, args);
- }
-
- /*------------------------------------------------------------------------*/
-
- {
- if (args.length < 2 || args[1].length() <= 0)
- {
- err.println("Error: Invalid " + cmd + "-command paket from proxy. Ignoring...");
- notifyInteractive();
- return;
- }
-
- long filesize;
- if ((filesize = Utils.parseHeaderNum(args, 1)) < 0)
- return;
-
- file.delete();
- try
- {
- try
- {
- }
- {
- err.println("Error: Unable to write to file '" + file + "': " + e.getMessage());
- }
-
- byte[] buffer = new byte[8 * 1024];
- int toread = buffer.length;
- while(filesize > 0)
- {
- if (filesize < toread)
- toread = (int) filesize;
- int count = oin.read(buffer, 0, toread);
- if (count == -1)
-
- if (fout != null)
- {
- /* decode that chunk */
- byte[] decbuffer = new byte[aesdecrypt.getOutputSize(count)];
- int deccount = aesdecrypt.update(buffer, 0, count, decbuffer);
- fout.write(decbuffer, 0, deccount);
- }
- filesize -= count;
- }
- if (fout != null)
- {
- /* decryption must be finalized */
- byte[] decbuffer = aesdecrypt.doFinal();
- fout.write(decbuffer);
- }
- }
- {
- err.println("Error: Error during file transfer: " + e.getMessage());
- }
- {
- err.println("Error: Error during decrypting file transfer: " + e.getMessage());
- }
-
- notifyInteractive();
- }
-
- /*------------------------------------------------------------------------*/
-
- {
- if (args.length < 1 || args[0].length() <= 0)
- {
- err.println("Error: Invalid " + cmd + "-command paket from proxy. Ignoring...");
- notifyInteractive();
- return;
- }
-
- if (!file.exists() || !file.getParent().equals(sharedFolder))
- {
- err.println("Error: File '" + args[0] + "' does not exist");
- notifyInteractive();
- return;
- }
- if (!file.isFile())
- {
- err.println("Error: File '" + args[0] + "' is not a file");
- notifyInteractive();
- return;
- }
- if (!file.canRead())
- {
- err.println("Error: File '" + args[0] + "' is not readable");
- notifyInteractive();
- return;
- }
-
- long filesize = file.length();
- try
- {
-
- oout.writeLine("!upload2 " + file.getName() + " " + filesize);
-
- byte[] buffer = new byte[8 * 1024];
- int toread = buffer.length;
- while(filesize > 0)
- {
- if (filesize < toread)
- toread = (int) filesize;
- int count = fin.read(buffer, 0, toread);
- if (count == -1)
-
- /* encode that chunk */
- byte[] encbuffer = new byte[aesencrypt.getOutputSize(count)];
- int enccount = aesencrypt.update(buffer, 0, count, encbuffer);
-
- oout.write(encbuffer, 0, enccount);
- filesize -= count;
- }
- /* encryption must be finalized */
- oout.write(aesencrypt.doFinal());
- oout.flush();
- }
- {
- err.println("Error: File '" + args[0] + "' is not readable");
- notifyInteractive();
- return;
- }
- {
- err.println("Error during file transfer: " + e.getMessage());
- notifyInteractive();
- return;
- }
- {
- err.println("Error during encrypting file transfer: " + e.getMessage() + "");
- notifyInteractive();
- return;
- }
- }
-
- /*------------------------------------------------------------------------*/
-
- throws IOException
- {
- if (args.length != 4)
- {
- err.println("Error: Invalid " + cmd + "-command paket from proxy. Ignoring...");
- notifyInteractive();
- return;
- }
-
- if (!args[0].equals(mychallenge))
- {
- err.println("Error: Received invalid challenge from proxy. Ignoring...");
- notifyInteractive();
- return;
- }
-
- assert secondMessage.matches("!ok [" + B64 + "]{43}= [" + B64 + "]{43}= [" + B64 + "]{43}= [" + B64 + "]{22}==") : "2nd message";
-
- javax.crypto.spec.SecretKeySpec seckey = new javax.crypto.spec.SecretKeySpec(Base64.decode(args[2].getBytes()), "AES");
- javax.crypto.spec.IvParameterSpec iv = new javax.crypto.spec.IvParameterSpec(Base64.decode(args[3].getBytes()));
-
- try
- {
- aesencrypt.init(Cipher.ENCRYPT_MODE, seckey, iv);
- aesdecrypt.init(Cipher.DECRYPT_MODE, seckey, iv);
- }
- {
- err.println("Error: invalid AES key: " + e.getMessage());
- notifyInteractive();
- return;
- }
- {
- err.println("Error: invalid AES parameter: " + e.getMessage());
- notifyInteractive();
- return;
- }
-
- oout.setCipher(aesencrypt);
- oin.setCipher(aesdecrypt);
-
- assert thirdMessage.matches("[" + B64 + "]{43}=") : "3rd message";
- oout.writeLine(thirdMessage);
- oout.flush();
- loggedin = true;
- }
-
- /*------------------------------------------------------------------------*/
-
- {
- err.println("Error: Unknown data from proxy: " + cmd + " "
- notifyInteractive();
- }
-
- /*------------------------------------------------------------------------*/
-
- public void run()
- {
- try
- {
- run(oin);
- }
- {
- err.println("Internal Error: " + e.getMessage());
- }
- {
- /* ignore that exception
- * thread will shutdown and unlock the main thread
- * which will shutdown the application
- */
- }
-
- notifyInteractive();
- synchronized(mainLock)
- {
- mainLock.notify();
- }
- }
- }
-
- /*==========================================================================*/
-
- public class Interactive
- extends CommandInteractive
- implements Runnable
- {
- private final Utils.EncObjectInputStream oin;
- private final Utils.EncObjectOutputStream oout;
-
- throws NoSuchMethodException
- {
- this.sin = sin;
- this.mainLock = mainLock;
- this.intLock = intLock;
- this.oin = oin;
- this.oout = oout;
-
- cmdHandler.register("unknown", this, "cmdUnknown");
- cmdHandler.register("!login", this, "cmdLogin");
- cmdHandler.register("!exit", this, "cmdExit");
- }
-
- /*------------------------------------------------------------------------*/
-
- public void waitForSocket()
- {
- synchronized(intLock)
- {
- try
- {
- intLock.wait(1000);
- }
- {
- /* if we get interrupted -> ignore */
- }
- }
- }
-
- /*------------------------------------------------------------------------*/
-
- throws IOException
- {
- if (!loggedin)
- {
- err.println("Not logged in");
- return;
- }
-
- oout.flush();
- waitForSocket();
- }
-
- /*------------------------------------------------------------------------*/
-
- throws IOException
- {
- if (args.length != 1)
- {
- err.println("Invalid Syntax: !login <username>");
- return;
- }
- if (loggedin)
- {
- err.println("Already logged in");
- return;
- }
-
- /* read users private key */
- if (!pemfile.isFile())
- {
- err.println("Error: Private keyfile '" + pemfile + "' doesn't exist.");
- return;
- }
- if (!pemfile.canRead())
- {
- err.println("Error: Private keyfile '" + pemfile + "' is not readable.");
- return;
- }
-
- try
- {
- {
- @Override
- public char[] getPassword()
- {
- try
- {
- /* reads the password from standard input for decrypting the private key */
- out.println("Enter pass phrase: ");
- }
- {
- char[] tmp = {};
- return tmp;
- }
- }
- });
- privateKey = keyPair.getPrivate();
- rsadecrypt.init(Cipher.DECRYPT_MODE, privateKey);
- oin.setCipher(rsadecrypt);
- }
- {
- err.println("Error while reading private key of " + user + ". Unable to read keyfile.");
- return;
- }
- {
- err.println("Error while reading private key of " + user + ". Maybe wrong pass phrase.");
- return;
- }
- {
- err.println("Error: invalid key file: " + e.getMessage());
- return;
- }
-
- /* generates a 32 byte secure random number */
- byte[] tmp = new byte[32];
- secureRandom.nextBytes(tmp);
-
- assert firstMessage.matches("!login \\w+ [" + B64 + "]{43}=") : "1st message";
-
- oout.writeLine(firstMessage);
- oout.flush();
- waitForSocket();
- }
-
- /*------------------------------------------------------------------------*/
-
- {
- stop();
- }
-
- /*------------------------------------------------------------------------*/
-
- public void printPrompt()
- {
- out.print(">: ");
- out.flush();
- }
-
- /*------------------------------------------------------------------------*/
-
- public void shutdown()
- {
- try
- {
- oout.flush();
- }
- {}
- }
-
- /*------------------------------------------------------------------------*/
-
- public void run()
- {
- try
- {
- run(sin);
- }
- {
- err.println("Internal Error: " + e.getMessage());
- }
- {
- /* ignore that exception
- * thread will shutdown and unlock the main thread
- * which will shutdown the application
- */
- }
-
- shutdown();
- synchronized(mainLock)
- {
- mainLock.notify();
- }
- }
- }
-
- /*==========================================================================*/
-
- private static int proxyTCPPort;
- private Utils.EncObjectInputStream oin = null;
- private Utils.EncObjectOutputStream oout = null;
-
- private Cipher rsaencrypt, rsadecrypt;
- private Cipher aesencrypt, aesdecrypt;
- private volatile boolean loggedin = false;
-
- /*--------------------------------------------------------------------------*/
-
- public Client()
- {
- try
- {
- rsaencrypt = Cipher.getInstance("RSA/NONE/OAEPWithSHA256AndMGF1Padding");
- rsadecrypt = Cipher.getInstance("RSA/NONE/OAEPWithSHA256AndMGF1Padding");
- aesencrypt = Cipher.getInstance("AES/CTR/NoPadding");
- aesdecrypt = Cipher.getInstance("AES/CTR/NoPadding");
- }
- {
- bailout("Unable to initialize cipher: " + e.getMessage());
- }
- catch(NoSuchPaddingException e)
- {
- bailout("Unable to initialize cipher: " + e.getMessage());
- }
- }
-
- /*--------------------------------------------------------------------------*/
-
- public static void usage()
- throws Utils.Shutdown
- {
- out.println("Usage: Client sharedFolder\n");
- out.println("sharedFolder\t...the directory to put downloaded files or to upload files from");
-
- // Java is some piece of crap which doesn't allow me to set exitcode w/o
- // using System.exit. Maybe someday Java will be a fully functional
- // programming language, but I wouldn't bet my money
- //System.exit(1);
- throw new Utils.Shutdown("FUCK YOU JAVA");
- }
-
- /*--------------------------------------------------------------------------*/
-
- throws Utils.Shutdown
- {
- err.println("Error: " + error);
- shutdown();
-
- // Java is some piece of crap which doesn't allow me to set exitcode w/o
- // using System.exit. Maybe someday Java will be a fully functional
- // programming language, but I wouldn't bet my money
- //System.exit(2);
- throw new Utils.Shutdown("FUCK YOU JAVA");
- }
-
- /*--------------------------------------------------------------------------*/
-
- {
- if (args.length != 1)
- usage();
-
- sharedFolder = args[0];
- if (!dldir.isDirectory())
- bailout("sharedFolder '" + sharedFolder + "' is not a directory");
- if (!dldir.canWrite())
- bailout("sharedFolder '" + sharedFolder + "' is not writeable");
- }
-
- /*--------------------------------------------------------------------------*/
-
- public void parseConfig()
- {
- Config config = null;
- try
- {
- config = new Config("client");
- }
- {
- bailout("configuration file doesn't exist or isn't readable");
- }
-
- try
- {
- directive = "proxy.host";
- proxyHost = config.getString(directive);
- if (proxyHost.length() == 0)
- bailout("configuration directive '" + directive + "' is empty or invalid");
-
- directive = "proxy.tcp.port";
- proxyTCPPort = config.getInt(directive);
- if (proxyTCPPort <= 0 || proxyTCPPort > 65536)
- bailout("configuration directive '" + directive + "' must be a valid port number (1 - 65535)");
-
- directive = "proxy.key";
- proxyKey = config.getString(directive);
- if (!key.isFile())
- bailout("configuration directive '" + directive + "' is not a file");
- if (!key.canRead())
- bailout("configuration directive '" + directive + "' is not readable");
- rsaencrypt.init(Cipher.ENCRYPT_MODE, publicKey);
-
- directive = "keys.dir";
- keysDir = config.getString(directive);
- if (!dir.isDirectory())
- bailout("configuration directive '" + directive + "' is not a directory");
- if (!dir.canRead())
- bailout("configuration directive '" + directive + "' is not readable");
- }
- {
- bailout("unable to read file of directive '" + directive + "'");
- }
- {
- bailout("while reading proxy public key");
- }
- {
- bailout("invalid key file: " + e.getMessage());
- }
- {
- bailout("configuration directive '" + directive + "' is not set");
- }
- {
- bailout("configuration directive '" + directive + "' must be numeric");
- }
- }
-
- /*--------------------------------------------------------------------------*/
-
- public void shutdown()
- {
- try
- {
- if (sockin != null)
- sockin.close();
- }
- {}
-
- try
- {
- if (sockout != null)
- sockout.close();
- }
- {}
-
- try
- {
- if (sock != null && !sock.isClosed())
- sock.close();
- }
- {}
-
- try
- {
- if (tPConnection != null)
- tPConnection.join();
- }
- {}
-
- try
- {
- if (tInteractive != null)
- {
- tInteractive.interrupt();
- tInteractive.join();
- }
- }
- {}
-
- try
- {
- if (stdin != null)
- stdin.close();
- }
- {}
- }
-
- /*--------------------------------------------------------------------------*/
-
- {
- parseArgs(args);
- parseConfig();
-
- try
- {
- out.println("Connecting to " + proxyHost + ":" + proxyTCPPort + "...");
- sockin = sock.getInputStream();
- sockout = sock.getOutputStream();
- out.println("Connected...");
- }
- {
- bailout("Unable to resolve hostname: " + e.getMessage());
- }
- {
- bailout("Unable to connect to proxy: " + e.getMessage());
- }
-
- synchronized(mainLock)
- {
- try
- {
- oout = new Utils.EncObjectOutputStream(sockout);
- oout.setCipher(rsaencrypt);
- }
- {
- bailout("Unable to create object stream: " + e.getMessage());
- }
-
- try
- {
- sharedFolder, mainLock, interactiveLock));
- tPConnection.start();
- }
- {
- bailout("Unable to setup remote command handler");
- }
-
- try
- {
- mainLock, interactiveLock));
- tInteractive.start();
- }
- {
- bailout("Unable to setup interactive command handler");
- }
-
- out.println("Client startup successful!");
- try
- {
- mainLock.wait();
- }
- {
- /* if we get interrupted -> ignore */
- }
-
- try
- {
- /* let the threads shutdown */
- }
- {}
- }
-
- if (tPConnection != null && !tPConnection.isAlive())
- bailout("Connection to proxy closed unexpected. Terminating...");
-
- shutdown();
- }
-
- /*--------------------------------------------------------------------------*/
-
- {
- try
- {
- Client cli = new Client();
- cli.run(args);
- }
- catch(Utils.Shutdown e)
- {}
- }
- }
-