Skip to content

Commit f0765be

Browse files
committed
tls_available support
1 parent 4aab962 commit f0765be

File tree

7 files changed

+154
-58
lines changed

7 files changed

+154
-58
lines changed

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import java.util.concurrent.*;
3939
import java.util.concurrent.atomic.AtomicInteger;
4040

41+
import static io.nats.client.support.ApiConstants.TLS_REQUIRED;
4142
import static io.nats.client.support.Encoding.uriDecode;
4243
import static io.nats.client.support.NatsConstants.*;
4344
import static io.nats.client.support.SSLUtils.DEFAULT_TLS_ALGORITHM;
@@ -492,7 +493,7 @@ public class Options {
492493
* Protocol key {@value}, see
493494
* {@link Builder#sslContext(SSLContext) sslContext}.
494495
*/
495-
static final String OPTION_TLS_REQUIRED = "tls_required";
496+
static final String OPTION_TLS_REQUIRED = TLS_REQUIRED;
496497

497498
/**
498499
* Protocol key {@value}, see {@link Builder#token(String)
@@ -1710,6 +1711,10 @@ else if (useDefaultTls) {
17101711
}
17111712
}
17121713

1714+
if (tlsFirst && sslContext == null) {
1715+
throw new IllegalStateException("SSL context required for tls handshake first");
1716+
}
1717+
17131718
if (credentialPath != null) {
17141719
File file = new File(credentialPath).getAbsoluteFile();
17151720
authHandler = Nats.credentials(file.toString());
@@ -2101,10 +2106,10 @@ public int getMaxControlLine() {
21012106

21022107
/**
21032108
*
2104-
* @return true if there is an sslContext for this Options, otherwise false, see {@link Builder#secure() secure()} in the builder doc
2109+
* @return true if there is an sslContext for these Options, otherwise false, see {@link Builder#secure() secure()} in the builder doc
21052110
*/
21062111
public boolean isTLSRequired() {
2107-
return tlsFirst || this.sslContext != null;
2112+
return sslContext != null;
21082113
}
21092114

21102115
/**

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

Lines changed: 40 additions & 33 deletions
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);
@@ -84,57 +86,61 @@ public boolean isLameDuckMode() {
8486
}
8587

8688
public String getServerId() {
87-
return this.serverId;
89+
return serverId;
8890
}
8991

9092
public String getServerName() {
9193
return serverName;
9294
}
9395

9496
public String getVersion() {
95-
return this.version;
97+
return version;
9698
}
9799

98100
public String getGoVersion() {
99-
return this.go;
101+
return go;
100102
}
101103

102104
public String getHost() {
103-
return this.host;
105+
return host;
104106
}
105107

106108
public int getPort() {
107-
return this.port;
109+
return port;
108110
}
109111

110112
public int getProtocolVersion() {
111-
return this.protocolVersion;
113+
return protocolVersion;
112114
}
113115

114-
public boolean isHeadersSupported() { return this.headersSupported; }
116+
public boolean isHeadersSupported() { return headersSupported; }
115117

116118
public boolean isAuthRequired() {
117-
return this.authRequired;
119+
return authRequired;
118120
}
119121

120122
public boolean isTLSRequired() {
121-
return this.tlsRequired;
123+
return tlsRequired;
124+
}
125+
126+
public boolean isTLSAvailable() {
127+
return tlsAvailable;
122128
}
123129

124130
public long getMaxPayload() {
125-
return this.maxPayload;
131+
return maxPayload;
126132
}
127133

128134
public List<String> getConnectURLs() {
129-
return this.connectURLs;
135+
return connectURLs;
130136
}
131137

132138
public byte[] getNonce() {
133-
return this.nonce;
139+
return nonce;
134140
}
135141

136142
public boolean isJetStreamAvailable() {
137-
return this.jetStream;
143+
return jetStream;
138144
}
139145

140146
public int getClientId() {
@@ -172,24 +178,25 @@ public boolean isSameOrNewerThanVersion(String vTarget) {
172178
@Override
173179
public String toString() {
174180
return "ServerInfo{" +
175-
"serverId='" + serverId + '\'' +
176-
", serverName='" + serverName + '\'' +
177-
", version='" + version + '\'' +
178-
", go='" + go + '\'' +
179-
", host='" + host + '\'' +
180-
", port=" + port +
181-
", headersSupported=" + headersSupported +
182-
", authRequired=" + authRequired +
183-
", tlsRequired=" + tlsRequired +
184-
", maxPayload=" + maxPayload +
185-
", connectURLs=" + connectURLs +
186-
", protocolVersion=" + protocolVersion +
187-
", nonce=" + Arrays.toString(nonce) +
188-
", lameDuckMode=" + lameDuckMode +
189-
", jetStream=" + jetStream +
190-
", clientId=" + clientId +
191-
", clientIp='" + clientIp + '\'' +
192-
", cluster='" + cluster + '\'' +
193-
'}';
181+
"serverId='" + serverId + '\'' +
182+
", serverName='" + serverName + '\'' +
183+
", version='" + version + '\'' +
184+
", go='" + go + '\'' +
185+
", host='" + host + '\'' +
186+
", port=" + port +
187+
", headersSupported=" + headersSupported +
188+
", authRequired=" + authRequired +
189+
", tlsRequired=" + tlsRequired +
190+
", tlsAvailable=" + tlsAvailable +
191+
", maxPayload=" + maxPayload +
192+
", connectURLs=" + connectURLs +
193+
", protocolVersion=" + protocolVersion +
194+
", nonce=" + Arrays.toString(nonce) +
195+
", lameDuckMode=" + lameDuckMode +
196+
", jetStream=" + jetStream +
197+
", clientId=" + clientId +
198+
", clientIp='" + clientIp + '\'' +
199+
", cluster='" + cluster + '\'' +
200+
'}';
194201
}
195202
}

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

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -577,40 +577,29 @@ public void run() {
577577
void checkVersionRequirements() throws IOException {
578578
Options opts = getOptions();
579579
ServerInfo info = getInfo();
580-
581580
if (opts.isNoEcho() && info.getProtocolVersion() < 1) {
582581
throw new IOException("Server does not support no echo.");
583582
}
584583
}
585584

586585
void upgradeToSecureIfNeeded(NatsUri nuri) throws IOException {
587-
Options clientOptions = getOptions();
588-
if (clientOptions.isTlsFirst()) {
589-
this.dataPort.upgradeToSecure();
586+
if (options.isTlsFirst()) {
587+
dataPort.upgradeToSecure();
590588
}
591589
else {
592590
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;
591+
if (options.isTLSRequired()) {
592+
if (!nuri.isWebsocket()) {
593+
// When already communicating over "https" websocket, do NOT try to upgrade to secure.
594+
if (!serverInfo.isTLSRequired() && !serverInfo.isTLSAvailable()) {
595+
throw new IOException("SSL connection wanted by client.");
596+
}
597+
dataPort.upgradeToSecure();
602598
}
603-
upgradeRequired = false;
604599
}
605-
if (isTLSRequired && !serverInfo.isTLSRequired()) {
606-
throw new IOException("SSL connection wanted by client.");
607-
}
608-
else if (!isTLSRequired && serverInfo.isTLSRequired()) {
600+
else if (serverInfo.isTLSRequired()) {
609601
throw new IOException("SSL required by server.");
610602
}
611-
if (upgradeRequired) {
612-
this.dataPort.upgradeToSecure();
613-
}
614603
}
615604
}
616605

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 & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,4 +397,96 @@ public void testSSLContextFactoryPropertiesPassOnCorrectly() throws NoSuchAlgori
397397
assertEquals("tlsAlgorithm", factory.properties.tlsAlgorithm);
398398
assertEquals("tlsAlgorithm", factory.properties.getTlsAlgorithm());
399399
}
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+
@SuppressWarnings({"resource"})
471+
@Test
472+
public void testProxyNotTlsFirst() throws Exception {
473+
try (NatsTestServer ts = new NatsTestServer("src/test/resources/tls.conf", false)) {
474+
// 4. client regular secure | secure proxy | server insecure -> mismatch exception
475+
ListenerForTesting listener = new ListenerForTesting();
476+
ProxyConnection connRI = new ProxyConnection(ts.getURI(), false, listener, SERVER_INSECURE);
477+
assertThrows(Exception.class, () -> connRI.connect(false));
478+
assertEquals(1, listener.getExceptions().size());
479+
assertTrue(listener.getExceptions().get(0).getMessage().contains("SSL connection wanted by client"));
480+
481+
// 5. client regular secure | secure proxy | server tls required -> connects
482+
ProxyConnection connRR = new ProxyConnection(ts.getURI(), false, null, SERVER_TLS_REQUIRED);
483+
connRR.connect(false);
484+
closeConnection(standardConnectionWait(connRR), 1000);
485+
486+
// 6. client regular secure | secure proxy | server tls available -> connects
487+
ProxyConnection connRA = new ProxyConnection(ts.getURI(), false, null, SERVER_TLS_AVAILABLE);
488+
connRA.connect(false);
489+
closeConnection(standardConnectionWait(connRA), 1000);
490+
}
491+
}
400492
}
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)