diff --git a/src/main/java/org/sasanlabs/service/vulnerability/jwt/IJWTTokenGenerator.java b/src/main/java/org/sasanlabs/service/vulnerability/jwt/IJWTTokenGenerator.java index d5c9f60d..4e960cdd 100755 --- a/src/main/java/org/sasanlabs/service/vulnerability/jwt/IJWTTokenGenerator.java +++ b/src/main/java/org/sasanlabs/service/vulnerability/jwt/IJWTTokenGenerator.java @@ -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; @@ -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 diff --git a/src/main/java/org/sasanlabs/service/vulnerability/jwt/JWTVulnerability.java b/src/main/java/org/sasanlabs/service/vulnerability/jwt/JWTVulnerability.java index d3de3f69..56e7e3c3 100644 --- a/src/main/java/org/sasanlabs/service/vulnerability/jwt/JWTVulnerability.java +++ b/src/main/java/org/sasanlabs/service/vulnerability/jwt/JWTVulnerability.java @@ -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; @@ -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; @@ -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> getJWTResponseBean( @@ -99,7 +99,6 @@ private ResponseEntity> getJWTResponseB public ResponseEntity> getVulnerablePayloadLevelUnsecure(@RequestParam Map queryParams) throws UnsupportedEncodingException, ServiceApplicationException { - JWTAlgorithmKMS jwtAlgorithmKMS = new JWTAlgorithmKMS(); Optional symmetricAlgorithmKey = jwtAlgorithmKMS.getSymmetricAlgorithmKey( JWTUtils.JWT_HMAC_SHA_256_ALGORITHM, KeyStrength.HIGH); @@ -130,10 +129,9 @@ private ResponseEntity> getJWTResponseB htmlTemplate = "LEVEL_2/JWT_Level2") public ResponseEntity> getVulnerablePayloadLevelUnsecure2CookieBased( - RequestEntity requestEntity, + RequestEntity requestEntity, @RequestParam Map queryParams) throws UnsupportedEncodingException, ServiceApplicationException { - JWTAlgorithmKMS jwtAlgorithmKMS = new JWTAlgorithmKMS(); Optional symmetricAlgorithmKey = jwtAlgorithmKMS.getSymmetricAlgorithmKey( JWTUtils.JWT_HMAC_SHA_256_ALGORITHM, KeyStrength.HIGH); @@ -183,10 +181,9 @@ private ResponseEntity> getJWTResponseB htmlTemplate = "LEVEL_2/JWT_Level2") public ResponseEntity> getVulnerablePayloadLevelUnsecure3CookieBased( - RequestEntity requestEntity, + RequestEntity requestEntity, @RequestParam Map queryParams) throws UnsupportedEncodingException, ServiceApplicationException { - JWTAlgorithmKMS jwtAlgorithmKMS = new JWTAlgorithmKMS(); Optional symmetricAlgorithmKey = jwtAlgorithmKMS.getSymmetricAlgorithmKey( JWTUtils.JWT_HMAC_SHA_256_ALGORITHM, KeyStrength.HIGH); @@ -238,10 +235,9 @@ private ResponseEntity> getJWTResponseB htmlTemplate = "LEVEL_2/JWT_Level2") public ResponseEntity> getVulnerablePayloadLevelUnsecure4CookieBased( - RequestEntity requestEntity, + RequestEntity requestEntity, @RequestParam Map queryParams) throws UnsupportedEncodingException, ServiceApplicationException { - JWTAlgorithmKMS jwtAlgorithmKMS = new JWTAlgorithmKMS(); Optional symmetricAlgorithmKey = jwtAlgorithmKMS.getSymmetricAlgorithmKey( JWTUtils.JWT_HMAC_SHA_256_ALGORITHM, KeyStrength.LOW); @@ -294,10 +290,9 @@ private ResponseEntity> getJWTResponseB htmlTemplate = "LEVEL_2/JWT_Level2") public ResponseEntity> getVulnerablePayloadLevelUnsecure5CookieBased( - RequestEntity requestEntity, + RequestEntity requestEntity, @RequestParam Map queryParams) throws UnsupportedEncodingException, ServiceApplicationException { - JWTAlgorithmKMS jwtAlgorithmKMS = new JWTAlgorithmKMS(); Optional symmetricAlgorithmKey = jwtAlgorithmKMS.getSymmetricAlgorithmKey( JWTUtils.JWT_HMAC_SHA_256_ALGORITHM, KeyStrength.HIGH); @@ -351,10 +346,9 @@ private ResponseEntity> getJWTResponseB htmlTemplate = "LEVEL_2/JWT_Level2") public ResponseEntity> getVulnerablePayloadLevelUnsecure6CookieBased( - RequestEntity requestEntity, + RequestEntity requestEntity, @RequestParam Map queryParams) throws UnsupportedEncodingException, ServiceApplicationException { - JWTAlgorithmKMS jwtAlgorithmKMS = new JWTAlgorithmKMS(); Optional symmetricAlgorithmKey = jwtAlgorithmKMS.getSymmetricAlgorithmKey( JWTUtils.JWT_HMAC_SHA_256_ALGORITHM, KeyStrength.HIGH); @@ -407,10 +401,9 @@ private ResponseEntity> getJWTResponseB @VulnerableAppRequestMapping(value = LevelConstants.LEVEL_7, htmlTemplate = "LEVEL_7/JWT_Level") public ResponseEntity> getVulnerablePayloadLevelUnsecure7CookieBased( - RequestEntity requestEntity, + RequestEntity requestEntity, @RequestParam Map queryParams) throws UnsupportedEncodingException, ServiceApplicationException { - JWTAlgorithmKMS jwtAlgorithmKMS = new JWTAlgorithmKMS(); Optional symmetricAlgorithmKey = jwtAlgorithmKMS.getSymmetricAlgorithmKey( JWTUtils.JWT_HMAC_SHA_256_ALGORITHM, KeyStrength.HIGH); @@ -457,10 +450,9 @@ private ResponseEntity> getJWTResponseB htmlTemplate = "LEVEL_2/JWT_Level2") public ResponseEntity> getVulnerablePayloadLevelUnsecure8CookieBased( - RequestEntity requestEntity, + RequestEntity requestEntity, @RequestParam Map queryParams) throws UnsupportedEncodingException, ServiceApplicationException { - JWTAlgorithmKMS jwtAlgorithmKMS = new JWTAlgorithmKMS(); Optional asymmetricAlgorithmKeyPair = jwtAlgorithmKMS.getAsymmetricAlgorithmKey("RS256"); LOGGER.info( @@ -513,10 +505,9 @@ private ResponseEntity> getJWTResponseB htmlTemplate = "LEVEL_2/JWT_Level2") public ResponseEntity> getVulnerablePayloadLevelUnsecure9CookieBased( - RequestEntity requestEntity, + RequestEntity requestEntity, @RequestParam Map queryParams) throws UnsupportedEncodingException, ServiceApplicationException { - JWTAlgorithmKMS jwtAlgorithmKMS = new JWTAlgorithmKMS(); Optional asymmetricAlgorithmKeyPair = jwtAlgorithmKMS.getAsymmetricAlgorithmKey("RS256"); LOGGER.info( @@ -543,24 +534,9 @@ private ResponseEntity> 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> headers = new HashMap<>(); headers.put("Set-Cookie", Arrays.asList(JWT_COOKIE_KEY + token + "; httponly")); ResponseEntity> responseEntity = @@ -580,10 +556,9 @@ private ResponseEntity> getJWTResponseB htmlTemplate = "LEVEL_2/JWT_Level2") public ResponseEntity> getVulnerablePayloadLevelUnsecure10CookieBased( - RequestEntity requestEntity, + RequestEntity requestEntity, @RequestParam Map queryParams) throws UnsupportedEncodingException, ServiceApplicationException { - JWTAlgorithmKMS jwtAlgorithmKMS = new JWTAlgorithmKMS(); Optional symmetricAlgorithmKey = jwtAlgorithmKMS.getSymmetricAlgorithmKey( JWTUtils.JWT_HMAC_SHA_256_ALGORITHM, KeyStrength.HIGH); @@ -642,7 +617,7 @@ private ResponseEntity> getJWTResponseB // requestParameterLocation = RequestParameterLocation.COOKIE, public ResponseEntity> getVulnerablePayloadLevelUnsecure11CookieBased( - RequestEntity requestEntity, + RequestEntity requestEntity, @RequestParam Map queryParams) throws UnsupportedEncodingException, ServiceApplicationException { List tokens = requestEntity.getHeaders().get("cookie"); diff --git a/src/main/java/org/sasanlabs/service/vulnerability/jwt/impl/LibBasedJWTGenerator.java b/src/main/java/org/sasanlabs/service/vulnerability/jwt/impl/LibBasedJWTGenerator.java index 1253e01f..1310a0cb 100755 --- a/src/main/java/org/sasanlabs/service/vulnerability/jwt/impl/LibBasedJWTGenerator.java +++ b/src/main/java/org/sasanlabs/service/vulnerability/jwt/impl/LibBasedJWTGenerator.java @@ -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; @@ -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()); + } } diff --git a/src/main/java/org/sasanlabs/service/vulnerability/jwt/keys/JWTAlgorithmKMS.java b/src/main/java/org/sasanlabs/service/vulnerability/jwt/keys/JWTAlgorithmKMS.java index 520067f5..660fd7b1 100644 --- a/src/main/java/org/sasanlabs/service/vulnerability/jwt/keys/JWTAlgorithmKMS.java +++ b/src/main/java/org/sasanlabs/service/vulnerability/jwt/keys/JWTAlgorithmKMS.java @@ -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 preetkaran20@gmail.com */ +@Component public class JWTAlgorithmKMS { - private static final Object MUTEX = new Object(); + private Set symmetricAlgorithmKeySet; - private static volatile boolean initDone = false; - - private static Set symmetricAlgorithmKeySet; - - public static Map asymmetricAlgorithmKeyMap = new HashMap(); + public Map asymmetricAlgorithmKeyMap = new HashMap(); private static final String SYMMETRIC_KEYS_FILE = "/scripts/JWT/SymmetricAlgoKeys.json"; @@ -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>() {}); + } catch (IOException e) { + LOGGER.error("Following error occurred while parsing SymmetricAlgoKeys", e); } + loadAsymmetricAlgorithmKeys(); } /** @@ -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>() {}); - } catch (IOException e) { - LOGGER.error("Following error occurred while parsing SymmetricAlgoKeys", e); - } - loadAsymmetricAlgorithmKeys(); - } } diff --git a/src/test/java/org/sasanlabs/service/vulnerability/jwt/JWTVulnerabilityTest.java b/src/test/java/org/sasanlabs/service/vulnerability/jwt/JWTVulnerabilityTest.java new file mode 100644 index 00000000..1ecc8ef6 --- /dev/null +++ b/src/test/java/org/sasanlabs/service/vulnerability/jwt/JWTVulnerabilityTest.java @@ -0,0 +1,572 @@ +package org.sasanlabs.service.vulnerability.jwt; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.io.UnsupportedEncodingException; +import java.security.KeyPair; +import java.util.HashMap; +import java.util.Optional; +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.sasanlabs.service.exception.ServiceApplicationException; +import org.sasanlabs.service.vulnerability.bean.GenericVulnerabilityResponseBean; +import org.sasanlabs.service.vulnerability.jwt.bean.JWTUtils; +import org.sasanlabs.service.vulnerability.jwt.impl.JWTValidator; +import org.sasanlabs.service.vulnerability.jwt.impl.LibBasedJWTGenerator; +import org.sasanlabs.service.vulnerability.jwt.keys.JWTAlgorithmKMS; +import org.sasanlabs.service.vulnerability.jwt.keys.KeyStrength; +import org.sasanlabs.service.vulnerability.jwt.keys.SymmetricAlgorithmKey; +import org.springframework.http.*; + +/** + * Tests for {@link JWTVulnerability} + * + * @author Joshua Kwiatkowski kw.joshua@mailbox.org + */ +class JWTVulnerabilityTest { + private static final HashMap EMPTY_QUERY = new HashMap<>(); + + private static JWTVulnerability jwtVulnerability; + private static String invalidToken; + private static String validHighStrengthToken; + private static String validLowStrengthToken; + private static String validAsymmetricToken; + private static String validAsymmetricTokenWithJwk; + private static HashMap fetchQuery; + private static JWTAlgorithmKMS jwtAlgorithmKmsSpy; + private static JWTValidator jwtValidator; + + @BeforeAll + static void setUpAll() throws UnsupportedEncodingException, ServiceApplicationException { + IJWTTokenGenerator jwtTokenGenerator = new LibBasedJWTGenerator(); + validHighStrengthToken = createSymmetricToken(KeyStrength.HIGH, jwtTokenGenerator); + invalidToken = validHighStrengthToken + "1"; + validLowStrengthToken = createSymmetricToken(KeyStrength.LOW, jwtTokenGenerator); + jwtAlgorithmKmsSpy = spy(new JWTAlgorithmKMS()); + validAsymmetricToken = createAsymmetricToken(jwtTokenGenerator, jwtAlgorithmKmsSpy); + validAsymmetricTokenWithJwk = + createAsymmetricTokenWithJwk(jwtTokenGenerator, jwtAlgorithmKmsSpy); + fetchQuery = new HashMap<>(); + fetchQuery.put("fetch", "true"); + jwtValidator = spy(new JWTValidator(jwtTokenGenerator)); + jwtVulnerability = + new JWTVulnerability(jwtTokenGenerator, jwtValidator, jwtAlgorithmKmsSpy); + } + + private static String createSymmetricToken( + KeyStrength keyStrength, IJWTTokenGenerator jwtTokenGenerator) + throws UnsupportedEncodingException, ServiceApplicationException { + Optional symmetricAlgorithmKey = + new JWTAlgorithmKMS() + .getSymmetricAlgorithmKey(JWTUtils.JWT_HMAC_SHA_256_ALGORITHM, keyStrength); + assertTrue(symmetricAlgorithmKey.isPresent(), "SymmetricAlgorithmKey should be present"); + return jwtTokenGenerator.getHMACSignedJWTToken( + JWTUtils.HS256_TOKEN_TO_BE_SIGNED, + JWTUtils.getBytes(symmetricAlgorithmKey.get().getKey()), + JWTUtils.JWT_HMAC_SHA_256_ALGORITHM); + } + + private static String createAsymmetricToken( + IJWTTokenGenerator jwtTokenGenerator, JWTAlgorithmKMS jwtAlgorithmKms) + throws ServiceApplicationException { + Optional asymmetricAlgorithmKeyPair = + jwtAlgorithmKms.getAsymmetricAlgorithmKey("RS256"); + assertTrue( + asymmetricAlgorithmKeyPair.isPresent(), "AsymmetricAlgorithmKey should be present"); + return jwtTokenGenerator.getJWTToken_RS256( + JWTUtils.RS256_TOKEN_TO_BE_SIGNED, asymmetricAlgorithmKeyPair.get().getPrivate()); + } + + private static String createAsymmetricTokenWithJwk( + IJWTTokenGenerator jwtTokenGenerator, JWTAlgorithmKMS jwtAlgorithmKms) + throws ServiceApplicationException, UnsupportedEncodingException { + Optional asymmetricAlgorithmKeyPair = + jwtAlgorithmKms.getAsymmetricAlgorithmKey("RS256"); + assertTrue( + asymmetricAlgorithmKeyPair.isPresent(), "AsymmetricAlgorithmKey should be present"); + return jwtTokenGenerator.getJWTTokenWithJWKHeader_RS256( + JWTUtils.GENERIC_BASE64_ENCODED_PAYLOAD, asymmetricAlgorithmKeyPair.get()); + } + + @BeforeEach + void resetSpies() { + reset(jwtAlgorithmKmsSpy); + reset(jwtValidator); + } + + private static void assertValidOkResponse( + ResponseEntity> response) { + assertEquals( + HttpStatus.OK.value(), + response.getStatusCodeValue(), + "Response code should be 200 - OK"); + assertNotNull(response.getBody(), "Response body should not be null"); + assertTrue(response.getBody().getIsValid(), "Response body should be marked valid"); + } + + private static void assertValidUnauthorizedResponse( + ResponseEntity> response) { + assertEquals( + HttpStatus.UNAUTHORIZED.value(), + response.getStatusCodeValue(), + "Response code should be 401 - Unauthorized"); + assertNotNull(response.getBody(), "Response body should not be null"); + assertFalse(response.getBody().getIsValid(), "Response body should not be marked valid"); + } + + private static void assertTokenInBody( + ResponseEntity> response) { + assertTrue( + StringUtils.isNotBlank(response.getBody().getContent()), + "Response body should contain JWT token"); + } + + private static void assertNoTokenInBody( + ResponseEntity> response) { + assertTrue( + StringUtils.isBlank(response.getBody().getContent()), + "Response body should NOT contain JWT token"); + } + + private void assertTokenInCookie( + ResponseEntity> response, + String expectedToken, + boolean httpOnlyExpected) { + HttpHeaders headers = response.getHeaders(); + assertTrue( + headers.containsKey(HttpHeaders.SET_COOKIE), + "Response headers should contain a cookie"); + String cookie = headers.getFirst(HttpHeaders.SET_COOKIE); + assertTrue( + cookie.contains(JWTVulnerability.JWT_COOKIE_KEY), + "Cookie should contain the JWT cookie key"); + assertTrue(cookie.contains(expectedToken), "Cookie should contain the JWT token"); + assertSame( + httpOnlyExpected, + cookie.contains("httponly"), + "Cookie should" + (!httpOnlyExpected ? " NOT " : "") + "contain HttpOnly flag"); + } + + private void assertTokenInAuthorizationHeader( + ResponseEntity> response, + String expectedToken) { + HttpHeaders headers = response.getHeaders(); + assertTrue( + headers.containsKey(HttpHeaders.AUTHORIZATION), + "Response should contain Authorization header"); + String authorization = headers.getFirst(HttpHeaders.AUTHORIZATION); + assertEquals( + expectedToken, authorization, "Authorization header should contain the JWT token"); + } + + private static void verifySymmetricAlgorithmKeyCreation(KeyStrength keyStrength) { + verify(jwtAlgorithmKmsSpy, times(1)) + .getSymmetricAlgorithmKey(JWTUtils.JWT_HMAC_SHA_256_ALGORITHM, keyStrength); + } + + @Test + @DisplayName("Level 1 - Test that a token is generated if none is submitted") + void testLevel1Creation() throws Exception { + ResponseEntity> response = + jwtVulnerability.getVulnerablePayloadLevelUnsecure(new HashMap<>()); + assertValidOkResponse(response); + assertNotNull(response.getBody(), "Response body should not be null"); + assertTokenInBody(response); + verifySymmetricAlgorithmKeyCreation(KeyStrength.HIGH); + } + + @Test + @DisplayName("Level 1 - Test that a valid token is validated successfully") + void testLevel1SuccessfulValidation() throws Exception { + HashMap query = new HashMap<>(); + query.put(JWTVulnerability.JWT, validHighStrengthToken); + ResponseEntity> response = + jwtVulnerability.getVulnerablePayloadLevelUnsecure(query); + assertValidOkResponse(response); + assertNoTokenInBody(response); + } + + @Test + @DisplayName("Level 1 - Test that an invalid token is not validated successfully") + void testLevel1FailedValidation() throws Exception { + HashMap query = new HashMap<>(); + query.put(JWTVulnerability.JWT, invalidToken); + ResponseEntity> response = + jwtVulnerability.getVulnerablePayloadLevelUnsecure(query); + assertValidUnauthorizedResponse(response); + assertEquals( + response.getBody().getContent(), + invalidToken, + "Response body should contain submitted JWT token"); + } + + private static RequestEntity getCookieTokenRequest(String token) { + return RequestEntity.get("/") + .header("cookie", JWTVulnerability.JWT_COOKIE_KEY + token) + .build(); + } + + @Test + @DisplayName("Level 2 - Test that a valid token cookie is validated successfully") + void testLevel2SuccessfulValidation() throws Exception { + RequestEntity requestEntity = getCookieTokenRequest(validHighStrengthToken); + ResponseEntity> response = + jwtVulnerability.getVulnerablePayloadLevelUnsecure2CookieBased( + requestEntity, EMPTY_QUERY); + assertValidOkResponse(response); + assertNoTokenInBody(response); + verifySymmetricAlgorithmKeyCreation(KeyStrength.HIGH); + } + + @Test + @DisplayName("Level 2 - Test that an invalid token cookie is not validated successfully") + void testLevel2FailedValidation() throws Exception { + RequestEntity requestEntity = getCookieTokenRequest(invalidToken); + ResponseEntity> response = + jwtVulnerability.getVulnerablePayloadLevelUnsecure2CookieBased( + requestEntity, EMPTY_QUERY); + assertValidUnauthorizedResponse(response); + assertTokenInBody(response); + } + + @Test + @DisplayName("Level 2 - Test that a token cookie is generated with fetch") + void testLevel2FetchCookieGeneration() throws Exception { + RequestEntity requestEntity = getCookieTokenRequest(invalidToken); + ResponseEntity> response = + jwtVulnerability.getVulnerablePayloadLevelUnsecure2CookieBased( + requestEntity, fetchQuery); + assertValidOkResponse(response); + assertTokenInBody(response); + assertTokenInCookie(response, response.getBody().getContent(), false); + } + + @Test + @DisplayName("Level 3 - Test that a valid token cookie is validated successfully") + void testLevel3SuccessfulValidation() throws Exception { + RequestEntity requestEntity = getCookieTokenRequest(validHighStrengthToken); + ResponseEntity> response = + jwtVulnerability.getVulnerablePayloadLevelUnsecure3CookieBased( + requestEntity, EMPTY_QUERY); + assertValidOkResponse(response); + assertNoTokenInBody(response); + assertTokenInCookie(response, validHighStrengthToken, true); + verifySymmetricAlgorithmKeyCreation(KeyStrength.HIGH); + } + + @Test + @DisplayName("Level 3 - Test that an invalid token cookie is not validated successfully") + void testLevel3FailedValidation() throws Exception { + RequestEntity requestEntity = getCookieTokenRequest(invalidToken); + ResponseEntity> response = + jwtVulnerability.getVulnerablePayloadLevelUnsecure3CookieBased( + requestEntity, EMPTY_QUERY); + assertValidUnauthorizedResponse(response); + assertTokenInBody(response); + assertTokenInCookie(response, invalidToken, true); + } + + @Test + @DisplayName("Level 3 - Test that a token cookie is generated with fetch") + void testLevel3FetchCookieGeneration() throws Exception { + RequestEntity requestEntity = getCookieTokenRequest(invalidToken); + ResponseEntity> response = + jwtVulnerability.getVulnerablePayloadLevelUnsecure3CookieBased( + requestEntity, fetchQuery); + assertValidOkResponse(response); + assertTokenInBody(response); + assertTokenInCookie(response, response.getBody().getContent(), true); + } + + @Test + @DisplayName("Level 4 - Test that a valid token cookie is validated successfully") + void testLevel4SuccessfulValidation() throws Exception { + RequestEntity requestEntity = getCookieTokenRequest(validLowStrengthToken); + ResponseEntity> response = + jwtVulnerability.getVulnerablePayloadLevelUnsecure4CookieBased( + requestEntity, EMPTY_QUERY); + assertValidOkResponse(response); + assertNoTokenInBody(response); + assertTokenInCookie(response, validLowStrengthToken, true); + verifySymmetricAlgorithmKeyCreation(KeyStrength.LOW); + } + + @Test + @DisplayName("Level 4 - Test that an invalid token cookie is not validated successfully") + void testLevel4FailedValidation() throws Exception { + RequestEntity requestEntity = getCookieTokenRequest(invalidToken); + ResponseEntity> response = + jwtVulnerability.getVulnerablePayloadLevelUnsecure4CookieBased( + requestEntity, EMPTY_QUERY); + assertValidUnauthorizedResponse(response); + assertTokenInBody(response); + assertTokenInCookie(response, invalidToken, true); + verifySymmetricAlgorithmKeyCreation(KeyStrength.LOW); + } + + @Test + @DisplayName("Level 4 - Test that a token cookie is generated with fetch") + void testLevel4FetchCookieGeneration() throws Exception { + RequestEntity requestEntity = getCookieTokenRequest(invalidToken); + ResponseEntity> response = + jwtVulnerability.getVulnerablePayloadLevelUnsecure4CookieBased( + requestEntity, fetchQuery); + assertValidOkResponse(response); + assertTokenInBody(response); + assertTokenInCookie(response, response.getBody().getContent(), true); + verifySymmetricAlgorithmKeyCreation(KeyStrength.LOW); + } + + @Test + @DisplayName("Level 5 - Test that a valid token cookie is validated successfully") + void testLevel5SuccessfulValidation() throws Exception { + RequestEntity requestEntity = getCookieTokenRequest(validHighStrengthToken); + ResponseEntity> response = + jwtVulnerability.getVulnerablePayloadLevelUnsecure5CookieBased( + requestEntity, EMPTY_QUERY); + assertValidOkResponse(response); + assertNoTokenInBody(response); + assertTokenInCookie(response, validHighStrengthToken, true); + verifySymmetricAlgorithmKeyCreation(KeyStrength.HIGH); + verify(jwtValidator, times(1)) + .customHMACNullByteVulnerableValidator( + eq(validHighStrengthToken), any(), eq(JWTUtils.JWT_HMAC_SHA_256_ALGORITHM)); + } + + @Test + @DisplayName("Level 5 - Test that an invalid token cookie is not validated successfully") + void testLevel5FailedValidation() throws Exception { + RequestEntity requestEntity = getCookieTokenRequest(invalidToken); + ResponseEntity> response = + jwtVulnerability.getVulnerablePayloadLevelUnsecure5CookieBased( + requestEntity, EMPTY_QUERY); + assertValidUnauthorizedResponse(response); + assertTokenInBody(response); + assertTokenInCookie(response, invalidToken, true); + verify(jwtValidator, times(1)) + .customHMACNullByteVulnerableValidator( + eq(invalidToken), any(), eq(JWTUtils.JWT_HMAC_SHA_256_ALGORITHM)); + } + + @Test + @DisplayName("Level 5 - Test that a token cookie is generated with fetch") + void testLevel5FetchCookieGeneration() throws Exception { + RequestEntity requestEntity = getCookieTokenRequest(invalidToken); + ResponseEntity> response = + jwtVulnerability.getVulnerablePayloadLevelUnsecure5CookieBased( + requestEntity, fetchQuery); + assertValidOkResponse(response); + assertTokenInBody(response); + assertTokenInCookie(response, response.getBody().getContent(), true); + } + + @Test + @DisplayName("Level 6 - Test that a valid token cookie is validated successfully") + void testLevel6SuccessfulValidation() throws Exception { + RequestEntity requestEntity = getCookieTokenRequest(validHighStrengthToken); + ResponseEntity> response = + jwtVulnerability.getVulnerablePayloadLevelUnsecure6CookieBased( + requestEntity, EMPTY_QUERY); + assertValidOkResponse(response); + assertNoTokenInBody(response); + assertTokenInCookie(response, validHighStrengthToken, true); + verifySymmetricAlgorithmKeyCreation(KeyStrength.HIGH); + verify(jwtValidator, times(1)) + .customHMACNoneAlgorithmVulnerableValidator( + eq(validHighStrengthToken), any(), eq(JWTUtils.JWT_HMAC_SHA_256_ALGORITHM)); + } + + @Test + @DisplayName("Level 6 - Test that an invalid token cookie is not validated successfully") + void testLevel6FailedValidation() throws Exception { + RequestEntity requestEntity = getCookieTokenRequest(invalidToken); + ResponseEntity> response = + jwtVulnerability.getVulnerablePayloadLevelUnsecure6CookieBased( + requestEntity, EMPTY_QUERY); + assertValidUnauthorizedResponse(response); + assertTokenInBody(response); + assertTokenInCookie(response, invalidToken, true); + verify(jwtValidator, times(1)) + .customHMACNoneAlgorithmVulnerableValidator( + eq(invalidToken), any(), eq(JWTUtils.JWT_HMAC_SHA_256_ALGORITHM)); + } + + @Test + @DisplayName("Level 6 - Test that a token cookie is generated with fetch") + void testLevel6FetchCookieGeneration() throws Exception { + RequestEntity requestEntity = getCookieTokenRequest(invalidToken); + ResponseEntity> response = + jwtVulnerability.getVulnerablePayloadLevelUnsecure6CookieBased( + requestEntity, fetchQuery); + assertValidOkResponse(response); + assertTokenInBody(response); + assertTokenInCookie(response, response.getBody().getContent(), true); + } + + private static RequestEntity getAuthorizationTokenRequest(String token) { + return RequestEntity.get("/").header(HttpHeaders.AUTHORIZATION, token).build(); + } + + @Test + @DisplayName("Level 7 - Test that a valid authorization token is validated successfully") + void testLevel7SuccessfulValidation() throws Exception { + RequestEntity requestEntity = getAuthorizationTokenRequest(validHighStrengthToken); + ResponseEntity> response = + jwtVulnerability.getVulnerablePayloadLevelUnsecure7CookieBased( + requestEntity, EMPTY_QUERY); + assertValidOkResponse(response); + assertNoTokenInBody(response); + assertTokenInAuthorizationHeader(response, validHighStrengthToken); + verifySymmetricAlgorithmKeyCreation(KeyStrength.HIGH); + } + + @Test + @DisplayName("Level 7 - Test that an invalid authorization token is not validated successfully") + void testLevel7FailedValidation() throws Exception { + RequestEntity requestEntity = getAuthorizationTokenRequest(invalidToken); + ResponseEntity> response = + jwtVulnerability.getVulnerablePayloadLevelUnsecure7CookieBased( + requestEntity, EMPTY_QUERY); + assertValidUnauthorizedResponse(response); + assertTokenInBody(response); + assertTokenInAuthorizationHeader(response, invalidToken); + } + + @Test + @DisplayName("Level 7 - Test that an authorization token is generated with fetch") + void testLevel7FetchCookieGeneration() throws Exception { + RequestEntity requestEntity = getAuthorizationTokenRequest(invalidToken); + ResponseEntity> response = + jwtVulnerability.getVulnerablePayloadLevelUnsecure7CookieBased( + requestEntity, fetchQuery); + assertValidOkResponse(response); + assertTokenInBody(response); + assertTokenInAuthorizationHeader(response, response.getBody().getContent()); + } + + @Test + @DisplayName("Level 8 - Test that a valid token cookie is validated successfully") + void testLevel8SuccessfulValidation() throws Exception { + RequestEntity requestEntity = getCookieTokenRequest(validAsymmetricToken); + ResponseEntity> response = + jwtVulnerability.getVulnerablePayloadLevelUnsecure8CookieBased( + requestEntity, EMPTY_QUERY); + assertValidOkResponse(response); + assertNoTokenInBody(response); + assertTokenInCookie(response, validAsymmetricToken, true); + verify(jwtValidator, times(1)) + .confusionAlgorithmVulnerableValidator(eq(validAsymmetricToken), any()); + } + + @Test + @DisplayName("Level 8 - Test that an invalid token cookie is not validated successfully") + void testLevel8FailedValidation() throws Exception { + RequestEntity requestEntity = getCookieTokenRequest(invalidToken); + ResponseEntity> response = + jwtVulnerability.getVulnerablePayloadLevelUnsecure8CookieBased( + requestEntity, EMPTY_QUERY); + assertValidUnauthorizedResponse(response); + assertTokenInBody(response); + assertTokenInCookie(response, invalidToken, true); + verify(jwtValidator, times(1)) + .confusionAlgorithmVulnerableValidator(eq(invalidToken), any()); + } + + @Test + @DisplayName("Level 8 - Test that a token cookie is generated with fetch") + void testLevel8FetchCookieGeneration() throws Exception { + RequestEntity requestEntity = getCookieTokenRequest(invalidToken); + ResponseEntity> response = + jwtVulnerability.getVulnerablePayloadLevelUnsecure8CookieBased( + requestEntity, fetchQuery); + assertValidOkResponse(response); + assertTokenInBody(response); + assertTokenInCookie(response, response.getBody().getContent(), true); + } + + @Test + @DisplayName("Level 9 - Test that a valid token cookie is validated successfully") + void testLevel9SuccessfulValidation() throws Exception { + RequestEntity requestEntity = getCookieTokenRequest(validAsymmetricTokenWithJwk); + ResponseEntity> response = + jwtVulnerability.getVulnerablePayloadLevelUnsecure9CookieBased( + requestEntity, EMPTY_QUERY); + assertValidOkResponse(response); + assertNoTokenInBody(response); + assertTokenInCookie(response, validAsymmetricTokenWithJwk, true); + verify(jwtValidator, times(1)) + .jwkKeyHeaderPublicKeyTrustingVulnerableValidator(validAsymmetricTokenWithJwk); + } + + @Test + @DisplayName("Level 9 - Test that an invalid token cookie is not validated successfully") + void testLevel9FailedValidation() throws Exception { + RequestEntity requestEntity = getCookieTokenRequest(invalidToken); + ResponseEntity> response = + jwtVulnerability.getVulnerablePayloadLevelUnsecure9CookieBased( + requestEntity, EMPTY_QUERY); + assertValidUnauthorizedResponse(response); + assertTokenInBody(response); + assertTokenInCookie(response, invalidToken, true); + verify(jwtValidator, times(1)) + .jwkKeyHeaderPublicKeyTrustingVulnerableValidator(invalidToken); + } + + @Test + @DisplayName("Level 9 - Test that a token cookie is generated with fetch") + void testLevel9FetchCookieGeneration() throws Exception { + RequestEntity requestEntity = getCookieTokenRequest(invalidToken); + ResponseEntity> response = + jwtVulnerability.getVulnerablePayloadLevelUnsecure9CookieBased( + requestEntity, fetchQuery); + assertValidOkResponse(response); + assertTokenInBody(response); + assertTokenInCookie(response, response.getBody().getContent(), true); + } + + @Test + @DisplayName("Level 10 - Test that a valid token cookie is validated successfully") + void testLevel10SuccessfulValidation() throws Exception { + RequestEntity requestEntity = getCookieTokenRequest(validHighStrengthToken); + ResponseEntity> response = + jwtVulnerability.getVulnerablePayloadLevelUnsecure10CookieBased( + requestEntity, EMPTY_QUERY); + assertValidOkResponse(response); + assertNoTokenInBody(response); + assertTokenInCookie(response, validHighStrengthToken, true); + verify(jwtValidator, times(1)) + .customHMACEmptyTokenVulnerableValidator( + eq(validHighStrengthToken), any(), eq(JWTUtils.JWT_HMAC_SHA_256_ALGORITHM)); + } + + @Test + @DisplayName("Level 10 - Test that an invalid token cookie is not validated successfully") + void testLevel10FailedValidation() throws Exception { + RequestEntity requestEntity = getCookieTokenRequest(invalidToken); + ResponseEntity> response = + jwtVulnerability.getVulnerablePayloadLevelUnsecure10CookieBased( + requestEntity, EMPTY_QUERY); + assertValidUnauthorizedResponse(response); + assertTokenInBody(response); + assertTokenInCookie(response, invalidToken, true); + verify(jwtValidator, times(1)) + .customHMACEmptyTokenVulnerableValidator( + eq(invalidToken), any(), eq(JWTUtils.JWT_HMAC_SHA_256_ALGORITHM)); + } + + @Test + @DisplayName("Level 10 - Test that a token cookie is generated with fetch") + void testLevel10FetchCookieGeneration() throws Exception { + RequestEntity requestEntity = getCookieTokenRequest(invalidToken); + ResponseEntity> response = + jwtVulnerability.getVulnerablePayloadLevelUnsecure10CookieBased( + requestEntity, fetchQuery); + assertValidOkResponse(response); + assertTokenInBody(response); + assertTokenInCookie(response, response.getBody().getContent(), true); + } +} diff --git a/src/test/java/org/sasanlabs/service/vulnerability/jwt/impl/JWTValidatorTest.java b/src/test/java/org/sasanlabs/service/vulnerability/jwt/impl/JWTValidatorTest.java new file mode 100644 index 00000000..d17a222d --- /dev/null +++ b/src/test/java/org/sasanlabs/service/vulnerability/jwt/impl/JWTValidatorTest.java @@ -0,0 +1,264 @@ +package org.sasanlabs.service.vulnerability.jwt.impl; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.reset; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.security.KeyPair; +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.sasanlabs.service.exception.ServiceApplicationException; +import org.sasanlabs.service.vulnerability.jwt.IJWTTokenGenerator; +import org.sasanlabs.service.vulnerability.jwt.bean.JWTUtils; +import org.sasanlabs.service.vulnerability.jwt.keys.JWTAlgorithmKMS; +import org.sasanlabs.service.vulnerability.jwt.keys.KeyStrength; +import org.sasanlabs.service.vulnerability.jwt.keys.SymmetricAlgorithmKey; + +/** + * Tests for {@link JWTValidator} + * + * @author Joshua Kwiatkowski kw.joshua@mailbox.org + */ +class JWTValidatorTest { + private static SymmetricAlgorithmKey symmetricAlgorithmKey; + private static IJWTTokenGenerator jwtGenerator; + private static String validHmacToken; + private static String validRS256Token; + private static KeyPair asymmetricAlgorithmKeyPair; + + private static JWTValidator jwtValidator; + + @BeforeAll + static void beforeAll() throws Exception { + jwtGenerator = new LibBasedJWTGenerator(); + JWTAlgorithmKMS jwtAlgorithmKms = new JWTAlgorithmKMS(); + symmetricAlgorithmKey = + jwtAlgorithmKms + .getSymmetricAlgorithmKey( + JWTUtils.JWT_HMAC_SHA_256_ALGORITHM, KeyStrength.HIGH) + .orElseThrow(IllegalStateException::new); + asymmetricAlgorithmKeyPair = + jwtAlgorithmKms + .getAsymmetricAlgorithmKey("RS256") + .orElseThrow(IllegalStateException::new); + validHmacToken = getHmacSignedJWTToken(JWTUtils.HS256_TOKEN_TO_BE_SIGNED); + validRS256Token = + jwtGenerator.getJWTTokenWithJWKHeader_RS256( + JWTUtils.GENERIC_BASE64_ENCODED_PAYLOAD, asymmetricAlgorithmKeyPair); + + jwtValidator = Mockito.spy(new JWTValidator(jwtGenerator)); + } + + @BeforeEach + void resetSpies() { + reset(jwtValidator); + } + + private static String getHmacSignedJWTToken(String payload) + throws UnsupportedEncodingException, ServiceApplicationException { + return jwtGenerator.getHMACSignedJWTToken( + payload, + JWTUtils.getBytes(symmetricAlgorithmKey.getKey()), + JWTUtils.JWT_HMAC_SHA_256_ALGORITHM); + } + + @Test + @DisplayName("Test that customHMACValidator validates a valid token successfully") + void customHMACValidatorValidToken() throws Exception { + assertTrue( + jwtValidator.customHMACValidator( + validHmacToken, + JWTUtils.getBytes(symmetricAlgorithmKey.getKey()), + JWTUtils.JWT_HMAC_SHA_256_ALGORITHM)); + } + + @Test + @DisplayName("Test that customHMACValidator does not validate an invalid token successfully") + void customHMACValidatorInvalidToken() throws Exception { + assertFalse( + jwtValidator.customHMACValidator( + validHmacToken + "a", + JWTUtils.getBytes(symmetricAlgorithmKey.getKey()), + JWTUtils.JWT_HMAC_SHA_256_ALGORITHM)); + } + + @Test + @DisplayName( + "Test that customHMACNullByteVulnerableValidator validates a valid token successfully") + void customHMACNullByteVulnerableValidatorValidToken() throws Exception { + assertTrue( + jwtValidator.customHMACNullByteVulnerableValidator( + validHmacToken, + JWTUtils.getBytes(symmetricAlgorithmKey.getKey()), + JWTUtils.JWT_HMAC_SHA_256_ALGORITHM)); + } + + @Test + @DisplayName( + "Test that customHMACNullByteVulnerableValidator does not validate an invalid token successfully") + void customHMACNullByteVulnerableValidatorInvalidToken() throws Exception { + assertFalse( + jwtValidator.customHMACNullByteVulnerableValidator( + validHmacToken + "a", + JWTUtils.getBytes(symmetricAlgorithmKey.getKey()), + JWTUtils.JWT_HMAC_SHA_256_ALGORITHM)); + } + + @Test + @DisplayName( + "Test that customHMACNullByteVulnerableValidator stops reading the signature at a 0 byte") + void customHMACNullByteVulnerableValidatorStopsReadingSignatureAtNullByte() throws Exception { + String nullByte = + URLEncoder.encode(String.valueOf((char) 0), StandardCharsets.UTF_8.name()); + jwtValidator.customHMACNullByteVulnerableValidator( + validHmacToken + nullByte + "this will not be read", + JWTUtils.getBytes(symmetricAlgorithmKey.getKey()), + JWTUtils.JWT_HMAC_SHA_256_ALGORITHM); + Mockito.verify(jwtValidator, Mockito.times(1)) + .customHMACValidator(eq(validHmacToken), any(), any()); + } + + @Test + @DisplayName( + "Test that customHMACNoneAlgorithmVulnerableValidator validates a valid token successfully") + void customHMACNoneAlgorithmVulnerableValidatorValidToken() throws Exception { + assertTrue( + jwtValidator.customHMACNoneAlgorithmVulnerableValidator( + validHmacToken, + JWTUtils.getBytes(symmetricAlgorithmKey.getKey()), + JWTUtils.JWT_HMAC_SHA_256_ALGORITHM)); + } + + @Test + @DisplayName( + "Test that customHMACNoneAlgorithmVulnerableValidator is vulnerable to an algorithm set to 'none'") + void customHMACNoneAlgorithmVulnerableValidatorVulnerableToNoneAlgorithm() throws Exception { + String maliciousHeader = + JWTUtils.getBase64UrlSafeWithoutPaddingEncodedString("{'alg':'none','typ':'JWT'}"); + String maliciousPayload = + maliciousHeader + + "." + + StringUtils.substringAfter(JWTUtils.GENERIC_BASE64_ENCODED_PAYLOAD, "."); + String maliciousToken = getHmacSignedJWTToken(maliciousPayload); + assertTrue( + jwtValidator.customHMACNoneAlgorithmVulnerableValidator( + maliciousToken, + JWTUtils.getBytes(symmetricAlgorithmKey.getKey()), + JWTUtils.JWT_HMAC_SHA_256_ALGORITHM)); + } + + @Test + @DisplayName( + "Test that customHMACNoneAlgorithmVulnerableValidator does not validate an invalid token successfully") + void customHMACNoneAlgorithmVulnerableValidatorInvalidToken() throws Exception { + assertFalse( + jwtValidator.customHMACNoneAlgorithmVulnerableValidator( + validHmacToken + "a", + JWTUtils.getBytes(symmetricAlgorithmKey.getKey()), + JWTUtils.JWT_HMAC_SHA_256_ALGORITHM)); + } + + @Test + @DisplayName( + "Test that customHMACEmptyTokenVulnerableValidator validates a valid token successfully") + void customHMACEmptyTokenVulnerableValidatorValidToken() throws Exception { + assertTrue( + jwtValidator.customHMACEmptyTokenVulnerableValidator( + validHmacToken, + symmetricAlgorithmKey.getKey(), + JWTUtils.JWT_HMAC_SHA_256_ALGORITHM)); + } + + @Test + @DisplayName("Test that customHMACEmptyTokenVulnerableValidator is vulnerable to a '.' token") + void customHMACEmptyTokenVulnerableValidatorVulnerableToEmptyToken() throws Exception { + String maliciousToken = "."; + assertTrue( + jwtValidator.customHMACEmptyTokenVulnerableValidator( + maliciousToken, + symmetricAlgorithmKey.getKey(), + JWTUtils.JWT_HMAC_SHA_256_ALGORITHM)); + } + + @Test + @DisplayName( + "Test that customHMACEmptyTokenVulnerableValidator does not validate an invalid token successfully") + void customHMACEmptyTokenVulnerableValidatorInvalidToken() throws Exception { + assertFalse( + jwtValidator.customHMACEmptyTokenVulnerableValidator( + validHmacToken + "a", + symmetricAlgorithmKey.getKey(), + JWTUtils.JWT_HMAC_SHA_256_ALGORITHM)); + } + + @Test + @DisplayName( + "Test that confusionAlgorithmVulnerableValidator validates a valid token successfully") + void confusionAlgorithmVulnerableValidatorValidToken() throws Exception { + assertTrue( + jwtValidator.confusionAlgorithmVulnerableValidator( + validRS256Token, asymmetricAlgorithmKeyPair.getPublic())); + } + + @Test + @DisplayName( + "Test that confusionAlgorithmVulnerableValidator is vulnerable to a token signed with a symmetric algorithm using the public key") + void confusionAlgorithmVulnerableValidatorVulnerableToPublicKeyEncryptedToken() + throws Exception { + Key publicKey = asymmetricAlgorithmKeyPair.getPublic(); + String tokenSignedWithPublicKey = + jwtGenerator.getHMACSignedJWTToken( + JWTUtils.HS256_TOKEN_TO_BE_SIGNED, + publicKey.getEncoded(), + JWTUtils.JWT_HMAC_SHA_256_ALGORITHM); + assertTrue( + jwtValidator.confusionAlgorithmVulnerableValidator( + tokenSignedWithPublicKey, publicKey)); + } + + @Test + @DisplayName( + "Test that confusionAlgorithmVulnerableValidator does not validate an invalid token successfully") + void confusionAlgorithmVulnerableValidatorInvalidToken() throws Exception { + assertFalse( + jwtValidator.confusionAlgorithmVulnerableValidator( + validRS256Token + "a", asymmetricAlgorithmKeyPair.getPublic())); + } + + @Test + @DisplayName( + "Test that jwkKeyHeaderPublicKeyTrustingVulnerableValidator validates a valid token successfully") + void jwkKeyHeaderPublicKeyTrustingVulnerableValidatorValidToken() throws Exception { + assertTrue(jwtValidator.jwkKeyHeaderPublicKeyTrustingVulnerableValidator(validRS256Token)); + } + + @Test + @DisplayName( + "Test that jwkKeyHeaderPublicKeyTrustingVulnerableValidator trusts a public key submitted by the client") + void jwkKeyHeaderPublicKeyTrustingVulnerableValidatorVulnerableToPublicKeyEncryptedToken() + throws Exception { + String token = + jwtGenerator.getJWTTokenWithJWKHeader_RS256( + JWTUtils.HS256_TOKEN_TO_BE_SIGNED, asymmetricAlgorithmKeyPair); + assertTrue(jwtValidator.jwkKeyHeaderPublicKeyTrustingVulnerableValidator(token)); + } + + @Test + @DisplayName( + "Test that jwkKeyHeaderPublicKeyTrustingVulnerableValidator does not validate an invalid token successfully") + void jwkKeyHeaderPublicKeyTrustingVulnerableValidatorInvalidToken() throws Exception { + assertFalse( + jwtValidator.jwkKeyHeaderPublicKeyTrustingVulnerableValidator( + validRS256Token + "a")); + } +}