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

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


当前回答

受下面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);

其他回答

这里基本上有两个选择:将自签名证书添加到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);

接受的答案很好,但我想添加一些东西,因为我在Mac上使用IntelliJ,无法使用JAVA_HOME路径变量让它工作。

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

要找出它的确切位置,只需执行System.getProperty("java.home"),因为可信证书就是从那里读取的。

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

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。

有一个更好的替代信任所有证书的方法:创建一个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将只信任这个特定的证书。