Keytool创建受信任的自签名证书

时间:2010-02-04 14:06:40

标签: java web-services certificate ssl-certificate keytool

我正在尝试使用(java)keytool来创建自签名证书,但是当我尝试使用它时,我得到以下异常(请参阅底部的整个异常)。

...<5 more exceptions above this>
Caused by: sun.security.validator.ValidatorException: No trusted certificate found
        at sun.security.validator.SimpleValidator.buildTrustedChain(SimpleValidator.java:304)
        at sun.security.validator.SimpleValidator.engineValidate(SimpleValidator.java:107)
        at sun.security.validator.Validator.validate(Validator.java:203)
        at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:172)
        at com.sun.net.ssl.internal.ssl.JsseX509TrustManager.checkServerTrusted(SSLContextImpl.java:320)
        at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:841)
        ... 22 more

我知道我可以用这段代码绕过这个:

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;

HostnameVerifier hv = new HostnameVerifier() {
    public boolean verify(String urlHostName, SSLSession session) {
        System.out.println("Warning: URL Host: " + urlHostName + " vs. " + session.getPeerHost());
        return true;
    }
};

HttpsURLConnection.setDefaultHostnameVerifier(hv);

(source)

但我对这个解决方案不感兴趣,因为我认为它会造成安全漏洞。 (如果我错了,请纠正我)。

有人能指出我正确的方向吗?我现在正在本地进行测试,因此很容易改变。我可以访问服务器代码,客户端代码和.keystore文件。

更新

我试图为客户端和服务器使用一个.keystore文件,但为了简化我的问题,我创建了server.keystore(见下文)和client.truststore(见下文)。我有理由相信证书是正确的,但如果有人可以证实我会感激。

server.keystore

hostname[username:/this/is/a/path][711]% keytool -list -keystore server.keystore -v
Enter keystore password:

Keystore type: JKS
Keystore provider: SUN

Your keystore contains 1 entry

Alias name: hostname
Creation date: Feb 4, 2010
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=hostname, OU=hostname, O=hostname, L=hostname, ST=hostname, C=hostname
Issuer: CN=hostname, OU=hostname, O=hostname, L=hostname, ST=hostname, C=hostname
Serial number: 4b6b0ea7
Valid from: Thu Feb 04 13:15:03 EST 2010 until: Wed May 05 14:15:03 EDT 2010
Certificate fingerprints:
         MD5:  81:C0:3F:EC:AD:5B:7B:C4:DA:08:CC:D7:11:1F:1D:38
         SHA1: F1:78:AD:C8:D0:3A:4C:0C:9A:4F:89:C0:2A:2F:E2:E6:D5:13:96:40
         Signature algorithm name: SHA1withDSA
         Version: 3


*******************************************
*******************************************

client.truststore

hostname[username:/this/is/a/path][713]% keytool -list -keystore client.truststore -v
Enter keystore password:

Keystore type: JKS
Keystore provider: SUN

Your keystore contains 1 entry

Alias name: mykey
Creation date: Feb 4, 2010
Entry type: trustedCertEntry

Owner: CN=hostname, OU=hostname, O=hostname, L=hostname, ST=hostname, C=hostname
Issuer: CN=hostname, OU=hostname, O=hostname, L=hostname, ST=hostname, C=hostname
Serial number: 4b6b0ea7
Valid from: Thu Feb 04 13:15:03 EST 2010 until: Wed May 05 14:15:03 EDT 2010
Certificate fingerprints:
         MD5:  81:C0:3F:EC:AD:5B:7B:C4:DA:08:CC:D7:11:1F:1D:38
         SHA1: F1:78:AD:C8:D0:3A:4C:0C:9A:4F:89:C0:2A:2F:E2:E6:D5:13:96:40
         Signature algorithm name: SHA1withDSA
         Version: 3


*******************************************
*******************************************

更新

我认为包含整个例外可能很有用:

javax.xml.soap.SOAPException: java.io.IOException: Could not transmit message
        at org.jboss.ws.core.soap.SOAPConnectionImpl.callInternal(SOAPConnectionImpl.java:115)
        at org.jboss.ws.core.soap.SOAPConnectionImpl.call(SOAPConnectionImpl.java:66)
        at com.alcatel.tpapps.common.utils.SOAPClient.execute(SOAPClient.java:193)
        at com.alcatel.tpapps.common.utils.SOAPClient.main(SOAPClient.java:280)
