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

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


当前回答

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

其他回答

这里基本上有两个选择:将自签名证书添加到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签名的“真实”证书。

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

从服务器本身检索证书(使用命令行工具而不是浏览器) 将该证书添加到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

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

接受的答案需要选项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();
    }
}

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