3

我有一个 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自动放入日志的错误)

4

3 回答 3

9

如果我是对的,您必须创建一个证书,对其进行签名并将其包含在您的应用程序中。或者更改服务器配置(更多信息在这里)。

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

在你的项目中包含这个类

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 中找到了这段代码,但目前找不到链接....

于 2014-11-25T11:57:05.200 回答
2

正如 longilong 所提到的,信任所有证书并不是最好的方法。事实上,这在生产环境中是一种非常糟糕的方法,因为它基本上消除了 SSL 的影响,并使您容易受到中间人攻击

问题归结为POODLE SSL3 错误导致一些(大多数?)服务提供商禁用了他们的 SSLv3 支持。这对我们 Android 开发人员来说是个坏消息,因为 HttpConnection 在 Build.VERSION.SDK_INT >= 9 && Build.VERSION.SDK_INT <= 20 中默认为 SSLv3。

Android 问题跟踪器中有一个包含更多信息的问题,解决方案包括提供一个自定义 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());
}
于 2014-12-17T10:38:14.993 回答
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=California/L=Mountain View/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 Public Primary Certification Authority

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

但是,将服务器配置为不包含必要的中间 CA 的情况并不少见。例如,下面是一个服务器,它可能导致 Android 浏览器出错和 Android 应用程序异常:

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

证书链 0 s:/C=US/ST=District Of Columbia/L=Washington/O=US Department of Homeland Security/OU=United States Citizenship and Immigration Services/OU=Terms of use at 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 International Server CA的使用条款- G3

值得注意的是,在大多数桌面浏览器中访问此服务器不会导致完全未知的 CA 或自签名服务器证书会导致的错误。这是因为大多数桌面浏览器会随着时间的推移缓存受信任的中间 CA。一旦浏览器从一个站点访问并了解了中间 CA,下次它就不需要将中间 CA 包含在证书链中。

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

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

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

于 2014-11-26T08:26:32.250 回答