Skip to content

Commit

Permalink
Add JWT unit tests (#467)
Browse files Browse the repository at this point in the history
* Manage JWTAlgorithmKMS lifecycle with Spring (#398)

Makes it easier to use in unit tests

* Move creation of token with JWK header to IJWTTokenGenerator (#398)

Allows reuse in controller unit test

* Replace String body type of GET-RequestEntities with Void (#398)

* Add unit test for JWTVulnerability controller (#398)

* Add unit test for JWTValidator (#398)
  • Loading branch information
kjosh authored Dec 25, 2023
1 parent a06af46 commit 1145456
Show file tree
Hide file tree
Showing 6 changed files with 917 additions and 73 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.sasanlabs.service.vulnerability.jwt;

import java.io.UnsupportedEncodingException;
import java.security.KeyPair;
import java.security.PrivateKey;
import org.sasanlabs.service.exception.ServiceApplicationException;

Expand All @@ -22,6 +23,19 @@ public interface IJWTTokenGenerator {
String getJWTToken_RS256(String tokenToBeSigned, PrivateKey key)
throws ServiceApplicationException;

/**
* Generates JWT token containing a JWK header using Nimbus+Jose library
*
* @param payloadToBeSigned
* @param asymmetricAlgorithmKeyPair
* @return
* @throws ServiceApplicationException
* @throws UnsupportedEncodingException
*/
String getJWTTokenWithJWKHeader_RS256(
String payloadToBeSigned, KeyPair asymmetricAlgorithmKeyPair)
throws ServiceApplicationException, UnsupportedEncodingException;

/**
* Signs token using provided secretKey based on the provided algorithm. This method handles
* signing of token using HS*(Hmac + Sha*) based algorithm and returns entire JWT token with
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@

import static org.sasanlabs.service.vulnerability.jwt.bean.JWTUtils.GENERIC_BASE64_ENCODED_PAYLOAD;

import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.KeyUse;
import com.nimbusds.jose.jwk.RSAKey;
import java.io.UnsupportedEncodingException;
import java.security.KeyPair;
import java.security.interfaces.RSAPrivateKey;
Expand All @@ -14,10 +11,8 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.json.JSONObject;
import org.sasanlabs.internal.utility.LevelConstants;
import org.sasanlabs.internal.utility.annotations.AttackVector;
import org.sasanlabs.internal.utility.annotations.VulnerableAppRequestMapping;
Expand Down Expand Up @@ -56,15 +51,20 @@ public class JWTVulnerability {

private IJWTTokenGenerator libBasedJWTGenerator;
private IJWTValidator jwtValidator;
private JWTAlgorithmKMS jwtAlgorithmKMS;

private static final transient Logger LOGGER = LogManager.getLogger(JWTVulnerability.class);

private static final String JWT = "JWT";
private static final String JWT_COOKIE_KEY = JWT + "=";
static final String JWT = "JWT";
static final String JWT_COOKIE_KEY = JWT + "=";

public JWTVulnerability(IJWTTokenGenerator libBasedJWTGenerator, IJWTValidator jwtValidator) {
public JWTVulnerability(
IJWTTokenGenerator libBasedJWTGenerator,
IJWTValidator jwtValidator,
JWTAlgorithmKMS jwtAlgorithmKMS) {
this.libBasedJWTGenerator = libBasedJWTGenerator;
this.jwtValidator = jwtValidator;
this.jwtAlgorithmKMS = jwtAlgorithmKMS;
}

private ResponseEntity<GenericVulnerabilityResponseBean<String>> getJWTResponseBean(
Expand Down Expand Up @@ -99,7 +99,6 @@ private ResponseEntity<GenericVulnerabilityResponseBean<String>> getJWTResponseB
public ResponseEntity<GenericVulnerabilityResponseBean<String>>
getVulnerablePayloadLevelUnsecure(@RequestParam Map<String, String> queryParams)
throws UnsupportedEncodingException, ServiceApplicationException {
JWTAlgorithmKMS jwtAlgorithmKMS = new JWTAlgorithmKMS();
Optional<SymmetricAlgorithmKey> symmetricAlgorithmKey =
jwtAlgorithmKMS.getSymmetricAlgorithmKey(
JWTUtils.JWT_HMAC_SHA_256_ALGORITHM, KeyStrength.HIGH);
Expand Down Expand Up @@ -130,10 +129,9 @@ private ResponseEntity<GenericVulnerabilityResponseBean<String>> getJWTResponseB
htmlTemplate = "LEVEL_2/JWT_Level2")
public ResponseEntity<GenericVulnerabilityResponseBean<String>>
getVulnerablePayloadLevelUnsecure2CookieBased(
RequestEntity<String> requestEntity,
RequestEntity<Void> requestEntity,
@RequestParam Map<String, String> queryParams)
throws UnsupportedEncodingException, ServiceApplicationException {
JWTAlgorithmKMS jwtAlgorithmKMS = new JWTAlgorithmKMS();
Optional<SymmetricAlgorithmKey> symmetricAlgorithmKey =
jwtAlgorithmKMS.getSymmetricAlgorithmKey(
JWTUtils.JWT_HMAC_SHA_256_ALGORITHM, KeyStrength.HIGH);
Expand Down Expand Up @@ -183,10 +181,9 @@ private ResponseEntity<GenericVulnerabilityResponseBean<String>> getJWTResponseB
htmlTemplate = "LEVEL_2/JWT_Level2")
public ResponseEntity<GenericVulnerabilityResponseBean<String>>
getVulnerablePayloadLevelUnsecure3CookieBased(
RequestEntity<String> requestEntity,
RequestEntity<Void> requestEntity,
@RequestParam Map<String, String> queryParams)
throws UnsupportedEncodingException, ServiceApplicationException {
JWTAlgorithmKMS jwtAlgorithmKMS = new JWTAlgorithmKMS();
Optional<SymmetricAlgorithmKey> symmetricAlgorithmKey =
jwtAlgorithmKMS.getSymmetricAlgorithmKey(
JWTUtils.JWT_HMAC_SHA_256_ALGORITHM, KeyStrength.HIGH);
Expand Down Expand Up @@ -238,10 +235,9 @@ private ResponseEntity<GenericVulnerabilityResponseBean<String>> getJWTResponseB
htmlTemplate = "LEVEL_2/JWT_Level2")
public ResponseEntity<GenericVulnerabilityResponseBean<String>>
getVulnerablePayloadLevelUnsecure4CookieBased(
RequestEntity<String> requestEntity,
RequestEntity<Void> requestEntity,
@RequestParam Map<String, String> queryParams)
throws UnsupportedEncodingException, ServiceApplicationException {
JWTAlgorithmKMS jwtAlgorithmKMS = new JWTAlgorithmKMS();
Optional<SymmetricAlgorithmKey> symmetricAlgorithmKey =
jwtAlgorithmKMS.getSymmetricAlgorithmKey(
JWTUtils.JWT_HMAC_SHA_256_ALGORITHM, KeyStrength.LOW);
Expand Down Expand Up @@ -294,10 +290,9 @@ private ResponseEntity<GenericVulnerabilityResponseBean<String>> getJWTResponseB
htmlTemplate = "LEVEL_2/JWT_Level2")
public ResponseEntity<GenericVulnerabilityResponseBean<String>>
getVulnerablePayloadLevelUnsecure5CookieBased(
RequestEntity<String> requestEntity,
RequestEntity<Void> requestEntity,
@RequestParam Map<String, String> queryParams)
throws UnsupportedEncodingException, ServiceApplicationException {
JWTAlgorithmKMS jwtAlgorithmKMS = new JWTAlgorithmKMS();
Optional<SymmetricAlgorithmKey> symmetricAlgorithmKey =
jwtAlgorithmKMS.getSymmetricAlgorithmKey(
JWTUtils.JWT_HMAC_SHA_256_ALGORITHM, KeyStrength.HIGH);
Expand Down Expand Up @@ -351,10 +346,9 @@ private ResponseEntity<GenericVulnerabilityResponseBean<String>> getJWTResponseB
htmlTemplate = "LEVEL_2/JWT_Level2")
public ResponseEntity<GenericVulnerabilityResponseBean<String>>
getVulnerablePayloadLevelUnsecure6CookieBased(
RequestEntity<String> requestEntity,
RequestEntity<Void> requestEntity,
@RequestParam Map<String, String> queryParams)
throws UnsupportedEncodingException, ServiceApplicationException {
JWTAlgorithmKMS jwtAlgorithmKMS = new JWTAlgorithmKMS();
Optional<SymmetricAlgorithmKey> symmetricAlgorithmKey =
jwtAlgorithmKMS.getSymmetricAlgorithmKey(
JWTUtils.JWT_HMAC_SHA_256_ALGORITHM, KeyStrength.HIGH);
Expand Down Expand Up @@ -407,10 +401,9 @@ private ResponseEntity<GenericVulnerabilityResponseBean<String>> getJWTResponseB
@VulnerableAppRequestMapping(value = LevelConstants.LEVEL_7, htmlTemplate = "LEVEL_7/JWT_Level")
public ResponseEntity<GenericVulnerabilityResponseBean<String>>
getVulnerablePayloadLevelUnsecure7CookieBased(
RequestEntity<String> requestEntity,
RequestEntity<Void> requestEntity,
@RequestParam Map<String, String> queryParams)
throws UnsupportedEncodingException, ServiceApplicationException {
JWTAlgorithmKMS jwtAlgorithmKMS = new JWTAlgorithmKMS();
Optional<SymmetricAlgorithmKey> symmetricAlgorithmKey =
jwtAlgorithmKMS.getSymmetricAlgorithmKey(
JWTUtils.JWT_HMAC_SHA_256_ALGORITHM, KeyStrength.HIGH);
Expand Down Expand Up @@ -457,10 +450,9 @@ private ResponseEntity<GenericVulnerabilityResponseBean<String>> getJWTResponseB
htmlTemplate = "LEVEL_2/JWT_Level2")
public ResponseEntity<GenericVulnerabilityResponseBean<String>>
getVulnerablePayloadLevelUnsecure8CookieBased(
RequestEntity<String> requestEntity,
RequestEntity<Void> requestEntity,
@RequestParam Map<String, String> queryParams)
throws UnsupportedEncodingException, ServiceApplicationException {
JWTAlgorithmKMS jwtAlgorithmKMS = new JWTAlgorithmKMS();
Optional<KeyPair> asymmetricAlgorithmKeyPair =
jwtAlgorithmKMS.getAsymmetricAlgorithmKey("RS256");
LOGGER.info(
Expand Down Expand Up @@ -513,10 +505,9 @@ private ResponseEntity<GenericVulnerabilityResponseBean<String>> getJWTResponseB
htmlTemplate = "LEVEL_2/JWT_Level2")
public ResponseEntity<GenericVulnerabilityResponseBean<String>>
getVulnerablePayloadLevelUnsecure9CookieBased(
RequestEntity<String> requestEntity,
RequestEntity<Void> requestEntity,
@RequestParam Map<String, String> queryParams)
throws UnsupportedEncodingException, ServiceApplicationException {
JWTAlgorithmKMS jwtAlgorithmKMS = new JWTAlgorithmKMS();
Optional<KeyPair> asymmetricAlgorithmKeyPair =
jwtAlgorithmKMS.getAsymmetricAlgorithmKey("RS256");
LOGGER.info(
Expand All @@ -543,24 +534,9 @@ private ResponseEntity<GenericVulnerabilityResponseBean<String>> getJWTResponseB
}
}

JWK jwk =
new RSAKey.Builder((RSAPublicKey) asymmetricAlgorithmKeyPair.get().getPublic())
.keyUse(KeyUse.SIGNATURE)
.keyID(UUID.randomUUID().toString())
.build();
JSONObject header = new JSONObject();
header.put(JWTUtils.JWT_ALGORITHM_KEY_HEADER, "RS256");
header.put("typ", "JWT");
header.put(JWTUtils.JSON_WEB_KEY_HEADER, new JSONObject(jwk.toJSONString()));

String base64EncodedHeader =
JWTUtils.getBase64UrlSafeWithoutPaddingEncodedString(header.toString());
String token =
libBasedJWTGenerator.getJWTToken_RS256(
base64EncodedHeader
+ JWTUtils.JWT_TOKEN_PERIOD_CHARACTER
+ GENERIC_BASE64_ENCODED_PAYLOAD,
asymmetricAlgorithmKeyPair.get().getPrivate());
libBasedJWTGenerator.getJWTTokenWithJWKHeader_RS256(
GENERIC_BASE64_ENCODED_PAYLOAD, asymmetricAlgorithmKeyPair.get());
Map<String, List<String>> headers = new HashMap<>();
headers.put("Set-Cookie", Arrays.asList(JWT_COOKIE_KEY + token + "; httponly"));
ResponseEntity<GenericVulnerabilityResponseBean<String>> responseEntity =
Expand All @@ -580,10 +556,9 @@ private ResponseEntity<GenericVulnerabilityResponseBean<String>> getJWTResponseB
htmlTemplate = "LEVEL_2/JWT_Level2")
public ResponseEntity<GenericVulnerabilityResponseBean<String>>
getVulnerablePayloadLevelUnsecure10CookieBased(
RequestEntity<String> requestEntity,
RequestEntity<Void> requestEntity,
@RequestParam Map<String, String> queryParams)
throws UnsupportedEncodingException, ServiceApplicationException {
JWTAlgorithmKMS jwtAlgorithmKMS = new JWTAlgorithmKMS();
Optional<SymmetricAlgorithmKey> symmetricAlgorithmKey =
jwtAlgorithmKMS.getSymmetricAlgorithmKey(
JWTUtils.JWT_HMAC_SHA_256_ALGORITHM, KeyStrength.HIGH);
Expand Down Expand Up @@ -642,7 +617,7 @@ private ResponseEntity<GenericVulnerabilityResponseBean<String>> getJWTResponseB
// requestParameterLocation = RequestParameterLocation.COOKIE,
public ResponseEntity<GenericVulnerabilityResponseBean<String>>
getVulnerablePayloadLevelUnsecure11CookieBased(
RequestEntity<String> requestEntity,
RequestEntity<Void> requestEntity,
@RequestParam Map<String, String> queryParams)
throws UnsupportedEncodingException, ServiceApplicationException {
List<String> tokens = requestEntity.getHeaders().get("cookie");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
package org.sasanlabs.service.vulnerability.jwt.impl;

import static org.sasanlabs.service.vulnerability.jwt.bean.JWTUtils.GENERIC_BASE64_ENCODED_PAYLOAD;

import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.KeyUse;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.util.Base64URL;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.text.ParseException;
import java.util.UUID;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.json.JSONObject;
import org.sasanlabs.service.exception.ExceptionStatusCodeEnum;
import org.sasanlabs.service.exception.ServiceApplicationException;
import org.sasanlabs.service.vulnerability.jwt.IJWTTokenGenerator;
Expand Down Expand Up @@ -98,4 +107,27 @@ public String getJWTToken_RS256(String tokenToBeSigned, PrivateKey privateKey)
e);
}
}

@Override
public String getJWTTokenWithJWKHeader_RS256(
String payloadToBeSigned, KeyPair asymmetricAlgorithmKeyPair)
throws ServiceApplicationException, UnsupportedEncodingException {
JWK jwk =
new RSAKey.Builder((RSAPublicKey) asymmetricAlgorithmKeyPair.getPublic())
.keyUse(KeyUse.SIGNATURE)
.keyID(UUID.randomUUID().toString())
.build();
JSONObject header = new JSONObject();
header.put(JWTUtils.JWT_ALGORITHM_KEY_HEADER, "RS256");
header.put("typ", "JWT");
header.put(JWTUtils.JSON_WEB_KEY_HEADER, new JSONObject(jwk.toJSONString()));

String base64EncodedHeader =
JWTUtils.getBase64UrlSafeWithoutPaddingEncodedString(header.toString());
return getJWTToken_RS256(
base64EncodedHeader
+ JWTUtils.JWT_TOKEN_PERIOD_CHARACTER
+ GENERIC_BASE64_ENCODED_PAYLOAD,
asymmetricAlgorithmKeyPair.getPrivate());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,20 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.sasanlabs.internal.utility.JSONSerializationUtils;
import org.springframework.stereotype.Component;

/**
* Singleton class parses SymmetricAlgoKeys.json from scripts/JWT and holds for entire Lifecycle of
* Application Also this class is responsible to generate the Asymmetric Algorithm keys.
* Parses SymmetricAlgoKeys.json from scripts/JWT. Initialization is costly, reuse of one instance
* is recommended. Also this class is responsible to generate the Asymmetric Algorithm keys.
*
* @author KSASAN [email protected]
*/
@Component
public class JWTAlgorithmKMS {

private static final Object MUTEX = new Object();
private Set<SymmetricAlgorithmKey> symmetricAlgorithmKeySet;

private static volatile boolean initDone = false;

private static Set<SymmetricAlgorithmKey> symmetricAlgorithmKeySet;

public static Map<String, KeyPair> asymmetricAlgorithmKeyMap = new HashMap<String, KeyPair>();
public Map<String, KeyPair> asymmetricAlgorithmKeyMap = new HashMap<String, KeyPair>();

private static final String SYMMETRIC_KEYS_FILE = "/scripts/JWT/SymmetricAlgoKeys.json";

Expand All @@ -47,14 +45,16 @@ public class JWTAlgorithmKMS {
private static final transient Logger LOGGER = LogManager.getLogger(JWTAlgorithmKMS.class);

public JWTAlgorithmKMS() {
if (!initDone) {
synchronized (MUTEX) {
if (!initDone) {
initialize();
initDone = true;
}
}
try (InputStream jwtSymmetricKeyStream =
this.getClass().getResourceAsStream(SYMMETRIC_KEYS_FILE)) {
symmetricAlgorithmKeySet =
JSONSerializationUtils.deserialize(
jwtSymmetricKeyStream,
new TypeReference<Set<SymmetricAlgorithmKey>>() {});
} catch (IOException e) {
LOGGER.error("Following error occurred while parsing SymmetricAlgoKeys", e);
}
loadAsymmetricAlgorithmKeys();
}

/**
Expand Down Expand Up @@ -107,17 +107,4 @@ private void loadAsymmetricAlgorithmKeys() {
LOGGER.error(e);
}
}

private void initialize() {
try (InputStream jwtSymmetricKeyStream =
this.getClass().getResourceAsStream(SYMMETRIC_KEYS_FILE)) {
symmetricAlgorithmKeySet =
JSONSerializationUtils.deserialize(
jwtSymmetricKeyStream,
new TypeReference<Set<SymmetricAlgorithmKey>>() {});
} catch (IOException e) {
LOGGER.error("Following error occurred while parsing SymmetricAlgoKeys", e);
}
loadAsymmetricAlgorithmKeys();
}
}
Loading

0 comments on commit 1145456

Please sign in to comment.