Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improving Options with better construction via properties #946

Merged
merged 9 commits into from
Jul 24, 2023
823 changes: 449 additions & 374 deletions src/main/java/io/nats/client/Options.java

Large diffs are not rendered by default.

75 changes: 61 additions & 14 deletions src/main/java/io/nats/client/support/SSLUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,36 +15,83 @@

import io.nats.client.Options;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.net.ssl.*;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.cert.X509Certificate;

import static io.nats.client.support.RandomUtils.SRAND;

public class SSLUtils {
private static TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {

public static final String DEFAULT_TLS_ALGORITHM = "SunX509";
public static final String DEFAULT_KEYSTORE_TYPE = "JKS";

private static final TrustManager[] TRUST_ALL_CERTS = new TrustManager[] { new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}

public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {}

public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
} };

public static SSLContext createOpenTLSContext() {
SSLContext context = null;

try {
context = SSLContext.getInstance(Options.DEFAULT_SSL_PROTOCOL);
context.init(null, trustAllCerts, SRAND);
} catch (Exception e) {
context = null;
return createTrustAllTlsContext();
}
catch (Exception e) {
return null;
}
}

public static SSLContext createTrustAllTlsContext() throws GeneralSecurityException {
SSLContext context = SSLContext.getInstance(Options.DEFAULT_SSL_PROTOCOL);
context.init(null, TRUST_ALL_CERTS, SRAND);
return context;
}

public static KeyStore loadKeystore(String keystorePath, char[] keystorePwd) throws GeneralSecurityException, IOException {
final KeyStore store = KeyStore.getInstance(DEFAULT_KEYSTORE_TYPE);
try (BufferedInputStream in = new BufferedInputStream(Files.newInputStream(Paths.get(keystorePath)))) {
store.load(in, keystorePwd);
}
return store;
}

public static KeyManager[] createKeyManagers(String keystorePath, char[] keystorePwd) throws GeneralSecurityException, IOException {
return createKeyManagers(keystorePath, keystorePwd, DEFAULT_TLS_ALGORITHM);
}

public static KeyManager[] createKeyManagers(String keystorePath, char[] keystorePwd, String tlsAlgo) throws GeneralSecurityException, IOException {
KeyStore store = loadKeystore(keystorePath, keystorePwd);
KeyManagerFactory factory = KeyManagerFactory.getInstance(tlsAlgo);
factory.init(store, keystorePwd);
return factory.getKeyManagers();
}

public static TrustManager[] createTrustManagers(String truststorePath, char[] truststorePwd) throws GeneralSecurityException, IOException {
return createTrustManagers(truststorePath, truststorePwd, DEFAULT_TLS_ALGORITHM);
}

public static TrustManager[] createTrustManagers(String truststorePath, char[] truststorePwd, String tlsAlgo) throws GeneralSecurityException, IOException {
KeyStore store = loadKeystore(truststorePath, truststorePwd);
TrustManagerFactory factory = TrustManagerFactory.getInstance(tlsAlgo);
factory.init(store);
return factory.getTrustManagers();
}

public static SSLContext createSSLContext(String keystorePath, char[] keystorePwd, String truststorePath, char[] truststorePwd) throws GeneralSecurityException, IOException {
return createSSLContext(keystorePath, keystorePwd, truststorePath, truststorePwd, DEFAULT_TLS_ALGORITHM);
}
public static SSLContext createSSLContext(String keystorePath, char[] keystorePwd, String truststorePath, char[] truststorePwd, String tlsAlgo) throws GeneralSecurityException, IOException {
SSLContext ctx = SSLContext.getInstance(Options.DEFAULT_SSL_PROTOCOL);
ctx.init(createKeyManagers(keystorePath, keystorePwd, tlsAlgo), createTrustManagers(truststorePath, truststorePwd, tlsAlgo), SRAND);
return ctx;
}
}
90 changes: 56 additions & 34 deletions src/test/java/io/nats/client/OptionsTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
import io.nats.client.ConnectionListener.Events;
import io.nats.client.impl.DataPort;
import io.nats.client.impl.ErrorListenerLoggerImpl;
import io.nats.client.impl.NatsServerPool;
import io.nats.client.impl.TestHandler;
import io.nats.client.support.HttpRequest;
import io.nats.client.support.NatsUri;
import io.nats.client.utils.CloseOnUpgradeAttempt;
import io.nats.client.utils.ResourceUtils;
import org.junit.jupiter.api.Test;

