在Java客户端中接受服务器的自签名ssl证书

时间:2010-05-23 22:48:32

标签: java ssl https keytool

它看起来像一个标准问题,但我无法在任何地方找到明确的指示。

我有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允许这种连接是可以的。

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

  

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

是否有人可以发布详细说明?

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

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

13 个答案:

答案 0 :(得分:274)

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

选项1

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

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

选项2

禁用证书验证:

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

答案 1 :(得分:7)

我将此问题追溯到JDK 8u74以外不属于默认JVM可信主机的证书提供程序。提供程序是www.identrust.com,但这不是我尝试连接的域。该域名已从该提供商处获得其证书。请参阅Will the cross root cover trust by the default list in the JDK/JRE? - 阅读几个条目。另请参阅Which browsers and operating systems support Let’s Encrypt

因此,为了连接到我感兴趣的域,其中有identrust.com颁发的证书,我执行了以下步骤。基本上,我必须得到jVM信任的identrust.com(DST Root CA X3)证书。我能够使用Apache HttpComponents 4.5这样做:

1:在Certificate Chain Download Instructions获取不信任证书。点击DST Root CA X3链接。

2:将字符串保存到名为&#34; DST Root CA X3.pem&#34;的文件中。一定要添加行&#34; ----- BEGIN CERTIFICATE -----&#34;和&#34; -----结束证书-----&#34;在文件的开头和结尾。

3:使用以下命令创建一个java密钥库文件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)应用程序的resources目录中。

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网站,但是如果上面的代码&#34;链接&#34;在这个证书中,他们也会工作,但我又不知道。

参考:Custom SSL contextHow do I accept a self-signed certificate with a Java HttpsURLConnection?

答案 2 :(得分:7)

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(如生产中)。

答案 3 :(得分:3)

而不是设置默认套接字工厂(IMO是一件坏事) - 这只会影响当前连接而不是您尝试打开的每个SSL连接:

URLConnection connection = url.openConnection();
    // JMD - this is a better way to do it that doesn't override the default SSL factory.
    if (connection instanceof HttpsURLConnection)
    {
        HttpsURLConnection conHttps = (HttpsURLConnection) connection;
        // Set up a Trust all manager
        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager()
        {

            public java.security.cert.X509Certificate[] getAcceptedIssuers()
            {
                return null;
            }

            public void checkClientTrusted(
                java.security.cert.X509Certificate[] certs, String authType)
            {
            }

            public void checkServerTrusted(
                java.security.cert.X509Certificate[] certs, String authType)
            {
            }
        } };

        // Get a new SSL context
        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
        HostnameVerifier allHostsValid = new HostnameVerifier() {
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };
        // and set the hostname verifier.
        conHttps.setHostnameVerifier(allHostsValid);
    }
InputStream stream = connection.getInputStream();

答案 4 :(得分:2)

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

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。

答案 5 :(得分:1)

信任所有SSL证书: - 如果要在测试服务器上进行测试,可以绕过SSL。 但是不要将此代码用于生产。

public static class NukeSSLCerts {
protected static final String TAG = "NukeSSLCerts";

public static void nuke() {
    try {
        TrustManager[] trustAllCerts = new TrustManager[] { 
            new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() {
                    X509Certificate[] myTrustedAnchors = new X509Certificate[0];  
                    return myTrustedAnchors;
                }

                @Override
                public void checkClientTrusted(X509Certificate[] certs, String authType) {}

                @Override
                public void checkServerTrusted(X509Certificate[] certs, String authType) {}
            }
        };

        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, new SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String arg0, SSLSession arg1) {
                return true;
            }
        });
    } catch (Exception e) { 
    }
}

}

请在Activity或您的Application Class中的onCreate()函数中调用此函数。

NukeSSLCerts.nuke();

这可以用于Android中的Volley。

答案 6 :(得分:1)

我遇到的一个问题是,我正在将URL传递到一个库中,该库正在调用url.openConnection();,我采用了乔恩·丹尼尔的回答,

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

答案 7 :(得分:0)

如果'他们'正在使用自签名证书,则他们需要采取使服务器可用的步骤。具体而言,这意味着以可信赖的方式离线提供他们的证书。所以让他们这样做。然后使用keytool将其导入您的信任库,如JSSE参考指南中所述。甚至不要考虑这里发布的不安全的TrustManager。

编辑为了以下 17个(!)downvoters和众多评论者的利益,他们显然还没有真正阅读过我在这里写的内容,这是 反对自签名证书的jeremiad。正确实施自签名证书没有任何问题。 但是,实现它们的正确方法是通过离线流程安全地交付证书 而不是通过未经身份验证的渠道,他们将用于验证。当然这很明显?对于我曾经工作过的每个安全意识组织来说,这显然是显而易见的,从拥有数千个分支机构的银行到我自己的公司。信任所有证书的客户端代码库“解决方案”,包括绝对任何人签名的自签名证书,或任何将自己设置为CA的任意仲裁机构,都是 ipso facto < / em>不安全。它只是在玩安全。这毫无意义。您正在与某人进行私密,防篡改,回复,防注射的对话。任何人。一个男人在中间。模仿者。任何人。你也可以只使用明文。

答案 8 :(得分:0)

可以接受的答案很好,但是我想在Mac上使用IntelliJ来添加一些内容,并且无法使用JAVA_HOME路径变量来使它正常工作。

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

要弄清楚它的确切位置,您只需执行System.getProperty("java.home"),因为那是从中读取受信任证书的地方。

答案 9 :(得分:0)

还有一种更好的替代方法,即信任所有证书:创建一个TrustStore来专门信任给定的证书,并使用它来创建一个SSLContext,从中获取SSLSocketFactory来设置HttpsURLConnection。这是完整的代码:

File crtFile = new File("server.crt");
Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(new FileInputStream(crtFile));

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仅信任此特定证书。

答案 10 :(得分:0)

这不是解决完整问题的方法,但是oracle拥有有关如何使用此keytool的详细文档。这说明了

  1. 使用键盘工具。
  2. 使用keytool生成证书/自签名证书。
  3. 将生成的证书导入Java客户端。

https://docs.oracle.com/cd/E54932_01/doc.705/e54936/cssg_create_ssl_cert.htm#CSVSG178

答案 11 :(得分:0)

您可以从RHEL 6的较新版本开始使用update-ca-trust,而不是使用顶部注释中建议的keytool。您需要使用pem格式的证书。然后

trust anchor <cert.pem>

编辑/etc/pki/ca-trust/source/cert.p11-kit并将“证书类别:其他条目”更改为“证书类别:权限”。 (或者使用sed在脚本中执行此操作。)然后执行

update-ca-trust

一些警告:

  • 我在RHEL 6服务器上找不到“信任”,并且yum也没有提供安装它。我最终在RHEL 7服务器上使用了它,并复制了.p11-kit文件。
  • 要为您完成这项工作,您可能需要执行update-ca-trust enable。这将用指向/ etc / pki / ca-trust / extracted / java / cacerts的符号链接替换/ etc / pki / java / cacerts。 (因此,您可能要先备份前者。)
  • 如果您的Java客户端使用存储在其他位置的cacerts,则需要将其手动替换为/ etc / pki / ca-trust / extracted / java / cacerts的符号链接,或将其替换为该文件。 / li>

答案 12 :(得分:0)

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