Skip to content

Commit

Permalink
Add digital signature section with basic JWT support
Browse files Browse the repository at this point in the history
  • Loading branch information
philsmart committed Oct 9, 2022
1 parent 2d808f2 commit 24249af
Show file tree
Hide file tree
Showing 8 changed files with 363 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@

package uk.ac.cardiff.nsa.hashenc.context;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

/**
* A context to store state specific to a user
*
* Threadsafe
*/
@Component("UserDigitalSignatureContext")
@Scope("session")
public class UserDigitalSignatureContext {

private String payload;

private String key;

/** The JOSE header.*/
private String header;

private byte[] signature;

/**
* Get the payload.
*
* @return the payload
*/
public synchronized String getPayload() {
return payload;
}

/**
* Set the payload.
*
* @param payload the payload to set
*/
public synchronized void setPayload(String payload) {
this.payload = payload;
}

/**
* Get the key.
*
* @return the key
*/
public synchronized String getKey() {
return key;
}

/**
* Set the key.
*
* @param key the key to set
*/
public synchronized void setKey(String key) {
this.key = key;
}

/**
* Get the header.
*
* @return the header
*/
public synchronized String getHeader() {
return header;
}

/**
* Set the header.
*
* @param header the header to set
*/
public synchronized void setHeader(String header) {
this.header = header;
}

/**
* Get the signature.
*
* @return the signature
*/
public synchronized byte[] getSignature() {
return signature;
}

/**
* Set the signature.
*
* @param signature the signature to set
*/
public synchronized void setSignature(byte[] signature) {
this.signature = signature;
}



}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@

package uk.ac.cardiff.nsa.hashenc.controller;

import java.util.Arrays;

import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import uk.ac.cardiff.nsa.hashenc.context.UserDigitalSignatureContext;
import uk.ac.cardiff.nsa.hashenc.context.UserEncryptionContext;
import uk.ac.cardiff.nsa.hashenc.engine.HashEngine;
import uk.ac.cardiff.nsa.hashenc.engine.JWTUtils;

