【问题标题】:Universal-Image-Loader | SSLHandshakeException: Handshake failed通用图像加载器 | SSLHandshakeException:握手失败
【发布时间】:2015-01-13 19:14:53
【问题描述】:

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

【问题讨论】:

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


    【解决方案1】:

    如果我是对的,您必须创建一个证书,对其进行签名并将其包含在您的应用中。或者更改服务器配置 (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 中调用“nuke”函数

    public class YOURApplication extends Application {
    
    @Override
    public void onCreate() {
        super.onCreate();
    
        //...
    
        // trust all SSL -> HTTPS connection
        SSLCertificateHandler.nuke();
    }
    

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

    【讨论】:

    • 我知道这在开发过程中很有用。但是,一旦应用程序上线,就不要考虑接受所有证书。这违背了使用 HTTPS 的全部目的,您不妨改用 HTTP。更多信息here(但忽略this答案)
    • 可以在 Java 中包含条件,因此 SSLCertificateHandler.nuke() 仅在开发版本中调用 - 请参阅 stackoverflow.com/a/23844716/1617737
    【解决方案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 秒:/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 Public Primary Certification Authority

    ​​>

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

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

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

    证书链 0 s:/C=US/ST=哥伦比亚特区/L=Washington/O=U.S.国土安全部/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 International Server CA - G3

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

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

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

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

    【讨论】:

      【解决方案3】:

      正如 longilong 所提到的,信任所有证书并不是最好的方法。事实上,在生产环境中这是一种非常糟糕的方法,因为它基本上抵消了 SSL 的影响,并使您对Man-in-the-middle attacks 感到尊敬。

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

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

      【讨论】:

      • 这对我帮助很大。我希望它也能帮助其他人。谢谢
      猜你喜欢
      • 2016-10-02
      • 1970-01-01
      • 2017-10-17
      • 2017-12-30
      • 1970-01-01
      • 2018-10-03
      • 2014-08-31
      • 2012-12-03
      • 2013-06-15
      相关资源
      最近更新 更多