Skip to content

Commit 14d841e

Browse files
authored
Handle null SSLSessions during invalidation (elastic#34130)
The SSLService invalidates SSLSessions when there is a change to any of the underlying key or trust material. However, this invalidation code did not check for a null SSLSession being returned from the context and assumed that the context would always return a non-null object. The return of a null object is possible in all versions, but JDK11 seems to return them more often due to changes for TLS 1.3. There are a number of reasons that we get a id of a session but the context returns null when the session with that id is requested. Some of the reasons for this are: * Session was evicted by session cache * Session has timed out * Session has been invalidated by another caller To handle this, the SSLService now checks if the value is null before calling invalidate on the SSLSession. Closes elastic#32124
1 parent 95977f4 commit 14d841e

File tree

2 files changed

+197
-11
lines changed

2 files changed

+197
-11
lines changed

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SSLService.java

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import javax.net.ssl.SSLContext;
2525
import javax.net.ssl.SSLEngine;
2626
import javax.net.ssl.SSLParameters;
27+
import javax.net.ssl.SSLSession;
2728
import javax.net.ssl.SSLSessionContext;
2829
import javax.net.ssl.SSLSocket;
2930
import javax.net.ssl.SSLSocketFactory;
@@ -542,17 +543,6 @@ SSLContext sslContext() {
542543
return context;
543544
}
544545

545-
/**
546-
* Invalidates the sessions in the provided {@link SSLSessionContext}
547-
*/
548-
private void invalidateSessions(SSLSessionContext sslSessionContext) {
549-
Enumeration<byte[]> sessionIds = sslSessionContext.getIds();
550-
while (sessionIds.hasMoreElements()) {
551-
byte[] sessionId = sessionIds.nextElement();
552-
sslSessionContext.getSession(sessionId).invalidate();
553-
}
554-
}
555-
556546
synchronized void reload() {
557547
invalidateSessions(context.getClientSessionContext());
558548
invalidateSessions(context.getServerSessionContext());
@@ -592,6 +582,24 @@ X509ExtendedTrustManager getEmptyTrustManager() throws GeneralSecurityException,
592582
}
593583
}
594584

585+
/**
586+
* Invalidates the sessions in the provided {@link SSLSessionContext}
587+
*/
588+
static void invalidateSessions(SSLSessionContext sslSessionContext) {
589+
Enumeration<byte[]> sessionIds = sslSessionContext.getIds();
590+
while (sessionIds.hasMoreElements()) {
591+
byte[] sessionId = sessionIds.nextElement();
592+
SSLSession session = sslSessionContext.getSession(sessionId);
593+
// a SSLSession could be null as there is no lock while iterating, the session cache
594+
// could have evicted a value, the session could be timed out, or the session could
595+
// have already been invalidated, which removes the value from the session cache in the
596+
// sun implementation
597+
if (session != null) {
598+
session.invalidate();
599+
}
600+
}
601+
}
602+
595603
/**
596604
* @return A map of Settings prefix to Settings object
597605
*/

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/SSLServiceTests.java

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.elasticsearch.ElasticsearchException;
2020
import org.elasticsearch.common.CheckedRunnable;
2121
import org.elasticsearch.common.Strings;
22+
import org.elasticsearch.common.SuppressForbidden;
2223
import org.elasticsearch.common.settings.MockSecureSettings;
2324
import org.elasticsearch.common.settings.Settings;
2425
import org.elasticsearch.env.Environment;
@@ -35,19 +36,29 @@
3536
import javax.net.ssl.SSLContext;
3637
import javax.net.ssl.SSLEngine;
3738
import javax.net.ssl.SSLParameters;
39+
import javax.net.ssl.SSLPeerUnverifiedException;
40+
import javax.net.ssl.SSLSession;
41+
import javax.net.ssl.SSLSessionContext;
3842
import javax.net.ssl.SSLSocket;
3943
import javax.net.ssl.SSLSocketFactory;
4044
import javax.net.ssl.X509ExtendedTrustManager;
45+
import javax.security.cert.X509Certificate;
4146
import java.nio.file.Path;
4247
import java.security.AccessController;
48+
import java.security.Principal;
4349
import java.security.PrivilegedActionException;
4450
import java.security.PrivilegedExceptionAction;
51+
import java.security.cert.Certificate;
4552
import java.util.ArrayList;
4653
import java.util.Arrays;
4754
import java.util.Collections;
4855
import java.util.Comparator;
56+
import java.util.Enumeration;
57+
import java.util.HashMap;
4958
import java.util.Iterator;
5059
import java.util.List;
60+
import java.util.Map;
61+
import java.util.concurrent.atomic.AtomicInteger;
5162

