Skip to content

Commit

Permalink
Add unit test for SSL session caching (java-native-access#365)
Browse files Browse the repository at this point in the history
Motivation:

07d872f added support for 0-RTT when on the client-side by adding client side session caching. While the implementation worked we missed to add a unit test

Modifications:

- Add a unit test

Result:

Test for SSL session re-use on the client-side
  • Loading branch information
normanmaurer authored Nov 29, 2021
1 parent 12a4ed1 commit ee7bf85
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ final class BoringSSLHandshakeCompleteCallback {

@SuppressWarnings("unused")
void handshakeComplete(long ssl, byte[] id, String cipher, String protocol, byte[] peerCertificate,
byte[][] peerCertificateChain, long creationTime, long timeout, byte[] applicationProtocol) {
byte[][] peerCertificateChain, long creationTime, long timeout, byte[] applicationProtocol,
boolean sessionReused) {
QuicheQuicSslEngine engine = map.get(ssl);
if (engine != null) {
engine.handshakeFinished(id, cipher, protocol, peerCertificate, peerCertificateChain, creationTime,
timeout, applicationProtocol);
timeout, applicationProtocol, sessionReused);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ final class QuicheQuicSslEngine extends QuicSslEngine {
private List<SNIServerName> sniHostNames;
private boolean handshakeFinished;
private String applicationProtocol;
private boolean sessionReused;
final String tlsHostName;
volatile QuicheQuicConnection connection;

Expand Down Expand Up @@ -253,20 +254,25 @@ public boolean getEnableSessionCreation() {
synchronized void handshakeFinished(byte[] id, String cipher, String protocol, byte[] peerCertificate,
byte[][] peerCertificateChain,
long creationTime, long timeout,
byte[] applicationProtocol) {
byte[] applicationProtocol, boolean sessionReused) {
if (applicationProtocol == null) {
this.applicationProtocol = null;
} else {
this.applicationProtocol = new String(applicationProtocol);
}
session.handshakeFinished(id, cipher, protocol, peerCertificate, peerCertificateChain, creationTime, timeout);
this.sessionReused = sessionReused;
handshakeFinished = true;
}

void removeSessionFromCacheIfInvalid() {
session.removeFromCacheIfInvalid();
}

synchronized boolean isSessionReused() {
return sessionReused;
}

private final class QuicheQuicSslSession implements SSLSession {
private X509Certificate[] x509PeerCerts;
private Certificate[] peerCerts;
Expand Down
5 changes: 3 additions & 2 deletions codec-native-quic/src/main/c/netty_quic_boringssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -524,10 +524,11 @@ void quic_SSL_info_callback(const SSL *ssl, int type, int value) {
jlong creationTime = netty_boringssl_SSL_getTime(e, ssl);
jlong timeout = netty_boringssl_SSL_getTimeout(e, ssl);
jbyteArray alpnSelected = netty_boringssl_SSL_getAlpnSelected(e, ssl);
jboolean sessionReused = SSL_session_reused((SSL *) ssl) == 1 ? JNI_TRUE : JNI_FALSE;

// Execute the java callback
(*e)->CallVoidMethod(e, handshakeCompleteCallback, handshakeCompleteCallbackMethod,
(jlong) ssl, session_id, cipher, version, peerCert, certChain, creationTime, timeout, alpnSelected);
(jlong) ssl, session_id, cipher, version, peerCert, certChain, creationTime, timeout, alpnSelected, sessionReused);
}
}

Expand Down Expand Up @@ -1012,7 +1013,7 @@ jint netty_boringssl_JNI_OnLoad(JNIEnv* env, const char* packagePrefix) {

NETTY_JNI_UTIL_PREPEND(packagePrefix, "io/netty/incubator/codec/quic/BoringSSLHandshakeCompleteCallback", name, done);
NETTY_JNI_UTIL_LOAD_CLASS(env, handshakeCompleteCallbackClass, name, done);
NETTY_JNI_UTIL_GET_METHOD(env, handshakeCompleteCallbackClass, handshakeCompleteCallbackMethod, "handshakeComplete", "(J[BLjava/lang/String;Ljava/lang/String;[B[[BJJ[B)V", done);
NETTY_JNI_UTIL_GET_METHOD(env, handshakeCompleteCallbackClass, handshakeCompleteCallbackMethod, "handshakeComplete", "(J[BLjava/lang/String;Ljava/lang/String;[B[[BJJ[BZ)V", done);

NETTY_JNI_UTIL_PREPEND(packagePrefix, "io/netty/incubator/codec/quic/BoringSSLTlsextServernameCallback", name, done);
NETTY_JNI_UTIL_LOAD_CLASS(env, servernameCallbackClass, name, done);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.junit.jupiter.api.Timeout;

import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.X509ExtendedTrustManager;
Expand All @@ -59,6 +60,7 @@

import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
Expand Down Expand Up @@ -784,6 +786,78 @@ InsecureQuicTokenHandler.INSTANCE, new ChannelInboundHandlerAdapter(),
}
}

@Test
@Timeout(5)
public void testSessionReusedOnClientSide() throws Exception {
Channel server = QuicTestUtils.newServer(
new ChannelInboundHandlerAdapter() {
@Override
public boolean isSharable() {
return true;
}

@Override
public void channelActive(ChannelHandlerContext ctx) {
((QuicChannel) ctx.channel()).createStream(QuicStreamType.BIDIRECTIONAL,
new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) {
ctx.writeAndFlush(ctx.alloc().directBuffer(10).writeZero(10))
.addListener(f -> ctx.close());
}
});
ctx.fireChannelActive();
}
},
new ChannelInboundHandlerAdapter());
InetSocketAddress address = (InetSocketAddress) server.localAddress();
QuicSslContext clientSslContext = QuicSslContextBuilder.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE).applicationProtocols(QuicTestUtils.PROTOS).build();

Channel channel = QuicTestUtils.newClient(QuicTestUtils.newQuicClientBuilder().sslEngineProvider(c ->
clientSslContext.newEngine(c.alloc(), "localhost", 9999)));
try {
QuicChannelBootstrap bootstrap = QuicChannel.newBootstrap(channel)
.streamHandler(new ChannelInboundHandlerAdapter())
.remoteAddress(address);

CountDownLatch latch1 = new CountDownLatch(1);
QuicChannel quicChannel1 = bootstrap
.streamHandler(new BytesCountingHandler(latch1, 10))
.connect()
.get();
latch1.await();
assertSessionReused(quicChannel1, false);

CountDownLatch latch2 = new CountDownLatch(1);
QuicChannel quicChannel2 = bootstrap
.streamHandler(new BytesCountingHandler(latch2, 10))
.connect()
.get();

latch2.await();

// Ensure the session is reused.
assertSessionReused(quicChannel2, true);

quicChannel1.close().sync();
quicChannel2.close().sync();
} finally {
server.close().sync();
// Close the parent Datagram channel as well.
channel.close().sync();
}
}

private static void assertSessionReused(QuicChannel channel, boolean reused) throws Exception {
QuicheQuicSslEngine engine = (QuicheQuicSslEngine) channel.sslEngine();
while (engine.getHandshakeStatus() != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
// Let's wait a bit and re-check if the handshake is done.
Thread.sleep(50);
}
assertEquals(reused, engine.isSessionReused());
}

private static final class BytesCountingHandler extends ChannelInboundHandlerAdapter {
private final CountDownLatch latch;
private final int numBytes;
Expand Down

0 comments on commit ee7bf85

Please sign in to comment.