diff --git a/src/main/java/dslab/client/MessageClient.java b/src/main/java/dslab/client/MessageClient.java index 87b8201..233baf6 100644 --- a/src/main/java/dslab/client/MessageClient.java +++ b/src/main/java/dslab/client/MessageClient.java @@ -1,28 +1,143 @@ package dslab.client; -import java.io.InputStream; -import java.io.PrintStream; +import java.io.*; +import java.net.Socket; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.*; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; +import java.util.logging.Logger; import dslab.ComponentFactory; import dslab.util.Config; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; + public class MessageClient implements IMessageClient, Runnable { + private static final Logger logger = Logger.getLogger(MessageClient.class.getName()); + + private final String transferHost; + private final int transferPort; + private final String mailboxHost; + private final int mailboxPort; + + private final String transferEmail; + + private final String mailboxUser; + private final String mailboxPassword; + + private Socket dmtpSocket; + private Socket dmapSocket; + + private PrintWriter dmtpOut; + private BufferedReader dmtpIn; + private PrintWriter dmapOut; + private BufferedReader dmapIn; /** * Creates a new client instance. * * @param componentId the id of the component that corresponds to the Config resource - * @param config the component config - * @param in the input stream to read console input from - * @param out the output stream to write console output to + * @param config the component config + * @param in the input stream to read console input from + * @param out the output stream to write console output to */ public MessageClient(String componentId, Config config, InputStream in, PrintStream out) { + this.transferHost = config.getString("transfer.host"); + this.transferPort = config.getInt("transfer.port"); + this.mailboxHost = config.getString("mailbox.host"); + this.mailboxPort = config.getInt("mailbox.port"); + this.transferEmail = config.getString("transfer.email"); + this.mailboxUser = config.getString("mailbox.user"); + this.mailboxPassword = config.getString("mailbox.password"); + logger.info(String.format("TransferHost: %s\nTransferPort: %d\nMailboxHost: %s\nMailboxPort: %d\nTransferEmail: %s\nMailboxUser: %s\nMailboxPassword: %s", + transferHost, transferPort, mailboxHost, mailboxPort, transferEmail, mailboxUser, mailboxPassword)); } @Override public void run() { + try { + logger.info("Starting connection to MailboxHost on " + this.mailboxHost + " on port " + this.mailboxPort); + this.dmapSocket = new Socket(this.mailboxHost, this.mailboxPort); + this.dmapIn = new BufferedReader(new InputStreamReader(this.dmapSocket.getInputStream())); + this.dmapOut = new PrintWriter(this.dmapSocket.getOutputStream(), true); + String input = null; + if (!(input = this.dmapIn.readLine()).startsWith("ok DMAP2.0")) + shutdown(); + + while (!Thread.currentThread().isInterrupted() && ((input = this.dmapIn.readLine()) != null)) { + // TODO initiate startsecure command + // TODO login user (mailboxUser, mailboxPassword) + // startSecure() + } + } catch (IOException e) { + logger.severe("Could not connect to MailboxHost " + this.mailboxHost + " on port " + this.mailboxPort); + shutdown(); + } + + } + + private void startSecure() throws IOException { + String input; + String componentId; + PublicKey serverPublicKey; + this.dmapOut.println("startsecure"); + input = this.dmapIn.readLine(); + if (input.startsWith("ok") && (input.split("\\s+").length == 2)) { + // Get the component-id from the server + componentId = input.split("\\s+")[1]; + try { + // Attempt to read server public key from file called _pub.der + byte[] keyBytes = Files.readAllBytes(Paths.get(componentId + "_pub.der")); + logger.info("Read bytes from path " + Paths.get(componentId + ".der") + ": " + Arrays.toString(keyBytes)); + // Create X509 spec object from key + X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes); + // Create generator for RSA scheme + KeyFactory kf = KeyFactory.getInstance("RSA"); + // Make generator generate public key from X509 spec + serverPublicKey = kf.generatePublic(spec); + logger.info("Server's Public Key: " + serverPublicKey); + String clientChallenge = generateChallenge(serverPublicKey); + // TODO send clientChallenge to server + // TODO Receive AES encrypted message saying "ok " + // TODO Compare received client challenge with generated client challenge + // TODO Answer with AES encrypted "ok" if matching and use AES cipher for subsequent communication + // TODO immediately terminate upon encountering an error (pull try-catch out of submethods) + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + logger.severe(e.getMessage()); + shutdown(); + } + } + } + + public String generateChallenge(PublicKey serverPublicKey) { + SecureRandom secureRandom = new SecureRandom(); + try { + Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + cipher.init(Cipher.ENCRYPT_MODE, serverPublicKey); + // Generate new random 32 byte challenge + byte[] clearTextChallenge = new byte[32]; + secureRandom.nextBytes(clearTextChallenge); + // Generate secretKey and initialization vector + String secretKey = ""; + String IV = ""; + // Encrypt "ok " + cipher.update(("ok " + (new String(clearTextChallenge)) + " " + secretKey + " " + IV).getBytes(StandardCharsets.UTF_8)); + byte[] cipherTextChallenge = cipher.doFinal(); + return (new String(cipherTextChallenge, StandardCharsets.UTF_8)); + } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) { + logger.severe(e.getMessage()); + shutdown(); + return null; + } } @Override @@ -42,12 +157,42 @@ public class MessageClient implements IMessageClient, Runnable { @Override public void msg(String to, String subject, String data) { - + try { + logger.info("Starting connection to TransferServer on host " + this.transferHost + " on port " + this.transferPort); + this.dmtpSocket = new Socket(this.transferHost, this.transferPort); + this.dmtpIn = new BufferedReader(new InputStreamReader(this.dmtpSocket.getInputStream())); + this.dmtpOut = new PrintWriter(this.dmtpSocket.getOutputStream(), true); + } catch (IOException e) { + logger.severe("Could not connect to TransferHost " + transferHost + " on port " + transferPort); + shutdown(); + } } @Override public void shutdown() { + if (dmtpSocket != null) { + try { + this.dmtpSocket.close(); + this.dmtpOut.close(); + this.dmtpIn.close(); + } catch (IOException e) { + logger.severe("Could not close connection to TransferHost"); + e.printStackTrace(); + } + } + if (dmapSocket != null) { + try { + this.dmapSocket.close(); + this.dmapOut.close(); + this.dmapIn.close(); + } catch (IOException e) { + logger.severe("Could not close connection to MailboxHost"); + e.printStackTrace(); + } + } + + Thread.currentThread().interrupt(); } public static void main(String[] args) throws Exception {