5263
import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
5364
import static org.hamcrest.Matchers.contains;
@@ -654,6 +665,57 @@ public void testReadCertificateInformation() throws Exception {
654665
assertFalse(iterator.hasNext());
655666
}
656667

668+
public void testSSLSessionInvalidationHandlesNullSessions() {
669+
final int numEntries = randomIntBetween(1, 32);
670+
final AtomicInteger invalidationCounter = new AtomicInteger();
671+
int numNull = 0;
672+
final Map<byte[], SSLSession> sessionMap = new HashMap<>();
673+
for (int i = 0; i < numEntries; i++) {
674+
final byte[] id = randomByteArrayOfLength(2);
675+
final SSLSession sslSession;
676+
if (rarely()) {
677+
sslSession = null;
678+
numNull++;
679+
} else {
680+
sslSession = new MockSSLSession(id, invalidationCounter::incrementAndGet);
681+
}
682+
sessionMap.put(id, sslSession);
683+
}
684+
685+
SSLSessionContext sslSessionContext = new SSLSessionContext() {
686+
@Override
687+
public SSLSession getSession(byte[] sessionId) {
688+
return sessionMap.get(sessionId);
689+
}
690+
691+
@Override
692+
public Enumeration<byte[]> getIds() {
693+
return Collections.enumeration(sessionMap.keySet());
694+
}
695+
696+
@Override
697+
public void setSessionTimeout(int seconds) throws IllegalArgumentException {
698+
}
699+
700+
@Override
701+
public int getSessionTimeout() {
702+
return 0;
703+
}
704+
705+
@Override
706+
public void setSessionCacheSize(int size) throws IllegalArgumentException {
707+
}
708+
709+
@Override
710+
public int getSessionCacheSize() {
711+
return 0;
712+
}
713+
};
714+
715+
SSLService.invalidateSessions(sslSessionContext);
716+
assertEquals(numEntries - numNull, invalidationCounter.get());
717+
}
718+
657719
@Network
658720
public void testThatSSLContextWithoutSettingsWorks() throws Exception {
659721
SSLService sslService = new SSLService(Settings.EMPTY, env);
@@ -761,4 +823,120 @@ private static void privilegedConnect(CheckedRunnable<Exception> runnable) throw
761823
}
762824
}
763825

826+
private static final class MockSSLSession implements SSLSession {
827+
828+
private final byte[] id;
829+
private final Runnable invalidation;
830+
831+
private MockSSLSession(byte[] id, Runnable invalidation) {
832+
this.id = id;
833+
this.invalidation = invalidation;
834+
}
835+
836+
@Override
837+
public byte[] getId() {
838+
return id;
839+
}
840+
841+
@Override
842+
public SSLSessionContext getSessionContext() {
843+
return null;
844+
}
845+
846+
@Override
847+
public long getCreationTime() {
848+
return 0;
849+
}
850+
851+
@Override
852+
public long getLastAccessedTime() {
853+
return 0;
854+
}
855+
856+
@Override
857+
public void invalidate() {
858+
invalidation.run();
859+
}
860+
861+
@Override
862+
public boolean isValid() {
863+
return false;
864+
}
865+
866+
@Override
867+
public void putValue(String name, Object value) {
868+
869+
}
870+
871+
@Override
872+
public Object getValue(String name) {
873+
return null;
874+
}
875+
876+
@Override
877+
public void removeValue(String name) {
878+
879+
}
880+
881+
@Override
882+
public String[] getValueNames() {
883+
return new String[0];
884+
}
885+
886+
@Override
887+
public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
888+
return new Certificate[0];
889+
}
890+
891+
@Override
892+
public Certificate[] getLocalCertificates() {
893+
return new Certificate[0];
894+
}
895+
896+
@SuppressForbidden(reason = "need to reference deprecated class to implement JDK interface")
897+
@Override
898+
public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException {
899+
return new X509Certificate[0];
900+
}
901+
902+
@Override
903+
public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
904+
return null;
905+
}
906+
907+
@Override
908+
public Principal getLocalPrincipal() {
909+
return null;
910+
}
911+
912+
@Override
913+
public String getCipherSuite() {
914+
return null;
915+
}
916+
917+
@Override
918+
public String getProtocol() {
919+
return null;
920+
}
921+
922+
@Override
923+
public String getPeerHost() {
924+
return null;
925+
}
926+
927+
@Override
928+
public int getPeerPort() {
929+
return 0;
930+
}
931+
932+
@Override
933+
public int getPacketBufferSize() {
934+
return 0;
935+
}
936+
937+
@Override
938+
public int getApplicationBufferSize() {
939+
return 0;
940+
}
941+
}
764942
}

0 commit comments

Comments
 (0)