web-dev-qa-db-ja.com

HttpsURLConnectionのプロトコルとしてSSLを無効にする

[〜#〜] poodle [〜#〜] 脆弱性のため、Amazon AWSでホストされている私のサーバーはSSLv3をサポートしなくなりました。

結果として、最初のHTTPS接続my Android appはサーバーに対して行うので、接続の確立中にエラーが発生します。

Error reading server response: javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x77d8ab68: Failure in SSL library, usually a protocol error
error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure (external/openssl/ssl/s23_clnt.c:741 0x7339ad74:0x00000000)
       [....]
Caused by: javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x77d8ab68: Failure in SSL library, usually a protocol error
error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure (external/openssl/ssl/s23_clnt.c:741 0x7339ad74:0x00000000)
       at com.Android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.Java:448)
       at com.Android.okhttp.Connection.upgradeToTls(Connection.Java:146)
       at com.Android.okhttp.Connection.connect(Connection.Java:107)
       at com.Android.okhttp.internal.http.HttpEngine.connect(HttpEngine.Java:294)
       at com.Android.okhttp.internal.http.HttpEngine.sendSocketRequest(HttpEngine.Java:255)
       at com.Android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.Java:206)

エラーは最初のリクエストでのみ発生します。後続のリクエストはしばらくの間機能します。

これを修正するために、Androidクライアントによって受け入れられるプロトコルのリストからSSLを削除し、TLSのみを使用するようにします。これを行うには、カスタムSSLSocketFactoryを設定します。有効なプロトコルとサポートされている暗号スイートのリストからSSLを削除します。

/**
 * SSLSocketFactory that wraps one existing SSLSocketFactory and delegetes into it adding
 * a new cipher suite
 */
public class TLSOnlySocketFactory extends SSLSocketFactory {

    private final SSLSocketFactory delegate;

    public TLSOnlySocketFactory(SSLSocketFactory delegate) {
        this.delegate = delegate;
    }

    @Override
    public String[] getDefaultCipherSuites() {

        return getPreferredDefaultCipherSuites(this.delegate);
    }

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



    @Override
    public Socket createSocket(Socket s, String Host, int port, boolean autoClose) throws IOException {
        final Socket socket = this.delegate.createSocket(s, Host, port, autoClose);

        ((SSLSocket)socket).setEnabledCipherSuites(getPreferredDefaultCipherSuites(delegate));
        ((SSLSocket)socket).setEnabledProtocols(getEnabledProtocols((SSLSocket)socket));

        return socket;
    }



   [.....]

        ((SSLSocket)socket).setEnabledCipherSuites(getPreferredDefaultCipherSuites(delegate));
        ((SSLSocket) socket).setEnabledProtocols(getEnabledProtocols((SSLSocket)socket));

        return socket;
    }

    @Override
    public Socket createSocket(InetAddress Host, int port) throws IOException {
        final Socket socket = this.delegate.createSocket(Host, port);

        ((SSLSocket)socket).setEnabledCipherSuites(getPreferredDefaultCipherSuites(delegate));
        ((SSLSocket) socket).setEnabledProtocols(getEnabledProtocols((SSLSocket)socket));

        return socket;
    }

    @Override
    public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
        final Socket socket = this.delegate.createSocket(address, port, localAddress, localPort);

        ((SSLSocket)socket).setEnabledCipherSuites(getPreferredDefaultCipherSuites(delegate));
        ((SSLSocket) socket).setEnabledProtocols(getEnabledProtocols((SSLSocket)socket));

        return socket;
    }

    private String[] getPreferredDefaultCipherSuites(SSLSocketFactory sslSocketFactory) {
        return getCipherSuites(sslSocketFactory.getDefaultCipherSuites());
    }

    private String[] getPreferredSupportedCipherSuites(SSLSocketFactory sslSocketFactory) {
        return getCipherSuites(sslSocketFactory.getSupportedCipherSuites());
    }

    private String[] getCipherSuites(String[] cipherSuites) {
        final ArrayList<String> suitesList = new ArrayList<String>(Arrays.asList(cipherSuites));
        final Iterator<String> iterator = suitesList.iterator();
        while (iterator.hasNext()) {
            final String cipherSuite = iterator.next();
            if (cipherSuite.contains("SSL")) {
                iterator.remove();
            }
        }
        return suitesList.toArray(new String[suitesList.size()]);
    }

    private String[] getEnabledProtocols(SSLSocket socket) {
        final ArrayList<String> protocolList = new ArrayList<String>(Arrays.asList(socket.getSupportedProtocols()));
        final Iterator<String> iterator = protocolList.iterator();
        while (iterator.hasNext()) {
            final String protocl = iterator.next();
            if (protocl.contains("SSL")) {
                iterator.remove();
            }
        }
        return protocolList.toArray(new String[protocolList.size()]);
     }

}

