[android] Supported client cert

This commit is contained in:
Dmitry Donskoy 2018-10-15 15:27:13 +03:00 committed by Aleksandr Zatsepin
parent 6975de3334
commit 1305da5b91
4 changed files with 87 additions and 367 deletions

View file

@ -0,0 +1,60 @@
package com.mapswithme.util;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.mapswithme.util.log.Logger;
import com.mapswithme.util.log.LoggerFactory;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.SecureRandom;
public class ClientCertTLSSocketFactory {
private static final Logger LOGGER = LoggerFactory.INSTANCE.getLogger(LoggerFactory.Type.NETWORK);
private static final String TAG = ClientCertTLSSocketFactory.class.getSimpleName();
private static final String PROTOCOL = "TLS";
private static final String ALGORITHM = "X509";
private static final String KEY_STORE_TYPE = "PKCS12";
public static SSLSocketFactory create(@NonNull byte[] payload, @Nullable char[] password)
{
InputStream inputStream = null;
try
{
inputStream = new ByteArrayInputStream(payload);
KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE);
keyStore.load(inputStream, password);
KeyManagerFactory kmf = KeyManagerFactory.getInstance(ALGORITHM);
kmf.init(keyStore, null);
SSLContext sslContext = SSLContext.getInstance(PROTOCOL);
sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
return sslContext.getSocketFactory();
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
finally
{
if (inputStream != null)
{
try
{
inputStream.close();
}
catch (IOException e)
{
LOGGER.d(TAG, "Stream not closed", e);
}
}
}
}
}

View file

@ -24,21 +24,15 @@
package com.mapswithme.util;
import android.content.res.Resources;
import android.os.Build;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import com.mapswithme.maps.MwmApplication;
import com.mapswithme.maps.R;
import com.mapswithme.util.log.Logger;
import com.mapswithme.util.log.LoggerFactory;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
@ -50,12 +44,6 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@ -83,7 +71,7 @@ public final class HttpClient
try
{
connection = (HttpURLConnection) new URL("https://user-request.mapsme.devmail.ru/binding_request").openConnection();
connection = (HttpURLConnection) new URL(p.url).openConnection();
setupTLSForPreLollipop(connection);
// NullPointerException, MalformedUrlException, IOException
@ -207,9 +195,6 @@ public final class HttpClient
ostream.close(); // IOException
if (ostream instanceof ByteArrayOutputStream)
p.data = ((ByteArrayOutputStream) ostream).toByteArray();
} catch (IOException e)
{
throw new IOException(e);
}
finally
{
@ -232,11 +217,6 @@ public final class HttpClient
SSLSocketFactory factory = sslConnection.getSSLSocketFactory();
sslConnection.setSSLSocketFactory(new PreLollipopSSLSocketFactory(factory));
}
if ((connection instanceof HttpsURLConnection)) {
SSLSocketFactory socketFactory = TLSSocketFactory.create(MwmApplication.get(), R.raw.cert);
((HttpsURLConnection)connection).setSSLSocketFactory(socketFactory);
}
}
@NonNull

View file

@ -10,6 +10,8 @@ import com.mapswithme.maps.Framework;
import com.mapswithme.util.log.Logger;
import com.mapswithme.util.log.LoggerFactory;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocketFactory;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
@ -20,6 +22,7 @@ import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
@ -78,11 +81,7 @@ public final class HttpUploader
{
URL url = new URL(mUrl);
connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(Constants.CONNECTION_TIMEOUT_MS);
connection.setReadTimeout(Constants.READ_TIMEOUT_MS);
connection.setUseCaches(false);
connection.setRequestMethod(mMethod);
connection.setDoOutput(mMethod.equals("POST"));
onPrepareConnection(connection);
long fileSize = StorageUtils.getFileSize(mFilePath);
StringBuilder paramsBuilder = new StringBuilder();
@ -132,6 +131,28 @@ public final class HttpUploader
return new Result(status, message);
}
private void onPrepareConnection(@NonNull HttpURLConnection connection) throws ProtocolException
{
connection.setConnectTimeout(Constants.CONNECTION_TIMEOUT_MS);
connection.setReadTimeout(Constants.READ_TIMEOUT_MS);
connection.setUseCaches(false);
connection.setRequestMethod(mMethod);
connection.setDoOutput(mMethod.equals("POST"));
if ("https".equals(connection.getURL().getProtocol()) && mNeedClientAuth)
{
HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
setupClientCert(httpsConnection);
}
}
private void setupClientCert(@NonNull HttpsURLConnection connection)
{
String cert = HttpUploader.nativeUserBindingCertificate();
String pwd = HttpUploader.nativeUserBindingPassword();
SSLSocketFactory socketFactory = ClientCertTLSSocketFactory.create(cert.getBytes(), pwd.toCharArray());
connection.setSSLSocketFactory(socketFactory);
}
private static void setStreamingMode(@NonNull HttpURLConnection connection, long bodyLength)
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)

