【问题标题】:How to fix "TrustAnchor is not a CA certificate" for self-signed certificates? (Java)如何修复自签名证书的“TrustAnchor 不是 CA 证书”? (爪哇)
【发布时间】:2021-10-02 01:25:17
【问题描述】:

我正在尝试为我的客户端/服务器应用程序(使用 Netty 库以 Java 编写)创建一个自签名服务器证书。据我了解,我必须创建一个 CA 密钥库和一个服务器密钥库,使用 CA 密钥库签署服务器证书并将 CA 证书放在客户端的信任库中。为此,我编写了以下 windows 脚本:

call keytool -genkey -alias ca -keyalg RSA -keystore ca.keystore -storetype JKS -storepass changeit -keypass changeit
:: create CA keystore

call keytool -exportcert -rfc -alias ca -file truststore.pem -keystore ca.keystore -storepass changeit
:: export CA certificate

call keytool -genkey -alias server -keyalg RSA -keystore server.keystore -storetype JKS -storepass changeit -keypass changeit
:: create server keystore

call keytool -certreq -alias server -keystore server.keystore -file server_signing_request.csr -storepass changeit
:: ask CA to sign server certificate

call keytool -gencert -infile server_signing_request.csr -outfile signed_server_cert.crt -keystore ca.keystore -alias ca -storepass changeit
:: sign server certificate

call keytool -importcert -alias ca -keystore server.keystore -file truststore.pem -storepass changeit
:: import root CA certificate into server keystore

call keytool -import -alias server -keystore server.keystore -trustcacerts -file signed_server_cert.crt -storepass changeit
:: import signed server certificate into server keystore

创建 SSL 引擎的服务器代码:

final KeyStore keyStore;
try (FileInputStream input = new FileInputStream(new File("server.keystore"))) {
  keyStore = KeyStore.getInstance("JKS");
  keyStore.load(input, "changeit".toCharArray());
 }
 final KeyManagerFactory factory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
 factory.init(keyStore, "changeit".toCharArray());
 final SSLContext context = SSLContext.getInstance("TLS");
 context.init(factory.getKeyManagers(), null, null);
 final SSLEngine sslEngine = context.createSSLEngine();
 sslEngine.setUseClientMode(false);
 sslEngine.setNeedClientAuth(false);

并在客户端创建 SslContext:

final SslContext sslContext = SslContextBuilder.forClient().trustManager(new File("truststore.pem")).build();

但是,从客户端到服务器的连接尝试会导致以下异常:

io.netty.handler.codec.DecoderException: javax.net.ssl.SSLHandshakeException: PKIX path validation failed: sun.security.validator.ValidatorException: TrustAnchor with subject "CN=CA, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown" is not a CA certificate
        at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:477)
        at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
        at de.klenze_kk.chess.client.netty.MyLoggingHandler.channelRead(MyLoggingHandler.java:28)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
        at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
        at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
        at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
        at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:719)
        at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655)
        at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581)
        at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
        at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:986)
        at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
        at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: javax.net.ssl.SSLHandshakeException: PKIX path validation failed: sun.security.validator.ValidatorException: TrustAnchor with subject "CN=CA, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown" is not a CA certificate
        at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
        at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:326)
        at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:269)
        at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:264)
        at java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.checkServerCerts(CertificateMessage.java:1339)
        at java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.onConsumeCertificate(CertificateMessage.java:1214)
        at java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.consume(CertificateMessage.java:1157)
        at java.base/sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:392)
        at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:444)
        at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask$DelegatedAction.run(SSLEngineImpl.java:1061)
        at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask$DelegatedAction.run(SSLEngineImpl.java:1048)
        at java.base/java.security.AccessController.doPrivileged(Native Method)
        at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask.run(SSLEngineImpl.java:995)
        at io.netty.handler.ssl.SslHandler.runDelegatedTasks(SslHandler.java:1550)
        at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1396)
        at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1237)
        at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1286)
        at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:507)
        at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:446)
        ... 21 more
Caused by: sun.security.validator.ValidatorException: PKIX path validation failed: sun.security.validator.ValidatorException: TrustAnchor with subject "CN=CA, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown" is not a CA certificate
        at java.base/sun.security.validator.PKIXValidator.doValidate(PKIXValidator.java:369)
        at java.base/sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:263)
        at java.base/sun.security.validator.Validator.validate(Validator.java:264)
        at java.base/sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:313)
        at java.base/sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:276)
        at java.base/sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:141)
        at java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.checkServerCerts(CertificateMessage.java:1317)
        ... 35 more
