在 Java 客户端中接受服务器的自签名 ssl 证书

Posted

技术标签:

【中文标题】在 Java 客户端中接受服务器的自签名 ssl 证书【英文标题】:Accept server's self-signed ssl certificate in Java client 【发布时间】:2011-02-23 01:01:25 【问题描述】:

这似乎是一个标准问题,但我在任何地方都找不到明确的方向。

我有 java 代码试图连接到可能带有自签名(或过期)证书的服务器。代码报如下错误:

[HttpMethodDirector] I/O exception (javax.net.ssl.SSLHandshakeException) caught 
when processing request: sun.security.validator.ValidatorException: PKIX path 
building failed: sun.security.provider.certpath.SunCertPathBuilderException: 
unable to find valid certification path to requested target

据我了解,我必须使用 keytool 并告诉 java 允许此连接是可以的。

解决此问题的所有说明均假设我完全精通 keytool,例如

为服务器生成私钥并将其导入密钥库

有没有人可以发布详细的说明?

我正在运行 unix,所以最好使用 bash 脚本。

不确定是否重要,但代码在 jboss 中执行。

【问题讨论】:

见How do I accept a self-signed certificate with a Java HttpsURLConnection?。显然,如果你能让网站使用有效的证书会更好。 感谢链接,搜索时没看到。但是那里的两种解决方案都涉及发送请求的特殊代码,我正在使用现有代码(用于 java 的亚马逊 ws 客户端)。分别,我连接的是他们的网站,我无法解决其证书问题。 @MatthewFlaschen - “显然,如果你能让网站使用有效证书会更好......” - 自签名证书是有效证书,如果客户信任它。许多人认为授予 CA/浏览器卡特尔信任是一个安全缺陷。 相关,请参阅The most dangerous code in the world: validating SSL certificates in non-browser software。 (提供该链接是因为您似乎收到了禁用验证的垃圾邮件答案)。 【参考方案1】:

Kotlin 中的变体

    @SuppressLint("CustomX509TrustManager", "TrustAllX509TrustManager")
    fun ignoreSsl() 
        val trustAllCerts: Array<TrustManager> = arrayOf(
            object : X509TrustManager 
                override fun getAcceptedIssuers(): Array<X509Certificate>? = null
                override fun checkClientTrusted(certs: Array<X509Certificate?>?, authType: String?) 
                override fun checkServerTrusted(certs: Array<X509Certificate?>?, authType: String?) 
            )
        val sc = SSLContext.getInstance("SSL")
        sc.init(null, trustAllCerts, SecureRandom())
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.socketFactory)
    

【讨论】:

【参考方案2】:

有一个更好的替代方法来信任所有证书:创建一个专门信任给定证书的TrustStore,并使用它来创建一个SSLContext,从中获取SSLSocketFactory 以设置HttpsURLConnection。完整代码如下:

File crtFile = new File("server.crt");
Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(new FileInputStream(crtFile));
// Or if the crt-file is packaged into a jar file:
// CertificateFactory.getInstance("X.509").generateCertificate(this.class.getClassLoader().getResourceAsStream("server.crt"));


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

HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();
connection.setSSLSocketFactory(sslContext.getSocketFactory());

您也可以直接从文件加载KeyStore,或从任何受信任的来源检索 X.509 证书。

请注意,使用此代码,cacerts 中的证书将不会被使用。这个特定的HttpsURLConnection 将只信任这个特定的证书。

【讨论】:

