Skip to content

Commit

Permalink
Merge pull request #393 from PerimeterX/release/v6.14.0
Browse files Browse the repository at this point in the history
release 6.14.0 -> master
  • Loading branch information
guyeisenbach authored Sep 15, 2024
2 parents c48c451 + 353b52a commit 904ef89
Show file tree
Hide file tree
Showing 26 changed files with 329 additions and 109 deletions.
7 changes: 6 additions & 1 deletion .github/workflows/fuzzer.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
name: "Fuzzing Test"
env:
MOCK_COLLECTOR_IMAGE_TAG: 1.3.6
FUZZER_TAG: 1.0.3
FUZZER_TAG: 1.0.4
SAMPLE_SITE_IMAGE_TAG: 1.0.0
ENFORCER_TAG: ${{ needs.extract_version.outputs.version }}

Expand Down Expand Up @@ -147,6 +147,11 @@ jobs:
PX_APP_ID: ${{ secrets.PX_APP_ID }}
SITE_URL: "http://java-enforcer-sample-site:3000"

- name: Deployment logs - mock collector
run: kubectl logs deployment/mock-collector-mock-collector
- name: Deployment logs - enforcer
run: kubectl logs deployment/java-enforcer-sample-site

- name: get tests results
if: ${{ always() }}
run: kubectl logs job/fuzzer-enforcer-fuzzer
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Change Log

## [v6.14.0](https://github.com/PerimeterX/perimeterx-java-sdk/compare/6.14.0...HEAD) (2024-09-15)
- Bump Fuzzer version
- Support 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
- Added Enforcer Fuzzer as part of the CI process
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

# [PerimeterX](http://www.perimeterx.com) Java SDK

> Latest stable version: [v6.13.0](https://search.maven.org/#artifactdetails%7Ccom.perimeterx%7Cperimeterx-sdk%7C6.13.0%7Cjar)
> Latest stable version: [v6.14.0](https://search.maven.org/#artifactdetails%7Ccom.perimeterx%7Cperimeterx-sdk%7C6.15.0%7Cjar)
## Table of Contents

Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<name>PerimeterX JAVA SDK</name>
<groupId>com.perimeterx</groupId>
<artifactId>perimeterx-sdk</artifactId>
<version>6.13.0</version>
<version>6.14.0</version>

<packaging>jar</packaging>
<description>PerimeterX Java SDK</description>
Expand Down
2 changes: 1 addition & 1 deletion px_metadata.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "6.13.0",
"version": "6.14.0",
"supported_features": [
"advanced_blocking_response",
"bypass_monitor_header",
Expand Down
22 changes: 14 additions & 8 deletions src/main/java/com/perimeterx/api/PerimeterX.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

/**
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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
*
Expand Down
50 changes: 31 additions & 19 deletions src/main/java/com/perimeterx/internals/cookie/AbstractPXCookie.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -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) {
Expand All @@ -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();
}

Expand Down Expand Up @@ -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();
}
Expand All @@ -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");
Expand All @@ -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 {
Expand All @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -48,6 +49,9 @@ public List<RawCookieData> createRawCookieDataList(String... cookieHeaders) {
}

public DataEnrichmentCookie getRawDataEnrichmentCookie(List<RawCookieData> rawCookies, String cookieKey) {
return getRawDataEnrichmentCookie(rawCookies, Collections.singletonList(cookieKey));
}
public DataEnrichmentCookie getRawDataEnrichmentCookie(List<RawCookieData> rawCookies, List<String> cookieKeys) {
ObjectMapper mapper = new ObjectMapper();
DataEnrichmentCookie dataEnrichmentCookie = new DataEnrichmentCookie(mapper.createObjectNode(), false);
RawCookieData rawDataEnrichmentCookie = null;
Expand All @@ -67,7 +71,8 @@ public DataEnrichmentCookie getRawDataEnrichmentCookie(List<RawCookieData> 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);
Expand Down
8 changes: 7 additions & 1 deletion src/main/java/com/perimeterx/models/PXContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -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();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<String> cookieKeys;

@JsonProperty("px_auth_token")
private String authToken;
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -16,7 +19,9 @@ public class PXDynamicConfiguration {
@JsonProperty("checksum")
private String checksum;
@JsonProperty("cookieKey")
private String cookieSecret;
@Getter
@Setter
private List<String> cookieSecrets;
@JsonProperty("appId")
private String appId;
@JsonProperty("blockingScore")
Expand Down Expand Up @@ -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;
}
Expand Down
Loading

0 comments on commit 904ef89

Please sign in to comment.