OKhttp: SSL协议异常:SSL握手终止

5

我正在努力弄清楚为什么有时会出现这个错误

javax.net.ssl.SSLProtocolException: SSL握手终止: ssl=0x60d46c90:SSL库中的故障,通常是协议错误 错误:1409443E:SSL例程:SSL3_READ_BYTES:tlsv1警报不适当 回退(external/openssl/ssl/s3_pkt.c:1256 0x60d57f40:0x00000003)

enter image description here

发起此请求的代码:

private <T> void send(final String url, final Map<String, String> args, 
    final RequestCallback<T> callback, final Parser<T> pParser, 
    final Context pContext, final HTTP_METHOD method, 
    final Map<String, String> headers, final BODY_TYPE bodyType) {

    final Request.Builder builder = getBuilder(url);
    populateHeaders(builder, headers);
    final RequestBody body = getBody(args, bodyType);

    if(method==HTTP_METHOD.POST) {
        builder.post(body);
    } else if(method==HTTP_METHOD.GET) {
        builder.get();
    } else if(method==HTTP_METHOD.PUT) {
        builder.put(body);
    } else if(method==HTTP_METHOD.PATCH) {
        builder.patch(body);
    } else {
        throw new RuntimeException("Please specify correct method name!");
    }

    Request request = builder.build();
    debugRequest(request, whoCalledThisMethod());
    call = client.newCall(request);
    call.enqueue(new StandardRequestCallback(pContext, callback, pParser));
}

我发现如果我的应用程序出现这个异常,强制关闭它,然后重新启动它,可以成功完成请求... 但是,不久之后,它又会出现SSL异常。

可能出了什么问题?


更新

我制作了一个非常小的测试应用程序,使用了来自okHttp配方页面https://github.com/square/okhttp/wiki/Recipes的代码,并且我已经重现了SSL握手异常!

