【问题标题】:Custom SSLSocketFactory not uses a custom trustmanager自定义 SSLSocketFactory 不使用自定义信任管理器
【发布时间】:2015-03-09 06:48:11
【问题描述】:

在我们的应用程序中,当应用程序与服务器通信时,我对证书(公钥固定)(针对 MITMA)进行了额外检查。 为了与服务器通信,我使用 HttpClient。 另外我在生产中有一些代理服务器,所以我需要使用 SNI。在我们将应用程序发布到生产环境之前,我们会在另一个环境(TEST env)中对其进行检查。 对于 TEST 环境,我们只有自签名证书,因为它仅用于测试,我们不想为这种情况购买新证书。

为了实现它,我创建了自定义 SSLSocketFactory (org.apache.http.conn.ssl.SSLSocketFactory)。 但问题是它不适用于自签名证书。 我将自定义 trustManager (IgnoreCertificatesTrustManager) 设置为 sslSocketFactory (SSLCertificateSocketFactory)。 如果我使用以下代码:

SSLContext sslContext = SSLContext.getInstance(SSLSocketFactory.TLS);
sslContext.init(null, new TrustManager[] { new IgnoreCertificatesTrustManager() }, null);
sslSocket = (SSLSocket) sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);

自签名证书的检查有效(忽略 TrustManager (IgnoreCertificatesTrustManager) 中的检查)。但代码不支持 SNI 解决方案。 我做错了什么?

谢谢。

private class CustomSSLSocketFactory extends SSLSocketFactory {

        public CustomSSLSocketFactory() throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException, CertificateException {
            super(null);
        }

        // Plain TCP/IP (layer below TLS)

        @Override
        public Socket connectSocket(Socket s, String host, int port, InetAddress localAddress, int localPort, HttpParams params) throws IOException {
            return null;
        }

        @Override
        public Socket createSocket() throws IOException {
            return null;
        }

        @Override
        public boolean isSecure(Socket s) throws IllegalArgumentException {
            if (s instanceof SSLSocket) {
                return ((SSLSocket) s).isConnected();
            }
            return false;
        }

        @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
        @Override
        public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
            SSLSocket sslSocket = null;

//          if (isProduction()) {
                if (autoClose) {
                    // we don't need the plainSocket
                    socket.close();
                }

                // create and connect SSL socket, but don't do hostname/certificate verification yet
                SSLCertificateSocketFactory sslSocketFactory = (SSLCertificateSocketFactory) SSLCertificateSocketFactory.getDefault(0, null);


                // NOT works!
                sslSocketFactory.setTrustManagers(new TrustManager[] { new IgnoreCertificatesTrustManager() });
                // ----


                sslSocket = (SSLSocket) sslSocketFactory.createSocket(socket, host, port, autoClose);

                // enable TLSv1.1/1.2 if available (see https://github.com/rfc2822/davdroid/issues/229 )
                sslSocket.setEnabledProtocols(sslSocket.getSupportedProtocols());

                // set up SNI before the handshake
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                    logger.debug("Setting SNI hostname");
                    sslSocketFactory.setHostname(sslSocket, host);
                } else {
                    logger.debug("No documented SNI support on Android <4.2, trying with reflection");
                    try {
                        java.lang.reflect.Method setHostnameMethod = sslSocket.getClass().getMethod("setHostname", String.class);
                        setHostnameMethod.invoke(sslSocket, host);
                    } catch (Exception e) {
                        logger.error("SNI not useable", e);
                    }
                }

//          } else {
//              try {
//                  SSLContext sslContext = SSLContext.getInstance(SSLSocketFactory.TLS);
//                  sslContext.init(null, new TrustManager[] { new IgnoreCertificatesTrustManager() }, null);
//                  sslSocket = (SSLSocket) sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
//              } catch (java.security.NoSuchAlgorithmException e) {
//                  throw new IOException(e);
//              } catch (KeyManagementException e) {
//                  throw new IOException(e);
//              }
//          }

            // verify certificate
            SSLSession session = sslSocket.getSession();
            X509Certificate[] certificates = (X509Certificate[]) session.getPeerCertificates();

            if (!checkPublicKey(certificates)) {
                throw new IOException("SSL_HANDSHAKE_FAILED");
            }

            return sslSocket;
        }

    }

【问题讨论】:

  • 你不需要这些。只需使用您的信任经理初始化SSLContextisSecure().的奇怪定义isConnected()和它有什么关系?
  • 您确定您使用的是支持 SNI 的 HTTPClient 版本吗?通常 Android 附带不支持 SNI 的旧版本的 Apache HTTPClient。
  • 如果您想同时支持 both 自签名证书和来自同一 SSLContext 的常规 CA 根证书,您需要制作一个合适的 TrustManager,它可以做布尔逻辑。在我的CWAC-Security library 中查看我的TrustManagerBuilder
  • 感谢大家的快速回复。
  • EJP 我需要 SNI 来检查证书的公钥。还有 isConnected() 我认为你是对的。这是我的错。它应该返回true。我会查一下。 Steffen Ullrich 我发现 httpClient 从 Android API 17 开始支持它。从 Android 2.3 开始,SNI 在 OpenSSL 实现中可用,所以我可以通过反射使用它。 CommonsWare 我不想添加对自签名证书的检查(在我们部门,我们无法访问它们,我无法获得密钥库)。我想在非产品环境中忽略它们

标签: android ssl https self-signed sni


【解决方案1】:

我发现了问题。我的错。而不是线: sslSocket = (SSLSocket) sslSocketFactory.createSocket(socket, host, port, autoClose); 你应该使用: sslSocket = (SSLSocket) sslSocketFactory.createSocket(InetAddress.getByName(host), port); 为什么?因为没有使用 sslSocketFactory.createSocket(InetAddress, int) 方法进行主机名验证。

另外方法‘isSecure’也写在帖子里了,因为我们不知道我们使用的是什么端口,所以如果socket的实例是ssl,请检查它是否连接。如果是,那么 isSecure 应该返回 true,否则返回 false。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-04-11
    • 1970-01-01
    • 1970-01-01
    • 2023-03-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多