Skip to content

Commit dc66424

Browse files
committed
Add IBM JSSE2 (ibmjava:8) TLS secret extraction support
IBM's JSSE2 provider classes are fully obfuscated, so the hook targets TlsKeyMaterialGenerator.engineGenerateKey() in the unobfuscated JCE crypto layer instead. The generator holds a TlsKeyMaterialParameterSpec field exposing getClientRandom() and getMasterSecret(). Field lookup scans by type to handle obfuscated field names that differ across IBMJCEPlus, IBMJCE, FIPS, and PKCS11 providers. - Add ibmjava:8 Dockerfile and test coverage for IBMJSSE2 in test.sh. - Extend AgentMain unsupported-provider check to include IBMJSSE2. - Add IBM attach API fallback (com.ibm.tools.attach.VirtualMachine) in AgentAttach.
1 parent f03fc1e commit dc66424

6 files changed

Lines changed: 120 additions & 4 deletions

File tree

src/main/java/name/neykov/secrets/agent/AgentMain.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,9 @@ private static void logSecurityProviders() {
176176
log.info("Registered TLS providers: " + Java6Compat.join(", ", names));
177177

178178
String activeSslProvider = sslProviders[0].getName();
179-
if (!activeSslProvider.equals("SunJSSE") && !activeSslProvider.equals("BCJSSE")) {
179+
if (!activeSslProvider.equals("SunJSSE")
180+
&& !activeSslProvider.equals("BCJSSE")
181+
&& !activeSslProvider.equals("IBMJSSE2")) {
180182
log.warning(
181183
"TLS provider '"
182184
+ activeSslProvider

src/main/java/name/neykov/secrets/agent/MasterSecretCallback.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.io.IOException;
55
import java.io.Writer;
66
import java.lang.reflect.Field;
7+
import java.lang.reflect.Method;
78
import java.security.Key;
89
import java.text.SimpleDateFormat;
910
import java.util.Collections;
@@ -175,6 +176,37 @@ public static void onBcTls13ApplicationSecrets(Object tlsContext) {
175176
}
176177
}
177178

179+
// IBM Java 8 JSSE2 uses obfuscated internal classes; the stable hook is the
180+
// TlsKeyMaterialGenerator. It holds a TlsKeyMaterialParameterSpec field that
181+
// exposes getClientRandom() and getMasterSecret(). The field name is
182+
// obfuscated and differs across provider implementations, so we scan
183+
// declared fields by type rather than hardcoding a name.
184+
@SuppressWarnings("unused")
185+
public static void onIbmKeyMaterial(Object generator) {
186+
try {
187+
Object spec = null;
188+
for (java.lang.reflect.Field f : generator.getClass().getDeclaredFields()) {
189+
if (f.getType().getName().endsWith("TlsKeyMaterialParameterSpec")) {
190+
f.setAccessible(true);
191+
spec = f.get(generator);
192+
break;
193+
}
194+
}
195+
if (spec == null) {
196+
return;
197+
}
198+
Method getClientRandom = spec.getClass().getMethod("getClientRandom");
199+
Method getMasterSecret = spec.getClass().getMethod("getMasterSecret");
200+
byte[] clientRandom = (byte[]) getClientRandom.invoke(spec);
201+
Key masterSecret = (Key) getMasterSecret.invoke(spec);
202+
String clientRandomHex = bytesToHex(clientRandom);
203+
String masterKeyHex = bytesToHex(masterSecret.getEncoded());
204+
write("CLIENT_RANDOM " + clientRandomHex + " " + masterKeyHex);
205+
} catch (Exception e) {
206+
log.log(Level.WARNING, "Error retrieving IBM JSSE2 master secret.", e);
207+
}
208+
}
209+
178210
private static String getConnectionDetails(SSLSession sslSession) {
179211
String dateNow;
180212
synchronized (DATE_FMT) {

src/main/java/name/neykov/secrets/agent/Transformer.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
public class Transformer implements ClassFileTransformer {
2020
private static final Logger log = Logger.getLogger(Transformer.class.getName());
2121
private static final AtomicBoolean bcjsseLogged = new AtomicBoolean(false);
22+
private static final AtomicBoolean ibmjsse2Logged = new AtomicBoolean(false);
2223

2324
private static void logBcjsseDetected() {
2425
if (bcjsseLogged.compareAndSet(false, true)) {
@@ -30,6 +31,12 @@ private static void logBcjsseDetected() {
3031
}
3132
}
3233

34+
private static void logIbmJsse2Detected() {
35+
if (ibmjsse2Logged.compareAndSet(false, true)) {
36+
log.info("IBM JSSE2 detected and instrumented.");
37+
}
38+
}
39+
3340
private static String getBcVersion() {
3441
try {
3542
ClassLoader cl = Thread.currentThread().getContextClassLoader();
@@ -161,13 +168,39 @@ protected void instrumentClass(CtClass instrumentedClass)
161168
}
162169
}
163170

171+
// IBM Java 8's JSSE2 provider classes are obfuscated; the stable hook is the
172+
// TlsKeyMaterialGenerator in the crypto layer, which holds both the master
173+
// secret and the client random in its TlsKeyMaterialParameterSpec field.
174+
// Multiple provider implementations exist (IBMJCEPlus, IBMJCE, FIPS, PKCS11);
175+
// the callback resolves the spec field via reflection so a single injected
176+
// expression covers all of them.
177+
private static class IbmKeyMaterialInjectCallback extends InjectCallback {
178+
public IbmKeyMaterialInjectCallback() {
179+
super(
180+
"com.ibm.crypto.plus.provider.TlsKeyMaterialGenerator",
181+
"com.ibm.crypto.provider.TlsKeyMaterialGenerator",
182+
"com.ibm.crypto.fips.provider.TlsKeyMaterialGenerator",
183+
"com.ibm.crypto.pkcs11impl.provider.PKCS11TlsKeyMaterialGenerator");
184+
}
185+
186+
@Override
187+
protected void instrumentClass(CtClass instrumentedClass)
188+
throws CannotCompileException, NotFoundException {
189+
String cb = MasterSecretCallback.class.getName();
190+
CtMethod method = instrumentedClass.getDeclaredMethod("engineGenerateKey");
191+
method.insertBefore(cb + ".onIbmKeyMaterial((java.lang.Object)this);");
192+
logIbmJsse2Detected();
193+
}
194+
}
195+
164196
private static final InjectCallback[] TRANSFORMERS =
165197
new InjectCallback[] {
166198
new SessionInjectCallback(),
167199
new HandshakerInjectCallback(),
168200
new SSLTrafficKeyDerivation(),
169201
new BcTlsProtocolInjectCallback(),
170-
new BcTlsUtilsInjectCallback()
202+
new BcTlsUtilsInjectCallback(),
203+
new IbmKeyMaterialInjectCallback()
171204
};
172205

173206
@Override

src/main/java/name/neykov/secrets/cli/AgentAttach.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,16 @@ private static boolean isAttachApiAvailable() {
141141
AgentAttach.class.getClassLoader().loadClass("com.sun.tools.attach.VirtualMachine");
142142
return true;
143143
} catch (ClassNotFoundException e) {
144-
return false;
144+
// IBM Java 8 ships its own attach API under com.ibm.tools.attach.
145+
// IBM's tools.jar also provides com.sun.tools.attach as a wrapper,
146+
// so AttachHelper (which uses com.sun.tools.attach) works once
147+
// tools.jar is on the classpath.
148+
try {
149+
AgentAttach.class.getClassLoader().loadClass("com.ibm.tools.attach.VirtualMachine");
150+
return true;
151+
} catch (ClassNotFoundException e2) {
152+
return false;
153+
}
145154
}
146155
}
147156
}

src/test/docker/Dockerfile.ibmjdk8

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
FROM ibmjava:8
2+
VOLUME /secrets /project

src/test/docker/test.sh

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,22 @@ PROVIDERS="JSSE BCJSSE"
3131

3232
JSSE_SKIP=""
3333
JSSE_MARKER="CipherSuite:"
34+
JSSE_SSL_PROVIDER="SunJSSE"
3435

3536
BCJSSE_SKIP=""
3637
BCJSSE_MARKER="BCJSSE CipherSuite:"
38+
# BCJSSE is registered by the application after premain; not visible in the
39+
# agent's initial provider log. Leave unset so check_provider_logs skips it.
40+
BCJSSE_SSL_PROVIDER=""
41+
42+
# IBM JSSE2 — ibmjava:8 (IBM SDK for Java 8, IBM J9 JVM) registers IBMJSSE2 as
43+
# provider #1 before premain; the provider-name check is valid for that image.
44+
# ibm-semeru-runtimes (open edition) uses SunJSSE and is covered by JSSE above.
45+
IBMJSSE2_SKIP=""
46+
# IBM's TlsKeyMaterialGenerator hook writes CLIENT_RANDOM directly without a
47+
# comment line (no SSL session available at the crypto layer).
48+
IBMJSSE2_MARKER="CLIENT_RANDOM"
49+
IBMJSSE2_SSL_PROVIDER="IBMJSSE2"
3750
# ──────────────────────────────────────────────────────────────────────────
3851

3952
# ── BC compat matrix ───────────────────────────────────────────────────────
@@ -122,8 +135,12 @@ start_server() {
122135
# Usage: check_provider_logs <provider> <inject_type>
123136
check_provider_logs() {
124137
local provider="$1" inject_type="$2"
125-
local ssl_provider
126138
wait_for_log ssl-secrets-server "Registered TLS providers"
139+
local ssl_provider_var="${provider}_SSL_PROVIDER"
140+
local expected_provider="${!ssl_provider_var}"
141+
if [ -n "$expected_provider" ]; then
142+
wait_for_log ssl-secrets-server "Registered TLS providers:.*$expected_provider"
143+
fi
127144
}
128145

129146
# Verify that a captured pcap can be decrypted using the given keylog file.
@@ -346,4 +363,25 @@ for BC_VERSION in $BC_COMPAT_VERSIONS; do
346363

347364
done
348365

366+
# ══════════════════════════════════════════════════════════════════════════════
367+
# IBM SDK for Java 8 (ibmjava:8): IBM J9 JVM + IBMJSSE2 provider
368+
# This is the JVM used by HCL Notes 12 and similar IBM products.
369+
# TLSv1.3 is not supported by IBM Java 8.
370+
# ══════════════════════════════════════════════════════════════════════════════
371+
372+
run_ibm_jdk8_tests() {
373+
echo -e "\n" \
374+
"=============================================\n" \
375+
" IBM SDK for Java 8 (IBMJSSE2) \n" \
376+
"=============================================\n\n"
377+
docker rm -f $(docker ps -qa) 2>/dev/null || true
378+
docker build -f $CWD/Dockerfile.ibmjdk8 $CWD -t ssl-secrets-server
379+
380+
local marker="${IBMJSSE2_MARKER}"
381+
start_server "$DEFAULT_CP" "IBMJSSE2"
382+
run_proto_test "TLSv1.2" "$DEFAULT_CP" "IBMJSSE2" "$marker"
383+
}
384+
385+
run_ibm_jdk8_tests
386+
349387
docker rm -f ssl-secrets-server

0 commit comments

Comments
 (0)