Implement HMAC verification
This commit is contained in:
parent
5ad8cded18
commit
a7994736f1
@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user