06-27 13:59:08.068 30006-32692/a.b.ssltestapp W/System.err: javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake terminated: ssl=0x60068d10: Failure in SSL library, usually a protocol error
06-27 13:59:08.068 30006-32692/a.b.ssltestapp W/System.err: error:1409443E:SSL routines:SSL3_READ_BYTES:tlsv1 alert inappropriate fallback (external/openssl/ssl/s3_pkt.c:1256 0x6006e6b0:0x00000003)
06-27 13:59:08.078 30006-32692/a.b.ssltestapp W/System.err:     at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:568)
06-27 13:59:08.078 30006-32692/a.b.ssltestapp W/System.err:     at okhttp3.internal.io.RealConnection.connectTls(RealConnection.java:188)
06-27 13:59:08.078 30006-32692/a.b.ssltestapp W/System.err:     at okhttp3.internal.io.RealConnection.connectSocket(RealConnection.java:145)
06-27 13:59:08.078 30006-32692/a.b.ssltestapp W/System.err:     at okhttp3.internal.io.RealConnection.connect(RealConnection.java:108)
06-27 13:59:08.078 30006-32692/a.b.ssltestapp W/System.err:     at okhttp3.internal.http.StreamAllocation.findConnection(StreamAllocation.java:188)
06-27 13:59:08.088 30006-32692/a.b.ssltestapp W/System.err:     at okhttp3.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:127)
06-27 13:59:08.088 30006-32692/a.b.ssltestapp W/System.err:     at okhttp3.internal.http.StreamAllocation.newStream(StreamAllocation.java:97)
06-27 13:59:08.088 30006-32692/a.b.ssltestapp W/System.err:     at okhttp3.internal.http.HttpEngine.connect(HttpEngine.java:289)
06-27 13:59:08.088 30006-32692/a.b.ssltestapp W/System.err:     at okhttp3.internal.http.HttpEngine.sendRequest(HttpEngine.java:241)
06-27 13:59:08.088 30006-32692/a.b.ssltestapp W/System.err:     at okhttp3.RealCall.getResponse(RealCall.java:240)
06-27 13:59:08.088 30006-32692/a.b.ssltestapp W/System.err:     at okhttp3.RealCall$ApplicationInterceptorChain.proceed(RealCall.java:198)
06-27 13:59:08.088 30006-32692/a.b.ssltestapp W/System.err:     at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:160)
06-27 13:59:08.098 30006-32692/a.b.ssltestapp W/System.err:     at okhttp3.RealCall.access$100(RealCall.java:30)
06-27 13:59:08.098 30006-32692/a.b.ssltestapp W/System.err:     at okhttp3.RealCall$AsyncCall.execute(RealCall.java:127)
06-27 13:59:08.098 30006-32692/a.b.ssltestapp W/System.err:     at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:33)
06-27 13:59:08.098 30006-32692/a.b.ssltestapp W/System.err:     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
06-27 13:59:08.098 30006-32692/a.b.ssltestapp W/System.err:     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
06-27 13:59:08.098 30006-32692/a.b.ssltestapp W/System.err:     at java.lang.Thread.run(Thread.java:841)
06-27 13:59:08.098 30006-32692/a.b.ssltestapp W/System.err:     Suppressed: javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake terminated: ssl=0x60068d10: Failure in SSL library, usually a protocol error
06-27 13:59:08.098 30006-32692/a.b.ssltestapp W/System.err: error:14094410:SSL routines:SSL3_READ_BYTES:sslv3 alert handshake failure (external/openssl/ssl/s3_pkt.c:1256 0x5ff9aa20:0x00000003)
06-27 13:59:08.108 30006-32692/a.b.ssltestapp W/System.err:         ... 18 more
06-27 13:59:08.108 30006-32692/a.b.ssltestapp W/System.err:     Caused by: javax.net.ssl.SSLProtocolException: SSL handshake terminated: ssl=0x60068d10: Failure in SSL library, usually a protocol error
06-27 13:59:08.108 30006-32692/a.b.ssltestapp W/System.err: error:14094410:SSL routines:SSL3_READ_BYTES:sslv3 alert handshake failure (external/openssl/ssl/s3_pkt.c:1256 0x5ff9aa20:0x00000003)
06-27 13:59:08.118 30006-32692/a.b.ssltestapp W/System.err:     at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
06-27 13:59:08.118 30006-32692/a.b.ssltestapp W/System.err:     at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:486)
06-27 13:59:08.118 30006-32692/a.b.ssltestapp W/System.err:         ... 17 more
06-27 13:59:08.118 30006-32692/a.b.ssltestapp W/System.err: Caused by: javax.net.ssl.SSLProtocolException: SSL handshake terminated: ssl=0x60068d10: Failure in SSL library, usually a protocol error
06-27 13:59:08.118 30006-32692/a.b.ssltestapp W/System.err: error:1409443E:SSL routines:SSL3_READ_BYTES:tlsv1 alert inappropriate fallback (external/openssl/ssl/s3_pkt.c:1256 0x6006e6b0:0x00000003)
06-27 13:59:08.118 30006-32692/a.b.ssltestapp W/System.err:     at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
06-27 13:59:08.118 30006-32692/a.b.ssltestapp W/System.err:     at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:486)
06-27 13:59:08.118 30006-32692/a.b.ssltestapp W/System.err:     ... 17 more

注意:ANDROID 4.4.4 - Someone Somewhere
这是OkHttp的已知错误:https://github.com/square/okhttp/issues/1940 - Alex
在我看来,也许是你的网络连接有时不太好,导致了这个问题。此外,你尝试过其他的Android API吗?这个问题只发生在模拟器上还是真实设备上或者两者都有? - BNK
1
不,网络连接还可以...但可能会更好。然而,这个问题在多个位置都出现了。这个问题可以在设备上重现(我不使用模拟器)。 - Someone Somewhere
2个回答

2

这不是一个解决方案,但如果与上面提到的问题有关,则提供一些网络调试。

