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

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

其他回答

如果“他们”使用自签名证书,则由他们采取必要的步骤使他们的服务器可用。具体来说,这意味着以一种值得信赖的方式离线向您提供他们的证书。所以让他们这么做。然后,按照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签名的“真实”证书。

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

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

在RHEL上,您可以从RHEL 6的新版本开始使用update-ca-trust,而不是像上面的注释所建议的那样使用keytool。您需要拥有pem格式的证书。然后

trust anchor <cert.pem>

编辑/etc/pki/ca-trust/source/cert.P11-kit,将“证书类别:other-entry”修改为“证书类别:authority”。(或使用sed在脚本中完成此操作。)然后做

update-ca-trust

几点注意事项:

I couldn't find "trust" on my RHEL 6 server and yum didn't offer to install it. I ended up using it on an RHEL 7 server and copying the .p11-kit file over. To make this work for you, you may need to do update-ca-trust enable. This will replace /etc/pki/java/cacerts with a symbolic link pointing to /etc/pki/ca-trust/extracted/java/cacerts. (So you might want to back up the former first.) If your java client uses cacerts stored in some other location, you'll want to manually replace it with a symlink to /etc/pki/ca-trust/extracted/java/cacerts, or replace it with that file.