-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add digital signature section with basic JWT support
- Loading branch information
Showing
8 changed files
with
363 additions
and
0 deletions.
There are no files selected for viewing
99 changes: 99 additions & 0 deletions
99
src/main/java/uk/ac/cardiff/nsa/hashenc/context/UserDigitalSignatureContext.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
|
||
|
||
|
||
} |
127 changes: 127 additions & 0 deletions
127
src/main/java/uk/ac/cardiff/nsa/hashenc/controller/DigitalSignatureController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
47 changes: 47 additions & 0 deletions
47
src/main/java/uk/ac/cardiff/nsa/hashenc/engine/JWTUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters