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

我有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允许此连接是OK的。

解决此问题的所有说明都假设我完全熟练使用keytool,例如

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

有人能给我详细说明吗?

我正在运行unix,所以bash脚本将是最好的。

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


当前回答

我找到了一个证书提供者,该提供者不属于JDK 8u74的默认JVM受信任主机。提供者是www.identrust.com,但这不是我试图连接的域。该域已从该提供者获得其证书。请参见交叉根覆盖信任JDK/JRE中的默认列表?——读几个条目。另请参阅哪些浏览器和操作系统支持Let 's Encrypt。

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

1:从证书链下载说明中的indettrust获取证书。单击DST根CA X3链接。

2:保存到“DST Root CA X3.pem”文件中。确保在文件的开头和结尾添加行“-----BEGIN CERTIFICATE-----”和“-----END CERTIFICATE-----”。

3:创建java的keystore文件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颁发的证书的域的问题,util 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站点,但如果上面的代码“链”在这个证书,那么他们也会工作,但是,我不知道。

参考:自定义SSL上下文和如何使用Java HttpsURLConnection接受自签名证书?

其他回答

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

EDIT For the benefit of the seventeen (!) downvoters, and numerous commenters below, who clearly have not actually read what I have written here, this is not a jeremiad against self-signed certificates. There is nothing wrong with self-signed certificates when implemented correctly. But, the correct way to implement them is to have the certificate delivered securely via an offline process, rather than via the unauthenticated channel they are going to be used to authenticate. Surely this is obvious? It is certainly obvious to every security-aware organization I have ever worked for, from banks with thousands of branches to my own companies. The client-side code-base 'solution' of trusting all certificates, including self-signed certificates signed by absolutely anybody, or any arbitary body setting itself up as a CA, is ipso facto not secure. It is just playing at security. It is pointless. You are having a private, tamperproof, reply-proof, injection-proof conversation with ... somebody. Anybody. A man in the middle. An impersonator. Anybody. You may as well just use plaintext.

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

选项1

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

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

选项2

禁用证书验证(代码来自Example Depot):

// 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签名的“真实”证书。

受下面annser的启发,我找到了一种信任自签名CA并保持信任默认CA的方法。

    File file = new File(System.getProperty("java.home"), "lib/security/cacerts");
    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
    keyStore.load(new FileInputStream(file), "changeit".toCharArray());


    InputStream resourceAsStream = getClass().getClassLoader().getResourceAsStream("testCer.cer");
    Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(resourceAsStream);
    keyStore.setCertificateEntry("my-server-alias", certificate);

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

    SSLContext sslContext = SSLContexts.createDefault();
    sslContext.init(null, trustManagerFactory.getTrustManagers(), null);


    // check domain
    // SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext);

    // not check domain
    SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext,
            new String[]{"TLSv1","TLSv1.1","TLSv1.2","SSLv3"},null, NoopHostnameVerifier.INSTANCE);

    CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory).build();
    factory.setHttpClient(httpClient);
    RestTemplate restTemplate = new RestTemplate(factory);

您可以通过在RestTemplate级别禁用它来实现这一点。 注意,此TrustStrategy将信任所有证书,并且使用NoopHostnameVerifier()禁用了主机名验证。

public RestTemplate getRestTemplate() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
    TrustStrategy acceptingTrustStrategy = (x509Certificates, s) -> true;
    SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
    SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
    CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(csf).build();
    HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
    requestFactory.setHttpClient(httpClient);
    return new RestTemplate(requestFactory);
}       

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

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

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