forked from organicmaps/organicmaps
[android] Supported client cert
This commit is contained in:
parent
6975de3334
commit
1305da5b91
4 changed files with 87 additions and 367 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue