Working on cleaning up TLS support

This commit is contained in:
Gideon le Grange 2016-01-03 17:37:42 +02:00
parent 84204938e9
commit 380ccda45d
8 changed files with 229 additions and 131 deletions

View File

@ -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<String> 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;
}

View File

@ -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 USERNAME = "gideon";
public static final String PASSWORD = "minapp";
}

View File

@ -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<Map<String, String>> results = con.execute("/interface/print");
for (Map<String, String> 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;
}

View File

@ -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,26 +26,29 @@ 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<Map<String, String>> results = con.execute("/interface/print");
for (Map<String, String> 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;

View File

@ -14,7 +14,7 @@ import me.legrange.mikrotik.ApiConnection;
}
protected void disconnect() throws Exception {
con.disconnect();
con.close();
}
protected ApiConnection con;

View File

@ -19,7 +19,6 @@ public class SimpleCommandWithResults extends Example {
}
private void test() throws MikrotikApiException {
con.setTimeout(50);
List<Map<String, String>> results = con.execute("/interface/print");
for (Map<String, String> result : results) {
System.out.println(result);

View File

@ -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;
/**
@ -12,80 +13,75 @@ import me.legrange.mikrotik.impl.ApiConnectionImpl;
*/
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
* @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
* @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
* @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
* @throws me.legrange.mikrotik.MikrotikApiException Thrown if there is a
* problem connecting
*/
public static ApiConnection connect(String host) throws MikrotikApiException {
return connect(host, DEFAULT_PORT);
@ -98,13 +94,6 @@ 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.
*
@ -115,14 +104,17 @@ public abstract class ApiConnection implements AutoCloseable {
*/
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<Map<String, String>> 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
@ -131,18 +123,22 @@ public abstract class ApiConnection implements AutoCloseable {
*/
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
/**
* 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.
* 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.
@ -152,11 +148,12 @@ public abstract class ApiConnection implements AutoCloseable {
/**
* 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;
}

View File

@ -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<String> 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);