Caused by: java.io.IOException: Could not transmit message
        at org.jboss.ws.core.client.RemotingConnectionImpl.invoke(RemotingConnectionImpl.java:192)
        at org.jboss.ws.core.client.SOAPRemotingConnection.invoke(SOAPRemotingConnection.java:77)
        at org.jboss.ws.core.soap.SOAPConnectionImpl.callInternal(SOAPConnectionImpl.java:106)
        ... 3 more
Caused by: org.jboss.remoting.CannotConnectException: Can not connect http client invoker. sun.security.validator.ValidatorException: No trusted certificate found.
        at org.jboss.remoting.transport.http.HTTPClientInvoker.useHttpURLConnection(HTTPClientInvoker.java:368)
        at org.jboss.remoting.transport.http.HTTPClientInvoker.transport(HTTPClientInvoker.java:148)
        at org.jboss.remoting.MicroRemoteClientInvoker.invoke(MicroRemoteClientInvoker.java:141)
        at org.jboss.remoting.Client.invoke(Client.java:1858)
        at org.jboss.remoting.Client.invoke(Client.java:718)
        at org.jboss.ws.core.client.RemotingConnectionImpl.invoke(RemotingConnectionImpl.java:171)
        ... 5 more
Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: No trusted certificate found
        at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:150)
        at com.sun.net.ssl.internal.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1584)
        at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Handshaker.java:174)
        at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Handshaker.java:168)
        at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:848)
        at com.sun.net.ssl.internal.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:106)
        at com.sun.net.ssl.internal.ssl.Handshaker.processLoop(Handshaker.java:495)
        at com.sun.net.ssl.internal.ssl.Handshaker.process_record(Handshaker.java:433)
        at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:877)
        at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1089)
        at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1116)
        at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1100)
        at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:402)
        at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:170)
        at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLConnection.java:857)
        at sun.net.www.protocol.https.HttpsURLConnectionImpl.getOutputStream(HttpsURLConnectionImpl.java:230)
        at org.jboss.remoting.transport.http.HTTPClientInvoker.useHttpURLConnection(HTTPClientInvoker.java:288)
        ... 10 more
Caused by: sun.security.validator.ValidatorException: No trusted certificate found
        at sun.security.validator.SimpleValidator.buildTrustedChain(SimpleValidator.java:304)
        at sun.security.validator.SimpleValidator.engineValidate(SimpleValidator.java:107)
        at sun.security.validator.Validator.validate(Validator.java:203)
        at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:172)
        at com.sun.net.ssl.internal.ssl.JsseX509TrustManager.checkServerTrusted(SSLContextImpl.java:320)
        at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:841)
        ... 22 more

5 个答案:

答案 0 :(得分:14)

您需要在服务器和客户端之间“建立信任”(我假设您只需要进行服务器端身份验证)。这是因为您使用自签名证书。 这涉及将服务器的证书导入客户端信任库:

在服务器端:

keytool -keystore <keystore file> -alias <alias> -export -file <certfilename>.cert

将.cert文件复制到客户端,然后:

keytool -keystore <truststore file> -alias <alias> -import -file <certfilename>.cert

答案 1 :(得分:9)

您无法在客户端和服务器之间共享密钥库,因为密钥库包含私钥。进行身份验证时,客户端会使用私钥跳过证书。如上所述,您需要在客户端部署信任库。

密钥库中的证书行为方式不同,具体取决于您生成或导入它们的方式。

导入证书的条目类型(在使用-list -v详细列出整个密钥库时看到)是“trustedCertEntry”。生成的证书的条目类型是“PrivateKeyEntry”。导出证书时,只导出其公钥和对其颁发者的可选引用。

似乎您需要将您的密钥库中的自签名证书导出为信任库中的可信证书(此处的名称有意义)。

我不会这样做,因为SSL / TLS实现可能不支持它。从现实世界的角度来看,就像在Verisign上部署一些不起眼的Web服务器上的最终秘密私钥来签署临时页面,而这个私钥的唯一目的是保持安全并签署其他证书。 SSL / TLS实现者可能不会使用这样的用例污染他们的代码,并且无论如何,“KeyUsage”证书扩展可能会将证书使用限制为签名,从而防止加密。

这就是为什么我建议为你的测试重建一系列证书。

keytool文档包含一个关于创建链(-gencert命令)的有趣部分,但它是一个非常简单的示例,它不包含keystore-truststore关系。我已经对它进行了增强,以模拟第三方认证机构。

临时存储their-keystore.jks表示证书发放机构。我使用ca2 -> ca1 -> ca的证书链来提供它,ca被视为根证书。链出现时,每个非根证书(即ca1ca2)将其颁发者称为Certificate[2]。请注意,每个证书都是“PrivateKeyEntry”。

然后我按顺序向my-keystore.jks提供这些证书:caca1ca2。我使用ca选项导入-trustcacerts,这意味着它成为根证书。在my-keystore.jks中,每个导入的证书现在都是“trustedCertEntry”,这意味着只有公钥。发行关系只出现在“发行人”字段中,但没关系,因为信任关系在导入时最为重要。

此时my-keystore.jks模拟包含一些可信证书的环境,如新鲜的JRE。 their-keystore.jks模拟这些证书的所有者,他们有权签署证书请求。

我也这样做:我在e1创建一个自签名证书my-keystore.jks,由ca2(通过their-keystore.jks)签名并将签名结果导回my-keystore.jkse1仍然是“PrivateKeyEntry”(因为其私钥仍在my-keystore.jks中),但现在我构建了以下链:e1 -> ca2 -> ca1。似乎ca1 -> ca隐含着ca作为证书颁发机构。

要构建信任库,我只需导入证书caca1ca2,方法与my-keystore.jks相同。请注意,我不会导入e1,因为我希望SSL / TLS客户端针对ca2对其进行验证。

我认为这与现实世界中的工作方式非常接近。这里有什么好处是你可以完全控制证书,而且不依赖于JRE的cacerts。

以下是我在实践中所说的代码。似乎可以使用Jetty(客户端和服务器),只要您禁用证书吊销列表(一个主题留待另一天)。

#!/bin/bash

rm  their-keystore.jks 2> /dev/null
rm  my-keystore.jks    2> /dev/null
rm  my-truststore.jks  2> /dev/null

echo "===================================================="
echo "Creating fake third-party chain ca2 -> ca1 -> ca ..."
echo "===================================================="

keytool -genkeypair -alias ca  -dname cn=ca                           \
  -validity 10000 -keyalg RSA -keysize 2048                           \
  -ext BasicConstraints:critical=ca:true,pathlen:10000                \
  -keystore their-keystore.jks -keypass Keypass -storepass Storepass

keytool -genkeypair -alias ca1 -dname cn=ca1                          \
  -validity 10000 -keyalg RSA -keysize 2048                           \
  -keystore their-keystore.jks -keypass Keypass -storepass Storepass

keytool -genkeypair -alias ca2 -dname cn=ca2                          \
  -validity 10000 -keyalg RSA -keysize 2048                           \
  -keystore their-keystore.jks -keypass Keypass -storepass Storepass


  keytool -certreq -alias ca1                                            \
    -keystore their-keystore.jks -keypass Keypass -storepass Storepass   \
| keytool -gencert -alias ca                                             \
    -ext KeyUsage:critical=keyCertSign                                   \
    -ext SubjectAlternativeName=dns:ca1                                  \
    -keystore their-keystore.jks -keypass Keypass -storepass Storepass   \
| keytool -importcert -alias ca1                                         \
    -keystore   their-keystore.jks -keypass Keypass -storepass Storepass

#echo "Debug exit" ; exit 0

  keytool -certreq -alias ca2                                           \
    -keystore their-keystore.jks -keypass Keypass -storepass Storepass  \
| keytool -gencert -alias ca1                                           \
    -ext KeyUsage:critical=keyCertSign                                  \
    -ext SubjectAlternativeName=dns:ca2                                 \
    -keystore their-keystore.jks -keypass Keypass -storepass Storepass  \
| keytool -importcert -alias ca2                                        \
    -keystore their-keystore.jks -keypass Keypass -storepass Storepass

keytool -list -v -storepass Storepass -keystore their-keystore.jks


echo  "===================================================================="
echo  "Fake third-party chain generated. Now generating my-keystore.jks ..."
echo  "===================================================================="
read -p "Press a key to continue."

# Import authority's certificate chain

  keytool -exportcert -alias ca                                         \
    -keystore their-keystore.jks -keypass Keypass -storepass Storepass  \