ご覧のとおり、私のSSLSocketFactoryは別のSSLSocketFactoryに委任しますが、それは単に有効なプロトコルのリストからSSLを削除するだけです。

私はこの工場を

final TLSOnlySocketFactory tlsOnlySocketFactory = new TLSOnlySocketFactory(HttpsURLConnection.getDefaultSSLSocketFactory());
HttpsURLConnection.setDefaultSSLSocketFactory(tlsOnlySocketFactory);

これは問題を解決しません。接続が確立されたときに、時々エラーが表示されることがあります。奇妙なことに、これで修正されるわけではありませんが、明らかに問題の発生を最小限に抑えます。

AndroidクライアントでTLSのみを使用するようにHttpsUrlConnectionを強制するにはどうすればよいですか?

ありがとうございました。

16
GaRRaPeTa

私はこれを解決したと思います。基本的な考え方は質問のコードと同じです(使用可能な唯一のプロトコルとしてSSLv3を避けてください)が、それを実行するコードは異なります。

import Java.io.IOException;
import Java.io.InputStream;
import Java.io.OutputStream;
import Java.lang.reflect.InvocationTargetException;
import Java.lang.reflect.Method;
import Java.net.InetAddress;
import Java.net.Socket;
import Java.net.SocketAddress;
import Java.net.SocketException;
import Java.nio.channels.SocketChannel;
import Java.util.ArrayList;
import Java.util.Arrays;
import Java.util.List;

