This commit is contained in:
Nanne Baars
2020-12-05 13:23:33 +01:00
committed by Nanne Baars
parent e78549fb72
commit 142631c7a0
12 changed files with 236 additions and 239 deletions

View File

@ -37,9 +37,9 @@
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
<groupId>org.bitbucket.b_c</groupId>
<artifactId>jose4j</artifactId>
<version>0.7.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@ -1,20 +1,28 @@
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 lombok.*;
import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.consumer.InvalidJwtException;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.jose4j.jwx.CompactSerializer;
import org.jose4j.keys.HmacKey;
import org.jose4j.lang.JoseException;
import org.springframework.util.StringUtils;
import java.util.Map;
import java.util.regex.Pattern;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.springframework.util.Base64Utils.decodeFromUrlSafeString;
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@Builder
public class JWTToken {
private static final Pattern jwtPattern = Pattern.compile("(.*)\\.(.*)\\.(.*)");
@ -24,63 +32,70 @@ public class JWTToken {
private String header;
private String payload;
private boolean signatureValid = true;
private boolean validToken = true;
public void decode() {
validToken = parseToken(encoded);
parseToken(encoded.trim().replace(System.getProperty("line.separator"), ""));
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();
JsonWebSignature jws = new JsonWebSignature();
jws.setPayload(payload);
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.HMAC_SHA256);
jws.setDoKeyValidation(false);
if (StringUtils.hasText(secretKey)) {
jws.setKey(new HmacKey(secretKey.getBytes(UTF_8)));
try {
encoded = jws.getCompactSerialization();
signatureValid = true;
} catch (JoseException e) {
header = "";
payload = "";
}
validToken = true;
} catch (JsonProcessingException e) {
validToken = false;
signatureValid = false;
} else {
var encodedHeader = jws.getHeaders().getEncodedHeader();
var encodedPayload = jws.getEncodedPayload();
encoded = CompactSerializer.serialize(new String[]{encodedHeader, encodedPayload});
}
}
private boolean parseToken(String jwt) {
var matcher = jwtPattern.matcher(jwt);
var mapper = new ObjectMapper().writerWithDefaultPrettyPrinter();
var mapper = new ObjectMapper();
if (matcher.matches()) {
try {
Jwt<Header, Claims> headerClaimsJwt = Jwts.parser().parseClaimsJwt(matcher.group(1) + "." + matcher.group(2) + ".");
this.header = mapper.writeValueAsString(headerClaimsJwt.getHeader());
this.payload = mapper.writeValueAsString(headerClaimsJwt.getBody());
var prettyPrint = mapper.writerWithDefaultPrettyPrinter();
this.header = prettyPrint.writeValueAsString(mapper.readValue(decodeFromUrlSafeString(matcher.group(1)), Map.class));
this.payload = prettyPrint.writeValueAsString(mapper.readValue(decodeFromUrlSafeString(matcher.group(2)), Map.class));
return true;
} 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;
}
this.header = new String(decodeFromUrlSafeString(matcher.group(1)));
this.payload = new String(decodeFromUrlSafeString(matcher.group(2)));
return false;
}
return true;
} else {
this.header = "error";
this.payload = "error";
}
return false;
}
private boolean validateSignature(String secretKey, String jwt) {
try {
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt);
return true;
} catch (Exception e) {
return false;
if (StringUtils.hasText(secretKey)) {
JwtConsumer jwtConsumer = new JwtConsumerBuilder()
.setSkipAllValidators()
.setVerificationKey(new HmacKey(secretKey.getBytes(UTF_8)))
.setRelaxVerificationKeyValidation()
.build();
try {
jwtConsumer.processToClaims(jwt);
return true;
} catch (InvalidJwtException e) {
return false;
}
}
return false;
}
}

View File

@ -8,10 +8,8 @@ $(document).ready(() => {
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);
}
$('#tokenHeader').val(data.header);
$('#tokenPayload').val(data.payload);
updateSignature(data);
},
contentType: "application/json",
@ -69,11 +67,10 @@ function parseJson(text) {
return true;
}
function updateSignature(data) {
if (data.signatureValid) {
$('#signatureValid').val("Signature valid");
$('#signatureValid').html("Signature valid");
} else {
$('#signatureValid').val("Signature invalid");
$('#signatureValid').html("Signature invalid");
}
}

View File

@ -16,7 +16,6 @@
<form th:action="@{/login}" method="post">
<fieldset>
<h2>Sign in</h2>
Use your WebGoat account.
<br/>
<div th:if="${param.error}">
<div class="alert alert-danger">
@ -31,11 +30,11 @@
<div class="form-group">
<input type="text" name="username" id="username" class="form-control input-lg"
placeholder="UserName" required="true" autofocus="true"/>
placeholder="Username WebGoat" required="true" autofocus="true"/>
</div>
<div class="form-group">
<input type="password" name="password" id="password" class="form-control input-lg"
placeholder="Password" required="true"/>
placeholder="Password WebGoat" required="true"/>
</div>
<div class="row">

View File

@ -0,0 +1,81 @@
package org.owasp.webwolf.jwt;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import org.junit.jupiter.api.Test;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
class JWTTokenTest {
@Test
void encodeCorrectTokenWithoutSignature() {
var headers = Map.of("alg", "HS256", "typ", "JWT");
var payload = Map.of("test", "test");
var token = JWTToken.builder().header(toString(headers)).payload(toString(payload)).build();
token.encode();
assertThat(token.getEncoded()).isEqualTo("eyJhbGciOiJIUzI1NiJ9.eyJ0ZXN0IjoidGVzdCJ9");
}
@Test
void encodeCorrectTokenWithSignature() {
var headers = Map.of("alg", "HS256", "typ", "JWT");
var payload = Map.of("test", "test");
var token = JWTToken.builder()
.header(toString(headers))
.payload(toString(payload))
.secretKey("test")
.build();
token.encode();
assertThat(token.getEncoded()).isEqualTo("eyJhbGciOiJIUzI1NiJ9.eyJ0ZXN0IjoidGVzdCJ9.KOobRHDYyaesV_doOk11XXGKSONwzllraAaqqM4VFE4");
}
@Test
void encodeTokenWithNonJsonInput() {
var token = JWTToken.builder()
.header("aaa")
.payload("bbb")
.secretKey("test")
.build();
token.encode();
assertThat(token.getEncoded()).isEqualTo("eyJhbGciOiJIUzI1NiJ9.YmJi.VAcRegquayARuahZZ1ednXpbAyv7KEFnyjNJlxLNX0I");
}
@Test
void decodeValidSignedToken() {
var token = JWTToken.builder()
.encoded("eyJhbGciOiJIUzI1NiJ9.eyJ0ZXN0IjoidGVzdCJ9.KOobRHDYyaesV_doOk11XXGKSONwzllraAaqqM4VFE4")
.secretKey("test")
.build();
token.decode();
assertThat(token.getHeader()).contains("\"alg\" : \"HS256\"");
assertThat(token.isSignatureValid()).isTrue();
}
@Test
void decodeInvalidSignedToken() {
var token = JWTToken.builder().encoded("eyJhbGciOiJIUzI1NiJ9.eyJ0ZXsdfdfsaasfddfasN0IjoidGVzdCJ9.KOobRHDYyaesV_doOk11XXGKSONwzllraAaqqM4VFE4").build();
token.decode();
assertThat(token.getHeader()).contains("\"alg\":\"HS256\"");
assertThat(token.getPayload()).contains("{\"te");
}
@SneakyThrows
private String toString(Map<String, String> map) {
var mapper = new ObjectMapper();
return mapper.writeValueAsString(map);
}
}