Android 自定义SSLSocketFactory未使用自定义trustmanager

Android 自定义SSLSocketFactory未使用自定义trustmanager,android,ssl,https,self-signed,sni,Android,Ssl,Https,Self Signed,Sni,在我们的应用程序中,当应用程序与服务器通信时,我对证书进行了额外检查(公钥锁定)(针对MITMA)。 为了与服务器通信,我使用HttpClient。 我还有一些代理服务器在生产中,所以我需要使用SNI。在将应用程序发布到生产环境之前,我们在另一个环境(testenv)中检查它。 对于TEST env,我们只有自签名证书,因为它仅用于测试,我们不想为这种情况购买新证书 为了实现它,我创建了定制的SSLSocketFactory(org.apache.http.conn.ssl.SSLSocketF

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

为了实现它,我创建了定制的SSLSocketFactory(org.apache.http.conn.ssl.SSLSocketFactory)。 但问题是它不适用于自签名证书。 我将自定义信任管理器(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;
        }

    }
私有类CustomSSLSocketFactory扩展了SSLSocketFactory{
public CustomsSSLSocketFactory()抛出NoSuchAlgorithmException、KeyManagementException、KeyStoreException、UnrecoverableKeyException、CertificateException{
超级(空);
}
//普通TCP/IP(TLS下的层)
@凌驾
公共套接字connectSocket(套接字s、字符串主机、int端口、InetAddress localAddress、int localPort、HttpParams params)引发IOException{
返回null;
}
@凌驾
公共套接字createSocket()引发IOException{
返回null;
}
@凌驾
公共布尔值IsSecurity(套接字s)引发IllegalArgumentException{
如果(SSLSocket的实例){
返回((SSLSocket)s).isConnected();
}
返回false;
}
@TargetApi(Build.VERSION\u code.JELLY\u BEAN\u MR1)
@凌驾
公共套接字createSocket(套接字套接字、字符串主机、int端口、布尔自动关闭)引发IOException、UnknownHostException{
SSLSocket SSLSocket=null;
//if(isProduction()){
如果(自动关闭){
//我们不需要普通插座
socket.close();
}
//创建并连接SSL套接字,但不进行主机名/证书验证
SSLCertificateSocketFactory sslSocketFactory=(SSLCertificateSocketFactory)SSLCertificateSocketFactory.getDefault(0,null);
//不行!
setTrustManager(新的TrustManager[]{new IgnoreCertificatesTrustManager()});
// ----
sslSocket=(sslSocket)sslSocketFactory.createSocket(套接字、主机、端口、自动关闭);
//启用TLSv1.1/1.2(如果可用)(请参阅https://github.com/rfc2822/davdroid/issues/229 )
sslSocket.setEnabledProtocols(sslSocket.getSupportedProtocols());
//握手前设置SNI
if(Build.VERSION.SDK\u INT>=Build.VERSION\u code.JELLY\u BEAN\u MR1){
debug(“设置SNI主机名”);
setHostname(sslSocket,主机);
}否则{

logger.debug(“Android上没有记录在案的SNI支持我发现了问题。这是我的错。而不是行:

sslSocket=(sslSocket)sslSocketFactory.createSocket(套接字、主机、端口、自动关闭);
你应使用:

sslSocket=(sslSocket)sslSocketFactory.createSocket(InetAddress.getByName(主机),端口);
为什么?因为主机名验证不是用sslSocketFactory.createSocket(InetAddress,int)方法执行的


另外,方法“IsSecurity”也在文章中写对了,因为我们不知道我们使用的是什么端口,所以如果套接字实例是ssl,请检查它是否连接。如果是,则IsSecurity应返回true,否则返回false。

您不需要这些。只需使用您的信任管理器初始化一个
SSLContext
。奇怪的
isSecure()。
什么是
isConnected()
与此有关?您确定使用支持SNI的HTTPClient版本吗?通常Android附带不支持SNI的Apache HTTPClient旧版本。如果您希望同时支持自签名证书和来自同一
SSLContext
的常规CA根证书,则需要创建一个合适的
信任域ger可以执行布尔逻辑。请参阅my中的my
TrustManagerBuilder
。感谢大家的快速响应。
EJP
我需要SNI来检查证书的公钥。还连接了()我认为你是对的。这是我的错误。它应该返回true。我会检查它。
Steffen Ullrich
我发现httpClient从Android API 17支持它。由于Android 2.3,SNI在OpenSSL实现中可用,所以我可以通过反射使用它。
Commonware
我不想添加对自签名证书的检查tes(在我们部门中,我们无法访问它们,我无法获取密钥库)。对于非prod环境,我希望忽略它们