Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@
package com.newrelic.agent.util;

import com.newrelic.agent.Agent;
import com.newrelic.agent.service.ServiceFactory;

import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class LicenseKeyUtil {
private static final String OBFUSCATED_LICENSE_KEY = "obfuscated";
// Matches license_key= (URL) or "license_key":" (JSON) and captures the prefix (group 1) and key value (group 2) separately.
private static final Pattern LICENSE_KEY_PATTERN = Pattern.compile("(.+?license_key(?:=|\":\"))([^&\"]+)");
private static final int KEY_LENGTH_CUTOFF = 10;

/**
* Removes the license_key value from a given string.
Expand All @@ -27,12 +32,48 @@ public static String obfuscateLicenseKey(String originalString) {
Agent.LOG.finest("Unable to obfuscate the license_key in a null or empty string.");
return originalString;
}
String licenseKey = ServiceFactory.getConfigService().getDefaultAgentConfig().getLicenseKey();
if (licenseKey == null) {
Agent.LOG.finest("Unable to obfuscate a null license_key.");

// Avoid regex overhead if string doesn't contain "license_key"
if (!originalString.contains("license_key")) {
return originalString;
}

Matcher matcher = LICENSE_KEY_PATTERN.matcher(originalString);
if (!matcher.find()) {
return originalString;
}

// appendReplacement: copies text between matches into sb, then appends the replacement string.
// quoteReplacement: escapes $ and \ in the replacement so they're treated as literals, not regex tokens.
// group(1): returns the prefix captured by group 1 (everything up to and including the delimiter).
// group(2): returns the license key value found in the string, used to drive the obfuscation.
// find(): advances the matcher to the next occurrence of the pattern.
// appendTail: flushes any remaining text after the last match into sb.
StringBuffer sb = new StringBuffer();
do {
matcher.appendReplacement(sb, Matcher.quoteReplacement(matcher.group(1) + partialObfuscation(matcher.group(2))));
} while (matcher.find());
matcher.appendTail(sb);

return sb.toString();
}

private static String partialObfuscation(String licenseKey) {
int keyLength = licenseKey.length();

// Per the spec: If length is <= 10, replace the full key with "*"
// Otherwise, keep the first 10 characters of the key and replace the
// remaining key characters with "*"
if (keyLength > KEY_LENGTH_CUTOFF) {
return licenseKey.substring(0, KEY_LENGTH_CUTOFF) + createAsterisks(keyLength - KEY_LENGTH_CUTOFF);
} else {
return originalString.replace(licenseKey, OBFUSCATED_LICENSE_KEY);
return createAsterisks(keyLength);
}
}
}

private static String createAsterisks(int count) {
char[] asterisks = new char[count];
Arrays.fill(asterisks, '*');
return new String(asterisks);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,15 @@ public void testObfuscateLicenseKey() {
serviceManager.setConfigService(configService);

// When
long startTime = System.nanoTime();
String actualRequestUrl = LicenseKeyUtil.obfuscateLicenseKey(originalRequestUrl);
System.out.println("Time (ns): " + (System.nanoTime() - startTime));
String actualJsonPayload = LicenseKeyUtil.obfuscateLicenseKey(originalJsonPayload);

// Then
String expectedRequestUrl = "https://staging-collector.newrelic.com:443/agent_listener/invoke_raw_method?method=connect&license_key=obfuscated&marshal_format=json&protocol_version=17";
String expectedRequestUrl = "https://staging-collector.newrelic.com:443/agent_listener/invoke_raw_method?method=connect&license_key=abcdefghij**************************&marshal_format=json&protocol_version=17";

String expectedJsonPayload = "[{\"license_key\":\"obfuscated\"}]";
String expectedJsonPayload = "[{\"license_key\":\"abcdefghij**************************\"}]";

Assert.assertEquals(expectedRequestUrl, actualRequestUrl);
Assert.assertEquals(expectedJsonPayload, actualJsonPayload);
Expand All @@ -68,33 +70,29 @@ public void testObfuscateLicenseKeyWithMultipleLicenseKeyEntries() {

// Then
String expectedJsonPayload = "[" +
"{\"license_key\":\"obfuscated\"}, {\"license_key\":\"obfuscated\"}, {\"license_key\":\"obfuscated\"}, {\"license_key\":\"obfuscated\"}]";
"{\"license_key\":\"abcdefghij**************************\"}, {\"license_key\":\"abcdefghij**************************\"}, {\"license_key\":\"abcdefghij**************************\"}, {\"license_key\":\"abcdefghij**************************\"}]";

Assert.assertEquals(expectedJsonPayload, actualJsonPayload);
}

public void testObfuscateLicenseKeyWithNullLicenseKey() {
// The configured license key is not used for obfuscation; the value found in the string drives it.
// Even with a null configured key, the pattern-based obfuscation still applies.
// Given
String originalRequestUrl = "https://staging-collector.newrelic.com:443/agent_listener/invoke_raw_method?method=connect&license_key=abcdefghijklmonpqrstuvwxyz1234567890&marshal_format=json&protocol_version=17";

String originalJsonPayload = "[{\"license_key\":\"abcdefghijklmonpqrstuvwxyz1234567890\"}]";

MockServiceManager serviceManager = new MockServiceManager();
ServiceFactory.setServiceManager(serviceManager);
Map<String, Object> configMap = new HashMap<>();
configMap.put("license_key", null);

AgentConfig config = AgentConfigImpl.createAgentConfig(configMap);
ConfigService configService = ConfigServiceFactory.createConfigService(config, configMap);
serviceManager.setConfigService(configService);

// When
String actualRequestUrl = LicenseKeyUtil.obfuscateLicenseKey(originalRequestUrl);
String actualJsonPayload = LicenseKeyUtil.obfuscateLicenseKey(originalJsonPayload);

// Then
Assert.assertEquals(originalRequestUrl, actualRequestUrl);
Assert.assertEquals(originalJsonPayload, actualJsonPayload);
String expectedRequestUrl = "https://staging-collector.newrelic.com:443/agent_listener/invoke_raw_method?method=connect&license_key=abcdefghij**************************&marshal_format=json&protocol_version=17";
String expectedJsonPayload = "[{\"license_key\":\"abcdefghij**************************\"}]";

Assert.assertEquals(expectedRequestUrl, actualRequestUrl);
Assert.assertEquals(expectedJsonPayload, actualJsonPayload);
}

public void testObfuscateLicenseKeyWithNullOrEmptyString() {
Expand All @@ -116,4 +114,53 @@ public void testObfuscateLicenseKeyWithNullOrEmptyString() {
Assert.assertEquals("", actualEmptyString);
Assert.assertNull(actualNullString);
}

public void testObfuscatedLicenseKeyWithSmallLicenseKey() {
// License keys <= 10 chars are fully obfuscated with "*"
// Given
String originalRequestUrl = "https://staging-collector.newrelic.com:443/agent_listener/invoke_raw_method?method=connect&license_key=abcde&marshal_format=json&protocol_version=17";

String originalJsonPayload = "[{\"license_key\":\"abcde\"}]";

MockServiceManager serviceManager = new MockServiceManager();
ServiceFactory.setServiceManager(serviceManager);
Map<String, Object> configMap = new HashMap<>();
configMap.put("license_key", "abcde");

AgentConfig config = AgentConfigImpl.createAgentConfig(configMap);
ConfigService configService = ConfigServiceFactory.createConfigService(config, configMap);
serviceManager.setConfigService(configService);

// When
String actualRequestUrl = LicenseKeyUtil.obfuscateLicenseKey(originalRequestUrl);
String actualJsonPayload = LicenseKeyUtil.obfuscateLicenseKey(originalJsonPayload);

// Then
String expectedRequestUrl = "https://staging-collector.newrelic.com:443/agent_listener/invoke_raw_method?method=connect&license_key=*****&marshal_format=json&protocol_version=17";
String expectedJsonPayload = "[{\"license_key\":\"*****\"}]";

Assert.assertEquals(expectedRequestUrl, actualRequestUrl);
Assert.assertEquals(expectedJsonPayload, actualJsonPayload);
}

public void testObfuscatedLicenseKeyIfOriginalLicenseKeyIsChanged() {
// If the key is changed in config mid-run, the agent still uses the original key in its requests.
// Obfuscation is driven by the value found in the string, not the configured key,
// so the original key is always obfuscated regardless of what the config currently holds.
String originalRequestUrl = "https://staging-collector.newrelic.com:443/agent_listener/invoke_raw_method?method=connect&license_key=abcdefghijklmonpqrstuvwxyz1234567890&marshal_format=json&protocol_version=17";

String originalJsonPayload = "[{\"license_key\":\"abcdefghijklmonpqrstuvwxyz1234567890\"}]";

// When
String actualRequestUrl = LicenseKeyUtil.obfuscateLicenseKey(originalRequestUrl);
String actualJsonPayload = LicenseKeyUtil.obfuscateLicenseKey(originalJsonPayload);

// Then
String expectedRequestUrl = "https://staging-collector.newrelic.com:443/agent_listener/invoke_raw_method?method=connect&license_key=abcdefghij**************************&marshal_format=json&protocol_version=17";

String expectedJsonPayload = "[{\"license_key\":\"abcdefghij**************************\"}]";

Assert.assertEquals(expectedRequestUrl, actualRequestUrl);
Assert.assertEquals(expectedJsonPayload, actualJsonPayload);
}
}
Loading