Skip to content

Commit

Permalink
SSLContextFactory (#1101)
Browse files Browse the repository at this point in the history
  • Loading branch information
scottf authored Mar 24, 2024
1 parent 8a69711 commit 7cd5785
Show file tree
Hide file tree
Showing 5 changed files with 276 additions and 47 deletions.
108 changes: 71 additions & 37 deletions src/main/java/io/nats/client/Options.java
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,11 @@ public class Options {
* {@link Builder#dispatcherFactory(DispatcherFactory) dispatcherFactory}.
*/
public static final String PROP_DISPATCHER_FACTORY_CLASS = "dispatcher.factory.class";
/**
* Property used to set class name for the SSLContextFactory
* {@link Builder#sslContextFactory(SSLContextFactory) sslContextFactory}.
*/
public static final String PROP_SSL_CONTEXT_FACTORY_CLASS = "ssl.context.factory.class";
/**
* Property for the keystore path used to create an SSLContext
*/
Expand Down Expand Up @@ -681,6 +686,7 @@ public static class Builder {
private boolean verbose = false;
private boolean pedantic = false;
private SSLContext sslContext = null;
private SSLContextFactory sslContextFactory = null;
private int maxControlLine = DEFAULT_MAX_CONTROL_LINE;
private int maxReconnect = DEFAULT_MAX_RECONNECT;
private Duration reconnectWait = DEFAULT_RECONNECT_WAIT;
Expand Down Expand Up @@ -787,9 +793,11 @@ public Builder properties(Properties props) {
charArrayProperty(props, PROP_USERNAME, ca -> this.username = ca);
charArrayProperty(props, PROP_PASSWORD, ca -> this.password = ca);
charArrayProperty(props, PROP_TOKEN, ca -> this.token = ca);

booleanProperty(props, PROP_SECURE, b -> this.useDefaultTls = b);
booleanProperty(props, PROP_OPENTLS, b -> this.useTrustAllTls = b);

classnameProperty(props, PROP_SSL_CONTEXT_FACTORY_CLASS, o -> this.sslContextFactory = (SSLContextFactory) o);
stringProperty(props, PROP_KEYSTORE, s -> this.keystore = s);
charArrayProperty(props, PROP_KEYSTORE_PASSWORD, ca -> this.keystorePassword = ca);
stringProperty(props, PROP_TRUSTSTORE, s -> this.truststore = s);
Expand Down Expand Up @@ -1061,6 +1069,8 @@ public Builder opentls() throws NoSuchAlgorithmException {
/**
* Set the SSL context, requires that the server supports TLS connections and
* the URI specifies TLS.
* If provided, the context takes precedence over any other TLS/SSL properties
* set in the builder, including the sslContextFactory
* @param ctx the SSL Context to use for TLS connections
* @return the Builder for chaining
*/
Expand All @@ -1069,6 +1079,17 @@ public Builder sslContext(SSLContext ctx) {
return this;
}

/**
* Set the factory that provides the ssl context. The factory is superseded
* by an instance of SSLContext
* @param sslContextFactory the SSL Context for use to create a ssl context
* @return the Builder for chaining
*/
public Builder sslContextFactory(SSLContextFactory sslContextFactory) {
this.sslContextFactory = sslContextFactory;
return this;
}

/**
*
* @param keystore the path to the keystore file
Expand Down Expand Up @@ -1625,52 +1646,65 @@ public Options build() throws IllegalStateException {
checkUrisForSecure = false;
}

// ssl context can be directly provided, but if it's not
// there might be a factory, or just see if we should make it ourselves
if (sslContext == null) {
// ssl context can be directly provided, but if it's not
if (keystore != null || truststore != null) {
try {
sslContext = SSLUtils.createSSLContext(keystore, keystorePassword, truststore, truststorePassword, tlsAlgorithm);
}
catch (Exception e) {
throw new IllegalStateException("Unable to create SSL context", e);
}
if (sslContextFactory != null) {
sslContext = sslContextFactory.createSSLContext(new SSLContextFactoryProperties.Builder()
.keystore(keystore)
.keystorePassword(keystorePassword)
.truststore(truststore)
.truststorePassword(truststorePassword)
.tlsAlgorithm(tlsAlgorithm)
.build());
}
else {
// the sslContext has not been requested via keystore/truststore properties
// If we haven't been told to use the default or the trust all context
// and the server isn't the default url, check to see if the server uris
// suggest we need the ssl context.
if (!useDefaultTls && !useTrustAllTls && checkUrisForSecure) {
for (int i = 0; sslContext == null && i < natsServerUris.size(); i++) {
NatsUri natsUri = natsServerUris.get(i);
switch (natsUri.getScheme()) {
case TLS_PROTOCOL:
case SECURE_WEBSOCKET_PROTOCOL:
useDefaultTls = true;
break;
case OPENTLS_PROTOCOL:
useTrustAllTls = true;
break;
}
}
}

// check trust all (open) first, in case they provided both
// PROP_SECURE (secure) and PROP_OPENTLS (opentls)
if (useTrustAllTls) {
if (keystore != null || truststore != null) {
// the user provided keystore/truststore properties, the want us to make the sslContext that way
try {
this.sslContext = SSLUtils.createTrustAllTlsContext();
sslContext = SSLUtils.createSSLContext(keystore, keystorePassword, truststore, truststorePassword, tlsAlgorithm);
}
catch (GeneralSecurityException e) {
catch (Exception e) {
throw new IllegalStateException("Unable to create SSL context", e);
}
}
else if (useDefaultTls) {
try {
this.sslContext = SSLContext.getDefault();
else {
// the sslContext has not been requested via factory or keystore/truststore properties
// If we haven't been told to use the default or the trust all context
// and the server isn't the default url, check to see if the server uris
// suggest we need the ssl context.
if (!useDefaultTls && !useTrustAllTls && checkUrisForSecure) {
for (int i = 0; sslContext == null && i < natsServerUris.size(); i++) {
NatsUri natsUri = natsServerUris.get(i);
switch (natsUri.getScheme()) {
case TLS_PROTOCOL:
case SECURE_WEBSOCKET_PROTOCOL:
useDefaultTls = true;
break;
case OPENTLS_PROTOCOL:
useTrustAllTls = true;
break;
}
}
}
catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("Unable to create default SSL context", e);

// check trust all (open) first, in case they provided both
// PROP_SECURE (secure) and PROP_OPENTLS (opentls)
if (useTrustAllTls) {
try {
this.sslContext = SSLUtils.createTrustAllTlsContext();
}
catch (GeneralSecurityException e) {
throw new IllegalStateException("Unable to create SSL context", e);
}
}
else if (useDefaultTls) {
try {
this.sslContext = SSLContext.getDefault();
}
catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("Unable to create default SSL context", e);
}
}
}
}
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/io/nats/client/impl/SSLContextFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2023 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package io.nats.client.impl;

import javax.net.ssl.SSLContext;

public interface SSLContextFactory {
SSLContext createSSLContext(SSLContextFactoryProperties properties);
}
87 changes: 87 additions & 0 deletions src/main/java/io/nats/client/impl/SSLContextFactoryProperties.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright 2023 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package io.nats.client.impl;

public class SSLContextFactoryProperties {
public final String keystorePath;
public final char[] keystorePassword;
public final String truststorePath;
public final char[] truststorePassword;
public final String tlsAlgorithm;

private SSLContextFactoryProperties(Builder b) {
this.keystorePath = b.keystore;
this.keystorePassword = b.keystorePassword;
this.truststorePath = b.truststore;
this.truststorePassword = b.truststorePassword;
this.tlsAlgorithm = b.tlsAlgorithm;
}

public String getKeystorePath() {
return keystorePath;
}

public char[] getKeystorePassword() {
return keystorePassword;
}

public String getTruststorePath() {
return truststorePath;
}

public char[] getTruststorePassword() {
return truststorePassword;
}

public String getTlsAlgorithm() {
return tlsAlgorithm;
}

public static class Builder {
String keystore;
char[] keystorePassword;
String truststore;
char[] truststorePassword;
String tlsAlgorithm;

public Builder keystore(String keystore) {
this.keystore = keystore;
return this;
}

public Builder keystorePassword(char[] keystorePassword) {
this.keystorePassword = keystorePassword;
return this;
}

public Builder truststore(String truststore) {
this.truststore = truststore;
return this;
}

public Builder truststorePassword(char[] truststorePassword) {
this.truststorePassword = truststorePassword;
return this;
}

public Builder tlsAlgorithm(String tlsAlgorithm) {
this.tlsAlgorithm = tlsAlgorithm;
return this;
}

public SSLContextFactoryProperties build() {
return new SSLContextFactoryProperties(this);
}
}
}
33 changes: 33 additions & 0 deletions src/test/java/io/nats/client/impl/SSLContextFactoryForTesting.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2024 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package io.nats.client.impl;

import io.nats.client.TestSSLUtils;

import javax.net.ssl.SSLContext;

public class SSLContextFactoryForTesting implements SSLContextFactory {
public SSLContextFactoryProperties properties;

@Override
public SSLContext createSSLContext(SSLContextFactoryProperties properties) {
this.properties = properties;
try {
return TestSSLUtils.createTestSSLContext();
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Loading

0 comments on commit 7cd5785

Please sign in to comment.