如何在Java中绕过SSL证书检查

19

我想访问一个托管在远程虚拟机上的https SOAP webservice url。使用HttpURLConnection访问时出现了异常。

以下是我的代码:

import javax.net.ssl.*;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

/**
 * Created by prasantabiswas on 07/03/17.
 */
public class Main
{
    public static void main(String [] args)
    {
        try
        {
            URL url = new URL("https://myhost:8913/myservice/service?wsdl");
            HttpURLConnection http = null;

            if (url.getProtocol().toLowerCase().equals("https")) {
                trustAllHosts();
                HttpsURLConnection https = (HttpsURLConnection) url.openConnection();
                https.setHostnameVerifier(DO_NOT_VERIFY);
                http = https;
            } else {
                http = (HttpURLConnection) url.openConnection();
            }
            String SOAPAction="";
//            http.setRequestProperty("Content-Length", String.valueOf(b.length));
            http.setRequestProperty("Content-Type", "text/xml; charset=utf-8");
            http.setRequestProperty("SOAPAction", SOAPAction);
            http.setRequestMethod("GET");
            http.setDoOutput(true);
            http.setDoInput(true);
            OutputStream out = http.getOutputStream();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }

    final static HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    };

    private static void trustAllHosts() {
        // Create a trust manager that does not validate certificate chains
        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return new java.security.cert.X509Certificate[] {};
            }

            public void checkClientTrusted(X509Certificate[] chain,
                                           String authType) throws CertificateException
            {
            }

            public void checkServerTrusted(X509Certificate[] chain,
                                           String authType) throws CertificateException {
            }
        } };

        // Install the all-trusting trust manager
        try {
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, trustAllCerts, new java.security.SecureRandom());
            HttpsURLConnection
                    .setDefaultSSLSocketFactory(sc.getSocketFactory());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

我遇到了以下异常:

javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: Certificates does not conform to algorithm constraints
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
    at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1949)
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:302)
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:296)
    at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1509)
    at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:216)
    at sun.security.ssl.Handshaker.processLoop(Handshaker.java:979)
    at sun.security.ssl.Handshaker.process_record(Handshaker.java:914)
    at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1062)
    at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1375)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1403)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1387)
    at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559)
    at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
    at sun.net.www.protocol.http.HttpURLConnection.getOutputStream0(HttpURLConnection.java:1283)
    at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLConnection.java:1258)
    at sun.net.www.protocol.https.HttpsURLConnectionImpl.getOutputStream(HttpsURLConnectionImpl.java:250)
    at Main.main(Main.java:35)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
Caused by: java.security.cert.CertificateException: Certificates does not conform to algorithm constraints
    at sun.security.ssl.AbstractTrustManagerWrapper.checkAlgorithmConstraints(SSLContextImpl.java:1055)
    at sun.security.ssl.AbstractTrustManagerWrapper.checkAdditionalTrust(SSLContextImpl.java:981)
    at sun.security.ssl.AbstractTrustManagerWrapper.checkServerTrusted(SSLContextImpl.java:923)
    at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1491)
    ... 18 more

我在谷歌搜索中尝试了不同的解决方案,但它们都没有起作用。我想避免使用keytool,因为我将在不同的虚拟机上运行我的测试。

请问有人有任何解决方案吗?


1
尝试使用更高版本的Java。您可能正在使用过时的Java版本,不支持所使用的加密类型。 - Tschallacka
我正在使用JAVA 8。 - Prasanta Biswas
你正在自己的私有服务器上执行SOAP请求?在测试VM上添加密钥到受信任的密钥中。对于公共SOAP,您不需要这样做,因为有CA可以证明其真实性。 - Tschallacka
SOAP服务托管在使用自签名证书的服务器上。我正在从本地以及任何虚拟机上运行我的测试。我不想在每个虚拟机中添加密钥到信任存储区。有其他解决方法吗? - Prasanta Biswas
尝试过了,对我没用。 - Prasanta Biswas
显示剩余3条评论
5个回答

27

使用X509ExtendedTrustManager而不是X509TrustManager()解决了这个问题。以下是示例:

public void trustAllHosts()
    {
        try
        {
            TrustManager[] trustAllCerts = new TrustManager[]{
                    new X509ExtendedTrustManager()
                    {
                        @Override
                        public java.security.cert.X509Certificate[] getAcceptedIssuers()
                        {
                            return null;
                        }

                        @Override
                        public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType)
                        {
                        }

                        @Override
                        public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType)
                        {
                        }

                        @Override
                        public void checkClientTrusted(java.security.cert.X509Certificate[] xcs, String string, Socket socket) throws CertificateException
                        {

                        }

                        @Override
                        public void checkServerTrusted(java.security.cert.X509Certificate[] xcs, String string, Socket socket) throws CertificateException
                        {

                        }

                        @Override
                        public void checkClientTrusted(java.security.cert.X509Certificate[] xcs, String string, SSLEngine ssle) throws CertificateException
                        {

                        }

                        @Override
                        public void checkServerTrusted(java.security.cert.X509Certificate[] xcs, String string, SSLEngine ssle) throws CertificateException
                        {

                        }

                    }
            };

            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, trustAllCerts, new java.security.SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

            // Create all-trusting host name verifier
            HostnameVerifier allHostsValid = new  HostnameVerifier()
            {
                @Override
                public boolean verify(String hostname, SSLSession session)
                {
                    return true;
                }
            };
            // Install the all-trusting host verifier
            HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
        }
        catch (Exception e)
        {
            log.error("Error occurred",e);
        }
    }