如果与上面 issue 1940 中提到的网络接口有问题,则可以通过实现Dns接口和socketFactory来解决该问题。你可能会得到两个DNS记录,其中一个适用于你,另一个不适用,并且在重新启动后会更改。

https://github.com/yschimke/oksocial/blob/master/src/main/java/com/baulsupp/oksocial/network/DnsSelector.java

https://github.com/yschimke/oksocial/blob/master/src/main/java/com/baulsupp/oksocial/network/InterfaceSocketFactory.java

尝试通过查看以下结果来确认:

Dns.SYSTEM.lookup(hostname)

NetworkInterface.getByInetAddress(address)

NetworkInterface.getNetworkInterfaces()


更新

在Callback.OnFailure中调试此问题的代码:

@Override
public void onFailure(final Call call, final IOException e) {
    if (call.isCanceled()) {
        SLog.w(TAG, "onFailure: Canceled");
        return;
    }

    if(e instanceof SSLException){

        try {
            Log.d(getClass().getSimpleName(), "Checking DNS hosts ... ");
            List<InetAddress> list = Dns.SYSTEM.lookup(call.request().url().host());
            Log.d(getClass().getSimpleName(), "Number of Entries: "+list.size());
            for (InetAddress address : list) {
                Log.d(getClass().getSimpleName(), address.toString());
            }
            Log.d(getClass().getSimpleName(), "End of Checking DNS hosts");

        } catch (UnknownHostException e1) {
            e1.printStackTrace();
        }


        try {
            Log.d(getClass().getSimpleName(), "Checking Network Interfaces ... ");
            Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
            ArrayList<NetworkInterface> list = Collections.list(interfaces);
            Log.d(getClass().getSimpleName(), "Number of Entries: "+list.size());
            for(NetworkInterface netintface : list) {
                Log.d(getClass().getSimpleName(), netintface.toString());
                List<InterfaceAddress> listaddresses = netintface.getInterfaceAddresses();
                Log.d(getClass().getSimpleName(), "   Number of Interface Addresses: "+listaddresses.size());
                for (InterfaceAddress infcaddress : listaddresses) {
                    Log.d(getClass().getSimpleName(), "   > InterfaceAddress : "+infcaddress.toString());
                }
            }
            Log.d(getClass().getSimpleName(), "End of Checking Network Interfaces");
        } catch (SocketException e1) {
            e1.printStackTrace();
        }


    }
}

上述代码的调试输出

06-21 11:43:19.495 12890-12890/com.app.stg D/[StandardRequest]: ->
                                                                   ------------- Request -------------
                                                                   ********** METHOD sendRequest() **********
                                                                   GET
                                                                   -----------------------------------