[android 文档] (developer.android.com/training/articles/security-ssl#SelfSigned) 基本上给出了你的解释。它只是提供了更多的解释、代码和警告。 在我看来这应该是公认的答案!这种方法的优点是您以编程方式执行所有操作并且仍然保持安全性,并且您不会更改 JRE 的信任库。出于安全目的,已接受答案中的选项#2 是完全错误的。感谢您提供此解决方案! 如果有人要将证书打包到jar文件中,则将方法generateCertificate中的InputStream替换为:this.class.getClassLoader().getResourceAsStream("server.crt")【参考方案3】:

使用浏览器从目标页面下载您的自签名证书并将其添加到使用默认密码的默认存储

keytool -import -v -trustcacerts -file selfsigned.crt -alias myserver -keystore /etc/alternatives/jre/lib/security/cacerts -storepass changeit

使用文件$JAVA_HOME/jre/lib/security/cacerts,我这里的例子来自Oracle linux 7.7。

【讨论】:

【参考方案4】:

接受的答案需要选项 3

也 选项 2 是可怕。它永远不应该被使用(尤其是在生产中),因为它提供了一种错误的安全感。只需使用 HTTP 而不是选项 2。

选项 3

使用自签名证书进行 Https 连接。

这是一个例子:

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.KeyStore;

/*
 * Use a SSLSocket to send a HTTP GET request and read the response from an HTTPS server.
 * It assumes that the client is not behind a proxy/firewall
 */

public class SSLSocketClientCert

    private static final String[] useProtocols = new String[] "TLSv1.2";
    public static void main(String[] args) throws Exception
    
        URL inputUrl = null;
        String certFile = null;
        if(args.length < 1)
        
            System.out.println("Usage: " + SSLSocketClient.class.getName() + " <url>");
            System.exit(1);
        
        if(args.length == 1)
        
            inputUrl = new URL(args[0]);
        
        else
        
            inputUrl = new URL(args[0]);
            certFile = args[1];
        
        SSLSocket sslSocket = null;
        PrintWriter outWriter = null;
        BufferedReader inReader = null;
        try
        
            SSLSocketFactory sslSocketFactory = getSSLSocketFactory(certFile);

            sslSocket = (SSLSocket) sslSocketFactory.createSocket(inputUrl.getHost(), inputUrl.getPort() == -1 ? inputUrl.getDefaultPort() : inputUrl.getPort());
            String[] enabledProtocols = sslSocket.getEnabledProtocols();
            System.out.println("Enabled Protocols: ");
            for(String enabledProtocol : enabledProtocols) System.out.println("\t" + enabledProtocol);

            String[] supportedProtocols = sslSocket.getSupportedProtocols();
            System.out.println("Supported Protocols: ");
            for(String supportedProtocol : supportedProtocols) System.out.println("\t" + supportedProtocol + ", ");

            sslSocket.setEnabledProtocols(useProtocols);

            /*
             * Before any data transmission, the SSL socket needs to do an SSL handshake.
             * We manually initiate the handshake so that we can see/catch any SSLExceptions.
             * The handshake would automatically  be initiated by writing & flushing data but
             * then the PrintWriter would catch all IOExceptions (including SSLExceptions),
             * set an internal error flag, and then return without rethrowing the exception.
             *
             * This means any error messages are lost, which causes problems here because
             * the only way to tell there was an error is to call PrintWriter.checkError().
             */
            sslSocket.startHandshake();
            outWriter = sendRequest(sslSocket, inputUrl);
            readResponse(sslSocket);
            closeAll(sslSocket, outWriter, inReader);
        
        catch(Exception e)
        
            e.printStackTrace();
        
        finally
        
            closeAll(sslSocket, outWriter, inReader);
        
    

    private static PrintWriter sendRequest(SSLSocket sslSocket, URL inputUrl) throws IOException
    
        PrintWriter outWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(sslSocket.getOutputStream())));
        outWriter.println("GET " + inputUrl.getPath() + " HTTP/1.1");
        outWriter.println("Host: " + inputUrl.getHost());
        outWriter.println("Connection: Close");
        outWriter.println();
        outWriter.flush();
        if(outWriter.checkError())        // Check for any PrintWriter errors
            System.out.println("SSLSocketClient: PrintWriter error");
        return outWriter;
    

    private static void readResponse(SSLSocket sslSocket) throws IOException
    
        BufferedReader inReader = new BufferedReader(new InputStreamReader(sslSocket.getInputStream()));
        String inputLine;
        while((inputLine = inReader.readLine()) != null)
            System.out.println(inputLine);
    

    // Terminate all streams
    private static void closeAll(SSLSocket sslSocket, PrintWriter outWriter, BufferedReader inReader) throws IOException
    
        if(sslSocket != null) sslSocket.close();
        if(outWriter != null) outWriter.close();
        if(inReader != null) inReader.close();
    

    // Create an SSLSocketFactory based on the certificate if it is available, otherwise use the JVM default certs
    public static SSLSocketFactory getSSLSocketFactory(String certFile)
        throws CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException, KeyManagementException
    
        if (certFile == null) return (SSLSocketFactory) SSLSocketFactory.getDefault();
        Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(new FileInputStream(new File(certFile)));

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


