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

Add support for TLS1.2 on pre-lollipop devices. #128

Merged
merged 7 commits into from
Nov 16, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions auth0/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ dependencies {
testCompile 'org.hamcrest:java-hamcrest:2.0.0.0'
testCompile 'org.powermock:powermock-module-junit4:1.6.5'
testCompile 'org.powermock:powermock-module-junit4-rule:1.6.5'
testCompile 'org.powermock:powermock-classloading-xstream:1.6.5'
testCompile 'org.powermock:powermock-api-mockito:1.6.5'
testCompile 'org.mockito:mockito-core:1.10.19'
testCompile 'com.squareup.okhttp:mockwebserver:2.7.5'
Expand Down
18 changes: 18 additions & 0 deletions auth0/src/main/java/com/auth0/android/Auth0.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public class Auth0 {
private Telemetry telemetry;
private boolean oidcConformant;
private boolean loggingEnabled;
private boolean tls12Enforced;

/**
* Creates a new Auth0 instance with the 'com_auth0_client_id' and 'com_auth0_domain' values
Expand Down Expand Up @@ -204,6 +205,23 @@ public void setLoggingEnabled(boolean enabled) {
loggingEnabled = enabled;
}

/**
* Getter for whether TLS 1.2 is enforced on devices with API 16-21.
*
* @return whether TLS 1.2 is enforced on devices with API 16-21.
*/
public boolean isTLS12Enforced() {
return tls12Enforced;
}

/**
* Set whether to enforce TLS 1.2 on devices with API 16-21.
* @param enforced whether TLS 1.2 is enforced on devices with API 16-21.
*/
public void setTLS12Enforced(boolean enforced) {
tls12Enforced = enforced;
}

private HttpUrl resolveConfiguration(@Nullable String configurationDomain, @NonNull HttpUrl domainUrl) {
HttpUrl url = ensureValidUrl(configurationDomain);
if (url == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,11 @@
import com.auth0.android.result.DatabaseUser;
import com.auth0.android.result.Delegation;
import com.auth0.android.result.UserProfile;
import com.auth0.android.request.internal.OkHttpClientFactory;
import com.auth0.android.util.Telemetry;
import com.google.gson.Gson;
import com.squareup.okhttp.HttpUrl;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.logging.HttpLoggingInterceptor;

import java.util.Map;

Expand Down Expand Up @@ -97,7 +97,7 @@ public class AuthenticationAPIClient {
private static final String HEADER_AUTHORIZATION = "Authorization";

private final Auth0 auth0;
private final OkHttpClient client;
@VisibleForTesting final OkHttpClient client;
private final Gson gson;
private final RequestFactory factory;
private final ErrorBuilder<AuthenticationException> authErrorBuilder;
Expand All @@ -109,7 +109,7 @@ public class AuthenticationAPIClient {
* @param auth0 account information
*/
public AuthenticationAPIClient(@NonNull Auth0 auth0) {
this(auth0, new RequestFactory(), new OkHttpClient(), GsonProvider.buildGson());
this(auth0, new RequestFactory(), new OkHttpClientFactory(), GsonProvider.buildGson());
}

/**
Expand All @@ -123,16 +123,13 @@ public AuthenticationAPIClient(Context context) {
}

@VisibleForTesting
AuthenticationAPIClient(Auth0 auth0, RequestFactory factory, OkHttpClient client) {
this(auth0, factory, client, GsonProvider.buildGson());
AuthenticationAPIClient(Auth0 auth0, RequestFactory factory, OkHttpClientFactory clientFactory) {
this(auth0, factory, clientFactory, GsonProvider.buildGson());
}

private AuthenticationAPIClient(Auth0 auth0, RequestFactory factory, OkHttpClient client, Gson gson) {
private AuthenticationAPIClient(Auth0 auth0, RequestFactory factory, OkHttpClientFactory clientFactory, Gson gson) {
this.auth0 = auth0;
this.client = client;
if (auth0.isLoggingEnabled()) {
this.client.interceptors().add(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY));
}
this.client = clientFactory.createClient(auth0.isLoggingEnabled(), auth0.isTLS12Enforced());
this.gson = gson;
this.factory = factory;
this.authErrorBuilder = new AuthenticationErrorBuilder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import com.auth0.android.request.internal.RequestFactory;
import com.auth0.android.result.UserIdentity;
import com.auth0.android.result.UserProfile;
import com.auth0.android.request.internal.OkHttpClientFactory;
import com.auth0.android.util.Telemetry;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
Expand Down Expand Up @@ -68,7 +69,7 @@ public class UsersAPIClient {
private static final String USER_METADATA_KEY = "user_metadata";

private final Auth0 auth0;
private final OkHttpClient client;
@VisibleForTesting final OkHttpClient client;
private final Gson gson;
private final RequestFactory factory;
private final ErrorBuilder<ManagementException> mgmtErrorBuilder;
Expand All @@ -80,7 +81,7 @@ public class UsersAPIClient {
* @param token of the primary identity
*/
public UsersAPIClient(Auth0 auth0, String token) {
this(auth0, new RequestFactory(token), new OkHttpClient(), GsonProvider.buildGson());
this(auth0, new RequestFactory(token), new OkHttpClientFactory(), GsonProvider.buildGson());
}

/**
Expand All @@ -95,16 +96,13 @@ public UsersAPIClient(Context context, String token) {
}

@VisibleForTesting
UsersAPIClient(Auth0 auth0, RequestFactory factory, OkHttpClient client) {
this(auth0, factory, client, GsonProvider.buildGson());
UsersAPIClient(Auth0 auth0, RequestFactory factory, OkHttpClientFactory clientFactory) {
this(auth0, factory, clientFactory, GsonProvider.buildGson());
}

private UsersAPIClient(Auth0 auth0, RequestFactory factory, OkHttpClient client, Gson gson) {
private UsersAPIClient(Auth0 auth0, RequestFactory factory, OkHttpClientFactory clientFactory, Gson gson) {
this.auth0 = auth0;
this.client = client;
if (auth0.isLoggingEnabled()) {
this.client.interceptors().add(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY));
}
client = clientFactory.createClient(auth0.isLoggingEnabled(), auth0.isTLS12Enforced());
this.gson = gson;
this.factory = factory;
this.mgmtErrorBuilder = new ManagementErrorBuilder();
Expand Down Expand Up @@ -132,7 +130,6 @@ public void setUserAgent(String userAgent) {
factory.setUserAgent(userAgent);
}


/**
* Link a user identity calling <a href="https://auth0.com/docs/link-accounts#the-management-api">'/api/v2/users/:primaryUserId/identities'</a> endpoint
* Example usage:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.auth0.android.request.internal;

import android.os.Build;
import android.support.annotation.VisibleForTesting;
import android.util.Log;

import com.squareup.okhttp.ConnectionSpec;
import com.squareup.okhttp.Interceptor;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.TlsVersion;
import com.squareup.okhttp.logging.HttpLoggingInterceptor;

import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;

import javax.net.ssl.SSLContext;

/**
* Factory class used to configure and obtain a new OkHttpClient instance.
* This class is meant for internal use only,
* breaking changes may appear at any time without backwards compatibility guarantee.
*/
public class OkHttpClientFactory {

private static final String TAG = OkHttpClientFactory.class.getSimpleName();

/**
* This method creates an instance of OKHttpClient according to the provided parameters.
* It is used internally and is not intended to be used directly.
* @param loggingEnabled Enable logging in the created OkHttpClient.
* @param tls12Enforced Enforce TLS 1.2 in the created OkHttpClient on devices with API 16-21
* @return new OkHttpClient instance created according to the parameters.
*/
public OkHttpClient createClient(boolean loggingEnabled, boolean tls12Enforced) {
return modifyClient(new OkHttpClient(), loggingEnabled, tls12Enforced);
}

@VisibleForTesting
OkHttpClient modifyClient(OkHttpClient client, boolean loggingEnabled, boolean tls12Enforced) {
if (loggingEnabled) {
enableLogging(client);
}
if (tls12Enforced) {
enforceTls12(client);
}
return client;
}

private void enableLogging(OkHttpClient client) {
Interceptor interceptor = new HttpLoggingInterceptor()
.setLevel(HttpLoggingInterceptor.Level.BODY);
client.interceptors().add(interceptor);
}

/**
* Enable TLS 1.2 on the OkHttpClient on API 16-21, which is supported but not enabled by default.
* @link https://github.com/square/okhttp/issues/2372
* @see TLS12SocketFactory
*/
private void enforceTls12(OkHttpClient client) {
// No need to modify client as TLS 1.2 is enabled by default on API21+
// Lollipop is included because some Samsung devices face the same problem on API 21.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN
|| Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
return;
}
try {
SSLContext sc = SSLContext.getInstance("TLSv1.2");
sc.init(null, null, null);
client.setSslSocketFactory(new TLS12SocketFactory(sc.getSocketFactory()));

ConnectionSpec cs = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.tlsVersions(TlsVersion.TLS_1_2)
.build();

List<ConnectionSpec> specs = new ArrayList<>();
specs.add(cs);
specs.add(ConnectionSpec.COMPATIBLE_TLS);
specs.add(ConnectionSpec.CLEARTEXT);

client.setConnectionSpecs(specs);
} catch (NoSuchAlgorithmException | KeyManagementException e) {
Log.e(TAG, "Error while setting TLS 1.2", e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.auth0.android.request.internal;

import android.support.annotation.VisibleForTesting;

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;

import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;

/**
* Enables TLS v1.2 when creating SSLSockets.
* <p/>
* For some reason, android supports TLS v1.2 from API 16, but enables it by
* default only from API 20.
* @link https://developer.android.com/reference/javax/net/ssl/SSLSocket.html
* @see SSLSocketFactory
*/
class TLS12SocketFactory extends SSLSocketFactory {
private static final String[] TLS_V12_ONLY = { "TLSv1.2" };

@VisibleForTesting
private final SSLSocketFactory delegate;

TLS12SocketFactory(SSLSocketFactory base) {
this.delegate = base;
}

@Override
public String[] getDefaultCipherSuites() {
return delegate.getDefaultCipherSuites();
}

@Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}

@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
return patch(delegate.createSocket(s, host, port, autoClose));
}

@Override
public Socket createSocket(String host, int port) throws IOException {
return patch(delegate.createSocket(host, port));
}

@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
return patch(delegate.createSocket(host, port, localHost, localPort));
}

@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return patch(delegate.createSocket(host, port));
}

@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return patch(delegate.createSocket(address, port, localAddress, localPort));
}

private static Socket patch(Socket s) {
if (s instanceof SSLSocket) {
((SSLSocket) s).setEnabledProtocols(TLS_V12_ONLY);
}
return s;
}
}
22 changes: 22 additions & 0 deletions auth0/src/test/java/com/auth0/android/Auth0Test.java
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,28 @@ public void shouldNotHaveLoggingEnabled() throws Exception {
assertThat(auth0.isLoggingEnabled(), is(false));
}

@Test
public void shouldNotEnforceTLS12ByDefault() throws Exception {
Auth0 auth0 = new Auth0(CLIENT_ID, DOMAIN);
assertThat(auth0.isTLS12Enforced(), is(false));
}

@Test
public void shouldHaveTLS12Enforced() throws Exception {
Auth0 auth0 = new Auth0(CLIENT_ID, DOMAIN);
auth0.setTLS12Enforced(true);

assertThat(auth0.isTLS12Enforced(), is(true));
}

@Test
public void shouldNotHaveTLS12Enforced() throws Exception {
Auth0 auth0 = new Auth0(CLIENT_ID, DOMAIN);
auth0.setTLS12Enforced(false);

assertThat(auth0.isTLS12Enforced(), is(false));
}

@Test
public void shouldNotHaveLoggingEnabledByDefault() throws Exception {
Auth0 auth0 = new Auth0(CLIENT_ID, DOMAIN);
Expand Down
Loading