Skip to content

Commit 874703a

Browse files
authored
Merge pull request #2864 from newrelic/lic-key-obfuscation
New license key obfuscation algorithm
2 parents a42637c + 49489b3 commit 874703a

2 files changed

Lines changed: 109 additions & 21 deletions

File tree

newrelic-agent/src/main/java/com/newrelic/agent/util/LicenseKeyUtil.java

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,15 @@
88
package com.newrelic.agent.util;
99

1010
import com.newrelic.agent.Agent;
11-
import com.newrelic.agent.service.ServiceFactory;
11+
12+
import java.util.Arrays;
13+
import java.util.regex.Matcher;
14+
import java.util.regex.Pattern;
1215

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

1621
/**
1722
* Removes the license_key value from a given string.
@@ -27,12 +32,48 @@ public static String obfuscateLicenseKey(String originalString) {
2732
Agent.LOG.finest("Unable to obfuscate the license_key in a null or empty string.");
2833
return originalString;
2934
}
30-
String licenseKey = ServiceFactory.getConfigService().getDefaultAgentConfig().getLicenseKey();
31-
if (licenseKey == null) {
32-
Agent.LOG.finest("Unable to obfuscate a null license_key.");
35+
36+
// Avoid regex overhead if string doesn't contain "license_key"
37+
if (!originalString.contains("license_key")) {
3338
return originalString;
39+
}
40+
41+
Matcher matcher = LICENSE_KEY_PATTERN.matcher(originalString);
42+
if (!matcher.find()) {
43+
return originalString;
44+
}
45+
46+
// appendReplacement: copies text between matches into sb, then appends the replacement string.
47+
// quoteReplacement: escapes $ and \ in the replacement so they're treated as literals, not regex tokens.
48+
// group(1): returns the prefix captured by group 1 (everything up to and including the delimiter).
49+
// group(2): returns the license key value found in the string, used to drive the obfuscation.
50+
// find(): advances the matcher to the next occurrence of the pattern.
51+
// appendTail: flushes any remaining text after the last match into sb.
52+
StringBuffer sb = new StringBuffer();
53+
do {
54+
matcher.appendReplacement(sb, Matcher.quoteReplacement(matcher.group(1) + partialObfuscation(matcher.group(2))));
55+
} while (matcher.find());
56+
matcher.appendTail(sb);
57+
58+
return sb.toString();
59+
}
60+
61+
private static String partialObfuscation(String licenseKey) {
62+
int keyLength = licenseKey.length();
63+
64+
// Per the spec: If length is <= 10, replace the full key with "*"
65+
// Otherwise, keep the first 10 characters of the key and replace the
66+
// remaining key characters with "*"
67+
if (keyLength > KEY_LENGTH_CUTOFF) {
68+
return licenseKey.substring(0, KEY_LENGTH_CUTOFF) + createAsterisks(keyLength - KEY_LENGTH_CUTOFF);
3469
} else {
35-
return originalString.replace(licenseKey, OBFUSCATED_LICENSE_KEY);
70+
return createAsterisks(keyLength);
3671
}
3772
}
38-
}
73+
74+
private static String createAsterisks(int count) {
75+
char[] asterisks = new char[count];
76+
Arrays.fill(asterisks, '*');
77+
return new String(asterisks);
78+
}
79+
}

newrelic-agent/src/test/java/com/newrelic/agent/util/LicenseKeyUtilTest.java

Lines changed: 61 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,15 @@ public void testObfuscateLicenseKey() {
3737
serviceManager.setConfigService(configService);
3838

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

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

46-
String expectedJsonPayload = "[{\"license_key\":\"obfuscated\"}]";
48+
String expectedJsonPayload = "[{\"license_key\":\"abcdefghij**************************\"}]";
4749

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

6971
// Then
7072
String expectedJsonPayload = "[" +
71-
"{\"license_key\":\"obfuscated\"}, {\"license_key\":\"obfuscated\"}, {\"license_key\":\"obfuscated\"}, {\"license_key\":\"obfuscated\"}]";
73+
"{\"license_key\":\"abcdefghij**************************\"}, {\"license_key\":\"abcdefghij**************************\"}, {\"license_key\":\"abcdefghij**************************\"}, {\"license_key\":\"abcdefghij**************************\"}]";
7274

7375
Assert.assertEquals(expectedJsonPayload, actualJsonPayload);
7476
}
7577

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

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

82-
MockServiceManager serviceManager = new MockServiceManager();
83-
ServiceFactory.setServiceManager(serviceManager);
84-
Map<String, Object> configMap = new HashMap<>();
85-
configMap.put("license_key", null);
86-
87-
AgentConfig config = AgentConfigImpl.createAgentConfig(configMap);
88-
ConfigService configService = ConfigServiceFactory.createConfigService(config, configMap);
89-
serviceManager.setConfigService(configService);
90-
9186
// When
9287
String actualRequestUrl = LicenseKeyUtil.obfuscateLicenseKey(originalRequestUrl);
9388
String actualJsonPayload = LicenseKeyUtil.obfuscateLicenseKey(originalJsonPayload);
9489

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

10098
public void testObfuscateLicenseKeyWithNullOrEmptyString() {
@@ -116,4 +114,53 @@ public void testObfuscateLicenseKeyWithNullOrEmptyString() {
116114
Assert.assertEquals("", actualEmptyString);
117115
Assert.assertNull(actualNullString);
118116
}
117+
118+
public void testObfuscatedLicenseKeyWithSmallLicenseKey() {
119+
// License keys <= 10 chars are fully obfuscated with "*"
120+
// Given
121+
String originalRequestUrl = "https://staging-collector.newrelic.com:443/agent_listener/invoke_raw_method?method=connect&license_key=abcde&marshal_format=json&protocol_version=17";
122+
123+
String originalJsonPayload = "[{\"license_key\":\"abcde\"}]";
124+
125+
MockServiceManager serviceManager = new MockServiceManager();
126+
ServiceFactory.setServiceManager(serviceManager);
127+
Map<String, Object> configMap = new HashMap<>();
128+
configMap.put("license_key", "abcde");
129+
130+
AgentConfig config = AgentConfigImpl.createAgentConfig(configMap);
131+
ConfigService configService = ConfigServiceFactory.createConfigService(config, configMap);
132+
serviceManager.setConfigService(configService);
133+
134+
// When
135+
String actualRequestUrl = LicenseKeyUtil.obfuscateLicenseKey(originalRequestUrl);
136+
String actualJsonPayload = LicenseKeyUtil.obfuscateLicenseKey(originalJsonPayload);
137+
138+
// Then
139+
String expectedRequestUrl = "https://staging-collector.newrelic.com:443/agent_listener/invoke_raw_method?method=connect&license_key=*****&marshal_format=json&protocol_version=17";
140+
String expectedJsonPayload = "[{\"license_key\":\"*****\"}]";
141+
142+
Assert.assertEquals(expectedRequestUrl, actualRequestUrl);
143+
Assert.assertEquals(expectedJsonPayload, actualJsonPayload);
144+
}
145+
146+
public void testObfuscatedLicenseKeyIfOriginalLicenseKeyIsChanged() {
147+
// If the key is changed in config mid-run, the agent still uses the original key in its requests.
148+
// Obfuscation is driven by the value found in the string, not the configured key,
149+
// so the original key is always obfuscated regardless of what the config currently holds.
150+
String originalRequestUrl = "https://staging-collector.newrelic.com:443/agent_listener/invoke_raw_method?method=connect&license_key=abcdefghijklmonpqrstuvwxyz1234567890&marshal_format=json&protocol_version=17";
151+
152+
String originalJsonPayload = "[{\"license_key\":\"abcdefghijklmonpqrstuvwxyz1234567890\"}]";
153+
154+
// When
155+
String actualRequestUrl = LicenseKeyUtil.obfuscateLicenseKey(originalRequestUrl);
156+
String actualJsonPayload = LicenseKeyUtil.obfuscateLicenseKey(originalJsonPayload);
157+
158+
// Then
159+
String expectedRequestUrl = "https://staging-collector.newrelic.com:443/agent_listener/invoke_raw_method?method=connect&license_key=abcdefghij**************************&marshal_format=json&protocol_version=17";
160+
161+
String expectedJsonPayload = "[{\"license_key\":\"abcdefghij**************************\"}]";
162+
163+
Assert.assertEquals(expectedRequestUrl, actualRequestUrl);
164+
Assert.assertEquals(expectedJsonPayload, actualJsonPayload);
165+
}
119166
}

0 commit comments

Comments
 (0)