【讨论】:

【参考方案5】:

我遇到了一个问题,我将一个 URL 传递到一个调用 url.openConnection(); 的库中,我改编了 jon-daniel 的答案,

public class TrustHostUrlStreamHandler extends URLStreamHandler 

    private static final Logger LOG = LoggerFactory.getLogger(TrustHostUrlStreamHandler.class);

    @Override
    protected URLConnection openConnection(final URL url) throws IOException 

        final URLConnection urlConnection = new URL(url.getProtocol(), url.getHost(), url.getPort(), url.getFile()).openConnection();

        // adapated from
        // https://***.com/questions/2893819/accept-servers-self-signed-ssl-certificate-in-java-client
        if (urlConnection instanceof HttpsURLConnection) 
            final HttpsURLConnection conHttps = (HttpsURLConnection) urlConnection;

            try 
                // Set up a Trust all manager
                final TrustManager[] trustAllCerts = new TrustManager[]  new X509TrustManager() 

                    @Override
                    public java.security.cert.X509Certificate[] getAcceptedIssuers() 
                        return null;
                    

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

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

                // Get a new SSL context
                final SSLContext sc = SSLContext.getInstance("TLSv1.2");
                sc.init(null, trustAllCerts, new java.security.SecureRandom());
                // Set our connection to use this SSL context, with the "Trust all" manager in place.
                conHttps.setSSLSocketFactory(sc.getSocketFactory());
                // Also force it to trust all hosts
                final HostnameVerifier allHostsValid = new HostnameVerifier() 
                    @Override
                    public boolean verify(final String hostname, final SSLSession session) 
                        return true;
                    
                ;

                // and set the hostname verifier.
                conHttps.setHostnameVerifier(allHostsValid);

             catch (final NoSuchAlgorithmException e) 
                LOG.warn("Failed to override URLConnection.", e);
             catch (final KeyManagementException e) 
                LOG.warn("Failed to override URLConnection.", e);
            

         else 
            LOG.warn("Failed to override URLConnection. Incorrect type: ", urlConnection.getClass().getName());
        

        return urlConnection;
    


使用这个类可以创建一个新的 URL:

trustedUrl = new URL(new URL(originalUrl), "", new TrustHostUrlStreamHandler());
trustedUrl.openConnection();

这样做的好处是它是本地化的,而不是替换默认的URL.openConnection

【讨论】:

非常感谢。它对我有用,正在努力为我的自签名 Spring Boot HTTPS 服务器找到一个 HTTPS GET 客户端。【参考方案6】:

在 RHEL 上,您可以从更新版本的 RHEL 6 开始使用 update-ca-trust,而不是使用顶部评论建议的 keytool。您需要拥有 pem 格式的证书。那么

trust anchor <cert.pem>

编辑 /etc/pki/ca-trust/source/cert.p11-kit 并将“证书类别:其他条目”更改为“证书类别:权限”。 (或使用 sed 在脚本中执行此操作。)然后执行

update-ca-trust

几个注意事项:

我在我的 RHEL 6 服务器上找不到“信任”,并且 yum 没有提供安装它。我最终在 RHEL 7 服务器上使用它并复制了 .p11-kit 文件。 要使这项工作适合您,您可能需要执行update-ca-trust enable。这会将 /etc/pki/java/cacerts 替换为指向 /etc/pki/ca-trust/extracted/java/cacerts 的符号链接。 (因此您可能需要先备份前者。) 如果您的 java 客户端使用存储在其他位置的 cacerts,您需要手动将其替换为 /etc/pki/ca-trust/extracted/java/cacerts 的符号链接,或将其替换为该文件。

【讨论】:

【参考方案7】:

这不是完整问题的解决方案,但 oracle 有关于如何使用此密钥工具的详细文档。这解释了如何

    使用密钥工具。 使用 keytool 生成证书/自签名证书。 将生成的证书导入 java 客户端。

https://docs.oracle.com/cd/E54932_01/doc.705/e54936/cssg_create_ssl_cert.htm#CSVSG178

【讨论】:

【参考方案8】:

接受的答案很好,但我想在此添加一些内容,因为我在 Mac 上使用 IntelliJ 并且无法使用 JAVA_HOME 路径变量使其工作。

事实证明,从 IntelliJ 运行应用程序时,Java Home 有所不同。

要弄清楚它的确切位置,您只需执行System.getProperty("java.home"),因为这是读取受信任证书的位置。

【讨论】:

【参考方案9】:

您在这里基本上有两个选择:将自签名证书添加到您的 JVM 信任库或将您的客户端配置为

选项 1

从浏览器导出证书并将其导入 JVM 信任库(以建立信任链):

<JAVA_HOME>\bin\keytool -import -v -trustcacerts
-alias server-alias -file server.cer
-keystore cacerts.jks -keypass changeit
-storepass changeit 

选项 2

禁用证书验证:

// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[]  
    new X509TrustManager()      
        public java.security.cert.X509Certificate[] getAcceptedIssuers()  
            return new X509Certificate[0];
         
        public void checkClientTrusted( 
            java.security.cert.X509Certificate[] certs, String authType) 
             
        public void checkServerTrusted( 
            java.security.cert.X509Certificate[] certs, String authType) 
        
     
; 

// 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 (GeneralSecurityException e) 
 
// Now you can access an https URL without having the certificate in the truststore
try  
    URL url = new URL("https://hostname/index.html"); 
 catch (MalformedURLException e) 
 

请注意,我根本不推荐选项 #2。禁用信任管理器会破坏 SSL 的某些部分,并使您容易受到中间人攻击。首选选项 #1,或者更好的是,让服务器使用由知名 CA 签名的“真实”证书。

【讨论】:

不仅仅是 MIM 攻击。它使您容易连接到错误的站点。这是完全不安全的。请参阅 RFC 2246。我反对始终发布此 TrustManager。它甚至不正确w.r.t。它自己的规范。 @EJP 我真的不推荐第二个选项(我已经更新了我的答案以使其清楚)。但是,不发布它不会解决任何问题(这是公共信息),恕我直言,不值得投反对票。 @EJP 是通过教人来教育他们,而不是通过隐藏东西。因此,将事情保密或默默无闻根本不是解决方案。这段代码是公开的,Java API 是公开的,与其无视,不如谈论它。但我可以忍受你不同意。 另一个选项——你没有提到的那个——是修复服务器的证书,要么自己修复它,要么打电话给相关的支持人员。单主机证书真的很便宜;摆弄自签名的东西是一分钱一分货的愚蠢行为(即,对于那些不熟悉该英语习语的人来说,这是一套完全愚蠢的优先事项,几乎没有节省成本)。 @Rich,晚了 6 年,但您也可以通过单击锁定图标 -> 更多信息 -> 查看证书 -> 详细信息选项卡 -> 导出来获取 Firefox 中的服务器证书...它隐藏得很好。【参考方案10】:

Apache HttpClient 4.5 支持接受自签名证书:

SSLContext sslContext = SSLContexts.custom()
    .loadTrustMaterial(new TrustSelfSignedStrategy())
    .build();
SSLConnectionSocketFactory socketFactory =
    new SSLConnectionSocketFactory(sslContext);
Registry<ConnectionSocketFactory> reg =
    RegistryBuilder.<ConnectionSocketFactory>create()
    .register("https", socketFactory)
    .build();
HttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(reg);        
CloseableHttpClient httpClient = HttpClients.custom()
    .setConnectionManager(cm)
    .build();
HttpGet httpGet = new HttpGet(url);
CloseableHttpResponse sslResponse = httpClient.execute(httpGet);

这将构建一个使用TrustSelfSignedStrategy 的 SSL 套接字工厂,将其注册到自定义连接管理器,然后使用该连接管理器执行 HTTP GET。

我同意那些高喊“不要在生产环境中这样做”的人的观点,但是也有在生产环境之外接受自签名证书的用例;我们在自动化集成测试中使用它们,因此即使不在生产硬件上运行,我们也可以使用 SSL(就像在生产中一样)。

【讨论】:

【参考方案11】:

信任所有 SSL 证书:- 如果您想在测试服务器上进行测试,您可以绕过 SSL。 但不要将此代码用于生产。

public static class NukeSSLCerts 
protected static final String TAG = "NukeSSLCerts";

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)  
    

请在 Activity 或您的应用程序类中的 onCreate() 函数中调用此函数。

NukeSSLCerts.nuke();

这可以用于 Android 中的 Volley。

【讨论】:

不完全确定您被否决的原因,除非代码不起作用。当原始问题没有标记 Android 时,也许是假设以某种方式涉及 Android。【参考方案12】:

如果“他们”使用的是自签名证书,则由他们来决定是否采取必要的步骤使他们的服务器可用。具体来说,这意味着以值得信赖的方式离线向您提供他们的证书。所以让他们这样做。然后,您使用 JSSE 参考指南中描述的 keytool 将其导入您的信任库。甚至不要考虑此处发布的不安全的 TrustManager。

编辑为了 17 (!) 反对者以及下面的众多评论者的利益,他们显然没有真正阅读我在这里写的内容,这是 不是反对自签名证书。正确实施自签名证书没有任何问题。 但是,实施它们的正确方法是通过离线过程安全地交付证书 而不是通过未经身份验证的通道,它们将用于进行身份验证。这肯定很明显吗?从拥有数千家分支机构的银行到我自己的公司,这对于我曾经工作过的每个具有安全意识的组织来说都是显而易见的。信任所有证书的客户端代码库“解决方案”,包括由绝对任何人签署的自签名证书,或任何将自己设置为 CA 的任意机构,ipso facto 不安全。它只是在玩安全。这是没有意义的。您正在与……某人进行私密的、防篡改的、防回复的、防注入的对话。任何人。中间一个男人。一个模仿者。任何人。你也可以只使用纯文本。

【讨论】:

仅仅因为某些服务器决定使用 https,并不意味着 客户端的人出于自己的目的对安全性一无所知。 我很惊讶这个答案如此被否决。我很想了解更多原因。似乎 EJP 建议您不应该执行选项 2,因为它存在重大安全漏洞。有人可以解释为什么(除了部署前设置)为什么这个答案质量低吗? 好吧,我猜都是。 “由他们决定采取必要的步骤使他们的服务器可用” 是什么意思?服务器操作员必须做什么才能使其可用?你有什么步骤?你能提供他们的名单吗?但是回到 1,000 英尺,这与 OP 提出的问题有什么关系?他想知道如何让客户端接受 Java 中的自签名证书。由于 OP 认为证书是可接受的,因此授予对代码的信任是一件简单的事情。 @EJP - 如果我错了,请纠正我,但接受自签名证书是客户端策略决定。它与服务器端操作无关。客户必须做出决定。奇偶校验必须共同解决密钥分配问题,但这与使服务器可用无关。 @EJP - 我当然没有说,“信任所有证书”。也许我遗漏了一些东西(或者你读得太多了)...... OP 有证书,并且他可以接受。他想知道如何信任它。我可能正在分裂头发,但证书不需要离线交付。应该使用带外验证,但这与“必须离线交付给客户端”不同。他甚至可能是生产服务器和客户端的商店,因此他具有必要的先验知识。【参考方案13】:

而不是设置默认套接字工厂(IMO 是一件坏事)- yhi 只会影响当前连接,而不是您尝试打开的每个 SSL 连接:

