Implement HMAC verification

This commit is contained in:
Tobias Eidelpes 2020-12-30 14:32:18 +01:00
parent 5ad8cded18
commit a7994736f1
5 changed files with 113 additions and 4 deletions

View File

@ -1,8 +1,17 @@
package dslab; package dslab;
import dslab.exception.MissingInputException; 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.ArrayList;
import java.util.Base64;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class Message { public class Message {
@ -89,6 +98,23 @@ public class Message {
this.hash = hash; 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() { public String listMessage() {
return getId() + " " + getFrom() + " " + getSubject(); return getId() + " " + getFrom() + " " + getSubject();
} }

View File

@ -10,13 +10,17 @@ import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec; import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays; import java.util.Arrays;
import java.util.Base64; import java.util.Base64;
import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import at.ac.tuwien.dsg.orvell.Shell; import at.ac.tuwien.dsg.orvell.Shell;
import at.ac.tuwien.dsg.orvell.StopShellException; import at.ac.tuwien.dsg.orvell.StopShellException;
import at.ac.tuwien.dsg.orvell.annotation.Command; import at.ac.tuwien.dsg.orvell.annotation.Command;
import dslab.ComponentFactory; import dslab.ComponentFactory;
import dslab.Email;
import dslab.Message;
import dslab.exception.FailedVerificationException; import dslab.exception.FailedVerificationException;
import dslab.exception.MalformedInputException;
import dslab.util.Config; import dslab.util.Config;
import javax.crypto.*; import javax.crypto.*;
@ -277,12 +281,13 @@ public class MessageClient implements IMessageClient, Runnable {
/** /**
* Generates the full challenge message to be sent to the server. * Generates the full challenge message to be sent to the server.
* * <p>
* The challenge message is of the format: * The challenge message is of the format:
* ok <client-challenge> <secret-key> <iv> * ok <client-challenge> <secret-key> <iv>
* The parameters are base64 encoded individually, then they are concatenated: * The parameters are base64 encoded individually, then they are concatenated:
* ok <base64-client-challenge> <base64-secret-key> <base64-iv> * ok <base64-client-challenge> <base64-secret-key> <base64-iv>
* The whole string is then AES encrypted and the result base64 encoded again. * The whole string is then AES encrypted and the result base64 encoded again.
*
* @return A base64 encoded full client challenge. * @return A base64 encoded full client challenge.
*/ */
private String generateChallengeMessage(PublicKey serverPublicKey) { 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 @Command
@Override @Override
public void verify(String id) { public void verify(String id) {
logger.info("Received 'verify' command for id " + 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 @Command

View File

@ -109,6 +109,8 @@ public class DMAPConnection implements Runnable {
if (secure) { if (secure) {
out.println(getAesCiphertext("You are already secure!")); out.println(getAesCiphertext("You are already secure!"));
} else startSecure(); } else startSecure();
} else if (userInput.startsWith("inbox")) {
printInbox();
} else { } else {
if (secure) { if (secure) {
out.println(getAesCiphertext("error protocol error")); out.println(getAesCiphertext("error protocol error"));
@ -130,6 +132,9 @@ public class DMAPConnection implements Runnable {
} }
} }
private void printInbox() {
}
private void loginLoop() { private void loginLoop() {
String userInput; String userInput;
@ -330,8 +335,8 @@ public class DMAPConnection implements Runnable {
for (Message m : storage.get(currentUser)) { for (Message m : storage.get(currentUser)) {
if (m.getId() == i) { if (m.getId() == i) {
if (secure) { if (secure) {
out.println(getAesCiphertext(m.toString())); out.println(getAesCiphertext(m.toString() + "ok"));
} else out.println(m.toString()); } else out.println(m.toString() + "\nok");
return; return;
} }
} }
@ -349,11 +354,14 @@ public class DMAPConnection implements Runnable {
} }
for (Message m : storage.get(currentUser)) { for (Message m : storage.get(currentUser)) {
logger.info("Printing message from user: " + m.listMessage()); logger.fine("Printing message from user: " + m.listMessage());
if (secure) { if (secure) {
out.println(getAesCiphertext(m.listMessage())); out.println(getAesCiphertext(m.listMessage()));
} else out.println(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 { public void deleteMessage(String id) throws MessageNotFoundException, BadPaddingException, IllegalBlockSizeException {

View File

@ -110,6 +110,10 @@ public class DMTPConnection implements Runnable {
String data = userInput.split("\\s+", 2)[1]; String data = userInput.split("\\s+", 2)[1];
msg.setData(data); msg.setData(data);
out.println("ok"); out.println("ok");
} else if (userInput.startsWith("hash")) {
String hash = userInput.split("\\s+", 2)[1];
msg.setHash(hash);
out.println("ok");
} else { } else {
out.println("error protocol error"); out.println("error protocol error");
shutdown(); shutdown();

View File

@ -92,6 +92,13 @@ public class ClientConnection implements Runnable {
logger.info("Setting data to: " + data); logger.info("Setting data to: " + data);
msg.setData(data); msg.setData(data);
out.println("ok"); 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 { } else {
out.println("error protocol error"); out.println("error protocol error");
shutdown(); shutdown();