This is a small library that provides proxies for JSSE based classes such as SSLEngine
, KeyManager
and TrustManager
, and provides facilities to decorate behavior with logging and revocation.
Object capabilities are based around deferred execution and composition, so that instead of dealing with a direct object reference, you deal with a proxy that manages your access to the object.
Proxies are very simple to set up. You define a supplier, and then you delegate all access through resolving that supplier. Here's an example of X509ExtendedKeyManager set up with a proxy:
package com.tersesystems.ocapjsse;
public class ProxyX509ExtendedKeyManager extends X509ExtendedKeyManager {
protected Supplier<X509ExtendedKeyManager> supplier;
public ProxyX509ExtendedKeyManager(Supplier<X509ExtendedKeyManager> supplier) {
Objects.requireNonNull(supplier);
this.supplier = supplier;
}
public String chooseEngineClientAlias(
final String[] keyTypes, final Principal[] issuers, final SSLEngine engine) {
return supplier.get().chooseEngineClientAlias(keyTypes, issuers, engine);
}
// ...
}
Once you have a proxy, you can then set up logging around the manager:
package com.tersesystems.ocapjsse.revocable;
public class LoggingX509ExtendedTrustManager extends ProxyX509ExtendedTrustManager {
private final TraceLogger tracer;
public LoggingX509ExtendedTrustManager(
final Supplier<X509ExtendedTrustManager> supplier, final TraceLogger tracer) {
super(supplier);
this.tracer = tracer;
}
@Override
public void checkClientTrusted(final X509Certificate[] chain, final String authType,
final Socket socket)
throws CertificateException {
final Object[] params = {chain, authType, socket};
tracer.apply(
"checkClientTrusted", params, () -> super.checkClientTrusted(chain, authType, socket));
}
// ...
}
This is a lightweight alternative to using the debugjsse provider, as you can restrict logging to only a single trust manager.
public class LoggingX509ExtendedTrustManagerTest {
@Test
public void testLog() throws Exception {
final TraceLogger tracer =
new AbstractTraceLogger() {
@Override
protected void entry(final String methodName, final Object... parameters) {
System.out.println("entry: " + methodName);
}
@Override
protected <R> R exit(final R result, final String methodName,
final Object... parameters) {
System.out.println("exit: " + methodName);
return result;
}
@Override
protected void exit(final String methodName, final Object... parameters) {
System.out.println("exit: " + methodName);
}
@Override
protected void exception(final Throwable e, final String methodName,
final Object... parameters) {
System.out.println("exception: " + methodName);
}
};
try {
final SSLContext sslContext = SSLContext.getInstance("TLS");
final TrustManagerFactory tmf =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init((KeyStore) null);
final TrustManager[] tms =
Arrays.stream(tmf.getTrustManagers())
.map(LoggingX509ExtendedTrustManager.transform(tracer))
.toArray(TrustManager[]::new);
sslContext.init(null, tms, null);
final HttpsURLConnection urlConnection =
(HttpsURLConnection) new URL("https://www.google.com").openConnection();
urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());
// Call Google
try (final BufferedReader in =
new BufferedReader(new InputStreamReader(urlConnection.getInputStream()))) {
final String result = in.lines().collect(Collectors.joining());
System.out.println(result);
}
} catch (final Exception e) {
e.printStackTrace();
}
}
}
You can even use a caretaker to set up revocation. This can be used to break off communication immediately when a security guarantee is violated, or to enforce a time limit on access.
package com.tersesystems.ocapjsse.revocable;
public class RevocableTrustManagerTest {
@Test
public void testRevokedEngine() throws Exception {
X509ExtendedTrustManager trustManager = createTrustManager();
assertThat(trustManager).isNotNull();
Caretaker<X509ExtendedTrustManager> caretaker = Caretaker
.create(trustManager, ProxyX509ExtendedTrustManager::new);
X509ExtendedTrustManager proxyTrustManager = caretaker.getCapability();
X509Certificate[] issuers = proxyTrustManager.getAcceptedIssuers();
assertThat(issuers).isNotNull();
caretaker.getRevoker().revoke();
Throwable throwable = catchThrowable(proxyTrustManager::getAcceptedIssuers);
assertThat(throwable).isInstanceOf(RevokedException.class);
}
}
For more about revocation, see managing accessibility with revocation.