diff --git a/src/main/java/dslab/Message.java b/src/main/java/dslab/Message.java index fa6321e..0eee42f 100644 --- a/src/main/java/dslab/Message.java +++ b/src/main/java/dslab/Message.java @@ -1,8 +1,17 @@ package dslab; import dslab.exception.MissingInputException; +import dslab.util.Keys; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; +import java.util.Base64; import java.util.stream.Collectors; public class Message { @@ -89,6 +98,23 @@ public class Message { this.hash = hash; } + /** + * Calculate a HMAC hash of the message. + * + * @return A Base64 encoded hash of the message. + * @throws IOException Thrown if the HMAC key (in "keys/hmac.key") cannot be found. + * @throws NoSuchAlgorithmException Thrown if HmacSHA256 is not known. + * @throws InvalidKeyException Thrown if the key is not valid. + */ + public String calculateHash() throws IOException, NoSuchAlgorithmException, InvalidKeyException { + byte[] hashFormat = String.join("\n", getFrom().toString(), printTo(), getSubject(), getData()) + .getBytes(StandardCharsets.UTF_8); + Mac mac = Mac.getInstance("HmacSHA256"); + SecretKeySpec hmac = Keys.readSecretKey(new File("keys/hmac.key")); + mac.init(hmac); + return (Base64.getEncoder().encodeToString(mac.doFinal(hashFormat))); + } + public String listMessage() { return getId() + " " + getFrom() + " " + getSubject(); } diff --git a/src/main/java/dslab/client/MessageClient.java b/src/main/java/dslab/client/MessageClient.java index b783d4e..dd23ba6 100644 --- a/src/main/java/dslab/client/MessageClient.java +++ b/src/main/java/dslab/client/MessageClient.java @@ -10,13 +10,17 @@ import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import java.util.Arrays; import java.util.Base64; +import java.util.logging.Level; import java.util.logging.Logger; import at.ac.tuwien.dsg.orvell.Shell; import at.ac.tuwien.dsg.orvell.StopShellException; import at.ac.tuwien.dsg.orvell.annotation.Command; import dslab.ComponentFactory; +import dslab.Email; +import dslab.Message; import dslab.exception.FailedVerificationException; +import dslab.exception.MalformedInputException; import dslab.util.Config; import javax.crypto.*; @@ -277,12 +281,13 @@ public class MessageClient implements IMessageClient, Runnable { /** * Generates the full challenge message to be sent to the server. - * + *

* The challenge message is of the format: * ok * The parameters are base64 encoded individually, then they are concatenated: * ok * The whole string is then AES encrypted and the result base64 encoded again. + * * @return A base64 encoded full client challenge. */ private String generateChallengeMessage(PublicKey serverPublicKey) { @@ -331,11 +336,70 @@ public class MessageClient implements IMessageClient, Runnable { } + /** + * Pulls a message by id from the MailboxServer and compares the calculated hash against the one received via the + * 'hash' field. + * + * @param id The message id. + */ @Command @Override public void verify(String id) { logger.info("Received 'verify' command for id " + id); + Message msg = new Message(); + String response; + // Get message from MailboxServer + try { + dmapOut.println(getAesCiphertext("show " + id)); + response = getAesPlaintext(dmapIn.readLine()); + if (response.startsWith("error")) { + this.shell.out().println(response); + return; + } + String[] commands = response.split("\n"); + if (commands.length != 6) { + logger.severe("error expected different message format"); + return; + } + msg.setFrom(new Email(commands[0].split("\\s+")[1])); + String[] addresses = commands[1].split("\\s+")[1].split(","); + for (String email : addresses) { + msg.addTo(new Email(email)); + } + + String[] split = commands[2].split("\\s+", 2); + msg.setSubject(split.length == 2 ? split[1] : ""); + + split = commands[3].split("\\s+", 2); + msg.setData(split.length == 2 ? split[1] : ""); + + split = commands[4].split("\\s+", 2); + msg.setHash(split.length == 2 ? split[1] : ""); + + if (!commands[5].startsWith("ok")) { + logger.severe("error expected 'ok', got " + commands[5] + " instead"); + } + + String calculatedHash = msg.calculateHash(); + logger.info("Calculated hash: " + calculatedHash); + if (msg.getHash().equals(calculatedHash)) { + this.shell.out().println("ok"); + } else { + this.shell.out().println("error"); + } + } catch (IllegalBlockSizeException | BadPaddingException | NoSuchAlgorithmException | InvalidKeyException e) { + logger.severe("Error during encryption/decryption"); + e.printStackTrace(); + shutdown(); + } catch (IOException e) { + logger.severe("Cannot communicate with server"); + e.printStackTrace(); + shutdown(); + } catch (MalformedInputException e) { + logger.severe("Error during reading of message"); + e.printStackTrace(); + } } @Command diff --git a/src/main/java/dslab/mailbox/DMAPConnection.java b/src/main/java/dslab/mailbox/DMAPConnection.java index 01af5e8..4c63113 100644 --- a/src/main/java/dslab/mailbox/DMAPConnection.java +++ b/src/main/java/dslab/mailbox/DMAPConnection.java @@ -109,6 +109,8 @@ public class DMAPConnection implements Runnable { if (secure) { out.println(getAesCiphertext("You are already secure!")); } else startSecure(); + } else if (userInput.startsWith("inbox")) { + printInbox(); } else { if (secure) { out.println(getAesCiphertext("error protocol error")); @@ -130,6 +132,9 @@ public class DMAPConnection implements Runnable { } } + private void printInbox() { + } + private void loginLoop() { String userInput; @@ -330,8 +335,8 @@ public class DMAPConnection implements Runnable { for (Message m : storage.get(currentUser)) { if (m.getId() == i) { if (secure) { - out.println(getAesCiphertext(m.toString())); - } else out.println(m.toString()); + out.println(getAesCiphertext(m.toString() + "ok")); + } else out.println(m.toString() + "\nok"); return; } } @@ -349,11 +354,14 @@ public class DMAPConnection implements Runnable { } for (Message m : storage.get(currentUser)) { - logger.info("Printing message from user: " + m.listMessage()); + logger.fine("Printing message from user: " + m.listMessage()); if (secure) { out.println(getAesCiphertext(m.listMessage())); } else out.println(m.listMessage()); } + if (secure) { + out.println(getAesCiphertext("ok")); + } else out.println("ok"); } public void deleteMessage(String id) throws MessageNotFoundException, BadPaddingException, IllegalBlockSizeException { diff --git a/src/main/java/dslab/mailbox/DMTPConnection.java b/src/main/java/dslab/mailbox/DMTPConnection.java index da4d009..60805b0 100644 --- a/src/main/java/dslab/mailbox/DMTPConnection.java +++ b/src/main/java/dslab/mailbox/DMTPConnection.java @@ -110,6 +110,10 @@ public class DMTPConnection implements Runnable { String data = userInput.split("\\s+", 2)[1]; msg.setData(data); out.println("ok"); + } else if (userInput.startsWith("hash")) { + String hash = userInput.split("\\s+", 2)[1]; + msg.setHash(hash); + out.println("ok"); } else { out.println("error protocol error"); shutdown(); diff --git a/src/main/java/dslab/transfer/ClientConnection.java b/src/main/java/dslab/transfer/ClientConnection.java index 7a1ef64..58212ff 100644 --- a/src/main/java/dslab/transfer/ClientConnection.java +++ b/src/main/java/dslab/transfer/ClientConnection.java @@ -92,6 +92,13 @@ public class ClientConnection implements Runnable { logger.info("Setting data to: " + data); msg.setData(data); out.println("ok"); + } else if (userInput.startsWith("hash")) { + String hash = ""; + if (userInput.split("\\s+", 2).length > 1) + hash = userInput.split("\\s+", 2)[1]; + logger.info("Setting hash to: " + hash); + msg.setHash(hash); + out.println("ok"); } else { out.println("error protocol error"); shutdown();