代码之家  ›  专栏  ›  技术社区  ›  Brad Parks

如何将httpclient与任何ssl证书一起使用,不管它有多“坏”

  •  5
  • Brad Parks  · 技术社区  · 6 年前

    我在用 Apache HttpClient 在仅用于对公共数据进行爬网的Web爬网程序中。

    我希望它能够用无效的证书对站点进行爬行,不管证书有多无效。

    我的爬虫不会传入任何用户名、密码等,也不会发送或接收敏感数据。

    对于这个用例,我将 http 一个网站的版本,如果它存在,但有时不当然。

    这个怎么办 Apache的httpclient ?

    我试过一些建议,比如 this one ,但对于某些无效证书,它们仍然失败,例如:

    failed for url:https://dh480.badssl.com/, reason:java.lang.RuntimeException: Could not generate DH keypair
    failed for url:https://null.badssl.com/, reason:Received fatal alert: handshake_failure
    failed for url:https://rc4-md5.badssl.com/, reason:Received fatal alert: handshake_failure
    failed for url:https://rc4.badssl.com/, reason:Received fatal alert: handshake_failure
    failed for url:https://superfish.badssl.com/, reason:Connection reset
    

    注意,我用我的 $JAVA_HOME/jre/lib/security/java.security 文件 jdk.tls.disabledAlgorithms 设置为Nothing,以确保这不是问题,我仍然会遇到如上所述的失败。

    5 回复  |  直到 6 年前
        1
  •  4
  •   user3474985    6 年前

    对你的问题的简短回答,即特别信任所有证书,将使用 TrustAllStrategy 然后这样做:

    SSLContextBuilder sslContextBuilder = new SSLContextBuilder();
    sslContextBuilder.loadTrustMaterial(null, new TrustAllStrategy());
    SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
            sslContextBuilder.build());
    CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(
            socketFactory).build();
    

    然而。。。无效证书可能不是您的主要问题。握手失败的原因有很多,但根据我的经验,这通常是由于SSL/TLS版本不匹配或密码套件协商失败造成的。这并不意味着ssl证书是“坏的”,它只是服务器和客户机之间的不匹配。使用类似wireshark的工具,您可以准确地看到握手失败的地方。( more on that )

    虽然wireshark可以很好地看到它在哪里失败,但它不会帮助您想出解决方案。过去每当我调试握手失败时,我都会发现这个工具特别有用: https://testssl.sh/

    您可以将该脚本指向任何失败的网站,以进一步了解该目标上可用的协议以及客户机建立成功握手所需支持的内容。它还将打印有关证书的信息。

    例如(仅显示testsl.sh输出的两个部分):

    ./testssl.sh www.google.com
    ....
     Testing protocols (via sockets except TLS 1.2, SPDY+HTTP2) 
    
     SSLv2               not offered (OK)
     SSLv3               not offered (OK)
     TLS 1               offered
     TLS 1.1             offered
     TLS 1.2             offered (OK)
     ....
    Server Certificate #1
       Signature Algorithm          SHA256 with RSA
       Server key size              RSA 2048 bits
       Common Name (CN)             "www.google.com"
       subjectAltName (SAN)         "www.google.com" 
       Issuer                       "Google Internet Authority G3" ("Google Trust Services" from "US")
       Trust (hostname)             Ok via SAN and CN (works w/o SNI)
       Chain of trust               "/etc/*.pem" cannot be found / not readable
       Certificate Expiration       expires < 60 days (58) (2018-10-30 06:14 --> 2019-01-22 06:14 -0700)
     ....
     Testing all 102 locally available ciphers against the server, ordered by encryption strength 
    (Your /usr/bin/openssl cannot show DH/ECDH bits)
    
    Hexcode  Cipher Suite Name (OpenSSL)       KeyExch.  Encryption Bits
    ------------------------------------------------------------------------
    xc030   ECDHE-RSA-AES256-GCM-SHA384       ECDH       AESGCM    256       
    xc02c   ECDHE-ECDSA-AES256-GCM-SHA384     ECDH       AESGCM    256       
    xc014   ECDHE-RSA-AES256-SHA              ECDH       AES       256       
    xc00a   ECDHE-ECDSA-AES256-SHA            ECDH       AES       256       
    x9d     AES256-GCM-SHA384                 RSA        AESGCM    256       
    x35     AES256-SHA                        RSA        AES       256       
    xc02f   ECDHE-RSA-AES128-GCM-SHA256       ECDH       AESGCM    128       
    xc02b   ECDHE-ECDSA-AES128-GCM-SHA256     ECDH       AESGCM    128       
    xc013   ECDHE-RSA-AES128-SHA              ECDH       AES       128       
    xc009   ECDHE-ECDSA-AES128-SHA            ECDH       AES       128       
    x9c     AES128-GCM-SHA256                 RSA        AESGCM    128       
    x2f     AES128-SHA                        RSA        AES       128       
    x0a     DES-CBC3-SHA                      RSA        3DES      168 
    

    因此,使用这个输出,我们可以看到,如果您的客户机只支持SSLv3,那么握手将失败,因为服务器不支持该协议。协议提供不太可能出现问题,但您可以通过获取启用协议列表来仔细检查Java客户端的支持。您可以从上面的代码段提供sslconnectionsocketFactory的重写实现,以获取启用/支持的协议和密码套件的列表,如下所示( SSLSocket )以下内容:

    class MySSLConnectionSocketFactory extends SSLConnectionSocketFactory {
        @Override
        protected void prepareSocket(SSLSocket socket) throws IOException {
            System.out.println("Supported Ciphers" + Arrays.toString(socket.getSupportedCipherSuites()));
            System.out.println("Supported Protocols" + Arrays.toString(socket.getSupportedProtocols()));
            System.out.println("Enabled Ciphers" + Arrays.toString(socket.getEnabledCipherSuites()));
            System.out.println("Enabled Protocols" + Arrays.toString(socket.getEnabledProtocols()));
        }
    }
    

    当密码套件协商失败时,我经常会遇到握手失败。要避免此错误,您的客户端支持的密码套件列表必须至少包含一个与服务器支持的密码套件列表中的密码套件匹配的项。

    如果服务器需要基于AES256的加密套件,您可能需要Java加密扩展(JCE)。这些图书馆是国家限制的,因此它们可能不能为美国以外的人使用。

    有关密码限制的更多信息,如果您感兴趣: https://crypto.stackexchange.com/questions/20524/why-there-are-limitations-on-using-encryption-with-keys-beyond-certain-length

        2
  •  0
  •   nmorenor    6 年前

    我认为你所指的职位与需要做的工作非常接近。您是否尝试过类似的操作:

    HttpClientBuilder clientBuilder = HttpClientBuilder.create();
    SSLContextBuilder sslContextBuilder = SSLContextBuilder.create();
    sslContextBuilder.setSecureRandom(new java.security.SecureRandom());
    try {
        sslContextBuilder.loadTrustMaterial(new TrustStrategy() {
            @Override
            public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                return true;
            }
        });
        clientBuilder.setSSLContext(sslContextBuilder.build());
    } catch (Throwable t) {
        Logger.getLogger(getClass().getName()).log(Level.SEVERE, "Can't set ssl context", t);
    }
    CloseableHttpClient apacheHttpClient = clientBuilder.build();
    

    我没有尝试过这个代码,但希望它可以工作。

    干杯

        3
  •  0
  •   Ashish Patil    6 年前

    如果您可以使用其他开源库,比如 netty 下面值得一试:

    SslProvider provider = SslProvider.JDK;  // If you are not concerned about http2 / http1.1 then JDK provider will be enough
    SSLContext sslCtx = SslContextBuilder.forClient()
                    .sslProvider(provider)
                    .trustManager(InsecureTrustManagerFactory.INSTANCE) // This will trust all certs
                    ...  // Any other required parameters used for ssl context.e.g. protocols , ciphers etc.
                    .build();
    

    我使用下面的netty版本来信任具有上述代码的任何证书:

    <dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-all</artifactId>
        <version>4.1.29.Final</version>
    </dependency>
    
        4
  •  0
  •   ok2c    6 年前

    我认为@nmornor的答案非常接近目标。除此之外,我还将明确地 SSLv3 (由于安全问题,httpclient默认情况下自动禁用它)和禁用主机名验证。

    SSLContext sslContext = SSLContexts.custom()
            .loadTrustMaterial((chain, authType) -> true)
            .build();
    
    CloseableHttpClient client = HttpClients.custom()
            .setSSLSocketFactory(new SSLConnectionSocketFactory(sslContext,
                    new String[]{"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2"},
                    null,
                    NoopHostnameVerifier.INSTANCE))
            .build();
    
        5
  •  0
  •   user2023577    6 年前

    你也可以用核心JDK来实现,但是IIRC、HTTPClient也允许你设置SSL套接字工厂。

    工厂定义并使用与信任管理器一起构造的SSL上下文。正如上面的文章所示,该管理器不会简单地验证cert链。

    您还需要一个hostnameverifier实例,该实例还可以选择忽略cert hostname与URL主机(或IP)的潜在不匹配。否则,即使证书签名者被盲目信任,它仍然会失败。

    我曾经将许多客户机堆栈转换为“接受自签名”,在大多数堆栈中这很容易。更糟糕的情况是,第三方库不允许选择SSL套接字工厂实例,而只允许选择其clasname。在这种情况下,我使用一个threadlocalsslsocketfactory,它不拥有任何实际的工厂,但只需查找threadlocal,以找到上层stackframes(您可以控制)准备的一个。当然,只有当第三方库没有在不同的线程上进行工作时,这才有效。我知道HTTP客户机可以被告知使用特定的SSL套接字工厂,所以这很容易。

    还要花时间阅读JSSE文档,这是完全值得花时间阅读的。