Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

release 6.14.0 -> master #393

Merged
merged 16 commits into from
Sep 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading