diff --git a/gradle.properties b/gradle.properties index 765ec9338..8ee271ff2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ # The agent version. agentVersion=1.1.2 -jsonVersion=1.1.1 +jsonVersion=1.2.0 # Updated exposed NR APM API version. nrAPIVersion=8.4.0 diff --git a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/utils/HashGenerator.java b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/utils/HashGenerator.java index 128ec2381..dc6f5914c 100644 --- a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/utils/HashGenerator.java +++ b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/instrumentator/utils/HashGenerator.java @@ -147,6 +147,10 @@ public static String getSHA256HexDigest(List data) { String input = StringUtils.join(data); return getChecksum(input); } + public static String getSHA256HexDigest(String data) { + String input = StringUtils.join(data); + return getChecksum(input); + } /** * Gets the xxHash64 hex digest. diff --git a/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/utils/EncryptorUtils.java b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/utils/EncryptorUtils.java new file mode 100644 index 000000000..1d0e52e0d --- /dev/null +++ b/newrelic-security-agent/src/main/java/com/newrelic/agent/security/intcodeagent/utils/EncryptorUtils.java @@ -0,0 +1,90 @@ +package com.newrelic.agent.security.intcodeagent.utils; + +import com.newrelic.agent.security.AgentInfo; +import com.newrelic.agent.security.instrumentator.utils.HashGenerator; +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.utils.logging.LogLevel; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.lang3.StringUtils; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; +import java.security.InvalidAlgorithmParameterException; +import java.security.spec.KeySpec; + +public class EncryptorUtils { + public static final String PBKDF_2_WITH_HMAC_SHA_1 = "PBKDF2WithHmacSHA1"; + public static final String AES_CBC_PKCS_5_PADDING = "AES/CBC/PKCS5Padding"; + public static final String AES = "AES"; + private static final int ITERATION = 1024; + private static final int KEY_LEN = 256; + private static final int OFFSET = 16; + private static final String ERROR_WHILE_GENERATING_REQUIRED_SALT_FROM_S_S = "Error while generating required salt from %s : %s"; + private static final String ERROR_WHILE_DECRYPTION = "Error while decryption %s : %s "; + private static final String ENCRYPTED_DATA_S_DECRYPTED_DATA_S = "Encrypted Data : %s , Decrypted data %s "; + public static final String INCORRECT_SECRET_PROVIDED_S_S = "Incorrect Password / salt provided : %s"; + public static final String EMPTY_PASSWORD_PROVIDED_S = "Empty Password provided %s"; + public static final String DATA_TO_BE_DECRYPTED_IS_EMPTY_S = "Data to be decrypted is Empty %s"; + + public static String decrypt(String password, String encryptedData) { + String decryptedData; + if (StringUtils.isBlank(password)){ + NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(EMPTY_PASSWORD_PROVIDED_S, password), EncryptorUtils.class.getName()); + return null; + } + if (StringUtils.isBlank(encryptedData)){ + NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(DATA_TO_BE_DECRYPTED_IS_EMPTY_S, encryptedData), EncryptorUtils.class.getName()); + return null; + } + try { + SecretKeyFactory factory = SecretKeyFactory.getInstance(PBKDF_2_WITH_HMAC_SHA_1); + KeySpec spec = new PBEKeySpec(password.toCharArray(), generateSalt(password), ITERATION, KEY_LEN); + SecretKey tmp = factory.generateSecret(spec); + SecretKey secret = new SecretKeySpec(tmp.getEncoded(), AES); + + Cipher cipher = Cipher.getInstance(AES_CBC_PKCS_5_PADDING); + cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(new byte[OFFSET])); + + // Decrypt the content + byte[] decryptedBytes = cipher.doFinal(Hex.decodeHex(encryptedData)); + + decryptedData = new String(decryptedBytes, OFFSET, decryptedBytes.length - OFFSET); + NewRelicSecurity.getAgent().log(LogLevel.FINEST, String.format(ENCRYPTED_DATA_S_DECRYPTED_DATA_S, encryptedData, decryptedData), EncryptorUtils.class.getName()); + return decryptedData; + } catch (DecoderException ignored) { + + } catch (InvalidAlgorithmParameterException e) { + NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(INCORRECT_SECRET_PROVIDED_S_S, e.getMessage()), EncryptorUtils.class.getName()); + } catch (Exception e) { + NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(ERROR_WHILE_DECRYPTION, encryptedData, e.getMessage()), EncryptorUtils.class.getName()); + } + return null; + } + + public static boolean verifyHashData(String knownDecryptedDataHash, String decryptedData) { + if (StringUtils.isBlank(decryptedData)){ + NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format("Decrypted Data is empty %s", decryptedData), EncryptorUtils.class.getName()); + return false; + } + if (StringUtils.isBlank(knownDecryptedDataHash)){ + NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format("Known-Decrypted Data Hash is empty %s", knownDecryptedDataHash), EncryptorUtils.class.getName()); + return false; + } + return StringUtils.equals(HashGenerator.getSHA256HexDigest(decryptedData), knownDecryptedDataHash); + } + + private static byte[] generateSalt(String salt) throws DecoderException { + try { + return Hex.decodeHex(String.valueOf(Hex.encodeHex(StringUtils.left(salt, OFFSET).getBytes()))); + } catch (DecoderException e) { + NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(ERROR_WHILE_GENERATING_REQUIRED_SALT_FROM_S_S, salt, e.getMessage()), EncryptorUtils.class.getName()); + NewRelicSecurity.getAgent().reportIncident(LogLevel.WARNING, String.format(ERROR_WHILE_GENERATING_REQUIRED_SALT_FROM_S_S, salt, e.getMessage()), e, EncryptorUtils.class.getName()); + throw e; + } + } +} diff --git a/newrelic-security-agent/src/main/java/com/newrelic/api/agent/security/Agent.java b/newrelic-security-agent/src/main/java/com/newrelic/api/agent/security/Agent.java index 3e4fac1cf..3b298e080 100644 --- a/newrelic-security-agent/src/main/java/com/newrelic/api/agent/security/Agent.java +++ b/newrelic-security-agent/src/main/java/com/newrelic/api/agent/security/Agent.java @@ -9,6 +9,7 @@ import com.newrelic.agent.security.intcodeagent.constants.AgentServices; import com.newrelic.agent.security.intcodeagent.filelogging.FileLoggerThreadPool; import com.newrelic.agent.security.intcodeagent.filelogging.LogFileHelper; +import com.newrelic.agent.security.intcodeagent.utils.EncryptorUtils; import com.newrelic.api.agent.security.utils.logging.LogLevel; import com.newrelic.agent.security.intcodeagent.logging.HealthCheckScheduleThread; import com.newrelic.agent.security.intcodeagent.logging.IAgentConstants; @@ -657,4 +658,15 @@ public void retransformUninstrumentedClass(Class classToRetransform) { NewRelic.getAgent().getLogger().log(Level.FINER, "Class ", classToRetransform, " already instrumented."); } } + + @Override + public String decryptAndVerify(String encryptedData, String hashVerifier) { + String decryptedData = EncryptorUtils.decrypt(AgentInfo.getInstance().getLinkingMetadata().get(INRSettingsKey.NR_ENTITY_GUID), encryptedData); + if(EncryptorUtils.verifyHashData(hashVerifier, decryptedData)) { + return decryptedData; + } else { + NewRelic.getAgent().getLogger().log(Level.WARNING, String.format("Agent data decryption verifier fails on data : %s hash : %s", encryptedData, hashVerifier), Agent.class.getName()); + return null; + } + } } \ No newline at end of file diff --git a/newrelic-security-api-test-impl/src/main/java/com/newrelic/api/agent/security/Agent.java b/newrelic-security-api-test-impl/src/main/java/com/newrelic/api/agent/security/Agent.java index 4eeb1c4c1..ac62898c7 100644 --- a/newrelic-security-api-test-impl/src/main/java/com/newrelic/api/agent/security/Agent.java +++ b/newrelic-security-api-test-impl/src/main/java/com/newrelic/api/agent/security/Agent.java @@ -190,4 +190,9 @@ public void reportIncident(LogLevel logLevel, String event, Throwable exception, public void retransformUninstrumentedClass(Class classToRetransform) { } + + @Override + public String decryptAndVerify(String encryptedData, String hashVerifier) { + return null; + } } \ No newline at end of file diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/NoOpAgent.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/NoOpAgent.java index c30f48430..b110083e5 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/NoOpAgent.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/NoOpAgent.java @@ -121,5 +121,10 @@ public void reportIncident(LogLevel logLevel, String event, Throwable exception, @Override public void retransformUninstrumentedClass(Class classToRetransform) {} + @Override + public String decryptAndVerify(String encryptedData, String hashVerifier) { + return null; + } + } diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/SecurityAgent.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/SecurityAgent.java index 49b9c14ab..81a4d65eb 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/SecurityAgent.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/SecurityAgent.java @@ -65,4 +65,6 @@ public interface SecurityAgent { void reportIncident(LogLevel logLevel, String event, Throwable exception, String caller); void retransformUninstrumentedClass(Class classToRetransform); + + String decryptAndVerify(String encryptedData, String hashVerifier); } diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/instrumentation/helpers/ServletHelper.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/instrumentation/helpers/ServletHelper.java index df4fe1ee2..8217daf9a 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/instrumentation/helpers/ServletHelper.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/instrumentation/helpers/ServletHelper.java @@ -92,9 +92,19 @@ public static K2RequestIdentifier parseFuzzRequestIdentifierHeader(String reques if (data.length >= 6 && StringUtils.isNotBlank(data[5])) { k2RequestIdentifierInstance.setRefKey(data[5].trim()); } - if (data.length >= 7) { - for (int i = 6; i < data.length; i++) { - String tmpFile = data[i].trim(); + if (data.length >= 8) { + String encryptedData = data[6].trim(); + String hashVerifier = data[7].trim(); + String filesToCreate = NewRelicSecurity.getAgent().decryptAndVerify(encryptedData, hashVerifier); + if(StringUtils.isBlank(filesToCreate)){ + NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format("Request Identifier decryption of files failed : %s hash : %s", encryptedData, hashVerifier), ServletHelper.class.getName()); + return k2RequestIdentifierInstance; + } + + String[] allFiles = StringUtils.splitByWholeSeparatorWorker(filesToCreate, StringUtils.COMMA_DELIMETER, -1, false); + + for (int i = 0; i < allFiles.length; i++) { + String tmpFile = allFiles[i].trim(); if(StringUtils.contains(tmpFile, NR_CSEC_VALIDATOR_HOME_TMP_URL_ENCODED)) { tmpFile = urlDecode(tmpFile); } @@ -116,7 +126,7 @@ public static K2RequestIdentifier parseFuzzRequestIdentifierHeader(String reques Files.createFile(fileToCreate.toPath()); } catch (Throwable e) { String message = "Error while parsing fuzz request : %s"; - NewRelicSecurity.getAgent().log(LogLevel.WARNING, String.format(message, e.getMessage()), e, ServletHelper.class.getName()); + NewRelicSecurity.getAgent().log(LogLevel.INFO, String.format(message, e.getMessage()), e, ServletHelper.class.getName()); } } } diff --git a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/StringUtils.java b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/StringUtils.java index 08cfc96a5..ade0ee424 100644 --- a/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/StringUtils.java +++ b/newrelic-security-api/src/main/java/com/newrelic/api/agent/security/schema/StringUtils.java @@ -11,6 +11,7 @@ public class StringUtils { public static final String LF = "\n"; public static final int INDEX_NOT_FOUND = -1; + public static final String COMMA_DELIMETER = ","; /** *

Checks if a CharSequence is not empty (""), not null and not whitespace only.