| keytool -importcert -trustcacerts -noprompt -alias ca                 \
    -keystore  my-keystore.jks -keypass Keypass -storepass Storepass

  keytool -exportcert -alias ca1                                        \
    -keystore their-keystore.jks -keypass Keypass -storepass Storepass  \
| keytool -importcert -noprompt -alias ca1                              \
    -keystore  my-keystore.jks -keypass Keypass -storepass Storepass

  keytool -exportcert -alias ca2                                        \
    -keystore their-keystore.jks -keypass Keypass -storepass Storepass  \
| keytool -importcert -noprompt -alias ca2                              \
    -keystore  my-keystore.jks -keypass Keypass -storepass Storepass

# Create our own certificate, the authority signs it.

keytool -genkeypair -alias e1  -dname cn=e1                        \
  -validity 10000 -keyalg RSA -keysize 2048                        \
  -keystore my-keystore.jks -keypass Keypass -storepass Storepass

  keytool -certreq -alias e1                                            \
    -keystore my-keystore.jks -keypass Keypass -storepass Storepass     \
| keytool -gencert -alias ca2                                           \
    -ext SubjectAlternativeName=dns:localhost                           \
    -ext KeyUsage:critical=keyEncipherment,digitalSignature             \
    -ext ExtendedKeyUsage=serverAuth,clientAuth                         \
    -keystore their-keystore.jks -keypass Keypass -storepass Storepass  \
| keytool -importcert -alias e1                                         \
    -keystore my-keystore.jks -keypass Keypass -storepass Storepass

keytool -list -v  -storepass Storepass -keystore  my-keystore.jks

echo "================================================="
echo "Keystore generated. Now generating truststore ..."
echo "================================================="
read -p "Press a key to continue."

  keytool -exportcert -alias ca                                        \
    -keystore my-keystore.jks -keypass Keypass -storepass Storepass    \
| keytool -importcert -trustcacerts -noprompt -alias ca                \
    -keystore my-truststore.jks -keypass Keypass -storepass Storepass

  keytool -exportcert -alias ca1                                       \
    -keystore my-keystore.jks -keypass Keypass -storepass Storepass    \
| keytool -importcert -noprompt -alias ca1                             \
    -keystore my-truststore.jks -keypass Keypass -storepass Storepass

  keytool -exportcert -alias ca2                                       \
    -keystore my-keystore.jks -keypass Keypass -storepass Storepass    \
| keytool -importcert -noprompt -alias ca2                             \
    -keystore my-truststore.jks -keypass Keypass -storepass Storepass

keytool -list -v  -storepass Storepass -keystore  my-truststore.jks

rm  their-keystore.jks 2> /dev/null

答案 2 :(得分:3)

你不能这样做。密钥库是严格私密的。如果你把它泄露给任何人,你的安全性会受到严重损害。为了让它正常工作,做这种事是没有意义的,因为不是正在工作 - 这只是一个安全漏洞。您必须正确执行:从服务器的密钥库导出到客户端的信任库,以及从客户端的密钥库(如果有)导出到服务器的密钥库。

答案 3 :(得分:0)

我不明白。您是否在客户端使用服务器密钥库?你的用例究竟是什么?您是否尝试设置相互身份验证?

如果是的话,你走错了路。您需要一个客户端密钥存储区(用于客户端的自签名证书和私钥)和一个客户端信任存储区(用于服务器的“独立”自签名证书,即没有其私钥)。两者都与服务器密钥库不同。

答案 4 :(得分:-1)

Springboot 2.1.5 , java 1.8, keytool(it is part of JDK 8) 

these are the steps to follow to generate the self signed ssl certifcate in spring boot


1. Generate self signed ssl certificate

keytool -genkeypair -alias tomcat -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore keystore.p12 -validity 3650

ex: D:\example> <here run the above command>

if it is not working then make sure that your java bin path is set at environment variables to the PATH variable as

C:\Program Files\Java\jdk1.8.0_191\bin

2. after key generation has done then copy that file in to the src/main/resources folder in your project

3. add key store properties in applicaiton.properties 

server.port: 8443
server.ssl.key-store:classpath:keystore.p12
server.ssl.key-store-password: test123     # change the pwd
server.ssl.keyStoreType: PKCS12
server.ssl.keyAlias: tomcat

3. change your postman ssl verification settings to turn OFF
    go to settings and select the  ssl verification  to turn off

now verify the url ( your applicaiton url)
https://localhost:8443/test/hello