/**
* Basic Digital Signature controller.
*/
@Controller
@SessionAttributes("userDigitalSignatureContext")
public class DigitalSignatureController {

/** Class logger. */
private final Logger log = LoggerFactory.getLogger(DigitalSignatureController.class);

private final String TEMPLATE_JSON = """
{
"first_name": "John",
"Surname": "Doe",
"role" : "user"
}
""";

/** Constructor. */
public DigitalSignatureController() {

}

/**
* Allow spring to create a UserContext and place it inside the HTTP Session for
* use by the user (JSESSIONID) of this application.
*
* @return the user context.
*/
@ModelAttribute("userDigitalSignatureContext")
public UserDigitalSignatureContext constructUserContext() {
final UserDigitalSignatureContext userContext = new UserDigitalSignatureContext();

var key = "examplekey";

String compactHeaderPayload = JWTUtils.createJWTCompactSerlizationForSigning(JWTUtils.DEFAULT_JOSE_HEADER,
TEMPLATE_JSON);
final byte[] mac = HashEngine.constructHmacAsBytes(compactHeaderPayload, key);

userContext.setPayload(TEMPLATE_JSON);
userContext.setSignature(mac);
userContext.setKey(key);
userContext.setHeader(JWTUtils.DEFAULT_JOSE_HEADER);
return userContext;
}

@PostMapping(value = "/hmac-payload", params = "action=compute")
public String hmacPayload(@RequestParam("payload") final String payload,
@RequestParam("header") final String header, @RequestParam("key") final String key,
final RedirectAttributes model,
@ModelAttribute("userDigitalSignatureContext") final UserDigitalSignatureContext userCtx) {

String compactHeaderPayload = JWTUtils.createJWTCompactSerlizationForSigning(header, payload);

final byte[] mac = HashEngine.constructHmacAsBytes(compactHeaderPayload, key);
userCtx.setPayload(payload);
userCtx.setHeader(header);

model.addFlashAttribute("mac", Base64.encodeBase64URLSafeString(mac));
model.addFlashAttribute("jwtCompact", JWTUtils.appendSignature(compactHeaderPayload, mac));
return "redirect:sig";
}

@PostMapping(value = "/hmac-payload", params = "action=check")
public String verifyPayload(@RequestParam("payload") final String payload,
@RequestParam("header") final String header, @RequestParam("existingMac") final String existingMac,
final RedirectAttributes model,
@ModelAttribute("userDigitalSignatureContext") final UserDigitalSignatureContext userCtx) {

userCtx.setPayload(payload);
userCtx.setHeader(header);
byte[] macFromInput = Base64.decodeBase64(existingMac);
userCtx.setSignature(macFromInput);
String compactHeaderPayload = JWTUtils.createJWTCompactSerlizationForSigning(header, payload);
final byte[] mac = HashEngine.constructHmacAsBytes(compactHeaderPayload, userCtx.getKey());

model.addFlashAttribute("verified", Arrays.equals(mac, macFromInput));
return "redirect:sig";
}

/**
* Get the 'digital-signatures' page and set suitable model values.
*
* @param model the model to return
*
* @return the 'digital-signatures.html' page
*/
@GetMapping("/sig")
public String getEncryptionPage(final Model model,
@ModelAttribute("userDigitalSignatureContext") final UserDigitalSignatureContext userCtx) {

model.addAttribute("payload", userCtx.getPayload());
model.addAttribute("existingMac", Base64.encodeBase64URLSafeString(userCtx.getSignature()));
model.addAttribute("header", userCtx.getHeader());
model.addAttribute("key", userCtx.getKey());
if (model.getAttribute("verified") == null) {
String compactHeaderPayload = JWTUtils.createJWTCompactSerlizationForSigning(userCtx.getHeader(),
userCtx.getPayload());
final byte[] mac = HashEngine.constructHmacAsBytes(compactHeaderPayload, userCtx.getKey());
model.addAttribute("verified", Arrays.equals(mac, userCtx.getSignature()));
}
return "digital-signatures";
}

}
20 changes: 20 additions & 0 deletions src/main/java/uk/ac/cardiff/nsa/hashenc/engine/HashEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -210,5 +210,25 @@ public static String constructHmac(final String docOne, final String key) {
return null;
}
}

/**
* Construct a HMAC based on the SHA256 hashing algorithm.
*
* @param docOne the document to generate a HMAC for
* @param key the key to use with the hash to generate the HMAC
* @return the HMAC represented as bytes.
*/
public static byte[] constructHmacAsBytes(final String docOne, final String key) {
try {
final byte[] byteKey = key.getBytes(StandardCharsets.UTF_8);
final Mac sha512Hmac = Mac.getInstance("HmacSHA256");
final SecretKeySpec keySpec = new SecretKeySpec(byteKey, ("HmacSHA256"));
sha512Hmac.init(keySpec);
final byte[] macData = sha512Hmac.doFinal(docOne.getBytes(StandardCharsets.UTF_8));
return macData;
} catch (final NoSuchAlgorithmException | InvalidKeyException e) {
return null;
}
}

}
47 changes: 47 additions & 0 deletions src/main/java/uk/ac/cardiff/nsa/hashenc/engine/JWTUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package uk.ac.cardiff.nsa.hashenc.engine;

import java.nio.charset.StandardCharsets;

import org.apache.commons.codec.binary.Base64;

