Skip to content

Commit a75c17a

Browse files
authored
Accept Tls First as an Option (#1015)
1 parent 61257e4 commit a75c17a

File tree

5 files changed

+118
-24
lines changed

5 files changed

+118
-24
lines changed

README.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
[![Build Main Badge](https://github.com/nats-io/nats.java/actions/workflows/build-main.yml/badge.svg?event=push)](https://github.com/nats-io/nats.java/actions/workflows/build-main.yml)
1414
[![Release Badge](https://github.com/nats-io/nats.java/actions/workflows/build-release.yml/badge.svg?event=release)](https://github.com/nats-io/nats.java/actions/workflows/build-release.yml)
1515

16-
#
1716
### Simplification
1817

1918
There is a new simplified api that makes working with streams and consumers well, simpler! Simplification is released as of 2.16.14.
@@ -51,6 +50,26 @@ Version 2.5.0 adds some back pressure to publish calls to alleviate issues when
5150

5251
Previous versions are still available in the repo.
5352

53+
#### Version 2.17.1 Support for TLS First
54+
55+
There is a new connection Option, `tlsFirst` for "TLSHandshakeFirst"
56+
57+
In Server 2.10.3 and later, there is the ability to have TLS Handshake First.
58+
The server config will add this:
59+
60+
```text
61+
tls {
62+
...
63+
handshake_first: 300ms
64+
}
65+
```
66+
67+
TLS Handshake First is used to instruct the library perform
68+
the TLS handshake right after the connect and before receiving
69+
the INFO protocol from the server. If this option is enabled
70+
but the server is not configured to perform the TLS handshake
71+
first, the connection will fail.
72+
5473
#### Version 2.17.0: Server 2.10 support. Subject and Queue Name Validation
5574

5675
With the release of the 2.10 server, the client has been updated to support new features.

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

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,12 @@ public class Options {
432432
* Property used to set the path to a credentials file to be used in a FileAuthHandler
433433
*/
434434
public static final String PROP_CREDENTIAL_PATH = PFX + "credential.path";
435+
/**
436+
* Property used to configure tls first behavior
437+
* This property is a boolean flag, telling connections whether
438+
* to do TLS upgrade first, before INFO
439+
*/
440+
public static final String PROP_TLS_FIRST = PFX + "tls.first";
435441
/**
436442
* This property is used to enable support for UTF8 subjects. See {@link Builder#supportUTF8Subjects() supportUTF8Subjects()}
437443
* @deprecated only plain ascii subjects are supported
@@ -564,6 +570,7 @@ public class Options {
564570
private final int maxMessagesInOutgoingQueue;
565571
private final boolean discardMessagesWhenOutgoingQueueFull;
566572
private final boolean ignoreDiscoveredServers;
573+
private final boolean tlsFirst;
567574

568575
private final AuthHandler authHandler;
569576
private final ReconnectDelayHandler reconnectDelayHandler;
@@ -671,6 +678,7 @@ public static class Builder {
671678
private int maxMessagesInOutgoingQueue = DEFAULT_MAX_MESSAGES_IN_OUTGOING_QUEUE;
672679
private boolean discardMessagesWhenOutgoingQueueFull = DEFAULT_DISCARD_MESSAGES_WHEN_OUTGOING_QUEUE_FULL;
673680
private boolean ignoreDiscoveredServers = false;
681+
private boolean tlsFirst = false;
674682
private ServerPool serverPool = null;
675683
private DispatcherFactory dispatcherFactory = null;
676684

@@ -795,6 +803,8 @@ public Builder properties(Properties props) {
795803
booleanProperty(props, PROP_DISCARD_MESSAGES_WHEN_OUTGOING_QUEUE_FULL, b -> this.discardMessagesWhenOutgoingQueueFull = b);
796804

797805
booleanProperty(props, PROP_IGNORE_DISCOVERED_SERVERS, b -> this.ignoreDiscoveredServers = b);
806+
booleanProperty(props, PROP_TLS_FIRST, b -> this.tlsFirst = b);
807+
798808
classnameProperty(props, PROP_SERVERS_POOL_IMPLEMENTATION_CLASS, o -> this.serverPool = (ServerPool) o);
799809
classnameProperty(props, PROP_DISPATCHER_FACTORY_CLASS, o -> this.dispatcherFactory = (DispatcherFactory) o);
800810

@@ -1465,6 +1475,20 @@ public Builder ignoreDiscoveredServers() {
14651475
return this;
14661476
}
14671477

1478+
/**
1479+
* Set TLS Handshake First behavior on. Default is off.
1480+
* TLS Handshake First is used to instruct the library perform
1481+
* the TLS handshake right after the connect and before receiving
1482+
* the INFO protocol from the server. If this option is enabled
1483+
* but the server is not configured to perform the TLS handshake
1484+
* first, the connection will fail.
1485+
* @return the Builder for chaining
1486+
*/
1487+
public Builder tlsFirst() {
1488+
this.tlsFirst = true;
1489+
return this;
1490+
}
1491+
14681492
/**
14691493
* Set the ServerPool implementation for connections to use instead of the default implementation
14701494
* @param serverPool the implementation
@@ -1642,6 +1666,7 @@ public Builder(Options o) {
16421666
this.proxy = o.proxy;
16431667

16441668
this.ignoreDiscoveredServers = o.ignoreDiscoveredServers;
1669+
this.tlsFirst = o.tlsFirst;
16451670

16461671
this.serverPool = o.serverPool;
16471672
this.dispatcherFactory = o.dispatcherFactory;
@@ -1703,6 +1728,7 @@ private Options(Builder b) {
17031728
this.proxy = b.proxy;
17041729

17051730
this.ignoreDiscoveredServers = b.ignoreDiscoveredServers;
1731+
this.tlsFirst = b.tlsFirst;
17061732

17071733
this.serverPool = b.serverPool;
17081734
this.dispatcherFactory = b.dispatcherFactory;
@@ -1913,7 +1939,7 @@ public int getMaxControlLine() {
19131939
* @return true if there is an sslContext for this Options, otherwise false, see {@link Builder#secure() secure()} in the builder doc
19141940
*/
19151941
public boolean isTLSRequired() {
1916-
return (this.sslContext != null);
1942+
return tlsFirst || this.sslContext != null;
19171943
}
19181944

19191945
/**
@@ -2080,6 +2106,14 @@ public boolean isIgnoreDiscoveredServers() {
20802106
return ignoreDiscoveredServers;
20812107
}
20822108

2109+
/**
2110+
* Get whether to do tls first
2111+
* @return the flag
2112+
*/
2113+
public boolean isTlsFirst() {
2114+
return tlsFirst;
2115+
}
2116+
20832117
/**
20842118
* Get the ServerPool implementation. If null, a default implementation is used.
20852119
* @return the ServerPool implementation

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

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -431,9 +431,12 @@ void tryToConnect(NatsUri cur, NatsUri resolved, long now) {
431431

432432
// Wait for the INFO message manually
433433
// all other traffic will use the reader and writer
434+
// TLS First, don't read info until after upgrade
434435
Callable<Object> connectTask = () -> {
435-
readInitialInfo();
436-
checkVersionRequirements();
436+
if (!options.isTlsFirst()) {
437+
readInitialInfo();
438+
checkVersionRequirements();
439+
}
437440
long start = System.nanoTime();
438441
upgradeToSecureIfNeeded(resolved);
439442
if (trace && options.isTLSRequired()) {
@@ -442,6 +445,10 @@ void tryToConnect(NatsUri cur, NatsUri resolved, long now) {
442445
timeTrace(true, "TLS upgrade took: %.3f (s)",
443446
((double) (System.nanoTime() - start)) / 1_000_000_000.0);
444447
}
448+
if (options.isTlsFirst()) {
449+
readInitialInfo();
450+
checkVersionRequirements();
451+
}
445452
return null;
446453
};
447454

@@ -545,28 +552,33 @@ void checkVersionRequirements() throws IOException {
545552

546553
void upgradeToSecureIfNeeded(NatsUri nuri) throws IOException {
547554
Options clientOptions = getOptions();
548-
ServerInfo serverInfo = getInfo();
549-
boolean before2_9_19 = serverInfo.isOlderThanVersion("2.9.19");
550-
551-
boolean isTLSRequired = clientOptions.isTLSRequired();
552-
boolean upgradeRequired = isTLSRequired;
553-
if (isTLSRequired && nuri.isWebsocket()) {
554-
// We are already communicating over "https" websocket, so
555-
// do NOT try to upgrade to secure.
556-
if (before2_9_19) {
557-
isTLSRequired = false;
558-
}
559-
upgradeRequired = false;
560-
}
561-
if (isTLSRequired && !serverInfo.isTLSRequired()) {
562-
throw new IOException("SSL connection wanted by client.");
563-
}
564-
else if (!isTLSRequired && serverInfo.isTLSRequired()) {
565-
throw new IOException("SSL required by server.");
566-
}
567-
if (upgradeRequired) {
555+
if (clientOptions.isTlsFirst()) {
568556
this.dataPort.upgradeToSecure();
569557
}
558+
else {
559+
ServerInfo serverInfo = getInfo();
560+
boolean before2_9_19 = serverInfo.isOlderThanVersion("2.9.19");
561+
562+
boolean isTLSRequired = clientOptions.isTLSRequired();
563+
boolean upgradeRequired = isTLSRequired;
564+
if (isTLSRequired && nuri.isWebsocket()) {
565+
// We are already communicating over "https" websocket, so
566+
// do NOT try to upgrade to secure.
567+
if (before2_9_19) {
568+
isTLSRequired = false;
569+
}
570+
upgradeRequired = false;
571+
}
572+
if (isTLSRequired && !serverInfo.isTLSRequired()) {
573+
throw new IOException("SSL connection wanted by client.");
574+
}
575+
else if (!isTLSRequired && serverInfo.isTLSRequired()) {
576+
throw new IOException("SSL required by server.");
577+
}
578+
if (upgradeRequired) {
579+
this.dataPort.upgradeToSecure();
580+
}
581+
}
570582
}
571583

572584
// Called from reader/writer thread

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,20 @@ public void testSimpleTLSConnection() throws Exception {
6969
}
7070
}
7171

72+
@Test
73+
public void testSimpleTlsFirstConnection() throws Exception {
74+
try (NatsTestServer ts = new NatsTestServer("src/test/resources/tls_first.conf", false)) {
75+
String servers = ts.getURI();
76+
Options options = new Options.Builder()
77+
.server(servers)
78+
.maxReconnects(0)
79+
.tlsFirst()
80+
.sslContext(TestSSLUtils.createTestSSLContext())
81+
.build();
82+
assertCanConnectAndPubSub(options);
83+
}
84+
}
85+
7286
@Test
7387
public void testSimpleUrlTLSConnection() throws Exception {
7488
//System.setProperty("javax.net.debug", "all");

src/test/resources/tls_first.conf

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
2+
# Simple TLS config file
3+
4+
port: 4443
5+
net: localhost
6+
7+
tls {
8+
cert_file: "src/test/resources/certs/server.pem"
9+
key_file: "src/test/resources/certs/key.pem"
10+
timeout: 2
11+
handshake_first: 300ms
12+
13+
# Optional certificate authority for clients
14+
ca_file: "src/test/resources/certs/ca.pem"
15+
}

0 commit comments

Comments
 (0)