06-21 11:43:24.330 12890-21401/com.app.stg D/StandardRequestCallback: Checking DNS hosts ... 
06-21 11:43:24.450 12890-21401/com.app.stg D/StandardRequestCallback: Number of Entries: 8
06-21 11:43:24.450 12890-21401/com.app.stg D/StandardRequestCallback: arc.stg.issa.sainsburys.co.uk/52.85.69.145
06-21 11:43:24.450 12890-21401/com.app.stg D/StandardRequestCallback: arc.stg.issa.sainsburys.co.uk/52.85.69.20
06-21 11:43:24.450 12890-21401/com.app.stg D/StandardRequestCallback: arc.stg.issa.sainsburys.co.uk/52.85.69.185
06-21 11:43:24.450 12890-21401/com.app.stg D/StandardRequestCallback: arc.stg.issa.sainsburys.co.uk/52.85.69.84
06-21 11:43:24.450 12890-21401/com.app.stg D/StandardRequestCallback: arc.stg.issa.sainsburys.co.uk/52.85.69.249
06-21 11:43:24.450 12890-21401/com.app.stg D/StandardRequestCallback: arc.stg.issa.sainsburys.co.uk/52.85.69.52
06-21 11:43:24.450 12890-21401/com.app.stg D/StandardRequestCallback: arc.stg.issa.sainsburys.co.uk/52.85.69.159
06-21 11:43:24.450 12890-21401/com.app.stg D/StandardRequestCallback: arc.stg.issa.sainsburys.co.uk/52.85.69.246
06-21 11:43:24.460 12890-21401/com.app.stg D/StandardRequestCallback: End of Checking DNS hosts
06-21 11:43:24.460 12890-21401/com.app.stg D/StandardRequestCallback: Checking Network Interfaces ... 
06-21 11:43:24.480 12890-21401/com.app.stg D/StandardRequestCallback: Number of Entries: 4
06-21 11:43:24.480 12890-21401/com.app.stg D/StandardRequestCallback: [lo][1][/::1%1%1][/127.0.0.1]
06-21 11:43:24.480 12890-21401/com.app.stg D/StandardRequestCallback:    Number of Interface Addresses: 2
06-21 11:43:24.480 12890-21401/com.app.stg D/StandardRequestCallback:    > InterfaceAddress : /::1%1%1/128 [null]
06-21 11:43:24.480 12890-21401/com.app.stg D/StandardRequestCallback:    > InterfaceAddress : /127.0.0.1/8 [null]
06-21 11:43:24.480 12890-21401/com.app.stg D/StandardRequestCallback: [sit0][2]
06-21 11:43:24.480 12890-21401/com.app.stg D/StandardRequestCallback:    Number of Interface Addresses: 0
06-21 11:43:24.480 12890-21401/com.app.stg D/StandardRequestCallback: [wlan0][4][/fe80::4283:deff:fe94:b0ff%wlan0%4][/172.24.90.253]
06-21 11:43:24.480 12890-21401/com.app.stg D/StandardRequestCallback:    Number of Interface Addresses: 2
06-21 11:43:24.480 12890-21401/com.app.stg D/StandardRequestCallback:    > InterfaceAddress : /fe80::4283:deff:fe94:b0ff%wlan0%4/64 [null]
06-21 11:43:24.480 12890-21401/com.app.stg D/StandardRequestCallback:    > InterfaceAddress : /172.24.90.253/19 [/172.24.95.255]
06-21 11:43:24.480 12890-21401/com.app.stg D/StandardRequestCallback: [ip6tnl0][3]
06-21 11:43:24.480 12890-21401/com.app.stg D/StandardRequestCallback:    Number of Interface Addresses: 0
06-21 11:43:24.480 12890-21401/com.app.stg D/StandardRequestCallback: End of Checking Network Interfaces
06-21 11:43:24.480 12890-12890/com.app.stg D/JS-[LauncherActivity]: ErrorReport{type='SSLHandshakeException'}

嗨Yuri,感谢您的建议 - 我会实现这3个API调用,以便在SSL握手异常发生时为您提供结果。 - Someone Somewhere
请查看我在您的帖子中所做的编辑。可以看到,一旦捕获到SSL异常,所有数据都会被收集。看起来仍然有一个活动的WiFi连接。注意:此时,应用程序无法发送请求,直到重新启动。 - Someone Somewhere
我怀疑这可能是个死胡同。你得到了多条路线,所以只要没有禁用retryOnConnectionFailure,OkHttp应该能够尝试每一条。 你有一个IPv4和IPv6接口,也许其中一个对你来说是坏的,但我不知道为什么。 你可以尝试将其与事情正常时进行比较,但老实说,我不知道为什么它在失败后不会尝试依次每个路线并在不重启的情况下自行修复。对此我很抱歉。 - Yuri Schimke
我已经尝试了 builder.retryOnConnectionFailure(true);builder.followSslRedirects(true);,但没有任何区别。GET请求在应用启动时工作正常,然后几分钟后会因为SSL握手异常而失败。有可能追踪到握手异常的原因吗?我已经尝试了证书固定,但结果完全相同。 - Someone Somewhere
当GET请求成功时,我已经记录了网络信息,除了8个DNS主机的顺序不同之外,一切看起来都完全相同。 - Someone Somewhere
请阅读以下内容,看看是否有所帮助:https://github.com/square/okhttp/issues/1582这似乎与您收到的错误“tlsv1 alert inappropriate fallback”有关,但我在这方面已经超出了我的能力范围。 - Yuri Schimke