import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;

    /**
     * {@link javax.net.ssl.SSLSocketFactory} that doesn't allow {@code SSLv3} only connections
     * <p>fixes https://github.com/koush/ion/issues/386</p>
     *
     * <p> see https://code.google.com/p/Android/issues/detail?id=78187 </p>
     */
    public class NoSSLv3Factory extends SSLSocketFactory {
        private final SSLSocketFactory delegate;

        public NoSSLv3Factory() {
            this.delegate = HttpsURLConnection.getDefaultSSLSocketFactory();
        }

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

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

        private static Socket makeSocketSafe(Socket socket) {
            if (socket instanceof SSLSocket) {
                socket = new NoSSLv3SSLSocket((SSLSocket) socket);
            }
            return socket;
        }

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

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

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

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

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

        /**
         * Created by robUx4 on 25/10/2014.
         */
        private static class DelegateSSLSocket extends SSLSocket {

            protected final SSLSocket delegate;

            DelegateSSLSocket(SSLSocket delegate) {
                this.delegate = delegate;
            }

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

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

            @Override
            public void setEnabledCipherSuites(String[] suites) {
                delegate.setEnabledCipherSuites(suites);
            }

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

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

            @Override
            public void setEnabledProtocols(String[] protocols) {
                delegate.setEnabledProtocols(protocols);
            }

            @Override
            public SSLSession getSession() {
                return delegate.getSession();
            }

            @Override
            public void addHandshakeCompletedListener(HandshakeCompletedListener listener) {
                delegate.addHandshakeCompletedListener(listener);
            }

            @Override
            public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) {
                delegate.removeHandshakeCompletedListener(listener);
            }

            @Override
            public void startHandshake() throws IOException {
                delegate.startHandshake();
            }

            @Override
            public void setUseClientMode(boolean mode) {
                delegate.setUseClientMode(mode);
            }

            @Override
            public boolean getUseClientMode() {
                return delegate.getUseClientMode();
            }

            @Override
            public void setNeedClientAuth(boolean need) {
                delegate.setNeedClientAuth(need);
            }

            @Override
            public void setWantClientAuth(boolean want) {
                delegate.setWantClientAuth(want);
            }

            @Override
            public boolean getNeedClientAuth() {
                return delegate.getNeedClientAuth();
            }

            @Override
            public boolean getWantClientAuth() {
                return delegate.getWantClientAuth();
            }

            @Override
            public void setEnableSessionCreation(boolean flag) {
                delegate.setEnableSessionCreation(flag);
            }

            @Override
            public boolean getEnableSessionCreation() {
                return delegate.getEnableSessionCreation();
            }

            @Override
            public void bind(SocketAddress localAddr) throws IOException {
                delegate.bind(localAddr);
            }

            @Override
            public synchronized void close() throws IOException {
                delegate.close();
            }

            @Override
            public void connect(SocketAddress remoteAddr) throws IOException {
                delegate.connect(remoteAddr);
            }

            @Override
            public void connect(SocketAddress remoteAddr, int timeout) throws IOException {
                delegate.connect(remoteAddr, timeout);
            }

            @Override
            public SocketChannel getChannel() {
                return delegate.getChannel();
            }

            @Override
            public InetAddress getInetAddress() {
                return delegate.getInetAddress();
            }

            @Override
            public InputStream getInputStream() throws IOException {
                return delegate.getInputStream();
            }

            @Override
            public boolean getKeepAlive() throws SocketException {
                return delegate.getKeepAlive();
            }

            @Override
            public InetAddress getLocalAddress() {
                return delegate.getLocalAddress();
            }

            @Override
            public int getLocalPort() {
                return delegate.getLocalPort();
            }

            @Override
            public SocketAddress getLocalSocketAddress() {
                return delegate.getLocalSocketAddress();
            }

            @Override
            public boolean getOOBInline() throws SocketException {
                return delegate.getOOBInline();
            }

            @Override
            public OutputStream getOutputStream() throws IOException {
                return delegate.getOutputStream();
            }

            @Override
            public int getPort() {
                return delegate.getPort();
            }

            @Override
            public synchronized int getReceiveBufferSize() throws SocketException {
                return delegate.getReceiveBufferSize();
            }

            @Override
            public SocketAddress getRemoteSocketAddress() {
                return delegate.getRemoteSocketAddress();
            }

            @Override
            public boolean getReuseAddress() throws SocketException {
                return delegate.getReuseAddress();
            }

            @Override
            public synchronized int getSendBufferSize() throws SocketException {
                return delegate.getSendBufferSize();
            }

            @Override
            public int getSoLinger() throws SocketException {
                return delegate.getSoLinger();
            }

            @Override
            public synchronized int getSoTimeout() throws SocketException {
                return delegate.getSoTimeout();
            }

            @Override
            public boolean getTcpNoDelay() throws SocketException {
                return delegate.getTcpNoDelay();
            }

            @Override
            public int getTrafficClass() throws SocketException {
                return delegate.getTrafficClass();
            }

            @Override
            public boolean isBound() {
                return delegate.isBound();
            }

            @Override
            public boolean isClosed() {
                return delegate.isClosed();
            }

            @Override
            public boolean isConnected() {
                return delegate.isConnected();
            }

            @Override
            public boolean isInputShutdown() {
                return delegate.isInputShutdown();
            }

            @Override
            public boolean isOutputShutdown() {
                return delegate.isOutputShutdown();
            }

            @Override
            public void sendUrgentData(int value) throws IOException {
                delegate.sendUrgentData(value);
            }

            @Override
            public void setKeepAlive(boolean keepAlive) throws SocketException {
                delegate.setKeepAlive(keepAlive);
            }

            @Override
            public void setOOBInline(boolean oobinline) throws SocketException {
                delegate.setOOBInline(oobinline);
            }

            @Override
            public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
                delegate.setPerformancePreferences(connectionTime, latency, bandwidth);
            }

            @Override
            public synchronized void setReceiveBufferSize(int size) throws SocketException {
                delegate.setReceiveBufferSize(size);
            }

            @Override
            public void setReuseAddress(boolean reuse) throws SocketException {
                delegate.setReuseAddress(reuse);
            }

            @Override
            public synchronized void setSendBufferSize(int size) throws SocketException {
                delegate.setSendBufferSize(size);
            }

            @Override
            public void setSoLinger(boolean on, int timeout) throws SocketException {
                delegate.setSoLinger(on, timeout);
            }

            @Override
            public synchronized void setSoTimeout(int timeout) throws SocketException {
                delegate.setSoTimeout(timeout);
            }

            @Override
            public void setSSLParameters(SSLParameters p) {
                delegate.setSSLParameters(p);
            }

            @Override
            public void setTcpNoDelay(boolean on) throws SocketException {
                delegate.setTcpNoDelay(on);
            }

            @Override
            public void setTrafficClass(int value) throws SocketException {
                delegate.setTrafficClass(value);
            }

            @Override
            public void shutdownInput() throws IOException {
                delegate.shutdownInput();
            }

            @Override
            public void shutdownOutput() throws IOException {
                delegate.shutdownOutput();
            }

            @Override
            public String toString() {
                return delegate.toString();
            }

            @Override
            public boolean equals(Object o) {
                return delegate.equals(o);
            }
        }

        /**
         * An {@link javax.net.ssl.SSLSocket} that doesn't allow {@code SSLv3} only connections
         * <p>fixes https://github.com/koush/ion/issues/386</p>
         */
        private static class NoSSLv3SSLSocket extends DelegateSSLSocket {

            private NoSSLv3SSLSocket(SSLSocket delegate) {
                super(delegate);

                String canonicalName = delegate.getClass().getCanonicalName();
                if (!canonicalName.equals("org.Apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl")) {
                    // try replicate the code from HttpConnection.setupSecureSocket()
                    try {
                        Method msetUseSessionTickets = delegate.getClass().getMethod("setUseSessionTickets", boolean.class);
                        if (null != msetUseSessionTickets) {
                            msetUseSessionTickets.invoke(delegate, true);
                        }
                    } catch (NoSuchMethodException ignored) {
                    } catch (InvocationTargetException ignored) {
                    } catch (IllegalAccessException ignored) {
                    }
                }
            }

            @Override
            public void setEnabledProtocols(String[] protocols) {
                if (protocols != null && protocols.length == 1 && "SSLv3".equals(protocols[0])) {
                    // no way jose
                    // see issue https://code.google.com/p/Android/issues/detail?id=78187
                    List<String> enabledProtocols = new ArrayList<String>(Arrays.asList(delegate.getEnabledProtocols()));
                    if (enabledProtocols.size() > 1) {
                        enabledProtocols.remove("SSLv3");
                    }
                    protocols = enabledProtocols.toArray(new String[enabledProtocols.size()]);
                }
                super.setEnabledProtocols(protocols);
            }
        }

    }