URLConnection connection = url.openConnection();
    // JMD - this is a better way to do it that doesn't override the default SSL factory.
    if (connection instanceof HttpsURLConnection)
    
        HttpsURLConnection conHttps = (HttpsURLConnection) connection;
        // Set up a Trust all manager
        TrustManager[] trustAllCerts = new TrustManager[]  new X509TrustManager()
        

            public java.security.cert.X509Certificate[] getAcceptedIssuers()
            
                return null;
            

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

            public void checkServerTrusted(
                java.security.cert.X509Certificate[] certs, String authType)
            
            
         ;

        // Get a new SSL context
        SSLContext sc = SSLContext.getInstance("TLSv1.2");
        sc.init(null, trustAllCerts, new java.security.SecureRandom());
        // Set our connection to use this SSL context, with the "Trust all" manager in place.
        conHttps.setSSLSocketFactory(sc.getSocketFactory());
        // Also force it to trust all hosts
        HostnameVerifier allHostsValid = new HostnameVerifier() 
            public boolean verify(String hostname, SSLSession session) 
                return true;
            
        ;
        // and set the hostname verifier.
        conHttps.setHostnameVerifier(allHostsValid);
    
InputStream stream = connection.getInputStream();

【讨论】:

您的checkServerTrusted 没有实现真正信任证书的必要逻辑确保拒绝不受信任的证书。这可能会有所帮助:The most dangerous code in the world: validating SSL certificates in non-browser software. 您的getAcceptedIssuers() 方法不符合规范,并且这个“解决方案”仍然非常不安全。【参考方案14】:

我将这个问题追查到一个证书提供者,该提供者不属于 JDK 8u74 的默认 JVM 受信任主机。提供者是www.identrust.com,但这不是我试图连接的域。该域已从该提供商处获得其证书。见Will the cross root cover trust by the default list in the JDK/JRE?——阅读几个条目。另见Which browsers and operating systems support Let’s Encrypt。

因此,为了连接到我感兴趣的域,该域具有由identrust.com 颁发的证书,我执行了以下步骤。基本上,我必须获得 identrust.com (DST Root CA X3) 证书才能被 JVM 信任。我能够像这样使用 Apache HttpComponents 4.5 做到这一点:

1:从Certificate Chain Download Instructions 的indettrust 获取证书。点击DST Root CA X3 链接。

2:将字符串保存到名为“DST Root CA X3.pem”的文件中。请务必在文件的开头和结尾添加“-----BEGIN CERTIFICATE-----”和“-----END CERTIFICATE-----”行。

3:使用以下命令创建一个 java 密钥库文件 cacerts.jks:

keytool -import -v -trustcacerts -alias IdenTrust -keypass yourpassword -file dst_root_ca_x3.pem -keystore cacerts.jks -storepass yourpassword

4:将生成的 cacerts.jks 密钥库复制到您的 java/(maven) 应用程序的资源目录中。

5:使用以下代码加载此文件并将其附加到 Apache 4.5 HttpClient。这将解决所有具有从indetrust.com 颁发的证书的域的问题,Oracle 将证书包含在 JRE 默认密钥库中。

SSLContext sslcontext = SSLContexts.custom()
        .loadTrustMaterial(new File(CalRestClient.class.getResource("/cacerts.jks").getFile()), "yourpasword".toCharArray(),
                new TrustSelfSignedStrategy())
        .build();
// Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
        sslcontext,
        new String[]  "TLSv1" ,
        null,
        SSLConnectionSocketFactory.getDefaultHostnameVerifier());
CloseableHttpClient httpclient = HttpClients.custom()
        .setSSLSocketFactory(sslsf)
        .build();

当项目构建时,cacerts.jks 将被复制到类路径并从那里加载。目前我还没有针对其他 ssl 站点进行测试,但是如果上面的代码“链”在这个证书中,那么它们也可以工作,但同样,我不知道。

参考:Custom SSL context 和 How do I accept a self-signed certificate with a Java HttpsURLConnection?

【讨论】:

问题是关于自签名证书。这个答案不是。 仔细阅读问题(和答案)。

以上是关于在 Java 客户端中接受服务器的自签名 ssl 证书的主要内容,如果未能解决你的问题,请参考以下文章

Android 上的自签名 SSL 接受

Tomcat 服务器和 HTTP 客户端接受过期的自签名证书

Mac OS X - 接受自签名多域 SSL 证书

Java程序禁用SSL认证的安全风险

在 SSL WebSocket 连接 (wss) 中拦截请求

带有 Python SSL 套接字的自签名证书