Caused by: sun.security.validator.ValidatorException: TrustAnchor with subject "CN=CA, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown" is not a CA certificate
        at java.base/sun.security.validator.PKIXValidator.verifyTrustAnchor(PKIXValidator.java:393)
        at java.base/sun.security.validator.PKIXValidator.toArray(PKIXValidator.java:333)
        at java.base/sun.security.validator.PKIXValidator.doValidate(PKIXValidator.java:366)
        ... 41 more

有人可以帮忙吗?我做错了什么?

【问题讨论】:

  • 您无法使用 Java keytool 生成 CA 证书。为此,您必须使用 openssl
  • 是的,据我所知,大多数 openssl 命令都可以用 keytool 重现。请参阅我的回答如何为这个用例完成它:stackoverflow.com/a/69416217/6777695

标签: java ssl ssl-certificate


【解决方案1】:

看起来您的方向是正确的,但在创建 CA 和服务器证书时缺少几个重要部分。

我建议使用选项genkeypair 而不是genkey,您还需要一些扩展来为您的证书提供一些额外的功能。

对于 CA,我会推荐:

-ext KeyUsage=digitalSignature,keyCertSign -ext BasicConstraints=ca:true,PathLen:3

对于我推荐的服务器:

-ext KeyUsage=digitalSignature,dataEncipherment,keyEncipherment,keyAgreement -ext ExtendedKeyUsage=serverAuth,clientAuth -ext SubjectAlternativeName:c=DNS:localhost,IP:127.0.0.1

接下来,您需要确保将 CA 证书临时导入服务器证书,然后再将未签名的服务器证书替换为已签名的证书。在我看来,这很奇怪,但 oracle/sun 就是这样设计的。在您的步骤中,您已正确执行此操作,但您可以在导入签名的服务器证书后删除 CA 证书。

导入签名的服务器证书时,您也不需要-trustcacerts 选项。我建议在导入签名的服务器证书时使用importcert 而不是import 命令。

所以总结一下,我建议执行以下步骤:

1.创建 CA 密钥库

keytool -genkeypair -alias ca -keyalg RSA -keystore ca.keystore -storetype JKS -storepass changeit -keypass changeit -ext KeyUsage=digitalSignature,keyCertSign -ext BasicConstraints=ca:true,PathLen:3

2。创建服务器密钥库

keytool -genkeypair -alias server -keyalg RSA -keystore server.keystore -storetype JKS -storepass changeit -keypass changeit -ext KeyUsage=digitalSignature,dataEncipherment,keyEncipherment,keyAgreement -ext ExtendedKeyUsage=serverAuth,clientAuth -ext SubjectAlternativeName:c=DNS:localhost,IP:127.0.0.1

3.为服务器创建证书签名请求 (CSR)

keytool -certreq -alias server -keystore server.keystore -file server_signing_request.csr -storepass changeit -keyalg rsa

4.与 CA 签署服务器 CSR

keytool -gencert -infile server_signing_request.csr -outfile signed_server_cert.crt -keystore ca.keystore -alias ca -storepass changeit -ext KeyUsage=digitalSignature,dataEncipherment,keyEncipherment,keyAgreement -ext ExtendedKeyUsage=serverAuth,clientAuth -ext SubjectAlternativeName:c=DNS:localhost,IP:127.0.0.1 -rfc

5.导出 CA 证书

keytool -exportcert -rfc -alias ca -file truststore.pem -keystore ca.keystore -storepass changeit

6.将 CA 证书导入服务器密钥库

keytool -importcert -alias ca -keystore server.keystore -file truststore.pem -storepass changeit

7.导入签名的服务器证书

keytool -importcert -alias server -keystore server.keystore -file signed_server_cert.crt -storepass changeit

8.从服务器密钥库中删除导入的 CA 证书

keytool -delete -alias ca -keystore server.keystore -storepass changeit

你能用这些命令重试并分享结果吗?

顺便说一下,我在这里写了一个教程,可能对你有帮助:) 见:https://github.com/Hakky54/mutual-tls-ssl

【讨论】:

  • 它正在工作!非常感谢!
  • 对于 TLS,服务器永远不需要 dataEncipherment,而 RSA 密钥永远不需要 keyAgreement。 SAN 应该是用于连接到服务器的名称和/或地址,有时是 localhost/127.0.0.1,但有时不同。由于我们现在需要在命令行上使用 SAN,因此我还可以通过将-dname CN=(one)DNSname 放在那里来节省一点时间。
  • 感谢您对不同扩展名的解释!我没有包括它.. 顺便问一下,我们什么时候需要 dataEncipherment 扩展?为什么 RSA 密钥不需要 keyAgreement?
猜你喜欢
  • 2016-07-08
  • 2021-01-09
  • 2020-07-30
  • 1970-01-01
  • 1970-01-01
  • 2018-12-28
  • 1970-01-01
  • 1970-01-01
  • 2016-01-22
相关资源
最近更新 更多