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

我有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中执行的代码。


当前回答

我有一个问题,我正在传递一个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://stackoverflow.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。

其他回答

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(就像在生产中一样)。

您可以通过在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);
}       

我找到了一个证书提供者,该提供者不属于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接受自签名证书?

这是第一个有太多答案的古老问题,我认为我可以提供一个更有用的想法:如果服务器所有者拒绝以可信任的方式离线向我提供他们的证书,我会使用这个选项:

从服务器本身检索证书(使用命令行工具而不是浏览器) 将该证书添加到java密钥存储库以信任它。将显示证书的详细信息,以验证它。

# HOSTNAME_PORT is the host that you want to connect to - example: HOSTNAME_PORT=stackoverflow.com:443
HOSTNAME_PORT=hostname_part_of_url_without_https:port

# whatever you want to call the key within the Java key store
MY_KEY_ALIAS=the_key_I_added_with_help_from_stackoverflow

openssl s_client -showcerts -connect $HOSTNAME_PORT </dev/null 2>/dev/null|openssl x509 -outform PEM >mycertfile.pem
sudo keytool -trustcacerts -keystore $JAVA_HOME/jre/lib/security/cacerts/pki/java/cacerts -storepass changeit -importcert -alias $MY_KEY_ALIAS -file mycertfile.pem 

在出现提示时输入yes,但只有在您真正信任显示给您的证书并希望将其添加到计算机的全局java密钥存储库时才可以这样做。

通知你:$ JAVA_HOME / jre / lib /安全/除 在我的情况下(CentOS 7)指向: /etc/pki/java/cacerts

我遇到了这个问题,因为依赖项来自maven repo,它是我的本地服务器,具有自签名证书和自签名CA证书。为了解决这个错误,我必须运行以下两个命令:

<my_java_install_dir>\bin\keytool.exe -importcert -file <my-self-signed-CA-cert>.crt -keystore <my_java_install_dir>\lib\security\cacerts -alias my-CA-cert

然后

<my_java_install_dir>\jdk11.0.14_10\bin\keytool.exe -importcert -file <my-self-signed-maven-repo-cert>.crt -keystore <my_java_install_dir>\lib\security\cacerts -alias my-maven-repo-cert