connectionを作成する前に、コードのどこかに:

    static {
    HttpsURLConnection.setDefaultSSLSocketFactory(new NoSSLv3Factory());
}

このコードは https://code.google.com/p/Android/issues/detail?id=78187 から取得されています。このコードでは、Android 4.X.

私は1週間から本番環境でこれを使用しており、トリックを行ったようです。

18
GaRRaPeTa

@GaRRaPeTaの answer を取得し、デッドシンプルメソッド呼び出しにバンドルしました。 NetCipher ライブラリを使用して、AndroidのHttpsURLConnectionを使用するときに最新のTLS構成を取得できます。 NetCipherは、サポートされている最適なTLSバージョンを使用するように `HttpsURLConnectionインスタンスを構成し、SSLv3サポートを削除し、そのTLSバージョンに最適な暗号スイートを構成します。まず、build.gradleに追加します:

compile 'info.guardianproject.netcipher:netcipher:1.2'

または、netcipher-1.2.jarをダウンロードして、アプリに直接含めることができます。次に、呼び出す代わりに:

HttpURLConnection connection = (HttpURLConnection) sourceUrl.openConnection();

これを呼び出します:

HttpsURLConnection connection = NetCipher.getHttpsURLConnection(sourceUrl);

上記の解決策は私にとってはうまくいきませんでした。したがって、これは私がこの問題を解決するために学んだことです。

Android 5.0よりも古いデバイスの場合、デフォルトのセキュリティプロバイダーには次のプロパティがあります。

  1. TSLv1およびTSLv2プロトコルはデフォルトで有効になっていませんでした
  2. SSLv3プロトコルはデフォルトでは無効になっていません。

ここで私のために働いた解決策は、アプリの起動時に必要に応じて「プロバイダー」にパッチを適用することです。これにより、プロトコルのリストにSSLv3がなくなります。アプリからAndroidにパッチを適用する簡単な方法は次のとおりです。GooglePlayストアサービスにアクセスできると考えてください。)

private void updateAndroidSecurityProvider(Activity callingActivity) {
    try {
        ProviderInstaller.installIfNeeded(this);
    } catch (GooglePlayServicesRepairableException e) {
        // Thrown when Google Play Services is not installed, up-to-date, or enabled
        // Show dialog to allow users to install, update, or otherwise enable Google Play services.
        GooglePlayServicesUtil.getErrorDialog(e.getConnectionStatusCode(), callingActivity, 0);
    } catch (GooglePlayServicesNotAvailableException e) {
        Log.e("SecurityException", "Google Play Services not available.");
    }
}

詳細は https://developer.Android.com/training/articles/security-gms-provider.html?#patching をご覧ください。

3
Meriton Husaj

@GaRRaPeTaの応答は別として、makeSocketsafeメソッドは、Socketoverflowの問題を防ぐためにソケットがNoSSLv3SSLSocketにまだ変換されていないかどうかを判断するようにしてください。

    private static Socket makeSocketSafe(Socket socket) {
        if (socket instanceof SSLSocket && !(socket instanceof NoSSLv3SSLSocket)) {
            socket = new NoSSLv3SSLSocket((SSLSocket) socket);
        }
        return socket;
    }

PS。コメントできないため、別の投稿になります。

2
Pier Betos

また、Android 4.0がデフォルトで有効になっていない4.0デバイスに対してTLS v1.2を強制できることを知っておく必要があります。

これは、アプリケーションの最初の行にあります。

 try {
            ProviderInstaller.installIfNeeded(getApplicationContext());
            SSLContext sslContext;
            sslContext = SSLContext.getInstance("TLSv1.2");
            sslContext.init(null, null, null);
            sslContext.createSSLEngine();
        } catch (GooglePlayServicesRepairableException | GooglePlayServicesNotAvailableException
                | NoSuchAlgorithmException | KeyManagementException e) {
            e.printStackTrace();
        }
0
Mayur Gangurde

私は最近、独自のNoSSLv3Factoryを実装する代わりにSSLContextを使用して(Trustmanagerにアクセスする必要があるため)これをテストしましたが、これまでのところ何の問題もありません。

private getSSLContext()
{
    /* Load the keyStore that includes self-signed cert as a "trusted" entry. */
    KeyStore keyStore = ...  //optional
    TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); //optional
    tmf.init(keyStore); //optional

    //This is the important line, specifying the cipher to use and cipher provider
    SSLContext sslContext = SSLContext.getInstance("TLSv1","AndroidOpenSSL");
    ctx.init(null, tmf.getTrustManagers(), null); //if trustmanager not used pass null as the second parameter    
    return sslContext;
}

次に、これをHttpsURLConnectionオブジェクトで次のように使用できます。

...
URL url = new URL("https://yourwebapp.com/");
HttpsURLConnection webConnection = (HttpsURLConnection)url.openConnection();
webConnection.setSSLSocketFactory(getSSLContext())
...    

これは、TLSの脆弱性を常に把握し、SSL/TLSの脆弱性が公開されている場合は指定された暗号を変更する必要があることを意味します。

使用できるサポートされている暗号とプロバイダーのリスト ここにリストされています

このシナリオの主要な変更である最初のコードブロックは、主に this SO answer

0
skyjacks