0
我找到了一个解决方法 - 基本上是创建一个新的 OkHttpClient 实例!请参见下面的代码...
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import java.io.IOException;

import javax.net.ssl.SSLException;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class ActivityMain extends AppCompatActivity
{
    private Button buttonStart;
    private Button buttonStop;
    private TextView textView;
    private Handler handler;

    private Integer mCount = 0;
    private Boolean mQuit;
    private OkHttpClient client = new OkHttpClient();
    private Runnable mRunnable;

    final int DELAY_SUBSEQUENT_REQUEST = 5000;//5sec
    final String REQUEST_URL = "https://whatever.com/url/to/test";



    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        handler = new Handler();

        buttonStart = (Button)findViewById(R.id.buttonstart);
        buttonStop = (Button)findViewById(R.id.buttonstop);
        textView = (TextView)findViewById(R.id.textview);

        mRunnable = new Runnable() {
            @Override
            public void run() {
                if (!mQuit) {
                    mCount++;
                    textView.setText(String.valueOf(mCount));

                    // do the GET call here
                    RequestGet();
                }
            }
        };

        buttonStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                buttonStart.setEnabled(false);
                triggerDelayedSslRequest(0);
                mQuit = false;
            }
        });

        buttonStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                buttonStart.setEnabled(true);
                mQuit = true;
                handler.removeCallbacks(mRunnable);
                mCount = 0;
            }
        });
    }

    private void triggerDelayedSslRequest(int delay)
    {
        handler.postDelayed(mRunnable, delay);
    }

    private void RequestGet()
    {
        Request request = new Request.Builder()
                .url(REQUEST_URL)
                .build();

        // NOTE: onFailure and onResponse are not on the main thread
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, final IOException e) {
                // the SSL error is trapped here
                e.printStackTrace();

                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        textView.setText(e.getMessage());
                        buttonStart.setEnabled(true);
                    }
                });

                // Crazy "fix" for SSLException
                if(e instanceof SSLException) {
                    client = new OkHttpClient();
                }
            }

            @Override
            public void onResponse(Call call, final Response response) throws IOException {
                if (!response.isSuccessful())
                {
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            textView.setText("Error Response: "+response.toString());
                        }
                    });
                } else {
                    System.out.println(response.body().string());
                }

                // schedule the next GET request
                triggerDelayedSslRequest(DELAY_SUBSEQUENT_REQUEST);
            }
        });
    }
}


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="a.b.ssltestapp.ActivityMain">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Start"
        android:id="@+id/buttonstart"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Stop"
        android:id="@+id/buttonstop"
        android:layout_below="@+id/buttonstart"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/textview"
        android:layout_marginTop="100dp"
        android:text="Press START to test SSL"
        android:gravity="center"
        android:layout_below="@+id/buttonstop"
        android:layout_centerHorizontal="true"/>


</RelativeLayout>

然而,这个修复方法非常不方便。

总结修复步骤:

  • 打开应用并让它保持运行大约20分钟(也许在这一点上关闭并重新打开Wi-Fi?我确定在工作中会这样)
  • 现在,在应用中点击“开始”按钮
  • 观察SSL握手异常(在这一点上会创建一个新的OkHttpClient实例)
  • 再次点击“开始”按钮
  • 观察到GET请求现在是成功的

为什么创建一个新的OkHttpClient对象会使SSL握手再次生效?


你有没有找到解决办法?我也遇到了类似的问题。 - Ljdawson
还没有,什么都没有 :( - Someone Somewhere

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接