View file

@ -1,341 +0,0 @@
package com.mapswithme.util;
import android.content.Context;
import android.net.SSLCertificateSocketFactory;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RawRes;
import com.mapswithme.maps.MwmApplication;
import com.mapswithme.maps.R;
import com.mopub.common.Preconditions;
import com.mopub.common.VisibleForTesting;
import com.mopub.common.logging.MoPubLog;
import com.mopub.common.util.Reflection;
import com.mopub.network.InetAddressUtils;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
/**
* An {@link javax.net.ssl.SSLSocketFactory} that supports TLS settings for the MoPub ad servers.
*/
public class TLSSocketFactory extends SSLSocketFactory {
@Nullable private SSLSocketFactory mCertificateSocketFactory;
protected SSLContext sslContext = SSLContext.getInstance("TLS");
public TLSSocketFactory(KeyStore keyStore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException
{
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, "".toCharArray());
sslContext.init(kmf.getKeyManagers(), new TrustManager[]{ new AdditionalKeyStoresSSLSocketFactory.ClientKeyStoresTrustManager(keyStore)}, new SecureRandom());
}
@NonNull
public static TLSSocketFactory getDefault(KeyStore keyStore) throws UnrecoverableKeyException,
NoSuchAlgorithmException, KeyStoreException, KeyManagementException
{
TLSSocketFactory factory = new TLSSocketFactory(keyStore);
factory.mCertificateSocketFactory = SSLCertificateSocketFactory.getDefault((int) TimeUnit.MINUTES.toMillis(5), null);
return factory;
}
// Forward all methods. Enable TLS 1.1 and 1.2 before returning.
// SocketFactory overrides
@Override
public Socket createSocket() throws IOException {
if (mCertificateSocketFactory == null) {
throw new SocketException("SSLSocketFactory was null. Unable to create socket.");
}
final Socket socket = mCertificateSocketFactory.createSocket();
enableTlsIfAvailable(socket);
return socket;
}
@Override
public Socket createSocket(final String host, final int i) throws IOException, UnknownHostException {
if (mCertificateSocketFactory == null) {
throw new SocketException("SSLSocketFactory was null. Unable to create socket.");
}
final Socket socket = mCertificateSocketFactory.createSocket(host, i);
enableTlsIfAvailable(socket);
return socket;
}
@Override
public Socket createSocket(final String host, final int port, final InetAddress localhost, final int localPort) throws IOException, UnknownHostException {
if (mCertificateSocketFactory == null) {
throw new SocketException("SSLSocketFactory was null. Unable to create socket.");
}
final Socket socket = mCertificateSocketFactory.createSocket(host, port, localhost, localPort);
enableTlsIfAvailable(socket);
return socket;
}
@Override
public Socket createSocket(final InetAddress address, final int port) throws IOException {
if (mCertificateSocketFactory == null) {
throw new SocketException("SSLSocketFactory was null. Unable to create socket.");
}
final Socket socket = mCertificateSocketFactory.createSocket(address, port);
enableTlsIfAvailable(socket);
return socket;
}
@Override
public Socket createSocket(final InetAddress address, final int port, final InetAddress localhost, final int localPort) throws IOException {
if (mCertificateSocketFactory == null) {
throw new SocketException("SSLSocketFactory was null. Unable to create socket.");
}
final Socket socket = mCertificateSocketFactory.createSocket(address, port, localhost, localPort);
enableTlsIfAvailable(socket);
return socket;
}
// SSLSocketFactory overrides
@Override
public String[] getDefaultCipherSuites() {
if (mCertificateSocketFactory == null) {
return new String[]{};
}
return mCertificateSocketFactory.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
if (mCertificateSocketFactory == null) {
return new String[]{};
}
return mCertificateSocketFactory.getSupportedCipherSuites();
}
@Override
public Socket createSocket(final Socket socketParam, final String host, final int port, final boolean autoClose) throws IOException {
if (mCertificateSocketFactory == null) {
throw new SocketException("SSLSocketFactory was null. Unable to create socket.");
}
// There is a bug in Android before version 6.0 where SNI does not work, so we try to do
// it manually here.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
// Don't use the original socket and create a new one. This closes the original socket
// if the autoClose flag is set.
if (autoClose && socketParam != null) {
socketParam.close();
}
final Socket socket = mCertificateSocketFactory.createSocket(
InetAddressUtils.getInetAddressByName(host), port);
enableTlsIfAvailable(socket);
doManualServerNameIdentification(socket, host);
return socket;
}
final Socket socket = mCertificateSocketFactory.createSocket(socketParam, host, port,
autoClose);
enableTlsIfAvailable(socket);
return socket;
}
/**
* Some versions of Android fail to do server name identification (SNI) even though they are
* able to. This method forces SNI to happen, if possible. SNI is only used in https
* connections, and this method will no-op for http connections. This method throws an
* SSLHandshakeException if SNI fails. This method may also throw other socket-related
* IOExceptions.
*
* @param socket The socket to do SNI on
* @param host The host to verify the server name
* @throws IOException
*/
private void doManualServerNameIdentification(@NonNull final Socket socket,
@Nullable final String host) throws IOException {
Preconditions.checkNotNull(socket);
if (mCertificateSocketFactory == null) {
throw new SocketException("SSLSocketFactory was null. Unable to create socket.");
}
if (socket instanceof SSLSocket) {
final SSLSocket sslSocket = (SSLSocket) socket;
setHostnameOnSocket((SSLCertificateSocketFactory) mCertificateSocketFactory, sslSocket,
host);
verifyServerName(sslSocket, host);
}
}
/**
* Calling setHostname on a socket turns on the server name identification feature.
* Unfortunately, this was introduced in Android version 17, so we do what we can.
*/
static void setHostnameOnSocket(@NonNull final SSLCertificateSocketFactory certificateSocketFactory,
@NonNull final SSLSocket sslSocket, @Nullable final String host) {
Preconditions.checkNotNull(certificateSocketFactory);
Preconditions.checkNotNull(sslSocket);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
certificateSocketFactory.setHostname(sslSocket, host);
} else {
try {
new Reflection.MethodBuilder(sslSocket, "setHostname")
.addParam(String.class, host)
.execute();
} catch (Exception e) {
}
}
}
static void verifyServerName(@NonNull final SSLSocket sslSocket,
@Nullable final String host) throws IOException {
Preconditions.checkNotNull(sslSocket);
sslSocket.startHandshake();
final HostnameVerifier hostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
if (!hostnameVerifier.verify(host, sslSocket.getSession())) {
throw new SSLHandshakeException("Server Name Identification failed.");
}
}
private void enableTlsIfAvailable(@Nullable Socket socket) {
if (socket instanceof SSLSocket) {
SSLSocket sslSocket = (SSLSocket) socket;
String[] supportedProtocols = sslSocket.getSupportedProtocols();
// Make sure all supported protocols are enabled. Android does not enable TLSv1.1 or
// TLSv1.2 by default.
sslSocket.setEnabledProtocols(supportedProtocols);
}
}
public static SSLSocketFactory create()
{
InputStream stream = MwmApplication.get().getResources().openRawResource(R.raw.my_cret);
String type = KeyStore.getDefaultType();
try
{
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(stream, "123".toCharArray());
String algorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(algorithm);
trustManagerFactory.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
return sslContext.getSocketFactory();
}
catch (KeyStoreException e)
{
e.printStackTrace();
}
catch (CertificateException e)
{
e.printStackTrace();
}
catch (NoSuchAlgorithmException e)
{
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
catch (KeyManagementException e)
{
e.printStackTrace();
}
throw new RuntimeException("e");
}
public static SSLSocketFactory createFactory()
{
InputStream caInput = null;
try {
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(MwmApplication.get().getResources().openRawResource(R.raw.cert), "".toCharArray());
return new AdditionalKeyStoresSSLSocketFactory(keystore);
} catch (Exception ex) {
throw new RuntimeException(ex);
} finally {
if (caInput != null) {
try {
caInput.close();
} catch (IOException ignored) {
}
}
}
}
public static SSLSocketFactory create(Context context, @RawRes int caRawFile) {
InputStream caInput = null;
try {
/*/* InputStream stream = MwmApplication.get().getResources().openRawResource(R.raw.cert);
String type = KeyStore.getDefaultType();
try
{
KeyStore keyStore = KeyStore.getInstance(type);
keyStore.load(stream, null);
String algorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(algorithm);
trustManagerFactory.init(keyStore);
}*/
// Create an SSL context that uses the created trust manager
KeyStore ksClient = KeyStore.getInstance("PKCS12");
ksClient.load(MwmApplication.get().getResources().openRawResource(R.raw.my_cret), "123".toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
kmf.init(ksClient, "123".toCharArray());
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
return sslContext.getSocketFactory();
} catch (Exception ex) {
throw new RuntimeException(ex);
} finally {
if (caInput != null) {
try {
caInput.close();
} catch (IOException ignored) {
}
}
}
}
}