2
这是一种相当不安全的方法,因为它撤消了 https 的许多用途。更好的方法是使用一个包含发行 CA 证书的 KeyStore 的 TrustManager。 - Johannes Brodwall
3
这仅用于测试环境,不可用于生产。 - Prasanta Biswas
谢谢!我已经寻找这样简单的解决方案有一段时间了。也想知道如何在Spring Boot中实现它... - R13mus
1
@AntonCavanaugh 我没有使用 HttpClient。我使用了 Java 的核心 API。 - Prasanta Biswas
getAcceptedIssuers() 不允许返回 null - undefined
显示剩余4条评论

6

编辑:在使用之前,请了解这可能会带来的漏洞。这绝不是推荐用于生产环境的方法。

最好的方法是创建一个虚拟信任管理器,信任所有内容。

 TrustManager[] dummyTrustManager = new TrustManager[] { new X509TrustManager() {
      public java.security.cert.X509Certificate[] getAcceptedIssuers() {
        return null;
      }

      public void checkClientTrusted(X509Certificate[] certs, String authType) {
      }

      public void checkServerTrusted(X509Certificate[] certs, String authType) {
      }
    } };

然后使用虚拟信任管理器来初始化SSL上下文。

SSLContext sc = SSLContext.getInstance("SSL");
    sc.init(null, dummyTrustManager, new java.security.SecureRandom());

最后使用SSLContext打开连接

HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

    URL url = new URL("https://myhost:8913/myservice/service?wsdl");

这个问题已经在这里得到了更详细的回答:Java:覆盖函数以禁用SSL证书检查

更新:

上述问题是由于Java不支持证书签名算法导致的。根据此文章,Java 8的后续版本已禁用md5算法。

要启用md5支持,请找到 <jre_home>/lib/security 下的 java.security 文件,并定位到第535行

jdk.certpath.disabledAlgorithms=MD2, MD5, RSA keySize < 1024, 

并且移除MD5


如果你看过我的代码,我已经做过了。不起作用。当URL为https://google.com时它可以正常工作。 - Prasanta Biswas
@PrasantaBiswas 即使您绕过证书验证,连接仍然必须执行 SSL 握手并加密内容,如果您想使用 https。看起来证书无效。您能否使用浏览器下载证书并共享证书详细信息,特别是签名算法? - Monish Sen
@PrasantaBiswas 快速解决方法是尝试更新Java 8的JCE策略JAR包,如果还没有这样做。如果这个方法对您有用,请回复。 - Monish Sen
我知道这个。但我不想进入别人的机器并更改东西。相反,我使用了X509ExtendedTrustManager解决了这个问题。无论如何,感谢您的帮助。 - Prasanta Biswas
getAcceptedIssuers() 不允许返回 null - undefined
显示剩余3条评论

5

尝试使用Apache HTTP客户端,这对我来说很有效。

SSLContextBuilder builder = new SSLContextBuilder();
builder.loadTrustMaterial(null, new TrustStrategy() {
     public boolean isTrusted(final X509Certificate[] chain, String authType) throws CertificateException {
          return true;
     }
});
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(builder.build());

CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();

// GET or POST request with the client
...

我正在使用HttpURLConnection。 - Prasanta Biswas
Dereck,你能告诉我使用哪个版本的Apache HTTP客户端吗?有很多版本,其中许多版本都无法正常工作。谢谢。 - Anton Cavanaugh

1

不要使用 HttpsURLConnection.setDefaultSSLSocketFactory 和你自己实现的 TrustManagerX509ExtendedTrustManager,你可以使用 TrustManagerFactory,并使用颁发所需信任证书的证书的 KeyStore(对于自签名证书,这与主机证书相同),在特定实例上调用 HttpsURLConnection.setSSLSocketFactory。这样做既少写代码,又避免了信任所有 HTTPS 证书的安全问题。

main 中:

            if (url.getProtocol().toLowerCase().equals("https")) {
                HttpsURLConnection https = (HttpsURLConnection) url.openConnection();
                https.setSSLSocketFactory(createSSLSocketFactory());
                http = https;
            }

方法createSSLSocketFactory的样子如下:
    private static SSLSocketFactory createSSLSocketFactory() {
         File crtFile = new File("server.crt");
         Certificate certificate =          CertificateFactory.getInstance("X.509").generateCertificate(new FileInputStream(crtFile));

         KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
         keyStore.load(null, null);
         keyStore.setCertificateEntry("server", certificate);

         TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
         trustManagerFactory.init(keyStore);

         SSLContext sslContext = SSLContext.getInstance("TLS");
         sslContext.init(null, trustManagerFactory.getTrustManagers(), null);

         return sslContext.getSocketFactory();
    }

0

另一种方法是使用来自Apache的httpClient:

public static CloseableHttpClient getCloseableHttpClient()
{
    CloseableHttpClient httpClient = null;
    try {
        httpClient = HttpClients.custom().setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
                .setSSLContext(new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy()
                {
                    public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException
                    {
                        return true;
                    }
                }).build()).build();

    } catch (KeyManagementException e) {
        LOGGER.error("KeyManagementException in creating http client instance", e);
    } catch (NoSuchAlgorithmException e) {
        LOGGER.error("NoSuchAlgorithmException in creating http client instance", e);
    } catch (KeyStoreException e) {
        LOGGER.error("KeyStoreException in creating http client instance", e);
    }
    return httpClient;
}

然后:

HttpPost post = new HttpPost("你的URI");

CloseableHttpResponse response = getCloseableHttpClient.execute(post);

当然还有所有的try-catch块(或使用try-with-resources)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接