diff --git a/CHANGELOG.md b/CHANGELOG.md index 2970a000..afa40b07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## [vX.XX.X]() (XXX-XX-XX) - Bump Fuzzer version +- Support multiple cookie secret rotation ## [v6.13.0](https://github.com/PerimeterX/perimeterx-java-sdk/compare/6.13.0...HEAD) (2024-04-27) - Added vid Validation for _pxvid extraction diff --git a/src/main/java/com/perimeterx/api/PerimeterX.java b/src/main/java/com/perimeterx/api/PerimeterX.java index 1f09d0f9..5409ab0e 100644 --- a/src/main/java/com/perimeterx/api/PerimeterX.java +++ b/src/main/java/com/perimeterx/api/PerimeterX.java @@ -58,6 +58,7 @@ import com.perimeterx.utils.logger.IPXLogger; import com.perimeterx.utils.StringUtils; import com.perimeterx.utils.logger.LoggerFactory; +import edu.emory.mathcs.backport.java.util.Collections; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponseWrapper; @@ -68,8 +69,10 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Base64; +import java.util.List; import static com.perimeterx.utils.Constants.*; +import static com.perimeterx.utils.PXCommonUtils.cookieKeysToCheck; import static java.util.Objects.isNull; /** @@ -80,7 +83,7 @@ public class PerimeterX implements Closeable { - public static IPXLogger globalLogger = LoggerFactory.getGlobalLogger();; + public static IPXLogger globalLogger = LoggerFactory.getGlobalLogger(); private PXConfiguration configuration; private PXS2SValidator serverValidator; private PXCookieValidator cookieValidator; @@ -259,7 +262,7 @@ private void setAdditionalS2SActivityHeaders(HttpServletRequest request, PXConte public void pxPostVerify(ResponseWrapper response, PXContext context) throws PXException { try { - if (context != null){ + if (context != null) { if (response != null && !configuration.isAdditionalS2SActivityHeaderEnabled() && context.isContainCredentialsIntelligence()) { handleAdditionalS2SActivityWithCI(response, context); } @@ -303,21 +306,24 @@ public boolean isValidTelemetryRequest(HttpServletRequest request, PXContext con return false; } - final byte[] hmacBytes = HMACUtils.HMACString(timestamp, configuration.getCookieKey()); - final String generatedHmac = StringUtils.byteArrayToHexString(hmacBytes).toLowerCase(); + for (String key : cookieKeysToCheck(context, configuration)) { + final byte[] hmacBytes = HMACUtils.HMACString(timestamp, key); + final String generatedHmac = StringUtils.byteArrayToHexString(hmacBytes).toLowerCase(); - if (!MessageDigest.isEqual(generatedHmac.getBytes(), hmac.getBytes())) { - context.logger.error("Telemetry validation failed - invalid hmac, original=" + hmac + ", generated=" + generatedHmac); - return false; + if (MessageDigest.isEqual(generatedHmac.getBytes(), hmac.getBytes())) { + return true; + } } + context.logger.debug("Telemetry validation failed - invalid hmac, original=" + hmac); } catch (NoSuchAlgorithmException | InvalidKeyException | IllegalArgumentException e) { context.logger.error("Telemetry validation failed."); return false; } - return true; + return false; } + /** * Set activity handler * diff --git a/src/main/java/com/perimeterx/internals/cookie/AbstractPXCookie.java b/src/main/java/com/perimeterx/internals/cookie/AbstractPXCookie.java index a62ec1fa..d4c40bb8 100644 --- a/src/main/java/com/perimeterx/internals/cookie/AbstractPXCookie.java +++ b/src/main/java/com/perimeterx/internals/cookie/AbstractPXCookie.java @@ -11,13 +11,17 @@ import com.perimeterx.utils.logger.LogReason; import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; import java.util.Arrays; +import static com.perimeterx.utils.PXCommonUtils.cookieKeysToCheck; + /** * Created by nitzangoldfeder on 13/04/2017. */ @@ -38,7 +42,6 @@ public abstract class AbstractPXCookie implements PXCookie { protected PXConfiguration pxConfiguration; protected String pxCookie; protected JsonNode decodedCookie; - protected String cookieKey; protected String cookieOrig; public AbstractPXCookie(PXConfiguration pxConfiguration, CookieData cookieData, PXContext context) { @@ -49,7 +52,6 @@ public AbstractPXCookie(PXConfiguration pxConfiguration, CookieData cookieData, this.pxConfiguration = pxConfiguration; this.userAgent = cookieData.isMobileToken() ? "" : cookieData.getUserAgent(); this.ip = cookieData.getIp(); - this.cookieKey = pxConfiguration.getCookieKey(); this.cookieVersion = cookieData.getCookieVersion(); } @@ -81,7 +83,7 @@ public boolean deserialize() throws PXCookieDecryptionException { JsonNode decodedCookie; if (this.pxConfiguration.isEncryptionEnabled()) { - decodedCookie = this.decrypt(); + decodedCookie = this.decrypt(context); } else { decodedCookie = this.decode(); } @@ -94,7 +96,7 @@ public boolean deserialize() throws PXCookieDecryptionException { return true; } - private JsonNode decrypt() throws PXCookieDecryptionException { + private JsonNode decrypt(PXContext context) throws PXCookieDecryptionException { final String[] parts = this.pxCookie.split(":"); if (parts.length != 3) { throw new PXCookieDecryptionException("Part length invalid"); @@ -115,21 +117,30 @@ private JsonNode decrypt() throws PXCookieDecryptionException { final Cipher cipher; // aes-256-cbc decryptData no salt try { cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - final int dkLen = KEY_LEN + cipher.getBlockSize(); - PBKDF2Parameters p = new PBKDF2Parameters(HMAC_SHA_256, "UTF-8", salt, iterations); - byte[] dk = new PBKDF2Engine(p).deriveKey(this.cookieKey, dkLen); - byte[] key = Arrays.copyOf(dk, KEY_LEN); - byte[] iv = Arrays.copyOfRange(dk, KEY_LEN, dk.length); - SecretKey secretKey = new SecretKeySpec(key, "AES"); - IvParameterSpec parameterSpec = new IvParameterSpec(iv); - cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec); - final byte[] data = cipher.doFinal(encrypted, 0, encrypted.length); - - String decryptedString = new String(data, StandardCharsets.UTF_8); - return mapper.readTree(decryptedString); - } catch (Exception e) { - throw new PXCookieDecryptionException("Cookie decryption failed in reason => ".concat(e.getMessage())); + } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { + throw new PXCookieDecryptionException(e); + } + final int dkLen = KEY_LEN + cipher.getBlockSize(); + PBKDF2Parameters p = new PBKDF2Parameters(HMAC_SHA_256, "UTF-8", salt, iterations); + + for (String cookieKey : this.pxConfiguration.getCookieKeys()) { + try { + byte[] dk = new PBKDF2Engine(p).deriveKey(cookieKey, dkLen); + byte[] key = Arrays.copyOf(dk, KEY_LEN); + byte[] iv = Arrays.copyOfRange(dk, KEY_LEN, dk.length); + SecretKey secretKey = new SecretKeySpec(key, "AES"); + IvParameterSpec parameterSpec = new IvParameterSpec(iv); + cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec); + final byte[] data = cipher.doFinal(encrypted, 0, encrypted.length); + + String decryptedString = new String(data, StandardCharsets.UTF_8); + JsonNode result = mapper.readTree(decryptedString); + context.setCookieKeyUsed(cookieKey); + return result; + } catch (Exception ignored) { + } } + throw new PXCookieDecryptionException("Cookie decryption failed"); } private JsonNode decode() throws PXCookieDecryptionException { @@ -152,7 +163,8 @@ public boolean isExpired() { } public boolean isHmacValid(String hmacStr, String cookieHmac) { - boolean isValid = HMACUtils.isHMACValid(hmacStr, cookieHmac, this.cookieKey, logger); + boolean isValid = cookieKeysToCheck(this.context, this.pxConfiguration).stream() + .anyMatch(cookieKey -> HMACUtils.isHMACValid(hmacStr, cookieHmac, cookieKey, logger)); if (!isValid) { context.logger.debug(LogReason.DEBUG_COOKIE_DECRYPTION_HMAC_FAILED, pxCookie, this.userAgent); } diff --git a/src/main/java/com/perimeterx/internals/cookie/cookieparsers/HeaderParser.java b/src/main/java/com/perimeterx/internals/cookie/cookieparsers/HeaderParser.java index 9c6a6d7a..2243eef9 100644 --- a/src/main/java/com/perimeterx/internals/cookie/cookieparsers/HeaderParser.java +++ b/src/main/java/com/perimeterx/internals/cookie/cookieparsers/HeaderParser.java @@ -16,6 +16,7 @@ import java.util.*; import java.util.stream.Stream; +import static com.perimeterx.utils.PXCommonUtils.cookieKeysToCheck; import static java.util.stream.Collectors.toList; public abstract class HeaderParser { @@ -48,6 +49,9 @@ public List createRawCookieDataList(String... cookieHeaders) { } public DataEnrichmentCookie getRawDataEnrichmentCookie(List rawCookies, String cookieKey) { + return getRawDataEnrichmentCookie(rawCookies, Collections.singletonList(cookieKey)); + } + public DataEnrichmentCookie getRawDataEnrichmentCookie(List rawCookies, List cookieKeys) { ObjectMapper mapper = new ObjectMapper(); DataEnrichmentCookie dataEnrichmentCookie = new DataEnrichmentCookie(mapper.createObjectNode(), false); RawCookieData rawDataEnrichmentCookie = null; @@ -67,7 +71,8 @@ public DataEnrichmentCookie getRawDataEnrichmentCookie(List rawCo String hmac = cookiePayloadArray[0]; String encodedPayload = cookiePayloadArray[1]; - boolean isValid = HMACUtils.isHMACValid(encodedPayload, hmac, cookieKey, logger); + boolean isValid = cookieKeys.stream() + .anyMatch(cookieKey -> HMACUtils.isHMACValid(encodedPayload, hmac, cookieKey, logger)); dataEnrichmentCookie.setValid(isValid); byte[] decodedPayload = Base64.decode(encodedPayload); diff --git a/src/main/java/com/perimeterx/models/PXContext.java b/src/main/java/com/perimeterx/models/PXContext.java index 4073cb55..39d3bd90 100644 --- a/src/main/java/com/perimeterx/models/PXContext.java +++ b/src/main/java/com/perimeterx/models/PXContext.java @@ -41,6 +41,7 @@ import static com.perimeterx.utils.Constants.BREACHED_ACCOUNT_KEY_NAME; import static com.perimeterx.utils.Constants.LOGGER_TOKEN_HEADER_NAME; import static com.perimeterx.utils.PXCommonUtils.cookieHeadersNames; +import static com.perimeterx.utils.PXCommonUtils.cookieKeysToCheck; /** * PXContext - Populate relevant data from HttpRequest @@ -229,6 +230,11 @@ public class PXContext { private String pxhdDomain; private long enforcerStartTime; + /** + * The cookie key used to decrypt the cookie + */ + private String cookieKeyUsed; + /** * The base64 encoded request full url */ @@ -394,7 +400,7 @@ private void parseCookies(HttpServletRequest request, boolean isMobileToken) { setVidAndPxhd(cookies); tokens.addAll(headerParser.createRawCookieDataList(cookieHeaders)); this.tokens = tokens; - DataEnrichmentCookie deCookie = headerParser.getRawDataEnrichmentCookie(this.tokens, this.pxConfiguration.getCookieKey()); + DataEnrichmentCookie deCookie = headerParser.getRawDataEnrichmentCookie(this.tokens, cookieKeysToCheck(this, this.pxConfiguration)); this.pxde = deCookie.getJsonPayload(); this.pxdeVerified = deCookie.isValid(); } diff --git a/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java b/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java index a17fc9ca..1f0665d1 100644 --- a/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java +++ b/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java @@ -1,5 +1,6 @@ package com.perimeterx.models.configuration; +import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; import com.perimeterx.api.PerimeterX; import com.perimeterx.api.additionalContext.credentialsIntelligence.CIProtocol; @@ -31,11 +32,7 @@ import javax.servlet.http.HttpServletRequest; import java.io.IOException; -import java.lang.reflect.Field; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -66,7 +63,9 @@ public static void setPxLoggerSeverity(LoggerSeverity severity) { private String appId; @JsonProperty("px_cookie_secret") - private String cookieKey; + @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY) + @Singular + private List cookieKeys; @JsonProperty("px_auth_token") private String authToken; @@ -313,7 +312,7 @@ public static void setPxLoggerSeverity(LoggerSeverity severity) { * @return Configuration Object clone without cookieKey and authToken **/ public PXConfiguration getTelemetryConfig() { - return this.toBuilder().cookieKey(null).authToken(null).build(); + return this.toBuilder().clearCookieKeys().authToken(null).build(); } public void disableModule() { @@ -365,12 +364,11 @@ public ReverseProxy getReverseProxyInstance() { return reverseProxyInstance; } - public void update(PXDynamicConfiguration pxDynamicConfiguration) { PerimeterX.globalLogger.debug("Updating PXConfiguration file"); this.appId = pxDynamicConfiguration.getAppId(); this.checksum = pxDynamicConfiguration.getChecksum(); - this.cookieKey = pxDynamicConfiguration.getCookieSecret(); + this.cookieKeys = pxDynamicConfiguration.getCookieSecrets(); this.blockingScore = pxDynamicConfiguration.getBlockingScore(); this.apiTimeout = pxDynamicConfiguration.getApiConnectTimeout(); this.connectionTimeout = pxDynamicConfiguration.getApiConnectTimeout(); diff --git a/src/main/java/com/perimeterx/models/configuration/PXDynamicConfiguration.java b/src/main/java/com/perimeterx/models/configuration/PXDynamicConfiguration.java index 61dda021..8e0c277c 100644 --- a/src/main/java/com/perimeterx/models/configuration/PXDynamicConfiguration.java +++ b/src/main/java/com/perimeterx/models/configuration/PXDynamicConfiguration.java @@ -2,7 +2,10 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.Setter; +import java.util.List; import java.util.Set; /** @@ -16,7 +19,9 @@ public class PXDynamicConfiguration { @JsonProperty("checksum") private String checksum; @JsonProperty("cookieKey") - private String cookieSecret; + @Getter + @Setter + private List cookieSecrets; @JsonProperty("appId") private String appId; @JsonProperty("blockingScore") @@ -48,14 +53,6 @@ public void setChecksum(String checksum) { this.checksum = checksum; } - public String getCookieSecret() { - return cookieSecret; - } - - public void setCookieSecret(String cookieSecert) { - this.cookieSecret = cookieSecert; - } - public String getAppId() { return appId; } diff --git a/src/main/java/com/perimeterx/utils/PXCommonUtils.java b/src/main/java/com/perimeterx/utils/PXCommonUtils.java index b69c3721..0694d1bf 100644 --- a/src/main/java/com/perimeterx/utils/PXCommonUtils.java +++ b/src/main/java/com/perimeterx/utils/PXCommonUtils.java @@ -1,5 +1,6 @@ package com.perimeterx.utils; +import com.perimeterx.models.PXContext; import com.perimeterx.models.configuration.PXConfiguration; import org.apache.http.Header; import org.apache.http.HttpHeaders; @@ -19,6 +20,14 @@ public class PXCommonUtils { final static int FIRST_PARTY_CONNECTION_TIMEOUT_IN_MSEC = 4000; + public static List cookieKeysToCheck(PXContext context, PXConfiguration configuration) { + if (context.getCookieKeyUsed() == null) { + return configuration.getCookieKeys(); + } + + return Collections.singletonList(context.getCookieKeyUsed()); + } + public static List
getDefaultHeaders(String authToken) { Header contentType = new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json"); Header authorization = new BasicHeader(HttpHeaders.AUTHORIZATION, "Bearer " + authToken); diff --git a/src/test/java/com/perimeterx/api/PerimeterXTest.java b/src/test/java/com/perimeterx/api/PerimeterXTest.java index 9b4b1cfd..8bf453bf 100644 --- a/src/test/java/com/perimeterx/api/PerimeterXTest.java +++ b/src/test/java/com/perimeterx/api/PerimeterXTest.java @@ -45,7 +45,7 @@ public void testTelemetryObject() { Assert.assertTrue(!clonedConfig.equals(configuration)); Assert.assertTrue(!configuration.getAuthToken().equals(clonedConfig.getAuthToken())); - Assert.assertTrue(!configuration.getCookieKey().equals(clonedConfig.getCookieKey())); + Assert.assertTrue(!configuration.getCookieKeys().equals(clonedConfig.getCookieKeys())); Assert.assertEquals(clonedConfig.getAppId(), configuration.getAppId()); Assert.assertEquals(clonedConfig.isModuleEnabled(), configuration.isModuleEnabled()); diff --git a/src/test/java/com/perimeterx/api/RemoteConfigurationsTest.java b/src/test/java/com/perimeterx/api/RemoteConfigurationsTest.java index 23a7ef85..f3250705 100644 --- a/src/test/java/com/perimeterx/api/RemoteConfigurationsTest.java +++ b/src/test/java/com/perimeterx/api/RemoteConfigurationsTest.java @@ -15,6 +15,7 @@ import testutils.TestObjectUtils; import java.io.IOException; +import java.util.Collections; import java.util.HashSet; import static org.mockito.Mockito.mock; @@ -41,15 +42,15 @@ public void pullConfigurationsSuccess() throws IOException { RemoteConfigurationManager remoteConfigurationManager = new DefaultRemoteConfigManager(config, pxClient); TimerConfigUpdater timerConfigUpdater = new TimerConfigUpdater(remoteConfigurationManager, config, activityHandler); timerConfigUpdater.run(); - Assert.assertTrue(config.getAppId().equals("stub_app_id")); - Assert.assertTrue(config.getCookieKey().equals("stub_cookie_key")); - Assert.assertTrue(config.getChecksum().equals("stub_checksum")); - Assert.assertTrue(config.getBlockingScore() == 1000); - Assert.assertTrue(config.getConnectionTimeout() == 1500); - Assert.assertTrue(config.getApiTimeout() == 1500); - Assert.assertTrue(config.getSensitiveHeaders().equals(new HashSet())); - Assert.assertTrue(config.isModuleEnabled() == false); - Assert.assertTrue(config.getModuleMode().equals(ModuleMode.BLOCKING)); + Assert.assertEquals("stub_app_id", config.getAppId()); + Assert.assertEquals("stub_cookie_key", config.getCookieKeys().get(0)); + Assert.assertEquals("stub_checksum", config.getChecksum()); + Assert.assertEquals(1000, config.getBlockingScore()); + Assert.assertEquals(1500, config.getConnectionTimeout()); + Assert.assertEquals(1500, config.getApiTimeout()); + Assert.assertEquals(config.getSensitiveHeaders(), new HashSet()); + Assert.assertEquals(false, config.isModuleEnabled()); + Assert.assertEquals(config.getModuleMode(), ModuleMode.BLOCKING); } @Test @@ -71,7 +72,7 @@ private PXDynamicConfiguration getDynamicConfiguration(String appId, String chec pxDynamicConfig.setAppId(appId); pxDynamicConfig.setChecksum(checksum); pxDynamicConfig.setBlockingScore(blockingScore); - pxDynamicConfig.setCookieSecret(cookieSecert); + pxDynamicConfig.setCookieSecrets(Collections.singletonList(cookieSecert)); pxDynamicConfig.setS2sTimeout(s2sTimeout); pxDynamicConfig.setApiConnectTimeout(connectionTimeout); pxDynamicConfig.setSensitiveHeaders(sensitiveRotues); diff --git a/src/test/java/com/perimeterx/http/PXHttpClientTest.java b/src/test/java/com/perimeterx/http/PXHttpClientTest.java index 4efae94b..62905b04 100644 --- a/src/test/java/com/perimeterx/http/PXHttpClientTest.java +++ b/src/test/java/com/perimeterx/http/PXHttpClientTest.java @@ -43,7 +43,7 @@ public void testGetRemoteConfigurations() throws IOException { PXDynamicConfiguration config = pxClient.getConfigurationFromServer(); Assert.assertEquals("stub_app_id", config.getAppId()); Assert.assertEquals("stub_checksum", config.getChecksum()); - Assert.assertEquals("stub_cookie_key", config.getCookieSecret()); + Assert.assertEquals("stub_cookie_key", config.getCookieSecrets().get(0)); Assert.assertEquals(1000, config.getBlockingScore()); Assert.assertEquals(1500, config.getApiConnectTimeout()); Assert.assertEquals(1500, config.getS2sTimeout()); diff --git a/src/test/java/com/perimeterx/internal/TelemetryTest.java b/src/test/java/com/perimeterx/internal/TelemetryTest.java index a31fa9c6..3c131a66 100644 --- a/src/test/java/com/perimeterx/internal/TelemetryTest.java +++ b/src/test/java/com/perimeterx/internal/TelemetryTest.java @@ -16,6 +16,7 @@ import org.springframework.mock.web.MockHttpServletResponse; import org.testng.Assert; import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import testutils.ConfiguredTest; import testutils.TestObjectUtils; @@ -27,6 +28,7 @@ import static com.perimeterx.utils.Constants.DEFAULT_TELEMETRY_REQUEST_HEADER_NAME; import static org.mockito.Matchers.any; import static org.mockito.Mockito.*; +import static testutils.TestObjectUtils.cookieSecretsDataProvider; public class TelemetryTest extends ConfiguredTest { public static final int MS_IN_DAY = 86400000; @@ -38,6 +40,11 @@ public class TelemetryTest extends ConfiguredTest { private PerimeterX perimeterx; private HttpServletResponseWrapper mockHttpResponse; + @DataProvider(name = "cookieSecret") + public Object[][] cookieSecrets() { + return cookieSecretsDataProvider(this.configuration); + } + @BeforeMethod public void testSetup() { this.configuration = TestObjectUtils.generateConfiguration(); @@ -53,9 +60,9 @@ public void testSetup() { } } - @Test - public void testIsValidTelemetryRequestWithValidHeader() throws Exception { - final String encodedHmac = encodeHmac(this.configuration.getCookieKey(), VALID_EXPIRATION_TIME); + @Test(dataProvider = "cookieSecret") + public void testIsValidTelemetryRequestWithValidHeader(String cookieSecret) throws Exception { + final String encodedHmac = encodeHmac(cookieSecret, VALID_EXPIRATION_TIME); Assert.assertTrue(isValidTelemetryRequest(encodedHmac)); } @@ -68,10 +75,10 @@ public void testIsValidTelemetryRequestWithInvalidCookieSecret() throws Exceptio Assert.assertFalse(isValidTelemetryRequest(encodedHmac)); } - @Test - public void testIsValidTelemetryRequestWithInvalidTimestamp() throws Exception { + @Test(dataProvider = "cookieSecret") + public void testIsValidTelemetryRequestWithInvalidTimestamp(String cookieSecret) throws Exception { final String invalidExpiredTime = String.valueOf(System.currentTimeMillis() - MS_IN_DAY); - final String encodedHmac = encodeHmac(this.configuration.getCookieKey(), invalidExpiredTime); + final String encodedHmac = encodeHmac(cookieSecret, invalidExpiredTime); Assert.assertFalse(isValidTelemetryRequest(encodedHmac)); } @@ -89,18 +96,18 @@ public void testHandleTelemetryActivity() { Assert.assertFalse(thrown); } - @Test - public void testSendTelemetryWithValidHeader() throws Exception { - final String validHmac = encodeHmac(this.configuration.getCookieKey(), VALID_EXPIRATION_TIME); + @Test(dataProvider = "cookieSecret") + public void testSendTelemetryWithValidHeader(String cookieSecret) throws Exception { + final String validHmac = encodeHmac(cookieSecret, VALID_EXPIRATION_TIME); this.perimeterx.pxVerify(getMockHttpRequestWithTelemetryHeader(validHmac), this.mockHttpResponse); verify(this.pxHttpClient, times(1)).sendEnforcerTelemetry(any(EnforcerTelemetry.class), any(PXContext.class)); } - @Test - public void testWontSendTelemetryActivityWithInvalidHeader() throws Exception { + @Test(dataProvider = "cookieSecret") + public void testWontSendTelemetryActivityWithInvalidHeader(String cookieSecret) throws Exception { final String invalidExpirationTime = String.valueOf(System.currentTimeMillis() - MS_IN_DAY); - final String encodedHmac = encodeHmac(this.configuration.getCookieKey(), invalidExpirationTime); + final String encodedHmac = encodeHmac(cookieSecret, invalidExpirationTime); this.perimeterx.pxVerify(getMockHttpRequestWithTelemetryHeader(encodedHmac), this.mockHttpResponse); verify(this.pxHttpClient, never()).sendEnforcerTelemetry(any(EnforcerTelemetry.class), any(PXContext.class)); diff --git a/src/test/java/com/perimeterx/internal/cookie/CookieV3EncodedTest.java b/src/test/java/com/perimeterx/internal/cookie/CookieV3EncodedTest.java index 3b7590f0..6f5019a3 100644 --- a/src/test/java/com/perimeterx/internal/cookie/CookieV3EncodedTest.java +++ b/src/test/java/com/perimeterx/internal/cookie/CookieV3EncodedTest.java @@ -10,14 +10,19 @@ import com.perimeterx.models.risk.BlockReason; import com.perimeterx.models.risk.S2SCallReason; import org.springframework.mock.web.MockHttpServletRequest; +import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import testutils.CookieV3Generator; import javax.servlet.http.HttpServletRequest; +import java.time.LocalDateTime; import java.util.Arrays; import java.util.HashSet; import static org.junit.Assert.*; +import static testutils.TestObjectUtils.cookieSecretsDataProvider; /** * Created by johnnytordgeman on 05/07/2018. @@ -31,12 +36,14 @@ public class CookieV3EncodedTest { private HostnameProvider hostnameProvider; @BeforeMethod + @BeforeClass public void setUp() { request = new MockHttpServletRequest(); ipProvider = new RemoteAddressIPProvider(); hostnameProvider = new DefaultHostnameProvider(); this.pxConfiguration = PXConfiguration.builder() .cookieKey("COOKIE_KEY_STRING") + .cookieKey("COOKIE_KEY_STRING_2") .appId("APP_ID") .authToken("AUTH_TOKEN") .build(); @@ -50,9 +57,14 @@ public void testCookieV3EncodedFailOnNoCookie() { assertEquals(S2SCallReason.NO_COOKIE.getValue(), context.getS2sCallReason()); } - @Test - public void testCookieV3EncodedPass() { - String pxCookie = "_px3=2019a92cbfd71f4711e69c0365cc901e4344f842972582b1a52ff8548a2cf05d:m9ON6t71FV8=:1000:X413RgesdhajGQcg3sKOXmDe9OtDLcuus32yUgeXSRCC2Lwx0ecg0fEw7jlS8DzVrfZREhObOmM/l54wTak7uwD0iU9zDqmrmptG+185YKWcbZrpzQAlxoKLcXHOqGuVGjHoa5ScRBUfhpfPH7KelLY5dIvLp6pRz2JeSM/roRQMv0fRlp2xYZMj0UKUFOH1"; + @DataProvider(name ="cookieSecret") + public Object[][] cookieSecrets() { + return cookieSecretsDataProvider(this.pxConfiguration); + } + + @Test(dataProvider = "cookieSecret") + public void testCookieV3EncodedPass(String cookieSecret) { + String pxCookie = CookieV3Generator.builder(cookieSecret).build().toString(); ((MockHttpServletRequest) request).addHeader("cookie", pxCookie); ((MockHttpServletRequest) request).addHeader("user-agent", "test_user_agent"); this.context = new PXContext(request, ipProvider, hostnameProvider, pxConfiguration); @@ -63,13 +75,14 @@ public void testCookieV3EncodedPass() { @Test public void testCookieV3EncodedFailOnSensitiveRoute() { + String cookieKey = "COOKIE_KEY_STRING"; PXConfiguration configuration = PXConfiguration.builder() - .cookieKey("COOKIE_KEY_STRING") + .cookieKey(cookieKey) .appId("APP_ID") .authToken("AUTH_TOKEN") .sensitiveRoutes(new HashSet(Arrays.asList("/login"))) .build(); - String pxCookie = "_px3=74c096e83d72f304bcae6d91b8017bb1e4a7c270f876ebc08977653c1b724714%3ALE%2B3eusyK6vE1d1pvI4t8HDnGQ0NCyr6aPLOIXXwT5Kr9WW1Ficr9WohnPZLdtZn%2FdHOsEz0fbk0YRYiKP%2B81g%3D%3D%3A1000%3AGCTf15dR7qk%2Bh8B%2BG7n3iI%2B1JCxiUajyAn%2BOJ4IRnaqMFE69CJ72%2BvG2m0qqQQhSF%2BQ13r1oVb0dgFqg0smfyA%3D%3D"; + String pxCookie = CookieV3Generator.builder(cookieKey).build().toString(); ((MockHttpServletRequest) request).setServletPath("/login/user"); ((MockHttpServletRequest) request).addHeader("cookie", pxCookie); ((MockHttpServletRequest) request).addHeader("user-agent", "test_user_agent"); @@ -83,7 +96,7 @@ public void testCookieV3EncodedFailOnSensitiveRoute() { @Test public void testCookieV3EncodedFailOnDecryption() { - String pxCookie = "_px3=192aabc3a9134f771ed8e464817a419f3df6fb6c0aaa69f998cbb1a2224f4d3%3AR1dKoNUcq1e4W%2BeoC8dYg23pCtVo2wXrOYbybHmYC9FCyo7aEMt%2Btxk1QJqgltOCjcL54g8tkpa8wrlIMLt12w%3D%3D%3A1000%3Av515J1I1muBk4vN1M5IIpA0LhTTpj5ObGk6s%2FPzOIaQb03Mvq%2FLewcPsy85aZKsyDHDM%2F2BPzut7%2F9hhQCIkiQ%3D%3D"; + String pxCookie = CookieV3Generator.builder("COOKIE_KEY_STRING").build().toString(); ((MockHttpServletRequest) request).addHeader("cookie", pxCookie); this.pxConfiguration = PXConfiguration.builder() .cookieKey("INVALID COOKIE TOKEN") @@ -111,9 +124,12 @@ public void testCookieV3EncodedFailOnFakeCookie() { assertEquals(S2SCallReason.INVALID_DECRYPTION.getValue(), context.getS2sCallReason()); } - @Test - public void testCookieV3EncodedFailOnCookieExpired() { - String pxCookie = "_px3=634aa77f6c2c24f80af864b8e45f6678ae3f8b2f105b4bd426cf99f971134513%3AwcyrtwkdJ5sXYc79xt%2FDJrtYhc3PGdSMOoYHHd%2FcK9R9S3DJf8BKkL%2BU%2FgUDWpSRBY%2BMVALebg8u4sY8sgfcfQ%3D%3D%3A1000%3AXnn%2BL6scXhrw7UBBkfLEhkHJ15BspyH3HyspJnoC0Lx4eA67169cbbmzSYJQfbAor1SgS8%2BAe1KQXPdaI4%2Bxew%3D%3D"; + @Test(dataProvider = "cookieSecret") + public void testCookieV3EncodedFailOnCookieExpired(String cookieSecret) { + String pxCookie = CookieV3Generator.builder(cookieSecret) + .expiryDate(LocalDateTime.now().minusMinutes(5)) + .build() + .toString(); ((MockHttpServletRequest) request).addHeader("cookie", pxCookie); ((MockHttpServletRequest) request).addHeader("user-agent", "test_user_agent"); this.context = new PXContext(request, ipProvider, hostnameProvider, pxConfiguration); @@ -124,9 +140,9 @@ public void testCookieV3EncodedFailOnCookieExpired() { assertEquals(S2SCallReason.COOKIE_EXPIRED.getValue(), context.getS2sCallReason()); } - @Test - public void testCookieV3EncodedPassAndHighScore() { - String pxCookie = "_px3=69777b776fd822edd7857834ca03b09fa5453c260ba603d7b35e2b840480b47b:jE6jQAndx80=:1000:8Feb3FhgDelIXTRjHL2gyOAy+PCyDtKJ3bqhhAVfo8Sjdw2swLosAd6vSqXH/PCI4DAJezgZSf6AVAYbzU+JW/9v6gy9+uxjpvkYPY3oLvTeJp+f3FaXzUV9qYE4HZWTzCg1EoVK9D8TKw1g7Rk1C38kzt2X8DMyvSRLimr349Vw7xg3y6Vf2IspMVVy9c7f"; + @Test(dataProvider = "cookieSecret") + public void testCookieV3EncodedPassAndHighScore(String cookieSecret) { + String pxCookie = CookieV3Generator.builder(cookieSecret).score(100).build().toString(); ((MockHttpServletRequest) request).addHeader("cookie", pxCookie); ((MockHttpServletRequest) request).addHeader("user-agent", "test_user_agent"); this.context = new PXContext(request, ipProvider, hostnameProvider, pxConfiguration); @@ -136,9 +152,9 @@ public void testCookieV3EncodedPassAndHighScore() { assertEquals(BlockReason.COOKIE, context.getBlockReason()); } - @Test - public void testCookieV3EncodedPassLowScore() { - String pxCookie = "_px3=74c096e83d72f304bcae6d91b8017bb1e4a7c270f876ebc08977653c1b724714%3ALE%2B3eusyK6vE1d1pvI4t8HDnGQ0NCyr6aPLOIXXwT5Kr9WW1Ficr9WohnPZLdtZn%2FdHOsEz0fbk0YRYiKP%2B81g%3D%3D%3A1000%3AGCTf15dR7qk%2Bh8B%2BG7n3iI%2B1JCxiUajyAn%2BOJ4IRnaqMFE69CJ72%2BvG2m0qqQQhSF%2BQ13r1oVb0dgFqg0smfyA%3D%3D"; + @Test(dataProvider = "cookieSecret") + public void testCookieV3EncodedPassLowScore(String cookieSecret) { + String pxCookie = CookieV3Generator.builder(cookieSecret).build().toString(); ((MockHttpServletRequest) request).addHeader("cookie", pxCookie); ((MockHttpServletRequest) request).addHeader("user-agent", "test_user_agent"); this.context = new PXContext(request, ipProvider, hostnameProvider, pxConfiguration); diff --git a/src/test/java/com/perimeterx/internal/cookie/CookieV3MobileTest.java b/src/test/java/com/perimeterx/internal/cookie/CookieV3MobileTest.java index 68eea304..a5c51078 100644 --- a/src/test/java/com/perimeterx/internal/cookie/CookieV3MobileTest.java +++ b/src/test/java/com/perimeterx/internal/cookie/CookieV3MobileTest.java @@ -37,6 +37,7 @@ public void setUp() { hostnameProvider = new DefaultHostnameProvider(); this.pxConfiguration = PXConfiguration.builder() .cookieKey("COOKIE_KEY_STRING_MOBILE") + .cookieKey("COOKIE_KEY_STRING_MOBILE_2") .appId("APP_ID") .authToken("AUTH_TOKEN") .build(); diff --git a/src/test/java/com/perimeterx/internal/cookie/CookieV3Test.java b/src/test/java/com/perimeterx/internal/cookie/CookieV3Test.java index 8145717a..4d41ccc2 100644 --- a/src/test/java/com/perimeterx/internal/cookie/CookieV3Test.java +++ b/src/test/java/com/perimeterx/internal/cookie/CookieV3Test.java @@ -10,14 +10,16 @@ import com.perimeterx.models.risk.BlockReason; import com.perimeterx.models.risk.S2SCallReason; import org.springframework.mock.web.MockHttpServletRequest; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.testng.annotations.*; +import testutils.CookieV3Generator; import javax.servlet.http.HttpServletRequest; +import java.time.LocalDateTime; import java.util.Arrays; import java.util.HashSet; import static org.junit.Assert.*; +import static testutils.TestObjectUtils.cookieSecretsDataProvider; /** * Created by nitzangoldfeder on 13/04/2017. @@ -37,6 +39,7 @@ public void setUp() { hostnameProvider = new DefaultHostnameProvider(); this.pxConfiguration = PXConfiguration.builder() .cookieKey("COOKIE_KEY_STRING") + .cookieKey("COOKIE_KEY_STRING_2") .appId("APP_ID") .authToken("AUTH_TOKEN") .build(); @@ -50,9 +53,14 @@ public void testCookieV3FailOnNoCookie() { assertEquals(S2SCallReason.NO_COOKIE.getValue(), context.getS2sCallReason()); } - @Test - public void testCookieV3Pass() { - String pxCookie = "_px3=2019a92cbfd71f4711e69c0365cc901e4344f842972582b1a52ff8548a2cf05d:m9ON6t71FV8=:1000:X413RgesdhajGQcg3sKOXmDe9OtDLcuus32yUgeXSRCC2Lwx0ecg0fEw7jlS8DzVrfZREhObOmM/l54wTak7uwD0iU9zDqmrmptG+185YKWcbZrpzQAlxoKLcXHOqGuVGjHoa5ScRBUfhpfPH7KelLY5dIvLp6pRz2JeSM/roRQMv0fRlp2xYZMj0UKUFOH1"; + @DataProvider(name = "cookieSecret") + public Object[][] cookieSecrets() { + return cookieSecretsDataProvider(this.pxConfiguration); + } + + @Test(dataProvider = "cookieSecret") + public void testCookieV3Pass(String cookieSecret) { + String pxCookie = CookieV3Generator.builder(cookieSecret).build().toString(); ((MockHttpServletRequest) request).addHeader("cookie", pxCookie); ((MockHttpServletRequest) request).addHeader("user-agent", "test_user_agent"); this.context = new PXContext(request, ipProvider, hostnameProvider, pxConfiguration); @@ -69,7 +77,7 @@ public void testCookieV3FailOnSensitiveRoute() { .authToken("AUTH_TOKEN") .sensitiveRoutes(new HashSet(Arrays.asList("/login"))) .build(); - String pxCookie = "_px3=74c096e83d72f304bcae6d91b8017bb1e4a7c270f876ebc08977653c1b724714:LE+3eusyK6vE1d1pvI4t8HDnGQ0NCyr6aPLOIXXwT5Kr9WW1Ficr9WohnPZLdtZn/dHOsEz0fbk0YRYiKP+81g==:1000:GCTf15dR7qk+h8B+G7n3iI+1JCxiUajyAn+OJ4IRnaqMFE69CJ72+vG2m0qqQQhSF+Q13r1oVb0dgFqg0smfyA=="; + String pxCookie = CookieV3Generator.builder("COOKIE_KEY_STRING").build().toString(); ((MockHttpServletRequest) request).setServletPath("/login/user"); ((MockHttpServletRequest) request).addHeader("cookie", pxCookie); ((MockHttpServletRequest) request).addHeader("user-agent", "test_user_agent"); @@ -83,7 +91,7 @@ public void testCookieV3FailOnSensitiveRoute() { @Test public void testCookieV3FailOnDecryption() { - String pxCookie = "_px3=5192aabc3a9134f771ed8e464817a419f3df6fb6c0aaa69f998cbb1a2224f4d3:R1dKoNUcq1e4W+eoC8dYg23pCtVo2wXrOYbybHmYC9FCyo7aEMt+txk1QJqgltOCjcL54g8tkpa8wrlIMLt12w==:1000:v515J1I1muBk4vN1M5IIpA0LhTTpj5ObGk6s/PzOIaQb03Mvq/LewcPsy85aZKsyDHDM/2BPzut7/9hhQCIkiQ=="; + String pxCookie = CookieV3Generator.builder("COOKIE_KEY_STRING").build().toString(); ((MockHttpServletRequest) request).addHeader("cookie", pxCookie); this.pxConfiguration = PXConfiguration.builder() .cookieKey("INVALID COOKIE TOKEN") @@ -112,7 +120,10 @@ public void testCookieV3FailOnFakeCookie() { @Test public void testCookieV3FailOnCookieExpired() { - String pxCookie = "_px3=634aa77f6c2c24f80af864b8e45f6678ae3f8b2f105b4bd426cf99f971134513:wcyrtwkdJ5sXYc79xt/DJrtYhc3PGdSMOoYHHd/cK9R9S3DJf8BKkL+U/gUDWpSRBY+MVALebg8u4sY8sgfcfQ==:1000:Xnn+L6scXhrw7UBBkfLEhkHJ15BspyH3HyspJnoC0Lx4eA67169cbbmzSYJQfbAor1SgS8+Ae1KQXPdaI4+xew=="; + String pxCookie = CookieV3Generator.builder("COOKIE_KEY_STRING") + .expiryDate(LocalDateTime.now().minusMinutes(5)) + .build() + .toString(); ((MockHttpServletRequest) request).addHeader("cookie", pxCookie); ((MockHttpServletRequest) request).addHeader("user-agent", "test_user_agent"); this.context = new PXContext(request, ipProvider, hostnameProvider, pxConfiguration); @@ -122,9 +133,9 @@ public void testCookieV3FailOnCookieExpired() { assertEquals(S2SCallReason.COOKIE_EXPIRED.getValue(), context.getS2sCallReason()); } - @Test - public void testCookieV3PassAndHighScore() { - String pxCookie = "_px3=69777b776fd822edd7857834ca03b09fa5453c260ba603d7b35e2b840480b47b:jE6jQAndx80=:1000:8Feb3FhgDelIXTRjHL2gyOAy+PCyDtKJ3bqhhAVfo8Sjdw2swLosAd6vSqXH/PCI4DAJezgZSf6AVAYbzU+JW/9v6gy9+uxjpvkYPY3oLvTeJp+f3FaXzUV9qYE4HZWTzCg1EoVK9D8TKw1g7Rk1C38kzt2X8DMyvSRLimr349Vw7xg3y6Vf2IspMVVy9c7f"; + @Test(dataProvider = "cookieSecret") + public void testCookieV3PassAndHighScore(String cookieSecret) { + String pxCookie = CookieV3Generator.builder(cookieSecret).score(100).build().toString(); ((MockHttpServletRequest) request).addHeader("cookie", pxCookie); ((MockHttpServletRequest) request).addHeader("user-agent", "test_user_agent"); this.context = new PXContext(request, ipProvider, hostnameProvider, pxConfiguration); @@ -134,9 +145,11 @@ public void testCookieV3PassAndHighScore() { assertEquals(BlockReason.COOKIE, context.getBlockReason()); } - @Test - public void testCookieV3PassLowScore() { - String pxCookie = "_px3=74c096e83d72f304bcae6d91b8017bb1e4a7c270f876ebc08977653c1b724714:LE+3eusyK6vE1d1pvI4t8HDnGQ0NCyr6aPLOIXXwT5Kr9WW1Ficr9WohnPZLdtZn/dHOsEz0fbk0YRYiKP+81g==:1000:GCTf15dR7qk+h8B+G7n3iI+1JCxiUajyAn+OJ4IRnaqMFE69CJ72+vG2m0qqQQhSF+Q13r1oVb0dgFqg0smfyA=="; + @Test(dataProvider = "cookieSecret") + public void testCookieV3PassLowScore(String cookieSecret) { + String pxCookie = CookieV3Generator.builder(cookieSecret) + .build() + .toString(); ((MockHttpServletRequest) request).addHeader("cookie", pxCookie); ((MockHttpServletRequest) request).addHeader("user-agent", "test_user_agent"); this.context = new PXContext(request, ipProvider, hostnameProvider, pxConfiguration); diff --git a/src/test/java/com/perimeterx/models/PXConfigurationTest.java b/src/test/java/com/perimeterx/models/PXConfigurationTest.java index 15f4ccac..f9974a56 100644 --- a/src/test/java/com/perimeterx/models/PXConfigurationTest.java +++ b/src/test/java/com/perimeterx/models/PXConfigurationTest.java @@ -1,5 +1,6 @@ package com.perimeterx.models; +import com.fasterxml.jackson.databind.ObjectMapper; import com.perimeterx.models.configuration.ModuleMode; import com.perimeterx.models.configuration.PXConfiguration; import com.perimeterx.utils.FilesUtils; @@ -11,14 +12,26 @@ import org.testng.annotations.Test; import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; import java.util.Arrays; import java.util.HashSet; @PrepareForTest(FilesUtils.class) public class PXConfigurationTest extends PowerMockTestCase { + + @Test + public void readingConfigurationWithSingleCookieKeyFromJson() throws IOException { + try (InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream("basic_configuration.json")) { + ObjectMapper mapper = new ObjectMapper(); + PXConfiguration pxConfiguration = mapper.readValue(in, PXConfiguration.class); + Assert.assertEquals(pxConfiguration.getCookieKeys().get(0), "COOKIE_SECRET"); + } + } + @Test public void testMergeConfigurations() throws FileNotFoundException { - PXConfiguration pxConfiguration = PXConfiguration.builder() + PXConfiguration pxConfiguration = PXConfiguration.builder() .appId("appId") .cookieKey("cookieKey") .authToken("authToken") diff --git a/src/test/java/com/perimeterx/utils/CookieNamesExtractorTest.java b/src/test/java/com/perimeterx/utils/CookieNamesExtractorTest.java index fa1f8886..1f6e32bb 100644 --- a/src/test/java/com/perimeterx/utils/CookieNamesExtractorTest.java +++ b/src/test/java/com/perimeterx/utils/CookieNamesExtractorTest.java @@ -8,8 +8,6 @@ public class CookieNamesExtractorTest { - String cookieHeader = "_px3=px3Cookie;tempCookie=CookieTemp; _px7=NotARealCookie"; - @Test public void testExtractCookieNames(){ Cookie px3 = new Cookie("_px3","px3Cookie"); diff --git a/src/test/java/testutils/CookieV3Generator.java b/src/test/java/testutils/CookieV3Generator.java new file mode 100644 index 00000000..30352786 --- /dev/null +++ b/src/test/java/testutils/CookieV3Generator.java @@ -0,0 +1,113 @@ +package testutils; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.perimeterx.utils.HMACUtils; +import com.perimeterx.utils.PBKDF2Engine; +import com.perimeterx.utils.PBKDF2Parameters; +import com.perimeterx.utils.StringUtils; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.SneakyThrows; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.security.SecureRandom; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Arrays; +import java.util.Base64; +import java.util.UUID; + + +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Builder(toBuilder = true) +public final class CookieV3Generator { + + private String cookieSecret; + @Builder.Default + private int iterations = 1000; + @Builder.Default + private LocalDateTime expiryDate = LocalDateTime.now().plusMinutes(5); + @Builder.Default + private byte[] salt = randomBytes(16); + @Builder.Default + private String userAgent = "test_user_agent"; + @Builder.Default + private int keyLen = 32; + @Builder.Default + private String uuid = UUID.randomUUID().toString(); + @Builder.Default + private String vid = UUID.randomUUID().toString(); + @Builder.Default + private String action = "c"; + @Builder.Default + private int score = 0; + @Builder.Default + private String cookieSigningFields = "u"; + @Builder.Default + private String ip = ""; + + public static CookieV3GeneratorBuilder builder(String cookieSecret) { + return new CookieV3GeneratorBuilder().cookieSecret(cookieSecret); + } + + @Override + @SneakyThrows + public String toString() { + final Cipher cipher; // aes-256-cbc decryptData no salt + cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + final int dkLen = this.keyLen + cipher.getBlockSize(); + PBKDF2Parameters p = new PBKDF2Parameters("HmacSHA256", "UTF-8", this.salt, this.iterations); + byte[] plain = plain().toString().getBytes(); + byte[] dk = new PBKDF2Engine(p).deriveKey(this.cookieSecret, dkLen); + byte[] key = Arrays.copyOf(dk, this.keyLen); + byte[] iv = Arrays.copyOfRange(dk, this.keyLen, dk.length); + SecretKey secretKey = new SecretKeySpec(key, "AES"); + IvParameterSpec parameterSpec = new IvParameterSpec(iv); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec); + final byte[] cipherData = cipher.doFinal(plain, 0, plain.length); + return buildCookie(cipherData); + } + + public JsonNode plain() { + ObjectNode json = new ObjectMapper().createObjectNode(); + json.put("t", this.expiryDate.atZone(ZoneId.systemDefault()).toEpochSecond() * 1000); + json.put("u", this.uuid); + json.put("v", this.vid); + json.put("a", this.action); + json.put("s", this.score); + json.put("x", this.cookieSigningFields); + return json; + } + + @SneakyThrows + private String buildCookie(byte[] cipherData) { + Base64.Encoder b64Encoder = Base64.getEncoder(); + String content = b64Encoder.encodeToString(this.salt) + + ":" + + this.iterations + + ":" + + b64Encoder.encodeToString(cipherData); + + String hmac = StringUtils.byteArrayToHexString( + HMACUtils.HMACString(content + hmacAdditionalData(), this.cookieSecret)); + + return "_px3=" + hmac + ":" + content; + } + + private String hmacAdditionalData() { + return this.cookieSigningFields.replaceAll("s", this.ip).replace("u", this.userAgent); + } + + @SneakyThrows + private static byte[] randomBytes(int length) { + byte[] bytes = new byte[length]; + SecureRandom.getInstanceStrong().nextBytes(bytes); + return bytes; + } +} diff --git a/src/test/java/testutils/PXClientMock.java b/src/test/java/testutils/PXClientMock.java index 68e2cdee..6b76801d 100644 --- a/src/test/java/testutils/PXClientMock.java +++ b/src/test/java/testutils/PXClientMock.java @@ -11,6 +11,7 @@ import com.perimeterx.models.exceptions.PXException; import com.perimeterx.models.httpmodels.RiskResponse; import com.perimeterx.models.httpmodels.RiskResponseBody; +import edu.emory.mathcs.backport.java.util.Collections; import java.io.IOException; import java.util.HashSet; @@ -80,7 +81,7 @@ public PXDynamicConfiguration getConfigurationFromServer() { stub.setAppId("stub_app_id"); stub.setChecksum("stub_checksum"); stub.setBlockingScore(1000); - stub.setCookieSecret("stub_cookie_key"); + stub.setCookieSecrets(Collections.singletonList("stub_cookie_key")); stub.setS2sTimeout(1500); stub.setApiConnectTimeout(1500); stub.setSensitiveHeaders(new HashSet()); diff --git a/src/test/java/testutils/TestObjectUtils.java b/src/test/java/testutils/TestObjectUtils.java index 4cb21194..69de2d67 100644 --- a/src/test/java/testutils/TestObjectUtils.java +++ b/src/test/java/testutils/TestObjectUtils.java @@ -18,6 +18,14 @@ */ public class TestObjectUtils { + public static Object[][] cookieSecretsDataProvider(PXConfiguration configuration) { + return configuration.getCookieKeys() + .stream() + .map(key -> new Object[]{key}) + .toArray(Object[][]::new); + + } + public static PXClient blockingPXClient(int minScoreToBlock) { int scoreToReturn = minScoreToBlock; return new PXClientMock(scoreToReturn, Constants.CAPTCHA_SUCCESS_CODE); @@ -33,6 +41,8 @@ public static PXConfiguration generateConfiguration() { .appId("appId") .authToken("token") .cookieKey("cookieKey") + .cookieKey("cookieKey2") + .cookieKey("cookieKey3") .loggerAuthToken("logger_token_123") .moduleMode(ModuleMode.BLOCKING) .remoteConfigurationEnabled(false) diff --git a/src/test/resources/basic_configuration.json b/src/test/resources/basic_configuration.json new file mode 100644 index 00000000..7b47f069 --- /dev/null +++ b/src/test/resources/basic_configuration.json @@ -0,0 +1,5 @@ +{ + "px_app_id": "PX1234", + "px_cookie_secret": "COOKIE_SECRET", + "px_auth_token":"AUTH_TOKEN" +} \ No newline at end of file