Skip to content

Commit 3df1fd7

Browse files
authored
tls_available support (#1127)
1 parent 512361d commit 3df1fd7

File tree

8 files changed

+141
-31
lines changed

8 files changed

+141
-31
lines changed

src/main/java/io/nats/client/Options.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1710,6 +1710,10 @@ else if (useDefaultTls) {
17101710
}
17111711
}
17121712

1713+
if (tlsFirst && sslContext == null) {
1714+
throw new IllegalStateException("SSL context required for tls handshake first");
1715+
}
1716+
17131717
if (credentialPath != null) {
17141718
File file = new File(credentialPath).getAbsoluteFile();
17151719
authHandler = Nats.credentials(file.toString());
@@ -2101,10 +2105,10 @@ public int getMaxControlLine() {
21012105

21022106
/**
21032107
*
2104-
* @return true if there is an sslContext for this Options, otherwise false, see {@link Builder#secure() secure()} in the builder doc
2108+
* @return true if there is an sslContext for these Options, otherwise false, see {@link Builder#secure() secure()} in the builder doc
21052109
*/
21062110
public boolean isTLSRequired() {
2107-
return tlsFirst || this.sslContext != null;
2111+
return sslContext != null;
21082112
}
21092113

21102114
/**

src/main/java/io/nats/client/api/ServerInfo.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public class ServerInfo {
3535
private final boolean headersSupported;
3636
private final boolean authRequired;
3737
private final boolean tlsRequired;
38+
private final boolean tlsAvailable;
3839
private final long maxPayload;
3940
private final List<String> connectURLs;
4041
private final int protocolVersion;
@@ -67,7 +68,8 @@ public ServerInfo(String json) {
6768
headersSupported = readBoolean(jv, HEADERS);
6869
authRequired = readBoolean(jv, AUTH_REQUIRED);
6970
nonce = readBytes(jv, NONCE);
70-
tlsRequired = readBoolean(jv, TLS);
71+
tlsRequired = readBoolean(jv, TLS_REQUIRED);
72+
tlsAvailable = readBoolean(jv, TLS_AVAILABLE);
7173
lameDuckMode = readBoolean(jv, LAME_DUCK_MODE);
7274
jetStream = readBoolean(jv, JETSTREAM);
7375
port = readInteger(jv, PORT, 0);
@@ -121,6 +123,10 @@ public boolean isTLSRequired() {
121123
return this.tlsRequired;
122124
}
123125

126+
public boolean isTLSAvailable() {
127+
return tlsAvailable;
128+
}
129+
124130
public long getMaxPayload() {
125131
return this.maxPayload;
126132
}
@@ -181,6 +187,7 @@ public String toString() {
181187
", headersSupported=" + headersSupported +
182188
", authRequired=" + authRequired +
183189
", tlsRequired=" + tlsRequired +
190+
", tlsAvailable=" + tlsAvailable +
184191
", maxPayload=" + maxPayload +
185192
", connectURLs=" + connectURLs +
186193
", protocolVersion=" + protocolVersion +

src/main/java/io/nats/client/impl/NatsConnection.java

Lines changed: 15 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -584,36 +584,25 @@ void checkVersionRequirements() throws IOException {
584584
}
585585

586586
void upgradeToSecureIfNeeded(NatsUri nuri) throws IOException {
587-
Options clientOptions = getOptions();
588-
if (clientOptions.isTlsFirst()) {
589-
this.dataPort.upgradeToSecure();
590-
}
591-
else {
592-
ServerInfo serverInfo = getInfo();
593-
boolean before2_9_19 = serverInfo.isOlderThanVersion("2.9.19");
594-
595-
boolean isTLSRequired = clientOptions.isTLSRequired();
596-
boolean upgradeRequired = isTLSRequired;
597-
if (isTLSRequired && nuri.isWebsocket()) {
598-
// We are already communicating over "https" websocket, so
599-
// do NOT try to upgrade to secure.
600-
if (before2_9_19) {
601-
isTLSRequired = false;
602-
}
603-
upgradeRequired = false;
604-
}
605-
if (isTLSRequired && !serverInfo.isTLSRequired()) {
606-
throw new IOException("SSL connection wanted by client.");
607-
}
608-
else if (!isTLSRequired && serverInfo.isTLSRequired()) {
609-
throw new IOException("SSL required by server.");
587+
// When already communicating over "https" websocket, do NOT try to upgrade to secure.
588+
if (!nuri.isWebsocket()) {
589+
if (options.isTlsFirst()) {
590+
dataPort.upgradeToSecure();
610591
}
611-
if (upgradeRequired) {
612-
this.dataPort.upgradeToSecure();
592+
else {
593+
ServerInfo serverInfo = getInfo();
594+
if (options.isTLSRequired()) {
595+
if (!serverInfo.isTLSRequired() && !serverInfo.isTLSAvailable()) {
596+
throw new IOException("SSL connection wanted by client.");
597+
}
598+
dataPort.upgradeToSecure();
599+
}
600+
else if (serverInfo.isTLSRequired()) {
601+
throw new IOException("SSL required by server.");
602+
}
613603
}
614604
}
615605
}
616-
617606
// Called from reader/writer thread
618607
void handleCommunicationIssue(Exception io) {
619608
// If we are connecting or disconnecting, note exception and leave

src/main/java/io/nats/client/support/ApiConstants.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,8 @@ public interface ApiConstants {
195195
String TIME = "time";
196196
String TIMESTAMP = "ts";
197197
String TLS = "tls_required";
198+
String TLS_REQUIRED = TLS;
199+
String TLS_AVAILABLE = "tls_available";
198200
String TOTAL = "total";
199201
String TYPE = "type";
200202
String VERSION = "version";

src/test/java/io/nats/client/api/ServerInfoTests.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public void testValidInfoString() {
4040
assertEquals(7777, info.getPort());
4141
assertTrue(info.isAuthRequired());
4242
assertTrue(info.isTLSRequired());
43+
assertTrue(info.isTLSAvailable());
4344
assertTrue(info.isHeadersSupported());
4445
assertEquals(100_000_000_000L, info.getMaxPayload());
4546
assertEquals(1, info.getProtocolVersion());

src/test/java/io/nats/client/impl/TLSConnectTests.java

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,4 +397,95 @@ public void testSSLContextFactoryPropertiesPassOnCorrectly() throws NoSuchAlgori
397397
assertEquals("tlsAlgorithm", factory.properties.tlsAlgorithm);
398398
assertEquals("tlsAlgorithm", factory.properties.getTlsAlgorithm());
399399
}
400-
}
400+
401+
private static final int SERVER_INSECURE = 1;
402+
private static final int SERVER_TLS_AVAILABLE = 2;
403+
private static final int SERVER_TLS_REQUIRED = 3;
404+
static class ProxyConnection extends NatsConnection {
405+
int serverType;
406+
407+
public ProxyConnection(String servers, boolean tlsFirst, ErrorListener listener, int serverType) throws Exception {
408+
super(makeMiddleman(servers, tlsFirst, listener));
409+
this.serverType = serverType;
410+
}
411+
412+
private static Options makeMiddleman(String servers, boolean tlsFirst, ErrorListener listener) throws Exception {
413+
Options.Builder builder = new Options.Builder()
414+
.server(servers)
415+
.maxReconnects(0)
416+
.sslContext(SslTestingHelper.createTestSSLContext())
417+
.errorListener(listener);
418+
419+
if (tlsFirst) {
420+
builder.tlsFirst();
421+
}
422+
423+
return builder.build();
424+
}
425+
426+
@Override
427+
void handleInfo(String infoJson) {
428+
switch (serverType) {
429+
case SERVER_INSECURE:
430+
super.handleInfo(infoJson.replace(",\"tls_required\":true", "")); break;
431+
case SERVER_TLS_AVAILABLE:
432+
super.handleInfo(infoJson.replace("\"tls_required\":true", "\"tls_available\":true")); break;
433+
default:
434+
super.handleInfo(infoJson);
435+
}
436+
}
437+
}
438+
439+
/*
440+
1. client tls first | secure proxy | server insecure -> connects
441+
2. client tls first | secure proxy | server tls required -> connects
442+
3. client tls first | secure proxy | server tls available -> connects
443+
4. client regular secure | secure proxy | server insecure -> mismatch exception
444+
5. client regular secure | secure proxy | server tls required -> connects
445+
6. client regular secure | secure proxy | server tls available -> connects
446+
*/
447+
448+
@Test
449+
public void testProxyTlsFirst() throws Exception {
450+
if (TestBase.atLeast2_10_3(ensureRunServerInfo())) {
451+
try (NatsTestServer ts = new NatsTestServer("src/test/resources/tls_first.conf", false)) {
452+
// 1. client tls first | secure proxy | server insecure -> connects
453+
ProxyConnection connTI = new ProxyConnection(ts.getURI(), true, null, SERVER_INSECURE);
454+
connTI.connect(false);
455+
closeConnection(standardConnectionWait(connTI), 1000);
456+
457+
// 2. client tls first | secure proxy | server tls required -> connects
458+
ProxyConnection connTR = new ProxyConnection(ts.getURI(), true, null, SERVER_TLS_REQUIRED);
459+
connTR.connect(false);
460+
closeConnection(standardConnectionWait(connTR), 1000);
461+
462+
// 3. client tls first | secure proxy | server tls available -> connects
463+
ProxyConnection connTA = new ProxyConnection(ts.getURI(), true, null, SERVER_TLS_AVAILABLE);
464+
connTA.connect(false);
465+
closeConnection(standardConnectionWait(connTA), 1000);
466+
}
467+
}
468+
}
469+
470+
@Test
471+
public void testProxyNotTlsFirst() throws Exception {
472+
try (NatsTestServer ts = new NatsTestServer("src/test/resources/tls.conf", false)) {
473+
// 4. client regular secure | secure proxy | server insecure -> mismatch exception
474+
ListenerForTesting listener = new ListenerForTesting();
475+
ProxyConnection connRI = new ProxyConnection(ts.getURI(), false, listener, SERVER_INSECURE);
476+
assertThrows(Exception.class, () -> connRI.connect(false));
477+
assertEquals(1, listener.getExceptions().size());
478+
assertTrue(listener.getExceptions().get(0).getMessage().contains("SSL connection wanted by client"));
479+
480+
// 5. client regular secure | secure proxy | server tls required -> connects
481+
ProxyConnection connRR = new ProxyConnection(ts.getURI(), false, null, SERVER_TLS_REQUIRED);
482+
connRR.connect(false);
483+
closeConnection(standardConnectionWait(connRR), 1000);
484+
485+
// 6. client regular secure | secure proxy | server tls available -> connects
486+
ProxyConnection connRA = new ProxyConnection(ts.getURI(), false, null, SERVER_TLS_AVAILABLE);
487+
connRA.connect(false);
488+
closeConnection(standardConnectionWait(connRA), 1000);
489+
}
490+
}
491+
}

src/test/java/io/nats/client/impl/WebsocketConnectTests.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,22 @@ public void testURISchemeWSSConnection() throws Exception {
196196
}
197197
}
198198

199+
@Test
200+
public void testURISchemeWSSConnectionEnsureTlsFirstHasNoEffect() throws Exception {
201+
SSLContext originalDefault = SSLContext.getDefault();
202+
try (NatsTestServer ts = new NatsTestServer("src/test/resources/wss.conf", false)) {
203+
SSLContext.setDefault(SslTestingHelper.createTestSSLContext());
204+
Options options = Options.builder()
205+
.server(getLocalhostUri("wss", ts.getPort("wss")))
206+
.maxReconnects(0)
207+
.tlsFirst()
208+
.build();
209+
assertCanConnect(options);
210+
} finally {
211+
SSLContext.setDefault(originalDefault);
212+
}
213+
}
214+
199215
@Test
200216
public void testTLSMessageFlow() throws Exception {
201217
try (NatsTestServer ts = new NatsTestServer("src/test/resources/wssverify.conf", false)) {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
INFO {"server_id": "serverId","server_name": "serverName","version": "1.2.3","go": "go0.0.0","host": "host","port": 7777,"headersSupported": true,"auth_required": true,"tls_required": true,"max_payload": 100000000000,"proto": 1,"ldm": true,"jetstream": true,"client_id": 42,"client_ip": "127.0.0.1","cluster": "cluster","connect_urls":["url0", "url1"],"nonce":"<encoded>","headers": true,}
1+
INFO {"server_id": "serverId","server_name": "serverName","version": "1.2.3","go": "go0.0.0","host": "host","port": 7777,"headersSupported": true,"auth_required": true,"tls_required": true,"tls_available": true,"max_payload": 100000000000,"proto": 1,"ldm": true,"jetstream": true,"client_id": 42,"client_ip": "127.0.0.1","cluster": "cluster","connect_urls":["url0", "url1"],"nonce":"<encoded>","headers": true,}

0 commit comments

Comments
 (0)