Disable SSL as a protocol in HttpsURLConnection

后端 未结 6 1732
眼角桃花
眼角桃花 2020-12-02 15:14

Due to the POODLE vulnerability, my server, hosted in Amazon AWS does no longer support SSLv3.

As a result, the first HTTPS connection my Android app does against th

相关标签:
6条回答
  • 2020-12-02 15:26

    The above solution(s) didn't work for me, therefore this is what I learned and did to overcome this issue.

    For older devices than Android 5.0, the default security provider had those properties:

    1. TSLv1 and TSLv2 protocols were not enable by default
    2. SSLv3 protocol is not disable by default.

    A solution that worked for me here is to patch the "Provider" if needed when starting the app, so it will no longer have SSLv3 on it's list of protocols. A straightforward way to patch Android from your app is this: (considering you have access to Google Play Store services.)

    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.");
        }
    }
    

    Take a look at: https://developer.android.com/training/articles/security-gms-provider.html?#patching for more info.

    0 讨论(0)
  • 2020-12-02 15:31

    Aside from @GaRRaPeTa's response, please make that the makeSocketsafe method determines if the socket is not yet converted to NoSSLv3SSLSocket to prevent Stackoverflow issues:

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

    PS. Cannot comment so that it's on a separate post.

    0 讨论(0)
  • 2020-12-02 15:37

    I took @GaRRaPeTa's answer and bundled it into a dead simple method call. You can use the NetCipher library to get a modern TLS config when using Android's HttpsURLConnection. NetCipher configures the `HttpsURLConnection instance to use the best supported TLS version, removes SSLv3 support, and configures the best suite of ciphers for that TLS version. First, add it to your build.gradle:

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

    Or you can download the netcipher-1.2.jar and include it directly in your app. Then instead of calling:

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

    Call this:

    HttpsURLConnection connection = NetCipher.getHttpsURLConnection(sourceUrl);
    
    0 讨论(0)
  • 2020-12-02 15:40

    I've recently tested this using SSLContext (as I needed access to Trustmanager) instead of implementing my own NoSSLv3Factory and so far I haven't had any problems.

    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;
    }
    

    You could then use this in your HttpsURLConnection object like so:

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

    This does mean that you'll have to stay on top of any TLS vulnerabilities though and modify the specified cipher if any SSL/TLS vulnerabilities are publicly disclosed.

    A list of supported ciphers and providers you can use are listed here

    The first code block, minor the key change for this scenario, was primarily taken from this SO answer

    0 讨论(0)
  • 2020-12-02 15:41

    I think I have solved this. The fundamental idea is the same than in the code in the question (avoid SSLv3 as the only protocol available), but the code performing it is different:

    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);
                }
            }
    
        }
    

    and somewhere in your code, before creating the Connection:

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

    This code is taken from https://code.google.com/p/android/issues/detail?id=78187, where you can find a fully explanation on why this is happening in Android 4.X.

    I've had this in production from one week and seems to have done the trick.

    0 讨论(0)
  • 2020-12-02 15:48

    Also you should know that you can force TLS v1.2 for Android 4.0 devices that don't have it enabled by default:

    This should be in the first line of your Application:

     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 讨论(0)
提交回复
热议问题