/**
* Some util methods to help construct very simple JWTs - use Nimbus
* or better libraries in production system.
*/
public final class JWTUtils {

public final static String DEFAULT_JOSE_HEADER = """
{
"alg": "HS256",
"typ": "JWT"
}
""";

private JWTUtils() {

}

/**
* Construct a JWT/JWS/JWE using the compact serialization scheme.
*
* @param header the header, can be null, if so the default JOSE header will be used.
* @param payload the payload
* @return the compact serialization
*/
public static String createJWTCompactSerlizationForSigning(String header, String payload) {
String headerB64 = header == null ? Base64.encodeBase64URLSafeString(normalise(DEFAULT_JOSE_HEADER).getBytes(StandardCharsets.UTF_8)) :
Base64.encodeBase64URLSafeString(normalise(header).getBytes(StandardCharsets.UTF_8));
String payloadB64 = Base64.encodeBase64URLSafeString(normalise(payload).getBytes(StandardCharsets.UTF_8));

return headerB64+"."+payloadB64;
}

public static String normalise(String in) {
return in.replaceAll("\\s+", "");
}

public static Object appendSignature(String compactHeaderPayload, byte[] mac) {
return compactHeaderPayload+"."+Base64.encodeBase64URLSafeString(mac);
}

}
67 changes: 67 additions & 0 deletions src/main/resources/templates/digital-signatures.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Hashing And Encryption</title>
<link th:href="@{/styles/style.css}" rel="stylesheet" />
</head>
<body>
<header id="header-menu">
<ul>
<li><a href="/hashing">Hashing</a></li>
<li><a href="/hashing-usage">Hashing Usage</a></li>
<li><a href="/enc">Encryption</a></li>
<li><a class="active" href="/sig">Digital Signatures</a></li>
</ul>
</header>

<div id="game-container">
<h3>Creating an authentication tag (MAC)</h3>

<p>Input the data or payload of your message</p>
<form th:action="@{/hmac-payload}" method="post"
style="display: inline">
HEADER ('alg' is not 'live/used' yet):
<div>
<textarea id="header" name="header" rows="6" cols="50"
th:inline="text">[[${header}]]</textarea>
</div>
PAYLOAD:
<div>
<textarea id="payload" name="payload" rows="6" cols="50"
th:inline="text">[[${payload}]]</textarea>
</div>

SIGNATURE:<span th:if="${verified}" style="padding: 5px; color: green">VALID</span> <span th:if="${!verified}"
style="padding: 5px; color: red">INVALID</span>

<div>

<textarea id="existingMac" name="existingMac" rows="3" cols="50" th:if="${verified}"
th:inline="text" style="background-color:#d6ffd6">[[${existingMac}]]</textarea>
<textarea id="existingMac" name="existingMac" rows="3" cols="50" th:if="${!verified}"
th:inline="text" style="background-color:#ffcece">[[${existingMac}]]</textarea>
</div>

<button type="submit" name="action" value="check" style="width: 25%">Verify</button>
<hr />
<h3>Create a new MAC</h3>
<p>
<label for="key">Key:</label><input id="key" name="key"
th:value="${key}" />
<button type="submit" name="action" value="compute" style="width: 25%">Compute</button>
</p>

</form>
MAC: <span th:text="${mac}"></span>
<p>JWT Encoded (As an example):</p>
<p>

<textarea th:if="${jwtCompact}" id="jwtCompact" name="jwtCompact"
rows="4" cols="70" th:inline="text">[[${jwtCompact}]]</textarea>
</p>
</div>


</body>
</html>
1 change: 1 addition & 0 deletions src/main/resources/templates/enc.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<li><a href="/hashing">Hashing</a></li>
<li><a href="/hashing-usage">Hashing Usage</a></li>
<li><a class="active" href="/enc">Encryption</a></li>
<li><a href="/sig">Digital Signatures</a></li>
</ul>
</header>

Expand Down
1 change: 1 addition & 0 deletions src/main/resources/templates/hashing-usage.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<li><a href="/hashing">Hashing</a></li>
<li><a class="active" href="/hashing-usage">Hashing Usage</a></li>
<li><a href="/enc">Encryption</a></li>
<li><a href="/sig">Digital Signatures</a></li>
</ul>
</header>

Expand Down
1 change: 1 addition & 0 deletions src/main/resources/templates/hashing.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<li><a class="active" href="/hashing">Hashing</a></li>
<li><a href="/hashing-usage">Hashing Usage</a></li>
<li><a href="/enc">Encryption</a></li>
<li><a href="/sig">Digital Signatures</a></li>
</ul>
</header>

Expand Down

0 comments on commit 24249af

Please sign in to comment.