通用图像加载器| SSLHandshakeException:握手失败

时间:2014-11-16 23:06:37

标签: android imageview universal-image-loader image-loading sslhandshakeexception

我有一个ListView,其中包含一些内容(TextViews,ImageView ...)。我使用Nostra的UIL来加载项目中的图像,但其中一些无法加载。当我打电话给Log.v(String.valueOf(failReason.getCause());时,这就是我得到的:

11-16 23:52:20.447: V/javax.net.ssl.SSLHandshakeException: Handshake failed(17467): failz
11-16 23:52:20.657: V/NativeCrypto(17467): SSL handshake aborted: ssl=0x15fd758: Failure in SSL library, usually a protocol error
11-16 23:52:20.657: V/NativeCrypto(17467): error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure (external/openssl/ssl/s23_clnt.c:762 0x4c2ed485:0x00000000)
11-16 23:52:21.207: V/NativeCrypto(17467): SSL handshake aborted: ssl=0x1562468: Failure in SSL library, usually a protocol error
11-16 23:52:21.207: V/NativeCrypto(17467): error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure (external/openssl/ssl/s23_clnt.c:762 0x4c2ed485:0x00000000)

你知道吗,为什么会出现这个问题或者我该如何解决?

这是一个没有加载的示例图像:

http://bigparty.cz/photos/headlinefoto/13.jpg

(我可以附加一个包含整个错误的日志 - UIL 自动放入日志的错误)

3 个答案:

答案 0 :(得分:9)

如果我是对的,您必须创建证书,签名并将其包含在您的应用中。或者更改服务器配置(further information here)。

否则,您可以信任您的应用中的每次握手。这不是最好的方法,但在实施过程中非常有用。

在项目中包含此课程

public class SSLCertificateHandler {

protected static final String TAG = "NukeSSLCerts";

/**
 * Enables https connections
 */
public static void nuke() {
    try {
        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
            public X509Certificate[] getAcceptedIssuers() {
                X509Certificate[] myTrustedAnchors = new X509Certificate[0];
                return myTrustedAnchors;
            }

            @Override
            public void checkClientTrusted(X509Certificate[] certs, String authType) {
            }

            @Override
            public void checkServerTrusted(X509Certificate[] certs, String authType) {
            }
        } };

        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, new SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String arg0, SSLSession arg1) {
                return true;
            }
        });
    } catch (Exception e) {
    }
}

}

扩展您的Application,并致电“核心”'功能在onCreate

public class YOURApplication extends Application {

@Override
public void onCreate() {
    super.onCreate();

    //...

    // trust all SSL -> HTTPS connection
    SSLCertificateHandler.nuke();
}

我在SO中找到了这段代码,但目前无法找到该链接....

答案 1 :(得分:2)

您可能在服务器端缺少中间CA证书。

试试这个

大多数公共CA都不直接签署服务器证书。相反,他们使用他们的主CA证书(称为根CA)来签署中间CA.他们这样做是为了使根CA可以离线存储以降低危害的风险。但是,像Android这样的操作系统通常只直接信任根CA,这在由中间CA签名的服务器和知道根CA的证书验证者之间留下了很小的信任差距。要解决此问题,服务器在SSL握手期间不会向客户端发送它的证书,而是从服务器CA通过任何到达受信任根CA所需的中间件的证书链。

要查看实际情况,请参阅openssl s_client命令查看的mail.google.com证书链:

$ openssl s_client -connect mail.google.com:443

证书链  0 s:/ C = US / ST =加州/ L =山景/ O = Google Inc / CN = mail.google.com    i:/ C = ZA / O = Thawte Consulting(Pty)Ltd./CN=Thawte SGC CA.  1 s:/ C = ZA / O = Thawte Consulting(Pty)Ltd./CN=Thawte SGC CA

i:/ C = US / O = VeriSign,Inc。/ OU = Class 3公共主要证书颁发机构

这表明服务器发送由Thawte SGC CA(一个中间CA)发布的mail.google.com证书,以及由Verisign CA颁发的Thawte SGC CA的第二个证书,该证书是主要CA.这是受到Android信任的。

但是,将服务器配置为不包含必要的中间CA并不罕见。例如,这是一个服务器,可能会导致Android浏览器中的错误和Android应用中的异常:

$ openssl s_client -connect egov.uscis.gov:443

证书链  0 s:/ C = US / ST =哥伦比亚特区/ L =华盛顿/ O =美国。国土安全部/ OU =美国公民及移民服务局/ OU =使用条款:www.verisign.com/rpa(c)05 / CN = egov.uscis.gov

i:/ C = US / O = VeriSign,Inc. / OU = VeriSign Trust Network / OU =使用条款https://www.verisign.com/rpa(c)10 / CN = VeriSign Class 3国际服务器CA - G3 < / H2>

这里有趣的是,在大多数桌面浏览器中访问此服务器不会导致像完全未知的CA或自签名服务器证书那样的错误。这是因为大多数桌面浏览器会随着时间的推移缓存可信中间CA.一旦浏览器访问并从一个站点了解到中间CA,下次就不需要在证书链中包含中间CA.

有些网站会故意为用于提供资源的辅助Web服务器执行此操作。例如,他们的主HTML页面可能由具有完整证书链的服务器提供服务,但是服务器用于资源(如图像,CSS或JavaScript)不包括CA,可能是为了节省带宽。不幸的是,有时这些服务器可能会提供您尝试从Android应用程序调用的Web服务,这不是宽容的。

有两种方法可以解决这个问题:

配置服务器以在服务器链中包含中间CA.大多数CA提供有关如何为所有常见Web服务器执行此操作的文档。如果您需要网站至少通过Android 4.2使用默认Android浏览器,这是唯一的方法。 或者,将中间CA视为任何其他未知CA,并创建一个TrustManager直接信任它,如前两节所述。

答案 2 :(得分:2)

正如龙龙所说,所有证书都不是最好的方法。实际上,它在生产环境中是一种非常糟糕的方法,因为它基本上使SSL的效果无效并使您对Man-in-the-middle attacks感到尊敬。

问题归结为POODLE SSL3 bug,这使得一些(大多数?)服务提供商禁用了他们的SSLv3支持。这对Android开发人员来说是个坏消息,因为HttpConnection默认为Build.VERSION.SDK_INT&gt; = 9&amp;&amp ;;中的SSLv3。 Build.VERSION.SDK_INT&lt; = 20。

Android issue tracker中存在一个问题,其中包含更多信息,解决方案包括提供自定义SSLFactory,拒绝将SSLv3设置为唯一的回退协议,以下是该问题的代码。我不赞成这个解决方案,但它是我们目前在Lollipop之前所有版本使用的。

/**
 * 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");
        } else {
            LogManager.getLogger().w("SSL stuck with protocol available for " + String.valueOf(enabledProtocols));
        }
        protocols = enabledProtocols.toArray(new String[enabledProtocols.size()]);
    }
    super.setEnabledProtocols(protocols);
}
}


/**
 * {@link javax.net.ssl.SSLSocketFactory} that doesn't allow {@code SSLv3} only connections
 */
private static class NoSSLv3Factory extends SSLSocketFactory {
private final SSLSocketFactory delegate;

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

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