From 28e2cee720a6220faface6b38581b782d4677e6b Mon Sep 17 00:00:00 2001 From: mhadam Date: Wed, 18 Apr 2018 18:18:38 -0400 Subject: [PATCH] Add ability to specify TLS protocols to use for emitting events (close #245) --- .../snowplow/tracker/EmitterTest.java | 1 + .../snowplow/tracker/Emitter.java | 41 ++++++++- .../tracker/emitter/TLSArguments.java | 75 ++++++++++++++++ .../tracker/emitter/TLSSocketFactory.java | 85 +++++++++++++++++++ .../tracker/emitter/TLSVersionValidator.java | 51 +++++++++++ 5 files changed, 250 insertions(+), 3 deletions(-) create mode 100644 snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/emitter/TLSArguments.java create mode 100644 snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/emitter/TLSSocketFactory.java create mode 100644 snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/emitter/TLSVersionValidator.java diff --git a/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/tracker/EmitterTest.java b/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/tracker/EmitterTest.java index 6f3874a7a..e9684f3f7 100755 --- a/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/tracker/EmitterTest.java +++ b/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/tracker/EmitterTest.java @@ -498,6 +498,7 @@ public Emitter getEmitter(String uri, HttpMethod method, BufferOption option, Re .byteLimitGet(20000) .byteLimitPost(25000) .timeUnit(TimeUnit.SECONDS) + .tls("TLSv1.2") .build(); } diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/Emitter.java b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/Emitter.java index 932d15319..031041ed9 100755 --- a/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/Emitter.java +++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/Emitter.java @@ -23,6 +23,8 @@ import com.snowplowanalytics.snowplow.tracker.emitter.ReadyRequest; import com.snowplowanalytics.snowplow.tracker.emitter.RequestCallback; import com.snowplowanalytics.snowplow.tracker.emitter.RequestSecurity; +import com.snowplowanalytics.snowplow.tracker.emitter.TLSArguments; +import com.snowplowanalytics.snowplow.tracker.emitter.TLSVersionValidator; import com.snowplowanalytics.snowplow.tracker.payload.Payload; import com.snowplowanalytics.snowplow.tracker.storage.EventStore; import com.snowplowanalytics.snowplow.tracker.utils.Logger; @@ -68,6 +70,7 @@ public class Emitter { private HttpMethod httpMethod; private BufferOption bufferOption; private RequestSecurity requestSecurity; + private String[] tlsVersions; private String uri; private int emitterTick; private int emptyLimit; @@ -92,6 +95,7 @@ public static class EmitterBuilder { HttpMethod httpMethod = HttpMethod.POST; // Optional BufferOption bufferOption = BufferOption.DefaultGroup; // Optional RequestSecurity requestSecurity = RequestSecurity.HTTP; // Optional + String[] tlsVersions = new String[]{"TLSv1.2"}; // Optional int emitterTick = 5; // Optional int sendLimit = 250; // Optional int emptyLimit = 5; // Optional @@ -135,6 +139,26 @@ public EmitterBuilder security(RequestSecurity requestSecurity) { return this; } + /** + * @param version the TLS version allowed for requests + * @return itself + */ + public EmitterBuilder tls(String version) { + TLSVersionValidator versionValidator = new TLSVersionValidator(version); + this.tlsVersions = versionValidator.getVersions(); + return this; + } + + /** + * @param versions the TLS versions allowed for requests + * @return itself + */ + public EmitterBuilder tls(String[] versions) { + TLSVersionValidator versionValidator = new TLSVersionValidator(versions); + this.tlsVersions = versionValidator.getVersions(); + return this; + } + /** * @param requestCallback Request callback function * @return itself @@ -222,6 +246,7 @@ private Emitter(EmitterBuilder builder) { this.context = builder.context; this.bufferOption = builder.bufferOption; this.requestSecurity = builder.requestSecurity; + this.tlsVersions = builder.tlsVersions; this.emitterTick = builder.emitterTick; this.emptyLimit = builder.emptyLimit; this.sendLimit = builder.sendLimit; @@ -231,12 +256,15 @@ private Emitter(EmitterBuilder builder) { this.timeUnit = builder.timeUnit; this.eventStore = new EventStore(this.context, this.sendLimit); + TLSArguments tlsArguments = new TLSArguments(this.tlsVersions); buildEmitterUri(); client = new OkHttpClient.Builder() - .connectTimeout(15, TimeUnit.SECONDS) - .readTimeout(15, TimeUnit.SECONDS) - .build(); + .sslSocketFactory(tlsArguments.getSslSocketFactory(), + tlsArguments.getTrustManager()) + .connectTimeout(15, TimeUnit.SECONDS) + .readTimeout(15, TimeUnit.SECONDS) + .build(); Logger.v(TAG, "Emitter created successfully!"); } @@ -744,6 +772,13 @@ public RequestSecurity getRequestSecurity() { return this.requestSecurity; } + /** + * @return the TLS versions accepted for the emitter + */ + public String[] getTlsVersions() { + return this.tlsVersions; + } + /** * @return the emitter tick */ diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/emitter/TLSArguments.java b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/emitter/TLSArguments.java new file mode 100644 index 000000000..f23cdf828 --- /dev/null +++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/emitter/TLSArguments.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2015-2018 Snowplow Analytics Ltd. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ + +package com.snowplowanalytics.snowplow.tracker.emitter; + +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +import okhttp3.TlsVersion; + +public class TLSArguments { + private X509TrustManager trustManager = null; + private SSLSocketFactory sslSocketFactory = null; + + /** + * Builds an object to store arguments to pass to TLS connection configuration. + * + * @param versions Accepted TLS versions for connections + */ + public TLSArguments(String[] versions) { + try { + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( + TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init((KeyStore) null); + TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); + if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { + throw new IllegalStateException("Unexpected default trust managers:" + + Arrays.toString(trustManagers)); + } + this.trustManager = (X509TrustManager) trustManagers[0]; + + this.sslSocketFactory = new TLSSocketFactory(versions); + } catch (KeyStoreException e) { + e.printStackTrace(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } catch (KeyManagementException e) { + e.printStackTrace(); + } + this.trustManager = trustManager; + this.sslSocketFactory = sslSocketFactory; + } + + /** + * @return the trust manager argument + */ + public X509TrustManager getTrustManager() { + return this.trustManager; + } + + /** + * @return the ssl socket factory argument + */ + public SSLSocketFactory getSslSocketFactory() { + return this.sslSocketFactory; + } +} diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/emitter/TLSSocketFactory.java b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/emitter/TLSSocketFactory.java new file mode 100644 index 000000000..5e0a3dcf5 --- /dev/null +++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/emitter/TLSSocketFactory.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2015-2018 Snowplow Analytics Ltd. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ + +package com.snowplowanalytics.snowplow.tracker.emitter; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; + +public class TLSSocketFactory extends SSLSocketFactory { + + private SSLSocketFactory internalSSLSocketFactory; + private String[] versions = new String[]{"TLSv1.2"}; + + public TLSSocketFactory(String[] versions) throws KeyManagementException, NoSuchAlgorithmException { + this.versions = versions; + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, null, null); + internalSSLSocketFactory = context.getSocketFactory(); + } + + @Override + public String[] getDefaultCipherSuites() { + return internalSSLSocketFactory.getDefaultCipherSuites(); + } + + @Override + public String[] getSupportedCipherSuites() { + return internalSSLSocketFactory.getSupportedCipherSuites(); + } + + @Override + public Socket createSocket() throws IOException { + return enableTLSOnSocket(internalSSLSocketFactory.createSocket()); + } + + @Override + public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { + return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose)); + } + + @Override + public Socket createSocket(String host, int port) throws IOException, UnknownHostException { + return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port)); + } + + @Override + public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException { + return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort)); + } + + @Override + public Socket createSocket(InetAddress host, int port) throws IOException { + return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port)); + } + + @Override + public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { + return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort)); + } + + private Socket enableTLSOnSocket(Socket socket) { + if(socket != null && (socket instanceof SSLSocket)) { + ((SSLSocket) socket).setEnabledProtocols(this.versions); + } + return socket; + } +} \ No newline at end of file diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/emitter/TLSVersionValidator.java b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/emitter/TLSVersionValidator.java new file mode 100644 index 000000000..8209bfb76 --- /dev/null +++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/emitter/TLSVersionValidator.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2015-2017 Snowplow Analytics Ltd. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ + +package com.snowplowanalytics.snowplow.tracker.emitter; + +import java.util.Set; +import java.util.HashSet; + +public class TLSVersionValidator { + private Set versions = new HashSet<>(); + + /** + * Builds an object to only allow valid version values + * + * @param versions Versions allowed in TLS connection + */ + public TLSVersionValidator(String[] versions) { + for (String version : versions) { + if (version.equalsIgnoreCase("TLSv1.2")) { + this.versions.add("TLSv1.2"); + } else if (version.equalsIgnoreCase("TLSv1.1")) { + this.versions.add("TLSv1.1"); + } + } + } + + public TLSVersionValidator(String version) { + if (version.equalsIgnoreCase("TLSv1.2")) { + this.versions.add("TLSv1.2"); + } else if (version.equalsIgnoreCase("TLSv1.1")) { + this.versions.add("TLSv1.1"); + } + } + + /** + * @return Version of TLS connection + */ + public String[] getVersions() { + return versions.toArray(new String[versions.size()]); + } +}