From 380ccda45d0c4d9d4e53bfd3c029359573afe4a0 Mon Sep 17 00:00:00 2001 From: Gideon le Grange Date: Sun, 3 Jan 2016 17:37:42 +0200 Subject: [PATCH] Working on cleaning up TLS support --- .../java/examples/AnonymousSocketFactory.java | 81 ++++++++++ src/main/java/examples/Config.java | 9 +- .../java/examples/ConnectTLSAnonymous.java | 56 +++++++ ...ectTLS.java => ConnectTLSCertificate.java} | 23 +-- src/main/java/examples/Example.java | 2 +- .../examples/SimpleCommandWithResults.java | 1 - .../me/legrange/mikrotik/ApiConnection.java | 141 +++++++++--------- .../mikrotik/impl/ApiConnectionImpl.java | 47 +----- 8 files changed, 229 insertions(+), 131 deletions(-) create mode 100644 src/main/java/examples/AnonymousSocketFactory.java create mode 100644 src/main/java/examples/ConnectTLSAnonymous.java rename src/main/java/examples/{ConnectTLS.java => ConnectTLSCertificate.java} (67%) diff --git a/src/main/java/examples/AnonymousSocketFactory.java b/src/main/java/examples/AnonymousSocketFactory.java new file mode 100644 index 0000000..45eeab8 --- /dev/null +++ b/src/main/java/examples/AnonymousSocketFactory.java @@ -0,0 +1,81 @@ +/* + * Copyright 2016 gideon. + * + * 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 examples; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import java.util.LinkedList; +import java.util.List; +import javax.net.SocketFactory; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; + +/** + * + * @since 3.0 + * @author Gideon le Grange https://github.com/GideonLeGrange + */ +public class AnonymousSocketFactory extends SocketFactory { + + @Override + public Socket createSocket(String host, int port) throws IOException, UnknownHostException { + return fixSocket((SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port)); + } + + @Override + public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException { + return fixSocket((SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port, localHost, localPort)); + } + + @Override + public Socket createSocket(InetAddress address, int port) throws IOException { + return fixSocket((SSLSocket) SSLSocketFactory.getDefault().createSocket(address, port)); + } + + @Override + public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { + return fixSocket((SSLSocket) SSLSocketFactory.getDefault().createSocket(address, port, localAddress, localPort)); + } + + private Socket fixSocket(SSLSocket ssl) { + List cs = new LinkedList<>(); + // not happy with this code. Without it, SSL throws a "Remote host closed connection during handshake" error + // caused by a "SSL peer shut down incorrectly" error + for (String s : ssl.getSupportedCipherSuites()) { + if (s.startsWith("TLS_DH_anon")) { + cs.add(s); + } + } + ssl.setEnabledCipherSuites(cs.toArray(new String[]{})); + return ssl; + } + + public static SocketFactory getDefault() { + if (fact == null) { + fact = new AnonymousSocketFactory(); + } + return fact; + } + + private AnonymousSocketFactory() { + + } + + private static AnonymousSocketFactory fact; + +} diff --git a/src/main/java/examples/Config.java b/src/main/java/examples/Config.java index 57c81aa..a7dc309 100644 --- a/src/main/java/examples/Config.java +++ b/src/main/java/examples/Config.java @@ -2,14 +2,13 @@ package examples; /** * Config class for examples + * * @author gideon */ public class Config { - - - public static final String HOST = "192.168.1.34"; + + public static final String HOST = "192.168.1.34"; public static final String USERNAME = "gideon"; public static final String PASSWORD = "minapp"; - - + } diff --git a/src/main/java/examples/ConnectTLSAnonymous.java b/src/main/java/examples/ConnectTLSAnonymous.java new file mode 100644 index 0000000..93e1232 --- /dev/null +++ b/src/main/java/examples/ConnectTLSAnonymous.java @@ -0,0 +1,56 @@ +/* + * Copyright 2015 gideon. + * + * 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 examples; + +import java.util.List; +import java.util.Map; +import javax.net.ssl.SSLSocketFactory; +import me.legrange.mikrotik.ApiConnection; +import me.legrange.mikrotik.MikrotikApiException; + +/** + * Example: Open a TLS connection + * + * @author gideon + */ +public class ConnectTLSAnonymous { + + public static void main(String... args) throws Exception { + ConnectTLSAnonymous ex = new ConnectTLSAnonymous(); + ex.connect(); + ex.test(); + ex.disconnect(); + } + + private void test() throws MikrotikApiException { + List> results = con.execute("/interface/print"); + for (Map result : results) { + System.out.println(result); + } + } + + protected void connect() throws Exception { + con = ApiConnection.connect(AnonymousSocketFactory.getDefault(), Config.HOST, ApiConnection.DEFAULT_TLS_PORT, ApiConnection.DEFAULT_CONNECTION_TIMEOUT); + //con = ApiConnection.connect(SocketFactory.getDefault(), Config.HOST, ApiConnection.DEFAULT_PORT, ApiConnection.DEFAULT_CONNECTION_TIMEOUT); + con.login(Config.USERNAME, Config.PASSWORD); + } + + protected void disconnect() throws Exception { + con.close(); + } + + private ApiConnection con; +} diff --git a/src/main/java/examples/ConnectTLS.java b/src/main/java/examples/ConnectTLSCertificate.java similarity index 67% rename from src/main/java/examples/ConnectTLS.java rename to src/main/java/examples/ConnectTLSCertificate.java index e6d3cc1..310d6ce 100644 --- a/src/main/java/examples/ConnectTLS.java +++ b/src/main/java/examples/ConnectTLSCertificate.java @@ -13,12 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package examples; -import java.net.Socket; -import javax.net.SocketFactory; -import javax.net.ssl.SSLSocket; +import java.util.List; +import java.util.Map; import javax.net.ssl.SSLSocketFactory; import me.legrange.mikrotik.ApiConnection; import me.legrange.mikrotik.MikrotikApiException; @@ -28,27 +26,30 @@ import me.legrange.mikrotik.MikrotikApiException; * * @author gideon */ -public class ConnectTLS { +public class ConnectTLSCertificate { public static void main(String... args) throws Exception { - ConnectTLS ex = new ConnectTLS(); + ConnectTLSCertificate ex = new ConnectTLSCertificate(); ex.connect(); ex.test(); ex.disconnect(); } private void test() throws MikrotikApiException { - con.execute("/system/reboot"); + List> results = con.execute("/interface/print"); + for (Map result : results) { + System.out.println(result); + } } protected void connect() throws Exception { - SSLSocketFactory fact = (SSLSocketFactory) SSLSocketFactory.getDefault(); - SSLSocket sock = (SSLSocket) fact.createSocket(); + con = ApiConnection.connect(SSLSocketFactory.getDefault(), Config.HOST, ApiConnection.DEFAULT_TLS_PORT, ApiConnection.DEFAULT_CONNECTION_TIMEOUT); + con.login(Config.USERNAME, Config.PASSWORD); } protected void disconnect() throws Exception { - con.disconnect(); + con.close(); } - + private ApiConnection con; } diff --git a/src/main/java/examples/Example.java b/src/main/java/examples/Example.java index ff25eae..221f9bc 100644 --- a/src/main/java/examples/Example.java +++ b/src/main/java/examples/Example.java @@ -14,7 +14,7 @@ import me.legrange.mikrotik.ApiConnection; } protected void disconnect() throws Exception { - con.disconnect(); + con.close(); } protected ApiConnection con; diff --git a/src/main/java/examples/SimpleCommandWithResults.java b/src/main/java/examples/SimpleCommandWithResults.java index 604e185..63af6f4 100644 --- a/src/main/java/examples/SimpleCommandWithResults.java +++ b/src/main/java/examples/SimpleCommandWithResults.java @@ -19,7 +19,6 @@ public class SimpleCommandWithResults extends Example { } private void test() throws MikrotikApiException { - con.setTimeout(50); List> results = con.execute("/interface/print"); for (Map result : results) { System.out.println(result); diff --git a/src/main/java/me/legrange/mikrotik/ApiConnection.java b/src/main/java/me/legrange/mikrotik/ApiConnection.java index 59ca066..16b750c 100644 --- a/src/main/java/me/legrange/mikrotik/ApiConnection.java +++ b/src/main/java/me/legrange/mikrotik/ApiConnection.java @@ -2,6 +2,7 @@ package me.legrange.mikrotik; import java.util.List; import java.util.Map; +import javax.net.SocketFactory; import me.legrange.mikrotik.impl.ApiConnectionImpl; /** @@ -11,83 +12,78 @@ import me.legrange.mikrotik.impl.ApiConnectionImpl; * @author GideonLeGrange */ public abstract class ApiConnection implements AutoCloseable { - - /** default TCP port used by Mikrotik API */ + + /** + * default TCP port used by Mikrotik API + */ public static final int DEFAULT_PORT = 8728; - /** default TCP TLS port used by Mikrotik API */ + /** + * default TCP TLS port used by Mikrotik API + */ public static final int DEFAULT_TLS_PORT = 8729; - /** default connection timeout to use when opening the connection */ + /** + * default connection timeout to use when opening the connection + */ public static final int DEFAULT_CONNECTION_TIMEOUT = 60000; - /** default command timeout used for synchronous commands */ + /** + * default command timeout used for synchronous commands + */ public static final int DEFAULT_COMMAND_TIMEOUT = 60000; + /** - * Create a new API connection to the give device on the supplied port, using anonymous TLS for encryption. + * Create a new API connection to the give device on the supplied port using + * the supplied socket factory to create the socket. d + * * @param host The host to which to connect. * @param port The TCP port to use. * @param timeout The connection timeout to use when opening the connection. - * @return The ApiConnection - * @throws me.legrange.mikrotik.MikrotikApiException Thrown if there is a problem connecting - * @since 2.1 + * @return The ApiConnection + * @throws me.legrange.mikrotik.MikrotikApiException Thrown if there is a + * problem connecting + * @since 3.0 */ - public static ApiConnection connectTLS(String host, int port, int timeout) throws MikrotikApiException { - return ApiConnectionImpl.connect(host, port, true, timeout); - } - - - /** - * Create a new API connection to the give device on the supplied port, using anonymous TLS for encryption. - * @param host The host to which to connect. - * @param port The TCP port to use. - * @return The ApiConnection - * @throws me.legrange.mikrotik.MikrotikApiException Thrown if there is a problem connecting - */ - public static ApiConnection connectTLS(String host, int port) throws MikrotikApiException { - return ApiConnectionImpl.connect(host, port, true, DEFAULT_CONNECTION_TIMEOUT); - } - - - /** - * Create a new API connection to the give device on the default API port, using anonymous TLS for encryption. - * @param host The host to which to connect. - * @return The ApiConnection - * @throws me.legrange.mikrotik.MikrotikApiException Thrown if there is a problem connecting - */ - public static ApiConnection connectTLS(String host) throws MikrotikApiException { - return ApiConnectionImpl.connect(host, DEFAULT_TLS_PORT, true, DEFAULT_CONNECTION_TIMEOUT); + public static ApiConnection connect(SocketFactory fact, String host, int port, int timeout) throws MikrotikApiException { + return ApiConnectionImpl.connect(fact, host, port, timeout); } /** * Create a new API connection to the give device on the supplied port + * * @param host The host to which to connect. * @param port The TCP port to use. * @param timeout The connection timeout to use when opening the connection. - * @return The ApiConnection - * @throws me.legrange.mikrotik.MikrotikApiException Thrown if there is a problem connecting + * @return The ApiConnection + * @throws me.legrange.mikrotik.MikrotikApiException Thrown if there is a + * problem connecting * @since 2.1 */ public static ApiConnection connect(String host, int port, int timeout) throws MikrotikApiException { - return ApiConnectionImpl.connect(host, port, false, timeout); + return connect(SocketFactory.getDefault(), host, port, timeout); } /** * Create a new API connection to the give device on the supplied port + * * @param host The host to which to connect. * @param port The TCP port to use. - * @return The ApiConnection - * @throws me.legrange.mikrotik.MikrotikApiException Thrown if there is a problem connecting + * @return The ApiConnection + * @throws me.legrange.mikrotik.MikrotikApiException Thrown if there is a + * problem connecting */ public static ApiConnection connect(String host, int port) throws MikrotikApiException { - return ApiConnectionImpl.connect(host, port, false, DEFAULT_CONNECTION_TIMEOUT); + return connect(host, port, DEFAULT_CONNECTION_TIMEOUT); } /** * Create a new API connection to the give device on the default API port. + * * @param host The host to which to connect. - * @return The ApiConnection - * @throws me.legrange.mikrotik.MikrotikApiException Thrown if there is a problem connecting + * @return The ApiConnection + * @throws me.legrange.mikrotik.MikrotikApiException Thrown if there is a + * problem connecting */ - public static ApiConnection connect(String host) throws MikrotikApiException { + public static ApiConnection connect(String host) throws MikrotikApiException { return connect(host, DEFAULT_PORT); } @@ -99,64 +95,65 @@ public abstract class ApiConnection implements AutoCloseable { public abstract boolean isConnected(); /** - * @throws me.legrange.mikrotik.MikrotikApiException - * @deprecated Replaced by close() which conforms to AutoCloseable - */ - @Deprecated - public abstract void disconnect() throws MikrotikApiException; - - /** - * Log in to the remote router. + * Log in to the remote router. * * @param username - username of the user on the router * @param password - password for the user * @throws me.legrange.mikrotik.MikrotikApiException * @throws java.lang.InterruptedException */ - public abstract void login(String username, String password) throws MikrotikApiException, InterruptedException; + public abstract void login(String username, String password) throws MikrotikApiException, InterruptedException; - /** execute a command and return a list of results. + /** + * execute a command and return a list of results. + * * @param cmd Command to execute * @return The list of results * @throws me.legrange.mikrotik.MikrotikApiException */ public abstract List> execute(String cmd) throws MikrotikApiException; - /** execute a command and attach a result listener to receive it's results. - * + /** + * execute a command and attach a result listener to receive it's results. + * * @param cmd Command to execute * @param lis ResultListener that will receive the results * @return A command object that can be used to cancel the command. - * @throws MikrotikApiException + * @throws MikrotikApiException */ public abstract String execute(String cmd, ResultListener lis) throws MikrotikApiException; - /** cancel a command + /** + * cancel a command + * * @param tag The tag of the command to cancel - * @throws me.legrange.mikrotik.MikrotikApiException Thrown if there is a problem canceling the command */ + * @throws me.legrange.mikrotik.MikrotikApiException Thrown if there is a + * problem canceling the command + */ public abstract void cancel(String tag) throws MikrotikApiException; - - /** set the command timeout. The command timeout is used to time out API - * commands after a specific time. - * - * Note: This is not the same as the timeout value passed in the connect() and - * connectTLS() methods. This timeout is specific to synchronous commands, that - * timeout is applied to opening the API socket. - * + /** + * set the command timeout. The command timeout is used to time out API + * commands after a specific time. + * + * Note: This is not the same as the timeout value passed in the connect() + * and connectTLS() methods. This timeout is specific to synchronous + * commands, that timeout is applied to opening the API socket. + * * @param timeout The time out in milliseconds. - * @throws MikrotikApiException Thrown if the timeout specified is invalid. + * @throws MikrotikApiException Thrown if the timeout specified is invalid. * @since 2.1 */ public abstract void setTimeout(int timeout) throws MikrotikApiException; - /** + /** * Disconnect from the remote API - * @throws me.legrange.mikrotik.ApiConnectionException Thrown if there is a problem closing the connection. + * + * @throws me.legrange.mikrotik.ApiConnectionException Thrown if there is a + * problem closing the connection. * @since 2.2 - */ + */ @Override public abstract void close() throws ApiConnectionException; - -} \ No newline at end of file +} diff --git a/src/main/java/me/legrange/mikrotik/impl/ApiConnectionImpl.java b/src/main/java/me/legrange/mikrotik/impl/ApiConnectionImpl.java index 3afa2e3..914e1a7 100644 --- a/src/main/java/me/legrange/mikrotik/impl/ApiConnectionImpl.java +++ b/src/main/java/me/legrange/mikrotik/impl/ApiConnectionImpl.java @@ -7,7 +7,6 @@ import java.io.UnsupportedEncodingException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; -import java.net.SocketAddress; import java.net.UnknownHostException; import java.util.Arrays; import java.util.LinkedList; @@ -15,8 +14,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.LinkedBlockingQueue; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.SSLSocketFactory; +import javax.net.SocketFactory; import me.legrange.mikrotik.ApiConnection; import me.legrange.mikrotik.ApiConnectionException; import me.legrange.mikrotik.MikrotikApiException; @@ -41,9 +39,9 @@ public final class ApiConnectionImpl extends ApiConnection { * @throws me.legrange.mikrotik.ApiConnectionException Thrown if there is a * problem connecting */ - public static ApiConnection connect(String host, int port, boolean secure, int timeOut) throws ApiConnectionException { + public static ApiConnection connect(SocketFactory fact, String host, int port, int timeOut) throws ApiConnectionException { ApiConnectionImpl con = new ApiConnectionImpl(); - con.open(host, port, secure, timeOut); + con.open(host, port, fact, timeOut); return con; } @@ -52,11 +50,6 @@ public final class ApiConnectionImpl extends ApiConnection { return connected; } - @Override - public void disconnect() throws ApiConnectionException { - close(); - } - @Override public void login(String username, String password) throws MikrotikApiException, InterruptedException { if (username.trim().isEmpty()) { @@ -138,14 +131,11 @@ public final class ApiConnectionImpl extends ApiConnection { /** * Start the API. Connects to the Mikrotik */ - private void open(String host, int port, boolean secure, int conTimeout) throws ApiConnectionException { + private void open(String host, int port, SocketFactory fact, int conTimeout) throws ApiConnectionException { try { InetAddress ia = InetAddress.getByName(host.trim()); - if (secure) { - sock = openSSLSocket(ia, port, conTimeout); - } else { - sock = openClearSocket(ia, port, conTimeout); - } + sock = fact.createSocket(); + sock.connect(new InetSocketAddress(ia, port), conTimeout); in = new DataInputStream(sock.getInputStream()); out = new DataOutputStream(sock.getOutputStream()); connected = true; @@ -164,31 +154,6 @@ public final class ApiConnectionImpl extends ApiConnection { } } - private Socket openClearSocket(InetAddress ia, int port, int timeOut) throws IOException { - Socket clear = new Socket(); - SocketAddress addr = new InetSocketAddress(ia, port); - clear.connect(new InetSocketAddress(ia, port), timeOut); - return clear; - } - - /** - * open and configure a SSL socket. - */ - private Socket openSSLSocket(InetAddress ia, int port, int timeOut) throws IOException { - SSLSocket ssl = (SSLSocket) SSLSocketFactory.getDefault().createSocket(); - ssl.connect(new InetSocketAddress(ia, port), timeOut); - List cs = new LinkedList<>(); - // not happy with this code. Without it, SSL throws a "Remote host closed connection during handshake" error - // caused by a "SSL peer shut down incorrectly" error - for (String s : ssl.getSupportedCipherSuites()) { - if (s.startsWith("TLS_DH_anon")) { - cs.add(s); - } - } - ssl.setEnabledCipherSuites(cs.toArray(new String[]{})); - return ssl; - } - private synchronized String nextTag() { _tag++; return Integer.toHexString(_tag);