From e78549fb726f028f500f198a9675e735b402b477 Mon Sep 17 00:00:00 2001 From: Nanne Baars Date: Wed, 4 Nov 2020 20:37:25 +0100 Subject: [PATCH] Add JWT encoder to WebWolf --- webwolf/pom.xml | 5 ++ .../org/owasp/webwolf/jwt/JWTController.java | 27 ++++++ .../java/org/owasp/webwolf/jwt/JWTToken.java | 86 +++++++++++++++++++ webwolf/src/main/resources/static/js/jwt.js | 79 +++++++++++++++++ .../resources/templates/fragments/header.html | 3 + webwolf/src/main/resources/templates/jwt.html | 65 ++++++++++++++ 6 files changed, 265 insertions(+) create mode 100644 webwolf/src/main/java/org/owasp/webwolf/jwt/JWTController.java create mode 100644 webwolf/src/main/java/org/owasp/webwolf/jwt/JWTToken.java create mode 100644 webwolf/src/main/resources/static/js/jwt.js create mode 100644 webwolf/src/main/resources/templates/jwt.html diff --git a/webwolf/pom.xml b/webwolf/pom.xml index c5d66f19a..1a46f046f 100644 --- a/webwolf/pom.xml +++ b/webwolf/pom.xml @@ -36,6 +36,11 @@ org.apache.commons commons-lang3 + + io.jsonwebtoken + jjwt + 0.7.0 + org.springframework.boot spring-boot-starter-security diff --git a/webwolf/src/main/java/org/owasp/webwolf/jwt/JWTController.java b/webwolf/src/main/java/org/owasp/webwolf/jwt/JWTController.java new file mode 100644 index 000000000..3283c5544 --- /dev/null +++ b/webwolf/src/main/java/org/owasp/webwolf/jwt/JWTController.java @@ -0,0 +1,27 @@ +package org.owasp.webwolf.jwt; + +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.ModelAndView; + +@RestController +public class JWTController { + + @GetMapping("/WebWolf/jwt") + public ModelAndView jwt() { + return new ModelAndView("jwt"); + } + + @PostMapping(value = "/WebWolf/jwt/decode", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + public JWTToken decode(@RequestBody JWTToken token) { + token.decode(); + return token; + } + + @PostMapping(value = "/WebWolf/jwt/encode", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + public JWTToken encode(@RequestBody JWTToken token) { + token.encode(); + return token; + } + +} diff --git a/webwolf/src/main/java/org/owasp/webwolf/jwt/JWTToken.java b/webwolf/src/main/java/org/owasp/webwolf/jwt/JWTToken.java new file mode 100644 index 000000000..41e79c51a --- /dev/null +++ b/webwolf/src/main/java/org/owasp/webwolf/jwt/JWTToken.java @@ -0,0 +1,86 @@ +package org.owasp.webwolf.jwt; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.jsonwebtoken.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.util.Base64Utils; +import org.springframework.util.StringUtils; + +import java.util.Map; +import java.util.regex.Pattern; + +@NoArgsConstructor +@Getter +@Setter +public class JWTToken { + + private static final Pattern jwtPattern = Pattern.compile("(.*)\\.(.*)\\.(.*)"); + + private String encoded = ""; + private String secretKey; + private String header; + private String payload; + private boolean signatureValid = true; + private boolean validToken = true; + + public void decode() { + validToken = parseToken(encoded); + signatureValid = validateSignature(secretKey, encoded); + } + + public void encode() { + var mapper = new ObjectMapper(); + try { + if (StringUtils.hasText(secretKey)) { + encoded = Jwts.builder() + .signWith(SignatureAlgorithm.HS256, Base64Utils.encodeToUrlSafeString(secretKey.getBytes())) + .setClaims(mapper.readValue(payload, Map.class)) + .setHeader(mapper.readValue(header, Map.class)) + .compact(); + } else { + encoded = Jwts.builder() + .setClaims(mapper.readValue(payload, Map.class)) + .setHeader(mapper.readValue(header, Map.class)) + .compact(); + } + validToken = true; + } catch (JsonProcessingException e) { + validToken = false; + signatureValid = false; + } + } + + private boolean parseToken(String jwt) { + var matcher = jwtPattern.matcher(jwt); + var mapper = new ObjectMapper().writerWithDefaultPrettyPrinter(); + if (matcher.matches()) { + try { + Jwt headerClaimsJwt = Jwts.parser().parseClaimsJwt(matcher.group(1) + "." + matcher.group(2) + "."); + this.header = mapper.writeValueAsString(headerClaimsJwt.getHeader()); + this.payload = mapper.writeValueAsString(headerClaimsJwt.getBody()); + } catch (Exception e) { + try { + this.header = mapper.writeValueAsString(new String(Base64Utils.decodeFromUrlSafeString(matcher.group(1)))); + this.payload = mapper.writeValueAsString(new String(Base64Utils.decodeFromUrlSafeString(matcher.group(2)))); + } catch (Exception ex) { + return false; + } + } + return true; + } + return false; + } + + private boolean validateSignature(String secretKey, String jwt) { + try { + Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt); + return true; + } catch (Exception e) { + return false; + } + } + +} diff --git a/webwolf/src/main/resources/static/js/jwt.js b/webwolf/src/main/resources/static/js/jwt.js new file mode 100644 index 000000000..7a175818f --- /dev/null +++ b/webwolf/src/main/resources/static/js/jwt.js @@ -0,0 +1,79 @@ +$(document).ready(() => { + $('#encodedToken').on('input', () => { + var token = $('#encodedToken').val(); + var secretKey = $('#secretKey').val(); + + $.ajax({ + type: 'POST', + url: '/WebWolf/jwt/decode', + data: JSON.stringify({encoded: token, secretKey: secretKey}), + success: function (data) { + if (data.validToken) { + $('#tokenHeader').val(data.header); + $('#tokenPayload').val(data.payload); + } + updateSignature(data); + }, + contentType: "application/json", + dataType: 'json' + }); + }); +}); + +function encode() { + return () => { + var header = $('#tokenHeader').val(); + var payload = $('#tokenPayload').val(); + var secretKey = $('#secretKey').val(); + var token = {header: header, payload: payload, secretKey: secretKey}; + + if (!parseJson(header)) { + $('#encodedToken').val(""); + $('#tokenHeader').css('background-color', 'lightcoral'); + } else if (!parseJson(payload)) { + $('#encodedToken').val(""); + $('#tokenPayload').css('background-color', 'lightcoral'); + } else { + $.ajax({ + type: 'POST', + url: '/WebWolf/jwt/encode', + data: JSON.stringify(token), + success: function (data) { + $('#encodedToken').val(data.encoded); + $('#tokenPayload').css('background-color', '#FFFFFF'); + $('#encodedToken').css('background-color', '#FFFFFF'); + $('#tokenHeader').css('background-color', '#FFFFFF'); + updateSignature(data); + }, + contentType: "application/json", + dataType: 'json' + }); + } + }; +} + +$(document).ready(() => { + $('#tokenPayload').on('input', encode()); + $('#tokenHeader').on('input', encode()); + $('#secretKey').on('input', encode()); +}); + +function parseJson(text) { + try { + if (text) { + JSON.parse(text); + } + } catch (e) { + return false; + } + return true; +} + + +function updateSignature(data) { + if (data.signatureValid) { + $('#signatureValid').val("Signature valid"); + } else { + $('#signatureValid').val("Signature invalid"); + } +} diff --git a/webwolf/src/main/resources/templates/fragments/header.html b/webwolf/src/main/resources/templates/fragments/header.html index 1cf1965dd..6cbd9f0e3 100644 --- a/webwolf/src/main/resources/templates/fragments/header.html +++ b/webwolf/src/main/resources/templates/fragments/header.html @@ -30,6 +30,9 @@ +