import javax.net.ssl.SSLContext;
Expand Down Expand Up @@ -68,7 +68,6 @@ private static void _testDefaultOptions(Options o) {
assertFalse(o.isNoRandomize(), "default norandomize");
assertFalse(o.isOldRequestStyle(), "default oldstyle");
assertFalse(o.isNoEcho(), "default noEcho");
assertFalse(o.supportUTF8Subjects(), "default UTF8 Support");
assertFalse(o.isNoHeaders(), "default header support");
assertFalse(o.isNoNoResponders(), "default no responders support");
assertEquals(Options.DEFAULT_DISCARD_MESSAGES_WHEN_OUTGOING_QUEUE_FULL, o.isDiscardMessagesWhenOutgoingQueueFull(),
Expand Down Expand Up @@ -102,13 +101,17 @@ private static void _testDefaultOptions(Options o) {
public void testOldStyle() {
Options o = new Options.Builder().build();
assertFalse(o.isOldRequestStyle(), "default oldstyle");
//noinspection deprecation
o.setOldRequestStyle(true);
assertTrue(o.isOldRequestStyle(), "default oldstyle");
assertTrue(o.isOldRequestStyle(), "true oldstyle");
//noinspection deprecation
o.setOldRequestStyle(false);
assertFalse(o.isOldRequestStyle(), "false oldstyle");
}

@Test
public void testChainedBooleanOptions() {
Options o = new Options.Builder().verbose().pedantic().noRandomize().supportUTF8Subjects()
Options o = new Options.Builder().verbose().pedantic().noRandomize()
.noEcho().oldRequestStyle().noHeaders().noNoResponders()
.discardMessagesWhenOutgoingQueueFull()
.build();
Expand All @@ -123,7 +126,6 @@ private static void _testChainedBooleanOptions(Options o) {
assertTrue(o.isNoRandomize(), "chained norandomize");
assertTrue(o.isOldRequestStyle(), "chained oldstyle");
assertTrue(o.isNoEcho(), "chained noecho");
assertTrue(o.supportUTF8Subjects(), "chained utf8");
assertTrue(o.isNoHeaders(), "chained no headers");
assertTrue(o.isNoNoResponders(), "chained no noResponders");
assertTrue(o.isDiscardMessagesWhenOutgoingQueueFull(), "chained discard messages when outgoing queue full");
Expand Down Expand Up @@ -266,7 +268,6 @@ public void testPropertiesBooleanBuilder() {
props.setProperty(Options.PROP_USE_OLD_REQUEST_STYLE, "true");
props.setProperty(Options.PROP_OPENTLS, "true");
props.setProperty(Options.PROP_NO_ECHO, "true");
props.setProperty(Options.PROP_UTF8_SUBJECTS, "true");
props.setProperty(Options.PROP_DISCARD_MESSAGES_WHEN_OUTGOING_QUEUE_FULL, "true");

Options o = new Options.Builder(props).build();
Expand All @@ -281,7 +282,6 @@ private static void _testPropertiesBooleanBuilder(Options o) {
assertTrue(o.isNoRandomize(), "property norandomize");
assertTrue(o.isOldRequestStyle(), "property oldstyle");
assertTrue(o.isNoEcho(), "property noecho");
assertTrue(o.supportUTF8Subjects(), "property utf8");
assertTrue(o.isDiscardMessagesWhenOutgoingQueueFull(), "property discard messages when outgoing queue full");
assertNotNull(o.getSslContext(), "property opentls");
}
Expand Down Expand Up @@ -329,50 +329,83 @@ private static void _testPropertiesSSLOptions(Options o) {
assertNotNull(o.getSslContext(), "property context");
}

@SuppressWarnings("deprecation")
@Test
public void testBuilderCoverageOptions() {
public void testDeprecated() {
// clientSideLimitChecks, supportUTF8Subjects are deprecated and always returns false
Options o = new Options.Builder().build();
assertFalse(o.clientSideLimitChecks()); // clientSideLimitChecks is deprecated and always returns false
assertNull(o.getServerPool()); // there is a default provider
assertFalse(o.clientSideLimitChecks());
assertFalse(o.supportUTF8Subjects());

o = new Options.Builder()
.clientSideLimitChecks(true).build();
assertFalse(o.clientSideLimitChecks()); // clientSideLimitChecks is deprecated and always returns false
o = new Options.Builder().clientSideLimitChecks(true).supportUTF8Subjects().build();
assertFalse(o.clientSideLimitChecks());
assertFalse(o.supportUTF8Subjects());

o = new Options.Builder()
.clientSideLimitChecks(false)
.serverPool(new NatsServerPool())
.build();
Properties props = new Properties();
props.setProperty(Options.PROP_CLIENT_SIDE_LIMIT_CHECKS, "true");
props.setProperty(Options.PROP_UTF8_SUBJECTS, "true");
o = new Options.Builder(props).build();
assertFalse(o.clientSideLimitChecks());
assertNotNull(o.getServerPool());
assertFalse(o.supportUTF8Subjects());
}

@Test
public void testPropertiesCoverageOptions() throws Exception {
// don't use default for tests, issues with forcing algorithm exception in other tests break it
SSLContext.setDefault(TestSSLUtils.createTestSSLContext());
Properties props = new Properties();
props.setProperty(Options.PROP_SECURE, "false");
props.setProperty(Options.PROP_OPENTLS, "false");
props.setProperty(Options.PROP_NO_HEADERS, "true");
props.setProperty(Options.PROP_NO_NORESPONDERS, "true");
props.setProperty(Options.PROP_RECONNECT_JITTER, "1000");
props.setProperty(Options.PROP_RECONNECT_JITTER_TLS, "2000");
props.setProperty(Options.PROP_CLIENT_SIDE_LIMIT_CHECKS, "true"); // deprecated
props.setProperty(Options.PROP_IGNORE_DISCOVERED_SERVERS, "true");
props.setProperty(Options.PROP_SERVERS_POOL_IMPLEMENTATION_CLASS, "io.nats.client.utils.CoverageServerPool");
props.setProperty(Options.PROP_NO_RESOLVE_HOSTNAMES, "true");

Options o = new Options.Builder(props).build();
_testPropertiesCoverageOptions(o);
_testPropertiesCoverageOptions(new Options.Builder(o).build());

props = new Properties();
props.setProperty(Options.PROP_SECURE, "false");
props.setProperty(Options.PROP_OPENTLS, "false");
props.setProperty(Options.PROP_NO_HEADERS, "true");
props.setProperty(Options.PROP_NO_NORESPONDERS, "true");
props.setProperty(Options.PROP_RECONNECT_JITTER, "1000");
props.setProperty(Options.PROP_RECONNECT_JITTER_TLS, "2000");
props.setProperty(Options.PROP_IGNORE_DISCOVERED_SERVERS_PREFERRED, "true");
props.setProperty(Options.PROP_SERVERS_POOL_IMPLEMENTATION_CLASS_PREFERRED, "io.nats.client.utils.CoverageServerPool");
props.setProperty(Options.PROP_NO_RESOLVE_HOSTNAMES, "true");

o = new Options.Builder(props).build();
_testPropertiesCoverageOptions(o);
_testPropertiesCoverageOptions(new Options.Builder(o).build());

props = new Properties();
props.load(ResourceUtils.resourceAsInputStream("options_coverage_with_prefix.properties"));
o = new Options.Builder(props).build();
_testPropertiesCoverageOptions(o);

props = new Properties();
props.load(ResourceUtils.resourceAsInputStream("options_coverage_with_prefix_underscore.properties"));
o = new Options.Builder(props).build();
_testPropertiesCoverageOptions(o);

props = new Properties();
props.load(ResourceUtils.resourceAsInputStream("options_coverage_without_prefix.properties"));
o = new Options.Builder(props).build();
_testPropertiesCoverageOptions(o);

props = new Properties();
props.load(ResourceUtils.resourceAsInputStream("options_coverage_without_prefix_underscore.properties"));
o = new Options.Builder(props).build();
_testPropertiesCoverageOptions(o);
}

private static void _testPropertiesCoverageOptions(Options o) {
assertNull(o.getSslContext(), "property context");
assertTrue(o.isNoHeaders());
assertTrue(o.isNoNoResponders());
assertFalse(o.clientSideLimitChecks()); // clientSideLimitChecks is deprecated and always returns false
assertTrue(o.isIgnoreDiscoveredServers());
assertNotNull(o.getServerPool());
assertTrue(o.isNoResolveHostnames());
Expand Down Expand Up @@ -691,21 +724,10 @@ public void testThrowOnBadServerURI() {
() -> new Options.Builder().server("foo:/bar\\:blammer").build());
}

@Test
public void testThrowOnEmptyServersProp() {
assertThrows(IllegalArgumentException.class, () -> {
Properties props = new Properties();
props.setProperty(Options.PROP_SERVERS, "");
new Options.Builder(props).build();
});
}

@Test
public void testThrowOnBadServersURI() {
assertThrows(IllegalArgumentException.class, () -> {
String url1 = URL_PROTO_HOST_PORT_8080;
String url2 = "foo:/bar\\:blammer";
String[] serverUrls = {url1, url2};
String[] serverUrls = {URL_PROTO_HOST_PORT_8080, "foo:/bar\\:blammer"};
new Options.Builder().servers(serverUrls).build();
});
}
Expand Down
55 changes: 21 additions & 34 deletions src/test/java/io/nats/client/TestSSLUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,64 +13,51 @@

package io.nats.client;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.SecureRandom;
import io.nats.client.support.SSLUtils;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.Properties;

public class TestSSLUtils {
public static String KEYSTORE_PATH = "src/test/resources/keystore.jks";
public static String TRUSTSTORE_PATH = "src/test/resources/truststore.jks";
public static String STORE_PASSWORD = "password";
public static String KEY_PASSWORD = "password";
public static String ALGORITHM = "SunX509";
public static String PASSWORD = "password";
public static char[] PASSWORD_CHARS = PASSWORD.toCharArray();

public static KeyStore loadKeystore(String path) throws Exception {
KeyStore store = KeyStore.getInstance("JKS");
BufferedInputStream in = new BufferedInputStream(new FileInputStream(path));

try {
store.load(in, STORE_PASSWORD.toCharArray());
} finally {
if (in != null) {
in.close();
}
}
return SSLUtils.loadKeystore(path, PASSWORD_CHARS);
}

return store;
public static Properties createTestSSLProperties() {
Properties props = new Properties();
props.setProperty(Options.PROP_KEYSTORE_PATH, TestSSLUtils.KEYSTORE_PATH);
props.setProperty(Options.PROP_KEYSTORE_PASSWORD, TestSSLUtils.PASSWORD);
props.setProperty(Options.PROP_TRUSTSTORE_PATH, TestSSLUtils.KEYSTORE_PATH);
props.setProperty(Options.PROP_TRUSTSTORE_PASSWORD, TestSSLUtils.PASSWORD);
return props;
}

public static void setKeystoreSystemParameters() {
System.setProperty("javax.net.ssl.keyStore",KEYSTORE_PATH);
System.setProperty("javax.net.ssl.keyStorePassword",KEY_PASSWORD);
System.setProperty("javax.net.ssl.keyStore", KEYSTORE_PATH);
System.setProperty("javax.net.ssl.keyStorePassword", PASSWORD);
System.setProperty("javax.net.ssl.trustStore",TRUSTSTORE_PATH);
System.setProperty("javax.net.ssl.trustStorePassword",STORE_PASSWORD);
System.setProperty("javax.net.ssl.trustStorePassword", PASSWORD);
}

public static KeyManager[] createTestKeyManagers() throws Exception {
KeyStore store = loadKeystore(KEYSTORE_PATH);
KeyManagerFactory factory = KeyManagerFactory.getInstance(ALGORITHM);
factory.init(store, KEY_PASSWORD.toCharArray());
return factory.getKeyManagers();
return SSLUtils.createKeyManagers(KEYSTORE_PATH, PASSWORD_CHARS);
}

public static TrustManager[] createTestTrustManagers() throws Exception {
KeyStore store = loadKeystore(TRUSTSTORE_PATH);
TrustManagerFactory factory = TrustManagerFactory.getInstance(ALGORITHM);
factory.init(store);
return factory.getTrustManagers();
return SSLUtils.createTrustManagers(TRUSTSTORE_PATH, PASSWORD_CHARS);
}

public static SSLContext createTestSSLContext() throws Exception {
SSLContext ctx = SSLContext.getInstance(Options.DEFAULT_SSL_PROTOCOL);
ctx.init(createTestKeyManagers(), createTestTrustManagers(), new SecureRandom());
return ctx;
return SSLUtils.createSSLContext(KEYSTORE_PATH, PASSWORD_CHARS, TRUSTSTORE_PATH, PASSWORD_CHARS);
}

public static SSLContext createEmptySSLContext() throws Exception {
Expand Down
Loading